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.
- CHANGELOG.md +64 -87
- PKG-INFO +1 -1
- bec_widgets/cli/client.py +19 -0
- bec_widgets/examples/plugin_example_pyside/__init__.py +0 -0
- bec_widgets/examples/plugin_example_pyside/main.py +17 -0
- bec_widgets/examples/plugin_example_pyside/registertictactoe.py +12 -0
- bec_widgets/examples/plugin_example_pyside/taskmenuextension.pyproject +4 -0
- bec_widgets/examples/plugin_example_pyside/tictactoe.py +135 -0
- bec_widgets/examples/plugin_example_pyside/tictactoeplugin.py +68 -0
- bec_widgets/examples/plugin_example_pyside/tictactoetaskmenu.py +67 -0
- bec_widgets/utils/bec_designer.py +87 -0
- bec_widgets/utils/widget_io.py +18 -2
- bec_widgets/widgets/device_inputs/device_combobox/device_combobox.py +10 -13
- bec_widgets/widgets/device_inputs/device_combobox/device_combobox.pyproject +4 -0
- bec_widgets/widgets/device_inputs/device_combobox/device_combobox_plugin.py +54 -0
- bec_widgets/widgets/device_inputs/device_combobox/launch_device_combobox.py +11 -0
- bec_widgets/widgets/device_inputs/device_combobox/register_device_combobox.py +17 -0
- bec_widgets/widgets/device_inputs/device_input_base.py +5 -2
- bec_widgets/widgets/device_inputs/device_line_edit/device_line_edit.py +16 -14
- bec_widgets/widgets/device_inputs/device_line_edit/device_line_edit.pyproject +4 -0
- bec_widgets/widgets/device_inputs/device_line_edit/device_line_edit_plugin.py +54 -0
- bec_widgets/widgets/device_inputs/device_line_edit/launch_device_line_edit.py +11 -0
- bec_widgets/widgets/device_inputs/device_line_edit/register_device_line_edit.py +17 -0
- bec_widgets/widgets/scan_control/scan_control.py +133 -365
- bec_widgets/widgets/scan_control/scan_group_box.py +223 -0
- {bec_widgets-0.69.0.dist-info → bec_widgets-0.71.0.dist-info}/METADATA +1 -1
- {bec_widgets-0.69.0.dist-info → bec_widgets-0.71.0.dist-info}/RECORD +39 -18
- {bec_widgets-0.69.0.dist-info → bec_widgets-0.71.0.dist-info}/WHEEL +1 -1
- {bec_widgets-0.69.0.dist-info → bec_widgets-0.71.0.dist-info}/entry_points.txt +1 -0
- docs/user/widgets/bec_status_box.md +1 -1
- docs/user/widgets/scan_control.gif +0 -0
- docs/user/widgets/scan_control.md +35 -0
- pyproject.toml +2 -1
- tests/end-2-end/test_scan_control_e2e.py +71 -0
- tests/unit_tests/test_device_input_base.py +4 -4
- tests/unit_tests/test_device_input_widgets.py +10 -10
- tests/unit_tests/test_scan_control.py +255 -115
- tests/unit_tests/test_scan_control_group_box.py +160 -0
- {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
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,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()
|
bec_widgets/utils/widget_io.py
CHANGED
@@ -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.
|
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.
|
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
|
|