mininterface 0.7.1__tar.gz → 0.7.2__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.
- {mininterface-0.7.1 → mininterface-0.7.2}/PKG-INFO +4 -5
- {mininterface-0.7.1 → mininterface-0.7.2}/README.md +1 -1
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/tag.py +2 -2
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/tag_factory.py +5 -1
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/tk_interface/date_entry.py +134 -64
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/tk_interface/tk_window.py +3 -4
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/tk_interface/utils.py +15 -12
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/types.py +4 -4
- {mininterface-0.7.1 → mininterface-0.7.2}/pyproject.toml +5 -6
- {mininterface-0.7.1 → mininterface-0.7.2}/LICENSE +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/ValidationFail.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/__init__.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/__main__.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/auxiliary.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/cli_parser.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/exceptions.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/experimental.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/facet.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/form_dict.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/interfaces.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/mininterface.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/redirectable.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/showcase.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/start.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/subcommands.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/text_interface.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/textual_interface/__init__.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/textual_interface/textual_adaptor.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/textual_interface/textual_app.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/textual_interface/textual_button_app.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/textual_interface/textual_facet.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/textual_interface/widgets.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/tk_interface/__init__.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/tk_interface/redirect_text_tkinter.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/tk_interface/tk_facet.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/type_stubs.py +0 -0
- {mininterface-0.7.1 → mininterface-0.7.2}/mininterface/validators.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mininterface
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: A minimal access to GUI, TUI, CLI and config
|
|
5
5
|
Home-page: https://github.com/CZ-NIC/mininterface
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -14,15 +14,14 @@ Classifier: Programming Language :: Python :: 3.11
|
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
16
|
Provides-Extra: all
|
|
17
|
-
Provides-Extra:
|
|
17
|
+
Provides-Extra: gui
|
|
18
18
|
Provides-Extra: web
|
|
19
19
|
Requires-Dist: autocombobox (==1.4.2)
|
|
20
20
|
Requires-Dist: humanize
|
|
21
21
|
Requires-Dist: pyyaml
|
|
22
22
|
Requires-Dist: textual (>=0.84,<0.85)
|
|
23
|
-
Requires-Dist: tkcalendar
|
|
24
23
|
Requires-Dist: tkinter-tooltip
|
|
25
|
-
Requires-Dist: tkinter_form (==0.1
|
|
24
|
+
Requires-Dist: tkinter_form (==0.2.1)
|
|
26
25
|
Requires-Dist: tkscrollableframe
|
|
27
26
|
Requires-Dist: typing_extensions
|
|
28
27
|
Requires-Dist: tyro (==0.8.14)
|
|
@@ -132,7 +131,7 @@ The config variables needed by your program are kept in cozy dataclasses. Write
|
|
|
132
131
|
Install with a single command from [PyPi](https://pypi.org/project/mininterface/).
|
|
133
132
|
|
|
134
133
|
```bash
|
|
135
|
-
pip install mininterface
|
|
134
|
+
pip install mininterface[all] # GPLv3 and compatible
|
|
136
135
|
```
|
|
137
136
|
|
|
138
137
|
## Minimal installation
|
|
@@ -102,7 +102,7 @@ The config variables needed by your program are kept in cozy dataclasses. Write
|
|
|
102
102
|
Install with a single command from [PyPi](https://pypi.org/project/mininterface/).
|
|
103
103
|
|
|
104
104
|
```bash
|
|
105
|
-
pip install mininterface
|
|
105
|
+
pip install mininterface[all] # GPLv3 and compatible
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
## Minimal installation
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from ast import literal_eval
|
|
2
2
|
from dataclasses import dataclass, fields
|
|
3
|
-
from datetime import
|
|
3
|
+
from datetime import date, time
|
|
4
4
|
from enum import Enum
|
|
5
5
|
from types import FunctionType, MethodType, NoneType, UnionType
|
|
6
6
|
from typing import TYPE_CHECKING, Any, Callable, Iterable, Optional, Type, TypeVar, Union, get_args, get_origin
|
|
@@ -683,7 +683,7 @@ class Tag:
|
|
|
683
683
|
except (SyntaxError, ValueError):
|
|
684
684
|
self.set_error_text(f"Not a valid {self._repr_annotation()}")
|
|
685
685
|
return False
|
|
686
|
-
elif self.
|
|
686
|
+
elif self._is_subclass((time, date)):
|
|
687
687
|
try:
|
|
688
688
|
out_value = self.annotation.fromisoformat(ui_value)
|
|
689
689
|
except ValueError:
|
|
@@ -72,7 +72,11 @@ def tag_factory(val=None, description=None, annotation=None, *args, _src_obj=Non
|
|
|
72
72
|
new = copy(metadata)
|
|
73
73
|
new.val = val if val is not None else new.val
|
|
74
74
|
new.description = description or new.description
|
|
75
|
-
|
|
75
|
+
if new.annotation is None:
|
|
76
|
+
# pAnnot: Annotated[date, Tag(name="hello")] = datetime.fromisoformat(...)
|
|
77
|
+
# -> DatetimeTag(date=True)
|
|
78
|
+
new.annotation = annotation
|
|
79
|
+
return tag_assure_type(new._fetch_from(Tag(*args, **kwargs)))
|
|
76
80
|
# NOTE The mechanism is not perfect. When done, we may test configs.PathTagClass.
|
|
77
81
|
# * fetch_from will not transfer PathTag.multiple
|
|
78
82
|
# * copy will not transfer list[Path] from `Annotated[list[Path], Tag(...)]`
|
|
@@ -14,11 +14,24 @@ if TYPE_CHECKING:
|
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
class DateEntryFrame(tk.Frame):
|
|
17
|
+
|
|
17
18
|
def __init__(self, master, tk_app: "TkWindow", tag: DatetimeTag, variable: tk.Variable, **kwargs):
|
|
18
19
|
super().__init__(master, **kwargs)
|
|
19
20
|
|
|
20
21
|
self.tk_app = tk_app
|
|
21
22
|
self.tag = tag
|
|
23
|
+
if tag.date and tag.time:
|
|
24
|
+
if tag.full_precision:
|
|
25
|
+
self.datetimeformat = '%Y-%m-%d %H:%M:%S'
|
|
26
|
+
else:
|
|
27
|
+
self.datetimeformat = '%Y-%m-%d %H:%M'
|
|
28
|
+
elif tag.time and not tag.date:
|
|
29
|
+
if tag.full_precision:
|
|
30
|
+
self.datetimeformat = '%H:%M:%S'
|
|
31
|
+
else:
|
|
32
|
+
self.datetimeformat = '%H:%M'
|
|
33
|
+
else:
|
|
34
|
+
self.datetimeformat = '%Y-%m-%d'
|
|
22
35
|
|
|
23
36
|
# Date entry
|
|
24
37
|
self.spinbox = self.create_spinbox(variable)
|
|
@@ -27,7 +40,7 @@ class DateEntryFrame(tk.Frame):
|
|
|
27
40
|
self.frame = tk.Frame(self)
|
|
28
41
|
|
|
29
42
|
# The calendar widget
|
|
30
|
-
if Calendar:
|
|
43
|
+
if Calendar and tag.date:
|
|
31
44
|
# Toggle calendar button
|
|
32
45
|
tk.Button(self, text="…", command=self.toggle_calendar).grid(row=0, column=1)
|
|
33
46
|
|
|
@@ -37,19 +50,20 @@ class DateEntryFrame(tk.Frame):
|
|
|
37
50
|
self.calendar.bind("<<CalendarSelected>>", self.on_date_select)
|
|
38
51
|
self.calendar.grid()
|
|
39
52
|
# Initialize calendar with the current date
|
|
40
|
-
self.update_calendar(self.spinbox.get(),
|
|
53
|
+
self.update_calendar(self.spinbox.get(), self.datetimeformat)
|
|
41
54
|
else:
|
|
42
55
|
self.calendar = None
|
|
43
56
|
|
|
44
|
-
self.bind_all_events()
|
|
45
|
-
|
|
46
57
|
def create_spinbox(self, variable: tk.Variable):
|
|
47
|
-
spinbox = tk.Spinbox(self,
|
|
58
|
+
spinbox = tk.Spinbox(self, wrap=True, textvariable=variable)
|
|
48
59
|
spinbox.grid()
|
|
49
60
|
if not variable.get():
|
|
50
|
-
spinbox.insert(0, datetime.now().strftime(
|
|
61
|
+
spinbox.insert(0, datetime.now().strftime(self.datetimeformat))
|
|
51
62
|
spinbox.focus_set()
|
|
52
|
-
|
|
63
|
+
if (not self.tag.date and self.tag.time):
|
|
64
|
+
spinbox.icursor(0)
|
|
65
|
+
else:
|
|
66
|
+
spinbox.icursor(8)
|
|
53
67
|
|
|
54
68
|
# Bind up/down arrow keys
|
|
55
69
|
spinbox.bind("<Up>", self.increment_value)
|
|
@@ -60,20 +74,20 @@ class DateEntryFrame(tk.Frame):
|
|
|
60
74
|
|
|
61
75
|
# Bind key release event to update calendar when user changes the input field
|
|
62
76
|
spinbox.bind("<KeyRelease>", self.on_spinbox_change)
|
|
63
|
-
return spinbox
|
|
64
77
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
self.bind_all("<Control-c>", self.copy_to_clipboard)
|
|
78
|
+
# Toggle calendar widget with ctrl+shift+c
|
|
79
|
+
spinbox.bind("<Control-Shift-C>", self.toggle_calendar)
|
|
68
80
|
|
|
69
81
|
# Select all in the spinbox with ctrl+a
|
|
70
|
-
|
|
82
|
+
spinbox.bind("<Control-a>", self.select_all)
|
|
83
|
+
|
|
84
|
+
# Copy to clipboard with ctrl+c
|
|
85
|
+
spinbox.bind("<Control-c>", self.copy_to_clipboard)
|
|
71
86
|
|
|
72
87
|
# Paste from clipboard with ctrl+v
|
|
73
|
-
|
|
88
|
+
spinbox.bind("<Control-v>", self.paste_from_clipboard)
|
|
74
89
|
|
|
75
|
-
|
|
76
|
-
self.bind_all("<Control-Shift-C>", lambda event: self.toggle_calendar())
|
|
90
|
+
return spinbox
|
|
77
91
|
|
|
78
92
|
def toggle_calendar(self, event=None):
|
|
79
93
|
if not self.calendar:
|
|
@@ -102,7 +116,10 @@ class DateEntryFrame(tk.Frame):
|
|
|
102
116
|
def find_valid_time(self):
|
|
103
117
|
input = self.spinbox.get()
|
|
104
118
|
# use regex to find the time part
|
|
105
|
-
|
|
119
|
+
if self.tag.full_precision:
|
|
120
|
+
time_part = re.search(r'\d{2}:\d{2}:\d{2}', input)
|
|
121
|
+
else:
|
|
122
|
+
time_part = re.search(r'\d{2}:\d{2}', input)
|
|
106
123
|
if time_part:
|
|
107
124
|
return time_part.group()
|
|
108
125
|
return False
|
|
@@ -114,51 +131,82 @@ class DateEntryFrame(tk.Frame):
|
|
|
114
131
|
date = self.find_valid_date()
|
|
115
132
|
time = self.find_valid_time()
|
|
116
133
|
|
|
117
|
-
if date:
|
|
118
|
-
split_input = re.split(r'[-
|
|
119
|
-
|
|
134
|
+
if date and not time:
|
|
135
|
+
split_input = re.split(r'[-]', date)
|
|
136
|
+
new_value_str = self.increment_part(split_input, caret_pos, delta, '-')
|
|
137
|
+
elif date and time:
|
|
138
|
+
split_input = re.split(r'[- :]', date_str)
|
|
139
|
+
new_value_str = self.increment_part(split_input, caret_pos, delta, ' ')
|
|
140
|
+
elif not date and time:
|
|
141
|
+
split_input = re.split(r'[:]', time)
|
|
142
|
+
new_value_str = self.increment_part(split_input, caret_pos, delta, ':')
|
|
143
|
+
else:
|
|
144
|
+
return
|
|
120
145
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
146
|
+
# Validate the new date
|
|
147
|
+
try:
|
|
148
|
+
datetime.strptime(new_value_str, self.datetimeformat)
|
|
149
|
+
self.spinbox.delete(0, tk.END)
|
|
150
|
+
self.spinbox.insert(0, new_value_str)
|
|
151
|
+
self.spinbox.icursor(caret_pos)
|
|
152
|
+
if Calendar:
|
|
153
|
+
self.update_calendar(new_value_str, self.datetimeformat)
|
|
154
|
+
except ValueError as e:
|
|
155
|
+
pass
|
|
125
156
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
157
|
+
def increment_part(self, split_input, caret_pos, delta, separator):
|
|
158
|
+
part_index = self.get_part_index(caret_pos)
|
|
159
|
+
if part_index > len(split_input) - 1:
|
|
160
|
+
return separator.join(split_input)
|
|
161
|
+
|
|
162
|
+
# Increment or decrement the relevant part
|
|
163
|
+
number = int(split_input[part_index])
|
|
164
|
+
new_number = number + delta
|
|
165
|
+
split_input[part_index] = str(new_number).zfill(len(split_input[part_index]))
|
|
166
|
+
|
|
167
|
+
if self.tag.full_precision and separator == ' ':
|
|
168
|
+
return f"{split_input[0]}-{split_input[1]}-{split_input[2]} "\
|
|
169
|
+
f"{split_input[3]}:{split_input[4]}:{split_input[5]}"
|
|
170
|
+
elif separator == ' ':
|
|
171
|
+
return f"{split_input[0]}-{split_input[1]}-{split_input[2]} "\
|
|
172
|
+
f"{split_input[3]}:{split_input[4]}"
|
|
173
|
+
elif separator == ':':
|
|
174
|
+
if self.tag.full_precision:
|
|
175
|
+
return f"{split_input[0]}:{split_input[1]}:{split_input[2]}"
|
|
130
176
|
else:
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
def get_part_index(self, caret_pos, split_length):
|
|
146
|
-
if caret_pos < 5: # year
|
|
147
|
-
return 0
|
|
148
|
-
elif caret_pos < 8: # month
|
|
149
|
-
return 1
|
|
150
|
-
elif caret_pos < 11: # day
|
|
151
|
-
return 2
|
|
152
|
-
elif split_length > 3:
|
|
153
|
-
if caret_pos < 14: # hour
|
|
177
|
+
return f"{split_input[0]}:{split_input[1]}"
|
|
178
|
+
else:
|
|
179
|
+
return separator.join(split_input)
|
|
180
|
+
|
|
181
|
+
def get_part_index(self, caret_pos):
|
|
182
|
+
if self.tag.date and self.tag.time:
|
|
183
|
+
if caret_pos < 5: # year
|
|
184
|
+
return 0
|
|
185
|
+
elif caret_pos < 8: # month
|
|
186
|
+
return 1
|
|
187
|
+
elif caret_pos < 11: # day
|
|
188
|
+
return 2
|
|
189
|
+
elif caret_pos < 14: # hour
|
|
154
190
|
return 3
|
|
155
191
|
elif caret_pos < 17: # minute
|
|
156
192
|
return 4
|
|
157
|
-
|
|
193
|
+
else: # second
|
|
158
194
|
return 5
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
195
|
+
elif self.tag.date:
|
|
196
|
+
if caret_pos < 5: # year
|
|
197
|
+
return 0
|
|
198
|
+
elif caret_pos < 8: # month
|
|
199
|
+
return 1
|
|
200
|
+
elif caret_pos < 11: # day
|
|
201
|
+
return 2
|
|
202
|
+
elif self.tag.time:
|
|
203
|
+
if caret_pos < 3: # hour
|
|
204
|
+
return 0
|
|
205
|
+
elif caret_pos < 6: # minute
|
|
206
|
+
return 1
|
|
207
|
+
else: # second
|
|
208
|
+
return 2
|
|
209
|
+
return 0
|
|
162
210
|
|
|
163
211
|
def on_spinbox_click(self, event):
|
|
164
212
|
# Check if the click was on the spinbox arrows
|
|
@@ -168,21 +216,39 @@ class DateEntryFrame(tk.Frame):
|
|
|
168
216
|
self.decrement_value()
|
|
169
217
|
|
|
170
218
|
def on_date_select(self, event):
|
|
171
|
-
|
|
219
|
+
|
|
220
|
+
# find caret position to keep it in the same place
|
|
221
|
+
caret_pos = self.spinbox.index(tk.INSERT)
|
|
222
|
+
|
|
223
|
+
selected_date = self.calendar.selection_get().strftime('%Y-%m-%d')
|
|
224
|
+
if self.tag.time:
|
|
225
|
+
time = self.find_valid_time()
|
|
226
|
+
if time:
|
|
227
|
+
selected_date += f" {time}"
|
|
228
|
+
else:
|
|
229
|
+
if self.tag.full_precision:
|
|
230
|
+
selected_date += " 00:00:00"
|
|
231
|
+
else:
|
|
232
|
+
selected_date += " 00:00"
|
|
233
|
+
|
|
234
|
+
|
|
172
235
|
self.spinbox.delete(0, tk.END)
|
|
173
|
-
self.spinbox.insert(0, selected_date
|
|
174
|
-
|
|
236
|
+
self.spinbox.insert(0, selected_date)
|
|
237
|
+
|
|
238
|
+
# Keep the caret position
|
|
239
|
+
self.spinbox.icursor(caret_pos)
|
|
175
240
|
|
|
176
241
|
def on_spinbox_change(self, event):
|
|
177
242
|
if Calendar:
|
|
178
243
|
self.update_calendar(self.spinbox.get())
|
|
179
244
|
|
|
180
245
|
def update_calendar(self, date_str, string_format='%Y-%m-%d'):
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
246
|
+
if self.tag.date:
|
|
247
|
+
try:
|
|
248
|
+
date = datetime.strptime(date_str, string_format)
|
|
249
|
+
self.calendar.selection_set(date)
|
|
250
|
+
except ValueError:
|
|
251
|
+
pass
|
|
186
252
|
|
|
187
253
|
def copy_to_clipboard(self, event=None):
|
|
188
254
|
self.clipboard_clear()
|
|
@@ -194,7 +260,7 @@ class DateEntryFrame(tk.Frame):
|
|
|
194
260
|
popup = tk.Toplevel(self)
|
|
195
261
|
popup.wm_title("")
|
|
196
262
|
|
|
197
|
-
label = tk.Label(popup, text=message
|
|
263
|
+
label = tk.Label(popup, text=message)
|
|
198
264
|
label.pack(side="top", fill="x", pady=10, padx=10)
|
|
199
265
|
|
|
200
266
|
# Position the popup window in the top-left corner of the widget
|
|
@@ -218,4 +284,8 @@ class DateEntryFrame(tk.Frame):
|
|
|
218
284
|
|
|
219
285
|
def paste_from_clipboard(self, event=None):
|
|
220
286
|
self.spinbox.delete(0, tk.END)
|
|
221
|
-
|
|
287
|
+
|
|
288
|
+
def round_time(self, dt):
|
|
289
|
+
if self.tag.full_precision:
|
|
290
|
+
return dt
|
|
291
|
+
return dt[:-4]
|
|
@@ -72,17 +72,16 @@ class TkWindow(Tk, BackendAdaptor):
|
|
|
72
72
|
self.form = Form(self.frame,
|
|
73
73
|
name_form="",
|
|
74
74
|
form_dict=formdict_to_widgetdict(form, self.widgetize),
|
|
75
|
-
|
|
76
|
-
|
|
75
|
+
name_button=submit if isinstance(submit, str) else "Ok",
|
|
76
|
+
button_command=self._ok if submit else None
|
|
77
77
|
)
|
|
78
78
|
self.form.pack()
|
|
79
79
|
|
|
80
80
|
# Add radio etc.
|
|
81
|
-
replace_widgets(self, self.form.
|
|
81
|
+
replace_widgets(self, self.form.fields, form)
|
|
82
82
|
|
|
83
83
|
# Set the submit and exit options
|
|
84
84
|
if self.form.button:
|
|
85
|
-
self.form.button.config(command=self._ok)
|
|
86
85
|
tip, keysym = ("Enter", "<Return>")
|
|
87
86
|
ToolTip(self.form.button, msg=tip) # NOTE is not destroyed in _clear
|
|
88
87
|
self._bind_event(keysym, self._ok)
|
|
@@ -6,6 +6,8 @@ from typing import TYPE_CHECKING
|
|
|
6
6
|
|
|
7
7
|
from autocombobox import AutoCombobox
|
|
8
8
|
|
|
9
|
+
from tkinter_form.tkinter_form import Form, FieldForm
|
|
10
|
+
|
|
9
11
|
from ..auxiliary import flatten, flatten_keys
|
|
10
12
|
from ..experimental import MININTERFACE_CONFIG, FacetCallback, SubmitButton
|
|
11
13
|
from ..form_dict import TagDict
|
|
@@ -42,13 +44,12 @@ class AnyVariable(Variable):
|
|
|
42
44
|
|
|
43
45
|
|
|
44
46
|
def ready_to_replace(widget: Widget,
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
variable: Variable) -> tuple[Widget, dict]:
|
|
47
|
+
variable: Variable,
|
|
48
|
+
field_form: FieldForm) -> tuple[Widget, dict]:
|
|
48
49
|
if widget.winfo_manager() == 'grid':
|
|
49
50
|
grid_info = widget.grid_info()
|
|
50
51
|
widget.grid_forget()
|
|
51
|
-
|
|
52
|
+
field_form.variable = variable
|
|
52
53
|
return grid_info
|
|
53
54
|
else:
|
|
54
55
|
raise ValueError(f"GuiInterface: Cannot tackle the form, unknown winfo_manager {widget.winfo_manager()}.")
|
|
@@ -87,17 +88,18 @@ def _set_true(variable: Variable, tag: Tag):
|
|
|
87
88
|
|
|
88
89
|
def replace_widgets(tk_app: "TkWindow", nested_widgets, form: TagDict):
|
|
89
90
|
def _fetch(variable):
|
|
90
|
-
return ready_to_replace(widget,
|
|
91
|
+
return ready_to_replace(widget, variable, field_form)
|
|
91
92
|
|
|
92
93
|
# NOTE tab order broken, injected to another position
|
|
93
94
|
# NOTE should the button receive tag or directly
|
|
94
95
|
# the whole facet (to change the current form)? Specifiable by experimental.FacetCallback.
|
|
95
96
|
nested_widgets = widgets_to_dict(nested_widgets)
|
|
96
|
-
for
|
|
97
|
+
for tag, field_form in zip(flatten(form), flatten(nested_widgets)):
|
|
97
98
|
tag: Tag
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
field_form: FieldForm
|
|
100
|
+
label1: Widget = field_form.label
|
|
101
|
+
widget: Widget = field_form.widget
|
|
102
|
+
variable = field_form.variable
|
|
101
103
|
subwidgets = []
|
|
102
104
|
master = widget.master
|
|
103
105
|
|
|
@@ -136,6 +138,7 @@ def replace_widgets(tk_app: "TkWindow", nested_widgets, form: TagDict):
|
|
|
136
138
|
# Calendar
|
|
137
139
|
elif isinstance(tag, DatetimeTag):
|
|
138
140
|
grid_info = widget.grid_info()
|
|
141
|
+
widget.grid_forget()
|
|
139
142
|
nested_frame = DateEntryFrame(master, tk_app, tag, variable)
|
|
140
143
|
nested_frame.grid(row=grid_info['row'], column=grid_info['column'])
|
|
141
144
|
widget = nested_frame.spinbox
|
|
@@ -184,15 +187,15 @@ def create_button(master, _fetch, tag, label1, command=None):
|
|
|
184
187
|
return variable, widget2
|
|
185
188
|
|
|
186
189
|
|
|
187
|
-
def widgets_to_dict(widgets_dict) -> dict:
|
|
190
|
+
def widgets_to_dict(widgets_dict) -> dict[str, dict | FieldForm]:
|
|
188
191
|
""" Convert tkinter_form.widgets to a dict """
|
|
189
192
|
result = {}
|
|
190
193
|
for key, value in widgets_dict.items():
|
|
191
194
|
if isinstance(value, dict):
|
|
192
195
|
result[key] = widgets_to_dict(value)
|
|
193
|
-
elif
|
|
196
|
+
elif isinstance(value, Form):
|
|
194
197
|
# this is another tkinter_form.Form, recursively parse
|
|
195
|
-
result[key] = widgets_to_dict(value.
|
|
198
|
+
result[key] = widgets_to_dict(value.fields)
|
|
196
199
|
else: # value is a tuple of (Label, Widget (like Entry))
|
|
197
200
|
result[key] = value
|
|
198
201
|
return result
|
|
@@ -222,7 +222,7 @@ class DatetimeTag(Tag):
|
|
|
222
222
|
time: bool = False
|
|
223
223
|
""" The time part is active """
|
|
224
224
|
|
|
225
|
-
full_precision:
|
|
225
|
+
full_precision: bool = False
|
|
226
226
|
""" Include full time precison, seconds, microseconds. """
|
|
227
227
|
|
|
228
228
|
def __post_init__(self):
|
|
@@ -230,9 +230,9 @@ class DatetimeTag(Tag):
|
|
|
230
230
|
if self.annotation:
|
|
231
231
|
self.date = issubclass(self.annotation, date)
|
|
232
232
|
self.time = issubclass(self.annotation, time) or issubclass(self.annotation, datetime)
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
#
|
|
233
|
+
# NOTE: remove
|
|
234
|
+
# if not self.time and self.full_precision:
|
|
235
|
+
# self.full_precision = False
|
|
236
236
|
|
|
237
237
|
def _make_default_value(self):
|
|
238
238
|
return datetime.now()
|
|
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
|
|
|
4
4
|
|
|
5
5
|
[tool.poetry]
|
|
6
6
|
name = "mininterface"
|
|
7
|
-
version = "0.7.
|
|
7
|
+
version = "0.7.2"
|
|
8
8
|
description = "A minimal access to GUI, TUI, CLI and config"
|
|
9
9
|
authors = ["Edvard Rejthar <edvard.rejthar@nic.cz>"]
|
|
10
10
|
license = "GPL-3.0-or-later"
|
|
@@ -19,17 +19,16 @@ typing_extensions = "*"
|
|
|
19
19
|
pyyaml = "*"
|
|
20
20
|
# Standard requirements
|
|
21
21
|
autocombobox = "1.4.2"
|
|
22
|
-
humanize = "*"
|
|
22
|
+
humanize = "*" # used only in the TkInterface, hence it is not a minimal requirement
|
|
23
23
|
textual = "~0.84"
|
|
24
24
|
tkinter-tooltip = "*"
|
|
25
|
-
tkinter_form = "0.1
|
|
25
|
+
tkinter_form = "0.2.1"
|
|
26
26
|
tkscrollableframe = "*"
|
|
27
|
-
tkcalendar = "*" # TODO put into extras?
|
|
28
27
|
|
|
29
28
|
[tool.poetry.extras]
|
|
30
29
|
web = ["textual-serve"]
|
|
31
|
-
|
|
32
|
-
all = ["textual-serve", "pillow"]
|
|
30
|
+
gui = ["pillow", "tkcalendar"]
|
|
31
|
+
all = ["textual-serve", "pillow", "tkcalendar"]
|
|
33
32
|
|
|
34
33
|
[tool.poetry.scripts]
|
|
35
34
|
mininterface = "mininterface.__main__:main"
|
|
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
|
{mininterface-0.7.1 → mininterface-0.7.2}/mininterface/textual_interface/textual_button_app.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{mininterface-0.7.1 → mininterface-0.7.2}/mininterface/tk_interface/redirect_text_tkinter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|