setiastrosuitepro 1.6.7__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.

Files changed (394) hide show
  1. setiastro/__init__.py +2 -0
  2. setiastro/data/SASP_data.fits +0 -0
  3. setiastro/data/catalogs/List_of_Galaxies_with_Distances_Gly.csv +488 -0
  4. setiastro/data/catalogs/astrobin_filters.csv +890 -0
  5. setiastro/data/catalogs/astrobin_filters_page1_local.csv +51 -0
  6. setiastro/data/catalogs/cali2.csv +63 -0
  7. setiastro/data/catalogs/cali2color.csv +65 -0
  8. setiastro/data/catalogs/celestial_catalog - original.csv +16471 -0
  9. setiastro/data/catalogs/celestial_catalog.csv +24031 -0
  10. setiastro/data/catalogs/detected_stars.csv +24784 -0
  11. setiastro/data/catalogs/fits_header_data.csv +46 -0
  12. setiastro/data/catalogs/test.csv +8 -0
  13. setiastro/data/catalogs/updated_celestial_catalog.csv +16471 -0
  14. setiastro/images/Astro_Spikes.png +0 -0
  15. setiastro/images/Background_startup.jpg +0 -0
  16. setiastro/images/HRDiagram.png +0 -0
  17. setiastro/images/LExtract.png +0 -0
  18. setiastro/images/LInsert.png +0 -0
  19. setiastro/images/Oxygenation-atm-2.svg.png +0 -0
  20. setiastro/images/RGB080604.png +0 -0
  21. setiastro/images/abeicon.png +0 -0
  22. setiastro/images/aberration.png +0 -0
  23. setiastro/images/acv_icon.png +0 -0
  24. setiastro/images/andromedatry.png +0 -0
  25. setiastro/images/andromedatry_satellited.png +0 -0
  26. setiastro/images/annotated.png +0 -0
  27. setiastro/images/aperture.png +0 -0
  28. setiastro/images/astrosuite.ico +0 -0
  29. setiastro/images/astrosuite.png +0 -0
  30. setiastro/images/astrosuitepro.icns +0 -0
  31. setiastro/images/astrosuitepro.ico +0 -0
  32. setiastro/images/astrosuitepro.png +0 -0
  33. setiastro/images/background.png +0 -0
  34. setiastro/images/background2.png +0 -0
  35. setiastro/images/benchmark.png +0 -0
  36. setiastro/images/big_moon_stabilizer_timeline.png +0 -0
  37. setiastro/images/big_moon_stabilizer_timeline_clean.png +0 -0
  38. setiastro/images/blaster.png +0 -0
  39. setiastro/images/blink.png +0 -0
  40. setiastro/images/clahe.png +0 -0
  41. setiastro/images/collage.png +0 -0
  42. setiastro/images/colorwheel.png +0 -0
  43. setiastro/images/contsub.png +0 -0
  44. setiastro/images/convo.png +0 -0
  45. setiastro/images/copyslot.png +0 -0
  46. setiastro/images/cosmic.png +0 -0
  47. setiastro/images/cosmicsat.png +0 -0
  48. setiastro/images/crop1.png +0 -0
  49. setiastro/images/cropicon.png +0 -0
  50. setiastro/images/curves.png +0 -0
  51. setiastro/images/cvs.png +0 -0
  52. setiastro/images/debayer.png +0 -0
  53. setiastro/images/denoise_cnn_custom.png +0 -0
  54. setiastro/images/denoise_cnn_graph.png +0 -0
  55. setiastro/images/disk.png +0 -0
  56. setiastro/images/dse.png +0 -0
  57. setiastro/images/exoicon.png +0 -0
  58. setiastro/images/eye.png +0 -0
  59. setiastro/images/first_quarter.png +0 -0
  60. setiastro/images/fliphorizontal.png +0 -0
  61. setiastro/images/flipvertical.png +0 -0
  62. setiastro/images/font.png +0 -0
  63. setiastro/images/freqsep.png +0 -0
  64. setiastro/images/full_moon.png +0 -0
  65. setiastro/images/functionbundle.png +0 -0
  66. setiastro/images/graxpert.png +0 -0
  67. setiastro/images/green.png +0 -0
  68. setiastro/images/gridicon.png +0 -0
  69. setiastro/images/halo.png +0 -0
  70. setiastro/images/hdr.png +0 -0
  71. setiastro/images/histogram.png +0 -0
  72. setiastro/images/hubble.png +0 -0
  73. setiastro/images/imagecombine.png +0 -0
  74. setiastro/images/invert.png +0 -0
  75. setiastro/images/isophote.png +0 -0
  76. setiastro/images/isophote_demo_figure.png +0 -0
  77. setiastro/images/isophote_demo_image.png +0 -0
  78. setiastro/images/isophote_demo_model.png +0 -0
  79. setiastro/images/isophote_demo_residual.png +0 -0
  80. setiastro/images/jwstpupil.png +0 -0
  81. setiastro/images/last_quarter.png +0 -0
  82. setiastro/images/linearfit.png +0 -0
  83. setiastro/images/livestacking.png +0 -0
  84. setiastro/images/mask.png +0 -0
  85. setiastro/images/maskapply.png +0 -0
  86. setiastro/images/maskcreate.png +0 -0
  87. setiastro/images/maskremove.png +0 -0
  88. setiastro/images/morpho.png +0 -0
  89. setiastro/images/mosaic.png +0 -0
  90. setiastro/images/multiscale_decomp.png +0 -0
  91. setiastro/images/nbtorgb.png +0 -0
  92. setiastro/images/neutral.png +0 -0
  93. setiastro/images/new_moon.png +0 -0
  94. setiastro/images/nuke.png +0 -0
  95. setiastro/images/openfile.png +0 -0
  96. setiastro/images/pedestal.png +0 -0
  97. setiastro/images/pen.png +0 -0
  98. setiastro/images/pixelmath.png +0 -0
  99. setiastro/images/platesolve.png +0 -0
  100. setiastro/images/ppp.png +0 -0
  101. setiastro/images/pro.png +0 -0
  102. setiastro/images/project.png +0 -0
  103. setiastro/images/psf.png +0 -0
  104. setiastro/images/redo.png +0 -0
  105. setiastro/images/redoicon.png +0 -0
  106. setiastro/images/rescale.png +0 -0
  107. setiastro/images/rgbalign.png +0 -0
  108. setiastro/images/rgbcombo.png +0 -0
  109. setiastro/images/rgbextract.png +0 -0
  110. setiastro/images/rotate180.png +0 -0
  111. setiastro/images/rotatearbitrary.png +0 -0
  112. setiastro/images/rotateclockwise.png +0 -0
  113. setiastro/images/rotatecounterclockwise.png +0 -0
  114. setiastro/images/satellite.png +0 -0
  115. setiastro/images/script.png +0 -0
  116. setiastro/images/selectivecolor.png +0 -0
  117. setiastro/images/simbad.png +0 -0
  118. setiastro/images/slot0.png +0 -0
  119. setiastro/images/slot1.png +0 -0
  120. setiastro/images/slot2.png +0 -0
  121. setiastro/images/slot3.png +0 -0
  122. setiastro/images/slot4.png +0 -0
  123. setiastro/images/slot5.png +0 -0
  124. setiastro/images/slot6.png +0 -0
  125. setiastro/images/slot7.png +0 -0
  126. setiastro/images/slot8.png +0 -0
  127. setiastro/images/slot9.png +0 -0
  128. setiastro/images/spcc.png +0 -0
  129. setiastro/images/spin_precession_vs_lunar_distance.png +0 -0
  130. setiastro/images/spinner.gif +0 -0
  131. setiastro/images/stacking.png +0 -0
  132. setiastro/images/staradd.png +0 -0
  133. setiastro/images/staralign.png +0 -0
  134. setiastro/images/starnet.png +0 -0
  135. setiastro/images/starregistration.png +0 -0
  136. setiastro/images/starspike.png +0 -0
  137. setiastro/images/starstretch.png +0 -0
  138. setiastro/images/statstretch.png +0 -0
  139. setiastro/images/supernova.png +0 -0
  140. setiastro/images/uhs.png +0 -0
  141. setiastro/images/undoicon.png +0 -0
  142. setiastro/images/upscale.png +0 -0
  143. setiastro/images/viewbundle.png +0 -0
  144. setiastro/images/waning_crescent_1.png +0 -0
  145. setiastro/images/waning_crescent_2.png +0 -0
  146. setiastro/images/waning_crescent_3.png +0 -0
  147. setiastro/images/waning_crescent_4.png +0 -0
  148. setiastro/images/waning_crescent_5.png +0 -0
  149. setiastro/images/waning_gibbous_1.png +0 -0
  150. setiastro/images/waning_gibbous_2.png +0 -0
  151. setiastro/images/waning_gibbous_3.png +0 -0
  152. setiastro/images/waning_gibbous_4.png +0 -0
  153. setiastro/images/waning_gibbous_5.png +0 -0
  154. setiastro/images/waxing_crescent_1.png +0 -0
  155. setiastro/images/waxing_crescent_2.png +0 -0
  156. setiastro/images/waxing_crescent_3.png +0 -0
  157. setiastro/images/waxing_crescent_4.png +0 -0
  158. setiastro/images/waxing_crescent_5.png +0 -0
  159. setiastro/images/waxing_gibbous_1.png +0 -0
  160. setiastro/images/waxing_gibbous_2.png +0 -0
  161. setiastro/images/waxing_gibbous_3.png +0 -0
  162. setiastro/images/waxing_gibbous_4.png +0 -0
  163. setiastro/images/waxing_gibbous_5.png +0 -0
  164. setiastro/images/whitebalance.png +0 -0
  165. setiastro/images/wimi_icon_256x256.png +0 -0
  166. setiastro/images/wimilogo.png +0 -0
  167. setiastro/images/wims.png +0 -0
  168. setiastro/images/wrench_icon.png +0 -0
  169. setiastro/images/xisfliberator.png +0 -0
  170. setiastro/qml/ResourceMonitor.qml +128 -0
  171. setiastro/saspro/__init__.py +20 -0
  172. setiastro/saspro/__main__.py +964 -0
  173. setiastro/saspro/_generated/__init__.py +7 -0
  174. setiastro/saspro/_generated/build_info.py +3 -0
  175. setiastro/saspro/abe.py +1379 -0
  176. setiastro/saspro/abe_preset.py +196 -0
  177. setiastro/saspro/aberration_ai.py +910 -0
  178. setiastro/saspro/aberration_ai_preset.py +224 -0
  179. setiastro/saspro/accel_installer.py +218 -0
  180. setiastro/saspro/accel_workers.py +30 -0
  181. setiastro/saspro/acv_exporter.py +379 -0
  182. setiastro/saspro/add_stars.py +627 -0
  183. setiastro/saspro/astrobin_exporter.py +1010 -0
  184. setiastro/saspro/astrospike.py +153 -0
  185. setiastro/saspro/astrospike_python.py +1841 -0
  186. setiastro/saspro/autostretch.py +198 -0
  187. setiastro/saspro/backgroundneutral.py +639 -0
  188. setiastro/saspro/batch_convert.py +328 -0
  189. setiastro/saspro/batch_renamer.py +522 -0
  190. setiastro/saspro/blemish_blaster.py +494 -0
  191. setiastro/saspro/blink_comparator_pro.py +3149 -0
  192. setiastro/saspro/bundles.py +61 -0
  193. setiastro/saspro/bundles_dock.py +114 -0
  194. setiastro/saspro/cheat_sheet.py +213 -0
  195. setiastro/saspro/clahe.py +371 -0
  196. setiastro/saspro/comet_stacking.py +1442 -0
  197. setiastro/saspro/common_tr.py +107 -0
  198. setiastro/saspro/config.py +38 -0
  199. setiastro/saspro/config_bootstrap.py +40 -0
  200. setiastro/saspro/config_manager.py +316 -0
  201. setiastro/saspro/continuum_subtract.py +1620 -0
  202. setiastro/saspro/convo.py +1403 -0
  203. setiastro/saspro/convo_preset.py +414 -0
  204. setiastro/saspro/copyastro.py +190 -0
  205. setiastro/saspro/cosmicclarity.py +1593 -0
  206. setiastro/saspro/cosmicclarity_preset.py +407 -0
  207. setiastro/saspro/crop_dialog_pro.py +1005 -0
  208. setiastro/saspro/crop_preset.py +189 -0
  209. setiastro/saspro/curve_editor_pro.py +2608 -0
  210. setiastro/saspro/curves_preset.py +375 -0
  211. setiastro/saspro/debayer.py +673 -0
  212. setiastro/saspro/debug_utils.py +29 -0
  213. setiastro/saspro/dnd_mime.py +35 -0
  214. setiastro/saspro/doc_manager.py +2727 -0
  215. setiastro/saspro/exoplanet_detector.py +2258 -0
  216. setiastro/saspro/file_utils.py +284 -0
  217. setiastro/saspro/fitsmodifier.py +748 -0
  218. setiastro/saspro/fix_bom.py +32 -0
  219. setiastro/saspro/free_torch_memory.py +48 -0
  220. setiastro/saspro/frequency_separation.py +1352 -0
  221. setiastro/saspro/function_bundle.py +1596 -0
  222. setiastro/saspro/generate_translations.py +3092 -0
  223. setiastro/saspro/ghs_dialog_pro.py +728 -0
  224. setiastro/saspro/ghs_preset.py +284 -0
  225. setiastro/saspro/graxpert.py +638 -0
  226. setiastro/saspro/graxpert_preset.py +287 -0
  227. setiastro/saspro/gui/__init__.py +0 -0
  228. setiastro/saspro/gui/main_window.py +8928 -0
  229. setiastro/saspro/gui/mixins/__init__.py +33 -0
  230. setiastro/saspro/gui/mixins/dock_mixin.py +375 -0
  231. setiastro/saspro/gui/mixins/file_mixin.py +450 -0
  232. setiastro/saspro/gui/mixins/geometry_mixin.py +503 -0
  233. setiastro/saspro/gui/mixins/header_mixin.py +441 -0
  234. setiastro/saspro/gui/mixins/mask_mixin.py +421 -0
  235. setiastro/saspro/gui/mixins/menu_mixin.py +391 -0
  236. setiastro/saspro/gui/mixins/theme_mixin.py +367 -0
  237. setiastro/saspro/gui/mixins/toolbar_mixin.py +1824 -0
  238. setiastro/saspro/gui/mixins/update_mixin.py +323 -0
  239. setiastro/saspro/gui/mixins/view_mixin.py +477 -0
  240. setiastro/saspro/gui/statistics_dialog.py +47 -0
  241. setiastro/saspro/halobgon.py +492 -0
  242. setiastro/saspro/header_viewer.py +448 -0
  243. setiastro/saspro/headless_utils.py +88 -0
  244. setiastro/saspro/histogram.py +760 -0
  245. setiastro/saspro/history_explorer.py +941 -0
  246. setiastro/saspro/i18n.py +168 -0
  247. setiastro/saspro/image_combine.py +421 -0
  248. setiastro/saspro/image_peeker_pro.py +1608 -0
  249. setiastro/saspro/imageops/__init__.py +37 -0
  250. setiastro/saspro/imageops/mdi_snap.py +292 -0
  251. setiastro/saspro/imageops/scnr.py +36 -0
  252. setiastro/saspro/imageops/starbasedwhitebalance.py +210 -0
  253. setiastro/saspro/imageops/stretch.py +236 -0
  254. setiastro/saspro/isophote.py +1186 -0
  255. setiastro/saspro/layers.py +208 -0
  256. setiastro/saspro/layers_dock.py +714 -0
  257. setiastro/saspro/lazy_imports.py +193 -0
  258. setiastro/saspro/legacy/__init__.py +2 -0
  259. setiastro/saspro/legacy/image_manager.py +2360 -0
  260. setiastro/saspro/legacy/numba_utils.py +3676 -0
  261. setiastro/saspro/legacy/xisf.py +1213 -0
  262. setiastro/saspro/linear_fit.py +537 -0
  263. setiastro/saspro/live_stacking.py +1854 -0
  264. setiastro/saspro/log_bus.py +5 -0
  265. setiastro/saspro/logging_config.py +460 -0
  266. setiastro/saspro/luminancerecombine.py +510 -0
  267. setiastro/saspro/main_helpers.py +201 -0
  268. setiastro/saspro/mask_creation.py +1090 -0
  269. setiastro/saspro/masks_core.py +56 -0
  270. setiastro/saspro/mdi_widgets.py +353 -0
  271. setiastro/saspro/memory_utils.py +666 -0
  272. setiastro/saspro/metadata_patcher.py +75 -0
  273. setiastro/saspro/mfdeconv.py +3909 -0
  274. setiastro/saspro/mfdeconv_earlystop.py +71 -0
  275. setiastro/saspro/mfdeconvcudnn.py +3312 -0
  276. setiastro/saspro/mfdeconvsport.py +2459 -0
  277. setiastro/saspro/minorbodycatalog.py +567 -0
  278. setiastro/saspro/morphology.py +411 -0
  279. setiastro/saspro/multiscale_decomp.py +1751 -0
  280. setiastro/saspro/nbtorgb_stars.py +541 -0
  281. setiastro/saspro/numba_utils.py +3145 -0
  282. setiastro/saspro/numba_warmup.py +141 -0
  283. setiastro/saspro/ops/__init__.py +9 -0
  284. setiastro/saspro/ops/command_help_dialog.py +623 -0
  285. setiastro/saspro/ops/command_runner.py +217 -0
  286. setiastro/saspro/ops/commands.py +1594 -0
  287. setiastro/saspro/ops/script_editor.py +1105 -0
  288. setiastro/saspro/ops/scripts.py +1476 -0
  289. setiastro/saspro/ops/settings.py +637 -0
  290. setiastro/saspro/parallel_utils.py +554 -0
  291. setiastro/saspro/pedestal.py +121 -0
  292. setiastro/saspro/perfect_palette_picker.py +1105 -0
  293. setiastro/saspro/pipeline.py +110 -0
  294. setiastro/saspro/pixelmath.py +1604 -0
  295. setiastro/saspro/plate_solver.py +2480 -0
  296. setiastro/saspro/project_io.py +797 -0
  297. setiastro/saspro/psf_utils.py +136 -0
  298. setiastro/saspro/psf_viewer.py +631 -0
  299. setiastro/saspro/pyi_rthook_astroquery.py +95 -0
  300. setiastro/saspro/remove_green.py +331 -0
  301. setiastro/saspro/remove_stars.py +1599 -0
  302. setiastro/saspro/remove_stars_preset.py +446 -0
  303. setiastro/saspro/resources.py +570 -0
  304. setiastro/saspro/rgb_combination.py +208 -0
  305. setiastro/saspro/rgb_extract.py +19 -0
  306. setiastro/saspro/rgbalign.py +727 -0
  307. setiastro/saspro/runtime_imports.py +7 -0
  308. setiastro/saspro/runtime_torch.py +754 -0
  309. setiastro/saspro/save_options.py +73 -0
  310. setiastro/saspro/selective_color.py +1614 -0
  311. setiastro/saspro/sfcc.py +1530 -0
  312. setiastro/saspro/shortcuts.py +3125 -0
  313. setiastro/saspro/signature_insert.py +1106 -0
  314. setiastro/saspro/stacking_suite.py +19069 -0
  315. setiastro/saspro/star_alignment.py +7383 -0
  316. setiastro/saspro/star_alignment_preset.py +329 -0
  317. setiastro/saspro/star_metrics.py +49 -0
  318. setiastro/saspro/star_spikes.py +769 -0
  319. setiastro/saspro/star_stretch.py +542 -0
  320. setiastro/saspro/stat_stretch.py +554 -0
  321. setiastro/saspro/status_log_dock.py +78 -0
  322. setiastro/saspro/subwindow.py +3523 -0
  323. setiastro/saspro/supernovaasteroidhunter.py +1719 -0
  324. setiastro/saspro/swap_manager.py +134 -0
  325. setiastro/saspro/torch_backend.py +89 -0
  326. setiastro/saspro/torch_rejection.py +434 -0
  327. setiastro/saspro/translations/all_source_strings.json +4726 -0
  328. setiastro/saspro/translations/ar_translations.py +4096 -0
  329. setiastro/saspro/translations/de_translations.py +3728 -0
  330. setiastro/saspro/translations/es_translations.py +4169 -0
  331. setiastro/saspro/translations/fr_translations.py +4090 -0
  332. setiastro/saspro/translations/hi_translations.py +3803 -0
  333. setiastro/saspro/translations/integrate_translations.py +271 -0
  334. setiastro/saspro/translations/it_translations.py +4728 -0
  335. setiastro/saspro/translations/ja_translations.py +3834 -0
  336. setiastro/saspro/translations/pt_translations.py +3847 -0
  337. setiastro/saspro/translations/ru_translations.py +3082 -0
  338. setiastro/saspro/translations/saspro_ar.qm +0 -0
  339. setiastro/saspro/translations/saspro_ar.ts +16019 -0
  340. setiastro/saspro/translations/saspro_de.qm +0 -0
  341. setiastro/saspro/translations/saspro_de.ts +14548 -0
  342. setiastro/saspro/translations/saspro_es.qm +0 -0
  343. setiastro/saspro/translations/saspro_es.ts +16202 -0
  344. setiastro/saspro/translations/saspro_fr.qm +0 -0
  345. setiastro/saspro/translations/saspro_fr.ts +15870 -0
  346. setiastro/saspro/translations/saspro_hi.qm +0 -0
  347. setiastro/saspro/translations/saspro_hi.ts +14855 -0
  348. setiastro/saspro/translations/saspro_it.qm +0 -0
  349. setiastro/saspro/translations/saspro_it.ts +19046 -0
  350. setiastro/saspro/translations/saspro_ja.qm +0 -0
  351. setiastro/saspro/translations/saspro_ja.ts +14980 -0
  352. setiastro/saspro/translations/saspro_pt.qm +0 -0
  353. setiastro/saspro/translations/saspro_pt.ts +15024 -0
  354. setiastro/saspro/translations/saspro_ru.qm +0 -0
  355. setiastro/saspro/translations/saspro_ru.ts +11835 -0
  356. setiastro/saspro/translations/saspro_sw.qm +0 -0
  357. setiastro/saspro/translations/saspro_sw.ts +15237 -0
  358. setiastro/saspro/translations/saspro_uk.qm +0 -0
  359. setiastro/saspro/translations/saspro_uk.ts +15248 -0
  360. setiastro/saspro/translations/saspro_zh.qm +0 -0
  361. setiastro/saspro/translations/saspro_zh.ts +15289 -0
  362. setiastro/saspro/translations/sw_translations.py +3897 -0
  363. setiastro/saspro/translations/uk_translations.py +3929 -0
  364. setiastro/saspro/translations/zh_translations.py +3910 -0
  365. setiastro/saspro/versioning.py +77 -0
  366. setiastro/saspro/view_bundle.py +1558 -0
  367. setiastro/saspro/wavescale_hdr.py +648 -0
  368. setiastro/saspro/wavescale_hdr_preset.py +101 -0
  369. setiastro/saspro/wavescalede.py +683 -0
  370. setiastro/saspro/wavescalede_preset.py +230 -0
  371. setiastro/saspro/wcs_update.py +374 -0
  372. setiastro/saspro/whitebalance.py +540 -0
  373. setiastro/saspro/widgets/__init__.py +48 -0
  374. setiastro/saspro/widgets/common_utilities.py +306 -0
  375. setiastro/saspro/widgets/graphics_views.py +122 -0
  376. setiastro/saspro/widgets/image_utils.py +518 -0
  377. setiastro/saspro/widgets/minigame/game.js +991 -0
  378. setiastro/saspro/widgets/minigame/index.html +53 -0
  379. setiastro/saspro/widgets/minigame/style.css +241 -0
  380. setiastro/saspro/widgets/preview_dialogs.py +280 -0
  381. setiastro/saspro/widgets/resource_monitor.py +313 -0
  382. setiastro/saspro/widgets/spinboxes.py +290 -0
  383. setiastro/saspro/widgets/themed_buttons.py +13 -0
  384. setiastro/saspro/widgets/wavelet_utils.py +331 -0
  385. setiastro/saspro/wimi.py +7367 -0
  386. setiastro/saspro/wims.py +588 -0
  387. setiastro/saspro/window_shelf.py +185 -0
  388. setiastro/saspro/xisf.py +1213 -0
  389. setiastrosuitepro-1.6.7.dist-info/METADATA +279 -0
  390. setiastrosuitepro-1.6.7.dist-info/RECORD +394 -0
  391. setiastrosuitepro-1.6.7.dist-info/WHEEL +4 -0
  392. setiastrosuitepro-1.6.7.dist-info/entry_points.txt +6 -0
  393. setiastrosuitepro-1.6.7.dist-info/licenses/LICENSE +674 -0
  394. setiastrosuitepro-1.6.7.dist-info/licenses/license.txt +2580 -0
