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.
- batoolset/GUI/__init__.py +0 -0
- batoolset/GUI/customdialog.py +136 -0
- batoolset/GUI/exception_handler.py +74 -0
- batoolset/GUI/list_gui.py +514 -0
- batoolset/GUI/multi_image_display.py +1038 -0
- batoolset/GUI/open.py +325 -0
- batoolset/GUI/overlay_hints.py +45 -0
- batoolset/GUI/paint2.py +1369 -0
- batoolset/GUI/scrollablepaint.py +1165 -0
- batoolset/dialogs/__init__.py +0 -0
- batoolset/dialogs/opensave.py +228 -0
- batoolset/drawings/__init__.py +0 -0
- batoolset/drawings/math_text.py +294 -0
- batoolset/drawings/pdf_svg_renderer.py +376 -0
- batoolset/drawings/shapes/Position.py +183 -0
- batoolset/drawings/shapes/__init__.py +0 -0
- batoolset/drawings/shapes/automated_alignment_drawing_refactored.py +346 -0
- batoolset/drawings/shapes/circle2d.py +99 -0
- batoolset/drawings/shapes/compute_optimal_size_all_merged.py +328 -0
- batoolset/drawings/shapes/ellipse2d.py +336 -0
- batoolset/drawings/shapes/freehand2d.py +235 -0
- batoolset/drawings/shapes/group.py +1985 -0
- batoolset/drawings/shapes/image2d.py +3157 -0
- batoolset/drawings/shapes/line2d.py +579 -0
- batoolset/drawings/shapes/point2d.py +257 -0
- batoolset/drawings/shapes/polygon2d.py +357 -0
- batoolset/drawings/shapes/polyline2d.py +235 -0
- batoolset/drawings/shapes/rect2d.py +388 -0
- batoolset/drawings/shapes/rectangle2d.py +601 -0
- batoolset/drawings/shapes/scalebar.py +310 -0
- batoolset/drawings/shapes/serializablefont.py +133 -0
- batoolset/drawings/shapes/serializableguiparams.py +14 -0
- batoolset/drawings/shapes/square2d.py +102 -0
- batoolset/drawings/shapes/test_publication_style.py +220 -0
- batoolset/drawings/shapes/textorientation.py +45 -0
- batoolset/drawings/shapes/tools.py +467 -0
- batoolset/drawings/shapes/transform2D.py +350 -0
- batoolset/drawings/shapes/txt2d.py +615 -0
- batoolset/drawings/svg_clip_renderer.py +247 -0
- batoolset/drawings/svg_embed.py +165 -0
- batoolset/drawings/svg_text_renderer.py +183 -0
- batoolset/drawings/widgets/__init__.py +0 -0
- batoolset/drawings/widgets/col.py +343 -0
- batoolset/drawings/widgets/image_group.py +248 -0
- batoolset/drawings/widgets/image_organizer.py +96 -0
- batoolset/drawings/widgets/paint.py +184 -0
- batoolset/drawings/widgets/vectorial.py +256 -0
- batoolset/figure/__init__.py +0 -0
- batoolset/figure/alignment.py +822 -0
- batoolset/figure/column.py +1117 -0
- batoolset/figure/fig_tools.py +285 -0
- batoolset/figure/minimal_debugger_scale_etc.py +139 -0
- batoolset/figure/panel.py +697 -0
- batoolset/figure/row.py +852 -0
- batoolset/figure/user_selection.py +287 -0
- batoolset/icons/__init__.py +0 -0
- batoolset/icons/tools.py +248 -0
- batoolset/luts/__init__.py +0 -0
- batoolset/luts/lut_minimal_test.py +1308 -0
- batoolset/luts/lutcreatorfromformula.py +209 -0
- batoolset/luts/luteditor.py +243 -0
- batoolset/pyqts/__init__.py +0 -0
- batoolset/pyqts/pleasewaitdialog.py +14 -0
- batoolset/pyqts/tools.py +760 -0
- batoolset/serializations/__init__.py +0 -0
- batoolset/serializations/tools.py +1338 -0
- batoolset/theme.py +647 -0
- batoolset/uis/__init__.py +0 -0
- batoolset/uis/blinker.py +56 -0
- batoolset_gui-0.2.0.dist-info/METADATA +54 -0
- batoolset_gui-0.2.0.dist-info/RECORD +74 -0
- batoolset_gui-0.2.0.dist-info/WHEEL +5 -0
- batoolset_gui-0.2.0.dist-info/licenses/LICENSE +24 -0
- 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
|
batoolset/uis/blinker.py
ADDED
|
@@ -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()
|