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.

Potentially problematic release.


This version of setiastrosuitepro might be problematic. Click here for more details.

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
@@ -10,6 +10,9 @@ from PyQt6.QtCore import Qt, QTimer, QUrl
10
10
  from PyQt6.QtGui import QAction, QActionGroup, QIcon, QKeySequence, QDesktopServices
11
11
  from PyQt6.QtWidgets import QMenu, QToolButton
12
12
 
13
+ from PyQt6.QtCore import QElapsedTimer
14
+
15
+
13
16
  if TYPE_CHECKING:
14
17
  pass
15
18
 
@@ -20,7 +23,7 @@ from setiastro.saspro.resources import (
20
23
  LInsert_path, rgbcombo_path, rgbextract_path, graxperticon_path,
21
24
  cropicon_path, openfile_path, abeicon_path, undoicon_path, redoicon_path,
22
25
  blastericon_path, hdr_path, invert_path, fliphorizontal_path,
23
- flipvertical_path, rotateclockwise_path, rotatecounterclockwise_path,
26
+ flipvertical_path, rotateclockwise_path, rotatecounterclockwise_path,rotatearbitrary_path,
24
27
  rotate180_path, maskcreate_path, maskapply_path, maskremove_path,
25
28
  pixelmath_path, histogram_path, mosaic_path, rescale_path, staralign_path,
26
29
  platesolve_path, psf_path, supernova_path, starregistration_path,
@@ -73,7 +76,8 @@ class ToolbarMixin:
73
76
 
74
77
  def _init_toolbar(self):
75
78
  # View toolbar (Undo / Redo / Display-Stretch)
76
- tb = DraggableToolBar("View", self)
79
+ tb = DraggableToolBar(self.tr("View"), self)
80
+ tb.setObjectName("View")
77
81
  tb.setSettingsKey("Toolbar/View")
78
82
  self.addToolBar(Qt.ToolBarArea.TopToolBarArea, tb)
79
83
 
@@ -144,23 +148,30 @@ class ToolbarMixin:
144
148
  if isinstance(btn_fit, QToolButton):
145
149
  fit_menu = QMenu(btn_fit)
146
150
 
147
- self.act_auto_fit = fit_menu.addAction("Auto-fit on Resize")
148
- self.act_auto_fit.setCheckable(True)
149
- self.act_auto_fit.setChecked(self._auto_fit_on_resize)
150
- self.act_auto_fit.toggled.connect(self._toggle_auto_fit_on_resize)
151
+ # Use the existing action created in _create_actions()
152
+ fit_menu.addAction(self.act_auto_fit_resize)
153
+
154
+ # (Optional) make sure it reflects current flag at startup
155
+ self.act_auto_fit_resize.blockSignals(True)
156
+ try:
157
+ self.act_auto_fit_resize.setChecked(bool(self._auto_fit_on_resize))
158
+ finally:
159
+ self.act_auto_fit_resize.blockSignals(False)
151
160
 
152
161
  btn_fit.setMenu(fit_menu)
153
162
  btn_fit.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup)
154
163
 
155
- # Same style concept as Display-Stretch
156
164
  btn_fit.setStyleSheet("""
157
165
  QToolButton { color: #dcdcdc; }
158
166
  QToolButton:checked { color: #DAA520; font-weight: 600; }
159
167
  """)
160
168
 
169
+
161
170
  # Make sure the visual state matches the flag at startup
162
- self._sync_fit_auto_visual()
163
171
  self._restore_toolbar_order(tb, "Toolbar/View")
172
+ self._restore_toolbar_memberships() # (or keep your existing placement, but the bind must be AFTER it)
173
+ self._bind_view_toolbar_menus(tb)
174
+ self._sync_fit_auto_visual()
164
175
  # Apply hidden state immediately after order restore (prevents flash)
165
176
  try:
166
177
  tb.apply_hidden_state()
@@ -168,7 +179,8 @@ class ToolbarMixin:
168
179
  pass
169
180
 
170
181
  # Functions toolbar
171
- tb_fn = DraggableToolBar("Functions", self)
182
+ tb_fn = DraggableToolBar(self.tr("Functions"), self)
183
+ tb_fn.setObjectName("Functions")
172
184
  tb_fn.setSettingsKey("Toolbar/Functions")
173
185
  self.addToolBar(Qt.ToolBarArea.TopToolBarArea, tb_fn)
174
186
 
@@ -220,7 +232,8 @@ class ToolbarMixin:
220
232
  except Exception:
221
233
  pass
222
234
 
223
- tbCosmic = DraggableToolBar("Cosmic Clarity", self)
235
+ tbCosmic = DraggableToolBar(self.tr("Cosmic Clarity"), self)
236
+ tbCosmic.setObjectName("Cosmic Clarity")
224
237
  tbCosmic.setSettingsKey("Toolbar/Cosmic")
225
238
  self.addToolBar(Qt.ToolBarArea.TopToolBarArea, tbCosmic)
226
239
 
@@ -234,7 +247,8 @@ class ToolbarMixin:
234
247
  except Exception:
235
248
  pass
236
249
 
237
- tb_tl = DraggableToolBar("Tools", self)
250
+ tb_tl = DraggableToolBar(self.tr("Tools"), self)
251
+ tb_tl.setObjectName("Tools")
238
252
  tb_tl.setSettingsKey("Toolbar/Tools")
239
253
  self.addToolBar(Qt.ToolBarArea.TopToolBarArea, tb_tl)
240
254
 
@@ -253,7 +267,8 @@ class ToolbarMixin:
253
267
  except Exception:
254
268
  pass
255
269
 
256
- tb_geom = DraggableToolBar("Geometry", self)
270
+ tb_geom = DraggableToolBar(self.tr("Geometry"), self)
271
+ tb_geom.setObjectName("Geometry")
257
272
  tb_geom.setSettingsKey("Toolbar/Geometry")
258
273
  self.addToolBar(Qt.ToolBarArea.TopToolBarArea, tb_geom)
259
274
 
@@ -265,6 +280,7 @@ class ToolbarMixin:
265
280
  tb_geom.addAction(self.act_geom_rot_cw)
266
281
  tb_geom.addAction(self.act_geom_rot_ccw)
267
282
  tb_geom.addAction(self.act_geom_rot_180)
283
+ tb_geom.addAction(self.act_geom_rot_any)
268
284
  tb_geom.addSeparator()
269
285
  tb_geom.addAction(self.act_geom_rescale)
270
286
  tb_geom.addSeparator()
@@ -276,7 +292,8 @@ class ToolbarMixin:
276
292
  except Exception:
277
293
  pass
278
294
 
279
- tb_star = DraggableToolBar("Star Stuff", self)
295
+ tb_star = DraggableToolBar(self.tr("Star Stuff"), self)
296
+ tb_star.setObjectName("Star Stuff")
280
297
  tb_star.setSettingsKey("Toolbar/StarStuff")
281
298
  self.addToolBar(Qt.ToolBarArea.TopToolBarArea, tb_star)
282
299
 
@@ -301,7 +318,8 @@ class ToolbarMixin:
301
318
  except Exception:
302
319
  pass
303
320
 
304
- tb_msk = DraggableToolBar("Masks", self)
321
+ tb_msk = DraggableToolBar(self.tr("Masks"), self)
322
+ tb_msk.setObjectName("Masks")
305
323
  tb_msk.setSettingsKey("Toolbar/Masks")
306
324
  self.addToolBar(Qt.ToolBarArea.TopToolBarArea, tb_msk)
307
325
 
@@ -315,7 +333,8 @@ class ToolbarMixin:
315
333
  except Exception:
316
334
  pass
317
335
 
318
- tb_wim = DraggableToolBar("What's In My...", self)
336
+ tb_wim = DraggableToolBar(self.tr("What's In My..."), self)
337
+ tb_wim.setObjectName("What's In My...")
319
338
  tb_wim.setSettingsKey("Toolbar/WhatsInMy")
320
339
  self.addToolBar(Qt.ToolBarArea.TopToolBarArea, tb_wim)
321
340
 
@@ -328,7 +347,8 @@ class ToolbarMixin:
328
347
  except Exception:
329
348
  pass
330
349
 
331
- tb_bundle = DraggableToolBar("Bundles", self)
350
+ tb_bundle = DraggableToolBar(self.tr("Bundles"), self)
351
+ tb_bundle.setObjectName("Bundles")
332
352
  tb_bundle.setSettingsKey("Toolbar/Bundles")
333
353
  self.addToolBar(Qt.ToolBarArea.TopToolBarArea, tb_bundle)
334
354
 
@@ -352,74 +372,200 @@ class ToolbarMixin:
352
372
  except Exception:
353
373
  pass
354
374
 
