setiastrosuitepro 1.6.1.post1__py3-none-any.whl → 1.6.4__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/Background_startup.jpg +0 -0
- setiastro/images/rotatearbitrary.png +0 -0
- setiastro/qml/ResourceMonitor.qml +126 -0
- setiastro/saspro/__main__.py +162 -25
- setiastro/saspro/_generated/build_info.py +2 -1
- setiastro/saspro/abe.py +62 -11
- setiastro/saspro/aberration_ai.py +3 -3
- setiastro/saspro/add_stars.py +5 -2
- setiastro/saspro/astrobin_exporter.py +3 -0
- setiastro/saspro/astrospike_python.py +3 -1
- setiastro/saspro/autostretch.py +4 -2
- setiastro/saspro/backgroundneutral.py +60 -9
- setiastro/saspro/batch_convert.py +3 -0
- setiastro/saspro/batch_renamer.py +3 -0
- setiastro/saspro/blemish_blaster.py +3 -0
- setiastro/saspro/blink_comparator_pro.py +474 -251
- setiastro/saspro/cheat_sheet.py +50 -15
- setiastro/saspro/clahe.py +27 -1
- setiastro/saspro/comet_stacking.py +103 -38
- setiastro/saspro/convo.py +3 -0
- setiastro/saspro/copyastro.py +3 -0
- setiastro/saspro/cosmicclarity.py +70 -45
- setiastro/saspro/crop_dialog_pro.py +28 -1
- setiastro/saspro/curve_editor_pro.py +18 -0
- setiastro/saspro/debayer.py +3 -0
- setiastro/saspro/doc_manager.py +40 -17
- setiastro/saspro/fitsmodifier.py +3 -0
- setiastro/saspro/frequency_separation.py +8 -2
- setiastro/saspro/function_bundle.py +18 -16
- setiastro/saspro/generate_translations.py +715 -1
- setiastro/saspro/ghs_dialog_pro.py +3 -0
- setiastro/saspro/graxpert.py +3 -0
- setiastro/saspro/gui/main_window.py +364 -92
- setiastro/saspro/gui/mixins/dock_mixin.py +119 -7
- setiastro/saspro/gui/mixins/file_mixin.py +7 -0
- setiastro/saspro/gui/mixins/geometry_mixin.py +105 -5
- setiastro/saspro/gui/mixins/menu_mixin.py +29 -0
- setiastro/saspro/gui/mixins/toolbar_mixin.py +33 -10
- setiastro/saspro/gui/statistics_dialog.py +47 -0
- setiastro/saspro/halobgon.py +29 -3
- setiastro/saspro/histogram.py +3 -0
- setiastro/saspro/history_explorer.py +2 -0
- setiastro/saspro/i18n.py +22 -10
- setiastro/saspro/image_combine.py +3 -0
- setiastro/saspro/image_peeker_pro.py +3 -0
- setiastro/saspro/imageops/stretch.py +5 -13
- setiastro/saspro/isophote.py +3 -0
- setiastro/saspro/legacy/numba_utils.py +64 -47
- setiastro/saspro/linear_fit.py +3 -0
- setiastro/saspro/live_stacking.py +13 -2
- setiastro/saspro/mask_creation.py +3 -0
- setiastro/saspro/mfdeconv.py +5 -0
- setiastro/saspro/morphology.py +30 -5
- setiastro/saspro/multiscale_decomp.py +713 -256
- setiastro/saspro/nbtorgb_stars.py +12 -2
- setiastro/saspro/numba_utils.py +148 -47
- setiastro/saspro/ops/scripts.py +77 -17
- setiastro/saspro/ops/settings.py +1 -43
- setiastro/saspro/perfect_palette_picker.py +1 -0
- setiastro/saspro/pixelmath.py +6 -2
- setiastro/saspro/plate_solver.py +1 -0
- setiastro/saspro/remove_green.py +18 -1
- setiastro/saspro/remove_stars.py +136 -162
- setiastro/saspro/remove_stars_preset.py +55 -13
- setiastro/saspro/resources.py +36 -10
- setiastro/saspro/rgb_combination.py +1 -0
- setiastro/saspro/rgbalign.py +4 -4
- setiastro/saspro/save_options.py +1 -0
- setiastro/saspro/selective_color.py +79 -20
- setiastro/saspro/sfcc.py +50 -8
- setiastro/saspro/shortcuts.py +94 -21
- setiastro/saspro/signature_insert.py +3 -0
- setiastro/saspro/stacking_suite.py +924 -446
- setiastro/saspro/star_alignment.py +291 -331
- setiastro/saspro/star_spikes.py +116 -32
- setiastro/saspro/star_stretch.py +38 -1
- setiastro/saspro/stat_stretch.py +35 -3
- setiastro/saspro/status_log_dock.py +1 -1
- setiastro/saspro/subwindow.py +63 -2
- setiastro/saspro/supernovaasteroidhunter.py +3 -0
- setiastro/saspro/swap_manager.py +77 -42
- setiastro/saspro/translations/all_source_strings.json +4726 -0
- setiastro/saspro/translations/ar_translations.py +4096 -0
- setiastro/saspro/translations/de_translations.py +441 -446
- setiastro/saspro/translations/es_translations.py +278 -32
- setiastro/saspro/translations/fr_translations.py +280 -32
- setiastro/saspro/translations/hi_translations.py +3803 -0
- setiastro/saspro/translations/integrate_translations.py +38 -1
- setiastro/saspro/translations/it_translations.py +1211 -145
- setiastro/saspro/translations/ja_translations.py +556 -307
- setiastro/saspro/translations/pt_translations.py +3316 -3322
- setiastro/saspro/translations/ru_translations.py +3082 -0
- setiastro/saspro/translations/saspro_ar.qm +0 -0
- setiastro/saspro/translations/saspro_ar.ts +16019 -0
- 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 +14855 -0
- 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 +11835 -0
- setiastro/saspro/translations/saspro_sw.qm +0 -0
- setiastro/saspro/translations/saspro_sw.ts +15237 -0
- setiastro/saspro/translations/saspro_uk.qm +0 -0
- setiastro/saspro/translations/saspro_uk.ts +15248 -0
- setiastro/saspro/translations/saspro_zh.qm +0 -0
- setiastro/saspro/translations/saspro_zh.ts +10581 -7812
- setiastro/saspro/translations/sw_translations.py +3897 -0
- setiastro/saspro/translations/uk_translations.py +3929 -0
- setiastro/saspro/translations/zh_translations.py +283 -32
- setiastro/saspro/versioning.py +36 -5
- setiastro/saspro/view_bundle.py +20 -17
- setiastro/saspro/wavescale_hdr.py +22 -1
- setiastro/saspro/wavescalede.py +23 -1
- setiastro/saspro/whitebalance.py +39 -3
- setiastro/saspro/widgets/minigame/game.js +991 -0
- setiastro/saspro/widgets/minigame/index.html +53 -0
- setiastro/saspro/widgets/minigame/style.css +241 -0
- setiastro/saspro/widgets/resource_monitor.py +263 -0
- setiastro/saspro/widgets/spinboxes.py +18 -0
- setiastro/saspro/widgets/wavelet_utils.py +52 -20
- setiastro/saspro/wimi.py +100 -80
- setiastro/saspro/wims.py +33 -33
- setiastro/saspro/window_shelf.py +2 -2
- {setiastrosuitepro-1.6.1.post1.dist-info → setiastrosuitepro-1.6.4.dist-info}/METADATA +15 -4
- {setiastrosuitepro-1.6.1.post1.dist-info → setiastrosuitepro-1.6.4.dist-info}/RECORD +139 -115
- {setiastrosuitepro-1.6.1.post1.dist-info → setiastrosuitepro-1.6.4.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.6.1.post1.dist-info → setiastrosuitepro-1.6.4.dist-info}/entry_points.txt +0 -0
- {setiastrosuitepro-1.6.1.post1.dist-info → setiastrosuitepro-1.6.4.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.6.1.post1.dist-info → setiastrosuitepro-1.6.4.dist-info}/licenses/license.txt +0 -0
|
@@ -128,7 +128,8 @@ from PyQt6.QtGui import (QPixmap, QColor, QIcon, QKeySequence, QShortcut,
|
|
|
128
128
|
)
|
|
129
129
|
|
|
130
130
|
# ----- QtCore -----
|
|
131
|
-
from PyQt6.QtCore import (Qt, pyqtSignal, QCoreApplication, QTimer, QSize, QSignalBlocker, QModelIndex, QThread, QUrl, QSettings, QEvent, QByteArray, QObject
|
|
131
|
+
from PyQt6.QtCore import (Qt, pyqtSignal, QCoreApplication, QTimer, QSize, QSignalBlocker, QModelIndex, QThread, QUrl, QSettings, QEvent, QByteArray, QObject,
|
|
132
|
+
QPropertyAnimation, QEasingCurve, QElapsedTimer
|
|
132
133
|
)
|
|
133
134
|
|
|
134
135
|
from PyQt6.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
|
|
@@ -186,7 +187,7 @@ from setiastro.saspro.resources import (
|
|
|
186
187
|
platesolve_path, psf_path, supernova_path, starregistration_path,
|
|
187
188
|
stacking_path, pedestal_icon_path, starspike_path, aperture_path,
|
|
188
189
|
jwstpupil_path, signature_icon_path, livestacking_path, hrdiagram_path,
|
|
189
|
-
convoicon_path, spcc_icon_path, sasp_data_path, exoicon_path, peeker_icon,
|
|
190
|
+
convoicon_path, spcc_icon_path, sasp_data_path, exoicon_path, peeker_icon,rotatearbitrary_path,
|
|
190
191
|
dse_icon_path, astrobin_filters_csv_path, isophote_path, statstretch_path,
|
|
191
192
|
starstretch_path, curves_path, disk_path, uhs_path, blink_path, ppp_path,
|
|
192
193
|
nbtorgb_path, freqsep_path, contsub_path, halo_path, cosmic_path,
|
|
@@ -295,11 +296,21 @@ class AstroSuiteProMainWindow(
|
|
|
295
296
|
def __init__(self, image_manager=None, parent=None,
|
|
296
297
|
version: str = "dev", build_timestamp: str = "dev"):
|
|
297
298
|
super().__init__(parent)
|
|
299
|
+
# Prevent white flash: start strictly transparent and force dark bg
|
|
300
|
+
self.setWindowOpacity(0.0)
|
|
301
|
+
self.setStyleSheet("QMainWindow { background-color: #0F0F19; }")
|
|
302
|
+
|
|
303
|
+
# --- Usage Stats ---
|
|
304
|
+
self._session_start_time = time.time()
|
|
305
|
+
self._stats_timer = QTimer(self)
|
|
306
|
+
self._stats_timer.timeout.connect(self._update_usage_stats)
|
|
307
|
+
self._stats_timer.start(60000) # Update every minute
|
|
308
|
+
|
|
298
309
|
from setiastro.saspro.doc_manager import DocManager
|
|
299
310
|
from setiastro.saspro.window_shelf import WindowShelf, MinimizeInterceptor
|
|
300
311
|
from setiastro.saspro.imageops.mdi_snap import MdiSnapController
|
|
301
312
|
from setiastro.saspro.ops.scripts import ScriptManager
|
|
302
|
-
self._version =
|
|
313
|
+
self._version = version
|
|
303
314
|
self._build_timestamp = build_timestamp
|
|
304
315
|
self.setWindowTitle(f"Seti Astro Suite Pro v{self._version}")
|
|
305
316
|
self.resize(1400, 900)
|
|
@@ -425,6 +436,7 @@ class AstroSuiteProMainWindow(
|
|
|
425
436
|
self._init_console_dock()
|
|
426
437
|
self._init_header_viewer_dock()
|
|
427
438
|
self._init_layers_dock()
|
|
439
|
+
self._init_resource_monitor_overlay()
|
|
428
440
|
self._shutting_down = False
|
|
429
441
|
self._init_status_log_dock()
|
|
430
442
|
self._init_log_dock()
|
|
@@ -455,26 +467,23 @@ class AstroSuiteProMainWindow(
|
|
|
455
467
|
self.mdi.linkViewDropped.connect(self._on_linkview_drop)
|
|
456
468
|
|
|
457
469
|
self.doc_manager.set_mdi_area(self.mdi)
|
|
458
|
-
|
|
470
|
+
# Coalesce undo/redo label refreshes
|
|
471
|
+
self._undo_redo_refresh_pending = False
|
|
472
|
+
self._undo_redo_refresh_timer = QTimer(self)
|
|
473
|
+
self._undo_redo_refresh_timer.setSingleShot(True)
|
|
474
|
+
self._undo_redo_refresh_timer.timeout.connect(self._do_undo_redo_label_refresh)
|
|
459
475
|
# Keep the toolbar in sync whenever anything relevant changes
|
|
460
|
-
self.doc_manager.documentAdded.connect(lambda *_: self.
|
|
461
|
-
self.doc_manager.documentRemoved.connect(lambda *_: self.
|
|
462
|
-
self.doc_manager.imageRegionUpdated.connect(lambda *_: self.
|
|
463
|
-
self.doc_manager.previewRepaintRequested.connect(lambda *_: self.
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
try:
|
|
472
|
-
QApplication.instance().focusChanged.connect(
|
|
473
|
-
lambda *_: QTimer.singleShot(0, self.update_undo_redo_action_labels)
|
|
474
|
-
)
|
|
475
|
-
except Exception:
|
|
476
|
-
pass
|
|
477
|
-
|
|
476
|
+
self.doc_manager.documentAdded.connect(lambda *_: self._schedule_undo_redo_label_refresh())
|
|
477
|
+
self.doc_manager.documentRemoved.connect(lambda *_: self._schedule_undo_redo_label_refresh())
|
|
478
|
+
self.doc_manager.imageRegionUpdated.connect(lambda *_: self._schedule_undo_redo_label_refresh())
|
|
479
|
+
self.doc_manager.previewRepaintRequested.connect(lambda *_: self._schedule_undo_redo_label_refresh())
|
|
480
|
+
self.mdi.subWindowActivated.connect(lambda *_: self._schedule_undo_redo_label_refresh())
|
|
481
|
+
|
|
482
|
+
# optional: keep, but schedule (or remove entirely)
|
|
483
|
+
#try:
|
|
484
|
+
# QApplication.instance().focusChanged.connect(lambda *_: self._schedule_undo_redo_label_refresh())
|
|
485
|
+
#except Exception:
|
|
486
|
+
# pass
|
|
478
487
|
self.shortcuts.load_shortcuts()
|
|
479
488
|
self._ensure_persistent_names()
|
|
480
489
|
self._restore_window_placement()
|
|
@@ -558,6 +567,22 @@ class AstroSuiteProMainWindow(
|
|
|
558
567
|
|
|
559
568
|
# _init_log_dock, _hook_stdout_stderr, and _append_log_text are now in DockMixin
|
|
560
569
|
|
|
570
|
+
def _schedule_undo_redo_label_refresh(self):
|
|
571
|
+
# Coalesce many triggers into one UI update
|
|
572
|
+
if getattr(self, "_undo_redo_refresh_pending", False):
|
|
573
|
+
return
|
|
574
|
+
self._undo_redo_refresh_pending = True
|
|
575
|
+
# 0ms is fine *if* it’s a real attribute timer (not a local)
|
|
576
|
+
self._undo_redo_refresh_timer.start(0)
|
|
577
|
+
|
|
578
|
+
def _do_undo_redo_label_refresh(self):
|
|
579
|
+
self._undo_redo_refresh_pending = False
|
|
580
|
+
try:
|
|
581
|
+
self.update_undo_redo_action_labels()
|
|
582
|
+
except Exception:
|
|
583
|
+
pass
|
|
584
|
+
|
|
585
|
+
|
|
561
586
|
def _rebuild_menus_for_language(self):
|
|
562
587
|
"""Rebuild menus after language change to apply new translations."""
|
|
563
588
|
try:
|
|
@@ -568,6 +593,19 @@ class AstroSuiteProMainWindow(
|
|
|
568
593
|
except Exception:
|
|
569
594
|
pass
|
|
570
595
|
|
|
596
|
+
def createPopupMenu(self):
|
|
597
|
+
"""Override to add System Monitor to the toolbar/dock context menu."""
|
|
598
|
+
# Get the default popup menu from QMainWindow
|
|
599
|
+
menu = super().createPopupMenu()
|
|
600
|
+
if menu is None:
|
|
601
|
+
menu = QMenu(self)
|
|
602
|
+
|
|
603
|
+
# Add System Monitor toggle if available
|
|
604
|
+
if hasattr(self, "act_toggle_monitor") and self.act_toggle_monitor is not None:
|
|
605
|
+
menu.addSeparator()
|
|
606
|
+
menu.addAction(self.act_toggle_monitor)
|
|
607
|
+
|
|
608
|
+
return menu
|
|
571
609
|
|
|
572
610
|
def _on_sw_activated(self, sw):
|
|
573
611
|
if not sw:
|
|
@@ -583,7 +621,7 @@ class AstroSuiteProMainWindow(
|
|
|
583
621
|
doc.changed.connect(self.update_undo_redo_action_labels)
|
|
584
622
|
except Exception:
|
|
585
623
|
pass
|
|
586
|
-
self.
|
|
624
|
+
self._schedule_undo_redo_label_refresh()
|
|
587
625
|
|
|
588
626
|
def _promote_roi_preview_to_real_doc(self, st: dict, preview_doc) -> None:
|
|
589
627
|
"""
|
|
@@ -1181,11 +1219,11 @@ class AstroSuiteProMainWindow(
|
|
|
1181
1219
|
global_pos = lw.viewport().mapToGlobal(pos)
|
|
1182
1220
|
|
|
1183
1221
|
menu = QMenu(lw)
|
|
1184
|
-
act_copy_selected = menu.addAction("Copy Selected")
|
|
1185
|
-
act_copy_all = menu.addAction("Copy All")
|
|
1222
|
+
act_copy_selected = menu.addAction(self.tr("Copy Selected"))
|
|
1223
|
+
act_copy_all = menu.addAction(self.tr("Copy All"))
|
|
1186
1224
|
menu.addSeparator()
|
|
1187
|
-
act_select_all = menu.addAction("Select All Lines")
|
|
1188
|
-
act_clear = menu.addAction("Clear Console")
|
|
1225
|
+
act_select_all = menu.addAction(self.tr("Select All Lines"))
|
|
1226
|
+
act_clear = menu.addAction(self.tr("Clear Console"))
|
|
1189
1227
|
|
|
1190
1228
|
action = menu.exec(global_pos)
|
|
1191
1229
|
if action is None:
|
|
@@ -1779,7 +1817,7 @@ class AstroSuiteProMainWindow(
|
|
|
1779
1817
|
|
|
1780
1818
|
show_view_bundles(self)
|
|
1781
1819
|
except Exception as e:
|
|
1782
|
-
QMessageBox.warning(self, "View Bundles", f"Open failed:\n{e}")
|
|
1820
|
+
QMessageBox.warning(self, self.tr("View Bundles"), f"Open failed:\n{e}")
|
|
1783
1821
|
|
|
1784
1822
|
def _open_function_bundles(self):
|
|
1785
1823
|
from setiastro.saspro.function_bundle import show_function_bundles
|
|
@@ -1787,7 +1825,7 @@ class AstroSuiteProMainWindow(
|
|
|
1787
1825
|
|
|
1788
1826
|
show_function_bundles(self)
|
|
1789
1827
|
except Exception as e:
|
|
1790
|
-
QMessageBox.warning(self, "Function Bundles", f"Open failed:\n{e}")
|
|
1828
|
+
QMessageBox.warning(self, self.tr("Function Bundles"), f"Open failed:\n{e}")
|
|
1791
1829
|
|
|
1792
1830
|
def _open_scripts_folder(self):
|
|
1793
1831
|
if hasattr(self, "scriptman"):
|
|
@@ -1823,6 +1861,7 @@ class AstroSuiteProMainWindow(
|
|
|
1823
1861
|
actions = self._collect_all_qactions()
|
|
1824
1862
|
except Exception:
|
|
1825
1863
|
actions = self.findChildren(QAction)
|
|
1864
|
+
|
|
1826
1865
|
for act in actions:
|
|
1827
1866
|
for seq in _seqs_for_action(act):
|
|
1828
1867
|
rows.append((_qs_to_str(seq), _describe_action(act), _where_for_action(act)))
|
|
@@ -1833,6 +1872,12 @@ class AstroSuiteProMainWindow(
|
|
|
1833
1872
|
if seq and not seq.isEmpty():
|
|
1834
1873
|
rows.append((_qs_to_str(seq), _describe_shortcut(sc), _where_for_shortcut(sc)))
|
|
1835
1874
|
|
|
1875
|
+
# 3) App-level shortcuts not represented by QAction/QShortcut
|
|
1876
|
+
try:
|
|
1877
|
+
add_extra_shortcuts(rows) # ✅ Ctrl+K, Ctrl+Alt+M, etc.
|
|
1878
|
+
except Exception:
|
|
1879
|
+
pass
|
|
1880
|
+
|
|
1836
1881
|
# De-duplicate and sort by shortcut text
|
|
1837
1882
|
rows = _uniq_keep_order(rows)
|
|
1838
1883
|
rows.sort(key=lambda r: (r[0].lower(), r[1].lower()))
|
|
@@ -1842,43 +1887,43 @@ class AstroSuiteProMainWindow(
|
|
|
1842
1887
|
# Manual list (extend anytime). Format: (Gesture, Context, Effect)
|
|
1843
1888
|
rows = [
|
|
1844
1889
|
# Command search
|
|
1845
|
-
("A", "Display Stretch", "Toggle Display Auto-Stretch"),
|
|
1846
|
-
("Ctrl+I", "Invert", "Invert the Image"),
|
|
1847
|
-
("Ctrl+Shift+P", "Command Search", "Focus the command search bar; Enter runs first match"),
|
|
1890
|
+
("A", "Display Stretch", self.tr("Toggle Display Auto-Stretch")),
|
|
1891
|
+
("Ctrl+I", "Invert", self.tr("Invert the Image")),
|
|
1892
|
+
("Ctrl+Shift+P", "Command Search", self.tr("Focus the command search bar; Enter runs first match")),
|
|
1848
1893
|
|
|
1849
1894
|
# View Icon
|
|
1850
|
-
("Drag view -> Off to Canvas", "View", "Duplicate Image"),
|
|
1851
|
-
("Drag view -> On to Other Image", "View", "Copy Zoom and Pan"),
|
|
1852
|
-
("Shift+Drag -> On to Other Image", "View", "Apply that image to the other as a mask"),
|
|
1853
|
-
("Ctrl+Drag -> On to Other Image", "View", "Copy Astrometric Solution"),
|
|
1895
|
+
("Drag view -> Off to Canvas", "View", self.tr("Duplicate Image")),
|
|
1896
|
+
("Drag view -> On to Other Image", "View", self.tr("Copy Zoom and Pan")),
|
|
1897
|
+
("Shift+Drag -> On to Other Image", "View", self.tr("Apply that image to the other as a mask")),
|
|
1898
|
+
("Ctrl+Drag -> On to Other Image", "View", self.tr("Copy Astrometric Solution")),
|
|
1854
1899
|
|
|
1855
1900
|
# View zoom
|
|
1856
|
-
("Ctrl+1", "View", "Zoom to 100% (1:1)"),
|
|
1857
|
-
("Ctrl+0", "View", "Fit image to current window"),
|
|
1858
|
-
("Ctrl++", "View", "Zoom In"),
|
|
1859
|
-
("Ctrl+-", "View", "Zoom Out"),
|
|
1901
|
+
("Ctrl+1", "View", self.tr("Zoom to 100% (1:1)")),
|
|
1902
|
+
("Ctrl+0", "View", self.tr("Fit image to current window")),
|
|
1903
|
+
("Ctrl++", "View", self.tr("Zoom In")),
|
|
1904
|
+
("Ctrl+-", "View", self.tr("Zoom Out")),
|
|
1860
1905
|
|
|
1861
1906
|
# Window switching
|
|
1862
|
-
("Ctrl+PgDown", "MDI", "Switch to previously active view"),
|
|
1863
|
-
("Ctrl+PgUp", "MDI", "Switch to next active view"),
|
|
1907
|
+
("Ctrl+PgDown", "MDI", self.tr("Switch to previously active view")),
|
|
1908
|
+
("Ctrl+PgUp", "MDI", self.tr("Switch to next active view")),
|
|
1864
1909
|
|
|
1865
1910
|
# Shortcuts canvas + buttons
|
|
1866
|
-
("Alt+Drag (toolbar button)", "Toolbar", "Create a desktop shortcut for that action"),
|
|
1867
|
-
("Alt+Drag (shortcut button -> view)", "Shortcuts", "Headless apply the shortcut's command/preset to a view"),
|
|
1868
|
-
("Ctrl/Shift+Click", "Shortcuts", "Multi-select shortcut buttons"),
|
|
1869
|
-
("Drag (selection)", "Shortcuts", "Move selected shortcut buttons"),
|
|
1870
|
-
("Delete / Backspace", "Shortcuts", "Delete selected shortcut buttons"),
|
|
1871
|
-
("Ctrl+A", "Shortcuts", "Select all shortcut buttons"),
|
|
1872
|
-
("Double-click empty area", "MDI background", "Open files dialog"),
|
|
1911
|
+
("Alt+Drag (toolbar button)", "Toolbar", self.tr("Create a desktop shortcut for that action")),
|
|
1912
|
+
("Alt+Drag (shortcut button -> view)", "Shortcuts", self.tr("Headless apply the shortcut's command/preset to a view")),
|
|
1913
|
+
("Ctrl/Shift+Click", "Shortcuts", self.tr("Multi-select shortcut buttons")),
|
|
1914
|
+
("Drag (selection)", "Shortcuts", self.tr("Move selected shortcut buttons")),
|
|
1915
|
+
("Delete / Backspace", "Shortcuts", self.tr("Delete selected shortcut buttons")),
|
|
1916
|
+
("Ctrl+A", "Shortcuts", self.tr("Select all shortcut buttons")),
|
|
1917
|
+
("Double-click empty area", "MDI background", self.tr("Open files dialog")),
|
|
1873
1918
|
|
|
1874
1919
|
# Layers dock
|
|
1875
|
-
("Drag view -> Layers list", "Layers", "Add dragged view as a new layer (on top)"),
|
|
1876
|
-
("Shift+Drag mask -> Layers list", "Layers", "Attach dragged image as mask to the selected layer"),
|
|
1920
|
+
("Drag view -> Layers list", "Layers", self.tr("Add dragged view as a new layer (on top)")),
|
|
1921
|
+
("Shift+Drag mask -> Layers list", "Layers", self.tr("Attach dragged image as mask to the selected layer")),
|
|
1877
1922
|
|
|
1878
1923
|
# Crop tool
|
|
1879
|
-
("Click-drag", "Crop Tool", "Draw a crop rectangle"),
|
|
1880
|
-
("Drag corner handles", "Crop Tool", "Resize crop rectangle"),
|
|
1881
|
-
("Shift+Drag on box", "Crop Tool", "Rotate crop rectangle"),
|
|
1924
|
+
("Click-drag", "Crop Tool", self.tr("Draw a crop rectangle")),
|
|
1925
|
+
("Drag corner handles", "Crop Tool", self.tr("Resize crop rectangle")),
|
|
1926
|
+
("Shift+Drag on box", "Crop Tool", self.tr("Rotate crop rectangle")),
|
|
1882
1927
|
]
|
|
1883
1928
|
return rows
|
|
1884
1929
|
|
|
@@ -1995,7 +2040,7 @@ class AstroSuiteProMainWindow(
|
|
|
1995
2040
|
if getattr(self, "doc_manager", None) and self.doc_manager._docs:
|
|
1996
2041
|
if not self._confirm_discard(
|
|
1997
2042
|
title=title,
|
|
1998
|
-
msg=(
|
|
2043
|
+
msg=self.tr(
|
|
1999
2044
|
"Loading a project will close current views and replace desktop shortcuts.\n"
|
|
2000
2045
|
"Continue?"
|
|
2001
2046
|
),
|
|
@@ -5510,6 +5555,10 @@ class AstroSuiteProMainWindow(
|
|
|
5510
5555
|
"rotate_180": "geom_rotate_180",
|
|
5511
5556
|
"geom_rotate_180": "geom_rotate_180",
|
|
5512
5557
|
|
|
5558
|
+
"rotate_any": "geom_rotate_any",
|
|
5559
|
+
"rotate_arbitrary": "geom_rotate_any",
|
|
5560
|
+
"geom_rotate_any": "geom_rotate_any",
|
|
5561
|
+
|
|
5513
5562
|
"invert": "geom_invert",
|
|
5514
5563
|
"geom_invert": "geom_invert",
|
|
5515
5564
|
|
|
@@ -6510,6 +6559,17 @@ class AstroSuiteProMainWindow(
|
|
|
6510
6559
|
QMessageBox.warning(self, "Rotate 180Â deg", str(e))
|
|
6511
6560
|
return
|
|
6512
6561
|
|
|
6562
|
+
if cid == "geom_rotate_any":
|
|
6563
|
+
try:
|
|
6564
|
+
angle = float(preset.get("angle_deg", preset.get("angle", 0.0)))
|
|
6565
|
+
called = _call_any(["_apply_geom_rot_any_to_doc"], doc, angle_deg=angle)
|
|
6566
|
+
if not called:
|
|
6567
|
+
raise RuntimeError("No rotate-any apply method found")
|
|
6568
|
+
self._log(f"Rotate ({angle:g}°) applied to '{target_sw.windowTitle()}'")
|
|
6569
|
+
except Exception as e:
|
|
6570
|
+
QMessageBox.warning(self, "Rotate...", str(e))
|
|
6571
|
+
return
|
|
6572
|
+
|
|
6513
6573
|
if cid == "geom_rescale":
|
|
6514
6574
|
try:
|
|
6515
6575
|
factor = float(preset.get("factor", 1.0))
|
|
@@ -7315,7 +7375,7 @@ class AstroSuiteProMainWindow(
|
|
|
7315
7375
|
self._search_dock = None
|
|
7316
7376
|
|
|
7317
7377
|
# --- Right-side mini dock with the search box ---
|
|
7318
|
-
self._search_dock = QDockWidget("Command Search", self)
|
|
7378
|
+
self._search_dock = QDockWidget(self.tr("Command Search"), self)
|
|
7319
7379
|
self._search_dock.setObjectName("CommandSearchDock")
|
|
7320
7380
|
# âœ... Allow moving/closing like other panels
|
|
7321
7381
|
self._search_dock.setAllowedAreas(
|
|
@@ -7530,7 +7590,7 @@ class AstroSuiteProMainWindow(
|
|
|
7530
7590
|
except Exception:
|
|
7531
7591
|
pass
|
|
7532
7592
|
|
|
7533
|
-
try: self.
|
|
7593
|
+
try: self._schedule_undo_redo_label_refresh()
|
|
7534
7594
|
except Exception as e:
|
|
7535
7595
|
import logging
|
|
7536
7596
|
logging.debug(f"Exception suppressed: {type(e).__name__}: {e}")
|
|
@@ -7790,6 +7850,90 @@ class AstroSuiteProMainWindow(
|
|
|
7790
7850
|
flags |= Qt.WindowType.WindowMaximizeButtonHint
|
|
7791
7851
|
sw.setWindowFlags(flags)
|
|
7792
7852
|
|
|
7853
|
+
# -------------------------------------------------------------------------
|
|
7854
|
+
# Explicitly size the window to valid dimensions so it doesn't default
|
|
7855
|
+
# to "maximized" or "full MDI area" if the previous window was large.
|
|
7856
|
+
# We target ~60% of the viewport height, clamped to sane bounds.
|
|
7857
|
+
# -------------------------------------------------------------------------
|
|
7858
|
+
vp = self.mdi.viewport()
|
|
7859
|
+
area = vp.rect() if vp else self.mdi.rect()
|
|
7860
|
+
|
|
7861
|
+
# Determine aspect ratio
|
|
7862
|
+
img_w = img_h = None
|
|
7863
|
+
try:
|
|
7864
|
+
img_w, img_h = self._infer_image_size(view)
|
|
7865
|
+
except Exception:
|
|
7866
|
+
pass
|
|
7867
|
+
|
|
7868
|
+
if not img_w or not img_h:
|
|
7869
|
+
aspect = 1.0
|
|
7870
|
+
else:
|
|
7871
|
+
aspect = float(img_w) / float(img_h)
|
|
7872
|
+
|
|
7873
|
+
# Clamp aspect
|
|
7874
|
+
aspect = max(0.3, min(aspect, 4.0))
|
|
7875
|
+
|
|
7876
|
+
target_h = int(area.height() * 0.6)
|
|
7877
|
+
target_w = int(target_h * aspect)
|
|
7878
|
+
|
|
7879
|
+
# Ensure it fits within the area (with some margin)
|
|
7880
|
+
max_w = int(area.width() * 0.9)
|
|
7881
|
+
max_h = int(area.height() * 0.9)
|
|
7882
|
+
|
|
7883
|
+
if target_w > max_w:
|
|
7884
|
+
target_w = max_w
|
|
7885
|
+
# Recalculate height to preserve aspect, if possible
|
|
7886
|
+
target_h = int(target_w / aspect)
|
|
7887
|
+
|
|
7888
|
+
if target_h > max_h:
|
|
7889
|
+
target_h = max_h
|
|
7890
|
+
|
|
7891
|
+
# Enforce minimums
|
|
7892
|
+
target_w = max(200, target_w)
|
|
7893
|
+
target_h = max(200, target_h)
|
|
7894
|
+
|
|
7895
|
+
sw.resize(target_w, target_h)
|
|
7896
|
+
sw.showNormal() # CRITICAL: clears any "maximized" flag from previous active window
|
|
7897
|
+
|
|
7898
|
+
# -------------------------------------------------------------------------
|
|
7899
|
+
# Smart Cascade: Position relative to the *currently active* window
|
|
7900
|
+
# (before we make the new one active).
|
|
7901
|
+
# -------------------------------------------------------------------------
|
|
7902
|
+
new_x, new_y = 0, 0
|
|
7903
|
+
|
|
7904
|
+
# Get dominant/active window *before* we activate the new one
|
|
7905
|
+
active = self.mdi.activeSubWindow()
|
|
7906
|
+
if active and active.isVisible() and not (active.windowState() & Qt.WindowState.WindowMinimized):
|
|
7907
|
+
# Cascade from the active window
|
|
7908
|
+
geo = active.geometry()
|
|
7909
|
+
new_x = geo.x() + 30
|
|
7910
|
+
new_y = geo.y() + 30
|
|
7911
|
+
else:
|
|
7912
|
+
# Fallback: try to find the "last added" visible window to cascade from
|
|
7913
|
+
# (useful if active is None but windows exist)
|
|
7914
|
+
try:
|
|
7915
|
+
subs = [s for s in self.mdi.subWindowList() if s.isVisible() and s is not sw]
|
|
7916
|
+
if subs:
|
|
7917
|
+
# simplistic "last created" might be at end of list
|
|
7918
|
+
last = subs[-1]
|
|
7919
|
+
geo = last.geometry()
|
|
7920
|
+
new_x = geo.x() + 30
|
|
7921
|
+
new_y = geo.y() + 30
|
|
7922
|
+
except Exception:
|
|
7923
|
+
pass
|
|
7924
|
+
|
|
7925
|
+
# Bounds check: don't let it drift completely off-screen
|
|
7926
|
+
# (allow valid title bar to be visible at least)
|
|
7927
|
+
if (new_x + target_w > area.width() + 50) or (new_y + 50 > area.height()):
|
|
7928
|
+
new_x = 0
|
|
7929
|
+
new_y = 0
|
|
7930
|
+
|
|
7931
|
+
# Clamp to 0 if negative for some reason
|
|
7932
|
+
new_x = max(0, new_x)
|
|
7933
|
+
new_y = max(0, new_y)
|
|
7934
|
+
|
|
7935
|
+
sw.move(new_x, new_y)
|
|
7936
|
+
|
|
7793
7937
|
# ⌠removed the "fill MDI viewport" block - we *don't* want full-monitor first window
|
|
7794
7938
|
|
|
7795
7939
|
# Show / activate
|
|
@@ -7934,7 +8078,7 @@ class AstroSuiteProMainWindow(
|
|
|
7934
8078
|
# If no subwindows remain, clear all "active doc" UI bits, including header
|
|
7935
8079
|
if not self.mdi.subWindowList():
|
|
7936
8080
|
self.currentDocumentChanged.emit(None) # drives HeaderViewerDock.set_document(None)
|
|
7937
|
-
self.
|
|
8081
|
+
self._schedule_undo_redo_label_refresh()
|
|
7938
8082
|
self._hdr_refresh_timer.start(0) # belt-and-suspenders for manual widgets
|
|
7939
8083
|
# If your dock has its own set_document, call it explicitly too
|
|
7940
8084
|
hv = getattr(self, "header_viewer", None)
|
|
@@ -8276,19 +8420,20 @@ class AstroSuiteProMainWindow(
|
|
|
8276
8420
|
|
|
8277
8421
|
# Misc UI refreshes (guarded)
|
|
8278
8422
|
try:
|
|
8279
|
-
self.
|
|
8280
|
-
except Exception:
|
|
8281
|
-
pass
|
|
8282
|
-
try:
|
|
8283
|
-
if hasattr(self, "_hdr_refresh_timer") and self._hdr_refresh_timer is not None:
|
|
8284
|
-
self._hdr_refresh_timer.start(0)
|
|
8423
|
+
self._schedule_undo_redo_label_refresh()
|
|
8285
8424
|
except Exception:
|
|
8286
8425
|
pass
|
|
8426
|
+
#try:
|
|
8427
|
+
# if hasattr(self, "_hdr_refresh_timer") and self._hdr_refresh_timer is not None:
|
|
8428
|
+
# self._hdr_refresh_timer.start(0)
|
|
8429
|
+
#except Exception:
|
|
8430
|
+
# pass
|
|
8287
8431
|
try:
|
|
8288
8432
|
self._refresh_mask_action_states()
|
|
8289
8433
|
except Exception:
|
|
8290
8434
|
pass
|
|
8291
8435
|
|
|
8436
|
+
|
|
8292
8437
|
def _sync_docman_active(self, doc):
|
|
8293
8438
|
dm = self.doc_manager
|
|
8294
8439
|
try:
|
|
@@ -8449,6 +8594,40 @@ class AstroSuiteProMainWindow(
|
|
|
8449
8594
|
if self._suspend_dock_sync:
|
|
8450
8595
|
QTimer.singleShot(0, lambda: self.changeEvent(QEvent(QEvent.Type.WindowStateChange)))
|
|
8451
8596
|
|
|
8597
|
+
def resizeEvent(self, event):
|
|
8598
|
+
super().resizeEvent(event)
|
|
8599
|
+
# Update floating resource monitor position if it exists (from DockMixin)
|
|
8600
|
+
if hasattr(self, "_update_monitor_position"):
|
|
8601
|
+
self._update_monitor_position()
|
|
8602
|
+
|
|
8603
|
+
def moveEvent(self, event):
|
|
8604
|
+
super().moveEvent(event)
|
|
8605
|
+
# Update floating resource monitor position if it exists (from DockMixin)
|
|
8606
|
+
if hasattr(self, "_update_monitor_position"):
|
|
8607
|
+
self._update_monitor_position()
|
|
8608
|
+
|
|
8609
|
+
def changeEvent(self, event):
|
|
8610
|
+
super().changeEvent(event)
|
|
8611
|
+
# 1. Existing logic for dock sync (re-instated from showEvent logic if needed, but usually changeEvent is enough)
|
|
8612
|
+
# (The snippet viewed previously showed showEvent firing a oneshot to call changeEvent)
|
|
8613
|
+
|
|
8614
|
+
# 2. Resource Monitor Sync
|
|
8615
|
+
if event.type() == QEvent.Type.WindowStateChange:
|
|
8616
|
+
if self.windowState() & Qt.WindowState.WindowMinimized:
|
|
8617
|
+
# App minimized -> hide overlay
|
|
8618
|
+
if hasattr(self, "resource_monitor") and self.resource_monitor:
|
|
8619
|
+
self.resource_monitor.hide()
|
|
8620
|
+
elif not (self.windowState() & Qt.WindowState.WindowMinimized):
|
|
8621
|
+
# Only auto-show if the initial fade-in is done
|
|
8622
|
+
if getattr(self, "_fade_in_complete", False):
|
|
8623
|
+
# App restored -> show overlay if enabled in settings
|
|
8624
|
+
if hasattr(self, "resource_monitor") and self.resource_monitor:
|
|
8625
|
+
if self.settings.value("ui/resource_monitor_visible", True, type=bool):
|
|
8626
|
+
self.resource_monitor.show()
|
|
8627
|
+
# Ensure position is correct upon restore
|
|
8628
|
+
if hasattr(self, "_update_monitor_position"):
|
|
8629
|
+
self._update_monitor_position()
|
|
8630
|
+
|
|
8452
8631
|
def save_ui_state(self):
|
|
8453
8632
|
"""Save window geometry, state, and shortcuts to settings."""
|
|
8454
8633
|
self._ensure_persistent_names()
|
|
@@ -8478,12 +8657,69 @@ class AstroSuiteProMainWindow(
|
|
|
8478
8657
|
|
|
8479
8658
|
self.settings.sync()
|
|
8480
8659
|
|
|
8660
|
+
def on_fade_in_complete(self):
|
|
8661
|
+
"""Called when main window fade-in is finished."""
|
|
8662
|
+
self._fade_in_complete = True
|
|
8663
|
+
# Sync Monitor Visibility
|
|
8664
|
+
if hasattr(self, "resource_monitor") and self.resource_monitor:
|
|
8665
|
+
if not self.isMinimized() and self.settings.value("ui/resource_monitor_visible", True, type=bool):
|
|
8666
|
+
# Delay show to ensure visually pleasing sequence (monitor appears AFTER app)
|
|
8667
|
+
QTimer.singleShot(500, self.resource_monitor.show)
|
|
8668
|
+
# Ensure position
|
|
8669
|
+
QTimer.singleShot(600, self._update_monitor_position)
|
|
8670
|
+
|
|
8671
|
+
def keyPressEvent(self, event):
|
|
8672
|
+
"""Handle key press events for secret shortcuts."""
|
|
8673
|
+
if (event.modifiers() == (Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.AltModifier) and
|
|
8674
|
+
event.key() == Qt.Key.Key_M):
|
|
8675
|
+
|
|
8676
|
+
# Secret minigame launcher
|
|
8677
|
+
# __file__ is in .../saspro/gui/main_window.py
|
|
8678
|
+
# We want to go up to .../saspro/
|
|
8679
|
+
base_pkg = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
8680
|
+
minigame_path = os.path.join(base_pkg, "widgets", "minigame", "index.html")
|
|
8681
|
+
|
|
8682
|
+
if os.path.exists(minigame_path):
|
|
8683
|
+
QDesktopServices.openUrl(QUrl.fromLocalFile(minigame_path))
|
|
8684
|
+
event.accept()
|
|
8685
|
+
return
|
|
8686
|
+
|
|
8687
|
+
super().keyPressEvent(event)
|
|
8688
|
+
|
|
8689
|
+
def _update_usage_stats(self):
|
|
8690
|
+
try:
|
|
8691
|
+
now = time.time()
|
|
8692
|
+
elapsed = now - self._session_start_time
|
|
8693
|
+
self._session_start_time = now # Reset session start to avoid double counting
|
|
8694
|
+
|
|
8695
|
+
total = self.settings.value("stats/total_time_seconds", 0.0, type=float)
|
|
8696
|
+
self.settings.setValue("stats/total_time_seconds", total + elapsed)
|
|
8697
|
+
except Exception:
|
|
8698
|
+
pass
|
|
8699
|
+
|
|
8700
|
+
def _on_tool_triggered(self):
|
|
8701
|
+
"""Slot to track tool usage count."""
|
|
8702
|
+
try:
|
|
8703
|
+
count = self.settings.value("stats/opened_tools_count", 0, type=int)
|
|
8704
|
+
self.settings.setValue("stats/opened_tools_count", count + 1)
|
|
8705
|
+
except Exception:
|
|
8706
|
+
pass
|
|
8707
|
+
|
|
8481
8708
|
def closeEvent(self, e):
|
|
8709
|
+
self._update_usage_stats()
|
|
8710
|
+
|
|
8711
|
+
|
|
8482
8712
|
# Optimization: If restarting (e.g. language change), bypass confirmation and close immediately
|
|
8483
8713
|
if getattr(self, "_is_restarting", False):
|
|
8484
8714
|
e.accept()
|
|
8485
8715
|
return
|
|
8486
|
-
|
|
8716
|
+
|
|
8717
|
+
# Check if we have already faded out
|
|
8718
|
+
if getattr(self, "_fade_out_complete", False):
|
|
8719
|
+
# Proceed with shutdown
|
|
8720
|
+
self._do_shutdown_steps(e)
|
|
8721
|
+
return
|
|
8722
|
+
|
|
8487
8723
|
try:
|
|
8488
8724
|
if hasattr(self, "_orig_stdout") and self._orig_stdout is not None:
|
|
8489
8725
|
sys.stdout = self._orig_stdout
|
|
@@ -8491,6 +8727,8 @@ class AstroSuiteProMainWindow(
|
|
|
8491
8727
|
sys.stderr = self._orig_stderr
|
|
8492
8728
|
except Exception:
|
|
8493
8729
|
pass
|
|
8730
|
+
|
|
8731
|
+
# --- Confirmation Logic ---
|
|
8494
8732
|
self._shutting_down = True
|
|
8495
8733
|
# Gather open docs
|
|
8496
8734
|
docs = []
|
|
@@ -8501,35 +8739,68 @@ class AstroSuiteProMainWindow(
|
|
|
8501
8739
|
docs.append(d)
|
|
8502
8740
|
|
|
8503
8741
|
edited = [d for d in docs if self._document_has_edits(d)]
|
|
8504
|
-
|
|
8505
|
-
|
|
8506
|
-
|
|
8507
|
-
|
|
8508
|
-
|
|
8509
|
-
detail
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8742
|
+
# If user has disabled exit confirmation (optional setting, but default is confirm)
|
|
8743
|
+
confirm = True
|
|
8744
|
+
|
|
8745
|
+
if confirm:
|
|
8746
|
+
msg = self.tr("Exit Seti Astro Suite Pro?")
|
|
8747
|
+
detail = []
|
|
8748
|
+
if docs:
|
|
8749
|
+
detail.append(self.tr("Open images:") + f" {len(docs)}")
|
|
8750
|
+
if edited:
|
|
8751
|
+
detail.append(self.tr("Edited since open:") + f" {len(edited)}")
|
|
8752
|
+
if detail:
|
|
8753
|
+
msg += "\n\n" + "\n".join(detail)
|
|
8754
|
+
|
|
8755
|
+
# --- stay-on-top message box ---
|
|
8756
|
+
mbox = QMessageBox(self)
|
|
8757
|
+
mbox.setIcon(QMessageBox.Icon.Question)
|
|
8758
|
+
mbox.setWindowTitle(self.tr("Confirm Exit"))
|
|
8759
|
+
mbox.setText(msg)
|
|
8760
|
+
mbox.setStandardButtons(
|
|
8761
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
8762
|
+
)
|
|
8763
|
+
mbox.setDefaultButton(QMessageBox.StandardButton.No)
|
|
8764
|
+
mbox.setWindowFlag(Qt.WindowType.WindowStaysOnTopHint, True)
|
|
8765
|
+
mbox.raise_()
|
|
8766
|
+
mbox.activateWindow()
|
|
8767
|
+
btn = mbox.exec()
|
|
8768
|
+
|
|
8769
|
+
if btn != QMessageBox.StandardButton.Yes:
|
|
8770
|
+
e.ignore()
|
|
8771
|
+
self._shutting_down = False
|
|
8772
|
+
return
|
|
8527
8773
|
|
|
8528
|
-
|
|
8529
|
-
|
|
8530
|
-
|
|
8774
|
+
# --- User confirmed (or no confirm needed) ---
|
|
8775
|
+
# Start Fade Out Animation
|
|
8776
|
+
e.ignore() # Defer close until animation completes
|
|
8777
|
+
self.setEnabled(False) # Prevent further interaction
|
|
8778
|
+
|
|
8779
|
+
# Hide monitor immediately when fade starts (user preference)
|
|
8780
|
+
if hasattr(self, "resource_monitor") and self.resource_monitor:
|
|
8781
|
+
try:
|
|
8782
|
+
if hasattr(self.resource_monitor, "backend"):
|
|
8783
|
+
self.resource_monitor.backend.stop()
|
|
8784
|
+
except Exception:
|
|
8785
|
+
pass
|
|
8786
|
+
self.resource_monitor.hide()
|
|
8787
|
+
self.resource_monitor.close()
|
|
8788
|
+
|
|
8789
|
+
self._anim_close = QPropertyAnimation(self, b"windowOpacity")
|
|
8790
|
+
self._anim_close.setDuration(800)
|
|
8791
|
+
self._anim_close.setStartValue(1.0)
|
|
8792
|
+
self._anim_close.setEndValue(0.0)
|
|
8793
|
+
self._anim_close.setEasingCurve(QEasingCurve.Type.OutQuad)
|
|
8794
|
+
self._anim_close.finished.connect(self._on_fade_out_finished)
|
|
8795
|
+
self._anim_close.start()
|
|
8796
|
+
|
|
8797
|
+
def _on_fade_out_finished(self):
|
|
8798
|
+
"""Called when close animation completes."""
|
|
8799
|
+
self._fade_out_complete = True
|
|
8800
|
+
self.close()
|
|
8531
8801
|
|
|
8532
|
-
|
|
8802
|
+
def _do_shutdown_steps(self, e):
|
|
8803
|
+
"""Actual shutdown logic after verification and animation."""
|
|
8533
8804
|
self._force_close_all = True
|
|
8534
8805
|
self._shutting_down = True
|
|
8535
8806
|
|
|
@@ -8553,6 +8824,7 @@ class AstroSuiteProMainWindow(
|
|
|
8553
8824
|
# CheatSheet dialog and helper functions imported from setiastro.saspro.cheat_sheet
|
|
8554
8825
|
from setiastro.saspro.cheat_sheet import (
|
|
8555
8826
|
CheatSheetDialog as _CheatSheetDialog,
|
|
8827
|
+
add_extra_shortcuts,
|
|
8556
8828
|
_qs_to_str,
|
|
8557
8829
|
_clean_text,
|
|
8558
8830
|
_uniq_keep_order,
|