mininterface 0.7.2__tar.gz → 0.7.3__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.2 → mininterface-0.7.3}/PKG-INFO +8 -2
- {mininterface-0.7.2 → mininterface-0.7.3}/README.md +4 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/cli_parser.py +33 -22
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/textual_interface/textual_adaptor.py +5 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/textual_interface/textual_app.py +9 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/textual_interface/textual_button_app.py +3 -0
- mininterface-0.7.3/mininterface/textual_interface/textual_facet.py +64 -0
- mininterface-0.7.3/mininterface/tk_interface/external_fix.py +74 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/tk_interface/tk_facet.py +7 -4
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/tk_interface/utils.py +1 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/types.py +3 -10
- {mininterface-0.7.2 → mininterface-0.7.3}/pyproject.toml +4 -2
- mininterface-0.7.2/mininterface/textual_interface/textual_facet.py +0 -31
- {mininterface-0.7.2 → mininterface-0.7.3}/LICENSE +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/ValidationFail.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/__init__.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/__main__.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/auxiliary.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/exceptions.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/experimental.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/facet.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/form_dict.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/interfaces.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/mininterface.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/redirectable.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/showcase.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/start.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/subcommands.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/tag.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/tag_factory.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/text_interface.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/textual_interface/__init__.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/textual_interface/widgets.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/tk_interface/__init__.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/tk_interface/date_entry.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/tk_interface/redirect_text_tkinter.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/tk_interface/tk_window.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/type_stubs.py +0 -0
- {mininterface-0.7.2 → mininterface-0.7.3}/mininterface/validators.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: mininterface
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.3
|
|
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
|
|
@@ -15,6 +15,8 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
16
|
Provides-Extra: all
|
|
17
17
|
Provides-Extra: gui
|
|
18
|
+
Provides-Extra: img
|
|
19
|
+
Provides-Extra: tui
|
|
18
20
|
Provides-Extra: web
|
|
19
21
|
Requires-Dist: autocombobox (==1.4.2)
|
|
20
22
|
Requires-Dist: humanize
|
|
@@ -143,6 +145,10 @@ pip install --no-dependencies mininterface
|
|
|
143
145
|
pip install tyro typing_extensions pyyaml
|
|
144
146
|
```
|
|
145
147
|
|
|
148
|
+
## MacOS GUI
|
|
149
|
+
|
|
150
|
+
If the GUI does not work on MacOS, you might need to launch: `brew install python-tk`
|
|
151
|
+
|
|
146
152
|
# Docs
|
|
147
153
|
See the docs overview at [https://cz-nic.github.io/mininterface/](https://cz-nic.github.io/mininterface/Overview/).
|
|
148
154
|
|
|
@@ -114,6 +114,10 @@ pip install --no-dependencies mininterface
|
|
|
114
114
|
pip install tyro typing_extensions pyyaml
|
|
115
115
|
```
|
|
116
116
|
|
|
117
|
+
## MacOS GUI
|
|
118
|
+
|
|
119
|
+
If the GUI does not work on MacOS, you might need to launch: `brew install python-tk`
|
|
120
|
+
|
|
117
121
|
# Docs
|
|
118
122
|
See the docs overview at [https://cz-nic.github.io/mininterface/](https://cz-nic.github.io/mininterface/Overview/).
|
|
119
123
|
|
|
@@ -150,7 +150,7 @@ def run_tyro_parser(env_or_list: Type[EnvClass] | list[Type[EnvClass]],
|
|
|
150
150
|
if ask_for_missing and getattr(e, "code", None) == 2 and eavesdrop:
|
|
151
151
|
# Some required arguments are missing. Determine which.
|
|
152
152
|
wf = {}
|
|
153
|
-
for arg in
|
|
153
|
+
for arg in _fetch_eavesdrop_args():
|
|
154
154
|
treat_missing(type_form, kwargs, parser, wf, arg)
|
|
155
155
|
|
|
156
156
|
# Second attempt to parse CLI
|
|
@@ -195,7 +195,6 @@ def treat_missing(env_class, kwargs: dict, parser: ArgumentParser, wf: dict, arg
|
|
|
195
195
|
# However, the UI then is not able to use ex. the number filtering capabilities.
|
|
196
196
|
# Putting there None is not a good idea as dataclass_to_tagdict fails if None is not allowed by the annotation.
|
|
197
197
|
tag = wf[field_name] = tag_factory(MissingTagValue(),
|
|
198
|
-
# tag = wf[field_name] = tag_factory(MISSING,
|
|
199
198
|
argument.help.replace("(required)", ""),
|
|
200
199
|
validation=not_empty,
|
|
201
200
|
_src_class=env_class,
|
|
@@ -205,9 +204,17 @@ def treat_missing(env_class, kwargs: dict, parser: ArgumentParser, wf: dict, arg
|
|
|
205
204
|
# A None would be enough because Mininterface will ask for the missing values
|
|
206
205
|
# promply, however, Pydantic model would fail.
|
|
207
206
|
# As it serves only for tyro parsing and the field is marked wrong, the made up value is never used or seen.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
207
|
+
set_default(kwargs, field_name, tag._make_default_value())
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def _fetch_eavesdrop_args():
|
|
211
|
+
return eavesdrop.partition(":")[2].strip().split(", ")
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def set_default(kwargs, field_name, val):
|
|
215
|
+
if "default" not in kwargs:
|
|
216
|
+
kwargs["default"] = SimpleNamespace()
|
|
217
|
+
setattr(kwargs["default"], field_name, val)
|
|
211
218
|
|
|
212
219
|
|
|
213
220
|
def _parse_cli(env_or_list: Type[EnvClass] | list[Type[EnvClass]],
|
|
@@ -228,15 +235,20 @@ def _parse_cli(env_or_list: Type[EnvClass] | list[Type[EnvClass]],
|
|
|
228
235
|
Returns:
|
|
229
236
|
Configuration namespace.
|
|
230
237
|
"""
|
|
238
|
+
if isinstance(env_or_list, list):
|
|
239
|
+
subcommands, env = env_or_list, None
|
|
240
|
+
else:
|
|
241
|
+
subcommands, env = None, env_or_list
|
|
242
|
+
|
|
231
243
|
# Load config file
|
|
232
|
-
if config_file and
|
|
233
|
-
#
|
|
244
|
+
if config_file and subcommands:
|
|
245
|
+
# Reading config files when using subcommands is not implemented.
|
|
234
246
|
static = {}
|
|
235
247
|
kwargs["default"] = None
|
|
236
248
|
warnings.warn(f"Config file {config_file} is ignored because subcommands are used."
|
|
237
|
-
"It is not easy to set
|
|
238
|
-
"Describe the developer your usecase so that they might implement this.")
|
|
239
|
-
if "default" not in kwargs and not
|
|
249
|
+
" It is not easy to set how this should work."
|
|
250
|
+
" Describe the developer your usecase so that they might implement this.")
|
|
251
|
+
if "default" not in kwargs and not subcommands:
|
|
240
252
|
# Undocumented feature. User put a namespace into kwargs["default"]
|
|
241
253
|
# that already serves for defaults. We do not fetch defaults yet from a config file.
|
|
242
254
|
disk = {}
|
|
@@ -244,29 +256,28 @@ def _parse_cli(env_or_list: Type[EnvClass] | list[Type[EnvClass]],
|
|
|
244
256
|
disk = yaml.safe_load(config_file.read_text()) or {} # empty file is ok
|
|
245
257
|
# Nested dataclasses have to be properly initialized. YAML gave them as dicts only.
|
|
246
258
|
for key in (key for key, val in disk.items() if isinstance(val, dict)):
|
|
247
|
-
disk[key] =
|
|
259
|
+
disk[key] = env.__annotations__[key](**disk[key])
|
|
248
260
|
|
|
249
261
|
# Fill default fields
|
|
250
|
-
if pydantic and issubclass(
|
|
262
|
+
if pydantic and issubclass(env, BaseModel):
|
|
251
263
|
# Unfortunately, pydantic needs to fill the default with the actual values,
|
|
252
264
|
# the default value takes the precedence over the hard coded one, even if missing.
|
|
253
|
-
static = {key:
|
|
254
|
-
for ann in yield_annotations(
|
|
255
|
-
# static = {key:
|
|
256
|
-
# for key, _ in iterate_attributes(
|
|
257
|
-
elif attr and attr.has(
|
|
265
|
+
static = {key: env.model_fields.get(key).default
|
|
266
|
+
for ann in yield_annotations(env) for key in ann if not key.startswith("__") and not key in disk}
|
|
267
|
+
# static = {key: env_.model_fields.get(key).default
|
|
268
|
+
# for key, _ in iterate_attributes(env_) if not key in disk}
|
|
269
|
+
elif attr and attr.has(env):
|
|
258
270
|
# Unfortunately, attrs needs to fill the default with the actual values,
|
|
259
271
|
# the default value takes the precedence over the hard coded one, even if missing.
|
|
260
272
|
# NOTE Might not work for inherited models.
|
|
261
273
|
static = {key: field.default
|
|
262
|
-
for key, field in attr.fields_dict(
|
|
274
|
+
for key, field in attr.fields_dict(env).items() if not key.startswith("__") and not key in disk}
|
|
263
275
|
else:
|
|
264
276
|
# To ensure the configuration file does not need to contain all keys, we have to fill in the missing ones.
|
|
265
277
|
# Otherwise, tyro will spawn warnings about missing fields.
|
|
266
278
|
static = {key: val
|
|
267
|
-
for key, val in yield_defaults(
|
|
268
|
-
kwargs["default"] = SimpleNamespace(**(
|
|
279
|
+
for key, val in yield_defaults(env) if not key.startswith("__") and not key in disk}
|
|
280
|
+
kwargs["default"] = SimpleNamespace(**(static | disk))
|
|
269
281
|
|
|
270
282
|
# Load configuration from CLI
|
|
271
|
-
|
|
272
|
-
return env, wrong_fields
|
|
283
|
+
return run_tyro_parser(subcommands or env, kwargs, add_verbosity, ask_for_missing, args)
|
|
@@ -28,6 +28,8 @@ class TextualAdaptor(BackendAdaptor):
|
|
|
28
28
|
def __init__(self, interface: "TextualInterface"):
|
|
29
29
|
self.interface = interface
|
|
30
30
|
self.facet = interface.facet = TextualFacet(self, interface.env)
|
|
31
|
+
self.app: TextualApp | None = None
|
|
32
|
+
self.layout_elements = []
|
|
31
33
|
|
|
32
34
|
@staticmethod
|
|
33
35
|
def widgetize(tag: Tag) -> Widget | Changeable:
|
|
@@ -73,6 +75,9 @@ class TextualAdaptor(BackendAdaptor):
|
|
|
73
75
|
|
|
74
76
|
def run_dialog(self, form: TagDict, title: str = "", submit: bool | str = True) -> TagDict:
|
|
75
77
|
super().run_dialog(form, title, submit)
|
|
78
|
+
# Unfortunately, there seems to be no way to reuse the app.
|
|
79
|
+
# Which blocks using multiple form external .form() calls from the web interface.
|
|
80
|
+
# Textual cannot run in a thread, it seems it cannot run in another process, self.suspend() is of no use.
|
|
76
81
|
self.app = app = TextualApp(self, submit)
|
|
77
82
|
if title:
|
|
78
83
|
app.title = title
|
|
@@ -26,6 +26,14 @@ class TextualApp(App[bool | None]):
|
|
|
26
26
|
# ("down", "go_up", "Go down"),
|
|
27
27
|
# ]
|
|
28
28
|
|
|
29
|
+
DEFAULT_CSS = """
|
|
30
|
+
ImageViewer{
|
|
31
|
+
|
|
32
|
+
height: 20;
|
|
33
|
+
}
|
|
34
|
+
"""
|
|
35
|
+
""" Limit layout image size """
|
|
36
|
+
|
|
29
37
|
def __init__(self, adaptor: "TextualAdaptor", submit: str | bool = True):
|
|
30
38
|
super().__init__()
|
|
31
39
|
self.title = adaptor.facet._title
|
|
@@ -52,6 +60,7 @@ class TextualApp(App[bool | None]):
|
|
|
52
60
|
yield Label(text, id="buffered_text")
|
|
53
61
|
focus_set = False
|
|
54
62
|
with VerticalScroll():
|
|
63
|
+
yield from self.adaptor.layout_elements
|
|
55
64
|
for i, fieldt in enumerate(self.widgets):
|
|
56
65
|
if isinstance(fieldt, Input):
|
|
57
66
|
yield Label(fieldt.placeholder)
|
{mininterface-0.7.2 → mininterface-0.7.3}/mininterface/textual_interface/textual_button_app.py
RENAMED
|
@@ -2,6 +2,7 @@ from dataclasses import dataclass
|
|
|
2
2
|
from typing import TYPE_CHECKING, Any
|
|
3
3
|
|
|
4
4
|
from textual.app import App, ComposeResult
|
|
5
|
+
from textual.containers import VerticalScroll
|
|
5
6
|
from textual.widgets import Button, Footer, Label
|
|
6
7
|
|
|
7
8
|
from ..exceptions import Cancelled
|
|
@@ -60,6 +61,7 @@ class TextualButtonApp(App):
|
|
|
60
61
|
self.focused_i: int = 0
|
|
61
62
|
self.values = {}
|
|
62
63
|
self.interface = interface
|
|
64
|
+
self.adaptor = self.interface.adaptor
|
|
63
65
|
|
|
64
66
|
def yes_no(self, text: str, focus_no=True) -> DummyWrapper:
|
|
65
67
|
return self.buttons(text, [("Yes", True), ("No", False)], int(focus_no))
|
|
@@ -78,6 +80,7 @@ class TextualButtonApp(App):
|
|
|
78
80
|
yield Footer()
|
|
79
81
|
if text := self.interface._redirected.join():
|
|
80
82
|
yield Label(text, id="buffered_text")
|
|
83
|
+
yield from self.adaptor.layout_elements
|
|
81
84
|
yield Label(self.text, id="question")
|
|
82
85
|
|
|
83
86
|
self.values.clear()
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import TYPE_CHECKING
|
|
3
|
+
from warnings import warn
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from textual.widgets import (Checkbox, Footer, Header, Input, Label,
|
|
7
|
+
RadioButton, Static)
|
|
8
|
+
|
|
9
|
+
from humanize import naturalsize
|
|
10
|
+
|
|
11
|
+
from ..exceptions import DependencyRequired
|
|
12
|
+
from ..facet import Facet, Image, LayoutElement
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from .textual_adaptor import TextualAdaptor
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TextualFacet(Facet):
|
|
18
|
+
adaptor: "TextualAdaptor"
|
|
19
|
+
|
|
20
|
+
def __init__(self, *args, **kwargs):
|
|
21
|
+
super().__init__(*args, **kwargs)
|
|
22
|
+
# Since TextualApp turns off, we need to have its values stored somewhere
|
|
23
|
+
self._title = ""
|
|
24
|
+
|
|
25
|
+
# NOTE: multiline title will not show up
|
|
26
|
+
def set_title(self, title: str):
|
|
27
|
+
self._title = title
|
|
28
|
+
try:
|
|
29
|
+
self.adaptor.app.title = title
|
|
30
|
+
except:
|
|
31
|
+
# NOTE: When you receive Facet in Command.init, the app does not exist yet
|
|
32
|
+
warn("Setting textual title not implemented well.")
|
|
33
|
+
|
|
34
|
+
def _layout(self, elements: list[LayoutElement]):
|
|
35
|
+
append = self.adaptor.layout_elements.append
|
|
36
|
+
try:
|
|
37
|
+
from PIL import Image as ImagePIL
|
|
38
|
+
from textual_imageview.viewer import ImageViewer
|
|
39
|
+
PIL = True
|
|
40
|
+
except:
|
|
41
|
+
PIL = False
|
|
42
|
+
|
|
43
|
+
for el in elements:
|
|
44
|
+
match el:
|
|
45
|
+
case Image():
|
|
46
|
+
if not PIL:
|
|
47
|
+
raise DependencyRequired("img")
|
|
48
|
+
append(ImageViewer(ImagePIL.open(el.src)))
|
|
49
|
+
case Path():
|
|
50
|
+
size = naturalsize(el.stat().st_size)
|
|
51
|
+
mtime = datetime.fromtimestamp(el.stat().st_mtime).strftime("%Y-%m-%d %H:%M:%S")
|
|
52
|
+
append(Label(f"{el} / {size} / {mtime}"))
|
|
53
|
+
case str():
|
|
54
|
+
append(Label(el))
|
|
55
|
+
case _:
|
|
56
|
+
append(Label("Error in the layout: Unknown {el}"))
|
|
57
|
+
|
|
58
|
+
def submit(self, *args, **kwargs):
|
|
59
|
+
super().submit(*args, **kwargs)
|
|
60
|
+
try:
|
|
61
|
+
self.adaptor.app.action_confirm()
|
|
62
|
+
except:
|
|
63
|
+
# NOTE: When you receive Facet in Command.init, the app does not exist yet
|
|
64
|
+
warn("Setting textual title not implemented well.")
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# The purpose of the file is to put the descriptions to the bottom of the widgets
|
|
2
|
+
# as it was in the former version of the tkinter_form and to limit their width.
|
|
3
|
+
from tkinter import ttk
|
|
4
|
+
|
|
5
|
+
from tkinter_form import Form, Value, FieldForm
|
|
6
|
+
|
|
7
|
+
orig = Form._Form__create_widgets
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def __create_widgets_monkeypatched(
|
|
11
|
+
self, form_dict: dict, name_config: str, button_command: callable
|
|
12
|
+
) -> None:
|
|
13
|
+
"""
|
|
14
|
+
Create form widgets
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
form_dict (dict): form dict base
|
|
18
|
+
name_config (str): name_config
|
|
19
|
+
button (bool): button_config
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
index = 0
|
|
23
|
+
for _, (name_key, value) in enumerate(form_dict.items()):
|
|
24
|
+
index += 1
|
|
25
|
+
description = None
|
|
26
|
+
if isinstance(value, Value):
|
|
27
|
+
value, description = value.val, value.description
|
|
28
|
+
|
|
29
|
+
self.rowconfigure(index, weight=1)
|
|
30
|
+
|
|
31
|
+
if isinstance(value, dict):
|
|
32
|
+
widget = Form(self, name_key, value)
|
|
33
|
+
widget.grid(row=index, column=0, columnspan=3, sticky="nesw")
|
|
34
|
+
|
|
35
|
+
self.fields[name_key] = widget
|
|
36
|
+
last_index = index
|
|
37
|
+
continue
|
|
38
|
+
|
|
39
|
+
variable = self._Form__type_vars[type(value)]()
|
|
40
|
+
widget = self._Form__type_widgets[type(value)](self)
|
|
41
|
+
|
|
42
|
+
self.columnconfigure(1, weight=1)
|
|
43
|
+
widget.grid(row=index, column=1, sticky="nesw", padx=2, pady=2)
|
|
44
|
+
label = ttk.Label(self, text=name_key)
|
|
45
|
+
self.columnconfigure(0, weight=1)
|
|
46
|
+
label.grid(row=index, column=0, sticky="nes", padx=2, pady=2)
|
|
47
|
+
|
|
48
|
+
# Add a further description to the row below the widget
|
|
49
|
+
description_label = None
|
|
50
|
+
if not description is None:
|
|
51
|
+
index += 1
|
|
52
|
+
description_label = ttk.Label(self, text=description, wraplength=1000)
|
|
53
|
+
description_label.grid(row=index, column=1, columnspan=2, sticky="nesw", padx=2, pady=2)
|
|
54
|
+
|
|
55
|
+
self.fields[name_key] = FieldForm(
|
|
56
|
+
master=self,
|
|
57
|
+
label=label,
|
|
58
|
+
widget=widget,
|
|
59
|
+
variable=variable,
|
|
60
|
+
value=value,
|
|
61
|
+
description=description_label,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
last_index = index
|
|
65
|
+
|
|
66
|
+
if button_command:
|
|
67
|
+
self._Form__command = button_command
|
|
68
|
+
self.button = ttk.Button(
|
|
69
|
+
self, text=name_config, command=self._Form__command_button
|
|
70
|
+
)
|
|
71
|
+
self.button.grid(row=last_index + 1, column=0, columnspan=3, sticky="nesw")
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
Form._Form__create_widgets = __create_widgets_monkeypatched
|
|
@@ -36,10 +36,13 @@ class TkFacet(Facet):
|
|
|
36
36
|
raise DependencyRequired("img")
|
|
37
37
|
filename = el.src
|
|
38
38
|
img = ImagePIL.open(filename)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
max_width, max_height = 250, 250
|
|
40
|
+
w_o, h_o = img.size
|
|
41
|
+
scale = min(max_width / w_o, max_height / h_o)
|
|
42
|
+
img = img.resize((int(w_o * scale), int(h_o * scale)), ImagePIL.LANCZOS)
|
|
43
|
+
img_p = ImageTk.PhotoImage(img)
|
|
44
|
+
panel = Label(self.adaptor.frame, image=img_p)
|
|
45
|
+
panel.image = img_p
|
|
43
46
|
panel.pack()
|
|
44
47
|
case Path():
|
|
45
48
|
size = naturalsize(el.stat().st_size)
|
|
@@ -159,10 +159,7 @@ class PathTag(Tag):
|
|
|
159
159
|
@dataclass(repr=False)
|
|
160
160
|
class DatetimeTag(Tag):
|
|
161
161
|
"""
|
|
162
|
-
|
|
163
|
-
Experimental. Still in development.
|
|
164
|
-
|
|
165
|
-
Datetime is supported.
|
|
162
|
+
Datetime, date and time types are supported.
|
|
166
163
|
|
|
167
164
|
```python3
|
|
168
165
|
from datetime import datetime
|
|
@@ -214,13 +211,12 @@ class DatetimeTag(Tag):
|
|
|
214
211
|
# 
|
|
215
212
|
|
|
216
213
|
# NOTE: It would be nice we might put any date format to be parsed.
|
|
217
|
-
# NOTE: The parameters are still ignored.
|
|
218
214
|
|
|
219
215
|
date: bool = False
|
|
220
|
-
""" The date part is active """
|
|
216
|
+
""" The date part is active. True for datetime and date. """
|
|
221
217
|
|
|
222
218
|
time: bool = False
|
|
223
|
-
""" The time part is active """
|
|
219
|
+
""" The time part is active. True for datetime and time. """
|
|
224
220
|
|
|
225
221
|
full_precision: bool = False
|
|
226
222
|
""" Include full time precison, seconds, microseconds. """
|
|
@@ -230,9 +226,6 @@ class DatetimeTag(Tag):
|
|
|
230
226
|
if self.annotation:
|
|
231
227
|
self.date = issubclass(self.annotation, date)
|
|
232
228
|
self.time = issubclass(self.annotation, time) or issubclass(self.annotation, datetime)
|
|
233
|
-
# NOTE: remove
|
|
234
|
-
# if not self.time and self.full_precision:
|
|
235
|
-
# self.full_precision = False
|
|
236
229
|
|
|
237
230
|
def _make_default_value(self):
|
|
238
231
|
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.3"
|
|
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"
|
|
@@ -27,8 +27,10 @@ tkscrollableframe = "*"
|
|
|
27
27
|
|
|
28
28
|
[tool.poetry.extras]
|
|
29
29
|
web = ["textual-serve"]
|
|
30
|
+
img = ["pillow", "textual_imageview"]
|
|
31
|
+
tui = ["textual_imageview"]
|
|
30
32
|
gui = ["pillow", "tkcalendar"]
|
|
31
|
-
all = ["textual-serve", "pillow", "tkcalendar"]
|
|
33
|
+
all = ["textual-serve", "pillow", "tkcalendar", "textual_imageview"]
|
|
32
34
|
|
|
33
35
|
[tool.poetry.scripts]
|
|
34
36
|
mininterface = "mininterface.__main__:main"
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING
|
|
2
|
-
from warnings import warn
|
|
3
|
-
from ..facet import Facet
|
|
4
|
-
if TYPE_CHECKING:
|
|
5
|
-
from .textual_adaptor import TextualAdaptor
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class TextualFacet(Facet):
|
|
9
|
-
adaptor: "TextualAdaptor"
|
|
10
|
-
|
|
11
|
-
def __init__(self, *args, **kwargs):
|
|
12
|
-
super().__init__(*args, **kwargs)
|
|
13
|
-
# Since TextualApp turns off, we need to have its values stored somewhere
|
|
14
|
-
self._title = ""
|
|
15
|
-
|
|
16
|
-
# NOTE: multiline title will not show up
|
|
17
|
-
def set_title(self, title: str):
|
|
18
|
-
self._title = title
|
|
19
|
-
try:
|
|
20
|
-
self.adaptor.app.title = title
|
|
21
|
-
except:
|
|
22
|
-
# NOTE: When you receive Facet in Command.init, the app does not exist yet
|
|
23
|
-
warn("Setting textual title not implemented well.")
|
|
24
|
-
|
|
25
|
-
def submit(self, *args, **kwargs):
|
|
26
|
-
super().submit(*args, **kwargs)
|
|
27
|
-
try:
|
|
28
|
-
self.adaptor.app.action_confirm()
|
|
29
|
-
except:
|
|
30
|
-
# NOTE: When you receive Facet in Command.init, the app does not exist yet
|
|
31
|
-
warn("Setting textual title not implemented well.")
|
|
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
|
{mininterface-0.7.2 → mininterface-0.7.3}/mininterface/tk_interface/redirect_text_tkinter.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|