375
+ self._rebind_view_dropdowns()
376
+
377
+ def _toolbar_containing_action(self, action: QAction):
378
+ from setiastro.saspro.shortcuts import DraggableToolBar
379
+ for tb in self.findChildren(DraggableToolBar):
380
+ if action in tb.actions():
381
+ return tb
382
+ return None
383
+
384
+
385
+ def _rebind_view_dropdowns(self):
386
+ """
387
+ Rebind dropdown menus for Display-Stretch + Fit buttons
388
+ on whatever toolbar those actions currently live in.
389
+ Call this AFTER all restore/reorder/membership moves.
390
+ """
391
+ # ---- Display-Stretch dropdown ----
392
+ tb = self._toolbar_containing_action(self.act_autostretch)
393
+ if tb:
394
+ btn = tb.widgetForAction(self.act_autostretch)
395
+ if isinstance(btn, QToolButton):
396
+ menu = QMenu(btn)
397
+ menu.addAction(self.act_stretch_linked)
398
+ menu.addAction(self.act_hardstretch)
399
+
400
+ menu.addSeparator()
401
+ menu.addAction(self.act_display_target)
402
+ menu.addAction(self.act_display_sigma)
403
+
404
+ presets = QMenu(self.tr("Presets"), menu)
405
+ a_norm = presets.addAction(self.tr("Normal (target 0.30, σ 5)"))
406
+ a_midy = presets.addAction(self.tr("Mid (target 0.40, σ 3)"))
407
+ a_hard = presets.addAction(self.tr("Hard (target 0.50, σ 2)"))
408
+ menu.addMenu(presets)
409
+
410
+ menu.addSeparator()
411
+ menu.addAction(self.act_bake_display_stretch)
412
+
413
+ def _apply_preset(t, s, also_enable=True):
414
+ self.settings.setValue("display/target", float(t))
415
+ self.settings.setValue("display/sigma", float(s))
416
+ sw = self.mdi.activeSubWindow()
417
+ if not sw:
418
+ return
419
+ view = sw.widget()
420
+ if hasattr(view, "set_autostretch_target"):
421
+ view.set_autostretch_target(float(t))
422
+ if hasattr(view, "set_autostretch_sigma"):
423
+ view.set_autostretch_sigma(float(s))
424
+ if also_enable and not getattr(view, "autostretch_enabled", False):
425
+ if hasattr(view, "set_autostretch"):
426
+ view.set_autostretch(True)
427
+ self._sync_autostretch_action(True)
428
+
429
+ a_norm.triggered.connect(lambda: _apply_preset(0.30, 5.0))
430
+ a_midy.triggered.connect(lambda: _apply_preset(0.40, 3.0))
431
+ a_hard.triggered.connect(lambda: _apply_preset(0.50, 2.0))
432
+
433
+ btn.setMenu(menu)
434
+ btn.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup)
435
+
436
+ # ---- Fit dropdown ----
437
+ tb_fit = self._toolbar_containing_action(self.act_zoom_fit)
438
+ if tb_fit:
439
+ btn_fit = tb_fit.widgetForAction(self.act_zoom_fit)
440
+ if isinstance(btn_fit, QToolButton):
441
+ fit_menu = QMenu(btn_fit)
442
+ fit_menu.addAction(self.act_auto_fit_resize) # use the real action
443
+ btn_fit.setMenu(fit_menu)
444
+ btn_fit.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup)
445
+
446
+
447
+ def _bind_view_toolbar_menus(self, tb: DraggableToolBar):
448
+ # --- Display-Stretch menu ---
449
+ btn = tb.widgetForAction(self.act_autostretch)
450
+ if isinstance(btn, QToolButton):
451
+ menu = QMenu(btn)
452
+ menu.addAction(self.act_stretch_linked)
453
+ menu.addAction(self.act_hardstretch)
454
+
455
+ menu.addSeparator()
456
+ menu.addAction(self.act_display_target)
457
+ menu.addAction(self.act_display_sigma)
458
+
459
+ presets = QMenu(self.tr("Presets"), menu)
460
+ a_norm = presets.addAction(self.tr("Normal (target 0.30, σ 5)"))
461
+ a_midy = presets.addAction(self.tr("Mid (target 0.40, σ 3)"))
462
+ a_hard = presets.addAction(self.tr("Hard (target 0.50, σ 2)"))
463
+ menu.addMenu(presets)
464
+ menu.addSeparator()
465
+ menu.addAction(self.act_bake_display_stretch)
466
+
467
+ def _apply_preset(t, s, also_enable=True):
468
+ self.settings.setValue("display/target", float(t))
469
+ self.settings.setValue("display/sigma", float(s))
470
+ sw = self.mdi.activeSubWindow()
471
+ if not sw:
472
+ return
473
+ view = sw.widget()
474
+ if hasattr(view, "set_autostretch_target"):
475
+ view.set_autostretch_target(float(t))
476
+ if hasattr(view, "set_autostretch_sigma"):
477
+ view.set_autostretch_sigma(float(s))
478
+ if also_enable and not getattr(view, "autostretch_enabled", False):
479
+ if hasattr(view, "set_autostretch"):
480
+ view.set_autostretch(True)
481
+ self._sync_autostretch_action(True)
482
+
483
+ a_norm.triggered.connect(lambda: _apply_preset(0.30, 5.0))
484
+ a_midy.triggered.connect(lambda: _apply_preset(0.40, 3.0))
485
+ a_hard.triggered.connect(lambda: _apply_preset(0.50, 2.0))
486
+
487
+ btn.setMenu(menu)
488
+ btn.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup)
489
+
490
+ # --- Fit menu (Auto-fit checkbox) ---
491
+ btn_fit = tb.widgetForAction(self.act_zoom_fit)
492
+ if isinstance(btn_fit, QToolButton):
493
+ fit_menu = QMenu(btn_fit)
494
+
495
+ # IMPORTANT: use your existing action (don’t create a new one)
496
+ fit_menu.addAction(self.act_auto_fit_resize)
497
+
498
+ btn_fit.setMenu(fit_menu)
499
+ btn_fit.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup)
500
+
355
501
 
356
502
  def _create_actions(self):
357
503
  # File actions
358
- self.act_open = QAction(QIcon(openfile_path), "Open...", self)
504
+ self.act_open = QAction(QIcon(openfile_path), self.tr("Open..."), self)
359
505
  self.act_open.setIconVisibleInMenu(True)
360
506
  self.act_open.setShortcut(QKeySequence.StandardKey.Open)
361
- self.act_open.setStatusTip("Open image(s)")
507
+ self.act_open.setStatusTip(self.tr("Open image(s)"))
362
508
  self.act_open.triggered.connect(self.open_files)
363
509
 
364
510
 
365
- self.act_project_new = QAction("New Project", self)
366
- self.act_project_save = QAction("Save Project...", self)
367
- self.act_project_load = QAction("Load Project...", self)
511
+ self.act_project_new = QAction(self.tr("New Project"), self)
512
+ self.act_project_save = QAction(self.tr("Save Project..."), self)
513
+ self.act_project_load = QAction(self.tr("Load Project..."), self)
368
514
 
369
- self.act_project_new.setStatusTip("Close all views and clear shortcuts")
370
- self.act_project_save.setStatusTip("Save all views, histories, and shortcuts to a .sas file")
371
- self.act_project_load.setStatusTip("Load a .sas project (views, histories, shortcuts)")
515
+ self.act_project_new.setStatusTip(self.tr("Close all views and clear shortcuts"))
516
+ self.act_project_save.setStatusTip(self.tr("Save all views, histories, and shortcuts to a .sas file"))
517
+ self.act_project_load.setStatusTip(self.tr("Load a .sas project (views, histories, shortcuts)"))
372
518
 
373
519
  self.act_project_new.triggered.connect(self._new_project)
374
520
  self.act_project_save.triggered.connect(self._save_project)
375
521
  self.act_project_load.triggered.connect(self._load_project)
376
522
 
377
- self.act_clear_views = QAction("Clear All Views", self)
378
- self.act_clear_views.setStatusTip("Close all views and documents, keep desktop shortcuts")
523
+ self.act_clear_views = QAction(self.tr("Clear All Views"), self)
524
+ self.act_clear_views.setStatusTip(self.tr("Close all views and documents, keep desktop shortcuts"))
379
525
  # optional shortcut (pick anything you like or omit)
380
526
  # self.act_clear_views.setShortcut(QKeySequence("Ctrl+Shift+W"))
381
527
  self.act_clear_views.triggered.connect(self._clear_views_keep_shortcuts)
382
528
 
383
- self.act_save = QAction(QIcon(disk_path), "Save As...", self)
529
+ self.act_save = QAction(QIcon(disk_path), self.tr("Save As..."), self)
384
530
  self.act_save.setIconVisibleInMenu(True)
385
531
  self.act_save.setShortcut(QKeySequence.StandardKey.SaveAs)
386
- self.act_save.setStatusTip("Save the active image")
532
+ self.act_save.setStatusTip(self.tr("Save the active image"))
387
533
  self.act_save.triggered.connect(self.save_active)
388
534
 
389
- self.act_exit = QAction("&Exit", self)
535
+ self.act_exit = QAction(self.tr("&Exit"), self)
390
536
  self.act_exit.setShortcut(QKeySequence.StandardKey.Quit) # Cmd+Q / Ctrl+Q
391
537
  # Make it appear under the app menu on macOS automatically:
392
538
  self.act_exit.setMenuRole(QAction.MenuRole.QuitRole)
393
539
  self.act_exit.triggered.connect(self._on_exit)
394
540
 
395
- self.act_cascade = QAction("Cascade Views", self)
396
- self.act_cascade.setStatusTip("Cascade all subwindows")
541
+ self.act_cascade = QAction(self.tr("Cascade Views"), self)
542
+ self.act_cascade.setStatusTip(self.tr("Cascade all subwindows"))
397
543
  self.act_cascade.setShortcut(QKeySequence("Ctrl+Shift+C"))
398
544
  self.act_cascade.triggered.connect(self._cascade_views)
399
545
 
400
- self.act_tile = QAction("Tile Views", self)
401
- self.act_tile.setStatusTip("Tile all subwindows")
546
+ self.act_tile = QAction(self.tr("Tile Views"), self)
547
+ self.act_tile.setStatusTip(self.tr("Tile all subwindows"))
402
548
  self.act_tile.setShortcut(QKeySequence("Ctrl+Shift+T"))
403
549
  self.act_tile.triggered.connect(self._tile_views)
404
550
 
405
- self.act_tile_vert = QAction("Tile Vertically", self)
406
- self.act_tile_vert.setStatusTip("Split the workspace into equal vertical columns")
551
+ self.act_tile_vert = QAction(self.tr("Tile Vertically"), self)
552
+ self.act_tile_vert.setStatusTip(self.tr("Split the workspace into equal vertical columns"))
407
553
  self.act_tile_vert.triggered.connect(lambda: self._tile_views_direction("v"))
408
554
 
409
- self.act_tile_horiz = QAction("Tile Horizontally", self)
410
- self.act_tile_horiz.setStatusTip("Split the workspace into equal horizontal rows")
555
+ self.act_tile_horiz = QAction(self.tr("Tile Horizontally"), self)
556
+ self.act_tile_horiz.setStatusTip(self.tr("Split the workspace into equal horizontal rows"))
411
557
  self.act_tile_horiz.triggered.connect(lambda: self._tile_views_direction("h"))
412
558
 
