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.
Files changed (68) hide show
  1. {psiutils-0.2.20 → psiutils-0.2.23}/PKG-INFO +1 -1
  2. {psiutils-0.2.20 → psiutils-0.2.23}/pyproject.toml +1 -1
  3. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/_date_picker.py +3 -2
  4. psiutils-0.2.23/src/psiutils/_version.py +1 -0
  5. psiutils-0.2.23/src/psiutils/date_picker.py +411 -0
  6. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/widgets.py +0 -1
  7. psiutils-0.2.20/src/psiutils/_version.py +0 -1
  8. {psiutils-0.2.20 → psiutils-0.2.23}/README.md +0 -0
  9. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/__init__.py +0 -0
  10. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/_about_frame.py +0 -0
  11. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/_logger.py +0 -0
  12. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/_notify.py +0 -0
  13. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/buttons.py +0 -0
  14. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/constants.py +0 -0
  15. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/drag_manager.py +0 -0
  16. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/errors.py +0 -0
  17. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icecream_init.py +0 -0
  18. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/backup.png +0 -0
  19. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/build.png +0 -0
  20. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/cancel.png +0 -0
  21. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/check.png +0 -0
  22. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/checkbox_checked.png +0 -0
  23. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/checkbox_unchecked.png +0 -0
  24. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/clear.png +0 -0
  25. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/code.png +0 -0
  26. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/compare.png +0 -0
  27. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/copy_clipboard.png +0 -0
  28. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/copy_docs.png +0 -0
  29. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/delete.png +0 -0
  30. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/diff.png +0 -0
  31. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/done.png +0 -0
  32. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/download.png +0 -0
  33. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/edit.png +0 -0
  34. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/gear.png +0 -0
  35. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/new.png +0 -0
  36. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/next.png +0 -0
  37. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/open.png +0 -0
  38. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/pause.png +0 -0
  39. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/preferences.png +0 -0
  40. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/previous.png +0 -0
  41. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/process.png +0 -0
  42. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/redo.png +0 -0
  43. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/refresh.png +0 -0
  44. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/rename.png +0 -0
  45. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/report.png +0 -0
  46. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/reset.png +0 -0
  47. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/restore.png +0 -0
  48. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/restore_database.png +0 -0
  49. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/restore_page.png +0 -0
  50. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/revert.png +0 -0
  51. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/save.png +0 -0
  52. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/script.png +0 -0
  53. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/search.png +0 -0
  54. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/send.png +0 -0
  55. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/start.png +0 -0
  56. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/update.png +0 -0
  57. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/upgrade.png +0 -0
  58. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/upload.png +0 -0
  59. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/icons/windows.png +0 -0
  60. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/images/icon-error.png +0 -0
  61. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/images/icon-info.png +0 -0
  62. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/images/icon-query.png +0 -0
  63. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/known_paths.py +0 -0
  64. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/menus.py +0 -0
  65. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/messagebox.py +0 -0
  66. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/text.py +0 -0
  67. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/treeview.py +0 -0
  68. {psiutils-0.2.20 → psiutils-0.2.23}/src/psiutils/utilities.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: psiutils
3
- Version: 0.2.20
3
+ Version: 0.2.23
4
4
  Summary: Various TKinter utilities.
5
5
  Author: Jeff
6
6
  Author-email: Jeff <<jeffwatkins2000@gmail.com>>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "psiutils"
3
- version = "0.2.20"
3
+ version = "0.2.23"
4
4
  description = "Various TKinter utilities."
5
5
  authors = [{name = "Jeff", "email" = "<jeffwatkins2000@gmail.com>"}]
6
6
  requires-python = '>= 3.10, < 3.13'
@@ -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
+ )
@@ -6,7 +6,6 @@ import contextlib
6
6
 
7
7
  from .constants import PAD, COLOURS
8
8
  from ._about_frame import AboutFrame
9
- from psiutils._date_picker import DatePicker, TimePicker
10
9
 
11
10
  HAND = 'hand2'
12
11
  DIM_TEXT = '#555'
@@ -1 +0,0 @@
1
- __version__ = '0.2.20'
File without changes