diff --git a/libs/pyTermTk/TermTk/TTkWidgets/__init__.py b/libs/pyTermTk/TermTk/TTkWidgets/__init__.py index 93245a1b..771e06d5 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/__init__.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/__init__.py @@ -38,6 +38,7 @@ from .TTkTerminal import * from .datetime_time import * from .datetime_date import * +from .datetime_datetime import * from .datetime_date_form import * from .Fancy import * diff --git a/libs/pyTermTk/TermTk/TTkWidgets/datetime_date_form.py b/libs/pyTermTk/TermTk/TTkWidgets/datetime_date_form.py index f4935b44..1c896db4 100644 --- a/libs/pyTermTk/TermTk/TTkWidgets/datetime_date_form.py +++ b/libs/pyTermTk/TermTk/TTkWidgets/datetime_date_form.py @@ -756,9 +756,8 @@ class TTkDateForm(TTkContainer): self._state = _TTkDateWidgetState(date=date) self._state.highlightedChanged.connect(self.update) self.dateChanged = self._state.dateChanged - _layout=TTkLayout() size = (20,8) - super().__init__(**kwargs|{'layout':_layout, 'size':size, 'minSize':size}) + super().__init__(**kwargs|{'size':size, 'minSize':size}) self._calWidget = _TTkDateCal(parent=self, pos=(0,2), state=self._state) self._yearWidget = _TTkDateYear(parent=self, pos=(2,0), state=self._state) self._monthWidget = _TTkDateMonth(parent=self, pos=(12,0), state=self._state) diff --git a/libs/pyTermTk/TermTk/TTkWidgets/datetime_datetime.py b/libs/pyTermTk/TermTk/TTkWidgets/datetime_datetime.py new file mode 100644 index 00000000..c68f63e8 --- /dev/null +++ b/libs/pyTermTk/TermTk/TTkWidgets/datetime_datetime.py @@ -0,0 +1,143 @@ +# MIT License +# +# Copyright (c) 2025 Eugenio Parodi +# +# 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. + +__all__ = ['TTkDateTime'] + +from enum import IntEnum,Enum,auto +from dataclasses import dataclass +import datetime as dt + +from typing import Optional + +from TermTk.TTkCore.color import TTkColor +from TermTk.TTkCore.string import TTkString +from TermTk.TTkCore.constant import TTkK +from TermTk.TTkCore.signal import pyTTkSignal, pyTTkSlot +from TermTk.TTkCore.TTkTerm.inputkey import TTkKeyEvent +from TermTk.TTkCore.TTkTerm.inputmouse import TTkMouseEvent +from TermTk.TTkLayouts import TTkGridLayout, TTkLayout +from TermTk.TTkWidgets.widget import TTkWidget +from TermTk.TTkWidgets.container import TTkContainer +from TermTk.TTkWidgets.spinbox import TTkSpinBox +from TermTk.TTkWidgets.datetime_date import TTkDate +from TermTk.TTkWidgets.datetime_time import TTkTime + +class TTkDateTime(TTkContainer): + ''' TTkDateTime: + + A composite widget for displaying and editing date and time values. + + Combines :class:`~TermTk.TTkWidgets.datetime_date.TTkDate` and :class:`~TermTk.TTkWidgets.datetime_time.TTkTime` widgets + into a single datetime editor. + + :: + + 2025/11/04 📅 12:30:45 + + .. code:: python + + import TermTk as ttk + + root = ttk.TTk(mouseTrack=True) + + ttk.TTkDateTime(parent=root) # Defaults to the current datetime + + root.mainloop() + + :param datetime: The initial datetime to display, defaults to the current datetime. + :type datetime: :py:class:`datetime.datetime`, optional + ''' + + __slots__ = ( + '_datetime', + '_dateWidget', '_timeWidget', + # Signals + 'datetimeChanged') + + datetimeChanged:pyTTkSignal + ''' + This signal is emitted whenever the datetime changes. + + :param datetime: The new datetime value + :type datetime: :py:class:`datetime.datetime` + ''' + + _datetime:dt.datetime + _dateWidget:TTkDate + _timeWidget:TTkTime + + def __init__(self, *, + datetime:Optional[dt.datetime]=None, + **kwargs) -> None: + ''' + Initializes the TTkDateTime widget. + + :param datetime: The initial datetime to display. If None, the current datetime is used. + :type datetime: :py:class:`datetime.datetime`, optional + ''' + self.datetimeChanged = pyTTkSignal(dt.datetime) + if not datetime: + datetime = dt.datetime.now().replace(microsecond=0) + self._datetime = datetime + _layout=TTkLayout() + size = (13+1+8,1) + super().__init__(**kwargs|{'layout':_layout, 'size':size, 'minSize':size}) + self._dateWidget = TTkDate(parent=self, pos=( 0,0), date=datetime.date()) + self._timeWidget = TTkTime(parent=self, pos=(14,0), time=datetime.time()) + self._dateWidget.dateChanged.connect(self._somethingChanged) + self._timeWidget.timeChanged.connect(self._somethingChanged) + + @pyTTkSlot() + def _somethingChanged(self) -> None: + ''' + Internal slot that synchronizes the datetime value when either date or time changes. + ''' + self.setDatetime( + self._datetime.combine( + date=self._dateWidget.date(), + time=self._timeWidget.time())) + + def datetime(self) -> dt.datetime: + ''' + Returns the current datetime of the widget. + + :return: The current datetime + :rtype: :py:class:`datetime.datetime` + ''' + return self._datetime + + @pyTTkSlot(dt.datetime) + def setDatetime(self, datetime:dt.datetime) -> None: + ''' + Sets the current datetime of the widget. + + This updates both the internal date and time widgets and emits the + :py:attr:`datetimeChanged` signal if the value changes. + + :param datetime: The new datetime to set + :type datetime: :py:class:`datetime.datetime` + ''' + if datetime != self._datetime: + self._datetime = datetime + self._dateWidget.setDate(datetime.date()) + self._timeWidget.setTime(datetime.time()) + self.datetimeChanged.emit(datetime) \ No newline at end of file diff --git a/tests/t.ui/test.ui.036.datetime.04.py b/tests/t.ui/test.ui.036.datetime.04.py new file mode 100755 index 00000000..13a99d07 --- /dev/null +++ b/tests/t.ui/test.ui.036.datetime.04.py @@ -0,0 +1,76 @@ +#!/usr/bin/env python3 + +# MIT License +# +# Copyright (c) 2021 Eugenio Parodi +# +# 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. + +import sys, os +from datetime import time,date,datetime + +sys.path.append(os.path.join(sys.path[0],'../../libs/pyTermTk')) +import TermTk as ttk + +root = ttk.TTk(mouseTrack=True) +winLog = ttk.TTkWindow(parent=root, pos=(20,1), size=(80,30), layout=ttk.TTkGridLayout()) +ttk.TTkLogViewer(parent=winLog) + +win = ttk.TTkWindow(parent=root, pos=( 0,0), size=(57,14), title='Generic') +win1 = ttk.TTkWindow(parent=root, pos=( 60,0), size=(22,12), title='Date Form', layout=ttk.TTkGridLayout()) +win2 = ttk.TTkWindow(parent=root, pos=( 85,0), size=(15,12), title='Date') +win3 = ttk.TTkWindow(parent=root, pos=(103,0), size=(25,12), title='DateTime') + +testTime1=time(hour=3,minute=30) +timeWidget1 = ttk.TTkTime(parent=win, pos=(1,0), time=testTime1) +timeWidget2 = ttk.TTkTime(parent=win, pos=(1,2)) + +timeLabel1 = ttk.TTkLabel(parent=win, pos=(1,4), text=str(testTime1)) +timeLabel2 = ttk.TTkLabel(parent=win, pos=(1,6), text=str(timeWidget2.time())) +timeWidget1.timeChanged.connect(lambda _t:timeLabel1.setText(str(_t))) +timeWidget2.timeChanged.connect(lambda _t:timeLabel2.setText(str(_t))) + +testDate = date(year=1980, month=12, day=25) +dateFormWidget1 = ttk.TTkDateForm(parent=win, pos=(12,0), date=testDate) +dateFormWidget2 = ttk.TTkDateForm(parent=win, pos=(34,0)) +dateFormWidget3 = ttk.TTkDateForm(parent=win1, date=testDate.replace(year=2000)) +dateLabel = ttk.TTkLabel(parent=win, pos=(1,8), text=str(testDate)) +dateFormWidget1.dateChanged.connect(lambda _d: dateLabel.setText(str(_d))) +dateFormWidget1.dateChanged.connect(dateFormWidget2.setDate) +dateFormWidget2.dateChanged.connect(dateFormWidget1.setDate) +dateFormWidget3.dateChanged.connect(dateFormWidget2.setDate) + +dateWidget1 = ttk.TTkDate(parent=win2, pos=(0,0), date=testDate) +dateWidget2 = ttk.TTkDate(parent=win2, pos=(0,2)) +dateLabel1 = ttk.TTkLabel(parent=win2, pos=(0,4), text=str(testDate)) +dateLabel2 = ttk.TTkLabel(parent=win2, pos=(0,6), text=str(dateWidget2.date())) +dateWidget1.dateChanged.connect(lambda _d:dateLabel1.setText(str(_d))) +dateWidget1.dateChanged.connect(dateFormWidget1.setDate) +dateWidget2.dateChanged.connect(lambda _d:dateLabel2.setText(str(_d))) + +testDatetime = datetime(1995,10,5, 23,30,5) +dateTimeWidget1 = ttk.TTkDateTime(parent=win3, pos=(0,0)) +dateTimeWidget2 = ttk.TTkDateTime(parent=win3, pos=(0,4), datetime=testDatetime) +datetimeLabel1 = ttk.TTkLabel(parent=win3, pos=(0,2), text=str(dateTimeWidget1.datetime())) +datetimeLabel2 = ttk.TTkLabel(parent=win3, pos=(0,6), text=str(testDatetime)) +dateTimeWidget1.datetimeChanged.connect(lambda _d:datetimeLabel1.setText(str(_d))) +dateTimeWidget1.datetimeChanged.connect(dateTimeWidget2.setDatetime) +dateTimeWidget2.datetimeChanged.connect(lambda _d:datetimeLabel2.setText(str(_d))) + +root.mainloop()