413
- self.act_tile_grid = QAction("Smart Grid", self)
414
- self.act_tile_grid.setStatusTip("Arrange subwindows in a near-square grid")
559
+ self.act_tile_grid = QAction(self.tr("Smart Grid"), self)
560
+ self.act_tile_grid.setStatusTip(self.tr("Arrange subwindows in a near-square grid"))
415
561
  self.act_tile_grid.triggered.connect(self._tile_views_grid)
416
562
 
417
- self.act_link_group = QAction("Link Pan/Zoom", self)
563
+ self.act_link_group = QAction(self.tr("Link Pan/Zoom"), self)
418
564
  self.act_link_group.setCheckable(True) # checked when in any group
419
565
  self.act_link_group.triggered.connect(self._cycle_group_for_active) # << add
420
566
 
421
- self.act_undo = QAction(QIcon(undoicon_path), "Undo", self)
422
- self.act_redo = QAction(QIcon(redoicon_path), "Redo", self)
567
+ self.act_undo = QAction(QIcon(undoicon_path), self.tr("Undo"), self)
568
+ self.act_redo = QAction(QIcon(redoicon_path), self.tr("Redo"), self)
423
569
  self.act_undo.setShortcut(QKeySequence.StandardKey.Undo) # Ctrl+Z
424
570
  self.act_redo.setShortcuts([QKeySequence.StandardKey.Redo, "Ctrl+Y"]) # Shift+Ctrl+Z / Ctrl+Y
425
571
  self.act_undo.setIconVisibleInMenu(True)
@@ -428,34 +574,34 @@ class ToolbarMixin:
428
574
  self.act_redo.triggered.connect(self._redo_active)
429
575
 
430
576
  # View-ish action (toolbar toggle)
431
- self.act_autostretch = QAction("Display-Stretch", self, checkable=True)
432
- self.act_autostretch.setStatusTip("Toggle display auto-stretch for the active window")
577
+ self.act_autostretch = QAction(self.tr("Display-Stretch"), self, checkable=True)
578
+ self.act_autostretch.setStatusTip(self.tr("Toggle display auto-stretch for the active window"))
433
579
  self.act_autostretch.setShortcut(QKeySequence("A")) # optional: mirror the view shortcut
434
580
  self.act_autostretch.toggled.connect(self._toggle_autostretch)
435
581
 
436
- self.act_hardstretch = QAction("Hard-Display-Stretch", self, checkable=True)
582
+ self.act_hardstretch = QAction(self.tr("Hard-Display-Stretch"), self, checkable=True)
437
583
  self.addAction(self.act_hardstretch)
438
584
  self.act_hardstretch.setShortcut(QKeySequence("H"))
439
- self.act_hardstretch.setStatusTip("Toggle hard profile for Display-Stretch (H)")
585
+ self.act_hardstretch.setStatusTip(self.tr("Toggle hard profile for Display-Stretch (H)"))
440
586
 
441
587
  # use toggled(bool), not triggered()
442
588
  self.act_hardstretch.toggled.connect(self._set_hard_autostretch_from_action)
443
589
 
444
590
  # NEW: Linked/Unlinked toggle (global default via QSettings, per-view runtime)
445
- self.act_stretch_linked = QAction("Link RGB channels", self, checkable=True)
446
- self.act_stretch_linked.setStatusTip("Apply the same stretch to all RGB channels")
591
+ self.act_stretch_linked = QAction(self.tr("Link RGB channels"), self, checkable=True)
592
+ self.act_stretch_linked.setStatusTip(self.tr("Apply the same stretch to all RGB channels"))
447
593
  self.act_stretch_linked.setShortcut(QKeySequence("Ctrl+Shift+L"))
448
594
  self.act_stretch_linked.setChecked(
449
595
  self.settings.value("display/stretch_linked", False, type=bool)
450
596
  )
451
597
  self.act_stretch_linked.toggled.connect(self._set_linked_stretch_from_action)
452
598
 
453
- self.act_display_target = QAction("Set Target Median...", self)
454
- self.act_display_target.setStatusTip("Set the target median for Display-Stretch (e.g., 0.30)")
599
+ self.act_display_target = QAction(self.tr("Set Target Median..."), self)
600
+ self.act_display_target.setStatusTip(self.tr("Set the target median for Display-Stretch (e.g., 0.30)"))
455
601
  self.act_display_target.triggered.connect(self._edit_display_target)
456
602
 
457
- self.act_display_sigma = QAction("Set Sigma...", self)
458
- self.act_display_sigma.setStatusTip("Set the sigma for Display-Stretch (e.g., 5.0)")
603
+ self.act_display_sigma = QAction(self.tr("Set Sigma..."), self)
604
+ self.act_display_sigma.setStatusTip(self.tr("Set the sigma for Display-Stretch (e.g., 5.0)"))
459
605
  self.act_display_sigma.triggered.connect(self._edit_display_sigma)
460
606
 
461
607
  # Defaults if not already present
@@ -464,9 +610,9 @@ class ToolbarMixin:
464
610
  if self.settings.value("display/sigma", None) is None:
465
611
  self.settings.setValue("display/sigma", 5.0)
466
612
 
467
- self.act_bake_display_stretch = QAction("Make Display-Stretch Permanent", self)
613
+ self.act_bake_display_stretch = QAction(self.tr("Make Display-Stretch Permanent"), self)
468
614
  self.act_bake_display_stretch.setStatusTip(
469
- "Apply the current Display-Stretch to the image and add an undo step"
615
+ self.tr("Apply the current Display-Stretch to the image and add an undo step")
470
616
  )
471
617
  # choose any shortcut you like; avoid Ctrl+A etc
472
618
  self.act_bake_display_stretch.setShortcut(QKeySequence("Shift+A"))
@@ -474,31 +620,31 @@ class ToolbarMixin:
474
620
 
475
621
  # --- Zoom controls ---
476
622
  # --- Zoom controls (themed icons) ---
477
- self.act_zoom_out = QAction(QIcon.fromTheme("zoom-out"), "Zoom Out", self)
478
- self.act_zoom_out.setStatusTip("Zoom out")
623
+ self.act_zoom_out = QAction(QIcon.fromTheme("zoom-out"), self.tr("Zoom Out"), self)
624
+ self.act_zoom_out.setStatusTip(self.tr("Zoom out"))
479
625
  self.act_zoom_out.setShortcuts([QKeySequence("Ctrl+-")])
480
626
  self.act_zoom_out.triggered.connect(lambda: self._zoom_step_active(-1))
481
627
 
482
- self.act_zoom_in = QAction(QIcon.fromTheme("zoom-in"), "Zoom In", self)
483
- self.act_zoom_in.setStatusTip("Zoom in")
628
+ self.act_zoom_in = QAction(QIcon.fromTheme("zoom-in"), self.tr("Zoom In"), self)
629
+ self.act_zoom_in.setStatusTip(self.tr("Zoom in"))
484
630
  self.act_zoom_in.setShortcuts([
485
631
  QKeySequence("Ctrl++"), # Ctrl + (Shift + = on many keyboards)
486
632
  QKeySequence("Ctrl+="), # fallback
487
633
  ])
488
634
  self.act_zoom_in.triggered.connect(lambda: self._zoom_step_active(+1))
489
635
 
490
- self.act_zoom_1_1 = QAction(QIcon.fromTheme("zoom-original"), "1:1", self)
491
- self.act_zoom_1_1.setStatusTip("Zoom to 100% (pixel-for-pixel)")
636
+ self.act_zoom_1_1 = QAction(QIcon.fromTheme("zoom-original"), self.tr("1:1"), self)
637
+ self.act_zoom_1_1.setStatusTip(self.tr("Zoom to 100% (pixel-for-pixel)"))
492
638
  self.act_zoom_1_1.setShortcut(QKeySequence("Ctrl+1"))
493
639
  self.act_zoom_1_1.triggered.connect(self._zoom_active_1_1)
494
640
 
495
- self.act_zoom_fit = QAction(QIcon.fromTheme("zoom-fit-best"), "Fit", self)
496
- self.act_zoom_fit.setStatusTip("Fit image to current window")
641
+ self.act_zoom_fit = QAction(QIcon.fromTheme("zoom-fit-best"), self.tr("Fit"), self)
642
+ self.act_zoom_fit.setStatusTip(self.tr("Fit image to current window"))
497
643
  self.act_zoom_fit.setShortcut(QKeySequence("Ctrl+0"))
498
644
  self.act_zoom_fit.triggered.connect(self._zoom_active_fit)
499
645
  self.act_zoom_fit.setCheckable(True)
500
646
 
501
- self.act_auto_fit_resize = QAction("Auto-fit on Resize", self)
647
+ self.act_auto_fit_resize = QAction(self.tr("Auto-fit on Resize"), self)
502
648
  self.act_auto_fit_resize.setCheckable(True)
503
649
 
504
650
  auto_on = self.settings.value("view/auto_fit_on_resize", False, type=bool)
@@ -509,102 +655,102 @@ class ToolbarMixin:
509
655
 
510
656
  # View state copy/paste (optional quick commands)
511
657
  self._copied_view_state = None
512
- self.act_copy_view = QAction("Copy View (zoom/pan)", self)
513
- self.act_paste_view = QAction("Paste View", self)
658
+ self.act_copy_view = QAction(self.tr("Copy View (zoom/pan)"), self)
659
+ self.act_paste_view = QAction(self.tr("Paste View"), self)
514
660
  self.act_copy_view.setShortcut("Ctrl+Shift+C")
515
661
  self.act_paste_view.setShortcut("Ctrl+Shift+V")
516
662
  self.act_copy_view.triggered.connect(self._copy_active_view)
517
663
  self.act_paste_view.triggered.connect(self._paste_active_view)
518
664
 
519
665
  # Functions
520
- self.act_crop = QAction(QIcon(cropicon_path), "Crop...", self)
521
- self.act_crop.setStatusTip("Crop / rotate with handles")
666
+ self.act_crop = QAction(QIcon(cropicon_path), self.tr("Crop..."), self)
667
+ self.act_crop.setStatusTip(self.tr("Crop / rotate with handles"))
522
668
  self.act_crop.setIconVisibleInMenu(True)
523
669
  self.act_crop.triggered.connect(self._open_crop_dialog)
524
670
 
