tinyprint 0.2.0__tar.gz → 0.2.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.
Files changed (31) hide show
  1. {tinyprint-0.2.0/tinyprint.egg-info → tinyprint-0.2.2}/PKG-INFO +5 -3
  2. {tinyprint-0.2.0 → tinyprint-0.2.2}/pyproject.toml +6 -4
  3. tinyprint-0.2.2/tests/test_integration.py +72 -0
  4. tinyprint-0.2.2/tests/test_jobconfig.py +157 -0
  5. {tinyprint-0.2.0 → tinyprint-0.2.2}/tinyprint/__init__.py +1 -1
  6. tinyprint-0.2.2/tinyprint/__main__.py +21 -0
  7. tinyprint-0.2.2/tinyprint/gui.py +248 -0
  8. tinyprint-0.2.2/tinyprint/gui_misc.py +160 -0
  9. tinyprint-0.2.2/tinyprint/job.py +151 -0
  10. {tinyprint-0.2.0 → tinyprint-0.2.2}/tinyprint/jobconfig.py +1 -1
  11. tinyprint-0.2.2/tinyprint/patches/__init__.py +5 -0
  12. tinyprint-0.2.0/tinyprint/xescpos.py → tinyprint-0.2.2/tinyprint/patches/escpos.py +11 -5
  13. tinyprint-0.2.0/tinyprint/patches.py → tinyprint-0.2.2/tinyprint/patches/mako.py +1 -3
  14. {tinyprint-0.2.0 → tinyprint-0.2.2}/tinyprint/preprocessor.py +66 -22
  15. tinyprint-0.2.2/tinyprint/printer/__init__.py +2 -0
  16. tinyprint-0.2.2/tinyprint/printer/manager.py +137 -0
  17. tinyprint-0.2.2/tinyprint/printer/profile.py +91 -0
  18. {tinyprint-0.2.0 → tinyprint-0.2.2/tinyprint.egg-info}/PKG-INFO +5 -3
  19. {tinyprint-0.2.0 → tinyprint-0.2.2}/tinyprint.egg-info/SOURCES.txt +10 -3
  20. {tinyprint-0.2.0 → tinyprint-0.2.2}/tinyprint.egg-info/requires.txt +3 -1
  21. tinyprint-0.2.0/tests/test_jobconfig.py +0 -12
  22. tinyprint-0.2.0/tinyprint/__main__.py +0 -263
  23. tinyprint-0.2.0/tinyprint/job.py +0 -321
  24. {tinyprint-0.2.0 → tinyprint-0.2.2}/LICENSE +0 -0
  25. {tinyprint-0.2.0 → tinyprint-0.2.2}/README.md +0 -0
  26. {tinyprint-0.2.0 → tinyprint-0.2.2}/setup.cfg +0 -0
  27. {tinyprint-0.2.0 → tinyprint-0.2.2}/tests/test_job.py +0 -0
  28. {tinyprint-0.2.0 → tinyprint-0.2.2}/tinyprint/config.py +0 -0
  29. {tinyprint-0.2.0 → tinyprint-0.2.2}/tinyprint.egg-info/dependency_links.txt +0 -0
  30. {tinyprint-0.2.0 → tinyprint-0.2.2}/tinyprint.egg-info/entry_points.txt +0 -0
  31. {tinyprint-0.2.0 → tinyprint-0.2.2}/tinyprint.egg-info/top_level.txt +0 -0
@@ -1,18 +1,20 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tinyprint
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: ui & cli to print zines with esc/pos printer
5
- Author-email: Levin Eric Zimmermann <levin.eric.zimmermann@posteo.eu>
5
+ Author-email: Levin Eric Zimmermann <levin.eric.zimmermann@posteo.com>
6
6
  License: GPL
7
7
  Requires-Python: >=3.10
8
8
  Description-Content-Type: text/markdown
9
9
  License-File: LICENSE
10
- Requires-Dist: python-escpos>=3.1
10
+ Requires-Dist: python-escpos[serial,usb]>=3.1
11
11
  Requires-Dist: dataclasses-json>=0.6.0
12
12
  Requires-Dist: marko>=2.1.2
13
13
  Requires-Dist: pdf2image>=1.17.0
14
14
  Provides-Extra: dev
15
+ Requires-Dist: coverage; extra == "dev"
15
16
  Requires-Dist: pytest; extra == "dev"
