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,367 @@
1
+ # pro/gui/mixins/theme_mixin.py
2
+ """
3
+ Theme management mixin for AstroSuiteProMainWindow.
4
+
5
+ This mixin contains all theme-related functionality: palette definitions,
6
+ theme application, and system theme detection.
7
+ """
8
+ from __future__ import annotations
9
+ from typing import TYPE_CHECKING
10
+
11
+ from PyQt6.QtCore import Qt, QTimer
12
+ from PyQt6.QtGui import QBrush, QColor, QFont, QPalette
13
+ from PyQt6.QtWidgets import QApplication
14
+
15
+ if TYPE_CHECKING:
16
+ pass
17
+
18
+
19
+ class ThemeMixin:
20
+ """
21
+ Mixin for theme management.
22
+
23
+ Provides methods for creating palettes, applying themes, and
24
+ responding to system theme changes.
25
+ """
26
+
27
+ def _apply_workspace_theme(self):
28
+ """Retint the QMdiArea background + viewport to current theme colors."""
29
+ pal = QApplication.palette()
30
+ # Use Base for light, Window for dark (looks better with your palettes)
31
+ role = QPalette.ColorRole.Base if self._theme_mode() == "light" else QPalette.ColorRole.Window
32
+ col = pal.color(role)
33
+
34
+ # 1) Tell QMdiArea to use a flat color background
35
+ try:
36
+ self.mdi.setBackground(QBrush(col))
37
+ except Exception:
38
+ pass
39
+
40
+ # 2) Also set the viewport palette (some styles ignore setBackground)
41
+ try:
42
+ vp = self.mdi.viewport()
43
+ vp.setAutoFillBackground(True)
44
+ p = vp.palette()
45
+ p.setColor(QPalette.ColorRole.Window, col)
46
+ vp.setPalette(p)
47
+ vp.update()
48
+ except Exception:
49
+ pass
50
+
51
+ # 3) Ensure the overlay canvas stays transparent and refreshes
52
+ try:
53
+ if hasattr(self, "shortcuts") and self.shortcuts and getattr(self.shortcuts, "canvas", None):
54
+ c = self.shortcuts.canvas
55
+ c.setStyleSheet("background: transparent;")
56
+ c.update()
57
+ except Exception:
58
+ pass
59
+
60
+ def apply_theme_from_settings(self):
61
+ """Apply the theme based on current settings."""
62
+ mode = self._theme_mode()
63
+ app = QApplication.instance()
64
+ color_scheme = app.styleHints().colorScheme()
65
+
66
+ # Resolve "system" to dark/light
67
+ if mode == "system":
68
+ if color_scheme == Qt.ColorScheme.Dark:
69
+ print("System is in Dark Mode")
70
+ mode = "dark"
71
+ else:
72
+ print("System is in Light Mode")
73
+ mode = "light"
74
+
75
+ # Base style
76
+ if mode in ("dark", "gray", "light", "custom"):
77
+ app.setStyle("Fusion")
78
+ else:
79
+ app.setStyle(None)
80
+
81
+ # Palettes
82
+ if mode == "dark":
83
+ app.setPalette(self._dark_palette())
84
+ app.setStyleSheet(
85
+ "QToolTip { color: #ffffff; background-color: #2a2a2a; border: 1px solid #5a5a5a; }"
86
+ )
87
+ elif mode == "gray":
88
+ app.setPalette(self._gray_palette())
89
+ app.setStyleSheet(
90
+ "QToolTip { color: #f0f0f0; background-color: #3a3a3a; border: 1px solid #5a5a5a; }"
91
+ )
92
+ elif mode == "light":
93
+ app.setPalette(self._light_palette())
94
+ app.setStyleSheet(
95
+ "QToolTip { color: #141414; background-color: #ffffee; border: 1px solid #c8c8c8; }"
96
+ )
97
+ elif mode == "custom":
98
+ app.setPalette(self._custom_palette())
99
+ # Tooltips roughly matching the custom dark-ish style
100
+ app.setStyleSheet(
101
+ "QToolTip { color: #f0f0f0; background-color: #303030; border: 1px solid #5a5a5a; }"
102
+ )
103
+ else: # system/native fallback
104
+ app.setPalette(QApplication.style().standardPalette())
105
+ app.setStyleSheet("")
106
+
107
+ # Optional: apply custom font
108
+ if mode == "custom":
109
+ font_str = self.settings.value("ui/custom/font", "", type=str) or ""
110
+ if font_str:
111
+ try:
112
+ f = QFont()
113
+ if f.fromString(font_str):
114
+ app.setFont(f)
115
+ except Exception:
116
+ pass
117
+
118
+ # Nudge widgets to pick up role changes
119
+ self._repolish_top_levels()
120
+ self._apply_workspace_theme()
121
+ self._style_mdi_titlebars()
122
+ self._menu_view_panels = None
123
+
124
+ try:
125
+ vp = self.mdi.viewport()
126
+ vp.setAutoFillBackground(True)
127
+ vp.setPalette(QApplication.palette())
128
+ vp.update()
129
+ except Exception:
130
+ pass
131
+
132
+ def _repolish_top_levels(self):
133
+ """Force all top-level widgets to repolish their styles."""
134
+ app = QApplication.instance()
135
+ for w in app.topLevelWidgets():
136
+ w.setUpdatesEnabled(False)
137
+ w.style().unpolish(w)
138
+ w.style().polish(w)
139
+ w.setUpdatesEnabled(True)
140
+
141
+ def _style_mdi_titlebars(self):
142
+ """Apply theme-specific styles to MDI subwindow titlebars."""
143
+ mode = self._theme_mode()
144
+ if mode == "dark":
145
+ base = "#1b1b1b" # inactive titlebar
146
+ active = "#242424" # active titlebar
147
+ fg = "#dcdcdc"
148
+ elif mode in ("gray", "custom"):
149
+ base = "#3a3a3a"
150
+ active = "#454545"
151
+ fg = "#f0f0f0"
152
+ else:
153
+ # No override in light / system modes
154
+ self.mdi.setStyleSheet("")
155
+ return
156
+
157
+ self.mdi.setStyleSheet(f"""
158
+ QMdiSubWindow::titlebar {{ background: {base}; color: {fg}; }}
159
+ QMdiSubWindow::titlebar:active {{ background: {active}; color: {fg}; }}
160
+ """)
161
+
162
+ def _dark_palette(self) -> QPalette:
163
+ """Create a dark theme palette."""
164
+ p = QPalette()
165
+
166
+ # Bases
167
+ bg = QColor(18, 18, 18) # editor / view backgrounds (Base)
168
+ panel = QColor(27, 27, 27) # window / panels (Window, Button)
169
+ altbase = QColor(33, 33, 33)
170
+ text = QColor(220, 220, 220)
171
+ dis = QColor(140, 140, 140)
172
+ hi = QColor(30, 144, 255) # highlight (dodger blue)
173
+
174
+ p.setColor(QPalette.ColorRole.Window, panel)
175
+ p.setColor(QPalette.ColorRole.WindowText, text)
176
+ p.setColor(QPalette.ColorRole.Base, bg)
177
+ p.setColor(QPalette.ColorRole.AlternateBase, altbase)
178
+ p.setColor(QPalette.ColorRole.ToolTipBase, panel)
179
+ p.setColor(QPalette.ColorRole.ToolTipText, text)
180
+ p.setColor(QPalette.ColorRole.Text, text)
181
+ p.setColor(QPalette.ColorRole.Button, panel)
182
+ p.setColor(QPalette.ColorRole.ButtonText, text)
183
+ p.setColor(QPalette.ColorRole.BrightText, QColor(255, 0, 0))
184
+ p.setColor(QPalette.ColorRole.Highlight, hi)
185
+ p.setColor(QPalette.ColorRole.HighlightedText, QColor(255, 255, 255))
186
+ p.setColor(QPalette.ColorRole.Link, QColor(90, 160, 255))
187
+ p.setColor(QPalette.ColorRole.LinkVisited, QColor(160, 140, 255))
188
+
189
+ # Qt6: explicit placeholder color helps avoid faint-on-faint
190
+ try:
191
+ p.setColor(QPalette.ColorRole.PlaceholderText, QColor(160, 160, 160))
192
+ except Exception:
193
+ pass
194
+
195
+ # Disabled
196
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, dis)
197
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, dis)
198
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.WindowText, dis)
199
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Base, QColor(24, 24, 24))
200
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Highlight, QColor(60, 60, 60))
201
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.HighlightedText, QColor(210, 210, 210))
202
+
203
+ return p
204
+
205
+ def _custom_palette(self) -> QPalette:
206
+ """
207
+ Build a QPalette from user-defined colors in QSettings.
208
+ Falls back to a gray-ish baseline if any key is missing.
209
+ """
210
+ s = self.settings
211
+
212
+ def col(key: str, default: QColor) -> QColor:
213
+ val = s.value(key, default.name(), type=str) or default.name()
214
+ return QColor(val)
215
+
216
+ window = col("ui/custom/window", QColor(54, 54, 54))
217
+ base = col("ui/custom/base", QColor(40, 40, 40))
218
+ altbase = col("ui/custom/altbase", QColor(64, 64, 64))
219
+ text = col("ui/custom/text", QColor(230, 230, 230))
220
+ button = col("ui/custom/button", window)
221
+ hi = col("ui/custom/highlight", QColor(95, 145, 230))
222
+ link = col("ui/custom/link", QColor(120, 170, 255))
223
+ linkv = col("ui/custom/link_visited", QColor(180, 150, 255))
224
+
225
+ p = QPalette()
226
+
227
+ # Core roles
228
+ p.setColor(QPalette.ColorRole.Window, window)
229
+ p.setColor(QPalette.ColorRole.WindowText, text)
230
+ p.setColor(QPalette.ColorRole.Base, base)
231
+ p.setColor(QPalette.ColorRole.AlternateBase, altbase)
232
+ p.setColor(QPalette.ColorRole.ToolTipBase, window)
233
+ p.setColor(QPalette.ColorRole.ToolTipText, text)
234
+ p.setColor(QPalette.ColorRole.Text, text)
235
+ p.setColor(QPalette.ColorRole.Button, button)
236
+ p.setColor(QPalette.ColorRole.ButtonText, text)
237
+ p.setColor(QPalette.ColorRole.BrightText, QColor(255, 0, 0))
238
+ p.setColor(QPalette.ColorRole.Highlight, hi)
239
+ p.setColor(QPalette.ColorRole.HighlightedText, QColor(255, 255, 255))
240
+ p.setColor(QPalette.ColorRole.Link, link)
241
+ p.setColor(QPalette.ColorRole.LinkVisited, linkv)
242
+
243
+ # Placeholder / disabled
244
+ try:
245
+ p.setColor(QPalette.ColorRole.PlaceholderText, QColor(170, 170, 170))
246
+ except Exception:
247
+ pass
248
+
249
+ dis = QColor(150, 150, 150)
250
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, dis)
251
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, dis)
252
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.WindowText, dis)
253
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Base, base.darker(115))
254
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Highlight, hi.darker(140))
255
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.HighlightedText, QColor(210, 210, 210))
256
+
257
+ return p
258
+
259
+ def _gray_palette(self) -> QPalette:
260
+ """Create a mid-gray theme palette."""
261
+ p = QPalette()
262
+
263
+ # Mid-gray neutrals
264
+ window = QColor(54, 54, 54) # panels/docks
265
+ base = QColor(64, 64, 64) # editors / text fields
266
+ altbase = QColor(72, 72, 72) # alternating rows
267
+ text = QColor(230, 230, 230)
268
+ btn = window
269
+ dis = QColor(150, 150, 150)
270
+ link = QColor(120, 170, 255)
271
+ linkv = QColor(180, 150, 255)
272
+ hi = QColor(95, 145, 230)
273
+ hitxt = QColor(255, 255, 255)
274
+
275
+ # Core roles
276
+ p.setColor(QPalette.ColorRole.Window, window)
277
+ p.setColor(QPalette.ColorRole.WindowText, text)
278
+ p.setColor(QPalette.ColorRole.Base, base)
279
+ p.setColor(QPalette.ColorRole.AlternateBase, altbase)
280
+ p.setColor(QPalette.ColorRole.ToolTipBase, QColor(60, 60, 60))
281
+ p.setColor(QPalette.ColorRole.ToolTipText, text)
282
+ p.setColor(QPalette.ColorRole.Text, text)
283
+ p.setColor(QPalette.ColorRole.Button, btn)
284
+ p.setColor(QPalette.ColorRole.ButtonText, text)
285
+ p.setColor(QPalette.ColorRole.BrightText, QColor(255, 0, 0))
286
+ p.setColor(QPalette.ColorRole.Highlight, hi)
287
+ p.setColor(QPalette.ColorRole.HighlightedText, hitxt)
288
+ p.setColor(QPalette.ColorRole.Link, link)
289
+ p.setColor(QPalette.ColorRole.LinkVisited, linkv)
290
+
291
+ # Placeholder
292
+ try:
293
+ p.setColor(QPalette.ColorRole.PlaceholderText, QColor(170, 170, 170))
294
+ except Exception:
295
+ pass
296
+
297
+ # Disabled group
298
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, dis)
299
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, dis)
300
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.WindowText, dis)
301
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Base, QColor(58, 58, 58))
302
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Highlight, QColor(80, 80, 80))
303
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.HighlightedText, QColor(210, 210, 210))
304
+
305
+ return p
306
+
307
+ def _light_palette(self) -> QPalette:
308
+ """Create a light theme palette."""
309
+ p = QPalette()
310
+
311
+ # Light neutrals
312
+ window = QColor(246, 246, 246) # panels/docks
313
+ base = QColor(255, 255, 255) # text fields, editors
314
+ altbase = QColor(242, 242, 242) # alternating rows
315
+ text = QColor(20, 20, 20) # primary text
316
+ btn = QColor(246, 246, 246) # buttons same as window
317
+ dis = QColor(140, 140, 140) # disabled text
318
+ link = QColor(25, 100, 210) # link blue
319
+ linkv = QColor(120, 70, 200) # visited
320
+ hi = QColor(43, 120, 228) # selection blue (Windows-like)
321
+ hitxt = QColor(255, 255, 255) # text over selection
322
+
323
+ # Core roles
324
+ p.setColor(QPalette.ColorRole.Window, window)
325
+ p.setColor(QPalette.ColorRole.WindowText, text)
326
+ p.setColor(QPalette.ColorRole.Base, base)
327
+ p.setColor(QPalette.ColorRole.AlternateBase, altbase)
328
+ p.setColor(QPalette.ColorRole.ToolTipBase, QColor(255, 255, 238)) # soft yellow tooltip
329
+ p.setColor(QPalette.ColorRole.ToolTipText, text)
330
+ p.setColor(QPalette.ColorRole.Text, text)
331
+ p.setColor(QPalette.ColorRole.Button, btn)
332
+ p.setColor(QPalette.ColorRole.ButtonText, text)
333
+ p.setColor(QPalette.ColorRole.BrightText, QColor(180, 0, 0))
334
+ p.setColor(QPalette.ColorRole.Highlight, hi)
335
+ p.setColor(QPalette.ColorRole.HighlightedText, hitxt)
336
+ p.setColor(QPalette.ColorRole.Link, link)
337
+ p.setColor(QPalette.ColorRole.LinkVisited, linkv)
338
+
339
+ # Helps line edits/placeholders avoid too-faint gray
340
+ try:
341
+ p.setColor(QPalette.ColorRole.PlaceholderText, QColor(110, 110, 110))
342
+ except Exception:
343
+ pass
344
+
345
+ # Disabled group (keep contrasts sane)
346
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Text, dis)
347
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.ButtonText, dis)
348
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.WindowText, dis)
349
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Highlight, QColor(200, 200, 200))
350
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.HighlightedText, QColor(120, 120, 120))
351
+ p.setColor(QPalette.ColorGroup.Disabled, QPalette.ColorRole.Base, QColor(248, 248, 248))
352
+
353
+ return p
354
+
355
+ def _apply_theme_safely(self):
356
+ """Apply theme with re-entrancy guard."""
357
+ if self._theme_guard:
358
+ return
359
+ self._theme_guard = True
360
+ try:
361
+ self.apply_theme_from_settings()
362
+ finally:
363
+ QTimer.singleShot(0, lambda: setattr(self, "_theme_guard", False))
364
+
365
+ def _theme_mode(self) -> str:
366
+ """Get the current theme mode from settings."""
367
+ return (self.settings.value("ui/theme", "system", type=str) or "system").lower()