525
- self.act_histogram = QAction(QIcon(histogram_path), "Histogram...", self)
526
- self.act_histogram.setStatusTip("View histogram and basic stats for the active image")
671
+ self.act_histogram = QAction(QIcon(histogram_path), self.tr("Histogram..."), self)
672
+ self.act_histogram.setStatusTip(self.tr("View histogram and basic stats for the active image"))
527
673
  self.act_histogram.setIconVisibleInMenu(True)
528
674
  self.act_histogram.triggered.connect(self._open_histogram)
529
675
 
530
- self.act_stat_stretch = QAction(QIcon(statstretch_path), "Statistical Stretch...", self)
531
- self.act_stat_stretch.setStatusTip("Stretch the image using median/SD statistics")
676
+ self.act_stat_stretch = QAction(QIcon(statstretch_path), self.tr("Statistical Stretch..."), self)
677
+ self.act_stat_stretch.setStatusTip(self.tr("Stretch the image using median/SD statistics"))
532
678
  self.act_stat_stretch.setIconVisibleInMenu(True)
533
679
  self.act_stat_stretch.triggered.connect(self._open_statistical_stretch)
534
680
 
535
- self.act_star_stretch = QAction(QIcon(starstretch_path), "Star Stretch...", self)
536
- self.act_star_stretch.setStatusTip("Arcsinh star stretch with optional SCNR and color boost")
681
+ self.act_star_stretch = QAction(QIcon(starstretch_path), self.tr("Star Stretch..."), self)
682
+ self.act_star_stretch.setStatusTip(self.tr("Arcsinh star stretch with optional SCNR and color boost"))
537
683
  self.act_star_stretch.setIconVisibleInMenu(True)
538
684
  self.act_star_stretch.triggered.connect(self._open_star_stretch)
539
685
 
540
- self.act_curves = QAction(QIcon(curves_path), "Curves Editor...", self) # add an icon later if you want
541
- self.act_curves.setStatusTip("Open the Curves Editor for the active image")
686
+ self.act_curves = QAction(QIcon(curves_path), self.tr("Curves Editor..."), self)
687
+ self.act_curves.setStatusTip(self.tr("Open the Curves Editor for the active image"))
542
688
  self.act_curves.setIconVisibleInMenu(True)
543
689
  self.act_curves.triggered.connect(self._open_curves_editor)
544
690
 
545
- self.act_ghs = QAction(QIcon(uhs_path), "Hyperbolic Stretch...", self)
546
- self.act_ghs.setStatusTip("Generalized hyperbolic stretch (α/beta/gamma, LP/HP, pivot)")
691
+ self.act_ghs = QAction(QIcon(uhs_path), self.tr("Hyperbolic Stretch..."), self)
692
+ self.act_ghs.setStatusTip(self.tr("Generalized hyperbolic stretch (α/beta/gamma, LP/HP, pivot)"))
547
693
  self.act_ghs.setIconVisibleInMenu(True)
548
694
  self.act_ghs.triggered.connect(self._open_hyperbolic)
549
695
 
550
- self.act_abe = QAction(QIcon(abeicon_path), "ABE...", self)
551
- self.act_abe.setStatusTip("Automatic Background Extraction")
696
+ self.act_abe = QAction(QIcon(abeicon_path), self.tr("ABE..."), self)
697
+ self.act_abe.setStatusTip(self.tr("Automatic Background Extraction"))
552
698
  self.act_abe.setIconVisibleInMenu(True)
553
699
  self.act_abe.triggered.connect(self._open_abe_tool)
554
700
 
555
- self.act_graxpert = QAction(QIcon(graxperticon_path), "Remove Gradient (GraXpert)...", self)
701
+ self.act_graxpert = QAction(QIcon(graxperticon_path), self.tr("Remove Gradient (GraXpert)..."), self)
556
702
  self.act_graxpert.setIconVisibleInMenu(True)
557
- self.act_graxpert.setStatusTip("Run GraXpert background extraction on the active image")
703
+ self.act_graxpert.setStatusTip(self.tr("Run GraXpert background extraction on the active image"))
558
704
  self.act_graxpert.triggered.connect(self._open_graxpert)
559
705
 
560
- self.act_remove_stars = QAction(QIcon(starnet_path), "Remove Stars...", self)
706
+ self.act_remove_stars = QAction(QIcon(starnet_path), self.tr("Remove Stars..."), self)
561
707
  self.act_remove_stars.setIconVisibleInMenu(True)
562
- self.act_remove_stars.setStatusTip("Run star removal on the active image")
708
+ self.act_remove_stars.setStatusTip(self.tr("Run star removal on the active image"))
563
709
  self.act_remove_stars.triggered.connect(lambda: self._remove_stars())
564
710
 
565
- self.act_add_stars = QAction(QIcon(staradd_path), "Add Stars...", self)
566
- self.act_add_stars.setStatusTip("Blend a starless view with a stars-only view")
711
+ self.act_add_stars = QAction(QIcon(staradd_path), self.tr("Add Stars..."), self)
712
+ self.act_add_stars.setStatusTip(self.tr("Blend a starless view with a stars-only view"))
567
713
  self.act_add_stars.setIconVisibleInMenu(True)
568
714
  self.act_add_stars.triggered.connect(lambda: self._add_stars())
569
715
 
570
- self.act_pedestal = QAction(QIcon(pedestal_icon_path), "Remove Pedestal", self)
571
- self.act_pedestal.setToolTip("Subtract per-channel minimum.\nClick: active view\nAlt+Drag: drop onto a view")
716
+ self.act_pedestal = QAction(QIcon(pedestal_icon_path), self.tr("Remove Pedestal"), self)
717
+ self.act_pedestal.setToolTip(self.tr("Subtract per-channel minimum.\nClick: active view\nAlt+Drag: drop onto a view"))
572
718
  self.act_pedestal.setShortcut("Ctrl+P")
573
719
  self.act_pedestal.triggered.connect(self._on_remove_pedestal)
574
720
 
575
- self.act_linear_fit = QAction(QIcon(linearfit_path),"Linear Fit...", self)
721
+ self.act_linear_fit = QAction(QIcon(linearfit_path), self.tr("Linear Fit..."), self)
576
722
  self.act_linear_fit.setIconVisibleInMenu(True)
577
- self.act_linear_fit.setStatusTip("Match image levels using Linear Fit")
723
+ self.act_linear_fit.setStatusTip(self.tr("Match image levels using Linear Fit"))
578
724
  # optional shortcut; change if you already use it elsewhere
579
725
  self.act_linear_fit.setShortcut("Ctrl+L")
580
726
  self.act_linear_fit.triggered.connect(self._open_linear_fit)
581
727
 
582
- self.act_remove_green = QAction(QIcon(green_path), "Remove Green...", self)
583
- self.act_remove_green.setToolTip("SCNR-style green channel removal.")
728
+ self.act_remove_green = QAction(QIcon(green_path), self.tr("Remove Green..."), self)
729
+ self.act_remove_green.setToolTip(self.tr("SCNR-style green channel removal."))
584
730
  self.act_remove_green.setIconVisibleInMenu(True)
585
731
  self.act_remove_green.triggered.connect(self._open_remove_green)
586
732
 
587
- self.act_background_neutral = QAction(QIcon(neutral_path), "Background Neutralization...", self)
588
- self.act_background_neutral.setStatusTip("Neutralize background color balance using a sampled region")
733
+ self.act_background_neutral = QAction(QIcon(neutral_path), self.tr("Background Neutralization..."), self)
734
+ self.act_background_neutral.setStatusTip(self.tr("Neutralize background color balance using a sampled region"))
589
735
  self.act_background_neutral.setIconVisibleInMenu(True)
590
736
  self.act_background_neutral.triggered.connect(self._open_background_neutral)
591
737
 
592
- self.act_white_balance = QAction(QIcon(whitebalance_path), "White Balance...", self)
593
- self.act_white_balance.setStatusTip("Apply white balance (Star-Based, Manual, or Auto)")
738
+ self.act_white_balance = QAction(QIcon(whitebalance_path), self.tr("White Balance..."), self)
739
+ self.act_white_balance.setStatusTip(self.tr("Apply white balance (Star-Based, Manual, or Auto)"))
594
740
  self.act_white_balance.triggered.connect(self._open_white_balance)
595
741
 
596
- self.act_sfcc = QAction(QIcon(spcc_icon_path), "Spectral Flux Color Calibration...", self)
742
+ self.act_sfcc = QAction(QIcon(spcc_icon_path), self.tr("Spectral Flux Color Calibration..."), self)
597
743
  self.act_sfcc.setObjectName("sfcc")
598
- self.act_sfcc.setToolTip("Open SFCC (Pickles + Filters + Sensor QE)")
744
+ self.act_sfcc.setToolTip(self.tr("Open SFCC (Pickles + Filters + Sensor QE)"))
599
745
  self.act_sfcc.triggered.connect(self.SFCC_show)
600
746
 
601
- self.act_convo = QAction(QIcon(convoicon_path), "Convolution / Deconvolution...", self)
747
+ self.act_convo = QAction(QIcon(convoicon_path), self.tr("Convolution / Deconvolution..."), self)
602
748
  self.act_convo.setObjectName("convo_deconvo")
603
- self.act_convo.setToolTip("Open Convolution / Deconvolution")
749
+ self.act_convo.setToolTip(self.tr("Open Convolution / Deconvolution"))
604
750
  self.act_convo.triggered.connect(self.show_convo_deconvo)
605
751
 
606
- self.act_multiscale_decomp = QAction(QIcon(multiscale_decomp_path), "Multiscale Decomposition...", self)
607
- self.act_multiscale_decomp.setStatusTip("Multiscale detail/residual decomposition with per-layer controls")
752
+ self.act_multiscale_decomp = QAction(QIcon(multiscale_decomp_path), self.tr("Multiscale Decomposition..."), self)
753
+ self.act_multiscale_decomp.setStatusTip(self.tr("Multiscale detail/residual decomposition with per-layer controls"))
608
754
  self.act_multiscale_decomp.setIconVisibleInMenu(True)
609
755
  self.act_multiscale_decomp.triggered.connect(self._open_multiscale_decomp)
610
756
 
