setiastrosuitepro 1.6.4__py3-none-any.whl → 1.7.1.post2__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/TextureClarity.svg +56 -0
- 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/narrowbandnormalization.png +0 -0
- setiastro/images/new_moon.png +0 -0
- setiastro/images/pixelmath.svg +42 -0
- setiastro/images/planetarystacker.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 +364 -33
- setiastro/saspro/aberration_ai_preset.py +29 -3
- setiastro/saspro/acv_exporter.py +379 -0
- setiastro/saspro/add_stars.py +33 -6
- setiastro/saspro/astrospike_python.py +45 -3
- setiastro/saspro/backgroundneutral.py +108 -40
- setiastro/saspro/blemish_blaster.py +4 -1
- setiastro/saspro/blink_comparator_pro.py +150 -55
- 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 +181 -64
- setiastro/saspro/curves_preset.py +249 -47
- 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 +706 -264
- setiastro/saspro/gui/mixins/dock_mixin.py +245 -24
- setiastro/saspro/gui/mixins/file_mixin.py +35 -16
- setiastro/saspro/gui/mixins/menu_mixin.py +35 -1
- setiastro/saspro/gui/mixins/theme_mixin.py +160 -14
- setiastro/saspro/gui/mixins/toolbar_mixin.py +499 -24
- 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 +184 -8
- setiastro/saspro/image_combine.py +4 -0
- setiastro/saspro/image_peeker_pro.py +4 -0
- setiastro/saspro/imageops/narrowband_normalization.py +816 -0
- setiastro/saspro/imageops/serloader.py +1345 -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 +68 -48
- setiastro/saspro/legacy/xisf.py +240 -98
- setiastro/saspro/live_stacking.py +203 -82
- 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 +81 -29
- setiastro/saspro/narrowband_normalization.py +1618 -0
- 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_green.py +1 -1
- setiastro/saspro/resources.py +73 -0
- setiastro/saspro/rgbalign.py +460 -12
- setiastro/saspro/selective_color.py +4 -1
- setiastro/saspro/ser_stack_config.py +82 -0
- setiastro/saspro/ser_stacker.py +2321 -0
- setiastro/saspro/ser_stacker_dialog.py +1838 -0
- setiastro/saspro/ser_tracking.py +206 -0
- setiastro/saspro/serviewer.py +1625 -0
- setiastro/saspro/sfcc.py +662 -216
- setiastro/saspro/shortcuts.py +171 -33
- setiastro/saspro/signature_insert.py +692 -33
- setiastro/saspro/stacking_suite.py +1347 -485
- 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 +892 -129
- setiastro/saspro/subwindow.py +787 -363
- setiastro/saspro/supernovaasteroidhunter.py +1 -1
- setiastro/saspro/texture_clarity.py +593 -0
- 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 +209 -111
- 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.7.1.post2.dist-info}/METADATA +4 -2
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/RECORD +132 -87
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/entry_points.txt +0 -0
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.6.4.dist-info → setiastrosuitepro-1.7.1.post2.dist-info}/licenses/license.txt +0 -0
|
@@ -12,13 +12,15 @@ from PyQt6.QtWidgets import QMenu, QToolButton
|
|
|
12
12
|
|
|
13
13
|
from PyQt6.QtCore import QElapsedTimer
|
|
14
14
|
|
|
15
|
+
import sys
|
|
16
|
+
import os
|
|
15
17
|
|
|
16
18
|
if TYPE_CHECKING:
|
|
17
19
|
pass
|
|
18
20
|
|
|
19
21
|
# Import icon paths - these are needed at runtime
|
|
20
22
|
from setiastro.saspro.resources import (
|
|
21
|
-
icon_path, green_path, neutral_path, whitebalance_path,
|
|
23
|
+
icon_path, green_path, neutral_path, whitebalance_path, texture_clarity_path,
|
|
22
24
|
morpho_path, clahe_path, starnet_path, staradd_path, LExtract_path,
|
|
23
25
|
LInsert_path, rgbcombo_path, rgbextract_path, graxperticon_path,
|
|
24
26
|
cropicon_path, openfile_path, abeicon_path, undoicon_path, redoicon_path,
|
|
@@ -30,10 +32,10 @@ from setiastro.saspro.resources import (
|
|
|
30
32
|
stacking_path, pedestal_icon_path, starspike_path, astrospike_path,
|
|
31
33
|
signature_icon_path, livestacking_path, convoicon_path, spcc_icon_path,
|
|
32
34
|
exoicon_path, peeker_icon, dse_icon_path, isophote_path, statstretch_path,
|
|
33
|
-
starstretch_path, curves_path, disk_path, uhs_path, blink_path, ppp_path,
|
|
35
|
+
starstretch_path, curves_path, disk_path, uhs_path, blink_path, ppp_path, narrowbandnormalization_path,
|
|
34
36
|
nbtorgb_path, freqsep_path, multiscale_decomp_path, contsub_path, halo_path, cosmic_path,
|
|
35
37
|
satellite_path, imagecombine_path, wims_path, wimi_path, linearfit_path,
|
|
36
|
-
debayer_path, aberration_path, functionbundles_path, viewbundles_path,
|
|
38
|
+
debayer_path, aberration_path, functionbundles_path, viewbundles_path, planetarystacker_path,
|
|
37
39
|
selectivecolor_path, rgbalign_path,
|
|
38
40
|
)
|
|
39
41
|
|
|
@@ -203,16 +205,16 @@ class ToolbarMixin:
|
|
|
203
205
|
tb_fn.addAction(self.act_convo)
|
|
204
206
|
tb_fn.addAction(self.act_extract_luma)
|
|
205
207
|
|
|
206
|
-
btn_luma = tb_fn.widgetForAction(self.act_extract_luma)
|
|
207
|
-
if isinstance(btn_luma, QToolButton):
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
208
|
+
#btn_luma = tb_fn.widgetForAction(self.act_extract_luma)
|
|
209
|
+
#if isinstance(btn_luma, QToolButton):
|
|
210
|
+
# luma_menu = QMenu(btn_luma)
|
|
211
|
+
# luma_menu.addActions(self._luma_group.actions())
|
|
212
|
+
# btn_luma.setMenu(luma_menu)
|
|
213
|
+
# btn_luma.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup)
|
|
214
|
+
# btn_luma.setStyleSheet("""
|
|
215
|
+
# QToolButton { color: #dcdcdc; }
|
|
216
|
+
# QToolButton:pressed, QToolButton:checked { color: #DAA520; font-weight: 600; }
|
|
217
|
+
# """)
|
|
216
218
|
|
|
217
219
|
tb_fn.addAction(self.act_recombine_luma)
|
|
218
220
|
tb_fn.addAction(self.act_rgb_extract)
|
|
@@ -221,6 +223,7 @@ class ToolbarMixin:
|
|
|
221
223
|
tb_fn.addAction(self.act_wavescale_hdr)
|
|
222
224
|
tb_fn.addAction(self.act_wavescale_de)
|
|
223
225
|
tb_fn.addAction(self.act_clahe)
|
|
226
|
+
tb_fn.addAction(self.act_texture_clarity)
|
|
224
227
|
tb_fn.addAction(self.act_morphology)
|
|
225
228
|
tb_fn.addAction(self.act_pixelmath)
|
|
226
229
|
tb_fn.addAction(self.act_signature)
|
|
@@ -255,6 +258,8 @@ class ToolbarMixin:
|
|
|
255
258
|
tb_tl.addAction(self.act_blink) # Tools start here; Blink shows with QIcon(blink_path)
|
|
256
259
|
tb_tl.addAction(self.act_ppp) # Perfect Palette Picker
|
|
257
260
|
tb_tl.addAction(self.act_nbtorgb)
|
|
261
|
+
tb_tl.addAction(self.act_narrowband_normalization)
|
|
262
|
+
|
|
258
263
|
tb_tl.addAction(self.act_selective_color)
|
|
259
264
|
tb_tl.addAction(self.act_freqsep)
|
|
260
265
|
tb_tl.addAction(self.act_multiscale_decomp)
|
|
@@ -301,6 +306,7 @@ class ToolbarMixin:
|
|
|
301
306
|
tb_star.addAction(self.act_psf_viewer)
|
|
302
307
|
tb_star.addAction(self.act_stacking_suite)
|
|
303
308
|
tb_star.addAction(self.act_live_stacking)
|
|
309
|
+
tb_star.addAction(self.act_planetary_stacker)
|
|
304
310
|
tb_star.addAction(self.act_plate_solve)
|
|
305
311
|
tb_star.addAction(self.act_star_align)
|
|
306
312
|
tb_star.addAction(self.act_star_register)
|
|
@@ -361,9 +367,26 @@ class ToolbarMixin:
|
|
|
361
367
|
except Exception:
|
|
362
368
|
pass
|
|
363
369
|
|
|
370
|
+
|
|
371
|
+
tb_hidden = DraggableToolBar(self.tr("Hidden"), self)
|
|
372
|
+
tb_hidden.setObjectName("Hidden")
|
|
373
|
+
tb_hidden.setSettingsKey("Toolbar/Hidden")
|
|
374
|
+
self.addToolBar(Qt.ToolBarArea.TopToolBarArea, tb_hidden)
|
|
375
|
+
#tb_hidden.addAction(self.act_narrowband_normalization)
|
|
376
|
+
tb_hidden.setVisible(False) # <- always hidden
|
|
377
|
+
|
|
364
378
|
# This can move actions between toolbars, so do it after each toolbar has its base order restored.
|
|
365
379
|
self._restore_toolbar_memberships()
|
|
366
380
|
|
|
381
|
+
# Migrate legacy per-toolbar Hidden lists into the new Hidden toolbar
|
|
382
|
+
self._migrate_old_hidden_sets_to_hidden_toolbar()
|
|
383
|
+
|
|
384
|
+
# (Optional) Re-apply per-toolbar order after migration (since we removed actions)
|
|
385
|
+
for _tb in self.findChildren(DraggableToolBar):
|
|
386
|
+
key = getattr(_tb, "_settings_key", None)
|
|
387
|
+
if key:
|
|
388
|
+
self._restore_toolbar_order(_tb, str(key))
|
|
389
|
+
|
|
367
390
|
# Re-apply hidden state AFTER memberships (actions may have moved toolbars).
|
|
368
391
|
# This also guarantees correctness even if any toolbar was rebuilt/adjusted internally.
|
|
369
392
|
for _tb in self.findChildren(DraggableToolBar):
|
|
@@ -372,7 +395,9 @@ class ToolbarMixin:
|
|
|
372
395
|
except Exception:
|
|
373
396
|
pass
|
|
374
397
|
|
|
398
|
+
# Rebind ALL dropdowns after reorder/membership moves
|
|
375
399
|
self._rebind_view_dropdowns()
|
|
400
|
+
self._rebind_extract_luma_dropdown()
|
|
376
401
|
|
|
377
402
|
def _toolbar_containing_action(self, action: QAction):
|
|
378
403
|
from setiastro.saspro.shortcuts import DraggableToolBar
|
|
@@ -381,6 +406,140 @@ class ToolbarMixin:
|
|
|
381
406
|
return tb
|
|
382
407
|
return None
|
|
383
408
|
|
|
409
|
+
def _rebind_extract_luma_dropdown(self):
|
|
410
|
+
from PyQt6.QtWidgets import QMenu, QToolButton
|
|
411
|
+
from setiastro.saspro.luminancerecombine import LUMA_PROFILES
|
|
412
|
+
|
|
413
|
+
tb = self._toolbar_containing_action(self.act_extract_luma)
|
|
414
|
+
if not tb:
|
|
415
|
+
return
|
|
416
|
+
|
|
417
|
+
btn = tb.widgetForAction(self.act_extract_luma)
|
|
418
|
+
if not isinstance(btn, QToolButton):
|
|
419
|
+
return
|
|
420
|
+
|
|
421
|
+
menu = QMenu(btn)
|
|
422
|
+
|
|
423
|
+
# Ensure cache exists (created in _create_actions(), but be defensive)
|
|
424
|
+
if not hasattr(self, "_luma_sensor_actions"):
|
|
425
|
+
self._luma_sensor_actions = {} # key -> QAction
|
|
426
|
+
|
|
427
|
+
cur_method = str(getattr(self, "luma_method", "rec709"))
|
|
428
|
+
|
|
429
|
+
# ============================================================
|
|
430
|
+
# PATCH SITE #1 (Standard menu) <-- THIS IS WHERE "C" GOES
|
|
431
|
+
# Find your old block:
|
|
432
|
+
#
|
|
433
|
+
# # ---- Standard (use your QActionGroup, keep it simple) ----
|
|
434
|
+
# if getattr(self, "_luma_group", None) is not None:
|
|
435
|
+
# std_menu = QMenu(self.tr("Standard"), menu)
|
|
436
|
+
# std_menu.addActions(self._luma_group.actions())
|
|
437
|
+
# menu.addMenu(std_menu)
|
|
438
|
+
#
|
|
439
|
+
# Replace it with the block below.
|
|
440
|
+
# ============================================================
|
|
441
|
+
if getattr(self, "_luma_group", None) is not None:
|
|
442
|
+
# Sync standard checked states from self.luma_method
|
|
443
|
+
for a in self._luma_group.actions():
|
|
444
|
+
data = str(a.data() or "")
|
|
445
|
+
if data.startswith("sensor:"):
|
|
446
|
+
continue # standards only in Standard menu
|
|
447
|
+
a.blockSignals(True)
|
|
448
|
+
try:
|
|
449
|
+
a.setChecked(data == cur_method)
|
|
450
|
+
finally:
|
|
451
|
+
a.blockSignals(False)
|
|
452
|
+
|
|
453
|
+
# Add ONLY standard actions to the Standard menu (exclude sensors)
|
|
454
|
+
std_menu = QMenu(self.tr("Standard"), menu)
|
|
455
|
+
for a in self._luma_group.actions():
|
|
456
|
+
data = str(a.data() or "")
|
|
457
|
+
if data.startswith("sensor:"):
|
|
458
|
+
continue
|
|
459
|
+
std_menu.addAction(a)
|
|
460
|
+
menu.addMenu(std_menu)
|
|
461
|
+
|
|
462
|
+
# ---- Sensors (nested by category path) ----
|
|
463
|
+
sensors_root = QMenu(self.tr("Sensors"), menu)
|
|
464
|
+
|
|
465
|
+
# Build tree of submenus keyed by "Sensors/<path>"
|
|
466
|
+
submenu_cache: dict[str, QMenu] = {}
|
|
467
|
+
|
|
468
|
+
def get_or_make_path(root_menu: QMenu, path: str) -> QMenu:
|
|
469
|
+
parts = [p for p in path.split("/") if p.strip()]
|
|
470
|
+
cur = root_menu
|
|
471
|
+
acc = ""
|
|
472
|
+
for part in parts:
|
|
473
|
+
acc = f"{acc}/{part}" if acc else part
|
|
474
|
+
if acc not in submenu_cache:
|
|
475
|
+
sm = QMenu(self.tr(part), cur)
|
|
476
|
+
cur.addMenu(sm)
|
|
477
|
+
submenu_cache[acc] = sm
|
|
478
|
+
cur = submenu_cache[acc]
|
|
479
|
+
return cur
|
|
480
|
+
|
|
481
|
+
any_sensor = False
|
|
482
|
+
for key, prof in LUMA_PROFILES.items():
|
|
483
|
+
if not str(key).startswith("sensor:"):
|
|
484
|
+
continue
|
|
485
|
+
|
|
486
|
+
cat = str(prof.get("category", "Sensors/Other"))
|
|
487
|
+
group_path = cat.split("Sensors/", 1)[1] if "Sensors/" in cat else cat
|
|
488
|
+
parent_menu = get_or_make_path(sensors_root, group_path)
|
|
489
|
+
|
|
490
|
+
display_name = key.split("sensor:", 1)[1].strip()
|
|
491
|
+
desc = str(prof.get("description", display_name))
|
|
492
|
+
info = str(prof.get("info", "")).strip()
|
|
493
|
+
|
|
494
|
+
# ============================================================
|
|
495
|
+
# PATCH SITE #2 (Sensors become mutually exclusive with standards)
|
|
496
|
+
# - Cache the QAction so we don't pile up connections/actions
|
|
497
|
+
# - Add it to the SAME QActionGroup (exclusive) as standards
|
|
498
|
+
# - Do NOT use _pick_sensor; rely on QActionGroup.triggered
|
|
499
|
+
# ============================================================
|
|
500
|
+
act = self._luma_sensor_actions.get(key)
|
|
501
|
+
if act is None:
|
|
502
|
+
act = parent_menu.addAction(self.tr(display_name))
|
|
503
|
+
act.setCheckable(True)
|
|
504
|
+
act.setData(key) # IMPORTANT: enables group.triggered to set luma_method
|
|
505
|
+
|
|
506
|
+
# Put sensors into the SAME exclusive group so selecting one
|
|
507
|
+
# deselects everything else (standards and other sensors).
|
|
508
|
+
if getattr(self, "_luma_group", None) is not None:
|
|
509
|
+
self._luma_group.addAction(act)
|
|
510
|
+
|
|
511
|
+
self._luma_sensor_actions[key] = act
|
|
512
|
+
else:
|
|
513
|
+
# Reuse action, but re-add it to the correct submenu location
|
|
514
|
+
parent_menu.addAction(act)
|
|
515
|
+
act.setText(self.tr(display_name))
|
|
516
|
+
|
|
517
|
+
# Update UI info each bind (safe if translations change)
|
|
518
|
+
if info:
|
|
519
|
+
act.setStatusTip(info)
|
|
520
|
+
act.setToolTip(f"{desc}\n{info}")
|
|
521
|
+
else:
|
|
522
|
+
act.setToolTip(desc)
|
|
523
|
+
|
|
524
|
+
# Checked state reflects current selection
|
|
525
|
+
act.blockSignals(True)
|
|
526
|
+
try:
|
|
527
|
+
act.setChecked(cur_method == str(key))
|
|
528
|
+
finally:
|
|
529
|
+
act.blockSignals(False)
|
|
530
|
+
|
|
531
|
+
any_sensor = True
|
|
532
|
+
|
|
533
|
+
if any_sensor:
|
|
534
|
+
menu.addMenu(sensors_root)
|
|
535
|
+
|
|
536
|
+
btn.setMenu(menu)
|
|
537
|
+
btn.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup)
|
|
538
|
+
btn.setStyleSheet("""
|
|
539
|
+
QToolButton { color: #dcdcdc; }
|
|
540
|
+
QToolButton:pressed, QToolButton:checked { color: #DAA520; font-weight: 600; }
|
|
541
|
+
""")
|
|
542
|
+
|
|
384
543
|
|
|
385
544
|
def _rebind_view_dropdowns(self):
|
|
386
545
|
"""
|
|
@@ -442,7 +601,16 @@ class ToolbarMixin:
|
|
|
442
601
|
fit_menu.addAction(self.act_auto_fit_resize) # use the real action
|
|
443
602
|
btn_fit.setMenu(fit_menu)
|
|
444
603
|
btn_fit.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup)
|
|
604
|
+
tb = self._toolbar_containing_action(self.act_autostretch)
|
|
605
|
+
if tb:
|
|
606
|
+
btn = tb.widgetForAction(self.act_autostretch)
|
|
607
|
+
if isinstance(btn, QToolButton):
|
|
608
|
+
# ... build menu ...
|
|
609
|
+
btn.setMenu(menu)
|
|
610
|
+
btn.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup)
|
|
445
611
|
|
|
612
|
+
# IMPORTANT: re-apply style after action moves / rebind
|
|
613
|
+
self._style_toggle_toolbutton(btn)
|
|
446
614
|
|
|
447
615
|
def _bind_view_toolbar_menus(self, tb: DraggableToolBar):
|
|
448
616
|
# --- Display-Stretch menu ---
|
|
@@ -498,6 +666,12 @@ class ToolbarMixin:
|
|
|
498
666
|
btn_fit.setMenu(fit_menu)
|
|
499
667
|
btn_fit.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup)
|
|
500
668
|
|
|
669
|
+
def _linux_force_text_action(self, act: QAction, text: str) -> None:
|
|
670
|
+
"""On Linux, show text-only for this action (no theme icon)."""
|
|
671
|
+
if not sys.platform.startswith("linux"):
|
|
672
|
+
return
|
|
673
|
+
act.setIcon(QIcon()) # remove whatever theme icon got assigned
|
|
674
|
+
act.setText(text) # show the glyph/text
|
|
501
675
|
|
|
502
676
|
def _create_actions(self):
|
|
503
677
|
# File actions
|
|
@@ -619,30 +793,30 @@ class ToolbarMixin:
|
|
|
619
793
|
self.act_bake_display_stretch.triggered.connect(self._bake_display_stretch)
|
|
620
794
|
|
|
621
795
|
# --- Zoom controls ---
|
|
622
|
-
# --- Zoom controls (themed icons) ---
|
|
623
796
|
self.act_zoom_out = QAction(QIcon.fromTheme("zoom-out"), self.tr("Zoom Out"), self)
|
|
624
797
|
self.act_zoom_out.setStatusTip(self.tr("Zoom out"))
|
|
625
798
|
self.act_zoom_out.setShortcuts([QKeySequence("Ctrl+-")])
|
|
626
799
|
self.act_zoom_out.triggered.connect(lambda: self._zoom_step_active(-1))
|
|
800
|
+
self._linux_force_text_action(self.act_zoom_out, "−") # true minus
|
|
627
801
|
|
|
628
802
|
self.act_zoom_in = QAction(QIcon.fromTheme("zoom-in"), self.tr("Zoom In"), self)
|
|
629
803
|
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
|
-
])
|
|
804
|
+
self.act_zoom_in.setShortcuts([QKeySequence("Ctrl++"), QKeySequence("Ctrl+=")])
|
|
634
805
|
self.act_zoom_in.triggered.connect(lambda: self._zoom_step_active(+1))
|
|
806
|
+
self._linux_force_text_action(self.act_zoom_in, "+")
|
|
635
807
|
|
|
636
808
|
self.act_zoom_1_1 = QAction(QIcon.fromTheme("zoom-original"), self.tr("1:1"), self)
|
|
637
809
|
self.act_zoom_1_1.setStatusTip(self.tr("Zoom to 100% (pixel-for-pixel)"))
|
|
638
810
|
self.act_zoom_1_1.setShortcut(QKeySequence("Ctrl+1"))
|
|
639
811
|
self.act_zoom_1_1.triggered.connect(self._zoom_active_1_1)
|
|
812
|
+
self._linux_force_text_action(self.act_zoom_1_1, "1:1")
|
|
640
813
|
|
|
641
814
|
self.act_zoom_fit = QAction(QIcon.fromTheme("zoom-fit-best"), self.tr("Fit"), self)
|
|
642
815
|
self.act_zoom_fit.setStatusTip(self.tr("Fit image to current window"))
|
|
643
816
|
self.act_zoom_fit.setShortcut(QKeySequence("Ctrl+0"))
|
|
644
817
|
self.act_zoom_fit.triggered.connect(self._zoom_active_fit)
|
|
645
818
|
self.act_zoom_fit.setCheckable(True)
|
|
819
|
+
self._linux_force_text_action(self.act_zoom_fit, "Fit")
|
|
646
820
|
|
|
647
821
|
self.act_auto_fit_resize = QAction(self.tr("Auto-fit on Resize"), self)
|
|
648
822
|
self.act_auto_fit_resize.setCheckable(True)
|
|
@@ -661,7 +835,13 @@ class ToolbarMixin:
|
|
|
661
835
|
self.act_paste_view.setShortcut("Ctrl+Shift+V")
|
|
662
836
|
self.act_copy_view.triggered.connect(self._copy_active_view)
|
|
663
837
|
self.act_paste_view.triggered.connect(self._paste_active_view)
|
|
664
|
-
|
|
838
|
+
# --- Edit: Mono -> RGB (triplicate channels) ---
|
|
839
|
+
self.act_mono_to_rgb = QAction(self.tr("Convert Mono to RGB"), self)
|
|
840
|
+
self.act_mono_to_rgb.setStatusTip(self.tr("Convert a mono image to RGB by duplicating the channel"))
|
|
841
|
+
self.act_mono_to_rgb.triggered.connect(self._convert_mono_to_rgb_active)
|
|
842
|
+
self.act_swap_rb = QAction(self.tr("Swap R and B Channels"), self)
|
|
843
|
+
self.act_swap_rb.setStatusTip(self.tr("Swap red and blue channels on the active RGB image"))
|
|
844
|
+
self.act_swap_rb.triggered.connect(self._swap_rb_active)
|
|
665
845
|
# Functions
|
|
666
846
|
self.act_crop = QAction(QIcon(cropicon_path), self.tr("Crop..."), self)
|
|
667
847
|
self.act_crop.setStatusTip(self.tr("Crop / rotate with handles"))
|
|
@@ -725,11 +905,17 @@ class ToolbarMixin:
|
|
|
725
905
|
self.act_linear_fit.setShortcut("Ctrl+L")
|
|
726
906
|
self.act_linear_fit.triggered.connect(self._open_linear_fit)
|
|
727
907
|
|
|
728
|
-
self.act_remove_green = QAction(QIcon(green_path), self.tr("Remove Green..."), self)
|
|
908
|
+
self.act_remove_green = QAction(QIcon(green_path), self.tr("Remove Green (SCNR)..."), self)
|
|
729
909
|
self.act_remove_green.setToolTip(self.tr("SCNR-style green channel removal."))
|
|
730
910
|
self.act_remove_green.setIconVisibleInMenu(True)
|
|
731
911
|
self.act_remove_green.triggered.connect(self._open_remove_green)
|
|
732
912
|
|
|
913
|
+
# Texture and Clarity
|
|
914
|
+
self.act_texture_clarity = QAction(QIcon(texture_clarity_path), self.tr("Texture and Clarity..."), self)
|
|
915
|
+
self.act_texture_clarity.setToolTip(self.tr("Enhance texture and clarity using Unsharp Masking"))
|
|
916
|
+
self.act_texture_clarity.setIconVisibleInMenu(True)
|
|
917
|
+
self.act_texture_clarity.triggered.connect(self._open_texture_clarity)
|
|
918
|
+
|
|
733
919
|
self.act_background_neutral = QAction(QIcon(neutral_path), self.tr("Background Neutralization..."), self)
|
|
734
920
|
self.act_background_neutral.setStatusTip(self.tr("Neutralize background color balance using a sampled region"))
|
|
735
921
|
self.act_background_neutral.setIconVisibleInMenu(True)
|
|
@@ -767,6 +953,7 @@ class ToolbarMixin:
|
|
|
767
953
|
self.luma_method = getattr(self, "luma_method", "rec709") # default
|
|
768
954
|
self._luma_group = QActionGroup(self)
|
|
769
955
|
self._luma_group.setExclusive(True)
|
|
956
|
+
self._luma_sensor_actions = {} # key -> QAction
|
|
770
957
|
|
|
771
958
|
def _mk(method_key, text):
|
|
772
959
|
act = QAction(text, self, checkable=True)
|
|
@@ -786,8 +973,10 @@ class ToolbarMixin:
|
|
|
786
973
|
|
|
787
974
|
# update method when user picks from the menu
|
|
788
975
|
def _on_luma_pick(act):
|
|
789
|
-
|
|
790
|
-
|
|
976
|
+
key = act.data()
|
|
977
|
+
if key is None:
|
|
978
|
+
return
|
|
979
|
+
self.luma_method = str(key)
|
|
791
980
|
try:
|
|
792
981
|
self.settings.setValue("ui/luminance_method", self.luma_method)
|
|
793
982
|
except Exception:
|
|
@@ -947,6 +1136,13 @@ class ToolbarMixin:
|
|
|
947
1136
|
self.act_ppp.setStatusTip(self.tr("Pick the perfect palette for your image"))
|
|
948
1137
|
self.act_ppp.triggered.connect(self._open_ppp_tool)
|
|
949
1138
|
|
|
1139
|
+
self.act_narrowband_normalization = QAction(QIcon(narrowbandnormalization_path), self.tr("Narrowband Normalization..."), self)
|
|
1140
|
+
self.act_narrowband_normalization.setStatusTip(
|
|
1141
|
+
self.tr("Normalize HOO/SHO/HSO/HOS (PixelMath port by Bill Blanshan and Mike Cranfield)")
|
|
1142
|
+
)
|
|
1143
|
+
|
|
1144
|
+
self.act_narrowband_normalization.triggered.connect(self._open_narrowband_normalization_tool)
|
|
1145
|
+
|
|
950
1146
|
self.act_nbtorgb = QAction(QIcon(nbtorgb_path), self.tr("NB->RGB Stars..."), self)
|
|
951
1147
|
self.act_nbtorgb.setStatusTip(self.tr("Combine narrowband to RGB with optional OSC stars"))
|
|
952
1148
|
self.act_nbtorgb.setIconVisibleInMenu(True)
|
|
@@ -994,6 +1190,11 @@ class ToolbarMixin:
|
|
|
994
1190
|
self.act_live_stacking.setStatusTip(self.tr("Live monitor and stack incoming frames"))
|
|
995
1191
|
self.act_live_stacking.triggered.connect(self._open_live_stacking)
|
|
996
1192
|
|
|
1193
|
+
self.act_planetary_stacker = QAction(QIcon(planetarystacker_path), self.tr("Planetary Stacker..."), self)
|
|
1194
|
+
self.act_planetary_stacker.setIconVisibleInMenu(True)
|
|
1195
|
+
self.act_planetary_stacker.setStatusTip(self.tr("Stack SER videos (planetary/solar/lunar)"))
|
|
1196
|
+
self.act_planetary_stacker.triggered.connect(self._open_planetary_stacker)
|
|
1197
|
+
|
|
997
1198
|
self.act_plate_solve = QAction(QIcon(platesolve_path), self.tr("Plate Solver..."), self)
|
|
998
1199
|
self.act_plate_solve.setIconVisibleInMenu(True)
|
|
999
1200
|
self.act_plate_solve.setStatusTip(self.tr("Solve WCS/SIP for the active image or a file"))
|
|
@@ -1105,6 +1306,13 @@ class ToolbarMixin:
|
|
|
1105
1306
|
self.act_copy_astrometry = QAction(self.tr("Copy Astrometric Solution..."), self)
|
|
1106
1307
|
self.act_copy_astrometry.triggered.connect(self._open_copy_astrometry)
|
|
1107
1308
|
|
|
1309
|
+
self.act_acv_exporter = QAction(self.tr("Astro Catalogue Viewer Exporter..."), self)
|
|
1310
|
+
self.act_acv_exporter.setIconVisibleInMenu(True)
|
|
1311
|
+
self.act_acv_exporter.setStatusTip(self.tr("Export current view into Astro Catalogue Viewer folders"))
|
|
1312
|
+
self.act_acv_exporter.triggered.connect(self._open_acv_exporter)
|
|
1313
|
+
|
|
1314
|
+
|
|
1315
|
+
|
|
1108
1316
|
# Create Mask
|
|
1109
1317
|
self.act_create_mask = QAction(QIcon(maskcreate_path), self.tr("Create Mask..."), self)
|
|
1110
1318
|
self.act_create_mask.setIconVisibleInMenu(True)
|
|
@@ -1183,6 +1391,7 @@ class ToolbarMixin:
|
|
|
1183
1391
|
reg("nbtorgb", self.act_nbtorgb)
|
|
1184
1392
|
reg("freqsep", self.act_freqsep)
|
|
1185
1393
|
reg("selective_color", self.act_selective_color)
|
|
1394
|
+
reg("narrowband_normalization", self.act_narrowband_normalization)
|
|
1186
1395
|
reg("contsub", self.act_contsub)
|
|
1187
1396
|
reg("abe", self.act_abe)
|
|
1188
1397
|
reg("create_mask", self.act_create_mask)
|
|
@@ -1191,6 +1400,7 @@ class ToolbarMixin:
|
|
|
1191
1400
|
reg("add_stars", self.act_add_stars)
|
|
1192
1401
|
reg("pedestal", self.act_pedestal)
|
|
1193
1402
|
reg("remove_green", self.act_remove_green)
|
|
1403
|
+
reg("texture_clarity", self.act_texture_clarity)
|
|
1194
1404
|
reg("background_neutral", self.act_background_neutral)
|
|
1195
1405
|
reg("white_balance", self.act_white_balance)
|
|
1196
1406
|
reg("sfcc", self.act_sfcc)
|
|
@@ -1207,7 +1417,7 @@ class ToolbarMixin:
|
|
|
1207
1417
|
reg("pixel_math", self.act_pixelmath)
|
|
1208
1418
|
reg("signature_insert", self.act_signature)
|
|
1209
1419
|
reg("halo_b_gon", self.act_halobgon)
|
|
1210
|
-
|
|
1420
|
+
reg("planetary_stacker", self.act_planetary_stacker)
|
|
1211
1421
|
reg("multiscale_decomp", self.act_multiscale_decomp)
|
|
1212
1422
|
reg("geom_invert", self.act_geom_invert)
|
|
1213
1423
|
reg("geom_flip_horizontal", self.act_geom_flip_h)
|
|
@@ -1245,6 +1455,83 @@ class ToolbarMixin:
|
|
|
1245
1455
|
reg("view_bundles", self.act_view_bundles)
|
|
1246
1456
|
reg("function_bundles", self.act_function_bundles)
|
|
1247
1457
|
|
|
1458
|
+
def _reset_all_toolbars_to_factory(self):
|
|
1459
|
+
"""
|
|
1460
|
+
Clears all persisted toolbar membership/order/hidden state so the UI
|
|
1461
|
+
returns to the factory layout defined in code.
|
|
1462
|
+
"""
|
|
1463
|
+
if not hasattr(self, "settings"):
|
|
1464
|
+
return
|
|
1465
|
+
|
|
1466
|
+
# 1) Clear global toolbar persistence
|
|
1467
|
+
keys_to_clear = [
|
|
1468
|
+
"Toolbar/Assignments",
|
|
1469
|
+
"Toolbar/HiddenPrev",
|
|
1470
|
+
"Toolbar/HiddenMigrationDone",
|
|
1471
|
+
|
|
1472
|
+
# Per-toolbar order lists
|
|
1473
|
+
"Toolbar/View",
|
|
1474
|
+
"Toolbar/Functions",
|
|
1475
|
+
"Toolbar/Cosmic",
|
|
1476
|
+
"Toolbar/Tools",
|
|
1477
|
+
"Toolbar/Geometry",
|
|
1478
|
+
"Toolbar/StarStuff",
|
|
1479
|
+
"Toolbar/Masks",
|
|
1480
|
+
"Toolbar/WhatsInMy",
|
|
1481
|
+
"Toolbar/Bundles",
|
|
1482
|
+
"Toolbar/Hidden",
|
|
1483
|
+
]
|
|
1484
|
+
|
|
1485
|
+
for k in keys_to_clear:
|
|
1486
|
+
try:
|
|
1487
|
+
self.settings.remove(k)
|
|
1488
|
+
except Exception:
|
|
1489
|
+
pass
|
|
1490
|
+
|
|
1491
|
+
# 2) Clear legacy hidden lists (old system) if they exist
|
|
1492
|
+
# (These are the ones like "Toolbar/Tools/Hidden")
|
|
1493
|
+
try:
|
|
1494
|
+
# brute-force remove known ones
|
|
1495
|
+
legacy_hidden = [
|
|
1496
|
+
"Toolbar/View/Hidden",
|
|
1497
|
+
"Toolbar/Functions/Hidden",
|
|
1498
|
+
"Toolbar/Cosmic/Hidden",
|
|
1499
|
+
"Toolbar/Tools/Hidden",
|
|
1500
|
+
"Toolbar/Geometry/Hidden",
|
|
1501
|
+
"Toolbar/StarStuff/Hidden",
|
|
1502
|
+
"Toolbar/Masks/Hidden",
|
|
1503
|
+
"Toolbar/WhatsInMy/Hidden",
|
|
1504
|
+
"Toolbar/Bundles/Hidden",
|
|
1505
|
+
]
|
|
1506
|
+
for k in legacy_hidden:
|
|
1507
|
+
self.settings.remove(k)
|
|
1508
|
+
except Exception:
|
|
1509
|
+
pass
|
|
1510
|
+
|
|
1511
|
+
try:
|
|
1512
|
+
self.settings.sync()
|
|
1513
|
+
except Exception:
|
|
1514
|
+
pass
|
|
1515
|
+
|
|
1516
|
+
# 3) Rebuild toolbars from code defaults
|
|
1517
|
+
# Safest approach: remove existing toolbars and rebuild.
|
|
1518
|
+
from setiastro.saspro.shortcuts import DraggableToolBar
|
|
1519
|
+
for tb in self.findChildren(DraggableToolBar):
|
|
1520
|
+
try:
|
|
1521
|
+
self.removeToolBar(tb)
|
|
1522
|
+
tb.deleteLater()
|
|
1523
|
+
except Exception:
|
|
1524
|
+
pass
|
|
1525
|
+
|
|
1526
|
+
# Build fresh
|
|
1527
|
+
self._init_toolbar()
|
|
1528
|
+
|
|
1529
|
+
# Optional: ensure Hidden stays hidden
|
|
1530
|
+
tb_hidden = self._hidden_toolbar()
|
|
1531
|
+
if tb_hidden:
|
|
1532
|
+
tb_hidden.setVisible(False)
|
|
1533
|
+
|
|
1534
|
+
|
|
1248
1535
|
def _restore_toolbar_order(self, tb, settings_key: str):
|
|
1249
1536
|
"""
|
|
1250
1537
|
Restore toolbar action order from QSettings, using command_id/objectName.
|
|
@@ -1356,6 +1643,188 @@ class ToolbarMixin:
|
|
|
1356
1643
|
if key:
|
|
1357
1644
|
self._restore_toolbar_order(tb, str(key))
|
|
1358
1645
|
|
|
1646
|
+
def _hidden_toolbar(self):
|
|
1647
|
+
from setiastro.saspro.shortcuts import DraggableToolBar
|
|
1648
|
+
for tb in self.findChildren(DraggableToolBar):
|
|
1649
|
+
if getattr(tb, "_settings_key", None) == "Toolbar/Hidden" or tb.objectName() == "Hidden":
|
|
1650
|
+
return tb
|
|
1651
|
+
return None
|
|
1652
|
+
|
|
1653
|
+
def _toolbar_by_settings_key(self, key: str):
|
|
1654
|
+
from setiastro.saspro.shortcuts import DraggableToolBar
|
|
1655
|
+
for tb in self.findChildren(DraggableToolBar):
|
|
1656
|
+
if getattr(tb, "_settings_key", None) == key:
|
|
1657
|
+
return tb
|
|
1658
|
+
return None
|
|
1659
|
+
|
|
1660
|
+
def _action_cid(self, act):
|
|
1661
|
+
return str(act.property("command_id") or act.objectName() or "")
|
|
1662
|
+
|
|
1663
|
+
def _hide_action_to_hidden_toolbar(self, act):
|
|
1664
|
+
"""
|
|
1665
|
+
Move action to the Hidden toolbar, remembering where it came from.
|
|
1666
|
+
"""
|
|
1667
|
+
tb_hidden = self._hidden_toolbar()
|
|
1668
|
+
if not tb_hidden:
|
|
1669
|
+
return
|
|
1670
|
+
|
|
1671
|
+
cid = self._action_cid(act)
|
|
1672
|
+
if not cid:
|
|
1673
|
+
return
|
|
1674
|
+
|
|
1675
|
+
# Find current toolbar (if any) and remember it
|
|
1676
|
+
cur_tb = self._toolbar_containing_action(act)
|
|
1677
|
+
if cur_tb and getattr(cur_tb, "_settings_key", None) and cur_tb is not tb_hidden:
|
|
1678
|
+
prev_key = str(cur_tb._settings_key)
|
|
1679
|
+
|
|
1680
|
+
# Persist "previous toolbar" so unhide can restore
|
|
1681
|
+
try:
|
|
1682
|
+
raw = self.settings.value("Toolbar/HiddenPrev", "", type=str) or ""
|
|
1683
|
+
except Exception:
|
|
1684
|
+
raw = ""
|
|
1685
|
+
try:
|
|
1686
|
+
prev = json.loads(raw) if raw else {}
|
|
1687
|
+
except Exception:
|
|
1688
|
+
prev = {}
|
|
1689
|
+
prev[cid] = prev_key
|
|
1690
|
+
self.settings.setValue("Toolbar/HiddenPrev", json.dumps(prev))
|
|
1691
|
+
|
|
1692
|
+
# Remove from all toolbars, add to hidden
|
|
1693
|
+
from setiastro.saspro.shortcuts import DraggableToolBar
|
|
1694
|
+
for t in self.findChildren(DraggableToolBar):
|
|
1695
|
+
if act in t.actions():
|
|
1696
|
+
t.removeAction(act)
|
|
1697
|
+
|
|
1698
|
+
tb_hidden.addAction(act)
|
|
1699
|
+
|
|
1700
|
+
# Update assignment mapping so restore works on next launch
|
|
1701
|
+
tb_hidden._update_assignment_for_action(act)
|
|
1702
|
+
|
|
1703
|
+
# Persist ordering (optional but nice)
|
|
1704
|
+
try:
|
|
1705
|
+
tb_hidden._persist_order()
|
|
1706
|
+
except Exception:
|
|
1707
|
+
pass
|
|
1708
|
+
if cur_tb:
|
|
1709
|
+
try:
|
|
1710
|
+
cur_tb._persist_order()
|
|
1711
|
+
except Exception:
|
|
1712
|
+
pass
|
|
1713
|
+
|
|
1714
|
+
def _unhide_action_from_hidden_toolbar(self, act):
|
|
1715
|
+
"""
|
|
1716
|
+
Move action out of Hidden toolbar back to its remembered toolbar.
|
|
1717
|
+
Falls back to Toolbar/View if missing.
|
|
1718
|
+
"""
|
|
1719
|
+
tb_hidden = self._hidden_toolbar()
|
|
1720
|
+
if not tb_hidden:
|
|
1721
|
+
return
|
|
1722
|
+
|
|
1723
|
+
cid = self._action_cid(act)
|
|
1724
|
+
if not cid:
|
|
1725
|
+
return
|
|
1726
|
+
|
|
1727
|
+
# Load prev mapping
|
|
1728
|
+
try:
|
|
1729
|
+
raw = self.settings.value("Toolbar/HiddenPrev", "", type=str) or ""
|
|
1730
|
+
except Exception:
|
|
1731
|
+
raw = ""
|
|
1732
|
+
try:
|
|
1733
|
+
prev = json.loads(raw) if raw else {}
|
|
1734
|
+
except Exception:
|
|
1735
|
+
prev = {}
|
|
1736
|
+
|
|
1737
|
+
target_key = prev.get(cid) or "Toolbar/View"
|
|
1738
|
+
tb_target = self._toolbar_by_settings_key(target_key) or self._toolbar_by_settings_key("Toolbar/View")
|
|
1739
|
+
if not tb_target:
|
|
1740
|
+
return
|
|
1741
|
+
|
|
1742
|
+
tb_hidden.removeAction(act)
|
|
1743
|
+
tb_target.addAction(act)
|
|
1744
|
+
|
|
1745
|
+
# Update assignment mapping
|
|
1746
|
+
tb_target._update_assignment_for_action(act)
|
|
1747
|
+
|
|
1748
|
+
# Cleanup prev mapping (optional)
|
|
1749
|
+
if cid in prev:
|
|
1750
|
+
prev.pop(cid, None)
|
|
1751
|
+
self.settings.setValue("Toolbar/HiddenPrev", json.dumps(prev))
|
|
1752
|
+
|
|
1753
|
+
try:
|
|
1754
|
+
tb_target._persist_order()
|
|
1755
|
+
tb_hidden._persist_order()
|
|
1756
|
+
except Exception:
|
|
1757
|
+
pass
|
|
1758
|
+
|
|
1759
|
+
def _migrate_old_hidden_sets_to_hidden_toolbar(self):
|
|
1760
|
+
"""
|
|
1761
|
+
One-time migration:
|
|
1762
|
+
Old system: per-toolbar QSettings lists at "<ToolbarKey>/Hidden" storing action IDs
|
|
1763
|
+
New system: move those actions into the dedicated Hidden toolbar (Toolbar/Hidden)
|
|
1764
|
+
and persist membership via Toolbar/Assignments.
|
|
1765
|
+
"""
|
|
1766
|
+
if not hasattr(self, "settings"):
|
|
1767
|
+
return
|
|
1768
|
+
|
|
1769
|
+
# Run once
|
|
1770
|
+
if self.settings.value("Toolbar/HiddenMigrationDone", False, type=bool):
|
|
1771
|
+
return
|
|
1772
|
+
|
|
1773
|
+
tb_hidden = self._hidden_toolbar()
|
|
1774
|
+
if not tb_hidden:
|
|
1775
|
+
return
|
|
1776
|
+
|
|
1777
|
+
from setiastro.saspro.shortcuts import DraggableToolBar
|
|
1778
|
+
|
|
1779
|
+
# If Hidden toolbar already has actions, assume user is already on new system
|
|
1780
|
+
# but we still can migrate any remaining old keys.
|
|
1781
|
+
# (We won't early-return.)
|
|
1782
|
+
|
|
1783
|
+
for tb in self.findChildren(DraggableToolBar):
|
|
1784
|
+
k = getattr(tb, "_settings_key", None)
|
|
1785
|
+
if not k or str(k) == "Toolbar/Hidden":
|
|
1786
|
+
continue
|
|
1787
|
+
|
|
1788
|
+
# Old storage key
|
|
1789
|
+
old_key = f"{k}/Hidden"
|
|
1790
|
+
|
|
1791
|
+
# Read old hidden list (Qt backend may return list OR str)
|
|
1792
|
+
try:
|
|
1793
|
+
raw = self.settings.value(old_key, [], type=list)
|
|
1794
|
+
except Exception:
|
|
1795
|
+
raw = self.settings.value(old_key, []) # fallback
|
|
1796
|
+
|
|
1797
|
+
if not raw:
|
|
1798
|
+
continue
|
|
1799
|
+
|
|
1800
|
+
if isinstance(raw, str):
|
|
1801
|
+
raw_list = [raw]
|
|
1802
|
+
else:
|
|
1803
|
+
try:
|
|
1804
|
+
raw_list = list(raw)
|
|
1805
|
+
except Exception:
|
|
1806
|
+
raw_list = []
|
|
1807
|
+
|
|
1808
|
+
hidden_ids = {str(x) for x in raw_list if str(x).strip()}
|
|
1809
|
+
if not hidden_ids:
|
|
1810
|
+
# Clear junk so we don’t keep trying
|
|
1811
|
+
self.settings.setValue(old_key, [])
|
|
1812
|
+
continue
|
|
1813
|
+
|
|
1814
|
+
# Move matching actions into Hidden toolbar
|
|
1815
|
+
for act in list(tb.actions()):
|
|
1816
|
+
if act is None or act.isSeparator():
|
|
1817
|
+
continue
|
|
1818
|
+
cid = str(act.property("command_id") or act.objectName() or "")
|
|
1819
|
+
if cid and cid in hidden_ids:
|
|
1820
|
+
self._hide_action_to_hidden_toolbar(act)
|
|
1821
|
+
|
|
1822
|
+
# Clear old storage so you don't keep re-migrating
|
|
1823
|
+
self.settings.setValue(old_key, [])
|
|
1824
|
+
|
|
1825
|
+
# Mark migration complete
|
|
1826
|
+
self.settings.setValue("Toolbar/HiddenMigrationDone", True)
|
|
1827
|
+
|
|
1359
1828
|
|
|
1360
1829
|
def update_undo_redo_action_labels(self):
|
|
1361
1830
|
if not hasattr(self, "act_undo"): # not built yet
|
|
@@ -1477,4 +1946,10 @@ class ToolbarMixin:
|
|
|
1477
1946
|
if hasattr(self, "act_hide_mask"):
|
|
1478
1947
|
self.act_hide_mask.setEnabled(has_mask and overlay_on)
|
|
1479
1948
|
|
|
1480
|
-
|
|
1949
|
+
def _style_toggle_toolbutton(self, btn: QToolButton):
|
|
1950
|
+
# Make sure the action visually shows "on" state
|
|
1951
|
+
btn.setCheckable(True) # safe even if already
|
|
1952
|
+
btn.setStyleSheet("""
|
|
1953
|
+
QToolButton { color: #dcdcdc; }
|
|
1954
|
+
QToolButton:checked { color: #DAA520; font-weight: 600; }
|
|
1955
|
+
""")
|