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.
Files changed (293) hide show
  1. setiastro/data/SASP_data.fits +0 -0
  2. setiastro/data/catalogs/List_of_Galaxies_with_Distances_Gly.csv +488 -0
  3. setiastro/data/catalogs/astrobin_filters.csv +890 -0
  4. setiastro/data/catalogs/astrobin_filters_page1_local.csv +51 -0
  5. setiastro/data/catalogs/cali2.csv +63 -0
  6. setiastro/data/catalogs/cali2color.csv +65 -0
  7. setiastro/data/catalogs/celestial_catalog - original.csv +16471 -0
  8. setiastro/data/catalogs/celestial_catalog.csv +24031 -0
  9. setiastro/data/catalogs/detected_stars.csv +24784 -0
  10. setiastro/data/catalogs/fits_header_data.csv +46 -0
  11. setiastro/data/catalogs/test.csv +8 -0
  12. setiastro/data/catalogs/updated_celestial_catalog.csv +16471 -0
  13. setiastro/images/Astro_Spikes.png +0 -0
  14. setiastro/images/Background_startup.jpg +0 -0
  15. setiastro/images/HRDiagram.png +0 -0
  16. setiastro/images/LExtract.png +0 -0
  17. setiastro/images/LInsert.png +0 -0
  18. setiastro/images/Oxygenation-atm-2.svg.png +0 -0
  19. setiastro/images/RGB080604.png +0 -0
  20. setiastro/images/abeicon.png +0 -0
  21. setiastro/images/aberration.png +0 -0
  22. setiastro/images/andromedatry.png +0 -0
  23. setiastro/images/andromedatry_satellited.png +0 -0
  24. setiastro/images/annotated.png +0 -0
  25. setiastro/images/aperture.png +0 -0
  26. setiastro/images/astrosuite.ico +0 -0
  27. setiastro/images/astrosuite.png +0 -0
  28. setiastro/images/astrosuitepro.icns +0 -0
  29. setiastro/images/astrosuitepro.ico +0 -0
  30. setiastro/images/astrosuitepro.png +0 -0
  31. setiastro/images/background.png +0 -0
  32. setiastro/images/background2.png +0 -0
  33. setiastro/images/benchmark.png +0 -0
  34. setiastro/images/big_moon_stabilizer_timeline.png +0 -0
  35. setiastro/images/big_moon_stabilizer_timeline_clean.png +0 -0
  36. setiastro/images/blaster.png +0 -0
  37. setiastro/images/blink.png +0 -0
  38. setiastro/images/clahe.png +0 -0
  39. setiastro/images/collage.png +0 -0
  40. setiastro/images/colorwheel.png +0 -0
  41. setiastro/images/contsub.png +0 -0
  42. setiastro/images/convo.png +0 -0
  43. setiastro/images/copyslot.png +0 -0
  44. setiastro/images/cosmic.png +0 -0
  45. setiastro/images/cosmicsat.png +0 -0
  46. setiastro/images/crop1.png +0 -0
  47. setiastro/images/cropicon.png +0 -0
  48. setiastro/images/curves.png +0 -0
  49. setiastro/images/cvs.png +0 -0
  50. setiastro/images/debayer.png +0 -0
  51. setiastro/images/denoise_cnn_custom.png +0 -0
  52. setiastro/images/denoise_cnn_graph.png +0 -0
  53. setiastro/images/disk.png +0 -0
  54. setiastro/images/dse.png +0 -0
  55. setiastro/images/exoicon.png +0 -0
  56. setiastro/images/eye.png +0 -0
  57. setiastro/images/fliphorizontal.png +0 -0
  58. setiastro/images/flipvertical.png +0 -0
  59. setiastro/images/font.png +0 -0
  60. setiastro/images/freqsep.png +0 -0
  61. setiastro/images/functionbundle.png +0 -0
  62. setiastro/images/graxpert.png +0 -0
  63. setiastro/images/green.png +0 -0
  64. setiastro/images/gridicon.png +0 -0
  65. setiastro/images/halo.png +0 -0
  66. setiastro/images/hdr.png +0 -0
  67. setiastro/images/histogram.png +0 -0
  68. setiastro/images/hubble.png +0 -0
  69. setiastro/images/imagecombine.png +0 -0
  70. setiastro/images/invert.png +0 -0
  71. setiastro/images/isophote.png +0 -0
  72. setiastro/images/isophote_demo_figure.png +0 -0
  73. setiastro/images/isophote_demo_image.png +0 -0
  74. setiastro/images/isophote_demo_model.png +0 -0
  75. setiastro/images/isophote_demo_residual.png +0 -0
  76. setiastro/images/jwstpupil.png +0 -0
  77. setiastro/images/linearfit.png +0 -0
  78. setiastro/images/livestacking.png +0 -0
  79. setiastro/images/mask.png +0 -0
  80. setiastro/images/maskapply.png +0 -0
  81. setiastro/images/maskcreate.png +0 -0
  82. setiastro/images/maskremove.png +0 -0
  83. setiastro/images/morpho.png +0 -0
  84. setiastro/images/mosaic.png +0 -0
  85. setiastro/images/multiscale_decomp.png +0 -0
  86. setiastro/images/nbtorgb.png +0 -0
  87. setiastro/images/neutral.png +0 -0
  88. setiastro/images/nuke.png +0 -0
  89. setiastro/images/openfile.png +0 -0
  90. setiastro/images/pedestal.png +0 -0
  91. setiastro/images/pen.png +0 -0
  92. setiastro/images/pixelmath.png +0 -0
  93. setiastro/images/platesolve.png +0 -0
  94. setiastro/images/ppp.png +0 -0
  95. setiastro/images/pro.png +0 -0
  96. setiastro/images/project.png +0 -0
  97. setiastro/images/psf.png +0 -0
  98. setiastro/images/redo.png +0 -0
  99. setiastro/images/redoicon.png +0 -0
  100. setiastro/images/rescale.png +0 -0
  101. setiastro/images/rgbalign.png +0 -0
  102. setiastro/images/rgbcombo.png +0 -0
  103. setiastro/images/rgbextract.png +0 -0
  104. setiastro/images/rotate180.png +0 -0
  105. setiastro/images/rotatearbitrary.png +0 -0
  106. setiastro/images/rotateclockwise.png +0 -0
  107. setiastro/images/rotatecounterclockwise.png +0 -0
  108. setiastro/images/satellite.png +0 -0
  109. setiastro/images/script.png +0 -0
  110. setiastro/images/selectivecolor.png +0 -0
  111. setiastro/images/simbad.png +0 -0
  112. setiastro/images/slot0.png +0 -0
  113. setiastro/images/slot1.png +0 -0
  114. setiastro/images/slot2.png +0 -0
  115. setiastro/images/slot3.png +0 -0
  116. setiastro/images/slot4.png +0 -0
  117. setiastro/images/slot5.png +0 -0
  118. setiastro/images/slot6.png +0 -0
  119. setiastro/images/slot7.png +0 -0
  120. setiastro/images/slot8.png +0 -0
  121. setiastro/images/slot9.png +0 -0
  122. setiastro/images/spcc.png +0 -0
  123. setiastro/images/spin_precession_vs_lunar_distance.png +0 -0
  124. setiastro/images/spinner.gif +0 -0
  125. setiastro/images/stacking.png +0 -0
  126. setiastro/images/staradd.png +0 -0
  127. setiastro/images/staralign.png +0 -0
  128. setiastro/images/starnet.png +0 -0
  129. setiastro/images/starregistration.png +0 -0
  130. setiastro/images/starspike.png +0 -0
  131. setiastro/images/starstretch.png +0 -0
  132. setiastro/images/statstretch.png +0 -0
  133. setiastro/images/supernova.png +0 -0
  134. setiastro/images/uhs.png +0 -0
  135. setiastro/images/undoicon.png +0 -0
  136. setiastro/images/upscale.png +0 -0
  137. setiastro/images/viewbundle.png +0 -0
  138. setiastro/images/whitebalance.png +0 -0
  139. setiastro/images/wimi_icon_256x256.png +0 -0
  140. setiastro/images/wimilogo.png +0 -0
  141. setiastro/images/wims.png +0 -0
  142. setiastro/images/wrench_icon.png +0 -0
  143. setiastro/images/xisfliberator.png +0 -0
  144. setiastro/qml/ResourceMonitor.qml +126 -0
  145. setiastro/saspro/__main__.py +228 -67
  146. setiastro/saspro/_generated/build_info.py +2 -1
  147. setiastro/saspro/abe.py +76 -25
  148. setiastro/saspro/aberration_ai.py +14 -14
  149. setiastro/saspro/add_stars.py +15 -12
  150. setiastro/saspro/astrobin_exporter.py +61 -58
  151. setiastro/saspro/astrospike_python.py +3 -1
  152. setiastro/saspro/autostretch.py +4 -2
  153. setiastro/saspro/backgroundneutral.py +65 -14
  154. setiastro/saspro/batch_convert.py +8 -5
  155. setiastro/saspro/batch_renamer.py +39 -36
  156. setiastro/saspro/blemish_blaster.py +15 -12
  157. setiastro/saspro/blink_comparator_pro.py +605 -379
  158. setiastro/saspro/cheat_sheet.py +62 -17
  159. setiastro/saspro/clahe.py +34 -8
  160. setiastro/saspro/comet_stacking.py +103 -38
  161. setiastro/saspro/common_tr.py +107 -0
  162. setiastro/saspro/continuum_subtract.py +7 -7
  163. setiastro/saspro/convo.py +12 -9
  164. setiastro/saspro/copyastro.py +3 -0
  165. setiastro/saspro/cosmicclarity.py +77 -52
  166. setiastro/saspro/crop_dialog_pro.py +80 -45
  167. setiastro/saspro/curve_editor_pro.py +51 -33
  168. setiastro/saspro/debayer.py +6 -3
  169. setiastro/saspro/doc_manager.py +49 -19
  170. setiastro/saspro/exoplanet_detector.py +11 -11
  171. setiastro/saspro/fitsmodifier.py +48 -44
  172. setiastro/saspro/fix_bom.py +32 -0
  173. setiastro/saspro/frequency_separation.py +18 -12
  174. setiastro/saspro/function_bundle.py +18 -16
  175. setiastro/saspro/generate_translations.py +3092 -0
  176. setiastro/saspro/ghs_dialog_pro.py +19 -16
  177. setiastro/saspro/graxpert.py +3 -0
  178. setiastro/saspro/gui/main_window.py +471 -126
  179. setiastro/saspro/gui/mixins/dock_mixin.py +123 -11
  180. setiastro/saspro/gui/mixins/file_mixin.py +25 -20
  181. setiastro/saspro/gui/mixins/geometry_mixin.py +115 -15
  182. setiastro/saspro/gui/mixins/header_mixin.py +6 -6
  183. setiastro/saspro/gui/mixins/mask_mixin.py +8 -8
  184. setiastro/saspro/gui/mixins/menu_mixin.py +62 -33
  185. setiastro/saspro/gui/mixins/toolbar_mixin.py +382 -226
  186. setiastro/saspro/gui/mixins/update_mixin.py +26 -26
  187. setiastro/saspro/gui/statistics_dialog.py +47 -0
  188. setiastro/saspro/halobgon.py +29 -3
  189. setiastro/saspro/header_viewer.py +21 -18
  190. setiastro/saspro/histogram.py +29 -26
  191. setiastro/saspro/history_explorer.py +2 -0
  192. setiastro/saspro/i18n.py +168 -0
  193. setiastro/saspro/image_combine.py +3 -0
  194. setiastro/saspro/image_peeker_pro.py +52 -44
  195. setiastro/saspro/imageops/stretch.py +5 -13
  196. setiastro/saspro/isophote.py +3 -0
  197. setiastro/saspro/legacy/numba_utils.py +64 -47
  198. setiastro/saspro/linear_fit.py +3 -0
  199. setiastro/saspro/live_stacking.py +13 -2
  200. setiastro/saspro/mask_creation.py +180 -22
  201. setiastro/saspro/mfdeconv.py +5 -0
  202. setiastro/saspro/morphology.py +38 -13
  203. setiastro/saspro/multiscale_decomp.py +713 -256
  204. setiastro/saspro/nbtorgb_stars.py +12 -2
  205. setiastro/saspro/numba_utils.py +149 -48
  206. setiastro/saspro/ops/scripts.py +77 -17
  207. setiastro/saspro/ops/settings.py +177 -100
  208. setiastro/saspro/perfect_palette_picker.py +25 -7
  209. setiastro/saspro/pixelmath.py +114 -110
  210. setiastro/saspro/plate_solver.py +118 -108
  211. setiastro/saspro/remove_green.py +24 -7
  212. setiastro/saspro/remove_stars.py +136 -162
  213. setiastro/saspro/remove_stars_preset.py +55 -13
  214. setiastro/saspro/resources.py +46 -15
  215. setiastro/saspro/rgb_combination.py +19 -18
  216. setiastro/saspro/rgbalign.py +11 -11
  217. setiastro/saspro/save_options.py +5 -4
  218. setiastro/saspro/selective_color.py +84 -25
  219. setiastro/saspro/sfcc.py +119 -72
  220. setiastro/saspro/shortcuts.py +345 -36
  221. setiastro/saspro/signature_insert.py +4 -1
  222. setiastro/saspro/stacking_suite.py +2066 -1119
  223. setiastro/saspro/star_alignment.py +291 -331
  224. setiastro/saspro/star_spikes.py +137 -53
  225. setiastro/saspro/star_stretch.py +47 -10
  226. setiastro/saspro/stat_stretch.py +52 -16
  227. setiastro/saspro/status_log_dock.py +1 -1
  228. setiastro/saspro/subwindow.py +97 -36
  229. setiastro/saspro/supernovaasteroidhunter.py +68 -61
  230. setiastro/saspro/swap_manager.py +77 -42
  231. setiastro/saspro/translations/all_source_strings.json +4726 -0
  232. setiastro/saspro/translations/ar_translations.py +4096 -0
  233. setiastro/saspro/translations/de_translations.py +3728 -0
  234. setiastro/saspro/translations/es_translations.py +4169 -0
  235. setiastro/saspro/translations/fr_translations.py +4090 -0
  236. setiastro/saspro/translations/hi_translations.py +3803 -0
  237. setiastro/saspro/translations/integrate_translations.py +271 -0
  238. setiastro/saspro/translations/it_translations.py +4728 -0
  239. setiastro/saspro/translations/ja_translations.py +3834 -0
  240. setiastro/saspro/translations/pt_translations.py +3847 -0
  241. setiastro/saspro/translations/ru_translations.py +3082 -0
  242. setiastro/saspro/translations/saspro_ar.qm +0 -0
  243. setiastro/saspro/translations/saspro_ar.ts +16019 -0
  244. setiastro/saspro/translations/saspro_de.qm +0 -0
  245. setiastro/saspro/translations/saspro_de.ts +14548 -0
  246. setiastro/saspro/translations/saspro_es.qm +0 -0
  247. setiastro/saspro/translations/saspro_es.ts +16202 -0
  248. setiastro/saspro/translations/saspro_fr.qm +0 -0
  249. setiastro/saspro/translations/saspro_fr.ts +15870 -0
  250. setiastro/saspro/translations/saspro_hi.qm +0 -0
  251. setiastro/saspro/translations/saspro_hi.ts +14855 -0
  252. setiastro/saspro/translations/saspro_it.qm +0 -0
  253. setiastro/saspro/translations/saspro_it.ts +19046 -0
  254. setiastro/saspro/translations/saspro_ja.qm +0 -0
  255. setiastro/saspro/translations/saspro_ja.ts +14980 -0
  256. setiastro/saspro/translations/saspro_pt.qm +0 -0
  257. setiastro/saspro/translations/saspro_pt.ts +15024 -0
  258. setiastro/saspro/translations/saspro_ru.qm +0 -0
  259. setiastro/saspro/translations/saspro_ru.ts +11835 -0
  260. setiastro/saspro/translations/saspro_sw.qm +0 -0
  261. setiastro/saspro/translations/saspro_sw.ts +15237 -0
  262. setiastro/saspro/translations/saspro_uk.qm +0 -0
  263. setiastro/saspro/translations/saspro_uk.ts +15248 -0
  264. setiastro/saspro/translations/saspro_zh.qm +0 -0
  265. setiastro/saspro/translations/saspro_zh.ts +15289 -0
  266. setiastro/saspro/translations/sw_translations.py +3897 -0
  267. setiastro/saspro/translations/uk_translations.py +3929 -0
  268. setiastro/saspro/translations/zh_translations.py +3910 -0
  269. setiastro/saspro/versioning.py +77 -0
  270. setiastro/saspro/view_bundle.py +20 -17
  271. setiastro/saspro/wavescale_hdr.py +54 -33
  272. setiastro/saspro/wavescale_hdr_preset.py +6 -5
  273. setiastro/saspro/wavescalede.py +54 -31
  274. setiastro/saspro/wavescalede_preset.py +9 -7
  275. setiastro/saspro/whitebalance.py +58 -22
  276. setiastro/saspro/widgets/common_utilities.py +12 -11
  277. setiastro/saspro/widgets/minigame/game.js +991 -0
  278. setiastro/saspro/widgets/minigame/index.html +53 -0
  279. setiastro/saspro/widgets/minigame/style.css +241 -0
  280. setiastro/saspro/widgets/preview_dialogs.py +8 -8
  281. setiastro/saspro/widgets/resource_monitor.py +263 -0
  282. setiastro/saspro/widgets/spinboxes.py +18 -0
  283. setiastro/saspro/widgets/wavelet_utils.py +52 -20
  284. setiastro/saspro/wimi.py +7996 -0
  285. setiastro/saspro/wims.py +578 -0
  286. setiastro/saspro/window_shelf.py +2 -2
  287. {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/METADATA +15 -3
  288. setiastrosuitepro-1.6.4.post1.dist-info/RECORD +368 -0
  289. setiastrosuitepro-1.6.0.dist-info/RECORD +0 -174
  290. {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/WHEEL +0 -0
  291. {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/entry_points.txt +0 -0
  292. {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/licenses/LICENSE +0 -0
  293. {setiastrosuitepro-1.6.0.dist-info → setiastrosuitepro-1.6.4.post1.dist-info}/licenses/license.txt +0 -0
@@ -5,6 +5,9 @@ from PyQt6.QtWidgets import (
5
5
  from PyQt6.QtCore import QSettings, Qt
6
6
  import pytz # for timezone list
7
7
 
8
+ # i18n support
9
+ from setiastro.saspro.i18n import get_available_languages, get_saved_language, save_language
10
+
8
11
 
9
12
  class SettingsDialog(QDialog):
10
13
  """
@@ -13,8 +16,11 @@ class SettingsDialog(QDialog):
13
16
  """
14
17
  def __init__(self, parent, settings: QSettings):
15
18
  super().__init__(parent)
16
- self.setWindowTitle("Preferences")
19
+ self.setWindowTitle(self.tr("Preferences"))
17
20
  self.settings = settings
21
+
22
+ # Ensure we don't delete on close, so we can cache it
23
+ self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, False)
18
24
 
19
25
  # ---- Existing fields (paths, checkboxes, etc.) ----
20
26
  self.le_graxpert = QLineEdit()
@@ -22,23 +28,13 @@ class SettingsDialog(QDialog):
22
28
  self.le_starnet = QLineEdit()
23
29
  self.le_astap = QLineEdit()
24
30
 
25
- self.chk_updates_startup = QCheckBox("Check for updates on startup")
26
- self.chk_updates_startup.setChecked(
27
- self.settings.value("updates/check_on_startup", True, type=bool)
28
- )
31
+ self.chk_updates_startup = QCheckBox(self.tr("Check for updates on startup"))
29
32
 
30
33
  self.le_updates_url = QLineEdit()
31
34
  self.le_updates_url.setPlaceholderText("Raw JSON URL (advanced)")
32
- self.le_updates_url.setText(
33
- self.settings.value(
34
- "updates/url",
35
- "https://raw.githubusercontent.com/setiastro/setiastrosuitepro/main/updates.json",
36
- type=str
37
- )
38
- )
39
35
 
40
- btn_reset_updates_url = QPushButton("Reset")
41
- btn_reset_updates_url.setToolTip("Restore default updates URL")
36
+ btn_reset_updates_url = QPushButton(self.tr("Reset"))
37
+ btn_reset_updates_url.setToolTip(self.tr("Restore default updates URL"))
42
38
  btn_reset_updates_url.clicked.connect(
43
39
  lambda: self.le_updates_url.setText(
44
40
  "https://raw.githubusercontent.com/setiastro/setiastrosuitepro/main/updates.json"
@@ -46,8 +42,8 @@ class SettingsDialog(QDialog):
46
42
  )
47
43
 
48
44
  # Optional: “Check Now…” button
49
- self.btn_check_now = QPushButton("Check Now…")
50
- self.btn_check_now.setToolTip("Run an update check immediately")
45
+ self.btn_check_now = QPushButton(self.tr("Check Now…"))
46
+ self.btn_check_now.setToolTip(self.tr("Run an update check immediately"))
51
47
  self.btn_check_now.setVisible(hasattr(parent, "_check_for_updates_async"))
52
48
  self.btn_check_now.clicked.connect(self._check_updates_now_clicked)
53
49
 
@@ -57,37 +53,31 @@ class SettingsDialog(QDialog):
57
53
  row_updates_url.addWidget(btn_reset_updates_url)
58
54
  row_updates_url.addWidget(self.btn_check_now)
59
55
 
60
- self.chk_save_shortcuts = QCheckBox("Save desktop shortcuts on exit")
61
- self.chk_save_shortcuts.setChecked(
62
- self.settings.value("shortcuts/save_on_exit", True, type=bool)
63
- )
56
+ self.chk_save_shortcuts = QCheckBox(self.tr("Save desktop shortcuts on exit"))
64
57
 
65
58
  self.cb_theme = QComboBox()
66
59
  # Order: Dark, Gray, Light, System, Custom
67
60
  self.cb_theme.addItems(["Dark", "Gray", "Light", "System", "Custom"])
68
61
 
69
- theme_val = (self.settings.value("ui/theme", "system", type=str) or "system").lower()
70
- index_map = {"dark": 0, "gray": 1, "light": 2, "system": 3, "custom": 4}
71
- self.cb_theme.setCurrentIndex(index_map.get(theme_val, 2))
72
-
73
62
  # "Customize…" button for custom theme
74
- self.btn_theme_custom = QPushButton("Customize…")
75
- self.btn_theme_custom.setToolTip("Edit custom colors and font")
76
- self.btn_theme_custom.setEnabled(theme_val == "custom")
63
+ self.btn_theme_custom = QPushButton(self.tr("Customize…"))
64
+ self.btn_theme_custom.setToolTip(self.tr("Edit custom colors and font"))
77
65
  self.btn_theme_custom.clicked.connect(self._open_theme_editor)
78
66
 
79
67
  # Keep button enabled state in sync with combo
80
68
  self.cb_theme.currentIndexChanged.connect(self._on_theme_changed)
81
69
 
82
- self.le_graxpert.setText(self.settings.value("paths/graxpert", "", type=str))
83
- self.le_cosmic.setText(self.settings.value("paths/cosmic_clarity", "", type=str))
84
- self.le_starnet.setText(self.settings.value("paths/starnet", "", type=str))
85
- self.le_astap.setText(self.settings.value("paths/astap", "", type=str))
70
+ # ---- Language selector ----
71
+ self.cb_language = QComboBox()
72
+ self._lang_codes = list(get_available_languages().keys()) # ["en", "it", "fr", "es"]
73
+ self._lang_names = list(get_available_languages().values()) # ["English", "Italiano", ...]
74
+ self.cb_language.addItems(self._lang_names)
75
+ self._initial_language = "en" # placeholder, set in refresh_ui
86
76
 
87
- btn_grax = QPushButton("Browse…"); btn_grax.clicked.connect(lambda: self._browse_into(self.le_graxpert))
88
- btn_ccl = QPushButton("Browse…"); btn_ccl.clicked.connect(lambda: self._browse_dir(self.le_cosmic))
89
- btn_star = QPushButton("Browse…"); btn_star.clicked.connect(lambda: self._browse_into(self.le_starnet))
90
- btn_astap = QPushButton("Browse…"); btn_astap.clicked.connect(lambda: self._browse_into(self.le_astap))
77
+ btn_grax = QPushButton(self.tr("Browse…")); btn_grax.clicked.connect(lambda: self._browse_into(self.le_graxpert))
78
+ btn_ccl = QPushButton(self.tr("Browse…")); btn_ccl.clicked.connect(lambda: self._browse_dir(self.le_cosmic))
79
+ btn_star = QPushButton(self.tr("Browse…")); btn_star.clicked.connect(lambda: self._browse_into(self.le_starnet))
80
+ btn_astap = QPushButton(self.tr("Browse…")); btn_astap.clicked.connect(lambda: self._browse_into(self.le_astap))
91
81
 
92
82
  row_grax = QHBoxLayout(); row_grax.addWidget(self.le_graxpert); row_grax.addWidget(btn_grax)
93
83
  row_ccl = QHBoxLayout(); row_ccl.addWidget(self.le_cosmic); row_ccl.addWidget(btn_ccl)
@@ -96,7 +86,6 @@ class SettingsDialog(QDialog):
96
86
 
97
87
  self.le_astrometry = QLineEdit()
98
88
  self.le_astrometry.setEchoMode(QLineEdit.EchoMode.Password)
99
- self.le_astrometry.setText(self.settings.value("api/astrometry_key", "", type=str))
100
89
 
101
90
  # ---- WIMS defaults ----
102
91
  self.sp_lat = QDoubleSpinBox(); self.sp_lat.setRange(-90.0, 90.0); self.sp_lat.setDecimals(6)
@@ -107,29 +96,14 @@ class SettingsDialog(QDialog):
107
96
  self.sp_min_alt = QDoubleSpinBox(); self.sp_min_alt.setRange(0.0, 90.0); self.sp_min_alt.setDecimals(1)
108
97
  self.sp_obj_limit = QSpinBox(); self.sp_obj_limit.setRange(1, 1000)
109
98
 
110
- self.sp_lat.setValue(self.settings.value("latitude", 0.0, type=float))
111
- self.sp_lon.setValue(self.settings.value("longitude", 0.0, type=float))
112
- self.le_date.setText(self.settings.value("date", "", type=str) or "")
113
- self.le_time.setText(self.settings.value("time", "", type=str) or "")
114
- tz_val = self.settings.value("timezone", "UTC", type=str) or "UTC"
115
- idx = max(0, self.cb_tz.findText(tz_val)); self.cb_tz.setCurrentIndex(idx)
116
- self.sp_min_alt.setValue(self.settings.value("min_altitude", 0.0, type=float))
117
- self.sp_obj_limit.setValue(self.settings.value("object_limit", 100, type=int))
118
-
119
- self.chk_autostretch_16bit = QCheckBox("High-quality autostretch (16-bit; better gradients)")
120
- self.chk_autostretch_16bit.setToolTip("Compute autostretch on a 16-bit histogram (smoother gradients).")
121
- self.chk_autostretch_16bit.setChecked(
122
- self.settings.value("display/autostretch_16bit", True, type=bool)
123
- )
99
+ self.chk_autostretch_16bit = QCheckBox(self.tr("High-quality autostretch (16-bit; better gradients)"))
100
+ self.chk_autostretch_16bit.setToolTip(self.tr("Compute autostretch on a 16-bit histogram (smoother gradients)."))
124
101
 
125
102
  self.slider_bg_opacity = QSlider(Qt.Orientation.Horizontal)
126
103
  self.slider_bg_opacity.setRange(0, 100)
127
- current_opacity = self.settings.value("display/bg_opacity", 50, type=int)
128
- self.slider_bg_opacity.setValue(current_opacity)
129
- # Keep a copy of the original opacity so we can restore it if the user cancels
130
- self._initial_bg_opacity = int(current_opacity)
104
+ self._initial_bg_opacity = 50
131
105
 
132
- self.lbl_bg_opacity_val = QLabel(f"{current_opacity}%")
106
+ self.lbl_bg_opacity_val = QLabel("50%")
133
107
  self.lbl_bg_opacity_val.setFixedWidth(40)
134
108
 
135
109
  def _on_opacity_changed(val):
@@ -153,14 +127,13 @@ class SettingsDialog(QDialog):
153
127
  # ---- Custom background: choose/clear preview ----
154
128
  self.le_bg_path = QLineEdit()
155
129
  self.le_bg_path.setReadOnly(True)
156
- # remember initial custom background so Cancel can restore it
157
- self._initial_bg_path = self.settings.value("ui/custom_background", "", type=str) or ""
158
- self.le_bg_path.setText(self._initial_bg_path)
159
- btn_choose_bg = QPushButton("Choose Background…")
160
- btn_choose_bg.setToolTip("Pick a PNG or JPG to use as the application background")
130
+ self._initial_bg_path = ""
131
+
132
+ btn_choose_bg = QPushButton(self.tr("Choose Background…"))
133
+ btn_choose_bg.setToolTip(self.tr("Pick a PNG or JPG to use as the application background"))
161
134
  btn_choose_bg.clicked.connect(self._choose_background_clicked)
162
- btn_clear_bg = QPushButton("Clear")
163
- btn_clear_bg.setToolTip("Remove custom background and restore default")
135
+ btn_clear_bg = QPushButton(self.tr("Clear"))
136
+ btn_clear_bg.setToolTip(self.tr("Remove custom background and restore default"))
164
137
  btn_clear_bg.clicked.connect(self._clear_background_clicked)
165
138
 
166
139
  row_bg_image = QHBoxLayout()
@@ -188,57 +161,49 @@ class SettingsDialog(QDialog):
188
161
  cols.addLayout(right_col, 1)
189
162
 
190
163
  # ---- Left column: Paths & Integrations ----
191
- left_col.addRow(QLabel("<b>Paths & Integrations</b>"))
192
- w = QWidget(); w.setLayout(row_grax); left_col.addRow("GraXpert executable:", w)
193
- w = QWidget(); w.setLayout(row_ccl); left_col.addRow("Cosmic Clarity folder:", w)
194
- w = QWidget(); w.setLayout(row_star); left_col.addRow("StarNet executable:", w)
195
- w = QWidget(); w.setLayout(row_astap); left_col.addRow("ASTAP executable:", w)
196
- left_col.addRow("Astrometry.net API key:", self.le_astrometry)
164
+ left_col.addRow(QLabel(self.tr("<b>Paths & Integrations</b>")))
165
+ w = QWidget(); w.setLayout(row_grax); left_col.addRow(self.tr("GraXpert executable:"), w)
166
+ w = QWidget(); w.setLayout(row_ccl); left_col.addRow(self.tr("Cosmic Clarity folder:"), w)
167
+ w = QWidget(); w.setLayout(row_star); left_col.addRow(self.tr("StarNet executable:"), w)
168
+ w = QWidget(); w.setLayout(row_astap); left_col.addRow(self.tr("ASTAP executable:"), w)
169
+ left_col.addRow(self.tr("Astrometry.net API key:"), self.le_astrometry)
197
170
  left_col.addRow(self.chk_save_shortcuts)
198
171
  row_theme = QHBoxLayout()
199
172
  row_theme.addWidget(self.cb_theme, 1)
200
173
  row_theme.addWidget(self.btn_theme_custom)
201
174
  w_theme = QWidget()
202
175
  w_theme.setLayout(row_theme)
203
- left_col.addRow("Theme:", w_theme)
176
+ left_col.addRow(self.tr("Theme:"), w_theme)
177
+ left_col.addRow(self.tr("Language:"), self.cb_language)
204
178
 
205
179
  # ---- Display (moved under Theme) ----
206
- left_col.addRow(QLabel("<b>Display</b>"))
180
+ left_col.addRow(QLabel(self.tr("<b>Display</b>")))
207
181
  left_col.addRow(self.chk_autostretch_16bit)
208
- left_col.addRow("Background Opacity:", w_bg_opacity)
209
- left_col.addRow("Background Image:", w_bg_image)
182
+ left_col.addRow(self.tr("Background Opacity:"), w_bg_opacity)
183
+ left_col.addRow(self.tr("Background Image:"), w_bg_image)
210
184
 
211
185
  # ---- Right column: WIMS + RA/Dec + Updates + Display ----
212
- right_col.addRow(QLabel("<b>What's In My Sky — Defaults</b>"))
213
- right_col.addRow("Latitude (°):", self.sp_lat)
214
- right_col.addRow("Longitude (°):", self.sp_lon)
215
- right_col.addRow("Date (YYYY-MM-DD):", self.le_date)
216
- right_col.addRow("Time (HH:MM):", self.le_time)
217
- right_col.addRow("Time Zone:", self.cb_tz)
218
- right_col.addRow("Min Altitude (°):", self.sp_min_alt)
219
- right_col.addRow("Object Limit:", self.sp_obj_limit)
186
+ right_col.addRow(QLabel(self.tr("<b>What's In My Sky — Defaults</b>")))
187
+ right_col.addRow(self.tr("Latitude (°):"), self.sp_lat)
188
+ right_col.addRow(self.tr("Longitude (°):"), self.sp_lon)
189
+ right_col.addRow(self.tr("Date (YYYY-MM-DD):"), self.le_date)
190
+ right_col.addRow(self.tr("Time (HH:MM):"), self.le_time)
191
+ right_col.addRow(self.tr("Time Zone:"), self.cb_tz)
192
+ right_col.addRow(self.tr("Min Altitude (°):"), self.sp_min_alt)
193
+ right_col.addRow(self.tr("Object Limit:"), self.sp_obj_limit)
220
194
 
221
195
  # ---- RA/Dec Overlay ----
222
- right_col.addRow(QLabel("<b>RA/Dec Overlay</b>"))
223
- self.chk_wcs_enabled = QCheckBox("Show RA/Dec grid")
224
- self.chk_wcs_enabled.setChecked(self.settings.value("wcs_grid/enabled", True, type=bool))
196
+ right_col.addRow(QLabel(self.tr("<b>RA/Dec Overlay</b>")))
197
+ self.chk_wcs_enabled = QCheckBox(self.tr("Show RA/Dec grid"))
198
+
225
199
  right_col.addRow(self.chk_wcs_enabled)
226
200
 
227
201
  self.cb_wcs_mode = QComboBox(); self.cb_wcs_mode.addItems(["Auto", "Fixed spacing"])
228
- self.cb_wcs_mode.setCurrentIndex(
229
- 0 if (self.settings.value("wcs_grid/mode", "auto", type=str) == "auto") else 1
230
- )
231
-
232
202
  self.cb_wcs_unit = QComboBox(); self.cb_wcs_unit.addItems(["deg", "arcmin"])
233
- self.cb_wcs_unit.setCurrentIndex(
234
- 0 if (self.settings.value("wcs_grid/step_unit", "deg", type=str) == "deg") else 1
235
- )
236
-
203
+
237
204
  self.sp_wcs_step = QDoubleSpinBox()
238
205
  self.sp_wcs_step.setDecimals(3); self.sp_wcs_step.setRange(0.001, 90.0)
239
- self.sp_wcs_step.setValue(self.settings.value("wcs_grid/step_value", 1.0, type=float))
240
- self.sp_wcs_step.setEnabled(self.cb_wcs_mode.currentIndex() == 1)
241
-
206
+
242
207
  def _sync_suffix():
243
208
  self.sp_wcs_step.setSuffix(" °" if self.cb_wcs_unit.currentIndex() == 0 else " arcmin")
244
209
  _sync_suffix()
@@ -246,20 +211,18 @@ class SettingsDialog(QDialog):
246
211
  self.cb_wcs_mode.currentIndexChanged.connect(lambda i: self.sp_wcs_step.setEnabled(i == 1))
247
212
 
248
213
  row_wcs = QHBoxLayout()
249
- row_wcs.addWidget(QLabel("Mode:")); row_wcs.addWidget(self.cb_wcs_mode)
214
+ row_wcs.addWidget(QLabel(self.tr("Mode:"))); row_wcs.addWidget(self.cb_wcs_mode)
250
215
  row_wcs.addSpacing(8)
251
- row_wcs.addWidget(QLabel("Step:")); row_wcs.addWidget(self.sp_wcs_step, 1); row_wcs.addWidget(self.cb_wcs_unit)
216
+ row_wcs.addWidget(QLabel(self.tr("Step:"))); row_wcs.addWidget(self.sp_wcs_step, 1); row_wcs.addWidget(self.cb_wcs_unit)
252
217
  _w = QWidget(); _w.setLayout(row_wcs)
253
218
  right_col.addRow(_w)
254
219
 
255
220
  # ---- Updates ----
256
- right_col.addRow(QLabel("<b>Updates</b>"))
221
+ right_col.addRow(QLabel(self.tr("<b>Updates</b>")))
257
222
  right_col.addRow(self.chk_updates_startup)
258
223
  w = QWidget(); w.setLayout(row_updates_url)
259
- right_col.addRow("Updates JSON URL:", w)
224
+ right_col.addRow(self.tr("Updates JSON URL:"), w)
260
225
 
261
-
262
-
263
226
  # ---- Buttons ----
264
227
  btns = QDialogButtonBox(
265
228
  QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, parent=self
@@ -268,6 +231,105 @@ class SettingsDialog(QDialog):
268
231
  btns.rejected.connect(self.reject)
269
232
  root.addWidget(btns)
270
233
 
234
+ # Initial Load:
235
+ self.refresh_ui()
236
+
237
+ def refresh_ui(self):
238
+ """
239
+ Reloads all settings from self.settings and updates the UI widgets.
240
+ Call this before showing the cached dialog to ensure it matches current state.
241
+ """
242
+ # Updates
243
+ self.chk_updates_startup.setChecked(
244
+ self.settings.value("updates/check_on_startup", True, type=bool)
245
+ )
246
+ self.le_updates_url.setText(
247
+ self.settings.value(
248
+ "updates/url",
249
+ "https://raw.githubusercontent.com/setiastro/setiastrosuitepro/main/updates.json",
250
+ type=str
251
+ )
252
+ )
253
+
254
+ # Shortcuts
255
+ self.chk_save_shortcuts.setChecked(
256
+ self.settings.value("shortcuts/save_on_exit", True, type=bool)
257
+ )
258
+
259
+ # Theme
260
+ theme_val = (self.settings.value("ui/theme", "system", type=str) or "system").lower()
261
+ index_map = {"dark": 0, "gray": 1, "light": 2, "system": 3, "custom": 4}
262
+ self.cb_theme.setCurrentIndex(index_map.get(theme_val, 2))
263
+ self.btn_theme_custom.setEnabled(theme_val == "custom")
264
+
265
+ # Language
266
+ current_lang = get_saved_language()
267
+ try:
268
+ lang_idx = self._lang_codes.index(current_lang)
269
+ except ValueError:
270
+ lang_idx = 0
271
+ # fallback to whatever is first or default
272
+ if "en" in self._lang_codes:
273
+ lang_idx = self._lang_codes.index("en")
274
+
275
+ self.cb_language.blockSignals(True)
276
+ self.cb_language.setCurrentIndex(lang_idx)
277
+ self.cb_language.blockSignals(False)
278
+ self._initial_language = current_lang # Track for restart notification
279
+
280
+ # Path fields
281
+ self.le_graxpert.setText(self.settings.value("paths/graxpert", "", type=str))
282
+ self.le_cosmic.setText(self.settings.value("paths/cosmic_clarity", "", type=str))
283
+ self.le_starnet.setText(self.settings.value("paths/starnet", "", type=str))
284
+ self.le_astap.setText(self.settings.value("paths/astap", "", type=str))
285
+ self.le_astrometry.setText(self.settings.value("api/astrometry_key", "", type=str))
286
+
287
+ # WIMS
288
+ self.sp_lat.setValue(self.settings.value("latitude", 0.0, type=float))
289
+ self.sp_lon.setValue(self.settings.value("longitude", 0.0, type=float))
290
+ self.le_date.setText(self.settings.value("date", "", type=str) or "")
291
+ self.le_time.setText(self.settings.value("time", "", type=str) or "")
292
+ tz_val = self.settings.value("timezone", "UTC", type=str) or "UTC"
293
+ idx = max(0, self.cb_tz.findText(tz_val))
294
+ self.cb_tz.setCurrentIndex(idx)
295
+ self.sp_min_alt.setValue(self.settings.value("min_altitude", 0.0, type=float))
296
+ self.sp_obj_limit.setValue(self.settings.value("object_limit", 100, type=int))
297
+
298
+ # Display
299
+ self.chk_autostretch_16bit.setChecked(
300
+ self.settings.value("display/autostretch_16bit", True, type=bool)
301
+ )
302
+
303
+ current_opacity = self.settings.value("display/bg_opacity", 50, type=int)
304
+ self.slider_bg_opacity.blockSignals(True)
305
+ self.slider_bg_opacity.setValue(current_opacity)
306
+ self.slider_bg_opacity.blockSignals(False)
307
+ self.lbl_bg_opacity_val.setText(f"{current_opacity}%")
308
+ self._initial_bg_opacity = int(current_opacity) # For cancel/revert
309
+
310
+ # Custom background
311
+ self._initial_bg_path = self.settings.value("ui/custom_background", "", type=str) or ""
312
+ self.le_bg_path.setText(self._initial_bg_path)
313
+
314
+ # RA/Dec Overlay
315
+ self.chk_wcs_enabled.setChecked(self.settings.value("wcs_grid/enabled", True, type=bool))
316
+
317
+ self.cb_wcs_mode.blockSignals(True)
318
+ self.cb_wcs_mode.setCurrentIndex(
319
+ 0 if (self.settings.value("wcs_grid/mode", "auto", type=str) == "auto") else 1
320
+ )
321
+ self.cb_wcs_mode.blockSignals(False)
322
+
323
+ self.cb_wcs_unit.blockSignals(True)
324
+ self.cb_wcs_unit.setCurrentIndex(
325
+ 0 if (self.settings.value("wcs_grid/step_unit", "deg", type=str) == "deg") else 1
326
+ )
327
+ self.cb_wcs_unit.blockSignals(False)
328
+
329
+ self.sp_wcs_step.setValue(self.settings.value("wcs_grid/step_value", 1.0, type=float))
330
+ self.sp_wcs_step.setEnabled(self.cb_wcs_mode.currentIndex() == 1)
331
+ self.sp_wcs_step.setSuffix(" °" if self.cb_wcs_unit.currentIndex() == 0 else " arcmin")
332
+
271
333
  def reject(self):
272
334
  """User cancelled: restore the original background opacity (revert live changes)."""
273
335
  try:
@@ -430,6 +492,21 @@ class SettingsDialog(QDialog):
430
492
  theme_val = "custom"
431
493
  self.settings.setValue("ui/theme", theme_val)
432
494
 
495
+ # Language
496
+ lang_idx = self.cb_language.currentIndex()
497
+ new_lang = self._lang_codes[lang_idx] if 0 <= lang_idx < len(self._lang_codes) else "en"
498
+ save_language(new_lang)
499
+
500
+ # Apply language change immediately if changed
501
+ if new_lang != self._initial_language:
502
+ from PyQt6.QtWidgets import QMessageBox
503
+
504
+ QMessageBox.information(
505
+ self,
506
+ self.tr("Restart required"),
507
+ self.tr("Language changed. Please manually restart the application to apply the new language.")
508
+ )
509
+
433
510
  self.settings.sync()
434
511
 
435
512
  # Apply now if the parent knows how
@@ -28,6 +28,7 @@ class PaletteAdjustDialog(QDialog):
28
28
  super().__init__(owner)
29
29
  self.setWindowTitle("Adjust Palette Intensities")
30
30
  self.setWindowFlag(Qt.WindowType.Window, True)
31
+ self.setWindowModality(Qt.WindowModality.NonModal)
31
32
  self.setModal(False)
32
33
  #self.setAttribute(Qt.WidgetAttribute.WA_DeleteOnClose, True)
33
34
 
@@ -53,10 +54,21 @@ class PaletteAdjustDialog(QDialog):
53
54
 
54
55
  # Zoom controls
55
56
  zoom_layout = QHBoxLayout()
56
- btn_zoom_in = themed_toolbtn("zoom-in", "Zoom In")
57
- btn_zoom_out = themed_toolbtn("zoom-out", "Zoom Out")
58
- btn_fit = themed_toolbtn("zoom-fit-best", "Fit to Preview")
59
- zoom_layout.addWidget(btn_zoom_in); zoom_layout.addWidget(btn_zoom_out); zoom_layout.addWidget(btn_fit)
57
+
58
+ self.btn_zoom_in = themed_toolbtn("zoom-in", "Zoom In")
59
+ self.btn_zoom_out = themed_toolbtn("zoom-out", "Zoom Out")
60
+ self.btn_fit = themed_toolbtn("zoom-fit-best", "Fit to Preview")
61
+
62
+ self.btn_zoom_in.clicked.connect(lambda: self._change_zoom(1.25))
63
+ self.btn_zoom_out.clicked.connect(lambda: self._change_zoom(0.8))
64
+ self.btn_fit.clicked.connect(self._fit_to_preview)
65
+
66
+ zoom_layout.addStretch(1)
67
+ zoom_layout.addWidget(self.btn_zoom_out)
68
+ zoom_layout.addWidget(self.btn_zoom_in)
69
+ zoom_layout.addWidget(self.btn_fit)
70
+ zoom_layout.addStretch(1)
71
+
60
72
  vlayout.addLayout(zoom_layout)
61
73
 
62
74
  # Preview
@@ -136,11 +148,17 @@ class PaletteAdjustDialog(QDialog):
136
148
  self._rescale_pixmap()
137
149
 
138
150
  def _fit_to_preview(self):
139
- if not hasattr(self, "_base_pixmap"): return
140
- vp_w = self.preview_area.viewport().width()
141
- self.zoom_factor = vp_w / max(1, self._base_pixmap.width())
151
+ if not hasattr(self, "_base_pixmap"):
152
+ return
153
+ vp = self.preview_area.viewport().size()
154
+ pm = self._base_pixmap.size()
155
+ if pm.width() <= 0 or pm.height() <= 0:
156
+ return
157
+ k = min(vp.width() / pm.width(), vp.height() / pm.height())
158
+ self.zoom_factor = max(0.1, min(10.0, k))
142
159
  self._rescale_pixmap()
143
160
 
161
+
144
162
  def _on_reset(self):
145
163
  for s in (self._ha_slider, self._oiii_slider, self._sii_slider):
146
164
  s.setValue(100)