@@ -612,8 +758,8 @@ class ToolbarMixin:
612
758
 
613
759
 
614
760
  # --- Extract Luminance main action ---
615
- self.act_extract_luma = QAction(QIcon(LExtract_path), "Extract Luminance", self)
616
- self.act_extract_luma.setStatusTip("Create a new mono document using the selected luminance method")
761
+ self.act_extract_luma = QAction(QIcon(LExtract_path), self.tr("Extract Luminance"), self)
762
+ self.act_extract_luma.setStatusTip(self.tr("Create a new mono document using the selected luminance method"))
617
763
  self.act_extract_luma.setIconVisibleInMenu(True)
618
764
  self.act_extract_luma.triggered.connect(lambda: self._extract_luminance(doc=None))
619
765
 
@@ -649,106 +795,112 @@ class ToolbarMixin:
649
795
 
650
796
  self._luma_group.triggered.connect(_on_luma_pick)
651
797
 
652
- self.act_recombine_luma = QAction(QIcon(LInsert_path), "Recombine Luminance...", self)
653
- self.act_recombine_luma.setStatusTip("Replace the active image's luminance from another view")
798
+ self.act_recombine_luma = QAction(QIcon(LInsert_path), self.tr("Recombine Luminance..."), self)
799
+ self.act_recombine_luma.setStatusTip(self.tr("Replace the active image's luminance from another view"))
654
800
  self.act_recombine_luma.setIconVisibleInMenu(True)
655
801
  self.act_recombine_luma.triggered.connect(lambda: self._recombine_luminance_ui(target_doc=None))
656
802
 
657
- self.act_rgb_extract = QAction(QIcon(rgbextract_path), "RGB Extract", self)
803
+ self.act_rgb_extract = QAction(QIcon(rgbextract_path), self.tr("RGB Extract"), self)
658
804
  self.act_rgb_extract.setIconVisibleInMenu(True)
659
- self.act_rgb_extract.setStatusTip("Extract R/G/B as three mono documents")
805
+ self.act_rgb_extract.setStatusTip(self.tr("Extract R/G/B as three mono documents"))
660
806
  self.act_rgb_extract.triggered.connect(self._rgb_extract_active)
661
807
 
662
- self.act_rgb_combine = QAction(QIcon(rgbcombo_path), "RGB Combination...", self)
808
+ self.act_rgb_combine = QAction(QIcon(rgbcombo_path), self.tr("RGB Combination..."), self)
663
809
  self.act_rgb_combine.setIconVisibleInMenu(True)
664
- self.act_rgb_combine.setStatusTip("Combine three mono images into RGB")
810
+ self.act_rgb_combine.setStatusTip(self.tr("Combine three mono images into RGB"))
665
811
  self.act_rgb_combine.triggered.connect(self._open_rgb_combination)
666
812
 
667
- self.act_blemish = QAction(QIcon(blastericon_path), "Blemish Blaster...", self)
813
+ self.act_blemish = QAction(QIcon(blastericon_path), self.tr("Blemish Blaster..."), self)
668
814
  self.act_blemish.setIconVisibleInMenu(True)
669
- self.act_blemish.setStatusTip("Interactive blemish removal on the active view")
815
+ self.act_blemish.setStatusTip(self.tr("Interactive blemish removal on the active view"))
670
816
  self.act_blemish.triggered.connect(self._open_blemish_blaster)
671
817
 
672
- self.act_wavescale_hdr = QAction(QIcon(hdr_path), "WaveScale HDR...", self)
673
- self.act_wavescale_hdr.setStatusTip("Wave-scale HDR with luminance-masked starlet")
818
+ self.act_wavescale_hdr = QAction(QIcon(hdr_path), self.tr("WaveScale HDR..."), self)
819
+ self.act_wavescale_hdr.setStatusTip(self.tr("Wave-scale HDR with luminance-masked starlet"))
674
820
  self.act_wavescale_hdr.setIconVisibleInMenu(True)
675
821
  self.act_wavescale_hdr.triggered.connect(self._open_wavescale_hdr)
676
822
 
677
- self.act_wavescale_de = QAction(QIcon(dse_icon_path), "WaveScale Dark Enhancer...", self)
678
- self.act_wavescale_de.setStatusTip("Enhance faint/dark structures with wavelet-guided masking")
823
+ self.act_wavescale_de = QAction(QIcon(dse_icon_path), self.tr("WaveScale Dark Enhancer..."), self)
824
+ self.act_wavescale_de.setStatusTip(self.tr("Enhance faint/dark structures with wavelet-guided masking"))
679
825
  self.act_wavescale_de.setIconVisibleInMenu(True)
680
826
  self.act_wavescale_de.triggered.connect(self._open_wavescale_dark_enhance)
681
827
 
682
- self.act_clahe = QAction(QIcon(clahe_path), "CLAHE...", self)
683
- self.act_clahe.setStatusTip("Contrast Limited Adaptive Histogram Equalization")
828
+ self.act_clahe = QAction(QIcon(clahe_path), self.tr("CLAHE..."), self)
829
+ self.act_clahe.setStatusTip(self.tr("Contrast Limited Adaptive Histogram Equalization"))
684
830
  self.act_clahe.setIconVisibleInMenu(True)
685
831
  self.act_clahe.triggered.connect(self._open_clahe)
686
832
 
687
- self.act_morphology = QAction(QIcon(morpho_path), "Morphological Operations...", self)
688
- self.act_morphology.setStatusTip("Erosion, dilation, opening, and closing.")
833
+ self.act_morphology = QAction(QIcon(morpho_path), self.tr("Morphological Operations..."), self)
834
+ self.act_morphology.setStatusTip(self.tr("Erosion, dilation, opening, and closing."))
689
835
  self.act_morphology.setIconVisibleInMenu(True)
690
836
  self.act_morphology.triggered.connect(self._open_morphology)
691
837
 
692
- self.act_pixelmath = QAction(QIcon(pixelmath_path), "Pixel Math...", self)
693
- self.act_pixelmath.setStatusTip("Evaluate expressions using open view names")
838
+ self.act_pixelmath = QAction(QIcon(pixelmath_path), self.tr("Pixel Math..."), self)
839
+ self.act_pixelmath.setStatusTip(self.tr("Evaluate expressions using open view names"))
694
840
  self.act_pixelmath.setIconVisibleInMenu(True)
695
841
  self.act_pixelmath.triggered.connect(self._open_pixel_math)
696
842
 
697
- self.act_signature = QAction(QIcon(signature_icon_path), "Signature / Insert...", self)
843
+ self.act_signature = QAction(QIcon(signature_icon_path), self.tr("Signature / Insert..."), self)
698
844
  self.act_signature.setIconVisibleInMenu(True)
699
- self.act_signature.setStatusTip("Add signatures/overlays and bake them into the active image")
845
+ self.act_signature.setStatusTip(self.tr("Add signatures/overlays and bake them into the active image"))
700
846
  self.act_signature.triggered.connect(self._open_signature_insert)
701
847
 
702
- self.act_halobgon = QAction(QIcon(halo_path), "Halo-B-Gon...", self)
848
+ self.act_halobgon = QAction(QIcon(halo_path), self.tr("Halo-B-Gon..."), self)
703
849
  self.act_halobgon.setIconVisibleInMenu(True)
704
- self.act_halobgon.setStatusTip("Remove those pesky halos around your stars")
850
+ self.act_halobgon.setStatusTip(self.tr("Remove those pesky halos around your stars"))
705
851
  self.act_halobgon.triggered.connect(self._open_halo_b_gon)
706
852
 
707
- self.act_image_combine = QAction(QIcon(imagecombine_path), "Image Combine...", self)
853
+ self.act_image_combine = QAction(QIcon(imagecombine_path), self.tr("Image Combine..."), self)
708
854
  self.act_image_combine.setIconVisibleInMenu(True)
709
- self.act_image_combine.setStatusTip("Blend two open images (replace A or create new)")
855
+ self.act_image_combine.setStatusTip(self.tr("Blend two open images (replace A or create new)"))
710
856
  self.act_image_combine.triggered.connect(self._open_image_combine)
711
857
 
712
858
  # --- Geometry ---
713
- self.act_geom_invert = QAction(QIcon(invert_path), "Invert", self)
859
+ self.act_geom_invert = QAction(QIcon(invert_path), self.tr("Invert"), self)
714
860
  self.act_geom_invert.setIconVisibleInMenu(True)
715
- self.act_geom_invert.setStatusTip("Invert image colors")
861
+ self.act_geom_invert.setStatusTip(self.tr("Invert image colors"))
716
862
  self.act_geom_invert.triggered.connect(self._exec_geom_invert)
717
863
 
718
- self.act_geom_flip_h = QAction(QIcon(fliphorizontal_path), "Flip Horizontal", self)
864
+ self.act_geom_flip_h = QAction(QIcon(fliphorizontal_path), self.tr("Flip Horizontal"), self)
719
865
  self.act_geom_flip_h.setIconVisibleInMenu(True)
720
- self.act_geom_flip_h.setStatusTip("Flip image left<->right")
866
+ self.act_geom_flip_h.setStatusTip(self.tr("Flip image left<->right"))
721
867
  self.act_geom_flip_h.triggered.connect(self._exec_geom_flip_h)
722
868
 
723
- self.act_geom_flip_v = QAction(QIcon(flipvertical_path), "Flip Vertical", self)
869
+ self.act_geom_flip_v = QAction(QIcon(flipvertical_path), self.tr("Flip Vertical"), self)
724
870
  self.act_geom_flip_v.setIconVisibleInMenu(True)
725
- self.act_geom_flip_v.setStatusTip("Flip image top<->bottom")
871
+ self.act_geom_flip_v.setStatusTip(self.tr("Flip image top<->bottom"))
726
872
  self.act_geom_flip_v.triggered.connect(self._exec_geom_flip_v)
727
873
 
728
- self.act_geom_rot_cw = QAction(QIcon(rotateclockwise_path), "Rotate 90Â deg Clockwise", self)
874
+ self.act_geom_rot_cw = QAction(QIcon(rotateclockwise_path), self.tr("Rotate 90° Clockwise"), self)
729
875
  self.act_geom_rot_cw.setIconVisibleInMenu(True)
