batoolset-gui 0.2.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.
Files changed (74) hide show
  1. batoolset/GUI/__init__.py +0 -0
  2. batoolset/GUI/customdialog.py +136 -0
  3. batoolset/GUI/exception_handler.py +74 -0
  4. batoolset/GUI/list_gui.py +514 -0
  5. batoolset/GUI/multi_image_display.py +1038 -0
  6. batoolset/GUI/open.py +325 -0
  7. batoolset/GUI/overlay_hints.py +45 -0
  8. batoolset/GUI/paint2.py +1369 -0
  9. batoolset/GUI/scrollablepaint.py +1165 -0
  10. batoolset/dialogs/__init__.py +0 -0
  11. batoolset/dialogs/opensave.py +228 -0
  12. batoolset/drawings/__init__.py +0 -0
  13. batoolset/drawings/math_text.py +294 -0
  14. batoolset/drawings/pdf_svg_renderer.py +376 -0
  15. batoolset/drawings/shapes/Position.py +183 -0
  16. batoolset/drawings/shapes/__init__.py +0 -0
  17. batoolset/drawings/shapes/automated_alignment_drawing_refactored.py +346 -0
  18. batoolset/drawings/shapes/circle2d.py +99 -0
  19. batoolset/drawings/shapes/compute_optimal_size_all_merged.py +328 -0
  20. batoolset/drawings/shapes/ellipse2d.py +336 -0
  21. batoolset/drawings/shapes/freehand2d.py +235 -0
  22. batoolset/drawings/shapes/group.py +1985 -0
  23. batoolset/drawings/shapes/image2d.py +3157 -0
  24. batoolset/drawings/shapes/line2d.py +579 -0
  25. batoolset/drawings/shapes/point2d.py +257 -0
  26. batoolset/drawings/shapes/polygon2d.py +357 -0
  27. batoolset/drawings/shapes/polyline2d.py +235 -0
  28. batoolset/drawings/shapes/rect2d.py +388 -0
  29. batoolset/drawings/shapes/rectangle2d.py +601 -0
  30. batoolset/drawings/shapes/scalebar.py +310 -0
  31. batoolset/drawings/shapes/serializablefont.py +133 -0
  32. batoolset/drawings/shapes/serializableguiparams.py +14 -0
  33. batoolset/drawings/shapes/square2d.py +102 -0
  34. batoolset/drawings/shapes/test_publication_style.py +220 -0
  35. batoolset/drawings/shapes/textorientation.py +45 -0
  36. batoolset/drawings/shapes/tools.py +467 -0
  37. batoolset/drawings/shapes/transform2D.py +350 -0
  38. batoolset/drawings/shapes/txt2d.py +615 -0
  39. batoolset/drawings/svg_clip_renderer.py +247 -0
  40. batoolset/drawings/svg_embed.py +165 -0
  41. batoolset/drawings/svg_text_renderer.py +183 -0
  42. batoolset/drawings/widgets/__init__.py +0 -0
  43. batoolset/drawings/widgets/col.py +343 -0
  44. batoolset/drawings/widgets/image_group.py +248 -0
  45. batoolset/drawings/widgets/image_organizer.py +96 -0
  46. batoolset/drawings/widgets/paint.py +184 -0
  47. batoolset/drawings/widgets/vectorial.py +256 -0
  48. batoolset/figure/__init__.py +0 -0
  49. batoolset/figure/alignment.py +822 -0
  50. batoolset/figure/column.py +1117 -0
  51. batoolset/figure/fig_tools.py +285 -0
  52. batoolset/figure/minimal_debugger_scale_etc.py +139 -0
  53. batoolset/figure/panel.py +697 -0
  54. batoolset/figure/row.py +852 -0
  55. batoolset/figure/user_selection.py +287 -0
  56. batoolset/icons/__init__.py +0 -0
  57. batoolset/icons/tools.py +248 -0
  58. batoolset/luts/__init__.py +0 -0
  59. batoolset/luts/lut_minimal_test.py +1308 -0
  60. batoolset/luts/lutcreatorfromformula.py +209 -0
  61. batoolset/luts/luteditor.py +243 -0
  62. batoolset/pyqts/__init__.py +0 -0
  63. batoolset/pyqts/pleasewaitdialog.py +14 -0
  64. batoolset/pyqts/tools.py +760 -0
  65. batoolset/serializations/__init__.py +0 -0
  66. batoolset/serializations/tools.py +1338 -0
  67. batoolset/theme.py +647 -0
  68. batoolset/uis/__init__.py +0 -0
  69. batoolset/uis/blinker.py +56 -0
  70. batoolset_gui-0.2.0.dist-info/METADATA +54 -0
  71. batoolset_gui-0.2.0.dist-info/RECORD +74 -0
  72. batoolset_gui-0.2.0.dist-info/WHEEL +5 -0
  73. batoolset_gui-0.2.0.dist-info/licenses/LICENSE +24 -0
  74. batoolset_gui-0.2.0.dist-info/top_level.txt +1 -0
