shinestacker 0.4.0__tar.gz → 0.5.0__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.
Potentially problematic release.
This version of shinestacker might be problematic. Click here for more details.
- {shinestacker-0.4.0 → shinestacker-0.5.0}/CHANGELOG.md +12 -0
- {shinestacker-0.4.0/src/shinestacker.egg-info → shinestacker-0.5.0}/PKG-INFO +1 -1
- shinestacker-0.5.0/scripts/git-rev-list.sh +6 -0
- shinestacker-0.5.0/src/shinestacker/_version.py +1 -0
- shinestacker-0.5.0/src/shinestacker/app/about_dialog.py +96 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/config/gui_constants.py +7 -5
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/gui/actions_window.py +8 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/gui/main_window.py +13 -8
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/brush_tool.py +23 -6
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/display_manager.py +57 -20
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/image_editor.py +5 -9
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/image_editor_ui.py +53 -15
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/io_gui_handler.py +71 -22
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/io_manager.py +23 -7
- {shinestacker-0.4.0 → shinestacker-0.5.0/src/shinestacker.egg-info}/PKG-INFO +1 -1
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker.egg-info/SOURCES.txt +1 -0
- shinestacker-0.4.0/src/shinestacker/_version.py +0 -1
- shinestacker-0.4.0/src/shinestacker/app/about_dialog.py +0 -28
- {shinestacker-0.4.0 → shinestacker-0.5.0}/.coveragerc +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/.flake8 +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/.github/workflows/ci-multiplatform.yml +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/.github/workflows/pylint.yml +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/.github/workflows/pypi-publish.yml +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/.github/workflows/release.yml +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/.gitignore +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/.pylintrc +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/.readthedocs.yaml +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/LICENSE +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/MANIFEST.in +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/README.md +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/THIRD_PARTY_LICENSES.txt +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/docs/alignment.md +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/docs/api.md +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/docs/balancing.md +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/docs/conf.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/docs/focus_stacking.md +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/docs/gui.md +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/docs/index.md +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/docs/job.md +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/docs/main.md +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/docs/multilayer.md +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/docs/noise.md +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/docs/requirements.txt +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/docs/vignetting.md +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/img/coffee.gif +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/img/coffee_stack.jpg +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/img/extreme-vignetting.jpg +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/img/flies.gif +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/img/flies_stack.jpg +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/img/flow-diagram.png +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/img/gui-finder.png +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/img/gui-project-new.png +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/img/gui-project-run.png +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/img/gui-retouch.png +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/pyproject.toml +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/requirements.txt +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/scripts/build_release.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/scripts/validate-tomli.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/setup.cfg +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/__init__.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/algorithms/__init__.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/algorithms/align.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/algorithms/balance.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/algorithms/base_stack_algo.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/algorithms/denoise.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/algorithms/depth_map.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/algorithms/exif.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/algorithms/multilayer.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/algorithms/noise_detection.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/algorithms/pyramid.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/algorithms/sharpen.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/algorithms/stack.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/algorithms/stack_framework.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/algorithms/utils.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/algorithms/vignetting.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/algorithms/white_balance.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/app/__init__.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/app/app_config.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/app/gui_utils.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/app/help_menu.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/app/main.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/app/open_frames.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/app/project.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/app/retouch.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/config/__init__.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/config/config.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/config/constants.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/core/__init__.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/core/colors.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/core/core_utils.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/core/exceptions.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/core/framework.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/core/logging.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/gui/__init__.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/gui/action_config.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/gui/colors.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/gui/gui_images.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/gui/gui_logging.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/gui/gui_run.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/gui/ico/focus_stack_bkg.png +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/gui/ico/shinestacker.icns +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/gui/ico/shinestacker.png +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/gui/img/close-round-line-icon.png +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/gui/img/forward-button-icon.png +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/gui/img/play-button-round-icon.png +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/gui/img/plus-round-line-icon.png +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/gui/new_project.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/gui/project_converter.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/gui/project_editor.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/gui/project_model.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/gui/select_path_widget.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/__init__.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/base_filter.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/brush.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/brush_gradient.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/brush_preview.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/denoise_filter.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/exif_data.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/file_loader.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/filter_manager.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/icon_container.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/image_filters.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/image_viewer.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/layer_collection.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/shortcuts_help.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/undo_manager.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/unsharp_mask_filter.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker/retouch/white_balance_filter.py +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker.egg-info/dependency_links.txt +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker.egg-info/entry_points.txt +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker.egg-info/requires.txt +0 -0
- {shinestacker-0.4.0 → shinestacker-0.5.0}/src/shinestacker.egg-info/top_level.txt +0 -0
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
This page reports the main releases only and the main changes therein.
|
|
4
4
|
|
|
5
|
+
## [v0.5.0] - 2025-08-20
|
|
6
|
+
**GUI and robustness improvements**
|
|
7
|
+
|
|
8
|
+
### Changes
|
|
9
|
+
|
|
10
|
+
* layer selection highlightted with a blue border
|
|
11
|
+
* improved font rendering in brush preview
|
|
12
|
+
* fixed thumbnail spacing
|
|
13
|
+
* fixed and improved save strategy for retouched images
|
|
14
|
+
* added checks for updated version in about dialog
|
|
15
|
+
* disable "Save" and "Save As..." menus if do not apply to current status
|
|
16
|
+
|
|
5
17
|
---
|
|
6
18
|
|
|
7
19
|
## [v0.4.0] - 2025-08-19
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '0.5.0'
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# pylint: disable=C0114, C0116, E0611, W0718
|
|
2
|
+
import json
|
|
3
|
+
from urllib.request import urlopen, Request
|
|
4
|
+
from urllib.error import URLError
|
|
5
|
+
from PySide6.QtWidgets import QMessageBox
|
|
6
|
+
from PySide6.QtCore import Qt
|
|
7
|
+
from .. import __version__
|
|
8
|
+
from .. config.constants import constants
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def compare_versions(current, latest):
|
|
12
|
+
def parse_version(v):
|
|
13
|
+
v = v.lstrip('v')
|
|
14
|
+
parts = v.split('.')
|
|
15
|
+
result = []
|
|
16
|
+
for part in parts:
|
|
17
|
+
try:
|
|
18
|
+
result.append(int(part))
|
|
19
|
+
except ValueError:
|
|
20
|
+
result.append(part)
|
|
21
|
+
return result
|
|
22
|
+
current_parts = parse_version(current)
|
|
23
|
+
latest_parts = parse_version(latest)
|
|
24
|
+
for i in range(max(len(current_parts), len(latest_parts))):
|
|
25
|
+
c = current_parts[i] if i < len(current_parts) else 0
|
|
26
|
+
l = latest_parts[i] if i < len(latest_parts) else 0 # noqa: E741
|
|
27
|
+
if isinstance(c, int) and isinstance(l, int):
|
|
28
|
+
if c < l:
|
|
29
|
+
return -1
|
|
30
|
+
if c > l:
|
|
31
|
+
return 1
|
|
32
|
+
else:
|
|
33
|
+
if str(c) < str(l):
|
|
34
|
+
return -1
|
|
35
|
+
if str(c) > str(l):
|
|
36
|
+
return 1
|
|
37
|
+
return 0
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_latest_version():
|
|
41
|
+
try:
|
|
42
|
+
url = "https://api.github.com/repos/lucalista/shinestacker/releases/latest"
|
|
43
|
+
headers = {'User-Agent': 'ShineStacker'}
|
|
44
|
+
req = Request(url, headers=headers)
|
|
45
|
+
with urlopen(req, timeout=5) as response:
|
|
46
|
+
data = json.loads(response.read().decode())
|
|
47
|
+
return data['tag_name']
|
|
48
|
+
except (URLError, ValueError, KeyError, TimeoutError):
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def show_about_dialog():
|
|
53
|
+
version_clean = __version__.split("+", maxsplit=1)[0]
|
|
54
|
+
latest_version = None
|
|
55
|
+
try:
|
|
56
|
+
latest_version = get_latest_version()
|
|
57
|
+
except Exception:
|
|
58
|
+
pass
|
|
59
|
+
update_text = ""
|
|
60
|
+
# pyling: disable=XXX
|
|
61
|
+
if latest_version:
|
|
62
|
+
latest_clean = latest_version.lstrip('v')
|
|
63
|
+
if compare_versions(version_clean, latest_clean) < 0:
|
|
64
|
+
update_text = f"""
|
|
65
|
+
<p style="color: red; font-weight: bold;">
|
|
66
|
+
Update available! Latest version: {latest_version}
|
|
67
|
+
<br><a href="https://github.com/lucalista/shinestacker/releases/latest">Download here</a>
|
|
68
|
+
</p>
|
|
69
|
+
""" # noqa E501
|
|
70
|
+
else:
|
|
71
|
+
update_text = f"""
|
|
72
|
+
<p style="color: green; font-weight: bold;">
|
|
73
|
+
You are using the lastet version: {latest_version}.
|
|
74
|
+
</p>
|
|
75
|
+
"""
|
|
76
|
+
about_text = f"""
|
|
77
|
+
<h3>{constants.APP_TITLE}</h3>
|
|
78
|
+
<h4>version: v{version_clean}</h4>
|
|
79
|
+
{update_text}
|
|
80
|
+
<p style='font-weight: normal;'>App and framework to combine multiple images
|
|
81
|
+
into a single focused image.</p>
|
|
82
|
+
<p>Author: Luca Lista<br/>
|
|
83
|
+
Email: <a href="mailto:luka.lista@gmail.com">luka.lista@gmail.com</a></p>
|
|
84
|
+
<ul>
|
|
85
|
+
<li><a href="https://shinestacker.wordpress.com/">Website on Wordpress</a></li>
|
|
86
|
+
<li><a href="https://github.com/lucalista/shinestacker">GitHub project repository</a></li>
|
|
87
|
+
</ul>
|
|
88
|
+
"""
|
|
89
|
+
# pyling: enable=XXX
|
|
90
|
+
msg = QMessageBox()
|
|
91
|
+
msg.setWindowTitle(f"About {constants.APP_STRING}")
|
|
92
|
+
msg.setIcon(QMessageBox.Icon.Information)
|
|
93
|
+
msg.setTextFormat(Qt.TextFormat.RichText)
|
|
94
|
+
msg.setText(about_text)
|
|
95
|
+
msg.setIcon(QMessageBox.Icon.NoIcon)
|
|
96
|
+
msg.exec_()
|
|
@@ -36,9 +36,10 @@ class _GuiConstants:
|
|
|
36
36
|
|
|
37
37
|
THUMB_WIDTH = 120 # px
|
|
38
38
|
THUMB_HEIGHT = 80 # px
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
39
|
+
THUMB_HI_COLOR = '#0000FF'
|
|
40
|
+
THUMB_LO_COLOR = '#0000FF'
|
|
41
|
+
THUMB_MASTER_HI_COLOR = '#0000FF'
|
|
42
|
+
THUMB_MASTER_LO_COLOR = 'transparent'
|
|
42
43
|
|
|
43
44
|
MAX_UNDO_STEPS = 50
|
|
44
45
|
|
|
@@ -46,8 +47,9 @@ class _GuiConstants:
|
|
|
46
47
|
|
|
47
48
|
UI_SIZES = {
|
|
48
49
|
'brush_preview': (100, 80),
|
|
49
|
-
'
|
|
50
|
-
'master_thumb': (THUMB_WIDTH, THUMB_HEIGHT)
|
|
50
|
+
'thumbnail_width': 100,
|
|
51
|
+
'master_thumb': (THUMB_WIDTH, THUMB_HEIGHT),
|
|
52
|
+
'label_height': 20
|
|
51
53
|
}
|
|
52
54
|
|
|
53
55
|
DEFAULT_BRUSH_HARDNESS = 50
|
|
@@ -30,6 +30,7 @@ class ActionsWindow(ProjectEditor):
|
|
|
30
30
|
def mark_as_modified(self):
|
|
31
31
|
self._modified_project = True
|
|
32
32
|
self.project_buffer.append(self.project.clone())
|
|
33
|
+
self.save_actions_set_enabled(True)
|
|
33
34
|
self.update_title()
|
|
34
35
|
|
|
35
36
|
def close_project(self):
|
|
@@ -40,6 +41,7 @@ class ActionsWindow(ProjectEditor):
|
|
|
40
41
|
self.job_list.clear()
|
|
41
42
|
self.action_list.clear()
|
|
42
43
|
self._modified_project = False
|
|
44
|
+
self.save_actions_set_enabled(False)
|
|
43
45
|
|
|
44
46
|
def new_project(self):
|
|
45
47
|
if not self._check_unsaved_changes():
|
|
@@ -51,8 +53,10 @@ class ActionsWindow(ProjectEditor):
|
|
|
51
53
|
self.job_list.clear()
|
|
52
54
|
self.action_list.clear()
|
|
53
55
|
self.set_project(Project())
|
|
56
|
+
self.save_actions_set_enabled(False)
|
|
54
57
|
dialog = NewProjectDialog(self)
|
|
55
58
|
if dialog.exec() == QDialog.Accepted:
|
|
59
|
+
self.save_actions_set_enabled(True)
|
|
56
60
|
input_folder = dialog.get_input_folder().split('/')
|
|
57
61
|
working_path = '/'.join(input_folder[:-1])
|
|
58
62
|
input_path = input_folder[-1]
|
|
@@ -142,6 +146,7 @@ class ActionsWindow(ProjectEditor):
|
|
|
142
146
|
if len(self.project.jobs) > 0:
|
|
143
147
|
self.job_list.setCurrentRow(0)
|
|
144
148
|
self.activateWindow()
|
|
149
|
+
self.save_actions_set_enabled(True)
|
|
145
150
|
for job in self.project.jobs:
|
|
146
151
|
if 'working_path' in job.params.keys():
|
|
147
152
|
working_path = job.params['working_path']
|
|
@@ -256,3 +261,6 @@ class ActionsWindow(ProjectEditor):
|
|
|
256
261
|
if dialog.exec() == QDialog.Accepted:
|
|
257
262
|
self.on_job_selected(self.job_list.currentRow())
|
|
258
263
|
self.mark_as_modified()
|
|
264
|
+
|
|
265
|
+
def save_actions_set_enabled(self, enabled):
|
|
266
|
+
pass
|
|
@@ -189,19 +189,24 @@ class MainWindow(ActionsWindow, LogManager):
|
|
|
189
189
|
open_action.setShortcut("Ctrl+O")
|
|
190
190
|
open_action.triggered.connect(self.open_project)
|
|
191
191
|
menu.addAction(open_action)
|
|
192
|
-
save_action = QAction("&Save", self)
|
|
193
|
-
save_action.setShortcut("Ctrl+S")
|
|
194
|
-
save_action.triggered.connect(self.save_project)
|
|
195
|
-
menu.addAction(save_action)
|
|
196
|
-
save_as_action = QAction("Save &As...", self)
|
|
197
|
-
save_as_action.setShortcut("Ctrl+Shift+S")
|
|
198
|
-
save_as_action.triggered.connect(self.save_project_as)
|
|
199
|
-
menu.addAction(save_as_action)
|
|
192
|
+
self.save_action = QAction("&Save", self)
|
|
193
|
+
self.save_action.setShortcut("Ctrl+S")
|
|
194
|
+
self.save_action.triggered.connect(self.save_project)
|
|
195
|
+
menu.addAction(self.save_action)
|
|
196
|
+
self.save_as_action = QAction("Save &As...", self)
|
|
197
|
+
self.save_as_action.setShortcut("Ctrl+Shift+S")
|
|
198
|
+
self.save_as_action.triggered.connect(self.save_project_as)
|
|
199
|
+
menu.addAction(self.save_as_action)
|
|
200
|
+
self.save_actions_set_enabled(False)
|
|
200
201
|
close_action = QAction("&Close", self)
|
|
201
202
|
close_action.setShortcut("Ctrl+W")
|
|
202
203
|
close_action.triggered.connect(self.close_project)
|
|
203
204
|
menu.addAction(close_action)
|
|
204
205
|
|
|
206
|
+
def save_actions_set_enabled(self, enabled):
|
|
207
|
+
self.save_action.setEnabled(enabled)
|
|
208
|
+
self.save_as_action.setEnabled(enabled)
|
|
209
|
+
|
|
205
210
|
def add_edit_menu(self, menubar):
|
|
206
211
|
menu = menubar.addMenu("&Edit")
|
|
207
212
|
undo_action = QAction("&Undo", self)
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, E0611, R0902, R0913, R0917, R0914
|
|
2
2
|
import numpy as np
|
|
3
|
-
from PySide6.
|
|
3
|
+
from PySide6.QtWidgets import QApplication, QLabel
|
|
4
|
+
from PySide6.QtGui import QPixmap, QPainter, QColor, QPen, QBrush, QFont
|
|
4
5
|
from PySide6.QtCore import Qt, QPoint
|
|
5
6
|
from .brush_gradient import create_default_brush_gradient
|
|
6
7
|
from .. config.gui_constants import gui_constants
|
|
@@ -18,11 +19,16 @@ class BrushTool:
|
|
|
18
19
|
self.opacity_slider = None
|
|
19
20
|
self.flow_slider = None
|
|
20
21
|
self._brush_mask_cache = {}
|
|
22
|
+
self.brush_text = None
|
|
21
23
|
|
|
22
24
|
def setup_ui(self, brush, brush_preview, image_viewer, size_slider, hardness_slider,
|
|
23
25
|
opacity_slider, flow_slider):
|
|
24
26
|
self.brush = brush
|
|
25
27
|
self.brush_preview = brush_preview
|
|
28
|
+
self.brush_text = QLabel(brush_preview.parent())
|
|
29
|
+
self.brush_text.setStyleSheet("color: navy; background: transparent;")
|
|
30
|
+
self.brush_text.setAlignment(Qt.AlignLeft | Qt.AlignTop)
|
|
31
|
+
self.brush_text.raise_()
|
|
26
32
|
self.image_viewer = image_viewer
|
|
27
33
|
self.size_slider = size_slider
|
|
28
34
|
self.hardness_slider = hardness_slider
|
|
@@ -86,7 +92,7 @@ class BrushTool:
|
|
|
86
92
|
pixmap = QPixmap(width, height)
|
|
87
93
|
pixmap.fill(Qt.transparent)
|
|
88
94
|
painter = QPainter(pixmap)
|
|
89
|
-
painter.setRenderHint(QPainter.
|
|
95
|
+
painter.setRenderHint(QPainter.TextAntialiasing, True)
|
|
90
96
|
preview_size = min(self.brush.size, width + 30, height + 30)
|
|
91
97
|
center_x, center_y = width // 2, height // 2
|
|
92
98
|
radius = preview_size // 2
|
|
@@ -109,10 +115,21 @@ class BrushTool:
|
|
|
109
115
|
painter.drawEllipse(QPoint(center_x, center_y), radius, radius)
|
|
110
116
|
if self.image_viewer.cursor_style == 'preview':
|
|
111
117
|
painter.setPen(QPen(QColor(0, 0, 160)))
|
|
112
|
-
|
|
113
|
-
painter.
|
|
114
|
-
|
|
115
|
-
painter.
|
|
118
|
+
font = QApplication.font()
|
|
119
|
+
painter.setFont(font)
|
|
120
|
+
font.setHintingPreference(QFont.PreferFullHinting)
|
|
121
|
+
painter.setFont(font)
|
|
122
|
+
self.brush_text.setText(
|
|
123
|
+
f"Size: {int(self.brush.size)}px\n"
|
|
124
|
+
f"Hardness: {self.brush.hardness}%\n"
|
|
125
|
+
f"Opacity: {self.brush.opacity}%\n"
|
|
126
|
+
f"Flow: {self.brush.flow}%"
|
|
127
|
+
)
|
|
128
|
+
self.brush_text.adjustSize()
|
|
129
|
+
self.brush_text.move(10, self.brush_preview.height() // 2 + 125)
|
|
130
|
+
self.brush_text.show()
|
|
131
|
+
else:
|
|
132
|
+
self.brush_text.hide()
|
|
116
133
|
painter.end()
|
|
117
134
|
self.brush_preview.setPixmap(pixmap)
|
|
118
135
|
self.image_viewer.update_brush_cursor()
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
# pylint: disable=C0114, C0115, C0116, E0611, R0903, R0913, R0917, E1121
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, R0903, R0913, R0917, E1121, R0902
|
|
2
2
|
import numpy as np
|
|
3
|
-
from PySide6.QtWidgets import QWidget, QListWidgetItem, QVBoxLayout, QLabel, QInputDialog
|
|
3
|
+
from PySide6.QtWidgets import (QWidget, QListWidgetItem, QVBoxLayout, QLabel, QInputDialog,
|
|
4
|
+
QAbstractItemView)
|
|
4
5
|
from PySide6.QtGui import QPixmap, QImage
|
|
5
6
|
from PySide6.QtCore import Qt, QObject, QTimer, QSize, Signal
|
|
6
7
|
from .. config.gui_constants import gui_constants
|
|
@@ -39,6 +40,7 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
39
40
|
self.update_timer = QTimer()
|
|
40
41
|
self.update_timer.setInterval(gui_constants.PAINT_REFRESH_TIMER)
|
|
41
42
|
self.update_timer.timeout.connect(self.process_pending_updates)
|
|
43
|
+
self.thumbnail_highlight = gui_constants.THUMB_LO_COLOR
|
|
42
44
|
|
|
43
45
|
def process_pending_updates(self):
|
|
44
46
|
if self.needs_update:
|
|
@@ -64,15 +66,15 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
64
66
|
self.display_master_layer()
|
|
65
67
|
|
|
66
68
|
def create_thumbnail(self, layer):
|
|
67
|
-
if layer.dtype == np.uint16
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
qimg = QImage(layer.data, width, height, 3 * width, QImage.Format_RGB888)
|
|
69
|
+
source_layer = (layer // 256).astype(np.uint8) if layer.dtype == np.uint16 else layer
|
|
70
|
+
height, width = source_layer.shape[:2]
|
|
71
|
+
if layer.ndim == 3 and source_layer.shape[-1] == 3:
|
|
72
|
+
qimg = QImage(source_layer.data, width, height, 3 * width, QImage.Format_RGB888)
|
|
72
73
|
else:
|
|
73
|
-
qimg = QImage(
|
|
74
|
+
qimg = QImage(source_layer.data, width, height, width, QImage.Format_Grayscale8)
|
|
74
75
|
return QPixmap.fromImage(
|
|
75
|
-
qimg.
|
|
76
|
+
qimg.scaledToWidth(
|
|
77
|
+
gui_constants.UI_SIZES['thumbnail_width'], Qt.SmoothTransformation))
|
|
76
78
|
|
|
77
79
|
def update_thumbnails(self):
|
|
78
80
|
self.update_master_thumbnail()
|
|
@@ -103,17 +105,22 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
103
105
|
self.master_thumbnail_label.setPixmap(pixmap)
|
|
104
106
|
|
|
105
107
|
def add_thumbnail_item(self, thumbnail, label, i, is_current):
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
108
|
+
container = QWidget()
|
|
109
|
+
container.setFixedWidth(gui_constants.UI_SIZES['thumbnail_width'] + 4)
|
|
110
|
+
container.setObjectName("thumbnailContainer")
|
|
111
|
+
container_layout = QVBoxLayout(container)
|
|
112
|
+
container_layout.setContentsMargins(2, 2, 2, 2)
|
|
113
|
+
container_layout.setSpacing(0)
|
|
114
|
+
content_widget = QWidget()
|
|
115
|
+
content_layout = QVBoxLayout(content_widget)
|
|
116
|
+
content_layout.setContentsMargins(0, 0, 0, 0)
|
|
117
|
+
content_layout.setSpacing(0)
|
|
111
118
|
thumbnail_label = QLabel()
|
|
112
119
|
thumbnail_label.setPixmap(thumbnail)
|
|
113
120
|
thumbnail_label.setAlignment(Qt.AlignCenter)
|
|
114
|
-
|
|
115
|
-
|
|
121
|
+
content_layout.addWidget(thumbnail_label)
|
|
116
122
|
label_widget = ClickableLabel(label)
|
|
123
|
+
label_widget.setFixedHeight(gui_constants.UI_SIZES['label_height'])
|
|
117
124
|
label_widget.setAlignment(Qt.AlignCenter)
|
|
118
125
|
|
|
119
126
|
def rename_label(label_widget, old_label, i):
|
|
@@ -124,21 +131,45 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
124
131
|
self.set_layer_labels(i, new_label)
|
|
125
132
|
|
|
126
133
|
label_widget.double_clicked.connect(lambda: rename_label(label_widget, label, i))
|
|
127
|
-
|
|
134
|
+
content_layout.addWidget(label_widget)
|
|
135
|
+
container_layout.addWidget(content_widget)
|
|
136
|
+
if is_current:
|
|
137
|
+
container.setStyleSheet(
|
|
138
|
+
f"#thumbnailContainer{{ border: 2px solid {self.thumbnail_highlight}; }}")
|
|
139
|
+
else:
|
|
140
|
+
container.setStyleSheet("#thumbnailContainer{ border: 2px solid transparent; }")
|
|
128
141
|
item = QListWidgetItem()
|
|
129
|
-
item.setSizeHint(QSize(gui_constants.
|
|
142
|
+
item.setSizeHint(QSize(gui_constants.UI_SIZES['thumbnail_width'] + 4,
|
|
143
|
+
thumbnail.height() + label_widget.height() + 4))
|
|
130
144
|
self.thumbnail_list.addItem(item)
|
|
131
|
-
self.thumbnail_list.setItemWidget(item,
|
|
132
|
-
|
|
145
|
+
self.thumbnail_list.setItemWidget(item, container)
|
|
133
146
|
if is_current:
|
|
134
147
|
self.thumbnail_list.setCurrentItem(item)
|
|
135
148
|
|
|
149
|
+
def highlight_thumbnail(self, index):
|
|
150
|
+
for i in range(self.thumbnail_list.count()):
|
|
151
|
+
item = self.thumbnail_list.item(i)
|
|
152
|
+
widget = self.thumbnail_list.itemWidget(item)
|
|
153
|
+
if widget:
|
|
154
|
+
widget.setStyleSheet("#thumbnailContainer{ border: 2px solid transparent; }")
|
|
155
|
+
current_item = self.thumbnail_list.item(index)
|
|
156
|
+
if current_item:
|
|
157
|
+
widget = self.thumbnail_list.itemWidget(current_item)
|
|
158
|
+
if widget:
|
|
159
|
+
widget.setStyleSheet(
|
|
160
|
+
f"#thumbnailContainer{{ border: 2px solid {self.thumbnail_highlight}; }}")
|
|
161
|
+
self.thumbnail_list.setCurrentRow(index)
|
|
162
|
+
self.thumbnail_list.scrollToItem(
|
|
163
|
+
self.thumbnail_list.item(index), QAbstractItemView.PositionAtCenter)
|
|
164
|
+
|
|
136
165
|
def set_view_master(self):
|
|
137
166
|
if self.has_no_master_layer():
|
|
138
167
|
return
|
|
139
168
|
self.view_mode = 'master'
|
|
140
169
|
self.temp_view_individual = False
|
|
141
170
|
self.display_master_layer()
|
|
171
|
+
self.thumbnail_highlight = gui_constants.THUMB_LO_COLOR
|
|
172
|
+
self.highlight_thumbnail(self.current_layer_idx())
|
|
142
173
|
self.status_message_requested.emit("View mode: Master")
|
|
143
174
|
self.cursor_preview_state_changed.emit(True) # True = allow preview
|
|
144
175
|
|
|
@@ -148,6 +179,8 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
148
179
|
self.view_mode = 'individual'
|
|
149
180
|
self.temp_view_individual = False
|
|
150
181
|
self.display_current_layer()
|
|
182
|
+
self.thumbnail_highlight = gui_constants.THUMB_HI_COLOR
|
|
183
|
+
self.highlight_thumbnail(self.current_layer_idx())
|
|
151
184
|
self.status_message_requested.emit("View mode: Individual layers")
|
|
152
185
|
self.cursor_preview_state_changed.emit(False) # False = no preview
|
|
153
186
|
|
|
@@ -155,6 +188,8 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
155
188
|
if not self.temp_view_individual and self.view_mode == 'master':
|
|
156
189
|
self.temp_view_individual = True
|
|
157
190
|
self.image_viewer.update_brush_cursor()
|
|
191
|
+
self.thumbnail_highlight = gui_constants.THUMB_HI_COLOR
|
|
192
|
+
self.highlight_thumbnail(self.current_layer_idx())
|
|
158
193
|
self.display_current_layer()
|
|
159
194
|
self.status_message_requested.emit("Temporary view: Individual layer (hold X)")
|
|
160
195
|
|
|
@@ -162,6 +197,8 @@ class DisplayManager(QObject, LayerCollectionHandler):
|
|
|
162
197
|
if self.temp_view_individual:
|
|
163
198
|
self.temp_view_individual = False
|
|
164
199
|
self.image_viewer.update_brush_cursor()
|
|
200
|
+
self.thumbnail_highlight = gui_constants.THUMB_LO_COLOR
|
|
201
|
+
self.highlight_thumbnail(self.current_layer_idx())
|
|
165
202
|
self.display_master_layer()
|
|
166
203
|
self.status_message_requested.emit("View mode: Master")
|
|
167
204
|
self.cursor_preview_state_changed.emit(True) # Restore preview
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, E0611, R0902
|
|
2
|
-
from PySide6.QtWidgets import QMainWindow, QMessageBox
|
|
2
|
+
from PySide6.QtWidgets import QMainWindow, QMessageBox
|
|
3
3
|
from .. config.constants import constants
|
|
4
4
|
from .undo_manager import UndoManager
|
|
5
5
|
from .layer_collection import LayerCollection
|
|
@@ -87,7 +87,7 @@ class ImageEditor(QMainWindow, LayerCollectionHandler):
|
|
|
87
87
|
def update_title(self):
|
|
88
88
|
title = constants.APP_TITLE
|
|
89
89
|
if self.io_gui_handler is not None:
|
|
90
|
-
path = self.io_gui_handler.current_file_path
|
|
90
|
+
path = self.io_gui_handler.current_file_path()
|
|
91
91
|
if path != '':
|
|
92
92
|
title += f" - {path.split('/')[-1]}"
|
|
93
93
|
if self.modified:
|
|
@@ -96,6 +96,7 @@ class ImageEditor(QMainWindow, LayerCollectionHandler):
|
|
|
96
96
|
|
|
97
97
|
def mark_as_modified(self):
|
|
98
98
|
self.modified = True
|
|
99
|
+
self.save_actions_set_enabled(True)
|
|
99
100
|
self.update_title()
|
|
100
101
|
|
|
101
102
|
def change_layer(self, layer_idx):
|
|
@@ -114,19 +115,14 @@ class ImageEditor(QMainWindow, LayerCollectionHandler):
|
|
|
114
115
|
new_idx = max(0, self.current_layer_idx() - 1)
|
|
115
116
|
if new_idx != self.current_layer_idx():
|
|
116
117
|
self.change_layer(new_idx)
|
|
117
|
-
self.highlight_thumbnail(new_idx)
|
|
118
|
+
self.display_manager.highlight_thumbnail(new_idx)
|
|
118
119
|
|
|
119
120
|
def next_layer(self):
|
|
120
121
|
if self.layer_stack() is not None:
|
|
121
122
|
new_idx = min(self.number_of_layers() - 1, self.current_layer_idx() + 1)
|
|
122
123
|
if new_idx != self.current_layer_idx():
|
|
123
124
|
self.change_layer(new_idx)
|
|
124
|
-
self.highlight_thumbnail(new_idx)
|
|
125
|
-
|
|
126
|
-
def highlight_thumbnail(self, index):
|
|
127
|
-
self.thumbnail_list.setCurrentRow(index)
|
|
128
|
-
self.thumbnail_list.scrollToItem(
|
|
129
|
-
self.thumbnail_list.item(index), QAbstractItemView.PositionAtCenter)
|
|
125
|
+
self.display_manager.highlight_thumbnail(new_idx)
|
|
130
126
|
|
|
131
127
|
def copy_layer_to_master(self):
|
|
132
128
|
if self.layer_stack() is None or self.master_layer() is None:
|
|
@@ -23,6 +23,7 @@ def brush_size_to_slider(size):
|
|
|
23
23
|
|
|
24
24
|
class ImageEditorUI(ImageFilters):
|
|
25
25
|
def __init__(self):
|
|
26
|
+
self.thumbnail_highlight = gui_constants.THUMB_MASTER_HI_COLOR
|
|
26
27
|
super().__init__()
|
|
27
28
|
self.brush = Brush()
|
|
28
29
|
self.setup_ui()
|
|
@@ -125,16 +126,19 @@ class ImageEditorUI(ImageFilters):
|
|
|
125
126
|
}
|
|
126
127
|
""")
|
|
127
128
|
master_label.setAlignment(Qt.AlignCenter)
|
|
128
|
-
master_label.setFixedHeight(gui_constants.
|
|
129
|
+
master_label.setFixedHeight(gui_constants.UI_SIZES['label_height'])
|
|
129
130
|
side_layout.addWidget(master_label)
|
|
130
131
|
self.master_thumbnail_frame = QFrame()
|
|
132
|
+
self.master_thumbnail_frame.setObjectName("thumbnailContainer")
|
|
133
|
+
self.master_thumbnail_frame.setStyleSheet(
|
|
134
|
+
f"#thumbnailContainer{{ border: 2px solid {self.thumbnail_highlight}; }}")
|
|
131
135
|
self.master_thumbnail_frame.setFrameShape(QFrame.StyledPanel)
|
|
132
136
|
master_thumbnail_layout = QVBoxLayout(self.master_thumbnail_frame)
|
|
133
|
-
master_thumbnail_layout.setContentsMargins(
|
|
137
|
+
master_thumbnail_layout.setContentsMargins(8, 8, 8, 8)
|
|
134
138
|
self.master_thumbnail_label = QLabel()
|
|
135
139
|
self.master_thumbnail_label.setAlignment(Qt.AlignCenter)
|
|
136
|
-
self.master_thumbnail_label.
|
|
137
|
-
gui_constants.
|
|
140
|
+
self.master_thumbnail_label.setFixedWidth(
|
|
141
|
+
gui_constants.UI_SIZES['thumbnail_width'])
|
|
138
142
|
self.master_thumbnail_label.mousePressEvent = \
|
|
139
143
|
lambda e: self.display_manager.set_view_master()
|
|
140
144
|
master_thumbnail_layout.addWidget(self.master_thumbnail_label)
|
|
@@ -152,7 +156,7 @@ class ImageEditorUI(ImageFilters):
|
|
|
152
156
|
}
|
|
153
157
|
""")
|
|
154
158
|
layers_label.setAlignment(Qt.AlignCenter)
|
|
155
|
-
layers_label.setFixedHeight(gui_constants.
|
|
159
|
+
layers_label.setFixedHeight(gui_constants.UI_SIZES['label_height'])
|
|
156
160
|
side_layout.addWidget(layers_label)
|
|
157
161
|
self.thumbnail_list = QListWidget()
|
|
158
162
|
self.thumbnail_list.setFocusPolicy(Qt.StrongFocus)
|
|
@@ -204,18 +208,29 @@ class ImageEditorUI(ImageFilters):
|
|
|
204
208
|
layout.setSpacing(2)
|
|
205
209
|
super().setup_ui()
|
|
206
210
|
|
|
211
|
+
def highlight_master_thumbnail(self):
|
|
212
|
+
self.master_thumbnail_frame.setStyleSheet(
|
|
213
|
+
f"#thumbnailContainer{{ border: 2px solid {self.thumbnail_highlight}; }}")
|
|
214
|
+
|
|
207
215
|
def setup_menu(self):
|
|
208
216
|
menubar = self.menuBar()
|
|
209
217
|
file_menu = menubar.addMenu("&File")
|
|
210
218
|
file_menu.addAction("&Open...", self.io_gui_handler.open_file, "Ctrl+O")
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
self.
|
|
214
|
-
self.
|
|
215
|
-
self.
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
file_menu.addAction(
|
|
219
|
+
self.save_action = QAction("&Save", self)
|
|
220
|
+
self.save_action.setShortcut("Ctrl+S")
|
|
221
|
+
self.save_action.triggered.connect(self.io_gui_handler.save_file)
|
|
222
|
+
file_menu.addAction(self.save_action)
|
|
223
|
+
self.save_as_action = QAction("Save &As...", self)
|
|
224
|
+
self.save_as_action.setShortcut("Ctrl+Shift+S")
|
|
225
|
+
self.save_as_action.triggered.connect(self.io_gui_handler.save_file_as)
|
|
226
|
+
file_menu.addAction(self.save_as_action)
|
|
227
|
+
self.io_gui_handler.save_master_only = QAction("Save Master &Only", self)
|
|
228
|
+
self.io_gui_handler.save_master_only.setCheckable(True)
|
|
229
|
+
self.io_gui_handler.save_master_only.setChecked(True)
|
|
230
|
+
file_menu.addAction(self.io_gui_handler.save_master_only)
|
|
231
|
+
self.save_actions_set_enabled(False)
|
|
232
|
+
|
|
233
|
+
file_menu.addAction("&Close", self.close_file, "Ctrl+W")
|
|
219
234
|
file_menu.addSeparator()
|
|
220
235
|
file_menu.addAction("&Import frames", self.io_gui_handler.import_frames)
|
|
221
236
|
file_menu.addAction("Import &EXIF data", self.io_gui_handler.select_exif_path)
|
|
@@ -271,12 +286,12 @@ class ImageEditorUI(ImageFilters):
|
|
|
271
286
|
|
|
272
287
|
view_master_action = QAction("View Master", self)
|
|
273
288
|
view_master_action.setShortcut("M")
|
|
274
|
-
view_master_action.triggered.connect(self.
|
|
289
|
+
view_master_action.triggered.connect(self.set_view_master)
|
|
275
290
|
view_menu.addAction(view_master_action)
|
|
276
291
|
|
|
277
292
|
view_individual_action = QAction("View Individual", self)
|
|
278
293
|
view_individual_action.setShortcut("L")
|
|
279
|
-
view_individual_action.triggered.connect(self.
|
|
294
|
+
view_individual_action.triggered.connect(self.set_view_individual)
|
|
280
295
|
view_menu.addAction(view_individual_action)
|
|
281
296
|
view_menu.addSeparator()
|
|
282
297
|
|
|
@@ -334,6 +349,25 @@ class ImageEditorUI(ImageFilters):
|
|
|
334
349
|
shortcuts_help_action.triggered.connect(self.shortcuts_help)
|
|
335
350
|
help_menu.addAction(shortcuts_help_action)
|
|
336
351
|
|
|
352
|
+
def save_actions_set_enabled(self, enabled):
|
|
353
|
+
self.save_action.setEnabled(enabled)
|
|
354
|
+
self.save_as_action.setEnabled(enabled)
|
|
355
|
+
self.io_gui_handler.save_master_only.setEnabled(enabled)
|
|
356
|
+
|
|
357
|
+
def close_file(self):
|
|
358
|
+
self.io_gui_handler.close_file()
|
|
359
|
+
self.save_actions_set_enabled(False)
|
|
360
|
+
|
|
361
|
+
def set_view_master(self):
|
|
362
|
+
self.display_manager.set_view_master()
|
|
363
|
+
self.thumbnail_highlight = gui_constants.THUMB_MASTER_HI_COLOR
|
|
364
|
+
self.highlight_master_thumbnail()
|
|
365
|
+
|
|
366
|
+
def set_view_individual(self):
|
|
367
|
+
self.display_manager.set_view_individual()
|
|
368
|
+
self.thumbnail_highlight = gui_constants.THUMB_MASTER_LO_COLOR
|
|
369
|
+
self.highlight_master_thumbnail()
|
|
370
|
+
|
|
337
371
|
def shortcuts_help(self):
|
|
338
372
|
self._dialog = ShortcutsHelp(self)
|
|
339
373
|
self._dialog.exec()
|
|
@@ -365,8 +399,12 @@ class ImageEditorUI(ImageFilters):
|
|
|
365
399
|
def handle_temp_view(self, start):
|
|
366
400
|
if start:
|
|
367
401
|
self.display_manager.start_temp_view()
|
|
402
|
+
self.thumbnail_highlight = gui_constants.THUMB_MASTER_LO_COLOR
|
|
403
|
+
self.highlight_master_thumbnail()
|
|
368
404
|
else:
|
|
369
405
|
self.display_manager.end_temp_view()
|
|
406
|
+
self.thumbnail_highlight = gui_constants.THUMB_MASTER_HI_COLOR
|
|
407
|
+
self.highlight_master_thumbnail()
|
|
370
408
|
|
|
371
409
|
def handle_brush_size_change(self, delta):
|
|
372
410
|
if delta > 0:
|