setiastrosuitepro 1.6.0__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 (174) hide show
  1. setiastro/__init__.py +2 -0
  2. setiastro/saspro/__init__.py +20 -0
  3. setiastro/saspro/__main__.py +784 -0
  4. setiastro/saspro/_generated/__init__.py +7 -0
  5. setiastro/saspro/_generated/build_info.py +2 -0
  6. setiastro/saspro/abe.py +1295 -0
  7. setiastro/saspro/abe_preset.py +196 -0
  8. setiastro/saspro/aberration_ai.py +694 -0
  9. setiastro/saspro/aberration_ai_preset.py +224 -0
  10. setiastro/saspro/accel_installer.py +218 -0
  11. setiastro/saspro/accel_workers.py +30 -0
  12. setiastro/saspro/add_stars.py +621 -0
  13. setiastro/saspro/astrobin_exporter.py +1007 -0
  14. setiastro/saspro/astrospike.py +153 -0
  15. setiastro/saspro/astrospike_python.py +1839 -0
  16. setiastro/saspro/autostretch.py +196 -0
  17. setiastro/saspro/backgroundneutral.py +560 -0
  18. setiastro/saspro/batch_convert.py +325 -0
  19. setiastro/saspro/batch_renamer.py +519 -0
  20. setiastro/saspro/blemish_blaster.py +488 -0
  21. setiastro/saspro/blink_comparator_pro.py +2923 -0
  22. setiastro/saspro/bundles.py +61 -0
  23. setiastro/saspro/bundles_dock.py +114 -0
  24. setiastro/saspro/cheat_sheet.py +168 -0
  25. setiastro/saspro/clahe.py +342 -0
  26. setiastro/saspro/comet_stacking.py +1377 -0
  27. setiastro/saspro/config.py +38 -0
  28. setiastro/saspro/config_bootstrap.py +40 -0
  29. setiastro/saspro/config_manager.py +316 -0
  30. setiastro/saspro/continuum_subtract.py +1617 -0
  31. setiastro/saspro/convo.py +1397 -0
  32. setiastro/saspro/convo_preset.py +414 -0
  33. setiastro/saspro/copyastro.py +187 -0
  34. setiastro/saspro/cosmicclarity.py +1564 -0
  35. setiastro/saspro/cosmicclarity_preset.py +407 -0
  36. setiastro/saspro/crop_dialog_pro.py +948 -0
  37. setiastro/saspro/crop_preset.py +189 -0
  38. setiastro/saspro/curve_editor_pro.py +2544 -0
  39. setiastro/saspro/curves_preset.py +375 -0
  40. setiastro/saspro/debayer.py +670 -0
  41. setiastro/saspro/debug_utils.py +29 -0
  42. setiastro/saspro/dnd_mime.py +35 -0
  43. setiastro/saspro/doc_manager.py +2634 -0
  44. setiastro/saspro/exoplanet_detector.py +2166 -0
  45. setiastro/saspro/file_utils.py +284 -0
  46. setiastro/saspro/fitsmodifier.py +744 -0
  47. setiastro/saspro/free_torch_memory.py +48 -0
  48. setiastro/saspro/frequency_separation.py +1343 -0
  49. setiastro/saspro/function_bundle.py +1594 -0
  50. setiastro/saspro/ghs_dialog_pro.py +660 -0
  51. setiastro/saspro/ghs_preset.py +284 -0
  52. setiastro/saspro/graxpert.py +634 -0
  53. setiastro/saspro/graxpert_preset.py +287 -0
  54. setiastro/saspro/gui/__init__.py +0 -0
  55. setiastro/saspro/gui/main_window.py +8494 -0
  56. setiastro/saspro/gui/mixins/__init__.py +33 -0
  57. setiastro/saspro/gui/mixins/dock_mixin.py +263 -0
  58. setiastro/saspro/gui/mixins/file_mixin.py +445 -0
  59. setiastro/saspro/gui/mixins/geometry_mixin.py +403 -0
  60. setiastro/saspro/gui/mixins/header_mixin.py +441 -0
  61. setiastro/saspro/gui/mixins/mask_mixin.py +421 -0
  62. setiastro/saspro/gui/mixins/menu_mixin.py +361 -0
  63. setiastro/saspro/gui/mixins/theme_mixin.py +367 -0
  64. setiastro/saspro/gui/mixins/toolbar_mixin.py +1324 -0
  65. setiastro/saspro/gui/mixins/update_mixin.py +309 -0
  66. setiastro/saspro/gui/mixins/view_mixin.py +435 -0
  67. setiastro/saspro/halobgon.py +462 -0
  68. setiastro/saspro/header_viewer.py +445 -0
  69. setiastro/saspro/headless_utils.py +88 -0
  70. setiastro/saspro/histogram.py +753 -0
  71. setiastro/saspro/history_explorer.py +939 -0
  72. setiastro/saspro/image_combine.py +414 -0
  73. setiastro/saspro/image_peeker_pro.py +1596 -0
  74. setiastro/saspro/imageops/__init__.py +37 -0
  75. setiastro/saspro/imageops/mdi_snap.py +292 -0
  76. setiastro/saspro/imageops/scnr.py +36 -0
  77. setiastro/saspro/imageops/starbasedwhitebalance.py +210 -0
  78. setiastro/saspro/imageops/stretch.py +244 -0
  79. setiastro/saspro/isophote.py +1179 -0
  80. setiastro/saspro/layers.py +208 -0
  81. setiastro/saspro/layers_dock.py +714 -0
  82. setiastro/saspro/lazy_imports.py +193 -0
  83. setiastro/saspro/legacy/__init__.py +2 -0
  84. setiastro/saspro/legacy/image_manager.py +2226 -0
  85. setiastro/saspro/legacy/numba_utils.py +3659 -0
  86. setiastro/saspro/legacy/xisf.py +1071 -0
  87. setiastro/saspro/linear_fit.py +534 -0
  88. setiastro/saspro/live_stacking.py +1830 -0
  89. setiastro/saspro/log_bus.py +5 -0
  90. setiastro/saspro/logging_config.py +460 -0
  91. setiastro/saspro/luminancerecombine.py +309 -0
  92. setiastro/saspro/main_helpers.py +201 -0
  93. setiastro/saspro/mask_creation.py +928 -0
  94. setiastro/saspro/masks_core.py +56 -0
  95. setiastro/saspro/mdi_widgets.py +353 -0
  96. setiastro/saspro/memory_utils.py +666 -0
  97. setiastro/saspro/metadata_patcher.py +75 -0
  98. setiastro/saspro/mfdeconv.py +3826 -0
  99. setiastro/saspro/mfdeconv_earlystop.py +71 -0
  100. setiastro/saspro/mfdeconvcudnn.py +3263 -0
  101. setiastro/saspro/mfdeconvsport.py +2382 -0
  102. setiastro/saspro/minorbodycatalog.py +567 -0
  103. setiastro/saspro/morphology.py +382 -0
  104. setiastro/saspro/multiscale_decomp.py +1290 -0
  105. setiastro/saspro/nbtorgb_stars.py +531 -0
  106. setiastro/saspro/numba_utils.py +3044 -0
  107. setiastro/saspro/numba_warmup.py +141 -0
  108. setiastro/saspro/ops/__init__.py +9 -0
  109. setiastro/saspro/ops/command_help_dialog.py +623 -0
  110. setiastro/saspro/ops/command_runner.py +217 -0
  111. setiastro/saspro/ops/commands.py +1594 -0
  112. setiastro/saspro/ops/script_editor.py +1102 -0
  113. setiastro/saspro/ops/scripts.py +1413 -0
  114. setiastro/saspro/ops/settings.py +560 -0
  115. setiastro/saspro/parallel_utils.py +554 -0
  116. setiastro/saspro/pedestal.py +121 -0
  117. setiastro/saspro/perfect_palette_picker.py +1053 -0
  118. setiastro/saspro/pipeline.py +110 -0
  119. setiastro/saspro/pixelmath.py +1600 -0
  120. setiastro/saspro/plate_solver.py +2435 -0
  121. setiastro/saspro/project_io.py +797 -0
  122. setiastro/saspro/psf_utils.py +136 -0
  123. setiastro/saspro/psf_viewer.py +549 -0
  124. setiastro/saspro/pyi_rthook_astroquery.py +95 -0
  125. setiastro/saspro/remove_green.py +314 -0
  126. setiastro/saspro/remove_stars.py +1625 -0
  127. setiastro/saspro/remove_stars_preset.py +404 -0
  128. setiastro/saspro/resources.py +472 -0
  129. setiastro/saspro/rgb_combination.py +207 -0
  130. setiastro/saspro/rgb_extract.py +19 -0
  131. setiastro/saspro/rgbalign.py +723 -0
  132. setiastro/saspro/runtime_imports.py +7 -0
  133. setiastro/saspro/runtime_torch.py +754 -0
  134. setiastro/saspro/save_options.py +72 -0
  135. setiastro/saspro/selective_color.py +1552 -0
  136. setiastro/saspro/sfcc.py +1425 -0
  137. setiastro/saspro/shortcuts.py +2807 -0
  138. setiastro/saspro/signature_insert.py +1099 -0
  139. setiastro/saspro/stacking_suite.py +17712 -0
  140. setiastro/saspro/star_alignment.py +7420 -0
  141. setiastro/saspro/star_alignment_preset.py +329 -0
  142. setiastro/saspro/star_metrics.py +49 -0
  143. setiastro/saspro/star_spikes.py +681 -0
  144. setiastro/saspro/star_stretch.py +470 -0
  145. setiastro/saspro/stat_stretch.py +502 -0
  146. setiastro/saspro/status_log_dock.py +78 -0
  147. setiastro/saspro/subwindow.py +3267 -0
  148. setiastro/saspro/supernovaasteroidhunter.py +1712 -0
  149. setiastro/saspro/swap_manager.py +99 -0
  150. setiastro/saspro/torch_backend.py +89 -0
  151. setiastro/saspro/torch_rejection.py +434 -0
  152. setiastro/saspro/view_bundle.py +1555 -0
  153. setiastro/saspro/wavescale_hdr.py +624 -0
  154. setiastro/saspro/wavescale_hdr_preset.py +100 -0
  155. setiastro/saspro/wavescalede.py +657 -0
  156. setiastro/saspro/wavescalede_preset.py +228 -0
  157. setiastro/saspro/wcs_update.py +374 -0
  158. setiastro/saspro/whitebalance.py +456 -0
  159. setiastro/saspro/widgets/__init__.py +48 -0
  160. setiastro/saspro/widgets/common_utilities.py +305 -0
  161. setiastro/saspro/widgets/graphics_views.py +122 -0
  162. setiastro/saspro/widgets/image_utils.py +518 -0
  163. setiastro/saspro/widgets/preview_dialogs.py +280 -0
  164. setiastro/saspro/widgets/spinboxes.py +275 -0
  165. setiastro/saspro/widgets/themed_buttons.py +13 -0
  166. setiastro/saspro/widgets/wavelet_utils.py +299 -0
  167. setiastro/saspro/window_shelf.py +185 -0
  168. setiastro/saspro/xisf.py +1123 -0
  169. setiastrosuitepro-1.6.0.dist-info/METADATA +266 -0
  170. setiastrosuitepro-1.6.0.dist-info/RECORD +174 -0
  171. setiastrosuitepro-1.6.0.dist-info/WHEEL +4 -0
  172. setiastrosuitepro-1.6.0.dist-info/entry_points.txt +6 -0
  173. setiastrosuitepro-1.6.0.dist-info/licenses/LICENSE +674 -0
  174. setiastrosuitepro-1.6.0.dist-info/licenses/license.txt +2580 -0
