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.

Files changed (38) hide show
  1. shinestacker/_version.py +1 -1
  2. shinestacker/algorithms/multilayer.py +1 -1
  3. shinestacker/algorithms/stack.py +17 -9
  4. shinestacker/app/args_parser_opts.py +4 -0
  5. shinestacker/app/gui_utils.py +10 -2
  6. shinestacker/app/main.py +5 -2
  7. shinestacker/app/project.py +4 -2
  8. shinestacker/app/retouch.py +3 -1
  9. shinestacker/app/settings_dialog.py +171 -0
  10. shinestacker/config/app_config.py +30 -0
  11. shinestacker/config/constants.py +3 -0
  12. shinestacker/config/gui_constants.py +4 -2
  13. shinestacker/config/settings.py +110 -0
  14. shinestacker/gui/action_config.py +6 -5
  15. shinestacker/gui/action_config_dialog.py +17 -74
  16. shinestacker/gui/config_dialog.py +78 -0
  17. shinestacker/gui/main_window.py +16 -6
  18. shinestacker/gui/menu_manager.py +16 -10
  19. shinestacker/gui/new_project.py +2 -1
  20. shinestacker/gui/project_controller.py +11 -6
  21. shinestacker/gui/project_model.py +16 -1
  22. shinestacker/gui/recent_file_manager.py +3 -21
  23. shinestacker/retouch/display_manager.py +47 -5
  24. shinestacker/retouch/image_editor_ui.py +59 -15
  25. shinestacker/retouch/image_view_status.py +4 -1
  26. shinestacker/retouch/io_gui_handler.py +3 -0
  27. shinestacker/retouch/overlaid_view.py +6 -43
  28. shinestacker/retouch/sidebyside_view.py +45 -41
  29. shinestacker/retouch/transformation_manager.py +0 -1
  30. shinestacker/retouch/undo_manager.py +1 -1
  31. shinestacker/retouch/view_strategy.py +125 -61
  32. shinestacker/retouch/white_balance_filter.py +5 -6
  33. {shinestacker-1.5.3.dist-info → shinestacker-1.6.0.dist-info}/METADATA +1 -1
  34. {shinestacker-1.5.3.dist-info → shinestacker-1.6.0.dist-info}/RECORD +38 -34
  35. {shinestacker-1.5.3.dist-info → shinestacker-1.6.0.dist-info}/WHEEL +0 -0
  36. {shinestacker-1.5.3.dist-info → shinestacker-1.6.0.dist-info}/entry_points.txt +0 -0
  37. {shinestacker-1.5.3.dist-info → shinestacker-1.6.0.dist-info}/licenses/LICENSE +0 -0
  38. {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
- transf_menu.addAction(rotate_90_cw_action)
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
- transf_menu.addAction(rotate_90_ccw_action)
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
- copy_action = QAction("Copy Current Layer to Master", self)
293
- copy_action.setShortcut("Ctrl+M")
294
- copy_action.triggered.connect(self.copy_layer_to_master)
295
- edit_menu.addAction(copy_action)
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('overlaid')
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.triggered.connect(lambda: self.sort_layers('asc'))
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.triggered.connect(lambda: self.sort_layers('desc'))
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.view_master_action.setEnabled(enable_shortcuts)
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 sort_layers(self, order):
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.current_layer())
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
- if self.empty() or self.gesture_active:
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
- img_width, img_height = pixmap.width(), pixmap.height()
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.master_view.horizontalScrollBar().setValue)
90
+ lambda value: sync_scrollbars(self.current_view.horizontalScrollBar(),
91
+ self.master_view.horizontalScrollBar()))
82
92
  self.current_view.verticalScrollBar().valueChanged.connect(
83
- self.master_view.verticalScrollBar().setValue)
93
+ lambda value: sync_scrollbars(self.current_view.verticalScrollBar(),
94
+ self.master_view.verticalScrollBar()))
84
95
  self.master_view.horizontalScrollBar().valueChanged.connect(
85
- self.current_view.horizontalScrollBar().setValue)
96
+ lambda value: sync_scrollbars(self.master_view.horizontalScrollBar(),
97
+ self.current_view.horizontalScrollBar()))
86
98
  self.master_view.verticalScrollBar().valueChanged.connect(
87
- self.current_view.verticalScrollBar().setValue)
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
- # pylint: enable=R0801
233
- def handle_wheel_event(self, event):
234
- if self.empty() or self.gesture_active:
235
- return
236
- if event.source() == Qt.MouseEventNotSynthesized: # Physical mouse
237
- if self.control_pressed:
238
- self.brush_size_change_requested.emit(1 if event.angleDelta().y() > 0 else -1)
239
- else:
240
- if event.angleDelta().y() > 0: # Zoom in
241
- self.zoom_in()
242
- else: # Zoom out
243
- self.zoom_out()
244
- else: # Touchpad event - handle scrolling
245
- if not self.control_pressed:
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
- self.master_view.setSceneRect(QRectF(pixmap.rect()))
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
- img_width, img_height = pixmap.width(), pixmap.height()
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
- self.current_scene.setSceneRect(QRectF(pixmap.rect()))
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.master_view.scale(self.zoom_factor(), self.zoom_factor())
411
- self.current_scene.setSceneRect(QRectF(self.pixmap_item_current.boundingRect()))
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
  }