shinestacker 1.3.1__py3-none-any.whl → 1.4.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 (34) hide show
  1. shinestacker/_version.py +1 -1
  2. shinestacker/algorithms/align.py +198 -18
  3. shinestacker/algorithms/align_parallel.py +17 -1
  4. shinestacker/algorithms/balance.py +23 -13
  5. shinestacker/algorithms/noise_detection.py +3 -1
  6. shinestacker/algorithms/utils.py +21 -10
  7. shinestacker/algorithms/vignetting.py +2 -0
  8. shinestacker/config/gui_constants.py +2 -2
  9. shinestacker/core/core_utils.py +10 -1
  10. shinestacker/gui/action_config.py +172 -7
  11. shinestacker/gui/action_config_dialog.py +246 -285
  12. shinestacker/gui/gui_run.py +2 -2
  13. shinestacker/gui/main_window.py +14 -5
  14. shinestacker/gui/menu_manager.py +26 -2
  15. shinestacker/gui/project_controller.py +4 -0
  16. shinestacker/gui/recent_file_manager.py +93 -0
  17. shinestacker/retouch/base_filter.py +5 -5
  18. shinestacker/retouch/brush_preview.py +3 -0
  19. shinestacker/retouch/brush_tool.py +11 -11
  20. shinestacker/retouch/display_manager.py +21 -37
  21. shinestacker/retouch/image_editor_ui.py +129 -71
  22. shinestacker/retouch/image_view_status.py +61 -0
  23. shinestacker/retouch/image_viewer.py +89 -431
  24. shinestacker/retouch/io_gui_handler.py +12 -2
  25. shinestacker/retouch/overlaid_view.py +212 -0
  26. shinestacker/retouch/shortcuts_help.py +13 -3
  27. shinestacker/retouch/sidebyside_view.py +479 -0
  28. shinestacker/retouch/view_strategy.py +466 -0
  29. {shinestacker-1.3.1.dist-info → shinestacker-1.4.0.dist-info}/METADATA +1 -1
  30. {shinestacker-1.3.1.dist-info → shinestacker-1.4.0.dist-info}/RECORD +34 -29
  31. {shinestacker-1.3.1.dist-info → shinestacker-1.4.0.dist-info}/WHEEL +0 -0
  32. {shinestacker-1.3.1.dist-info → shinestacker-1.4.0.dist-info}/entry_points.txt +0 -0
  33. {shinestacker-1.3.1.dist-info → shinestacker-1.4.0.dist-info}/licenses/LICENSE +0 -0
  34. {shinestacker-1.3.1.dist-info → shinestacker-1.4.0.dist-info}/top_level.txt +0 -0
@@ -3,9 +3,11 @@
3
3
  import traceback
4
4
  from abc import ABC, abstractmethod
5
5
  import os.path
6
+ from PySide6.QtCore import Qt, QTimer
6
7
  from PySide6.QtWidgets import (QPushButton, QHBoxLayout, QFileDialog, QLabel, QComboBox,
7
- QMessageBox, QSizePolicy, QLineEdit, QSpinBox,
8
- QDoubleSpinBox, QCheckBox, QTreeView, QAbstractItemView, QListView)
8
+ QMessageBox, QSizePolicy, QLineEdit, QSpinBox, QFrame,
9
+ QDoubleSpinBox, QCheckBox, QTreeView, QAbstractItemView, QListView,
10
+ QWidget, QScrollArea, QFormLayout, QDialog, QTabWidget)
9
11
  from .. config.constants import constants
10
12
  from .select_path_widget import (create_select_file_paths_widget, create_layout_widget_no_margins,
11
13
  create_layout_widget_and_connect)
@@ -27,12 +29,11 @@ FIELD_REF_IDX_MAX = 1000
27
29
 
28
30
 
29
31
  class ActionConfigurator(ABC):
30
- def __init__(self, expert, current_wd):
31
- self.expert = expert
32
+ def __init__(self, current_wd):
32
33
  self.current_wd = current_wd
33
34
 
34
35
  @abstractmethod
