diff --git a/demo/showcase/formwidgets01.py b/demo/showcase/formwidgets01.py index 9d4d73c8..94a78d28 100755 --- a/demo/showcase/formwidgets01.py +++ b/demo/showcase/formwidgets01.py @@ -62,14 +62,14 @@ def demoFormWidgets(root=None): win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='Line Edit Test 3 oަ -'),row,2) row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Input Number'),row,0) - win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='123456', inputType=ttk.TTkK.Input_Number),row,2) + win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='123456', inputType=ttk.TTkK.InputType.Input_Number),row,2) row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Input Wrong Number'),row,0) - win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='No num Text', inputType=ttk.TTkK.Input_Number),row,2) + win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='No num Text', inputType=ttk.TTkK.InputType.Input_Number),row,2) row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Input Password'),row,0) - win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='Password', inputType=ttk.TTkK.Input_Password),row,2) + win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='Password', inputType=ttk.TTkK.InputType.Input_Password),row,2) row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Number Password'),row,0) - win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='Password', inputType=ttk.TTkK.Input_Password+ttk.TTkK.Input_Number),row,2) + win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='Password', inputType=ttk.TTkK.InputType.Input_Password|ttk.TTkK.InputType.Input_Number),row,2) row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Spinbox (default [0,99])'),row,0) win_form1_grid_layout.addWidget(ttk.TTkSpinBox(),row,2) diff --git a/demo/showcase/formwidgets02.py b/demo/showcase/formwidgets02.py index e7c76d31..44232686 100755 --- a/demo/showcase/formwidgets02.py +++ b/demo/showcase/formwidgets02.py @@ -76,17 +76,17 @@ def demoFormWidgets(root=None): win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Input Number'),row,0) - win_form1_grid_layout.addWidget(_wid := ttk.TTkLineEdit(text='123456', inputType=ttk.TTkK.Input_Number),row,2) + win_form1_grid_layout.addWidget(_wid := ttk.TTkLineEdit(text='123456', inputType=ttk.TTkK.InputType.Input_Number),row,2) win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Input Wrong Number'),row,0) - win_form1_grid_layout.addWidget(_wid := ttk.TTkLineEdit(text='No num Text', inputType=ttk.TTkK.Input_Number),row,2) + win_form1_grid_layout.addWidget(_wid := ttk.TTkLineEdit(text='No num Text', inputType=ttk.TTkK.InputType.Input_Number),row,2) win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Input Password'),row,0) - win_form1_grid_layout.addWidget(_wid := ttk.TTkLineEdit(text='Password', inputType=ttk.TTkK.Input_Password),row,2) + win_form1_grid_layout.addWidget(_wid := ttk.TTkLineEdit(text='Password', inputType=ttk.TTkK.InputType.Input_Password),row,2) win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Number Password'),row,0) - win_form1_grid_layout.addWidget(_wid := ttk.TTkLineEdit(text='Password', inputType=ttk.TTkK.Input_Password+ttk.TTkK.Input_Number),row,2) + win_form1_grid_layout.addWidget(_wid := ttk.TTkLineEdit(text='Password', inputType=ttk.TTkK.InputType.Input_Password|ttk.TTkK.InputType.Input_Number),row,2) win_form1_grid_layout.addWidget(_en_dis_cb := ttk.TTkCheckbox(text=" en/dis", checked=True),row,3); _en_dis_cb.clicked.connect(_wid.setEnabled) row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Spinbox (default [0,99])'),row,0) diff --git a/libs/pyTermTk/TermTk/TTkCore/constant.py b/libs/pyTermTk/TermTk/TTkCore/constant.py index 95d515ec..8db24292 100644 --- a/libs/pyTermTk/TermTk/TTkCore/constant.py +++ b/libs/pyTermTk/TermTk/TTkCore/constant.py @@ -194,7 +194,7 @@ class TTkConstant: Background=0x02 '''The color type returned is Background''' - class CheckState(int): + class CheckState(IntEnum): ''' This class type is used to describe the check status. .. autosummary:: @@ -213,7 +213,7 @@ class TTkConstant: PartiallyChecked = CheckState.PartiallyChecked Checked = CheckState.Checked - class InsertPolicy(int): + class InsertPolicy(IntEnum): '''Specifies what the :py:class:`TTkComboBox` should do when a new string is entered by the user. .. autosummary:: @@ -423,10 +423,26 @@ class TTkConstant: Cursor_Blinking_Bar = 0x0006 Cursor_Steady_Bar = 0x0007 + class InputType(Flag): + ''' + This enum type describes the input validation types for text input widgets. + + .. autosummary:: + Input_Text + Input_Number + Input_Password + ''' + Input_Text = 0x01 + '''Accept any text input (default)''' + Input_Number = 0x02 + '''Accept only numeric input (integers and decimals)''' + Input_Password = 0x04 + '''Password input type (deprecated - use :py:class:`TTkLineEdit.EchoMode.Password` instead)''' + # Input types - Input_Text = 0x01 - Input_Number = 0x02 - Input_Password = 0x04 + Input_Text = InputType.Input_Text + Input_Number = InputType.Input_Number + Input_Password = InputType.Input_Password # Alignment class Alignment(IntEnum): diff --git a/libs/pyTermTk/TermTk/TTkCore/helper.py b/libs/pyTermTk/TermTk/TTkCore/helper.py index 7fa34a10..b99938cc 100644 --- a/libs/pyTermTk/TermTk/TTkCore/helper.py +++ b/libs/pyTermTk/TermTk/TTkCore/helper.py @@ -127,7 +127,7 @@ class TTkHelper: return TTkGlbl.term_w, TTkGlbl.term_h @staticmethod - def rootOverlay(widget: TTkWidget) -> Optional[TTkWidget]: + def rootOverlay(widget: Optional[TTkWidget]) -> Optional[TTkWidget]: if not widget: return None if not TTkHelper._overlay: @@ -174,7 +174,7 @@ class TTkHelper: return False @staticmethod - def isOverlay(widget: TTkWidget) -> bool: + def isOverlay(widget: Optional[TTkWidget]) -> bool: return TTkHelper.rootOverlay(widget) is not None @staticmethod @@ -241,8 +241,8 @@ class TTkHelper: bkFocus.setFocus() @staticmethod - def removeOverlayAndChild(widget: TTkWidget) -> None: - if not TTkHelper._rootWidget: + def removeOverlayAndChild(widget: Optional[TTkWidget]) -> None: + if not TTkHelper._rootWidget or not widget: return if not TTkHelper.isOverlay(widget): return diff --git a/libs/pyTermTk/TermTk/TTkWidgets/checkbox.py b/libs/pyTermTk/TermTk/TTkWidgets/checkbox.py index 79919432..47d11035 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/checkbox.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/checkbox.py @@ -22,11 +22,13 @@ __all__ = ['TTkCheckbox'] +from typing import Optional + from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.color import TTkColor from TermTk.TTkCore.canvas import TTkCanvas -from TermTk.TTkCore.string import TTkString +from TermTk.TTkCore.string import TTkString, TTkStringType from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent @@ -93,9 +95,9 @@ class TTkCheckbox(TTkWidget): 'clicked', 'stateChanged', 'toggled' ) def __init__(self, *, - text:TTkString='', + text:TTkStringType='', checked:bool=False, - checkStatus:TTkK.CheckState = None, + checkStatus:Optional[TTkK.CheckState] = None, tristate:bool=False, **kwargs) -> None: ''' @@ -115,7 +117,7 @@ class TTkCheckbox(TTkWidget): self.clicked = pyTTkSignal(bool) self.toggled = pyTTkSignal(bool) - self._text = TTkString(text) + self._text = text if isinstance(text,TTkString) else TTkString(text) if checkStatus is not None : self._checkStatus = checkStatus diff --git a/libs/pyTermTk/TermTk/TTkWidgets/combobox.py b/libs/pyTermTk/TermTk/TTkWidgets/combobox.py index c7c459db..7b1163e1 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/combobox.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/combobox.py @@ -22,6 +22,8 @@ __all__ = ['TTkComboBox'] +from typing import Dict,Any,List,Optional + from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.constant import TTkK from TermTk.TTkCore.log import TTkLog @@ -41,7 +43,7 @@ from TermTk.TTkWidgets.resizableframe import TTkResizableFrame class _TTkComboBoxPopup(TTkResizableFrame): - classStyle = TTkResizableFrame.classStyle + classStyle:Dict[str,Dict[str,Any]] = TTkResizableFrame.classStyle classStyle['default'] |= {'searchColor': TTkColor.fg("#FFFF00")} __slots__ = ('_list', @@ -66,13 +68,13 @@ class _TTkComboBoxPopup(TTkResizableFrame): def paintEvent(self, canvas:TTkCanvas) -> None: super().paintEvent(canvas) - if text := self._list.search(): + if str_text := self._list.search(): w = self.width()-6 color = self.currentStyle()['searchColor'] - if len(text) > w: - text = TTkString("≼",TTkColor.BG_BLUE+TTkColor.FG_CYAN)+TTkString(text[-w+1:],color) + if len(str_text) > w: + text = TTkString("≼",TTkColor.BG_BLUE+TTkColor.FG_CYAN)+TTkString(str_text[-w+1:],color) else: - text = TTkString(text,color) + text = TTkString(str_text,color) canvas.drawText(pos=(1,0), text=f"╼ {text} ╾") canvas.drawTTkString(pos=(3,0), text=text) @@ -103,24 +105,52 @@ class TTkComboBox(TTkContainer): __slots__ = ('_list', '_id', '_lineEdit', '_editable', '_insertPolicy', '_textAlign', '_popupFrame', #signals 'currentIndexChanged', 'currentTextChanged', 'editTextChanged') + + currentIndexChanged:pyTTkSignal + ''' + This signal is emitted when the index in the combobox changes either through user interaction or programmatically. + + :param index: the new current index + :type index: int + ''' + currentTextChanged:pyTTkSignal + ''' + This signal is emitted when the text of the current item changes. The text is passed as parameter. + + :param text: the new current text + :type text: str + ''' + editTextChanged:pyTTkSignal + ''' + This signal is emitted when the text in the combobox's line edit widget is changed. This signal is only emitted when the combobox is editable. + + :param text: the new text in the line edit + :type text: str + ''' + + _list:List[str] + _popupFrame:Optional[_TTkComboBoxPopup] def __init__(self, *, - list:list = None, + list:Optional[List[str]] = None, index:int = -1, insertPolicy:TTkK.InsertPolicy = TTkK.InsertAtBottom, textAlign:TTkK.Alignment = TTkK.CENTER_ALIGN, editable:bool = False, **kwargs) -> None: ''' - :param list: the list of the items selectable by this combobox, defaults to "[]" - :type list: list(str), optional + :param list: the list of the items selectable by this combobox, defaults to [] + :type list: list[str], optional + + :param index: the initial selected index, defaults to -1 (no selection) + :type index: int, optional - :param insertPolicy: the policy used to determine where user-inserted items should appear in the combobox, defaults to :py:class:`TTkConstant.InsertPolicy.InsertAtBottom` - :type insertPolicy: :py:class:`TTkConstant.InsertPolicy`, optional + :param insertPolicy: the policy used to determine where user-inserted items should appear in the combobox, defaults to :py:class:`TTkK.InsertPolicy.InsertAtBottom` + :type insertPolicy: :py:class:`TTkK.InsertPolicy`, optional - :param textAlign: This enum type is used to define the text alignment, defaults to :py:class:`TTkConstant.Alignment.CENTER_ALIGN` - :tye textAlign: :py:class:`TTkConstant.Alignment`, optional + :param textAlign: the text alignment for the displayed text, defaults to :py:class:`TTkK.Alignment.CENTER_ALIGN` + :type textAlign: :py:class:`TTkK.Alignment`, optional - :param editable: This property holds whether the combo box can be edited by the user, defaults to False + :param editable: whether the combo box can be edited by the user, defaults to False :type editable: bool, optional ''' @@ -142,7 +172,7 @@ class TTkComboBox(TTkContainer): self.setMaximumHeight(1) def _lineEditChanged(self) -> None: - text = self._lineEdit.text() + text = self._lineEdit.text().toAscii() self._id=-1 if text in self._list: self._id = self._list.index(text) @@ -169,9 +199,10 @@ class TTkComboBox(TTkContainer): self.editTextChanged.emit(text) def textAlign(self) -> TTkK.Alignment: - '''his property holds the displayed text alignment + '''This property holds the displayed text alignment - :return: :py:class:`TTkConstant.Alignment` + :return: the current text alignment + :rtype: :py:class:`TTkK.Alignment` ''' return self._textAlign @@ -179,32 +210,32 @@ class TTkComboBox(TTkContainer): '''This property holds the displayed text alignment :param align: - :type align: :py:class:`TTkConstant.Alignment` + :type align: :py:class:`TTkK.Alignment` ''' if self._textAlign != align: self._textAlign = align self.update() - def addItem(self, text:TTkString): + def addItem(self, text:str): ''' Adds an item to the combobox with the given text. The item is appended to the list of existing items. - :param text: - :type text: :py:class:`TTkString` + :param text: the text of the item to add + :type text: str ''' self._list.append(text) self.update() - def addItems(self, items:list[TTkString]) -> None: + def addItems(self, items:list[str]) -> None: ''' Adds a list of items to the combobox with the given text. The items are appended to the list of existing items. :param items: - :type items: list[:py:class:`TTkString`] + :type items: list[str] ''' for item in items: self.addItem(item) @@ -217,11 +248,12 @@ class TTkComboBox(TTkContainer): self._id = -1 self.update() - def lineEdit(self) -> TTkLineEdit: + def lineEdit(self) -> Optional[TTkLineEdit]: ''' Returns the :py:class:`TTkLineEdit` widget used if the editable flag is enabled - :return: :py:class:`TTkLineEdit` + :return: the line edit if available, None otherwise + :rtype: :py:class:`TTkLineEdit` | None ''' return self._lineEdit if self._editable else None @@ -247,23 +279,25 @@ class TTkComboBox(TTkContainer): else: canvas.drawText(pos=(w-2,0), text="^]", color=borderColor) - def currentText(self) -> TTkString: + def currentText(self) -> str: ''' Returns the selected text - :return: :py:class:`TTkString` + :return: the current text + :rtype: str ''' if self._editable: - return self._lineEdit.text() + return self._lineEdit.text().toAscii() elif self._id >= 0: return self._list[self._id] return "" def currentIndex(self) -> int: ''' - Return the current seleccted index. + Return the current selected index. - :return: int + :return: the current index, -1 if no selection + :rtype: int ''' return self._id @@ -287,12 +321,12 @@ class TTkComboBox(TTkContainer): self.update() @pyTTkSlot(str) - def setCurrentText(self, text:TTkString) -> None: + def setCurrentText(self, text:str) -> None: ''' Set the selected Text :param text: - :type text: :py:class:`TTkString` + :type text: str ''' if self._editable: self.setEditText(text) @@ -318,7 +352,8 @@ class TTkComboBox(TTkContainer): ''' Retrieve the insert policy used when a new item is added if the combobox editable flag is true. - :return: :py:class:`TTkK.InsertPolicy` + :return: the current insert policy + :rtype: :py:class:`TTkK.InsertPolicy` ''' return self._insertPolicy @@ -335,7 +370,8 @@ class TTkComboBox(TTkContainer): ''' This field holds the editable status of this widget. - :return: bool + :return: True if editable, False otherwise + :rtype: bool ''' return self._editable @@ -355,7 +391,7 @@ class TTkComboBox(TTkContainer): self.setFocusPolicy(TTkK.ClickFocus | TTkK.TabFocus) @pyTTkSlot(str) - def _callback(self, label:TTkString) -> None: + def _callback(self, label:str) -> None: if self._editable: self._lineEdit.setText(label) self.setCurrentIndex(self._list.index(label)) diff --git a/libs/pyTermTk/TermTk/TTkWidgets/datetime_time.py b/libs/pyTermTk/TermTk/TTkWidgets/datetime_time.py index b173226d..c3b9a1a8 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/datetime_time.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/datetime_time.py @@ -262,7 +262,7 @@ class TTkTime(TTkWidget): self._state.selected = _FieldSelected.NONE return True else: - if '0' <= evt.key <= '9': + if isinstance(evt.key,str) and '0' <= evt.key <= '9': value = int(evt.key) secondDigit = self._state.secondDigit self._state.secondDigit = not secondDigit diff --git a/libs/pyTermTk/TermTk/TTkWidgets/frame.py b/libs/pyTermTk/TermTk/TTkWidgets/frame.py index bf719bf4..3e1a0d7b 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/frame.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/frame.py @@ -22,8 +22,10 @@ __all__ = ['TTkFrame'] +from typing import Dict,Any + from TermTk.TTkCore.constant import TTkK -from TermTk.TTkCore.string import TTkString +from TermTk.TTkCore.string import TTkString, TTkStringType from TermTk.TTkCore.color import TTkColor from TermTk.TTkWidgets.container import TTkContainer @@ -45,7 +47,7 @@ class TTkFrame(TTkContainer): Demo2: `splitter.py `_ ''' - classStyle = { + classStyle:Dict[str,Dict[str,Any]] = { 'default': {'color': TTkColor.fg("#dddddd")+TTkColor.bg("#222222"), 'fillColor': TTkColor.RST, 'borderColor': TTkColor.RST}, @@ -58,7 +60,7 @@ class TTkFrame(TTkContainer): '_border','_title', '_titleAlign', '_menubarTop', '_menubarTopPosition', '_menubarBottom', '_menubarBottomPosition') def __init__(self, *, - title:TTkString='', + title:TTkStringType='', border:bool=True, titleAlign:TTkK.Alignment=TTkK.CENTER_ALIGN, **kwargs) -> None: @@ -72,7 +74,7 @@ class TTkFrame(TTkContainer): ''' self._titleAlign = titleAlign - self._title = TTkString(title) + self._title = title if isinstance(title,TTkString) else TTkString(title) self._border = border self._menubarBottomPosition = 0 self._menubarTop = None diff --git a/libs/pyTermTk/TermTk/TTkWidgets/lineedit.py b/libs/pyTermTk/TermTk/TTkWidgets/lineedit.py index c71c0764..0dad9a24 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/lineedit.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/lineedit.py @@ -22,6 +22,10 @@ __all__ = ['TTkLineEdit'] +from enum import IntEnum + +from typing import Optional + from TermTk.TTkCore.cfg import TTkCfg from TermTk.TTkCore.log import TTkLog from TermTk.TTkCore.constant import TTkK @@ -42,10 +46,53 @@ from TermTk.TTkWidgets.widget import TTkWidget <--> Offset ''' class TTkLineEdit(TTkWidget): - '''TTkLineEdit''' + ''' TTkLineEdit: + + A single-line text editor widget for user input. (`demo `__) + + :: + + Standard: |Hello World________| + Password: |**********_________| + Number: |123.45_____________| + + .. code:: python + + import TermTk as ttk + + root = ttk.TTk() + + # Basic text input + edit = ttk.TTkLineEdit(parent=root, pos=(1,1), size=(20,1), text="Hello") + + # Password input + password = ttk.TTkLineEdit(parent=root, pos=(1,2), size=(20,1), + echoMode=ttk.TTkLineEdit.EchoMode.Password) + + # Numeric input + number = ttk.TTkLineEdit(parent=root, pos=(1,3), size=(20,1), + inputType=ttk.TTkK.InputType.Input_Number) + + # Connect to signal + edit.textEdited.connect(lambda text: ttk.TTkLog.debug(f"Edited: {text}")) + + root.mainloop() + + :param text: Initial text content + :type text: :py:class:`TTkString` or str, optional + :param hint: Placeholder text shown when empty + :type hint: :py:class:`TTkString` or str, optional + :param inputType: Input validation type (TTkK.InputType.Input_Text or TTkK.InputType.Input_Number) + :type inputType: int, optional + :param echoMode: How text is displayed (Normal, NoEcho, Password, PasswordEchoOnEdit) + :type echoMode: :py:class:`EchoMode`, optional + ''' + + class EchoMode(IntEnum): + ''' EchoMode: - class EchoMode(int): - '''EchoMode''' + Defines how text input is displayed in the line edit. + ''' Normal = 0x00 '''Display characters as they are entered. This is the default.''' NoEcho = 0x01 @@ -73,10 +120,37 @@ class TTkLineEdit(TTkWidget): '_hint', # Signals 'returnPressed', 'textChanged', 'textEdited' ) + + returnPressed:pyTTkSignal + ''' + This signal is emitted when the Return or Enter key is pressed. + ''' + + textChanged:pyTTkSignal + ''' + This signal is emitted whenever the text changes programmatically or through user input. + + :param text: The new text content + :type text: str + ''' + + textEdited:pyTTkSignal + ''' + This signal is emitted whenever the text is edited by the user (not programmatically). + + :param text: The edited text content + :type text: str + ''' + + _text:TTkString + _hint:TTkString + _cursorPos:int + _inputType:TTkK.InputType + def __init__(self, *, - text:TTkString='', - hint:TTkString='', - inputType:int=TTkK.Input_Text, + text:TTkStringType='', + hint:TTkStringType='', + inputType:TTkK.InputType=TTkK.InputType.Input_Text, echoMode:EchoMode=EchoMode.Normal, **kwargs) -> None: # Signals @@ -88,8 +162,8 @@ class TTkLineEdit(TTkWidget): self._selectionFrom = 0 self._selectionTo = 0 self._replace=False - self._text = TTkString(text) - self._hint = TTkString(hint) + self._text = text if isinstance(text,TTkString) else TTkString(text) + self._hint = hint if isinstance(hint,TTkString) else TTkString(hint) self._inputType = inputType self._echoMode = echoMode self._clipboard = TTkClipboard() @@ -101,8 +175,14 @@ class TTkLineEdit(TTkWidget): self.enableWidgetCursor() @pyTTkSlot(TTkStringType) - def setText(self, text:TTkStringType, cursorPos=0x1000): - '''setText''' + def setText(self, text:TTkStringType, cursorPos:int=0x1000) -> None: + ''' Set the text content of the line edit + + :param text: The new text to display + :type text: :py:class:`TTkString` or str + :param cursorPos: Position to place the cursor (defaults to end of text) + :type cursorPos: int, optional + ''' if text != self._text: self.textChanged.emit(text) self._text = TTkString(text) @@ -110,35 +190,59 @@ class TTkLineEdit(TTkWidget): self._cursorPos = max(0,min(cursorPos, len(text))) self._pushCursor() - def text(self): - '''text''' + def text(self) -> TTkString: + ''' Get the current text content + + :return: The current text + :rtype: :py:class:`TTkString` + ''' return self._text - def inputType(self): - '''inputType''' + def inputType(self) -> TTkK.InputType: + ''' Get the current input validation type + + :return: The input type (:py:class:`TTkK.InputType.Input_Text` or :py:class:`TTkK.InputType.Input_Number`) + :rtype: :py:class:`TTkK.InputType` + ''' return self._inputType - def setInputType(self, inputType): - '''inputType''' - if bool(inputType & TTkK.Input_Text) and bool(inputType & TTkK.Input_Number): + def setInputType(self, inputType:TTkK.InputType) -> None: + ''' Set the input validation type + + When set to Input_Number, only numeric values (including decimals) are accepted. + + :param inputType: The input type to validate against + :type inputType: :py:class:`TTkK.InputType` + ''' + if bool(inputType & TTkK.InputType.Input_Text) and bool(inputType & TTkK.InputType.Input_Number): return # Kept here for retrocompatibility - if inputType & TTkK.Input_Password: - TTkLog.warn("TTkK.Input_Password is deprecated, use the EchoMode instead") + if inputType & TTkK.InputType.Input_Password: + TTkLog.warn("TTkK.InputType.Input_Password is deprecated, use the EchoMode instead") self._echoMode = TTkLineEdit.EchoMode.Password - inputType &= ~TTkK.Input_Password - if inputType & ~(TTkK.Input_Text|TTkK.Input_Number): + inputType &= ~TTkK.InputType.Input_Password + if inputType & ~(TTkK.InputType.Input_Text|TTkK.InputType.Input_Number): return - self._inputType = inputType & (TTkK.Input_Text|TTkK.Input_Number) if inputType else TTkK.Input_Text - if ( self._inputType == TTkK.Input_Number and + self._inputType = inputType & (TTkK.InputType.Input_Text|TTkK.InputType.Input_Number) if inputType else TTkK.InputType.Input_Text + if ( self._inputType == TTkK.InputType.Input_Number and not self._isFloat(self._text)): self._text = TTkString('0') self.update() def echoMode(self) -> EchoMode: + ''' Get the current echo mode + + :return: The current echo mode + :rtype: :py:class:`EchoMode` + ''' return self._echoMode def setEchoMode(self, echoMode:EchoMode): + ''' Set how text is displayed + + :param echoMode: The echo mode (Normal, NoEcho, Password, PasswordEchoOnEdit) + :type echoMode: :py:class:`EchoMode` + ''' self._echoMode = echoMode self.update() @@ -229,12 +333,16 @@ class TTkLineEdit(TTkWidget): @pyTTkSlot() def copy(self): + ''' Copy the selected text to the clipboard + ''' if self._selectionFrom >= self._selectionTo: return txt = self._text.substring(fr=self._selectionFrom,to=self._selectionTo) self._clipboard.setText(txt) @pyTTkSlot() def cut(self): + ''' Cut the selected text to the clipboard + ''' self.copy() self._text = self._text.substring(to=self._selectionFrom) + self._text.substring(fr=self._selectionTo) self._cursorPos = self._selectionFrom @@ -242,11 +350,20 @@ class TTkLineEdit(TTkWidget): @pyTTkSlot() def paste(self): + ''' Paste text from the clipboard at the cursor position + ''' txt = self._clipboard.text() self.pasteEvent(txt) def pasteEvent(self, txt:str): - txt = TTkString().join(txt.split('\n')) + ''' Handle paste event with custom text + + :param txt: The text to paste + :type txt: str + :return: True if the event was handled + :rtype: bool + ''' + ttk_txt = TTkString(''.join(txt.split('\n'))) text = self._text @@ -261,11 +378,11 @@ class TTkLineEdit(TTkWidget): else: post = text.substring(fr=self._cursorPos) - text = pre + txt + post - if ( self._inputType & TTkK.Input_Number and + text = pre + ttk_txt + post + if ( self._inputType & TTkK.InputType.Input_Number and not self._isFloat(text) ): return True - self.setText(text, self._cursorPos+txt.termWidth()) + self.setText(text, self._cursorPos+ttk_txt.termWidth()) self._pushCursor() self.textEdited.emit(self._text) @@ -323,7 +440,7 @@ class TTkLineEdit(TTkWidget): text = self._text.substring(to=prev) + self._text.substring(fr=self._cursorPos) cursorPos = prev - if ( self._inputType & TTkK.Input_Number and + if ( self._inputType & TTkK.InputType.Input_Number and not self._isFloat(self._text) ): self.setText('0', 1) else: @@ -333,7 +450,7 @@ class TTkLineEdit(TTkWidget): if evt.key == TTkK.Key_Enter: self.returnPressed.emit() - else: + elif isinstance(evt.key,str): text = self._text if self._selectionFrom < self._selectionTo: @@ -348,7 +465,7 @@ class TTkLineEdit(TTkWidget): post = text.substring(fr=self._cursorPos) text = pre + evt.key + post - if ( self._inputType & TTkK.Input_Number and + if ( self._inputType & TTkK.InputType.Input_Number and ( evt.key in (' ') or not self._isFloat(text) )): return True self.setText(text, self._cursorPos+1) diff --git a/tests/t.ui/test.ui.009.widgets.form.py b/tests/t.ui/test.ui.009.widgets.form.py index fdb82853..24d2f5bd 100755 --- a/tests/t.ui/test.ui.009.widgets.form.py +++ b/tests/t.ui/test.ui.009.widgets.form.py @@ -67,14 +67,14 @@ row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Test 5'), win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='Line Edit Test 5'),row,2) row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Input Number'),row,0) -win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='123456', inputType=ttk.TTkK.Input_Number),row,2) +win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='123456', inputType=ttk.TTkK.InputType.Input_Number),row,2) row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Input Wrong Number'),row,0) -win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='No num Text', inputType=ttk.TTkK.Input_Number),row,2) +win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='No num Text', inputType=ttk.TTkK.InputType.Input_Number),row,2) row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Input Password'),row,0) -win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='Password', inputType=ttk.TTkK.Input_Password),row,2) +win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='Password', inputType=ttk.TTkK.InputType.Input_Password),row,2) row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Line Edit Number Password'),row,0) -win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='Password', inputType=ttk.TTkK.Input_Password+ttk.TTkK.Input_Number),row,2) +win_form1_grid_layout.addWidget(ttk.TTkLineEdit(text='Password', inputType=ttk.TTkK.InputType.Input_Password|ttk.TTkK.InputType.Input_Number),row,2) row += 1; win_form1_grid_layout.addWidget(ttk.TTkLabel(text='Checkbox'),row,0) win_form1_grid_layout.addWidget(ttk.TTkCheckbox(),row,2) diff --git a/tools/check.import.sh b/tools/check.import.sh index ee07a561..84849a1d 100755 --- a/tools/check.import.sh +++ b/tools/check.import.sh @@ -4,6 +4,7 @@ __check(){ grep -r -e "^import" -e "^from" libs/pyTermTk/TermTk | grep -v -e "from TermTk" -e "import TermTk" | grep -v "from typing import" | + grep -v "from enum import" | grep -v "__init__.py:from \.[^ ]* *import" | grep -v -e "import re" -e "import os" -e "import datetime" | grep -v \ @@ -46,9 +47,7 @@ __check(){ -e "propertyanimation.py:import time, math" \ -e "savetools.py:import importlib.util" \ -e "savetools.py:import json" \ - -e "TTkCore/constant.py:from enum import IntEnum" | grep -v \ - -e "TTkTerm/term_base.py:from enum import Flag" \ -e "TTkTerm/input_mono.py:from time import time" \ -e "TTkTerm/input_mono.py:import platform" \ -e "TTkTerm/input_mono.py:from ..drivers import TTkInputDriver" \ @@ -58,7 +57,6 @@ __check(){ -e "TTkTerm/input.py:from .input_thread import *" | grep -v \ -e "TTkGui/__init__.py:import importlib.util" \ - -e "TTkGui/textcursor.py:from enum import IntEnum" \ -e "TTkGui/textdocument.py:from threading import Lock" \ -e "TTkGui/textdocument_highlight_pygments.py:from pygments" | grep -v \ @@ -116,19 +114,12 @@ __check(){ -e "TTkTerminal/__init__.py:import platform" | grep -v \ -e "TTkWidgets/widget.py:from __future__ import annotations" \ - -e "TTkWidgets/tabwidget.py:from enum import Enum" \ - -e "TTkWidgets/apptemplate.py:from enum import IntEnum" \ -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" | grep -v \ - -e "TTkWidgets/datetime_date.py:from enum import IntEnum,auto" \ -e "TTkWidgets/datetime_date.py:import calendar" \ - -e "TTkWidgets/datetime_time.py:from enum import IntEnum,auto" \ - -e "TTkWidgets/datetime_datetime.py:from enum import IntEnum,auto" \ - -e "TTkWidgets/datetime_date_form.py:from enum import IntEnum,auto" \ -e "TTkWidgets/datetime_date_form.py:import calendar" } ;