Browse Source

TTkTable: added per cell item selection flags

pull/272/head
Eugenio Parodi 1 year ago
parent
commit
50992d3332
  1. 26
      TermTk/TTkAbstract/abstracttablemodel.py
  2. 24
      TermTk/TTkCore/constant.py
  3. 6
      TermTk/TTkWidgets/TTkModelView/tablemodellist.py
  4. 113
      TermTk/TTkWidgets/TTkModelView/tablewidget.py
  5. 61
      tests/t.ui/test.ui.032.table.08.py

26
TermTk/TTkAbstract/abstracttablemodel.py

@ -177,7 +177,7 @@ class TTkAbstractTableModel():
The base class implementation returns false. This function and :meth:`data` must be reimplemented for editable models.
:param row: the row position od the data
:param row: the row position of the data
:type row: int
:param col: the column position of the data
:type col: int
@ -192,6 +192,11 @@ class TTkAbstractTableModel():
'''
Returns the :class:`~TermTk.TTkCore.string.TTkString` reprsents the ddata stored in the row/column.
:param row: the row position of the data
:type row: int
:param col: the column position of the data
:type col: int
:return: :class:`~TermTk.TTkCore.string.TTkString`
'''
data = self.data(row,col)
@ -222,6 +227,25 @@ class TTkAbstractTableModel():
return TTkString(str(pos))
return TTkString()
def flags(self, row:int, col:int) -> TTkK.ItemFlag:
'''
Returns the item flags for the given row,column.
The base class implementation returns a combination of flags that
enables the item (:class:`~TermTk.TTkCore.constant.TTkConstant.ItemFlag.ItemIsEnabled`)
and allows it to be selected (:class:`~TermTk.TTkCore.constant.TTkConstant.ItemFlag.ItemIsSelectable`).
:param row: the row position od the data
:type row: int
:param col: the column position of the data
:type col: int
:return: :class:`~TermTk.TTkCore.constant.TTkConstant.ItemFlag`
'''
return (
TTkK.ItemFlag.ItemIsEnabled |
TTkK.ItemFlag.ItemIsSelectable )
def sort(self, column:int, order:TTkK.SortOrder) -> None:
'''
Sorts the model by column in the given order.

24
TermTk/TTkCore/constant.py

@ -424,6 +424,30 @@ class TTkConstant:
ClearAndSelect = Clear | Select
'''A combination of Clear and Select, provided for convenience.'''
class ItemFlag(int):
''':class:`ItemFlag` describes the properties of an item
.. autosummary::
NoItemFlags
ItemIsSelectable
ItemIsEditable
ItemIsEnabled
'''
NoItemFlags = 0x0000
'''It does not have any properties set.'''
ItemIsSelectable = 0x0001
'''It can be selected.'''
ItemIsEditable = 0x0002
'''It can be edited.'''
# ItemIsDragEnabled = 0x0004
# '''It can be dragged.'''
# ItemIsDropEnabled = 0x0008
# '''It can be used as a drop target.'''
# ItemIsUserCheckable = 0x0010
# '''It can be checked or unchecked by the user.'''
ItemIsEnabled = 0x0020
'''The user can interact with the item.'''
# LayoutItem Types
class LayoutItemTypes(int):
'''Types used internally in :mod:`~TermTk.TTkLayouts`

6
TermTk/TTkWidgets/TTkModelView/tablemodellist.py

@ -104,6 +104,12 @@ class TTkTableModelList(TTkAbstractTableModel):
return self._vheader[num]
return super().headerData(num, orientation)
def flags(self, row:int, col:int) -> TTkK.ItemFlag:
return (
TTkK.ItemFlag.ItemIsEnabled |
TTkK.ItemFlag.ItemIsEditable |
TTkK.ItemFlag.ItemIsSelectable )
def sort(self, column:int, order:TTkK.SortOrder) -> None:
if column == -1:
self._data = self._dataOriginal

113
TermTk/TTkWidgets/TTkModelView/tablewidget.py

