diff --git a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidget.py b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidget.py index 016937af..6cb2d22a 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidget.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidget.py @@ -25,6 +25,7 @@ __all__ = ['TTkTreeWidget'] from typing import List from TermTk.TTkCore.cfg import TTkCfg +from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.string import TTkString @@ -153,12 +154,14 @@ class TTkTreeWidget(TTkAbstractScrollView): 'default': { 'color': TTkColor.RST, 'lineColor': TTkColor.fg("#444444"), + 'lineHeightColor': TTkColor.fg("#666666"), 'headerColor': TTkColor.fg("#ffffff")+TTkColor.bg("#444444")+TTkColor.BOLD, 'selectedColor': TTkColor.fg("#ffff88")+TTkColor.bg("#000066")+TTkColor.BOLD, 'separatorColor': TTkColor.fg("#444444")}, 'disabled': { 'color': TTkColor.fg("#888888"), 'lineColor': TTkColor.fg("#888888"), + 'lineHeightColor': TTkColor.fg("#666666"), 'headerColor': TTkColor.fg("#888888"), 'selectedColor': TTkColor.fg("#888888"), 'separatorColor': TTkColor.fg("#888888")}, @@ -174,15 +177,6 @@ class TTkTreeWidget(TTkAbstractScrollView): '_itemChanged', '_itemClicked', '_itemDoubleClicked', '_itemExpanded', '_itemCollapsed', '_itemActivated' ) - @dataclass(frozen=True) - class _Cache: - item: TTkTreeWidgetItem - level: int - data: list - widgets: list - firstLine: bool - - _cache:List[_Cache] _selected:List[TTkTreeWidgetItem] @dataclass(frozen=True) @@ -214,6 +208,8 @@ class TTkTreeWidget(TTkAbstractScrollView): self._itemExpanded = pyTTkSignal(TTkTreeWidgetItem) self._itemCollapsed = pyTTkSignal(TTkTreeWidgetItem) + self._cache = [] + self._selectionMode = selectionMode self._dndMode = dragDropMode self._selected = [] @@ -221,7 +217,6 @@ class TTkTreeWidget(TTkAbstractScrollView): self._separatorSelected = None self._header = header if header else [] self._columnsPos = [] - self._cache = [] self._sortingEnabled=sortingEnabled self._sortColumn = -1 self._sortOrder = TTkK.AscendingOrder @@ -363,8 +358,8 @@ class TTkTreeWidget(TTkAbstractScrollView): def resizeColumnToContents(self, column:int) -> None: '''resizeColumnToContents''' - if not self._cache: - return + TTkLog.critical('resizeColumnToContents Method Unimplemented') + return contentSize = max(row.data[column].termWidth() for row in self._cache) self.setColumnWidth(column, contentSize) @@ -403,8 +398,9 @@ class TTkTreeWidget(TTkAbstractScrollView): return True y += oy-1 - if 0 <= y < len(self._cache): - item = self._cache[y].item + if _item_at := self._rootItem._item_at(y+1): + _,_,_i = _item_at + item = _i if item.childIndicatorPolicy() == TTkK.DontShowIndicatorWhenChildless and item.children() or \ item.childIndicatorPolicy() == TTkK.ShowIndicator: item.setExpanded(not item.isExpanded()) @@ -453,13 +449,14 @@ class TTkTreeWidget(TTkAbstractScrollView): return True # Handle Tree/Table Events y += oy-1 - if 0 <= y < len(self._cache): - item = self._cache[y].item - level = self._cache[y].level + if _item_at := self._rootItem._item_at(y+1): + _l, _yi, _i = _item_at + item = _i + level = _l # check if the expand button is pressed with +-1 tollerance - if level*2 <= x < level*2+3 and \ - ( item.childIndicatorPolicy() == TTkK.DontShowIndicatorWhenChildless and item.children() or - item.childIndicatorPolicy() == TTkK.ShowIndicator ): + if ( _yi==0 and level*2 <= x < level*2+3 and \ + ( item.childIndicatorPolicy() == TTkK.DontShowIndicatorWhenChildless and item.children() or + item.childIndicatorPolicy() == TTkK.ShowIndicator )): item.setExpanded(not item.isExpanded()) if item.isExpanded(): self.itemExpanded.emit(item) @@ -548,6 +545,10 @@ class TTkTreeWidget(TTkAbstractScrollView): @pyTTkSlot() def _refreshCache(self) -> None: + self._alignWidgets() + self.update() + self.viewChanged.emit() + return # I save a representation of the displayed tree in a cache array # to avoid eccessve recursion over the items and # identify quickly the nth displayed line to improve the interaction @@ -606,6 +607,7 @@ class TTkTreeWidget(TTkAbstractScrollView): color= style['color'] lineColor= style['lineColor'] + lineHeightColor= style['lineHeightColor'] headerColor= style['headerColor'] selectedColor= style['selectedColor'] separatorColor= style['separatorColor'] @@ -628,16 +630,25 @@ class TTkTreeWidget(TTkAbstractScrollView): for sy in range(1,h): canvas.drawChar(pos=(sx-x,sy), char=tt[4], color=lineColor) - # Draw cache - for i, c in enumerate(self._cache): - if i-y<0: continue - item = c.item + col_slices = list(zip([0]+[_p+1 for _p in self._columnsPos], self._columnsPos)) + for _y, (_l, _yi, _i) in enumerate(self._rootItem._get_page(-1,1+y,h+1)): for il in range(len(self._header)): - lx = 0 if il==0 else self._columnsPos[il-1]+1 - lx1 = self._columnsPos[il] - - text = c.data[il] - if item.isSelected(): - canvas.drawText(pos=(lx-x,i-y+1), text=text.completeColor(selectedColor), width=lx1-lx, alignment=item.textAlignment(il), color=selectedColor) - else: - canvas.drawText(pos=(lx-x,i-y+1), text=text, width=lx1-lx, alignment=item.textAlignment(il)) + _lx,_lx1 = col_slices[il] + _width = _lx1-_lx + _ih = _i.height() + _data = _i.data(il).split('\n') + [TTkString()]*_ih + if il==0: # First Column + if _yi == 0: + _icon = f"{' '*_l} "+_i.icon(il)+" " + elif _yi == _ih-1: + _icon = TTkString(f"{' '*_l} ╽ ", lineHeightColor) + elif _yi == 1: + _icon = TTkString(f"{' '*_l} ┊ ", lineHeightColor) + else: + _icon = TTkString(f"{' '*_l} │ ", lineHeightColor) + _text=_icon+_data[_yi] + else: # Other columns + _text=_data[_yi] + if _i.isSelected(): + _text = (_text + ' '*_width).completeColor(selectedColor) + canvas.drawTTkString(text=_text,pos=(_lx-x,_y+1),width=_width) diff --git a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidgetitem.py b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidgetitem.py index e0c4ab57..6a68f916 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidgetitem.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidgetitem.py @@ -20,12 +20,12 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # SOFTWARE. +from __future__ import annotations + __all__ = ['TTkTreeWidgetItem'] -try: - from typing import Self -except: - class Self(): pass +from dataclasses import dataclass +from typing import List, Tuple, Iterator, Generator, Optional, Callable, Any from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.constant import TTkK @@ -34,6 +34,89 @@ from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkWidgets import TTkWidget from TermTk.TTkAbstract.abstractitemmodel import TTkAbstractItemModel +@dataclass +class _TTkTreePageItem(): + y:int + level:int + item:TTkTreeWidgetItem + +@dataclass +class _TTkTreeBuffer(): + level:int + total_size:int + buffered_size:int + buffer:List[Tuple[int,int,TTkTreeWidgetItem]] + buffer_link:List[Tuple[int,int]] + + + def __init__(self): + self.level = 0 + self.total_size = 0 + self.buffer = [] + self.buffer_link = [] + self._gen:Optional[Iterator] = None + + def get_link(self, index:int) -> int: + if index<0: + return 0 + if index >= len(self.buffer_link): + return len(self.buffer) + return self.buffer_link[index][0] + + def clearBuffer(self): + self.buffer = [] + self.buffer_link = [] + + def clearBufferFromIndex(self, index:int) -> None: + if index<0: + self.clearBuffer() + elif index >= len(self.buffer_link): + pass + else: + link = self.buffer_link[index][0] + self.buffer[link:] = [] + self.buffer_link[index+1:] = [] + link = self.buffer_link[index] = (link,0) + + def get_page(self, item:TTkTreeWidgetItem, level:int, index:int, size:int) -> List[Tuple[int,int,TTkTreeWidgetItem]]: + # Add the item to the buffer + if self.level != level: + self.clearBuffer() + if not self.buffer: + self.buffer = [(level, _y, item) for _y in range(item._height)] + self.buffer_link = [(item._height, 0)] + final_index = index+size + buffered_size = len(self.buffer) + if item._expanded: + while buffered_size < final_index: + last_index = len(self.buffer_link)-1 + if len(item._children) <= last_index: + break + # | last_index + # | ch_last_index = ch_h + # | | => left to fetch = (ch_h, final_index - buffered_size) + # Children * <---> * <--|xxxx| > * < > + # / + # item *<-------------|xxxx| buffer + # | final_index + # buffered_size + # + ch_buffer_index, ch_h = self.buffer_link[last_index] + child = item._children[last_index] + ch_s = child.size() + if ch_h != ch_s: + ch_index = ch_h + ch_size = final_index - ch_buffer_index - ch_h + child_page = child._get_page(level+1, ch_index, ch_size) + + self.buffer.extend(child_page) + ch_h += len(child_page) + self.buffer_link[last_index] = (ch_buffer_index, ch_h) + buffered_size = len(self.buffer) + if ch_h == ch_s: + self.buffer_link.append((buffered_size, 0)) + return self.buffer[index:final_index] + class TTkTreeWidgetItem(TTkAbstractItemModel): ''' The :py:class:`TTkTreeWidgetItem` class provides an item for use with the :py:class:'TTkTree' convenience class. @@ -63,13 +146,22 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): __slots__ = ('_parent', '_data', '_widgets', '_height', '_alignment', '_children', '_expanded', '_selected', '_hidden', '_childIndicatorPolicy', '_icon', '_defaultIcon', '_sortColumn', '_sortOrder', '_hasWidgets', '_parentWidget', + '_list_bk', '_list_h_bk', + '_buffer', # Signals # 'refreshData' - 'heightChanged' + 'heightChanged', '_invalidateListBuffer', '_sizeChanged', + + # Slot that accept itself + '_sizeChangedHandler' ) + _children:List[TTkTreeWidgetItem] + _buffer:_TTkTreeBuffer + _sizeChangedHandler: Callable[[TTkTreeWidgetItem,int,int], None] + def __init__(self, *args, - parent:Self=None, + parent:TTkTreeWidgetItem=None, expanded:bool=False, selected:bool=False, hidden:bool=False, @@ -79,8 +171,13 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): # Signals # self.refreshData = pyTTkSignal(TTkTreeWidgetItem) self.heightChanged = pyTTkSignal(int) + self._invalidateListBuffer = pyTTkSignal(TTkTreeWidgetItem) + self._sizeChanged = pyTTkSignal(TTkTreeWidgetItem,int) self._hasWidgets = False self._children = [] + self._list_bk = [] + self._list_h_bk = [] + self._buffer = _TTkTreeBuffer() self._parentWidget = None self._height = 1 data = args[0] if len(args)>0 and type(args[0])==list else [TTkString()] @@ -94,6 +191,18 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): self._sortColumn = -1 self._sortOrder = TTkK.AscendingOrder + # I need this hack because I cannot define the class itself in the slot + @pyTTkSlot(TTkTreeWidgetItem, int) + def _sch(item:TTkTreeWidgetItem, diffSize:int) -> None: + if item == self or self._expanded: + if item==self: + self._buffer.clearBuffer() + else: + self._buffer.clearBufferFromIndex(self._children.index(item)) + self._buffer.total_size += diffSize + self._sizeChanged.emit(self, diffSize) + self._sizeChangedHandler = _sch + super().__init__(**kwargs) self._data, self._widgets = self._processDataInput(data) self._alignment = [TTkK.LEFT_ALIGN]*len(self._data) @@ -141,6 +250,7 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): retData.append(TTkString()) retWidgets.append(None) self._height = max(self._height,len(retData[-1].split('\n'))) + self._buffer.total_size = self._height return retData, retWidgets def _setDefaultIcon(self): @@ -158,8 +268,10 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): if h != self._height: h = max(max([len(s.split("\n")) for s in self._data]), max(w.height() for w in self._widgets if w)) if h != self._height: + diffSize = h - self._height self._height = h self.heightChanged.emit(h) + self._sizeChangedHandler(self,diffSize) if self._parentWidget: self._parentWidget._refreshCache() @@ -190,6 +302,49 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): widgets += c._setTreeItemParent(parent) return widgets + def _item_at(self, pos:int) -> Optional[Tuple[int,int,TTkTreeWidgetItem]]: + if pos < 0: + return None + if page := self._get_page(self._buffer.level, pos, 1): + return page[0] + else: + return None + + def _get_page(self, level:int, index:int, size:int) -> List[Tuple[int,int,TTkTreeWidgetItem]]: + return self._buffer.get_page(self,level,index,size) + + def _iterate(self, level:int=0, skip:int=0) -> Generator[Tuple[TTkTreeWidgetItem, int], None, int]: + for _c in self._children: + if skip>0: + skip -= 1 + else: + yield _c, level + if _c._expanded: + skip = yield from _c._iterate(level+1, skip) + return skip + + def _iterate_h(self, level:int=0, skip:int=0) -> Generator[Tuple[TTkTreeWidgetItem, int, int], None, int]: + for _c in self._children: + for _y in range(_c._height): + if skip>0: + skip -= 1 + else: + yield _c, level, _y + if _c._expanded: + skip = yield from _c._iterate_h(level+1, skip) + return skip + + def _listify(self, level:int): + self._list_h_bk = [(self,level,_y) for _y in range(self._height)] + if self._expanded: + for _c in self._children: + self._list_h_bk.extend(_c.listify(level+1)) + + def listify(self,level:int=0) -> List[TTkTreeWidgetItem, int, int]: + if not self._list_h_bk: + self._listify(level=level) + return self._list_h_bk + def setTreeItemParent(self, parent): if parent: widgets = self._setTreeItemParent(parent) @@ -199,8 +354,6 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): widgets = self._clearTreeItemParent() # pw.rootLayout().removeWidgets(widgets) - - def hasWidgets(self): return self._hasWidgets @@ -210,7 +363,7 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): def setHidden(self, hide): if hide == self._hidden: return self._hidden = hide - self.dataChanged.emit() + self.emitDataChanged() def childIndicatorPolicy(self): return self._childIndicatorPolicy @@ -219,7 +372,7 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): self._childIndicatorPolicy = policy self._setDefaultIcon() - def _addChild(self, child): + def _addChild(self, child:TTkTreeWidgetItem): self._children.append(child) child._parent = self child._sortOrder = self._sortOrder @@ -229,17 +382,28 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): if self._parentWidget: child.setTreeItemParent(self._parentWidget) child.dataChanged.connect(self.emitDataChanged) + child._sizeChanged.connect(self._sizeChangedHandler) + child._invalidateListBuffer.connect(self._invalidateListBufferHandler) - def addChild(self, child): + def addChild(self, child:TTkTreeWidgetItem): self._addChild(child) - self.dataChanged.emit() + self._list_h_bk = [] + self._invalidateListBuffer.emit(self) + if self._expanded: + self._sizeChangedHandler(self, child.size()) + self.emitDataChanged() - def addChildren(self, children): + def addChildren(self, children:List[TTkTreeWidgetItem]): for child in children: self._addChild(child) - self.dataChanged.emit() + self._list_h_bk = [] + self._invalidateListBuffer.emit(self) + if self._expanded: + sizes = sum(_c.size() for _c in children) + self._sizeChangedHandler(self, sizes) + self.emitDataChanged() - def removeChild(self, child): + def removeChild(self, child:TTkTreeWidgetItem): if child in self._children: self.takeChild(self._children.index(child)) @@ -248,29 +412,38 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): return None child = self._children.pop(index) child.dataChanged.disconnect(self.emitDataChanged) + child._sizeChanged.disconnect(self._sizeChangedHandler) + child._invalidateListBuffer.disconnect(self._invalidateListBufferHandler) child.setTreeItemParent(None) - self.dataChanged.emit() + self._sizeChangedHandler(self, -child.size()) + self._list_h_bk = [] + self._invalidateListBuffer.emit(self) + self.emitDataChanged() return child def takeChildren(self): children = self._children for child in children: child.dataChanged.disconnect(self.emitDataChanged) + child._sizeChanged.disconnect(self._sizeChangedHandler) + child._invalidateListBuffer.disconnect(self._invalidateListBufferHandler) child.setTreeItemParent(None) + self._sizeChangedHandler(self, self._height-self._buffer.total_size) self._children = [] - self.dataChanged.emit() + self._list_h_bk = [] + self._invalidateListBuffer.emit(self) + self.emitDataChanged() return children - - def child(self, index): + def child(self, index:int) -> TTkTreeWidgetItem: if 0 <= index < len(self._children): return self._children[index] return None - def children(self): + def children(self) -> List[TTkTreeWidgetItem]: return [x for x in self._children if not x.isHidden()] - def indexOfChild(self, child): + def indexOfChild(self, child:TTkTreeWidgetItem) -> Optional[int]: if child in self._children: return self._children.index(child) return None @@ -328,7 +501,11 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): if children: for c in self._children: c.dataChanged.disconnect(self.emitDataChanged) + c._sizeChanged.disconnect(self._sizeChangedHandler) + c._invalidateListBuffer.disconnect(self._invalidateListBufferHandler) c.sortChildren(self._sortColumn, self._sortOrder) + c._invalidateListBuffer.connect(self._invalidateListBufferHandler) + c._sizeChanged.connect(self._sizeChangedHandler) c.dataChanged.connect(self.emitDataChanged) def sortChildren(self, col, order): @@ -336,16 +513,24 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): self._sortOrder = order if not self._children: return self._sort(children=True) + self._list_h_bk = [] + self._invalidateListBuffer.emit(self) self.dataChanged.emit() @pyTTkSlot() def emitDataChanged(self): self.dataChanged.emit() + @pyTTkSlot() + def _invalidateListBufferHandler(self): + self._list_h_bk = [] + if self._expanded: + self._invalidateListBuffer.emit(self) + # def setDisabled(disabled): # pass - def setExpanded(self, expand): + def setExpanded(self, expand:bool): # hide all the widgets if this item is not expanded if not expand: def _recurseHide(item): @@ -356,9 +541,16 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): if c._expanded: _recurseHide(c) _recurseHide(self) + if self._expanded != expand and self._children: + self._list_h_bk = [] + if expand: + self._sizeChangedHandler(self, sum(_c.size() for _c in self._children)) + else: + self._sizeChangedHandler(self, self._height-self._buffer.total_size) + self._invalidateListBuffer.emit(self) self._expanded = expand self._setDefaultIcon() - self.emitDataChanged() + self.dataChanged.emit() def setSelected(self, select): self._selected = select @@ -373,7 +565,14 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): return self._selected def size(self): - if self._expanded: - return self._height + sum(c.size() for c in self.children()) - else: + if not self._children: return self._height + if not self._buffer.total_size: + if self._expanded: + ret = self._height + for _c in self._children: + ret += _c.size() + self._buffer.total_size = ret + else: + self._buffer.total_size = self._height + return self._buffer.total_size diff --git a/tests/pytest/test_005_tree.py b/tests/pytest/test_005_tree.py new file mode 100644 index 00000000..0816da9f --- /dev/null +++ b/tests/pytest/test_005_tree.py @@ -0,0 +1,231 @@ +# MIT License +# +# Copyright (c) 2025 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 +import pytest +from typing import Union, Optional, List, Tuple + +sys.path.append(os.path.join(sys.path[0],'../../libs/pyTermTk')) + +import TermTk as ttk + +def _gen_childs(num:int,prefix:str,nesting=2) -> List[ttk.TTkTreeWidgetItem]: + ret = [] + for i in range(num): + _c = ttk.TTkTreeWidgetItem([f"{prefix} A {i}", f"{prefix} B {i}", f"{prefix} C {i}"]) + _c._height = i+1 + ret.append(_c) + if i%2: + _c.setExpanded(True) + if nesting: + _c.addChildren(_gen_childs(num,f"{prefix}_X",nesting-1)) + return ret + +def _create_tree() -> Tuple[ttk.TTkTreeWidgetItem,ttk.TTkTreeWidgetItem,ttk.TTkTreeWidgetItem]: + l0 = ttk.TTkTreeWidgetItem(["XX0","XX0","XX0"]) + l1 = ttk.TTkTreeWidgetItem(["String A", "String B", "String C"]) + l2 = ttk.TTkTreeWidgetItem(["String AA", "String BB", "String CC"]) + l3 = ttk.TTkTreeWidgetItem(["String AAA", "String BBB", "String CCC"]) + l4 = ttk.TTkTreeWidgetItem(["String AAAA", "String BBBB", "String CCCC"]) + l5 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + + l2.addChild(l5) + + l1.addChildren(_gen_childs(2,'l1',1)) + l2.addChildren(_gen_childs(4,'l2',2)) + l3.addChildren(_gen_childs(3,'l3',1)) + l4.addChildren(_gen_childs(2,'l4',1)) + l5.addChildren(_gen_childs(2,'l5',1)) + + l0.addChild(l1) + l0.addChild(l2) + l0.addChild(l3) + l0.addChild(l4) + + l0.setExpanded(True) + l2.setExpanded(True) + l5.setExpanded(True) + l4.setExpanded(True) + + return l0, l2, l5 + +def _format_item(item:ttk.TTkTreeWidgetItem) -> str: + return f"{item.data(0)} {item.data(1)} {item.data(2)}" + +def _print_tree(child:ttk.TTkTreeWidgetItem, level:int=0): + if child.isExpanded() and child.children(): + print(' '*level, ' v ', _format_item(child)) + for i,c in enumerate(child.children()): + _print_tree(c,level+1) + elif child.children(): + print(' '*level, ' > ', _format_item(child)) + else: + print(' '*level, ' - ', _format_item(child)) + +def test_tree_item_iterate_skip(): + tree,_,_ = _create_tree() + + print('\nTree:') + _print_tree(tree) + + # for i,(a,b) in enumerate(tree.iterate()): + # print(f"{i:03} - ", b, ' '*b, _format_item(a)) + + # print('\nSkip 3') + # for i,(a,b) in enumerate(tree.iterate(skip=3),3): + # print(f"{i:03} - ", b, ' '*b, _format_item(a)) + + # print('\nSkip 7') + # for i,(a,b) in enumerate(tree.iterate(skip=7),7): + # print(f"{i:03} - ", b, ' '*b, _format_item(a)) + + full = [(a,b) for a,b in tree._iterate()] + + assert full == [(a,b) for a,b in tree._iterate()] + assert full == [(a,b) for a,b in tree._iterate(skip= 0)] + assert full[ 3:] == [(a,b) for a,b in tree._iterate(skip= 3)] + assert full[ 5:] == [(a,b) for a,b in tree._iterate(skip= 5)] + assert full[ 6:] == [(a,b) for a,b in tree._iterate(skip= 6)] + assert full[10:] == [(a,b) for a,b in tree._iterate(skip=10)] + assert full[15:] == [(a,b) for a,b in tree._iterate(skip=15)] + assert full[20:] == [(a,b) for a,b in tree._iterate(skip=20)] + assert full[30:] == [(a,b) for a,b in tree._iterate(skip=30)] + assert full[80:] == [(a,b) for a,b in tree._iterate(skip=80)] + assert [] == [(a,b) for a,b in tree._iterate(skip=80)] + + + +def test_tree_item_iterate_skip_2(): + def my_gen(): + for i in range(10): + yield i + + # Save progress + gen = my_gen() + + # Resume from saved_index + for i,item in enumerate(gen): + print(i, item) + if i==5: + break + + for i,item in enumerate(gen): + print(i, item) + +def test_tree_item_listify(): + tree,c1,c2 = _create_tree() + + print('\nTree:') + _print_tree(tree) + + for i,(a,b,c) in enumerate(tree.listify()): + print(f"{i:03} - ", b, c, ' '*b, _format_item(a)) + + full = [(a,b) for a,b in tree._iterate()] + + print('expand') + c1.setExpanded(False) + for i,(a,b,c) in enumerate(tree.listify()): + print(f"{i:03} - ", b, c, ' '*b, _format_item(a)) + + print('expand') + c1.setExpanded(True) + for i,(a,b,c) in enumerate(tree.listify()): + print(f"{i:03} - ", b, c, ' '*b, _format_item(a)) + + print('expand') + c2.setExpanded(False) + for i,(a,b,c) in enumerate(tree.listify()): + print(f"{i:03} - ", b, c, ' '*b, _format_item(a)) + + print('expand') + c2.setExpanded(True) + for i,(a,b,c) in enumerate(tree.listify()): + print(f"{i:03} - ", b, c, ' '*b, _format_item(a)) + + print('expand c1 False') + c1.setExpanded(False) + for i,(a,b,c) in enumerate(tree.listify()): + print(f"{i:03} - ", b, c, ' '*b, _format_item(a)) + + print('expand c2 False') + c2.setExpanded(False) + for i,(a,b,c) in enumerate(tree.listify()): + print(f"{i:03} - ", b, c, ' '*b, _format_item(a)) + + print('expand c2 True') + c2.setExpanded(True) + for i,(a,b,c) in enumerate(tree.listify()): + print(f"{i:03} - ", b, c, ' '*b, _format_item(a)) + + print('expand c1 True') + c1.setExpanded(True) + for i,(a,b,c) in enumerate(tree.listify()): + print(f"{i:03} - ", b, c, ' '*b, _format_item(a)) + + +def _get_full_tree_page(item:ttk.TTkTreeWidgetItem) -> List[ttk.TTkTreeWidgetItem]: + ret = [item]*item._height + if item.isExpanded(): + for ch in item.children(): + ret.extend(_get_full_tree_page(ch)) + return ret + +def test_tree_get_page(): + tree,c1,c2 = _create_tree() + + full_page = _get_full_tree_page(tree) + + print('\nTree:') + _print_tree(tree) + + def _test_page(index,size): + page = tree._get_page(0,index,size) + # print(f"Testing: {index=} {size=} , page size={len(page)}") + assert [f"{c.isExpanded()} {c.data(0)}" for c in full_page[index:index+size]] == [f"{c.isExpanded()} {c.data(0)}" for a,b,c in page] + + # _test_page(0,1) + # _test_page(0,2) + # _test_page(0,3) + # _test_page(0,4) + # _test_page(0,5) + # _test_page(0,5) + # _test_page(0,6) + # _test_page(0,7) + # _test_page(0,10) + # _test_page(0,100) + + for i in range(0,100,1): + for j in range(0,100,1): + _test_page(i,j) + + print("\n - 0,10") + page = tree._get_page(0,0,10) + for a,b,c in page: + print(a, b, ' '*a, _format_item(c)) + + print("\n - 2,5") + page = tree._get_page(0,2,5) + for a,b,c in page: + print(a, b, ' '*a, _format_item(c)) + + diff --git a/tests/t.ui/test.ui.011.tree.05.paging.py b/tests/t.ui/test.ui.011.tree.05.paging.py new file mode 100755 index 00000000..3bc2e6d9 --- /dev/null +++ b/tests/t.ui/test.ui.011.tree.05.paging.py @@ -0,0 +1,142 @@ +#!/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. + +# Demo inspired from: +# https://stackoverflow.com/questions/41204234/python-pyqt5-qtreewidget-sub-item + +import os +import sys +from threading import Thread + +sys.path.append(os.path.join(sys.path[0],'../..')) +import TermTk as ttk + +ttk.TTkLog.use_default_file_logging() + + +root = ttk.TTk() + +base_btn = ttk.TTkButton(parent=root, text="Test Base", pos=(0,0), size=(20,3), border=True) +enough_btn = ttk.TTkButton(parent=root, text="Test Enough", pos=(20,0), size=(20,3), border=True) +many_btn = ttk.TTkButton(parent=root, text="Test Many", pos=(40,0), size=(20,3), border=True) +winTree = ttk.TTkWindow(parent=root,pos = (0,3), size=(80,30), title="Test Tree 1", layout=ttk.TTkGridLayout(), border=True) + +winLog = ttk.TTkWindow(parent=root,pos = (5,10), size=(100,30), title="Logs", layout=ttk.TTkGridLayout(), border=True) +ttk.TTkLogViewer(parent=winLog) + +tw = ttk.TTkTree(parent=winTree) +tw.setHeaderLabels(["Column 1", "Column 2", "Column 3"]) + +@ttk.pyTTkSlot() +def _add_base(): + tw.clear() + l1 = ttk.TTkTreeWidgetItem(["String A", "String B\nxyz\nabc\n123", "String C"]) + l2 = ttk.TTkTreeWidgetItem(["String AA", "String BB", "String CC"]) + l3 = ttk.TTkTreeWidgetItem(["String AAA", "String BBB", "String CCC\nxyz\nabc\n123"]) + l4 = ttk.TTkTreeWidgetItem(["String AAAA", "String BBBB", "String CCCC"]) + l5 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB\nxyz\nabc\n123", "String CCCCC"]) + l2.addChild(l5) + + + for i in range(3): + l1_child = ttk.TTkTreeWidgetItem(["Child A" + str(i), "Child B" + str(i), "Child C" + str(i)]) + l1.addChild(l1_child) + + for j in range(2): + l2_child = ttk.TTkTreeWidgetItem(["Child AA\nxyz\nabc\n123" + str(j), "Child BB" + str(j), "Child CC" + str(j)]) + l2.addChild(l2_child) + + for j in range(2): + l3_child = ttk.TTkTreeWidgetItem(["Child AAA" + str(j), "Child BBB" + str(j), "Child CCC" + str(j)]) + l3.addChild(l3_child) + + for j in range(2): + l4_child = ttk.TTkTreeWidgetItem(["Child AAAA" + str(j), "Child BBBB\nxyz\nabc\n123" + str(j), "Child CCCC" + str(j)]) + l4.addChild(l4_child) + + for j in range(2): + l5_child = ttk.TTkTreeWidgetItem(["Child AAAAA" + str(j), "Child BBBBB\nxyz\nabc\n123" + str(j), "Child CCCCC" + str(j)]) + l5.addChild(l5_child) + + + tw.addTopLevelItem(l1) + tw.addTopLevelItem(l2) + tw.addTopLevelItem(l3) + tw.addTopLevelItem(l4) + l1.setExpanded(True) + l3.setExpanded(True) + + +@ttk.pyTTkSlot() +def _add_many(): + _add_base() + + def _many_loop(): + ttk.TTkLog.debug('Many Loop START!!!') + num = 1 + for i in range(10): + ttk.TTkLog.debug(f"Loop: {i}") + _entries = [] + num = num<<1 + for ii in range(num): + _e = ttk.TTkTreeWidgetItem([f"({i}-{ii}) String A", "String B", "String C"]) + _e.addChildren([ + ttk.TTkTreeWidgetItem(["Child A" + str(ii) + (f"\nl:{ii}"*ii), "Child B" + str(ii), "Child C" + str(ii)]) + for i in range(3) + ]) + _entries.append(_e) + if not ii%3: + _e.setExpanded(True) + tw.addTopLevelItems(_entries) + ttk.TTkLog.debug('DONE!!!') + Thread(target=_many_loop).start() + +@ttk.pyTTkSlot() +def _add_enough(): + _add_base() + + def _many_loop(): + ttk.TTkLog.debug('Many Loop START!!!') + num = 1 + for i in range(4): + _entries = [] + num = num<<1 + for ii in range(num): + _e = ttk.TTkTreeWidgetItem([f"({i}-{ii}) String A", "String B", "String C"]) + _e.addChildren([ + ttk.TTkTreeWidgetItem(["Child A" + str(ii) + (f"\nl:{ii}"*ii), "Child B" + str(ii), "Child C" + str(ii)]) + for i in range(3) + ]) + _entries.append(_e) + if not ii%3: + _e.setExpanded(True) + tw.addTopLevelItems(_entries) + ttk.TTkLog.debug('DONE!!!') + Thread(target=_many_loop).start() + +base_btn.clicked.connect(_add_base) +enough_btn.clicked.connect(_add_enough) +many_btn.clicked.connect(_add_many) + +root.mainloop() \ No newline at end of file diff --git a/tests/timeit/02.array.10.List.creation.py b/tests/timeit/02.array.10.List.creation.py new file mode 100755 index 00000000..a77079dc --- /dev/null +++ b/tests/timeit/02.array.10.List.creation.py @@ -0,0 +1,90 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2025 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. + +from __future__ import annotations + +import sys, os + +from dataclasses import dataclass +from enum import Enum,Flag,auto +import timeit + +from typing import List, Tuple, Iterator + +sys.path.append(os.path.join(sys.path[0],'../../libs/pyTermTk')) + +import TermTk as ttk + +txt = "Eugenio" + +l = [txt] * 1000000 +l1 = [txt] * 1000000 +l2 = [txt] * 1000000 +l3 = [txt] * 1000000 +l4 = [txt] * 1000000 + +def test_ti_01_append(): + _ret = [] + for i in l: + _ret.append(i) + return len(_ret) + +def test_ti_02_copy(): + _ret = [] + _ret[:] = l + return len(_ret) + +def test_ti_03_copy_2(): + _ret = [None]*len(l) + for i,ii in enumerate(l): + _ret[i] = ii + return len(_ret) + +def test_ti_03_copy_3(): + _ret = [] + for i in range(len(l) // 256): + _ret.extend(l[i:i+256]) + return len(_ret) + +def test_ti_03_copy_4(): + _ret = [] + _ret.extend(l) + return len(_ret) + +def test_ti_03_copy_5(): + _ret = l.copy() + return len(_ret) + +def test_ti_04_reduce_01(): + _ret = l4[:5000] + return len(_ret) + +loop = 100 + +a:dict = {} + +for testName in sorted([tn for tn in globals() if tn.startswith('test_ti_')]): + result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop) + # print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}") + print(f"{testName} | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}") diff --git a/tests/timeit/33.tree.01.iterate.py b/tests/timeit/33.tree.01.iterate.py new file mode 100755 index 00000000..5f835442 --- /dev/null +++ b/tests/timeit/33.tree.01.iterate.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2025 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. + +from __future__ import annotations + +import sys, os + +from dataclasses import dataclass +from enum import Enum,Flag,auto +import timeit + +from typing import List, Tuple, Iterator + +sys.path.append(os.path.join(sys.path[0],'../../libs/pyTermTk')) + +import TermTk as ttk + +def _create_tree() -> ttk.TTkTreeWidgetItem: + l0 = ttk.TTkTreeWidgetItem(["XX0","XX0","XX0"]) + l1 = ttk.TTkTreeWidgetItem(["String A", "String B", "String C"]) + l2 = ttk.TTkTreeWidgetItem(["String AA", "String BB", "String CC"]) + l3 = ttk.TTkTreeWidgetItem(["String AAA", "String BBB", "String CCC"]) + l4 = ttk.TTkTreeWidgetItem(["String AAAA", "String BBBB", "String CCCC"]) + l5 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + l51 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + l511 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + l5111 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + l52 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + l521 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + + l2.addChild(l5) + l5.addChild(l51) + l51.addChild(l511) + l511.addChild(l5111) + l5.addChild(l52) + l52.addChild(l521) + + def _addChilds(p:ttk.TTkTreeWidgetItem,num:int,prefix:str,nesting=2): + for i in range(num): + _c = ttk.TTkTreeWidgetItem([f"{prefix} A {i}", f"{prefix} B {i}", f"{prefix} C {i}"]) + _c._height = i + p.addChild(_c) + if i%2: + _c.setExpanded(True) + if nesting: + _addChilds(_c,num,f"{prefix}_X",nesting-1) + + _addChilds(l1,10,'l1',3) + _addChilds(l2,10000,'l2',0) + _addChilds(l2,20,'l2',2) + _addChilds(l3,30,'l3',2) + _addChilds(l4,10,'l4',2) + _addChilds(l5,20,'l5',2) + _addChilds(l51,30,'l51',2) + _addChilds(l511,30,'l511',2) + _addChilds(l5111,40,'l5111',2) + _addChilds(l52,50,'l52',2) + _addChilds(l521,10,'l521',2) + + l0.addChild(l1) + l0.addChild(l2) + l0.addChild(l3) + l0.addChild(l4) + + l0.setExpanded(True) + l2.setExpanded(True) + l5.setExpanded(True) + l51.setExpanded(True) + l511.setExpanded(True) + l5111.setExpanded(True) + l52.setExpanded(True) + l521.setExpanded(True) + l4.setExpanded(True) + + return l0 + +def _format_item(item:ttk.TTkTreeWidgetItem) -> str: + return f"{item.data(0)} {item.data(1)} {item.data(2)}" + +def _full_iterate(p:ttk.TTkTreeWidgetItem, level:int=0) -> Iterator[ttk.TTkTreeWidgetItem]: + for _c in p._children: + yield _c, level + if _c._expanded: + yield from _full_iterate(_c,level+1) + +def _full_full_iterate(p:ttk.TTkTreeWidgetItem, level:int=0) -> Iterator[ttk.TTkTreeWidgetItem]: + for _c in p._children: + yield _c, level + yield from _c._iterate(level+1) + +def _get_size_iterate_1(p:ttk.TTkTreeWidgetItem) -> int: + return len(p._children) + sum(_get_size_iterate_1(_c) for _c in p._children if _c.isExpanded()) + +def _get_size_iterate_2(p:ttk.TTkTreeWidgetItem) -> int: + _ret = len(p._children) + for _c in p._children: + if _c.isExpanded(): + _ret += _get_size_iterate_2(_c) + return _ret + +def _get_size_iterate_3(p:ttk.TTkTreeWidgetItem) -> int: + # return p.height() + _ret = 0 + for _c in p._children: + _ret += _c._height + if _c.isExpanded(): + _ret += _get_size_iterate_3(_c) + return _ret + +tree = _create_tree() + +print('Total: ', len(list(_full_full_iterate(tree)))) + +def test_ti_0_00_0(): return len([(a,b) for a,b in _full_iterate(tree)]) +def test_ti_0_01_0(): return len([(a,b) for a,b in tree._iterate()]) +def test_ti_0_02_0(): return len([(a,b) for a,b in tree._iterate(skip=10)]) +def test_ti_0_03_0(): return len([(a,b) for a,b in tree._iterate(skip=100)]) +def test_ti_0_04_0(): return len([(a,b) for a,b in tree._iterate(skip=500)]) +def test_ti_0_05_0(): return len([(a,b) for a,b in tree._iterate(skip=1000)]) +def test_ti_0_06_0(): return len([(a,b) for a,b in tree._iterate(skip=10000)]) +def test_ti_0_07_0(): return len([(a,b) for a,b in tree._iterate(skip=50000)]) +def test_ti_0_08_0(): return len([(a,b) for a,b in tree._iterate(skip=100000)]) + +def test_ti_0_00_1(): return len([None for _ in _full_iterate(tree)]) +def test_ti_0_01_1(): return len([None for _ in tree._iterate()]) +def test_ti_0_02_1(): return len([None for _ in tree._iterate(skip=10)]) +def test_ti_0_03_1(): return len([None for _ in tree._iterate(skip=100)]) +def test_ti_0_04_1(): return len([None for _ in tree._iterate(skip=500)]) +def test_ti_0_05_1(): return len([None for _ in tree._iterate(skip=1000)]) +def test_ti_0_06_1(): return len([None for _ in tree._iterate(skip=10000)]) +def test_ti_0_07_1(): return len([None for _ in tree._iterate(skip=50000)]) +def test_ti_0_08_1(): return len([None for _ in tree._iterate(skip=100000)]) + +def test_ti_0_00_2(): return len(list(_full_iterate(tree))) +def test_ti_0_01_2(): return len(list(tree._iterate())) +def test_ti_0_02_2(): return len(list(tree._iterate(skip=10))) +def test_ti_0_03_2(): return len(list(tree._iterate(skip=100))) +def test_ti_0_04_2(): return len(list(tree._iterate(skip=500))) +def test_ti_0_05_2(): return len(list(tree._iterate(skip=1000))) +def test_ti_0_06_2(): return len(list(tree._iterate(skip=10000))) +def test_ti_0_07_2(): return len(list(tree._iterate(skip=50000))) +def test_ti_0_08_2(): return len(list(tree._iterate(skip=100000))) + +def test_ti_1_00(): + for a,b in _full_iterate(tree): + return _format_item(a) +def test_ti_1_01(): + for a,b in tree._iterate(): + return _format_item(a) +def test_ti_1_02(): + for a,b in tree._iterate(skip=10): + return _format_item(a) +def test_ti_1_03(): + for a,b in tree._iterate(skip=100): + return _format_item(a) +def test_ti_1_04(): + for a,b in tree._iterate(skip=500): + return _format_item(a) +def test_ti_1_05(): + for a,b in tree._iterate(skip=1000): + return _format_item(a) +def test_ti_1_06(): + for a,b in tree._iterate(skip=10000): + return _format_item(a) +def test_ti_1_07(): + for a,b in tree._iterate(skip=50000): + return _format_item(a) +def test_ti_1_08(): + for a,b in tree._iterate(skip=100000): + return _format_item(a) + +def test_ti_2_00(): + for _ in _full_iterate(tree): pass +def test_ti_2_01(): + for _ in tree._iterate(): pass +def test_ti_2_02(): + for _ in tree._iterate(skip=10): pass +def test_ti_2_03(): + for _ in tree._iterate(skip=100): pass +def test_ti_2_04(): + for _ in tree._iterate(skip=500): pass +def test_ti_2_05(): + for _ in tree._iterate(skip=1000): pass +def test_ti_2_06(): + for _ in tree._iterate(skip=10000): pass +def test_ti_2_07(): + for _ in tree._iterate(skip=50000): pass +def test_ti_2_08(): + for _ in tree._iterate(skip=100000): pass + +loop = 100 + +a:dict = {} + +for testName in sorted([tn for tn in globals() if tn.startswith('test_ti_')]): + result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop) + # print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}") + print(f"{testName} | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}") diff --git a/tests/timeit/33.tree.02.iterate.py b/tests/timeit/33.tree.02.iterate.py new file mode 100755 index 00000000..18398770 --- /dev/null +++ b/tests/timeit/33.tree.02.iterate.py @@ -0,0 +1,159 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2025 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. + +from __future__ import annotations + +import sys, os + +from dataclasses import dataclass +from enum import Enum,Flag,auto +import timeit + +from typing import List, Tuple, Iterator + +sys.path.append(os.path.join(sys.path[0],'../../libs/pyTermTk')) + +import TermTk as ttk + +def _create_tree() -> ttk.TTkTreeWidgetItem: + l0 = ttk.TTkTreeWidgetItem(["XX0","XX0","XX0"]) + l1 = ttk.TTkTreeWidgetItem(["String A", "String B", "String C"]) + l2 = ttk.TTkTreeWidgetItem(["String AA", "String BB", "String CC"]) + l3 = ttk.TTkTreeWidgetItem(["String AAA", "String BBB", "String CCC"]) + l4 = ttk.TTkTreeWidgetItem(["String AAAA", "String BBBB", "String CCCC"]) + l5 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + l51 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + l511 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + l5111 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + l52 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + l521 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + + l2.addChild(l5) + l5.addChild(l51) + l51.addChild(l511) + l511.addChild(l5111) + l5.addChild(l52) + l52.addChild(l521) + + def _addChilds(p:ttk.TTkTreeWidgetItem,num:int,prefix:str,nesting=2): + for i in range(num): + _c = ttk.TTkTreeWidgetItem([f"{prefix} A {i}", f"{prefix} B {i}", f"{prefix} C {i}"]) + _c._height = i + p.addChild(_c) + if i%2: + _c.setExpanded(True) + if nesting: + _addChilds(_c,num,f"{prefix}_X",nesting-1) + + _addChilds(l1,10,'l1',3) + _addChilds(l2,10000,'l2',0) + _addChilds(l2,20,'l2',2) + _addChilds(l3,30,'l3',2) + _addChilds(l4,10,'l4',2) + _addChilds(l5,20,'l5',2) + _addChilds(l51,30,'l51',2) + _addChilds(l511,30,'l511',2) + _addChilds(l5111,40,'l5111',2) + _addChilds(l52,50,'l52',2) + _addChilds(l521,10,'l521',2) + + l0.addChild(l1) + l0.addChild(l2) + l0.addChild(l3) + l0.addChild(l4) + + l0.setExpanded(True) + l2.setExpanded(True) + l5.setExpanded(True) + l51.setExpanded(True) + l511.setExpanded(True) + l5111.setExpanded(True) + l52.setExpanded(True) + l521.setExpanded(True) + l4.setExpanded(True) + + return l0 + +def _format_item(item:ttk.TTkTreeWidgetItem) -> str: + return f"{item.data(0)} {item.data(1)} {item.data(2)}" + +def _full_iterate(p:ttk.TTkTreeWidgetItem, level:int=0) -> Iterator[ttk.TTkTreeWidgetItem]: + for _c in p._children: + yield _c, level + if _c._expanded: + yield from _c._iterate(level+1) + +def _full_full_iterate(p:ttk.TTkTreeWidgetItem, level:int=0) -> Iterator[ttk.TTkTreeWidgetItem]: + for _c in p._children: + yield _c, level + yield from _c._iterate(level+1) + +def _get_size_iterate_1(p:ttk.TTkTreeWidgetItem) -> int: + return len(p._children) + sum(_get_size_iterate_1(_c) for _c in p._children if _c.isExpanded()) + +def _get_size_iterate_2(p:ttk.TTkTreeWidgetItem) -> int: + _ret = len(p._children) + for _c in p._children: + if _c.isExpanded(): + _ret += _get_size_iterate_2(_c) + return _ret + +def _get_size_iterate_3(p:ttk.TTkTreeWidgetItem) -> int: + # return p.height() + _ret = 0 + for _c in p._children: + _ret += _c._height + if _c.isExpanded(): + _ret += _get_size_iterate_3(_c) + return _ret + +tree = _create_tree() + +print('Total: ', len(list(_full_full_iterate(tree)))) + +def test_ti_0_00_01(): return _get_size_iterate_1(tree) +def test_ti_0_00_02(): return _get_size_iterate_2(tree) +def test_ti_0_00_03(): return _get_size_iterate_3(tree) +def test_ti_0_00_04(): return _get_size_iterate_3(tree) +def test_ti_0_00_05(): return _get_size_iterate_2(tree) +def test_ti_0_00_06(): return _get_size_iterate_1(tree) +def test_ti_0_00_07(): return _get_size_iterate_1(tree) +def test_ti_0_00_08(): return _get_size_iterate_2(tree) +def test_ti_0_00_09(): return _get_size_iterate_3(tree) +def test_ti_0_00_10(): return _get_size_iterate_3(tree) +def test_ti_0_00_11(): return _get_size_iterate_2(tree) +def test_ti_0_00_12(): return _get_size_iterate_1(tree) + +def test_ti_0_01_01(): return tree.size() +def test_ti_0_01_02(): return tree.size() + + +loop = 100 + +a:dict = {} + +for testName in sorted([tn for tn in globals() if tn.startswith('test_ti_')]): + result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop) + # print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}") + print(f"{testName} | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}") diff --git a/tests/timeit/33.tree.03.iterate.py b/tests/timeit/33.tree.03.iterate.py new file mode 100755 index 00000000..31ebc89d --- /dev/null +++ b/tests/timeit/33.tree.03.iterate.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2025 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. + +from __future__ import annotations + +import sys, os + +from dataclasses import dataclass +from enum import Enum,Flag,auto +import timeit + +from typing import List, Tuple, Iterator + +sys.path.append(os.path.join(sys.path[0],'../../libs/pyTermTk')) + +import TermTk as ttk + +def _create_tree() -> ttk.TTkTreeWidgetItem: + l0 = ttk.TTkTreeWidgetItem(["XX0","XX0","XX0"]) + l1 = ttk.TTkTreeWidgetItem(["String A", "String B", "String C"]) + l2 = ttk.TTkTreeWidgetItem(["String AA", "String BB", "String CC"]) + l3 = ttk.TTkTreeWidgetItem(["String AAA", "String BBB", "String CCC"]) + l4 = ttk.TTkTreeWidgetItem(["String AAAA", "String BBBB", "String CCCC"]) + l5 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + l51 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + l511 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + l5111 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + l52 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + l521 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + + l2.addChild(l5) + l5.addChild(l51) + l51.addChild(l511) + l511.addChild(l5111) + l5.addChild(l52) + l52.addChild(l521) + + def _addChilds(p:ttk.TTkTreeWidgetItem,num:int,prefix:str,nesting=2): + for i in range(num): + _c = ttk.TTkTreeWidgetItem([f"{prefix} A {i}", f"{prefix} B {i}", f"{prefix} C {i}"]) + _c._height = i + p.addChild(_c) + if i%2: + _c.setExpanded(True) + if nesting: + _addChilds(_c,num,f"{prefix}_X",nesting-1) + + _addChilds(l1,10,'l1',3) + _addChilds(l2,100,'l2',0) + _addChilds(l2,20,'l2',2) + _addChilds(l3,30,'l3',2) + _addChilds(l4,10,'l4',2) + _addChilds(l5,20,'l5',2) + _addChilds(l51,30,'l51',2) + _addChilds(l511,30,'l511',2) + _addChilds(l5111,40,'l5111',2) + _addChilds(l52,50,'l52',2) + _addChilds(l521,10,'l521',2) + + l0.addChild(l1) + l0.addChild(l2) + l0.addChild(l3) + l0.addChild(l4) + + l0.setExpanded(True) + l2.setExpanded(True) + l5.setExpanded(True) + l51.setExpanded(True) + l511.setExpanded(True) + l5111.setExpanded(True) + l52.setExpanded(True) + l521.setExpanded(True) + l4.setExpanded(True) + + return l0 + +def _format_item(item:ttk.TTkTreeWidgetItem) -> str: + return f"{item.data(0)} {item.data(1)} {item.data(2)}" + +def _full_iterate(p:ttk.TTkTreeWidgetItem, level:int=0) -> Iterator[ttk.TTkTreeWidgetItem]: + for _c in p._children: + for _y in range(_c._height): + yield _c, level, _y + if _c._expanded: + yield from _full_iterate(_c,level+1) + +def _full_full_iterate(p:ttk.TTkTreeWidgetItem, level:int=0) -> Iterator[ttk.TTkTreeWidgetItem]: + for _c in p._children: + yield _c, level + yield from _c._iterate(level+1) + +def _get_size_iterate_1(p:ttk.TTkTreeWidgetItem) -> int: + return len(p._children) + sum(_get_size_iterate_1(_c) for _c in p._children if _c.isExpanded()) + +def _get_size_iterate_2(p:ttk.TTkTreeWidgetItem) -> int: + _ret = len(p._children) + for _c in p._children: + if _c.isExpanded(): + _ret += _get_size_iterate_2(_c) + return _ret + +def _get_size_iterate_3(p:ttk.TTkTreeWidgetItem) -> int: + # return p.height() + _ret = 0 + for _c in p._children: + _ret += _c._height + if _c.isExpanded(): + _ret += _get_size_iterate_3(_c) + return _ret + +tree = _create_tree() + +print('Total: ', len(list(_full_full_iterate(tree)))) + +def test_ti_0_00_0(): return len([x for x in _full_iterate(tree)]) +def test_ti_0_00_1(): return len([x for x in _full_iterate(tree)]) +def test_ti_0_01_0(): return len([x for x in tree.iterate_h()]) +def test_ti_0_02_0(): return len([x for x in tree.iterate_h(skip=10)]) +def test_ti_0_03_0(): return len([x for x in tree.iterate_h(skip=100)]) +def test_ti_0_04_0(): return len([x for x in tree.iterate_h(skip=500)]) +def test_ti_0_05_0(): return len([x for x in tree.iterate_h(skip=1000)]) +def test_ti_0_06_0(): return len([x for x in tree.iterate_h(skip=10000)]) +def test_ti_0_07_0(): return len([x for x in tree.iterate_h(skip=100000)]) +def test_ti_0_08_0(): return len([x for x in tree.iterate_h(skip=1000000)]) + +def test_ti_0_00_1(): return len([None for _ in _full_iterate(tree)]) +def test_ti_0_01_1(): return len([None for _ in tree.iterate_h()]) +def test_ti_0_02_1(): return len([None for _ in tree.iterate_h(skip=10)]) +def test_ti_0_03_1(): return len([None for _ in tree.iterate_h(skip=100)]) +def test_ti_0_04_1(): return len([None for _ in tree.iterate_h(skip=500)]) +def test_ti_0_05_1(): return len([None for _ in tree.iterate_h(skip=1000)]) +def test_ti_0_06_1(): return len([None for _ in tree.iterate_h(skip=10000)]) +def test_ti_0_07_1(): return len([None for _ in tree.iterate_h(skip=100000)]) +def test_ti_0_08_1(): return len([None for _ in tree.iterate_h(skip=1000000)]) + +def test_ti_0_00_2(): return len(list(_full_iterate(tree))) +def test_ti_0_01_2(): return len(list(tree.iterate_h())) +def test_ti_0_02_2(): return len(list(tree.iterate_h(skip=10))) +def test_ti_0_03_2(): return len(list(tree.iterate_h(skip=100))) +def test_ti_0_04_2(): return len(list(tree.iterate_h(skip=500))) +def test_ti_0_05_2(): return len(list(tree.iterate_h(skip=1000))) +def test_ti_0_06_2(): return len(list(tree.iterate_h(skip=10000))) +def test_ti_0_07_2(): return len(list(tree.iterate_h(skip=100000))) +def test_ti_0_08_2(): return len(list(tree.iterate_h(skip=1000000))) + +def test_ti_1_00(): + for a,b,c in _full_iterate(tree): + return _format_item(a) +def test_ti_1_01(): + for a,b,c in tree.iterate_h(): + return _format_item(a) +def test_ti_1_02(): + for a,b,c in tree.iterate_h(skip=10): + return _format_item(a) +def test_ti_1_03(): + for a,b,c in tree.iterate_h(skip=100): + return _format_item(a) +def test_ti_1_04(): + for a,b,c in tree.iterate_h(skip=500): + return _format_item(a) +def test_ti_1_05(): + for a,b,c in tree.iterate_h(skip=1000): + return _format_item(a) +def test_ti_1_06(): + for a,b,c in tree.iterate_h(skip=10000): + return _format_item(a) +def test_ti_1_07(): + for a,b,c in tree.iterate_h(skip=100000): + return _format_item(a) +def test_ti_1_08(): + for a,b,c in tree.iterate_h(skip=1000000): + return _format_item(a) + +def test_ti_2_00(): + for _ in _full_iterate(tree): pass +def test_ti_2_01(): + for _ in tree.iterate_h(): pass +def test_ti_2_02(): + for _ in tree.iterate_h(skip=10): pass +def test_ti_2_03(): + for _ in tree.iterate_h(skip=100): pass +def test_ti_2_04(): + for _ in tree.iterate_h(skip=500): pass +def test_ti_2_05(): + for _ in tree.iterate_h(skip=1000): pass +def test_ti_2_06(): + for _ in tree.iterate_h(skip=10000): pass +def test_ti_2_07(): + for _ in tree.iterate_h(skip=100000): pass +def test_ti_2_08(): + for _ in tree.iterate_h(skip=1000000): pass + +loop = 10 + +a:dict = {} + +for testName in sorted([tn for tn in globals() if tn.startswith('test_ti_')]): + result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop) + # print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}") + print(f"{testName} | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}") diff --git a/tests/timeit/33.tree.04.listify.py b/tests/timeit/33.tree.04.listify.py new file mode 100755 index 00000000..654e2f6f --- /dev/null +++ b/tests/timeit/33.tree.04.listify.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2025 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. + +from __future__ import annotations + +import sys, os + +from dataclasses import dataclass +from enum import Enum,Flag,auto +import timeit + +from typing import List, Tuple, Iterator + +sys.path.append(os.path.join(sys.path[0],'../../libs/pyTermTk')) + +import TermTk as ttk + +def _create_tree() -> ttk.TTkTreeWidgetItem: + l0 = ttk.TTkTreeWidgetItem(["XX0","XX0","XX0"]) + l1 = ttk.TTkTreeWidgetItem(["String A", "String B", "String C"]) + l2 = ttk.TTkTreeWidgetItem(["String AA", "String BB", "String CC"]) + l3 = ttk.TTkTreeWidgetItem(["String AAA", "String BBB", "String CCC"]) + l4 = ttk.TTkTreeWidgetItem(["String AAAA", "String BBBB", "String CCCC"]) + l5 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + l51 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + l511 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + l5111 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + l52 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + l521 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB", "String CCCCC"]) + + l2.addChild(l5) + l5.addChild(l51) + l51.addChild(l511) + l511.addChild(l5111) + l5.addChild(l52) + l52.addChild(l521) + + def _addChilds(p:ttk.TTkTreeWidgetItem,num:int,prefix:str,nesting=2): + _children = [] + for i in range(num): + _c = ttk.TTkTreeWidgetItem([f"{prefix} A {i}", f"{prefix} B {i}", f"{prefix} C {i}"]) + _c._height = i + _children.append(_c) + if i%2: + _c.setExpanded(True) + if nesting: + _addChilds(_c,num,f"{prefix}_X",nesting-1) + p.addChildren(_children) + + _addChilds(l1,10,'l1',3) + _addChilds(l2,100,'l2',0) + _addChilds(l2,20,'l2',2) + _addChilds(l3,30,'l3',2) + _addChilds(l4,10,'l4',2) + _addChilds(l5,20,'l5',2) + _addChilds(l51,30,'l51',2) + _addChilds(l511,30,'l511',2) + _addChilds(l5111,40,'l5111',2) + _addChilds(l52,50,'l52',2) + _addChilds(l521,10,'l521',2) + + l0.addChild(l1) + l0.addChild(l2) + l0.addChild(l3) + l0.addChild(l4) + + l0.setExpanded(True) + l2.setExpanded(True) + l5.setExpanded(True) + l51.setExpanded(True) + l511.setExpanded(True) + l5111.setExpanded(True) + l52.setExpanded(True) + l521.setExpanded(True) + l4.setExpanded(True) + + return l0 + +def _format_item(item:ttk.TTkTreeWidgetItem) -> str: + return f"{item.data(0)} {item.data(1)} {item.data(2)}" + +def _full_iterate(p:ttk.TTkTreeWidgetItem, level:int=0) -> Iterator[ttk.TTkTreeWidgetItem]: + for _c in p._children: + for _y in range(_c._height): + yield _c, level, _y + if _c._expanded: + yield from _full_iterate(_c,level+1) + +def _full_full_iterate(p:ttk.TTkTreeWidgetItem, level:int=0) -> Iterator[ttk.TTkTreeWidgetItem]: + for _c in p._children: + yield _c, level + yield from _c._iterate(level+1) + +def _get_size_iterate_1(p:ttk.TTkTreeWidgetItem) -> int: + return len(p._children) + sum(_get_size_iterate_1(_c) for _c in p._children if _c.isExpanded()) + +def _get_size_iterate_2(p:ttk.TTkTreeWidgetItem) -> int: + _ret = len(p._children) + for _c in p._children: + if _c.isExpanded(): + _ret += _get_size_iterate_2(_c) + return _ret + +def _get_size_iterate_3(p:ttk.TTkTreeWidgetItem) -> int: + # return p.height() + _ret = 0 + for _c in p._children: + _ret += _c._height + if _c.isExpanded(): + _ret += _get_size_iterate_3(_c) + return _ret + +tree = _create_tree() + +print('Total: ', len(list(_full_full_iterate(tree)))) + +def test_ti_0_00_0(): return len([x for x in _full_iterate(tree)]) +def test_ti_0_00_1(): return len([x for x in _full_iterate(tree)]) +def test_ti_0_01_0(): return len(tree.listify()) +def test_ti_0_02_0(): return tree.size() + +loop = 100 + +a:dict = {} + +for testName in sorted([tn for tn in globals() if tn.startswith('test_ti_')]): + result = timeit.timeit(f'{testName}(*a)', globals=globals(), number=loop) + # print(f"test{iii}) fps {loop / result :.3f} - s {result / loop:.10f} - {result / loop} {globals()[testName](*a)}") + print(f"{testName} | {result / loop:.10f} sec. | {loop / result : 15.3f} Fps ╞╡-> {globals()[testName](*a)}")