Skip to content

Commit 89f4e02

Browse files
committed
date entry integration
1 parent 7ef7386 commit 89f4e02

File tree

11 files changed

+187
-106
lines changed

11 files changed

+187
-106
lines changed
5.28 KB
Binary file not shown.

asset/datetimetag_datetime.avif

2.94 KB
Binary file not shown.

docs/Changelog.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# Changelog
22

3-
## 0.7.1
3+
## 0.7.1 (2024-11-27)
44
* GUI scrollbars if window is bigger than the screen
55
* [non-interactive][mininterface.Mininterface.__enter__] session support
6+
* [datetime](Types/#mininterface.types.DatetimeTag) support
7+
* nested generics support (a tuple in a list)
68

79
## 0.7.0 (2024-11-08)
810
* hidden [`--integrate-to-system`](Overview.md#bash-completion) argument

mininterface/cli_parser.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@
1515
import yaml
1616
from tyro import cli
1717
from tyro._argparse_formatter import TyroArgumentParser
18-
from tyro._singleton import NonpropagatingMissingType
18+
from tyro._fields import NonpropagatingMissingType
19+
# NOTE in the future versions of tyro, include that way:
20+
# from tyro._singleton import NonpropagatingMissingType
1921
from tyro.extras import get_parser
2022

21-
from .form_dict import MissingTagValue
22-
2323
from .auxiliary import yield_annotations, yield_defaults
24-
from .form_dict import EnvClass
24+
from .form_dict import EnvClass, MissingTagValue
2525
from .tag import Tag
2626
from .tag_factory import tag_factory
2727
from .validators import not_empty

mininterface/tag_factory.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
from copy import copy
2-
from datetime import datetime
2+
from datetime import date, datetime, time
33
from pathlib import Path
44
from typing import Type, get_type_hints
55

66
from .auxiliary import matches_annotation
77

88
from .tag import Tag
99
from .type_stubs import TagCallback
10-
from .types import CallbackTag, DateTag, PathTag
10+
from .types import CallbackTag, DatetimeTag, PathTag
1111

1212

1313
def _get_annotation_from_class_hierarchy(cls, key):
@@ -28,8 +28,8 @@ def get_type_hint_from_class_hierarchy(cls, key):
2828
def _get_tag_type(tag: Tag) -> Type[Tag]:
2929
if tag._is_subclass(Path):
3030
return PathTag
31-
if tag._is_subclass(datetime):
32-
return DateTag
31+
if tag._is_subclass(date) or tag._is_subclass(time):
32+
return DatetimeTag
3333
return Tag
3434

3535

@@ -39,7 +39,7 @@ def tag_fetch(tag: Tag, ref: dict | None):
3939

4040
def tag_assure_type(tag: Tag):
4141
""" morph to correct class `Tag("", annotation=Path)` -> `PathTag("", annotation=Path)` """
42-
if (type_ := _get_tag_type(tag)) is not Tag:
42+
if (type_ := _get_tag_type(tag)) is not Tag and not isinstance(tag, type_):
4343
return type_(annotation=tag.annotation)._fetch_from(tag)
4444
return tag
4545

mininterface/tk_interface/date_entry.py

Lines changed: 58 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,66 @@
11
import tkinter as tk
22
import re
33
from datetime import datetime
4+
from typing import TYPE_CHECKING
45

56
try:
67
from tkcalendar import Calendar
78
except ImportError:
89
Calendar = None
910

10-
class DateEntry(tk.Frame):
11-
def __init__(self, master=None, **kwargs):
11+
from ..types import DatetimeTag
12+
if TYPE_CHECKING:
13+
from tk_window import TkWindow
14+
15+
16+
class DateEntryFrame(tk.Frame):
17+
def __init__(self, master, tk_app: "TkWindow", tag: DatetimeTag, variable: tk.Variable, **kwargs):
1218
super().__init__(master, **kwargs)
13-
self.create_widgets()
14-
self.pack(expand=True, fill=tk.BOTH)
19+
20+
self.tk_app = tk_app
21+
self.tag = tag
22+
23+
# Date entry
24+
self.spinbox = self.create_spinbox(variable)
25+
26+
# Frame holding the calendar
27+
self.frame = tk.Frame(self)
28+
29+
# The calendar widget
30+
if Calendar:
31+
# Toggle calendar button
32+
tk.Button(self, text="…", command=self.toggle_calendar).grid(row=0, column=1)
33+
34+
# Add a calendar widget
35+
self.calendar = Calendar(self.frame, selectmode='day', date_pattern='yyyy-mm-dd')
36+
# Bind date selection event
37+
self.calendar.bind("<<CalendarSelected>>", self.on_date_select)
38+
self.calendar.grid()
39+
# Initialize calendar with the current date
40+
self.update_calendar(self.spinbox.get(), '%Y-%m-%d %H:%M:%S.%f')
41+
else:
42+
self.calendar = None
43+
1544
self.bind_all_events()
1645

17-
def create_widgets(self):
18-
self.spinbox = tk.Spinbox(self, font=("Arial", 16), width=30, wrap=True)
19-
self.spinbox.pack(padx=20, pady=20)
20-
self.spinbox.insert(0, datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-4])
21-
self.spinbox.focus_set()
22-
self.spinbox.icursor(8)
46+
def create_spinbox(self, variable: tk.Variable):
47+
spinbox = tk.Spinbox(self, font=("Arial", 16), width=30, wrap=True, textvariable=variable)
48+
spinbox.grid()
49+
if not variable.get():
50+
spinbox.insert(0, datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")[:-4])
51+
spinbox.focus_set()
52+
spinbox.icursor(8)
2353

2454
# Bind up/down arrow keys
25-
self.spinbox.bind("<Up>", self.increment_value)
26-
self.spinbox.bind("<Down>", self.decrement_value)
55+
spinbox.bind("<Up>", self.increment_value)
56+
spinbox.bind("<Down>", self.decrement_value)
2757

2858
# Bind mouse click on spinbox arrows
29-
self.spinbox.bind("<ButtonRelease-1>", self.on_spinbox_click)
59+
spinbox.bind("<ButtonRelease-1>", self.on_spinbox_click)
3060

3161
# Bind key release event to update calendar when user changes the input field
32-
self.spinbox.bind("<KeyRelease>", self.on_spinbox_change)
33-
34-
# Toggle calendar button
35-
self.toggle_button = tk.Button(self, text="Show/Hide Calendar", command=self.toggle_calendar)
36-
self.toggle_button.pack(pady=10)
37-
38-
if Calendar:
39-
self.create_calendar()
62+
spinbox.bind("<KeyRelease>", self.on_spinbox_change)
63+
return spinbox
4064

4165
def bind_all_events(self):
4266
# Copy to clipboard with ctrl+c
@@ -51,27 +75,15 @@ def bind_all_events(self):
5175
# Toggle calendar widget with ctrl+shift+c
5276
self.bind_all("<Control-Shift-C>", lambda event: self.toggle_calendar())
5377

54-
def create_calendar(self):
55-
# Create a frame to hold the calendar
56-
self.frame = tk.Frame(self)
57-
self.frame.pack(padx=20, pady=20, expand=True, fill=tk.BOTH)
58-
59-
# Add a calendar widget
60-
self.calendar = Calendar(self.frame, selectmode='day', date_pattern='yyyy-mm-dd')
61-
self.calendar.place(relwidth=0.7, relheight=0.8, anchor='n', relx=0.5)
62-
63-
# Bind date selection event
64-
self.calendar.bind("<<CalendarSelected>>", self.on_date_select)
65-
66-
# Initialize calendar with the current date
67-
self.update_calendar(self.spinbox.get(), '%Y-%m-%d %H:%M:%S.%f')
68-
6978
def toggle_calendar(self, event=None):
70-
if Calendar:
71-
if hasattr(self, 'frame') and self.frame.winfo_ismapped():
72-
self.frame.pack_forget()
73-
else:
74-
self.frame.pack(padx=20, pady=20, expand=True, fill=tk.BOTH)
79+
if not self.calendar:
80+
return
81+
if self.calendar.winfo_ismapped():
82+
self.frame.grid_forget()
83+
else:
84+
self.frame.grid(row=1, column=0)
85+
self.tk_app._refresh_size()
86+
return
7587

7688
def increment_value(self, event=None):
7789
self.change_date(1)
@@ -112,7 +124,8 @@ def change_date(self, delta):
112124
split_input[part_index] = str(new_number).zfill(len(split_input[part_index]))
113125

114126
if time:
115-
new_value_str = f"{split_input[0]}-{split_input[1]}-{split_input[2]} {split_input[3]}:{split_input[4]}:{split_input[5]}.{split_input[6][:2]}"
127+
new_value_str = f"{split_input[0]}-{split_input[1]}-{split_input[2]} "\
128+
f"{split_input[3]}:{split_input[4]}:{split_input[5]}.{split_input[6][:2]}"
116129
string_format = '%Y-%m-%d %H:%M:%S.%f'
117130
else:
118131
new_value_str = f"{split_input[0]}-{split_input[1]}-{split_input[2]}"
@@ -139,9 +152,9 @@ def get_part_index(self, caret_pos, split_length):
139152
elif split_length > 3:
140153
if caret_pos < 14: # hour
141154
return 3
142-
elif caret_pos < 17: # minute
155+
elif caret_pos < 17: # minute
143156
return 4
144-
elif caret_pos < 20: # second
157+
elif caret_pos < 20: # second
145158
return 5
146159
else: # millisecond
147160
return 6
@@ -187,7 +200,7 @@ def show_popup(self, message):
187200
# Position the popup window in the top-left corner of the widget
188201
x = self.winfo_rootx()
189202
y = self.winfo_rooty()
190-
203+
191204
# Position of the popup window has to be "inside" the main window or it will be focused on popup
192205
popup.geometry(f"400x100+{x+200}+{y-150}")
193206

@@ -197,7 +210,6 @@ def show_popup(self, message):
197210
# Keep focus on the spinbox
198211
self.spinbox.focus_force()
199212

200-
201213
def select_all(self, event=None):
202214
self.spinbox.selection_range(0, tk.END)
203215
self.spinbox.focus_set()
@@ -207,31 +219,3 @@ def select_all(self, event=None):
207219
def paste_from_clipboard(self, event=None):
208220
self.spinbox.delete(0, tk.END)
209221
self.spinbox.insert(0, self.clipboard_get())
210-
211-
if __name__ == "__main__":
212-
root = tk.Tk()
213-
# Get the screen width and height
214-
# This is calculating the position of the TOTAL dimentions of all screens combined
215-
# How to calculate the position of the window on the current screen?
216-
screen_width = root.winfo_screenwidth()
217-
screen_height = root.winfo_screenheight()
218-
219-
print(screen_width, screen_height)
220-
221-
# Calculate the position to center the window
222-
x = (screen_width // 2) - 400
223-
y = (screen_height // 2) - 600
224-
225-
print(x, y)
226-
227-
# Set the position of the window
228-
root.geometry(f"800x600+{x}+{y}")
229-
# keep the main widget on top all the time
230-
root.wm_attributes("-topmost", False)
231-
root.wm_attributes("-topmost", True)
232-
root.title("Date Editor")
233-
234-
date_entry = DateEntry(root)
235-
date_entry.pack(expand=True, fill=tk.BOTH)
236-
root.mainloop()
237-

mininterface/tk_interface/utils.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
from typing import TYPE_CHECKING
2-
from autocombobox import AutoCombobox
31
from pathlib import Path, PosixPath
4-
from tkinter import Button, Entry, Label, TclError, Variable, Widget
2+
from tkinter import Button, Entry, Label, TclError, Variable, Widget, Spinbox
53
from tkinter.filedialog import askopenfilename, askopenfilenames
64
from tkinter.ttk import Checkbutton, Combobox, Frame, Radiobutton, Widget
5+
from typing import TYPE_CHECKING
76

7+
from autocombobox import AutoCombobox
88

9-
from ..types import DateTag, PathTag
109
from ..auxiliary import flatten, flatten_keys
1110
from ..experimental import MININTERFACE_CONFIG, FacetCallback, SubmitButton
1211
from ..form_dict import TagDict
1312
from ..tag import Tag
13+
from ..types import DatetimeTag, PathTag
14+
from .date_entry import DateEntryFrame
1415

1516
if TYPE_CHECKING:
1617
from tk_window import TkWindow
@@ -132,14 +133,12 @@ def _fetch(variable):
132133
widget2 = Button(master, text='…', command=choose_file_handler(variable, tag))
133134
widget2.grid(row=grid_info['row'], column=grid_info['column']+1)
134135

135-
# TODO
136136
# Calendar
137-
# elif isinstance(tag, DateTag):
138-
# grid_info = widget.grid_info()
139-
# nested_frame = Frame(master)
140-
# nested_frame.grid(row=grid_info['row'], column=grid_info['column'])
141-
# widget = DateEntry(nested_frame)
142-
# widget.pack()
137+
elif isinstance(tag, DatetimeTag):
138+
grid_info = widget.grid_info()
139+
nested_frame = DateEntryFrame(master, tk_app, tag, variable)
140+
nested_frame.grid(row=grid_info['row'], column=grid_info['column'])
141+
widget = nested_frame.spinbox
143142

144143
# Special type: Submit button
145144
elif tag.annotation is SubmitButton: # NOTE EXPERIMENTAL
@@ -162,7 +161,7 @@ def inner(tag: Tag):
162161
h = on_change_handler(variable, tag)
163162
if isinstance(w, Combobox):
164163
w.bind("<<ComboboxSelected>>", h)
165-
elif isinstance(w, Entry):
164+
elif isinstance(w, (Entry, Spinbox)):
166165
w.bind("<FocusOut>", h)
167166
elif isinstance(w, Checkbutton):
168167
w.configure(command=h)

0 commit comments

Comments
 (0)