1
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2026-04-08 09:47:52 +02:00

Compare commits

...

59 Commits

Author SHA1 Message Date
semantic-release
9df1e0899b 0.95.1
Automatically generated by python-semantic-release
2024-08-22 07:36:54 +00:00
640464a654 fix(docs): changed link to scan gui config in main docs 2024-08-21 21:46:44 +02:00
84abe46050 refactor: removed designer pngs 2024-08-21 21:28:32 +02:00
1d2afaa09e refactor: moved to dynamically loaded material design icons 2024-08-21 21:28:32 +02:00
2bf5c7096e docs: links section added 2024-08-21 21:07:50 +02:00
semantic-release
41dc6e6cfd 0.95.0
Automatically generated by python-semantic-release
2024-08-21 13:50:10 +00:00
650039303a fix(device_browser): fixed plugin assignment for designer 2024-08-21 15:41:23 +02:00
196504b533 feat(cli): added device_browser to cli 2024-08-21 15:29:22 +02:00
2c31cc90ae docs(device_browser): added user docs 2024-08-21 15:29:02 +02:00
e870e5ba08 test: added test for device browser 2024-08-21 14:38:34 +02:00
73f5a2f085 feat(widgets): added device_browser widget 2024-08-21 14:38:34 +02:00
4790afde3d refactor(docs): review response 2024-08-21 13:18:48 +02:00
7357f3d2a1 docs(user): widget gallery with documentation added 2024-08-21 13:18:48 +02:00
e9ecd268c6 docs: added sphinx-inline-tabs as sphinx dependency 2024-08-21 13:18:48 +02:00
91ba30e8d0 docs(cards): changed index cards to custom css class instead of overwriting the default sd-card theme 2024-08-21 13:18:48 +02:00
semantic-release
d36d801ef1 0.94.7
Automatically generated by python-semantic-release
2024-08-20 13:17:23 +00:00
939f834a26 fix: formatting of stdout, stderr captured text for logger 2024-08-14 18:01:51 +02:00
semantic-release
bee51bd86e 0.94.6
Automatically generated by python-semantic-release
2024-08-14 15:05:11 +00:00
bc2abe945f fix(server): emit heartbeat with state 2024-08-14 16:55:04 +02:00
semantic-release
49a5a23d41 0.94.5
Automatically generated by python-semantic-release
2024-08-14 12:01:45 +00:00
4f96d0e4a1 build: increased min version of bec to 2.21.4
Since we now rely on reusing the BECClient singleton, we need the fix introduced with 2.21.4 in BEC.
2024-08-14 12:32:34 +02:00
ea9240d2f7 fix(rpc): use client singleton instead of dispatcher 2024-08-14 12:32:34 +02:00
4d02b42f11 fix: removed qcoreapplication for polling events 2024-08-14 12:32:34 +02:00
semantic-release
9509be14be 0.94.4
Automatically generated by python-semantic-release
2024-08-14 08:46:53 +00:00
198c1d1064 fix: do not shutdown client in "close"
Terminating client connections has to be done at the application level
2024-08-13 12:23:51 +02:00
2af5c94913 docs: review developer section; add introduction 2024-08-13 11:06:24 +02:00
semantic-release
a4a0bac3c1 0.94.3
Automatically generated by python-semantic-release
2024-08-13 09:03:11 +00:00
f285b35b49 test(waveform_widget): added tests for axis setting and curve dialog 2024-08-13 10:53:44 +02:00
7aeb2b5c26 fix(curve_dialog): async curves are shown in curve dialog after addition. 2024-08-13 10:53:44 +02:00
d56ea95ef9 fix(waveform): async device entry is correctly passed, updated and with new scan the previous data are cleared 2024-08-13 10:53:44 +02:00
semantic-release
5733fea98c 0.94.2
Automatically generated by python-semantic-release
2024-08-13 08:53:10 +00:00
98b79aac7b fix(image): image is single image mode do not raise popup error when connected twice with the same monitor 2024-08-12 11:24:08 +02:00
semantic-release
4212fe0e32 0.94.1
Automatically generated by python-semantic-release
2024-08-12 08:53:48 +00:00
93d397759c fix: issue #292, wrong key was used to clean _slots internal dictionary 2024-08-12 10:32:35 +02:00
semantic-release
8c5b901a37 0.94.0
Automatically generated by python-semantic-release
2024-08-08 14:59:14 +00:00
0273bf4856 refactor: adjust dimensions 2024-08-08 15:11:43 +02:00
c80a7cd108 feat: add PositionerControlLine 2024-08-08 14:58:16 +02:00
semantic-release
a50d9c7b3f 0.93.5
Automatically generated by python-semantic-release
2024-08-08 11:48:07 +00:00
281633deff fix(positioner_box): icons fixed 2024-08-08 13:34:07 +02:00
0d190c5c59 refactor: add button for positioner selection 2024-08-08 13:34:07 +02:00
6269009e54 test(dap): wait for fit 2024-08-07 20:19:37 +02:00
6d2442d23c test(auto-update): wait for rendering 2024-08-07 19:58:26 +02:00
semantic-release
110b27351b 0.93.4
Automatically generated by python-semantic-release
2024-08-07 16:21:43 +00:00
37aa371e7c fix: rename DeviceBox to PositionerBox, fix test for validation 2024-08-07 17:56:48 +02:00
eb54e9f788 fix: add validation for bec_lib.device.Positioner; closes #268 2024-08-07 15:45:39 +02:00
semantic-release
482efeb340 0.93.3
Automatically generated by python-semantic-release
2024-08-07 13:14:54 +00:00
99ee545e41 fix(dock): properly shut down docks and temp areas 2024-08-07 13:58:43 +02:00
cf94599c25 test: removed quit from teardown 2024-08-07 12:25:54 +02:00
b50b3a27e6 fix(settings): shut down settings dialog 2024-08-07 12:25:54 +02:00
bf6294ecbf test: removed explicit call to close the widget 2024-08-07 12:25:54 +02:00
a3d4f5ac4b fix(website): fixed teardown of website widgets 2024-08-07 11:15:14 +02:00
bc264975b1 fix(dock): properly shut down docks and dock areas 2024-08-07 11:00:25 +02:00
ad07bbf85e fix(figure): cleanup pyqtgraph 2024-08-07 10:12:49 +02:00
9856857f4c test: use factory instead of fixture to properly cleanup widgets on teardown 2024-08-07 10:12:49 +02:00
f9e5897900 test: ensure all toplevelwidgets are closed 2024-08-07 10:12:49 +02:00
semantic-release
39fb22b716 0.93.2
Automatically generated by python-semantic-release
2024-08-07 07:57:18 +00:00
a372925fff fix(scan_group_box): Scan Spinboxes limits increased to max allowed values; setting dialog for step size and decimal precision for ScanDoubleSpinBox on right click 2024-08-07 09:47:06 +02:00
semantic-release
ec54440569 0.93.1
Automatically generated by python-semantic-release
2024-08-06 21:56:54 +00:00
af86860bf3 fix(dock): docks have more recognizable red icon for closing docks 2024-08-06 19:23:31 +02:00
185 changed files with 3629 additions and 962 deletions

View File

