bec-widgets 1.17.2__py3-none-any.whl → 1.18.1__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.
- .gitlab-ci.yml +5 -6
- CHANGELOG.md +37 -0
- PKG-INFO +1 -4
- README.md +15 -19
- bec_widgets/cli/generate_cli.py +15 -6
- bec_widgets/examples/jupyter_console/jupyter_console_window.py +12 -0
- bec_widgets/qt_utils/round_frame.py +6 -4
- bec_widgets/qt_utils/side_panel.py +60 -79
- bec_widgets/widgets/control/device_input/signal_combobox/register_signal_combo_box.py +17 -0
- bec_widgets/widgets/control/device_input/signal_combobox/signal_combo_box.pyproject +1 -0
- bec_widgets/widgets/control/device_input/signal_combobox/signal_combo_box_plugin.py +54 -0
- bec_widgets/widgets/control/device_input/signal_line_edit/signal_line_edit_plugin.py +1 -1
- bec_widgets/widgets/plots_next_gen/plot_base.py +571 -0
- bec_widgets/widgets/plots_next_gen/setting_menus/__init__.py +0 -0
- bec_widgets/widgets/plots_next_gen/setting_menus/axis_settings.py +95 -0
- bec_widgets/widgets/plots_next_gen/setting_menus/axis_settings_horizontal.ui +256 -0
- bec_widgets/widgets/plots_next_gen/setting_menus/axis_settings_vertical.ui +240 -0
- bec_widgets/widgets/plots_next_gen/toolbar_bundles/__init__.py +0 -0
- bec_widgets/widgets/plots_next_gen/toolbar_bundles/mouse_interactions.py +88 -0
- bec_widgets/widgets/plots_next_gen/toolbar_bundles/plot_export.py +63 -0
- bec_widgets/widgets/plots_next_gen/toolbar_bundles/save_state.py +48 -0
- {bec_widgets-1.17.2.dist-info → bec_widgets-1.18.1.dist-info}/METADATA +1 -4
- {bec_widgets-1.17.2.dist-info → bec_widgets-1.18.1.dist-info}/RECORD +27 -15
- pyproject.toml +1 -2
- {bec_widgets-1.17.2.dist-info → bec_widgets-1.18.1.dist-info}/WHEEL +0 -0
- {bec_widgets-1.17.2.dist-info → bec_widgets-1.18.1.dist-info}/entry_points.txt +0 -0
- {bec_widgets-1.17.2.dist-info → bec_widgets-1.18.1.dist-info}/licenses/LICENSE +0 -0
.gitlab-ci.yml
CHANGED
@@ -78,9 +78,9 @@ formatter:
|
|
78
78
|
stage: Formatter
|
79
79
|
needs: []
|
80
80
|
script:
|
81
|
-
- pip install
|
82
|
-
- isort --check --diff ./
|
83
|
-
- black --check --diff --color ./
|
81
|
+
- pip install bec_lib[dev]
|
82
|
+
- isort --check --diff --line-length=100 --profile=black --multi-line=3 --trailing-comma ./
|
83
|
+
- black --check --diff --color --line-length=100 --skip-magic-trailing-comma ./
|
84
84
|
rules:
|
85
85
|
- if: $CI_PROJECT_PATH == "bec/bec_widgets"
|
86
86
|
|
@@ -148,7 +148,7 @@ tests:
|
|
148
148
|
- *clone-repos
|
149
149
|
- *install-os-packages
|
150
150
|
- *install-repos
|
151
|
-
- pip install -e .[dev,
|
151
|
+
- pip install -e .[dev,pyside6]
|
152
152
|
- coverage run --source=./bec_widgets -m pytest -v --junitxml=report.xml --maxfail=2 --random-order --full-trace ./tests/unit_tests
|
153
153
|
- coverage report
|
154
154
|
- coverage xml
|
@@ -172,7 +172,6 @@ test-matrix:
|
|
172
172
|
- "3.12"
|
173
173
|
QT_PCKG:
|
174
174
|
- "pyside6"
|
175
|
-
- "pyqt6"
|
176
175
|
|
177
176
|
stage: AdditionalTests
|
178
177
|
needs: []
|
@@ -211,7 +210,7 @@ end-2-end-conda:
|
|
211
210
|
- cd ../
|
212
211
|
- pip install -e ./ophyd_devices
|
213
212
|
|
214
|
-
- pip install -e .[dev,
|
213
|
+
- pip install -e .[dev,pyside6]
|
215
214
|
- cd ./tests/end-2-end
|
216
215
|
- pytest -v --start-servers --flush-redis --random-order
|
217
216
|
|
CHANGELOG.md
CHANGED
@@ -1,6 +1,43 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
3
|
|
4
|
+
## v1.18.1 (2025-01-30)
|
5
|
+
|
6
|
+
### Bug Fixes
|
7
|
+
|
8
|
+
- **signal_combo_box**: Added missing plugin modules for signal line_edit/combobox
|
9
|
+
([`db70442`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/db70442cc21247d20e6f6ad78ad0e1d3aca24bf7))
|
10
|
+
|
11
|
+
### Documentation
|
12
|
+
|
13
|
+
- Add screenshots for device and signal input
|
14
|
+
([`f0c4efe`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f0c4efefa03bf36ae57bf1a17f6a1b2e4d32c6c4))
|
15
|
+
|
16
|
+
|
17
|
+
## v1.18.0 (2025-01-30)
|
18
|
+
|
19
|
+
### Bug Fixes
|
20
|
+
|
21
|
+
- **generate_cli**: Widgets can be tagged with RPC=False, then they are excluded from client.py for
|
22
|
+
RPC
|
23
|
+
([`48fc63d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/48fc63d83e26889843b09b1eb4792612b53200ec))
|
24
|
+
|
25
|
+
### Build System
|
26
|
+
|
27
|
+
- Pyqt6 support dropped
|
28
|
+
([`a20935e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a20935e8625a9490e6c451a3b4012476e19317e5))
|
29
|
+
|
30
|
+
### Continuous Integration
|
31
|
+
|
32
|
+
- Fix formatter 2024 versions
|
33
|
+
([`4f8e683`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4f8e6835fe2312151dc2b40f0ab9eb50a9173f7c))
|
34
|
+
|
35
|
+
### Features
|
36
|
+
|
37
|
+
- **plot_base_next_gen**: New type of plot base inherited from QWidget
|
38
|
+
([`e7c9729`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/e7c97290cd783d19128625567835d7ae9a414989))
|
39
|
+
|
40
|
+
|
4
41
|
## v1.17.2 (2025-01-28)
|
5
42
|
|
6
43
|
### Bug Fixes
|
PKG-INFO
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: bec_widgets
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.18.1
|
4
4
|
Summary: BEC Widgets
|
5
5
|
Project-URL: Bug Tracker, https://gitlab.psi.ch/bec/bec_widgets/issues
|
6
6
|
Project-URL: Homepage, https://gitlab.psi.ch/bec/bec_widgets
|
@@ -28,8 +28,5 @@ Requires-Dist: pytest-random-order~=1.1; extra == 'dev'
|
|
28
28
|
Requires-Dist: pytest-timeout~=2.2; extra == 'dev'
|
29
29
|
Requires-Dist: pytest-xvfb~=3.0; extra == 'dev'
|
30
30
|
Requires-Dist: pytest~=8.0; extra == 'dev'
|
31
|
-
Provides-Extra: pyqt6
|
32
|
-
Requires-Dist: pyqt6-webengine>=6.7; extra == 'pyqt6'
|
33
|
-
Requires-Dist: pyqt6>=6.7; extra == 'pyqt6'
|
34
31
|
Provides-Extra: pyside6
|
35
32
|
Requires-Dist: pyside6==6.7.2; extra == 'pyside6'
|
README.md
CHANGED
@@ -1,12 +1,17 @@
|
|
1
1
|
# BEC Widgets
|
2
2
|
|
3
|
+
**⚠️ Important Notice:**
|
4
|
+
|
5
|
+
🚨 **PyQt6 is no longer supported** due to incompatibilities with Qt Designer. Please use **PySide6** instead. 🚨
|
6
|
+
|
3
7
|
BEC Widgets is a GUI framework designed for interaction with [BEC (Beamline Experiment Control)](https://gitlab.psi.ch/bec/bec).
|
8
|
+
|
4
9
|
## Installation
|
5
10
|
|
6
11
|
Use the package manager [pip](https://pip.pypa.io/en/stable/) to install BEC Widgets:
|
7
12
|
|
8
13
|
```bash
|
9
|
-
pip install bec_widgets
|
14
|
+
pip install bec_widgets[pyside6]
|
10
15
|
```
|
11
16
|
|
12
17
|
For development purposes, you can clone the repository and install the package locally in editable mode:
|
@@ -14,22 +19,12 @@ For development purposes, you can clone the repository and install the package l
|
|
14
19
|
```bash
|
15
20
|
git clone https://gitlab.psi.ch/bec/bec-widgets
|
16
21
|
cd bec_widgets
|
17
|
-
pip install -e .[dev,
|
22
|
+
pip install -e .[dev,pyside6]
|
18
23
|
```
|
19
24
|
|
20
|
-
BEC Widgets
|
21
|
-
|
22
|
-
|
23
|
-
To select a specific Python Qt distribution, install the package with an additional tag:
|
24
|
-
|
25
|
-
```bash
|
26
|
-
pip install bec_widgets[pyqt6]
|
27
|
-
```
|
28
|
-
or
|
25
|
+
BEC Widgets now **only supports PySide6**. Users must manually install PySide6 as no default Qt distribution is
|
26
|
+
specified.
|
29
27
|
|
30
|
-
```bash
|
31
|
-
pip install bec_widgets[pyside6]
|
32
|
-
```
|
33
28
|
## Documentation
|
34
29
|
|
35
30
|
Documentation of BEC Widgets can be found [here](https://bec-widgets.readthedocs.io/en/latest/). The documentation of the BEC can be found [here](https://bec.readthedocs.io/en/latest/).
|
@@ -39,7 +34,7 @@ Documentation of BEC Widgets can be found [here](https://bec-widgets.readthedocs
|
|
39
34
|
All commits should use the Angular commit scheme:
|
40
35
|
|
41
36
|
> #### <a name="commit-header"></a>Angular Commit Message Header
|
42
|
-
>
|
37
|
+
>
|
43
38
|
> ```
|
44
39
|
> <type>(<scope>): <short summary>
|
45
40
|
> │ │ │
|
@@ -53,13 +48,13 @@ All commits should use the Angular commit scheme:
|
|
53
48
|
> │
|
54
49
|
> └─⫸ Commit Type: build|ci|docs|feat|fix|perf|refactor|test
|
55
50
|
> ```
|
56
|
-
>
|
51
|
+
>
|
57
52
|
> The `<type>` and `<summary>` fields are mandatory, the `(<scope>)` field is optional.
|
58
53
|
|
59
54
|
> ##### Type
|
60
|
-
>
|
55
|
+
>
|
61
56
|
> Must be one of the following:
|
62
|
-
>
|
57
|
+
>
|
63
58
|
> * **build**: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
|
64
59
|
> * **ci**: Changes to our CI configuration files and scripts (examples: CircleCi, SauceLabs)
|
65
60
|
> * **docs**: Documentation only changes
|
@@ -71,4 +66,5 @@ All commits should use the Angular commit scheme:
|
|
71
66
|
|
72
67
|
## License
|
73
68
|
|
74
|
-
[BSD-3-Clause](https://choosealicense.com/licenses/bsd-3-clause/)
|
69
|
+
[BSD-3-Clause](https://choosealicense.com/licenses/bsd-3-clause/)
|
70
|
+
|
bec_widgets/cli/generate_cli.py
CHANGED
@@ -43,14 +43,21 @@ from bec_widgets.cli.rpc.rpc_base import RPCBase, rpc_call
|
|
43
43
|
|
44
44
|
def generate_client(self, class_container: BECClassContainer):
|
45
45
|
"""
|
46
|
-
Generate the client for the published classes
|
46
|
+
Generate the client for the published classes, skipping any classes
|
47
|
+
that have `RPC = False`.
|
47
48
|
|
48
49
|
Args:
|
49
50
|
class_container: The class container with the classes to generate the client for.
|
50
51
|
"""
|
51
|
-
|
52
|
+
# Filter out classes that explicitly have RPC=False
|
53
|
+
rpc_top_level_classes = [
|
54
|
+
cls for cls in class_container.rpc_top_level_classes if getattr(cls, "RPC", True)
|
55
|
+
]
|
52
56
|
rpc_top_level_classes.sort(key=lambda x: x.__name__)
|
53
|
-
|
57
|
+
|
58
|
+
connector_classes = [
|
59
|
+
cls for cls in class_container.connector_classes if getattr(cls, "RPC", True)
|
60
|
+
]
|
54
61
|
connector_classes.sort(key=lambda x: x.__name__)
|
55
62
|
|
56
63
|
self.write_client_enum(rpc_top_level_classes)
|
@@ -81,13 +88,13 @@ class Widgets(str, enum.Enum):
|
|
81
88
|
|
82
89
|
class_name = cls.__name__
|
83
90
|
|
84
|
-
|
85
|
-
if cls.__name__ == "BECDockArea":
|
91
|
+
if class_name == "BECDockArea":
|
86
92
|
self.content += f"""
|
87
93
|
class {class_name}(RPCBase):"""
|
88
94
|
else:
|
89
95
|
self.content += f"""
|
90
96
|
class {class_name}(RPCBase):"""
|
97
|
+
|
91
98
|
if not cls.USER_ACCESS:
|
92
99
|
self.content += """...
|
93
100
|
"""
|
@@ -100,8 +107,10 @@ class {class_name}(RPCBase):"""
|
|
100
107
|
method = method.split(".setter")[0]
|
101
108
|
if obj is None:
|
102
109
|
raise AttributeError(
|
103
|
-
f"Method {method} not found in class {cls.__name__}.
|
110
|
+
f"Method {method} not found in class {cls.__name__}. "
|
111
|
+
f"Please check the USER_ACCESS list."
|
104
112
|
)
|
113
|
+
|
105
114
|
if isinstance(obj, (property, QtProperty)):
|
106
115
|
# for the cli, we can map qt properties to regular properties
|
107
116
|
if is_property_setter:
|
@@ -20,6 +20,7 @@ from bec_widgets.widgets.containers.dock import BECDockArea
|
|
20
20
|
from bec_widgets.widgets.containers.figure import BECFigure
|
21
21
|
from bec_widgets.widgets.containers.layout_manager.layout_manager import LayoutManagerWidget
|
22
22
|
from bec_widgets.widgets.editors.jupyter_console.jupyter_console import BECJupyterConsole
|
23
|
+
from bec_widgets.widgets.plots_next_gen.plot_base import PlotBase
|
23
24
|
|
24
25
|
|
25
26
|
class JupyterConsoleWindow(QWidget): # pragma: no cover:
|
@@ -62,6 +63,8 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
|
|
62
63
|
"btn4": self.btn4,
|
63
64
|
"btn5": self.btn5,
|
64
65
|
"btn6": self.btn6,
|
66
|
+
"pb": self.pb,
|
67
|
+
"pi": self.pi,
|
65
68
|
}
|
66
69
|
)
|
67
70
|
|
@@ -92,6 +95,15 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
|
|
92
95
|
third_tab_layout.addWidget(self.lm)
|
93
96
|
tab_widget.addTab(third_tab, "Layout Manager Widget")
|
94
97
|
|
98
|
+
fourth_tab = QWidget()
|
99
|
+
fourth_tab_layout = QVBoxLayout(fourth_tab)
|
100
|
+
self.pb = PlotBase()
|
101
|
+
self.pi = self.pb.plot_item
|
102
|
+
fourth_tab_layout.addWidget(self.pb)
|
103
|
+
tab_widget.addTab(fourth_tab, "PltoBase")
|
104
|
+
|
105
|
+
tab_widget.setCurrentIndex(3)
|
106
|
+
|
95
107
|
group_box = QGroupBox("Jupyter Console", splitter)
|
96
108
|
group_box_layout = QVBoxLayout(group_box)
|
97
109
|
self.console = BECJupyterConsole(inprocess=True)
|
@@ -114,10 +114,12 @@ class RoundedFrame(BECWidget, QFrame):
|
|
114
114
|
|
115
115
|
# Apply axis label and tick colors
|
116
116
|
plot_item = self.content_widget.getPlotItem()
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
117
|
+
for axis in ["left", "right", "top", "bottom"]:
|
118
|
+
plot_item.getAxis(axis).setPen(pg.mkPen(color=axis_color))
|
119
|
+
plot_item.getAxis(axis).setTextPen(pg.mkPen(color=label_color))
|
120
|
+
|
121
|
+
# Change title color
|
122
|
+
plot_item.titleLabel.setText(plot_item.titleLabel.text, color=label_color)
|
121
123
|
|
122
124
|
# Apply border style via stylesheet
|
123
125
|
self.content_widget.setStyleSheet(
|
@@ -5,18 +5,18 @@ from qtpy.QtCore import Property, QEasingCurve, QPropertyAnimation
|
|
5
5
|
from qtpy.QtGui import QAction
|
6
6
|
from qtpy.QtWidgets import (
|
7
7
|
QApplication,
|
8
|
+
QFrame,
|
8
9
|
QHBoxLayout,
|
9
10
|
QLabel,
|
10
11
|
QMainWindow,
|
12
|
+
QScrollArea,
|
11
13
|
QSizePolicy,
|
12
|
-
QSpacerItem,
|
13
14
|
QStackedWidget,
|
14
15
|
QVBoxLayout,
|
15
16
|
QWidget,
|
16
17
|
)
|
17
18
|
|
18
19
|
from bec_widgets.qt_utils.toolbar import MaterialIconAction, ModularToolBar
|
19
|
-
from bec_widgets.widgets.plots.waveform.waveform_widget import BECWaveformWidget
|
20
20
|
|
21
21
|
|
22
22
|
class SidePanel(QWidget):
|
@@ -41,7 +41,6 @@ class SidePanel(QWidget):
|
|
41
41
|
self._panel_max_width = panel_max_width
|
42
42
|
self._animation_duration = animation_duration
|
43
43
|
self._animations_enabled = animations_enabled
|
44
|
-
self._orientation = orientation
|
45
44
|
|
46
45
|
self._panel_width = 0
|
47
46
|
self._panel_height = 0
|
@@ -71,6 +70,7 @@ class SidePanel(QWidget):
|
|
71
70
|
self.stack_widget = QStackedWidget()
|
72
71
|
self.stack_widget.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
|
73
72
|
self.stack_widget.setMinimumWidth(5)
|
73
|
+
self.stack_widget.setMaximumWidth(self._panel_max_width)
|
74
74
|
|
75
75
|
if self._orientation == "left":
|
76
76
|
self.main_layout.addWidget(self.toolbar)
|
@@ -80,7 +80,10 @@ class SidePanel(QWidget):
|
|
80
80
|
self.main_layout.addWidget(self.toolbar)
|
81
81
|
|
82
82
|
self.container.layout.addWidget(self.stack_widget)
|
83
|
-
|
83
|
+
|
84
|
+
self.menu_anim = QPropertyAnimation(self, b"panel_width")
|
85
|
+
self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
|
86
|
+
self.panel_width = 0 # start hidden
|
84
87
|
|
85
88
|
else:
|
86
89
|
self.main_layout = QVBoxLayout(self)
|
@@ -97,6 +100,7 @@ class SidePanel(QWidget):
|
|
97
100
|
self.stack_widget = QStackedWidget()
|
98
101
|
self.stack_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
99
102
|
self.stack_widget.setMinimumHeight(5)
|
103
|
+
self.stack_widget.setMaximumHeight(self._panel_max_width)
|
100
104
|
|
101
105
|
if self._orientation == "top":
|
102
106
|
self.main_layout.addWidget(self.toolbar)
|
@@ -106,74 +110,46 @@ class SidePanel(QWidget):
|
|
106
110
|
self.main_layout.addWidget(self.toolbar)
|
107
111
|
|
108
112
|
self.container.layout.addWidget(self.stack_widget)
|
109
|
-
self.stack_widget.setMaximumHeight(self._panel_max_width)
|
110
113
|
|
111
|
-
if self._orientation in ("left", "right"):
|
112
|
-
self.menu_anim = QPropertyAnimation(self, b"panel_width")
|
113
|
-
else:
|
114
114
|
self.menu_anim = QPropertyAnimation(self, b"panel_height")
|
115
|
+
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
116
|
+
self.panel_height = 0 # start hidden
|
115
117
|
|
116
118
|
self.menu_anim.setDuration(self._animation_duration)
|
117
119
|
self.menu_anim.setEasingCurve(QEasingCurve.InOutQuad)
|
118
120
|
|
119
|
-
if self._orientation in ("left", "right"):
|
120
|
-
self.panel_width = 0
|
121
|
-
else:
|
122
|
-
self.panel_height = 0
|
123
|
-
|
124
121
|
@Property(int)
|
125
122
|
def panel_width(self):
|
126
|
-
"""
|
127
|
-
Get the panel width.
|
128
|
-
"""
|
123
|
+
"""Get the panel width."""
|
129
124
|
return self._panel_width
|
130
125
|
|
131
126
|
@panel_width.setter
|
132
127
|
def panel_width(self, width: int):
|
133
|
-
"""
|
134
|
-
Set the panel width.
|
135
|
-
|
136
|
-
Args:
|
137
|
-
width(int): The width of the panel.
|
138
|
-
"""
|
128
|
+
"""Set the panel width."""
|
139
129
|
self._panel_width = width
|
140
130
|
if self._orientation in ("left", "right"):
|
141
131
|
self.stack_widget.setFixedWidth(width)
|
142
132
|
|
143
133
|
@Property(int)
|
144
134
|
def panel_height(self):
|
145
|
-
"""
|
146
|
-
Get the panel height.
|
147
|
-
"""
|
135
|
+
"""Get the panel height."""
|
148
136
|
return self._panel_height
|
149
137
|
|
150
138
|
@panel_height.setter
|
151
139
|
def panel_height(self, height: int):
|
152
|
-
"""
|
153
|
-
Set the panel height.
|
154
|
-
|
155
|
-
Args:
|
156
|
-
height(int): The height of the panel.
|
157
|
-
"""
|
140
|
+
"""Set the panel height."""
|
158
141
|
self._panel_height = height
|
159
142
|
if self._orientation in ("top", "bottom"):
|
160
143
|
self.stack_widget.setFixedHeight(height)
|
161
144
|
|
162
145
|
@Property(int)
|
163
146
|
def panel_max_width(self):
|
164
|
-
"""
|
165
|
-
Get the maximum width of the panel.
|
166
|
-
"""
|
147
|
+
"""Get the maximum width of the panel."""
|
167
148
|
return self._panel_max_width
|
168
149
|
|
169
150
|
@panel_max_width.setter
|
170
151
|
def panel_max_width(self, size: int):
|
171
|
-
"""
|
172
|
-
Set the maximum width of the panel.
|
173
|
-
|
174
|
-
Args:
|
175
|
-
size(int): The maximum width of the panel.
|
176
|
-
"""
|
152
|
+
"""Set the maximum width of the panel."""
|
177
153
|
self._panel_max_width = size
|
178
154
|
if self._orientation in ("left", "right"):
|
179
155
|
self.stack_widget.setMaximumWidth(self._panel_max_width)
|
@@ -182,45 +158,28 @@ class SidePanel(QWidget):
|
|
182
158
|
|
183
159
|
@Property(int)
|
184
160
|
def animation_duration(self):
|
185
|
-
"""
|
186
|
-
Get the duration of the animation.
|
187
|
-
"""
|
161
|
+
"""Get the duration of the animation."""
|
188
162
|
return self._animation_duration
|
189
163
|
|
190
164
|
@animation_duration.setter
|
191
165
|
def animation_duration(self, duration: int):
|
192
|
-
"""
|
193
|
-
Set the duration of the animation.
|
194
|
-
|
195
|
-
Args:
|
196
|
-
duration(int): The duration of the animation.
|
197
|
-
"""
|
166
|
+
"""Set the duration of the animation."""
|
198
167
|
self._animation_duration = duration
|
199
168
|
self.menu_anim.setDuration(duration)
|
200
169
|
|
201
170
|
@Property(bool)
|
202
171
|
def animations_enabled(self):
|
203
|
-
"""
|
204
|
-
Get the status of the animations.
|
205
|
-
"""
|
172
|
+
"""Get the status of the animations."""
|
206
173
|
return self._animations_enabled
|
207
174
|
|
208
175
|
@animations_enabled.setter
|
209
176
|
def animations_enabled(self, enabled: bool):
|
210
|
-
"""
|
211
|
-
Set the status of the animations.
|
212
|
-
|
213
|
-
Args:
|
214
|
-
enabled(bool): The status of the animations.
|
215
|
-
"""
|
177
|
+
"""Set the status of the animations."""
|
216
178
|
self._animations_enabled = enabled
|
217
179
|
|
218
180
|
def show_panel(self, idx: int):
|
219
181
|
"""
|
220
182
|
Show the side panel with animation and switch to idx.
|
221
|
-
|
222
|
-
Args:
|
223
|
-
idx(int): The index of the panel to show.
|
224
183
|
"""
|
225
184
|
self.stack_widget.setCurrentIndex(idx)
|
226
185
|
self.panel_visible = True
|
@@ -268,9 +227,6 @@ class SidePanel(QWidget):
|
|
268
227
|
def switch_to(self, idx: int):
|
269
228
|
"""
|
270
229
|
Switch to the specified index without animation.
|
271
|
-
|
272
|
-
Args:
|
273
|
-
idx(int): The index of the panel to switch to.
|
274
230
|
"""
|
275
231
|
if self.current_index != idx:
|
276
232
|
self.stack_widget.setCurrentIndex(idx)
|
@@ -287,20 +243,35 @@ class SidePanel(QWidget):
|
|
287
243
|
widget(QWidget): The widget to add to the panel.
|
288
244
|
title(str): The title of the panel.
|
289
245
|
"""
|
246
|
+
# container_widget: top-level container for the stacked page
|
290
247
|
container_widget = QWidget()
|
291
248
|
container_layout = QVBoxLayout(container_widget)
|
249
|
+
container_layout.setContentsMargins(0, 0, 0, 0)
|
250
|
+
container_layout.setSpacing(5)
|
251
|
+
|
292
252
|
title_label = QLabel(f"<b>{title}</b>")
|
293
253
|
title_label.setStyleSheet("font-size: 16px;")
|
294
|
-
spacer = QSpacerItem(0, 0, QSizePolicy.Minimum, QSizePolicy.Expanding)
|
295
254
|
container_layout.addWidget(title_label)
|
296
|
-
container_layout.addWidget(widget)
|
297
|
-
container_layout.addItem(spacer)
|
298
|
-
container_layout.setContentsMargins(5, 5, 5, 5)
|
299
|
-
container_layout.setSpacing(5)
|
300
255
|
|
256
|
+
# Create a QScrollArea for the actual widget to ensure scrolling if the widget inside is too large
|
257
|
+
scroll_area = QScrollArea()
|
258
|
+
scroll_area.setFrameShape(QFrame.NoFrame)
|
259
|
+
scroll_area.setWidgetResizable(True)
|
260
|
+
# Let the scroll area expand in both directions if there's room
|
261
|
+
scroll_area.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
262
|
+
scroll_area.setWidget(widget)
|
263
|
+
|
264
|
+
# Put the scroll area in the container layout
|
265
|
+
container_layout.addWidget(scroll_area)
|
266
|
+
|
267
|
+
# Optionally stretch the scroll area to fill vertical space
|
268
|
+
container_layout.setStretchFactor(scroll_area, 1)
|
269
|
+
|
270
|
+
# Add container_widget to the stacked widget
|
301
271
|
index = self.stack_widget.count()
|
302
272
|
self.stack_widget.addWidget(container_widget)
|
303
273
|
|
274
|
+
# Add an action to the toolbar
|
304
275
|
action = MaterialIconAction(icon_name=icon_name, tooltip=tooltip, checkable=True)
|
305
276
|
self.toolbar.add_action(action_id, action, target_widget=self)
|
306
277
|
|
@@ -328,6 +299,11 @@ class SidePanel(QWidget):
|
|
328
299
|
action.action.toggled.connect(on_action_toggled)
|
329
300
|
|
330
301
|
|
302
|
+
############################################
|
303
|
+
# DEMO APPLICATION
|
304
|
+
############################################
|
305
|
+
|
306
|
+
|
331
307
|
class ExampleApp(QMainWindow): # pragma: no cover
|
332
308
|
def __init__(self):
|
333
309
|
super().__init__()
|
@@ -335,20 +311,24 @@ class ExampleApp(QMainWindow): # pragma: no cover
|
|
335
311
|
|
336
312
|
central_widget = QWidget()
|
337
313
|
self.setCentralWidget(central_widget)
|
338
|
-
|
339
|
-
self.side_panel = SidePanel(self, orientation="left")
|
340
|
-
|
341
314
|
self.layout = QHBoxLayout(central_widget)
|
342
315
|
|
316
|
+
# Create side panel
|
317
|
+
self.side_panel = SidePanel(self, orientation="left", panel_max_width=250)
|
343
318
|
self.layout.addWidget(self.side_panel)
|
319
|
+
|
320
|
+
from bec_widgets.widgets.plots.waveform.waveform_widget import BECWaveformWidget
|
321
|
+
|
344
322
|
self.plot = BECWaveformWidget()
|
345
323
|
self.layout.addWidget(self.plot)
|
324
|
+
|
346
325
|
self.add_side_menus()
|
347
326
|
|
348
327
|
def add_side_menus(self):
|
349
328
|
widget1 = QWidget()
|
350
|
-
|
351
|
-
|
329
|
+
layout1 = QVBoxLayout(widget1)
|
330
|
+
for i in range(15):
|
331
|
+
layout1.addWidget(QLabel(f"Widget 1 label row {i}"))
|
352
332
|
self.side_panel.add_menu(
|
353
333
|
action_id="widget1",
|
354
334
|
icon_name="counter_1",
|
@@ -358,8 +338,8 @@ class ExampleApp(QMainWindow): # pragma: no cover
|
|
358
338
|
)
|
359
339
|
|
360
340
|
widget2 = QWidget()
|
361
|
-
|
362
|
-
|
341
|
+
layout2 = QVBoxLayout(widget2)
|
342
|
+
layout2.addWidget(QLabel("Short widget 2 content"))
|
363
343
|
self.side_panel.add_menu(
|
364
344
|
action_id="widget2",
|
365
345
|
icon_name="counter_2",
|
@@ -369,8 +349,9 @@ class ExampleApp(QMainWindow): # pragma: no cover
|
|
369
349
|
)
|
370
350
|
|
371
351
|
widget3 = QWidget()
|
372
|
-
|
373
|
-
|
352
|
+
layout3 = QVBoxLayout(widget3)
|
353
|
+
for i in range(10):
|
354
|
+
layout3.addWidget(QLabel(f"Line {i} for Widget 3"))
|
374
355
|
self.side_panel.add_menu(
|
375
356
|
action_id="widget3",
|
376
357
|
icon_name="counter_3",
|
@@ -383,6 +364,6 @@ class ExampleApp(QMainWindow): # pragma: no cover
|
|
383
364
|
if __name__ == "__main__": # pragma: no cover
|
384
365
|
app = QApplication(sys.argv)
|
385
366
|
window = ExampleApp()
|
386
|
-
window.resize(
|
367
|
+
window.resize(1000, 700)
|
387
368
|
window.show()
|
388
369
|
sys.exit(app.exec())
|
@@ -0,0 +1,17 @@
|
|
1
|
+
def main(): # pragma: no cover
|
2
|
+
from qtpy import PYSIDE6
|
3
|
+
|
4
|
+
if not PYSIDE6:
|
5
|
+
print("PYSIDE6 is not available in the environment. Cannot patch designer.")
|
6
|
+
return
|
7
|
+
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
8
|
+
|
9
|
+
from bec_widgets.widgets.control.device_input.signal_combobox.signal_combo_box_plugin import (
|
10
|
+
SignalComboBoxPlugin,
|
11
|
+
)
|
12
|
+
|
13
|
+
QPyDesignerCustomWidgetCollection.addCustomWidget(SignalComboBoxPlugin())
|
14
|
+
|
15
|
+
|
16
|
+
if __name__ == "__main__": # pragma: no cover
|
17
|
+
main()
|
@@ -0,0 +1 @@
|
|
1
|
+
{'files': ['signal_combobox.py']}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# Copyright (C) 2022 The Qt Company Ltd.
|
2
|
+
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
3
|
+
|
4
|
+
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
|
5
|
+
|
6
|
+
from bec_widgets.utils.bec_designer import designer_material_icon
|
7
|
+
from bec_widgets.widgets.control.device_input.signal_combobox.signal_combobox import SignalComboBox
|
8
|
+
|
9
|
+
DOM_XML = """
|
10
|
+
<ui language='c++'>
|
11
|
+
<widget class='SignalComboBox' name='signal_combo_box'>
|
12
|
+
</widget>
|
13
|
+
</ui>
|
14
|
+
"""
|
15
|
+
|
16
|
+
|
17
|
+
class SignalComboBoxPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
|
18
|
+
def __init__(self):
|
19
|
+
super().__init__()
|
20
|
+
self._form_editor = None
|
21
|
+
|
22
|
+
def createWidget(self, parent):
|
23
|
+
t = SignalComboBox(parent)
|
24
|
+
return t
|
25
|
+
|
26
|
+
def domXml(self):
|
27
|
+
return DOM_XML
|
28
|
+
|
29
|
+
def group(self):
|
30
|
+
return "BEC Input Widgets"
|
31
|
+
|
32
|
+
def icon(self):
|
33
|
+
return designer_material_icon(SignalComboBox.ICON_NAME)
|
34
|
+
|
35
|
+
def includeFile(self):
|
36
|
+
return "signal_combo_box"
|
37
|
+
|
38
|
+
def initialize(self, form_editor):
|
39
|
+
self._form_editor = form_editor
|
40
|
+
|
41
|
+
def isContainer(self):
|
42
|
+
return False
|
43
|
+
|
44
|
+
def isInitialized(self):
|
45
|
+
return self._form_editor is not None
|
46
|
+
|
47
|
+
def name(self):
|
48
|
+
return "SignalComboBox"
|
49
|
+
|
50
|
+
def toolTip(self):
|
51
|
+
return "Signal ComboBox Example for BEC Widgets with autocomplete."
|
52
|
+
|
53
|
+
def whatsThis(self):
|
54
|
+
return self.toolTip()
|
@@ -50,7 +50,7 @@ class SignalLineEditPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
|
|
50
50
|
return "SignalLineEdit"
|
51
51
|
|
52
52
|
def toolTip(self):
|
53
|
-
return ""
|
53
|
+
return "Signal LineEdit Example for BEC Widgets with autocomplete."
|
54
54
|
|
55
55
|
def whatsThis(self):
|
56
56
|
return self.toolTip()
|