From cad463bba8244501eed906dd55f676a35d4817a6 Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Sun, 9 Jan 2022 14:52:18 +0000 Subject: [PATCH 1/2] TTkTree added sorting capabilities --- TermTk/TTkCore/constant.py | 7 +++++ TermTk/TTkCore/string.py | 8 ++++++ TermTk/TTkGui/draw_ascii.py | 2 +- TermTk/TTkGui/draw_utf8.py | 2 +- TermTk/TTkWidgets/treewidget.py | 43 +++++++++++++++++++++++------ TermTk/TTkWidgets/treewidgetitem.py | 19 +++++++++++-- 6 files changed, 67 insertions(+), 14 deletions(-) diff --git a/TermTk/TTkCore/constant.py b/TermTk/TTkCore/constant.py index c5c0551d..8062842a 100644 --- a/TermTk/TTkCore/constant.py +++ b/TermTk/TTkCore/constant.py @@ -113,6 +113,13 @@ class TTkConstant: DontShowIndicator = ChildIndicatorPolicy.DontShowIndicator DontShowIndicatorWhenChildless = ChildIndicatorPolicy.DontShowIndicatorWhenChildless + class SortOrder: + AscendingOrder = 0x00 + DescendingOrder = 0x01 + + AscendingOrder = SortOrder.AscendingOrder + DescendingOrder = SortOrder.DescendingOrder + NoInsert = InsertPolicy.NoInsert InsertAtTop = InsertPolicy.InsertAtTop # InsertAtCurrent = InsertPolicy.InsertAtCurrent diff --git a/TermTk/TTkCore/string.py b/TermTk/TTkCore/string.py index 7721ad83..b5ef4a7a 100644 --- a/TermTk/TTkCore/string.py +++ b/TermTk/TTkCore/string.py @@ -72,6 +72,14 @@ class TTkString(): def __getitem__(self, index): raise NotImplementedError() + # Operators + def __lt__(self, other): return self._text < other._text + def __le__(self, other): return self._text <= other._text + def __eq__(self, other): return self._text == other._text + def __ne__(self, other): return self._text != other._text + def __gt__(self, other): return self._text > other._text + def __ge__(self, other): return self._text >= other._text + def toAscii(self): return self._text diff --git a/TermTk/TTkGui/draw_ascii.py b/TermTk/TTkGui/draw_ascii.py index 9e86d297..f5cbea35 100644 --- a/TermTk/TTkGui/draw_ascii.py +++ b/TermTk/TTkGui/draw_ascii.py @@ -81,7 +81,7 @@ class TTkTheme(): vscroll = ('^','|','X','v') tree = (' ','+','-',' ', - '|','|') + '|','|','v','^',) # 0 1 2 3 4 5 diff --git a/TermTk/TTkGui/draw_utf8.py b/TermTk/TTkGui/draw_utf8.py index 4e466339..c74fdb27 100644 --- a/TermTk/TTkGui/draw_utf8.py +++ b/TermTk/TTkGui/draw_utf8.py @@ -132,7 +132,7 @@ class TTkTheme(): vscroll = ('▲','┊','▓','▼') tree = ('•','▶','▼',' ', - '│','╿') + '│','╿','▼','▲') # 0 1 2 3 4 5 diff --git a/TermTk/TTkWidgets/treewidget.py b/TermTk/TTkWidgets/treewidget.py index 5fc66950..b550d27f 100644 --- a/TermTk/TTkWidgets/treewidget.py +++ b/TermTk/TTkWidgets/treewidget.py @@ -33,9 +33,10 @@ from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot from dataclasses import dataclass class TTkTreeWidget(TTkAbstractScrollView): - __slots__ = ( '_items', '_header', '_columnsPos', '_cache', + __slots__ = ( '_rootItem', '_header', '_columnsPos', '_cache', '_selectedId', '_selected', '_separatorSelected', '_mouseDelta', '_headerColor', '_selectedColor', '_lineColor', + '_sortColumn', '_sortOrder', # Signals 'itemChanged', 'itemClicked', 'itemDoubleClicked', 'itemExpanded', 'itemCollapsed', 'itemActivated' ) @@ -59,20 +60,24 @@ class TTkTreeWidget(TTkAbstractScrollView): self._selected = None self._selectedId = None self._separatorSelected = None - self._items = [] self._header = kwargs.get('header',[]) self._columnsPos = [] self._cache = [] + self._sortColumn = -1 + self._sortOrder = TTkK.AscendingOrder self._headerColor = kwargs.get('headerColor', TTkCfg.theme.treeHeaderColor) self._selectedColor = kwargs.get('selectedColor', TTkCfg.theme.treeSelectedColor) self._lineColor = kwargs.get('lineColor', TTkCfg.theme.treeLineColor) self.setMinimumHeight(1) self.setFocusPolicy(TTkK.ClickFocus) + self._rootItem = None + self.clear() + # Overridden function def viewFullAreaSize(self) -> (int, int): w = self._columnsPos[-1]+1 if self._columnsPos else 0 - h = 1+sum([c.size() for c in self._items]) + h = self._rootItem.size() # TTkLog.debug(f"{w=} {h=}") return w,h @@ -82,16 +87,16 @@ class TTkTreeWidget(TTkAbstractScrollView): return self.size() def clear(self): - for item in self._items: - item.dataChanged.disconnect(self._refreshCache) - self._items = [] + if self._rootItem: + self._rootItem.dataChanged.disconnect(self._refreshCache) + self._rootItem = TTkTreeWidgetItem(expanded=True) + self._rootItem.dataChanged.connect(self._refreshCache) self._refreshCache() self.viewChanged.emit() self.update() def addTopLevelItem(self, item): - item.dataChanged.connect(self._refreshCache) - self._items.append(item) + self._rootItem.addChild(item) self._refreshCache() self.viewChanged.emit() self.update() @@ -103,6 +108,16 @@ class TTkTreeWidget(TTkAbstractScrollView): self.viewChanged.emit() self.update() + def sortColumn(self): + '''Returns the column used to sort the contents of the widget.''' + return self._sortColumn + + def sortItems(self, col, order): + '''Sorts the items in the widget in the specified order by the values in the given column.''' + self._sortColumn = col + self._sortOrder = order + self._rootItem.sortChildren(col, order) + def mouseDoubleClickEvent(self, evt): x,y = evt.x, evt.y ox, oy = self.getViewOffsets() @@ -149,8 +164,15 @@ class TTkTreeWidget(TTkAbstractScrollView): if y == 0: for i, c in enumerate(self._columnsPos): if x == c: + # I-th separator selected self._separatorSelected = i self.update() + break + elif x < c: + # I-th header selected + order = not self._sortOrder if self._sortColumn == i else TTkK.AscendingOrder + self.sortItems(i, order) + break return True # Handle Tree/Table Events y += oy-1 @@ -240,7 +262,7 @@ class TTkTreeWidget(TTkAbstractScrollView): if _child.isExpanded(): for _c in _child.children(): _addToCache(_c, _level+1) - for c in self._items: + for c in self._rootItem.children(): _addToCache(c,0) self.update() self.viewChanged.emit() @@ -255,6 +277,9 @@ class TTkTreeWidget(TTkAbstractScrollView): hx = 0 if i==0 else self._columnsPos[i-1]+1 hx1 = self._columnsPos[i] self._canvas.drawText(pos=(hx-x,0), text=l, width=hx1-hx, color=self._headerColor) + if i == self._sortColumn: + s = tt[6] if self._sortOrder == TTkK.AscendingOrder else tt[7] + self._canvas.drawText(pos=(hx1-x-1,0), text=s, color=self._headerColor) # Draw header separators for sx in self._columnsPos: self._canvas.drawChar(pos=(sx-x,0), char=tt[5], color=self._headerColor) diff --git a/TermTk/TTkWidgets/treewidgetitem.py b/TermTk/TTkWidgets/treewidgetitem.py index 8b43c5c1..b40e3d51 100644 --- a/TermTk/TTkWidgets/treewidgetitem.py +++ b/TermTk/TTkWidgets/treewidgetitem.py @@ -42,14 +42,14 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): tt = TTkCfg.theme.tree super().__init__(self, *args, **kwargs) self._children = [] - self._data = args[0] if len(args)>0 and type(args[0])==list else None + self._data = args[0] if len(args)>0 and type(args[0])==list else [''] self._alignment = [TTkK.LEFT_ALIGN]*len(self._data) self._parent = kwargs.get('parent', None) self._childIndicatorPolicy = kwargs.get('childIndicatorPolicy', TTkK.DontShowIndicatorWhenChildless) self._defaultIcon = True - self._expanded = False - self._selected = False + self._expanded = kwargs.get('expanded', False) + self._selected = kwargs.get('selected', False) self._parent = kwargs.get("parent", None) self._icon = ['']*len(self._data) @@ -119,6 +119,19 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): return '' return self._data[col] + def sortChildren(self, col, order): + if not self._children: return + self._children = sorted( + self._children, + key = lambda x : x.data(col), + reverse = order == TTkK.DescendingOrder) + # Broadcast the sorting to the childrens + for c in self._children: + c.dataChanged.disconnect(self.emitDataChanged) + c.sortChildren(col, order) + c.dataChanged.connect(self.emitDataChanged) + self.dataChanged.emit() + @pyTTkSlot() def emitDataChanged(self): self.dataChanged.emit() From 2d92454f1c30154c9b19cb3a6f3dfe32b4b644e3 Mon Sep 17 00:00:00 2001 From: Eugenio Parodi Date: Sun, 9 Jan 2022 15:43:29 +0000 Subject: [PATCH 2/2] Add Sorting capabilities to file picker --- TermTk/TTkWidgets/TTkPickers/filepicker.py | 18 +++++++---- TermTk/TTkWidgets/treewidgetitem.py | 37 ++++++++++++++++------ 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/TermTk/TTkWidgets/TTkPickers/filepicker.py b/TermTk/TTkWidgets/TTkPickers/filepicker.py index b4496092..cd393b60 100644 --- a/TermTk/TTkWidgets/TTkPickers/filepicker.py +++ b/TermTk/TTkWidgets/TTkPickers/filepicker.py @@ -64,13 +64,17 @@ class _FileTreeWidgetItem(TTkTreeWidgetItem): FILE = 0x00 DIR = 0x01 - __slots__ = ('_path', '_type') + __slots__ = ('_path', '_type', '_raw') def __init__(self, *args, **kwargs): TTkTreeWidgetItem.__init__(self, *args, **kwargs) self._path = kwargs.get('path', '.') self._type = kwargs.get('type', _FileTreeWidgetItem.FILE) + self._raw = kwargs.get('raw') self.setTextAlignment(1, TTkK.RIGHT_ALIGN) + def sortData(self, col): + return self._raw[col] + def path(self): return self._path @@ -275,14 +279,14 @@ class TTkFileDialogPicker(TTkWindow): size = f"{info.st_size/1024:.2f} KB" else: size = f"{info.st_size} bytes" - return time, size + return time, size, info.st_ctime, info.st_size if os.path.isdir(nodePath): if os.path.exists(nodePath): - time, _ = _getStat(nodePath) + time, _, rawTime, _ = _getStat(nodePath) color = TTkCfg.theme.folderNameColor else: - time, _ = "" + time, _, rawTime, _ = "" color = TTkCfg.theme.failNameColor if os.path.islink(nodePath): @@ -294,6 +298,7 @@ class TTkFileDialogPicker(TTkWindow): ret.append(_FileTreeWidgetItem( [ name, "", typef, time], + raw = [ n , -1 , typef , rawTime ], path=nodePath, type=_FileTreeWidgetItem.DIR, icon=TTkString() + TTkCfg.theme.folderIconColor + TTkCfg.theme.fileIcon.folderClose + TTkColor.RST, @@ -301,7 +306,7 @@ class TTkFileDialogPicker(TTkWindow): elif os.path.isfile(nodePath) or os.path.islink(nodePath): if os.path.exists(nodePath): - time, size = _getStat(nodePath) + time, size, rawTime, rawSize = _getStat(nodePath) if os.access(nodePath, os.X_OK): color = TTkCfg.theme.executableColor typef="Exec" @@ -309,7 +314,7 @@ class TTkFileDialogPicker(TTkWindow): color = TTkCfg.theme.fileNameColor typef="File" else: - time, size = "", "" + time, size, rawTime, rawSize = "", "", 0, 0 color = TTkCfg.theme.failNameColor typef="Broken" @@ -323,6 +328,7 @@ class TTkFileDialogPicker(TTkWindow): if ext: ext = f"{ext[1:]} " ret.append(_FileTreeWidgetItem( [ name, size, typef, time], + raw = [ n , rawSize , typef , rawTime ], path=nodePath, type=_FileTreeWidgetItem.FILE, icon=TTkString() + TTkCfg.theme.fileIconColor + TTkCfg.theme.fileIcon.getIcon(n) + TTkColor.RST, diff --git a/TermTk/TTkWidgets/treewidgetitem.py b/TermTk/TTkWidgets/treewidgetitem.py index b40e3d51..0108b6e2 100644 --- a/TermTk/TTkWidgets/treewidgetitem.py +++ b/TermTk/TTkWidgets/treewidgetitem.py @@ -31,7 +31,8 @@ from TermTk.TTkAbstract.abstractitemmodel import TTkAbstractItemModel class TTkTreeWidgetItem(TTkAbstractItemModel): __slots__ = ('_parent', '_data', '_alignment', '_children', '_expanded', '_selected', - '_childIndicatorPolicy', '_icon', '_defaultIcon' + '_childIndicatorPolicy', '_icon', '_defaultIcon', + '_sortColumn', '_sortOrder' # Signals # 'refreshData' ) @@ -52,6 +53,9 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): self._selected = kwargs.get('selected', False) self._parent = kwargs.get("parent", None) + self._sortColumn = -1 + self._sortOrder = TTkK.AscendingOrder + self._icon = ['']*len(self._data) self._setDefaultIcon() if 'icon' in kwargs: @@ -78,8 +82,11 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): def addChild(self, child): self._children.append(child) child._parent = self - child.dataChanged.connect(self.emitDataChanged) + child._sortOrder = self._sortOrder + child._sortColumn = self._sortColumn self._setDefaultIcon() + self._sort(children=False) + child.dataChanged.connect(self.emitDataChanged) self.dataChanged.emit() def addChildren(self, children): @@ -119,17 +126,27 @@ class TTkTreeWidgetItem(TTkAbstractItemModel): return '' return self._data[col] - def sortChildren(self, col, order): - if not self._children: return + 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.data(col), - reverse = order == TTkK.DescendingOrder) + key = lambda x : x.sortData(self._sortColumn), + reverse = self._sortOrder == TTkK.DescendingOrder) # Broadcast the sorting to the childrens - for c in self._children: - c.dataChanged.disconnect(self.emitDataChanged) - c.sortChildren(col, order) - c.dataChanged.connect(self.emitDataChanged) + if children: + for c in self._children: + c.dataChanged.disconnect(self.emitDataChanged) + c.sortChildren(self._sortColumn, self._sortOrder) + c.dataChanged.connect(self.emitDataChanged) + + def sortChildren(self, col, order): + self._sortColumn = col + self._sortOrder = order + if not self._children: return + self._sort(children=True) self.dataChanged.emit() @pyTTkSlot()