bec-widgets 0.69.0__py3-none-any.whl → 0.71.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 (39) hide show
  1. CHANGELOG.md +64 -87
  2. PKG-INFO +1 -1
  3. bec_widgets/cli/client.py +19 -0
  4. bec_widgets/examples/plugin_example_pyside/__init__.py +0 -0
  5. bec_widgets/examples/plugin_example_pyside/main.py +17 -0
  6. bec_widgets/examples/plugin_example_pyside/registertictactoe.py +12 -0
  7. bec_widgets/examples/plugin_example_pyside/taskmenuextension.pyproject +4 -0
  8. bec_widgets/examples/plugin_example_pyside/tictactoe.py +135 -0
  9. bec_widgets/examples/plugin_example_pyside/tictactoeplugin.py +68 -0
  10. bec_widgets/examples/plugin_example_pyside/tictactoetaskmenu.py +67 -0
  11. bec_widgets/utils/bec_designer.py +87 -0
  12. bec_widgets/utils/widget_io.py +18 -2
  13. bec_widgets/widgets/device_inputs/device_combobox/device_combobox.py +10 -13
  14. bec_widgets/widgets/device_inputs/device_combobox/device_combobox.pyproject +4 -0
  15. bec_widgets/widgets/device_inputs/device_combobox/device_combobox_plugin.py +54 -0
  16. bec_widgets/widgets/device_inputs/device_combobox/launch_device_combobox.py +11 -0
  17. bec_widgets/widgets/device_inputs/device_combobox/register_device_combobox.py +17 -0
  18. bec_widgets/widgets/device_inputs/device_input_base.py +5 -2
  19. bec_widgets/widgets/device_inputs/device_line_edit/device_line_edit.py +16 -14
  20. bec_widgets/widgets/device_inputs/device_line_edit/device_line_edit.pyproject +4 -0
  21. bec_widgets/widgets/device_inputs/device_line_edit/device_line_edit_plugin.py +54 -0
  22. bec_widgets/widgets/device_inputs/device_line_edit/launch_device_line_edit.py +11 -0
  23. bec_widgets/widgets/device_inputs/device_line_edit/register_device_line_edit.py +17 -0
  24. bec_widgets/widgets/scan_control/scan_control.py +133 -365
  25. bec_widgets/widgets/scan_control/scan_group_box.py +223 -0
  26. {bec_widgets-0.69.0.dist-info → bec_widgets-0.71.0.dist-info}/METADATA +1 -1
  27. {bec_widgets-0.69.0.dist-info → bec_widgets-0.71.0.dist-info}/RECORD +39 -18
  28. {bec_widgets-0.69.0.dist-info → bec_widgets-0.71.0.dist-info}/WHEEL +1 -1
  29. {bec_widgets-0.69.0.dist-info → bec_widgets-0.71.0.dist-info}/entry_points.txt +1 -0
  30. docs/user/widgets/bec_status_box.md +1 -1
  31. docs/user/widgets/scan_control.gif +0 -0
  32. docs/user/widgets/scan_control.md +35 -0
  33. pyproject.toml +2 -1
  34. tests/end-2-end/test_scan_control_e2e.py +71 -0
  35. tests/unit_tests/test_device_input_base.py +4 -4
  36. tests/unit_tests/test_device_input_widgets.py +10 -10
  37. tests/unit_tests/test_scan_control.py +255 -115
  38. tests/unit_tests/test_scan_control_group_box.py +160 -0
  39. {bec_widgets-0.69.0.dist-info → bec_widgets-0.71.0.dist-info}/licenses/LICENSE +0 -0
