Skip to content

Commit de0aefc

Browse files
feat(Tree): add multiselect (#436)
1 parent e278104 commit de0aefc

2 files changed

Lines changed: 47 additions & 23 deletions

File tree

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

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -166,12 +166,14 @@ def itemCollapsed(self) -> pyTTkSignal:
166166

167167
__slots__ = ( '_rootItem', '_cache',
168168
'_header', '_columnsPos',
169+
'_selectionMode',
169170
'_selectedId', '_selected', '_separatorSelected',
170171
'_sortColumn', '_sortOrder', '_sortingEnabled',
171172
'_dndMode',
172173
# Signals
173174
'_itemChanged', '_itemClicked', '_itemDoubleClicked', '_itemExpanded', '_itemCollapsed', '_itemActivated'
174175
)
176+
175177
@dataclass(frozen=True)
176178
class _Cache:
177179
item: TTkTreeWidgetItem
@@ -180,6 +182,9 @@ class _Cache:
180182
widgets: list
181183
firstLine: bool
182184

185+
_cache:List[_Cache]
186+
_selected:List[TTkTreeWidgetItem]
187+
183188
@dataclass(frozen=True)
184189
class _DropTreeData:
185190
widget: TTkAbstractScrollView
@@ -188,13 +193,16 @@ class _DropTreeData:
188193
def __init__(self, *,
189194
header=None,
190195
sortingEnabled=True,
196+
selectionMode:TTkK.SelectionMode=TTkK.SelectionMode.SingleSelection,
191197
dragDropMode:TTkK.DragDropMode=TTkK.DragDropMode.NoDragDrop,
192198
**kwargs) -> None:
193199
'''
194200
:param header: define the header labels of each column, defaults to []
195201
:type header: list[TTkString], optional
196202
:param sortingEnabled: enable the column sorting, defaults to False
197203
:type sortingEnabled: bool, optional
204+
:param selectionMode: This property controls whether the user can select one or many items, defaults to :py:class:`TTkK.SelectionMode.SingleSelection`.
205+
:type selectionMode: :py:class:`TTkK.SelectionMode`, optional
198206
:param dragDropMode: This property holds the drag and drop event the view will act upon, defaults to :py:class:`TTkK.DragDropMode.NoDragDrop`.
199207
:type dragDropMode: :py:class:`TTkK.DragDropMode`, optional
200208
'''
@@ -206,8 +214,9 @@ def __init__(self, *,
206214
self._itemExpanded = pyTTkSignal(TTkTreeWidgetItem)
207215
self._itemCollapsed = pyTTkSignal(TTkTreeWidgetItem)
208216

217+
self._selectionMode = selectionMode
209218
self._dndMode = dragDropMode
210-
self._selected = None
219+
self._selected = []
211220
self._selectedId = None
212221
self._separatorSelected = None
213222
self._header = header if header else []
@@ -283,10 +292,18 @@ def indexOfTopLevelItem(self, item:TTkTreeWidgetItem) -> int:
283292
'''indexOfTopLevelItem'''
284293
return self._rootItem.indexOfChild(item)
285294

295+
def selectionMode(self) -> TTkK.SelectionMode:
296+
'''selectionMode'''
297+
return self._selectionMode
298+
299+
def setSelectionMode(self, mode:TTkK.SelectionMode) -> None:
300+
'''setSelectionMode'''
301+
self._selectionMode = mode
302+
286303
def selectedItems(self) -> list[TTkTreeWidgetItem]:
287304
'''selectedItems'''
288305
if self._selected:
289-
return [self._selected]
306+
return self._selected
290307
return None
291308

292309
def setHeaderLabels(self, labels:TTkString) -> None:
@@ -395,11 +412,11 @@ def mouseDoubleClickEvent(self, evt:TTkMouseEvent) -> bool:
395412
self.itemExpanded.emit(item)
396413
else:
397414
self.itemCollapsed.emit(item)
398-
if self._selected:
399-
self._selected.setSelected(False)
415+
for _s in self._selected:
416+
_s.setSelected(False)
400417
self._selectedId = y
401-
self._selected = item
402-
self._selected.setSelected(True)
418+
self._selected = [item]
419+
item.setSelected(True)
403420
col = -1
404421
for i, c in enumerate(self._columnsPos):
405422
if x < c:
@@ -449,11 +466,16 @@ def mousePressEvent(self, evt:TTkMouseEvent) -> bool:
449466
else:
450467
self.itemCollapsed.emit(item)
451468
else:
452-
if self._selected:
453-
self._selected.setSelected(False)
454-
self._selectedId = y
455-
self._selected = item
456-
self._selected.setSelected(True)
469+
if self._selectionMode in (TTkK.SelectionMode.SingleSelection,TTkK.SelectionMode.MultiSelection):
470+
if not (
471+
bool(evt.mod & TTkK.ControlModifier) and
472+
self._selectionMode == TTkK.SelectionMode.MultiSelection ):
473+
for _s in self._selected:
474+
_s.setSelected(False)
475+
self._selected.clear()
476+
self._selectedId = y
477+
self._selected.append(item)
478+
item.setSelected(True)
457479
col = -1
458480
for i, c in enumerate(self._columnsPos):
459481
if x < c:
@@ -492,14 +514,16 @@ def mouseDragEvent(self, evt:TTkMouseEvent) -> bool:
492514
elif ( self._dndMode & TTkK.DragDropMode.AllowDrag and
493515
evt.key == TTkMouseEvent.LeftButton and self._selected ):
494516
drag = TTkDrag()
495-
data = TTkTreeWidget._DropTreeData(widget=self,items=[self._selected])
496-
text = self._selected.data(0)
497-
if text.termWidth() > 30:
498-
text = '['+text.substring(to=27)+'...]'
499-
else:
500-
text = '['+text+']'
501-
pm = TTkCanvas(text.termWidth()+2,1)
502-
pm.drawTTkString(pos=(0,0),text=text)
517+
data = TTkTreeWidget._DropTreeData(widget=self,items=self._selected)
518+
text = [(_n.substring(to=27)+'...') if (_n:=_s.data(0)).termWidth()>30 else _n for _s in self._selected[:4]]
519+
dh = len(text) + 2
520+
dw = max(_t.termWidth() for _t in text[:3])+2
521+
pm = TTkCanvas(width=dw,height=dh)
522+
for _y,_t in enumerate(text[:3],1):
523+
pm.drawTTkString(pos=(1,_y),text=_t)
524+
if len(self._selected) > 3:
525+
pm.drawText(pos=(1,4),text='...')
526+
pm.drawBox(pos=(0,0),size=(dw,dh))
503527
drag.setPixmap(pm)
504528
drag.setData(data)
505529
drag.exec()

libs/pyTermTk/TermTk/TTkWidgets/texedit.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,7 +1191,7 @@ def currentColorChanged(self) -> pyTTkSignal:
11911191
11921192
This signal is emitted if the current character color has changed,
11931193
for example caused by a change of the cursor position.
1194-
1194+
11951195
:param color: the new color
11961196
:type color: :py:class:`TTkColor`
11971197
'''
@@ -1202,7 +1202,7 @@ def cursorPositionChanged(self) -> pyTTkSignal:
12021202
.. seealso:: this method is forwarded to :py:meth:`TTkTextEditView.cursorPositionChanged`
12031203
12041204
This signal is emitted whenever the position of the cursor changed.
1205-
1205+
12061206
:param cursor: the cursor changed.
12071207
:type cursor: :py:class:`TTkTextCursor`
12081208
'''
@@ -1214,7 +1214,7 @@ def undoAvailable(self) -> pyTTkSignal:
12141214
12151215
This signal is emitted whenever undo operations become available (available is true)
12161216
or unavailable (available is false).
1217-
1217+
12181218
:param available: the availability of undo
12191219
:type available: bool
12201220
'''
@@ -1226,7 +1226,7 @@ def redoAvailable(self) -> pyTTkSignal:
12261226
12271227
This signal is emitted whenever redo operations become available (available is true)
12281228
or unavailable (available is false).
1229-
1229+
12301230
:param available: the availability of redo
12311231
:type available: bool
12321232
'''

0 commit comments

Comments
 (0)