bec-widgets 2.14.0__py3-none-any.whl → 2.15.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.
- CHANGELOG.md +24 -0
- PKG-INFO +1 -1
- bec_widgets/widgets/containers/main_window/addons/scroll_label.py +89 -0
- bec_widgets/widgets/containers/main_window/main_window.py +94 -20
- {bec_widgets-2.14.0.dist-info → bec_widgets-2.15.0.dist-info}/METADATA +1 -1
- {bec_widgets-2.14.0.dist-info → bec_widgets-2.15.0.dist-info}/RECORD +10 -9
- pyproject.toml +1 -1
- {bec_widgets-2.14.0.dist-info → bec_widgets-2.15.0.dist-info}/WHEEL +0 -0
- {bec_widgets-2.14.0.dist-info → bec_widgets-2.15.0.dist-info}/entry_points.txt +0 -0
- {bec_widgets-2.14.0.dist-info → bec_widgets-2.15.0.dist-info}/licenses/LICENSE +0 -0
CHANGELOG.md
CHANGED
@@ -1,6 +1,30 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
3
|
|
4
|
+
## v2.15.0 (2025-06-15)
|
5
|
+
|
6
|
+
### Bug Fixes
|
7
|
+
|
8
|
+
- **main_window**: Central widget cleanup check to not remove None
|
9
|
+
([`644be62`](https://github.com/bec-project/bec_widgets/commit/644be621f20cf09037da763f6217df9d1e4642bc))
|
10
|
+
|
11
|
+
### Features
|
12
|
+
|
13
|
+
- **main_window**: Main window can display the messages from the send_client_info as a scrolling
|
14
|
+
horizontal text; closes #700
|
15
|
+
([`0dec78a`](https://github.com/bec-project/bec_widgets/commit/0dec78afbaddbef98d20949d3a0ba4e0dc8529df))
|
16
|
+
|
17
|
+
### Refactoring
|
18
|
+
|
19
|
+
- **main_window**: App id is displayed as QLabel instead of message
|
20
|
+
([`57b9a57`](https://github.com/bec-project/bec_widgets/commit/57b9a57a631f267a8cb3622bf73035ffb15510e6))
|
21
|
+
|
22
|
+
### Testing
|
23
|
+
|
24
|
+
- **main_window**: Becmainwindow tests extended
|
25
|
+
([`30acc4c`](https://github.com/bec-project/bec_widgets/commit/30acc4c236bfbfed19f56512b264a52b4359e6c1))
|
26
|
+
|
27
|
+
|
4
28
|
## v2.14.0 (2025-06-13)
|
5
29
|
|
6
30
|
### Features
|
PKG-INFO
CHANGED
@@ -0,0 +1,89 @@
|
|
1
|
+
from qtpy.QtCore import QTimer
|
2
|
+
from qtpy.QtGui import QFontMetrics, QPainter
|
3
|
+
from qtpy.QtWidgets import QLabel
|
4
|
+
|
5
|
+
|
6
|
+
class ScrollLabel(QLabel):
|
7
|
+
"""A QLabel that scrolls its text horizontally across the widget."""
|
8
|
+
|
9
|
+
def __init__(self, parent=None, speed_ms=30, step_px=1, delay_ms=2000):
|
10
|
+
super().__init__(parent=parent)
|
11
|
+
self._offset = 0
|
12
|
+
self._text_width = 0
|
13
|
+
|
14
|
+
# scrolling timer (runs continuously once started)
|
15
|
+
self._timer = QTimer(self)
|
16
|
+
self._timer.setInterval(speed_ms)
|
17
|
+
self._timer.timeout.connect(self._scroll)
|
18
|
+
|
19
|
+
# delay‑before‑scroll timer (single‑shot)
|
20
|
+
self._delay_timer = QTimer(self)
|
21
|
+
self._delay_timer.setSingleShot(True)
|
22
|
+
self._delay_timer.setInterval(delay_ms)
|
23
|
+
self._delay_timer.timeout.connect(self._timer.start)
|
24
|
+
|
25
|
+
self._step_px = step_px
|
26
|
+
|
27
|
+
def setText(self, text):
|
28
|
+
super().setText(text)
|
29
|
+
fm = QFontMetrics(self.font())
|
30
|
+
self._text_width = fm.horizontalAdvance(text)
|
31
|
+
self._offset = 0
|
32
|
+
self._update_timer()
|
33
|
+
|
34
|
+
def resizeEvent(self, event):
|
35
|
+
super().resizeEvent(event)
|
36
|
+
self._update_timer()
|
37
|
+
|
38
|
+
def _update_timer(self):
|
39
|
+
"""
|
40
|
+
Decide whether to start or stop scrolling.
|
41
|
+
|
42
|
+
If the text is wider than the visible area, start a single‑shot
|
43
|
+
delay timer (2s by default). Scrolling begins only after this
|
44
|
+
delay. Any change (resize or new text) restarts the logic.
|
45
|
+
"""
|
46
|
+
needs_scroll = self._text_width > self.width()
|
47
|
+
|
48
|
+
if needs_scroll:
|
49
|
+
if self._timer.isActive():
|
50
|
+
self._timer.stop()
|
51
|
+
self._offset = 0
|
52
|
+
if not self._delay_timer.isActive():
|
53
|
+
self._delay_timer.start()
|
54
|
+
else:
|
55
|
+
if self._delay_timer.isActive():
|
56
|
+
self._delay_timer.stop()
|
57
|
+
if self._timer.isActive():
|
58
|
+
self._timer.stop()
|
59
|
+
self.update()
|
60
|
+
|
61
|
+
def _scroll(self):
|
62
|
+
self._offset += self._step_px
|
63
|
+
if self._offset >= self._text_width:
|
64
|
+
self._offset = 0
|
65
|
+
self.update()
|
66
|
+
|
67
|
+
def paintEvent(self, event):
|
68
|
+
painter = QPainter(self)
|
69
|
+
painter.setRenderHint(QPainter.TextAntialiasing)
|
70
|
+
text = self.text()
|
71
|
+
if not text:
|
72
|
+
return
|
73
|
+
fm = QFontMetrics(self.font())
|
74
|
+
y = (self.height() + fm.ascent() - fm.descent()) // 2
|
75
|
+
if self._text_width <= self.width():
|
76
|
+
painter.drawText(0, y, text)
|
77
|
+
else:
|
78
|
+
x = -self._offset
|
79
|
+
gap = 50 # space between repeating text blocks
|
80
|
+
while x < self.width():
|
81
|
+
painter.drawText(x, y, text)
|
82
|
+
x += self._text_width + gap
|
83
|
+
|
84
|
+
def cleanup(self):
|
85
|
+
"""Stop all timers to prevent memory leaks."""
|
86
|
+
if self._timer.isActive():
|
87
|
+
self._timer.stop()
|
88
|
+
if self._delay_timer.isActive():
|
89
|
+
self._delay_timer.stop()
|
@@ -1,16 +1,17 @@
|
|
1
1
|
import os
|
2
2
|
|
3
|
-
from
|
3
|
+
from bec_lib.endpoints import MessageEndpoints
|
4
|
+
from qtpy.QtCore import QEvent, QSize, Qt
|
4
5
|
from qtpy.QtGui import QAction, QActionGroup, QIcon
|
5
|
-
from qtpy.QtWidgets import QApplication, QMainWindow, QStyle
|
6
|
+
from qtpy.QtWidgets import QApplication, QFrame, QLabel, QMainWindow, QStyle, QVBoxLayout, QWidget
|
6
7
|
|
7
8
|
import bec_widgets
|
8
9
|
from bec_widgets.utils import UILoader
|
9
10
|
from bec_widgets.utils.bec_widget import BECWidget
|
10
11
|
from bec_widgets.utils.colors import apply_theme
|
11
|
-
from bec_widgets.utils.container_utils import WidgetContainerUtils
|
12
12
|
from bec_widgets.utils.error_popups import SafeSlot
|
13
13
|
from bec_widgets.utils.widget_io import WidgetHierarchy
|
14
|
+
from bec_widgets.widgets.containers.main_window.addons.scroll_label import ScrollLabel
|
14
15
|
from bec_widgets.widgets.containers.main_window.addons.web_links import BECWebLinksMixin
|
15
16
|
|
16
17
|
MODULE_PATH = os.path.dirname(bec_widgets.__file__)
|
@@ -36,6 +37,14 @@ class BECMainWindow(BECWidget, QMainWindow):
|
|
36
37
|
self._init_ui()
|
37
38
|
self._connect_to_theme_change()
|
38
39
|
|
40
|
+
# Connections to BEC Notifications
|
41
|
+
self.bec_dispatcher.connect_slot(
|
42
|
+
self.display_client_message, MessageEndpoints.client_info()
|
43
|
+
)
|
44
|
+
|
45
|
+
################################################################################
|
46
|
+
# MainWindow Elements Initialization
|
47
|
+
################################################################################
|
39
48
|
def _init_ui(self):
|
40
49
|
|
41
50
|
# Set the icon
|
@@ -43,40 +52,72 @@ class BECMainWindow(BECWidget, QMainWindow):
|
|
43
52
|
|
44
53
|
# Set Menu and Status bar
|
45
54
|
self._setup_menu_bar()
|
55
|
+
self._init_status_bar_widgets()
|
46
56
|
|
47
57
|
# BEC Specific UI
|
48
58
|
self.display_app_id()
|
49
59
|
|
60
|
+
def _init_status_bar_widgets(self):
|
61
|
+
"""
|
62
|
+
Prepare the BEC specific widgets in the status bar.
|
63
|
+
"""
|
64
|
+
status_bar = self.statusBar()
|
65
|
+
|
66
|
+
# Left: App‑ID label
|
67
|
+
self._app_id_label = QLabel()
|
68
|
+
self._app_id_label.setAlignment(
|
69
|
+
Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter
|
70
|
+
)
|
71
|
+
status_bar.addWidget(self._app_id_label)
|
72
|
+
|
73
|
+
# Add a separator after the app ID label
|
74
|
+
self._add_separator()
|
75
|
+
|
76
|
+
# Centre: Client‑info label (stretch=1 so it expands)
|
77
|
+
self._client_info_label = ScrollLabel()
|
78
|
+
self._client_info_label.setAlignment(
|
79
|
+
Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignVCenter
|
80
|
+
)
|
81
|
+
status_bar.addWidget(self._client_info_label, 1)
|
82
|
+
|
83
|
+
def _add_separator(self):
|
84
|
+
"""
|
85
|
+
Add a vertically centred separator to the status bar.
|
86
|
+
"""
|
87
|
+
status_bar = self.statusBar()
|
88
|
+
|
89
|
+
# The actual line
|
90
|
+
line = QFrame()
|
91
|
+
line.setFrameShape(QFrame.VLine)
|
92
|
+
line.setFrameShadow(QFrame.Sunken)
|
93
|
+
line.setFixedHeight(status_bar.sizeHint().height() - 2)
|
94
|
+
|
95
|
+
# Wrapper to center the line vertically -> work around for QFrame not being able to center itself
|
96
|
+
wrapper = QWidget()
|
97
|
+
vbox = QVBoxLayout(wrapper)
|
98
|
+
vbox.setContentsMargins(0, 0, 0, 0)
|
99
|
+
vbox.addStretch()
|
100
|
+
vbox.addWidget(line, alignment=Qt.AlignHCenter)
|
101
|
+
vbox.addStretch()
|
102
|
+
wrapper.setFixedWidth(line.sizeHint().width())
|
103
|
+
|
104
|
+
status_bar.addWidget(wrapper)
|
105
|
+
|
50
106
|
def _init_bec_icon(self):
|
51
107
|
icon = self.app.windowIcon()
|
52
108
|
if icon.isNull():
|
53
|
-
print("No icon is set, setting default icon")
|
54
109
|
icon = QIcon()
|
55
110
|
icon.addFile(
|
56
111
|
os.path.join(MODULE_PATH, "assets", "app_icons", "bec_widgets_icon.png"),
|
57
112
|
size=QSize(48, 48),
|
58
113
|
)
|
59
114
|
self.app.setWindowIcon(icon)
|
60
|
-
else:
|
61
|
-
print("An icon is set")
|
62
115
|
|
63
116
|
def load_ui(self, ui_file):
|
64
117
|
loader = UILoader(self)
|
65
118
|
self.ui = loader.loader(ui_file)
|
66
119
|
self.setCentralWidget(self.ui)
|
67
120
|
|
68
|
-
def display_app_id(self):
|
69
|
-
"""
|
70
|
-
Display the app ID in the status bar.
|
71
|
-
"""
|
72
|
-
if self.bec_dispatcher.cli_server is None:
|
73
|
-
status_message = "Not connected"
|
74
|
-
else:
|
75
|
-
# Get the server ID from the dispatcher
|
76
|
-
server_id = self.bec_dispatcher.cli_server.gui_id
|
77
|
-
status_message = f"App ID: {server_id}"
|
78
|
-
self.statusBar().showMessage(status_message)
|
79
|
-
|
80
121
|
def _fetch_theme(self) -> str:
|
81
122
|
return self.app.theme.theme
|
82
123
|
|
@@ -164,8 +205,37 @@ class BECMainWindow(BECWidget, QMainWindow):
|
|
164
205
|
help_menu.addAction(widgets_docs)
|
165
206
|
help_menu.addAction(bug_report)
|
166
207
|
|
208
|
+
################################################################################
|
209
|
+
# Status Bar Addons
|
210
|
+
################################################################################
|
211
|
+
def display_app_id(self):
|
212
|
+
"""
|
213
|
+
Display the app ID in the status bar.
|
214
|
+
"""
|
215
|
+
if self.bec_dispatcher.cli_server is None:
|
216
|
+
status_message = "Not connected"
|
217
|
+
else:
|
218
|
+
# Get the server ID from the dispatcher
|
219
|
+
server_id = self.bec_dispatcher.cli_server.gui_id
|
220
|
+
status_message = f"App ID: {server_id}"
|
221
|
+
self._app_id_label.setText(status_message)
|
222
|
+
|
223
|
+
@SafeSlot(dict, dict)
|
224
|
+
def display_client_message(self, msg: dict, meta: dict):
|
225
|
+
message = msg.get("message", "")
|
226
|
+
self._client_info_label.setText(message)
|
227
|
+
|
228
|
+
################################################################################
|
229
|
+
# General and Cleanup Methods
|
230
|
+
################################################################################
|
167
231
|
@SafeSlot(str)
|
168
232
|
def change_theme(self, theme: str):
|
233
|
+
"""
|
234
|
+
Change the theme of the application.
|
235
|
+
|
236
|
+
Args:
|
237
|
+
theme(str): The theme to apply, either "light" or "dark".
|
238
|
+
"""
|
169
239
|
apply_theme(theme)
|
170
240
|
|
171
241
|
def event(self, event):
|
@@ -175,8 +245,9 @@ class BECMainWindow(BECWidget, QMainWindow):
|
|
175
245
|
|
176
246
|
def cleanup(self):
|
177
247
|
central_widget = self.centralWidget()
|
178
|
-
central_widget
|
179
|
-
|
248
|
+
if central_widget is not None:
|
249
|
+
central_widget.close()
|
250
|
+
central_widget.deleteLater()
|
180
251
|
if not isinstance(central_widget, BECWidget):
|
181
252
|
# if the central widget is not a BECWidget, we need to call the cleanup method
|
182
253
|
# of all widgets whose parent is the current BECMainWindow
|
@@ -187,6 +258,9 @@ class BECMainWindow(BECWidget, QMainWindow):
|
|
187
258
|
child.cleanup()
|
188
259
|
child.close()
|
189
260
|
child.deleteLater()
|
261
|
+
|
262
|
+
# Status bar widgets cleanup
|
263
|
+
self._client_info_label.cleanup()
|
190
264
|
super().cleanup()
|
191
265
|
|
192
266
|
|
@@ -2,11 +2,11 @@
|
|
2
2
|
.gitlab-ci.yml,sha256=1nMYldzVk0tFkBWYTcUjumOrdSADASheWOAc0kOFDYs,9509
|
3
3
|
.pylintrc,sha256=eeY8YwSI74oFfq6IYIbCqnx3Vk8ZncKaatv96n_Y8Rs,18544
|
4
4
|
.readthedocs.yaml,sha256=ivqg3HTaOxNbEW3bzWh9MXAkrekuGoNdj0Mj3SdRYuw,639
|
5
|
-
CHANGELOG.md,sha256=
|
5
|
+
CHANGELOG.md,sha256=n_bLGhoWteR3IlmPcheOc1NmwwuA9YT-U8wH9qdqvTc,300622
|
6
6
|
LICENSE,sha256=Daeiu871NcAp8uYi4eB_qHgvypG-HX0ioRQyQxFwjeg,1531
|
7
|
-
PKG-INFO,sha256=
|
7
|
+
PKG-INFO,sha256=UG0NtEIGEXUQb9P-vMk67mOQHLGP06_1SRQ3Hdf8D0U,1252
|
8
8
|
README.md,sha256=oY5Jc1uXehRASuwUJ0umin2vfkFh7tHF-LLruHTaQx0,3560
|
9
|
-
pyproject.toml,sha256=
|
9
|
+
pyproject.toml,sha256=A3AL_HaiNQUaOAmO1eBS6lIrkDjfJb8DIP-BwuqMeLw,2827
|
10
10
|
.git_hooks/pre-commit,sha256=n3RofIZHJl8zfJJIUomcMyYGFi_rwq4CC19z0snz3FI,286
|
11
11
|
.github/pull_request_template.md,sha256=F_cJXzooWMFgMGtLK-7KeGcQt0B4AYFse5oN0zQ9p6g,801
|
12
12
|
.github/ISSUE_TEMPLATE/bug_report.yml,sha256=WdRnt7HGxvsIBLzhkaOWNfg8IJQYa_oV9_F08Ym6znQ,1081
|
@@ -121,8 +121,9 @@ bec_widgets/widgets/containers/dock/register_dock_area.py,sha256=L7BL4qknCjtqsDP
|
|
121
121
|
bec_widgets/widgets/containers/layout_manager/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
122
122
|
bec_widgets/widgets/containers/layout_manager/layout_manager.py,sha256=V7s8mtB3VLPstyGVaR9YKcoTVlfMMOYNpIJUsw2WQVc,35198
|
123
123
|
bec_widgets/widgets/containers/main_window/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
124
|
-
bec_widgets/widgets/containers/main_window/main_window.py,sha256=
|
124
|
+
bec_widgets/widgets/containers/main_window/main_window.py,sha256=vp3KiHg0uCA2z9JeeV1ArFNYAdrWRWqDKhct8AOab4g,9169
|
125
125
|
bec_widgets/widgets/containers/main_window/addons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
126
|
+
bec_widgets/widgets/containers/main_window/addons/scroll_label.py,sha256=ERZVE5J4uRyuizf9nbvnHAJZo2xGaVmgLBlrJaYpVSg,2943
|
126
127
|
bec_widgets/widgets/containers/main_window/addons/web_links.py,sha256=d5OgzgI9zb-NAC0pOGanOtJX3nZoe4x8QuQTw-_hK_8,434
|
127
128
|
bec_widgets/widgets/control/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
128
129
|
bec_widgets/widgets/control/buttons/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -408,8 +409,8 @@ bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.py,sha256=O
|
|
408
409
|
bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.pyproject,sha256=Lbi9zb6HNlIq14k6hlzR-oz6PIFShBuF7QxE6d87d64,34
|
409
410
|
bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button_plugin.py,sha256=CzChz2SSETYsR8-36meqWnsXCT-FIy_J_xeU5coWDY8,1350
|
410
411
|
bec_widgets/widgets/utility/visual/dark_mode_button/register_dark_mode_button.py,sha256=rMpZ1CaoucwobgPj1FuKTnt07W82bV1GaSYdoqcdMb8,521
|
411
|
-
bec_widgets-2.
|
412
|
-
bec_widgets-2.
|
413
|
-
bec_widgets-2.
|
414
|
-
bec_widgets-2.
|
415
|
-
bec_widgets-2.
|
412
|
+
bec_widgets-2.15.0.dist-info/METADATA,sha256=UG0NtEIGEXUQb9P-vMk67mOQHLGP06_1SRQ3Hdf8D0U,1252
|
413
|
+
bec_widgets-2.15.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
414
|
+
bec_widgets-2.15.0.dist-info/entry_points.txt,sha256=dItMzmwA1wizJ1Itx15qnfJ0ZzKVYFLVJ1voxT7K7D4,214
|
415
|
+
bec_widgets-2.15.0.dist-info/licenses/LICENSE,sha256=Daeiu871NcAp8uYi4eB_qHgvypG-HX0ioRQyQxFwjeg,1531
|
416
|
+
bec_widgets-2.15.0.dist-info/RECORD,,
|
pyproject.toml
CHANGED
File without changes
|
File without changes
|
File without changes
|