CHANGELOG.md CHANGED
@@ -1,5 +1,69 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## v0.71.0 (2024-06-23)
4
+
5
+ ### Feature
6
+
7
+ * feat(scan_group_box): scan box for args and kwargs separated from ScanControlGUI code ([`d8cf441`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d8cf44134c30063e586771f9068947fef7a306d1))
8
+
9
+ ### Fix
10
+
11
+ * fix(cleanup): cleanup added to device_input widgets and scan_control ([`8badb6a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8badb6adc1d003dbf0b2b1a800c34821f3fc9aa3))
12
+
13
+ * fix(scan_group_box): added row counter based on widgets ([`37682e7`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/37682e7b8a6ede38308880d285e41a948d6fe831))
14
+
15
+ * fix(scan_control): added default min limit for args bundle if specified ([`ec4574e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ec4574ed5c2c85ea6fbbe2b98f162a8e1220653b))
16
+
17
+ * fix(scan_control): argbox delete later added to prevent overlapping gui if scan changed ([`7ce3a83`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/7ce3a83c58cb69c2bf7cb7f4eaba7e6a2ca6c546))
18
+
19
+ * fix(scan_control): only scans with defined gui_config are allowed ([`6dff187`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6dff1879c4178df0f8ebfd35101acdebb028d572))
20
+
21
+ * fix(WidgetIO): find handlers within base classes ([`ca85638`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ca856384f380dabf28d43f1cd48511af784c035b))
22
+
23
+ * fix(scan_control): adapted widget to scan BEC gui config ([`8b822e0`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8b822e0fa8e28f080b9a4bf81948a7280a4c07bf))
24
+
25
+ * fix(scan_control): scan_control.py combatible with the newest BEC versions, test disabled ([`67d398c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/67d398caf74e08ab25a70cc5d85a5f0c2de8212d))
26
+
27
+ ### Refactor
28
+
29
+ * refactor(device_line_edit): renamed default_device to default ([`4e2c9df`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4e2c9df6a4979d935285fd7eba17fd7fd455a35c))
30
+
31
+ ### Test
32
+
33
+ * test(scan_control): tests added ([`56e74a0`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/56e74a0e7da72d18e89bc30d1896dbf9ef97cd6b))
34
+
35
+ ### Unknown
36
+
37
+ * test(scan_control):e2e tests added ([`83001a0`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/83001a0d8267e1320549b07032857dcf46ecd293))
38
+
39
+ * doc(scan_control): docs added ([`1b7921a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1b7921a7f2e3bcc846219a2a7aa0de0fd27bb8fe))
40
+
41
+ * fix(device_line_edit):SizePolicy fixed for 100 horizontal ([`21d20e0`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/21d20e0fc78e9a3853abe802733388cce119ce20))
42
+
43
+ * tests WIP ([`c09644b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c09644b29ddb291c91dc58bcd6ebf02ff45cab36))
44
+
45
+ ## v0.70.0 (2024-06-21)
46
+
47
+ ### Documentation
48
+
49
+ * docs: fix typo in link ([`fdf11d8`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fdf11d8147750e379af9b17792761a267b49ae53))
50
+
51
+ ### Feature
52
+
53
+ * feat(bec-designer): automatic plugin discovery ([`4639eee`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4639eee0b975ebd7a946e0e290449f5b88c372eb))
54
+
55
+ * feat(device_line_edit): plugin added to bec-designer ([`b4b27ae`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b4b27aea3d8c08fa3d5d5514c69dbde32721d1dc))
56
+
57
+ * feat(device_combobox): plugin added to bec-designer ([`e483b28`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/e483b282db20a81182b87938ea172654092419b5))
58
+
59
+ * feat: added entry point for bec-designer ([`36391db`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/36391db60735d57b371211791ddf8d3d00cebcf1))
60
+
61
+ * feat(utils/bec-designer): added startup script to launched QtDesigner compatible with conda environments ([`5362334`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/5362334ff3b07fc83653323a084a4b6946bade96))
62
+
63
+ ### Fix
64
+
65
+ * fix(bec-desiger+plugins): imports fixed, PYSIDE6 check to not enable run plugins with pyqt6 ([`50b3422`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/50b3422528d46d74317e8c903b6286e868ab7fe0))
66
+
3
67
  ## v0.69.0 (2024-06-21)
4
68
 
5
69
  ### Feature
@@ -82,8 +146,6 @@ in their parent process ([`ce37416`](https://gitlab.psi.ch/bec/bec_widgets/-/com
82
146
 
83
147
  * feat(device_input): DeviceLineEdit with QCompleter added ([`50e41ff`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/50e41ff26160ec26d77feb6d519e4dad902a9b9b))
84
148
 
85
- * feat(device_combobox): DeviceInputBase and DeviceComboBox added ([`430b282`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/430b282039806e3fbc6cf98e958861a065760620))
86
-
87
149
  ### Fix
88
150
 
89
151
  * fix(device_input_base): bug with setting config and overwriting default device and filter ([`d79f7e9`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d79f7e9ccde03dc77819ca556c79736d30f7821a))
@@ -91,88 +153,3 @@ in their parent process ([`ce37416`](https://gitlab.psi.ch/bec/bec_widgets/-/com
91
153
  ### Test
92
154
 
93
155
  * test(device_input): tests added ([`1a0a98a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1a0a98a45367db414bed813bbd346b3e1ae8d550))
