Browse Source

feat(table): add edit proxy widget to allow a common extensible interface for the cell editing (#517)

pull/518/head
Pier CeccoPierangioliEugenio 4 months ago committed by GitHub
parent
commit
9331c03689
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/__init__.py
  2. 484
      libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/table_edit_proxy.py
  3. 203
      libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tablewidget.py
  4. 1
      tools/check.import.sh

1
libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/__init__.py

@ -11,6 +11,7 @@ from .tablewidget import *
from .tablewidgetitem import *
from .tablemodellist import *
from .tablemodelcsv import *
from .table_edit_proxy import *
if find_spec('sqlite3'):
from .tablemodelsqlite3 import *

484
libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/table_edit_proxy.py

@ -0,0 +1,484 @@
# MIT License
#
# Copyright (c) 2025 Eugenio Parodi <ceccopierangiolieugenio AT googlemail DOT com>
#
# 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.
from __future__ import annotations
__all__ = ['TTkTableProxyEdit', 'TTkTableEditLeaving', 'TTkTableProxyEditWidget']
from dataclasses import dataclass
from enum import Enum, auto
from typing import Union, Tuple, Type, List, Optional, Any
from TermTk.TTkCore.constant import TTkK
from TermTk.TTkCore.string import TTkString, TTkStringType
from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot
from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent
from TermTk.TTkWidgets.widget import TTkWidget
from TermTk.TTkWidgets.texedit import TTkTextEdit, TTkTextEditView
from TermTk.TTkWidgets.spinbox import TTkSpinBox
from TermTk.TTkWidgets.TTkPickers.textpicker import TTkTextPicker
class TTkTableEditLeaving(Enum):
''' Enum indicating the direction the user is leaving the table cell editor
Used by :py:class:`TTkTableProxyEditWidget` to signal navigation intent
'''
NONE = auto()
TOP = auto()
BOTTOM = auto()
LEFT = auto()
RIGHT = auto()
class TTkTableProxyEditWidget(TTkWidget):
''' Protocol for table cell editor widgets
Any widget implementing these signals can be used as a table cell editor.
The protocol ensures consistent behavior across different editor types.
Example implementation::
class MyEditor(TTkLineEdit):
__slots__ = ('leavingTriggered', 'dataChanged')
def __init__(self, **kwargs):
self.leavingTriggered = pyTTkSignal(TTkTableEditLeaving)
self.dataChanged = pyTTkSignal(object)
super().__init__(**kwargs)
self.textChanged.connect(self.dataChanged.emit)
'''
leavingTriggered: pyTTkSignal
'''
This signal is emitted when the user navigates out of the editor
:param direction: The direction of navigation
:type direction: TTkTableEditLeaving
'''
dataChanged: pyTTkSignal
'''
This signal is emitted when the editor data changes
:param data: The new data value
:type data: object
'''
@staticmethod
def editWidgetFactory(data: object) -> TTkTableProxyEditWidget:
''' Factory method to create an editor widget from data
:param data: The initial data value for the editor
:type data: object
:return: A new editor widget instance
:rtype: TTkTableProxyEditWidget
:raises NotImplementedError: Must be implemented by subclasses
'''
raise NotImplementedError()
def getCellData(self) -> object:
''' Get the current data value from the editor
:return: The current cell data
:rtype: object
:raises NotImplementedError: Must be implemented by subclasses
'''
raise NotImplementedError()
def proxyDispose(self) -> None:
''' Clean up the editor widget and disconnect signals
'''
self.leavingTriggered.clear()
self.dataChanged.clear()
self.close()
class _TextEditViewProxy(TTkTextEditView, TTkTableProxyEditWidget):
''' Text editor view for table cells
Extends :py:class:`TTkTextEditView` with table-specific signals
for navigation and data change notification.
'''
__slots__ = ('leavingTriggered', 'dataChanged')
leavingTriggered: pyTTkSignal
'''
This signal is emitted when the user navigates out of the editor
:param direction: The direction of navigation
:type direction: TTkTableEditLeaving
'''
dataChanged: pyTTkSignal
'''
This signal is emitted when the text content changes
:param data: The new text value
:type data: Union[str, TTkString]
'''
def __init__(self, **kwargs):
''' Initialize the text edit view proxy
:param kwargs: Additional keyword arguments passed to parent
:type kwargs: dict
'''
self.leavingTriggered = pyTTkSignal(TTkTableEditLeaving)
self.dataChanged = pyTTkSignal(object)
super().__init__(**kwargs)
self.textChanged.connect(self._emitDataChanged)
def keyEvent(self, evt: TTkKeyEvent) -> bool:
''' Handle keyboard events for navigation and data entry
:param evt: The keyboard event
:type evt: TTkKeyEvent
:return: True if event was handled, False otherwise
:rtype: bool
'''
if (evt.type == TTkK.SpecialKey):
_cur = self.textCursor()
_doc = self.document()
_line = _cur.anchor().line
_pos = _cur.anchor().pos
_lineCount = _doc.lineCount()
if evt.mod == TTkK.NoModifier:
if evt.key == TTkK.Key_Enter:
self.leavingTriggered.emit(TTkTableEditLeaving.BOTTOM)
return True
elif evt.key == TTkK.Key_Up:
if _line == 0:
self.leavingTriggered.emit(TTkTableEditLeaving.TOP)
return True
elif evt.key == TTkK.Key_Down:
if _lineCount == 1:
self.leavingTriggered.emit(TTkTableEditLeaving.BOTTOM)
return True
elif evt.key == TTkK.Key_Left:
if _pos == _line == 0:
self.leavingTriggered.emit(TTkTableEditLeaving.LEFT)
return True
elif evt.key == TTkK.Key_Right:
if _lineCount == 1 and _pos == len(_doc.toPlainText()):
self.leavingTriggered.emit(TTkTableEditLeaving.RIGHT)
return True
elif (evt.type == TTkK.SpecialKey and
evt.mod == TTkK.ControlModifier | TTkK.AltModifier and
evt.key == TTkK.Key_M):
evt.mod = TTkK.NoModifier
evt.key = TTkK.Key_Enter
return super().keyEvent(evt)
@pyTTkSlot()
def _emitDataChanged(self) -> None:
''' Emit dataChanged signal when text changes
'''
txt = self.toRawText()
val = str(txt) if txt.isPlainText() else txt
self.dataChanged.emit(val)
class _TextEditProxy(TTkTextEdit, TTkTableProxyEditWidget):
''' Text editor for table cells
Extends :py:class:`TTkTextEdit` with table-specific signals
for navigation and data change notification.
'''
__slots__ = ('leavingTriggered', 'dataChanged')
leavingTriggered: pyTTkSignal
'''
This signal is emitted when the user navigates out of the editor
:param direction: The direction of navigation
:type direction: TTkTableEditLeaving
'''
dataChanged: pyTTkSignal
'''
This signal is emitted when the text content changes
:param data: The new text value
:type data: Union[str, TTkString]
'''
def __init__(self, **kwargs):
''' Initialize the text edit proxy
:param kwargs: Additional keyword arguments passed to parent
:type kwargs: dict
'''
self.leavingTriggered = pyTTkSignal(TTkTableEditLeaving)
self.dataChanged = pyTTkSignal(object)
tew = _TextEditViewProxy()
super().__init__(**kwargs | {'textEditView': tew})
tew.leavingTriggered.connect(self.leavingTriggered.emit)
tew.dataChanged.connect(self.dataChanged.emit)
@staticmethod
def editWidgetFactory(data: Any) -> TTkTableProxyEditWidget:
''' Factory method to create a text editor from string data
:param data: The initial text value
:type data: Union[str, TTkString]
:return: A new text editor instance
:rtype: TTkTableProxyEditWidget
:raises ValueError: If data is not a string or TTkString
'''
if not isinstance(data, (TTkString, str)):
raise ValueError(f"{data} is not a TTkStringType")
te = _TextEditProxy()
te.setText(data)
return te
def getCellData(self) -> TTkStringType:
''' Get the current text value from the editor
:return: The current text content
:rtype: Union[str, TTkString]
'''
txt = self.toRawText()
val = str(txt) if txt.isPlainText() else txt
return val
class _SpinBoxProxy(TTkSpinBox, TTkTableProxyEditWidget):
''' Numeric editor for table cells
Extends :py:class:`TTkSpinBox` with table-specific signals
for navigation and data change notification.
'''
__slots__ = ('leavingTriggered', 'dataChanged')
leavingTriggered: pyTTkSignal
'''
This signal is emitted when the user navigates out of the editor
:param direction: The direction of navigation
:type direction: TTkTableEditLeaving
'''
dataChanged: pyTTkSignal
'''
This signal is emitted when the numeric value changes
:param data: The new numeric value
:type data: Union[int, float]
'''
def __init__(self, **kwargs):
''' Initialize the spin box proxy
:param kwargs: Additional keyword arguments passed to parent
:type kwargs: dict
'''
self.leavingTriggered = pyTTkSignal(TTkTableEditLeaving)
self.dataChanged = pyTTkSignal(object)
super().__init__(**kwargs)
self.valueChanged.connect(self.dataChanged.emit)
@staticmethod
def editWidgetFactory(data: Any) -> TTkTableProxyEditWidget:
''' Factory method to create a spin box from numeric data
:param data: The initial numeric value
:type data: Union[int, float]
:return: A new spin box instance
:rtype: TTkTableProxyEditWidget
:raises ValueError: If data is not an int or float
'''
if not isinstance(data, (int, float)):
raise ValueError(f"{data} is not a int or float")
sb = _SpinBoxProxy(
minimum=-1000000,
maximum=1000000,
value=data)
return sb
def getCellData(self) -> Union[float, int]:
''' Get the current numeric value from the editor
:return: The current spin box value
:rtype: Union[int, float]
'''
return self.value()
def keyEvent(self, evt: TTkKeyEvent) -> bool:
''' Handle keyboard events for navigation
:param evt: The keyboard event
:type evt: TTkKeyEvent
:return: True if event was handled, False otherwise
:rtype: bool
'''
if (evt.type == TTkK.SpecialKey):
if evt.mod == TTkK.NoModifier:
if evt.key == TTkK.Key_Enter:
self.leavingTriggered.emit(TTkTableEditLeaving.RIGHT)
return True
return super().keyEvent(evt)
class _TextPickerProxy(TTkTextPicker, TTkTableProxyEditWidget):
''' Rich text editor for table cells
Extends :py:class:`TTkTextPicker` with table-specific signals
for navigation and data change notification.
'''
__slots__ = ('leavingTriggered', 'dataChanged')
leavingTriggered: pyTTkSignal
'''
This signal is emitted when the user navigates out of the editor
:param direction: The direction of navigation
:type direction: TTkTableEditLeaving
'''
dataChanged: pyTTkSignal
'''
This signal is emitted when the rich text content changes
:param data: The new TTkString value
:type data: TTkString
'''
def __init__(self, **kwargs):
''' Initialize the text picker proxy
:param kwargs: Additional keyword arguments passed to parent
:type kwargs: dict
'''
self.leavingTriggered = pyTTkSignal(TTkTableEditLeaving)
self.dataChanged = pyTTkSignal(object)
super().__init__(**kwargs)
self.textChanged.connect(self._textChanged)
@pyTTkSlot()
def _textChanged(self):
''' Internal slot to emit dataChanged signal
'''
self.dataChanged.emit(self.getCellData())
@staticmethod
def editWidgetFactory(data: Any) -> TTkTableProxyEditWidget:
''' Factory method to create a text picker from string data
:param data: The initial text value
:type data: Union[str, TTkString]
:return: A new text picker instance
:rtype: TTkTableProxyEditWidget
:raises ValueError: If data is not a string or TTkString
'''
if not isinstance(data, (TTkString, str)):
raise ValueError(f"{data} is not a TTkStringType")
te = _TextPickerProxy(
text=data,
autoSize=False,
wrapMode=TTkK.NoWrap)
return te
def getCellData(self) -> TTkString:
''' Get the current rich text value from the editor
:return: The current TTkString content
:rtype: TTkString
'''
return self.getTTkString()
def keyEvent(self, evt: TTkKeyEvent) -> bool:
''' Handle keyboard events for navigation
:param evt: The keyboard event
:type evt: TTkKeyEvent
:return: True if event was handled, False otherwise
:rtype: bool
'''
if (evt.type == TTkK.SpecialKey):
if evt.mod == TTkK.NoModifier:
if evt.key == TTkK.Key_Enter:
self.leavingTriggered.emit(TTkTableEditLeaving.RIGHT)
return True
return super().keyEvent(evt)
@dataclass
class TTkProxyEditDef():
''' Definition for table cell editor proxy
:param types: Tuple of data types this editor handles (e.g., (int, float))
:type types: Tuple[type, ...]
:param class_def: Widget class implementing TTkTableProxyEditWidget protocol
:type class_def: Type[TTkTableProxyEditWidget]
:param rich: Whether this editor supports rich text formatting
:type rich: bool
'''
types: Tuple[type, ...]
class_def: Type[TTkTableProxyEditWidget]
rich: bool = False
class TTkTableProxyEdit():
''' Proxy class for managing table cell editors
Creates and configures appropriate editor widgets based on cell data type.
All editors implement the :py:class:`TTkTableProxyEditWidget` protocol.
Automatically selects the correct editor type:
- :py:class:`_SpinBoxProxy` for int and float values
- :py:class:`_TextEditProxy` for plain text strings
- :py:class:`_TextPickerProxy` for rich text (TTkString with formatting)
Example usage::
proxy = TTkTableProxyEdit()
editor = proxy.getProxyWidget(data=42, rich=False)
if editor:
editor.leavingTriggered.connect(handleNavigation)
editor.dataChanged.connect(handleDataChange)
'''
__slots__ = ('_proxies',)
_proxies: List[TTkProxyEditDef]
def __init__(self):
''' Initialize the table proxy edit manager
'''
self._proxies = [
TTkProxyEditDef(class_def=_SpinBoxProxy, types=(int, float)),
TTkProxyEditDef(class_def=_TextEditProxy, types=(str, TTkString)),
TTkProxyEditDef(class_def=_TextEditProxy, types=(str,), rich=True),
TTkProxyEditDef(class_def=_TextPickerProxy, types=(TTkString,), rich=True),
]
def getProxyWidget(self, data, rich: bool = False) -> Optional[TTkTableProxyEditWidget]:
''' Get an appropriate editor widget for the given data
:param data: The data value to edit
:type data: object
:param rich: Whether rich text editing is required
:type rich: bool
:return: An editor widget instance, or None if no suitable editor found
:rtype: Optional[TTkTableProxyEditWidget]
'''
for proxy in self._proxies:
if proxy.rich == rich and isinstance(data, proxy.types):
return proxy.class_def.editWidgetFactory(data)
for proxy in self._proxies:
if isinstance(data, proxy.types):
return proxy.class_def.editWidgetFactory(data)
return None

203
libs/pyTermTk/TermTk/TTkWidgets/TTkModelView/tablewidget.py

@ -27,7 +27,7 @@ __all__ = ['TTkTableWidget','TTkHeaderView']
from typing import Optional, List, Tuple, Callable, Iterator, Any, Protocol
from dataclasses import dataclass
from TermTk.TTkCore.log import TTkLog
from TermTk.TTkCore.helper import TTkHelper
from TermTk.TTkCore.constant import TTkK
from TermTk.TTkCore.string import TTkString
from TermTk.TTkCore.color import TTkColor
@ -42,6 +42,7 @@ from TermTk.TTkWidgets.texedit import TTkTextEdit
from TermTk.TTkWidgets.spinbox import TTkSpinBox
from TermTk.TTkWidgets.TTkPickers.textpicker import TTkTextPicker
from TermTk.TTkWidgets.TTkModelView.tablemodellist import TTkTableModelList, TTkModelIndex
from TermTk.TTkWidgets.TTkModelView.table_edit_proxy import TTkTableProxyEdit, TTkTableProxyEditWidget, TTkTableEditLeaving
from TermTk.TTkAbstract.abstractscrollview import TTkAbstractScrollView
from TermTk.TTkAbstract.abstracttablemodel import TTkAbstractTableModel
@ -251,6 +252,13 @@ class _DragPosType():
fr:Tuple[int,int]
to:Tuple[int,int]
@dataclass
class _ProxyWidgetLocation:
__slots__ = ('widget', 'row', 'col')
widget: TTkTableProxyEditWidget
row: int
col: int
class TTkTableWidget(TTkAbstractScrollView):
'''
A :py:class:`TTkTableWidget` implements a table view that displays items from a model.
@ -403,6 +411,7 @@ class TTkTableWidget(TTkAbstractScrollView):
'_sortColumn', '_sortOrder',
'_fastCheck', '_guessDataEdit',
'_snapshot', '_snapshotId',
'_edit_proxy', '_edit_proxy_widget',
# Signals
# '_cellActivated',
'_cellChanged',
@ -412,6 +421,8 @@ class TTkTableWidget(TTkAbstractScrollView):
)
_select_proxy:_SelectionProxy
_edit_proxy:TTkTableProxyEdit
_edit_proxy_widget:Optional[_ProxyWidgetLocation]
_snapshot:List[_SnapshotItems]
_hoverPos:Optional[Tuple[int,int]]
_currentPos:Optional[Tuple[int,int]]
@ -421,6 +432,7 @@ class TTkTableWidget(TTkAbstractScrollView):
def __init__(self, *,
tableModel:Optional[TTkAbstractTableModel]=None,
tableEditProxy:Optional[TTkTableProxyEdit]=None,
vSeparator:bool=True,
hSeparator:bool=True,
vHeader:bool=True,
@ -480,6 +492,8 @@ class TTkTableWidget(TTkAbstractScrollView):
self._verticalHeader = TTkHeaderView(visible=vHeader)
self._horizontallHeader = TTkHeaderView(visible=hHeader)
self._select_proxy = _SelectionProxy()
self._edit_proxy = tableEditProxy if tableEditProxy else TTkTableProxyEdit()
self._edit_proxy_widget = None
self._hoverPos = None
self._dragPos = None
self._currentPos = None
@ -500,8 +514,6 @@ class TTkTableWidget(TTkAbstractScrollView):
self._verticalHeader.visibilityUpdated.connect( self._headerVisibilityChanged)
self._horizontallHeader.visibilityUpdated.connect(self._headerVisibilityChanged)
def _saveSnapshot(self, items:list[_SnapItem], currentPos:TTkModelIndex) -> None:
self._snapshot = self._snapshot[:self._snapshotId] + [_SnapshotItems(pos=currentPos, items=items)]
self._snapshotId += 1
@ -734,6 +746,7 @@ class TTkTableWidget(TTkAbstractScrollView):
Deselects all selected items.
The current index will not be changed.
'''
self._removeProxyWidget()
self._select_proxy.clear()
self.update()
@ -742,6 +755,7 @@ class TTkTableWidget(TTkAbstractScrollView):
Selects all items in the view.
This function will use the selection behavior set on the view when selecting.
'''
self._removeProxyWidget()
self._select_proxy.selectAll()
self.update()
@ -756,6 +770,7 @@ class TTkTableWidget(TTkAbstractScrollView):
:param flags: the selection model used (i.e. :py:class:`TTkItemSelectionModel.Select`)
:type flags: :py:class:`TTkItemSelectionModel`
'''
self._removeProxyWidget()
self._select_proxy.setSelection(pos=pos, size=size, flags=flags)
self.update()
@ -766,6 +781,7 @@ class TTkTableWidget(TTkAbstractScrollView):
:param row: the row to be selected
:type row: int
'''
self._removeProxyWidget()
self._select_proxy.selectRow(row=row)
self.update()
@ -776,6 +792,7 @@ class TTkTableWidget(TTkAbstractScrollView):
:param col: the column to be selected
:type col: int
'''
self._removeProxyWidget()
self._select_proxy.selectColumn(col=col)
self.update()
@ -786,6 +803,7 @@ class TTkTableWidget(TTkAbstractScrollView):
:param row: the row to be unselected
:type row: int
'''
self._removeProxyWidget()
self._select_proxy.unselectRow(row=row)
self.update()
@ -796,6 +814,7 @@ class TTkTableWidget(TTkAbstractScrollView):
:param col: the column to be unselected
:type col: int
'''
self._removeProxyWidget()
self._select_proxy.unselectColumn(col=col)
self.update()
@ -1105,122 +1124,50 @@ class TTkTableWidget(TTkAbstractScrollView):
return row,col
def _editStr(self, x,y,w,h, row, col, data):
_te = TTkTextEdit(
parent=self, pos=(x, y), size=(w,h),
readOnly=False, wrapMode=TTkK.NoWrap)
_tev = _te.textEditView()
_te.setText(data)
_te.textCursor().movePosition(operation=TTkTextCursor.EndOfLine)
_te.setFocus()
@pyTTkSlot(bool)
def _processClose(change):
if change:
self.focusChanged.disconnect(_processClose)
txt = _te.toRawText()
val = str(txt) if txt.isPlainText() else txt
self._tableModel_setData([(row,col,val)])
self.update()
_te.close()
self.setFocus()
# Override the key event
_ke = _tev.keyEvent
_doc = _tev.document()
_cur = _tev.textCursor()
def _keyEvent(evt):
if ( evt.type == TTkK.SpecialKey):
_line = _cur.anchor().line
_pos = _cur.anchor().pos
_lineCount = _doc.lineCount()
# _lineLen
if evt.mod==TTkK.NoModifier:
if evt.key == TTkK.Key_Enter:
# self.enterPressed.emit(True)
self._moveCurrentCell(diff=(0,+1))
_processClose(True)
return True
elif evt.key == TTkK.Key_Up:
if _line == 0:
self._moveCurrentCell(diff=(0,-1))
_processClose(True)
return True
elif evt.key == TTkK.Key_Down:
if _lineCount == 1:
self._moveCurrentCell(diff=(0,+1))
_processClose(True)
return True
elif evt.key == TTkK.Key_Left:
if _pos == _line == 0:
self._moveCurrentCell(diff=(-1, 0))
_processClose(True)
return True
elif evt.key == TTkK.Key_Right:
if _lineCount == 1 and _pos==len(_doc.toPlainText()):
self._moveCurrentCell(diff=(+1, 0))
_processClose(True)
return True
elif ( evt.type == TTkK.SpecialKey and
evt.mod==TTkK.ControlModifier|TTkK.AltModifier and
evt.key == TTkK.Key_M ):
evt.mod = TTkK.NoModifier
evt.key = TTkK.Key_Enter
return _ke(evt)
_tev.keyEvent = _keyEvent
# _tev.enterPressed.connect(_processClose)
self.focusChanged.connect(_processClose)
def _editNum(self, x,y,w,h, row, col, data):
_sb = TTkSpinBox(
parent=self, pos=(x, y), size=(w,1),
minimum=-1000000, maximum=1000000,
value=data)
_sb.setFocus()
@pyTTkSlot(bool)
def _processClose(change):
if change:
self.focusChanged.disconnect(_processClose)
val = _sb.value()
self._tableModel_setData([(row,col,val)])
self.update()
_sb.close()
self.setFocus()
# Override the key event
_ke = _sb.keyEvent
def _keyEvent(evt):
if ( evt.type == TTkK.SpecialKey):
if evt.mod==TTkK.NoModifier:
if evt.key == TTkK.Key_Enter:
self._moveCurrentCell( 0,+1)
_processClose(True)
return True
return _ke(evt)
_sb.keyEvent = _keyEvent
self.focusChanged.connect(_processClose)
def _editTTkString(self, x,y,w,h, row, col, data):
_tp = TTkTextPicker(
parent=self, pos=(x, y), size=(w,h),
text=data, autoSize=False, wrapMode=TTkK.NoWrap)
_tp.setFocus()
@pyTTkSlot(bool)
def _processClose(change):
if change:
self.focusChanged.disconnect(_processClose)
txt = _tp.getTTkString()
self._tableModel_setData([(row,col,txt)])
self.update()
_tp.close()
self.setFocus()
def _alignWidgets(self) -> None:
if not (epwl:=self._edit_proxy_widget):
return
epw = epwl.widget
row = epwl.row
col = epwl.col
showHS = self._showHSeparators
showVS = self._showVSeparators
rp = self._rowsPos
cp = self._colsPos
xa,xb = 1+cp[col-1] if col>0 else 0, cp[col] + (0 if showVS else 1)
ya,yb = 1+rp[row-1] if row>0 else 0, rp[row] + (0 if showHS else 1)
epw.setGeometry(xa,ya,xb-xa,yb-ya)
self.focusChanged.connect(_processClose)
def _removeProxyWidget(self, direction:TTkTableEditLeaving=TTkTableEditLeaving.NONE) -> None:
if not (epwl:=self._edit_proxy_widget):
return
epw = epwl.widget
row = epwl.row
col = epwl.col
self._edit_proxy_widget = None
self.layout().removeWidget(epw)
data = epw.getCellData()
self._tableModel_setData([(row,col,data)])
epw.proxyDispose()
if direction == TTkTableEditLeaving.TOP:
self._moveCurrentCell(diff=(0,-1))
elif direction == TTkTableEditLeaving.BOTTOM:
self._moveCurrentCell(diff=(0,+1))
if direction == TTkTableEditLeaving.LEFT:
self._moveCurrentCell(diff=(-1,0))
elif direction == TTkTableEditLeaving.RIGHT:
self._moveCurrentCell(diff=(+1,0))
TTkHelper.hideCursor()
self.setFocus()
def _placeProxyWidget(self, proxyWidgetLocation:_ProxyWidgetLocation) -> None:
self._removeProxyWidget()
self._edit_proxy_widget = proxyWidgetLocation
proxyWidget = proxyWidgetLocation.widget
self._alignWidgets()
self.layout().addWidget(proxyWidget)
proxyWidget.leavingTriggered.connect(self._removeProxyWidget)
proxyWidget.setFocus()
def _editCell(self, row:int, col:int, richEditSupport:bool=True) -> None:
if not (self._tableModel.flags(row=row,col=col) & TTkK.ItemFlag.ItemIsEditable):
@ -1239,16 +1186,10 @@ class TTkTableWidget(TTkAbstractScrollView):
self.setSelection(pos=(col,row),size=(1,1),flags=TTkK.TTkItemSelectionModel.Select)
data = self._tableModel.data(row, col)
if type(data) is str:
self._editStr(xa,ya,xb-xa,yb-ya,row,col,data)
elif type(data) in [int,float]:
self._editNum(xa,ya,xb-xa,yb-ya,row,col,data)
else:
data = self._tableModel.ttkStringData(row, col)
if richEditSupport:
self._editTTkString(xa,ya,xb-xa,yb-ya,row,col,data)
else:
self._editStr(xa,ya,xb-xa,yb-ya,row,col,data)
if proxyWidget := self._edit_proxy.getProxyWidget(data, rich=richEditSupport):
epwl = _ProxyWidgetLocation(widget=proxyWidget, row=row, col=col)
self._placeProxyWidget(epwl)
def _setCurrentCell(self, currRow:int, currCol:int) -> None:
prevRow,prevCol = self._currentPos if self._currentPos else (0,0)
@ -1527,7 +1468,7 @@ class TTkTableWidget(TTkAbstractScrollView):
# Align all the other Separators relative to the selection
for i in range(ss, len(self._colsPos)):
self._colsPos[i] += diff
# self._alignWidgets()
self._alignWidgets()
self.viewChanged.emit()
self.update()
return True
@ -1542,7 +1483,7 @@ class TTkTableWidget(TTkAbstractScrollView):
# Align all the other Separators relative to the selection
for i in range(ss, len(self._rowsPos)):
self._rowsPos[i] += diff
# self._alignWidgets()
self._alignWidgets()
self.viewChanged.emit()
self.update()
return True
@ -1579,6 +1520,8 @@ class TTkTableWidget(TTkAbstractScrollView):
self._hoverPos = None
self._dragPos = None
if self._edit_proxy_widget:
self._edit_proxy_widget.widget.setFocus()
self.update()
return True

1
tools/check.import.sh

@ -118,6 +118,7 @@ __check(){
-e "TTkWidgets/widget.py:from __future__ import annotations" \
-e "TTkWidgets/tabwidget.py:from enum import Enum" \
-e "TTkModelView/__init__.py:from importlib.util import find_spec" \
-e "TTkModelView/table_edit_proxy.py:from enum import Enum, auto" \
-e "TTkModelView/tablemodelcsv.py:import csv" \
-e "TTkModelView/tablemodelsqlite3.py:import sqlite3" \
-e "TTkModelView/tablemodelsqlite3.py:import threading" |

Loading…
Cancel
Save