17
+ Requires-Dist: syrupy; extra == "dev"
16
18
  Requires-Dist: mypy; extra == "dev"
17
19
  Requires-Dist: types-six; extra == "dev"
18
20
  Requires-Dist: types-pillow; extra == "dev"
@@ -1,14 +1,14 @@
1
1
  [project]
2
2
  name = "tinyprint"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  authors = [
5
- {name = "Levin Eric Zimmermann", email = "levin.eric.zimmermann@posteo.eu"},
5
+ {name = "Levin Eric Zimmermann", email = "levin.eric.zimmermann@posteo.com"},
6
6
  ]
7
7
  description = "ui & cli to print zines with esc/pos printer"
8
8
  requires-python = ">=3.10"
9
9
  readme = "README.md"
10
10
  dependencies = [
11
- 'python-escpos>=3.1',
11
+ 'python-escpos[serial,usb]>=3.1',
12
12
  'dataclasses-json>=0.6.0',
13
13
  'marko>=2.1.2',
14
14
  'pdf2image>=1.17.0',
@@ -16,7 +16,9 @@ dependencies = [
16
16
 
17
17
  [project.optional-dependencies]
18
18
  dev = [
19
+ "coverage",
19
20
  "pytest",
21
+ "syrupy",
20
22
  "mypy",
21
23
  "types-six",
22
24
  "types-pillow",
@@ -26,7 +28,7 @@ dev = [
26
28
  text = "GPL"
27
29
 
28
30
  [tool.setuptools]
29
- packages = ["tinyprint"]
31
+ packages = ["tinyprint", "tinyprint.patches", "tinyprint.printer"]
30
32
 
31
33
  [project.scripts]
32
34
  tinyprint = "tinyprint.__main__:main"
@@ -0,0 +1,72 @@
1
+ """Integration tests for the tinyprint package.
2
+
3
+ This module contains end-to-end tests that verify the printing functionality
4
+ with different types of resources and job configurations using a dummy printer.
5
+ """
6
+
7
+ from itertools import product
8
+ from pathlib import Path
9
+
10
+ import pytest
11
+
12
+ from tinyprint.job import Job
13
+ from tinyprint.jobconfig import JobConfig, Resource, Orientation
14
+
15
+ # Test data directory
16
+ TEST_DATA = Path(__file__).parent / "data"
17
+
18
+ # Test configurations
19
+ RESOURCES = {
20
+ "text": ["text/test.md"],
21
+ "image": [f"images/test-{i}.png" for i in range(4)],
22
+ "pdf": ["pdfs/test.pdf"],
23
+ }
24
+ ORIENTATIONS = list(Orientation)
25
+ COPY_COUNTS = [1, 2]
26
+ CUT_OPTIONS = [True, False]
27
+
28
+
29
+ def generate_test_cases():
30
+ """Generate test cases for all combinations of resource types and config options."""
31
+ test_cases = []
32
+ # Define all possible combinations
33
+ combinations = product(RESOURCES.items(), ORIENTATIONS, COPY_COUNTS, CUT_OPTIONS)
34
+ for (rname, rpath_list), orient, copy_count, cut in combinations:
35
+ test_id = f"{rname}-{orient.name.lower()}-copies_{copy_count}-{'cut' if cut else 'nocut'}"
36
+ test_cases.append(
37
+ pytest.param(
38
+ rpath_list,
39
+ {"orientation": orient, "copy_count": copy_count, "cut": cut},
40
+ id=test_id,
41
+ )
42
+ )
43
+ return test_cases
44
+
45
+
46
+ @pytest.mark.parametrize("resource_path_list,config_kwargs", generate_test_cases())
47
+ def test_print(snapshot, resource_path_list, config_kwargs):
48
+ """
49
+ Test printing with various resource types and job configurations.
50
+
51
+ Args:
52
+ snapshot: The snapshot fixture for assertion
53
+ resource_path_list: List of relative path to the resource
54
+ files (from TEST_DATA)
55
+ config_kwargs: Configuration options for JobConfig
56
+ """
57
+ config = JobConfig(
58
+ resource_list=[Resource(str(TEST_DATA / p)) for p in resource_path_list],
59
+ **config_kwargs,
60
+ )
61
+
62
+ job = Job(config)
63
+
64
+ # Set a default width to prevent
65
+ # 'AssertionError: max_width of printer unknown!'
66
+ profile = job.manager.printer.profile
67
+ profile.profile_data["media"]["width"]["pixels"] = 384
68
+
69
+ job.start()
70
+ job.wait()
71
+
72
+ assert snapshot == job.manager.printer.output
@@ -0,0 +1,157 @@
1
+ import pytest
2
+ from pathlib import Path
3
+
4
+ from tinyprint.jobconfig import (
5
+ JobConfig,
6
+ PageSize,
7
+ Resource,
8
+ Orientation,
9
+ _parse_str,
10
+ )
11
+
12
+
13
+ def test_orientation_enum():
14
+ """Test Orientation enum values and string parsing."""
15
+ # Test enum values
16
+ assert Orientation.HORIZONTAL.value == 0
17
+ assert Orientation.VERTICAL.value == 1
18
+
19
+ # Test string parsing
20
+ assert Orientation.parse("horizontal") == Orientation.HORIZONTAL
21
+ assert Orientation.parse("HORIZONTAL") == Orientation.HORIZONTAL
22
+ assert Orientation.parse("vertical") == Orientation.VERTICAL
23
+ assert Orientation.parse("VERTICAL") == Orientation.VERTICAL
24
+
25
+ # Test invalid enum value
26
+ with pytest.raises(AttributeError):
27
+ Orientation.parse("invalid")
28
+
29
+
30
+ def test_parse_str_utility():
31
+ """Test the _parse_str utility function."""
32
+
33
+ class TestEnum:
34
+ FOO = 1
35
+ BAR = 2
36
+
37
+ assert _parse_str(TestEnum, "foo") == TestEnum.FOO
38
+ assert _parse_str(TestEnum, "BAR") == TestEnum.BAR
39
+
40
+ with pytest.raises(AttributeError):
41
+ _parse_str(TestEnum, "baz")
42
+
43
+
44
+ def test_page_size_creation():
45
+ """Test PageSize creation and from_any method."""
46
+ # Test direct creation
47
+ size1 = PageSize(10.5, 20.5)
48
+ assert size1.width == 10.5
49
+ assert size1.heigth == 20.5
50
+
51
+ # Test from_any with PageSize
52
+ size2 = PageSize.from_any(size1)
53
+ assert size2 == size1
54
+
55
+ # Test from_any with tuple
56
+ size3 = PageSize.from_any((30.0, 40.0))
57
+ assert size3.width == 30.0
58
+ assert size3.heigth == 40.0
59
+
60
+ # Test from_any with list
61
+ size4 = PageSize.from_any([50.0, 60.0])
62
+ assert size4.width == 50.0
63
+ assert size4.heigth == 60.0
64
+
65
+ # Test invalid type
66
+ with pytest.raises(NotImplementedError):
67
+ PageSize.from_any("invalid")
68
+
69
+
70
+ def test_resource_creation():
71
+ """Test Resource creation and default values."""
72
+ # Test with required fields only
73
+ res1 = Resource("path/to/file.pdf")
74
+ assert res1.path == "path/to/file.pdf"
75
+ assert res1.fragment_height is None
76
+ assert res1.color == 0
77
+
78
+ # Test with all fields
79
+ res2 = Resource("path/to/other.pdf", fragment_height=100, color=1)
80
+ assert res2.path == "path/to/other.pdf"
81
+ assert res2.fragment_height == 100
82
+ assert res2.color == 1
83
+
84
+
85
+ def test_job_config_creation():
86
+ """Test JobConfig creation and default values."""
87
+ resources = [
88
+ Resource("file1.pdf"),
89
+ Resource("file2.pdf", fragment_height=200, color=1),
90
+ ]
91
+
92
+ # Test with required fields only
93
+ c1 = JobConfig(resource_list=resources)
94
+ assert c1.resource_list == resources
95
+ assert c1.printer_config is None
96
+ assert c1.copy_count == 1
97
+ assert c1.cut is True
98
+ assert c1.orientation == Orientation.HORIZONTAL
99
+ assert c1.page_size is None
100
+ assert c1.sleep_time is None
101
+
102
+ # Test with all fields
103
+ page_size = PageSize(70.0, 30.0)
104
+ c2 = JobConfig(
105
+ resource_list=resources,
106
+ printer_config="myprinter.yml",
107
+ copy_count=3,
108
+ cut=False,
109
+ orientation=Orientation.VERTICAL,
110
+ page_size=page_size,
111
+ sleep_time=1.5,
112
+ )
113
+ assert c2.printer_config == "myprinter.yml"
114
+ assert c2.copy_count == 3
115
+ assert c2.cut is False
116
+ assert c2.orientation == Orientation.VERTICAL
117
+ assert c2.page_size == page_size
118
+ assert c2.sleep_time == 1.5
119
+
120
+
121
+ def test_job_config_serialization(tmp_path: Path):
122
+ """Test JobConfig serialization to/from JSON file."""
123
+ # Create test resources
124
+ resources = [
125
+ Resource("file1.pdf"),
126
+ Resource("file2.pdf", fragment_height=200, color=1),
127
+ ]
128
+ page_size = PageSize(70.0, 30.0)
129
+
130
+ # Create and save config
131
+ config = JobConfig(
132
+ resource_list=resources,
133
+ printer_config="myprinter.yml",
134
+ copy_count=2,
135
+ cut=True,
136
+ orientation=Orientation.VERTICAL,
137
+ page_size=page_size,
138
+ sleep_time=0.5,
139
+ )
140
+
141
+ # Test serialization/deserialization
142
+ json_path = tmp_path / "config.json"
143
+ config.to_file(str(json_path))
144
+ loaded_config = JobConfig.from_file(str(json_path))
145
+
146
+ # Verify all fields are preserved
147
+ assert loaded_config.resource_list[0].path == "file1.pdf"
148
+ assert loaded_config.resource_list[1].fragment_height == 200
149
+ assert loaded_config.resource_list[1].color == 1
150
+ assert loaded_config.printer_config == "myprinter.yml"
151
+ assert loaded_config.copy_count == 2
152
+ assert loaded_config.cut is True
153
+ assert loaded_config.orientation == Orientation.VERTICAL
154
+ assert loaded_config.page_size is not None
155
+ assert loaded_config.page_size.width == 70.0
156
+ assert loaded_config.page_size.heigth == 30.0
157
+ assert loaded_config.sleep_time == 0.5
@@ -2,7 +2,7 @@ from . import patches
2
2
  del patches
3
3
 
4
4
  from . import config
5
- from . import xescpos
5
+ from . import gui
6
6
  from . import jobconfig
7
7
  from . import job
8
8
  from . import preprocessor
@@ -0,0 +1,21 @@
1
+ """Main entry point for the TinyPrint application."""
2
+
3
+ import logging
4
+ from tinyprint.gui import GUI
5
+
6
+
7
+ def main():
8
+ """Entry point for the application."""
9
+ # Configure logging
10
+ logging.basicConfig(
11
+ level=logging.INFO,
12
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
13
+ )
14
+
15
+ # Start the application
16
+ with GUI() as gui:
17
+ gui.run()
18
+
19
+
20
+ if __name__ == "__main__":
21
+ main()
@@ -0,0 +1,248 @@
1
+ """TinyPrint GUI"""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import logging
7
+ import tempfile
8
+ from pathlib import Path
9
+ from typing import Optional
10
+
11
+ from tkinter import Tk, filedialog
12
+ from tkinter.ttk import Frame
13
+
14
+ from tinyprint.jobconfig import JobConfig, Resource
15
+ from tinyprint.job import Job
16
+ from tinyprint.gui_misc import Block, Blocks, no_hidden_files
17
+
18
+
19
+ class GUI:
20
+ """Main GUI class for the TinyPrint application."""
21
+
22
+ COLUMN_COUNT = 5
23
+
24
+ def __enter__(self):
25
+ self._tempdir = None
26
+ self._tempdir_context = tempfile.TemporaryDirectory(prefix="tinyprint")
27
+ self.tempdir = self._tempdir_context.name
28
+ self.setup()
29
+ return self
30
+
31
+ def __exit__(self, exc_type, exc_val, exc_tb):
32
+ self.cleanup()
33
+
34
+ def setup(self):
35
+ """Set up the main window and all components."""
36
+ self.root = Tk()
37
+ no_hidden_files(self.root)
38
+ self.root.title("~ tiny print ~")
39
+ self.main_frame = Frame(self.root, padding=15)
40
+ self.main_frame.grid(sticky="nsew")
41
+ blk = self.blk = Blocks(self)
42
+ blk.ADD.job_controller(JobController)
43
+ blk.ADD.job_config_manager(JobConfigManager)
44
+ blk.ADD.job_config_editor(JobConfigEditor)
45
+ for i in range(GUI.COLUMN_COUNT):
46
+ self.main_frame.columnconfigure(i, weight=1)
47
+
48
+ def run(self):
49
+ """Start the main event loop."""
50
+ self.root.mainloop()
51
+
52
+ def cleanup(self):
53
+ """Clean up resources, including temporary directories."""
54
+ g = self
55
+ g.blk and g.blk.close()
56
+ g._tempdir_context and g._tempdir_context.cleanup()
57
+ g.blk, g.root, g.main_frame, g._tempdir_context, g._tempdir = [None] * 5
58
+
59
+
60
+ class JobController(Block):
61
+ """Controls to start/stop print job."""
62
+
63
+ def __init__(self, *args, **kwargs):
64
+ self.job = None
65
+ super().__init__(*args, **kwargs)
66
+
67
+ def create_widgets(self):
68
+ btn = self.btn
69
+ for i, cmd in enumerate("print stop".split()):
70
+ b = btn.ADD(cmd, text=cmd, command=getattr(self, cmd))
71
+ b.grid(column=i, row=0)
72
+
73
+ def print(self):
74
+ if self.job and self.job.is_running:
75
+ return logging.warning("job already printing!")
76
+ self.stop()
77
+ config = self.gui.blk.job_config_manager.job_config
78
+ if config:
79
+ self.job = Job(config)
80
+ self.job.start()
81
+ else:
82
+ logging.warning("no job config set")
83
+
84
+ def stop(self):
85
+ if not self.job:
86
+ return
87
+ self.job.stop()
88
+ self.job.close()
89
+ self.job = None
90
+
91
+
92
+ class JobConfigManager(Block):
93
+ """Load/save a job configuration"""
94
+
95
+ def __init__(self, gui, *args, **kwargs):
96
+ self.job_config = None
97
+ self.filename = None
98
+ super().__init__(gui, *args, **kwargs)
99
+ self.temp_resource = os.path.join(gui.tempdir, "tmp-resource.md")
100
+ with open(self.temp_resource, "w") as f:
101
+ f.write("tiny printer test")
102
+ self.new(os.path.join(self.gui.tempdir, "tmp-print-job.json"))
103
+
104
+ def create_widgets(self):
105
+ var, btn, lbl = self.var, self.btn, self.lbl
106
+ var.str.ADD.filename_indicator()
107
+ lbl.ADD.job_config(text="job config: ")
108
+ lbl.job_config.grid(column=0, row=0)
109
+ lbl.ADD.filename(textvariable=var.filename_indicator)
110
+ lbl.filename.grid(column=1, row=0, columnspan=3, pady=5, sticky="w")
111
+ for i, cmd in enumerate("new open save save_as reload".split()):
112
+ b = btn.ADD(cmd, text=cmd, command=getattr(self, cmd))
113
+ b.grid(column=i, row=1, pady=5, padx=2)
114
+
115
+ def new(self, *args, **kwargs):
116
+ self.job_config = JobConfig([Resource(self.temp_resource)])
117
+ self.save_as(*args, **kwargs)
118
+
119
+ def open(self):
120
+ if filename := self.select_file(
121
+ (("print job configuration", "*.json"), ("All files", "*.*"))
122
+ ):
123
+ self.set_filename(filename)
124
+ self.reload()
125
+
126
+ def save(self):
127
+ self.job_config.to_file(self.filename)
128
+
129
+ def save_as(self, filename: Optional[str] = None):
130
+ if not filename:
131
+ filetypes = (("print job configuration", "*.json"), ("All files", "*.*"))
132
+ filename = filedialog.asksaveasfilename(
133
+ title="File path", filetypes=filetypes, initialdir=str(Path.home())
134
+ )
135
+ if not filename:
136
+ return
137
+ if not filename.endswith(".json"):
138
+ filename = f"{filename}.json"
139
+ self.set_filename(filename)
140
+ self.save()
141
+
142
+ def reload(self):
143
+ if not self.filename:
144
+ return logging.warning("can't load as no file loaded yet")
145
+ logging.info(f"load {self.filename}")
146
+ self.job_config = JobConfig.from_file(self.filename)
147
+ self.gui.blk.job_config_editor.sync_from_model()
148
+
149
+ def set_filename(self, filename: str):
150
+ self.filename = filename
151
+ self.var.filename_indicator.set(Path(filename).name)
152
+
153
+
154
+ class JobConfigEditor(Block):
155
+ """Edit parameters of currently loaded job configuration"""
156
+
157
+ def __init__(self, *args, **kwargs):
158
+ self.temp_resource = None
159
+ super().__init__(*args, **kwargs)
160
+
161
+ def create_widgets(self):
162
+ var, btn, lbl, cbx, scl = self.var, self.btn, self.lbl, self.cbx, self.scl
163
+
164
+ var.str.ADD.resource_list()
165
+ var.str.ADD.printer_config()
166
+ var.double.ADD.sleep_time()
167
+
168
+ for i, t in enumerate( # render names of parameters
169
+ ("resource", "printer configuration", "cut", "sleep time")
170
+ ):
171
+ label = lbl.ADD(f"arg{i}", text=f"{t}: ")
172
+ label.grid(column=0, row=i, columnspan=2, sticky="w", pady=2)
173
+
174
+ btn.ADD.select_resource(text="select", command=self.select_resource)
175
+ lbl.ADD.resource_list(textvariable=var.resource_list)
176
+ btn.select_resource.grid(column=2, row=0, sticky="w")
177
+ lbl.resource_list.grid(column=4, row=0, columnspan=2, sticky="w")
178
+
179
+ btn.ADD.select_printer(text="select", command=self.select_printer)
180
+ lbl.ADD.printer_value(textvariable=var.printer_config)
181
+ btn.select_printer.grid(column=2, row=1, sticky="w")
182
+ lbl.printer_value.grid(column=4, row=1, sticky="w")
183
+
184
+ cbx.ADD.cut(values=["yes", "no"])
185
+ cbx.cut.set("no")
186
+ cbx.cut.bind("<<ComboboxSelected>>", self.select_cut)
187
+ cbx.cut.grid(column=2, row=2)
188
+
189
+ lbl.ADD.sleep_value(textvariable=var.sleep_time)
190
+ scl.ADD.sleep_time(
191
+ from_=0,
192
+ to=2,
193
+ command=self.select_sleep_time,
194
+ variable=var.sleep_time,
195
+ length=500,
196
+ )
197
+ lbl.sleep_value.grid(column=2, row=3, sticky="w")
198
+ scl.sleep_time.grid(column=3, row=3, columnspan=2)
199
+
200
+ def sync_from_model(self):
201
+ job_config, var, cbx = self.job_config, self.var, self.cbx
202
+ if not job_config:
203
+ return
204
+ resource_list = "; ".join(
205
+ [r.path.split(os.sep)[-1] for r in job_config.resource_list]
206
+ )
207
+ if len(resource_list) > 25:
208
+ resource_list = resource_list[:30] + " ..."
209
+ var.resource_list.set(resource_list)
210
+ var.printer_config.set(job_config.printer_config)
211
+ var.sleep_time.set(job_config.sleep_time)
212
+ cbx.cut.set(("no", "yes")[job_config.cut])
213
+
214
+ def select_resource(self):
215
+ self.select_file(
216
+ "resource_list",
217
+ (("all files", "*.*"), ("pdf", "*.pdf"), ("images", "*.jpg")),
218
+ multiple=True,
219
+ )
220
+
221
+ def select_printer(self):
222
+ self.select_file(
223
+ "printer_config", (("printer config", "*.yml"), ("all files", "*.*"))
224
+ )
225
+
226
+ def select_file(self, attr, filetypes, multiple=False):
227
+ if file_list := super().select_file(filetypes, multiple):
228
+ setattr(
229
+ self.job_config,
230
+ attr,
231
+ [Resource(f) for f in file_list] if multiple else file_list[0],
232
+ )
233
+ self.sync_from_model()
234
+
235
+ def select_cut(self, *_):
236
+ self.job_config.cut = {"yes": True, "no": False}[self.cbx.cut.get()]
237
+
238
+ def select_sleep_time(self, value: str):
239
+ v = round(float(value), 2)
240
+ self.job_config.sleep_time = v
241
+ # NOTE Since var.sleep_time is also 'variable' of scale, this
242
+ # already gets set automatically - yet we still re-set it here
243
+ # to get the rounded value.
244
+ self.var.sleep_time.set(v)
245
+
246
+ @property
247
+ def job_config(self):
248
+ return self.gui.blk.job_config_manager.job_config