@@ -0,0 +1,637 @@
1
+ # ops.settings.py
2
+ from PyQt6.QtWidgets import (
3
+ QLineEdit, QDialogButtonBox, QFileDialog, QDialog, QPushButton, QFormLayout,QApplication,
4
+ QHBoxLayout, QVBoxLayout, QWidget, QCheckBox, QComboBox, QSpinBox, QDoubleSpinBox, QLabel, QColorDialog, QFontDialog, QSlider)
5
+ from PyQt6.QtCore import QSettings, Qt
6
+ import pytz # for timezone list
7
+
8
+ # i18n support
9
+ from setiastro.saspro.i18n import get_available_languages, get_saved_language, save_language
10
+
11
+
12
+ class SettingsDialog(QDialog):
13
+ """
14
+ Simple settings UI for external executable paths + WIMS defaults.
15
+ Values are persisted via the provided QSettings instance.
16
+ """
17
+ def __init__(self, parent, settings: QSettings):
18
+ super().__init__(parent)
19
+ self.setWindowTitle(self.tr("Preferences"))
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)
24
+
25
+ # ---- Existing fields (paths, checkboxes, etc.) ----
26
+ self.le_graxpert = QLineEdit()
27
+ self.le_cosmic = QLineEdit()
28
+ self.le_starnet = QLineEdit()
29
+ self.le_astap = QLineEdit()
30
+
31
+ self.chk_updates_startup = QCheckBox(self.tr("Check for updates on startup"))
32
+
33
+ self.le_updates_url = QLineEdit()
34
+ self.le_updates_url.setPlaceholderText("Raw JSON URL (advanced)")
35
+
36
+ btn_reset_updates_url = QPushButton(self.tr("Reset"))
37
+ btn_reset_updates_url.setToolTip(self.tr("Restore default updates URL"))
38
+ btn_reset_updates_url.clicked.connect(
39
+ lambda: self.le_updates_url.setText(
40
+ "https://raw.githubusercontent.com/setiastro/setiastrosuitepro/main/updates.json"
41
+ )
42
+ )
43
+
44
+ # Optional: “Check Now…” button
45
+ self.btn_check_now = QPushButton(self.tr("Check Now…"))
46
+ self.btn_check_now.setToolTip(self.tr("Run an update check immediately"))
47
+ self.btn_check_now.setVisible(hasattr(parent, "_check_for_updates_async"))
48
+ self.btn_check_now.clicked.connect(self._check_updates_now_clicked)
49
+
50
+ # Build the updates URL row ONCE (we'll insert it later on the right column)
51
+ row_updates_url = QHBoxLayout()
52
+ row_updates_url.addWidget(self.le_updates_url, 1)
53
+ row_updates_url.addWidget(btn_reset_updates_url)
54
+ row_updates_url.addWidget(self.btn_check_now)
55
+
56
+ self.chk_save_shortcuts = QCheckBox(self.tr("Save desktop shortcuts on exit"))
57
+
58
+ self.cb_theme = QComboBox()
59
+ # Order: Dark, Gray, Light, System, Custom
60
+ self.cb_theme.addItems(["Dark", "Gray", "Light", "System", "Custom"])
61
+
62
+ # "Customize…" button for custom theme
63
+ self.btn_theme_custom = QPushButton(self.tr("Customize…"))
64
+ self.btn_theme_custom.setToolTip(self.tr("Edit custom colors and font"))
65
+ self.btn_theme_custom.clicked.connect(self._open_theme_editor)
66
+
67
+ # Keep button enabled state in sync with combo
68
+ self.cb_theme.currentIndexChanged.connect(self._on_theme_changed)
69
+
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
76
+
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))
81
+
82
+ row_grax = QHBoxLayout(); row_grax.addWidget(self.le_graxpert); row_grax.addWidget(btn_grax)
83
+ row_ccl = QHBoxLayout(); row_ccl.addWidget(self.le_cosmic); row_ccl.addWidget(btn_ccl)
84
+ row_star = QHBoxLayout(); row_star.addWidget(self.le_starnet); row_star.addWidget(btn_star)
85
+ row_astap = QHBoxLayout(); row_astap.addWidget(self.le_astap); row_astap.addWidget(btn_astap)
86
+
87
+ self.le_astrometry = QLineEdit()
88
+ self.le_astrometry.setEchoMode(QLineEdit.EchoMode.Password)
89
+
90
+ # ---- WIMS defaults ----
91
+ self.sp_lat = QDoubleSpinBox(); self.sp_lat.setRange(-90.0, 90.0); self.sp_lat.setDecimals(6)
92
+ self.sp_lon = QDoubleSpinBox(); self.sp_lon.setRange(-180.0, 180.0); self.sp_lon.setDecimals(6)
93
+ self.le_date = QLineEdit() # YYYY-MM-DD
94
+ self.le_time = QLineEdit() # HH:MM
95
+ self.cb_tz = QComboBox(); self.cb_tz.addItems(pytz.all_timezones)
96
+ self.sp_min_alt = QDoubleSpinBox(); self.sp_min_alt.setRange(0.0, 90.0); self.sp_min_alt.setDecimals(1)
97
+ self.sp_obj_limit = QSpinBox(); self.sp_obj_limit.setRange(1, 1000)
98
+
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)."))
101
+
102
+ self.slider_bg_opacity = QSlider(Qt.Orientation.Horizontal)
103
+ self.slider_bg_opacity.setRange(0, 100)
104
+ self._initial_bg_opacity = 50
105
+
106
+ self.lbl_bg_opacity_val = QLabel("50%")
107
+ self.lbl_bg_opacity_val.setFixedWidth(40)
108
+
109
+ def _on_opacity_changed(val):
110
+ self.lbl_bg_opacity_val.setText(f"{val}%")
111
+ # Aggiorna in tempo reale il valore nei settings
112
+ self.settings.setValue("display/bg_opacity", val)
113
+ self.settings.sync()
114
+ # Richiedi al parent (main window) di aggiornare il rendering della MDI
115
+ parent = self.parent()
116
+ if parent and hasattr(parent, "mdi") and hasattr(parent.mdi, "viewport"):
117
+ parent.mdi.viewport().update()
118
+
119
+ self.slider_bg_opacity.valueChanged.connect(_on_opacity_changed)
120
+
121
+ row_bg_opacity = QHBoxLayout()
122
+ row_bg_opacity.addWidget(self.slider_bg_opacity)
123
+ row_bg_opacity.addWidget(self.lbl_bg_opacity_val)
124
+ w_bg_opacity = QWidget()
125
+ w_bg_opacity.setLayout(row_bg_opacity)
126
+
127
+ # ---- Custom background: choose/clear preview ----
128
+ self.le_bg_path = QLineEdit()
129
+ self.le_bg_path.setReadOnly(True)
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"))
134
+ btn_choose_bg.clicked.connect(self._choose_background_clicked)
135
+ btn_clear_bg = QPushButton(self.tr("Clear"))
136
+ btn_clear_bg.setToolTip(self.tr("Remove custom background and restore default"))
137
+ btn_clear_bg.clicked.connect(self._clear_background_clicked)
138
+
139
+ row_bg_image = QHBoxLayout()
140
+ row_bg_image.addWidget(self.le_bg_path, 1)
141
+ row_bg_image.addWidget(btn_choose_bg)
142
+ row_bg_image.addWidget(btn_clear_bg)
143
+ w_bg_image = QWidget()
144
+ w_bg_image.setLayout(row_bg_image)
145
+
146
+ # ─────────────────────────────────────────────────────────────────────
147
+ # LAYOUT MUST EXIST BEFORE ANY addRow(...) — build it here
148
+ # ─────────────────────────────────────────────────────────────────────
149
+ root = QVBoxLayout(self)
150
+ cols = QHBoxLayout(); root.addLayout(cols)
151
+
152
+ left_col = QFormLayout()
153
+ right_col = QFormLayout()
154
+ for f in (left_col, right_col):
155
+ f.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.ExpandingFieldsGrow)
156
+ f.setRowWrapPolicy(QFormLayout.RowWrapPolicy.DontWrapRows)
157
+ f.setFormAlignment(Qt.AlignmentFlag.AlignTop)
158
+
159
+ cols.addLayout(left_col, 1)
160
+ cols.addSpacing(16)
161
+ cols.addLayout(right_col, 1)
162
+
163
+ # ---- Left column: Paths & Integrations ----
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)
170
+ left_col.addRow(self.chk_save_shortcuts)
171
+ row_theme = QHBoxLayout()
172
+ row_theme.addWidget(self.cb_theme, 1)
173
+ row_theme.addWidget(self.btn_theme_custom)
174
+ w_theme = QWidget()
175
+ w_theme.setLayout(row_theme)
176
+ left_col.addRow(self.tr("Theme:"), w_theme)
177
+ left_col.addRow(self.tr("Language:"), self.cb_language)
178
+
179
+ # ---- Display (moved under Theme) ----
180
+ left_col.addRow(QLabel(self.tr("<b>Display</b>")))
181
+ left_col.addRow(self.chk_autostretch_16bit)
182
+ left_col.addRow(self.tr("Background Opacity:"), w_bg_opacity)
183
+ left_col.addRow(self.tr("Background Image:"), w_bg_image)
184
+
185
+ # ---- Right column: WIMS + RA/Dec + Updates + Display ----
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)
194
+
195
+ # ---- RA/Dec Overlay ----
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
+
199
+ right_col.addRow(self.chk_wcs_enabled)
200
+
201
+ self.cb_wcs_mode = QComboBox(); self.cb_wcs_mode.addItems(["Auto", "Fixed spacing"])
202
+ self.cb_wcs_unit = QComboBox(); self.cb_wcs_unit.addItems(["deg", "arcmin"])
203
+
204
+ self.sp_wcs_step = QDoubleSpinBox()
205
+ self.sp_wcs_step.setDecimals(3); self.sp_wcs_step.setRange(0.001, 90.0)
206
+
207
+ def _sync_suffix():
208
+ self.sp_wcs_step.setSuffix(" °" if self.cb_wcs_unit.currentIndex() == 0 else " arcmin")
209
+ _sync_suffix()
210
+ self.cb_wcs_unit.currentIndexChanged.connect(_sync_suffix)
211
+ self.cb_wcs_mode.currentIndexChanged.connect(lambda i: self.sp_wcs_step.setEnabled(i == 1))
212
+
213
+ row_wcs = QHBoxLayout()
214
+ row_wcs.addWidget(QLabel(self.tr("Mode:"))); row_wcs.addWidget(self.cb_wcs_mode)
215
+ row_wcs.addSpacing(8)
216
+ row_wcs.addWidget(QLabel(self.tr("Step:"))); row_wcs.addWidget(self.sp_wcs_step, 1); row_wcs.addWidget(self.cb_wcs_unit)
217
+ _w = QWidget(); _w.setLayout(row_wcs)
218
+ right_col.addRow(_w)
219
+
220
+ # ---- Updates ----
221
+ right_col.addRow(QLabel(self.tr("<b>Updates</b>")))
222
+ right_col.addRow(self.chk_updates_startup)
223
+ w = QWidget(); w.setLayout(row_updates_url)
224
+ right_col.addRow(self.tr("Updates JSON URL:"), w)
225
+
226
+ # ---- Buttons ----
227
+ btns = QDialogButtonBox(
228
+ QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel, parent=self
229
+ )
230
+ btns.accepted.connect(self._save_and_accept)
231
+ btns.rejected.connect(self.reject)
232
+ root.addWidget(btns)
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
+
333
+ def reject(self):
334
+ """User cancelled: restore the original background opacity (revert live changes)."""
335
+ try:
336
+ # Restore saved original value
337
+ self.settings.setValue("display/bg_opacity", int(self._initial_bg_opacity))
338
+ self.settings.sync()
339
+ # Ask parent to redraw with restored value
340
+ parent = self.parent()
341
+ if parent:
342
+ # restore original custom background (may be empty)
343
+ try:
344
+ # If there was an initial custom background, restore it; otherwise clear.
345
+ if self._initial_bg_path:
346
+ if hasattr(parent, "_apply_custom_background"):
347
+ parent._apply_custom_background(self._initial_bg_path)
348
+ else:
349
+ # Avoid calling _apply_custom_background("") which shows a warning
350
+ if hasattr(parent, "_clear_custom_background"):
351
+ parent._clear_custom_background()
352
+ elif hasattr(parent, "_apply_custom_background"):
353
+ parent._apply_custom_background("")
354
+ except Exception:
355
+ pass
356
+ # update MDI viewport/redraw
357
+ try:
358
+ if hasattr(parent, "mdi") and hasattr(parent.mdi, "viewport"):
359
+ parent.mdi.viewport().update()
360
+ except Exception:
361
+ pass
362
+ except Exception:
363
+ pass
364
+ super().reject()
365
+
366
+
367
+ # ----------------- helpers -----------------
368
+ def _browse_into(self, lineedit: QLineEdit):
369
+ path, _ = QFileDialog.getOpenFileName(self, "Select Executable", "", "Executables (*)")
370
+ if path:
371
+ lineedit.setText(path)
372
+
373
+ def _browse_dir(self, lineedit: QLineEdit):
374
+ path = QFileDialog.getExistingDirectory(self, "Select Folder", "")
375
+ if path:
376
+ lineedit.setText(path)
377
+
378
+ def _check_updates_now_clicked(self):
379
+ """Persist update settings, then ask the main window to run an interactive check (if available)."""
380
+ self.settings.setValue("updates/check_on_startup", self.chk_updates_startup.isChecked())
381
+ self.settings.setValue("updates/url", self.le_updates_url.text().strip())
382
+ self.settings.sync()
383
+
384
+ parent = self.parent()
385
+ if parent and hasattr(parent, "_check_for_updates_async"):
386
+ try:
387
+ parent._check_for_updates_async(interactive=True)
388
+ except Exception:
389
+ pass
390
+
391
+ def _choose_background_clicked(self):
392
+ """Open a file picker and apply a custom background image for the app."""
393
+ path, _ = QFileDialog.getOpenFileName(self, "Select background image", "", "Images (*.png *.jpg *.jpeg)")
394
+ if not path:
395
+ return
396
+ try:
397
+ # Do NOT persist yet — just update UI and preview via parent.
398
+ self.le_bg_path.setText(path)
399
+ parent = self.parent()
400
+ if parent and hasattr(parent, "_apply_custom_background"):
401
+ try:
402
+ parent._apply_custom_background(path)
403
+ except Exception:
404
+ pass
405
+ except Exception:
406
+ pass
407
+
408
+ def _clear_background_clicked(self):
409
+ """Clear persisted custom background and ask main window to restore defaults."""
410
+ try:
411
+ # Do NOT modify settings yet — clear preview and let Save apply
412
+ self.le_bg_path.setText("")
413
+ parent = self.parent()
414
+ if parent:
415
+ # request parent to clear preview/background for now
416
+ try:
417
+ if hasattr(parent, "_clear_custom_background"):
418
+ parent._clear_custom_background()
419
+ elif hasattr(parent, "_apply_custom_background"):
420
+ parent._apply_custom_background("")
421
+ except Exception:
422
+ pass
423
+ except Exception:
424
+ pass
425
+
426
+ def _on_theme_changed(self, idx: int):
427
+ # Enable the "Customize…" button only when Custom is selected
428
+ text = self.cb_theme.currentText().lower()
429
+ self.btn_theme_custom.setEnabled(text == "custom")
430
+
431
+ def _open_theme_editor(self):
432
+ from PyQt6.QtWidgets import QDialog
433
+ dlg = ThemeEditorDialog(self, self.settings)
434
+ if dlg.exec() == QDialog.DialogCode.Accepted:
435
+ # If user saved a custom theme, make sure "Custom" is selected
436
+ self.cb_theme.setCurrentIndex(4) # Custom
437
+
438
+
439
+ def _save_and_accept(self):
440
+ # Paths / Integrations
441
+ self.settings.setValue("paths/graxpert", self.le_graxpert.text().strip())
442
+ self.settings.setValue("paths/cosmic_clarity", self.le_cosmic.text().strip())
443
+ self.settings.setValue("paths/starnet", self.le_starnet.text().strip())
444
+ self.settings.setValue("paths/astap", self.le_astap.text().strip())
445
+ self.settings.setValue("shortcuts/save_on_exit", self.chk_save_shortcuts.isChecked())
446
+ self.settings.setValue("api/astrometry_key", self.le_astrometry.text().strip())
447
+
448
+ # WIMS defaults
449
+ self.settings.setValue("latitude", float(self.sp_lat.value()))
450
+ self.settings.setValue("longitude", float(self.sp_lon.value()))
451
+ self.settings.setValue("date", self.le_date.text().strip())
452
+ self.settings.setValue("time", self.le_time.text().strip())
453
+ self.settings.setValue("timezone", self.cb_tz.currentText())
454
+ self.settings.setValue("min_altitude", float(self.sp_min_alt.value()))
455
+ self.settings.setValue("object_limit", int(self.sp_obj_limit.value()))
456
+
457
+ # RA/Dec Overlay
458
+ self.settings.setValue("wcs_grid/enabled", self.chk_wcs_enabled.isChecked())
459
+ self.settings.setValue("wcs_grid/mode", "auto" if self.cb_wcs_mode.currentIndex() == 0 else "fixed")
460
+ self.settings.setValue("wcs_grid/step_unit", "deg" if self.cb_wcs_unit.currentIndex() == 0 else "arcmin")
461
+ self.settings.setValue("wcs_grid/step_value", float(self.sp_wcs_step.value()))
462
+
463
+
464
+ # Updates + Display
465
+ self.settings.setValue("updates/check_on_startup", self.chk_updates_startup.isChecked())
466
+ self.settings.setValue("updates/url", self.le_updates_url.text().strip())
467
+ self.settings.setValue("display/autostretch_16bit", self.chk_autostretch_16bit.isChecked())
468
+
469
+ # Custom background: persist the chosen path (empty -> remove)
470
+ bg_path = (self.le_bg_path.text() or "").strip()
471
+ if bg_path:
472
+ self.settings.setValue("ui/custom_background", bg_path)
473
+ else:
474
+ try:
475
+ self.settings.remove("ui/custom_background")
476
+ except Exception:
477
+ self.settings.setValue("ui/custom_background", "")
478
+
479
+ # bg_opacity is already saved in real-time by _on_opacity_changed()
480
+
481
+ # Theme
482
+ idx = max(0, self.cb_theme.currentIndex())
483
+ if idx == 0:
484
+ theme_val = "dark"
485
+ elif idx == 1:
486
+ theme_val = "gray"
487
+ elif idx == 2:
488
+ theme_val = "light"
489
+ elif idx == 3:
490
+ theme_val = "system"
491
+ else:
492
+ theme_val = "custom"
493
+ self.settings.setValue("ui/theme", theme_val)
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
+
510
+ self.settings.sync()
511
+
512
+ # Apply now if the parent knows how
513
+ p = self.parent()
514
+ if p and hasattr(p, "apply_theme_from_settings"):
515
+ try:
516
+ p.apply_theme_from_settings()
517
+ except Exception:
518
+ pass
519
+
520
+ if hasattr(p, "mdi") and hasattr(p.mdi, "viewport"):
521
+ p.mdi.viewport().update()
522
+
523
+ self.accept()
524
+
525
+ from PyQt6.QtGui import QColor, QFont
526
+
527
+
528
+ class ThemeEditorDialog(QDialog):
529
+ """
530
+ Simple "Custom Theme" editor: lets the user pick main colors and a UI font.
531
+ Colors are stored in QSettings as hex strings (e.g. '#404040').
532
+ """
533
+ def __init__(self, parent, settings: QSettings):
534
+ super().__init__(parent)
535
+ self.settings = settings
536
+ self.setWindowTitle("Custom Theme")
537
+ self.colors: dict[str, QColor] = {}
538
+ self.font_str: str = self.settings.value("ui/custom/font", "", type=str) or ""
539
+
540
+ form = QFormLayout(self)
541
+
542
+ # Helper: add color pickers for key roles
543
+ self._add_color_picker(form, "Window / Panels", "ui/custom/window", QColor(40, 40, 40))
544
+ self._add_color_picker(form, "Base (Editors)", "ui/custom/base", QColor(24, 24, 24))
545
+ self._add_color_picker(form, "Alternate Base", "ui/custom/altbase", QColor(32, 32, 32))
546
+ self._add_color_picker(form, "Text", "ui/custom/text", QColor(230, 230, 230))
547
+ self._add_color_picker(form, "Buttons", "ui/custom/button", QColor(40, 40, 40))
548
+ self._add_color_picker(form, "Highlight / Accent","ui/custom/highlight",QColor(30, 144, 255))
549
+ self._add_color_picker(form, "Link", "ui/custom/link", QColor(120, 170, 255))
550
+ self._add_color_picker(form, "Visited Link", "ui/custom/link_visited", QColor(180, 150, 255))
551
+
552
+ # Font picker
553
+ self.btn_font = QPushButton("Choose…")
554
+ self.btn_font.clicked.connect(self._pick_font)
555
+ form.addRow("UI Font:", self.btn_font)
556
+
557
+ # Buttons
558
+ btns = QDialogButtonBox(
559
+ QDialogButtonBox.StandardButton.Ok | QDialogButtonBox.StandardButton.Cancel,
560
+ parent=self
561
+ )
562
+ btns.accepted.connect(self._save_and_accept)
563
+ btns.rejected.connect(self.reject)
564
+ form.addRow(btns)
565
+
566
+ # ---------- helpers ----------
567
+
568
+ def _add_color_picker(self, form: QFormLayout, label_text: str,
569
+ key: str, default: QColor):
570
+ # Load from settings or default
571
+ stored = self.settings.value(key, default.name(), type=str)
572
+ color = QColor(stored) if stored else default
573
+ self.colors[key] = color
574
+
575
+ btn = QPushButton(color.name())
576
+ btn.setMinimumWidth(90)
577
+ btn.setStyleSheet(f"background-color: {color.name()}; color: #ffffff;")
578
+ btn.clicked.connect(lambda _=False, k=key, b=btn: self._pick_color(k, b))
579
+
580
+ form.addRow(label_text + ":", btn)
581
+
582
+ def _pick_color(self, key: str, button: QPushButton):
583
+ initial = self.colors.get(key, QColor("#404040"))
584
+ col = QColorDialog.getColor(initial, self, "Select Color")
585
+ if col.isValid():
586
+ self.colors[key] = col
587
+ button.setText(col.name())
588
+ button.setStyleSheet(f"background-color: {col.name()}; color: #ffffff;")
589
+
590
+ from PyQt6.QtGui import QFont
591
+ from PyQt6.QtWidgets import QFontDialog
592
+
593
+ def _pick_font(self):
594
+ # Load previous font if we have one
595
+ base_str = self.settings.value("ui/custom_font", "", type=str)
596
+ base_font = QFont()
597
+ if base_str:
598
+ try:
599
+ base_font.fromString(base_str)
600
+ except Exception:
601
+ pass
602
+
603
+ # ✅ NOTE: (font, ok) — NOT (ok, font)
604
+ font, ok = QFontDialog.getFont(base_font, self, "Select UI Font")
605
+ if not ok:
606
+ return # user cancelled
607
+
608
+ # Store and update preview
609
+ self.font_str = font.toString()
610
+ self.settings.setValue("ui/custom_font", self.font_str)
611
+ self.settings.sync()
612
+
613
+ # If you have a label/button to show the chosen font:
614
+ try:
615
+ self.font_button.setText(f"{font.family()}, {font.pointSize()} pt")
616
+ except Exception:
617
+ pass
618
+
619
+ # Re-apply theme so the new font takes effect
620
+ parent = self.parent()
621
+ if parent and hasattr(parent, "apply_theme_from_settings"):
622
+ try:
623
+ parent.apply_theme_from_settings()
624
+ except Exception:
625
+ pass
626
+
627
+ def _save_and_accept(self):
628
+ # Persist colors
629
+ for key, col in self.colors.items():
630
+ self.settings.setValue(key, col.name())
631
+
632
+ # Persist font if chosen
633
+ if self.font_str:
634
+ self.settings.setValue("ui/custom/font", self.font_str)
635
+
636
+ self.settings.sync()
637
+ self.accept()