@ -262,9 +262,11 @@ class TTkTableWidget(TTkAbstractScrollView):
def _restoreSnapshot(self, snapId:int,newData=True):
rows = self._tableModel.rowCount()
cols = self._tableModel.columnCount()
self._selected = [[False]*cols for _ in range(rows)]
self.clearSelection()
for i in self._snapshot[snapId][1:]:
self._selected[i.dataIndex.row()][i.dataIndex.col()]=True
row=i.dataIndex.row()
col=i.dataIndex.col()
self.setSelection(pos=(col,row),size=(1,1),flags=TTkK.TTkItemSelectionModel.Select)
i.dataIndex.setData(i.newData if newData else i.oldData)
cpsi:TTkModelIndex = self._snapshot[snapId][0]
self._currentPos = (cpsi.row(),cpsi.col())
@ -456,7 +458,7 @@ class TTkTableWidget(TTkAbstractScrollView):
self._rowsPos = [1+x for x in range(rows)]
# self._selectedBase = sb = [False]*cols
# self._selected = [sb]*rows
self._selected = [[False]*cols for _ in range(rows)]
self.clearSelection()
# Overridden function
def viewFullAreaSize(self) -> tuple[int, int]:
@ -472,6 +474,28 @@ class TTkTableWidget(TTkAbstractScrollView):
def viewDisplayedSize(self) -> tuple[int, int]:
return self.size()
def clearSelection(self) -> None:
'''
Deselects all selected items.
The current index will not be changed.
'''
rows = self._tableModel.rowCount()
cols = self._tableModel.columnCount()
self._selected = [[False]*cols for _ in range(rows)]
self.update()
def selectAll(self) -> None:
'''
Selects all items in the view.
This function will use the selection behavior set on the view when selecting.
'''
rows = self._tableModel.rowCount()
cols = self._tableModel.columnCount()
flagFunc = self._tableModel.flags
cmp = TTkK.ItemFlag.ItemIsSelectable
self._selected = [[cmp==(cmp&flagFunc(_r,_c)) for _c in range(cols)] for _r in range(rows)]
self.update()
def setSelection(self, pos:tuple[int,int], size:tuple[int,int], flags:TTkK.TTkItemSelectionModel) -> None:
'''
Selects the items within the given rect and in accordance with the specified selection flags.
@ -485,12 +509,14 @@ class TTkTableWidget(TTkAbstractScrollView):
'''
x,y = pos
w,h = size
flagFunc = self._tableModel.flags
cmp = TTkK.ItemFlag.ItemIsSelectable
if flags & (TTkK.TTkItemSelectionModel.Clear|TTkK.TTkItemSelectionModel.Deselect):
for line in self._selected[y:y+h]:
line[x:x+w]=[False]*w
elif flags & TTkK.TTkItemSelectionModel.Select:
for line in self._selected[y:y+h]:
line[x:x+w]=[True]*w
for _r, line in enumerate(self._selected[y:y+h],y):
line[x:x+w]=[cmp==(cmp&flagFunc(_r,_c)) for _c in range(x,x+w)]
self.update()
def selectRow(self, row:int) -> None:
@ -501,18 +527,44 @@ class TTkTableWidget(TTkAbstractScrollView):
:type row: int
'''
cols = self._tableModel.columnCount()
self._selected[row] = [True]*cols
cmp = TTkK.ItemFlag.ItemIsSelectable
flagFunc = self._tableModel.flags
self._selected[row] = [cmp==(cmp&flagFunc(row,col)) for col in range(cols)]
self.update()
def selectColumn(self, column:int) -> None:
def selectColumn(self, col:int) -> None:
'''
Selects the given column in the table view
:param column: the column to be selected
:param col: the column to be selected
:type col: int
'''
cmp = TTkK.ItemFlag.ItemIsSelectable
flagFunc = self._tableModel.flags
for row,line in enumerate(self._selected):
line[col] = cmp==(cmp&flagFunc(row,col))
self.update()
def unselectRow(self, row:int) -> None:
'''
Unselects the given row in the table view
:param row: the row to be unselected
:type row: int
'''
cols = self._tableModel.columnCount()
self._selected[row] = [False]*cols
self.update()
def unselectColumn(self, column:int) -> None:
'''
Unselects the given column in the table view
:param column: the column to be unselected
:type column: int
'''
for row in self._selected:
self._selected[column] = True
for line in self._selected:
line[column] = False
self.update()
@pyTTkSlot()
@ -944,8 +996,8 @@ class TTkTableWidget(TTkAbstractScrollView):
# Mark only the current cell as aselected
rows = self._tableModel.rowCount()
cols = self._tableModel.columnCount()
self._selected = [[False]*cols for _ in range(rows)]
self._selected[row][col]=True
self.clearSelection()
self.setSelection(pos=(col,row),size=(1,1),flags=TTkK.TTkItemSelectionModel.Select)
data = self._tableModel.data(row, col)
if type(data) is str:
@ -1154,31 +1206,38 @@ class TTkTableWidget(TTkAbstractScrollView):
row,col = self._findCell(x,y, headers=True)
if not row==col==-1:
self._dragPos = [(row,col),(row,col)]
rows = self._tableModel.rowCount()
cols = self._tableModel.columnCount()
_ctrl = evt.mod==TTkK.ControlModifier
if row==col==-1:
# Corner Press
# Select Everything
self._selected = [[True]*cols for _ in range(rows)]
self.selectAll()
elif col==-1:
# Row select
state = all(self._selected[row])
flagFunc = self._tableModel.flags
cmp = TTkK.ItemFlag.ItemIsSelectable
state = all(_sel for i,_sel in enumerate(self._selected[row]) if flagFunc(row,i)&cmp)
if not _ctrl:
self._selected = [[False]*cols for _ in range(rows)]
self._selected[row] = [not state]*cols
self.clearSelection()
if state:
self.unselectRow(row)
else:
self.selectRow(row)
elif row==-1:
# Col select
state = all(line[col] for line in self._selected)
flagFunc = self._tableModel.flags
cmp = TTkK.ItemFlag.ItemIsSelectable
state = all(_sel[col] for i,_sel in enumerate(self._selected) if flagFunc(i,col)&cmp)
if not _ctrl:
self._selected = [[False]*cols for _ in range(rows)]
for line in self._selected:
line[col] = not state
self.clearSelection()
if state:
self.unselectColumn(col)
else:
self.selectColumn(col)
else:
# Cell Select
self._currentPos = (row,col)
if _ctrl: self._selected[row][col] = not self._selected[row][col]
else: self._selected[row][col] = True
self.setSelection(pos = (col,row), size = (1,1),
flags = TTkK.TTkItemSelectionModel.Clear if (self._selected[row][col] and _ctrl) else TTkK.TTkItemSelectionModel.Select)
self._hoverPos = None
self.update()
return True
@ -1246,7 +1305,7 @@ class TTkTableWidget(TTkAbstractScrollView):
state = self._selected[max(0,rowa)][max(0,cola)]
else:
# Clear the selection if no ctrl has been pressed
self._selected = [[False]*cols for _ in range(rows)]
self.clearSelection()
if rowa == -1:
cola,colb=min(cola,colb),max(cola,colb)
@ -1258,8 +1317,8 @@ class TTkTableWidget(TTkAbstractScrollView):
cola,colb=min(cola,colb),max(cola,colb)
rowa,rowb=min(rowa,rowb),max(rowa,rowb)
for line in self._selected[rowa:rowb+1]:
line[cola:colb+1] = [state]*(colb-cola+1)
self.setSelection(pos = (cola,rowa), size = (colb-cola+1,rowb-rowa+1),
flags = TTkK.TTkItemSelectionModel.Select if state else TTkK.TTkItemSelectionModel.Clear)
self._hoverPos = None
self._dragPos = None

61
tests/t.ui/test.ui.032.table.08.py

@ -137,6 +137,17 @@ class MyTableModel(ttk.TTkTableModelList):
return f"{prefix[num%len(prefix)]}:{num:03}"
return super().headerData(num, orientation)
def flags(self, row: int, col: int) -> ttk.TTkConstant.ItemFlag:
if col==0:
return (
ttk.TTkK.ItemFlag.ItemIsEnabled |
ttk.TTkK.ItemFlag.ItemIsSelectable )
if col==1:
return (
ttk.TTkK.ItemFlag.ItemIsEnabled |
ttk.TTkK.ItemFlag.ItemIsEditable )
return super().flags(row, col)
txt1 = "Text"
txt2 = txt1*5
@ -255,32 +266,34 @@ tableStyle5 = {'default': defaultStyle|{
'selectedColor': ttk.TTkColor.bg("#FFAA66"),
'separatorColor': ttk.TTkColor.fg("#330055")+ttk.TTkColor.bg("#660066")} }
quitBtn = ttk.TTkButton(parent=controls, pos=(0,0), size=(3,6), text="Q\nU\nI\nT")
quitBtn = ttk.TTkButton(parent=controls, pos=(0,0), size=(5,6), text="Q\nU\nI\nT")
quitBtn.clicked.connect(root.quit)
offsetQuit = 6
# Header Checkboxes
ttk.TTkLabel(parent=controls, pos=(4,0), text="Header:")
ht = ttk.TTkCheckbox(parent=controls, pos=( 4,1), size=(8,1), text=' Top ',checked=True)
hl = ttk.TTkCheckbox(parent=controls, pos=(13,1), size=(8,1), text=' Left',checked=True)
ttk.TTkLabel(parent=controls, pos=(offsetQuit,0), text="Header:")
ht = ttk.TTkCheckbox(parent=controls, pos=( offsetQuit ,1), size=(8,1), text=' Top ',checked=True)
hl = ttk.TTkCheckbox(parent=controls, pos=( offsetQuit+9,1), size=(8,1), text=' Left',checked=True)
ht.toggled.connect(table.horizontalHeader().setVisible)
hl.toggled.connect(table.verticalHeader().setVisible)
# Lines/Separator Checkboxes
ttk.TTkLabel(parent=controls, pos=(4,2), text="Lines:")
vli = ttk.TTkCheckbox(parent=controls, pos=( 4,3), size=(5,1), text=' V',checked=True)
hli = ttk.TTkCheckbox(parent=controls, pos=(13,3), size=(5,1), text=' H',checked=True)
ttk.TTkLabel(parent=controls, pos=(offsetQuit,2), text="Lines:")
vli = ttk.TTkCheckbox(parent=controls, pos=(offsetQuit ,3), size=(5,1), text=' V',checked=True)
hli = ttk.TTkCheckbox(parent=controls, pos=(offsetQuit+9,3), size=(5,1), text=' H',checked=True)
vli.toggled.connect(table.setVSeparatorVisibility)
hli.toggled.connect(table.setHSeparatorVisibility)
# Themes Control
t1 = ttk.TTkRadioButton(parent=controls, pos=(23,1), size=(11,1), text=' Theme 1', radiogroup='Themes', checked=True)
t2 = ttk.TTkRadioButton(parent=controls, pos=(23,2), size=(11,1), text=' Theme 2', radiogroup='Themes')
t3 = ttk.TTkRadioButton(parent=controls, pos=(23,3), size=(11,1), text=' Theme 3', radiogroup='Themes')
t4 = ttk.TTkRadioButton(parent=controls, pos=(23,4), size=(11,1), text=' Theme 4', radiogroup='Themes')
t5 = ttk.TTkRadioButton(parent=controls, pos=(23,5), size=(11,1), text=' Theme 5', radiogroup='Themes')
t1 = ttk.TTkRadioButton(parent=controls, pos=(offsetQuit+19,1), size=(11,1), text=' Theme 1', radiogroup='Themes', checked=True)
t2 = ttk.TTkRadioButton(parent=controls, pos=(offsetQuit+19,2), size=(11,1), text=' Theme 2', radiogroup='Themes')
t3 = ttk.TTkRadioButton(parent=controls, pos=(offsetQuit+19,3), size=(11,1), text=' Theme 3', radiogroup='Themes')
t4 = ttk.TTkRadioButton(parent=controls, pos=(offsetQuit+19,4), size=(11,1), text=' Theme 4', radiogroup='Themes')
t5 = ttk.TTkRadioButton(parent=controls, pos=(offsetQuit+19,5), size=(11,1), text=' Theme 5', radiogroup='Themes')
t1.clicked.connect(lambda : table.mergeStyle(tableStyle1))
t2.clicked.connect(lambda : table.mergeStyle(tableStyle2))
@ -290,10 +303,10 @@ t5.clicked.connect(lambda : table.mergeStyle(tableStyle5))
# Model Picker
m1 = ttk.TTkRadioButton(parent=controls, pos=(36,0), size=(11,1), text=' Model 1', radiogroup='Models', checked=True)
m2 = ttk.TTkRadioButton(parent=controls, pos=(36,1), size=(11,1), text=' Model 2', radiogroup='Models')
m3 = ttk.TTkRadioButton(parent=controls, pos=(36,2), size=(11,1), text=' Model 3', radiogroup='Models')
m4 = ttk.TTkRadioButton(parent=controls, pos=(36,3), size=(11,1), text=' Model 4', radiogroup='Models')
m1 = ttk.TTkRadioButton(parent=controls, pos=(offsetQuit+32,0), size=(11,1), text=' Model 1', radiogroup='Models', checked=True)
m2 = ttk.TTkRadioButton(parent=controls, pos=(offsetQuit+32,1), size=(11,1), text=' Model 2', radiogroup='Models')
m3 = ttk.TTkRadioButton(parent=controls, pos=(offsetQuit+32,2), size=(11,1), text=' Model 3', radiogroup='Models')
m4 = ttk.TTkRadioButton(parent=controls, pos=(offsetQuit+32,3), size=(11,1), text=' Model 4', radiogroup='Models')
m1.clicked.connect(lambda : table.setModel(table_model1))
m2.clicked.connect(lambda : table.setModel(table_model2))
@ -302,30 +315,30 @@ m4.clicked.connect(lambda : table.setModel(table_model4))
if args.csv:
table_model_csv = ttk.TTkTableModelCSV(fd=args.csv)
m_csv = ttk.TTkRadioButton(parent=controls, pos=(36,4), size=(11,1), text=' CSV', radiogroup='Models')
m_csv = ttk.TTkRadioButton(parent=controls, pos=(offsetQuit+32,4), size=(11,1), text=' CSV', radiogroup='Models')
m_csv.clicked.connect(lambda : table.setModel(table_model_csv))
# Resize Button
rcb = ttk.TTkButton(parent=controls, pos=( 4,5), size=( 3,1), text="C", border=False)
rrb = ttk.TTkButton(parent=controls, pos=( 7,5), size=( 3,1), text="R", border=False)
rb = ttk.TTkButton(parent=controls, pos=(10,5), size=(11,1), text="Resize", border=False)
rcb = ttk.TTkButton(parent=controls, pos=(offsetQuit ,5), size=( 3,1), text="C", border=False)
rrb = ttk.TTkButton(parent=controls, pos=(offsetQuit+3,5), size=( 3,1), text="R", border=False)
rb = ttk.TTkButton(parent=controls, pos=(offsetQuit+6,5), size=(11,1), text="Resize", border=False)
rrb.clicked.connect(table.resizeRowsToContents)
rcb.clicked.connect(table.resizeColumnsToContents)
rb.clicked.connect( table.resizeRowsToContents)
rb.clicked.connect( table.resizeColumnsToContents)
controlAndLogsSplitter.addWidget(controls, size=50)
controlAndLogsSplitter.addWidget(controls, size=51)
controlAndLogsSplitter.addWidget(ttk.TTkLogViewer())
cbs = ttk.TTkCheckbox(parent=controls, pos=( 36,5), size=(8,1), text='-Sort', checked=False)
cbs = ttk.TTkCheckbox(parent=controls, pos=(offsetQuit+32,5), size=(8,1), text='-Sort', checked=False)
cbs.toggled.connect(table.setSortingEnabled)
wtb = ttk.TTkButton(parent=controls, pos=(46,4), size=( 4,1), text="👌", border=False)
wkb = ttk.TTkButton(parent=controls, pos=(46,5), size=( 4,1), text="🤌", border=False)
wtb = ttk.TTkButton(parent=controls, pos=(offsetQuit+41,4), size=( 4,1), text="👌", border=False)
wkb = ttk.TTkButton(parent=controls, pos=(offsetQuit+41,5), size=( 4,1), text="🤌", border=False)
@ttk.pyTTkSlot()

Loading…
Cancel
Save