shinestacker 1.3.1__py3-none-any.whl → 1.5.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/align.py +198 -18
- shinestacker/algorithms/align_parallel.py +17 -1
- shinestacker/algorithms/balance.py +23 -13
- shinestacker/algorithms/noise_detection.py +3 -1
- shinestacker/algorithms/utils.py +21 -10
- shinestacker/algorithms/vignetting.py +2 -0
- shinestacker/app/main.py +1 -1
- shinestacker/config/gui_constants.py +7 -2
- shinestacker/core/core_utils.py +10 -1
- shinestacker/gui/action_config.py +172 -7
- shinestacker/gui/action_config_dialog.py +246 -285
- shinestacker/gui/gui_run.py +2 -2
- shinestacker/gui/main_window.py +14 -5
- shinestacker/gui/menu_manager.py +26 -2
- shinestacker/gui/project_controller.py +4 -0
- shinestacker/gui/recent_file_manager.py +93 -0
- shinestacker/retouch/base_filter.py +13 -15
- shinestacker/retouch/brush_preview.py +3 -1
- shinestacker/retouch/brush_tool.py +11 -11
- shinestacker/retouch/display_manager.py +43 -59
- shinestacker/retouch/image_editor_ui.py +161 -82
- shinestacker/retouch/image_view_status.py +65 -0
- shinestacker/retouch/image_viewer.py +95 -431
- shinestacker/retouch/io_gui_handler.py +12 -2
- shinestacker/retouch/layer_collection.py +3 -0
- shinestacker/retouch/overlaid_view.py +215 -0
- shinestacker/retouch/shortcuts_help.py +13 -3
- shinestacker/retouch/sidebyside_view.py +477 -0
- shinestacker/retouch/transformation_manager.py +43 -0
- shinestacker/retouch/undo_manager.py +22 -3
- shinestacker/retouch/view_strategy.py +557 -0
- {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/METADATA +7 -7
- {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/RECORD +38 -32
- {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/WHEEL +0 -0
- {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.3.1.dist-info → shinestacker-1.5.0.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.3.1.dist-info → shinestacker-1.5.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,
|
|
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,
|
|
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,
|
|
45
|
-
self.main_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)
|