730
- self.act_geom_rot_cw.setStatusTip("Rotate image 90Â deg clockwise")
876
+ self.act_geom_rot_cw.setStatusTip(self.tr("Rotate image 90° clockwise"))
731
877
  self.act_geom_rot_cw.triggered.connect(self._exec_geom_rot_cw)
732
878
 
733
- self.act_geom_rot_ccw = QAction(QIcon(rotatecounterclockwise_path), "Rotate 90Â deg Counterclockwise", self)
879
+ self.act_geom_rot_ccw = QAction(QIcon(rotatecounterclockwise_path), self.tr("Rotate 90° Counterclockwise"), self)
734
880
  self.act_geom_rot_ccw.setIconVisibleInMenu(True)
735
- self.act_geom_rot_ccw.setStatusTip("Rotate image 90Â deg counterclockwise")
881
+ self.act_geom_rot_ccw.setStatusTip(self.tr("Rotate image 90° counterclockwise"))
736
882
  self.act_geom_rot_ccw.triggered.connect(self._exec_geom_rot_ccw)
737
883
 
738
- self.act_geom_rot_180 = QAction(QIcon(rotate180_path), "Rotate 180Â deg", self)
884
+ self.act_geom_rot_180 = QAction(QIcon(rotate180_path), self.tr("Rotate 180°"), self)
739
885
  self.act_geom_rot_180.setIconVisibleInMenu(True)
740
- self.act_geom_rot_180.setStatusTip("Rotate image 180Â deg")
886
+ self.act_geom_rot_180.setStatusTip(self.tr("Rotate image 180°"))
741
887
  self.act_geom_rot_180.triggered.connect(self._exec_geom_rot_180)
742
888
 
743
- self.act_geom_rescale = QAction(QIcon(rescale_path), "Rescale...", self)
889
+ self.act_geom_rot_any = QAction(QIcon(rotatearbitrary_path), self.tr("Rotate..."), self)
890
+ self.act_geom_rot_any.setIconVisibleInMenu(True)
891
+ self.act_geom_rot_any.setStatusTip(self.tr("Rotate image by an arbitrary angle (degrees)"))
892
+ self.act_geom_rot_any.triggered.connect(self._exec_geom_rot_any)
893
+
894
+
895
+ self.act_geom_rescale = QAction(QIcon(rescale_path), self.tr("Rescale..."), self)
744
896
  self.act_geom_rescale.setIconVisibleInMenu(True)
745
- self.act_geom_rescale.setStatusTip("Rescale image by a factor")
897
+ self.act_geom_rescale.setStatusTip(self.tr("Rescale image by a factor"))
746
898
  self.act_geom_rescale.triggered.connect(self._exec_geom_rescale)
747
899
 
748
- self.act_debayer = QAction(QIcon(debayer_path), "Debayer...", self)
900
+ self.act_debayer = QAction(QIcon(debayer_path), self.tr("Debayer..."), self)
749
901
  self.act_debayer.setObjectName("debayer")
750
902
  self.act_debayer.setProperty("command_id", "debayer")
751
- self.act_debayer.setStatusTip("Demosaic a Bayer-mosaic mono image to RGB")
903
+ self.act_debayer.setStatusTip(self.tr("Demosaic a Bayer-mosaic mono image to RGB"))
752
904
  self.act_debayer.triggered.connect(self._open_debayer)
753
905
 
754
906
  # (Optional example shortcuts; uncomment if you want)
@@ -772,8 +924,8 @@ class ToolbarMixin:
772
924
  except Exception:
773
925
  sat_icon = QIcon()
774
926
 
775
- self.actCosmicUI = QAction(cosmic_icon, "Cosmic Clarity UI...", self)
776
- self.actCosmicSat = QAction(sat_icon, "Cosmic Clarity Satellite...", self)
927
+ self.actCosmicUI = QAction(cosmic_icon, self.tr("Cosmic Clarity UI..."), self)
928
+ self.actCosmicSat = QAction(sat_icon, self.tr("Cosmic Clarity Satellite..."), self)
777
929
 
778
930
  self.actCosmicUI.triggered.connect(self._open_cosmic_clarity_ui)
779
931
  self.actCosmicSat.triggered.connect(self._open_cosmic_clarity_satellite)
@@ -781,207 +933,207 @@ class ToolbarMixin:
781
933
 
782
934
  ab_icon = QIcon(aberration_path) # falls back if file missing
783
935
 
784
- self.actAberrationAI = QAction(ab_icon, "Aberration Correction (AI)...", self)
936
+ self.actAberrationAI = QAction(ab_icon, self.tr("Aberration Correction (AI)..."), self)
785
937
  self.actAberrationAI.triggered.connect(self._open_aberration_ai)
786
938
 
787
939
 
788
940
 
789
941
  #Tools
790
- self.act_blink = QAction(QIcon(blink_path), "Blink Comparator...", self)
791
- self.act_blink.setStatusTip("Compare a stack of images by blinking")
942
+ self.act_blink = QAction(QIcon(blink_path), self.tr("Blink Comparator..."), self)
943
+ self.act_blink.setStatusTip(self.tr("Compare a stack of images by blinking"))
792
944
  self.act_blink.triggered.connect(self._open_blink_tool)
793
945
 
794
- self.act_ppp = QAction(QIcon(ppp_path), "Perfect Palette Picker...", self)
795
- self.act_ppp.setStatusTip("Pick the perfect palette for your image")
946
+ self.act_ppp = QAction(QIcon(ppp_path), self.tr("Perfect Palette Picker..."), self)
947
+ self.act_ppp.setStatusTip(self.tr("Pick the perfect palette for your image"))
796
948
  self.act_ppp.triggered.connect(self._open_ppp_tool)
797
949
 
798
- self.act_nbtorgb = QAction(QIcon(nbtorgb_path), "NB->RGB Stars...", self)
799
- self.act_nbtorgb.setStatusTip("Combine narrowband to RGB with optional OSC stars")
950
+ self.act_nbtorgb = QAction(QIcon(nbtorgb_path), self.tr("NB->RGB Stars..."), self)
951
+ self.act_nbtorgb.setStatusTip(self.tr("Combine narrowband to RGB with optional OSC stars"))
800
952
  self.act_nbtorgb.setIconVisibleInMenu(True)
801
953
  self.act_nbtorgb.triggered.connect(self._open_nbtorgb_tool)
802
954
 
803
- self.act_selective_color = QAction(QIcon(selectivecolor_path), "Selective Color Correction...", self)
804
- self.act_selective_color.setStatusTip("Adjust specific hue ranges with CMY/RGB controls")
955
+ self.act_selective_color = QAction(QIcon(selectivecolor_path), self.tr("Selective Color Correction..."), self)
956
+ self.act_selective_color.setStatusTip(self.tr("Adjust specific hue ranges with CMY/RGB controls"))
805
957
  self.act_selective_color.triggered.connect(self._open_selective_color_tool)
806
958
 
807
959
  # NEW: Frequency Separation
808
- self.act_freqsep = QAction(QIcon(freqsep_path), "Frequency Separation...", self)
809
- self.act_freqsep.setStatusTip("Split into LF/HF and enhance HF (scale, wavelet, denoise)")
960
+ self.act_freqsep = QAction(QIcon(freqsep_path), self.tr("Frequency Separation..."), self)
961
+ self.act_freqsep.setStatusTip(self.tr("Split into LF/HF and enhance HF (scale, wavelet, denoise)"))
810
962
  self.act_freqsep.setIconVisibleInMenu(True)
811
963
  self.act_freqsep.triggered.connect(self._open_freqsep_tool)
812
964
 
813
- self.act_contsub = QAction(QIcon(contsub_path), "Continuum Subtract...", self)
814
- self.act_contsub.setStatusTip("Continuum subtract (NB - scaled broadband)")
965
+ self.act_contsub = QAction(QIcon(contsub_path), self.tr("Continuum Subtract..."), self)
966
+ self.act_contsub.setStatusTip(self.tr("Continuum Subtract (NB - scaled broadband)"))
815
967
  self.act_contsub.setIconVisibleInMenu(True)
816
968
  self.act_contsub.triggered.connect(self._open_contsub_tool)
817
969
 
818
970
  # History
819
- self.act_history_explorer = QAction("History Explorer...", self)
820
- self.act_history_explorer.setStatusTip("Inspect and restore from the slot's history")
971
+ self.act_history_explorer = QAction(self.tr("History Explorer..."), self)
972
+ self.act_history_explorer.setStatusTip(self.tr("Inspect and restore from the slot's history"))
821
973
  self.act_history_explorer.triggered.connect(self._open_history_explorer)
822
974
 
823
975
 
824
976
  #STAR STUFF
825
- self.act_image_peeker = QAction(QIcon(peeker_icon), "Image Peeker...", self)
977
+ self.act_image_peeker = QAction(QIcon(peeker_icon), self.tr("Image Peeker..."), self)
826
978
  self.act_image_peeker.setIconVisibleInMenu(True)
827
- self.act_image_peeker.setStatusTip("Image Inspector and Focal Plane Analysis")
979
+ self.act_image_peeker.setStatusTip(self.tr("Image Inspector and Focal Plane Analysis"))
828
980
  self.act_image_peeker.triggered.connect(self._open_image_peeker)
829
981
 
830
- self.act_psf_viewer = QAction(QIcon(psf_path), "PSF Viewer...", self)
982
+ self.act_psf_viewer = QAction(QIcon(psf_path), self.tr("PSF Viewer..."), self)
831
983
  self.act_psf_viewer.setIconVisibleInMenu(True)
832
- self.act_psf_viewer.setStatusTip("Inspect star PSF/HFR and flux histograms (SEP)")
984
+ self.act_psf_viewer.setStatusTip(self.tr("Inspect star PSF/HFR and flux histograms (SEP)"))
833
985
  self.act_psf_viewer.triggered.connect(self._open_psf_viewer)
834
986
 
835
- self.act_stacking_suite = QAction(QIcon(stacking_path), "Stacking Suite...", self)
987
+ self.act_stacking_suite = QAction(QIcon(stacking_path), self.tr("Stacking Suite..."), self)
836
988
  self.act_stacking_suite.setIconVisibleInMenu(True)
