Browse Source

chore(TTkTree): performance improvement due to smart caching (#451)

pull/434/head
Pier CeccoPierangioliEugenio 7 months ago committed by GitHub
parent
commit
26ce74c022
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 28
      .vscode/launch.json
  2. 1
      TermTk
  3. 2
      demo/showcase/filepicker.py
  4. 4
      libs/pyTermTk/TermTk/TTkCore/constant.py
  5. 4
      libs/pyTermTk/TermTk/TTkCore/string.py
  6. 11
      libs/pyTermTk/TermTk/TTkLayouts/layout.py
  7. 12
      libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/filetreewidget.py
  8. 15
      libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/filetreewidgetitem.py
  9. 238
      libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidget.py
  10. 523
      libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidgetitem.py
  11. 2
      tests/pytest/run_test_001_demo.py
  12. 2
      tests/pytest/run_test_002_textedit.py
  13. 2
      tests/pytest/test_003_string.py
  14. 472
      tests/pytest/test_005_tree.py
  15. 2
      tests/t.ui/test.ui.011.tree.02.widgets.py
  16. 2
      tests/t.ui/test.ui.011.tree.03.widgets.py
  17. 2
      tests/t.ui/test.ui.011.tree.04.dnd.01.py
  18. 144
      tests/t.ui/test.ui.011.tree.05.paging.py
  19. 90
      tests/timeit/02.array.10.List.creation.py
  20. 219
      tests/timeit/33.tree.01.iterate.py
  21. 159
      tests/timeit/33.tree.02.iterate.py
  22. 221
      tests/timeit/33.tree.03.iterate.py
  23. 150
      tests/timeit/33.tree.04.listify.py

28
.vscode/launch.json vendored

@ -46,7 +46,8 @@
"console": "integratedTerminal",
"justMyCode": true,
"env": {
"TERMTK_GPM": "1"
"TERMTK_GPM": "1",
"PYTHONPATH": "./libs/pyTermTk"
},
},
{
@ -59,7 +60,10 @@
"args": [
"-p",
"tmp/test.input.001.bin"
]
],
"env": {
"PYTHONPATH": "./libs/pyTermTk"
}
},
{
"name": "py Debug: Module ttkDesigner",
@ -69,7 +73,7 @@
"console": "integratedTerminal",
"justMyCode": true,
"env": {
"PYTHONPATH": "./apps/ttkDesigner"
"PYTHONPATH": "./libs/pyTermTk:./apps/ttkDesigner"
}
},
{
@ -80,7 +84,7 @@
"console": "integratedTerminal",
"justMyCode": true,
"env": {
"PYTHONPATH": "./tools"
"PYTHONPATH": "./libs/pyTermTk:./tools"
},
"args": [
"ttkDesigner/tui/newWindow.tui.json"
@ -94,7 +98,7 @@
"console": "integratedTerminal",
"justMyCode": true,
"env": {
"PYTHONPATH": "./apps/dumbPaintTool"
"PYTHONPATH": "./libs/pyTermTk:./apps/dumbPaintTool"
}
},
{
@ -105,7 +109,7 @@
"console": "integratedTerminal",
"justMyCode": true,
"env": {
"PYTHONPATH": "./tools"
"PYTHONPATH": "./libs/pyTermTk:./tools"
},
"args": [
"experiments/untitled.DPT.json"
@ -130,7 +134,7 @@
"console": "integratedTerminal",
"justMyCode": true,
"env": {
"PYTHONPATH": "./apps/tlogg"
"PYTHONPATH": "./apps/tlogg:./libs/pyTermTk"
}
},
{
@ -139,7 +143,10 @@
"request": "launch",
"program": "demo/demo.py",
"console": "integratedTerminal",
"justMyCode": true
"justMyCode": true,
"env": {
"PYTHONPATH": "./libs/pyTermTk"
}
},
{
"name": "Python: Demo Mouse tracking",
@ -150,7 +157,10 @@
"justMyCode": true,
"args": [
"-t"
]
],
"env": {
"PYTHONPATH": "./libs/pyTermTk"
}
},
{
"name": "Python: sphinx",

1
TermTk

@ -1 +0,0 @@
libs/pyTermTk/TermTk

2
demo/showcase/filepicker.py

@ -24,7 +24,7 @@
import sys, os, argparse
sys.path.append(os.path.join(sys.path[0],'../..'))
sys.path.append(os.path.join(sys.path[0],'../../libs/pyTermTk'))
import TermTk as ttk

4
libs/pyTermTk/TermTk/TTkCore/constant.py

@ -262,7 +262,7 @@ class TTkConstant:
DontShowIndicator = ChildIndicatorPolicy.DontShowIndicator
DontShowIndicatorWhenChildless = ChildIndicatorPolicy.DontShowIndicatorWhenChildless
class SortOrder(int):
class SortOrder(IntEnum):
'''This enum describes how the items in a widget are sorted.
.. autosummary::
@ -418,7 +418,7 @@ class TTkConstant:
Input_Password = 0x04
# Alignment
class Alignment(int):
class Alignment(IntEnum):
''' This type is used to describe alignment.
.. autosummary::

4
libs/pyTermTk/TermTk/TTkCore/string.py

@ -119,7 +119,7 @@ class TTkString():
def __str__(self) -> str:
return self._text
def __add__(self, other:TTkString) -> TTkString:
def __add__(self, other:Union[TTkStringType,TTkColor]) -> TTkString:
ret = TTkString()
ret._baseColor = self._baseColor
if isinstance(other, TTkString):
@ -141,7 +141,7 @@ class TTkString():
ret._baseColor = other
return ret
def __radd__(self, other:TTkString) -> TTkString:
def __radd__(self, other:TTkStringType) -> TTkString:
ret = TTkString()
ret._baseColor = self._baseColor
if isinstance(other, TTkString):

11
libs/pyTermTk/TermTk/TTkLayouts/layout.py

@ -301,9 +301,18 @@ class TTkLayout(TTkLayoutItem):
'''removeItem'''
self.removeItems([item])
def clear(self) -> None:
'''clear'''
for item in self._items:
if item._layoutItemType == TTkK.WidgetItem:
item.widget().setParent(None)
item.setParent(None)
self._items = []
self._zSortItems()
def removeItems(self, items):
'''removeItems'''
for item in items:
for item in items.copy():
if item in self._items:
self._items.remove(item)
if item._layoutItemType == TTkK.WidgetItem:

12
libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/filetreewidget.py

@ -158,8 +158,6 @@ class TTkFileTreeWidget(TTkTreeWidget):
self.resizeColumnToContents(1)
self.resizeColumnToContents(2)
self.resizeColumnToContents(3)
self.itemExpanded.connect(self._folderExpanded)
self.itemCollapsed.connect(self._folderCollapsed)
self.itemExpanded.connect(self._updateChildren)
self.itemActivated.connect(self._activated)
@ -222,7 +220,6 @@ class TTkFileTreeWidget(TTkTreeWidget):
raw = [ n , -1 , typef , rawTime ],
path=nodePath,
type=TTkFileTreeWidgetItem.DIR,
icon=TTkString() + TTkCfg.theme.folderIconColor + TTkCfg.theme.fileIcon.folderClose + TTkColor.RST,
childIndicatorPolicy=TTkK.ShowIndicator))
elif os.path.isfile(nodePath) or os.path.islink(nodePath):
@ -252,18 +249,9 @@ class TTkFileTreeWidget(TTkTreeWidget):
raw = [ n , rawSize , typef , rawTime ],
path=nodePath,
type=TTkFileTreeWidgetItem.FILE,
icon=TTkString() + TTkCfg.theme.fileIconColor + TTkCfg.theme.fileIcon.getIcon(n) + TTkColor.RST,
childIndicatorPolicy=TTkK.DontShowIndicator))
return ret
@staticmethod
def _folderExpanded(item):
item.setIcon(0, TTkString() + TTkCfg.theme.folderIconColor + TTkCfg.theme.fileIcon.folderOpen + TTkColor.RST,)
@staticmethod
def _folderCollapsed(item):
item.setIcon(0, TTkString() + TTkCfg.theme.folderIconColor + TTkCfg.theme.fileIcon.folderClose + TTkColor.RST,)
@pyTTkSlot(TTkFileTreeWidgetItem)
def _updateChildren(self, item):
if item.children(): return