@@ -1,151 +1,159 @@
# CHANGELOG
## v0.93.0 (2024-08-05)
## v0.95.1 (2024-08-22)
### Feature
### Documentation
* feat(themes): moved themes to bec_qthemes
This reverts commit fd6ae91993a23a7b8dbb2cf3c4b7c3eda6d2b0f6 ([`5aad401`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/5aad401ef8774c7330784f72cd3b9d8c253e2b6a))
## v0.92.5 (2024-08-05)
* docs: links section added ([`2bf5c70`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2bf5c7096e7d822713e1b50bde89f072e6356e17))
### Fix
* fix(spinner): stop timer on close event ([`30fef92`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/30fef929cf6fb4b73f48151c92a0ee54c734031d))
* fix(status_box): fix cleanup of status box ([`1f30dd7`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1f30dd73a9c1e3135087a5eef92c7329f54a604e))
* fix(docs): changed link to scan gui config in main docs ([`640464a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/640464a6543b2111bdb58d0174f2ce86c5836cbe))
### Refactor
* refactor(queue): refactored bec queue to inherit only from qwidget ([`7616ca0`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/7616ca0e145e233ccb48029a8c0b54b54b5b4194))
* refactor: removed designer pngs ([`84abe46`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/84abe460502d838aac41bb8ff63d93c9fcec9214))
* refactor: moved to dynamically loaded material design icons ([`1d2afaa`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1d2afaa09e64b7f714d72796e87e2cb49b2a75a7))
## v0.95.0 (2024-08-21)
### Documentation
* docs(device_browser): added user docs ([`2c31cc9`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2c31cc90ae751f14a653cbbdd6c353d6359aaafe))
* docs(user): widget gallery with documentation added ([`7357f3d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/7357f3d2a189f9f04954a027f39ce07c394d57ec))
* docs: added sphinx-inline-tabs as sphinx dependency ([`e9ecd26`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/e9ecd268c602ea9572df0e8d508e49ee62d0c170))
* docs(cards): changed index cards to custom css class instead of overwriting the default sd-card theme ([`91ba30e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/91ba30e8d054a9c7f6c6d98b21113a5d0b1bbbbb))
### Feature
* feat(cli): added device_browser to cli ([`196504b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/196504b533367a899c19b88af4ccd5b39dc46aac))
* feat(widgets): added device_browser widget ([`73f5a2f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/73f5a2f085b289ac18fa8a918b6ad7cfed595fb4))
### Fix
* fix(device_browser): fixed plugin assignment for designer ([`6500393`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/650039303aae9bbec62c676285938416fff146ce))
### Refactor
* refactor(docs): review response ([`4790afd`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4790afde3d61fc9beb073c2775c339d4f80779e3))
### Test
* test: register all widgets with qtbot and close them ([`73cd11e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/73cd11e47277e4437554b785a9551b28a572094f))
* test: added test for device browser ([`e870e5b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/e870e5ba083c61df581c9c0305adabe72967f997))
## v0.92.4 (2024-07-31)
## v0.94.7 (2024-08-20)
### Fix
* fix: fix missmatch of signal/slot in image and motormap ([`dcc5fd7`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/dcc5fd71ee9f51767a7b2b1ed6200e89d1ef754c))
* fix: formatting of stdout, stderr captured text for logger ([`939f834`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/939f834a26ddbac0bdead0b60b1cdf52014f182f))
## v0.92.3 (2024-07-28)
## v0.94.6 (2024-08-14)
### Fix
* fix(docs): moved to pyside6 ([`71873dd`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/71873ddf359516ded8f74f4d2f73df4156aa1368))
* fix(server): emit heartbeat with state ([`bc2abe9`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/bc2abe945fb5adeec89ed5ac45e966db86ce6ffc))
## v0.92.2 (2024-07-28)
### Fix
* fix(widgets): fixed import for tictactoe example ([`995a795`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/995a795060bebe25c17108d80ae0fa30463f03b1))
## v0.92.1 (2024-07-28)
## v0.94.5 (2024-08-14)
### Build
* build(ci): install ophyd_devices in editable mode for pipelines ([`06205e0`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/06205e07903d93accf40abab153f440059f236ed))
* build: increased min version of bec to 2.21.4
Since we now rely on reusing the BECClient singleton, we need the fix introduced with 2.21.4 in BEC. ([`4f96d0e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4f96d0e4a14edc4b2839c1dddeda384737dc7a8a))
### Fix
* fix: use SafeSlot instead of Slot ([`bc1e239`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/bc1e23944cc0e5a861e3d0b4dc5b4ac6292d5269))
* fix(rpc): use client singleton instead of dispatcher ([`ea9240d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ea9240d2f71931082f33fb6b68231469875c3d63))
* fix: linting ([`a3fe205`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a3fe20500ae2ac03dcde07432f7e21ce5262ce46))
* fix: removed qcoreapplication for polling events ([`4d02b42`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4d02b42f11e9882b843317255a4975565c8a536f))
* fix: always add a QApplication for tests ([`61a4e32`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/61a4e32deb337ed27f2f43358b88b7266413b58e))
## v0.94.4 (2024-08-14)
* fix: add xvfb to draw offscreen ([`3d681f7`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3d681f77e144e74138fc5fa65630004d7c166878))
### Documentation
* fix: reset ErrorPopup singleton between tests ([`5a9ccfd`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/5a9ccfd1f6d2aacd5d86c1a34f74163b272d1ae4))
* fix: metaclass + QObject segfaults PyQt(cpp bindings) ([`fc57b7a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fc57b7a1262031a2df9e6a99493db87e766b779a))
### Refactor
* refactor: renamed DeviceMonitor2DMessage ([`4be6fd6`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4be6fd6b83ea1048f16310f7d2bbe777b13b245e))
* refactor: rename device_monitor to device_monitor_2d ([`714e1e1`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/714e1e139e0033d2725fefb636c419ca137a68c6))
## v0.92.0 (2024-07-24)
### Feature
* feat(dock): dock style sheets updated ([`8ca60d5`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/8ca60d54b3cfa621172ce097fc1ba514c47ebac7))
* feat(general_gui): general gui added ([`5696c99`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/5696c993dc1c0da40ff3e99f754c246cc017ea32))
* docs: review developer section; add introduction ([`2af5c94`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/2af5c94913a3435c1839034df4f45f885b56d08b))
### Fix
* fix(dock): custom label can be created closable ([`4457ef2`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/4457ef2147e21b856c9dcaf63c81ba98002dcaf1))
* fix: do not shutdown client in "close"
* fix(device_combobox): set minimum size to 125px ([`1206e15`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1206e153094cd8505badf69a1461572a76b4c5ad))
Terminating client connections has to be done at the application level ([`198c1d1`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/198c1d1064cc2dae55de4b941929341faddacb28))
## v0.91.0 (2024-07-23)
### Feature
* feat(dock_area): plugin added ([`a16b87a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a16b87ac28d164230dd2e8020f50ff3a63cd407e))
* feat(dock_area): Added toolbar to dock area to add widgets without CLI interactions ([`cce1367`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/cce1367a72fca7206d351894bd1831b7bbfa7ec6))
* feat(toolbar): expandable menu actions ([`28f26e9`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/28f26e92a46063db1a194be552156a5d3b2c43e7))
## v0.94.3 (2024-08-13)
### Fix
* fix(status_item): icons changed to material design ([`1b9c55a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1b9c55a46a0dfd8678c8e95ff64dd6e8cfb9233e))
* fix(curve_dialog): async curves are shown in curve dialog after addition. ([`7aeb2b5`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/7aeb2b5c26c7c2851e8d663d32521da8daec95ef))
* fix(plugins): Qt Designer plugins icons adjusted ([`f4844d2`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f4844d2e067ce75dc64b89b230d7932b308ddfc2))
* fix(waveform): async device entry is correctly passed, updated and with new scan the previous data are cleared ([`d56ea95`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/d56ea95ef97bfdd0bc3eeddc4505d20b38e28559))
### Test
* test(dock_area): tests extended ([`06fab0e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/06fab0eab926cef5677d4988fd1fce09da342dd8))
* test(waveform_widget): added tests for axis setting and curve dialog ([`f285b35`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/f285b35b491660549e74349318119f7c2c44f619))
## v0.90.0 (2024-07-23)
### Feature
* feat(image_widget): plugin added ([`4371168`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/43711680ba253f81fb0ffe764bcaae701b02bb49))
* feat(image_widget): all toolbar actions added ([`501eb92`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/501eb923f12fa6aaa93f5428ca78e57694edfbc0))
* feat(image_widget): image_widget added ([`6a9317f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6a9317facda896ee784c7fc1db0cd3d68cdfcf73))
## v0.94.2 (2024-08-13)
### Fix
* fix(axis_setting): fix compatibility for issue with horizontal line for PyQt6 ([`1cf6e32`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/1cf6e32303f82bc7c3f3391d0e96a88bc31f29fc))
* fix(image): image is single image mode do not raise popup error when connected twice with the same monitor ([`98b79aa`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/98b79aac7b47b73137f4d582f7f1d552b1d95366))
* fix(image_widget): image_widget autorange fixed ([`7f49893`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/7f49893d2ce3b9d02efa764f7f10442ed6ab8f3c))
## v0.94.1 (2024-08-12)
* fix(image_widget): image widget adjusted ([`3d2ca48`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3d2ca4855c36fe0af59a4b540caa3c8023a81773))
### Fix
* fix(image): only single monitor image is allowed ([`fe7e542`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fe7e542b19dc5b401523501acb74ac03edf62ad4))
* fix: issue #292, wrong key was used to clean _slots internal dictionary ([`93d3977`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/93d397759c756397604ebff5e24f3a580be8620d))
* fix(image): raw data are saved in image item to always have precise processing ([`c15035b`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c15035b6b769a96780a16da9e7f75af3b823654c))
### Refactor
* refactor(jupyter_console_example): added examples of standalone widgets ([`ba0d1ea`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ba0d1ea9031b4ae2e2e73bf269fbfad973b924a5))
### Test
* test(image_widget): tests added ([`70fb276`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/70fb276fdf31dffc105435d3dfe7c5caea0b10ce))
## v0.89.0 (2024-07-22)
## v0.94.0 (2024-08-08)
### Feature
* feat(themes): moved themes to bec_qthemes ([`3798714`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/3798714369adf4023f833b7749d2f46a0ec74eee))
### Unknown
* Revert "feat(themes): moved themes to bec_qthemes"
This reverts commit 3798714369adf4023f833b7749d2f46a0ec74eee ([`fd6ae91`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/fd6ae91993a23a7b8dbb2cf3c4b7c3eda6d2b0f6))
## v0.88.1 (2024-07-22)
* feat: add PositionerControlLine ([`c80a7cd`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/c80a7cd1083baa9543a2cee2e3c3a51dfd209b19))
### Refactor
* refactor(toolbar): generalizations of the ToolBarAction ([`ad112d1`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/ad112d1f08157f6987edd48a0bacf9f669ef1997))
* refactor: adjust dimensions ([`0273bf4`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/0273bf485694609325b5b556a3c69fb53c18446e))
## v0.93.5 (2024-08-08)
### Fix
* fix(positioner_box): icons fixed ([`281633d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/281633deff15b6879dac3a4f0770fa6949aaecdc))
### Refactor
* refactor: add button for positioner selection ([`0d190c5`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/0d190c5c5996e59fec4bdd44d2003e10e200b009))
### Test
* test(dap): wait for fit ([`6269009`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6269009e5451f830cdee58a514c7858483488a8d))
* test(auto-update): wait for rendering ([`6d2442d`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/6d2442d23c683fe92af13df982ce681c07e99cde))
## v0.93.4 (2024-08-07)
### Fix
* fix: rename DeviceBox to PositionerBox, fix test for validation ([`37aa371`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/37aa371e7c4c62d70abf37abc125db0c088790fe))
* fix: add validation for bec_lib.device.Positioner; closes #268 ([`eb54e9f`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/eb54e9f788e97af23db8fe0c78f8facb8688bb99))
## v0.93.3 (2024-08-07)
### Fix
* fix(dock): properly shut down docks and temp areas ([`99ee545`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/99ee545e41c6078654958b668b5b329f85553d16))
* fix(settings): shut down settings dialog ([`b50b3a2`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/b50b3a27e68956e10e8169a0aa698c911d2d9642))
* fix(website): fixed teardown of website widgets ([`a3d4f5a`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/a3d4f5ac4bc52acfed2791a1724fade6972ed320))
### Test
* test: removed quit from teardown ([`cf94599`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/cf94599c2544d6831c8afbe7b340082077557ed1))
* test: removed explicit call to close the widget ([`bf6294e`](https://gitlab.psi.ch/bec/bec_widgets/-/commit/bf6294ecbfd494565d2dc215e4d7e0c280ac7745))

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" height="48px" viewBox="0 -960 960 960" width="48px" fill="#FFFFFF">
<path d="M180-404.87v-50.26h289.75v50.26H180Zm0-162.57v-50.25h454.62v50.25H180Zm0-162.3V-780h454.62v50.26H180ZM524.62-180v-105.69l217.15-216.16q7.46-7.07 16.11-10.3 8.65-3.23 17.3-3.23 9.43 0 18.25 3.53 8.82 3.54 16.03 10.62l37 37.38q6.87 7.47 10.21 16.16Q860-439 860-430.31t-3.37 17.69q-3.37 9-10.52 16.46L630.31-180H524.62Zm250.69-211.69 37-38.62-37-37.38-38 38 38 38Z"/>
</svg>

After

Width:  |  Height:  |  Size: 492 B

View File

Before

Width:  |  Height:  |  Size: 266 B

After

Width:  |  Height:  |  Size: 266 B

View File

@@ -1,5 +1,7 @@
from __future__ import annotations
import threading
from queue import Queue
from typing import TYPE_CHECKING
from pydantic import BaseModel
@@ -25,6 +27,17 @@ class AutoUpdates:
def __init__(self, gui: BECDockArea):
self.gui = gui
self.msg_queue = Queue()
self.auto_update_thread = None
self._shutdown_sentinel = object()
self.start()
def start(self):
"""
Start the auto update thread.
"""
self.auto_update_thread = threading.Thread(target=self.process_queue)
self.auto_update_thread.start()
def start_default_dock(self):
"""
@@ -79,6 +92,16 @@ class AutoUpdates:
info = self.get_scan_info(msg)
self.handler(info)
def process_queue(self):
"""
Process the message queue.
"""
while True:
msg = self.msg_queue.get()
if msg is self._shutdown_sentinel:
break
self.run(msg)
@staticmethod
def get_selected_device(monitored_devices, selected_device):
"""
@@ -151,3 +174,11 @@ class AutoUpdates:
fig.clear_all()
plt = fig.plot(x_name=dev_x, y_name=dev_y, label=f"Scan {info.scan_number} - {dev_y}")
plt.set(title=f"Scan {info.scan_number}", x_label=dev_x, y_label=dev_y)
def shutdown(self):
"""
Shutdown the auto update thread.
"""
self.msg_queue.put(self._shutdown_sentinel)
if self.auto_update_thread:
self.auto_update_thread.join()

View File

@@ -21,9 +21,11 @@ class Widgets(str, enum.Enum):
BECQueue = "BECQueue"
BECStatusBox = "BECStatusBox"
BECWaveformWidget = "BECWaveformWidget"
DeviceBox = "DeviceBox"
DeviceBrowser = "DeviceBrowser"
DeviceComboBox = "DeviceComboBox"
DeviceLineEdit = "DeviceLineEdit"
PositionerBox = "PositionerBox"
PositionerControlLine = "PositionerControlLine"
RingProgressBar = "RingProgressBar"
ScanControl = "ScanControl"
StopButton = "StopButton"
@@ -2287,7 +2289,7 @@ class BECWaveformWidget(RPCBase):
"""
class DeviceBox(RPCBase):
class DeviceBrowser(RPCBase):
@property
@rpc_call
def _config_dict(self) -> "dict":
@@ -2359,6 +2361,28 @@ class DeviceLineEdit(RPCBase):
"""
class PositionerBox(RPCBase):
@rpc_call
def set_positioner(self, positioner: str):
"""
Set the device
Args:
positioner (Positioner | str) : Positioner to set, accepts str or the device
"""
class PositionerControlLine(RPCBase):
@rpc_call
def set_positioner(self, positioner: str):
"""
Set the device
Args:
positioner (Positioner | str) : Positioner to set, accepts str or the device
"""
class Ring(RPCBase):
@rpc_call
def _get_all_rpc(self) -> "dict":

View File

@@ -6,17 +6,16 @@ import json
import os
import select
import subprocess
import sys
import threading
import time
import uuid
from functools import wraps
from typing import TYPE_CHECKING
from bec_lib.client import BECClient
from bec_lib.endpoints import MessageEndpoints
from bec_lib.logger import bec_logger
from bec_lib.utils.import_utils import isinstance_based_on_class_name, lazy_import, lazy_import_from
from qtpy.QtCore import QEventLoop, QSocketNotifier, QTimer
import bec_widgets.cli.client as client
from bec_widgets.cli.auto_updates import AutoUpdates
@@ -24,10 +23,6 @@ from bec_widgets.cli.auto_updates import AutoUpdates
if TYPE_CHECKING:
from bec_lib.device import DeviceBase
from bec_widgets.cli.client import BECDockArea, BECFigure
from bec_lib.serialization import MsgpackSerialization
messages = lazy_import("bec_lib.messages")
# from bec_lib.connector import MessageObject
MessageObject = lazy_import_from("bec_lib.connector", ("MessageObject",))
@@ -105,6 +100,7 @@ def _start_plot_process(gui_id: str, gui_class: type, config: dict | str, logger
env_dict = os.environ.copy()
env_dict["PYTHONUNBUFFERED"] = "1"
if logger is None:
stdout_redirect = subprocess.DEVNULL
stderr_redirect = subprocess.DEVNULL
@@ -184,7 +180,7 @@ class BECGuiClientMixin:
if isinstance(msg, messages.ScanStatusMessage):
if not self.gui_is_alive():
return
self.auto_updates.run(msg)
self.auto_updates.msg_queue.put(msg)
def show(self) -> None:
"""
@@ -213,6 +209,8 @@ class BECGuiClientMixin:
self._process_output_processing_thread.join()
self._process.wait()
self._process = None
if self.auto_updates is not None:
self.auto_updates.shutdown()
class RPCResponseTimeoutError(Exception):
@@ -224,54 +222,14 @@ class RPCResponseTimeoutError(Exception):
)
class QtRedisMessageWaiter:
def __init__(self, redis_connector, message_to_wait):
self.ev_loop = QEventLoop()
self.response = None
self.connector = redis_connector
self.message_to_wait = message_to_wait
self.pubsub = redis_connector._redis_conn.pubsub()
self.pubsub.subscribe(self.message_to_wait.endpoint)
fd = self.pubsub.connection._sock.fileno()
self.notifier = QSocketNotifier(fd, QSocketNotifier.Read)
self.notifier.activated.connect(self._pubsub_readable)
def _msg_received(self, msg_obj):
self.response = msg_obj.value
self.ev_loop.quit()
def wait(self, timeout=1):
timer = QTimer()
timer.singleShot(timeout * 1000, self.ev_loop.quit)
self.ev_loop.exec_()
timer.stop()
self.notifier.setEnabled(False)
self.pubsub.close()
return self.response
def _pubsub_readable(self, fd):
while True:
msg = self.pubsub.get_message()
if msg:
if msg["type"] == "subscribe":
# get_message buffers, so we may already have the answer
# let's check...
continue
else:
break
else:
return
channel = msg["channel"].decode()
msg = MessageObject(topic=channel, value=MsgpackSerialization.loads(msg["data"]))
self.connector._execute_callback(self._msg_received, msg, {})
class RPCBase:
def __init__(self, gui_id: str = None, config: dict = None, parent=None) -> None:
self._client = BECDispatcher().client
self._client = BECClient() # BECClient is a singleton; here, we simply get the instance
self._config = config if config is not None else {}
self._gui_id = gui_id if gui_id is not None else str(uuid.uuid4())
self._parent = parent
self._msg_wait_event = threading.Event()
self._rpc_response = None
super().__init__()
# print(f"RPCBase: {self._gui_id}")
@@ -315,24 +273,39 @@ class RPCBase:
# pylint: disable=protected-access
receiver = self._root._gui_id
if wait_for_rpc_response:
redis_msg = QtRedisMessageWaiter(
self._client.connector, MessageEndpoints.gui_instruction_response(request_id)
self._rpc_response = None
self._msg_wait_event.clear()
self._client.connector.register(
MessageEndpoints.gui_instruction_response(request_id),
cb=self._on_rpc_response,
parent=self,
)
self._client.connector.set_and_publish(MessageEndpoints.gui_instructions(receiver), rpc_msg)
if wait_for_rpc_response:
response = redis_msg.wait(timeout)
if response is None:
raise RPCResponseTimeoutError(request_id, timeout)
try:
finished = self._msg_wait_event.wait(10)
if not finished:
raise RPCResponseTimeoutError(request_id, timeout)
finally:
self._msg_wait_event.clear()
self._client.connector.unregister(
MessageEndpoints.gui_instruction_response(request_id), cb=self._on_rpc_response
)
# get class name
if not response.accepted:
raise ValueError(response.message["error"])
msg_result = response.message.get("result")
if not self._rpc_response.accepted:
raise ValueError(self._rpc_response.message["error"])
msg_result = self._rpc_response.message.get("result")
self._rpc_response = None
return self._create_widget_from_msg_result(msg_result)
@staticmethod
def _on_rpc_response(msg: MessageObject, parent: RPCBase) -> None:
msg = msg.value
parent._msg_wait_event.set()
parent._rpc_response = msg
def _create_widget_from_msg_result(self, msg_result):
if msg_result is None:
return None
@@ -359,4 +332,8 @@ class RPCBase:
Check if the GUI is alive.
"""
heart = self._client.connector.get(MessageEndpoints.gui_heartbeat(self._root._gui_id))
return heart is not None
if heart is None:
return False
if heart.status == messages.BECStatus.RUNNING:
return True
return False

View File

@@ -28,12 +28,13 @@ class BECWidgetsCLIServer:
def __init__(
self,
gui_id: str = None,
gui_id: str,
dispatcher: BECDispatcher = None,
client=None,
config=None,
gui_class: Union[BECFigure, BECDockArea] = BECFigure,
) -> None:
self.status = messages.BECStatus.BUSY
self.dispatcher = BECDispatcher(config=config) if dispatcher is None else dispatcher
self.client = self.dispatcher.client if client is None else client
self.client.start()
@@ -47,11 +48,12 @@ class BECWidgetsCLIServer:
)
# Setup QTimer for heartbeat
self._shutdown_event = False
self._heartbeat_timer = QTimer()
self._heartbeat_timer.timeout.connect(self.emit_heartbeat)
self._heartbeat_timer.start(200)
self.status = messages.BECStatus.RUNNING
def on_rpc_update(self, msg: dict, metadata: dict):
request_id = metadata.get("request_id")
try:
@@ -111,16 +113,16 @@ class BECWidgetsCLIServer:
return obj
def emit_heartbeat(self):
if self._shutdown_event is False:
self.client.connector.set(
MessageEndpoints.gui_heartbeat(self.gui_id),
messages.StatusMessage(name=self.gui_id, status=1, info={}),
expire=1,
)
self.client.connector.set(
MessageEndpoints.gui_heartbeat(self.gui_id),
messages.StatusMessage(name=self.gui_id, status=self.status, info={}),
expire=10,
)
def shutdown(self): # TODO not sure if needed when cleanup is done at level of BECConnector
self._shutdown_event = True
self.status = messages.BECStatus.IDLE
self._heartbeat_timer.stop()
self.emit_heartbeat()
self.gui.close()
self.client.shutdown()
@@ -128,15 +130,15 @@ class BECWidgetsCLIServer:
class SimpleFileLikeFromLogOutputFunc:
def __init__(self, log_func):
self._log_func = log_func
self._buffer = []
def write(self, buffer):
for line in buffer.rstrip().splitlines():
line = line.rstrip()
if line:
self._log_func(line)
self._buffer.append(buffer)
def flush(self):
return
lines, _, remaining = "".join(self._buffer).rpartition("\n")
self._log_func(lines)
self._buffer = [remaining]
def close(self):
return
@@ -231,4 +233,5 @@ def main():
if __name__ == "__main__": # pragma: no cover
sys.argv = ["bec_widgets.cli.server", "--id", "test", "--gui_class", "BECDockArea"]
main()

View File

@@ -46,6 +46,7 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
"w7": self.w7,
"w8": self.w8,
"w9": self.w9,
"w10": self.w10,
"d0": self.d0,
"d1": self.d1,
"d2": self.d2,

View File

@@ -2,8 +2,9 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from qtpy.QtGui import QGuiApplication, QIcon
import bec_widgets
from bec_widgets.examples.plugin_example_pyside.tictactoe import TicTacToe
@@ -46,8 +47,9 @@ class TicTacToePlugin(QDesignerCustomWidgetInterface): # pragma: no cover
return "Games"
def icon(self):
icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "games.png")
return QIcon(icon_path)
palette = QGuiApplication.palette()
pixmap = material_icon("sports_esports", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "tictactoe"

View File

@@ -0,0 +1,47 @@
from bec_lib.serialization import MsgpackSerialization
from bec_lib.utils import lazy_import_from
from qtpy.QtCore import QEventLoop, QSocketNotifier, QTimer
MessageObject = lazy_import_from("bec_lib.connector", ("MessageObject",))
class QtRedisMessageWaiter:
def __init__(self, redis_connector, message_to_wait):
self.ev_loop = QEventLoop()
self.response = None
self.connector = redis_connector
self.message_to_wait = message_to_wait
self.pubsub = redis_connector._redis_conn.pubsub()
self.pubsub.subscribe(self.message_to_wait.endpoint)
fd = self.pubsub.connection._sock.fileno()
self.notifier = QSocketNotifier(fd, QSocketNotifier.Read)
self.notifier.activated.connect(self._pubsub_readable)
def _msg_received(self, msg_obj):
self.response = msg_obj.value
self.ev_loop.quit()
def wait(self, timeout=1):
timer = QTimer()
timer.singleShot(timeout * 1000, self.ev_loop.quit)
self.ev_loop.exec_()
timer.stop()
self.notifier.setEnabled(False)
self.pubsub.close()
return self.response
def _pubsub_readable(self, fd):
while True:
msg = self.pubsub.get_message()
if msg:
if msg["type"] == "subscribe":
# get_message buffers, so we may already have the answer
# let's check...
continue
else:
break
else:
return
channel = msg["channel"].decode()
msg = MessageObject(topic=channel, value=MsgpackSerialization.loads(msg["data"]))
self.connector._execute_callback(self._msg_received, msg, {})

View File

@@ -106,3 +106,14 @@ class SettingsDialog(QDialog):
Apply the changes made in the settings widget without closing the dialog.
"""
self.widget.accept_changes()
def cleanup(self):
"""
Cleanup the dialog.
"""
self.button_box.close()
self.button_box.deleteLater()
def closeEvent(self, event):
self.cleanup()
super().closeEvent(event)

View File

@@ -8,7 +8,7 @@ import redis
from bec_lib.client import BECClient
from bec_lib.redis_connector import MessageObject, RedisConnector
from bec_lib.service_config import ServiceConfig
from qtpy.QtCore import PYQT6, PYSIDE6, QCoreApplication, QObject
from qtpy.QtCore import QObject
from qtpy.QtCore import Signal as pyqtSignal
if TYPE_CHECKING:
@@ -75,7 +75,6 @@ class BECDispatcher:
_instance = None
_initialized = False
qapp = None
def __new__(cls, client=None, config: str = None, *args, **kwargs):
if cls._instance is None:
@@ -87,9 +86,6 @@ class BECDispatcher:
if self._initialized:
return
if not QCoreApplication.instance():
BECDispatcher.qapp = QCoreApplication([])
self._slots = collections.defaultdict(set)
self.client = client
@@ -123,16 +119,6 @@ class BECDispatcher:
cls._instance = None
cls._initialized = False
if not cls.qapp:
return
# shutdown QCoreApp if it exists
if PYQT6:
cls.qapp.exit()
elif PYSIDE6:
cls.qapp.shutdown()
cls.qapp = None
def connect_slot(
self,
slot: Callable,
@@ -162,9 +148,9 @@ class BECDispatcher:
return
self.client.connector.unregister(topics, cb=connected_slot)
topics_str, _ = self.client.connector._convert_endpointinfo(topics)
self._slots[slot].difference_update(set(topics_str))
if not self._slots[slot]:
del self._slots[slot]
self._slots[connected_slot].difference_update(set(topics_str))
if not self._slots[connected_slot]:
del self._slots[connected_slot]
def disconnect_topics(self, topics: Union[str, list]):
self.client.connector.unregister(topics)

View File

@@ -2,8 +2,9 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from qtpy.QtGui import QGuiApplication, QIcon
import bec_widgets
from bec_widgets.widgets.bec_queue.bec_queue import BECQueue
@@ -34,8 +35,9 @@ class BECQueuePlugin(QDesignerCustomWidgetInterface): # pragma: no cover
return "BEC Services"
def icon(self):
icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "device_line_edit.png")
return QIcon(icon_path)
palette = QGuiApplication.palette()
pixmap = material_icon("edit_note", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "bec_queue"

View File

@@ -2,8 +2,9 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from qtpy.QtGui import QGuiApplication, QIcon
import bec_widgets
from bec_widgets.widgets.bec_status_box.bec_status_box import BECStatusBox
@@ -34,8 +35,9 @@ class BECStatusBoxPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
return "BEC Services"
def icon(self):
icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "status.png")
return QIcon(icon_path)
palette = QGuiApplication.palette()
pixmap = material_icon("dns", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "bec_status_box"

View File

@@ -34,3 +34,10 @@ class ColorButton(pg.ColorButton):
return self.color().getRgb()
if format == "HEX":
return self.color().name()
def cleanup(self):
"""
Clean up the ColorButton.
"""
self.colorDialog.close()
self.colorDialog.deleteLater()

View File

@@ -1,7 +1,8 @@
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from qtpy.QtGui import QGuiApplication, QIcon
import bec_widgets
from bec_widgets.widgets.color_button.color_button import ColorButton
@@ -31,8 +32,9 @@ class ColorButtonPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
return "BEC Buttons"
def icon(self):
icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "color_button.png")
return QIcon(icon_path)
palette = QGuiApplication.palette()
pixmap = material_icon("colors", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "color_button"

View File

@@ -2,8 +2,9 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from qtpy.QtGui import QGuiApplication, QIcon
import bec_widgets
from bec_widgets.widgets.colormap_selector.colormap_selector import ColormapSelector
@@ -34,8 +35,9 @@ class ColormapSelectorPlugin(QDesignerCustomWidgetInterface): # pragma: no cove
return "BEC Buttons"
def icon(self):
icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "colormap_selector.png")
return QIcon(icon_path)
palette = QGuiApplication.palette()
pixmap = material_icon("palette", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "colormap_selector"

View File

@@ -1 +0,0 @@
{'files': ['device_box.py']}

View File

@@ -0,0 +1,107 @@
import os
import re
from typing import Optional
from bec_lib.callback_handler import EventType
from pyqtgraph import SignalProxy
from qtpy.QtCore import Signal, Slot
from qtpy.QtWidgets import QLineEdit, QListWidgetItem, QVBoxLayout, QWidget
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.ui_loader import UILoader
from bec_widgets.widgets.device_browser.device_item import DeviceItem
class DeviceBrowser(BECWidget, QWidget):
device_update: Signal = Signal()
def __init__(
self,
parent: Optional[QWidget] = None,
config=None,
client=None,
gui_id: Optional[str] = None,
) -> None:
super().__init__(client=client, config=config, gui_id=gui_id)
QWidget.__init__(self, parent)
self.get_bec_shortcuts()
self.ui = None
self.ini_ui()
self.proxy_device_update = SignalProxy(
self.ui.filter_input.textChanged, rateLimit=500, slot=self.update_device_list
)
self.bec_dispatcher.client.callbacks.register(
EventType.DEVICE_UPDATE, self.on_device_update
)
self.device_update.connect(self.update_device_list)
self.update_device_list()
def ini_ui(self) -> None:
"""
Initialize the UI by loading the UI file and setting the layout.
"""
layout = QVBoxLayout()
layout.setContentsMargins(0, 0, 0, 0)
ui_file_path = os.path.join(os.path.dirname(__file__), "device_browser.ui")
self.ui = UILoader(self).loader(ui_file_path)
layout.addWidget(self.ui)
self.setLayout(layout)
def on_device_update(self, action: str, content: dict) -> None:
"""
Callback for device update events. Triggers the device_update signal.
Args:
action (str): The action that triggered the event.
content (dict): The content of the config update.
"""
if action in ["add", "remove", "reload"]:
self.device_update.emit()
@Slot()
def update_device_list(self) -> None:
"""
Update the device list based on the filter input.
There are two ways to trigger this function:
1. By changing the text in the filter input.
2. By emitting the device_update signal.
Either way, the function will filter the devices based on the filter input text and update the device list.
"""
filter_text = self.ui.filter_input.text()
try:
regex = re.compile(filter_text, re.IGNORECASE)
except re.error:
regex = None # Invalid regex, disable filtering
dev_list = self.ui.device_list
dev_list.clear()
for device in self.dev:
if regex is None or regex.search(device):
item = QListWidgetItem(dev_list)
device_item = DeviceItem(device)
# pylint: disable=protected-access
tooltip = self.dev[device]._config.get("description", "")
device_item.setToolTip(tooltip)
item.setSizeHint(device_item.sizeHint())
dev_list.setItemWidget(item, device_item)
dev_list.addItem(item)
if __name__ == "__main__": # pragma: no cover
import sys
from qtpy.QtWidgets import QApplication
from bec_widgets.utils.colors import apply_theme
app = QApplication(sys.argv)
apply_theme("light")
widget = DeviceBrowser()
widget.show()
sys.exit(app.exec_())

View File

@@ -0,0 +1 @@
{'files': ['device_browser.py']}

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>406</width>
<height>500</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="browser_group_box">
<property name="title">
<string>Device Browser</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="filter_layout">
<item>
<widget class="QLineEdit" name="filter_input">
<property name="placeholderText">
<string>Filter</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QListWidget" name="device_list"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -1,44 +1,42 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from qtpy.QtGui import QGuiApplication, QIcon
import bec_widgets
from bec_widgets.widgets.device_box.device_box import DeviceBox
from bec_widgets.widgets.device_browser.device_browser import DeviceBrowser
DOM_XML = """
<ui language='c++'>
<widget class='DeviceBox' name='device_box'>
<widget class='DeviceBrowser' name='device_browser'>
</widget>
</ui>
"""
MODULE_PATH = os.path.dirname(bec_widgets.__file__)
class DeviceBoxPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
class DeviceBrowserPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
def __init__(self):
super().__init__()
self._form_editor = None
def createWidget(self, parent):
t = DeviceBox(parent)
t = DeviceBrowser(parent)
return t
def domXml(self):
return DOM_XML
def group(self):
return "Device Control"
return "BEC Services"
def icon(self):
icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "device_box.png")
return QIcon(icon_path)
palette = QGuiApplication.palette()
pixmap = material_icon("lists", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "device_box"
return "device_browser"
def initialize(self, form_editor):
self._form_editor = form_editor
@@ -50,10 +48,10 @@ class DeviceBoxPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
return self._form_editor is not None
def name(self):
return "DeviceBox"
return "DeviceBrowser"
def toolTip(self):
return "A widget for controlling a single positioner. "
return "DeviceBrowser"
def whatsThis(self):
return self.toolTip()

View File

@@ -0,0 +1 @@
from .device_item import DeviceItem

View File

@@ -0,0 +1,53 @@
from __future__ import annotations
from typing import TYPE_CHECKING
from qtpy.QtCore import QMimeData, Qt
from qtpy.QtGui import QDrag
from qtpy.QtWidgets import QHBoxLayout, QLabel, QWidget
if TYPE_CHECKING:
from qtpy.QtGui import QMouseEvent
class DeviceItem(QWidget):
def __init__(self, device: str) -> None:
super().__init__()
self.device = device
layout = QHBoxLayout()
layout.setContentsMargins(10, 2, 10, 2)
self.label = QLabel(device)
layout.addWidget(self.label)
self.setLayout(layout)
self.setStyleSheet(
"""
border: 1px solid #ddd;
border-radius: 5px;
padding: 10px;
"""
)
def mousePressEvent(self, event: QMouseEvent) -> None:
if event.button() == Qt.LeftButton:
drag = QDrag(self)
mime_data = QMimeData()
mime_data.setText(self.device)
drag.setMimeData(mime_data)
drag.exec_(Qt.MoveAction)
def mouseDoubleClickEvent(self, event: QMouseEvent) -> None:
print("Double Clicked")
# TODO: Implement double click action for opening the device properties dialog
return super().mouseDoubleClickEvent(event)
if __name__ == "__main__": # pragma: no cover
import sys
from qtpy.QtWidgets import QApplication
app = QApplication(sys.argv)
widget = DeviceItem("Device")
widget.show()
sys.exit(app.exec_())

View File

@@ -6,9 +6,9 @@ def main(): # pragma: no cover
return
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
from bec_widgets.widgets.device_box.device_box_plugin import DeviceBoxPlugin
from bec_widgets.widgets.device_browser.device_browser_plugin import DeviceBrowserPlugin
QPyDesignerCustomWidgetCollection.addCustomWidget(DeviceBoxPlugin())
QPyDesignerCustomWidgetCollection.addCustomWidget(DeviceBrowserPlugin())
if __name__ == "__main__": # pragma: no cover

View File

@@ -2,8 +2,9 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from qtpy.QtGui import QGuiApplication, QIcon
import bec_widgets
from bec_widgets.widgets.device_combobox.device_combobox import DeviceComboBox
@@ -34,8 +35,9 @@ class DeviceComboBoxPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
return "Device Control"
def icon(self):
icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "device_combo_box.png")
return QIcon(icon_path)
palette = QGuiApplication.palette()
pixmap = material_icon("list_alt", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "device_combobox"

View File

@@ -2,8 +2,9 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from qtpy.QtGui import QGuiApplication, QIcon
import bec_widgets
from bec_widgets.widgets.device_line_edit.device_line_edit import DeviceLineEdit
@@ -34,8 +35,9 @@ class DeviceLineEditPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
return "Device Control"
def icon(self):
icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "device_line_edit.png")
return QIcon(icon_path)
palette = QGuiApplication.palette()
pixmap = material_icon("edit_note", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "device_line_edit"

View File

@@ -4,6 +4,7 @@ from typing import TYPE_CHECKING, Any, Literal, Optional
from pydantic import Field
from pyqtgraph.dockarea import Dock, DockLabel
from qtpy import QtCore, QtGui
from bec_widgets.cli.rpc_wigdet_handler import widget_handler
from bec_widgets.utils import ConnectionConfig, GridLayoutManager
@@ -12,8 +13,6 @@ from bec_widgets.utils.bec_widget import BECWidget
if TYPE_CHECKING:
from qtpy.QtWidgets import QWidget
from bec_widgets.widgets.dock import BECDockArea
class DockConfig(ConnectionConfig):
widgets: dict[str, Any] = Field({}, description="The widgets in the dock.")
@@ -26,6 +25,23 @@ class DockConfig(ConnectionConfig):
class CustomDockLabel(DockLabel):
def __init__(self, text: str, closable: bool = True):
super().__init__(text, closable)
if closable:
red_icon = QtGui.QIcon()
pixmap = QtGui.QPixmap(32, 32)
pixmap.fill(QtCore.Qt.GlobalColor.red)
painter = QtGui.QPainter(pixmap)
pen = QtGui.QPen(QtCore.Qt.GlobalColor.white)
pen.setWidth(2)
painter.setPen(pen)
painter.drawLine(8, 8, 24, 24)
painter.drawLine(24, 8, 8, 24)
painter.end()
red_icon.addPixmap(pixmap)
self.closeButton.setIcon(red_icon)
def updateStyle(self):
r = "3px"
if self.dim:
@@ -137,6 +153,7 @@ class BECDock(BECWidget, Dock):
super().dropEvent(event)
if old_area in self.orig_area.tempAreas and old_area != self.orig_area:
self.orig_area.removeTempArea(old_area)
old_area.window().deleteLater()
def float(self):
"""
@@ -268,7 +285,7 @@ class BECDock(BECWidget, Dock):
"""
Attach the dock to the parent dock area.
"""
self.orig_area.removeTempArea(self.area)
self.parent_dock_area.remove_temp_area(self.area)
def detach(self):
"""
@@ -303,6 +320,8 @@ class BECDock(BECWidget, Dock):
if hasattr(widget, "cleanup"):
widget.cleanup()
self.widgets.clear()
self.label.close()
self.label.deleteLater()
super().cleanup()
def close(self):

View File

@@ -83,8 +83,8 @@ class BECDockArea(BECWidget, QWidget):
"scan_control": IconAction(
icon_path="scan_control.svg", tooltip="Add Scan Control"
),
"device_box": IconAction(
icon_path="device_box.svg", tooltip="Add Device Box"
"positioner_box": IconAction(
icon_path="positioner_box.svg", tooltip="Add Device Box"
),
},
),
@@ -132,8 +132,8 @@ class BECDockArea(BECWidget, QWidget):
self.toolbar.widgets["menu_devices"].widgets["scan_control"].triggered.connect(
lambda: self.add_dock(widget="ScanControl", prefix="scan_control")
)
self.toolbar.widgets["menu_devices"].widgets["device_box"].triggered.connect(
lambda: self.add_dock(widget="DeviceBox", prefix="device_box")
self.toolbar.widgets["menu_devices"].widgets["positioner_box"].triggered.connect(
lambda: self.add_dock(widget="PositionerBox", prefix="positioner_box")
)
# Menu Utils
@@ -231,6 +231,7 @@ class BECDockArea(BECWidget, QWidget):
self.config.docks.pop(name, None)
if dock:
dock.close()
dock.deleteLater()
if len(self.dock_area.docks) <= 1:
for dock in self.dock_area.docks.values():
dock.hide_title_bar()
@@ -329,7 +330,16 @@ class BECDockArea(BECWidget, QWidget):
"""
while self.dock_area.tempAreas:
for temp_area in self.dock_area.tempAreas:
self.dock_area.removeTempArea(temp_area)
self.remove_temp_area(temp_area)
def remove_temp_area(self, area):
"""
Remove a temporary area from the dock area.
This is a patched method of pyqtgraph's removeTempArea
"""
self.dock_area.tempAreas.remove(area)
area.window().close()
area.window().deleteLater()
def clear_all(self):
"""
@@ -345,6 +355,10 @@ class BECDockArea(BECWidget, QWidget):
Cleanup the dock area.
"""
self.clear_all()
self.toolbar.close()
self.toolbar.deleteLater()
self.dock_area.close()
self.dock_area.deleteLater()
super().cleanup()
def close(self):

View File

@@ -2,8 +2,9 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from qtpy.QtGui import QGuiApplication, QIcon
import bec_widgets
from bec_widgets.widgets.dock import BECDockArea
@@ -34,8 +35,9 @@ class BECDockAreaPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
return "BEC Plots"
def icon(self):
icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "dock_area.png")
return QIcon(icon_path)
palette = QGuiApplication.palette()
pixmap = material_icon("widgets", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "dock_area"

View File

@@ -513,6 +513,13 @@ class BECFigure(BECWidget, pg.GraphicsLayoutWidget):
if widget_id in self._widgets:
raise ValueError(f"Widget with ID '{widget_id}' already exists.")
# Check if position is occupied
if row is not None and col is not None:
if self.getItem(row, col):
raise ValueError(f"Position at row {row} and column {col} is already occupied.")
else:
row, col = self._find_next_empty_position()
widget = self.widget_handler.create_widget(
widget_type=widget_type,
widget_id=widget_id,
@@ -525,23 +532,11 @@ class BECFigure(BECWidget, pg.GraphicsLayoutWidget):
# used otherwise multiple times
widget.set_gui_id(widget_id)
# Check if position is occupied
if row is not None and col is not None:
if self.getItem(row, col):
raise ValueError(f"Position at row {row} and column {col} is already occupied.")
widget.config.row = row
widget.config.col = col
widget.config.row = row
widget.config.col = col
# Add widget to the figure
self.addItem(widget, row=row, col=col)
else:
row, col = self._find_next_empty_position()
widget.config.row = row
widget.config.col = col
# Add widget to the figure
self.addItem(widget, row=row, col=col)
# Add widget to the figure
self.addItem(widget, row=row, col=col)
# Update num_cols and num_rows based on the added widget
self.config.num_rows = max(self.config.num_rows, row + 1)
@@ -620,6 +615,7 @@ class BECFigure(BECWidget, pg.GraphicsLayoutWidget):
"""
if widget_id in self._widgets:
widget = self._widgets.pop(widget_id)
widget.cleanup_pyqtgraph()
widget.cleanup()
self.removeItem(widget)
self.grid[widget.config.row][widget.config.col] = None
@@ -745,3 +741,12 @@ class BECFigure(BECWidget, pg.GraphicsLayoutWidget):
self.config = FigureConfig(
widget_class=self.__class__.__name__, gui_id=self.gui_id, theme=theme
)
def cleanup_pyqtgraph_all_widgets(self):
"""Clean up the pyqtgraph widget."""
for widget in self.widget_list:
widget.cleanup_pyqtgraph()
def cleanup(self):
"""Close the figure widget."""
self.cleanup_pyqtgraph_all_widgets()

View File

@@ -251,9 +251,10 @@ class BECImageShow(BECPlotBase):
image_exits = self._check_image_id(monitor, self._images)
if image_exits:
raise ValueError(
f"Monitor with ID '{monitor}' already exists in widget '{self.gui_id}'."
)
# raise ValueError(
# f"Monitor with ID '{monitor}' already exists in widget '{self.gui_id}'."
# )
return
# monitor = self.entry_validator.validate_monitor(monitor)
@@ -577,10 +578,10 @@ class BECImageShow(BECPlotBase):
self, source: str, name: str, config: ImageItemConfig, data=None
) -> BECImageItem: # TODO fix types
config.parent_id = self.gui_id
image = BECImageItem(config=config, parent_image=self)
self.plot_item.addItem(image)
if self.single_image is True and len(self.images) > 0:
self.remove_image(0)
image = BECImageItem(config=config, parent_image=self)
self.plot_item.addItem(image)
self._images[source][name] = image
if source == "device_monitor_2d":
self._connect_device_monitor_2d(config.monitor)
@@ -681,3 +682,17 @@ class BECImageShow(BECPlotBase):
self.on_image_update, MessageEndpoints.device_monitor_2d(monitor)
)
self.images.clear()
def cleanup_pyqtgraph(self):
"""Cleanup pyqtgraph items."""
super().cleanup_pyqtgraph()
item = self.plot_item
if not item.items:
return
cbar = item.items[0].color_bar
cbar.vb.menu.close()
cbar.vb.menu.deleteLater()
cbar.gradient.menu.close()
cbar.gradient.menu.deleteLater()
cbar.gradient.colorDialog.close()
cbar.gradient.colorDialog.deleteLater()

View File

@@ -314,3 +314,11 @@ class BECPlotBase(BECConnector, pg.GraphicsLayout):
"""Remove the plot widget from the figure."""
if self.figure is not None:
self.figure.remove(widget_id=self.gui_id)
def cleanup_pyqtgraph(self):
"""Cleanup pyqtgraph items."""
item = self.plot_item
item.vb.menu.close()
item.vb.menu.deleteLater()
item.ctrlMenu.close()
item.ctrlMenu.deleteLater()

View File

@@ -943,7 +943,9 @@ class BECWaveform(BECPlotBase):
self.setup_dap(self.old_scan_id, self.scan_id)
if self._curves_data["async"]:
for curve_id, curve in self._curves_data["async"].items():
self.setup_async(curve.config.signals.y.name)
self.setup_async(
name=curve.config.signals.y.name, entry=curve.config.signals.y.entry
)
@Slot(dict, dict)
def on_scan_segment(self, msg: dict, metadata: dict):
@@ -1005,18 +1007,18 @@ class BECWaveform(BECPlotBase):
)
@Slot(str)
def setup_async(self, device: str):
def setup_async(self, name: str, entry: str):
self.bec_dispatcher.disconnect_slot(
self.on_async_readback, MessageEndpoints.device_async_readback(self.old_scan_id, device)
self.on_async_readback, MessageEndpoints.device_async_readback(self.old_scan_id, name)
)
try:
self._curves_data["async"][f"{device}-{device}"].clear_data()
self._curves_data["async"][f"{name}-{entry}"].clear_data()
except KeyError:
pass
if len(self._curves_data["async"]) > 0:
self.bec_dispatcher.connect_slot(
self.on_async_readback,
MessageEndpoints.device_async_readback(self.scan_id, device),
MessageEndpoints.device_async_readback(self.scan_id, name),
from_start=True,
)

View File

@@ -2,8 +2,9 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from qtpy.QtGui import QGuiApplication, QIcon
import bec_widgets
from bec_widgets.widgets.image.image_widget import BECImageWidget
@@ -34,8 +35,9 @@ class BECImageWidgetPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
return "BEC Plots"
def icon(self):
icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "image.png")
return QIcon(icon_path)
palette = QGuiApplication.palette()
pixmap = material_icon("image", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "bec_image_widget"

View File

@@ -457,7 +457,8 @@ class BECImageWidget(BECWidget, QWidget):
def cleanup(self):
self.fig.cleanup()
self.client.shutdown()
self.toolbar.close()
self.toolbar.deleteLater()
return super().cleanup()

View File

@@ -63,8 +63,6 @@ class BECJupyterConsole(RichJupyterWidget): # pragma: no cover:
def closeEvent(self, event):
self.shutdown_kernel()
if self.client:
self.client.shutdown()
if __name__ == "__main__": # pragma: no cover

View File

@@ -1,7 +1,8 @@
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from qtpy.QtGui import QGuiApplication, QIcon
import bec_widgets
from bec_widgets.widgets.motor_map.motor_map_widget import BECMotorMapWidget
@@ -32,8 +33,9 @@ class BECMotorMapWidgetPlugin(QDesignerCustomWidgetInterface): # pragma: no cov
return "BEC Plots"
def icon(self):
icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "motor_map.png")
return QIcon(icon_path)
palette = QGuiApplication.palette()
pixmap = material_icon("my_location", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "bec_motor_map_widget"

View File

@@ -45,3 +45,12 @@ class MotorMapSettings(SettingWidget):
self.target_widget.set_scatter_size(scatter_size)
self.target_widget.set_background_value(background_intensity)
self.target_widget.set_color(color)
def cleanup(self):
self.ui.color.cleanup()
self.ui.color.close()
self.ui.color.deleteLater()
def closeEvent(self, event):
self.cleanup()
super().closeEvent(event)

View File

@@ -2,8 +2,9 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from qtpy.QtGui import QGuiApplication, QIcon
import bec_widgets
from bec_widgets.widgets.position_indicator.position_indicator import PositionIndicator
@@ -34,8 +35,9 @@ class PositionIndicatorPlugin(QDesignerCustomWidgetInterface): # pragma: no cov
return "BEC Utils"
def icon(self):
icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "position_indicator.png")
return QIcon(icon_path)
palette = QGuiApplication.palette()
pixmap = material_icon("horizontal_distribute", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "position_indicator"

View File

@@ -1,22 +1,43 @@
""" Module for a PositionerBox widget to control a positioner device."""
import os
import uuid
from bec_lib.device import Positioner
from bec_lib.endpoints import MessageEndpoints
from bec_lib.logger import bec_logger
from bec_lib.messages import ScanQueueMessage
from qtpy.QtCore import Property, Signal, Slot
from qtpy.QtGui import QDoubleValidator
from qtpy.QtWidgets import QDoubleSpinBox, QVBoxLayout, QWidget
from qtpy.QtCore import Property, QSize, Signal, Slot
from qtpy.QtGui import QDoubleValidator, QIcon
from qtpy.QtWidgets import QDialog, QDoubleSpinBox, QPushButton, QVBoxLayout, QWidget
from bec_widgets.utils import UILoader
from bec_widgets.utils.bec_widget import BECWidget
from bec_widgets.utils.colors import apply_theme
from bec_widgets.widgets.device_line_edit.device_line_edit import DeviceLineEdit
logger = bec_logger.logger
MODULE_PATH = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
class DeviceBox(BECWidget, QWidget):
class PositionerBox(BECWidget, QWidget):
"""Simple Widget to control a positioner in box form"""
ui_file = "positioner_box.ui"
dimensions = (234, 224)
USER_ACCESS = ["set_positioner"]
device_changed = Signal(str, str)
def __init__(self, parent=None, device=None, *args, **kwargs):
super().__init__(*args, **kwargs)
def __init__(self, parent=None, device: Positioner = None, *args, **kwargs):
"""Initialize the PositionerBox widget.
Args:
parent: The parent widget.
device (Positioner): The device to control.
"""
super().__init__(**kwargs)
QWidget.__init__(self, parent=parent)
self.get_bec_shortcuts()
self._device = ""
@@ -29,10 +50,11 @@ class DeviceBox(BECWidget, QWidget):
self.init_device()
def init_ui(self):
"""Init the ui"""
self.device_changed.connect(self.on_device_change)
current_path = os.path.dirname(__file__)
self.ui = UILoader(self).loader(os.path.join(current_path, "device_box.ui"))
self.ui = UILoader(self).loader(os.path.join(current_path, self.ui_file))
self.layout = QVBoxLayout(self)
self.layout.addWidget(self.ui)
@@ -41,8 +63,8 @@ class DeviceBox(BECWidget, QWidget):
# fix the size of the device box
db = self.ui.device_box
db.setFixedHeight(234)
db.setFixedWidth(224)
db.setFixedHeight(self.dimensions[0])
db.setFixedWidth(self.dimensions[1])
self.ui.step_size.setStepType(QDoubleSpinBox.AdaptiveDecimalStepType)
self.ui.stop.clicked.connect(self.on_stop)
@@ -55,30 +77,105 @@ class DeviceBox(BECWidget, QWidget):
self.setpoint_validator = QDoubleValidator()
self.ui.setpoint.setValidator(self.setpoint_validator)
self.ui.spinner_widget.start()
self.ui.tool_button.clicked.connect(self._open_dialog_selection)
icon = QIcon()
icon.addFile(
os.path.join(MODULE_PATH, "assets", "toolbar_icons", "device_line_edit.svg"),
size=QSize(16, 16),
)
self.ui.tool_button.setIcon(icon)
def _open_dialog_selection(self):
"""Open dialog window for positioner selection"""
dialog = QDialog(self)
dialog.setWindowTitle("Positioner Selection")
layout = QVBoxLayout()
line_edit = DeviceLineEdit(self, client=self.client, device_filter="Positioner")
line_edit.textChanged.connect(self._positioner_changed)
layout.addWidget(line_edit)
close_button = QPushButton("Close")
close_button.clicked.connect(dialog.accept)
layout.addWidget(close_button)
dialog.setLayout(layout)
dialog.exec()
@Slot(str)
def _positioner_changed(self, positioner_name: str):
"""Changed input in combobox.
Args:
positioner_name (str): name of the positioner
"""
self.set_positioner(positioner_name)
def init_device(self):
if self.device in self.dev:
"""Init the device view and readback"""
if self._check_device_is_valid(self.device):
data = self.dev[self.device].read()
self.on_device_readback({"signals": data}, {})
def _toogle_enable_buttons(self, enable: bool) -> None:
"""Toogle enable/disable on available buttons
Args:
enable (bool): Enable buttons
"""
self.ui.tweak_left.setEnabled(enable)
self.ui.tweak_right.setEnabled(enable)
self.ui.stop.setEnabled(enable)
self.ui.setpoint.setEnabled(enable)
self.ui.step_size.setEnabled(enable)
@Property(str)
def device(self):
"""Property to set the device"""
return self._device
@device.setter
def device(self, value):
def device(self, value: str):
"""Setter, checks if device is a string"""
if not value or not isinstance(value, str):
return
old_device = self._device
self._device = value
self.device_changed.emit(old_device, value)
def set_positioner(self, positioner: str):
"""Set the device
Args:
positioner (Positioner | str) : Positioner to set, accepts str or the device
"""
if isinstance(positioner, Positioner):
positioner = positioner.name
self.device = positioner
def _check_device_is_valid(self, device: str):
"""Check if the device is a positioner
Args:
device (str): The device name
"""
if device not in self.dev:
logger.info(f"Device {device} not found in the device list")
return False
if not isinstance(self.dev[device], Positioner):
logger.info(f"Device {device} is not a positioner")
return False
return True
@Slot(str, str)
def on_device_change(self, old_device: str, new_device: str):
if new_device not in self.dev:
print(f"Device {new_device} not found in the device list")
"""Upon changing the device, a check will be performed if the device is a Positioner.
Args:
old_device (str): The old device name.
new_device (str): The new device name.
"""
if not self._check_device_is_valid(new_device):
return
print(f"Device changed from {old_device} to {new_device}")
logger.info(f"Device changed from {old_device} to {new_device}")
self._toogle_enable_buttons(True)
self.init_device()
self.bec_dispatcher.disconnect_slot(
self.on_device_readback, MessageEndpoints.device_readback(old_device)
@@ -98,6 +195,12 @@ class DeviceBox(BECWidget, QWidget):
@Slot(dict, dict)
def on_device_readback(self, msg_content: dict, metadata: dict):
"""Callback for device readback.
Args:
msg_content (dict): The message content.
metadata (dict): The message metadata.
"""
signals = msg_content.get("signals", {})
# pylint: disable=protected-access
hinted_signals = self.dev[self.device]._hints
@@ -134,7 +237,12 @@ class DeviceBox(BECWidget, QWidget):
pos = (readback_val - limits[0]) / (limits[1] - limits[0])
self.ui.position_indicator.on_position_update(pos)
def update_limits(self, limits):
def update_limits(self, limits: tuple):
"""Update limits
Args:
limits (tuple): Limits of the positioner
"""
if limits == self._limits:
return
self._limits = limits
@@ -147,6 +255,7 @@ class DeviceBox(BECWidget, QWidget):
@Slot()
def on_stop(self):
"""Stop call"""
request_id = str(uuid.uuid4())
params = {
"device": self.device,
@@ -165,18 +274,22 @@ class DeviceBox(BECWidget, QWidget):
@property
def step_size(self):
"""Step size for tweak"""
return self.ui.step_size.value()
@Slot()
def on_tweak_right(self):
"""Tweak motor right"""
self.dev[self.device].move(self.step_size, relative=True)
@Slot()
def on_tweak_left(self):
"""Tweak motor left"""
self.dev[self.device].move(-self.step_size, relative=True)
@Slot()
def on_setpoint_change(self):
"""Change the setpoint for the motor"""
self.ui.setpoint.clearFocus()
setpoint = self.ui.setpoint.text()
self.dev[self.device].move(float(setpoint), relative=False)
@@ -190,8 +303,8 @@ if __name__ == "__main__": # pragma: no cover
from qtpy.QtWidgets import QApplication
app = QApplication(sys.argv)
apply_theme("light")
widget = DeviceBox(device="samx")
apply_theme("dark")
widget = PositionerBox(device="bpm4i")
widget.show()
sys.exit(app.exec_())

View File

@@ -0,0 +1 @@
{'files': ['positioner_box.py']}

View File

@@ -29,17 +29,24 @@
<item>
<widget class="QGroupBox" name="device_box">
<property name="title">
<string>Device Name</string>
<string>No positioner selected</string>
</property>
<layout class="QGridLayout" name="gridLayout" rowstretch="0,0,0,0,0">
<property name="topMargin">
<number>0</number>
</property>
<item row="3" column="1">
<widget class="QDoubleSpinBox" name="step_size"/>
<widget class="QDoubleSpinBox" name="step_size">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QToolButton" name="tweak_right">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>50</width>
@@ -67,10 +74,20 @@
</widget>
</item>
<item row="2" column="0" colspan="3">
<widget class="QLineEdit" name="setpoint"/>
<widget class="QLineEdit" name="setpoint">
<property name="enabled">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QToolButton" name="tweak_left">
<property name="enabled">
<bool>false</bool>
</property>
<property name="minimumSize">
<size>
<width>50</width>
@@ -99,6 +116,9 @@
</item>
<item row="4" column="0" colspan="3">
<widget class="QPushButton" name="stop">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Stop</string>
</property>
@@ -108,6 +128,13 @@
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QToolButton" name="tool_button">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">

View File

@@ -0,0 +1,60 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QGuiApplication, QIcon
from bec_widgets.widgets.positioner_box.positioner_box import PositionerBox
DOM_XML = """
<ui language='c++'>
<widget class='PositionerBox' name='positioner_box'>
</widget>
</ui>
"""
MODULE_PATH = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
class PositionerBoxPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
def __init__(self):
super().__init__()
self._form_editor = None
def createWidget(self, parent):
t = PositionerBox(parent)
return t
def domXml(self):
return DOM_XML
def group(self):
return "Device Control"
def icon(self):
palette = QGuiApplication.palette()
pixmap = material_icon("switch_right", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "positioner_box"
def initialize(self, form_editor):
self._form_editor = form_editor
def isContainer(self):
return False
def isInitialized(self):
return self._form_editor is not None
def name(self):
return "PositionerBox"
def toolTip(self):
return "Simple Widget to control a positioner in box form"
def whatsThis(self):
return self.toolTip()

View File

@@ -0,0 +1,33 @@
from bec_lib.device import Positioner
from bec_widgets.widgets.positioner_box.positioner_box import PositionerBox
class PositionerControlLine(PositionerBox):
"""A widget that controls a single device."""
ui_file = "positioner_control_line.ui"
dimensions = (60, 600) # height, width
def __init__(self, parent=None, device: Positioner = None, *args, **kwargs):
"""Initialize the DeviceControlLine.
Args:
parent: The parent widget.
device (Positioner): The device to control.
"""
super().__init__(parent=parent, device=device, *args, **kwargs)
if __name__ == "__main__": # pragma: no cover
import sys
import qdarktheme
from qtpy.QtWidgets import QApplication
app = QApplication(sys.argv)
qdarktheme.setup_theme("dark")
widget = PositionerControlLine(device="samy")
widget.show()
sys.exit(app.exec_())

View File

@@ -0,0 +1 @@
{'files': ['positioner_control_line.py']}

View File

@@ -0,0 +1,216 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>785</width>
<height>91</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="device_box">
<property name="title">
<string>Device Name</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QToolButton" name="tool_button">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="text">
<string>...</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="PositionIndicator" name="position_indicator">
<property name="minimumSize">
<size>
<width>80</width>
<height>10</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="SpinnerWidget" name="spinner_widget">
<property name="minimumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>25</width>
<height>25</height>
</size>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="readback">
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Position</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="setpoint">
<property name="minimumSize">
<size>
<width>80</width>
<height>24</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>150</width>
<height>16777215</height>
</size>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="stop">
<property name="text">
<string>Stop</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="tweak_left">
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>30</width>
<height>30</height>
</size>
</property>
<property name="text">
<string>...</string>
</property>
<property name="iconSize">
<size>
<width>30</width>
<height>30</height>
</size>
</property>
<property name="arrowType">
<enum>Qt::ArrowType::LeftArrow</enum>
</property>
</widget>
</item>
<item>
<widget class="QDoubleSpinBox" name="step_size"/>
</item>
<item>
<widget class="QToolButton" name="tweak_right">
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>30</width>
<height>30</height>
</size>
</property>
<property name="text">
<string>...</string>
</property>
<property name="iconSize">
<size>
<width>30</width>
<height>30</height>
</size>
</property>
<property name="arrowType">
<enum>Qt::ArrowType::RightArrow</enum>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>SpinnerWidget</class>
<extends>QWidget</extends>
<header>spinner_widget</header>
</customwidget>
<customwidget>
<class>PositionIndicator</class>
<extends>QWidget</extends>
<header>position_indicator</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,60 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QGuiApplication, QIcon
from bec_widgets.widgets.positioner_box.positioner_control_line import PositionerControlLine
DOM_XML = """
<ui language='c++'>
<widget class='PositionerControlLine' name='positioner_control_line'>
</widget>
</ui>
"""
MODULE_PATH = os.path.dirname(os.path.dirname(os.path.dirname(__file__)))
class PositionerControlLinePlugin(QDesignerCustomWidgetInterface): # pragma: no cover
def __init__(self):
super().__init__()
self._form_editor = None
def createWidget(self, parent):
t = PositionerControlLine(parent)
return t
def domXml(self):
return DOM_XML
def group(self):
return "Device Control"
def icon(self):
palette = QGuiApplication.palette()
pixmap = material_icon("switch_left", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "positioner_control_line"
def initialize(self, form_editor):
self._form_editor = form_editor
def isContainer(self):
return False
def isInitialized(self):
return self._form_editor is not None
def name(self):
return "PositionerControlLine"
def toolTip(self):
return "A widget that controls a single positioner in line form."
def whatsThis(self):
return self.toolTip()

View File

@@ -0,0 +1,15 @@
def main(): # pragma: no cover
from qtpy import PYSIDE6
if not PYSIDE6:
print("PYSIDE6 is not available in the environment. Cannot patch designer.")
return
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
from bec_widgets.widgets.positioner_box.positioner_box_plugin import PositionerBoxPlugin
QPyDesignerCustomWidgetCollection.addCustomWidget(PositionerBoxPlugin())
if __name__ == "__main__": # pragma: no cover
main()

View File

@@ -0,0 +1,17 @@
def main(): # pragma: no cover
from qtpy import PYSIDE6
if not PYSIDE6:
print("PYSIDE6 is not available in the environment. Cannot patch designer.")
return
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
from bec_widgets.widgets.positioner_box.positioner_control_line_plugin import (
PositionerControlLinePlugin,
)
QPyDesignerCustomWidgetCollection.addCustomWidget(PositionerControlLinePlugin())
if __name__ == "__main__": # pragma: no cover
main()

View File

@@ -2,8 +2,9 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from qtpy.QtGui import QGuiApplication, QIcon
import bec_widgets
from bec_widgets.widgets.ring_progress_bar.ring_progress_bar import RingProgressBar
@@ -33,8 +34,9 @@ class RingProgressBarPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
return "BEC Utils"
def icon(self):
icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "ring_progress.png")
return QIcon(icon_path)
palette = QGuiApplication.palette()
pixmap = material_icon("track_changes", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "ring_progress_bar"

View File

@@ -2,8 +2,9 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from qtpy.QtGui import QGuiApplication, QIcon
import bec_widgets
from bec_widgets.widgets.scan_control.scan_control import ScanControl
@@ -33,8 +34,9 @@ class ScanControlPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
return "Device Control"
def icon(self):
icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "scan_control.png")
return QIcon(icon_path)
palette = QGuiApplication.palette()
pixmap = material_icon("stacked_line_chart", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "scan_control"

View File

@@ -1,9 +1,13 @@
from typing import Literal
from qtpy.QtCore import Qt
from qtpy.QtWidgets import (
QCheckBox,
QComboBox,
QDialog,
QDialogButtonBox,
QDoubleSpinBox,
QFormLayout,
QGridLayout,
QGroupBox,
QLabel,
@@ -25,13 +29,46 @@ class ScanArgType:
LITERALS = "dict"
class SettingsDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Settings")
layout = QFormLayout()
self.precision_spin_box = QSpinBox()
self.precision_spin_box.setRange(
-2147483647, 2147483647
) # 2147483647 is the largest int which qt allows
self.step_size_spin_box = QDoubleSpinBox()
self.step_size_spin_box.setRange(-float("inf"), float("inf"))
fixed_width = 80
self.precision_spin_box.setFixedWidth(fixed_width)
self.step_size_spin_box.setFixedWidth(fixed_width)
layout.addRow("Decimal Precision:", self.precision_spin_box)
layout.addRow("Step Size:", self.step_size_spin_box)
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
button_box.accepted.connect(self.accept)
button_box.rejected.connect(self.reject)
layout.addWidget(button_box)
self.setLayout(layout)
def getValues(self):
return self.precision_spin_box.value(), self.step_size_spin_box.value()
class ScanSpinBox(QSpinBox):
def __init__(
self, parent=None, arg_name: str = None, default: int | None = None, *args, **kwargs
):
super().__init__(parent=parent, *args, **kwargs)
self.arg_name = arg_name
self.setRange(-9999, 9999)
self.setRange(-2147483647, 2147483647) # 2147483647 is the largest int which qt allows
if default is not None:
self.setValue(default)
@@ -42,10 +79,25 @@ class ScanDoubleSpinBox(QDoubleSpinBox):
):
super().__init__(parent=parent, *args, **kwargs)
self.arg_name = arg_name
self.setRange(-9999, 9999)
self.setRange(-float("inf"), float("inf"))
if default is not None:
self.setValue(default)
self.setContextMenuPolicy(Qt.CustomContextMenu)
self.customContextMenuRequested.connect(self.showSettingsDialog)
self.setToolTip("Right click to open settings dialog for decimal precision and step size.")
def showSettingsDialog(self):
dialog = SettingsDialog(self)
dialog.precision_spin_box.setValue(self.decimals())
dialog.step_size_spin_box.setValue(self.singleStep())
if dialog.exec_() == QDialog.Accepted:
precision, step_size = dialog.getValues()
self.setDecimals(precision)
self.setSingleStep(step_size)
class ScanLineEdit(QLineEdit):
def __init__(

View File

@@ -2,8 +2,9 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from qtpy.QtGui import QGuiApplication, QIcon
import bec_widgets
from bec_widgets.widgets.spinner.spinner import SpinnerWidget
@@ -34,8 +35,9 @@ class SpinnerWidgetPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
return "BEC Utils"
def icon(self):
icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "spinner.png")
return QIcon(icon_path)
palette = QGuiApplication.palette()
pixmap = material_icon("progress_activity", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "spinner_widget"

View File

@@ -2,8 +2,9 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from qtpy.QtGui import QGuiApplication, QIcon
import bec_widgets
from bec_widgets.widgets.stop_button.stop_button import StopButton
@@ -34,8 +35,9 @@ class StopButtonPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
return "BEC Utils"
def icon(self):
icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "stop.png")
return QIcon(icon_path)
palette = QGuiApplication.palette()
pixmap = material_icon("dangerous", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "stop_button"

View File

@@ -2,8 +2,9 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from qtpy.QtGui import QGuiApplication, QIcon
import bec_widgets
from bec_widgets.widgets.text_box.text_box import TextBox
@@ -33,8 +34,9 @@ class TextBoxPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
return "BEC Utils"
def icon(self):
icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "text.png")
return QIcon(icon_path)
palette = QGuiApplication.palette()
pixmap = material_icon("chat", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "text_box"

View File

@@ -2,8 +2,9 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from qtpy.QtGui import QGuiApplication, QIcon
import bec_widgets
from bec_widgets.widgets.toggle.toggle import ToggleSwitch
@@ -34,8 +35,9 @@ class ToggleSwitchPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
return "BEC Utils"
def icon(self):
icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "toggle.png")
return QIcon(icon_path)
palette = QGuiApplication.palette()
pixmap = material_icon("toggle_on", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "toggle_switch"

View File

@@ -2,8 +2,9 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from qtpy.QtGui import QGuiApplication, QIcon
import bec_widgets
from bec_widgets.widgets.vscode.vscode import VSCodeEditor
@@ -34,8 +35,9 @@ class VSCodeEditorPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
return "BEC Developer"
def icon(self):
icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "code.png")
return QIcon(icon_path)
palette = QGuiApplication.palette()
pixmap = material_icon("developer_mode_tv", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "vs_code_editor"

View File

@@ -2,8 +2,9 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from qtpy.QtGui import QGuiApplication, QIcon
import bec_widgets
from bec_widgets.widgets.waveform.waveform_widget import BECWaveformWidget
@@ -34,8 +35,9 @@ class BECWaveformWidgetPlugin(QDesignerCustomWidgetInterface): # pragma: no cov
return "BEC Plots"
def icon(self):
icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "waveform.png")
return QIcon(icon_path)
palette = QGuiApplication.palette()
pixmap = material_icon("show_chart", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "bec_waveform_widget"

View File

@@ -58,16 +58,17 @@ class CurveSettings(SettingWidget):
self.ui.color_map_selector_scan.combo.setCurrentText(cm)
# Scan Curve Table
for label, curve in config["scan_segment"].items():
row_count = self.ui.scan_table.rowCount()
self.ui.scan_table.insertRow(row_count)
DialogRow(
parent=self,
table_widget=self.ui.scan_table,
client=self.target_widget.client,
row=row_count,
config=curve.config,
).add_scan_row()
for source in ["scan_segment", "async"]:
for label, curve in config[source].items():
row_count = self.ui.scan_table.rowCount()
self.ui.scan_table.insertRow(row_count)
DialogRow(
parent=self,
table_widget=self.ui.scan_table,
client=self.target_widget.client,
row=row_count,
config=curve.config,
).add_scan_row()
# Add DAP Curves
for label, curve in config["DAP"].items():
@@ -132,11 +133,12 @@ class CurveSettings(SettingWidget):
self.accept_curve_changes()
def accept_curve_changes(self):
old_curves_scans = list(self.target_widget.waveform._curves_data["scan_segment"].values())
old_curves_dap = list(self.target_widget.waveform._curves_data["DAP"].values())
for curve in old_curves_scans:
curve.remove()
for curve in old_curves_dap:
sources = ["scan_segment", "async", "DAP"]
old_curves = []
for source in sources:
old_curves += list(self.target_widget.waveform._curves_data[source].values())
for curve in old_curves:
curve.remove()
self.get_curve_params()

View File

@@ -561,7 +561,6 @@ class BECWaveformWidget(BECWidget, QWidget):
def cleanup(self):
self.fig.cleanup()
self.client.shutdown()
return super().cleanup()

View File

@@ -64,6 +64,12 @@ class WebsiteWidget(BECWidget, QWebEngineView):
"""
QWebEngineView.forward(self)
def cleanup(self):
"""
Cleanup the widget
"""
self.page().deleteLater()
if __name__ == "__main__":
import sys

View File

@@ -2,8 +2,9 @@
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
import os
from bec_qthemes import material_icon
from qtpy.QtDesigner import QDesignerCustomWidgetInterface
from qtpy.QtGui import QIcon
from qtpy.QtGui import QGuiApplication, QIcon
import bec_widgets
from bec_widgets.widgets.website.website import WebsiteWidget
@@ -33,8 +34,9 @@ class WebsiteWidgetPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
return "BEC Utils"
def icon(self):
icon_path = os.path.join(MODULE_PATH, "assets", "designer_icons", "web.png")
return QIcon(icon_path)
palette = QGuiApplication.palette()
pixmap = material_icon("travel_explore", color=palette.text().color(), filled=True)
return QIcon(pixmap)
def includeFile(self):
return "website_widget"

View File

@@ -59,29 +59,29 @@
/* Main index page overview cards */
.sd-card {
.index-card .sd-card {
background: #fff;
border-radius: 0;
padding: 30px 10px 20px 10px;
margin: 10px 0px;
}
.sd-card .sd-card-header {
.index-card .sd-card-header {
text-align: center;
}
.sd-card .sd-card-header .sd-card-text {
.index-card .sd-card-header .sd-card-text {
margin: 0px;
}
.sd-card .sd-card-img-top {
.index-card .sd-card-img-top {
height: 52px;
width: 52px;
margin-left: auto;
margin-right: auto;
}
.sd-card .sd-card-header {
.index-card .sd-card-header {
border: none;
background-color:white;
color: #150458 !important;
@@ -91,13 +91,13 @@
border-bottom: none !important;
}
.sd-card .sd-card-footer {
.index-card .sd-card-footer {
border: none;
background-color:white;
border-top: none !important;
}
.sd-card .sd-card-footer .sd-card-text{
.index-card .sd-card-footer .sd-card-text{
max-width: 220px;
margin-left: auto;
margin-right: auto;
@@ -143,7 +143,7 @@
/* Main index page overview cards */
html[data-theme=dark] .sd-card {
html[data-theme=dark] .index-card .sd-card {
background-color:var(--pst-color-background);
border: none
}
@@ -152,12 +152,12 @@
box-shadow: 0 .1rem 0.5rem rgba(250, 250, 250, .2) !important
}
html[data-theme=dark] .sd-card .sd-card-header {
html[data-theme=dark] .index-card .sd-card-header {
background-color:var(--pst-color-background);
color: #150458 !important;
}
html[data-theme=dark] .sd-card .sd-card-footer {
html[data-theme=dark] .index-card .sd-card-footer {
background-color:var(--pst-color-background);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

Some files were not shown because too many files have changed in this diff Show More