837
- self.act_stacking_suite.setStatusTip("Stacking! Darks, Flats, Lights, Calibration, Drizzle, and more!!")
989
+ self.act_stacking_suite.setStatusTip(self.tr("Stacking! Darks, Flats, Lights, Calibration, Drizzle, and more!!"))
838
990
  self.act_stacking_suite.triggered.connect(self._open_stacking_suite)
839
991
 
840
- self.act_live_stacking = QAction(QIcon(livestacking_path), "Live Stacking...", self)
992
+ self.act_live_stacking = QAction(QIcon(livestacking_path), self.tr("Live Stacking..."), self)
841
993
  self.act_live_stacking.setIconVisibleInMenu(True)
842
- self.act_live_stacking.setStatusTip("Live monitor and stack incoming frames")
994
+ self.act_live_stacking.setStatusTip(self.tr("Live monitor and stack incoming frames"))
843
995
  self.act_live_stacking.triggered.connect(self._open_live_stacking)
844
996
 
845
- self.act_plate_solve = QAction(QIcon(platesolve_path), "Plate Solver...", self)
997
+ self.act_plate_solve = QAction(QIcon(platesolve_path), self.tr("Plate Solver..."), self)
846
998
  self.act_plate_solve.setIconVisibleInMenu(True)
847
- self.act_plate_solve.setStatusTip("Solve WCS/SIP for the active image or a file")
999
+ self.act_plate_solve.setStatusTip(self.tr("Solve WCS/SIP for the active image or a file"))
848
1000
  self.act_plate_solve.triggered.connect(self._open_plate_solver)
849
1001
 
850
- self.act_star_align = QAction(QIcon(staralign_path), "Stellar Alignment...", self)
1002
+ self.act_star_align = QAction(QIcon(staralign_path), self.tr("Stellar Alignment..."), self)
851
1003
  self.act_star_align.setIconVisibleInMenu(True)
852
- self.act_star_align.setStatusTip("Align images via astroalign / triangles")
1004
+ self.act_star_align.setStatusTip(self.tr("Align images via astroalign / triangles"))
853
1005
  self.act_star_align.triggered.connect(self._open_stellar_alignment)
854
1006
 
855
- self.act_star_register = QAction(QIcon(starregistration_path), "Stellar Register...", self)
1007
+ self.act_star_register = QAction(QIcon(starregistration_path), self.tr("Stellar Register..."), self)
856
1008
  self.act_star_register.setIconVisibleInMenu(True)
857
- self.act_star_register.setStatusTip("Batch-align frames to a reference")
1009
+ self.act_star_register.setStatusTip(self.tr("Batch-align frames to a reference"))
858
1010
  self.act_star_register.triggered.connect(self._open_stellar_registration)
859
1011
 
860
- self.act_mosaic_master = QAction(QIcon(mosaic_path), "Mosaic Master...", self)
1012
+ self.act_mosaic_master = QAction(QIcon(mosaic_path), self.tr("Mosaic Master..."), self)
861
1013
  self.act_mosaic_master.setIconVisibleInMenu(True)
862
- self.act_mosaic_master.setStatusTip("Build mosaics from overlapping frames")
1014
+ self.act_mosaic_master.setStatusTip(self.tr("Build mosaics from overlapping frames"))
863
1015
  self.act_mosaic_master.triggered.connect(self._open_mosaic_master)
864
1016
 
865
- self.act_supernova_hunter = QAction(QIcon(supernova_path), "Supernova / Asteroid Hunter...", self)
1017
+ self.act_supernova_hunter = QAction(QIcon(supernova_path), self.tr("Supernova / Asteroid Hunter..."), self)
866
1018
  self.act_supernova_hunter.setIconVisibleInMenu(True)
867
- self.act_supernova_hunter.setStatusTip("Find transients/anomalies across frames")
1019
+ self.act_supernova_hunter.setStatusTip(self.tr("Find transients/anomalies across frames"))
868
1020
  self.act_supernova_hunter.triggered.connect(self._open_supernova_hunter)
869
1021
 
870
- self.act_star_spikes = QAction(QIcon(starspike_path), "Diffraction Spikes...", self)
1022
+ self.act_star_spikes = QAction(QIcon(starspike_path), self.tr("Diffraction Spikes..."), self)
871
1023
  self.act_star_spikes.setIconVisibleInMenu(True)
872
- self.act_star_spikes.setStatusTip("Add diffraction spikes to detected stars")
1024
+ self.act_star_spikes.setStatusTip(self.tr("Add diffraction spikes to detected stars"))
873
1025
  self.act_star_spikes.triggered.connect(self._open_star_spikes)
874
1026
 
875
- self.act_astrospike = QAction(QIcon(astrospike_path), "AstroSpike...", self)
1027
+ self.act_astrospike = QAction(QIcon(astrospike_path), self.tr("AstroSpike..."), self)
876
1028
  self.act_astrospike.setIconVisibleInMenu(True)
877
- self.act_astrospike.setStatusTip("Advanced diffraction spikes with halos, flares and rainbow effects")
1029
+ self.act_astrospike.setStatusTip(self.tr("Advanced diffraction spikes with halos, flares and rainbow effects"))
878
1030
  self.act_astrospike.triggered.connect(self._open_astrospike)
879
1031
 
880
- self.act_exo_detector = QAction(QIcon(exoicon_path), "Exoplanet Detector...", self)
1032
+ self.act_exo_detector = QAction(QIcon(exoicon_path), self.tr("Exoplanet Detector..."), self)
881
1033
  self.act_exo_detector.setIconVisibleInMenu(True)
882
- self.act_exo_detector.setStatusTip("Detect exoplanet transits from time-series subs")
1034
+ self.act_exo_detector.setStatusTip(self.tr("Detect exoplanet transits from time-series subs"))
883
1035
  self.act_exo_detector.triggered.connect(self._open_exo_detector)
884
1036
 
885
- self.act_isophote = QAction(QIcon(isophote_path), "GLIMR -- Isophote Modeler...", self)
1037
+ self.act_isophote = QAction(QIcon(isophote_path), self.tr("GLIMR -- Isophote Modeler..."), self)
886
1038
  self.act_isophote.setIconVisibleInMenu(True)
887
- self.act_isophote.setStatusTip("Fit galaxy isophotes and reveal residuals")
1039
+ self.act_isophote.setStatusTip(self.tr("Fit galaxy isophotes and reveal residuals"))
888
1040
  self.act_isophote.triggered.connect(self._open_isophote)
889
1041
 
890
- self.act_rgb_align = QAction(QIcon(rgbalign_path), "RGB Align...", self)
1042
+ self.act_rgb_align = QAction(QIcon(rgbalign_path), self.tr("RGB Align..."), self)
891
1043
  self.act_rgb_align.setIconVisibleInMenu(True)
892
- self.act_rgb_align.setStatusTip("Align R and B channels to G using astroalign (affine/homography/poly)")
1044
+ self.act_rgb_align.setStatusTip(self.tr("Align R and B channels to G using astroalign (affine/homography/poly)"))
893
1045
  self.act_rgb_align.triggered.connect(self._open_rgb_align)
894
1046
 
895
- self.act_whats_in_my_sky = QAction(QIcon(wims_path), "What's In My Sky...", self)
1047
+ self.act_whats_in_my_sky = QAction(QIcon(wims_path), self.tr("What's In My Sky..."), self)
896
1048
  self.act_whats_in_my_sky.setIconVisibleInMenu(True)
897
- self.act_whats_in_my_sky.setStatusTip("Plan targets by altitude, transit time, and lunar separation")
1049
+ self.act_whats_in_my_sky.setStatusTip(self.tr("Plan targets by altitude, transit time, and lunar separation"))
898
1050
  self.act_whats_in_my_sky.triggered.connect(self._open_whats_in_my_sky)
899
1051
 
900
- self.act_wimi = QAction(QIcon(wimi_path), "What's In My Image...", self)
1052
+ self.act_wimi = QAction(QIcon(wimi_path), self.tr("What's In My Image..."), self)
901
1053
  self.act_wimi.setIconVisibleInMenu(True)
902
- self.act_wimi.setStatusTip("Identify objects in a plate-solved frame")
1054
+ self.act_wimi.setStatusTip(self.tr("Identify objects in a plate-solved frame"))
903
1055
  self.act_wimi.triggered.connect(self._open_wimi)
904
1056
 
905
1057
  # --- Scripts actions ---
906
- self.act_open_scripts_folder = QAction("Open Scripts Folder...", self)
907
- self.act_open_scripts_folder.setStatusTip("Open the SASpro user scripts folder")
1058
+ self.act_open_scripts_folder = QAction(self.tr("Open Scripts Folder..."), self)
1059
+ self.act_open_scripts_folder.setStatusTip(self.tr("Open the SASpro user scripts folder"))
908
1060
  self.act_open_scripts_folder.triggered.connect(self._open_scripts_folder)
909
1061
 
910
- self.act_reload_scripts = QAction("Reload Scripts", self)
911
- self.act_reload_scripts.setStatusTip("Rescan the scripts folder and reload .py files")
1062
+ self.act_reload_scripts = QAction(self.tr("Reload Scripts"), self)
1063
+ self.act_reload_scripts.setStatusTip(self.tr("Rescan the scripts folder and reload .py files"))
912
1064
  self.act_reload_scripts.triggered.connect(self._reload_scripts)
913
1065
 
914
- self.act_create_sample_script = QAction("Create Sample Scripts...", self)
915
- self.act_create_sample_script.setStatusTip("Write a ready-to-edit sample script into the scripts folder")
1066
+ self.act_create_sample_script = QAction(self.tr("Create Sample Scripts..."), self)
1067
+ self.act_create_sample_script.setStatusTip(self.tr("Write a ready-to-edit sample script into the scripts folder"))
916
1068
  self.act_create_sample_script.triggered.connect(self._create_sample_script)
917
1069
 
