setiastrosuitepro 1.6.2__py3-none-any.whl → 1.6.12__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 setiastrosuitepro might be problematic. Click here for more details.
- setiastro/images/abeicon.svg +16 -0
- setiastro/images/acv_icon.png +0 -0
- setiastro/images/colorwheel.svg +97 -0
- setiastro/images/cosmic.svg +40 -0
- setiastro/images/cosmicsat.svg +24 -0
- setiastro/images/first_quarter.png +0 -0
- setiastro/images/full_moon.png +0 -0
- setiastro/images/graxpert.svg +19 -0
- setiastro/images/last_quarter.png +0 -0
- setiastro/images/linearfit.svg +32 -0
- setiastro/images/new_moon.png +0 -0
- setiastro/images/pixelmath.svg +42 -0
- setiastro/images/rotatearbitrary.png +0 -0
- setiastro/images/waning_crescent_1.png +0 -0
- setiastro/images/waning_crescent_2.png +0 -0
- setiastro/images/waning_crescent_3.png +0 -0
- setiastro/images/waning_crescent_4.png +0 -0
- setiastro/images/waning_crescent_5.png +0 -0
- setiastro/images/waning_gibbous_1.png +0 -0
- setiastro/images/waning_gibbous_2.png +0 -0
- setiastro/images/waning_gibbous_3.png +0 -0
- setiastro/images/waning_gibbous_4.png +0 -0
- setiastro/images/waning_gibbous_5.png +0 -0
- setiastro/images/waxing_crescent_1.png +0 -0
- setiastro/images/waxing_crescent_2.png +0 -0
- setiastro/images/waxing_crescent_3.png +0 -0
- setiastro/images/waxing_crescent_4.png +0 -0
- setiastro/images/waxing_crescent_5.png +0 -0
- setiastro/images/waxing_gibbous_1.png +0 -0
- setiastro/images/waxing_gibbous_2.png +0 -0
- setiastro/images/waxing_gibbous_3.png +0 -0
- setiastro/images/waxing_gibbous_4.png +0 -0
- setiastro/images/waxing_gibbous_5.png +0 -0
- setiastro/qml/ResourceMonitor.qml +84 -82
- setiastro/saspro/__main__.py +20 -1
- setiastro/saspro/_generated/build_info.py +2 -2
- setiastro/saspro/abe.py +37 -4
- setiastro/saspro/aberration_ai.py +237 -21
- setiastro/saspro/acv_exporter.py +379 -0
- setiastro/saspro/add_stars.py +33 -6
- setiastro/saspro/backgroundneutral.py +114 -37
- setiastro/saspro/blemish_blaster.py +4 -1
- setiastro/saspro/blink_comparator_pro.py +548 -275
- setiastro/saspro/clahe.py +4 -1
- setiastro/saspro/continuum_subtract.py +4 -1
- setiastro/saspro/convo.py +13 -7
- setiastro/saspro/cosmicclarity.py +129 -18
- setiastro/saspro/crop_dialog_pro.py +134 -8
- setiastro/saspro/curve_editor_pro.py +109 -42
- setiastro/saspro/doc_manager.py +246 -16
- setiastro/saspro/exoplanet_detector.py +120 -28
- setiastro/saspro/frequency_separation.py +1158 -204
- setiastro/saspro/function_bundle.py +16 -16
- setiastro/saspro/ghs_dialog_pro.py +81 -16
- setiastro/saspro/graxpert.py +1 -0
- setiastro/saspro/gui/main_window.py +519 -289
- setiastro/saspro/gui/mixins/dock_mixin.py +276 -42
- setiastro/saspro/gui/mixins/geometry_mixin.py +105 -5
- setiastro/saspro/gui/mixins/menu_mixin.py +28 -1
- setiastro/saspro/gui/mixins/theme_mixin.py +160 -14
- setiastro/saspro/gui/mixins/toolbar_mixin.py +416 -27
- setiastro/saspro/gui/mixins/update_mixin.py +138 -36
- setiastro/saspro/gui/mixins/view_mixin.py +42 -0
- setiastro/saspro/halobgon.py +4 -0
- setiastro/saspro/histogram.py +5 -1
- setiastro/saspro/image_combine.py +4 -0
- setiastro/saspro/image_peeker_pro.py +4 -0
- setiastro/saspro/imageops/starbasedwhitebalance.py +23 -52
- setiastro/saspro/imageops/stretch.py +582 -62
- setiastro/saspro/isophote.py +4 -0
- setiastro/saspro/layers.py +13 -9
- setiastro/saspro/layers_dock.py +183 -3
- setiastro/saspro/legacy/image_manager.py +154 -20
- setiastro/saspro/legacy/numba_utils.py +67 -47
- setiastro/saspro/legacy/xisf.py +240 -98
- setiastro/saspro/live_stacking.py +180 -79
- setiastro/saspro/luminancerecombine.py +228 -27
- setiastro/saspro/mask_creation.py +174 -15
- setiastro/saspro/mfdeconv.py +113 -35
- setiastro/saspro/mfdeconvcudnn.py +119 -70
- setiastro/saspro/mfdeconvsport.py +112 -35
- setiastro/saspro/morphology.py +4 -0
- setiastro/saspro/multiscale_decomp.py +748 -255
- setiastro/saspro/numba_utils.py +72 -57
- setiastro/saspro/ops/commands.py +18 -18
- setiastro/saspro/ops/script_editor.py +10 -2
- setiastro/saspro/ops/scripts.py +122 -0
- setiastro/saspro/perfect_palette_picker.py +37 -3
- setiastro/saspro/plate_solver.py +84 -49
- setiastro/saspro/psf_viewer.py +119 -37
- setiastro/saspro/remove_stars_preset.py +55 -13
- setiastro/saspro/resources.py +97 -11
- setiastro/saspro/rgbalign.py +4 -0
- setiastro/saspro/selective_color.py +83 -21
- setiastro/saspro/sfcc.py +364 -152
- setiastro/saspro/shortcuts.py +253 -49
- setiastro/saspro/signature_insert.py +692 -33
- setiastro/saspro/stacking_suite.py +1610 -574
- setiastro/saspro/star_alignment.py +522 -453
- setiastro/saspro/star_spikes.py +4 -0
- setiastro/saspro/star_stretch.py +38 -3
- setiastro/saspro/stat_stretch.py +743 -128
- setiastro/saspro/status_log_dock.py +1 -1
- setiastro/saspro/subwindow.py +786 -360
- setiastro/saspro/supernovaasteroidhunter.py +1 -1
- setiastro/saspro/swap_manager.py +77 -42
- setiastro/saspro/translations/all_source_strings.json +1588 -516
- setiastro/saspro/translations/ar_translations.py +915 -684
- setiastro/saspro/translations/de_translations.py +442 -463
- setiastro/saspro/translations/es_translations.py +277 -47
- setiastro/saspro/translations/fr_translations.py +279 -47
- setiastro/saspro/translations/hi_translations.py +253 -21
- setiastro/saspro/translations/integrate_translations.py +3 -2
- setiastro/saspro/translations/it_translations.py +1211 -161
- setiastro/saspro/translations/ja_translations.py +3340 -3107
- setiastro/saspro/translations/pt_translations.py +3315 -3337
- setiastro/saspro/translations/ru_translations.py +351 -117
- setiastro/saspro/translations/saspro_ar.qm +0 -0
- setiastro/saspro/translations/saspro_ar.ts +15902 -138
- setiastro/saspro/translations/saspro_de.qm +0 -0
- setiastro/saspro/translations/saspro_de.ts +14428 -133
- setiastro/saspro/translations/saspro_es.qm +0 -0
- setiastro/saspro/translations/saspro_es.ts +11503 -7821
- setiastro/saspro/translations/saspro_fr.qm +0 -0
- setiastro/saspro/translations/saspro_fr.ts +11168 -7812
- setiastro/saspro/translations/saspro_hi.qm +0 -0
- setiastro/saspro/translations/saspro_hi.ts +14733 -135
- setiastro/saspro/translations/saspro_it.qm +0 -0
- setiastro/saspro/translations/saspro_it.ts +14347 -7821
- setiastro/saspro/translations/saspro_ja.qm +0 -0
- setiastro/saspro/translations/saspro_ja.ts +14860 -137
- setiastro/saspro/translations/saspro_pt.qm +0 -0
- setiastro/saspro/translations/saspro_pt.ts +14904 -137
- setiastro/saspro/translations/saspro_ru.qm +0 -0
- setiastro/saspro/translations/saspro_ru.ts +11766 -168
- setiastro/saspro/translations/saspro_sw.qm +0 -0
- setiastro/saspro/translations/saspro_sw.ts +15115 -135
- setiastro/saspro/translations/saspro_uk.qm +0 -0
- setiastro/saspro/translations/saspro_uk.ts +11206 -6729
- setiastro/saspro/translations/saspro_zh.qm +0 -0
- setiastro/saspro/translations/saspro_zh.ts +10581 -7812
- setiastro/saspro/translations/sw_translations.py +282 -56
- setiastro/saspro/translations/uk_translations.py +264 -35
- setiastro/saspro/translations/zh_translations.py +282 -47
- setiastro/saspro/view_bundle.py +17 -17
- setiastro/saspro/wavescale_hdr.py +4 -1
- setiastro/saspro/wavescalede.py +4 -1
- setiastro/saspro/whitebalance.py +84 -12
- setiastro/saspro/widgets/common_utilities.py +28 -21
- setiastro/saspro/widgets/minigame/game.js +11 -6
- setiastro/saspro/widgets/resource_monitor.py +133 -57
- setiastro/saspro/widgets/spinboxes.py +28 -13
- setiastro/saspro/wimi.py +92 -721
- setiastro/saspro/wims.py +46 -36
- setiastro/saspro/window_shelf.py +2 -2
- setiastro/saspro/xisf.py +101 -11
- {setiastrosuitepro-1.6.2.dist-info → setiastrosuitepro-1.6.12.dist-info}/METADATA +8 -7
- {setiastrosuitepro-1.6.2.dist-info → setiastrosuitepro-1.6.12.dist-info}/RECORD +162 -128
- {setiastrosuitepro-1.6.2.dist-info → setiastrosuitepro-1.6.12.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.6.2.dist-info → setiastrosuitepro-1.6.12.dist-info}/entry_points.txt +0 -0
- {setiastrosuitepro-1.6.2.dist-info → setiastrosuitepro-1.6.12.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.6.2.dist-info → setiastrosuitepro-1.6.12.dist-info}/licenses/license.txt +0 -0
setiastro/saspro/shortcuts.py
CHANGED
|
@@ -209,6 +209,17 @@ class DraggableToolBar(QToolBar):
|
|
|
209
209
|
s.setValue(self._settings_key, ids)
|
|
210
210
|
|
|
211
211
|
|
|
212
|
+
def _is_locked(self) -> bool:
|
|
213
|
+
"""Check if toolbar icon movement is locked globally."""
|
|
214
|
+
s = self._settings()
|
|
215
|
+
# Default to False (unlocked)
|
|
216
|
+
return s.value("UI/ToolbarLocked", False, type=bool)
|
|
217
|
+
|
|
218
|
+
def _set_locked(self, locked: bool):
|
|
219
|
+
"""Set the global lock state."""
|
|
220
|
+
s = self._settings()
|
|
221
|
+
s.setValue("UI/ToolbarLocked", locked)
|
|
222
|
+
|
|
212
223
|
# install/remove our event filter when actions are added/removed
|
|
213
224
|
def actionEvent(self, e):
|
|
214
225
|
super().actionEvent(e)
|
|
@@ -259,6 +270,7 @@ class DraggableToolBar(QToolBar):
|
|
|
259
270
|
if delta.manhattanLength() > QApplication.startDragDistance():
|
|
260
271
|
mods_now = QApplication.keyboardModifiers()
|
|
261
272
|
had_mod = self._press_had_mod.get(obj, False)
|
|
273
|
+
|
|
262
274
|
# CASE 1: had/has modifiers → create desktop shortcut / function-bundle drag (existing behavior)
|
|
263
275
|
if had_mod or self._mods_ok(mods_now):
|
|
264
276
|
act = self._find_action_for_button(obj)
|
|
@@ -270,6 +282,20 @@ class DraggableToolBar(QToolBar):
|
|
|
270
282
|
return True # consume
|
|
271
283
|
else:
|
|
272
284
|
# CASE 2: plain drag (no modifiers) → reorder within this toolbar
|
|
285
|
+
# CHECK LOCK STATE FIRST
|
|
286
|
+
if self._is_locked():
|
|
287
|
+
# Lock is active: DO NOT start drag.
|
|
288
|
+
# Should we consume the event?
|
|
289
|
+
# If we consume it, the button won't feel "pressed" anymore if the user keeps dragging?
|
|
290
|
+
# Actually, if we just return False, standard QToolButton behavior applies (it might think it's being pressed).
|
|
291
|
+
# However, we want to prevent the *reorder* logic.
|
|
292
|
+
# So simply doing nothing here is enough to prevent the reorder drag from starting.
|
|
293
|
+
|
|
294
|
+
# But we might want to let the user know, or just silently fail distinctively?
|
|
295
|
+
# Silently failing distinctively is what the user asked for (prevent involuntary move).
|
|
296
|
+
# If we return False, the button keeps tracking the mouse, which is fine (it won't click unless released inside).
|
|
297
|
+
return False
|
|
298
|
+
|
|
273
299
|
self._start_reorder_drag_for_button(obj)
|
|
274
300
|
self._suppress_release.add(obj)
|
|
275
301
|
self._press_pos.pop(obj, None)
|
|
@@ -461,50 +487,73 @@ class DraggableToolBar(QToolBar):
|
|
|
461
487
|
def _show_toolbutton_context_menu(self, btn: QToolButton, act: QAction, gpos: QPoint):
|
|
462
488
|
m = QMenu(btn)
|
|
463
489
|
|
|
464
|
-
m.addAction("Create Desktop Shortcut", lambda: self._add_shortcut_for_action(act))
|
|
490
|
+
m.addAction(self.tr("Create Desktop Shortcut"), lambda: self._add_shortcut_for_action(act))
|
|
465
491
|
|
|
466
492
|
# Hide this icon
|
|
467
493
|
cid = self._action_id(act)
|
|
468
494
|
if cid:
|
|
469
495
|
m.addSeparator()
|
|
470
|
-
m.addAction("Hide this icon", lambda: self.
|
|
496
|
+
m.addAction(self.tr("Hide this icon"), lambda: self.window()._hide_action_to_hidden_toolbar(act))
|
|
497
|
+
|
|
471
498
|
|
|
472
499
|
# (Optional) teach users about Alt+Drag:
|
|
473
500
|
m.addSeparator()
|
|
474
|
-
tip = m.addAction("Tip: Alt+Drag to create")
|
|
501
|
+
tip = m.addAction(self.tr("Tip: Alt+Drag to create"))
|
|
475
502
|
tip.setEnabled(False)
|
|
476
503
|
|
|
477
504
|
m.exec(gpos)
|
|
478
505
|
|
|
506
|
+
|
|
479
507
|
def contextMenuEvent(self, ev):
|
|
480
508
|
# Right-click on empty toolbar area
|
|
481
509
|
m = QMenu(self)
|
|
482
510
|
|
|
511
|
+
# 1. Lock/Unlock Action
|
|
512
|
+
is_locked = self._is_locked()
|
|
513
|
+
act_lock = m.addAction(self.tr("Lock Toolbar Icons"))
|
|
514
|
+
act_lock.setCheckable(True)
|
|
515
|
+
act_lock.setChecked(is_locked)
|
|
516
|
+
|
|
517
|
+
def _toggle_lock(checked):
|
|
518
|
+
self._set_locked(checked)
|
|
519
|
+
|
|
520
|
+
act_lock.triggered.connect(_toggle_lock)
|
|
521
|
+
|
|
522
|
+
m.addSeparator()
|
|
523
|
+
|
|
483
524
|
# Submenu listing hidden actions for this toolbar
|
|
484
|
-
|
|
485
|
-
sub = m.addMenu("Show hidden…")
|
|
525
|
+
sub = m.addMenu(self.tr("Show hidden…"))
|
|
486
526
|
|
|
487
|
-
|
|
527
|
+
mw = self.window()
|
|
528
|
+
tb_hidden = getattr(mw, "_hidden_toolbar", lambda: None)()
|
|
488
529
|
any_hidden = False
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
530
|
+
if tb_hidden:
|
|
531
|
+
for act in tb_hidden.actions():
|
|
532
|
+
# Skip separators
|
|
533
|
+
if act.isSeparator():
|
|
534
|
+
continue
|
|
492
535
|
any_hidden = True
|
|
493
|
-
sub.addAction(act.text() or
|
|
536
|
+
sub.addAction(act.text() or (act.property("command_id") or act.objectName() or "item"),
|
|
537
|
+
lambda a=act: mw._unhide_action_from_hidden_toolbar(a))
|
|
494
538
|
|
|
495
539
|
if not any_hidden:
|
|
496
540
|
sub.setEnabled(False)
|
|
497
541
|
|
|
498
542
|
m.addSeparator()
|
|
499
|
-
m.addAction("Reset hidden icons", self._reset_hidden_icons)
|
|
543
|
+
m.addAction(self.tr("Reset hidden icons"), self._reset_hidden_icons)
|
|
500
544
|
|
|
501
545
|
m.exec(ev.globalPos())
|
|
502
546
|
|
|
503
547
|
def _reset_hidden_icons(self):
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
548
|
+
mw = self.window()
|
|
549
|
+
tb_hidden = getattr(mw, "_hidden_toolbar", lambda: None)()
|
|
550
|
+
if not tb_hidden:
|
|
551
|
+
return
|
|
552
|
+
# copy list because we'll mutate
|
|
553
|
+
acts = [a for a in tb_hidden.actions() if not a.isSeparator()]
|
|
554
|
+
for a in acts:
|
|
555
|
+
mw._unhide_action_from_hidden_toolbar(a)
|
|
556
|
+
|
|
508
557
|
|
|
509
558
|
|
|
510
559
|
_PRESET_UI_IDS = {
|
|
@@ -515,7 +564,7 @@ _PRESET_UI_IDS = {
|
|
|
515
564
|
"remove_green","star_align","background_neutral","white_balance","clahe",
|
|
516
565
|
"morphology","pixel_math","rgb_align","signature_insert","signature_adder",
|
|
517
566
|
"signature","halo_b_gon","geom_rescale","rescale","debayer","image_combine",
|
|
518
|
-
"star_spikes","diffraction_spikes", "multiscale_decomp",
|
|
567
|
+
"star_spikes","diffraction_spikes", "multiscale_decomp","geom_rotate_any",
|
|
519
568
|
}
|
|
520
569
|
|
|
521
570
|
def _has_preset_editor_for_command(command_id: str) -> bool:
|
|
@@ -548,6 +597,12 @@ def _open_preset_editor_for_command(parent, command_id: str, initial: dict | Non
|
|
|
548
597
|
})
|
|
549
598
|
return dlg.result_dict() if dlg.exec() == QDialog.DialogCode.Accepted else None
|
|
550
599
|
|
|
600
|
+
if command_id == "geom_rotate_any":
|
|
601
|
+
dlg = _GeomRotateAnyPresetDialog(parent, initial=cur or {
|
|
602
|
+
"angle_deg": 0.0,
|
|
603
|
+
})
|
|
604
|
+
return dlg.result_dict() if dlg.exec() == QDialog.DialogCode.Accepted else None
|
|
605
|
+
|
|
551
606
|
if command_id == "curves":
|
|
552
607
|
dlg = _CurvesPresetDialog(parent, initial=cur or {"shape":"linear","amount":0.5,"mode":"K (Brightness)"})
|
|
553
608
|
return dlg.result_dict() if dlg.exec() == QDialog.DialogCode.Accepted else None
|
|
@@ -741,18 +796,18 @@ class ShortcutButton(QToolButton):
|
|
|
741
796
|
# --- Context menu (run / preset / delete) ----------------------------
|
|
742
797
|
def _context_menu(self, pos):
|
|
743
798
|
m = QMenu(self)
|
|
744
|
-
m.addAction("Run", lambda: self._mgr.trigger(self.command_id))
|
|
799
|
+
m.addAction(self.tr("Run"), lambda: self._mgr.trigger(self.command_id))
|
|
745
800
|
m.addSeparator()
|
|
746
|
-
m.addAction("Edit Preset…", self._edit_preset_ui)
|
|
747
|
-
m.addAction("Clear Preset", lambda: self._save_preset(None))
|
|
748
|
-
m.addAction("Rename…", self._rename) # ← NEW
|
|
801
|
+
m.addAction(self.tr("Edit Preset…"), self._edit_preset_ui)
|
|
802
|
+
m.addAction(self.tr("Clear Preset"), lambda: self._save_preset(None))
|
|
803
|
+
m.addAction(self.tr("Rename…"), self._rename) # ← NEW
|
|
749
804
|
m.addSeparator()
|
|
750
|
-
m.addAction("Delete", self._delete)
|
|
805
|
+
m.addAction(self.tr("Delete"), self._delete)
|
|
751
806
|
m.exec(self.mapToGlobal(pos))
|
|
752
807
|
|
|
753
808
|
def _rename(self):
|
|
754
809
|
current = self.text()
|
|
755
|
-
new_name, ok = QInputDialog.getText(self, "Rename Shortcut", "Name:", text=current)
|
|
810
|
+
new_name, ok = QInputDialog.getText(self, self.tr("Rename Shortcut"), self.tr("Name:"), text=current)
|
|
756
811
|
if not ok or not new_name.strip():
|
|
757
812
|
return
|
|
758
813
|
self.setText(new_name.strip())
|
|
@@ -764,21 +819,21 @@ class ShortcutButton(QToolButton):
|
|
|
764
819
|
result = _open_preset_editor_for_command(self, cid, cur)
|
|
765
820
|
if result is not None:
|
|
766
821
|
self._save_preset(result)
|
|
767
|
-
QMessageBox.information(self, "Preset saved", "Preset stored on shortcut.")
|
|
822
|
+
QMessageBox.information(self, self.tr("Preset saved"), self.tr("Preset stored on shortcut."))
|
|
768
823
|
return
|
|
769
824
|
|
|
770
825
|
# Fallback: JSON editor
|
|
771
826
|
raw = json.dumps(cur or {}, indent=2)
|
|
772
|
-
text, ok = QInputDialog.getMultiLineText(self, "Edit Preset (JSON)", "Preset:", raw)
|
|
827
|
+
text, ok = QInputDialog.getMultiLineText(self, self.tr("Edit Preset (JSON)"), self.tr("Preset:"), raw)
|
|
773
828
|
if ok:
|
|
774
829
|
try:
|
|
775
830
|
preset = json.loads(text or "{}")
|
|
776
831
|
if not isinstance(preset, dict):
|
|
777
|
-
raise ValueError("Preset must be a JSON object")
|
|
832
|
+
raise ValueError(self.tr("Preset must be a JSON object"))
|
|
778
833
|
self._save_preset(preset)
|
|
779
|
-
QMessageBox.information(self, "Preset saved", "Preset stored on shortcut.")
|
|
834
|
+
QMessageBox.information(self, self.tr("Preset saved"), self.tr("Preset stored on shortcut."))
|
|
780
835
|
except Exception as e:
|
|
781
|
-
QMessageBox.warning(self, "Invalid JSON", str(e))
|
|
836
|
+
QMessageBox.warning(self, self.tr("Invalid JSON"), str(e))
|
|
782
837
|
|
|
783
838
|
|
|
784
839
|
def _start_command_drag(self):
|
|
@@ -978,17 +1033,14 @@ class ShortcutCanvas(QWidget):
|
|
|
978
1033
|
return False
|
|
979
1034
|
sw = self._top_subwindow_at(e.position().toPoint())
|
|
980
1035
|
if sw is None:
|
|
981
|
-
|
|
982
|
-
QApplication.processEvents()
|
|
1036
|
+
|
|
983
1037
|
return False
|
|
984
1038
|
try:
|
|
985
1039
|
raw = bytes(md.data(MIME_CMD))
|
|
986
1040
|
payload = _unpack_cmd_payload(raw) # your existing helper
|
|
987
|
-
|
|
988
|
-
QApplication.processEvents()
|
|
1041
|
+
|
|
989
1042
|
except Exception as ex:
|
|
990
|
-
|
|
991
|
-
QApplication.processEvents()
|
|
1043
|
+
|
|
992
1044
|
return False
|
|
993
1045
|
self._mgr.apply_command_to_subwindow(sw, payload)
|
|
994
1046
|
e.acceptProposedAction()
|
|
@@ -1066,11 +1118,11 @@ class ShortcutCanvas(QWidget):
|
|
|
1066
1118
|
def contextMenuEvent(self, e):
|
|
1067
1119
|
menu = QMenu(self)
|
|
1068
1120
|
has_sel = bool(self._mgr.selected)
|
|
1069
|
-
a_del = menu.addAction("Delete Selected", self._mgr.delete_selected); a_del.setEnabled(has_sel)
|
|
1070
|
-
a_clr = menu.addAction("Clear Selection", self._mgr.clear_selection); a_clr.setEnabled(has_sel)
|
|
1121
|
+
a_del = menu.addAction(self.tr("Delete Selected"), self._mgr.delete_selected); a_del.setEnabled(has_sel)
|
|
1122
|
+
a_clr = menu.addAction(self.tr("Clear Selection"), self._mgr.clear_selection); a_clr.setEnabled(has_sel)
|
|
1071
1123
|
menu.addSeparator()
|
|
1072
|
-
a_vb = menu.addAction("View Bundles…", lambda: _open_view_bundles_from_canvas(self))
|
|
1073
|
-
a_fb = menu.addAction("Function Bundles…", lambda: _open_function_bundles_from_canvas(self))
|
|
1124
|
+
a_vb = menu.addAction(self.tr("View Bundles…"), lambda: _open_view_bundles_from_canvas(self))
|
|
1125
|
+
a_fb = menu.addAction(self.tr("Function Bundles…"), lambda: _open_function_bundles_from_canvas(self))
|
|
1074
1126
|
menu.exec(e.globalPos())
|
|
1075
1127
|
|
|
1076
1128
|
|
|
@@ -1490,6 +1542,7 @@ class ShortcutManager:
|
|
|
1490
1542
|
|
|
1491
1543
|
# ---- persistence (QSettings JSON blob) ----
|
|
1492
1544
|
def save_shortcuts(self):
|
|
1545
|
+
|
|
1493
1546
|
data = []
|
|
1494
1547
|
for sid, w in list(self.widgets.items()):
|
|
1495
1548
|
if _is_dead(w):
|
|
@@ -1790,6 +1843,7 @@ class ShortcutManager:
|
|
|
1790
1843
|
|
|
1791
1844
|
|
|
1792
1845
|
def clear(self):
|
|
1846
|
+
|
|
1793
1847
|
for sid, w in list(self.widgets.items()):
|
|
1794
1848
|
try:
|
|
1795
1849
|
if not _is_dead(w):
|
|
@@ -1864,50 +1918,173 @@ class ShortcutManager:
|
|
|
1864
1918
|
# legacy single-remove (kept for callers)
|
|
1865
1919
|
self.delete_by_id(sid, persist=True)
|
|
1866
1920
|
|
|
1867
|
-
|
|
1868
1921
|
class _StatStretchPresetDialog(QDialog):
|
|
1922
|
+
"""
|
|
1923
|
+
Preset editor for headless replay: command_id="stat_stretch"
|
|
1924
|
+
|
|
1925
|
+
Keys supported:
|
|
1926
|
+
target_median: float
|
|
1927
|
+
linked: bool
|
|
1928
|
+
normalize: bool
|
|
1929
|
+
apply_curves: bool
|
|
1930
|
+
curves_boost: float # 0..1
|
|
1931
|
+
blackpoint_sigma: float # 0..1 (matches your slider/100)
|
|
1932
|
+
no_black_clip: bool
|
|
1933
|
+
hdr_compress: bool
|
|
1934
|
+
hdr_amount: float # 0..1
|
|
1935
|
+
hdr_knee: float # 0..1
|
|
1936
|
+
luma_only: bool
|
|
1937
|
+
luma_mode: str # e.g. "rec709"
|
|
1938
|
+
luma_blend: float #0..1 (0=normal linked, 1=pure luma-only)
|
|
1939
|
+
"""
|
|
1869
1940
|
def __init__(self, parent=None, initial: dict | None = None):
|
|
1870
1941
|
super().__init__(parent)
|
|
1871
1942
|
self.setWindowTitle("Statistical Stretch — Preset")
|
|
1872
1943
|
init = dict(initial or {})
|
|
1873
1944
|
|
|
1945
|
+
# --- Target median ---
|
|
1874
1946
|
self.spin_target = QDoubleSpinBox()
|
|
1875
|
-
self.spin_target.setRange(0.0, 1.0)
|
|
1947
|
+
self.spin_target.setRange(0.0, 1.0)
|
|
1948
|
+
self.spin_target.setDecimals(3)
|
|
1876
1949
|
self.spin_target.setSingleStep(0.01)
|
|
1877
1950
|
self.spin_target.setValue(float(init.get("target_median", 0.25)))
|
|
1878
1951
|
|
|
1952
|
+
# --- Linked / Normalize ---
|
|
1879
1953
|
self.chk_linked = QCheckBox("Linked RGB channels")
|
|
1880
1954
|
self.chk_linked.setChecked(bool(init.get("linked", False)))
|
|
1881
1955
|
|
|
1882
1956
|
self.chk_normalize = QCheckBox("Normalize to [0..1]")
|
|
1883
1957
|
self.chk_normalize.setChecked(bool(init.get("normalize", False)))
|
|
1884
1958
|
|
|
1959
|
+
# --- Curves ---
|
|
1960
|
+
self.chk_curves = QCheckBox("Apply Curves")
|
|
1961
|
+
self.chk_curves.setChecked(bool(init.get("apply_curves", False)))
|
|
1962
|
+
|
|
1885
1963
|
self.spin_curves = QDoubleSpinBox()
|
|
1886
|
-
self.spin_curves.setRange(0.0, 1.0)
|
|
1964
|
+
self.spin_curves.setRange(0.0, 1.0)
|
|
1965
|
+
self.spin_curves.setDecimals(2)
|
|
1887
1966
|
self.spin_curves.setSingleStep(0.05)
|
|
1888
|
-
self.spin_curves.setValue(float(init.get("curves_boost", 0.0
|
|
1967
|
+
self.spin_curves.setValue(float(init.get("curves_boost", 0.0)))
|
|
1968
|
+
self.spin_curves.setEnabled(self.chk_curves.isChecked())
|
|
1969
|
+
self.chk_curves.toggled.connect(self.spin_curves.setEnabled)
|
|
1970
|
+
|
|
1971
|
+
# --- Blackpoint sigma ---
|
|
1972
|
+
self.spin_bp = QDoubleSpinBox()
|
|
1973
|
+
self.spin_bp.setRange(0.0, 1.0)
|
|
1974
|
+
self.spin_bp.setDecimals(2)
|
|
1975
|
+
self.spin_bp.setSingleStep(0.05)
|
|
1976
|
+
self.spin_bp.setValue(float(init.get("blackpoint_sigma", 0.0)))
|
|
1977
|
+
|
|
1978
|
+
self.chk_no_black_clip = QCheckBox("No black clip")
|
|
1979
|
+
self.chk_no_black_clip.setChecked(bool(init.get("no_black_clip", False)))
|
|
1980
|
+
|
|
1981
|
+
# --- HDR compress ---
|
|
1982
|
+
self.chk_hdr = QCheckBox("HDR compression")
|
|
1983
|
+
self.chk_hdr.setChecked(bool(init.get("hdr_compress", False)))
|
|
1984
|
+
|
|
1985
|
+
self.spin_hdr_amt = QDoubleSpinBox()
|
|
1986
|
+
self.spin_hdr_amt.setRange(0.0, 1.0)
|
|
1987
|
+
self.spin_hdr_amt.setDecimals(2)
|
|
1988
|
+
self.spin_hdr_amt.setSingleStep(0.05)
|
|
1989
|
+
self.spin_hdr_amt.setValue(float(init.get("hdr_amount", 0.0)))
|
|
1990
|
+
|
|
1991
|
+
self.spin_hdr_knee = QDoubleSpinBox()
|
|
1992
|
+
self.spin_hdr_knee.setRange(0.0, 1.0)
|
|
1993
|
+
self.spin_hdr_knee.setDecimals(2)
|
|
1994
|
+
self.spin_hdr_knee.setSingleStep(0.05)
|
|
1995
|
+
self.spin_hdr_knee.setValue(float(init.get("hdr_knee", 0.5)))
|
|
1996
|
+
|
|
1997
|
+
def _set_hdr_enabled(on: bool):
|
|
1998
|
+
on = bool(on)
|
|
1999
|
+
self.spin_hdr_amt.setEnabled(on)
|
|
2000
|
+
self.spin_hdr_knee.setEnabled(on)
|
|
2001
|
+
|
|
2002
|
+
_set_hdr_enabled(self.chk_hdr.isChecked())
|
|
2003
|
+
self.chk_hdr.toggled.connect(_set_hdr_enabled)
|
|
2004
|
+
|
|
2005
|
+
# --- Luma only ---
|
|
2006
|
+
self.chk_luma_only = QCheckBox("Luma-only mode")
|
|
2007
|
+
self.chk_luma_only.setChecked(bool(init.get("luma_only", False)))
|
|
2008
|
+
|
|
2009
|
+
self.cmb_luma = QComboBox()
|
|
2010
|
+
# keep in sync with your tool’s supported modes
|
|
2011
|
+
self.cmb_luma.addItems(["rec709", "avg", "hsp", "max"])
|
|
2012
|
+
init_mode = str(init.get("luma_mode", "rec709") or "rec709")
|
|
2013
|
+
idx = self.cmb_luma.findText(init_mode)
|
|
2014
|
+
if idx >= 0:
|
|
2015
|
+
self.cmb_luma.setCurrentIndex(idx)
|
|
2016
|
+
|
|
2017
|
+
self.spin_luma_blend = QDoubleSpinBox()
|
|
2018
|
+
self.spin_luma_blend.setRange(0.0, 1.0)
|
|
2019
|
+
self.spin_luma_blend.setDecimals(2)
|
|
2020
|
+
self.spin_luma_blend.setSingleStep(0.05)
|
|
2021
|
+
self.spin_luma_blend.setValue(float(init.get("luma_blend", 0.70)))
|
|
2022
|
+
|
|
2023
|
+
def _set_luma_enabled(on: bool):
|
|
2024
|
+
on = bool(on)
|
|
2025
|
+
self.cmb_luma.setEnabled(on)
|
|
2026
|
+
self.spin_luma_blend.setEnabled(on)
|
|
2027
|
+
|
|
2028
|
+
_set_luma_enabled(self.chk_luma_only.isChecked())
|
|
2029
|
+
self.chk_luma_only.toggled.connect(_set_luma_enabled)
|
|
2030
|
+
|
|
2031
|
+
# --- Layout ---
|
|
2032
|
+
form = QFormLayout()
|
|
1889
2033
|
|
|
1890
|
-
form = QFormLayout(self)
|
|
1891
2034
|
form.addRow("Target median:", self.spin_target)
|
|
1892
2035
|
form.addRow("", self.chk_linked)
|
|
1893
2036
|
form.addRow("", self.chk_normalize)
|
|
2037
|
+
|
|
2038
|
+
form.addRow("", QLabel("— Tone shaping —"))
|
|
2039
|
+
form.addRow("", self.chk_curves)
|
|
1894
2040
|
form.addRow("Curves boost (0–1):", self.spin_curves)
|
|
1895
|
-
form.addRow(QLabel("Curves are applied only if boost > 0."))
|
|
1896
2041
|
|
|
1897
|
-
|
|
1898
|
-
|
|
2042
|
+
form.addRow("", QLabel("— Blackpoint / HDR —"))
|
|
2043
|
+
form.addRow("Blackpoint σ (0–1):", self.spin_bp)
|
|
2044
|
+
form.addRow("", self.chk_no_black_clip)
|
|
2045
|
+
|
|
2046
|
+
form.addRow("", self.chk_hdr)
|
|
2047
|
+
form.addRow("HDR amount (0–1):", self.spin_hdr_amt)
|
|
2048
|
+
form.addRow("HDR knee (0–1):", self.spin_hdr_knee)
|
|
2049
|
+
|
|
2050
|
+
form.addRow("", QLabel("— Luma mode —"))
|
|
2051
|
+
form.addRow("", self.chk_luma_only)
|
|
2052
|
+
form.addRow("Luma mode:", self.cmb_luma)
|
|
2053
|
+
form.addRow("Luma blend (0–1):", self.spin_luma_blend)
|
|
2054
|
+
btns = QDialogButtonBox(
|
|
2055
|
+
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel,
|
|
2056
|
+
parent=self
|
|
2057
|
+
)
|
|
2058
|
+
btns.accepted.connect(self.accept)
|
|
2059
|
+
btns.rejected.connect(self.reject)
|
|
1899
2060
|
form.addRow(btns)
|
|
1900
2061
|
|
|
2062
|
+
self.setLayout(form)
|
|
2063
|
+
|
|
1901
2064
|
def result_dict(self) -> dict:
|
|
1902
|
-
|
|
2065
|
+
hdr_on = bool(self.chk_hdr.isChecked())
|
|
2066
|
+
curves_on = bool(self.chk_curves.isChecked())
|
|
2067
|
+
luma_on = bool(self.chk_luma_only.isChecked())
|
|
2068
|
+
|
|
1903
2069
|
return {
|
|
1904
2070
|
"target_median": float(self.spin_target.value()),
|
|
1905
2071
|
"linked": bool(self.chk_linked.isChecked()),
|
|
1906
2072
|
"normalize": bool(self.chk_normalize.isChecked()),
|
|
1907
|
-
|
|
1908
|
-
"
|
|
2073
|
+
|
|
2074
|
+
"apply_curves": curves_on,
|
|
2075
|
+
"curves_boost": float(self.spin_curves.value()) if curves_on else 0.0,
|
|
2076
|
+
|
|
2077
|
+
"blackpoint_sigma": float(self.spin_bp.value()),
|
|
2078
|
+
"no_black_clip": bool(self.chk_no_black_clip.isChecked()),
|
|
2079
|
+
|
|
2080
|
+
"hdr_compress": hdr_on,
|
|
2081
|
+
"hdr_amount": float(self.spin_hdr_amt.value()) if hdr_on else 0.0,
|
|
2082
|
+
"hdr_knee": float(self.spin_hdr_knee.value()) if hdr_on else 0.0,
|
|
2083
|
+
|
|
2084
|
+
"luma_only": luma_on,
|
|
2085
|
+
"luma_mode": str(self.cmb_luma.currentText()) if luma_on else "rec709",
|
|
2086
|
+
"luma_blend": float(self.spin_luma_blend.value()) if luma_on else 0.0,
|
|
1909
2087
|
}
|
|
1910
|
-
|
|
1911
2088
|
|
|
1912
2089
|
class _StarStretchPresetDialog(QDialog):
|
|
1913
2090
|
def __init__(self, parent=None, initial: dict | None = None):
|
|
@@ -3041,3 +3218,30 @@ class _RGBAlignPresetDialog(QDialog):
|
|
|
3041
3218
|
"new_doc": bool(self.chk_new.isChecked()),
|
|
3042
3219
|
}
|
|
3043
3220
|
|
|
3221
|
+
class _GeomRotateAnyPresetDialog(QDialog):
|
|
3222
|
+
def __init__(self, parent=None, initial: dict | None = None):
|
|
3223
|
+
super().__init__(parent)
|
|
3224
|
+
self.setWindowTitle("Arbitrary Rotation — Preset")
|
|
3225
|
+
init = dict(initial or {})
|
|
3226
|
+
|
|
3227
|
+
self.spin_angle = QDoubleSpinBox()
|
|
3228
|
+
self.spin_angle.setRange(-360.0, 360.0)
|
|
3229
|
+
self.spin_angle.setDecimals(2)
|
|
3230
|
+
self.spin_angle.setSingleStep(0.25)
|
|
3231
|
+
self.spin_angle.setValue(float(init.get("angle_deg", init.get("angle", 0.0))))
|
|
3232
|
+
|
|
3233
|
+
form = QFormLayout(self)
|
|
3234
|
+
form.addRow("Angle (degrees):", self.spin_angle)
|
|
3235
|
+
|
|
3236
|
+
btns = QDialogButtonBox(
|
|
3237
|
+
QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel,
|
|
3238
|
+
parent=self
|
|
3239
|
+
)
|
|
3240
|
+
btns.accepted.connect(self.accept)
|
|
3241
|
+
btns.rejected.connect(self.reject)
|
|
3242
|
+
form.addRow(btns)
|
|
3243
|
+
|
|
3244
|
+
def result_dict(self) -> dict:
|
|
3245
|
+
return {
|
|
3246
|
+
"angle_deg": float(self.spin_angle.value()),
|
|
3247
|
+
}
|