94
-
95
- ## v0.64.2 (2024-06-19)
96
-
97
- ### Fix
98
-
99
- * fix(client_utils): added close rpc command to shutdown of gui from bec_ipython_client ([`e5a7d47`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/e5a7d47b21cbf066f740f1d11d7c9ea7c70f3080))
100
-
101
- ## v0.64.1 (2024-06-19)
102
-
103
- ### Fix
104
-
105
- * fix(widgets): removed widget module import of sub widgets ([`216511b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/216511b951ff0e15b6d7c70133095f3ac45c23f4))
106
-
107
- ### Refactor
108
-
109
- * refactor(utils): moved get_rpc_widgets to plugin_utils ([`6dabbf8`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6dabbf874fbbdde89c34a7885bf95aa9c895a28b))
110
-
111
- ### Test
112
-
113
- * test: moved rpc_classes test ([`b3575eb`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b3575eb06852b456cde915dfda281a3e778e3aeb))
114
-
115
- ## v0.64.0 (2024-06-19)
116
-
117
- ### Ci
118
-
119
- * ci: add job optional dependency check ([`27426ce`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/27426ce7a52b4cbad7f3bef114d6efe6ad73bd7f))
120
-
121
- ### Documentation
122
-
123
- * docs: fix links in developer section ([`9e16f2f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/9e16f2faf9c59a5d36ae878512c5a910cca31e69))
124
-
125
- * docs: refactor developer section, add widget tutorial ([`2a36d93`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2a36d9364f242bf42e4cda4b50e6f46aa3833bbd))
126
-
127
- ### Feature
128
-
129
- * feat: add option to change size of the fonts ([`ea805d1`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ea805d1362fc084d3b703b6f81b0180072f0825d))
130
-
131
- ### Fix
132
-
133
- * fix(plot_base): font size is set with setScale which is scaling the whole legend window ([`5d66720`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/5d6672069ea1cbceb62104f66c127e4e3c23e4a4))
134
-
135
- ### Test
136
-
137
- * test: add tests ([`140ad83`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/140ad83380808928edf7953e23c762ab72a0a1e9))
138
-
139
- ## v0.63.2 (2024-06-14)
140
-
141
- ### Fix
142
-
143
- * fix: do not import "server" in client, prevents from having trouble with QApplication creation order
144
-
145
- Like with QtWebEngine ([`6f96498`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6f96498de66358b89f3a2035627eed2e02dde5a1))
146
-
147
- ### Unknown
148
-
149
- * Reapply "feat: implement non-polling, interruptible waiting of gui instruction response with timeout"
150
-
151
- This reverts commit fe04dd80e59a0e74f7fdea603e0642707ecc7c2a. ([`836b6e6`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/836b6e64f694916d6b6f909dedf11a4a6d2c86a4))
152
-
153
- ## v0.63.1 (2024-06-13)
154
-
155
- ### Fix
156
-
157
- * fix: just terminate the remote process in close() instead of communicating
158
-
159
- The proper finalization sequence will be executed by the remote process
160
- on SIGTERM ([`9263f8e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/9263f8ef5c17ae7a007a1a564baf787b39061756))
161
-
162
- ## v0.63.0 (2024-06-13)
163
-
164
- ### Documentation
165
-
166
- * docs: add documentation ([`bc709c4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/bc709c4184c985d4e721f9ea7d1b3dad5e9153a7))
167
-
168
- ### Feature
169
-
170
- * feat: add textbox widget ([`d9d4e3c`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d9d4e3c9bf73ab2a5629c2867b50fc91e69489ec))
171
-
172
- ### Refactor
173
-
174
- * refactor: add pydantic config, add change_theme ([`6b8432f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6b8432f5b20a71175a3537b5f6832b76e3b67d73))
175
-
176
- ### Test
177
-
178
- * test: add test for text box ([`b49462a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b49462abeb186e56bac79d2ef0b0add1ef28a1a5))
PKG-INFO CHANGED
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: bec_widgets
3
- Version: 0.69.0
3
+ Version: 0.71.0
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
bec_widgets/cli/client.py CHANGED
@@ -17,6 +17,7 @@ class Widgets(str, enum.Enum):
17
17
  BECDock = "BECDock"
