Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,30 +61,30 @@ jobs:
shell: bash
run: |
python -m pip install --upgrade pip
python -m pip install -e '.[test]'
python -m pip install uv
uv venv
uv pip install -e '.[test]'

- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude .venv,build,tmp
uv run flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude .venv,build,tmp
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
# flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics

- name: pytest
run: |
DDDD=$(pwd)
pytest ${DDDD}/tests/pytest
pytest ${DDDD}/apps
uv run pytest ${DDDD}/tests/pytest
uv run pytest ${DDDD}/apps

- name: Dumb Smoke Test
run: |
# To fix a folder permissin issue let's try to run the test from /tmp
DDDD=$(pwd)
mkdir -p /tmp/Eugenio
cd /tmp/Eugenio
# Download the input test
mkdir -p tmp
wget -O tmp/test.input.001.bin https://github.com/ceccopierangiolieugenio/binaryRepo/raw/master/pyTermTk/tests/test.input.001.bin
wget -O tmp/test.input.002.bin https://github.com/ceccopierangiolieugenio/binaryRepo/raw/master/pyTermTk/tests/test.input.002.bin
wget -O tmp/test.input.003.bin https://github.com/ceccopierangiolieugenio/binaryRepo/raw/master/pyTermTk/tests/test.input.003.bin
pytest ${DDDD}/tests/pytest/run_*
uv run pytest ${DDDD}/tests/pytest/run_*
49 changes: 49 additions & 0 deletions libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/filetree.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,55 @@ def setDragDropMode(self, dndMode:TTkK.DragDropMode):
setDragDropMode
'''
return self._fileTreeWidget.setDragDropMode(dndMode=dndMode)
def setSelectionMode(self, mode:TTkK.SelectionMode) -> None:
'''
.. seealso:: this method is forwarded to :py:meth:`TTkFileTreeWidget.setSelectionMode`

Sets the current selection model to the given selectionModel.

:param mode: the selection mode used in this tree
:type mode: :py:class:`TTkK.SelectionMode`
'''
return self._fileTreeWidget.setSelectionMode(mode=mode)
def clearSelection(self) -> None:
'''
.. seealso:: this method is forwarded to :py:meth:`TTkFileTreeWidget.clearSelection`

Deselects all selected items.
'''
return self._fileTreeWidget.clearSelection()
def setCurrentItem(self, item:Optional[TTkTreeWidgetItem]) -> None:
'''
.. seealso:: this method is forwarded to :py:meth:`TTkFileTreeWidget.setCurrentItem`

Selects the specified item as the current one.

:param item: the item to be selected, None clears the selection
:type item: :py:class:`TTkTreeWidgetItem` or None
'''
return self._fileTreeWidget.setCurrentItem(item=item)
def selectItem(self, item:TTkTreeWidgetItem) -> None:
'''
.. seealso:: this method is forwarded to :py:meth:`TTkFileTreeWidget.selectItem`

Adds the specified item to the current selection.

In single selection mode this replaces the previous selection.

:param item: the item to be selected
:type item: :py:class:`TTkTreeWidgetItem`
'''
return self._fileTreeWidget.selectItem(item=item)
def deselectItem(self, item:TTkTreeWidgetItem) -> None:
'''
.. seealso:: this method is forwarded to :py:meth:`TTkFileTreeWidget.deselectItem`

Removes the specified item from the current selection.

:param item: the item to be deselected
:type item: :py:class:`TTkTreeWidgetItem`
'''
return self._fileTreeWidget.deselectItem(item=item)
@pyTTkSlot()
def expandAll(self) -> None:
'''
Expand Down
51 changes: 51 additions & 0 deletions libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ class TTkTree(TTkAbstractScrollArea):
'setColumnWidth', 'resizeColumnToContents',
'sortColumn', 'sortItems',
'dragDropMode', 'setDragDropMode',
'setSelectionMode',
'clearSelection', 'setCurrentItem', 'selectItem', 'deselectItem',
'expandAll', 'collapseAll',
'invisibleRootItem', 'itemAt',
# 'appendItem', 'setAlignment', 'setColumnColors', 'setColumnSize', 'setHeader',
Expand Down Expand Up @@ -217,6 +219,55 @@ def setDragDropMode(self, dndMode:TTkK.DragDropMode):
setDragDropMode
'''
return self._treeView.setDragDropMode(dndMode=dndMode)
def setSelectionMode(self, mode:TTkK.SelectionMode) -> None:
'''
.. seealso:: this method is forwarded to :py:meth:`TTkTreeWidget.setSelectionMode`

