picview 0.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.
- picview-0.0.1/PKG-INFO +93 -0
- picview-0.0.1/README.md +79 -0
- picview-0.0.1/pyproject.toml +31 -0
- picview-0.0.1/pyproject.toml.orig +31 -0
- picview-0.0.1/src/picview/__init__.py +3 -0
- picview-0.0.1/src/picview/__main__.py +3 -0
- picview-0.0.1/src/picview/app.py +264 -0
- picview-0.0.1/src/picview/assets/picview-icon.png +0 -0
- picview-0.0.1/src/picview/image_pane.py +215 -0
- picview-0.0.1/src/picview/qt_app.py +589 -0
- picview-0.0.1/src/picview/settings.py +46 -0
- picview-0.0.1/src/picview/thumbnail_pane.py +108 -0
- picview-0.0.1/src/picview/ui.py +108 -0
- picview-0.0.1/src/picview/utils.py +32 -0
picview-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: picview
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: A lightweight Windows image folder viewer
|
|
5
|
+
Requires-Dist: pillow>=10.0
|
|
6
|
+
Requires-Dist: tkinterdnd2>=0.4.3
|
|
7
|
+
Requires-Dist: customtkinter>=5.2.2
|
|
8
|
+
Requires-Dist: pyside6>=6.8
|
|
9
|
+
Requires-Python: >=3.11
|
|
10
|
+
Project-URL: Homepage, https://github.com/codemee/picview
|
|
11
|
+
Project-URL: Repository, https://github.com/codemee/picview
|
|
12
|
+
Project-URL: Issues, https://github.com/codemee/picview/issues
|
|
13
|
+
Description-Content-Type: text/markdown
|
|
14
|
+
|
|
15
|
+
# PicView
|
|
16
|
+
|
|
17
|
+
Image folder viewer built with Qt.
|
|
18
|
+
|
|
19
|
+
[繁體中文](https://github.com/codemee/picview/blob/main/README.zh-TW.md)
|
|
20
|
+
|
|
21
|
+
## Install and run
|
|
22
|
+
|
|
23
|
+
- Requires [uv](https://docs.astral.sh/uv/getting-started/installation/) and a graphical desktop. PicView is developed and tested on Windows.
|
|
24
|
+
- Run without a permanent tool installation:
|
|
25
|
+
|
|
26
|
+
```powershell
|
|
27
|
+
uvx picview
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- Install as a command-line tool, then launch:
|
|
31
|
+
|
|
32
|
+
```powershell
|
|
33
|
+
uv tool install picview
|
|
34
|
+
picview
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
- Upgrade an installed copy:
|
|
38
|
+
|
|
39
|
+
```powershell
|
|
40
|
+
uv tool upgrade picview
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- Check the version:
|
|
44
|
+
|
|
45
|
+
```powershell
|
|
46
|
+
picview --version
|
|
47
|
+
# Or without installing:
|
|
48
|
+
uvx picview --version
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Run from source
|
|
52
|
+
|
|
53
|
+
From the project directory, run:
|
|
54
|
+
|
|
55
|
+
```powershell
|
|
56
|
+
uv run picview
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Usage
|
|
60
|
+
|
|
61
|
+
### Open images
|
|
62
|
+
|
|
63
|
+
- The window title shows the current version (for example, **PicView v0.0.1**).
|
|
64
|
+
- Click **Open folder**, or drop a folder or image file onto the window.
|
|
65
|
+
- Rotation and mirror controls only affect the current view; image files are never modified.
|
|
66
|
+
|
|
67
|
+
### Zoom and thumbnail size
|
|
68
|
+
|
|
69
|
+
- Images open at their original pixel size (100%) when they fit; larger images are scaled to fit the pane.
|
|
70
|
+
- Click **Fit to pane** to toggle between fitted and original size.
|
|
71
|
+
- Use the zoom slider or `+` / `-` to resize the view; press `0` to return to 100%.
|
|
72
|
+
- Click the value to the left of either slider to enter an exact zoom percentage or thumbnail size.
|
|
73
|
+
- In the value popup:
|
|
74
|
+
- Press `Enter` to apply.
|
|
75
|
+
- Press `Esc` or click outside to cancel.
|
|
76
|
+
- Use the reset button to restore 100% zoom or the 140 px thumbnail size.
|
|
77
|
+
|
|
78
|
+
### Keyboard shortcuts
|
|
79
|
+
|
|
80
|
+
| Action | Shortcut |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| Previous image | `Left`, `Page Up`, or `Backspace` |
|
|
83
|
+
| Next image | `Right`, `Page Down`, or `Space` |
|
|
84
|
+
| First / last image | `Home` / `End` |
|
|
85
|
+
| Open full screen | `F11` |
|
|
86
|
+
| Close full screen | `Esc` |
|
|
87
|
+
| Open a folder | System Open shortcut |
|
|
88
|
+
| Copy the selected image file to the clipboard | `Ctrl+C` (Windows / Linux), `Command+C` (macOS) |
|
|
89
|
+
|
|
90
|
+
### Theme
|
|
91
|
+
|
|
92
|
+
- Click the theme button to cycle through **Follow system**, **Light**, and **Dark**.
|
|
93
|
+
- **Follow system** is the default.
|
picview-0.0.1/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# PicView
|
|
2
|
+
|
|
3
|
+
Image folder viewer built with Qt.
|
|
4
|
+
|
|
5
|
+
[繁體中文](https://github.com/codemee/picview/blob/main/README.zh-TW.md)
|
|
6
|
+
|
|
7
|
+
## Install and run
|
|
8
|
+
|
|
9
|
+
- Requires [uv](https://docs.astral.sh/uv/getting-started/installation/) and a graphical desktop. PicView is developed and tested on Windows.
|
|
10
|
+
- Run without a permanent tool installation:
|
|
11
|
+
|
|
12
|
+
```powershell
|
|
13
|
+
uvx picview
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
- Install as a command-line tool, then launch:
|
|
17
|
+
|
|
18
|
+
```powershell
|
|
19
|
+
uv tool install picview
|
|
20
|
+
picview
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
- Upgrade an installed copy:
|
|
24
|
+
|
|
25
|
+
```powershell
|
|
26
|
+
uv tool upgrade picview
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- Check the version:
|
|
30
|
+
|
|
31
|
+
```powershell
|
|
32
|
+
picview --version
|
|
33
|
+
# Or without installing:
|
|
34
|
+
uvx picview --version
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Run from source
|
|
38
|
+
|
|
39
|
+
From the project directory, run:
|
|
40
|
+
|
|
41
|
+
```powershell
|
|
42
|
+
uv run picview
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
### Open images
|
|
48
|
+
|
|
49
|
+
- The window title shows the current version (for example, **PicView v0.0.1**).
|
|
50
|
+
- Click **Open folder**, or drop a folder or image file onto the window.
|
|
51
|
+
- Rotation and mirror controls only affect the current view; image files are never modified.
|
|
52
|
+
|
|
53
|
+
### Zoom and thumbnail size
|
|
54
|
+
|
|
55
|
+
- Images open at their original pixel size (100%) when they fit; larger images are scaled to fit the pane.
|
|
56
|
+
- Click **Fit to pane** to toggle between fitted and original size.
|
|
57
|
+
- Use the zoom slider or `+` / `-` to resize the view; press `0` to return to 100%.
|
|
58
|
+
- Click the value to the left of either slider to enter an exact zoom percentage or thumbnail size.
|
|
59
|
+
- In the value popup:
|
|
60
|
+
- Press `Enter` to apply.
|
|
61
|
+
- Press `Esc` or click outside to cancel.
|
|
62
|
+
- Use the reset button to restore 100% zoom or the 140 px thumbnail size.
|
|
63
|
+
|
|
64
|
+
### Keyboard shortcuts
|
|
65
|
+
|
|
66
|
+
| Action | Shortcut |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| Previous image | `Left`, `Page Up`, or `Backspace` |
|
|
69
|
+
| Next image | `Right`, `Page Down`, or `Space` |
|
|
70
|
+
| First / last image | `Home` / `End` |
|
|
71
|
+
| Open full screen | `F11` |
|
|
72
|
+
| Close full screen | `Esc` |
|
|
73
|
+
| Open a folder | System Open shortcut |
|
|
74
|
+
| Copy the selected image file to the clipboard | `Ctrl+C` (Windows / Linux), `Command+C` (macOS) |
|
|
75
|
+
|
|
76
|
+
### Theme
|
|
77
|
+
|
|
78
|
+
- Click the theme button to cycle through **Follow system**, **Light**, and **Dark**.
|
|
79
|
+
- **Follow system** is the default.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "picview"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "A lightweight Windows image folder viewer"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"pillow>=10.0",
|
|
9
|
+
"tkinterdnd2>=0.4.3",
|
|
10
|
+
"customtkinter>=5.2.2",
|
|
11
|
+
"pyside6>=6.8",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[project.scripts]
|
|
15
|
+
picview = "picview.qt_app:main"
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://github.com/codemee/picview"
|
|
19
|
+
Repository = "https://github.com/codemee/picview"
|
|
20
|
+
Issues = "https://github.com/codemee/picview/issues"
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["uv_build>=0.12.0,<0.13.0"]
|
|
24
|
+
build-backend = "uv_build"
|
|
25
|
+
|
|
26
|
+
[dependency-groups]
|
|
27
|
+
dev = ["pytest>=8.0"]
|
|
28
|
+
|
|
29
|
+
[tool.pytest.ini_options]
|
|
30
|
+
pythonpath = ["src"]
|
|
31
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "picview"
|
|
3
|
+
version = "0.0.1"
|
|
4
|
+
description = "A lightweight Windows image folder viewer"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.11"
|
|
7
|
+
dependencies = [
|
|
8
|
+
"pillow>=10.0",
|
|
9
|
+
"tkinterdnd2>=0.4.3",
|
|
10
|
+
"customtkinter>=5.2.2",
|
|
11
|
+
"pyside6>=6.8",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[project.scripts]
|
|
15
|
+
picview = "picview.qt_app:main"
|
|
16
|
+
|
|
17
|
+
[project.urls]
|
|
18
|
+
Homepage = "https://github.com/codemee/picview"
|
|
19
|
+
Repository = "https://github.com/codemee/picview"
|
|
20
|
+
Issues = "https://github.com/codemee/picview/issues"
|
|
21
|
+
|
|
22
|
+
[build-system]
|
|
23
|
+
requires = ["uv_build>=0.12.0,<0.13.0"]
|
|
24
|
+
build-backend = "uv_build"
|
|
25
|
+
|
|
26
|
+
[dependency-groups]
|
|
27
|
+
dev = ["pytest>=8.0"]
|
|
28
|
+
|
|
29
|
+
[tool.pytest.ini_options]
|
|
30
|
+
pythonpath = ["src"]
|
|
31
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import tkinter as tk
|
|
4
|
+
import locale
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from tkinter import filedialog, messagebox, ttk
|
|
7
|
+
import customtkinter as ctk
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
from tkinterdnd2 import DND_FILES, TkinterDnD
|
|
11
|
+
except ImportError: # Allows unit-level imports if optional UI dependency is absent.
|
|
12
|
+
DND_FILES = "DND_Files"
|
|
13
|
+
TkinterDnD = None
|
|
14
|
+
|
|
15
|
+
from .image_pane import ImagePane
|
|
16
|
+
from .settings import load_settings, save_settings
|
|
17
|
+
from .thumbnail_pane import ThumbnailPane
|
|
18
|
+
from .utils import image_files
|
|
19
|
+
from .ui import TEXT, IconButton, configure_flat_style
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PicViewApp:
|
|
23
|
+
def __init__(self, root: tk.Tk) -> None:
|
|
24
|
+
self.root = root
|
|
25
|
+
self.settings = load_settings()
|
|
26
|
+
self.folder: Path | None = None
|
|
27
|
+
self.files: list[Path] = []
|
|
28
|
+
self.orientation = self.settings["orientation"]
|
|
29
|
+
# A maximized pane is a transient viewing mode. Restoring it before a
|
|
30
|
+
# ttk PanedWindow has completed geometry produces an orphaned sash and
|
|
31
|
+
# hides the other pane, so startup always restores the normal layout.
|
|
32
|
+
self.maximized_pane = ""
|
|
33
|
+
self.sash_fraction = float(self.settings["sash_fraction"])
|
|
34
|
+
self.language_mode = self.settings["language"]
|
|
35
|
+
system_language = (locale.getlocale()[0] or "en").lower()
|
|
36
|
+
self.language = "zh" if self.language_mode == "system" and system_language.startswith("zh") else (self.language_mode if self.language_mode in TEXT else "en")
|
|
37
|
+
self.labels = TEXT[self.language]
|
|
38
|
+
self._build_window()
|
|
39
|
+
self._restore_state()
|
|
40
|
+
|
|
41
|
+
def _build_window(self) -> None:
|
|
42
|
+
self.root.title("PicView")
|
|
43
|
+
self.root.minsize(720, 500)
|
|
44
|
+
self.root.geometry("1100x780")
|
|
45
|
+
configure_flat_style(self.root)
|
|
46
|
+
icon_path = Path(__file__).parent / "assets" / "picview-icon.png"
|
|
47
|
+
if icon_path.exists():
|
|
48
|
+
self.icon_image = tk.PhotoImage(file=icon_path)
|
|
49
|
+
self.root.iconphoto(True, self.icon_image)
|
|
50
|
+
self.root.protocol("WM_DELETE_WINDOW", self.close)
|
|
51
|
+
self.root.drop_target_register(DND_FILES)
|
|
52
|
+
self.root.dnd_bind("<<Drop>>", self._on_drop)
|
|
53
|
+
|
|
54
|
+
ctk.set_appearance_mode("system")
|
|
55
|
+
ctk.set_default_color_theme("blue")
|
|
56
|
+
toolbar = ctk.CTkFrame(self.root, height=48, corner_radius=0, fg_color=("#eaf1f4", "#16232c"))
|
|
57
|
+
toolbar.pack(fill="x")
|
|
58
|
+
toolbar.pack_propagate(False)
|
|
59
|
+
self._icon_button(toolbar, "folder", "open", self.choose_folder).pack(side="left")
|
|
60
|
+
ctk.CTkFrame(toolbar, width=1, fg_color=("#c9d8dd", "#36505d")).pack(side="left", fill="y", pady=9, padx=8)
|
|
61
|
+
self._icon_button(toolbar, "split_h", "vertical", lambda: self.set_orientation("vertical")).pack(side="left")
|
|
62
|
+
self._icon_button(toolbar, "split_v", "horizontal", lambda: self.set_orientation("horizontal")).pack(side="left", padx=2)
|
|
63
|
+
ctk.CTkFrame(toolbar, width=1, fg_color=("#c9d8dd", "#36505d")).pack(side="left", fill="y", pady=9, padx=8)
|
|
64
|
+
self._icon_button(toolbar, "En" if self.language == "zh" else "文", "language", self.toggle_language).pack(side="left")
|
|
65
|
+
|
|
66
|
+
self.pane_host = ctk.CTkFrame(self.root, corner_radius=0, fg_color=("#dce7eb", "#101820"))
|
|
67
|
+
self.pane_host.pack(fill="both", expand=True, padx=8, pady=8)
|
|
68
|
+
self._create_panes()
|
|
69
|
+
|
|
70
|
+
def _create_panes(self) -> None:
|
|
71
|
+
old_thumbnail_size = int(self.settings["thumbnail_size"])
|
|
72
|
+
old_image_zoom = float(self.settings["image_zoom"])
|
|
73
|
+
selected = None
|
|
74
|
+
if hasattr(self, "thumbnails"):
|
|
75
|
+
old_thumbnail_size = self.thumbnails.thumbnail_size
|
|
76
|
+
old_image_zoom = self.image.zoom_var.get()
|
|
77
|
+
selected = self.thumbnails.selected
|
|
78
|
+
self.paned.destroy()
|
|
79
|
+
orient = tk.VERTICAL if self.orientation == "vertical" else tk.HORIZONTAL
|
|
80
|
+
self.paned = ttk.Panedwindow(self.pane_host, orient=orient)
|
|
81
|
+
self.paned.pack(fill="both", expand=True)
|
|
82
|
+
self.thumbnails = ThumbnailPane(self.paned, self.select_image, self.labels, self.toggle_maximize, old_thumbnail_size)
|
|
83
|
+
self.image = ImagePane(self.paned, self.labels, self.toggle_maximize, old_image_zoom)
|
|
84
|
+
self.paned.add(self.thumbnails, weight=1)
|
|
85
|
+
self.paned.add(self.image, weight=2)
|
|
86
|
+
self.thumbnails.set_files(self.files, selected)
|
|
87
|
+
if self.maximized_pane:
|
|
88
|
+
self.root.after_idle(lambda: self._apply_maximized(self.maximized_pane))
|
|
89
|
+
else:
|
|
90
|
+
self.root.after_idle(self._restore_sash)
|
|
91
|
+
|
|
92
|
+
def choose_folder(self) -> None:
|
|
93
|
+
chosen = filedialog.askdirectory(parent=self.root, title="Open image folder", initialdir=str(self.folder) if self.folder else None)
|
|
94
|
+
if chosen:
|
|
95
|
+
self.open_folder(Path(chosen))
|
|
96
|
+
|
|
97
|
+
def open_folder(self, folder: Path, selected: Path | None = None) -> None:
|
|
98
|
+
try:
|
|
99
|
+
folder = folder.resolve()
|
|
100
|
+
if not folder.is_dir():
|
|
101
|
+
raise OSError
|
|
102
|
+
except OSError:
|
|
103
|
+
messagebox.showerror("PicView", "That folder cannot be opened.", parent=self.root)
|
|
104
|
+
return
|
|
105
|
+
self.folder = folder
|
|
106
|
+
self.files = image_files(folder)
|
|
107
|
+
self._create_panes()
|
|
108
|
+
target = selected if selected in self.files else (self.files[0] if self.files else None)
|
|
109
|
+
if target:
|
|
110
|
+
self.thumbnails.select(target)
|
|
111
|
+
else:
|
|
112
|
+
self.image.load_empty()
|
|
113
|
+
self._update_title()
|
|
114
|
+
|
|
115
|
+
def select_image(self, path: Path) -> None:
|
|
116
|
+
self.image.load(path)
|
|
117
|
+
|
|
118
|
+
def current_selection(self) -> Path | None:
|
|
119
|
+
return getattr(self, "thumbnails", None).selected if hasattr(self, "thumbnails") else None
|
|
120
|
+
|
|
121
|
+
def set_orientation(self, orientation: str) -> None:
|
|
122
|
+
if orientation == self.orientation:
|
|
123
|
+
return
|
|
124
|
+
# Layout switches are always two-pane layouts; do not route through
|
|
125
|
+
# the maximize restore path, which can leave a stale sash in ttk.
|
|
126
|
+
self.sash_fraction = self._current_sash_fraction()
|
|
127
|
+
self.maximized_pane = ""
|
|
128
|
+
self.orientation = orientation
|
|
129
|
+
self._create_panes()
|
|
130
|
+
self.root.after(40, self._ensure_two_panes)
|
|
131
|
+
|
|
132
|
+
def _ensure_two_panes(self) -> None:
|
|
133
|
+
"""Repair a deferred ttk PanedWindow layout after orientation changes."""
|
|
134
|
+
if self.maximized_pane:
|
|
135
|
+
return
|
|
136
|
+
current = set(self.paned.panes())
|
|
137
|
+
wanted = {str(self.thumbnails), str(self.image)}
|
|
138
|
+
if current != wanted:
|
|
139
|
+
for pane in current:
|
|
140
|
+
self.paned.forget(pane)
|
|
141
|
+
self.paned.add(self.thumbnails, weight=1)
|
|
142
|
+
self.paned.add(self.image, weight=2)
|
|
143
|
+
self.thumbnails.set_maximized(False)
|
|
144
|
+
self.image.set_maximized(False)
|
|
145
|
+
self._restore_sash()
|
|
146
|
+
|
|
147
|
+
def toggle_maximize(self, pane: str) -> None:
|
|
148
|
+
if pane == self.maximized_pane or not pane:
|
|
149
|
+
self.maximized_pane = ""
|
|
150
|
+
self._apply_maximized("")
|
|
151
|
+
else:
|
|
152
|
+
if self.maximized_pane:
|
|
153
|
+
self.maximized_pane = ""
|
|
154
|
+
self._apply_maximized("")
|
|
155
|
+
self.sash_fraction = self._current_sash_fraction()
|
|
156
|
+
self.maximized_pane = pane
|
|
157
|
+
self._apply_maximized(pane)
|
|
158
|
+
|
|
159
|
+
def _apply_maximized(self, pane: str) -> None:
|
|
160
|
+
panes = list(self.paned.panes())
|
|
161
|
+
target = str(self.thumbnails if pane == "thumbnails" else self.image)
|
|
162
|
+
if pane:
|
|
163
|
+
for item in panes:
|
|
164
|
+
if item != target:
|
|
165
|
+
self.paned.forget(item)
|
|
166
|
+
self.thumbnails.set_maximized(pane == "thumbnails")
|
|
167
|
+
self.image.set_maximized(pane == "image")
|
|
168
|
+
else:
|
|
169
|
+
for item in panes:
|
|
170
|
+
self.paned.forget(item)
|
|
171
|
+
self.paned.add(self.thumbnails, weight=1)
|
|
172
|
+
self.paned.add(self.image, weight=2)
|
|
173
|
+
self.thumbnails.set_maximized(False)
|
|
174
|
+
self.image.set_maximized(False)
|
|
175
|
+
self.root.after(20, self._restore_sash)
|
|
176
|
+
|
|
177
|
+
def _restore_sash(self, attempts: int = 0) -> None:
|
|
178
|
+
if not self.paned.panes() or self.maximized_pane:
|
|
179
|
+
return
|
|
180
|
+
try:
|
|
181
|
+
dimension = self.paned.winfo_height() if self.orientation == "vertical" else self.paned.winfo_width()
|
|
182
|
+
# `after_idle` can run before the PanedWindow receives its final
|
|
183
|
+
# geometry (dimension == 1). Setting sashpos then collapses the
|
|
184
|
+
# first pane to zero, which looks like a false maximized image.
|
|
185
|
+
if dimension <= 1 and attempts < 10:
|
|
186
|
+
self.root.after(50, lambda: self._restore_sash(attempts + 1))
|
|
187
|
+
return
|
|
188
|
+
if dimension <= 1:
|
|
189
|
+
return
|
|
190
|
+
self.paned.sashpos(0, int(dimension * self.sash_fraction))
|
|
191
|
+
except tk.TclError:
|
|
192
|
+
pass
|
|
193
|
+
|
|
194
|
+
def _on_drop(self, event: tk.Event) -> str:
|
|
195
|
+
items = self.root.tk.splitlist(event.data)
|
|
196
|
+
if not items:
|
|
197
|
+
return "break"
|
|
198
|
+
path = Path(items[0])
|
|
199
|
+
if path.is_dir():
|
|
200
|
+
self.open_folder(path)
|
|
201
|
+
elif path.is_file():
|
|
202
|
+
self.open_folder(path.parent, path)
|
|
203
|
+
return "break"
|
|
204
|
+
|
|
205
|
+
def _restore_state(self) -> None:
|
|
206
|
+
folder = Path(self.settings["folder"]) if self.settings["folder"] else None
|
|
207
|
+
if folder and folder.is_dir():
|
|
208
|
+
saved = Path(self.settings["selected_file"])
|
|
209
|
+
self.open_folder(folder, saved if saved.is_file() else None)
|
|
210
|
+
|
|
211
|
+
def _update_title(self) -> None:
|
|
212
|
+
self.root.title(f"PicView — {self.folder}" if self.folder else "PicView")
|
|
213
|
+
|
|
214
|
+
def close(self) -> None:
|
|
215
|
+
sash_fraction = self._current_sash_fraction()
|
|
216
|
+
selected = self.current_selection()
|
|
217
|
+
save_settings({
|
|
218
|
+
"folder": str(self.folder) if self.folder else "",
|
|
219
|
+
"selected_file": str(selected) if selected else "",
|
|
220
|
+
"orientation": self.orientation,
|
|
221
|
+
"sash_fraction": sash_fraction,
|
|
222
|
+
"maximized_pane": "",
|
|
223
|
+
"thumbnail_size": self.thumbnails.thumbnail_size,
|
|
224
|
+
"image_zoom": self.image.zoom_var.get(),
|
|
225
|
+
"language": self.language_mode,
|
|
226
|
+
})
|
|
227
|
+
self.image.close_fullscreen()
|
|
228
|
+
self.root.destroy()
|
|
229
|
+
|
|
230
|
+
def _current_sash_fraction(self) -> float:
|
|
231
|
+
if self.maximized_pane or not self.paned.panes():
|
|
232
|
+
return self.sash_fraction
|
|
233
|
+
try:
|
|
234
|
+
dimension = self.paned.winfo_height() if self.orientation == "vertical" else self.paned.winfo_width()
|
|
235
|
+
return self.paned.sashpos(0) / dimension if dimension else self.sash_fraction
|
|
236
|
+
except tk.TclError:
|
|
237
|
+
return self.sash_fraction
|
|
238
|
+
|
|
239
|
+
def toggle_language(self) -> None:
|
|
240
|
+
selected = self.current_selection()
|
|
241
|
+
thumbnail_size = self.thumbnails.thumbnail_size
|
|
242
|
+
image_zoom = self.image.zoom_var.get()
|
|
243
|
+
self.language_mode = "en" if self.language == "zh" else "zh"
|
|
244
|
+
self.language = self.language_mode
|
|
245
|
+
self.labels = TEXT[self.language]
|
|
246
|
+
self.settings["thumbnail_size"] = thumbnail_size
|
|
247
|
+
self.settings["image_zoom"] = image_zoom
|
|
248
|
+
for child in self.root.winfo_children():
|
|
249
|
+
child.destroy()
|
|
250
|
+
del self.thumbnails, self.image, self.paned, self.pane_host
|
|
251
|
+
self._build_window()
|
|
252
|
+
if selected:
|
|
253
|
+
self.thumbnails.select(selected)
|
|
254
|
+
|
|
255
|
+
def _icon_button(self, parent: tk.Misc, icon: str, label_key: str, command) -> ttk.Button:
|
|
256
|
+
return IconButton(parent, icon, self.labels[label_key], command)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def main() -> None:
|
|
260
|
+
if TkinterDnD is None:
|
|
261
|
+
raise RuntimeError("tkinterdnd2 is required. Run `uv sync` before launching PicView.")
|
|
262
|
+
root = TkinterDnD.Tk()
|
|
263
|
+
PicViewApp(root)
|
|
264
|
+
root.mainloop()
|
|
Binary file
|