18
18
  BECDockArea = "BECDockArea"
19
19
  BECFigure = "BECFigure"
20
+ ScanControl = "ScanControl"
20
21
  SpiralProgressBar = "SpiralProgressBar"
21
22
  TextBox = "TextBox"
22
23
  VSCodeEditor = "VSCodeEditor"
@@ -1823,6 +1824,24 @@ class Ring(RPCBase):
1823
1824
  """
1824
1825
 
1825
1826
 
1827
+ class ScanControl(RPCBase):
1828
+ @property
1829
+ @rpc_call
1830
+ def config_dict(self) -> "dict":
1831
+ """
1832
+ Get the configuration of the widget.
1833
+
1834
+ Returns:
1835
+ dict: The configuration of the widget.
1836
+ """
1837
+
1838
+ @rpc_call
1839
+ def get_all_rpc(self) -> "dict":
1840
+ """
1841
+ Get all registered RPC objects.
1842
+ """
1843
+
1844
+
1826
1845
  class SpiralProgressBar(RPCBase):
1827
1846
  @rpc_call
1828
1847
  def get_all_rpc(self) -> "dict":
File without changes
@@ -0,0 +1,17 @@
1
+ # Copyright (C) 2022 The Qt Company Ltd.
2
+ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
3
+
4
+ """PySide6 port of the Qt Designer taskmenuextension example from Qt v6.x"""
5
+
6
+ import sys
7
+
8
+ from bec_ipython_client.main import BECIPythonClient
9
+ from qtpy.QtWidgets import QApplication
10
+ from tictactoe import TicTacToe
11
+
12
+ if __name__ == "__main__": # pragma: no cover
13
+ app = QApplication(sys.argv)
14
+ window = TicTacToe()
15
+ window.state = "-X-XO----"
16
+ window.show()
17
+ sys.exit(app.exec())
@@ -0,0 +1,12 @@
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 QPyDesignerCustomWidgetCollection
5
+ from tictactoe import TicTacToe
6
+ from tictactoeplugin import TicTacToePlugin
7
+
8
+ # Set PYSIDE_DESIGNER_PLUGINS to point to this directory and load the plugin
9
+
10
+
11
+ if __name__ == "__main__": # pragma: no cover
12
+ QPyDesignerCustomWidgetCollection.addCustomWidget(TicTacToePlugin())
@@ -0,0 +1,4 @@
1
+ {
2
+ "files": ["tictactoe.py", "main.py", "registertictactoe.py", "tictactoeplugin.py",
3
+ "tictactoetaskmenu.py"]
4
+ }
@@ -0,0 +1,135 @@
1
+ # Copyright (C) 2022 The Qt Company Ltd.
2
+ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
3
+
4
+ from qtpy.QtCore import Property, QPoint, QRect, QSize, Qt, Slot
5
+ from qtpy.QtGui import QPainter, QPen
6
+ from qtpy.QtWidgets import QWidget
7
+
8
+ EMPTY = "-"
9
+ CROSS = "X"
10
+ NOUGHT = "O"
11
+ DEFAULT_STATE = "---------"
12
+
13
+
14
+ class TicTacToe(QWidget): # pragma: no cover
15
+ def __init__(self, parent=None):
16
+ super().__init__(parent)
17
+ self._state = DEFAULT_STATE
18
+ self._turn_number = 0
19
+
20
+ def minimumSizeHint(self):
21
+ return QSize(200, 200)
22
+
23
+ def sizeHint(self):
24
+ return QSize(200, 200)
25
+
26
+ def setState(self, new_state):
27
+ self._turn_number = 0
28
+ self._state = DEFAULT_STATE
29
+ for position in range(min(9, len(new_state))):
30
+ mark = new_state[position]
31
+ if mark == CROSS or mark == NOUGHT:
32
+ self._turn_number += 1
33
+ self._change_state_at(position, mark)
34
+ position += 1
35
+ self.update()
36
+
37
+ def state(self):
38
+ return self._state
39
+
40
+ @Slot()
41
+ def clear_board(self):
42
+ self._state = DEFAULT_STATE
43
+ self._turn_number = 0
44
+ self.update()
45
+
46
+ def _change_state_at(self, pos, new_state):
47
+ self._state = self._state[:pos] + new_state + self._state[pos + 1 :]
48
+
49
+ def mousePressEvent(self, event):
50
+ if self._turn_number == 9:
51
+ self.clear_board()
52
+ return
53
+ for position in range(9):
54
+ cell = self._cell_rect(position)
55
+ if cell.contains(event.position().toPoint()):
56
+ if self._state[position] == EMPTY:
57
+ new_state = CROSS if self._turn_number % 2 == 0 else NOUGHT
58
+ self._change_state_at(position, new_state)
59
+ self._turn_number += 1
60
+ self.update()
61
+
62
+ def paintEvent(self, event):
63
+ with QPainter(self) as painter:
64
+ painter.setRenderHint(QPainter.Antialiasing)
65
+
66
+ painter.setPen(QPen(Qt.darkGreen, 1))
67
+ painter.drawLine(self._cell_width(), 0, self._cell_width(), self.height())
68
+ painter.drawLine(2 * self._cell_width(), 0, 2 * self._cell_width(), self.height())
69
+ painter.drawLine(0, self._cell_height(), self.width(), self._cell_height())
70
+ painter.drawLine(0, 2 * self._cell_height(), self.width(), 2 * self._cell_height())
71
+
72
+ painter.setPen(QPen(Qt.darkBlue, 2))
73
+
74
+ for position in range(9):
75
+ cell = self._cell_rect(position)
76
+ if self._state[position] == CROSS:
77
+ painter.drawLine(cell.topLeft(), cell.bottomRight())
78
+ painter.drawLine(cell.topRight(), cell.bottomLeft())
79
+ elif self._state[position] == NOUGHT:
80
+ painter.drawEllipse(cell)
81
+
82
+ painter.setPen(QPen(Qt.yellow, 3))
83
+
84
+ for position in range(0, 8, 3):
85
+ if (
86
+ self._state[position] != EMPTY
87
+ and self._state[position + 1] == self._state[position]
88
+ and self._state[position + 2] == self._state[position]
89
+ ):
90
+ y = self._cell_rect(position).center().y()
91
+ painter.drawLine(0, y, self.width(), y)
92
+ self._turn_number = 9
93
+
94
+ for position in range(3):
95
+ if (
96
+ self._state[position] != EMPTY
97
+ and self._state[position + 3] == self._state[position]
98
+ and self._state[position + 6] == self._state[position]
99
+ ):
100
+ x = self._cell_rect(position).center().x()
101
+ painter.drawLine(x, 0, x, self.height())
102
+ self._turn_number = 9
103
+
104
+ if (
105
+ self._state[0] != EMPTY
106
+ and self._state[4] == self._state[0]
107
+ and self._state[8] == self._state[0]
108
+ ):
109
+ painter.drawLine(0, 0, self.width(), self.height())
110
+ self._turn_number = 9
111
+
112
+ if (
113
+ self._state[2] != EMPTY
114
+ and self._state[4] == self._state[2]
115
+ and self._state[6] == self._state[2]
116
+ ):
117
+ painter.drawLine(0, self.height(), self.width(), 0)
118
+ self._turn_number = 9
119
+
120
+ def _cell_rect(self, position):
121
+ h_margin = self.width() / 30
122
+ v_margin = self.height() / 30
123
+ row = int(position / 3)
124
+ column = position - 3 * row
125
+ pos = QPoint(column * self._cell_width() + h_margin, row * self._cell_height() + v_margin)
126
+ size = QSize(self._cell_width() - 2 * h_margin, self._cell_height() - 2 * v_margin)
127
+ return QRect(pos, size)
128
+
129
+ def _cell_width(self):
130
+ return self.width() / 3
131
+
132
+ def _cell_height(self):
133
+ return self.height() / 3
134
+
135
+ state = Property(str, state, setState)
@@ -0,0 +1,68 @@
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
+ from qtpy.QtGui import QIcon
6
+ from tictactoe import TicTacToe
7
+ from tictactoetaskmenu import TicTacToeTaskMenuFactory
8
+
9
+ DOM_XML = """
10
+ <ui language='c++'>
11
+ <widget class='TicTacToe' name='ticTacToe'>
12
+ <property name='geometry'>
13
+ <rect>
14
+ <x>0</x>
15
+ <y>0</y>
16
+ <width>200</width>
17
+ <height>200</height>
18
+ </rect>
19
+ </property>
20
+ <property name='state'>
21
+ <string>-X-XO----</string>
22
+ </property>
23
+ </widget>
24
+ </ui>
25
+ """
26
+
27
+
28
+ class TicTacToePlugin(QDesignerCustomWidgetInterface): # pragma: no cover
29
+ def __init__(self):
30
+ super().__init__()
31
+ self._form_editor = None
32
+
33
+ def createWidget(self, parent):
34
+ t = TicTacToe(parent)
35
+ return t
36
+
37
+ def domXml(self):
38
+ return DOM_XML
39
+
40
+ def group(self):
41
+ return ""
42
+
43
+ def icon(self):
44
+ return QIcon()
45
+
46
+ def includeFile(self):
47
+ return "tictactoe"
48
+
49
+ def initialize(self, form_editor):
50
+ self._form_editor = form_editor
51
+ manager = form_editor.extensionManager()
52
+ iid = TicTacToeTaskMenuFactory.task_menu_iid()
53
+ manager.registerExtensions(TicTacToeTaskMenuFactory(manager), iid)
54
+
55
+ def isContainer(self):
56
+ return False
57
+
58
+ def isInitialized(self):
59
+ return self._form_editor is not None
60
+
61
+ def name(self):
62
+ return "TicTacToe"
63
+
64
+ def toolTip(self):
65
+ return "Tic Tac Toe Example, demonstrating class QDesignerTaskMenuExtension (Python)"
66
+
67
+ def whatsThis(self):
68
+ return self.toolTip()
@@ -0,0 +1,67 @@
1
+ # Copyright (C) 2022 The Qt Company Ltd.
2
+ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
3
+
4
+ from qtpy.QtCore import Slot
5
+ from qtpy.QtDesigner import QExtensionFactory, QPyDesignerTaskMenuExtension
6
+ from qtpy.QtGui import QAction
7
+ from qtpy.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout
8
+ from tictactoe import TicTacToe
9
+
10
+
11
+ class TicTacToeDialog(QDialog): # pragma: no cover
12
+ def __init__(self, parent):
13
+ super().__init__(parent)
14
+ layout = QVBoxLayout(self)
15
+ self._ticTacToe = TicTacToe(self)
16
+ layout.addWidget(self._ticTacToe)
17
+ button_box = QDialogButtonBox(
18
+ QDialogButtonBox.Ok | QDialogButtonBox.Cancel | QDialogButtonBox.Reset
19
+ )
20
+ button_box.accepted.connect(self.accept)
21
+ button_box.rejected.connect(self.reject)
22
+ reset_button = button_box.button(QDialogButtonBox.Reset)
23
+ reset_button.clicked.connect(self._ticTacToe.clear_board)
24
+ layout.addWidget(button_box)
25
+
26
+ def set_state(self, new_state):
27
+ self._ticTacToe.setState(new_state)
28
+
29
+ def state(self):
30
+ return self._ticTacToe.state
31
+
32
+
33
+ class TicTacToeTaskMenu(QPyDesignerTaskMenuExtension):
34
+ def __init__(self, ticTacToe, parent):
35
+ super().__init__(parent)
36
+ self._ticTacToe = ticTacToe
37
+ self._edit_state_action = QAction("Edit State...", None)
38
+ self._edit_state_action.triggered.connect(self._edit_state)
39
+
40
+ def taskActions(self):
41
+ return [self._edit_state_action]
42
+
43
+ def preferredEditAction(self):
44
+ return self._edit_state_action
45
+
46
+ @Slot()
47
+ def _edit_state(self):
48
+ dialog = TicTacToeDialog(self._ticTacToe)
49
+ dialog.set_state(self._ticTacToe.state)
50
+ if dialog.exec() == QDialog.Accepted:
51
+ self._ticTacToe.state = dialog.state()
52
+
53
+
54
+ class TicTacToeTaskMenuFactory(QExtensionFactory):
55
+ def __init__(self, extension_manager):
56
+ super().__init__(extension_manager)
57
+
58
+ @staticmethod
59
+ def task_menu_iid():
60
+ return "org.qt-project.Qt.Designer.TaskMenu"
61
+
62
+ def createExtension(self, object, iid, parent):
63
+ if iid != TicTacToeTaskMenuFactory.task_menu_iid():
64
+ return None
65
+ if object.__class__.__name__ != "TicTacToe":
66
+ return None
67
+ return TicTacToeTaskMenu(object, parent)
@@ -0,0 +1,87 @@
1
+ import os
2
+ import sys
3
+ import sysconfig
4
+ from pathlib import Path
5
+
6
+ from qtpy import PYSIDE6
7
+
8
+ if PYSIDE6:
9
+ from PySide6.scripts.pyside_tool import (
10
+ _extend_path_var,
11
+ init_virtual_env,
12
+ is_pyenv_python,
13
+ is_virtual_env,
14
+ qt_tool_wrapper,
15
+ ui_tool_binary,
16
+ )
17
+
18
+ import bec_widgets
19
+
20
+
21
+ def patch_designer(): # pragma: no cover
22
+ if not PYSIDE6:
23
+ print("PYSIDE6 is not available in the environment. Cannot patch designer.")
24
+ return
25
+
26
+ init_virtual_env()
27
+
28
+ major_version = sys.version_info[0]
29
+ minor_version = sys.version_info[1]
30
+ os.environ["PY_MAJOR_VERSION"] = str(major_version)
31
+ os.environ["PY_MINOR_VERSION"] = str(minor_version)
32
+
33
+ if sys.platform == "linux":
34
+ version = f"{major_version}.{minor_version}"
35
+ library_name = f"libpython{version}{sys.abiflags}.so"
36
+ if is_pyenv_python():
37
+ library_name = str(Path(sysconfig.get_config_var("LIBDIR")) / library_name)
38
+ os.environ["LD_PRELOAD"] = library_name
39
+ elif sys.platform == "darwin":
40
+ library_name = f"libpython{major_version}.{minor_version}.dylib"
41
+ lib_path = str(Path(sysconfig.get_config_var("LIBDIR")) / library_name)
42
+ os.environ["DYLD_INSERT_LIBRARIES"] = lib_path
43
+ elif sys.platform == "win32":
44
+ if is_virtual_env():
45
+ _extend_path_var("PATH", os.fspath(Path(sys._base_executable).parent), True)
46
+
47
+ qt_tool_wrapper(ui_tool_binary("designer"), sys.argv[1:])
48
+
49
+
50
+ def find_plugin_paths(base_path: Path):
51
+ """
52
+ Recursively find all directories containing a .pyproject file.
53
+ """
54
+ plugin_paths = []
55
+ for path in base_path.rglob("*.pyproject"):
56
+ plugin_paths.append(str(path.parent))
57
+ return plugin_paths
58
+
59
+
60
+ def set_plugin_environment_variable(plugin_paths):
61
+ """
62
+ Set the PYSIDE_DESIGNER_PLUGINS environment variable with the given plugin paths.
63
+ """
64
+ current_paths = os.environ.get("PYSIDE_DESIGNER_PLUGINS", "")
65
+ if current_paths:
66
+ current_paths = current_paths.split(os.pathsep)
67
+ else:
68
+ current_paths = []
69
+
70
+ current_paths.extend(plugin_paths)
71
+ os.environ["PYSIDE_DESIGNER_PLUGINS"] = os.pathsep.join(current_paths)
72
+
73
+
74
+ # Patch the designer function
75
+ def main(): # pragma: no cover
76
+ if not PYSIDE6:
77
+ print("PYSIDE6 is not available in the environment. Exiting...")
78
+ return
79
+ base_dir = Path(os.path.dirname(bec_widgets.__file__)).resolve()
80
+ plugin_paths = find_plugin_paths(base_dir)
81
+ set_plugin_environment_variable(plugin_paths)
82
+
83
+ patch_designer()
84
+
85
+
86
+ if __name__ == "__main__": # pragma: no cover
87
+ main()
@@ -119,7 +119,7 @@ class WidgetIO:
119
119
  widget: Widget instance.
120
120
  ignore_errors(bool, optional): Whether to ignore if no handler is found.
121
121
  """