Sets the current selection model to the given selectionModel.

:param mode: the selection mode used in this tree
:type mode: :py:class:`TTkK.SelectionMode`
'''
return self._treeView.setSelectionMode(mode=mode)
def clearSelection(self) -> None:
'''
.. seealso:: this method is forwarded to :py:meth:`TTkTreeWidget.clearSelection`

Deselects all selected items.
'''
return self._treeView.clearSelection()
def setCurrentItem(self, item:Optional[TTkTreeWidgetItem]) -> None:
'''
.. seealso:: this method is forwarded to :py:meth:`TTkTreeWidget.setCurrentItem`

Selects the specified item as the current one.

:param item: the item to be selected, None clears the selection
:type item: :py:class:`TTkTreeWidgetItem` or None
'''
return self._treeView.setCurrentItem(item=item)
def selectItem(self, item:TTkTreeWidgetItem) -> None:
'''
.. seealso:: this method is forwarded to :py:meth:`TTkTreeWidget.selectItem`

Adds the specified item to the current selection.

In single selection mode this replaces the previous selection.

:param item: the item to be selected
:type item: :py:class:`TTkTreeWidgetItem`
'''
return self._treeView.selectItem(item=item)
def deselectItem(self, item:TTkTreeWidgetItem) -> None:
'''
.. seealso:: this method is forwarded to :py:meth:`TTkTreeWidget.deselectItem`

Removes the specified item from the current selection.

:param item: the item to be deselected
:type item: :py:class:`TTkTreeWidgetItem`
'''
return self._treeView.deselectItem(item=item)
@pyTTkSlot()
def expandAll(self) -> None:
'''
Expand Down
99 changes: 94 additions & 5 deletions libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidget.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ def _getColumnContentSize(self, column:int, offset:int) -> int:
if not limited_page:
return 0
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)
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), default=1)
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)
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), default=1)
return size-1

def _get_page_root(self, index:int, size:int) -> List[Tuple[int,int,TTkTreeWidgetItem]]:
Expand Down Expand Up @@ -246,7 +246,7 @@ class _DropTreeData:
items: List[TTkTreeWidgetItem]