batoolset/theme.py ADDED
@@ -0,0 +1,647 @@
1
+ from __future__ import annotations
2
+ """
3
+ Centralised theme management for all batoolset-based Qt applications.
4
+
5
+ Two built-in themes (Catppuccin palette):
6
+ "dark" – Catppuccin Mocha [default]
7
+ "white" – Catppuccin Latte
8
+
9
+ Public API
10
+ ----------
11
+ apply_theme(app, name, *, font_size=None)
12
+ Apply a theme to a QApplication. Pass font_size (px) to also set the
13
+ application font; omit to leave the font unchanged.
14
+
15
+ current_theme() -> str
16
+ Return the name of the currently active theme.
17
+
18
+ painter_colors() -> dict
19
+ Colour-token dict for the active theme. Use this in QPainter-based
20
+ widgets that QSS cannot reach.
21
+
22
+ load_theme(settings_path=None, default='dark') -> str
23
+ Read the saved theme from a JSON file. If settings_path is None the
24
+ call returns default without touching the filesystem.
25
+
26
+ save_theme(name, settings_path=None) -> None
27
+ Persist the theme name to a JSON file. No-op when settings_path is None.
28
+
29
+ Typical usage in an application
30
+ ---------------------------------
31
+ import os
32
+ from batoolset.theme import apply_theme, load_theme, save_theme, current_theme
33
+
34
+ _SETTINGS = os.path.join(os.path.dirname(__file__), 'settings.json')
35
+
36
+ # On startup:
37
+ t = load_theme(_SETTINGS)
38
+ apply_theme(app, t)
39
+
40
+ # When the user picks a theme:
41
+ apply_theme(app, 'dark')
42
+ save_theme('dark', _SETTINGS)
43
+ """
44
+
45
+ import json
46
+ import os
47
+
48
+ from qtpy.QtCore import Qt
49
+ from qtpy.QtGui import QColor, QFont, QPainter, QPainterPath, QPalette
50
+ from qtpy.QtWidgets import QApplication, QProxyStyle, QStyle
51
+
52
+ # ---------------------------------------------------------------------------
53
+ # Proxy style – draws visible filled-triangle arrows for QSpinBox / QComboBox
54
+ #
55
+ # When QSS sets background-color on QSpinBox, Fusion hands full sub-control
56
+ # ownership to the stylesheet engine and the native arrows vanish unless an
57
+ # image: rule is provided. We intercept all four PE_IndicatorArrow* primitives
58
+ # and paint filled triangles in the palette's ButtonText colour, which tracks
59
+ # theme changes automatically.
60
+ # ---------------------------------------------------------------------------
61
+
62
+ class _FusionArrowFix(QProxyStyle):
63
+ _ARROW_ELEMENTS = frozenset({
64
+ QStyle.PrimitiveElement.PE_IndicatorArrowUp,
65
+ QStyle.PrimitiveElement.PE_IndicatorArrowDown,
66
+ QStyle.PrimitiveElement.PE_IndicatorArrowLeft,
67
+ QStyle.PrimitiveElement.PE_IndicatorArrowRight,
68
+ })
69
+
70
+ def drawPrimitive(self, element, option, painter, widget=None):
71
+ if element not in self._ARROW_ELEMENTS:
72
+ super().drawPrimitive(element, option, painter, widget)
73
+ return
74
+ color = QApplication.instance().palette().color(QPalette.ColorRole.ButtonText)
75
+ r = option.rect
76
+ cx = r.center().x()
77
+ cy = r.center().y()
78
+ half = max(3, min(r.width(), r.height()) // 4)
79
+ path = QPainterPath()
80
+ PE = QStyle.PrimitiveElement
81
+ if element == PE.PE_IndicatorArrowUp:
82
+ path.moveTo(cx, cy - half)
83
+ path.lineTo(cx - half, cy + half)
84
+ path.lineTo(cx + half, cy + half)
85
+ elif element == PE.PE_IndicatorArrowDown:
86
+ path.moveTo(cx, cy + half)
87
+ path.lineTo(cx - half, cy - half)
88
+ path.lineTo(cx + half, cy - half)
89
+ elif element == PE.PE_IndicatorArrowLeft:
90
+ path.moveTo(cx - half, cy)
91
+ path.lineTo(cx + half, cy - half)
92
+ path.lineTo(cx + half, cy + half)
93
+ else:
94
+ path.moveTo(cx + half, cy)
95
+ path.lineTo(cx - half, cy - half)
96
+ path.lineTo(cx - half, cy + half)
97
+ path.closeSubpath()
98
+ painter.save()
99
+ painter.setRenderHint(QPainter.RenderHint.Antialiasing)
100
+ painter.setPen(Qt.PenStyle.NoPen)
101
+ painter.setBrush(color)
102
+ painter.drawPath(path)
103
+ painter.restore()
104
+
105
+
106
+ # Kept alive at module level so GC does not destroy it after setStyle()
107
+ # transfers C++ ownership.
108
+ _proxy_style: _FusionArrowFix | None = None
109
+ _active: str = "default"
110
+ # Original palette/style captured before the first theme change so 'default'
111
+ # can restore the platform appearance.
112
+ _original_palette: QPalette | None = None
113
+ _original_style_name: str | None = None
114
+
115
+ # ---------------------------------------------------------------------------
116
+ # WHITE — Catppuccin Latte
117
+ # ---------------------------------------------------------------------------
118
+
119
+ WHITE_QSS = """
120
+ QWidget {
121
+ background-color: #eff1f5;
122
+ color: #4c4f69;
123
+ font-size: 13px;
124
+ }
125
+ QMainWindow, QDialog { background-color: #eff1f5; }
126
+ QFrame { background-color: #eff1f5; border: none; }
127
+ QSplitter::handle { background-color: #ccd0da; }
128
+
129
+ QMenuBar {
130
+ background-color: #e6e9ef;
131
+ color: #4c4f69;
132
+ border-bottom: 1px solid #ccd0da;
133
+ }
134
+ QMenuBar::item:selected { background-color: #ccd0da; }
135
+ QMenu {
136
+ background-color: #eff1f5;
137
+ border: 1px solid #bcc0cc;
138
+ color: #4c4f69;
139
+ }
140
+ QMenu::item:selected { background-color: #ccd0da; }
141
+ QMenu::separator { height: 1px; background: #bcc0cc; margin: 2px 8px; }
142
+
143
+ QToolBar {
144
+ background-color: #e6e9ef;
145
+ border-bottom: 1px solid #ccd0da;
146
+ spacing: 3px;
147
+ padding: 2px 4px;
148
+ }
149
+ QToolBar::separator { background-color: #bcc0cc; width: 1px; margin: 3px 4px; }
150
+
151
+ QPushButton {
152
+ background-color: #ccd0da;
153
+ color: #4c4f69;
154
+ border: 1px solid #bcc0cc;
155
+ border-radius: 4px;
156
+ padding: 3px 10px;
157
+ }
158
+ QPushButton:hover { background-color: #bcc0cc; border-color: #acb0be; }
159
+ QPushButton:pressed { background-color: #acb0be; }
160
+ QPushButton:checked { background-color: #1e66f5; color: #eff1f5; border-color: #1e66f5; }
161
+ QPushButton:disabled { background-color: #dce0e8; color: #9ca0b0; border-color: #ccd0da; }
162
+
163
+ QLineEdit, QTextEdit, QPlainTextEdit {
164
+ background-color: #ffffff;
165
+ color: #4c4f69;
166
+ border: 1px solid #bcc0cc;
167
+ border-radius: 4px;
168
+ padding: 2px 6px;
169
+ selection-background-color: #1e66f5;
170
+ selection-color: #ffffff;
171
+ }
172
+ QLineEdit:focus, QTextEdit:focus, QPlainTextEdit:focus { border-color: #1e66f5; }
173
+ QLineEdit:read-only { background-color: #eff1f5; color: #9ca0b0; }
174
+
175
+ /* SpinBox / date-time: styled only via the palette so Fusion renders
176
+ up/down buttons and arrows natively. Only font-size is set here. */
177
+ QSpinBox, QDoubleSpinBox, QDateEdit, QTimeEdit, QDateTimeEdit { font-size: 13px; }
178
+
179
+ QComboBox {
180
+ background-color: #ffffff;
181
+ color: #4c4f69;
182
+ border: 1px solid #bcc0cc;
183
+ border-radius: 4px;
184
+ padding: 2px 6px;
185
+ }
186
+ QComboBox:focus { border-color: #1e66f5; }
187
+ QComboBox::drop-down { border: none; width: 20px; }
188
+ QComboBox QAbstractItemView {
189
+ background-color: #ffffff;
190
+ color: #4c4f69;
191
+ selection-background-color: #ccd0da;
192
+ border: 1px solid #bcc0cc;
193
+ }
194
+
195
+ QLabel { background: transparent; color: #4c4f69; }
196
+
197
+ QCheckBox { color: #4c4f69; spacing: 6px; }
198
+ QCheckBox::indicator {
199
+ width: 14px; height: 14px;
200
+ border: 1px solid #bcc0cc;
201
+ border-radius: 3px;
202
+ background-color: #ffffff;
203
+ }
204
+ QCheckBox::indicator:checked { background-color: #1e66f5; border-color: #1e66f5; }
205
+
206
+ QListWidget, QTreeWidget, QListView, QTreeView {
207
+ background-color: #ffffff;
208
+ color: #4c4f69;
209
+ border: 1px solid #ccd0da;
210
+ border-radius: 4px;
211
+ alternate-background-color: #eff1f5;
212
+ outline: none;
213
+ }
214
+ QListWidget::item, QTreeWidget::item { padding: 3px 6px; }
215
+ QListWidget::item:selected, QTreeWidget::item:selected {
216
+ background-color: #ccd0da; color: #1e66f5;
217
+ }
218
+ QListWidget::item:hover, QTreeWidget::item:hover { background-color: #e6e9ef; }
219
+
220
+ QTableWidget, QTableView {
221
+ background-color: #ffffff;
222
+ color: #4c4f69;
223
+ gridline-color: #ccd0da;
224
+ border: 1px solid #ccd0da;
225
+ alternate-background-color: #eff1f5;
226
+ outline: none;
227
+ }
228
+ QTableWidget::item:selected, QTableView::item:selected {
229
+ background-color: #ccd0da; color: #1e66f5;
230
+ }
231
+ QHeaderView::section {
232
+ background-color: #e6e9ef;
233
+ color: #5c5f77;
234
+ border: none;
235
+ border-right: 1px solid #ccd0da;
236
+ border-bottom: 1px solid #ccd0da;
237
+ padding: 4px 8px;
238
+ font-weight: 600;
239
+ }
240
+ QHeaderView::section:checked { background-color: #ccd0da; }
241
+
242
+ QScrollBar:vertical { background: #e6e9ef; width: 10px; margin: 0; }
243
+ QScrollBar::handle:vertical { background: #bcc0cc; border-radius: 5px; min-height: 20px; }
244
+ QScrollBar::handle:vertical:hover { background: #acb0be; }
245
+ QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0; }
246
+ QScrollBar:horizontal { background: #e6e9ef; height: 10px; margin: 0; }
247
+ QScrollBar::handle:horizontal { background: #bcc0cc; border-radius: 5px; min-width: 20px; }
248
+ QScrollBar::handle:horizontal:hover { background: #acb0be; }
249
+ QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0; }
250
+
251
+ QTabWidget::pane { border: 1px solid #ccd0da; background-color: #eff1f5; }
252
+ QTabBar::tab {
253
+ background-color: #e6e9ef; color: #6c6f85;
254
+ border: 1px solid #ccd0da; border-bottom: none;
255
+ padding: 5px 14px;
256
+ border-top-left-radius: 4px; border-top-right-radius: 4px;
257
+ }
258
+ QTabBar::tab:selected { background-color: #eff1f5; color: #4c4f69; border-bottom: 2px solid #1e66f5; }
259
+ QTabBar::tab:hover:!selected { background-color: #dce0e8; }
260
+
261
+ QDockWidget { color: #4c4f69; titlebar-close-icon: none; }
262
+ QDockWidget::title {
263
+ background-color: #e6e9ef; color: #4c4f69;
264
+ padding: 4px 8px; border-bottom: 1px solid #ccd0da;
265
+ }
266
+
267
+ QGroupBox {
268
+ border: 1px solid #ccd0da; border-radius: 4px;
269
+ margin-top: 12px; color: #6c6f85;
270
+ }
271
+ QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; padding: 0 4px; left: 10px; }
272
+
273
+ QStatusBar { background-color: #e6e9ef; color: #5c5f77; border-top: 1px solid #ccd0da; }
274
+ QStatusBar::item { border: none; }
275
+
276
+ QProgressDialog { background-color: #eff1f5; color: #4c4f69; }
277
+ QProgressBar {
278
+ background-color: #dce0e8; border: 1px solid #bcc0cc;
279
+ border-radius: 4px; text-align: center; color: #4c4f69; height: 14px;
280
+ }
281
+ QProgressBar::chunk { background-color: #1e66f5; border-radius: 4px; }
282
+
283
+ QScrollArea { border: none; background-color: transparent; }
284
+ QScrollArea > QWidget > QWidget { background-color: transparent; }
285
+ QMessageBox { background-color: #eff1f5; }
286
+ """
287
+
288
+ # ---------------------------------------------------------------------------
289
+ # DARK — Catppuccin Mocha
290
+ # ---------------------------------------------------------------------------
291
+
292
+ DARK_QSS = """
293
+ QWidget {
294
+ background-color: #1e1e2e;
295
+ color: #cdd6f4;
296
+ font-size: 13px;
297
+ }
298
+ QMainWindow, QDialog { background-color: #1e1e2e; }
299
+ QFrame { background-color: #1e1e2e; border: none; }
300
+ QSplitter::handle { background-color: #313244; }
301
+
302
+ QMenuBar {
303
+ background-color: #181825;
304
+ color: #cdd6f4;
305
+ border-bottom: 1px solid #313244;
306
+ }
307
+ QMenuBar::item:selected { background-color: #313244; }
308
+ QMenu {
309
+ background-color: #1e1e2e;
310
+ border: 1px solid #45475a;
311
+ color: #cdd6f4;
312
+ }
313
+ QMenu::item:selected { background-color: #313244; }
314
+ QMenu::separator { height: 1px; background: #45475a; margin: 2px 8px; }
315
+
316
+ QToolBar {
317
+ background-color: #181825;
318
+ border-bottom: 1px solid #313244;
319
+ spacing: 3px;
320
+ padding: 2px 4px;
321
+ }
322
+ QToolBar::separator { background-color: #45475a; width: 1px; margin: 3px 4px; }
323
+
324
+ QPushButton {
325
+ background-color: #313244;
326
+ color: #cdd6f4;
327
+ border: 1px solid #45475a;
328
+ border-radius: 4px;
329
+ padding: 3px 10px;
330
+ }
331
+ QPushButton:hover { background-color: #45475a; border-color: #585b70; }
332
+ QPushButton:pressed { background-color: #585b70; }
333
+ QPushButton:checked { background-color: #89b4fa; color: #1e1e2e; border-color: #89b4fa; }
334
+ QPushButton:disabled { background-color: #27273a; color: #6c7086; border-color: #313244; }
335
+
336
+ QLineEdit, QTextEdit, QPlainTextEdit {
337
+ background-color: #181825;
338
+ color: #cdd6f4;
339
+ border: 1px solid #45475a;
340
+ border-radius: 4px;
341
+ padding: 2px 6px;
342
+ selection-background-color: #89b4fa;
343
+ selection-color: #1e1e2e;
344
+ }
345
+ QLineEdit:focus, QTextEdit:focus, QPlainTextEdit:focus { border-color: #89b4fa; }
346
+ QLineEdit:read-only { background-color: #1e1e2e; color: #6c7086; }
347
+
348
+ /* SpinBox / date-time: styled only via the palette so Fusion renders
349
+ up/down buttons and arrows natively. */
350
+ QSpinBox, QDoubleSpinBox, QDateEdit, QTimeEdit, QDateTimeEdit { font-size: 13px; }
351
+
352
+ QComboBox {
353
+ background-color: #181825;
354
+ color: #cdd6f4;
355
+ border: 1px solid #45475a;
356
+ border-radius: 4px;
357
+ padding: 2px 6px;
358
+ }
359
+ QComboBox:focus { border-color: #89b4fa; }
360
+ QComboBox::drop-down { border: none; width: 20px; }
361
+ QComboBox QAbstractItemView {
362
+ background-color: #181825;
363
+ color: #cdd6f4;
364
+ selection-background-color: #313244;
365
+ border: 1px solid #45475a;
366
+ }
367
+
368
+ QLabel { background: transparent; color: #cdd6f4; }
369
+
370
+ QCheckBox { color: #cdd6f4; spacing: 6px; }
371
+ QCheckBox::indicator {
372
+ width: 14px; height: 14px;
373
+ border: 1px solid #45475a;
374
+ border-radius: 3px;
375
+ background-color: #181825;
376
+ }
377
+ QCheckBox::indicator:checked { background-color: #89b4fa; border-color: #89b4fa; }
378
+
379
+ QListWidget, QTreeWidget, QListView, QTreeView {
380
+ background-color: #181825;
381
+ color: #cdd6f4;
382
+ border: 1px solid #313244;
383
+ border-radius: 4px;
384
+ alternate-background-color: #1e1e2e;
385
+ outline: none;
386
+ }
387
+ QListWidget::item, QTreeWidget::item { padding: 3px 6px; }
388
+ QListWidget::item:selected, QTreeWidget::item:selected {
389
+ background-color: #313244; color: #89b4fa;
390
+ }
391
+ QListWidget::item:hover, QTreeWidget::item:hover { background-color: #27273a; }
392
+
393
+ QTableWidget, QTableView {
394
+ background-color: #181825;
395
+ color: #cdd6f4;
396
+ gridline-color: #313244;
397
+ border: 1px solid #313244;
398
+ alternate-background-color: #1e1e2e;
399
+ outline: none;
400
+ }
401
+ QTableWidget::item:selected, QTableView::item:selected {
402
+ background-color: #313244; color: #89b4fa;
403
+ }
404
+ QHeaderView::section {
405
+ background-color: #1e1e2e;
406
+ color: #a6adc8;
407
+ border: none;
408
+ border-right: 1px solid #313244;
409
+ border-bottom: 1px solid #313244;
410
+ padding: 4px 8px;
411
+ font-weight: 600;
412
+ }
413
+ QHeaderView::section:checked { background-color: #313244; }
414
+
415
+ QScrollBar:vertical { background: #181825; width: 10px; margin: 0; }
416
+ QScrollBar::handle:vertical { background: #45475a; border-radius: 5px; min-height: 20px; }
417
+ QScrollBar::handle:vertical:hover { background: #585b70; }
418
+ QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical { height: 0; }
419
+ QScrollBar:horizontal { background: #181825; height: 10px; margin: 0; }
420
+ QScrollBar::handle:horizontal { background: #45475a; border-radius: 5px; min-width: 20px; }
421
+ QScrollBar::handle:horizontal:hover { background: #585b70; }
422
+ QScrollBar::add-line:horizontal, QScrollBar::sub-line:horizontal { width: 0; }
423
+
424
+ QTabWidget::pane { border: 1px solid #313244; background-color: #1e1e2e; }
425
+ QTabBar::tab {
426
+ background-color: #181825; color: #a6adc8;
427
+ border: 1px solid #313244; border-bottom: none;
428
+ padding: 5px 14px;
429
+ border-top-left-radius: 4px; border-top-right-radius: 4px;
430
+ }
431
+ QTabBar::tab:selected { background-color: #1e1e2e; color: #cdd6f4; border-bottom: 2px solid #89b4fa; }
432
+ QTabBar::tab:hover:!selected { background-color: #27273a; }
433
+
434
+ QDockWidget { color: #cdd6f4; titlebar-close-icon: none; }
435
+ QDockWidget::title {
436
+ background-color: #181825; color: #cdd6f4;
437
+ padding: 4px 8px; border-bottom: 1px solid #313244;
438
+ }
439
+
440
+ QGroupBox {
441
+ border: 1px solid #313244; border-radius: 4px;
442
+ margin-top: 12px; color: #a6adc8;
443
+ }
444
+ QGroupBox::title { subcontrol-origin: margin; subcontrol-position: top left; padding: 0 4px; left: 10px; }
445
+
446
+ QStatusBar { background-color: #181825; color: #a6adc8; border-top: 1px solid #313244; }
447
+ QStatusBar::item { border: none; }
448
+
449
+ QProgressDialog { background-color: #1e1e2e; color: #cdd6f4; }
450
+ QProgressBar {
451
+ background-color: #181825; border: 1px solid #45475a;
452
+ border-radius: 4px; text-align: center; color: #cdd6f4; height: 14px;
453
+ }
454
+ QProgressBar::chunk { background-color: #89b4fa; border-radius: 4px; }
455
+
456
+ QScrollArea { border: none; background-color: transparent; }
457
+ QScrollArea > QWidget > QWidget { background-color: transparent; }
458
+ QMessageBox { background-color: #1e1e2e; }
459
+ """
460
+
461
+ # ---------------------------------------------------------------------------
462
+ # Painter colour tokens
463
+ # For QPainter-based widgets that QSS cannot reach (canvas, ruler, etc.)
464
+ # ---------------------------------------------------------------------------
465
+
466
+ _DARK_COLORS: dict = {
467
+ "bg": "#1e1e2e",
468
+ "bg_empty": "#1e1e2e",
469
+ "empty_text": "#888888",
470
+ "backbone": "#cccccc",
471
+ "tick_minor": "#555555",
472
+ "tick_major": "#888888",
473
+ "tick_label": "#999999",
474
+ "title": "#eeeeee",
475
+ "ruler_line": "#555555",
476
+ "ruler_tick": "#888888",
477
+ "ruler_label": "#999999",
478
+ "sel_outline": "#ffffff",
479
+ "feat_label_sel": "#ffffff",
480
+ "seq_row_bg": "#181825",
481
+ "seq_text": "#cdd6f4",
482
+ }
483
+
484
+ _WHITE_COLORS: dict = {
485
+ "bg": "#eff1f5",
486
+ "bg_empty": "#eff1f5",
487
+ "empty_text": "#9ca0b0",
488
+ "backbone": "#4c4f69",
489
+ "tick_minor": "#bcc0cc",
490
+ "tick_major": "#9ca0b0",
491
+ "tick_label": "#6c6f85",
492
+ "title": "#4c4f69",
493
+ "ruler_line": "#bcc0cc",
494
+ "ruler_tick": "#9ca0b0",
495
+ "ruler_label": "#6c6f85",
496
+ "sel_outline": "#313244",
497
+ "feat_label_sel": "#1e1e2e",
498
+ "seq_row_bg": "#ffffff",
499
+ "seq_text": "#4c4f69",
500
+ }
501
+
502
+ # ---------------------------------------------------------------------------
503
+ # Public API
504
+ # ---------------------------------------------------------------------------
505
+
506
+ def current_theme() -> str:
507
+ """Return the name of the currently active theme ('dark' or 'white')."""
508
+ return _active
509
+
510
+
511
+ def painter_colors() -> dict:
512
+ """Return the colour-token dict for the active theme.
513
+
514
+ Use these values in QPainter-based widgets that cannot be reached by QSS.
515
+ Falls back to white palette tokens for the 'default' theme.
516
+ """
517
+ if _active == "dark":
518
+ return _DARK_COLORS
519
+ return _WHITE_COLORS
520
+
521
+
522
+ def apply_theme(app: QApplication, name: str, *, font_size: int | None = None) -> None:
523
+ """Apply a theme to *app*.
524
+
525
+ Parameters
526
+ ----------
527
+ app:
528
+ The QApplication instance to style.
529
+ name:
530
+ 'dark', 'white', or 'default'. 'default' restores the platform
531
+ appearance (clears QSS and resets the palette/style to what they were
532
+ before the first call to apply_theme). Any unrecognised value is
533
+ treated as 'default'.
534
+ font_size:
535
+ If given, set the application-wide font to this pixel size.
536
+ Omit to leave the font unchanged.
537
+ """
538
+ global _active, _proxy_style, _original_palette, _original_style_name
539
+
540
+ # Capture the pristine palette/style name on the very first call.
541
+ if _original_palette is None:
542
+ _original_palette = QPalette(app.palette())
543
+ if _original_style_name is None:
544
+ _original_style_name = app.style().objectName() # e.g. 'windowsvista'
545
+
546
+ name_lc = name.lower() if name else 'default'
547
+ _active = name_lc if name_lc in ('dark', 'white') else 'default'
548
+
549
+ # --- Default: restore platform appearance ---
550
+ if _active == 'default':
551
+ app.setStyleSheet('')
552
+ app.setPalette(_original_palette)
553
+ if _proxy_style is not None:
554
+ app.setStyle(_original_style_name)
555
+ return
556
+
557
+ # --- Dark / White: apply Fusion + Catppuccin ---
558
+ if _proxy_style is None:
559
+ _proxy_style = _FusionArrowFix('Fusion')
560
+ app.setStyle(_proxy_style)
561
+
562
+ if font_size is not None:
563
+ f = QFont(app.font())
564
+ f.setPixelSize(font_size)
565
+ app.setFont(f)
566
+
567
+ app.setStyleSheet(DARK_QSS if _active == 'dark' else WHITE_QSS)
568
+
569
+ CR = QPalette.ColorRole
570
+ pal = app.palette()
571
+ if _active == 'dark':
572
+ pal.setColor(CR.Window, QColor("#1e1e2e"))
573
+ pal.setColor(CR.WindowText, QColor("#cdd6f4"))
574
+ pal.setColor(CR.Base, QColor("#181825"))
575
+ pal.setColor(CR.AlternateBase, QColor("#1e1e2e"))
576
+ pal.setColor(CR.Text, QColor("#cdd6f4"))
577
+ pal.setColor(CR.Button, QColor("#313244"))
578
+ pal.setColor(CR.ButtonText, QColor("#cdd6f4"))
579
+ pal.setColor(CR.Mid, QColor("#45475a"))
580
+ pal.setColor(CR.Dark, QColor("#585b70"))
581
+ pal.setColor(CR.Highlight, QColor("#89b4fa"))
582
+ pal.setColor(CR.HighlightedText, QColor("#1e1e2e"))
583
+ pal.setColor(CR.ToolTipBase, QColor("#181825"))
584
+ pal.setColor(CR.ToolTipText, QColor("#cdd6f4"))
585
+ else:
586
+ pal.setColor(CR.Window, QColor("#eff1f5"))
587
+ pal.setColor(CR.WindowText, QColor("#4c4f69"))
588
+ pal.setColor(CR.Base, QColor("#ffffff"))
589
+ pal.setColor(CR.AlternateBase, QColor("#eff1f5"))
590
+ pal.setColor(CR.Text, QColor("#4c4f69"))
591
+ pal.setColor(CR.Button, QColor("#dce0e8"))
592
+ pal.setColor(CR.ButtonText, QColor("#4c4f69"))
593
+ pal.setColor(CR.Mid, QColor("#bcc0cc"))
594
+ pal.setColor(CR.Dark, QColor("#9ca0b0"))
595
+ pal.setColor(CR.Highlight, QColor("#1e66f5"))
596
+ pal.setColor(CR.HighlightedText, QColor("#ffffff"))
597
+ pal.setColor(CR.ToolTipBase, QColor("#ffffff"))
598
+ pal.setColor(CR.ToolTipText, QColor("#4c4f69"))
599
+ app.setPalette(pal)
600
+
601
+
602
+ def load_theme(settings_path: str | None = None, default: str = "dark") -> str:
603
+ """Load the saved theme name from a JSON settings file.
604
+
605
+ Parameters
606
+ ----------
607
+ settings_path:
608
+ Path to a JSON file that contains a ``"theme"`` key.
609
+ Pass None (or omit) to skip file I/O and just return *default*.
610
+ default:
611
+ Fallback value when the file is missing, unreadable, or has no
612
+ ``"theme"`` key. Must be ``'dark'`` or ``'white'``.
613
+ """
614
+ if settings_path is None:
615
+ return default
616
+ try:
617
+ with open(settings_path, "r", encoding="utf-8") as f:
618
+ data = json.load(f)
619
+ return data.get("theme", default)
620
+ except (FileNotFoundError, json.JSONDecodeError, OSError):
621
+ return default
622
+
623
+
624
+ def save_theme(name: str, settings_path: str | None = None) -> None:
625
+ """Persist *name* to a JSON settings file.
626
+
627
+ Parameters
628
+ ----------
629
+ name:
630
+ Theme name to save (``'dark'`` or ``'white'``).
631
+ settings_path:
632
+ Path to the JSON file. If None the call is a no-op.
633
+ """
634
+ if settings_path is None:
635
+ return
636
+ data: dict = {}
637
+ try:
638
+ with open(settings_path, "r", encoding="utf-8") as f:
639
+ data = json.load(f)
640
+ except (FileNotFoundError, json.JSONDecodeError, OSError):
641
+ pass
642
+ data["theme"] = name
643
+ try:
644
+ with open(settings_path, "w", encoding="utf-8") as f:
645
+ json.dump(data, f, indent=2)
646
+ except OSError:
647
+ pass
File without changes
@@ -0,0 +1,56 @@
1
+ from batoolset.settings.global_settings import set_UI
2
+ set_UI()
3
+ import os
4
+ from qtpy.QtCore import QTimer
5
+
6
+
7
+ class Blinker:
8
+ """
9
+ Utility class for blinking components by changing their background color to red temporarily.
10
+
11
+ Methods:
12
+ blink(component): Blinks the given component or a list of components.
13
+ reset_stylesheet(component): Resets the stylesheet of the given component or a list of components.
14
+
15
+ """
16
+
17
+ def __init__(self):
18
+ pass
19
+
20
+ def blink(self, component):
21
+ """
22
+ Blinks the given component or a list of components by changing their background color to red temporarily.
23
+
24
+ Args:
25
+ component (QWidget or list[QWidget]): The component or list of components to be blinked.
26
+
27
+ """
28
+ if isinstance(component, list):
29
+ for comp in component:
30
+ comp.setStyleSheet("background-color: red;")
31
+ else:
32
+ component.setStyleSheet("background-color: red;")
33
+
34
+ self.current_timer = QTimer()
35
+ self.current_timer.timeout.connect(lambda: self.reset_stylesheet(component))
36
+ self.current_timer.setSingleShot(True)
37
+ self.current_timer.start(3000)
38
+
39
+ def reset_stylesheet(self, component):
40
+ """
41
+ Resets the stylesheet of the given component or a list of components, restoring the original appearance.
42
+
43
+ Args:
44
+ component (QWidget or list[QWidget]): The component or list of components to reset the stylesheet.
45
+
46
+ """
47
+ if isinstance(component, list):
48
+ for comp in component:
49
+ comp.setStyleSheet('background-color: none;')
50
+ else:
51
+ component.setStyleSheet('background-color: none;')
52
+
53
+
54
+ if __name__ == '__main__':
55
+ blinker = Blinker()
56
+ # blinker.blink()