15
libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/filetreewidgetitem.py

@ -24,6 +24,8 @@ __all__ = ['TTkFileTreeWidgetItem']
import re
from TermTk.TTkCore.cfg import TTkCfg
from TermTk.TTkCore.string import TTkString
from TermTk.TTkCore.constant import TTkK
from TermTk.TTkWidgets.TTkModelView.treewidgetitem import TTkTreeWidgetItem
@ -44,7 +46,7 @@ class TTkFileTreeWidgetItem(TTkTreeWidgetItem):
self.setTextAlignment(1, TTkK.RIGHT_ALIGN)
def setFilter(self, filter:str) -> None:
for c in self._children:
for c in self.children():
c.dataChanged.disconnect(self.emitDataChanged)
c._processFilter(filter)
c.setFilter(filter)
@ -59,6 +61,17 @@ class TTkFileTreeWidgetItem(TTkTreeWidgetItem):
else:
self.setHidden(True)
def icon(self, col):
if col > 0:
return super().icon(col)
if self._type == TTkFileTreeWidgetItem.FILE:
return TTkString( ' '+TTkCfg.theme.fileIcon.getIcon(self._path)+' ', TTkCfg.theme.fileIconColor)
else:
if self._expanded:
return TTkString(' '+TTkCfg.theme.fileIcon.folderOpen+' ', TTkCfg.theme.folderIconColor)
else:
return TTkString(' '+TTkCfg.theme.fileIcon.folderClose+' ', TTkCfg.theme.folderIconColor)
def sortData(self, col:int):
return self._raw[col]

238
libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidget.py

