pic-select 1.0.1__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 (33) hide show
  1. pic_select-1.0.1/LICENSE +9 -0
  2. pic_select-1.0.1/MANIFEST.in +0 -0
  3. pic_select-1.0.1/PKG-INFO +42 -0
  4. pic_select-1.0.1/README.md +20 -0
  5. pic_select-1.0.1/pic_select/__init__.py +0 -0
  6. pic_select-1.0.1/pic_select/__main__.py +9 -0
  7. pic_select-1.0.1/pic_select/gui/app.py +115 -0
  8. pic_select-1.0.1/pic_select/gui/frames/components/borderedImageFrame.py +35 -0
  9. pic_select-1.0.1/pic_select/gui/frames/components/imageFrame.py +61 -0
  10. pic_select-1.0.1/pic_select/gui/frames/components/paddedImageFrame.py +24 -0
  11. pic_select-1.0.1/pic_select/gui/frames/displayFrame.py +90 -0
  12. pic_select-1.0.1/pic_select/gui/frames/displays/imageViewFrame.py +108 -0
  13. pic_select-1.0.1/pic_select/gui/frames/displays/loadFrame.py +20 -0
  14. pic_select-1.0.1/pic_select/gui/frames/displays/selectImageFrame.py +16 -0
  15. pic_select-1.0.1/pic_select/gui/selectFile.py +9 -0
  16. pic_select-1.0.1/pic_select/log/__init__.py +4 -0
  17. pic_select-1.0.1/pic_select/log/logger.py +29 -0
  18. pic_select-1.0.1/pic_select/logic/clipboard.py +13 -0
  19. pic_select-1.0.1/pic_select/logic/input/UserInput.py +12 -0
  20. pic_select-1.0.1/pic_select/logic/input/userInputHandler.py +94 -0
  21. pic_select-1.0.1/pic_select/logic/load/loader.py +105 -0
  22. pic_select-1.0.1/pic_select/logic/load/loaderThread.py +124 -0
  23. pic_select-1.0.1/pic_select/logic/load/pictureDirectory.py +53 -0
  24. pic_select-1.0.1/pic_select/logic/selector.py +78 -0
  25. pic_select-1.0.1/pic_select/logic/sizeManager.py +19 -0
  26. pic_select-1.0.1/pic_select/pic_select.py +36 -0
  27. pic_select-1.0.1/pic_select.egg-info/PKG-INFO +42 -0
  28. pic_select-1.0.1/pic_select.egg-info/SOURCES.txt +31 -0
  29. pic_select-1.0.1/pic_select.egg-info/dependency_links.txt +1 -0
  30. pic_select-1.0.1/pic_select.egg-info/top_level.txt +1 -0
  31. pic_select-1.0.1/setup.cfg +4 -0
  32. pic_select-1.0.1/setup.py +26 -0
  33. pic_select-1.0.1/tests/test.py +141 -0
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2022 Nils Urbach
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
File without changes
@@ -0,0 +1,42 @@
1
+ Metadata-Version: 2.4
2
+ Name: pic_select
3
+ Version: 1.0.1
4
+ Author: Nils Urbach
5
+ Author-email: ndu01u@gmail.com
6
+ License: MIT
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Classifier: Natural Language :: English
12
+ Classifier: Programming Language :: Python
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Dynamic: author
16
+ Dynamic: author-email
17
+ Dynamic: classifier
18
+ Dynamic: description
19
+ Dynamic: description-content-type
20
+ Dynamic: license
21
+ Dynamic: license-file
22
+
23
+ # pic_select
24
+
25
+ This is a small module that easily lets you view; select and copy images in a directory.
26
+
27
+ ## Installation and Start
28
+
29
+ - install using `python -m pip install pic_select`
30
+ - run using `python -m pic_select`
31
+
32
+ ## Usage
33
+
34
+ - Ctrl+O: open a image file
35
+ - Left arrow: show next picture in directory (alphabetical order)
36
+ - Right arrow: show next picture in directory (alphabetical order)
37
+ - Space bar: (un)select an image
38
+ - Ctrl+C: copy all selected images to clipboard
39
+ - Ctrl+A: select all images in the directory
40
+ - Esc: unselect all images
41
+ - Ctrl+T: exit the program
42
+ - F11: toggle fullscreen
@@ -0,0 +1,20 @@
1
+ # pic_select
2
+
3
+ This is a small module that easily lets you view; select and copy images in a directory.
4
+
5
+ ## Installation and Start
6
+
7
+ - install using `python -m pip install pic_select`
8
+ - run using `python -m pic_select`
9
+
10
+ ## Usage
11
+
12
+ - Ctrl+O: open a image file
13
+ - Left arrow: show next picture in directory (alphabetical order)
14
+ - Right arrow: show next picture in directory (alphabetical order)
15
+ - Space bar: (un)select an image
16
+ - Ctrl+C: copy all selected images to clipboard
17
+ - Ctrl+A: select all images in the directory
18
+ - Esc: unselect all images
19
+ - Ctrl+T: exit the program
20
+ - F11: toggle fullscreen
File without changes
@@ -0,0 +1,9 @@
1
+ from .pic_select import start
2
+
3
+
4
+
5
+ def main():
6
+ start()
7
+
8
+ if __name__ == "__main__":
9
+ main()
@@ -0,0 +1,115 @@
1
+ import tkinter as tk
2
+
3
+ from ..logic.input.UserInput import UserInput
4
+ from ..logic.input.userInputHandler import UserInputHandler
5
+ from ..logic.load.loader import Loader
6
+ from ..logic.load.loaderThread import LoaderThread
7
+ from ..logic.selector import Selector
8
+
9
+ from .frames.displayFrame import DisplayFrame
10
+ from .frames.displays.imageViewFrame import ImageViewFrame
11
+
12
+ from .selectFile import ask_file_name
13
+
14
+
15
+
16
+ _PADDING_PERCENT = 0.15
17
+
18
+ _MIN_WINDOW_DIM = int(25 / _PADDING_PERCENT) + 1
19
+
20
+
21
+
22
+ class App(tk.Tk):
23
+
24
+ def __init__(
25
+ self,
26
+ title: str,
27
+ input_handler: UserInputHandler,
28
+ loader: Loader,
29
+ loader_thread: LoaderThread,
30
+ selector: Selector,
31
+ fullscreen = True):
32
+ super().__init__()
33
+
34
+ self.title(title)
35
+
36
+ self.minsize(_MIN_WINDOW_DIM, _MIN_WINDOW_DIM)
37
+ self.state("zoomed")
38
+ self.focus_force()
39
+ self.lift()
40
+
41
+ self.bind("<Left>", self._on_left_arrow)
42
+ self.bind("<Right>", self._on_right_arrow)
43
+ self.bind("<space>", self._on_space)
44
+ self.bind("<Control-c>", self._on_strg_c)
45
+ self.bind("<Control-C>", self._on_strg_c)
46
+ self.bind("<Control-a>", self._on_strg_a)
47
+ self.bind("<Control-A>", self._on_strg_a)
48
+ self.bind("<Control-t>", self._on_strg_t)
49
+ self.bind("<Control-T>", self._on_strg_t)
50
+ self.bind("<Control-o>", self._on_strg_o)
51
+ self.bind("<Control-O>", self._on_strg_o)
52
+ self.bind("<Escape>", self._on_escape)
53
+ self.bind("<F11>", self._on_f_11)
54
+
55
+ self._is_fullscreen = fullscreen
56
+ if self._is_fullscreen:
57
+ self.enter_fullscreen()
58
+
59
+ self._input_handler = input_handler
60
+
61
+ self._display_frame = DisplayFrame(master=self, loader_controller=loader_thread, selector=selector)
62
+ self._display_frame.pack(fill="both", expand=True)
63
+ self._display_frame.setup_displays(selector=selector, loader=loader)
64
+ self._display_frame.show_select_image()
65
+
66
+ @property
67
+ def display_frame(self) -> DisplayFrame:
68
+ return self._display_frame
69
+
70
+ @property
71
+ def image_view_frame(self) -> ImageViewFrame:
72
+ return self._display_frame.image_view_frame
73
+
74
+ def _on_right_arrow(self, event) -> None:
75
+ self._input_handler.handle_input(input=UserInput.FORWARD)
76
+
77
+ def _on_left_arrow(self, event) -> None:
78
+ self._input_handler.handle_input(input=UserInput.BACKWARD)
79
+
80
+ def _on_space(self, event) -> None:
81
+ self._input_handler.handle_input(input=UserInput.SELECT)
82
+
83
+ def _on_strg_c(self, evetn) -> None:
84
+ self._input_handler.handle_input(input=UserInput.COPY)
85
+
86
+ def _on_strg_a(self, event) -> None:
87
+ self._input_handler.handle_input(input=UserInput.SELECT_ALL)
88
+
89
+ def _on_escape(self, event) -> None:
90
+ self._input_handler.handle_input(input=UserInput.UNSELECT_ALL)
91
+
92
+ def _on_strg_t(self, event) -> None:
93
+ self.quit()
94
+
95
+ def _on_f_11(self, event) -> None:
96
+ self.toggle_fullscreen()
97
+
98
+ def _on_strg_o(self, event) -> None:
99
+ file = ask_file_name(parent=self)
100
+ self._input_handler.handle_input(input=UserInput.OPEN, file=file)
101
+
102
+ def toggle_fullscreen(self) -> None:
103
+ self._is_fullscreen = not self._is_fullscreen
104
+ self.attributes("-fullscreen", self._is_fullscreen)
105
+
106
+ def enter_fullscreen(self) -> None:
107
+ self.attributes("-fullscreen", True)
108
+ self._is_fullscreen = True
109
+
110
+ def exit_fullscreen(self) -> None:
111
+ self.attributes("-fullscreen", False)
112
+ self._is_fullscreen = False
113
+
114
+ def quit(self) -> None:
115
+ self.destroy()
@@ -0,0 +1,35 @@
1
+ import tkinter as tk
2
+ from PIL import ImageTk
3
+
4
+ from .paddedImageFrame import PaddedImageFrame
5
+
6
+
7
+
8
+ class BorderedImageFrame(tk.Frame):
9
+
10
+ def __init__(self, master: tk.Widget, image: ImageTk.PhotoImage, padding: int, borderwidth: int):
11
+ full_width = image.width() + (padding * 2) + (borderwidth * 2)
12
+ full_height = image.height() + (padding * 2) + (borderwidth * 2)
13
+
14
+ super().__init__(master, width = full_width, height = full_height, borderwidth=0, highlightthickness=0)
15
+
16
+ self._border_frame = tk.Frame(master=self,
17
+ background="blue",
18
+ width=full_width,
19
+ height=full_height,
20
+ borderwidth=0,
21
+ highlightthickness=0)
22
+
23
+ self._padded_frame = PaddedImageFrame(master=self._border_frame, image=image, padding=padding)
24
+
25
+ self._padded_frame.place(x=borderwidth, y=borderwidth)
26
+ self._border_frame.place(x=0, y=0)
27
+
28
+ self.pack_propagate(False)
29
+
30
+
31
+
32
+
33
+
34
+
35
+
@@ -0,0 +1,61 @@
1
+ import tkinter as tk
2
+ from PIL import Image, ImageTk
3
+ from typing import Tuple
4
+ import math
5
+
6
+ from .borderedImageFrame import BorderedImageFrame
7
+
8
+
9
+
10
+ _GAP_SIZE = 5
11
+ _BORDER_SIZE = 7
12
+
13
+
14
+
15
+ class ImageFrame(tk.Frame):
16
+
17
+ def __init__(self, master: tk.Widget, display_size: Tuple[int, int], image: Image.Image, selected: bool = False):
18
+ super().__init__(master=master, width=display_size[0], height=display_size[1])
19
+
20
+ image_bounds = display_size[0] - (_GAP_SIZE * 2) - (_BORDER_SIZE * 2), display_size[1] - (_GAP_SIZE * 2) - (_BORDER_SIZE * 2)
21
+ image_size = ImageFrame.calculate_max_inbound_size(image_bounds=image_bounds, full_size=(image.width, image.height))
22
+ resized_image = image.resize(image_size, resample=Image.Resampling.LANCZOS)
23
+ self._image = ImageTk.PhotoImage(image=resized_image)
24
+ resized_image.close()
25
+
26
+ self._borderedFrame = BorderedImageFrame(master=self, image=self._image, padding=_GAP_SIZE, borderwidth=_BORDER_SIZE)
27
+ self._borderedFrame.place(relx=0.5, rely=0.5, anchor="center")
28
+
29
+ self._selected = selected
30
+
31
+ if self._selected:
32
+ self.select()
33
+ else:
34
+ self.unselect()
35
+
36
+ self.pack_propagate(False)
37
+
38
+ @staticmethod
39
+ def calculate_max_inbound_size(image_bounds: Tuple[int, int], full_size: Tuple[int, int]) -> Tuple[int, int]:
40
+ width_factor = image_bounds[0] / full_size[0]
41
+ height_factor = image_bounds[1] / full_size[1]
42
+ factor = min(width_factor, height_factor)
43
+ return (int(math.floor(full_size[0] * factor)), int(math.floor(full_size[1] * factor)))
44
+
45
+ @property
46
+ def selected(self) -> bool:
47
+ return self._selected
48
+
49
+ def select(self) -> None:
50
+ self._selected = True
51
+ self._borderedFrame._border_frame.configure(background="blue")
52
+
53
+ def unselect(self) -> None:
54
+ self._selected = False
55
+ self._borderedFrame._border_frame.configure(background="SystemButtonFace")
56
+
57
+ def toggle(self) -> None:
58
+ if self.selected:
59
+ self.unselect()
60
+ else:
61
+ self.select()
@@ -0,0 +1,24 @@
1
+ import tkinter as tk
2
+ from PIL import ImageTk
3
+
4
+
5
+
6
+ class PaddedImageFrame(tk.Frame):
7
+
8
+ def __init__(self, master: tk.Widget, image: ImageTk.PhotoImage, padding: int):
9
+ full_width = image.width() + (padding * 2)
10
+ full_height =image.height() + (padding * 2)
11
+
12
+ super().__init__(master, width = full_width, height = full_height, borderwidth=0, highlightthickness=0)
13
+
14
+ self._pad_Frame = tk.Frame(master=self, width = full_width, height=full_height, borderwidth=0, highlightthickness=0)
15
+ self._image_label = tk.Label(master=self._pad_Frame, image=image, borderwidth=0, highlightthickness=0, padx=0, pady=0)
16
+
17
+ self._image_label.place(x=padding, y=padding)
18
+ self._pad_Frame.place(x=0, y=0)
19
+
20
+ self.pack_propagate(False)
21
+
22
+
23
+
24
+
@@ -0,0 +1,90 @@
1
+ import tkinter as tk
2
+ from tkinter import ttk
3
+ from typing import Tuple
4
+ from pathlib import Path
5
+
6
+ from ...logic.load.loaderThread import LoaderThread
7
+ from ...logic.load.loader import Loader
8
+ from ...logic.selector import Selector
9
+
10
+ from .displays.selectImageFrame import SelectImageFrame
11
+ from .displays.imageViewFrame import ImageViewFrame
12
+
13
+
14
+
15
+ _PADDING_PIXELS = 70
16
+
17
+
18
+
19
+ class DisplayFrame(ttk.Frame):
20
+
21
+ def __init__(self, master: tk.Widget, loader_controller: LoaderThread, selector: Selector):
22
+ super().__init__(master=master)
23
+
24
+ loader_controller.subscribe_on_busy(callback=self._on_loader_busy)
25
+ loader_controller.subscribe_on_idle(callback=self._on_loader_idle)
26
+
27
+ selector.subscribe_on_select(callback=self._on_select)
28
+
29
+ self._selection_count: tk.StringVar = tk.StringVar(self, f"{0:05}")
30
+ self._selection_count_label: tk.Label = tk.Label(master=self,
31
+ textvariable=self._selection_count,
32
+ font=("Arial", -20))
33
+ self._selection_count_label.place(relx=0, rely=0, anchor="nw")
34
+
35
+ self._loader_busy: tk.StringVar = tk.StringVar(self, "")
36
+ self._busy_indicator_label: tk.Label = tk.Label(master=self,
37
+ textvariable=self._loader_busy,
38
+ font=("Arial", -20))
39
+ self._busy_indicator_label.place(relx=0, rely=1, anchor="sw")
40
+
41
+ self._image_name: tk.StringVar = tk.StringVar(self, "")
42
+ self._image_name_label: tk.Label = tk.Label(master=self,
43
+ textvariable=self._image_name,
44
+ font=("Arial", -20))
45
+ self._image_name_label.place(relx=0.5, rely=1, anchor="s")
46
+
47
+ self._select_image_frame: SelectImageFrame = None
48
+ self._image_view_frame: ImageViewFrame = None
49
+
50
+ @property
51
+ def image_view_frame(self) -> ImageViewFrame:
52
+ return self._image_view_frame
53
+
54
+ def _on_loader_busy(self) -> None:
55
+ self._loader_busy.set("Loading ...")
56
+
57
+ def _on_loader_idle(self) -> None:
58
+ self._loader_busy.set("")
59
+
60
+ def _on_new_image(self, path: Path) -> None:
61
+ self.show_image(path=path)
62
+
63
+ def _on_select(self, path: Path, selected: bool, count: int) -> None:
64
+ self._selection_count.set(f"{count:05}")
65
+
66
+ def setup_displays(self, selector: Selector, loader: Loader) -> None:
67
+ self.master.update_idletasks()
68
+
69
+ self._image_view_frame = ImageViewFrame(master=self, display_size=self.display_size(), selector=selector, loader=loader)
70
+ self._image_view_frame.place(relx=0.5, rely=0.5, anchor="center")
71
+
72
+ self._select_image_frame = SelectImageFrame(master=self, display_size=self.display_size())
73
+ self._select_image_frame.place(relx=0.5, rely=0.5, anchor="center")
74
+
75
+ def display_size(self) -> Tuple[int, int]:
76
+ width, height = self.winfo_width(), self.winfo_height()
77
+ return width - _PADDING_PIXELS, height -_PADDING_PIXELS
78
+
79
+ def show_image(self, path: Path) -> None:
80
+ self._image_name.set(path.name)
81
+ self._image_view_frame.display_image_frame(path=path)
82
+ self._image_view_frame.tkraise()
83
+ self._image_view_frame.focus()
84
+
85
+ def show_select_image(self) -> None:
86
+ self._select_image_frame.tkraise()
87
+ self._select_image_frame.focus()
88
+
89
+ def set_image_name(self, path: Path) -> None:
90
+ self._image_name.set(path.name)
@@ -0,0 +1,108 @@
1
+ import tkinter as tk
2
+ from typing import Tuple, Dict, Self
3
+ from pathlib import Path
4
+ from collections.abc import Mapping
5
+ from PIL import Image
6
+
7
+ from ....logic.selector import Selector
8
+ from ....logic.load.loader import Loader
9
+
10
+ from ..components.imageFrame import ImageFrame
11
+ from ..displays.loadFrame import LoadFrame
12
+
13
+
14
+
15
+ class ImageViewFrame(tk.Frame, Mapping):
16
+
17
+ def __init__(self, master: tk.Widget, display_size: Tuple[int, int], selector: Selector, loader: Loader):
18
+ super().__init__(master=master, width=display_size[0], height=display_size[1])
19
+
20
+ self.pack_propagate(False)
21
+
22
+ loader.subscribe_on_load(callback=self._on_load)
23
+ loader.subscribe_on_unload(callback=self._on_unload)
24
+
25
+ selector.subscribe_on_select(callback=self._on_select)
26
+
27
+ self._selector = selector
28
+
29
+ self._image_frames: Dict[Path, ImageFrame] = {}
30
+
31
+ self._current_image_path: Path = None
32
+
33
+ self._load_frame: LoadFrame = LoadFrame(master=self, display_size=display_size)
34
+ self._load_frame.place(relx=0.5, rely=0.5, anchor="center", )
35
+
36
+ def __getitem__(self, path: Path):
37
+ if path not in self._images:
38
+ raise KeyError(f"{path} is not framed")
39
+ return self._image_frames[path]
40
+
41
+ def __setitem__(self, path: Path, image: Image.Image):
42
+ raise RuntimeError("use ImageViewFrame.create_image_frame()")
43
+
44
+ def __delitem__(self, path: Path):
45
+ raise RuntimeError("use ImageViewFrame.remove_image_frame()")
46
+
47
+ def __contains__(self, path: Path):
48
+ return path in self._image_frames
49
+
50
+ def __iter__(self):
51
+ return iter(self._image_frames)
52
+
53
+ def __len__(self):
54
+ return len(self._image_frames)
55
+
56
+ def _on_load(self, path: Path, image: Image.Image) -> None:
57
+ self._create_image_frame(path=path, image=image)
58
+ if self._current_image_path == path:
59
+ self.display_image_frame(path=path)
60
+
61
+ def _on_unload(self, unloaded: Path) -> None:
62
+ if unloaded is not None:
63
+ self.remove_image_frame(path=unloaded)
64
+
65
+ def _on_select(self, path: Path, selected: bool, count: int) -> None:
66
+ if path not in self._image_frames:
67
+ return None
68
+ if selected:
69
+ self._image_frames[path].select()
70
+ else:
71
+ self._image_frames[path].unselect()
72
+
73
+ def _create_image_frame(self, path: Path, image: Image.Image) -> Self:
74
+ self._image_frames[path] = ImageFrame(master=self,
75
+ display_size=(self.winfo_width(), self.winfo_height()),
76
+ image=image,
77
+ selected=self._selector.is_selected(path=path))
78
+ self._image_frames[path].place(relx=0.5, rely=0.5, anchor="center")
79
+
80
+ if self._current_image_path in self:
81
+ self._image_frames[self._current_image_path].tkraise()
82
+ else:
83
+ self._load_frame.tkraise()
84
+
85
+ @property
86
+ def current_image_path(self) -> Path:
87
+ return self._current_image_path
88
+
89
+ def remove_image_frame(self, path: Path) -> None:
90
+ self._image_frames.pop(path)
91
+
92
+ def display_image_frame(self, path: Path) -> None:
93
+ if path not in self:
94
+ self._load_frame.set_text(name=path.name)
95
+ self._load_frame.tkraise()
96
+ else:
97
+ self._image_frames[path].tkraise()
98
+ self._current_image_path = path
99
+
100
+ def select_all(self, unselect = False):
101
+ for frame in self._image_frames.values():
102
+ if unselect:
103
+ frame.unselect()
104
+ else:
105
+ frame.select()
106
+
107
+ def is_waiting_on_loader(self) -> bool:
108
+ return self._current_image_path not in self
@@ -0,0 +1,20 @@
1
+ import tkinter as tk
2
+ from typing import Tuple
3
+
4
+
5
+
6
+ class LoadFrame(tk.Frame):
7
+
8
+ def __init__(self, master: tk.Widget, display_size: Tuple[int, int]):
9
+ super().__init__(master=master, width=display_size[0], height=display_size[1])
10
+
11
+ self.pack_propagate(False)
12
+
13
+ self._text: tk.StringVar = tk.StringVar(self)
14
+ self._label: tk.Label = tk.Label(master=self,
15
+ textvariable=self._text,
16
+ font=("Arial, 18"))
17
+ self._label.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
18
+
19
+ def set_text(self, name: str) -> None:
20
+ self._text.set(f"loading image '{name}'")
@@ -0,0 +1,16 @@
1
+ import tkinter as tk
2
+ from typing import Tuple
3
+
4
+
5
+
6
+ class SelectImageFrame(tk.Frame):
7
+
8
+ def __init__(self, master: tk.Widget, display_size: Tuple[int, int]):
9
+ super().__init__(master=master, width=display_size[0], height=display_size[1])
10
+
11
+ self.pack_propagate(False)
12
+
13
+ self._label: tk.Label = tk.Label(master=self,
14
+ text="Select a file (Strg + O)",
15
+ font=("Arial, 18"))
16
+ self._label.place(relx=0.5, rely=0.5, anchor=tk.CENTER)
@@ -0,0 +1,9 @@
1
+ from pathlib import Path
2
+ import tkinter as tk
3
+ from tkinter import filedialog
4
+
5
+
6
+
7
+ def ask_file_name(parent: tk.Tk) -> Path:
8
+ path = filedialog.askopenfilename(parent=parent)
9
+ return Path(path)
@@ -0,0 +1,4 @@
1
+ from .logger import Logger
2
+
3
+
4
+ logger = Logger()
@@ -0,0 +1,29 @@
1
+ from pathlib import Path
2
+ from datetime import datetime
3
+
4
+
5
+
6
+ class Logger():
7
+ def __init__(self, log_file: Path = None):
8
+ self._log_file = log_file
9
+
10
+ def log_file_action(self, thread: str, file: Path, action: str) -> None:
11
+ message = f"{thread} - {file.name} - {action}"
12
+ self._log(message=message)
13
+
14
+ def log_user_input(self, input: str) -> None:
15
+ self._log(message=input, new_line=True)
16
+
17
+ def log_benchmark(self, title: str, time_ms: int) -> None:
18
+ message = f"{title} - {time_ms}ms"
19
+ self._log(message=message, new_line=True)
20
+
21
+ def _log(self, message: str, new_line: bool = False) -> None:
22
+ current_time = datetime.now().strftime("%H:%M:%S")
23
+ good_message = f"{current_time}: {message}"
24
+ if self._log_file is None:
25
+ if new_line:
26
+ print()
27
+ print(good_message)
28
+ else:
29
+ raise NotImplementedError()
@@ -0,0 +1,13 @@
1
+ import subprocess
2
+ from typing import List
3
+ from pathlib import Path
4
+
5
+
6
+
7
+ def copy_files_to_clipboard(files: List[Path]) -> None:
8
+ if files is None or len(files) == 0:
9
+ return None
10
+ seperator = "', '"
11
+ cmd = f"Set-Clipboard -Path '{seperator.join([str(p) for p in files])}'"
12
+ subprocess.run(["powershell", cmd], shell=True)
13
+
@@ -0,0 +1,12 @@
1
+ from enum import Enum
2
+
3
+
4
+
5
+ class UserInput(Enum):
6
+ OPEN = "open"
7
+ FORWARD = "forward"
8
+ BACKWARD = "backward"
9
+ SELECT = "select"
10
+ COPY = "copy"
11
+ SELECT_ALL = "selectAll"
12
+ UNSELECT_ALL = "unselectAll"