122
- handler_class = WidgetIO._handlers.get(type(widget))
122
+ handler_class = WidgetIO._find_handler(widget)
123
123
  if handler_class:
124
124
  return handler_class().get_value(widget) # Instantiate the handler
125
125
  if not ignore_errors:
@@ -136,12 +136,28 @@ class WidgetIO:
136
136
  value: Value to set.
137
137
  ignore_errors(bool, optional): Whether to ignore if no handler is found.
138
138
  """
139
- handler_class = WidgetIO._handlers.get(type(widget))
139
+ handler_class = WidgetIO._find_handler(widget)
140
140
  if handler_class:
141
141
  handler_class().set_value(widget, value) # Instantiate the handler
142
142
  elif not ignore_errors:
143
143
  raise ValueError(f"No handler for widget type: {type(widget)}")
144
144
 
145
+ @staticmethod
146
+ def _find_handler(widget):
147
+ """
148
+ Find the appropriate handler for the widget by checking its base classes.
149
+
150
+ Args:
151
+ widget: Widget instance.
152
+
153
+ Returns:
154
+ handler_class: The handler class if found, otherwise None.
155
+ """
156
+ for base in type(widget).__mro__:
157
+ if base in WidgetIO._handlers:
158
+ return WidgetIO._handlers[base]
159
+ return None
160
+
145
161
 
146
162
  ################## for exporting and importing widget hierarchies ##################
147
163