@ -22,9 +22,10 @@
__all__ = ['TTkTreeWidget']
from typing import List
from typing import List,Tuple,Optional
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
@ -39,6 +40,59 @@ from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot
from dataclasses import dataclass
class _RootWidgetItem(TTkTreeWidgetItem):
__slots__ = ('_widgets_buffer','_widgets_buffer_check')
_widgets_buffer:List[int]
_widgets_buffer_check:int
def __init__(self):
self._widgets_buffer = []
self._widgets_buffer_check=0
super().__init__(expanded=True)
def _getColumnContentSize(self, column:int, offset:int) -> int:
if offset+0x200 > (_sz:=self.size()):
offset = _sz-0x200
if offset < 0x200:
offset = 0x200
limited_page = self._get_page_root(offset-0x200,0x400)
if column==0:
size = max(max(_l+_i.icon(column).termWidth()+_t.termWidth() for _t in _i.data(column).split('\n')) for _l,_y,_i in limited_page if not _y)
else:
size = max(max((_i.icon(column)+_t).termWidth() for _t in _i.data(column).split('\n')) for _l,_y,_i in limited_page if not _y)
return size-1
def _get_page_root(self, index:int, size:int) -> List[Tuple[int,int,TTkTreeWidgetItem]]:
if self._children:
if self._widgets_buffer_check >= len(self._children._buffer):
self._widgets_buffer = []
self._widgets_buffer_check=0
page = self._children.get_page(0, index, size)
if any(_wbi[1] != self._children._buffer[_wbi[0]] for _wbi in self._widgets_buffer):
self._widgets_buffer = []
self._widgets_buffer_check=0
if self._widgets_buffer_check < len(self._children._buffer):
for i,(_l,_y,_i) in enumerate(self._children._buffer[self._widgets_buffer_check:]):
if not _y and _i.hasWidgets():
self._widgets_buffer.append((_l,i,_i))
self._widgets_buffer_check = len(self._children._buffer)
return page
return []
def _item_at(self, pos:int) -> Optional[Tuple[int,int,TTkTreeWidgetItem]]:
if pos < 0 or not self._children:
return None
if page := self._children.get_page(0, pos, 1):
return page[0]
else:
return None
def size(self):
if self._children:
return self._children.size()
return 0
class TTkTreeWidget(TTkAbstractScrollView):
'''
The :py:class:`TTkTreeWidget` class is a convenience class that provides a standard tree
@ -153,12 +207,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,16 +230,8 @@ 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]
_rootItem:_RootWidgetItem
@dataclass(frozen=True)
class _DropTreeData:
@ -214,6 +262,8 @@ class TTkTreeWidget(TTkAbstractScrollView):
self._itemExpanded = pyTTkSignal(TTkTreeWidgetItem)
self._itemCollapsed = pyTTkSignal(TTkTreeWidgetItem)
self._cache = []
self._selectionMode = selectionMode
self._dndMode = dragDropMode
self._selected = []
@ -221,17 +271,19 @@ 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
self._rootItem = TTkTreeWidgetItem(expanded=True)
self._rootItem = _RootWidgetItem()
super().__init__(**kwargs)
self.setMinimumHeight(1)
self.setFocusPolicy(TTkK.ClickFocus)
self.clear()
self.setPadding(1,0,0,0)
self.viewChanged.connect(self._viewChangedHandler)
self._alignWidgets()
self.sizeChanged.connect(self._alignWidgets)
self._rootItem.dataChanged.connect(self._refreshCache)
@pyTTkSlot()
def _viewChangedHandler(self) -> None:
@ -241,46 +293,36 @@ class TTkTreeWidget(TTkAbstractScrollView):
# Overridden function
def viewFullAreaSize(self) -> tuple[int, int]:
w = self._columnsPos[-1]+1 if self._columnsPos else 0
h = self._rootItem.size()
h = self._rootItem.size()+1
# TTkLog.debug(f"{w=} {h=}")
return w,h
def clear(self) -> None:
'''clear'''
# Remove all the widgets
for ri in self._rootItem.children():
ri.setTreeItemParent(None)
if self._rootItem:
self._rootItem.dataChanged.disconnect(self._refreshCache)
self._rootItem = TTkTreeWidgetItem(expanded=True)
self._rootItem = _RootWidgetItem()
self._rootItem.dataChanged.connect(self._refreshCache)
self.sortItems(self._sortColumn, self._sortOrder)
self._refreshCache()
self.viewChanged.emit()
self.update()
def addTopLevelItem(self, item:TTkTreeWidgetItem) -> None:
'''addTopLevelItem'''
self._rootItem.addChild(item)
item.setTreeItemParent(self)
self._refreshCache()
self.viewChanged.emit()
self.update()
def addTopLevelItems(self, items:TTkTreeWidgetItem) -> None:
'''addTopLevelItems'''
self._rootItem.addChildren(items)
self._rootItem.setTreeItemParent(self)
#for item in items:
# item.setTreeItemParent(self)
self._refreshCache()
self.viewChanged.emit()
self.update()
def takeTopLevelItem(self, index) -> None:
'''takeTopLevelItem'''
self._rootItem.takeChild(index)
self._refreshCache()
self.viewChanged.emit()
self.update()
@ -341,7 +383,10 @@ class TTkTreeWidget(TTkAbstractScrollView):
if not self._sortingEnabled: return
self._sortColumn = col
self._sortOrder = order
self._rootItem.dataChanged.disconnect(self._refreshCache)
self._rootItem.sortChildren(col, order)
self._rootItem.dataChanged.connect(self._refreshCache)
self._refreshCache()
def columnWidth(self, column:int) -> int:
'''columnWidth'''
@ -363,9 +408,8 @@ class TTkTreeWidget(TTkAbstractScrollView):
def resizeColumnToContents(self, column:int) -> None:
'''resizeColumnToContents'''
if not self._cache:
return
contentSize = max(row.data[column].termWidth() for row in self._cache)
_,oy = self.getViewOffsets()
contentSize = self._rootItem._getColumnContentSize(column, oy)
self.setColumnWidth(column, contentSize)
@pyTTkSlot()
@ -376,7 +420,6 @@ class TTkTreeWidget(TTkAbstractScrollView):
self._rootItem.dataChanged.disconnect(self._refreshCache)
self._rootItem.expandAll()
self._rootItem.dataChanged.connect(self._refreshCache)
self._refreshCache()
@pyTTkSlot()
def collapseAll(self) -> None:
@ -386,7 +429,6 @@ class TTkTreeWidget(TTkAbstractScrollView):
self._rootItem.dataChanged.disconnect(self._refreshCache)
self._rootItem.collapseAll()
self._rootItem.dataChanged.connect(self._refreshCache)
self._refreshCache()
def mouseDoubleClickEvent(self, evt:TTkMouseEvent) -> bool:
x,y = evt.x, evt.y
@ -403,8 +445,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):
_,_,_i = _item_at
item = _i
if item.childIndicatorPolicy() == TTkK.DontShowIndicatorWhenChildless and item.children() or \
item.childIndicatorPolicy() == TTkK.ShowIndicator:
item.setExpanded(not item.isExpanded())
@ -424,7 +467,6 @@ class TTkTreeWidget(TTkAbstractScrollView):
break
self.itemDoubleClicked.emit(item, col)
self.itemActivated.emit(item, col)
self.update()
return True
@ -453,13 +495,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):
_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)
@ -487,7 +530,7 @@ class TTkTreeWidget(TTkAbstractScrollView):
break
self.itemClicked.emit(item, col)
self.update()
return True
return True
def mouseDragEvent(self, evt:TTkMouseEvent) -> bool:
# columnPos (Selected = 2)
@ -534,78 +577,44 @@ class TTkTreeWidget(TTkAbstractScrollView):
return True
return False
@pyTTkSlot()
def _alignWidgets(self) -> None:
for y,c in enumerate(self._cache):
if not c.firstLine:
continue
for i,w in enumerate(c.widgets):
if w:
_pos = self._columnsPos[i-1]+1 if i else 3 + c.level*2
_width = self._columnsPos[i] - _pos
_height = w.height()
w.setGeometry(_pos,y,_width,_height)
w.show()
self.layout().clear()
ox, oy = self.getViewOffsets()
w,h = self.size()
self._rootItem._get_page_root(0,oy+h)
if not self._rootItem._widgets_buffer:
self.update()
return
wids = []
for _l,_y,_i in self._rootItem._widgets_buffer:
for _il in range(len(self._header)):
if _wid:=_i.widget(_il):
_pos = self._columnsPos[_il-1]+1 if _il else 3 + _l*2
_width = self._columnsPos[_il] - _pos
_height = _wid.height()
_wid.setGeometry(_pos,_y,_width,_height)
_wid.show()
wids.append(_wid)
if wids:
self.layout().addWidgets(wids)
@pyTTkSlot()
def _refreshCache(self) -> None:
# 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
#
# _cache is an array of TTkTreeWidget._Cache:
# [ item, level, data=[txtCol1, txtCol2, txtCol3, ... ]]
self._cache = []
def _addToCache(_child, _level:int) -> None:
_data = []
_widgets = []
_h =_child.height()
for _il in range(len(self._header)):
_lines = _child.data(_il).split('\n')
if _il==0:
_data0 = []
for _id in range(_h):
# Trying to define an icon to obtain this results on multiline field
# ▶ Label
# ┊ NewLine 1
# │ NewLine 2
# ╽
if _id == 0:
_icon = " "+_child.icon(_il)+" "
elif _id == _h-1:
_icon = TTkString("", TTkColor.fg("#666666"))
elif _id == 1:
_icon = TTkString("", TTkColor.fg("#666666"))
else:
_icon = TTkString("", TTkColor.fg("#666666"))
_text = _lines[_id] if _id<len(_lines) else ""
_data0.append(' '*_level+_icon+_text)
_data.append(_data0)
_widgets.append(_child.widget(_il))
else:
_data.append([TTkString(s) for s in _lines]+[TTkString()]*(_h-len(_lines)))
_widgets.append(_child.widget(_il))
for _id in range(_h):
self._cache.append(TTkTreeWidget._Cache(
item = _child,
level = _level,
data = [ dt[_id] for dt in _data],
widgets = _widgets,
firstLine=_id==0))
if _child.isExpanded():
for _c in _child.children():
_addToCache(_c, _level+1)
for c in self._rootItem.children():
_addToCache(c,0)
self._alignWidgets()
self.update()
self.viewChanged.emit()
return
def paintEvent(self, canvas) -> None:
style = self.currentStyle()
color= style['color']
lineColor= style['lineColor']
lineHeightColor= style['lineHeightColor']
headerColor= style['headerColor']
selectedColor= style['selectedColor']
separatorColor= style['separatorColor']
@ -628,16 +637,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_root(y,h)):
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)

523
libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidgetitem.py

@ -20,20 +20,209 @@
# 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, ClassVar
from TermTk.TTkCore.cfg import TTkCfg
from TermTk.TTkCore.constant import TTkK
from TermTk.TTkCore.string import TTkString
from TermTk.TTkCore.string import TTkString, TTkStringType
from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal
from TermTk.TTkWidgets import TTkWidget
from TermTk.TTkAbstract.abstractitemmodel import TTkAbstractItemModel
@dataclass
class _TTkTreeChildren(TTkAbstractItemModel):
__slots__ = (
'_parent',
'_level',
'_total_size',
'_buffer','_buffer_link',
'_children',
'_childrenSizeChanged')
_parent:TTkTreeWidgetItem
_level:int
_total_size:int
_buffer:List[Tuple[int,int,TTkTreeWidgetItem]]
_buffer_link:List[Tuple[int,int]]
_children: List[TTkTreeWidgetItem]
def __init__(self, parent:TTkTreeWidgetItem):
self._parent = parent
self._childrenSizeChanged = pyTTkSignal(TTkTreeWidgetItem,int)
self._level = 0
self._total_size = 0
self._buffer = []
self._buffer_link = []
self._children = []
super().__init__()
def _childrenSizeChangedHandler(self, item:Optional[TTkTreeWidgetItem], diffSize:int) -> None:
if item:
self.clearBufferFromIndex(self._children.index(item))
else:
self.clearBuffer()
self._total_size += diffSize
self._childrenSizeChanged.emit(self, diffSize)
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:] = []
self._buffer_link[index] = (link,0)
def get_page(self, level:int, index:int, size:int) -> List[Tuple[int,int,TTkTreeWidgetItem]]:
# Add the item to the buffer
if self._level != level:
self.clearBuffer()
self._level = level
if not self._buffer:
self._buffer_link = [(0, 0)]
final_index = index+size
buffered_size = len(self._buffer)
while buffered_size < final_index:
last_index = len(self._buffer_link)-1
if len(self._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 = self._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, 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]
@pyTTkSlot()
def emitDataChanged(self):
self.dataChanged.emit()
def _addChild(self, parent:TTkTreeWidgetItem, child:TTkTreeWidgetItem):
self._children.append(child)
child._parent = self._parent
child._sortOrder = self._parent._sortOrder
child._sortColumn = self._parent._sortColumn
child.dataChanged.connect(self.emitDataChanged)
child._sizeChanged.connect(self._childrenSizeChangedHandler)
def addChild(self, parent:TTkTreeWidgetItem, child:TTkTreeWidgetItem):
self._addChild(parent, child)
self._childrenSizeChangedHandler(child, child.size())
self.sort()
self.emitDataChanged()
def addChildren(self, parent:TTkTreeWidgetItem, children:List[TTkTreeWidgetItem]):
if children:
for child in children:
self._addChild(parent, child)
sizes = sum(_c.size() for _c in children)
self._childrenSizeChangedHandler(children[0], sizes)
self.sort()
self.emitDataChanged()
def removeChild(self, child:TTkTreeWidgetItem) -> None:
if child in self._children:
self.takeChild(self._children.index(child))
def takeChild(self, index) -> Optional[TTkTreeWidgetItem]:
if not ( self._children and
0<= index < len(self._children) ):
return None
child = self._children.pop(index)
child.dataChanged.disconnect(self.emitDataChanged)
child._sizeChanged.disconnect(self._childrenSizeChangedHandler)
self._childrenSizeChangedHandler(None, -child.size())
self.emitDataChanged()
return child
def takeChildren(self) -> List[TTkTreeWidgetItem]:
children = self._children
for child in children:
child.dataChanged.disconnect(self.emitDataChanged)
child._sizeChanged.disconnect(self._childrenSizeChangedHandler)
self._childrenSizeChangedHandler(None, -self._total_size)
self.emitDataChanged()
return children
def child(self, index:int) -> Optional[TTkTreeWidgetItem]:
if 0 <= index < len(self._children):
return self._children[index]
return None
def children(self) -> List[TTkTreeWidgetItem]:
return self._children
def indexOfChild(self, child:TTkTreeWidgetItem) -> Optional[int]:
if child in self._children:
return self._children.index(child)
return None
def expandAll(self) -> None:
for child in self._children:
child.setExpanded(True)
child.expandAll()
def collapseAll(self) -> None:
for child in self._children:
child.setExpanded(False)
child.collapseAll()
def sort(self):
if self._parent._sortColumn == -1: return
self._children = sorted(
self._children,
key = lambda _i : _i.data(self._parent._sortColumn),
reverse = self._parent._sortOrder == TTkK.DescendingOrder)
for c in self._children:
c.dataChanged.disconnect(self.emitDataChanged)
c._sizeChanged.disconnect(self._childrenSizeChangedHandler)
c.sortChildren(self._parent._sortColumn, self._parent._sortOrder)
c._sizeChanged.connect(self._childrenSizeChangedHandler)
c.dataChanged.connect(self.emitDataChanged)
self.clearBuffer()
self.emitDataChanged()
def size(self):
if not self._total_size:
self._total_size = sum(_c.size() for _c in self._children)
return self._total_size
class TTkTreeWidgetItem(TTkAbstractItemModel):
'''
The :py:class:`TTkTreeWidgetItem` class provides an item for use with the :py:class:'TTkTree' convenience class.
@ -60,32 +249,45 @@ class TTkTreeWidgetItem(TTkAbstractItemModel):
'''
__slots__ = ('_parent', '_data', '_widgets', '_height', '_alignment', '_children', '_expanded', '_selected', '_hidden',
'_childIndicatorPolicy', '_icon', '_defaultIcon',
'_sortColumn', '_sortOrder', '_hasWidgets', '_parentWidget',
__slots__ = (
'_parent', '_data', '_widgets', '_height', '_alignment',
'_children', '_expanded', '_selected', '_hidden',
'_childIndicatorPolicy', '_icon', '_defaultIcon',
'_sortColumn', '_sortOrder', '_hasWidgets',
'_buffer', '_level',
# Signals
# 'refreshData'
'heightChanged'
'heightChanged', '_sizeChanged',
)
_icon:List[TTkString]
_alignment:List[TTkK.Alignment]
_sortOrder:TTkK.SortOrder
_buffer:List[Tuple[int,int,TTkTreeWidgetItem]]
_children:Optional[_TTkTreeChildren]
_childIndicatorPolicy:TTkK.ChildIndicatorPolicy
def __init__(self, *args,
parent:Self=None,
parent:Optional[TTkTreeWidgetItem]=None,
expanded:bool=False,
selected:bool=False,
hidden:bool=False,
icon:TTkString=None,
icon:TTkStringType='',
childIndicatorPolicy:TTkK.ChildIndicatorPolicy =TTkK.ChildIndicatorPolicy.DontShowIndicatorWhenChildless,
**kwargs) -> None:
# Signals
# self.refreshData = pyTTkSignal(TTkTreeWidgetItem)
self.heightChanged = pyTTkSignal(int)
self._sizeChanged = pyTTkSignal(TTkTreeWidgetItem,int)
self._children = None
self._buffer = []
self._level = 0
self._hasWidgets = False
self._children = []
self._parentWidget = None
self._height = 1
data = args[0] if len(args)>0 and type(args[0])==list else [TTkString()]
# self._data = [i if issubclass(type(i), TTkString) else TTkString(i) if isinstance(i,str) else TTkString() for i in data]
self._parent = None
self._parent = parent
self._childIndicatorPolicy = childIndicatorPolicy
self._defaultIcon = True
self._expanded = expanded
@ -98,21 +300,23 @@ class TTkTreeWidgetItem(TTkAbstractItemModel):
self._data, self._widgets = self._processDataInput(data)
self._alignment = [TTkK.LEFT_ALIGN]*len(self._data)
self._icon = ['']*len(self._data)
self._icon = [TTkString()]*len(self._data)
self._setDefaultIcon()
if icon:
self._icon[0] = icon
self._icon[0] = ' '+TTkString(icon)+TTkString(' ')
self._defaultIcon = False
if parent:
parent.addChild(self)
def _processDataInputWidget(self, widget, index):
def _sizeChangedHandler(self, item:TTkTreeWidgetItem, diffSize:int) -> None:
if self._expanded or item==self:
self._sizeChanged.emit(self, diffSize)
def _processDataInputWidget(self, widget:TTkWidget, index:int) -> TTkString:
self._hasWidgets = True
widget.hide()
widget.sizeChanged.connect(self._widgetSizeChanged)
self._height = max(self._height,widget.height())
if self._parentWidget:
widget.setTreeItemParent(self._parentWidget)
if hasattr(widget, 'text'):
ret = widget.text()
if hasattr(widget,'textChanged'):
@ -145,235 +349,218 @@ class TTkTreeWidgetItem(TTkAbstractItemModel):
def _setDefaultIcon(self):
if not self._defaultIcon: return
self._icon[0] = TTkCfg.theme.tree[0]
if self._childIndicatorPolicy == TTkK.DontShowIndicatorWhenChildless and self._children or \
self._childIndicatorPolicy == TTkK.ShowIndicator:
self._icon[0] = TTkString(' '+TTkCfg.theme.tree[0]+' ')
if ( self._childIndicatorPolicy == TTkK.DontShowIndicatorWhenChildless and
self._children and self._children._children or
self._childIndicatorPolicy == TTkK.ShowIndicator ):
if self._expanded:
self._icon[0] = TTkCfg.theme.tree[2]
self._icon[0] = TTkString(' '+TTkCfg.theme.tree[2]+' ')
else:
self._icon[0] = TTkCfg.theme.tree[1]
self._icon[0] = TTkString(' '+TTkCfg.theme.tree[1]+' ')
@pyTTkSlot(int, int)
def _widgetSizeChanged(self, _, h):
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._buffer = []
self.heightChanged.emit(h)
if self._parentWidget:
self._parentWidget._refreshCache()
self._sizeChangedHandler(self,diffSize)
def height(self):
return self._height
def _clearTreeItemParent(self):
widgets = []
if self._hasWidgets:
widgets += [w for w in self._widgets if w and w.parentWidget()]
# for widget in widgets:
# if pw := widget.parentWidget():
# pw.rootLayout().removeWidgets([w for w in self._widgets if w])
if self._parentWidget:
self._parentWidget.rootLayout().removeWidgets(widgets)
self._parentWidget = None
for c in self._children:
widgets += c._clearTreeItemParent()
return widgets
def _setTreeItemParent(self, parent):
self._parentWidget = parent
widgets = []
if self._hasWidgets:
widgets += [w for w in self._widgets if w]
# parent.layout().addWidgets(widgets)
for c in self._children:
widgets += c._setTreeItemParent(parent)
return widgets
def setTreeItemParent(self, parent):
if parent:
widgets = self._setTreeItemParent(parent)
parent.layout().addWidgets(widgets)
else:
# pw = self._parentWidget
widgets = self._clearTreeItemParent()
# pw.rootLayout().removeWidgets(widgets)
return 0 if self._hidden else self._height
def _get_page(self, level:int, index:int, size:int) -> List[Tuple[int,int,TTkTreeWidgetItem]]:
if self._hidden:
return []
_h = self._height
_to = index+size
if self._level != level:
self._buffer=[]
self._level = level
if not self._buffer:
self._buffer = [(level, _y, self) for _y in range(_h)]
# The page is among the children
if self._expanded and self._children and index >= _h :
return self._children.get_page(level+1, index-_h, size)
elif _to <= _h: # The page is in this node
return self._buffer[index:_to]
elif index < _h and self._expanded and self._children: # the page include the current item and the children
return self._buffer[index:] + self._children.get_page(level+1, 0, size+index-_h)
return self._buffer[index:]
def hasWidgets(self):
return self._hasWidgets
def isHidden(self):
def isHidden(self) -> bool:
return self._hidden
def setHidden(self, hide):
if hide == self._hidden: return
def setHidden(self, hide:bool) -> None:
if hide == self._hidden:
return
self._hidden = hide
self.dataChanged.emit()
if hide:
self._sizeChangedHandler(self,-self._height)
else:
self._sizeChangedHandler(self,self._height)
self.emitDataChanged()
def childIndicatorPolicy(self):
def childIndicatorPolicy(self) -> TTkK.ChildIndicatorPolicy:
return self._childIndicatorPolicy
def setChildIndicatorPolicy(self, policy):
def setChildIndicatorPolicy(self, policy:TTkK.ChildIndicatorPolicy) -> None:
self._childIndicatorPolicy = policy
self._setDefaultIcon()
def _addChild(self, child):
self._children.append(child)
child._parent = self
child._sortOrder = self._sortOrder
child._sortColumn = self._sortColumn
self._setDefaultIcon()
self._sort(children=False)
if self._parentWidget:
child.setTreeItemParent(self._parentWidget)
child.dataChanged.connect(self.emitDataChanged)
def addChild(self, child):
self._addChild(child)
self.dataChanged.emit()
def addChild(self, child:TTkTreeWidgetItem) -> None:
if not self._children:
self._children = _TTkTreeChildren(self)
self._children._childrenSizeChanged.connect(self._sizeChangedHandler)
self._children.dataChanged.connect(self.emitDataChanged)
child = self._children.addChild(self, child)
self._setDefaultIcon()
def addChildren(self, children):
for child in children:
self._addChild(child)
self.dataChanged.emit()
def addChildren(self, children:List[TTkTreeWidgetItem]) -> None:
if not self._children:
self._children = _TTkTreeChildren(self)
self._children._childrenSizeChanged.connect(self._sizeChangedHandler)
self._children.dataChanged.connect(self.emitDataChanged)
children = self._children.addChildren(self, children)
self._setDefaultIcon()
def removeChild(self, child):
if child in self._children:
self.takeChild(self._children.index(child))
def removeChild(self, child:TTkTreeWidgetItem) -> None:
if not self._children:
return
self._children.removeChild(child)
if not self._children.size():
self._children.dataChanged.disconnect(self.emitDataChanged)
self._children._childrenSizeChanged.disconnect(self._sizeChangedHandler)
self._children = None
self._setDefaultIcon()
def takeChild(self, index):
if not (self._children and 0<= index < len(self._children)):
def takeChild(self, index:int) -> Optional[TTkTreeWidgetItem]:
if not self._children:
return None
child = self._children.pop(index)
child.dataChanged.disconnect(self.emitDataChanged)
child.setTreeItemParent(None)
self.dataChanged.emit()
child = self._children.takeChild(index)
if not self._children.size():
self._children.dataChanged.disconnect(self.emitDataChanged)
self._children._childrenSizeChanged.disconnect(self._sizeChangedHandler)
self._children = None
self._setDefaultIcon()
return child
def takeChildren(self):
children = self._children
for child in children:
child.dataChanged.disconnect(self.emitDataChanged)
child.setTreeItemParent(None)
self._children = []
self.dataChanged.emit()
def takeChildren(self) -> List[TTkTreeWidgetItem]:
if not self._children:
return []
children = self._children.takeChildren()
self._children.dataChanged.disconnect(self.emitDataChanged)
self._children._childrenSizeChanged.disconnect(self._sizeChangedHandler)
self._children = None
self._setDefaultIcon()
return children
def child(self, index:int) -> Optional[TTkTreeWidgetItem]:
if not self._children:
return None
return self._children.child(index)
def child(self, index):
if 0 <= index < len(self._children):
return self._children[index]
return None
def children(self):
return [x for x in self._children if not x.isHidden()]
def children(self) -> List[TTkTreeWidgetItem]:
if not self._children:
return []
return self._children.children()
def indexOfChild(self, child):
if child in self._children:
return self._children.index(child)
return None
def indexOfChild(self, child:TTkTreeWidgetItem) -> Optional[int]:
if not self._children:
return None
return self._children.indexOfChild(child)
def icon(self, col):
def icon(self, col:int) -> TTkString:
if col >= len(self._icon):
return ''
return TTkString()
return self._icon[col]
def setIcon(self, col, icon):
def setIcon(self, col:int, icon:TTkStringType) -> None:
if col==0:
self._defaultIcon = False
self._icon[col] = icon
if isinstance(icon,str):
self._icon[col] = TTkString(' '+icon+' ')
else:
self._icon[col] = ' '+icon+TTkString(' ')
self.dataChanged.emit()
def textAlignment(self, col):
def textAlignment(self, col:int) -> TTkK.Alignment:
if col >= len(self._alignment):
return TTkK.LEFT_ALIGN
return self._alignment[col]
def setTextAlignment(self, col, alignment):
def setTextAlignment(self, col:int, alignment:TTkK.Alignment) -> None:
self._alignment[col] = alignment
self.dataChanged.emit()
def data(self, col, role=None):
def data(self, col:int, role:Any=None) -> TTkString:
if col >= len(self._data):
return ''
return TTkString()
return self._data[col]
def widget(self, col, role=None):
def widget(self, col:int, role:Any=None) -> Optional[TTkWidget]:
if col >= len(self._data):
return None
return self._widgets[col]
def expandAll(self) -> None:
for child in self._children:
child.setExpanded(True)
child.expandAll()
if self._children:
self._children.expandAll()
def collapseAll(self) -> None:
for child in self._children:
child.setExpanded(False)
child.collapseAll()
def sortData(self, col):
return self.data(col)
def _sort(self, children):
if self._sortColumn == -1: return
self._children = sorted(
self._children,
key = lambda x : x.sortData(self._sortColumn),
reverse = self._sortOrder == TTkK.DescendingOrder)
# Broadcast the sorting to the children
if children:
for c in self._children:
c.dataChanged.disconnect(self.emitDataChanged)
c.sortChildren(self._sortColumn, self._sortOrder)
c.dataChanged.connect(self.emitDataChanged)
if self._children:
self._children.collapseAll()
def sortChildren(self, col, order):
def sortChildren(self, col:int, order:TTkK.SortOrder) -> None:
self._sortColumn = col
self._sortOrder = order
if not self._children: return
self._sort(children=True)
self.dataChanged.emit()
if not self._children:
return
self._children.sort()
@pyTTkSlot()
def emitDataChanged(self):
def emitDataChanged(self) -> None:
self.dataChanged.emit()
# def setDisabled(disabled):
# pass
def setExpanded(self, expand):
# hide all the widgets if this item is not expanded
if not expand:
def _recurseHide(item):
for c in item._children:
if c._hasWidgets:
for widget in [w for w in c._widgets if w]:
widget.hide()
if c._expanded:
_recurseHide(c)
_recurseHide(self)
def setExpanded(self, expand:bool) -> None:
if self._expanded != expand and self._children:
if expand:
self._sizeChangedHandler(self, self._children.size())
else:
self._sizeChangedHandler(self, -self._children.size())
self._expanded = expand
self._setDefaultIcon()
self.emitDataChanged()
self.dataChanged.emit()
def setSelected(self, select):
def setSelected(self, select:bool) -> None:
self._selected = select
# def isDisabled():
# pass
def isExpanded(self):
def isExpanded(self) -> bool:
return self._expanded
def isSelected(self):
def isSelected(self) -> bool:
return self._selected
def size(self):
if self._expanded:
return self._height + sum(c.size() for c in self.children())
else:
return self._height
def size(self) -> int:
if self._hidden:
return 0
if ( self._expanded and
self._children ):
return self._height + self._children.size()
return self._height

2
tests/pytest/run_test_001_demo.py

@ -28,7 +28,7 @@ import pickle
import threading
sys.path.append(os.path.join(sys.path[0],'../../demo'))
sys.path.append(os.path.join(sys.path[0],'../..'))
sys.path.append(os.path.join(sys.path[0],'../../libs/pyTermTk'))
import demo

2
tests/pytest/run_test_002_textedit.py

@ -27,7 +27,7 @@ import queue
import pickle
import threading
sys.path.append(os.path.join(sys.path[0],'../..'))
sys.path.append(os.path.join(sys.path[0],'../../libs/pyTermTk'))
import TermTk as ttk

2
tests/pytest/test_003_string.py

@ -23,7 +23,7 @@
import sys, os
sys.path.append(os.path.join(sys.path[0],'../..'))
sys.path.append(os.path.join(sys.path[0],'../../libs/pyTermTk'))
import TermTk

472
tests/pytest/test_005_tree.py

@ -0,0 +1,472 @@
# MIT License
#
# Copyright (c) 2025 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.
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}"+'\nabc'*i, f"{prefix} B {i}", f"{prefix} C {i}"])
ret.append(_c)
if i%2:
_c.setExpanded(True)
if nesting:
_c.addChildren(_gen_childs(num,f"{prefix}_X",nesting-1))
return ret
def _add_children(item:ttk.TTkTreeWidgetItem):
item.addChildren(_gen_childs(4,'l2',2))
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)} h:{item.height()} {item.data(1)} {item.data(2)}".replace('\n','_\\n_')
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_node_size(item:ttk.TTkTreeWidgetItem):
size = item.height()
if item.isExpanded():
for _ch in item.children():
size += _get_node_size(_ch)
return size
def _test_size_ch(ch:ttk.TTkTreeWidgetItem):
print(_format_item(ch))
assert _get_node_size(ch) == ch.size() , _format_item(ch)
# Test first the children and the parent
def _loop_1(item:ttk.TTkTreeWidgetItem):
if item.isExpanded():
for _ch in item.children():
_loop_1(_ch)
_test_size_ch(item)
# Test first the parent and the children
def _loop_2(item:ttk.TTkTreeWidgetItem):
_test_size_ch(item)
if item.isExpanded():
for _ch in item.children():
_loop_2(_ch)
def test_tree_sizes_simple_1():
l0 = ttk.TTkTreeWidgetItem(["l0 XX0","XX0","XX0"], expanded=True)
l1 = ttk.TTkTreeWidgetItem(["l1 String A", "String B", "String C"], expanded=True)
l2 = ttk.TTkTreeWidgetItem(["l2 String AA", "String BB", "String CC"], expanded=True)
l3 = ttk.TTkTreeWidgetItem(["l3 String AAA\nAAA\nAAA", "String BBB", "String CCC"], expanded=False)
l4 = ttk.TTkTreeWidgetItem(["l4 String AAAA", "String BBBB", "String CCCC"], expanded=True)
l5 = ttk.TTkTreeWidgetItem(["l5 String AAAAA", "String BBBBB\nB\nB", "String CCCCC"], expanded=True)
l0.addChildren([l1,l2,l4])
l1.addChild(l3)
l3.addChild(l5)
_loop_1(l0)
def test_tree_sizes_simple_2():
l0 = ttk.TTkTreeWidgetItem(["XX0","XX0","XX0"], expanded=False)
l1 = ttk.TTkTreeWidgetItem(["String A", "String B", "String C"], expanded=False)
l2 = ttk.TTkTreeWidgetItem(["String AA", "String BB", "String CC"], expanded=False)
l0.addChild(l1)
l0.setExpanded(True)
l1.setExpanded(True)
l2.setExpanded(True)
l1.addChild(l2)
assert _get_node_size(l0) == l0.size() , _format_item(l0)
def test_tree_sizes_simple_3():
l0 = ttk.TTkTreeWidgetItem(["XX0","XX0","XX0"], expanded=True)
l1 = ttk.TTkTreeWidgetItem(["String A", "String B", "String C"], expanded=True)
l3 = ttk.TTkTreeWidgetItem(["String AAA\nAAA\nAAA", "String BBB", "String CCC"], expanded=False)
l5 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB\nB\nB", "String CCCCC"], expanded=True)
l0.addChild(l1)
l1.addChild(l3)
l3.addChild(l5)
_loop_1(l0)
def test_tree_sizes_simple_4_expanding():
l0 = ttk.TTkTreeWidgetItem(["XX0","XX0","XX0"], expanded=True)
l1 = ttk.TTkTreeWidgetItem(["String A", "String B", "String C"], expanded=True)
l2 = ttk.TTkTreeWidgetItem(["String AA", "String BB", "String CC"], expanded=True)
l3 = ttk.TTkTreeWidgetItem(["String AAA\nAAA\nAAA", "String BBB", "String CCC"], expanded=False)
l4 = ttk.TTkTreeWidgetItem(["String AAAA", "String BBBB", "String CCCC"], expanded=True)
l5 = ttk.TTkTreeWidgetItem(["String AAAAA", "String BBBBB\nB\nB", "String CCCCC"], expanded=True)
l0.addChildren([l1,l2,l4])
l1.addChild(l3)
l3.addChild(l5)
def _expand_test(item:ttk.TTkTreeWidgetItem):
item.setExpanded(True)
_loop_1(item)
item.setExpanded(False)
_loop_1(item)
item.setExpanded(True)
_loop_1(item)
for ch in item.children():
ch.setExpanded(True)
_loop_1(item)
ch.setExpanded(False)
_loop_1(item)
ch.setExpanded(True)
_loop_1(item)
ch.collapseAll()
_loop_1(item)
ch.expandAll()
_loop_1(item)
item.collapseAll()
_loop_1(item)
item.expandAll()
_loop_1(item)
_expand_test(l0)
_expand_test(l1)
_expand_test(l2)
_expand_test(l3)
_expand_test(l4)
_expand_test(l5)
def test_tree_sizes_widgets_1():
btn_1 = ttk.TTkButton(text='Test B 1', border=True, height=5)
btn_2 = ttk.TTkButton(text='Test B 2', border=True, height=5)
btn_3 = ttk.TTkButton(text='Test B 3', border=True, height=5)
l0 = ttk.TTkTreeWidgetItem(["l0 XX0","XX0","XX0"], expanded=True)
l1 = ttk.TTkTreeWidgetItem(["l1 String A", "String B", "String C"], expanded=True)
l2 = ttk.TTkTreeWidgetItem(["l2 String AA", "String BB", "String CC"], expanded=True)
l3 = ttk.TTkTreeWidgetItem([btn_1, "l3 String BBB", "String CCC"], expanded=False)
l4 = ttk.TTkTreeWidgetItem(["l4 String AAAA", "String BBBB", btn_3], expanded=True)
l5 = ttk.TTkTreeWidgetItem(["l5 String AAAAA", btn_2, "String CCCCC"], expanded=True)
l0.addChildren([l1,l2,l4])
l1.addChild(l3)
l3.addChild(l5)
print('\nTree:')
_print_tree(l0)
_loop_1(l0)
assert l0.size() == 13
btn_1.resize(10,10)
assert l0.size() == 18
btn_2.resize(10,10)
assert l0.size() == 18
btn_3.resize(10,10)
assert l0.size() == 23
def test_tree_sizes_complex():
tree,c1,c2 = _create_tree()
_loop_1(tree)
tree,c1,c2 = _create_tree()
_loop_2(tree)
def test_tree_sizes_adding_nodes():
tree,c1,c2 = _create_tree()
_loop_1(tree)
_add_children(c1)
_loop_1(tree)
_add_children(c2)
_loop_1(tree)
tree,c1,c2 = _create_tree()
_loop_2(tree)
_add_children(c1)
_loop_1(tree)
_add_children(c2)
_loop_1(tree)
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 _,_,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))
def test_tree_sort():
tree,c1,c2 = _create_tree()
def _test():
full_page = _get_full_tree_page(tree)
page = tree._get_page(0,0,10000)
assert (
[f"{c.isExpanded()} {c.data(0)}" for c in full_page] ==
[f"{c.isExpanded()} {c.data(0)}" for _,_,c in page] )
_test()
tree.sortChildren(0,ttk.TTkK.AscendingOrder)
_test()
tree.sortChildren(0,ttk.TTkK.DescendingOrder)
_test()
tree.sortChildren(1,ttk.TTkK.AscendingOrder)
_test()
tree.sortChildren(1,ttk.TTkK.DescendingOrder)
_test()
tree.sortChildren(2,ttk.TTkK.AscendingOrder)
_test()
tree.sortChildren(2,ttk.TTkK.DescendingOrder)
_test()
def test_tree_take_child():
tree,c1,c2 = _create_tree()
print('\nTree:')
_print_tree(tree)
def _test_page():
full_page = _get_full_tree_page(tree)
page = tree._get_page(0,0,1000)
assert len(full_page) == tree.size() == len(page)
assert (
[f"{c.isExpanded()} {c.data(0)}" for c in full_page] ==
[f"{c.isExpanded()} {c.data(0)}" for _,_,c in page] )
print(f"Testing: size={len(page)},{tree.size()}")
_test_page()
c2.takeChild(1)
_test_page()
c1.takeChildren()
_test_page()
def test_tree_show_hide():
l0 = ttk.TTkTreeWidgetItem(["l0", "l0", "l0", "l0", "l0"],expanded=True)
l1 = ttk.TTkTreeWidgetItem(["l1", "l1", "l1", "l1", "l1"],expanded=True)
l2 = ttk.TTkTreeWidgetItem(["l2", "l2", "l2", "l2", "l2"],expanded=True)
l3 = ttk.TTkTreeWidgetItem(["l3", "l3", "l3", "l3", "l3"],expanded=True)
l4 = ttk.TTkTreeWidgetItem(["l4", "l4", "l4", "l4", "l4"],expanded=True)
l5 = ttk.TTkTreeWidgetItem(["l5", "l5", "l5", "l5", "l5"],expanded=True)
l0.addChildren([l1,l2,l4])
l1.addChild(l3)
l3.addChild(l5)
print(l0.size())
l5.setHidden(True)
print(l0.size())
l4.setHidden(True)
print(l0.size())
l5.setHidden(False)
print(l0.size())
l4.setHidden(False)
print(l0.size())

2
tests/t.ui/test.ui.011.tree.02.widgets.py

@ -26,7 +26,7 @@ import os
import sys
import argparse
sys.path.append(os.path.join(sys.path[0],'../..'))
sys.path.append(os.path.join(sys.path[0],'../../libs/pyTermTk'))
import TermTk as ttk
ttk.TTkLog.use_default_file_logging()

2
tests/t.ui/test.ui.011.tree.03.widgets.py

@ -26,7 +26,7 @@ import os
import sys
import argparse
sys.path.append(os.path.join(sys.path[0],'../..'))
sys.path.append(os.path.join(sys.path[0],'../../libs/pyTermTk'))
import TermTk as ttk
ttk.TTkLog.use_default_file_logging()

2
tests/t.ui/test.ui.011.tree.04.dnd.01.py

@ -29,7 +29,7 @@ import os
import sys
import argparse
sys.path.append(os.path.join(sys.path[0],'../..'))
sys.path.append(os.path.join(sys.path[0],'../../libs/pyTermTk'))
import TermTk as ttk
class DropClass(ttk.TTkFrame):

144
tests/t.ui/test.ui.011.tree.05.paging.py

@ -0,0 +1,144 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2021 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# 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],'../../libs/pyTermTk'))
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):
_txt = f"Child A {ii}" + '\n'.join([f"l:{ii} - {_i}" for _i in range(ii)])
_e = ttk.TTkTreeWidgetItem([f"({i}-{ii}) String A", "String B", "String C"])
_e.addChildren([
ttk.TTkTreeWidgetItem([_txt, "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):
_txt = f"Child A {ii}" + '\n'.join([f"l:{ii} - {_i}" for _i in range(ii)])
_e = ttk.TTkTreeWidgetItem([f"({i}-{ii}) String A", "String B", "String C"])
_e.addChildren([
ttk.TTkTreeWidgetItem([_txt, "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()

90
tests/timeit/02.array.10.List.creation.py

@ -0,0 +1,90 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2025 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.
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)}")

219
tests/timeit/33.tree.01.iterate.py

@ -0,0 +1,219 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2025 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.
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)}")

159
tests/timeit/33.tree.02.iterate.py

@ -0,0 +1,159 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2025 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.
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)}")

221
tests/timeit/33.tree.03.iterate.py

@ -0,0 +1,221 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2025 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.
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)}")

150
tests/timeit/33.tree.04.listify.py

@ -0,0 +1,150 @@
#!/usr/bin/env python3
# MIT License
#
# Copyright (c) 2025 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.
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)}")
Loading…
Cancel
Save