psiutils 0.2.20__tar.gz → 0.2.23__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {psiutils-0.2.20 → psiutils-0.2.23}/PKG-INFO +1 -1
- {psiutils-0.2.20 → psiutils-0.2.23}/pyproject.toml +1 -1
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/_date_picker.py +3 -2
- psiutils-0.2.23/src/psiutils/_version.py +1 -0
- psiutils-0.2.23/src/psiutils/date_picker.py +411 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/widgets.py +0 -1
- psiutils-0.2.20/src/psiutils/_version.py +0 -1
- {psiutils-0.2.20 → psiutils-0.2.23}/README.md +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/__init__.py +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/_about_frame.py +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/_logger.py +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/_notify.py +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/buttons.py +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/constants.py +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/drag_manager.py +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/errors.py +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icecream_init.py +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/backup.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/build.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/cancel.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/check.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/checkbox_checked.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/checkbox_unchecked.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/clear.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/code.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/compare.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/copy_clipboard.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/copy_docs.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/delete.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/diff.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/done.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/download.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/edit.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/gear.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/new.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/next.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/open.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/pause.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/preferences.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/previous.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/process.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/redo.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/refresh.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/rename.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/report.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/reset.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/restore.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/restore_database.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/restore_page.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/revert.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/save.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/script.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/search.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/send.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/start.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/update.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/upgrade.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/upload.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/windows.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/images/icon-error.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/images/icon-info.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/images/icon-query.png +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/known_paths.py +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/menus.py +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/messagebox.py +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/text.py +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/treeview.py +0 -0
- {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/utilities.py +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
|
|
2
|
+
from functools import partial
|
|
1
3
|
import tkinter as tk
|
|
2
4
|
from tkinter import ttk
|
|
3
5
|
import tkinter.font as tkFont
|
|
4
6
|
from tkcalendar import DateEntry
|
|
5
|
-
from functools import partial
|
|
6
7
|
from datetime import datetime, timedelta
|
|
7
8
|
from dateutil.parser import parse
|
|
8
9
|
|
|
@@ -15,7 +16,7 @@ TIME_WIDTH = 3
|
|
|
15
16
|
INCREMENT_BUTTON_SIZE = 2
|
|
16
17
|
INCREMENT_BUTTON_FONT_SIZE = 8
|
|
17
18
|
DATE_FORMAT = '%d/%m/%Y'
|
|
18
|
-
PICKER_DATE_PATTERN = 'dd/mm/yyyy'
|
|
19
|
+
PICKER_DATE_PATTERN = 'dd/mm/yyyy'
|
|
19
20
|
MAX_HOURS = 23
|
|
20
21
|
MAX_MINS = 59
|
|
21
22
|
TALL_COMBO_PADDING = 6
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.2.23'
|
|
@@ -0,0 +1,411 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Implement DayPicker an TimePicker widgets for psiutils.
|
|
3
|
+
"""
|
|
4
|
+
import tkinter as tk
|
|
5
|
+
from tkinter import ttk
|
|
6
|
+
from datetime import datetime, timedelta
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from functools import partial
|
|
9
|
+
from tkcalendar import DateEntry
|
|
10
|
+
from dateutil.parser import parse
|
|
11
|
+
|
|
12
|
+
from psiutils.text import Text
|
|
13
|
+
|
|
14
|
+
txt = Text()
|
|
15
|
+
|
|
16
|
+
PAD = 2
|
|
17
|
+
TIME_WIDTH = 3
|
|
18
|
+
INCREMENT_BUTTON_SIZE = 2
|
|
19
|
+
INCREMENT_BUTTON_FONT_SIZE = 8
|
|
20
|
+
DATE_FORMAT = '%d/%m/%Y'
|
|
21
|
+
PICKER_DATE_PATTERN = 'dd/mm/yyyy'
|
|
22
|
+
MAX_HOURS = 23
|
|
23
|
+
MAX_MINS = 59
|
|
24
|
+
TALL_COMBO_PADDING = 6
|
|
25
|
+
|
|
26
|
+
HOUR_COL = 0
|
|
27
|
+
MINUTE_COL = 1
|
|
28
|
+
SECOND_COL = 2
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class DatePicker(tk.Frame):
|
|
32
|
+
"""
|
|
33
|
+
A Tkinter widget for selecting a date using a calendar picker
|
|
34
|
+
with increment and decrement controls.
|
|
35
|
+
|
|
36
|
+
The widget displays a date entry field backed by a StringVar and
|
|
37
|
+
allows the user to adjust the selected date one day at a time
|
|
38
|
+
using arrow buttons.
|
|
39
|
+
|
|
40
|
+
Example:
|
|
41
|
+
picker = DatePicker(root)
|
|
42
|
+
picker.pack()
|
|
43
|
+
|
|
44
|
+
selected_date = picker.date
|
|
45
|
+
"""
|
|
46
|
+
def __init__(
|
|
47
|
+
self, master: tk.Frame,
|
|
48
|
+
initial_date: datetime = None,
|
|
49
|
+
date_format: str = ''):
|
|
50
|
+
"""
|
|
51
|
+
Initialize the DatePicker widget.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
master (tk.Frame): Parent widget.
|
|
55
|
+
initial_date (datetime, optional): Initial date to display.
|
|
56
|
+
Defaults to the current date.
|
|
57
|
+
date_format (str, optional): Format string used to display
|
|
58
|
+
the date. Defaults to DATE_FORMAT.
|
|
59
|
+
"""
|
|
60
|
+
super().__init__(master)
|
|
61
|
+
if not initial_date:
|
|
62
|
+
initial_date = datetime.now()
|
|
63
|
+
if not date_format:
|
|
64
|
+
date_format = DATE_FORMAT
|
|
65
|
+
|
|
66
|
+
self._date_input = tk.StringVar(
|
|
67
|
+
value=initial_date.strftime(DATE_FORMAT))
|
|
68
|
+
|
|
69
|
+
style = ttk.Style()
|
|
70
|
+
style.configure(
|
|
71
|
+
'Increment.TButton',
|
|
72
|
+
font=('Helvetica', INCREMENT_BUTTON_FONT_SIZE),
|
|
73
|
+
padding=0,)
|
|
74
|
+
style.configure('Tall.TCombobox', padding=TALL_COMBO_PADDING)
|
|
75
|
+
|
|
76
|
+
main_frame = self._picker()
|
|
77
|
+
main_frame.pack()
|
|
78
|
+
|
|
79
|
+
def _picker(self) -> tk.Frame:
|
|
80
|
+
"""
|
|
81
|
+
Create and layout the main date picker UI.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
tk.Frame: The populated frame containing the date entry
|
|
85
|
+
and increment/decrement buttons.
|
|
86
|
+
"""
|
|
87
|
+
frame = ttk.Frame(self)
|
|
88
|
+
|
|
89
|
+
column = 0
|
|
90
|
+
date_picker = self._date_picker(frame, self._date_input)
|
|
91
|
+
date_picker.grid(row=0, column=column, rowspan=2, sticky=tk.NS)
|
|
92
|
+
|
|
93
|
+
column += 1
|
|
94
|
+
button = ttk.Button(
|
|
95
|
+
frame,
|
|
96
|
+
text=txt.INCREMENT_ARROW,
|
|
97
|
+
command=partial(self._date_increment, self._date_input),
|
|
98
|
+
width=INCREMENT_BUTTON_SIZE,
|
|
99
|
+
style='Increment.TButton',
|
|
100
|
+
)
|
|
101
|
+
button.grid(row=0, column=column, padx=PAD)
|
|
102
|
+
|
|
103
|
+
button = ttk.Button(
|
|
104
|
+
frame,
|
|
105
|
+
text=txt.DECREMENT_ARROW,
|
|
106
|
+
command=partial(self._date_increment, self._date_input, -1),
|
|
107
|
+
width=INCREMENT_BUTTON_SIZE,
|
|
108
|
+
style='Increment.TButton',
|
|
109
|
+
)
|
|
110
|
+
button.grid(row=1, column=column, padx=PAD)
|
|
111
|
+
|
|
112
|
+
column += 1
|
|
113
|
+
|
|
114
|
+
return frame
|
|
115
|
+
|
|
116
|
+
def _date_picker(
|
|
117
|
+
self, master: tk.Frame, textvariable: tk.StringVar) -> DateEntry:
|
|
118
|
+
"""
|
|
119
|
+
Create a calendar-based date entry widget.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
master (tk.Frame): Parent container.
|
|
123
|
+
textvariable (tk.StringVar): Variable bound to the selected date.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
DateEntry: A configured calendar date entry widget.
|
|
127
|
+
"""
|
|
128
|
+
event_date = datetime.now()
|
|
129
|
+
return DateEntry(
|
|
130
|
+
master,
|
|
131
|
+
date_pattern=PICKER_DATE_PATTERN,
|
|
132
|
+
year=event_date.year,
|
|
133
|
+
month=event_date.month,
|
|
134
|
+
day=event_date.day,
|
|
135
|
+
textvariable=textvariable,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
@property
|
|
139
|
+
def date(self) -> datetime:
|
|
140
|
+
"""
|
|
141
|
+
Get the currently selected date.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
datetime: The selected date parsed from the input field.
|
|
145
|
+
"""
|
|
146
|
+
return parse(self._date_input.get())
|
|
147
|
+
|
|
148
|
+
@date.setter
|
|
149
|
+
def date(self, value: datetime) -> None:
|
|
150
|
+
"""
|
|
151
|
+
Set the currently selected date.
|
|
152
|
+
"""
|
|
153
|
+
self._date_input.set(value.strftime(DATE_FORMAT))
|
|
154
|
+
|
|
155
|
+
def _date_increment(
|
|
156
|
+
self,
|
|
157
|
+
textvariable: tk.StringVar,
|
|
158
|
+
increment: int = 1,
|
|
159
|
+
) -> None:
|
|
160
|
+
"""
|
|
161
|
+
Increment or decrement the selected date by a number of days.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
textvariable (tk.StringVar): The date value to update.
|
|
165
|
+
increment (int): Number of days to add or subtract.
|
|
166
|
+
Defaults to 1.
|
|
167
|
+
"""
|
|
168
|
+
date = parse(textvariable.get(), dayfirst=True).date()
|
|
169
|
+
new_date = date + timedelta(days=increment)
|
|
170
|
+
textvariable.set(new_date.strftime(DATE_FORMAT))
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@dataclass
|
|
174
|
+
class Time():
|
|
175
|
+
"""
|
|
176
|
+
Simple value object representing a time selection.
|
|
177
|
+
|
|
178
|
+
Attributes:
|
|
179
|
+
hour (int): Hour value (0-24).
|
|
180
|
+
minute (int): Minute value (0-59).
|
|
181
|
+
second (int): Second value (0-59).
|
|
182
|
+
"""
|
|
183
|
+
hour: int = 0
|
|
184
|
+
minute: int = 0
|
|
185
|
+
second: int = 0
|
|
186
|
+
|
|
187
|
+
def on(self, date: datetime) -> datetime:
|
|
188
|
+
"""Return a datetime by applying this time to the given date."""
|
|
189
|
+
return datetime(
|
|
190
|
+
year=date.year,
|
|
191
|
+
month=date.month,
|
|
192
|
+
day=date.day,
|
|
193
|
+
hour=self.hour,
|
|
194
|
+
minute=self.minute,
|
|
195
|
+
second=self.second,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
DAY_START = Time(0, 0, 0)
|
|
200
|
+
MIDNIGHT = Time(23, 59, 59)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class TimePicker(tk.Frame):
|
|
204
|
+
"""
|
|
205
|
+
A Tkinter widget for selecting a time using comboboxes and
|
|
206
|
+
increment/decrement buttons.
|
|
207
|
+
|
|
208
|
+
The widget supports hours and minutes by default, with optional
|
|
209
|
+
seconds and optional column labels.
|
|
210
|
+
|
|
211
|
+
Example:
|
|
212
|
+
picker = TimePicker(root, use_seconds=True, use_labels=True)
|
|
213
|
+
picker.pack()
|
|
214
|
+
|
|
215
|
+
selected_time = picker.time
|
|
216
|
+
print(selected_time.hour, selected_time.minute, selected_time.second)
|
|
217
|
+
"""
|
|
218
|
+
def __init__(
|
|
219
|
+
self,
|
|
220
|
+
master: tk.Frame,
|
|
221
|
+
time: Time = Time(0, 0, 0),
|
|
222
|
+
use_seconds: bool = False,
|
|
223
|
+
use_labels: bool = False):
|
|
224
|
+
"""
|
|
225
|
+
Initialize the TimePicker widget.
|
|
226
|
+
|
|
227
|
+
Args:
|
|
228
|
+
master (tk.Frame): Parent widget.
|
|
229
|
+
time (Time): The initial time setting (hour, minute, second)
|
|
230
|
+
use_seconds (bool): Whether to include a seconds selector.
|
|
231
|
+
use_labels (bool): Whether to show labels above each selector.
|
|
232
|
+
"""
|
|
233
|
+
super().__init__(master)
|
|
234
|
+
self.use_seconds = use_seconds
|
|
235
|
+
self.use_labels = use_labels
|
|
236
|
+
|
|
237
|
+
self._hour_input = tk.StringVar(value=f'{time.hour:02d}')
|
|
238
|
+
self._minute_input = tk.StringVar(value=f'{time.minute:02d}')
|
|
239
|
+
self._second_input = tk.StringVar(value=f'{time.second:02d}')
|
|
240
|
+
|
|
241
|
+
style = ttk.Style()
|
|
242
|
+
style.configure('Increment.TButton', font=('Helvetica', 8))
|
|
243
|
+
|
|
244
|
+
main_frame = self._picker()
|
|
245
|
+
main_frame.grid(row=0, column=0)
|
|
246
|
+
|
|
247
|
+
def _picker(self) -> tk.Frame:
|
|
248
|
+
"""
|
|
249
|
+
Create and layout the main picker UI.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
tk.Frame: The populated frame containing the time controls.
|
|
253
|
+
"""
|
|
254
|
+
frame = ttk.Frame(self)
|
|
255
|
+
|
|
256
|
+
row = 0
|
|
257
|
+
if self.use_labels:
|
|
258
|
+
row = self._label_row(frame, row)
|
|
259
|
+
|
|
260
|
+
hour_timer = self._timer_element(frame, self._hour_input, MAX_HOURS)
|
|
261
|
+
hour_timer.grid(row=row, column=HOUR_COL)
|
|
262
|
+
|
|
263
|
+
minute_timer = self._timer_element(frame, self._minute_input)
|
|
264
|
+
minute_timer.grid(row=row, column=MINUTE_COL)
|
|
265
|
+
|
|
266
|
+
if self.use_seconds:
|
|
267
|
+
second_timer = self._timer_element(frame, self._second_input)
|
|
268
|
+
second_timer.grid(row=row, column=SECOND_COL)
|
|
269
|
+
|
|
270
|
+
return frame
|
|
271
|
+
|
|
272
|
+
def _label_row(self, frame: tk.Frame, row: int) -> int:
|
|
273
|
+
"""
|
|
274
|
+
Create and place the label row for the time picker.
|
|
275
|
+
|
|
276
|
+
Adds column headers for hours and minutes, and optionally seconds,
|
|
277
|
+
aligned with their corresponding timer controls.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
frame (tk.Frame): The parent container in which the labels are placed.
|
|
281
|
+
row (int): The grid row index to place the labels on.
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
int: The row index used for the labels (unchanged).
|
|
285
|
+
"""
|
|
286
|
+
label = ttk.Label(frame, text='Hour')
|
|
287
|
+
label.grid(row=row, column=HOUR_COL, sticky=tk.W)
|
|
288
|
+
|
|
289
|
+
label = ttk.Label(frame, text='Mins')
|
|
290
|
+
label.grid(row=row, column=MINUTE_COL, sticky=tk.W)
|
|
291
|
+
|
|
292
|
+
if self.use_seconds:
|
|
293
|
+
label = ttk.Label(frame, text='Secs')
|
|
294
|
+
label.grid(row=row, column=SECOND_COL, sticky=tk.E)
|
|
295
|
+
return row + 1
|
|
296
|
+
|
|
297
|
+
def _timer_element(
|
|
298
|
+
self,
|
|
299
|
+
master: tk.Frame,
|
|
300
|
+
textvariable: tk.StringVar,
|
|
301
|
+
max_value: int = MAX_MINS,
|
|
302
|
+
) -> tk.Frame:
|
|
303
|
+
"""
|
|
304
|
+
Create a single timer control consisting of a combobox and
|
|
305
|
+
increment/decrement buttons.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
master (tk.Frame): Parent container.
|
|
309
|
+
textvariable (tk.StringVar): Variable bound to the combobox value.
|
|
310
|
+
max_value (int): Maximum allowed value (wraps around).
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
tk.Frame: The assembled timer element.
|
|
314
|
+
"""
|
|
315
|
+
frame = ttk.Frame(master)
|
|
316
|
+
column = 0
|
|
317
|
+
|
|
318
|
+
combobox = ttk.Combobox(
|
|
319
|
+
frame,
|
|
320
|
+
textvariable=textvariable,
|
|
321
|
+
values=[f'{x:02d}' for x in range(max_value+1)],
|
|
322
|
+
width=TIME_WIDTH,
|
|
323
|
+
style='Tall.TCombobox',
|
|
324
|
+
)
|
|
325
|
+
combobox.grid(row=0, column=column, rowspan=2, sticky=tk.W)
|
|
326
|
+
|
|
327
|
+
column += 1
|
|
328
|
+
|
|
329
|
+
button = ttk.Button(
|
|
330
|
+
frame,
|
|
331
|
+
text=txt.INCREMENT_ARROW,
|
|
332
|
+
command=partial(self._time_increment, textvariable, 1, max_value),
|
|
333
|
+
width=INCREMENT_BUTTON_SIZE,
|
|
334
|
+
style='Increment.TButton',
|
|
335
|
+
)
|
|
336
|
+
button.grid(row=0, column=column, padx=PAD)
|
|
337
|
+
|
|
338
|
+
button = ttk.Button(
|
|
339
|
+
frame,
|
|
340
|
+
text=txt.DECREMENT_ARROW,
|
|
341
|
+
command=partial(self._time_increment, textvariable, -1, max_value),
|
|
342
|
+
width=INCREMENT_BUTTON_SIZE,
|
|
343
|
+
style='Increment.TButton',
|
|
344
|
+
)
|
|
345
|
+
button.grid(row=1, column=column, padx=PAD)
|
|
346
|
+
return frame
|
|
347
|
+
|
|
348
|
+
def _time_increment(
|
|
349
|
+
self,
|
|
350
|
+
textvariable: tk.StringVar,
|
|
351
|
+
increment,
|
|
352
|
+
max_value) -> None:
|
|
353
|
+
"""
|
|
354
|
+
Increment or decrement a time value with wrap-around behavior.
|
|
355
|
+
|
|
356
|
+
Args:
|
|
357
|
+
textvariable (tk.StringVar): The value to update.
|
|
358
|
+
increment (int): Amount to add (usually ±1).
|
|
359
|
+
max_value (int): Maximum allowed value.
|
|
360
|
+
"""
|
|
361
|
+
value = int(textvariable.get()) + increment
|
|
362
|
+
if value < 0:
|
|
363
|
+
value = max_value
|
|
364
|
+
if value > max_value:
|
|
365
|
+
value = 0
|
|
366
|
+
textvariable.set(f'{value:02d}')
|
|
367
|
+
|
|
368
|
+
@property
|
|
369
|
+
def time(self) -> Time:
|
|
370
|
+
"""
|
|
371
|
+
Get the currently selected time as a Time object.
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
Time: The selected hour, minute, and second values.
|
|
375
|
+
"""
|
|
376
|
+
return Time(self.hour, self.minute, self.second)
|
|
377
|
+
|
|
378
|
+
@time.setter
|
|
379
|
+
def time(self, value: Time) -> None:
|
|
380
|
+
"""
|
|
381
|
+
Set the currently selected time.
|
|
382
|
+
"""
|
|
383
|
+
self._hour_input.set(f'{value.hour:02d}')
|
|
384
|
+
self._minute_input.set(f'{value.minute:02d}')
|
|
385
|
+
self._second_input.set(f'{value.second:02d}')
|
|
386
|
+
|
|
387
|
+
@property
|
|
388
|
+
def hour(self) -> int:
|
|
389
|
+
"""Return the selected hour value."""
|
|
390
|
+
return int(self._hour_input.get())
|
|
391
|
+
|
|
392
|
+
@property
|
|
393
|
+
def minute(self) -> int:
|
|
394
|
+
"""Return the selected minute value."""
|
|
395
|
+
return int(self._minute_input.get())
|
|
396
|
+
|
|
397
|
+
@property
|
|
398
|
+
def second(self) -> int:
|
|
399
|
+
"""Return the selected second value."""
|
|
400
|
+
return int(self._second_input.get())
|
|
401
|
+
|
|
402
|
+
def on(self, date: datetime) -> datetime:
|
|
403
|
+
"""Return a datetime by applying this time to the given date."""
|
|
404
|
+
return datetime(
|
|
405
|
+
year=date.year,
|
|
406
|
+
month=date.month,
|
|
407
|
+
day=date.day,
|
|
408
|
+
hour=self.hour,
|
|
409
|
+
minute=self.minute,
|
|
410
|
+
second=self.second,
|
|
411
|
+
)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = '0.2.20'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|