diff --git a/libs/pyTermTk/TermTk/TTkCore/TTkTerm/inputmouse.py b/libs/pyTermTk/TermTk/TTkCore/TTkTerm/inputmouse.py index f753b7fc..1b627047 100644 --- a/libs/pyTermTk/TermTk/TTkCore/TTkTerm/inputmouse.py +++ b/libs/pyTermTk/TermTk/TTkCore/TTkTerm/inputmouse.py @@ -86,6 +86,13 @@ class TTkMouseEvent: Right = TTkK.WHEEL_Right __slots__ = ('x', 'y', 'key', 'evt', 'mod', 'tap', 'raw') + x: int + y: int + key: int + evt: int + mod: int + tap: int + raw: str def __init__(self, x: int, y: int, key: int, evt: int, mod: int, tap: int, raw: str): self.x = x self.y = y diff --git a/libs/pyTermTk/TermTk/TTkGui/drag.py b/libs/pyTermTk/TermTk/TTkGui/drag.py index 231d64b5..2fb3156d 100644 --- a/libs/pyTermTk/TermTk/TTkGui/drag.py +++ b/libs/pyTermTk/TermTk/TTkGui/drag.py @@ -100,6 +100,15 @@ class TTkDnD(): ''' return self._hotSpot + def clone(self): + ''' + Clone this event + + :return: the cloned event + :rtype: :py:class:`TTkDnD` + ''' + return TTkDnD(data=self._data, hotspot=self._hotSpot) + class TTkDnDEvent(TTkDnD): ''' Drag and Drop event class. @@ -123,6 +132,15 @@ class TTkDnDEvent(TTkDnD): ''' return self._pos + def clone(self): + ''' + Clone this event + + :return: the cloned event + :rtype: :py:class:`TTkDnDEvent` + ''' + return TTkDnDEvent(data=self._data, hotspot=self._hotSpot, pos=self._pos) + class TTkDrag(TTkDnD): __slots__ = ('_pixmap', '_showPixmap') def __init__(self, **kwargs) -> None: diff --git a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tree.py b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tree.py index ff195715..d12342f6 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tree.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tree.py @@ -41,6 +41,7 @@ class TTkTree(TTkAbstractScrollArea): 'setHeaderLabels', 'setColumnWidth', 'resizeColumnToContents', 'sortColumn', 'sortItems', + 'dragDropMode', 'setDragDropMode', # 'appendItem', 'setAlignment', 'setColumnColors', 'setColumnSize', 'setHeader', 'addTopLevelItem', 'addTopLevelItems', 'takeTopLevelItem', 'topLevelItem', 'indexOfTopLevelItem', 'selectedItems', 'clear']) ) diff --git a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidget.py b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidget.py index 703a9917..edc4ff37 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidget.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidget.py @@ -22,14 +22,19 @@ __all__ = ['TTkTreeWidget'] +from typing import List + from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.string import TTkString +from TermTk.TTkCore.canvas import TTkCanvas from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent +from TermTk.TTkGui.drag import TTkDrag, TTkDnDEvent from TermTk.TTkWidgets.TTkModelView.treewidgetitem import TTkTreeWidgetItem from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView +from TermTk.TTkAbstract.abstractitemmodel import TTkAbstractItemModel from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot from dataclasses import dataclass @@ -151,6 +156,7 @@ class TTkTreeWidget(TTkAbstractScrollView): '_header', '_columnsPos', '_selectedId', '_selected', '_separatorSelected', '_sortColumn', '_sortOrder', '_sortingEnabled', + '_dndMode', # Signals 'itemChanged', 'itemClicked', 'itemDoubleClicked', 'itemExpanded', 'itemCollapsed', 'itemActivated' ) @@ -162,15 +168,23 @@ class TTkTreeWidget(TTkAbstractScrollView): widgets: list firstLine: bool + @dataclass(frozen=True) + class _DropTreeData: + widget: TTkAbstractScrollView + items: List[TTkAbstractItemModel] + def __init__(self, *, header=None, sortingEnabled=True, + dragDropMode:TTkK.DragDropMode=TTkK.DragDropMode.NoDragDrop, **kwargs) -> None: ''' :param header: define the header labels of each column, defaults to [] :type header: list[TTkString], optional :param sortingEnabled: enable the column sorting, defaults to False :type sortingEnabled: bool, optional + :param dragDropMode: This property holds the drag and drop event the view will act upon, defaults to :py:class:`TTkK.DragDropMode.NoDragDrop`. + :type dragDropMode: :py:class:`TTkK.DragDropMode`, optional ''' # Signals self.itemActivated = pyTTkSignal(TTkTreeWidgetItem, int) @@ -180,6 +194,7 @@ class TTkTreeWidget(TTkAbstractScrollView): self.itemExpanded = pyTTkSignal(TTkTreeWidgetItem) self.itemCollapsed = pyTTkSignal(TTkTreeWidgetItem) + self._dndMode = dragDropMode self._selected = None self._selectedId = None self._separatorSelected = None @@ -270,6 +285,14 @@ class TTkTreeWidget(TTkAbstractScrollView): self.viewChanged.emit() self.update() + def dragDropMode(self): + '''dragDropMode''' + return self._dndMode + + def setDragDropMode(self, dndMode): + '''setDragDropMode''' + self._dndMode = dndMode + def isSortingEnabled(self) -> bool: 'isSortingEnabled' return self._sortingEnabled @@ -432,6 +455,21 @@ class TTkTreeWidget(TTkAbstractScrollView): self.viewChanged.emit() self.update() return True + elif ( self._dndMode & TTkK.DragDropMode.AllowDrag and + evt.key == TTkMouseEvent.LeftButton and self._selected ): + drag = TTkDrag() + data = TTkTreeWidget._DropTreeData(widget=self,items=[self._selected]) + text = self._selected.data(0) + if text.termWidth() > 30: + text = '['+text.substring(to=27)+'...]' + else: + text = '['+text+']' + pm = TTkCanvas(text.termWidth()+2,1) + pm.drawTTkString(pos=(0,0),text=text) + drag.setPixmap(pm) + drag.setData(data) + drag.exec() + return True return False def _alignWidgets(self) -> None: diff --git a/libs/pyTermTk/TermTk/TTkWidgets/kodetab.py b/libs/pyTermTk/TermTk/TTkWidgets/kodetab.py index d95fa65c..648ed4fe 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/kodetab.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/kodetab.py @@ -22,6 +22,8 @@ __all__ = ['TTkKodeTab'] +from typing import Callable + from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.helper import TTkHelper from TermTk.TTkCore.log import TTkLog @@ -29,7 +31,7 @@ from TermTk.TTkCore.string import TTkString from TermTk.TTkCore.color import TTkColor, TTkColorGradient from TermTk.TTkCore.signal import pyTTkSlot, pyTTkSignal from TermTk.TTkWidgets.widget import TTkWidget -from TermTk.TTkWidgets.tabwidget import TTkTabWidget +from TermTk.TTkWidgets.tabwidget import TTkTabWidget, _TTkNewTabWidgetDragData, _TTkTabWidgetDragData from TermTk.TTkWidgets.splitter import TTkSplitter from TermTk.TTkWidgets.frame import TTkFrame from TermTk.TTkLayouts.gridlayout import TTkGridLayout @@ -117,45 +119,78 @@ class _TTkKodeTab(TTkTabWidget): def dropEvent(self, evt:TTkDnDEvent) -> bool: self._frameOverlay = None x,y = evt.x, evt.y - ret = True data = evt.data() - tb = data.tabButton() - tw = data.tabWidget() + if issubclass(type(data),_TTkNewTabWidgetDragData): + tw = None + elif issubclass(type(data),_TTkTabWidgetDragData): + tw = data.tabWidget() + else: + return False + + def _processDrop(widget, label, orientation, offset): + fwold = self._baseWidget._getFirstWidget() + splitter = self.parentWidget() + index = splitter.indexOf(self) + if splitter.orientation() != orientation: + splitter.replaceWidget(index, splitter := TTkSplitter(orientation=orientation)) + splitter.addWidget(self) + index=offset + splitter.insertWidget(index+offset, kt:=_TTkKodeTab(baseWidget=self._baseWidget, border=self.border(), closable=self.tabsClosable())) + kt._dropEventProxy = self._dropEventProxy + kt.addTab(widget,label) + if fwold!=(fwnew := self._baseWidget._getFirstWidget()) and fwold._hasMenu(): + fwnew._importMenu(fwold) + + ret = True if y<3: ret = super().dropEvent(evt) - else: + elif issubclass(type(data),_TTkNewTabWidgetDragData): + tw = None + widget = data.widget() + tabData = data.data() + label = data.label() + closable = data.closable() + w,h = self.size() + h-=3 + y-=3 + if xw*3//4: + _processDrop(widget, label, TTkK.HORIZONTAL, 1) + elif yh*3//4: + _processDrop(widget, label, TTkK.VERTICAL, 1) + else: + ret = super().dropEvent(evt) + elif issubclass(type(data),_TTkTabWidgetDragData): + tb = data.tabButton() + tw = data.tabWidget() w,h = self.size() h-=3 y-=3 index = tw._tabBar._tabButtons.index(tb) widget = tw._tabWidgets[index] + label = tb.text() - def _processDrop(index, orientation, offset): - fwold = self._baseWidget._getFirstWidget() - tw.removeTab(index) - splitter = self.parentWidget() - index = splitter.indexOf(self) - if splitter.orientation() != orientation: - splitter.replaceWidget(index, splitter := TTkSplitter(orientation=orientation)) - splitter.addWidget(self) - index=offset - splitter.insertWidget(index+offset, kt:=_TTkKodeTab(baseWidget=self._baseWidget, border=self.border(), closable=self.tabsClosable())) - kt.addTab(widget,tb.text()) - if fwold!=(fwnew := self._baseWidget._getFirstWidget()) and fwold._hasMenu(): - fwnew._importMenu(fwold) if xw*3//4: - _processDrop(index, TTkK.HORIZONTAL, 1) + tw.removeTab(index) + _processDrop(widget, label, TTkK.HORIZONTAL, 1) elif yh*3//4: - _processDrop(index, TTkK.VERTICAL, 1) + tw.removeTab(index) + _processDrop(widget, label, TTkK.VERTICAL, 1) else: ret = super().dropEvent(evt) # Remove the widget and/or all the cascade empty splitters - self._kodeTabClosed(tw) + if tw: + self._kodeTabClosed(tw) self.update() return ret @@ -163,7 +198,7 @@ class _TTkKodeTab(TTkTabWidget): def _kodeTabClosed(self, widget=None): # Remove the widget and/or all the cascade empty splitters fwold = self._baseWidget._getFirstWidget() - widget = widget if type(widget) is _TTkKodeTab else self + widget = widget if issubclass(type(widget), _TTkKodeTab) else self if not widget._tabWidgets: if splitter := widget.parentWidget(): while splitter.count() == 1 and splitter != self._baseWidget: @@ -199,6 +234,7 @@ class TTkKodeTab(TTkSplitter): kwargs.pop('visible',None) # self.layout().addWidget(splitter := TTkSplitter()) self._lastKodeTabWidget = _TTkKodeTab(baseWidget=self, **kwargs) + self._lastKodeTabWidget._dropEventProxy = self._dropEventProxy self.addWidget(self._lastKodeTabWidget) def _getFirstWidget(self): @@ -207,6 +243,12 @@ class TTkKodeTab(TTkSplitter): while type(item:=kt.widget(0)) != _TTkKodeTab: kt = item return item if type(item)==_TTkKodeTab else None + def setDropEventProxy(self, proxy:Callable) -> None: + for widget in self.layout().iterWidgets(onlyVisible=False): + if issubclass(type(widget),_TTkKodeTab): + widget.setDropEventProxy(proxy) + return super().setDropEventProxy(proxy) + @pyTTkSlot(TTkWidget) def setCurrentWidget(self, *args, **kwargs) -> None: return self._lastKodeTabWidget.setCurrentWidget(*args, **kwargs) diff --git a/libs/pyTermTk/TermTk/TTkWidgets/tabwidget.py b/libs/pyTermTk/TermTk/TTkWidgets/tabwidget.py index 912e1ca9..91a5a6ff 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/tabwidget.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/tabwidget.py @@ -65,7 +65,6 @@ _tabStyleFocussed = { } class _TTkTabWidgetDragData(): - __slots__ = ('_tabButton', '_tabWidget') def __init__(self, b, tw): self._tabButton = b @@ -73,6 +72,18 @@ class _TTkTabWidgetDragData(): def tabButton(self): return self._tabButton def tabWidget(self): return self._tabWidget +class _TTkNewTabWidgetDragData(): + __slots__ = ('_label', '_widget', '_closable', '_data') + def __init__(self, label, widget:TTkWidget, data=None, closable:bool=False): + self._data = data + self._label = label + self._widget = widget + self._closable = closable + def data(self): return self._data + def label(self): return self._label + def widget(self): return self._widget + def closable(self): return self._closable + class _TTkTabBarDragData(): __slots__ = ('_tabButton','_tabBar') def __init__(self, b, tb): @@ -666,37 +677,54 @@ class TTkTabWidget(TTkFrame): def dropEvent(self, evt:TTkDnDEvent) -> bool: data = evt.data() x, y = evt.x, evt.y - if not issubclass(type (data),_TTkTabWidgetDragData): - return False - tb = data.tabButton() - tw = data.tabWidget() - index = tw._tabBar._tabButtons.index(tb) - widget = tw.widget(index) - data = tw.tabData(index) - if TTkHelper.isParent(self, tw): - return False - if y < 3: - tbx = self._tabBar.x() - newIndex = 0 - for b in self._tabBar._tabButtons: - if tbx+b.x()+b.width()/2 < x: - newIndex += 1 - if tw == self: - if index <= newIndex: - newIndex -= 1 - tw.removeTab(index) - self.insertTab(newIndex, widget, tb.text(), data, tb._closable) - self.setCurrentIndex(newIndex) - #self._tabChanged(newIndex) - elif tw != self: - tw.removeTab(index) - newIndex = len(self._tabWidgets) - self.addTab(widget, tb.text(), data) - self.setCurrentIndex(newIndex) - self._tabChanged(newIndex) - - TTkLog.debug(f"Drop -> pos={evt.pos()}") - return True + if issubclass(type(data),_TTkTabWidgetDragData): + tb = data.tabButton() + tw = data.tabWidget() + index = tw._tabBar._tabButtons.index(tb) + widget = tw.widget(index) + data = tw.tabData(index) + if TTkHelper.isParent(self, tw): + return False + if y < 3: + tbx = self._tabBar.x() + newIndex = 0 + for b in self._tabBar._tabButtons: + if tbx+b.x()+b.width()/2 < x: + newIndex += 1 + if tw == self: + if index <= newIndex: + newIndex -= 1 + tw.removeTab(index) + self.insertTab(newIndex, widget, tb.text(), data, tb._closable) + self.setCurrentIndex(newIndex) + #self._tabChanged(newIndex) + elif tw != self: + tw.removeTab(index) + newIndex = len(self._tabWidgets) + self.addTab(widget, tb.text(), data) + self.setCurrentIndex(newIndex) + self._tabChanged(newIndex) + TTkLog.debug(f"Drop -> pos={evt.pos()}") + return True + elif issubclass(type(data),_TTkNewTabWidgetDragData): + w = data.widget() + d = data.data() + l = data.label() + c = data.closable() + if y < 3: + tbx = self._tabBar.x() + newIndex = 0 + for b in self._tabBar._tabButtons: + if tbx+b.x()+b.width()/2 < x: + newIndex += 1 + self.insertTab(newIndex, w, l, d, c) + self.setCurrentIndex(newIndex) + else: + self.addTab(w, l, d, c) + self.setCurrentIndex(len(self._tabBar._tabButtons)-1) + TTkLog.debug(f"Drop -> pos={evt.pos()}") + return True + return False def addMenu(self, text, position=TTkK.LEFT, data=None) -> TTkMenuBarButton: '''addMenu''' diff --git a/libs/pyTermTk/TermTk/TTkWidgets/widget.py b/libs/pyTermTk/TermTk/TTkWidgets/widget.py index dd9eda9b..807a9965 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/widget.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/widget.py @@ -22,6 +22,8 @@ __all__ = ['TTkWidget'] +from typing import Callable, Any, List + try: from typing import Self except: @@ -115,6 +117,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): '_enabled', '_style', '_currentStyle', '_toolTip', + '_dropEventProxy', '_widgetCursor', '_widgetCursorEnabled', '_widgetCursorType', #Signals 'focusChanged', 'sizeChanged', 'currentStyleChanged', 'closed') @@ -194,6 +197,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): self.closed = pyTTkSignal(TTkWidget) # self.sizeChanged.connect(self.resizeEvent) + self._dropEventProxy = lambda x:x self._widgetCursor = (0,0) self._widgetCursorEnabled = False self._widgetCursorType = TTkK.Cursor_Blinking_Bar @@ -267,6 +271,13 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): ''' self._name = name + def setDropEventProxy(self, proxy:Callable) -> None: + ''' + .. warning:: + This is an alpha Method to prototype the Drag and Drop prosy feature and may change in the future + ''' + self._dropEventProxy = proxy + def widgetItem(self) -> TTkWidgetItem: return self._widgetItem @@ -444,7 +455,7 @@ class TTkWidget(TMouseEvents,TKeyEvents, TDragEvents): TTkHelper.dndEnter(self) return True if evt.evt == TTkK.Release: - if self.dropEvent(TTkHelper.dndGetDrag().getDropEvent(evt)): + if self.dropEvent(self._dropEventProxy(TTkHelper.dndGetDrag().getDropEvent(evt))): return True return ret diff --git a/tests/t.ui/test.ui.011.tree.04.dnd.01.py b/tests/t.ui/test.ui.011.tree.04.dnd.01.py new file mode 100755 index 00000000..1e0ba867 --- /dev/null +++ b/tests/t.ui/test.ui.011.tree.04.dnd.01.py @@ -0,0 +1,135 @@ +#!/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 +import argparse + +sys.path.append(os.path.join(sys.path[0],'../..')) +import TermTk as ttk + +class DropClass(ttk.TTkFrame): + def __init__(self, **kwargs): + super().__init__(**kwargs|{'title':'Drop Here'}) + + def dropEvent(self, evt): + ttk.TTkLog.debug(f"Dropped: {evt.data()}") + return super().dropEvent(evt) + def dragEnterEvent(self, evt): + ttk.TTkLog.debug(f"Drag Enter: {evt}") + return super().dragEnterEvent(evt) + def dragMoveEvent(self, evt): + ttk.TTkLog.debug(f"Drag Move: {evt}") + return super().dragMoveEvent(evt) + def dragLeaveEvent(self, evt): + ttk.TTkLog.debug(f"Drag Leave: {evt}") + return super().dragLeaveEvent(evt) + + +def dropEventProxy(evt:ttk.TTkDnDEvent): + data = evt.data() + ttk.TTkLog.debug(f"Proxy: {evt=} {data=}") + if issubclass(type(data), ttk.TTkTreeWidget._DropTreeData) and data.items: + item:ttk.TTkTreeWidgetItem = data.items[0] + newData = ttk.TTkWidgets.tabwidget._TTkNewTabWidgetDragData( + widget=ttk.TTkTestWidgetSizes(), + label=item.data(0), + data=None, + closable=True + ) + newEvt = evt.clone() + newEvt.setData(newData) + return newEvt + return evt + +ttk.TTkLog.use_default_file_logging() + +parser = argparse.ArgumentParser() +parser.add_argument('-f', help='Full Screen', action='store_true') +args = parser.parse_args() + +root = ttk.TTk() + +DropClass(parent=root, pos=(0,0), size=(30,5)) +logsWin = ttk.TTkWindow(parent=root, title='Logs', pos=(31,0), size=(100,30), layout=ttk.TTkGridLayout()) +ttk.TTkLogViewer(parent=logsWin) + +kodeWin = ttk.TTkWindow(parent=root,pos = (30,5), size=(80,40), title="KodeWin", layout=ttk.TTkGridLayout(), border=True) +kt = ttk.TTkKodeTab(parent=kodeWin) +kt.setDropEventProxy(dropEventProxy) +kt.addTab(ttk.TTkTestWidgetSizes(),"t 1") +kt.addTab(ttk.TTkTestWidgetSizes(),"t 2") +kt.addTab(ttk.TTkTestWidgetSizes(),"t 3") +kt.addTab(ttk.TTkTestWidgetSizes(),"t 4") +kt.addTab(ttk.TTkTestWidgetSizes(),"t 5") + + +fileTreeWin = ttk.TTkWindow(parent=root,pos = (3,12), size=(40,40), title="Test Tree 1", layout=ttk.TTkGridLayout(), border=True) +ft = ttk.TTkFileTree(parent=fileTreeWin, dragDropMode=ttk.TTkK.DragDropMode.AllowDrag) + +treeWin = ttk.TTkWindow(parent=root,pos = (0,6), size=(40,40), title="Test Tree 1", layout=ttk.TTkGridLayout(), border=True) + +tw = ttk.TTkTree(parent=treeWin, dragDropMode=ttk.TTkK.DragDropMode.AllowDrag) +tw.setHeaderLabels(["Column 1", "Column 2", "Column 3"]) + +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) + + +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" + 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" + 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" + 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) + +root.mainloop() \ No newline at end of file