shinestacker 1.5.3__py3-none-any.whl → 1.6.0__py3-none-any.whl
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/_version.py +1 -1
- shinestacker/algorithms/multilayer.py +1 -1
- shinestacker/algorithms/stack.py +17 -9
- shinestacker/app/args_parser_opts.py +4 -0
- shinestacker/app/gui_utils.py +10 -2
- shinestacker/app/main.py +5 -2
- shinestacker/app/project.py +4 -2
- shinestacker/app/retouch.py +3 -1
- shinestacker/app/settings_dialog.py +171 -0
- shinestacker/config/app_config.py +30 -0
- shinestacker/config/constants.py +3 -0
- shinestacker/config/gui_constants.py +4 -2
- shinestacker/config/settings.py +110 -0
- shinestacker/gui/action_config.py +6 -5
- shinestacker/gui/action_config_dialog.py +17 -74
- shinestacker/gui/config_dialog.py +78 -0
- shinestacker/gui/main_window.py +16 -6
- shinestacker/gui/menu_manager.py +16 -10
- shinestacker/gui/new_project.py +2 -1
- shinestacker/gui/project_controller.py +11 -6
- shinestacker/gui/project_model.py +16 -1
- shinestacker/gui/recent_file_manager.py +3 -21
- shinestacker/retouch/display_manager.py +47 -5
- shinestacker/retouch/image_editor_ui.py +59 -15
- shinestacker/retouch/image_view_status.py +4 -1
- shinestacker/retouch/io_gui_handler.py +3 -0
- shinestacker/retouch/overlaid_view.py +6 -43
- shinestacker/retouch/sidebyside_view.py +45 -41
- shinestacker/retouch/transformation_manager.py +0 -1
- shinestacker/retouch/undo_manager.py +1 -1
- shinestacker/retouch/view_strategy.py +125 -61
- shinestacker/retouch/white_balance_filter.py +5 -6
- {shinestacker-1.5.3.dist-info → shinestacker-1.6.0.dist-info}/METADATA +1 -1
- {shinestacker-1.5.3.dist-info → shinestacker-1.6.0.dist-info}/RECORD +38 -34
- {shinestacker-1.5.3.dist-info → shinestacker-1.6.0.dist-info}/WHEEL +0 -0
- {shinestacker-1.5.3.dist-info → shinestacker-1.6.0.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.5.3.dist-info → shinestacker-1.6.0.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.5.3.dist-info → shinestacker-1.6.0.dist-info}/top_level.txt +0 -0
|
@@ -6,6 +6,7 @@ from PySide6.QtGui import QShortcut, QKeySequence, QAction, QActionGroup
|
|
|
6
6
|
from PySide6.QtCore import Qt
|
|
7
7
|
from PySide6.QtGui import QGuiApplication
|
|
8
8
|
from .. config.constants import constants
|
|
9
|
+
from .. config.app_config import AppConfig
|
|
9
10
|
from .. config.gui_constants import gui_constants
|
|
10
11
|
from .. gui.recent_file_manager import RecentFileManager
|
|
11
12
|
from .image_viewer import ImageViewer
|
|
@@ -184,6 +185,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
184
185
|
def change_layer_item(item):
|
|
185
186
|
layer_idx = self.thumbnail_list.row(item)
|
|
186
187
|
self.change_layer(layer_idx)
|
|
188
|
+
self.display_manager.highlight_thumbnail(layer_idx)
|
|
187
189
|
|
|
188
190
|
self.thumbnail_list.itemClicked.connect(change_layer_item)
|
|
189
191
|
self.thumbnail_list.setStyleSheet("""
|
|
@@ -229,11 +231,14 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
229
231
|
self.io_gui_handler.mark_as_modified_requested.connect(self.mark_as_modified)
|
|
230
232
|
self.io_gui_handler.change_layer_requested.connect(self.change_layer)
|
|
231
233
|
self.io_gui_handler.add_recent_file_requested.connect(self.add_recent_file)
|
|
234
|
+
self.io_gui_handler.set_enabled_file_open_close_actions_requested.connect(
|
|
235
|
+
self.set_enabled_file_open_close_actions)
|
|
232
236
|
self.brush_tool.setup_ui(self.brush, self.brush_preview_widget, self.image_viewer,
|
|
233
237
|
self.brush_size_slider, self.hardness_slider, self.opacity_slider,
|
|
234
238
|
self.flow_slider)
|
|
235
239
|
self.image_viewer.set_brush(self.brush_tool.brush)
|
|
236
240
|
self.image_viewer.set_preview_brush(self.brush_tool.brush)
|
|
241
|
+
self.image_viewer.status.set_zoom_factor_requested.connect(self.handle_set_zoom_factor)
|
|
237
242
|
self.brush_tool.update_brush_thumb()
|
|
238
243
|
self.io_gui_handler.setup_ui(self.display_manager, self.image_viewer)
|
|
239
244
|
menubar = self.menuBar()
|
|
@@ -277,22 +282,26 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
277
282
|
|
|
278
283
|
transf_menu = QMenu("&Transform")
|
|
279
284
|
rotate_90_cw_action = QAction(gui_constants.ROTATE_90_CW_LABEL, self)
|
|
280
|
-
|
|
285
|
+
rotate_90_cw_action.setProperty("requires_file", True)
|
|
281
286
|
rotate_90_cw_action.triggered.connect(lambda: self.transformation_manager.rotate_90_cw())
|
|
287
|
+
transf_menu.addAction(rotate_90_cw_action)
|
|
282
288
|
rotate_90_ccw_action = QAction(gui_constants.ROTATE_90_CCW_LABEL, self)
|
|
283
|
-
|
|
289
|
+
rotate_90_ccw_action.setProperty("requires_file", True)
|
|
284
290
|
rotate_90_ccw_action.triggered.connect(lambda: self.transformation_manager.rotate_90_ccw())
|
|
291
|
+
transf_menu.addAction(rotate_90_ccw_action)
|
|
285
292
|
rotate_180_action = QAction(gui_constants.ROTATE_180_LABEL, self)
|
|
286
293
|
rotate_180_action.triggered.connect(lambda: self.transformation_manager.rotate_180())
|
|
294
|
+
rotate_180_action.setProperty("requires_file", True)
|
|
287
295
|
transf_menu.addAction(rotate_180_action)
|
|
288
296
|
edit_menu.addMenu(transf_menu)
|
|
289
297
|
|
|
290
298
|
edit_menu.addSeparator()
|
|
291
299
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
300
|
+
copy_current_to_master_action = QAction("Copy Current Layer to Master", self)
|
|
301
|
+
copy_current_to_master_action.setShortcut("Ctrl+M")
|
|
302
|
+
copy_current_to_master_action.setProperty("requires_file", True)
|
|
303
|
+
copy_current_to_master_action.triggered.connect(self.copy_layer_to_master)
|
|
304
|
+
edit_menu.addAction(copy_current_to_master_action)
|
|
296
305
|
|
|
297
306
|
view_menu = menubar.addMenu("&View")
|
|
298
307
|
|
|
@@ -351,15 +360,18 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
351
360
|
}
|
|
352
361
|
brush_action = self.cursor_style_actions['brush']
|
|
353
362
|
brush_action.setCheckable(True)
|
|
363
|
+
brush_action.setProperty("requires_file", True)
|
|
354
364
|
brush_action.triggered.connect(lambda: set_cursor_style('brush'))
|
|
355
365
|
cursor_menu.addAction(brush_action)
|
|
356
366
|
|
|
357
367
|
preview_action = self.cursor_style_actions['preview']
|
|
368
|
+
preview_action.setProperty("requires_file", True)
|
|
358
369
|
preview_action.setCheckable(True)
|
|
359
370
|
preview_action.triggered.connect(lambda: set_cursor_style('preview'))
|
|
360
371
|
cursor_menu.addAction(preview_action)
|
|
361
372
|
|
|
362
373
|
outline_action = self.cursor_style_actions['outline']
|
|
374
|
+
outline_action.setProperty("requires_file", True)
|
|
363
375
|
outline_action.setCheckable(True)
|
|
364
376
|
outline_action.triggered.connect(lambda: set_cursor_style('outline'))
|
|
365
377
|
cursor_menu.addAction(outline_action)
|
|
@@ -382,50 +394,59 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
382
394
|
|
|
383
395
|
zoom_in_action = QAction("Zoom In", self)
|
|
384
396
|
zoom_in_action.setShortcut("Ctrl++")
|
|
397
|
+
zoom_in_action.setProperty("requires_file", True)
|
|
385
398
|
zoom_in_action.triggered.connect(self.image_viewer.zoom_in)
|
|
386
399
|
view_menu.addAction(zoom_in_action)
|
|
387
400
|
|
|
388
401
|
zoom_out_action = QAction("Zoom Out", self)
|
|
389
402
|
zoom_out_action.setShortcut("Ctrl+-")
|
|
403
|
+
zoom_out_action.setProperty("requires_file", True)
|
|
390
404
|
zoom_out_action.triggered.connect(self.image_viewer.zoom_out)
|
|
391
405
|
view_menu.addAction(zoom_out_action)
|
|
392
406
|
|
|
393
407
|
adapt_action = QAction("Adapt to Screen", self)
|
|
394
408
|
adapt_action.setShortcut("Ctrl+0")
|
|
409
|
+
adapt_action.setProperty("requires_file", True)
|
|
395
410
|
adapt_action.triggered.connect(self.image_viewer.reset_zoom)
|
|
396
411
|
view_menu.addAction(adapt_action)
|
|
397
412
|
|
|
398
413
|
actual_size_action = QAction("Actual Size", self)
|
|
399
414
|
actual_size_action.setShortcut("Ctrl+R")
|
|
415
|
+
actual_size_action.setProperty("requires_file", True)
|
|
400
416
|
actual_size_action.triggered.connect(self.image_viewer.actual_size)
|
|
401
417
|
view_menu.addAction(actual_size_action)
|
|
402
418
|
view_menu.addSeparator()
|
|
403
419
|
|
|
404
420
|
self.view_master_action = QAction("View Master", self)
|
|
405
421
|
self.view_master_action.setShortcut("M")
|
|
422
|
+
self.view_master_action.setProperty("requires_file", True)
|
|
406
423
|
self.view_master_action.triggered.connect(self.set_view_master)
|
|
407
424
|
view_menu.addAction(self.view_master_action)
|
|
408
425
|
|
|
409
426
|
self.view_individual_action = QAction("View Individual", self)
|
|
410
427
|
self.view_individual_action.setShortcut("L")
|
|
428
|
+
self.view_individual_action.setProperty("requires_file", True)
|
|
411
429
|
self.view_individual_action.triggered.connect(self.set_view_individual)
|
|
412
430
|
view_menu.addAction(self.view_individual_action)
|
|
413
431
|
|
|
414
432
|
self.toggle_view_master_individual_action = QAction("Toggle Master/Individual", self)
|
|
415
433
|
self.toggle_view_master_individual_action.setShortcut("T")
|
|
434
|
+
self.toggle_view_master_individual_action.setProperty("requires_file", True)
|
|
416
435
|
self.toggle_view_master_individual_action.triggered.connect(
|
|
417
436
|
self.toggle_view_master_individual)
|
|
418
437
|
view_menu.addAction(self.toggle_view_master_individual_action)
|
|
419
438
|
view_menu.addSeparator()
|
|
420
439
|
|
|
421
|
-
self.set_strategy('
|
|
440
|
+
self.set_strategy(AppConfig.get('view_strategy'))
|
|
422
441
|
|
|
423
442
|
sort_asc_action = QAction("Sort Layers A-Z", self)
|
|
424
|
-
sort_asc_action.
|
|
443
|
+
sort_asc_action.setProperty("requires_file", True)
|
|
444
|
+
sort_asc_action.triggered.connect(lambda: self.sort_layers_ui('asc'))
|
|
425
445
|
view_menu.addAction(sort_asc_action)
|
|
426
446
|
|
|
427
447
|
sort_desc_action = QAction("Sort Layers Z-A", self)
|
|
428
|
-
sort_desc_action.
|
|
448
|
+
sort_desc_action.setProperty("requires_file", True)
|
|
449
|
+
sort_desc_action.triggered.connect(lambda: self.sort_layers_ui('desc'))
|
|
429
450
|
view_menu.addAction(sort_desc_action)
|
|
430
451
|
|
|
431
452
|
view_menu.addSeparator()
|
|
@@ -433,15 +454,19 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
433
454
|
filter_menu = menubar.addMenu("&Filter")
|
|
434
455
|
filter_menu.setObjectName("Filter")
|
|
435
456
|
denoise_action = QAction("Denoise", self)
|
|
457
|
+
denoise_action.setProperty("requires_file", True)
|
|
436
458
|
denoise_action.triggered.connect(self.denoise_filter)
|
|
437
459
|
filter_menu.addAction(denoise_action)
|
|
438
460
|
unsharp_mask_action = QAction("Unsharp Mask", self)
|
|
461
|
+
unsharp_mask_action.setProperty("requires_file", True)
|
|
439
462
|
unsharp_mask_action.triggered.connect(self.unsharp_mask)
|
|
440
463
|
filter_menu.addAction(unsharp_mask_action)
|
|
441
464
|
white_balance_action = QAction("White Balance", self)
|
|
465
|
+
white_balance_action.setProperty("requires_file", True)
|
|
442
466
|
white_balance_action.triggered.connect(self.white_balance)
|
|
443
467
|
filter_menu.addAction(white_balance_action)
|
|
444
468
|
vignetting_action = QAction("Vignetting Correction", self)
|
|
469
|
+
vignetting_action.setProperty("requires_file", True)
|
|
445
470
|
vignetting_action.triggered.connect(self.vignetting_correction)
|
|
446
471
|
filter_menu.addAction(vignetting_action)
|
|
447
472
|
|
|
@@ -449,6 +474,8 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
449
474
|
help_menu.setObjectName("Help")
|
|
450
475
|
shortcuts_help_action = QAction("Shortcuts and Mouse", self)
|
|
451
476
|
|
|
477
|
+
self.zoom_factor_label = QLabel("")
|
|
478
|
+
self.statusBar().addPermanentWidget(self.zoom_factor_label)
|
|
452
479
|
self.statusBar().showMessage("Shine Stacker ready.", 2000)
|
|
453
480
|
|
|
454
481
|
def shortcuts_help():
|
|
@@ -462,20 +489,33 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
462
489
|
prev_layer.activated.connect(self.prev_layer)
|
|
463
490
|
next_layer = QShortcut(QKeySequence(Qt.Key_Down), self, context=Qt.ApplicationShortcut)
|
|
464
491
|
next_layer.activated.connect(self.next_layer)
|
|
492
|
+
|
|
493
|
+
self.set_enabled_file_open_close_actions(False)
|
|
465
494
|
self.installEventFilter(self)
|
|
466
495
|
|
|
496
|
+
def handle_config(self):
|
|
497
|
+
self.set_strategy(AppConfig.get('view_strategy'))
|
|
498
|
+
self.display_manager.update_timer.setInterval(AppConfig.get('display_refresh_time'))
|
|
499
|
+
|
|
500
|
+
def set_enabled_view_toggles(self, enabled):
|
|
501
|
+
self.view_master_action.setEnabled(enabled)
|
|
502
|
+
self.view_individual_action.setEnabled(enabled)
|
|
503
|
+
self.toggle_view_master_individual_action.setEnabled(enabled)
|
|
504
|
+
|
|
467
505
|
def set_strategy(self, strategy):
|
|
468
506
|
self.image_viewer.set_strategy(strategy)
|
|
469
|
-
enable_shortcuts = strategy == 'overlaid'
|
|
470
507
|
self.display_manager.view_mode = 'master'
|
|
471
508
|
self.highlight_master_thumbnail(gui_constants.THUMB_MASTER_HI_COLOR)
|
|
472
|
-
self.
|
|
473
|
-
self.view_individual_action.setEnabled(enable_shortcuts)
|
|
474
|
-
self.toggle_view_master_individual_action.setEnabled(enable_shortcuts)
|
|
509
|
+
self.set_enabled_view_toggles(strategy == 'overlaid')
|
|
475
510
|
for label, mode in self.view_mode_actions.items():
|
|
476
511
|
mode.setEnabled(label != strategy)
|
|
477
512
|
mode.setChecked(label == strategy)
|
|
478
513
|
|
|
514
|
+
def set_enabled_file_open_close_actions(self, enabled):
|
|
515
|
+
for action in self.findChildren(QAction):
|
|
516
|
+
if action.property("requires_file"):
|
|
517
|
+
action.setEnabled(enabled)
|
|
518
|
+
|
|
479
519
|
def update_title(self):
|
|
480
520
|
title = constants.APP_TITLE
|
|
481
521
|
if self.io_gui_handler is not None:
|
|
@@ -544,10 +584,10 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
544
584
|
def save_master_only(self, _checked):
|
|
545
585
|
self.update_title()
|
|
546
586
|
|
|
547
|
-
def
|
|
587
|
+
def sort_layers_ui(self, order):
|
|
548
588
|
self.sort_layers(order)
|
|
549
589
|
self.display_manager.update_thumbnails()
|
|
550
|
-
self.change_layer(self.
|
|
590
|
+
self.change_layer(self.current_layer_idx())
|
|
551
591
|
|
|
552
592
|
def change_layer(self, layer_idx):
|
|
553
593
|
if 0 <= layer_idx < self.number_of_layers():
|
|
@@ -669,6 +709,7 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
669
709
|
self.io_gui_handler.close_file()
|
|
670
710
|
self.set_master_layer(None)
|
|
671
711
|
self.mark_as_modified(False)
|
|
712
|
+
self.zoom_factor_label.setText("")
|
|
672
713
|
|
|
673
714
|
def set_view_master(self):
|
|
674
715
|
self.display_manager.set_view_master()
|
|
@@ -721,3 +762,6 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
|
|
|
721
762
|
self.brush_tool.increase_brush_size()
|
|
722
763
|
else:
|
|
723
764
|
self.brush_tool.decrease_brush_size()
|
|
765
|
+
|
|
766
|
+
def handle_set_zoom_factor(self, zoom_factor):
|
|
767
|
+
self.zoom_factor_label.setText(f"zoom: {zoom_factor:.1%}")
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, E0611, R0902
|
|
2
|
-
from PySide6.QtCore import QObject, QRectF
|
|
2
|
+
from PySide6.QtCore import QObject, QRectF, Signal
|
|
3
3
|
from PySide6.QtGui import QPixmap
|
|
4
4
|
|
|
5
5
|
|
|
6
6
|
class ImageViewStatus(QObject):
|
|
7
|
+
set_zoom_factor_requested = Signal(float)
|
|
8
|
+
|
|
7
9
|
def __init__(self, parent=None):
|
|
8
10
|
super().__init__(parent)
|
|
9
11
|
self.pixmap_master = QPixmap()
|
|
@@ -53,6 +55,7 @@ class ImageViewStatus(QObject):
|
|
|
53
55
|
|
|
54
56
|
def set_zoom_factor(self, zoom_factor):
|
|
55
57
|
self.zoom_factor = zoom_factor
|
|
58
|
+
self.set_zoom_factor_requested.emit(zoom_factor)
|
|
56
59
|
|
|
57
60
|
def set_min_scale(self, min_scale):
|
|
58
61
|
self.min_scale = min_scale
|
|
@@ -17,6 +17,7 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
|
|
|
17
17
|
mark_as_modified_requested = Signal(bool)
|
|
18
18
|
change_layer_requested = Signal(int)
|
|
19
19
|
add_recent_file_requested = Signal(str)
|
|
20
|
+
set_enabled_file_open_close_actions_requested = Signal(bool)
|
|
20
21
|
|
|
21
22
|
def __init__(self, layer_collection, undo_manager, parent):
|
|
22
23
|
QObject.__init__(self, parent)
|
|
@@ -165,6 +166,7 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
|
|
|
165
166
|
self.change_layer_requested.emit(0)
|
|
166
167
|
self.status_message_requested.emit(message)
|
|
167
168
|
self.update_title_requested.emit()
|
|
169
|
+
self.set_enabled_file_open_close_actions_requested.emit(True)
|
|
168
170
|
self.add_recent_file_requested.emit(self.current_file_path_master)
|
|
169
171
|
|
|
170
172
|
def save_file(self):
|
|
@@ -277,4 +279,5 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
|
|
|
277
279
|
self.display_manager.thumbnail_list.clear()
|
|
278
280
|
self.display_manager.update_thumbnails()
|
|
279
281
|
self.update_title_requested.emit()
|
|
282
|
+
self.set_enabled_file_open_close_actions_requested.emit(False)
|
|
280
283
|
self.status_message_requested.emit("File closed")
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, E0611, E1101, R0904, R0912, R0914, R0902, E0202
|
|
2
2
|
from PySide6.QtCore import Qt, QPointF, QEvent, QRectF
|
|
3
|
-
from .. config.gui_constants import gui_constants
|
|
4
3
|
from .view_strategy import ViewStrategy, ImageGraphicsViewBase, ViewSignals
|
|
5
4
|
|
|
6
5
|
|
|
@@ -61,41 +60,9 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
|
|
|
61
60
|
self.mouse_release_event(event)
|
|
62
61
|
super().mouseReleaseEvent(event)
|
|
63
62
|
|
|
64
|
-
# pylint: enable=R0801
|
|
65
63
|
def wheelEvent(self, event):
|
|
66
|
-
|
|
67
|
-
return
|
|
68
|
-
if event.source() == Qt.MouseEventNotSynthesized: # Physical mouse
|
|
69
|
-
if self.control_pressed:
|
|
70
|
-
self.brush_size_change_requested.emit(1 if event.angleDelta().y() > 0 else -1)
|
|
71
|
-
else:
|
|
72
|
-
zoom_in_factor = gui_constants.ZOOM_IN_FACTOR
|
|
73
|
-
zoom_out_factor = gui_constants.ZOOM_OUT_FACTOR
|
|
74
|
-
current_scale = self.get_current_scale()
|
|
75
|
-
if event.angleDelta().y() > 0: # Zoom in
|
|
76
|
-
new_scale = current_scale * zoom_in_factor
|
|
77
|
-
if new_scale <= self.max_scale():
|
|
78
|
-
self.scale(zoom_in_factor, zoom_in_factor)
|
|
79
|
-
self.set_zoom_factor(new_scale)
|
|
80
|
-
else: # Zoom out
|
|
81
|
-
new_scale = current_scale * zoom_out_factor
|
|
82
|
-
if new_scale >= self.min_scale():
|
|
83
|
-
self.scale(zoom_out_factor, zoom_out_factor)
|
|
84
|
-
self.set_zoom_factor(new_scale)
|
|
85
|
-
self.update_brush_cursor()
|
|
86
|
-
else: # Touchpad event - fallback for systems without gesture recognition
|
|
87
|
-
if not self.control_pressed:
|
|
88
|
-
delta = event.pixelDelta() or event.angleDelta() / 8
|
|
89
|
-
if delta:
|
|
90
|
-
self.scroll_view(self, delta.x(), delta.y())
|
|
91
|
-
else: # Control + touchpad scroll for zoom
|
|
92
|
-
zoom_in = event.angleDelta().y() > 0
|
|
93
|
-
if zoom_in:
|
|
94
|
-
self.zoom_in()
|
|
95
|
-
else:
|
|
96
|
-
self.zoom_out()
|
|
64
|
+
self.handle_wheel_event(event)
|
|
97
65
|
event.accept()
|
|
98
|
-
# pylint: disable=R0801
|
|
99
66
|
|
|
100
67
|
def enterEvent(self, event):
|
|
101
68
|
self.activateWindow()
|
|
@@ -114,10 +81,13 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
|
|
|
114
81
|
def set_mouse_callbacks(self, callbacks):
|
|
115
82
|
self.mousePressEvent = callbacks
|
|
116
83
|
|
|
84
|
+
def get_view_with_mouse(self, event=None):
|
|
85
|
+
return self
|
|
86
|
+
|
|
87
|
+
# pylint: enable=C0103
|
|
117
88
|
def show(self):
|
|
118
89
|
self.show_master()
|
|
119
90
|
super().show()
|
|
120
|
-
# pylint: enable=C0103
|
|
121
91
|
|
|
122
92
|
def event(self, event):
|
|
123
93
|
if event.type() == QEvent.Gesture:
|
|
@@ -126,14 +96,7 @@ class OverlaidView(ViewStrategy, ImageGraphicsViewBase, ViewSignals):
|
|
|
126
96
|
|
|
127
97
|
def setup_scene_image(self, pixmap, pixmap_item):
|
|
128
98
|
self.setSceneRect(QRectF(pixmap.rect()))
|
|
129
|
-
|
|
130
|
-
self.set_max_min_scales(img_width, img_height)
|
|
131
|
-
view_rect = self.viewport().rect()
|
|
132
|
-
scale_x = view_rect.width() / img_width
|
|
133
|
-
scale_y = view_rect.height() / img_height
|
|
134
|
-
scale_factor = min(scale_x, scale_y)
|
|
135
|
-
scale_factor = max(self.min_scale(), min(scale_factor, self.max_scale()))
|
|
136
|
-
self.set_zoom_factor(scale_factor)
|
|
99
|
+
_img_width, _img_height, scale_factor = self.setup_view_image(self, pixmap)
|
|
137
100
|
self.resetTransform()
|
|
138
101
|
self.scale(scale_factor, scale_factor)
|
|
139
102
|
self.centerOn(pixmap_item)
|
|
@@ -3,6 +3,7 @@ import time
|
|
|
3
3
|
from PySide6.QtCore import Qt, Signal, QEvent, QRectF
|
|
4
4
|
from PySide6.QtGui import QCursor
|
|
5
5
|
from PySide6.QtWidgets import QWidget, QHBoxLayout, QVBoxLayout, QFrame
|
|
6
|
+
|
|
6
7
|
from .view_strategy import ViewStrategy, ImageGraphicsViewBase, ViewSignals
|
|
7
8
|
|
|
8
9
|
|
|
@@ -60,6 +61,7 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
60
61
|
self.master_view.setFocusPolicy(Qt.NoFocus)
|
|
61
62
|
self.current_brush_cursor = None
|
|
62
63
|
self.last_color_update_time_current = 0
|
|
64
|
+
self._updating_scrollbars = False
|
|
63
65
|
|
|
64
66
|
def setup_layout(self):
|
|
65
67
|
raise NotImplementedError("Subclasses must implement setup_layout")
|
|
@@ -77,14 +79,25 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
77
79
|
self.master_view.mouse_moved.connect(self.handle_master_mouse_move)
|
|
78
80
|
self.master_view.mouse_released.connect(self.handle_master_mouse_release)
|
|
79
81
|
self.master_view.gesture_event.connect(self.handle_gesture_event)
|
|
82
|
+
|
|
83
|
+
def sync_scrollbars(source, target):
|
|
84
|
+
if not self._updating_scrollbars:
|
|
85
|
+
self._updating_scrollbars = True
|
|
86
|
+
target.setValue(source.value())
|
|
87
|
+
self._updating_scrollbars = False
|
|
88
|
+
|
|
80
89
|
self.current_view.horizontalScrollBar().valueChanged.connect(
|
|
81
|
-
self.
|
|
90
|
+
lambda value: sync_scrollbars(self.current_view.horizontalScrollBar(),
|
|
91
|
+
self.master_view.horizontalScrollBar()))
|
|
82
92
|
self.current_view.verticalScrollBar().valueChanged.connect(
|
|
83
|
-
self.
|
|
93
|
+
lambda value: sync_scrollbars(self.current_view.verticalScrollBar(),
|
|
94
|
+
self.master_view.verticalScrollBar()))
|
|
84
95
|
self.master_view.horizontalScrollBar().valueChanged.connect(
|
|
85
|
-
self.
|
|
96
|
+
lambda value: sync_scrollbars(self.master_view.horizontalScrollBar(),
|
|
97
|
+
self.current_view.horizontalScrollBar()))
|
|
86
98
|
self.master_view.verticalScrollBar().valueChanged.connect(
|
|
87
|
-
self.
|
|
99
|
+
lambda value: sync_scrollbars(self.master_view.verticalScrollBar(),
|
|
100
|
+
self.current_view.verticalScrollBar()))
|
|
88
101
|
self.current_view.wheel_event.connect(self.handle_wheel_event)
|
|
89
102
|
self.master_view.wheel_event.connect(self.handle_wheel_event)
|
|
90
103
|
# pylint: disable=C0103, W0201
|
|
@@ -229,25 +242,20 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
229
242
|
self.current_view.mousePressEvent = callbacks
|
|
230
243
|
# pylint: enable=C0103
|
|
231
244
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
delta = event.pixelDelta() or event.angleDelta() / 8
|
|
247
|
-
if delta:
|
|
248
|
-
self.scroll_view(self.master_view, delta.x(), delta.y())
|
|
249
|
-
self.scroll_view(self.current_view, delta.x(), delta.y())
|
|
250
|
-
# pylint: disable=R0801
|
|
245
|
+
def get_view_with_mouse(self, event=None):
|
|
246
|
+
if event is None:
|
|
247
|
+
mouse_pos_global = QCursor.pos()
|
|
248
|
+
else:
|
|
249
|
+
mouse_pos_global = event.globalPosition().toPoint()
|
|
250
|
+
mouse_pos_current = self.current_view.mapFromGlobal(mouse_pos_global)
|
|
251
|
+
mouse_pos_master = self.master_view.mapFromGlobal(mouse_pos_global)
|
|
252
|
+
current_has_mouse = self.current_view.rect().contains(mouse_pos_current)
|
|
253
|
+
master_has_mouse = self.master_view.rect().contains(mouse_pos_master)
|
|
254
|
+
if master_has_mouse:
|
|
255
|
+
return self.master_view
|
|
256
|
+
if current_has_mouse:
|
|
257
|
+
return self.current_view
|
|
258
|
+
return None
|
|
251
259
|
|
|
252
260
|
def _apply_zoom_to_view(self, view, factor):
|
|
253
261
|
view.scale(factor, factor)
|
|
@@ -381,35 +389,32 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
381
389
|
def set_master_image(self, qimage):
|
|
382
390
|
self.status.set_master_image(qimage)
|
|
383
391
|
pixmap = self.status.pixmap_master
|
|
384
|
-
|
|
392
|
+
pixmap_rect = QRectF(pixmap.rect())
|
|
393
|
+
self.pixmap_item_master.setPos(0, 0)
|
|
394
|
+
self.master_view.setSceneRect(pixmap_rect)
|
|
395
|
+
self.master_scene.setSceneRect(pixmap_rect)
|
|
385
396
|
self.pixmap_item_master.setPixmap(pixmap)
|
|
386
|
-
|
|
387
|
-
self.set_max_min_scales(img_width, img_height)
|
|
388
|
-
view_rect = self.master_view.viewport().rect()
|
|
389
|
-
scale_x = view_rect.width() / img_width
|
|
390
|
-
scale_y = view_rect.height() / img_height
|
|
391
|
-
scale_factor = min(scale_x, scale_y)
|
|
392
|
-
scale_factor = max(self.min_scale(), min(scale_factor, self.max_scale()))
|
|
393
|
-
self.set_zoom_factor(scale_factor)
|
|
397
|
+
_img_width, _img_height, scale_factor = self.setup_view_image(self.master_view, pixmap)
|
|
394
398
|
self.master_view.resetTransform()
|
|
395
399
|
self.master_view.scale(scale_factor, scale_factor)
|
|
396
400
|
self.master_view.centerOn(self.pixmap_item_master)
|
|
397
|
-
center = self.master_scene.sceneRect().center()
|
|
398
|
-
self.brush_preview.setPos(max(0, min(center.x(), img_width)),
|
|
399
|
-
max(0, min(center.y(), img_height)))
|
|
400
|
-
self.master_scene.setSceneRect(QRectF(self.pixmap_item_master.boundingRect()))
|
|
401
401
|
self.center_image(self.master_view)
|
|
402
402
|
self.update_cursor_pen_width()
|
|
403
403
|
|
|
404
404
|
def set_current_image(self, qimage):
|
|
405
405
|
self.status.set_current_image(qimage)
|
|
406
406
|
pixmap = self.status.pixmap_current
|
|
407
|
-
|
|
407
|
+
pixmap_rect = QRectF(pixmap.rect())
|
|
408
|
+
self.pixmap_item_current.setPos(0, 0)
|
|
409
|
+
self.current_view.setSceneRect(pixmap_rect)
|
|
410
|
+
self.current_scene.setSceneRect(pixmap_rect)
|
|
408
411
|
self.pixmap_item_current.setPixmap(pixmap)
|
|
412
|
+
_img_width, _img_height, scale_factor = self.setup_view_image(self.current_view, pixmap)
|
|
409
413
|
self.current_view.resetTransform()
|
|
410
|
-
self.
|
|
411
|
-
self.
|
|
414
|
+
self.current_view.scale(scale_factor, scale_factor)
|
|
415
|
+
self.current_view.centerOn(self.pixmap_item_current)
|
|
412
416
|
self.center_image(self.current_view)
|
|
417
|
+
self.update_cursor_pen_width()
|
|
413
418
|
|
|
414
419
|
def arrange_images(self):
|
|
415
420
|
if self.status.empty():
|
|
@@ -439,8 +444,7 @@ class DoubleViewBase(ViewStrategy, QWidget, ViewSignals):
|
|
|
439
444
|
size = self.brush.size
|
|
440
445
|
radius = size / 2
|
|
441
446
|
self.current_brush_cursor.setRect(
|
|
442
|
-
scene_pos.x() - radius, scene_pos.y() - radius,
|
|
443
|
-
size, size)
|
|
447
|
+
scene_pos.x() - radius, scene_pos.y() - radius, size, size)
|
|
444
448
|
if self.brush_cursor.isVisible():
|
|
445
449
|
self.update_current_cursor_color()
|
|
446
450
|
self.current_brush_cursor.show()
|
|
@@ -26,7 +26,6 @@ class TransfromationManager(LayerCollectionHandler):
|
|
|
26
26
|
self.copy_master_layer()
|
|
27
27
|
self.editor.image_viewer.update_master_display()
|
|
28
28
|
self.editor.image_viewer.update_current_display()
|
|
29
|
-
self.editor.image_viewer.refresh_display()
|
|
30
29
|
self.editor.display_manager.update_thumbnails()
|
|
31
30
|
self.editor.mark_as_modified()
|
|
32
31
|
|
|
@@ -38,7 +38,7 @@ class UndoManager(QObject):
|
|
|
38
38
|
return
|
|
39
39
|
self.redo_stack = []
|
|
40
40
|
undo_state = {
|
|
41
|
-
'master': layer[self.y_start:self.y_end, self.x_start:self.x_end],
|
|
41
|
+
'master': layer[self.y_start:self.y_end, self.x_start:self.x_end].copy(),
|
|
42
42
|
'area': (self.x_start, self.y_start, self.x_end, self.y_end),
|
|
43
43
|
'description': description
|
|
44
44
|
}
|