def __init__(self, *,
header:List[TTkString]=[],
header:Optional[List[TTkString]]=None,
sortingEnabled:bool=True,
selectionMode:TTkK.SelectionMode=TTkK.SelectionMode.SingleSelection,
dragDropMode:TTkK.DragDropMode=TTkK.DragDropMode.NoDragDrop,
Expand Down Expand Up @@ -278,7 +278,7 @@ def __init__(self, *,
self._sortOrder = TTkK.AscendingOrder
self._rootItem = _RootWidgetItem()
super().__init__(**kwargs)
self.setHeaderLabels(header)
self.setHeaderLabels(header if header is not None else [])
self.setMinimumHeight(1)
self.setFocusPolicy(TTkK.ClickFocus)
self.clear()
Expand Down Expand Up @@ -321,6 +321,7 @@ def clear(self) -> None:
if self._rootItem:
self._rootItem.dataChanged.disconnect(self._refreshCache)
self._rootItem = _RootWidgetItem()
self._selected = []
self._rootItem.dataChanged.connect(self._refreshCache)
self.sortItems(self._sortColumn, self._sortOrder)
self.viewChanged.emit()
Expand Down Expand Up @@ -348,6 +349,24 @@ def addTopLevelItems(self, items:List[TTkTreeWidgetItem]) -> None:
self.viewChanged.emit()
self.update()

def _itemInTree(self, item:TTkTreeWidgetItem) -> bool:
if not item:
return False
if item is self._rootItem:
return True
# Traverse up from the item to the root using parent chain
current = item
while current is not None:
if current is self._rootItem:
return True
current = current._parent
return False

def _pruneSelection(self) -> None:
if not self._selected:
return
self._selected = [_i for _i in self._selected if self._itemInTree(_i)]

def takeTopLevelItem(self, index:int) -> Optional[TTkTreeWidgetItem]:
'''
Removes the top-level item at the given index in the tree and returns it, otherwise returns None;
Expand All @@ -358,6 +377,7 @@ def takeTopLevelItem(self, index:int) -> Optional[TTkTreeWidgetItem]:
:rtype: Optional[:py:class:`TTkTreeWidgetItem`]
'''
ret = self._rootItem.takeChild(index)
self._pruneSelection()
self.viewChanged.emit()
self.update()
return ret
Expand Down Expand Up @@ -396,18 +416,87 @@ def setSelectionMode(self, mode:TTkK.SelectionMode) -> None:
:param mode: the selection mode used in this tree
:type mode: :py:class:`TTkK.SelectionMode`
'''
self._pruneSelection()
self._selectionMode = mode
if mode == TTkK.SelectionMode.NoSelection:
self.clearSelection()
elif mode == TTkK.SelectionMode.SingleSelection and len(self._selected) > 1:
self._selected = self._selected[:1]
self.update()

def selectedItems(self) -> List[TTkTreeWidgetItem]:
'''
Returns a list of all selected non-hidden items.

:rtype: List[:py:class:`TTkTreeWidgetItem`]
'''
self._pruneSelection()
if self._selected:
return self._selected
return []

def clearSelection(self) -> None:
'''
Deselects all selected items.
'''
if not self._selected:
return
self._selected = []
self.update()

def setCurrentItem(self, item:Optional[TTkTreeWidgetItem]) -> None:
'''
Selects the specified item as the current one.

:param item: the item to be selected, None clears the selection
:type item: :py:class:`TTkTreeWidgetItem` or None
'''
if item is None:
self.clearSelection()
return
if not self._itemInTree(item):
return
if self._selectionMode == TTkK.SelectionMode.NoSelection:
return
self._selected = [item]
self.update()

def selectItem(self, item:TTkTreeWidgetItem) -> None:
'''
Adds the specified item to the current selection.

In single selection mode this replaces the previous selection.

:param item: the item to be selected
:type item: :py:class:`TTkTreeWidgetItem`
'''
if not self._itemInTree(item):
return
if self._selectionMode == TTkK.SelectionMode.NoSelection:
return
if self._selectionMode == TTkK.SelectionMode.SingleSelection:
self._selected = [item]
self.update()
return
if item not in self._selected:
self._selected.append(item)
self.update()

def deselectItem(self, item:TTkTreeWidgetItem) -> None:
'''
Removes the specified item from the current selection.

:param item: the item to be deselected
:type item: :py:class:`TTkTreeWidgetItem`
'''
if not self._selected:
return
if item in self._selected:
self._selected.remove(item)
self.update()
return
self._pruneSelection()

def setHeaderLabels(self, labels:List[TTkString]) -> None:
'''
Adds a column in the header for each item in the labels list, and sets the label for each column.
Expand Down Expand Up @@ -570,7 +659,7 @@ def mouseDoubleClickEvent(self, evt:TTkMouseEvent) -> bool:
self.itemExpanded.emit(item)
else:
self.itemCollapsed.emit(item)
self._selected = [item]
self.setCurrentItem(item)
col = -1
for i, c in enumerate(self._columnsPos):
if x < c:
Expand Down
15 changes: 9 additions & 6 deletions libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/treewidgetitem.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,9 @@ def emitDataChanged(self):

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._parent = parent
child._sortOrder = parent._sortOrder
child._sortColumn = parent._sortColumn
child.dataChanged.connect(self.emitDataChanged)
child._sizeChanged.connect(self._childrenSizeChangedHandler)

Expand Down Expand Up @@ -165,18 +165,21 @@ def takeChild(self, index) -> Optional[TTkTreeWidgetItem]:
0<= index < len(self._children) ):
return None
child = self._children.pop(index)
child._parent = None
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
children = self._children.copy()
for child in children:
child._parent = None
child.dataChanged.disconnect(self.emitDataChanged)
child._sizeChanged.disconnect(self._childrenSizeChangedHandler)
self._childrenSizeChangedHandler(None, -self._total_size)
self._children = []
self.emitDataChanged()
return children

Expand Down Expand Up @@ -421,15 +424,15 @@ def addChild(self, child:TTkTreeWidgetItem) -> None:
self._children = _TTkTreeChildren(self)
self._children._childrenSizeChanged.connect(self._sizeChangedHandler)
self._children.dataChanged.connect(self.emitDataChanged)
child = self._children.addChild(self, child)
self._children.addChild(self, child)
self._setDefaultIcon()

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._children.addChildren(self, children)
self._setDefaultIcon()

def removeChild(self, child:TTkTreeWidgetItem) -> None:
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@
"mypy>=1.15.0"
]
docs = [
"Sphinx==8.2.3",
"sphinx-book-theme==1.1.4"
"Sphinx==8.2.3; python_version>='3.11'",
"sphinx-book-theme==1.1.4; python_version>='3.11'"
]

[tool.setuptools]
Expand Down
Loading
Loading