@@ -0,0 +1,361 @@
1
+ # pro/gui/mixins/menu_mixin.py
2
+ """
3
+ Menu management mixin for AstroSuiteProMainWindow.
4
+ """
5
+ from __future__ import annotations
6
+ import os
7
+ from typing import TYPE_CHECKING
8
+
9
+ from PyQt6.QtGui import QAction
10
+ from PyQt6.QtWidgets import QMenu, QToolButton, QWidgetAction
11
+
12
+ if TYPE_CHECKING:
13
+ pass
14
+
15
+
16
+ class MenuMixin:
17
+ """
18
+ Mixin for menu management.
19
+
20
+ Provides methods for creating and managing menus in the main window.
21
+ """
22
+
23
+ def _init_menubar(self):
24
+ """Initialize the main menu bar."""
25
+ # This method will be implemented as part of the main window
26
+ # For now, this is a placeholder showing the mixin pattern
27
+ pass
28
+
29
+ def _rebuild_recent_menus(self):
30
+ """Rebuild the recent files and projects menus."""
31
+ if not hasattr(self, "_recent_image_paths") or not hasattr(self, "_recent_project_paths"):
32
+ return
33
+
34
+ # Recent images
35
+ if hasattr(self, "_recent_images_menu"):
36
+ self._recent_images_menu.clear()
37
+ for path in self._recent_image_paths[:10]: # Show top 10
38
+ action = self._recent_images_menu.addAction(path)
39
+ action.triggered.connect(lambda checked=False, p=path: self._open_recent_image(p))
40
+
41
+ # Recent projects
42
+ if hasattr(self, "_recent_projects_menu"):
43
+ self._recent_projects_menu.clear()
44
+ for path in self._recent_project_paths[:10]:
45
+ action = self._recent_projects_menu.addAction(path)
46
+ action.triggered.connect(lambda checked=False, p=path: self._open_recent_project(p))
47
+
48
+ def _strip_menu_text(self, text: str) -> str:
49
+ """Remove decorations and mnemonics from menu text."""
50
+ # Remove ampersands (mnemonics)
51
+ text = text.replace("&", "")
52
+ # Could add more stripping logic here
53
+ return text
54
+
55
+ def _iter_menu_actions(self, menu: QMenu):
56
+ """
57
+ Iterate over all actions in a menu recursively.
58
+
59
+ Args:
60
+ menu: The menu to iterate
61
+
62
+ Yields:
63
+ QAction objects
64
+ """
65
+ for action in menu.actions():
66
+ if action.menu():
67
+ yield from self._iter_menu_actions(action.menu())
68
+ else:
69
+ yield action
70
+ # Extracted MENU methods
71
+
72
+ def _init_menubar(self):
73
+ mb = self.menuBar()
74
+
75
+ # File
76
+ m_file = mb.addMenu("&File")
77
+ m_file.addAction(self.act_open)
78
+ m_file.addSeparator()
79
+ m_file.addAction(self.act_save)
80
+ m_file.addSeparator()
81
+ m_file.addAction(self.act_clear_views)
82
+ m_file.addSeparator()
83
+ m_file.addAction(self.act_project_new)
84
+ m_file.addAction(self.act_project_save)
85
+ m_file.addAction(self.act_project_load)
86
+ # --- Recent submenus ----------------------------------------
87
+ m_file.addSeparator()
88
+ self.m_recent_images_menu = m_file.addMenu("Open Recent Images")
89
+ self.m_recent_projects_menu = m_file.addMenu("Open Recent Projects")
90
+
91
+ m_file.addSeparator()
92
+ m_file.addAction(self.act_exit)
93
+
94
+ # Populate from QSettings
95
+ self._rebuild_recent_menus()
96
+
97
+ # Edit (with icons)
98
+ m_edit = mb.addMenu("&Edit")
99
+ m_edit.addAction(self.act_undo)
100
+ m_edit.addAction(self.act_redo)
101
+
102
+ # Functions
103
+ m_fn = mb.addMenu("&Functions")
104
+
105
+ m_fn.addAction(self.act_abe)
106
+ m_fn.addAction(self.act_add_stars)
107
+ m_fn.addAction(self.act_background_neutral)
108
+ m_fn.addAction(self.act_blemish)
109
+ m_fn.addAction(self.act_clahe)
110
+ m_fn.addAction(self.act_convo)
111
+ m_fn.addAction(self.act_crop)
112
+ m_fn.addAction(self.act_curves)
113
+ m_fn.addAction(self.act_extract_luma)
114
+ m_fn.addAction(self.act_graxpert)
115
+ m_fn.addAction(self.act_ghs)
116
+ m_fn.addAction(self.act_halobgon)
117
+ m_fn.addAction(self.act_histogram)
118
+ m_fn.addAction(self.act_linear_fit)
119
+ m_fn.addAction(self.act_morphology)
120
+ m_fn.addAction(self.act_pedestal)
121
+ m_fn.addAction(self.act_pixelmath)
122
+ m_fn.addAction(self.act_recombine_luma)
123
+ m_fn.addAction(self.act_remove_green)
124
+ m_fn.addAction(self.act_remove_stars)
125
+ m_fn.addAction(self.act_rgb_combine)
126
+ m_fn.addAction(self.act_rgb_extract)
127
+ m_fn.addAction(self.act_sfcc)
128
+ m_fn.addAction(self.act_signature)
129
+ m_fn.addAction(self.act_star_stretch)
130
+ m_fn.addAction(self.act_stat_stretch)
131
+ m_fn.addAction(self.act_wavescale_de)
132
+ m_fn.addAction(self.act_wavescale_hdr)
133
+ m_fn.addAction(self.act_white_balance)
134
+
135
+
136
+ mCosmic = mb.addMenu("&Smart Tools")
137
+ mCosmic.addAction(self.actAberrationAI)
138
+ mCosmic.addAction(self.actCosmicUI)
139
+ mCosmic.addAction(self.actCosmicSat)
140
+ mCosmic.addAction(self.act_graxpert)
141
+ mCosmic.addAction(self.act_remove_stars)
142
+
143
+ m_tools = mb.addMenu("&Tools")
144
+
145
+ m_tools.addAction(self.act_blink)
146
+ m_tools.addAction(self.act_contsub)
147
+ m_tools.addAction(self.act_freqsep)
148
+ m_tools.addAction(self.act_image_combine)
149
+ m_tools.addAction(self.act_multiscale_decomp)
150
+ m_tools.addAction(self.act_nbtorgb)
151
+ m_tools.addAction(self.act_ppp)
152
+ m_tools.addAction(self.act_selective_color)
153
+ m_tools.addSeparator()
154
+ m_tools.addAction(self.act_view_bundles)
155
+ m_tools.addAction(self.act_function_bundles)
156
+
157
+ m_geom = mb.addMenu("&Geometry")
158
+ m_geom.addAction(self.act_geom_invert)
159
+ m_geom.addSeparator()
160
+ m_geom.addAction(self.act_geom_flip_h)
161
+ m_geom.addAction(self.act_geom_flip_v)
162
+ m_geom.addSeparator()
163
+ m_geom.addAction(self.act_geom_rot_cw)
164
+ m_geom.addAction(self.act_geom_rot_ccw)
165
+ m_geom.addAction(self.act_geom_rot_180)
166
+ m_geom.addSeparator()
167
+ m_geom.addAction(self.act_geom_rescale)
168
+ m_geom.addSeparator()
169
+ m_geom.addAction(self.act_debayer)
170
+
171
+ m_star = mb.addMenu("&Star Stuff")
172
+
173
+ m_star.addAction(self.act_astrospike)
174
+ m_star.addAction(self.act_exo_detector)
175
+ m_star.addAction(self.act_image_peeker)
176
+ m_star.addAction(self.act_isophote)
177
+ m_star.addAction(self.act_live_stacking)
178
+ m_star.addAction(self.act_mosaic_master)
179
+ m_star.addAction(self.act_plate_solve)
180
+ m_star.addAction(self.act_psf_viewer)
181
+ m_star.addAction(self.act_rgb_align)
182
+ m_star.addAction(self.act_star_align)
183
+ m_star.addAction(self.act_star_register)
184
+ m_star.addAction(self.act_star_spikes)
185
+ m_star.addAction(self.act_stacking_suite)
186
+ m_star.addAction(self.act_supernova_hunter)
187
+
188
+ m_masks = mb.addMenu("&Masks")
189
+ m_masks.addAction(self.act_create_mask)
190
+ m_masks.addSeparator()
191
+ m_masks.addAction(self.act_apply_mask)
192
+ m_masks.addAction(self.act_remove_mask)
193
+ m_masks.addSeparator()
194
+ m_masks.addAction(self.act_show_mask)
195
+ m_masks.addAction(self.act_hide_mask)
196
+ m_masks.addSeparator()
197
+ m_masks.addAction(self.act_invert_mask)
198
+
199
+ m_wim = mb.addMenu("&What's In My...")
200
+ m_wim.addAction(self.act_whats_in_my_sky)
201
+ m_wim.addAction(self.act_wimi)
202
+
203
+ m_scripts = mb.addMenu("&Scripts")
204
+ self.menu_scripts = m_scripts
205
+ self.scriptman.rebuild_menu(m_scripts)
206
+
207
+
208
+ m_header = mb.addMenu("&Header Mods && Misc")
209
+ m_header.addAction(self.act_astrobin_exporter)
210
+ m_header.addAction(self.act_batch_convert)
211
+ m_header.addAction(self.act_batch_renamer)
212
+ m_header.addAction(self.act_copy_astrometry)
213
+ m_header.addAction(self.act_fits_batch_modifier)
214
+ m_header.addAction(self.act_fits_modifier)
215
+
216
+
217
+ m_hist = mb.addMenu("&History")
218
+ m_hist.addAction(self.act_history_explorer)
219
+
220
+ m_short = mb.addMenu("&Shortcuts")
221
+
222
+ act_cheats = QAction("Keyboard Shortcut Cheat Sheet...", self)
223
+ act_cheats.triggered.connect(self._show_cheat_sheet)
224
+ m_short.addAction(act_cheats)
225
+
226
+ # act_save_sc = QAction("Save Shortcuts Now", self, triggered=self.shortcuts.save_shortcuts)
227
+ # Keep it if you like, but add explicit export/import:
228
+ act_export_sc = QAction("Export Shortcuts...", self, triggered=self._export_shortcuts_dialog)
229
+ act_import_sc = QAction("Import Shortcuts...", self, triggered=self._import_shortcuts_dialog)
230
+ act_clear_sc = QAction("Clear All Shortcuts", self, triggered=self.shortcuts.clear)
231
+
232
+ m_short.addAction(act_export_sc)
233
+ m_short.addAction(act_import_sc)
234
+ m_short.addSeparator()
235
+ # m_short.addAction(act_save_sc) # optional: keep
236
+ m_short.addAction(act_clear_sc)
237
+
238
+ m_view = mb.addMenu("&View")
239
+ m_view.addAction(self.act_cascade)
240
+ m_view.addAction(self.act_tile)
241
+ m_view.addAction(self.act_tile_vert)
242
+ m_view.addAction(self.act_tile_horiz)
243
+ m_view.addAction(self.act_tile_grid)
244
+ m_view.addSeparator()
245
+
246
+
247
+ # a button that shows current group & opens a drop-down
248
+ self._link_btn = QToolButton(self)
249
+ self._link_btn.setDefaultAction(self.act_link_group) # text/checked state mirrors the action
250
+ self._link_btn.setPopupMode(QToolButton.ToolButtonPopupMode.MenuButtonPopup)
251
+
252
+ link_menu = QMenu(self._link_btn)
253
+ a_none = link_menu.addAction("None")
254
+ a_A = link_menu.addAction("Group A")
255
+ a_B = link_menu.addAction("Group B")
256
+ a_C = link_menu.addAction("Group C")
257
+ a_D = link_menu.addAction("Group D")
258
+ self._link_btn.setMenu(link_menu)
259
+
260
+ a_none.setCheckable(True)
261
+ a_A.setCheckable(True)
262
+ a_B.setCheckable(True)
263
+ a_C.setCheckable(True)
264
+ a_D.setCheckable(True)
265
+
266
+ def _sync_menu_checks():
267
+ g = self._current_group_of_active()
268
+ a_none.setChecked(g is None)
269
+ a_A.setChecked(g == "A")
270
+ a_B.setChecked(g == "B")
271
+ a_C.setChecked(g == "C")
272
+ a_D.setChecked(g == "D")
273
+
274
+ link_menu.aboutToShow.connect(_sync_menu_checks)
275
+
276
+ # hook the menu choices to your helpers
277
+ a_none.triggered.connect(lambda: self._set_group_for_active(None))
278
+ a_A.triggered.connect(lambda: self._set_group_for_active("A"))
279
+ a_B.triggered.connect(lambda: self._set_group_for_active("B"))
280
+ a_C.triggered.connect(lambda: self._set_group_for_active("C"))
281
+ a_D.triggered.connect(lambda: self._set_group_for_active("D"))
282
+
283
+ # wrap it so it can live inside the menu
284
+ wa = QWidgetAction(self)
285
+ wa.setDefaultWidget(self._link_btn)
286
+ m_view.addAction(wa)
287
+
288
+ # first-time sync of label/checked state
289
+ self._sync_link_action_state()
290
+
291
+ m_settings = mb.addMenu("&Settings")
292
+ m_settings.addAction("Preferences...", self._open_settings)
293
+
294
+ m_about = mb.addMenu("&About")
295
+ m_about.addAction(self.act_docs)
296
+ m_about.addSeparator()
297
+ m_about.addAction("About...", self._about)
298
+ m_about.addAction(self.act_check_updates)
299
+
300
+
301
+ # initialize enabled state + names
302
+ self.update_undo_redo_action_labels()
303
+
304
+ def _init_tools_menu(self):
305
+ tools = self.menuBar().addMenu("Tools")
306
+ act_batch = QAction("Batch Convert...", self)
307
+ act_batch.triggered.connect(self._open_batch_convert)
308
+ tools.addAction(act_batch)
309
+
310
+
311
+
312
+ #------------Tools-----------------
313
+
314
+ def _rebuild_recent_menus(self):
315
+ """Rebuild both 'Open Recent' submenus."""
316
+ # Menus might not exist yet if called very early
317
+ if not hasattr(self, "m_recent_images_menu") or not hasattr(self, "m_recent_projects_menu"):
318
+ return
319
+
320
+ # ---- Images ----------------------------------------
321
+ self.m_recent_images_menu.clear()
322
+ if not self._recent_image_paths:
323
+ act = self.m_recent_images_menu.addAction("No recent images")
324
+ act.setEnabled(False)
325
+ else:
326
+ for path in self._recent_image_paths:
327
+ label = os.path.basename(path) or path
328
+ act = self.m_recent_images_menu.addAction(label)
329
+ act.setToolTip(path)
330
+ act.triggered.connect(
331
+ lambda checked=False, p=path: self._open_recent_image(p)
332
+ )
333
+ self.m_recent_images_menu.addSeparator()
334
+ clear_act = self.m_recent_images_menu.addAction("Clear List")
335
+ clear_act.triggered.connect(self._clear_recent_images)
336
+
337
+ # ---- Projects ----------------------------------------
338
+ self.m_recent_projects_menu.clear()
339
+ if not self._recent_project_paths:
340
+ act = self.m_recent_projects_menu.addAction("No recent projects")
341
+ act.setEnabled(False)
342
+ else:
343
+ for path in self._recent_project_paths:
344
+ label = os.path.basename(path) or path
345
+ act = self.m_recent_projects_menu.addAction(label)
346
+ act.setToolTip(path)
347
+ act.triggered.connect(
348
+ lambda checked=False, p=path: self._open_recent_project(p)
349
+ )
350
+ self.m_recent_projects_menu.addSeparator()
351
+ clear_act = self.m_recent_projects_menu.addAction("Clear List")
352
+ clear_act.triggered.connect(self._clear_recent_projects)
353
+
354
+ def _iter_menu_actions(self, menu: QMenu):
355
+ """Depth-first iterator over all actions inside a QMenu tree."""
356
+ for act in menu.actions():
357
+ yield act
358
+ sub = act.menu()
359
+ if sub is not None:
360
+ yield from self._iter_menu_actions(sub)
361
+