35
- def create_form(self, layout, action, tag="Action"):
36
+ def create_form(self, main_layout, action, tag="Action"):
36
37
  pass
37
38
 
38
39
  @abstractmethod
@@ -41,8 +42,8 @@ class ActionConfigurator(ABC):
41
42
 
42
43
 
43
44
  class FieldBuilder:
44
- def __init__(self, layout, action, current_wd):
45
- self.main_layout = layout
45
+ def __init__(self, main_layout, action, current_wd):
46
+ self.main_layout = main_layout
46
47
  self.action = action
47
48
  self.current_wd = current_wd
48
49
  self.fields = {}
@@ -412,3 +413,167 @@ class FieldBuilder:
412
413
  checkbox = QCheckBox()
413
414
  checkbox.setChecked(self.action.params.get(tag, default))
414
415
  return checkbox
416
+
417
+
418
+ class NoNameActionConfigurator(ActionConfigurator):
419
+ def __init__(self, current_wd):
420
+ super().__init__(current_wd)
421
+ self.builder = None
422
+
423
+ def get_builder(self):
424
+ return self.builder
425
+
426
+ def update_params(self, params):
427
+ return self.builder.update_params(params)
428
+
429
+ def add_bold_label(self, label):
430
+ label = QLabel(label)
431
+ label.setStyleSheet("font-weight: bold")
432
+ self.add_row(label)
433
+
434
+ def add_row(self, row):
435
+ self.builder.main_layout.addRow(row)
436
+
437
+ def add_field(self, tag, field_type, label, required=False, add_to_layout=None,
438
+ **kwargs):
439
+ return self.builder.add_field(tag, field_type, label, required, add_to_layout, **kwargs)
440
+
441
+ def labelled_widget(self, label, widget):
442
+ row = QWidget()
443
+ main_layout = QHBoxLayout()
444
+ main_layout.setContentsMargins(2, 2, 2, 2)
445
+ main_layout.setSpacing(8)
446
+ label_widget = QLabel(label)
447
+ label_widget.setFixedWidth(120)
448
+ label_widget.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)
449
+ main_layout.addWidget(label_widget)
450
+ main_layout.addWidget(widget)
451
+ main_layout.setStretch(0, 1)
452
+ main_layout.setStretch(1, 3)
453
+ row.setLayout(main_layout)
454
+ return row
455
+
456
+ def add_labelled_row(self, label, widget):
457
+ self.add_row(self.labelled_widget(label, widget))
458
+
459
+ def create_tab_widget(self, main_layout):
460
+ tab_widget = QTabWidget()
461
+ main_layout.addRow(tab_widget)
462
+ return tab_widget
463
+
464
+ def create_tab_layout(self):
465
+ tab_layout = QFormLayout()
466
+ tab_layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
467
+ tab_layout.setRowWrapPolicy(QFormLayout.DontWrapRows)
468
+ tab_layout.setFormAlignment(Qt.AlignLeft | Qt.AlignTop)
469
+ tab_layout.setLabelAlignment(Qt.AlignLeft)
470
+ return tab_layout
471
+
472
+ def add_tab(self, tab_widget, title):
473
+ tab = QWidget()
474
+ tab_layout = self.create_tab_layout()
475
+ tab.setLayout(tab_layout)
476
+ tab_widget.addTab(tab, title)
477
+ return tab_layout
478
+
479
+ def add_field_to_layout(self, main_layout, tag, field_type, label, required=False, **kwargs):
480
+ return self.add_field(tag, field_type, label, required, add_to_layout=main_layout, **kwargs)
481
+
482
+ def add_bold_label_to_layout(self, main_layout, label):
483
+ label_widget = QLabel(label)
484
+ label_widget.setStyleSheet("font-weight: bold")
485
+ main_layout.addRow(label_widget)
486
+ return label_widget
487
+
488
+
489
+ class DefaultActionConfigurator(NoNameActionConfigurator):
490
+ def __init__(self, expert_init, current_wd, expert_toggle=True):
491
+ super().__init__(current_wd)
492
+ self.expert_toggle = expert_toggle
493
+ self._expert_init = expert_init
494
+ self.expert_cb = None
495
+ self.expert_widgets = []
496
+
497
+ def create_form(self, main_layout, action, tag='Action'):
498
+ self.builder = FieldBuilder(main_layout, action, self.current_wd)
499
+ name_row = QHBoxLayout()
500
+ name_row.setContentsMargins(0, 0, 0, 0)
501
+ name_label = QLabel(f"{tag} name:")
502
+ name_field = self.builder.create_text_field('name', required=True)
503
+ name_row.addWidget(name_label)
504
+ name_row.addWidget(name_field, 1)
505
+ name_row.addStretch()
506
+ if self.expert_toggle:
507
+ expert_layout = QHBoxLayout()
508
+ expert_layout.setContentsMargins(0, 0, 0, 0)
509
+ expert_label = QLabel("Show expert options:")
510
+ self.expert_cb = QCheckBox()
511
+ self.expert_cb.setChecked(self._expert_init)
512
+ self.expert_cb.stateChanged.connect(self.toggle_expert_options)
513
+ expert_layout.addWidget(expert_label)
514
+ expert_layout.addWidget(self.expert_cb)
515
+ name_row.addLayout(expert_layout)
516
+ main_layout.addRow(name_row)
517
+ separator = QFrame()
518
+ separator.setFrameShape(QFrame.HLine)
519
+ separator.setFrameShadow(QFrame.Sunken)
520
+ separator.setLineWidth(1)
521
+ main_layout.addRow(separator)
522
+
523
+ def main_layout(self):
524
+ return self.builder.main_layout
525
+
526
+ def add_field(self, tag, field_type, label, required=False, add_to_layout=None,
527
+ expert=False, **kwargs):
528
+ current_layout = add_to_layout if add_to_layout is not None else self.main_layout()
529
+ widget = super().add_field(tag, field_type, label, required, add_to_layout, **kwargs)
530
+ if expert:
531
+ label_widget = None
532
+ if hasattr(current_layout, 'labelForField'):
533
+ label_widget = current_layout.labelForField(widget)
534
+ if label_widget is None:
535
+ for i in range(current_layout.rowCount()):
536
+ item = current_layout.itemAt(i, QFormLayout.LabelRole)
537
+ if item and item.widget() and \
538
+ current_layout.itemAt(i, QFormLayout.FieldRole).widget() == widget:
539
+ label_widget = item.widget()
540
+ break
541
+ self.expert_widgets.append((widget, label_widget))
542
+ visible = self.expert_cb.isChecked() if self.expert_cb else self._expert_init
543
+ widget.setVisible(visible)
544
+ if label_widget:
545
+ label_widget.setVisible(visible)
546
+ return widget
547
+
548
+ def toggle_expert_options(self, state):
549
+ visible = state == Qt.CheckState.Checked.value
550
+ for widget, label_widget in self.expert_widgets:
551
+ widget.setVisible(visible)
552
+ if label_widget:
553
+ label_widget.setVisible(visible)
554
+ self.main_layout().invalidate()
555
+ self.main_layout().activate()
556
+ parent = self.main_layout().parent()
557
+ while parent and not isinstance(parent, QDialog):
558
+ parent = parent.parent()
559
+ if parent and isinstance(parent, QDialog):
560
+ QTimer.singleShot(50, lambda: self._resize_dialog(parent))
561
+
562
+ def _resize_dialog(self, dialog):
563
+ scroll_area = dialog.findChild(QScrollArea)
564
+ if not scroll_area:
565
+ return
566
+ container = scroll_area.widget()
567
+ content_size = container.sizeHint()
568
+ margin = 40
569
+ button_height = 50
570
+ new_height = content_size.height() + button_height + margin
571
+ screen_geo = dialog.screen().availableGeometry()
572
+ max_height = int(screen_geo.height() * 0.8)
573
+ current_size = dialog.size()
574
+ new_height = min(new_height, max_height)
575
+ dialog.resize(current_size.width(), new_height)
576
+ if content_size.height() <= max_height - button_height - margin:
577
+ scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
578
+ else:
579
+ scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)