setiastrosuitepro 1.6.0__py3-none-any.whl → 1.6.4.post1__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.
- setiastro/data/SASP_data.fits +0 -0
- setiastro/data/catalogs/List_of_Galaxies_with_Distances_Gly.csv +488 -0
- setiastro/data/catalogs/astrobin_filters.csv +890 -0
- setiastro/data/catalogs/astrobin_filters_page1_local.csv +51 -0
- setiastro/data/catalogs/cali2.csv +63 -0
- setiastro/data/catalogs/cali2color.csv +65 -0
- setiastro/data/catalogs/celestial_catalog - original.csv +16471 -0
- setiastro/data/catalogs/celestial_catalog.csv +24031 -0
- setiastro/data/catalogs/detected_stars.csv +24784 -0
- setiastro/data/catalogs/fits_header_data.csv +46 -0
- setiastro/data/catalogs/test.csv +8 -0
- setiastro/data/catalogs/updated_celestial_catalog.csv +16471 -0
- setiastro/images/Astro_Spikes.png +0 -0
- setiastro/images/Background_startup.jpg +0 -0
- setiastro/images/HRDiagram.png +0 -0
- setiastro/images/LExtract.png +0 -0
- setiastro/images/LInsert.png +0 -0
- setiastro/images/Oxygenation-atm-2.svg.png +0 -0
- setiastro/images/RGB080604.png +0 -0
- setiastro/images/abeicon.png +0 -0
- setiastro/images/aberration.png +0 -0
- setiastro/images/andromedatry.png +0 -0
- setiastro/images/andromedatry_satellited.png +0 -0
- setiastro/images/annotated.png +0 -0
- setiastro/images/aperture.png +0 -0
- setiastro/images/astrosuite.ico +0 -0
- setiastro/images/astrosuite.png +0 -0
- setiastro/images/astrosuitepro.icns +0 -0
- setiastro/images/astrosuitepro.ico +0 -0
- setiastro/images/astrosuitepro.png +0 -0
- setiastro/images/background.png +0 -0
- setiastro/images/background2.png +0 -0
- setiastro/images/benchmark.png +0 -0
- setiastro/images/big_moon_stabilizer_timeline.png +0 -0
- setiastro/images/big_moon_stabilizer_timeline_clean.png +0 -0
- setiastro/images/blaster.png +0 -0
- setiastro/images/blink.png +0 -0
- setiastro/images/clahe.png +0 -0
- setiastro/images/collage.png +0 -0
- setiastro/images/colorwheel.png +0 -0
- setiastro/images/contsub.png +0 -0
- setiastro/images/convo.png +0 -0
- setiastro/images/copyslot.png +0 -0
- setiastro/images/cosmic.png +0 -0
- setiastro/images/cosmicsat.png +0 -0
- setiastro/images/crop1.png +0 -0
- setiastro/images/cropicon.png +0 -0
- setiastro/images/curves.png +0 -0
- setiastro/images/cvs.png +0 -0
- setiastro/images/debayer.png +0 -0
- setiastro/images/denoise_cnn_custom.png +0 -0
- setiastro/images/denoise_cnn_graph.png +0 -0
- setiastro/images/disk.png +0 -0
- setiastro/images/dse.png +0 -0
- setiastro/images/exoicon.png +0 -0
- setiastro/images/eye.png +0 -0
- setiastro/images/fliphorizontal.png +0 -0
- setiastro/images/flipvertical.png +0 -0
- setiastro/images/font.png +0 -0
- setiastro/images/freqsep.png +0 -0
- setiastro/images/functionbundle.png +0 -0
- setiastro/images/graxpert.png +0 -0
- setiastro/images/green.png +0 -0
- setiastro/images/gridicon.png +0 -0
- setiastro/images/halo.png +0 -0
- setiastro/images/hdr.png +0 -0
- setiastro/images/histogram.png +0 -0
- setiastro/images/hubble.png +0 -0
- setiastro/images/imagecombine.png +0 -0
- setiastro/images/invert.png +0 -0
- setiastro/images/isophote.png +0 -0
- setiastro/images/isophote_demo_figure.png +0 -0
- setiastro/images/isophote_demo_image.png +0 -0
- setiastro/images/isophote_demo_model.png +0 -0
- setiastro/images/isophote_demo_residual.png +0 -0
- setiastro/images/jwstpupil.png +0 -0
- setiastro/images/linearfit.png +0 -0
- setiastro/images/livestacking.png +0 -0
- setiastro/images/mask.png +0 -0
- setiastro/images/maskapply.png +0 -0
- setiastro/images/maskcreate.png +0 -0
- setiastro/images/maskremove.png +0 -0
- setiastro/images/morpho.png +0 -0
- setiastro/images/mosaic.png +0 -0
- setiastro/images/multiscale_decomp.png +0 -0
- setiastro/images/nbtorgb.png +0 -0
- setiastro/images/neutral.png +0 -0
- setiastro/images/nuke.png +0 -0
- setiastro/images/openfile.png +0 -0
- setiastro/images/pedestal.png +0 -0
- setiastro/images/pen.png +0 -0
- setiastro/images/pixelmath.png +0 -0
- setiastro/images/platesolve.png +0 -0
- setiastro/images/ppp.png +0 -0
- setiastro/images/pro.png +0 -0
- setiastro/images/project.png +0 -0
- setiastro/images/psf.png +0 -0
- setiastro/images/redo.png +0 -0
- setiastro/images/redoicon.png +0 -0
- setiastro/images/rescale.png +0 -0
- setiastro/images/rgbalign.png +0 -0
- setiastro/images/rgbcombo.png +0 -0
- setiastro/images/rgbextract.png +0 -0
- setiastro/images/rotate180.png +0 -0
- setiastro/images/rotatearbitrary.png +0 -0
- setiastro/images/rotateclockwise.png +0 -0
- setiastro/images/rotatecounterclockwise.png +0 -0
- setiastro/images/satellite.png +0 -0
- setiastro/images/script.png +0 -0
- setiastro/images/selectivecolor.png +0 -0
- setiastro/images/simbad.png +0 -0
- setiastro/images/slot0.png +0 -0
- setiastro/images/slot1.png +0 -0
- setiastro/images/slot2.png +0 -0
- setiastro/images/slot3.png +0 -0
- setiastro/images/slot4.png +0 -0
- setiastro/images/slot5.png +0 -0
- setiastro/images/slot6.png +0 -0
- setiastro/images/slot7.png +0 -0
- setiastro/images/slot8.png +0 -0
- setiastro/images/slot9.png +0 -0
- setiastro/images/spcc.png +0 -0
- setiastro/images/spin_precession_vs_lunar_distance.png +0 -0
- setiastro/images/spinner.gif +0 -0
- setiastro/images/stacking.png +0 -0
- setiastro/images/staradd.png +0 -0
- setiastro/images/staralign.png +0 -0
- setiastro/images/starnet.png +0 -0
- setiastro/images/starregistration.png +0 -0
- setiastro/images/starspike.png +0 -0
- setiastro/images/starstretch.png +0 -0
- setiastro/images/statstretch.png +0 -0
- setiastro/images/supernova.png +0 -0
- setiastro/images/uhs.png +0 -0
- setiastro/images/undoicon.png +0 -0
- setiastro/images/upscale.png +0 -0
- setiastro/images/viewbundle.png +0 -0
- setiastro/images/whitebalance.png +0 -0
- setiastro/images/wimi_icon_256x256.png +0 -0
- setiastro/images/wimilogo.png +0 -0
- setiastro/images/wims.png +0 -0
- setiastro/images/wrench_icon.png +0 -0
- setiastro/images/xisfliberator.png +0 -0
- setiastro/qml/ResourceMonitor.qml +126 -0
- setiastro/saspro/__main__.py +228 -67
- setiastro/saspro/_generated/build_info.py +2 -1
- setiastro/saspro/abe.py +76 -25
- setiastro/saspro/aberration_ai.py +14 -14
- setiastro/saspro/add_stars.py +15 -12
- setiastro/saspro/astrobin_exporter.py +61 -58
- setiastro/saspro/astrospike_python.py +3 -1
- setiastro/saspro/autostretch.py +4 -2
- setiastro/saspro/backgroundneutral.py +65 -14
- setiastro/saspro/batch_convert.py +8 -5
- setiastro/saspro/batch_renamer.py +39 -36
- setiastro/saspro/blemish_blaster.py +15 -12
- setiastro/saspro/blink_comparator_pro.py +605 -379
- setiastro/saspro/cheat_sheet.py +62 -17
- setiastro/saspro/clahe.py +34 -8
- setiastro/saspro/comet_stacking.py +103 -38
- setiastro/saspro/common_tr.py +107 -0
- setiastro/saspro/continuum_subtract.py +7 -7
- setiastro/saspro/convo.py +12 -9
- setiastro/saspro/copyastro.py +3 -0
- setiastro/saspro/cosmicclarity.py +77 -52
- setiastro/saspro/crop_dialog_pro.py +80 -45
- setiastro/saspro/curve_editor_pro.py +51 -33
- setiastro/saspro/debayer.py +6 -3
- setiastro/saspro/doc_manager.py +49 -19
- setiastro/saspro/exoplanet_detector.py +11 -11
- setiastro/saspro/fitsmodifier.py +48 -44
- setiastro/saspro/fix_bom.py +32 -0
- setiastro/saspro/frequency_separation.py +18 -12
- setiastro/saspro/function_bundle.py +18 -16
- setiastro/saspro/generate_translations.py +3092 -0
- setiastro/saspro/ghs_dialog_pro.py +19 -16
- setiastro/saspro/graxpert.py +3 -0
- setiastro/saspro/gui/main_window.py +471 -126
- setiastro/saspro/gui/mixins/dock_mixin.py +123 -11
- setiastro/saspro/gui/mixins/file_mixin.py +25 -20
- setiastro/saspro/gui/mixins/geometry_mixin.py +115 -15
- setiastro/saspro/gui/mixins/header_mixin.py +6 -6
- setiastro/saspro/gui/mixins/mask_mixin.py +8 -8
- setiastro/saspro/gui/mixins/menu_mixin.py +62 -33
- setiastro/saspro/gui/mixins/toolbar_mixin.py +382 -226
- setiastro/saspro/gui/mixins/update_mixin.py +26 -26
- setiastro/saspro/gui/statistics_dialog.py +47 -0
- setiastro/saspro/halobgon.py +29 -3
- setiastro/saspro/header_viewer.py +21 -18
- setiastro/saspro/histogram.py +29 -26
- setiastro/saspro/history_explorer.py +2 -0
- setiastro/saspro/i18n.py +168 -0
- setiastro/saspro/image_combine.py +3 -0
- setiastro/saspro/image_peeker_pro.py +52 -44
- 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 +180 -22
- setiastro/saspro/mfdeconv.py +5 -0
- setiastro/saspro/morphology.py +38 -13
- setiastro/saspro/multiscale_decomp.py +713 -256
- setiastro/saspro/nbtorgb_stars.py +12 -2
- setiastro/saspro/numba_utils.py +149 -48
- setiastro/saspro/ops/scripts.py +77 -17
- setiastro/saspro/ops/settings.py +177 -100
- setiastro/saspro/perfect_palette_picker.py +25 -7
- setiastro/saspro/pixelmath.py +114 -110
- setiastro/saspro/plate_solver.py +118 -108
- setiastro/saspro/remove_green.py +24 -7
- setiastro/saspro/remove_stars.py +136 -162
- setiastro/saspro/remove_stars_preset.py +55 -13
- setiastro/saspro/resources.py +46 -15
- setiastro/saspro/rgb_combination.py +19 -18
- setiastro/saspro/rgbalign.py +11 -11
- setiastro/saspro/save_options.py +5 -4
- setiastro/saspro/selective_color.py +84 -25
- setiastro/saspro/sfcc.py +119 -72
- setiastro/saspro/shortcuts.py +345 -36
- setiastro/saspro/signature_insert.py +4 -1
- setiastro/saspro/stacking_suite.py +2066 -1119
- setiastro/saspro/star_alignment.py +291 -331
- setiastro/saspro/star_spikes.py +137 -53
- setiastro/saspro/star_stretch.py +47 -10
- setiastro/saspro/stat_stretch.py +52 -16
- setiastro/saspro/status_log_dock.py +1 -1
- setiastro/saspro/subwindow.py +97 -36
- setiastro/saspro/supernovaasteroidhunter.py +68 -61
- 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 +3728 -0
- setiastro/saspro/translations/es_translations.py +4169 -0
- setiastro/saspro/translations/fr_translations.py +4090 -0
- setiastro/saspro/translations/hi_translations.py +3803 -0
- setiastro/saspro/translations/integrate_translations.py +271 -0
- setiastro/saspro/translations/it_translations.py +4728 -0
- setiastro/saspro/translations/ja_translations.py +3834 -0
- setiastro/saspro/translations/pt_translations.py +3847 -0
- 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 +14548 -0
- setiastro/saspro/translations/saspro_es.qm +0 -0
- setiastro/saspro/translations/saspro_es.ts +16202 -0
- setiastro/saspro/translations/saspro_fr.qm +0 -0
- setiastro/saspro/translations/saspro_fr.ts +15870 -0
- 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 +19046 -0
- setiastro/saspro/translations/saspro_ja.qm +0 -0
- setiastro/saspro/translations/saspro_ja.ts +14980 -0
- setiastro/saspro/translations/saspro_pt.qm +0 -0
- setiastro/saspro/translations/saspro_pt.ts +15024 -0
- 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 +15289 -0
- setiastro/saspro/translations/sw_translations.py +3897 -0
- setiastro/saspro/translations/uk_translations.py +3929 -0
- setiastro/saspro/translations/zh_translations.py +3910 -0
- setiastro/saspro/versioning.py +77 -0
- setiastro/saspro/view_bundle.py +20 -17
- setiastro/saspro/wavescale_hdr.py +54 -33
- setiastro/saspro/wavescale_hdr_preset.py +6 -5
- setiastro/saspro/wavescalede.py +54 -31
- setiastro/saspro/wavescalede_preset.py +9 -7
- setiastro/saspro/whitebalance.py +58 -22
- setiastro/saspro/widgets/common_utilities.py +12 -11
- 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/preview_dialogs.py +8 -8
- 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 +7996 -0
- setiastro/saspro/wims.py +578 -0
- setiastro/saspro/window_shelf.py +2 -2
- {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/METADATA +15 -3
- setiastrosuitepro-1.6.4.post1.dist-info/RECORD +368 -0
- setiastrosuitepro-1.6.0.dist-info/RECORD +0 -174
- {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/entry_points.txt +0 -0
- {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.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,
|
|
@@ -201,9 +202,32 @@ from setiastro.saspro.resources import (
|
|
|
201
202
|
import faulthandler
|
|
202
203
|
|
|
203
204
|
def _install_crash_logging():
|
|
204
|
-
|
|
205
|
+
# Ensure logging is configured before we try to use it
|
|
206
|
+
if not logging.getLogger().hasHandlers():
|
|
207
|
+
logging.basicConfig(
|
|
208
|
+
level=logging.WARNING,
|
|
209
|
+
format='%(asctime)s - %(levelname)s - %(message)s',
|
|
210
|
+
handlers=[logging.StreamHandler(sys.stderr)]
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
try:
|
|
214
|
+
faulthandler.enable(all_threads=True)
|
|
215
|
+
except (PermissionError, OSError) as e:
|
|
216
|
+
# On macOS, faulthandler.enable() might fail due to security restrictions
|
|
217
|
+
# We can still continue without it as it's primarily for debugging
|
|
218
|
+
try:
|
|
219
|
+
logging.warning(f"Could not enable faulthandler: {e}")
|
|
220
|
+
except Exception:
|
|
221
|
+
# Fallback if logging isn't configured yet
|
|
222
|
+
print(f"WARNING: Could not enable faulthandler: {e}")
|
|
223
|
+
|
|
205
224
|
def _excepthook(t, v, tb):
|
|
206
|
-
|
|
225
|
+
try:
|
|
226
|
+
logging.critical("Uncaught exception", exc_info=(t, v, tb))
|
|
227
|
+
except Exception:
|
|
228
|
+
# Fallback if logging fails
|
|
229
|
+
print(f"CRITICAL: Uncaught exception: {t.__name__}: {v}")
|
|
230
|
+
traceback.print_tb(tb)
|
|
207
231
|
try:
|
|
208
232
|
faulthandler.dump_traceback(file=sys.stderr)
|
|
209
233
|
except Exception:
|
|
@@ -272,6 +296,16 @@ class AstroSuiteProMainWindow(
|
|
|
272
296
|
def __init__(self, image_manager=None, parent=None,
|
|
273
297
|
version: str = "dev", build_timestamp: str = "dev"):
|
|
274
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
|
+
|
|
275
309
|
from setiastro.saspro.doc_manager import DocManager
|
|
276
310
|
from setiastro.saspro.window_shelf import WindowShelf, MinimizeInterceptor
|
|
277
311
|
from setiastro.saspro.imageops.mdi_snap import MdiSnapController
|
|
@@ -285,7 +319,13 @@ class AstroSuiteProMainWindow(
|
|
|
285
319
|
self.setWindowIcon(self.app_icon)
|
|
286
320
|
self._doc = None
|
|
287
321
|
self._force_close_all = False
|
|
288
|
-
self.
|
|
322
|
+
self._is_restarting = False # Flag to bypass exit confirmation on restart
|
|
323
|
+
self.settings = QSettings("SetiAstro", "SetiAstroSuitePro")
|
|
324
|
+
|
|
325
|
+
# Optimization: Cache the settings dialog for instant opening
|
|
326
|
+
self._settings_dlg_cache = None
|
|
327
|
+
# Pre-load settings dialog after 2.5 seconds (background)
|
|
328
|
+
QTimer.singleShot(1000, self._preload_settings)
|
|
289
329
|
self._last_active_view = None
|
|
290
330
|
self._current_active_sw = None
|
|
291
331
|
self._last_active_sw = None
|
|
@@ -396,6 +436,7 @@ class AstroSuiteProMainWindow(
|
|
|
396
436
|
self._init_console_dock()
|
|
397
437
|
self._init_header_viewer_dock()
|
|
398
438
|
self._init_layers_dock()
|
|
439
|
+
self._init_resource_monitor_overlay()
|
|
399
440
|
self._shutting_down = False
|
|
400
441
|
self._init_status_log_dock()
|
|
401
442
|
self._init_log_dock()
|
|
@@ -426,26 +467,23 @@ class AstroSuiteProMainWindow(
|
|
|
426
467
|
self.mdi.linkViewDropped.connect(self._on_linkview_drop)
|
|
427
468
|
|
|
428
469
|
self.doc_manager.set_mdi_area(self.mdi)
|
|
429
|
-
|
|
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)
|
|
430
475
|
# Keep the toolbar in sync whenever anything relevant changes
|
|
431
|
-
self.doc_manager.documentAdded.connect(lambda *_: self.
|
|
432
|
-
self.doc_manager.documentRemoved.connect(lambda *_: self.
|
|
433
|
-
self.doc_manager.imageRegionUpdated.connect(lambda *_: self.
|
|
434
|
-
self.doc_manager.previewRepaintRequested.connect(lambda *_: self.
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
try:
|
|
443
|
-
QApplication.instance().focusChanged.connect(
|
|
444
|
-
lambda *_: QTimer.singleShot(0, self.update_undo_redo_action_labels)
|
|
445
|
-
)
|
|
446
|
-
except Exception:
|
|
447
|
-
pass
|
|
448
|
-
|
|
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
|
|
449
487
|
self.shortcuts.load_shortcuts()
|
|
450
488
|
self._ensure_persistent_names()
|
|
451
489
|
self._restore_window_placement()
|
|
@@ -529,6 +567,45 @@ class AstroSuiteProMainWindow(
|
|
|
529
567
|
|
|
530
568
|
# _init_log_dock, _hook_stdout_stderr, and _append_log_text are now in DockMixin
|
|
531
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
|
+
|
|
586
|
+
def _rebuild_menus_for_language(self):
|
|
587
|
+
"""Rebuild menus after language change to apply new translations."""
|
|
588
|
+
try:
|
|
589
|
+
# Clear the menubar and rebuild it
|
|
590
|
+
mb = self.menuBar()
|
|
591
|
+
mb.clear()
|
|
592
|
+
self._init_menubar()
|
|
593
|
+
except Exception:
|
|
594
|
+
pass
|
|
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
|
|
532
609
|
|
|
533
610
|
def _on_sw_activated(self, sw):
|
|
534
611
|
if not sw:
|
|
@@ -544,7 +621,7 @@ class AstroSuiteProMainWindow(
|
|
|
544
621
|
doc.changed.connect(self.update_undo_redo_action_labels)
|
|
545
622
|
except Exception:
|
|
546
623
|
pass
|
|
547
|
-
self.
|
|
624
|
+
self._schedule_undo_redo_label_refresh()
|
|
548
625
|
|
|
549
626
|
def _promote_roi_preview_to_real_doc(self, st: dict, preview_doc) -> None:
|
|
550
627
|
"""
|
|
@@ -1142,11 +1219,11 @@ class AstroSuiteProMainWindow(
|
|
|
1142
1219
|
global_pos = lw.viewport().mapToGlobal(pos)
|
|
1143
1220
|
|
|
1144
1221
|
menu = QMenu(lw)
|
|
1145
|
-
act_copy_selected = menu.addAction("Copy Selected")
|
|
1146
|
-
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"))
|
|
1147
1224
|
menu.addSeparator()
|
|
1148
|
-
act_select_all = menu.addAction("Select All Lines")
|
|
1149
|
-
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"))
|
|
1150
1227
|
|
|
1151
1228
|
action = menu.exec(global_pos)
|
|
1152
1229
|
if action is None:
|
|
@@ -1740,7 +1817,7 @@ class AstroSuiteProMainWindow(
|
|
|
1740
1817
|
|
|
1741
1818
|
show_view_bundles(self)
|
|
1742
1819
|
except Exception as e:
|
|
1743
|
-
QMessageBox.warning(self, "View Bundles", f"Open failed:\n{e}")
|
|
1820
|
+
QMessageBox.warning(self, self.tr("View Bundles"), f"Open failed:\n{e}")
|
|
1744
1821
|
|
|
1745
1822
|
def _open_function_bundles(self):
|
|
1746
1823
|
from setiastro.saspro.function_bundle import show_function_bundles
|
|
@@ -1748,7 +1825,7 @@ class AstroSuiteProMainWindow(
|
|
|
1748
1825
|
|
|
1749
1826
|
show_function_bundles(self)
|
|
1750
1827
|
except Exception as e:
|
|
1751
|
-
QMessageBox.warning(self, "Function Bundles", f"Open failed:\n{e}")
|
|
1828
|
+
QMessageBox.warning(self, self.tr("Function Bundles"), f"Open failed:\n{e}")
|
|
1752
1829
|
|
|
1753
1830
|
def _open_scripts_folder(self):
|
|
1754
1831
|
if hasattr(self, "scriptman"):
|
|
@@ -1784,6 +1861,7 @@ class AstroSuiteProMainWindow(
|
|
|
1784
1861
|
actions = self._collect_all_qactions()
|
|
1785
1862
|
except Exception:
|
|
1786
1863
|
actions = self.findChildren(QAction)
|
|
1864
|
+
|
|
1787
1865
|
for act in actions:
|
|
1788
1866
|
for seq in _seqs_for_action(act):
|
|
1789
1867
|
rows.append((_qs_to_str(seq), _describe_action(act), _where_for_action(act)))
|
|
@@ -1794,6 +1872,12 @@ class AstroSuiteProMainWindow(
|
|
|
1794
1872
|
if seq and not seq.isEmpty():
|
|
1795
1873
|
rows.append((_qs_to_str(seq), _describe_shortcut(sc), _where_for_shortcut(sc)))
|
|
1796
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
|
+
|
|
1797
1881
|
# De-duplicate and sort by shortcut text
|
|
1798
1882
|
rows = _uniq_keep_order(rows)
|
|
1799
1883
|
rows.sort(key=lambda r: (r[0].lower(), r[1].lower()))
|
|
@@ -1803,43 +1887,43 @@ class AstroSuiteProMainWindow(
|
|
|
1803
1887
|
# Manual list (extend anytime). Format: (Gesture, Context, Effect)
|
|
1804
1888
|
rows = [
|
|
1805
1889
|
# Command search
|
|
1806
|
-
("A", "Display Stretch", "Toggle Display Auto-Stretch"),
|
|
1807
|
-
("Ctrl+I", "Invert", "Invert the Image"),
|
|
1808
|
-
("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")),
|
|
1809
1893
|
|
|
1810
1894
|
# View Icon
|
|
1811
|
-
("Drag view -> Off to Canvas", "View", "Duplicate Image"),
|
|
1812
|
-
("Drag view -> On to Other Image", "View", "Copy Zoom and Pan"),
|
|
1813
|
-
("Shift+Drag -> On to Other Image", "View", "Apply that image to the other as a mask"),
|
|
1814
|
-
("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")),
|
|
1815
1899
|
|
|
1816
1900
|
# View zoom
|
|
1817
|
-
("Ctrl+1", "View", "Zoom to 100% (1:1)"),
|
|
1818
|
-
("Ctrl+0", "View", "Fit image to current window"),
|
|
1819
|
-
("Ctrl++", "View", "Zoom In"),
|
|
1820
|
-
("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")),
|
|
1821
1905
|
|
|
1822
1906
|
# Window switching
|
|
1823
|
-
("Ctrl+PgDown", "MDI", "Switch to previously active view"),
|
|
1824
|
-
("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")),
|
|
1825
1909
|
|
|
1826
1910
|
# Shortcuts canvas + buttons
|
|
1827
|
-
("Alt+Drag (toolbar button)", "Toolbar", "Create a desktop shortcut for that action"),
|
|
1828
|
-
("Alt+Drag (shortcut button -> view)", "Shortcuts", "Headless apply the shortcut's command/preset to a view"),
|
|
1829
|
-
("Ctrl/Shift+Click", "Shortcuts", "Multi-select shortcut buttons"),
|
|
1830
|
-
("Drag (selection)", "Shortcuts", "Move selected shortcut buttons"),
|
|
1831
|
-
("Delete / Backspace", "Shortcuts", "Delete selected shortcut buttons"),
|
|
1832
|
-
("Ctrl+A", "Shortcuts", "Select all shortcut buttons"),
|
|
1833
|
-
("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")),
|
|
1834
1918
|
|
|
1835
1919
|
# Layers dock
|
|
1836
|
-
("Drag view -> Layers list", "Layers", "Add dragged view as a new layer (on top)"),
|
|
1837
|
-
("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")),
|
|
1838
1922
|
|
|
1839
1923
|
# Crop tool
|
|
1840
|
-
("Click-drag", "Crop Tool", "Draw a crop rectangle"),
|
|
1841
|
-
("Drag corner handles", "Crop Tool", "Resize crop rectangle"),
|
|
1842
|
-
("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")),
|
|
1843
1927
|
]
|
|
1844
1928
|
return rows
|
|
1845
1929
|
|
|
@@ -1886,7 +1970,11 @@ class AstroSuiteProMainWindow(
|
|
|
1886
1970
|
except Exception:
|
|
1887
1971
|
pass
|
|
1888
1972
|
|
|
1889
|
-
def _confirm_discard(self, title=
|
|
1973
|
+
def _confirm_discard(self, title=None, msg=None):
|
|
1974
|
+
if title is None:
|
|
1975
|
+
title = self.tr("New Project")
|
|
1976
|
+
if msg is None:
|
|
1977
|
+
msg = self.tr("This will close all views and clear desktop shortcuts. Continue?")
|
|
1890
1978
|
btn = QMessageBox.question(self, title, msg,
|
|
1891
1979
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
1892
1980
|
QMessageBox.StandardButton.No)
|
|
@@ -1894,8 +1982,8 @@ class AstroSuiteProMainWindow(
|
|
|
1894
1982
|
|
|
1895
1983
|
def _clear_views_keep_shortcuts(self):
|
|
1896
1984
|
if not self._confirm_discard(
|
|
1897
|
-
title="Clear All Views",
|
|
1898
|
-
msg="Close all views and documents? Desktop shortcuts will be preserved."
|
|
1985
|
+
title=self.tr("Clear All Views"),
|
|
1986
|
+
msg=self.tr("Close all views and documents? Desktop shortcuts will be preserved.")
|
|
1899
1987
|
):
|
|
1900
1988
|
return
|
|
1901
1989
|
|
|
@@ -1952,7 +2040,7 @@ class AstroSuiteProMainWindow(
|
|
|
1952
2040
|
if getattr(self, "doc_manager", None) and self.doc_manager._docs:
|
|
1953
2041
|
if not self._confirm_discard(
|
|
1954
2042
|
title=title,
|
|
1955
|
-
msg=(
|
|
2043
|
+
msg=self.tr(
|
|
1956
2044
|
"Loading a project will close current views and replace desktop shortcuts.\n"
|
|
1957
2045
|
"Continue?"
|
|
1958
2046
|
),
|
|
@@ -2072,9 +2160,30 @@ class AstroSuiteProMainWindow(
|
|
|
2072
2160
|
self._refresh_mask_action_states()
|
|
2073
2161
|
|
|
2074
2162
|
|
|
2163
|
+
# ---------------- Settings Caching ----------------
|
|
2164
|
+
def _preload_settings(self):
|
|
2165
|
+
"""Build the SettingsDialog in the background so it opens instantly."""
|
|
2166
|
+
if getattr(self, "_settings_dlg_cache", None) is None:
|
|
2167
|
+
from setiastro.saspro.ops.settings import SettingsDialog
|
|
2168
|
+
try:
|
|
2169
|
+
self._settings_dlg_cache = SettingsDialog(self, self.settings)
|
|
2170
|
+
except Exception as e:
|
|
2171
|
+
# Log error but don't crash if preload fails
|
|
2172
|
+
print(f"Error preloading settings: {e}")
|
|
2173
|
+
|
|
2075
2174
|
def _open_settings(self):
|
|
2076
2175
|
from setiastro.saspro.ops.settings import SettingsDialog
|
|
2077
|
-
|
|
2176
|
+
|
|
2177
|
+
# Create cache if it doesn't exist (e.g. opened before preload timer fired)
|
|
2178
|
+
if getattr(self, "_settings_dlg_cache", None) is None:
|
|
2179
|
+
self._settings_dlg_cache = SettingsDialog(self, self.settings)
|
|
2180
|
+
|
|
2181
|
+
dlg = self._settings_dlg_cache
|
|
2182
|
+
|
|
2183
|
+
# Refresh UI from current settings before showing
|
|
2184
|
+
if hasattr(dlg, "refresh_ui"):
|
|
2185
|
+
dlg.refresh_ui()
|
|
2186
|
+
|
|
2078
2187
|
if dlg.exec():
|
|
2079
2188
|
# (Optional) react to changes if needed
|
|
2080
2189
|
pass
|
|
@@ -2592,8 +2701,8 @@ class AstroSuiteProMainWindow(
|
|
|
2592
2701
|
except Exception:
|
|
2593
2702
|
pass
|
|
2594
2703
|
|
|
2595
|
-
#
|
|
2596
|
-
dlg.setWindowFlag(Qt.WindowType.WindowStaysOnTopHint, True)
|
|
2704
|
+
# this is the key: stay on top
|
|
2705
|
+
# dlg.setWindowFlag(Qt.WindowType.WindowStaysOnTopHint, True)
|
|
2597
2706
|
|
|
2598
2707
|
dlg.show()
|
|
2599
2708
|
dlg.raise_()
|
|
@@ -4263,7 +4372,7 @@ class AstroSuiteProMainWindow(
|
|
|
4263
4372
|
dlg.show()
|
|
4264
4373
|
|
|
4265
4374
|
def _open_whats_in_my_sky(self):
|
|
4266
|
-
from wims import WhatsInMySkyDialog
|
|
4375
|
+
from setiastro.saspro.wims import WhatsInMySkyDialog
|
|
4267
4376
|
dlg = WhatsInMySkyDialog(
|
|
4268
4377
|
parent=self,
|
|
4269
4378
|
wims_path=wims_path, # window icon
|
|
@@ -4275,7 +4384,7 @@ class AstroSuiteProMainWindow(
|
|
|
4275
4384
|
|
|
4276
4385
|
def _open_wimi(self):
|
|
4277
4386
|
# Lazy import to avoid loading lightkurve at startup (~12s)
|
|
4278
|
-
from wimi import WIMIDialog
|
|
4387
|
+
from setiastro.saspro.wimi import WIMIDialog
|
|
4279
4388
|
dlg = WIMIDialog(
|
|
4280
4389
|
parent=self,
|
|
4281
4390
|
settings=getattr(self, "settings", None),
|
|
@@ -5446,6 +5555,10 @@ class AstroSuiteProMainWindow(
|
|
|
5446
5555
|
"rotate_180": "geom_rotate_180",
|
|
5447
5556
|
"geom_rotate_180": "geom_rotate_180",
|
|
5448
5557
|
|
|
5558
|
+
"rotate_any": "geom_rotate_any",
|
|
5559
|
+
"rotate_arbitrary": "geom_rotate_any",
|
|
5560
|
+
"geom_rotate_any": "geom_rotate_any",
|
|
5561
|
+
|
|
5449
5562
|
"invert": "geom_invert",
|
|
5450
5563
|
"geom_invert": "geom_invert",
|
|
5451
5564
|
|
|
@@ -6446,6 +6559,17 @@ class AstroSuiteProMainWindow(
|
|
|
6446
6559
|
QMessageBox.warning(self, "Rotate 180Â deg", str(e))
|
|
6447
6560
|
return
|
|
6448
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
|
+
|
|
6449
6573
|
if cid == "geom_rescale":
|
|
6450
6574
|
try:
|
|
6451
6575
|
factor = float(preset.get("factor", 1.0))
|
|
@@ -7251,7 +7375,7 @@ class AstroSuiteProMainWindow(
|
|
|
7251
7375
|
self._search_dock = None
|
|
7252
7376
|
|
|
7253
7377
|
# --- Right-side mini dock with the search box ---
|
|
7254
|
-
self._search_dock = QDockWidget("Command Search", self)
|
|
7378
|
+
self._search_dock = QDockWidget(self.tr("Command Search"), self)
|
|
7255
7379
|
self._search_dock.setObjectName("CommandSearchDock")
|
|
7256
7380
|
# âœ... Allow moving/closing like other panels
|
|
7257
7381
|
self._search_dock.setAllowedAreas(
|
|
@@ -7466,7 +7590,7 @@ class AstroSuiteProMainWindow(
|
|
|
7466
7590
|
except Exception:
|
|
7467
7591
|
pass
|
|
7468
7592
|
|
|
7469
|
-
try: self.
|
|
7593
|
+
try: self._schedule_undo_redo_label_refresh()
|
|
7470
7594
|
except Exception as e:
|
|
7471
7595
|
import logging
|
|
7472
7596
|
logging.debug(f"Exception suppressed: {type(e).__name__}: {e}")
|
|
@@ -7726,6 +7850,90 @@ class AstroSuiteProMainWindow(
|
|
|
7726
7850
|
flags |= Qt.WindowType.WindowMaximizeButtonHint
|
|
7727
7851
|
sw.setWindowFlags(flags)
|
|
7728
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
|
+
|
|
7729
7937
|
# ⌠removed the "fill MDI viewport" block - we *don't* want full-monitor first window
|
|
7730
7938
|
|
|
7731
7939
|
# Show / activate
|
|
@@ -7870,7 +8078,7 @@ class AstroSuiteProMainWindow(
|
|
|
7870
8078
|
# If no subwindows remain, clear all "active doc" UI bits, including header
|
|
7871
8079
|
if not self.mdi.subWindowList():
|
|
7872
8080
|
self.currentDocumentChanged.emit(None) # drives HeaderViewerDock.set_document(None)
|
|
7873
|
-
self.
|
|
8081
|
+
self._schedule_undo_redo_label_refresh()
|
|
7874
8082
|
self._hdr_refresh_timer.start(0) # belt-and-suspenders for manual widgets
|
|
7875
8083
|
# If your dock has its own set_document, call it explicitly too
|
|
7876
8084
|
hv = getattr(self, "header_viewer", None)
|
|
@@ -8212,19 +8420,20 @@ class AstroSuiteProMainWindow(
|
|
|
8212
8420
|
|
|
8213
8421
|
# Misc UI refreshes (guarded)
|
|
8214
8422
|
try:
|
|
8215
|
-
self.
|
|
8216
|
-
except Exception:
|
|
8217
|
-
pass
|
|
8218
|
-
try:
|
|
8219
|
-
if hasattr(self, "_hdr_refresh_timer") and self._hdr_refresh_timer is not None:
|
|
8220
|
-
self._hdr_refresh_timer.start(0)
|
|
8423
|
+
self._schedule_undo_redo_label_refresh()
|
|
8221
8424
|
except Exception:
|
|
8222
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
|
|
8223
8431
|
try:
|
|
8224
8432
|
self._refresh_mask_action_states()
|
|
8225
8433
|
except Exception:
|
|
8226
8434
|
pass
|
|
8227
8435
|
|
|
8436
|
+
|
|
8228
8437
|
def _sync_docman_active(self, doc):
|
|
8229
8438
|
dm = self.doc_manager
|
|
8230
8439
|
try:
|
|
@@ -8385,7 +8594,131 @@ class AstroSuiteProMainWindow(
|
|
|
8385
8594
|
if self._suspend_dock_sync:
|
|
8386
8595
|
QTimer.singleShot(0, lambda: self.changeEvent(QEvent(QEvent.Type.WindowStateChange)))
|
|
8387
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
|
+
|
|
8631
|
+
def save_ui_state(self):
|
|
8632
|
+
"""Save window geometry, state, and shortcuts to settings."""
|
|
8633
|
+
self._ensure_persistent_names()
|
|
8634
|
+
try:
|
|
8635
|
+
if self.isMaximized():
|
|
8636
|
+
self.settings.setValue("ui/main/maximized", True)
|
|
8637
|
+
# To get accurate geometry for non-maximized state, we'd need to showNormal()
|
|
8638
|
+
# but that causes flicker. For a restart, we'll just save what we have.
|
|
8639
|
+
else:
|
|
8640
|
+
self.settings.setValue("ui/main/maximized", False)
|
|
8641
|
+
self.settings.setValue("ui/main/geometry", self.saveGeometry())
|
|
8642
|
+
|
|
8643
|
+
self.settings.setValue("ui/main/state", self.saveState(version=1))
|
|
8644
|
+
except Exception:
|
|
8645
|
+
pass
|
|
8646
|
+
|
|
8647
|
+
# save shortcuts
|
|
8648
|
+
try:
|
|
8649
|
+
save_on_exit = self.settings.value("shortcuts/save_on_exit", True, type=bool)
|
|
8650
|
+
except Exception:
|
|
8651
|
+
save_on_exit = True
|
|
8652
|
+
if save_on_exit and hasattr(self, "shortcuts"):
|
|
8653
|
+
try:
|
|
8654
|
+
self.shortcuts.save_shortcuts()
|
|
8655
|
+
except Exception:
|
|
8656
|
+
pass
|
|
8657
|
+
|
|
8658
|
+
self.settings.sync()
|
|
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
|
+
|
|
8388
8708
|
def closeEvent(self, e):
|
|
8709
|
+
self._update_usage_stats()
|
|
8710
|
+
|
|
8711
|
+
|
|
8712
|
+
# Optimization: If restarting (e.g. language change), bypass confirmation and close immediately
|
|
8713
|
+
if getattr(self, "_is_restarting", False):
|
|
8714
|
+
e.accept()
|
|
8715
|
+
return
|
|
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
|
|
8389
8722
|
|
|
8390
8723
|
try:
|
|
8391
8724
|
if hasattr(self, "_orig_stdout") and self._orig_stdout is not None:
|
|
@@ -8394,6 +8727,8 @@ class AstroSuiteProMainWindow(
|
|
|
8394
8727
|
sys.stderr = self._orig_stderr
|
|
8395
8728
|
except Exception:
|
|
8396
8729
|
pass
|
|
8730
|
+
|
|
8731
|
+
# --- Confirmation Logic ---
|
|
8397
8732
|
self._shutting_down = True
|
|
8398
8733
|
# Gather open docs
|
|
8399
8734
|
docs = []
|
|
@@ -8404,64 +8739,73 @@ class AstroSuiteProMainWindow(
|
|
|
8404
8739
|
docs.append(d)
|
|
8405
8740
|
|
|
8406
8741
|
edited = [d for d in docs if self._document_has_edits(d)]
|
|
8407
|
-
|
|
8408
|
-
|
|
8409
|
-
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
detail
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
|
|
8418
|
-
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
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
|
|
8430
8773
|
|
|
8431
|
-
|
|
8432
|
-
|
|
8433
|
-
|
|
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()
|
|
8434
8801
|
|
|
8435
|
-
|
|
8802
|
+
def _do_shutdown_steps(self, e):
|
|
8803
|
+
"""Actual shutdown logic after verification and animation."""
|
|
8436
8804
|
self._force_close_all = True
|
|
8437
8805
|
self._shutting_down = True
|
|
8438
8806
|
|
|
8439
8807
|
# Save UI layout/placement
|
|
8440
|
-
self.
|
|
8441
|
-
try:
|
|
8442
|
-
if self.isMaximized():
|
|
8443
|
-
self.settings.setValue("ui/main/maximized", True)
|
|
8444
|
-
self.showNormal()
|
|
8445
|
-
self.settings.setValue("ui/main/geometry", self.saveGeometry())
|
|
8446
|
-
self.showMaximized()
|
|
8447
|
-
else:
|
|
8448
|
-
self.settings.setValue("ui/main/maximized", False)
|
|
8449
|
-
self.settings.setValue("ui/main/geometry", self.saveGeometry())
|
|
8450
|
-
|
|
8451
|
-
self.settings.setValue("ui/main/state", self.saveState(version=1))
|
|
8452
|
-
except Exception:
|
|
8453
|
-
pass
|
|
8454
|
-
|
|
8455
|
-
# save shortcuts
|
|
8456
|
-
try:
|
|
8457
|
-
save_on_exit = self.settings.value("shortcuts/save_on_exit", True, type=bool)
|
|
8458
|
-
except Exception:
|
|
8459
|
-
save_on_exit = True
|
|
8460
|
-
if save_on_exit and hasattr(self, "shortcuts"):
|
|
8461
|
-
try:
|
|
8462
|
-
self.shortcuts.save_shortcuts()
|
|
8463
|
-
except Exception:
|
|
8464
|
-
pass
|
|
8808
|
+
self.save_ui_state()
|
|
8465
8809
|
|
|
8466
8810
|
# wait on bg threads
|
|
8467
8811
|
try:
|
|
@@ -8480,6 +8824,7 @@ class AstroSuiteProMainWindow(
|
|
|
8480
8824
|
# CheatSheet dialog and helper functions imported from setiastro.saspro.cheat_sheet
|
|
8481
8825
|
from setiastro.saspro.cheat_sheet import (
|
|
8482
8826
|
CheatSheetDialog as _CheatSheetDialog,
|
|
8827
|
+
add_extra_shortcuts,
|
|
8483
8828
|
_qs_to_str,
|
|
8484
8829
|
_clean_text,
|
|
8485
8830
|
_uniq_keep_order,
|