918
- self.act_script_editor = QAction("Script Editor...", self)
919
- self.act_script_editor.setStatusTip("Open the built-in script editor")
1070
+ self.act_script_editor = QAction(self.tr("Script Editor..."), self)
1071
+ self.act_script_editor.setStatusTip(self.tr("Open the built-in script editor"))
920
1072
  self.act_script_editor.triggered.connect(self._show_script_editor)
921
1073
 
922
- self.act_open_user_scripts_github = QAction("Open User Scripts (GitHub)...", self)
1074
+ self.act_open_user_scripts_github = QAction(self.tr("Open User Scripts (GitHub)..."), self)
923
1075
  self.act_open_user_scripts_github.triggered.connect(self._open_user_scripts_github)
924
1076
 
925
- self.act_open_scripts_discord = QAction("Open Scripts Forum (Discord)...", self)
1077
+ self.act_open_scripts_discord = QAction(self.tr("Open Scripts Forum (Discord)..."), self)
926
1078
  self.act_open_scripts_discord.triggered.connect(self._open_scripts_discord_forum)
927
1079
 
928
1080
  # --- FITS Header Modifier action ---
929
- self.act_fits_modifier = QAction("FITS Header Modifier...", self)
1081
+ self.act_fits_modifier = QAction(self.tr("FITS Header Modifier..."), self)
930
1082
  # self.act_fits_modifier.setIcon(QIcon(path_to_icon)) # (optional) icon goes here later
931
1083
  self.act_fits_modifier.setIconVisibleInMenu(True)
932
- self.act_fits_modifier.setStatusTip("View/Edit FITS headers")
1084
+ self.act_fits_modifier.setStatusTip(self.tr("View/Edit FITS headers"))
933
1085
  self.act_fits_modifier.triggered.connect(self._open_fits_modifier)
934
1086
 
935
- self.act_fits_batch_modifier = QAction("FITS Header Batch Modifier...", self)
1087
+ self.act_fits_batch_modifier = QAction(self.tr("FITS Header Batch Modifier..."), self)
936
1088
  # self.act_fits_modifier.setIcon(QIcon(path_to_icon)) # (optional) icon goes here later
937
1089
  self.act_fits_batch_modifier.setIconVisibleInMenu(True)
938
- self.act_fits_batch_modifier.setStatusTip("Batch Modify FITS Headers")
1090
+ self.act_fits_batch_modifier.setStatusTip(self.tr("Batch Modify FITS Headers"))
939
1091
  self.act_fits_batch_modifier.triggered.connect(self._open_fits_batch_modifier)
940
1092
 
941
- self.act_batch_renamer = QAction("Batch Rename from FITS...", self)
1093
+ self.act_batch_renamer = QAction(self.tr("Batch Rename from FITS..."), self)
942
1094
  # self.act_batch_renamer.setIcon(QIcon(batch_renamer_icon_path)) # (optional icon)
943
1095
  self.act_batch_renamer.triggered.connect(self._open_batch_renamer)
944
1096
 
945
- self.act_astrobin_exporter = QAction("AstroBin Exporter...", self)
1097
+ self.act_astrobin_exporter = QAction(self.tr("AstroBin Exporter..."), self)
946
1098
  # self.act_astrobin_exporter.setIcon(QIcon(astrobin_icon_path)) # optional icon
947
1099
  self.act_astrobin_exporter.triggered.connect(self._open_astrobin_exporter)
948
1100
 
949
- self.act_batch_convert = QAction("Batch Converter...", self)
1101
+ self.act_batch_convert = QAction(self.tr("Batch Converter..."), self)
950
1102
  # self.act_batch_convert.setIcon(QIcon("path/to/icon.svg")) # optional later
951
1103
  self.act_batch_convert.triggered.connect(self._open_batch_convert)
952
1104
 
953
- self.act_copy_astrometry = QAction("Copy Astrometric Solution...", self)
1105
+ self.act_copy_astrometry = QAction(self.tr("Copy Astrometric Solution..."), self)
954
1106
  self.act_copy_astrometry.triggered.connect(self._open_copy_astrometry)
955
1107
 
956
1108
  # Create Mask
957
- self.act_create_mask = QAction(QIcon(maskcreate_path), "Create Mask...", self)
1109
+ self.act_create_mask = QAction(QIcon(maskcreate_path), self.tr("Create Mask..."), self)
958
1110
  self.act_create_mask.setIconVisibleInMenu(True)
959
- self.act_create_mask.setStatusTip("Create a mask from the active image")
1111
+ self.act_create_mask.setStatusTip(self.tr("Create a mask from the active image"))
960
1112
  self.act_create_mask.triggered.connect(self._action_create_mask)
961
1113
 
962
1114
  # --- Masks ---
963
- self.act_apply_mask = QAction(QIcon(maskapply_path), "Apply Mask", self)
964
- self.act_apply_mask.setStatusTip("Apply a mask document to the active image")
1115
+ self.act_apply_mask = QAction(QIcon(maskapply_path), self.tr("Apply Mask"), self)
1116
+ self.act_apply_mask.setStatusTip(self.tr("Apply a mask document to the active image"))
965
1117
  self.act_apply_mask.triggered.connect(self._apply_mask_menu)
966
1118
 
967
- self.act_remove_mask = QAction(QIcon(maskremove_path), "Remove Active Mask", self)
968
- self.act_remove_mask.setStatusTip("Remove the active mask from the active image")
1119
+ self.act_remove_mask = QAction(QIcon(maskremove_path), self.tr("Remove Active Mask"), self)
1120
+ self.act_remove_mask.setStatusTip(self.tr("Remove the active mask from the active image"))
969
1121
  self.act_remove_mask.triggered.connect(self._remove_mask_menu)
970
1122
 
971
- self.act_show_mask = QAction("Show Mask Overlay", self)
972
- self.act_hide_mask = QAction("Hide Mask Overlay", self)
1123
+ self.act_show_mask = QAction(self.tr("Show Mask Overlay"), self)
1124
+ self.act_hide_mask = QAction(self.tr("Hide Mask Overlay"), self)
973
1125
  self.act_show_mask.triggered.connect(self._show_mask_overlay)
974
1126
  self.act_hide_mask.triggered.connect(self._hide_mask_overlay)
975
1127
 
976
- self.act_invert_mask = QAction("Invert Mask", self)
1128
+ self.act_invert_mask = QAction(self.tr("Invert Mask"), self)
977
1129
  self.act_invert_mask.triggered.connect(self._invert_mask)
978
1130
  self.act_invert_mask.setShortcut("Ctrl+Shift+I")
979
1131
 
980
- self.act_check_updates = QAction("Check for Updates...", self)
1132
+ self.act_check_updates = QAction(self.tr("Check for Updates..."), self)
981
1133
  self.act_check_updates.triggered.connect(self.check_for_updates_now)
982
1134
 
983
- self.act_docs = QAction("Documentation...", self)
984
- self.act_docs.setStatusTip("Open the Seti Astro Suite Pro online documentation")
1135
+ self.act_docs = QAction(self.tr("Documentation..."), self)
1136
+ self.act_docs.setStatusTip(self.tr("Open the Seti Astro Suite Pro online documentation"))
985
1137
  self.act_docs.triggered.connect(
986
1138
  lambda: QDesktopServices.openUrl(QUrl("https://github.com/setiastro/setiastrosuitepro/wiki"))
987
1139
  )
@@ -994,12 +1146,12 @@ class ToolbarMixin:
994
1146
  # Fallback works everywhere
995
1147
  self.act_docs.setShortcut(QKeySequence("F1"))
996
1148
 
997
- self.act_view_bundles = QAction(QIcon(viewbundles_path), "View Bundles...", self)
998
- self.act_view_bundles.setStatusTip("Create bundles of views; drop shortcuts to apply to all")
1149
+ self.act_view_bundles = QAction(QIcon(viewbundles_path), self.tr("View Bundles..."), self)
1150
+ self.act_view_bundles.setStatusTip(self.tr("Create bundles of views; drop shortcuts to apply to all"))
999
1151
  self.act_view_bundles.triggered.connect(self._open_view_bundles)
1000
1152
 
1001
- self.act_function_bundles = QAction(QIcon(functionbundles_path), "Function Bundles...", self)
1002
- self.act_function_bundles.setStatusTip("Create and run bundles of functions/shortcuts")
1153
+ self.act_function_bundles = QAction(QIcon(functionbundles_path), self.tr("Function Bundles..."), self)
1154
+ self.act_function_bundles.setStatusTip(self.tr("Create and run bundles of functions/shortcuts"))
1003
1155
  self.act_function_bundles.triggered.connect(self._open_function_bundles)
1004
1156
 
1005
1157
  # give each action a stable id and register
@@ -1063,6 +1215,7 @@ class ToolbarMixin:
1063
1215
  reg("geom_rotate_clockwise", self.act_geom_rot_cw)
1064
1216
  reg("geom_rotate_counterclockwise",self.act_geom_rot_ccw)
1065
1217
  reg("geom_rotate_180", self.act_geom_rot_180)
1218
+ reg("geom_rotate_any", self.act_geom_rot_any)
1066
1219
  reg("geom_rescale", self.act_geom_rescale)
1067
1220
  reg("project_new", self.act_project_new)
1068
1221
  reg("project_save", self.act_project_save)
@@ -1254,6 +1407,7 @@ class ToolbarMixin:
1254
1407
  a.setStatusTip(tip)
1255
1408
  a.setEnabled(False)
1256
1409
 
1410
+
1257
1411
  def _sync_link_action_state(self):
1258
1412
  g = self._current_group_of_active()
1259
1413
  self.act_link_group.blockSignals(True)
@@ -1299,6 +1453,7 @@ class ToolbarMixin:
1299
1453
  QTimer.singleShot(0, self.update_undo_redo_action_labels)
1300
1454
 
1301
1455
  def _refresh_mask_action_states(self):
1456
+
1302
1457
  active_doc = self._active_doc()
1303
1458
 
1304
1459
  can_apply = bool(active_doc and self._list_candidate_mask_sources(exclude_doc=active_doc))
@@ -1322,3 +1477,4 @@ class ToolbarMixin:
1322
1477
  if hasattr(self, "act_hide_mask"):
1323
1478
  self.act_hide_mask.setEnabled(has_mask and overlay_on)
1324
1479
 
1480
+