setiastrosuitepro 1.6.4__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/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 +108 -40
- setiastro/saspro/blemish_blaster.py +4 -1
- setiastro/saspro/blink_comparator_pro.py +74 -24
- 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 +123 -7
- setiastro/saspro/curve_editor_pro.py +109 -42
- setiastro/saspro/doc_manager.py +245 -15
- setiastro/saspro/exoplanet_detector.py +120 -28
- setiastro/saspro/frequency_separation.py +1158 -204
- setiastro/saspro/ghs_dialog_pro.py +81 -16
- setiastro/saspro/graxpert.py +1 -0
- setiastro/saspro/gui/main_window.py +429 -228
- setiastro/saspro/gui/mixins/dock_mixin.py +245 -24
- setiastro/saspro/gui/mixins/menu_mixin.py +27 -1
- setiastro/saspro/gui/mixins/theme_mixin.py +160 -14
- setiastro/saspro/gui/mixins/toolbar_mixin.py +384 -18
- 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 +51 -12
- 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/resources.py +67 -0
- setiastro/saspro/rgbalign.py +4 -0
- setiastro/saspro/selective_color.py +4 -1
- setiastro/saspro/sfcc.py +364 -152
- setiastro/saspro/shortcuts.py +160 -29
- setiastro/saspro/signature_insert.py +692 -33
- setiastro/saspro/stacking_suite.py +1331 -484
- setiastro/saspro/star_alignment.py +247 -123
- setiastro/saspro/star_spikes.py +4 -0
- setiastro/saspro/star_stretch.py +38 -3
- setiastro/saspro/stat_stretch.py +743 -128
- setiastro/saspro/subwindow.py +786 -360
- setiastro/saspro/supernovaasteroidhunter.py +1 -1
- 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/resource_monitor.py +109 -59
- setiastro/saspro/widgets/spinboxes.py +10 -13
- setiastro/saspro/wimi.py +27 -656
- setiastro/saspro/wims.py +13 -3
- setiastro/saspro/xisf.py +101 -11
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.6.12.dist-info}/METADATA +2 -1
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.6.12.dist-info}/RECORD +115 -82
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.6.12.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.6.12.dist-info}/entry_points.txt +0 -0
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.6.12.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.6.12.dist-info}/licenses/license.txt +0 -0
|
@@ -12,6 +12,7 @@ from PyQt6.QtWidgets import QMenu, QToolButton
|
|
|
12
12
|
|
|
13
13
|
from PyQt6.QtCore import QElapsedTimer
|
|
14
14
|
|
|
15
|
+
import sys
|
|
15
16
|
|
|
16
17
|
if TYPE_CHECKING:
|
|
17
18
|
pass
|
|
@@ -203,16 +204,16 @@ class ToolbarMixin:
|
|
|
203
204
|
tb_fn.addAction(self.act_convo)
|
|
204
205
|
tb_fn.addAction(self.act_extract_luma)
|
|
205
206
|
|
|
206
|
-
btn_luma = tb_fn.widgetForAction(self.act_extract_luma)
|
|
207
|
-
if isinstance(btn_luma, QToolButton):
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
207
|
+
#btn_luma = tb_fn.widgetForAction(self.act_extract_luma)
|
|
208
|
+
#if isinstance(btn_luma, QToolButton):
|
|
209
|
+
# luma_menu = QMenu(btn_luma)
|
|
210
|
+
# luma_menu.addActions(self._luma_group.actions())
|
|
211
|
+
# btn_luma.setMenu(luma_menu)
|
|
212
|
+
# btn_luma.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup)
|
|
213
|
+
# btn_luma.setStyleSheet("""
|
|
214
|
+
# QToolButton { color: #dcdcdc; }
|
|
215
|
+
# QToolButton:pressed, QToolButton:checked { color: #DAA520; font-weight: 600; }
|
|
216
|
+
# """)
|
|
216
217
|
|
|
217
218
|
tb_fn.addAction(self.act_recombine_luma)
|
|
218
219
|
tb_fn.addAction(self.act_rgb_extract)
|
|
@@ -361,9 +362,25 @@ class ToolbarMixin:
|
|
|
361
362
|
except Exception:
|
|
362
363
|
pass
|
|
363
364
|
|
|
365
|
+
|
|
366
|
+
tb_hidden = DraggableToolBar(self.tr("Hidden"), self)
|
|
367
|
+
tb_hidden.setObjectName("Hidden")
|
|
368
|
+
tb_hidden.setSettingsKey("Toolbar/Hidden")
|
|
369
|
+
self.addToolBar(Qt.ToolBarArea.TopToolBarArea, tb_hidden)
|
|
370
|
+
tb_hidden.setVisible(False) # <- always hidden
|
|
371
|
+
|
|
364
372
|
# This can move actions between toolbars, so do it after each toolbar has its base order restored.
|
|
365
373
|
self._restore_toolbar_memberships()
|
|
366
374
|
|
|
375
|
+
# Migrate legacy per-toolbar Hidden lists into the new Hidden toolbar
|
|
376
|
+
self._migrate_old_hidden_sets_to_hidden_toolbar()
|
|
377
|
+
|
|
378
|
+
# (Optional) Re-apply per-toolbar order after migration (since we removed actions)
|
|
379
|
+
for _tb in self.findChildren(DraggableToolBar):
|
|
380
|
+
key = getattr(_tb, "_settings_key", None)
|
|
381
|
+
if key:
|
|
382
|
+
self._restore_toolbar_order(_tb, str(key))
|
|
383
|
+
|
|
367
384
|
# Re-apply hidden state AFTER memberships (actions may have moved toolbars).
|
|
368
385
|
# This also guarantees correctness even if any toolbar was rebuilt/adjusted internally.
|
|
369
386
|
for _tb in self.findChildren(DraggableToolBar):
|
|
@@ -372,7 +389,9 @@ class ToolbarMixin:
|
|
|
372
389
|
except Exception:
|
|
373
390
|
pass
|
|
374
391
|
|
|
392
|
+
# Rebind ALL dropdowns after reorder/membership moves
|
|
375
393
|
self._rebind_view_dropdowns()
|
|
394
|
+
self._rebind_extract_luma_dropdown()
|
|
376
395
|
|
|
377
396
|
def _toolbar_containing_action(self, action: QAction):
|
|
378
397
|
from setiastro.saspro.shortcuts import DraggableToolBar
|
|
@@ -381,6 +400,140 @@ class ToolbarMixin:
|
|
|
381
400
|
return tb
|
|
382
401
|
return None
|
|
383
402
|
|
|
403
|
+
def _rebind_extract_luma_dropdown(self):
|
|
404
|
+
from PyQt6.QtWidgets import QMenu, QToolButton
|
|
405
|
+
from setiastro.saspro.luminancerecombine import LUMA_PROFILES
|
|
406
|
+
|
|
407
|
+
tb = self._toolbar_containing_action(self.act_extract_luma)
|
|
408
|
+
if not tb:
|
|
409
|
+
return
|
|
410
|
+
|
|
411
|
+
btn = tb.widgetForAction(self.act_extract_luma)
|
|
412
|
+
if not isinstance(btn, QToolButton):
|
|
413
|
+
return
|
|
414
|
+
|
|
415
|
+
menu = QMenu(btn)
|
|
416
|
+
|
|
417
|
+
# Ensure cache exists (created in _create_actions(), but be defensive)
|
|
418
|
+
if not hasattr(self, "_luma_sensor_actions"):
|
|
419
|
+
self._luma_sensor_actions = {} # key -> QAction
|
|
420
|
+
|
|
421
|
+
cur_method = str(getattr(self, "luma_method", "rec709"))
|
|
422
|
+
|
|
423
|
+
# ============================================================
|
|
424
|
+
# PATCH SITE #1 (Standard menu) <-- THIS IS WHERE "C" GOES
|
|
425
|
+
# Find your old block:
|
|
426
|
+
#
|
|
427
|
+
# # ---- Standard (use your QActionGroup, keep it simple) ----
|
|
428
|
+
# if getattr(self, "_luma_group", None) is not None:
|
|
429
|
+
# std_menu = QMenu(self.tr("Standard"), menu)
|
|
430
|
+
# std_menu.addActions(self._luma_group.actions())
|
|
431
|
+
# menu.addMenu(std_menu)
|
|
432
|
+
#
|
|
433
|
+
# Replace it with the block below.
|
|
434
|
+
# ============================================================
|
|
435
|
+
if getattr(self, "_luma_group", None) is not None:
|
|
436
|
+
# Sync standard checked states from self.luma_method
|
|
437
|
+
for a in self._luma_group.actions():
|
|
438
|
+
data = str(a.data() or "")
|
|
439
|
+
if data.startswith("sensor:"):
|
|
440
|
+
continue # standards only in Standard menu
|
|
441
|
+
a.blockSignals(True)
|
|
442
|
+
try:
|
|
443
|
+
a.setChecked(data == cur_method)
|
|
444
|
+
finally:
|
|
445
|
+
a.blockSignals(False)
|
|
446
|
+
|
|
447
|
+
# Add ONLY standard actions to the Standard menu (exclude sensors)
|
|
448
|
+
std_menu = QMenu(self.tr("Standard"), menu)
|
|
449
|
+
for a in self._luma_group.actions():
|
|
450
|
+
data = str(a.data() or "")
|
|
451
|
+
if data.startswith("sensor:"):
|
|
452
|
+
continue
|
|
453
|
+
std_menu.addAction(a)
|
|
454
|
+
menu.addMenu(std_menu)
|
|
455
|
+
|
|
456
|
+
# ---- Sensors (nested by category path) ----
|
|
457
|
+
sensors_root = QMenu(self.tr("Sensors"), menu)
|
|
458
|
+
|
|
459
|
+
# Build tree of submenus keyed by "Sensors/<path>"
|
|
460
|
+
submenu_cache: dict[str, QMenu] = {}
|
|
461
|
+
|
|
462
|
+
def get_or_make_path(root_menu: QMenu, path: str) -> QMenu:
|
|
463
|
+
parts = [p for p in path.split("/") if p.strip()]
|
|
464
|
+
cur = root_menu
|
|
465
|
+
acc = ""
|
|
466
|
+
for part in parts:
|
|
467
|
+
acc = f"{acc}/{part}" if acc else part
|
|
468
|
+
if acc not in submenu_cache:
|
|
469
|
+
sm = QMenu(self.tr(part), cur)
|
|
470
|
+
cur.addMenu(sm)
|
|
471
|
+
submenu_cache[acc] = sm
|
|
472
|
+
cur = submenu_cache[acc]
|
|
473
|
+
return cur
|
|
474
|
+
|
|
475
|
+
any_sensor = False
|
|
476
|
+
for key, prof in LUMA_PROFILES.items():
|
|
477
|
+
if not str(key).startswith("sensor:"):
|
|
478
|
+
continue
|
|
479
|
+
|
|
480
|
+
cat = str(prof.get("category", "Sensors/Other"))
|
|
481
|
+
group_path = cat.split("Sensors/", 1)[1] if "Sensors/" in cat else cat
|
|
482
|
+
parent_menu = get_or_make_path(sensors_root, group_path)
|
|
483
|
+
|
|
484
|
+
display_name = key.split("sensor:", 1)[1].strip()
|
|
485
|
+
desc = str(prof.get("description", display_name))
|
|
486
|
+
info = str(prof.get("info", "")).strip()
|
|
487
|
+
|
|
488
|
+
# ============================================================
|
|
489
|
+
# PATCH SITE #2 (Sensors become mutually exclusive with standards)
|
|
490
|
+
# - Cache the QAction so we don't pile up connections/actions
|
|
491
|
+
# - Add it to the SAME QActionGroup (exclusive) as standards
|
|
492
|
+
# - Do NOT use _pick_sensor; rely on QActionGroup.triggered
|
|
493
|
+
# ============================================================
|
|
494
|
+
act = self._luma_sensor_actions.get(key)
|
|
495
|
+
if act is None:
|
|
496
|
+
act = parent_menu.addAction(self.tr(display_name))
|
|
497
|
+
act.setCheckable(True)
|
|
498
|
+
act.setData(key) # IMPORTANT: enables group.triggered to set luma_method
|
|
499
|
+
|
|
500
|
+
# Put sensors into the SAME exclusive group so selecting one
|
|
501
|
+
# deselects everything else (standards and other sensors).
|
|
502
|
+
if getattr(self, "_luma_group", None) is not None:
|
|
503
|
+
self._luma_group.addAction(act)
|
|
504
|
+
|
|
505
|
+
self._luma_sensor_actions[key] = act
|
|
506
|
+
else:
|
|
507
|
+
# Reuse action, but re-add it to the correct submenu location
|
|
508
|
+
parent_menu.addAction(act)
|
|
509
|
+
act.setText(self.tr(display_name))
|
|
510
|
+
|
|
511
|
+
# Update UI info each bind (safe if translations change)
|
|
512
|
+
if info:
|
|
513
|
+
act.setStatusTip(info)
|
|
514
|
+
act.setToolTip(f"{desc}\n{info}")
|
|
515
|
+
else:
|
|
516
|
+
act.setToolTip(desc)
|
|
517
|
+
|
|
518
|
+
# Checked state reflects current selection
|
|
519
|
+
act.blockSignals(True)
|
|
520
|
+
try:
|
|
521
|
+
act.setChecked(cur_method == str(key))
|
|
522
|
+
finally:
|
|
523
|
+
act.blockSignals(False)
|
|
524
|
+
|
|
525
|
+
any_sensor = True
|
|
526
|
+
|
|
527
|
+
if any_sensor:
|
|
528
|
+
menu.addMenu(sensors_root)
|
|
529
|
+
|
|
530
|
+
btn.setMenu(menu)
|
|
531
|
+
btn.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup)
|
|
532
|
+
btn.setStyleSheet("""
|
|
533
|
+
QToolButton { color: #dcdcdc; }
|
|
534
|
+
QToolButton:pressed, QToolButton:checked { color: #DAA520; font-weight: 600; }
|
|
535
|
+
""")
|
|
536
|
+
|
|
384
537
|
|
|
385
538
|
def _rebind_view_dropdowns(self):
|
|
386
539
|
"""
|
|
@@ -442,7 +595,16 @@ class ToolbarMixin:
|
|
|
442
595
|
fit_menu.addAction(self.act_auto_fit_resize) # use the real action
|
|
443
596
|
btn_fit.setMenu(fit_menu)
|
|
444
597
|
btn_fit.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup)
|
|
598
|
+
tb = self._toolbar_containing_action(self.act_autostretch)
|
|
599
|
+
if tb:
|
|
600
|
+
btn = tb.widgetForAction(self.act_autostretch)
|
|
601
|
+
if isinstance(btn, QToolButton):
|
|
602
|
+
# ... build menu ...
|
|
603
|
+
btn.setMenu(menu)
|
|
604
|
+
btn.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup)
|
|
445
605
|
|
|
606
|
+
# IMPORTANT: re-apply style after action moves / rebind
|
|
607
|
+
self._style_toggle_toolbutton(btn)
|
|
446
608
|
|
|
447
609
|
def _bind_view_toolbar_menus(self, tb: DraggableToolBar):
|
|
448
610
|
# --- Display-Stretch menu ---
|
|
@@ -498,6 +660,12 @@ class ToolbarMixin:
|
|
|
498
660
|
btn_fit.setMenu(fit_menu)
|
|
499
661
|
btn_fit.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup)
|
|
500
662
|
|
|
663
|
+
def _linux_force_text_action(self, act: QAction, text: str) -> None:
|
|
664
|
+
"""On Linux, show text-only for this action (no theme icon)."""
|
|
665
|
+
if not sys.platform.startswith("linux"):
|
|
666
|
+
return
|
|
667
|
+
act.setIcon(QIcon()) # remove whatever theme icon got assigned
|
|
668
|
+
act.setText(text) # show the glyph/text
|
|
501
669
|
|
|
502
670
|
def _create_actions(self):
|
|
503
671
|
# File actions
|
|
@@ -619,30 +787,30 @@ class ToolbarMixin:
|
|
|
619
787
|
self.act_bake_display_stretch.triggered.connect(self._bake_display_stretch)
|
|
620
788
|
|
|
621
789
|
# --- Zoom controls ---
|
|
622
|
-
# --- Zoom controls (themed icons) ---
|
|
623
790
|
self.act_zoom_out = QAction(QIcon.fromTheme("zoom-out"), self.tr("Zoom Out"), self)
|
|
624
791
|
self.act_zoom_out.setStatusTip(self.tr("Zoom out"))
|
|
625
792
|
self.act_zoom_out.setShortcuts([QKeySequence("Ctrl+-")])
|
|
626
793
|
self.act_zoom_out.triggered.connect(lambda: self._zoom_step_active(-1))
|
|
794
|
+
self._linux_force_text_action(self.act_zoom_out, "−") # true minus
|
|
627
795
|
|
|
628
796
|
self.act_zoom_in = QAction(QIcon.fromTheme("zoom-in"), self.tr("Zoom In"), self)
|
|
629
797
|
self.act_zoom_in.setStatusTip(self.tr("Zoom in"))
|
|
630
|
-
self.act_zoom_in.setShortcuts([
|
|
631
|
-
QKeySequence("Ctrl++"), # Ctrl + (Shift + = on many keyboards)
|
|
632
|
-
QKeySequence("Ctrl+="), # fallback
|
|
633
|
-
])
|
|
798
|
+
self.act_zoom_in.setShortcuts([QKeySequence("Ctrl++"), QKeySequence("Ctrl+=")])
|
|
634
799
|
self.act_zoom_in.triggered.connect(lambda: self._zoom_step_active(+1))
|
|
800
|
+
self._linux_force_text_action(self.act_zoom_in, "+")
|
|
635
801
|
|
|
636
802
|
self.act_zoom_1_1 = QAction(QIcon.fromTheme("zoom-original"), self.tr("1:1"), self)
|
|
637
803
|
self.act_zoom_1_1.setStatusTip(self.tr("Zoom to 100% (pixel-for-pixel)"))
|
|
638
804
|
self.act_zoom_1_1.setShortcut(QKeySequence("Ctrl+1"))
|
|
639
805
|
self.act_zoom_1_1.triggered.connect(self._zoom_active_1_1)
|
|
806
|
+
self._linux_force_text_action(self.act_zoom_1_1, "1:1")
|
|
640
807
|
|
|
641
808
|
self.act_zoom_fit = QAction(QIcon.fromTheme("zoom-fit-best"), self.tr("Fit"), self)
|
|
642
809
|
self.act_zoom_fit.setStatusTip(self.tr("Fit image to current window"))
|
|
643
810
|
self.act_zoom_fit.setShortcut(QKeySequence("Ctrl+0"))
|
|
644
811
|
self.act_zoom_fit.triggered.connect(self._zoom_active_fit)
|
|
645
812
|
self.act_zoom_fit.setCheckable(True)
|
|
813
|
+
self._linux_force_text_action(self.act_zoom_fit, "Fit")
|
|
646
814
|
|
|
647
815
|
self.act_auto_fit_resize = QAction(self.tr("Auto-fit on Resize"), self)
|
|
648
816
|
self.act_auto_fit_resize.setCheckable(True)
|
|
@@ -767,6 +935,7 @@ class ToolbarMixin:
|
|
|
767
935
|
self.luma_method = getattr(self, "luma_method", "rec709") # default
|
|
768
936
|
self._luma_group = QActionGroup(self)
|
|
769
937
|
self._luma_group.setExclusive(True)
|
|
938
|
+
self._luma_sensor_actions = {} # key -> QAction
|
|
770
939
|
|
|
771
940
|
def _mk(method_key, text):
|
|
772
941
|
act = QAction(text, self, checkable=True)
|
|
@@ -786,8 +955,10 @@ class ToolbarMixin:
|
|
|
786
955
|
|
|
787
956
|
# update method when user picks from the menu
|
|
788
957
|
def _on_luma_pick(act):
|
|
789
|
-
|
|
790
|
-
|
|
958
|
+
key = act.data()
|
|
959
|
+
if key is None:
|
|
960
|
+
return
|
|
961
|
+
self.luma_method = str(key)
|
|
791
962
|
try:
|
|
792
963
|
self.settings.setValue("ui/luminance_method", self.luma_method)
|
|
793
964
|
except Exception:
|
|
@@ -1105,6 +1276,13 @@ class ToolbarMixin:
|
|
|
1105
1276
|
self.act_copy_astrometry = QAction(self.tr("Copy Astrometric Solution..."), self)
|
|
1106
1277
|
self.act_copy_astrometry.triggered.connect(self._open_copy_astrometry)
|
|
1107
1278
|
|
|
1279
|
+
self.act_acv_exporter = QAction(self.tr("Astro Catalogue Viewer Exporter..."), self)
|
|
1280
|
+
self.act_acv_exporter.setIconVisibleInMenu(True)
|
|
1281
|
+
self.act_acv_exporter.setStatusTip(self.tr("Export current view into Astro Catalogue Viewer folders"))
|
|
1282
|
+
self.act_acv_exporter.triggered.connect(self._open_acv_exporter)
|
|
1283
|
+
|
|
1284
|
+
|
|
1285
|
+
|
|
1108
1286
|
# Create Mask
|
|
1109
1287
|
self.act_create_mask = QAction(QIcon(maskcreate_path), self.tr("Create Mask..."), self)
|
|
1110
1288
|
self.act_create_mask.setIconVisibleInMenu(True)
|
|
@@ -1356,6 +1534,188 @@ class ToolbarMixin:
|
|
|
1356
1534
|
if key:
|
|
1357
1535
|
self._restore_toolbar_order(tb, str(key))
|
|
1358
1536
|
|
|
1537
|
+
def _hidden_toolbar(self):
|
|
1538
|
+
from setiastro.saspro.shortcuts import DraggableToolBar
|
|
1539
|
+
for tb in self.findChildren(DraggableToolBar):
|
|
1540
|
+
if getattr(tb, "_settings_key", None) == "Toolbar/Hidden" or tb.objectName() == "Hidden":
|
|
1541
|
+
return tb
|
|
1542
|
+
return None
|
|
1543
|
+
|
|
1544
|
+
def _toolbar_by_settings_key(self, key: str):
|
|
1545
|
+
from setiastro.saspro.shortcuts import DraggableToolBar
|
|
1546
|
+
for tb in self.findChildren(DraggableToolBar):
|
|
1547
|
+
if getattr(tb, "_settings_key", None) == key:
|
|
1548
|
+
return tb
|
|
1549
|
+
return None
|
|
1550
|
+
|
|
1551
|
+
def _action_cid(self, act):
|
|
1552
|
+
return str(act.property("command_id") or act.objectName() or "")
|
|
1553
|
+
|
|
1554
|
+
def _hide_action_to_hidden_toolbar(self, act):
|
|
1555
|
+
"""
|
|
1556
|
+
Move action to the Hidden toolbar, remembering where it came from.
|
|
1557
|
+
"""
|
|
1558
|
+
tb_hidden = self._hidden_toolbar()
|
|
1559
|
+
if not tb_hidden:
|
|
1560
|
+
return
|
|
1561
|
+
|
|
1562
|
+
cid = self._action_cid(act)
|
|
1563
|
+
if not cid:
|
|
1564
|
+
return
|
|
1565
|
+
|
|
1566
|
+
# Find current toolbar (if any) and remember it
|
|
1567
|
+
cur_tb = self._toolbar_containing_action(act)
|
|
1568
|
+
if cur_tb and getattr(cur_tb, "_settings_key", None) and cur_tb is not tb_hidden:
|
|
1569
|
+
prev_key = str(cur_tb._settings_key)
|
|
1570
|
+
|
|
1571
|
+
# Persist "previous toolbar" so unhide can restore
|
|
1572
|
+
try:
|
|
1573
|
+
raw = self.settings.value("Toolbar/HiddenPrev", "", type=str) or ""
|
|
1574
|
+
except Exception:
|
|
1575
|
+
raw = ""
|
|
1576
|
+
try:
|
|
1577
|
+
prev = json.loads(raw) if raw else {}
|
|
1578
|
+
except Exception:
|
|
1579
|
+
prev = {}
|
|
1580
|
+
prev[cid] = prev_key
|
|
1581
|
+
self.settings.setValue("Toolbar/HiddenPrev", json.dumps(prev))
|
|
1582
|
+
|
|
1583
|
+
# Remove from all toolbars, add to hidden
|
|
1584
|
+
from setiastro.saspro.shortcuts import DraggableToolBar
|
|
1585
|
+
for t in self.findChildren(DraggableToolBar):
|
|
1586
|
+
if act in t.actions():
|
|
1587
|
+
t.removeAction(act)
|
|
1588
|
+
|
|
1589
|
+
tb_hidden.addAction(act)
|
|
1590
|
+
|
|
1591
|
+
# Update assignment mapping so restore works on next launch
|
|
1592
|
+
tb_hidden._update_assignment_for_action(act)
|
|
1593
|
+
|
|
1594
|
+
# Persist ordering (optional but nice)
|
|
1595
|
+
try:
|
|
1596
|
+
tb_hidden._persist_order()
|
|
1597
|
+
except Exception:
|
|
1598
|
+
pass
|
|
1599
|
+
if cur_tb:
|
|
1600
|
+
try:
|
|
1601
|
+
cur_tb._persist_order()
|
|
1602
|
+
except Exception:
|
|
1603
|
+
pass
|
|
1604
|
+
|
|
1605
|
+
def _unhide_action_from_hidden_toolbar(self, act):
|
|
1606
|
+
"""
|
|
1607
|
+
Move action out of Hidden toolbar back to its remembered toolbar.
|
|
1608
|
+
Falls back to Toolbar/View if missing.
|
|
1609
|
+
"""
|
|
1610
|
+
tb_hidden = self._hidden_toolbar()
|
|
1611
|
+
if not tb_hidden:
|
|
1612
|
+
return
|
|
1613
|
+
|
|
1614
|
+
cid = self._action_cid(act)
|
|
1615
|
+
if not cid:
|
|
1616
|
+
return
|
|
1617
|
+
|
|
1618
|
+
# Load prev mapping
|
|
1619
|
+
try:
|
|
1620
|
+
raw = self.settings.value("Toolbar/HiddenPrev", "", type=str) or ""
|
|
1621
|
+
except Exception:
|
|
1622
|
+
raw = ""
|
|
1623
|
+
try:
|
|
1624
|
+
prev = json.loads(raw) if raw else {}
|
|
1625
|
+
except Exception:
|
|
1626
|
+
prev = {}
|
|
1627
|
+
|
|
1628
|
+
target_key = prev.get(cid) or "Toolbar/View"
|
|
1629
|
+
tb_target = self._toolbar_by_settings_key(target_key) or self._toolbar_by_settings_key("Toolbar/View")
|
|
1630
|
+
if not tb_target:
|
|
1631
|
+
return
|
|
1632
|
+
|
|
1633
|
+
tb_hidden.removeAction(act)
|
|
1634
|
+
tb_target.addAction(act)
|
|
1635
|
+
|
|
1636
|
+
# Update assignment mapping
|
|
1637
|
+
tb_target._update_assignment_for_action(act)
|
|
1638
|
+
|
|
1639
|
+
# Cleanup prev mapping (optional)
|
|
1640
|
+
if cid in prev:
|
|
1641
|
+
prev.pop(cid, None)
|
|
1642
|
+
self.settings.setValue("Toolbar/HiddenPrev", json.dumps(prev))
|
|
1643
|
+
|
|
1644
|
+
try:
|
|
1645
|
+
tb_target._persist_order()
|
|
1646
|
+
tb_hidden._persist_order()
|
|
1647
|
+
except Exception:
|
|
1648
|
+
pass
|
|
1649
|
+
|
|
1650
|
+
def _migrate_old_hidden_sets_to_hidden_toolbar(self):
|
|
1651
|
+
"""
|
|
1652
|
+
One-time migration:
|
|
1653
|
+
Old system: per-toolbar QSettings lists at "<ToolbarKey>/Hidden" storing action IDs
|
|
1654
|
+
New system: move those actions into the dedicated Hidden toolbar (Toolbar/Hidden)
|
|
1655
|
+
and persist membership via Toolbar/Assignments.
|
|
1656
|
+
"""
|
|
1657
|
+
if not hasattr(self, "settings"):
|
|
1658
|
+
return
|
|
1659
|
+
|
|
1660
|
+
# Run once
|
|
1661
|
+
if self.settings.value("Toolbar/HiddenMigrationDone", False, type=bool):
|
|
1662
|
+
return
|
|
1663
|
+
|
|
1664
|
+
tb_hidden = self._hidden_toolbar()
|
|
1665
|
+
if not tb_hidden:
|
|
1666
|
+
return
|
|
1667
|
+
|
|
1668
|
+
from setiastro.saspro.shortcuts import DraggableToolBar
|
|
1669
|
+
|
|
1670
|
+
# If Hidden toolbar already has actions, assume user is already on new system
|
|
1671
|
+
# but we still can migrate any remaining old keys.
|
|
1672
|
+
# (We won't early-return.)
|
|
1673
|
+
|
|
1674
|
+
for tb in self.findChildren(DraggableToolBar):
|
|
1675
|
+
k = getattr(tb, "_settings_key", None)
|
|
1676
|
+
if not k or str(k) == "Toolbar/Hidden":
|
|
1677
|
+
continue
|
|
1678
|
+
|
|
1679
|
+
# Old storage key
|
|
1680
|
+
old_key = f"{k}/Hidden"
|
|
1681
|
+
|
|
1682
|
+
# Read old hidden list (Qt backend may return list OR str)
|
|
1683
|
+
try:
|
|
1684
|
+
raw = self.settings.value(old_key, [], type=list)
|
|
1685
|
+
except Exception:
|
|
1686
|
+
raw = self.settings.value(old_key, []) # fallback
|
|
1687
|
+
|
|
1688
|
+
if not raw:
|
|
1689
|
+
continue
|
|
1690
|
+
|
|
1691
|
+
if isinstance(raw, str):
|
|
1692
|
+
raw_list = [raw]
|
|
1693
|
+
else:
|
|
1694
|
+
try:
|
|
1695
|
+
raw_list = list(raw)
|
|
1696
|
+
except Exception:
|
|
1697
|
+
raw_list = []
|
|
1698
|
+
|
|
1699
|
+
hidden_ids = {str(x) for x in raw_list if str(x).strip()}
|
|
1700
|
+
if not hidden_ids:
|
|
1701
|
+
# Clear junk so we don’t keep trying
|
|
1702
|
+
self.settings.setValue(old_key, [])
|
|
1703
|
+
continue
|
|
1704
|
+
|
|
1705
|
+
# Move matching actions into Hidden toolbar
|
|
1706
|
+
for act in list(tb.actions()):
|
|
1707
|
+
if act is None or act.isSeparator():
|
|
1708
|
+
continue
|
|
1709
|
+
cid = str(act.property("command_id") or act.objectName() or "")
|
|
1710
|
+
if cid and cid in hidden_ids:
|
|
1711
|
+
self._hide_action_to_hidden_toolbar(act)
|
|
1712
|
+
|
|
1713
|
+
# Clear old storage so you don't keep re-migrating
|
|
1714
|
+
self.settings.setValue(old_key, [])
|
|
1715
|
+
|
|
1716
|
+
# Mark migration complete
|
|
1717
|
+
self.settings.setValue("Toolbar/HiddenMigrationDone", True)
|
|
1718
|
+
|
|
1359
1719
|
|
|
1360
1720
|
def update_undo_redo_action_labels(self):
|
|
1361
1721
|
if not hasattr(self, "act_undo"): # not built yet
|
|
@@ -1477,4 +1837,10 @@ class ToolbarMixin:
|
|
|
1477
1837
|
if hasattr(self, "act_hide_mask"):
|
|
1478
1838
|
self.act_hide_mask.setEnabled(has_mask and overlay_on)
|
|
1479
1839
|
|
|
1480
|
-
|
|
1840
|
+
def _style_toggle_toolbutton(self, btn: QToolButton):
|
|
1841
|
+
# Make sure the action visually shows "on" state
|
|
1842
|
+
btn.setCheckable(True) # safe even if already
|
|
1843
|
+
btn.setStyleSheet("""
|
|
1844
|
+
QToolButton { color: #dcdcdc; }
|
|
1845
|
+
QToolButton:checked { color: #DAA520; font-weight: 600; }
|
|
1846
|
+
""")
|