mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-04-12 19:50:54 +02:00
Compare commits
4 Commits
feat/plugi
...
feat/alt-d
| Author | SHA1 | Date | |
|---|---|---|---|
| 873937f33b | |||
| 81dffa11f0 | |||
| 66c0e4ecfc | |||
| c5cd3e3c3b |
139
.gitlab-ci.yml
139
.gitlab-ci.yml
@@ -1,7 +1,7 @@
|
||||
# This file is a template, and might need editing before it works on your project.
|
||||
# Official language image. Look for the different tagged releases at:
|
||||
# https://hub.docker.com/r/library/python/tags/
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.11
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.10
|
||||
#commands to run in the Docker container before starting each job.
|
||||
variables:
|
||||
DOCKER_TLS_CERTDIR: ""
|
||||
@@ -23,6 +23,7 @@ workflow:
|
||||
include:
|
||||
- template: Security/Secret-Detection.gitlab-ci.yml
|
||||
|
||||
|
||||
# different stages in the pipeline
|
||||
stages:
|
||||
- Formatter
|
||||
@@ -64,7 +65,7 @@ pylint:
|
||||
- ./pylint/
|
||||
expire_in: 1 week
|
||||
rules:
|
||||
- if: $CI_PROJECT_PATH == "bec/bec_widgets"
|
||||
- if: $CI_PROJECT_PATH == "bec/bec_widgets"
|
||||
|
||||
pylint-check:
|
||||
stage: Formatter
|
||||
@@ -97,7 +98,7 @@ pylint-check:
|
||||
- ./pylint/
|
||||
expire_in: 1 week
|
||||
rules:
|
||||
- if: $CI_PROJECT_PATH == "bec/bec_widgets"
|
||||
- if: $CI_PROJECT_PATH == "bec/bec_widgets"
|
||||
|
||||
tests:
|
||||
stage: test
|
||||
@@ -111,7 +112,7 @@ tests:
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3
|
||||
- pip install -e ./bec/bec_lib[dev]
|
||||
- pip install -e .[dev,pyside6]
|
||||
- pip install -e .[dev,pyqt6]
|
||||
- coverage run --source=./bec_widgets -m pytest -v --junitxml=report.xml --random-order --full-trace ./tests/unit_tests
|
||||
- coverage report
|
||||
- coverage xml
|
||||
@@ -123,140 +124,17 @@ tests:
|
||||
coverage_format: cobertura
|
||||
path: coverage.xml
|
||||
|
||||
tests-3.10-pyside6:
|
||||
extends: "tests"
|
||||
stage: AdditionalTests
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.10
|
||||
script:
|
||||
- git clone --branch $BEC_CORE_BRANCH https://gitlab.psi.ch/bec/bec.git
|
||||
- git clone --branch $OPHYD_DEVICES_BRANCH https://gitlab.psi.ch/bec/ophyd_devices.git
|
||||
- export OHPYD_DEVICES_PATH=$PWD/ophyd_devices
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3
|
||||
- pip install -e ./bec/bec_lib[dev]
|
||||
- pip install -e .[dev,pyside6]
|
||||
- coverage run --source=./bec_widgets -m pytest -v --junitxml=report.xml --random-order --full-trace ./tests/unit_tests
|
||||
- coverage report
|
||||
- coverage xml
|
||||
allow_failure: true
|
||||
|
||||
tests-3.12-pyside6:
|
||||
extends: "tests"
|
||||
stage: AdditionalTests
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.12
|
||||
script:
|
||||
- git clone --branch $BEC_CORE_BRANCH https://gitlab.psi.ch/bec/bec.git
|
||||
- git clone --branch $OPHYD_DEVICES_BRANCH https://gitlab.psi.ch/bec/ophyd_devices.git
|
||||
- export OHPYD_DEVICES_PATH=$PWD/ophyd_devices
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3
|
||||
- pip install -e ./bec/bec_lib[dev]
|
||||
- pip install -e .[dev,pyside6]
|
||||
- coverage run --source=./bec_widgets -m pytest -v --junitxml=report.xml --random-order --full-trace ./tests/unit_tests
|
||||
- coverage report
|
||||
- coverage xml
|
||||
allow_failure: true
|
||||
|
||||
tests-3.10-pyqt5:
|
||||
extends: "tests"
|
||||
stage: AdditionalTests
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.10
|
||||
script:
|
||||
- git clone --branch $BEC_CORE_BRANCH https://gitlab.psi.ch/bec/bec.git
|
||||
- git clone --branch $OPHYD_DEVICES_BRANCH https://gitlab.psi.ch/bec/ophyd_devices.git
|
||||
- export OHPYD_DEVICES_PATH=$PWD/ophyd_devices
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3
|
||||
- pip install -e ./bec/bec_lib[dev]
|
||||
- pip install -e .[dev,pyqt5]
|
||||
- coverage run --source=./bec_widgets -m pytest -v --junitxml=report.xml --random-order --full-trace ./tests/unit_tests
|
||||
- coverage report
|
||||
- coverage xml
|
||||
allow_failure: true
|
||||
|
||||
tests-3.11-pyqt5:
|
||||
tests-3.11:
|
||||
extends: "tests"
|
||||
stage: AdditionalTests
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.11
|
||||
script:
|
||||
- git clone --branch $BEC_CORE_BRANCH https://gitlab.psi.ch/bec/bec.git
|
||||
- git clone --branch $OPHYD_DEVICES_BRANCH https://gitlab.psi.ch/bec/ophyd_devices.git
|
||||
- export OHPYD_DEVICES_PATH=$PWD/ophyd_devices
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3
|
||||
- pip install -e ./bec/bec_lib[dev]
|
||||
- pip install -e .[dev,pyqt5]
|
||||
- coverage run --source=./bec_widgets -m pytest -v --junitxml=report.xml --random-order --full-trace ./tests/unit_tests
|
||||
- coverage report
|
||||
- coverage xml
|
||||
allow_failure: true
|
||||
|
||||
tests-3.12-pyqt5:
|
||||
tests-3.12:
|
||||
extends: "tests"
|
||||
stage: AdditionalTests
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.12
|
||||
script:
|
||||
- git clone --branch $BEC_CORE_BRANCH https://gitlab.psi.ch/bec/bec.git
|
||||
- git clone --branch $OPHYD_DEVICES_BRANCH https://gitlab.psi.ch/bec/ophyd_devices.git
|
||||
- export OHPYD_DEVICES_PATH=$PWD/ophyd_devices
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3
|
||||
- pip install -e ./bec/bec_lib[dev]
|
||||
- pip install -e .[dev,pyqt5]
|
||||
- coverage run --source=./bec_widgets -m pytest -v --junitxml=report.xml --random-order --full-trace ./tests/unit_tests
|
||||
- coverage report
|
||||
- coverage xml
|
||||
allow_failure: true
|
||||
|
||||
tests-3.10-pyqt6:
|
||||
extends: "tests"
|
||||
stage: AdditionalTests
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.10
|
||||
script:
|
||||
- git clone --branch $BEC_CORE_BRANCH https://gitlab.psi.ch/bec/bec.git
|
||||
- git clone --branch $OPHYD_DEVICES_BRANCH https://gitlab.psi.ch/bec/ophyd_devices.git
|
||||
- export OHPYD_DEVICES_PATH=$PWD/ophyd_devices
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3
|
||||
- pip install -e ./bec/bec_lib[dev]
|
||||
- pip install -e .[dev,pyqt6]
|
||||
- coverage run --source=./bec_widgets -m pytest -v --junitxml=report.xml --random-order --full-trace ./tests/unit_tests
|
||||
- coverage report
|
||||
- coverage xml
|
||||
allow_failure: true
|
||||
|
||||
tests-3.11-pyqt6:
|
||||
extends: "tests"
|
||||
stage: AdditionalTests
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.11
|
||||
script:
|
||||
- git clone --branch $BEC_CORE_BRANCH https://gitlab.psi.ch/bec/bec.git
|
||||
- git clone --branch $OPHYD_DEVICES_BRANCH https://gitlab.psi.ch/bec/ophyd_devices.git
|
||||
- export OHPYD_DEVICES_PATH=$PWD/ophyd_devices
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3
|
||||
- pip install -e ./bec/bec_lib[dev]
|
||||
- pip install -e .[dev,pyqt6]
|
||||
- coverage run --source=./bec_widgets -m pytest -v --junitxml=report.xml --random-order --full-trace ./tests/unit_tests
|
||||
- coverage report
|
||||
- coverage xml
|
||||
allow_failure: true
|
||||
|
||||
tests-3.12-pyqt6:
|
||||
extends: "tests"
|
||||
stage: AdditionalTests
|
||||
image: $CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX/python:3.12
|
||||
script:
|
||||
- git clone --branch $BEC_CORE_BRANCH https://gitlab.psi.ch/bec/bec.git
|
||||
- git clone --branch $OPHYD_DEVICES_BRANCH https://gitlab.psi.ch/bec/ophyd_devices.git
|
||||
- export OHPYD_DEVICES_PATH=$PWD/ophyd_devices
|
||||
- apt-get update
|
||||
- apt-get install -y libgl1-mesa-glx libegl1-mesa x11-utils libxkbcommon-x11-0 libdbus-1-3
|
||||
- pip install -e ./bec/bec_lib[dev]
|
||||
- pip install -e .[dev,pyqt6]
|
||||
- coverage run --source=./bec_widgets -m pytest -v --junitxml=report.xml --random-order --full-trace ./tests/unit_tests
|
||||
- coverage report
|
||||
- coverage xml
|
||||
allow_failure: true
|
||||
|
||||
end-2-end-conda:
|
||||
@@ -287,7 +165,7 @@ end-2-end-conda:
|
||||
- pip install -e ./bec_lib[dev]
|
||||
- pip install -e ./bec_ipython_client[dev]
|
||||
- cd ../
|
||||
- pip install -e .[dev,pyside6]
|
||||
- pip install -e .[dev,pyqt6]
|
||||
- cd ./tests/end-2-end
|
||||
- pytest --start-servers --flush-redis --random-order
|
||||
|
||||
@@ -305,6 +183,7 @@ end-2-end-conda:
|
||||
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main"'
|
||||
- if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "production"'
|
||||
|
||||
|
||||
semver:
|
||||
stage: Deploy
|
||||
needs: ["tests"]
|
||||
|
||||
@@ -6,13 +6,12 @@ import h5py
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
import zmq
|
||||
from pyqtgraph.Qt import uic
|
||||
from qtpy.QtCore import Signal as pyqtSignal
|
||||
from qtpy.QtCore import Slot as pyqtSlot
|
||||
from qtpy.QtGui import QKeySequence
|
||||
from qtpy.QtWidgets import QDialog, QFileDialog, QFrame, QLabel, QShortcut, QVBoxLayout, QWidget
|
||||
|
||||
from bec_widgets.utils import UILoader
|
||||
|
||||
# from scipy.stats import multivariate_normal
|
||||
|
||||
|
||||
@@ -24,7 +23,7 @@ class EigerPlot(QWidget):
|
||||
# pg.setConfigOptions(background="w", foreground="k", antialias=True)
|
||||
|
||||
current_path = os.path.dirname(__file__)
|
||||
self.ui = UILoader().load_ui(os.path.join(current_path, "eiger_plot.ui"), self)
|
||||
uic.loadUi(os.path.join(current_path, "eiger_plot.ui"), self)
|
||||
|
||||
# Set widow name
|
||||
self.setWindowTitle("Eiger Plot")
|
||||
@@ -61,22 +60,19 @@ class EigerPlot(QWidget):
|
||||
self.update_hist()
|
||||
|
||||
# Adding Items to Graphical Layout
|
||||
self.glw_layout = QVBoxLayout(self.ui.glw_placeholder)
|
||||
self.glw = pg.GraphicsLayoutWidget()
|
||||
self.glw_layout.addWidget(self.glw)
|
||||
self.glw.addItem(self.plot_item)
|
||||
self.glw.addItem(self.hist)
|
||||
|
||||
def hook_signals(self):
|
||||
# Buttons
|
||||
# self.pushButton_test.clicked.connect(self.start_sim_stream)
|
||||
self.ui.pushButton_mask.clicked.connect(self.load_mask_dialog)
|
||||
self.ui.pushButton_delete_mask.clicked.connect(self.delete_mask)
|
||||
self.ui.pushButton_help.clicked.connect(self.show_help_dialog)
|
||||
self.pushButton_mask.clicked.connect(self.load_mask_dialog)
|
||||
self.pushButton_delete_mask.clicked.connect(self.delete_mask)
|
||||
self.pushButton_help.clicked.connect(self.show_help_dialog)
|
||||
|
||||
# SpinBoxes
|
||||
self.ui.doubleSpinBox_hist_min.valueChanged.connect(self.update_hist)
|
||||
self.ui.doubleSpinBox_hist_max.valueChanged.connect(self.update_hist)
|
||||
self.doubleSpinBox_hist_min.valueChanged.connect(self.update_hist)
|
||||
self.doubleSpinBox_hist_max.valueChanged.connect(self.update_hist)
|
||||
|
||||
# Signal/Slots
|
||||
self.update_signal.connect(self.on_image_update)
|
||||
@@ -85,47 +81,47 @@ class EigerPlot(QWidget):
|
||||
# Key bindings for rotation
|
||||
rotate_plus = QShortcut(QKeySequence("Ctrl+A"), self)
|
||||
rotate_minus = QShortcut(QKeySequence("Ctrl+Z"), self)
|
||||
self.ui.comboBox_rotation.setToolTip("Increase rotation: Ctrl+A\nDecrease rotation: Ctrl+Z")
|
||||
self.ui.checkBox_transpose.setToolTip("Toggle transpose: Ctrl+T")
|
||||
self.comboBox_rotation.setToolTip("Increase rotation: Ctrl+A\nDecrease rotation: Ctrl+Z")
|
||||
self.checkBox_transpose.setToolTip("Toggle transpose: Ctrl+T")
|
||||
|
||||
max_index = self.ui.comboBox_rotation.count() - 1 # Maximum valid index
|
||||
max_index = self.comboBox_rotation.count() - 1 # Maximum valid index
|
||||
|
||||
rotate_plus.activated.connect(
|
||||
lambda: self.ui.comboBox_rotation.setCurrentIndex(
|
||||
min(self.ui.comboBox_rotation.currentIndex() + 1, max_index)
|
||||
lambda: self.comboBox_rotation.setCurrentIndex(
|
||||
min(self.comboBox_rotation.currentIndex() + 1, max_index)
|
||||
)
|
||||
)
|
||||
|
||||
rotate_minus.activated.connect(
|
||||
lambda: self.ui.comboBox_rotation.setCurrentIndex(
|
||||
max(self.ui.comboBox_rotation.currentIndex() - 1, 0)
|
||||
lambda: self.comboBox_rotation.setCurrentIndex(
|
||||
max(self.comboBox_rotation.currentIndex() - 1, 0)
|
||||
)
|
||||
)
|
||||
|
||||
# Key bindings for transpose
|
||||
transpose = QShortcut(QKeySequence("Ctrl+T"), self)
|
||||
transpose.activated.connect(self.ui.checkBox_transpose.toggle)
|
||||
transpose.activated.connect(self.checkBox_transpose.toggle)
|
||||
|
||||
FFT = QShortcut(QKeySequence("Ctrl+F"), self)
|
||||
FFT.activated.connect(self.ui.checkBox_FFT.toggle)
|
||||
self.ui.checkBox_FFT.setToolTip("Toggle FFT: Ctrl+F")
|
||||
FFT.activated.connect(self.checkBox_FFT.toggle)
|
||||
self.checkBox_FFT.setToolTip("Toggle FFT: Ctrl+F")
|
||||
|
||||
log = QShortcut(QKeySequence("Ctrl+L"), self)
|
||||
log.activated.connect(self.ui.checkBox_log.toggle)
|
||||
self.ui.checkBox_log.setToolTip("Toggle log: Ctrl+L")
|
||||
log.activated.connect(self.checkBox_log.toggle)
|
||||
self.checkBox_log.setToolTip("Toggle log: Ctrl+L")
|
||||
|
||||
mask = QShortcut(QKeySequence("Ctrl+M"), self)
|
||||
mask.activated.connect(self.ui.pushButton_mask.click)
|
||||
self.ui.pushButton_mask.setToolTip("Load mask: Ctrl+M")
|
||||
mask.activated.connect(self.pushButton_mask.click)
|
||||
self.pushButton_mask.setToolTip("Load mask: Ctrl+M")
|
||||
|
||||
delete_mask = QShortcut(QKeySequence("Ctrl+D"), self)
|
||||
delete_mask.activated.connect(self.ui.pushButton_delete_mask.click)
|
||||
self.ui.pushButton_delete_mask.setToolTip("Delete mask: Ctrl+D")
|
||||
delete_mask.activated.connect(self.pushButton_delete_mask.click)
|
||||
self.pushButton_delete_mask.setToolTip("Delete mask: Ctrl+D")
|
||||
|
||||
def update_hist(self):
|
||||
self.hist_levels = [
|
||||
self.ui.doubleSpinBox_hist_min.value(),
|
||||
self.ui.doubleSpinBox_hist_max.value(),
|
||||
self.doubleSpinBox_hist_min.value(),
|
||||
self.doubleSpinBox_hist_max.value(),
|
||||
]
|
||||
self.hist.setLevels(min=self.hist_levels[0], max=self.hist_levels[1])
|
||||
self.hist.setHistogramRange(
|
||||
@@ -164,18 +160,16 @@ class EigerPlot(QWidget):
|
||||
# self.image = np.ma.masked_array(self.image, mask=self.mask) #TODO test if np works
|
||||
self.image = self.image * (1 - self.mask) + 1
|
||||
|
||||
if self.ui.checkBox_FFT.isChecked():
|
||||
if self.checkBox_FFT.isChecked():
|
||||
self.image = np.abs(np.fft.fftshift(np.fft.fft2(self.image)))
|
||||
|
||||
if self.ui.comboBox_rotation.currentIndex() > 0: # rotate
|
||||
self.image = np.rot90(
|
||||
self.image, k=self.ui.comboBox_rotation.currentIndex(), axes=(0, 1)
|
||||
)
|
||||
if self.comboBox_rotation.currentIndex() > 0: # rotate
|
||||
self.image = np.rot90(self.image, k=self.comboBox_rotation.currentIndex(), axes=(0, 1))
|
||||
|
||||
if self.ui.checkBox_transpose.isChecked(): # transpose
|
||||
if self.checkBox_transpose.isChecked(): # transpose
|
||||
self.image = np.transpose(self.image)
|
||||
|
||||
if self.ui.checkBox_log.isChecked():
|
||||
if self.checkBox_log.isChecked():
|
||||
self.image = np.log10(self.image)
|
||||
|
||||
self.imageItem.setImage(self.image, autoLevels=False)
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2" stretch="1,4">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
@@ -191,10 +191,17 @@
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="glw_placeholder" native="true"/>
|
||||
<widget class="GraphicsLayoutWidget" name="glw"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>GraphicsLayoutWidget</class>
|
||||
<extends>QGraphicsView</extends>
|
||||
<header>pyqtgraph.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
@@ -2,7 +2,9 @@ import os
|
||||
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from pyqtgraph.Qt import QtWidgets
|
||||
import qdarktheme
|
||||
from PyQt6.QtWidgets import QPushButton, QTextEdit
|
||||
from pyqtgraph.Qt import QtWidgets, uic
|
||||
from qtconsole.inprocess import QtInProcessKernelManager
|
||||
from qtconsole.rich_jupyter_widget import RichJupyterWidget
|
||||
from qtpy.QtCore import QSize
|
||||
@@ -10,9 +12,10 @@ from qtpy.QtGui import QIcon
|
||||
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
|
||||
|
||||
from bec_widgets.cli.rpc_register import RPCRegister
|
||||
from bec_widgets.utils import BECDispatcher, UILoader
|
||||
from bec_widgets.utils import BECDispatcher
|
||||
from bec_widgets.widgets import BECFigure
|
||||
from bec_widgets.widgets.dock.dock_area import BECDockArea
|
||||
from bec_widgets.widgets.dock_area.dock_area import BECDockAreaAlt
|
||||
from bec_widgets.widgets.spiral_progress_bar.spiral_progress_bar import SpiralProgressBar
|
||||
|
||||
|
||||
@@ -40,11 +43,11 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
|
||||
super().__init__(parent)
|
||||
|
||||
current_path = os.path.dirname(__file__)
|
||||
self.ui = UILoader().load_ui(os.path.join(current_path, "jupyter_console_window.ui"), self)
|
||||
uic.loadUi(os.path.join(current_path, "jupyter_console_window.ui"), self)
|
||||
|
||||
self._init_ui()
|
||||
|
||||
self.ui.splitter.setSizes([200, 100])
|
||||
self.splitter.setSizes([200, 100])
|
||||
self.safe_close = False
|
||||
# self.figure.clean_signal.connect(self.confirm_close)
|
||||
|
||||
@@ -75,11 +78,11 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
|
||||
|
||||
def _init_ui(self):
|
||||
# Plotting window
|
||||
self.glw_1_layout = QVBoxLayout(self.ui.glw) # Create a new QVBoxLayout
|
||||
self.glw_1_layout = QVBoxLayout(self.glw) # Create a new QVBoxLayout
|
||||
self.figure = BECFigure(parent=self, gui_id="remote") # Create a new BECDeviceMonitor
|
||||
self.glw_1_layout.addWidget(self.figure) # Add BECDeviceMonitor to the layout
|
||||
|
||||
self.dock_layout = QVBoxLayout(self.ui.dock_placeholder)
|
||||
self.dock_layout = QVBoxLayout(self.dock_placeholder)
|
||||
self.dock = BECDockArea(gui_id="remote")
|
||||
self.dock_layout.addWidget(self.dock)
|
||||
|
||||
@@ -89,13 +92,13 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
|
||||
# init dock for testing
|
||||
self._init_dock()
|
||||
|
||||
self.console_layout = QVBoxLayout(self.ui.widget_console)
|
||||
self.console_layout = QVBoxLayout(self.widget_console)
|
||||
self.console = JupyterConsoleWidget()
|
||||
self.console_layout.addWidget(self.console)
|
||||
self.console.set_default_style("linux")
|
||||
|
||||
def _init_figure(self):
|
||||
self.figure.plot(x_name="samx", y_name="bpm4d")
|
||||
self.figure.plot("samx", "bpm4d")
|
||||
self.figure.motor_map("samx", "samy")
|
||||
self.figure.image("eiger", color_map="viridis", vrange=(0, 100))
|
||||
|
||||
@@ -111,28 +114,40 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
|
||||
self.c1 = self.w1.get_config()
|
||||
|
||||
def _init_dock(self):
|
||||
self.button_1 = QtWidgets.QPushButton("Button 1 ")
|
||||
self.button_2_a = QtWidgets.QPushButton("Button to be added at place 0,0 in d3")
|
||||
self.button_2_b = QtWidgets.QPushButton("button after without postions specified")
|
||||
self.button_2_c = QtWidgets.QPushButton("button super late")
|
||||
self.button_3 = QtWidgets.QPushButton("Button above Figure ")
|
||||
self.bar = SpiralProgressBar()
|
||||
|
||||
self.label_2 = QtWidgets.QLabel("label which is added separately")
|
||||
self.label_3 = QtWidgets.QLabel("Label above figure")
|
||||
self.d0 = self.dock.add_dock(name="dock_0")
|
||||
self.fig0 = self.d0.add_widget_bec("BECFigure")
|
||||
self.fig0.image("eiger", vrange=(0, 100))
|
||||
|
||||
self.d1 = self.dock.add_dock(widget=self.button_1, position="left")
|
||||
self.d1.addWidget(self.label_2)
|
||||
self.d2 = self.dock.add_dock(widget=self.bar, position="right")
|
||||
self.d3 = self.dock.add_dock(name="figure")
|
||||
self.fig_dock3 = BECFigure()
|
||||
self.fig_dock3.plot(x_name="samx", y_name="bpm4d")
|
||||
self.d3.add_widget(self.label_3)
|
||||
self.d3.add_widget(self.button_3)
|
||||
self.d3.add_widget(self.fig_dock3)
|
||||
self.d1 = self.dock.add_dock(name="dock_1", position="right")
|
||||
self.fig1 = self.d1.add_widget_bec("BECFigure")
|
||||
self.fig1.plot("samx", "bpm4i")
|
||||
self.fig1.plot("samx", "bpm3a")
|
||||
|
||||
self.d2 = self.dock.add_dock(name="dock_2", position="bottom")
|
||||
self.fi2 = self.d2.add_widget_bec("BECFigure", row=0, col=0)
|
||||
self.fi2.motor_map("samx", "samy")
|
||||
self.bar2 = self.d2.add_widget_bec("SpiralProgressBar", row=0, col=1)
|
||||
self.bar2.set_diameter(200)
|
||||
|
||||
self.dock.save_state()
|
||||
|
||||
def ini_dock_alt(self):
|
||||
self.d0_alt = self.dock_alt.add_dock(title="dock_0", layout="grid", position="top")
|
||||
self.fig0_alt = self.d0_alt.add_widget_bec("BECFigure")
|
||||
self.fig0_alt.image("eiger", vrange=(0, 100))
|
||||
|
||||
self.d1_alt = self.dock_alt.add_dock(title="dock_1", layout="grid", position="top")
|
||||
self.fig1_alt = self.d1_alt.add_widget_bec("BECFigure")
|
||||
self.fig1_alt.plot("samx", "bpm4i")
|
||||
self.fig1_alt.plot("samx", "bpm3a")
|
||||
|
||||
self.d2_alt = self.dock_alt.add_dock(title="dock_2", layout="grid", position="bottom")
|
||||
self.fi2_alt = self.d2_alt.add_widget_bec("BECFigure", row=0, col=0)
|
||||
self.fi2_alt.motor_map("samx", "samy")
|
||||
self.bar2_alt = self.d2_alt.add_widget_bec("SpiralProgressBar", row=0, col=1)
|
||||
self.bar2_alt.set_diameter(200)
|
||||
|
||||
def closeEvent(self, event):
|
||||
"""Override to handle things when main window is closed."""
|
||||
self.dock.cleanup()
|
||||
@@ -144,10 +159,6 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
import sys
|
||||
|
||||
import bec_widgets
|
||||
|
||||
module_path = os.path.dirname(bec_widgets.__file__)
|
||||
|
||||
bec_dispatcher = BECDispatcher()
|
||||
client = bec_dispatcher.client
|
||||
client.start()
|
||||
@@ -155,8 +166,9 @@ if __name__ == "__main__": # pragma: no cover
|
||||
app = QApplication(sys.argv)
|
||||
app.setApplicationName("Jupyter Console")
|
||||
app.setApplicationDisplayName("Jupyter Console")
|
||||
qdarktheme.setup_theme("auto")
|
||||
icon = QIcon()
|
||||
icon.addFile(os.path.join(module_path, "assets", "terminal_icon.png"), size=QSize(48, 48))
|
||||
icon.addFile("terminal_icon.png", size=QSize(48, 48))
|
||||
app.setWindowIcon(icon)
|
||||
win = JupyterConsoleWindow()
|
||||
win.show()
|
||||
|
||||
@@ -21,11 +21,11 @@
|
||||
</property>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
<number>1</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="tab_1">
|
||||
<attribute name="title">
|
||||
<string>BECDock</string>
|
||||
<string>Dock - Pyqtgraph</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
@@ -33,6 +33,16 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>Dock - Qt Native</string>
|
||||
</attribute>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||
<item>
|
||||
<widget class="QWidget" name="dock_alt_placeholder" native="true"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>BECFigure</string>
|
||||
|
||||
92
bec_widgets/examples/modular_app/modular.ui
Normal file
92
bec_widgets/examples/modular_app/modular.ui
Normal file
@@ -0,0 +1,92 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>MainWindow</class>
|
||||
<widget class="QMainWindow" name="MainWindow">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1433</width>
|
||||
<height>689</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>MainWindow</string>
|
||||
</property>
|
||||
<widget class="QWidget" name="centralwidget">
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="2">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Plot Config 2</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
<widget class="BECMonitor" name="plot_1"/>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="pushButton_setting_2">
|
||||
<property name="text">
|
||||
<string>Setting Plot 2</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="2" colspan="2">
|
||||
<widget class="BECMonitor" name="plot_2"/>
|
||||
</item>
|
||||
<item row="1" column="4">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Plot Scan Types = True</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="pushButton_setting_1">
|
||||
<property name="text">
|
||||
<string>Setting Plot 1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Plot Config 1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="5">
|
||||
<widget class="QPushButton" name="pushButton_setting_3">
|
||||
<property name="text">
|
||||
<string>Setting Plot 3</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="4" colspan="2">
|
||||
<widget class="BECMonitor" name="plot_3"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QMenuBar" name="menubar">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1433</width>
|
||||
<height>37</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QStatusBar" name="statusbar"/>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>BECMonitor</class>
|
||||
<extends>QGraphicsView</extends>
|
||||
<header location="global">bec_widgets.widgets.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
197
bec_widgets/examples/modular_app/modular_app.py
Normal file
197
bec_widgets/examples/modular_app/modular_app.py
Normal file
@@ -0,0 +1,197 @@
|
||||
import os
|
||||
|
||||
from qtpy import uic
|
||||
from qtpy.QtWidgets import QApplication, QMainWindow
|
||||
|
||||
from bec_widgets.utils.bec_dispatcher import BECDispatcher
|
||||
from bec_widgets.widgets import BECMonitor
|
||||
|
||||
# some default configs for demonstration purposes
|
||||
CONFIG_SIMPLE = {
|
||||
"plot_settings": {
|
||||
"background_color": "black",
|
||||
"num_columns": 2,
|
||||
"colormap": "plasma",
|
||||
"scan_types": False,
|
||||
},
|
||||
"plot_data": [
|
||||
{
|
||||
"plot_name": "BPM4i plots vs samx",
|
||||
"x_label": "Motor X",
|
||||
"y_label": "bpm4i",
|
||||
"sources": [
|
||||
{
|
||||
"type": "scan_segment",
|
||||
"signals": {
|
||||
"x": [{"name": "samx"}],
|
||||
"y": [{"name": "bpm4i", "entry": "bpm4i"}],
|
||||
},
|
||||
},
|
||||
# {
|
||||
# "type": "history",
|
||||
# "signals": {
|
||||
# "x": [{"name": "samx"}],
|
||||
# "y": [{"name": "bpm4i", "entry": "bpm4i"}],
|
||||
# },
|
||||
# },
|
||||
# {
|
||||
# "type": "dap",
|
||||
# 'worker':'some_worker',
|
||||
# "signals": {
|
||||
# "x": [{"name": "samx"}],
|
||||
# "y": [{"name": "bpm4i", "entry": "bpm4i"}],
|
||||
# },
|
||||
# },
|
||||
],
|
||||
},
|
||||
{
|
||||
"plot_name": "Gauss plots vs samx",
|
||||
"x_label": "Motor X",
|
||||
"y_label": "Gauss",
|
||||
"sources": [
|
||||
{
|
||||
"type": "scan_segment",
|
||||
"signals": {
|
||||
"x": [{"name": "samx", "entry": "samx"}],
|
||||
"y": [{"name": "gauss_bpm"}, {"name": "gauss_adc1"}],
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
CONFIG_SCAN_MODE = {
|
||||
"plot_settings": {
|
||||
"background_color": "white",
|
||||
"num_columns": 3,
|
||||
"colormap": "plasma",
|
||||
"scan_types": True,
|
||||
},
|
||||
"plot_data": {
|
||||
"grid_scan": [
|
||||
{
|
||||
"plot_name": "Grid plot 1",
|
||||
"x_label": "Motor X",
|
||||
"y_label": "BPM",
|
||||
"sources": [
|
||||
{
|
||||
"type": "scan_segment",
|
||||
"signals": {
|
||||
"x": [{"name": "samx", "entry": "samx"}],
|
||||
"y": [{"name": "gauss_bpm"}],
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"plot_name": "Grid plot 2",
|
||||
"x_label": "Motor X",
|
||||
"y_label": "BPM",
|
||||
"sources": [
|
||||
{
|
||||
"type": "scan_segment",
|
||||
"signals": {
|
||||
"x": [{"name": "samx", "entry": "samx"}],
|
||||
"y": [{"name": "gauss_adc1"}],
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"plot_name": "Grid plot 3",
|
||||
"x_label": "Motor X",
|
||||
"y_label": "BPM",
|
||||
"sources": [
|
||||
{
|
||||
"type": "scan_segment",
|
||||
"signals": {"x": [{"name": "samy"}], "y": [{"name": "gauss_adc2"}]},
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"plot_name": "Grid plot 4",
|
||||
"x_label": "Motor X",
|
||||
"y_label": "BPM",
|
||||
"sources": [
|
||||
{
|
||||
"type": "scan_segment",
|
||||
"signals": {
|
||||
"x": [{"name": "samy", "entry": "samy"}],
|
||||
"y": [{"name": "gauss_adc3"}],
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
"line_scan": [
|
||||
{
|
||||
"plot_name": "BPM plots vs samx",
|
||||
"x_label": "Motor X",
|
||||
"y_label": "Gauss",
|
||||
"sources": [
|
||||
{
|
||||
"type": "scan_segment",
|
||||
"signals": {
|
||||
"x": [{"name": "samx", "entry": "samx"}],
|
||||
"y": [{"name": "bpm4i"}],
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
{
|
||||
"plot_name": "Gauss plots vs samx",
|
||||
"x_label": "Motor X",
|
||||
"y_label": "Gauss",
|
||||
"sources": [
|
||||
{
|
||||
"type": "scan_segment",
|
||||
"signals": {
|
||||
"x": [{"name": "samx", "entry": "samx"}],
|
||||
"y": [{"name": "gauss_bpm"}, {"name": "gauss_adc1"}],
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
class ModularApp(QMainWindow):
|
||||
def __init__(self, client=None, parent=None):
|
||||
super(ModularApp, self).__init__(parent)
|
||||
|
||||
# Client and device manager from BEC
|
||||
self.client = BECDispatcher().client if client is None else client
|
||||
|
||||
# Loading UI
|
||||
current_path = os.path.dirname(__file__)
|
||||
uic.loadUi(os.path.join(current_path, "modular.ui"), self)
|
||||
|
||||
self._init_plots()
|
||||
|
||||
def _init_plots(self):
|
||||
"""Initialize plots and connect the buttons to the config dialogs"""
|
||||
plots = [self.plot_1, self.plot_2, self.plot_3]
|
||||
configs = [CONFIG_SIMPLE, CONFIG_SCAN_MODE, CONFIG_SCAN_MODE]
|
||||
buttons = [self.pushButton_setting_1, self.pushButton_setting_2, self.pushButton_setting_3]
|
||||
|
||||
# hook plots, configs and buttons together
|
||||
for plot, config, button in zip(plots, configs, buttons):
|
||||
plot.on_config_update(config)
|
||||
button.clicked.connect(plot.show_config_dialog)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# BECclient global variables
|
||||
client = BECDispatcher().client
|
||||
client.start()
|
||||
|
||||
app = QApplication([])
|
||||
modularApp = ModularApp(client=client)
|
||||
|
||||
window = modularApp
|
||||
window.show()
|
||||
app.exec()
|
||||
@@ -151,7 +151,7 @@ class MotorControlPanel(QWidget):
|
||||
self.selection_widget.selected_motors_signal.connect(self.absolute_widget.change_motors)
|
||||
|
||||
# Set the window to a fixed size based on its contents
|
||||
# self.layout().setSizeConstraint(layout.SetFixedSize)
|
||||
self.layout().setSizeConstraint(layout.SetFixedSize)
|
||||
|
||||
|
||||
class MotorControlPanelAbsolute(QWidget):
|
||||
@@ -178,6 +178,9 @@ class MotorControlPanelAbsolute(QWidget):
|
||||
# Connecting signals and slots
|
||||
self.selection_widget.selected_motors_signal.connect(self.absolute_widget.change_motors)
|
||||
|
||||
# Set the window to a fixed size based on its contents
|
||||
self.layout().setSizeConstraint(layout.SetFixedSize)
|
||||
|
||||
|
||||
class MotorControlPanelRelative(QWidget):
|
||||
def __init__(self, parent=None, client=None, config=None):
|
||||
@@ -203,6 +206,9 @@ class MotorControlPanelRelative(QWidget):
|
||||
# Connecting signals and slots
|
||||
self.selection_widget.selected_motors_signal.connect(self.relative_widget.change_motors)
|
||||
|
||||
# Set the window to a fixed size based on its contents
|
||||
self.layout().setSizeConstraint(layout.SetFixedSize)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
import argparse
|
||||
|
||||
@@ -29,10 +29,10 @@
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="glw_plot_placeholder" native="true"/>
|
||||
<widget class="QWidget" name="glw_image_placeholder" native="true"/>
|
||||
<widget class="GraphicsLayoutWidget" name="glw_plot"/>
|
||||
<widget class="GraphicsLayoutWidget" name="glw_image"/>
|
||||
</widget>
|
||||
<widget class="QWidget" name="layoutWidget">
|
||||
<widget class="QWidget" name="">
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="1,1,1,15">
|
||||
<item>
|
||||
<widget class="QPushButton" name="pushButton_generate">
|
||||
@@ -143,6 +143,13 @@
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>GraphicsLayoutWidget</class>
|
||||
<extends>QGraphicsView</extends>
|
||||
<header>pyqtgraph.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
|
||||
@@ -9,17 +9,18 @@ from bec_lib import messages
|
||||
from bec_lib.endpoints import MessageEndpoints
|
||||
from bec_lib.redis_connector import RedisConnector
|
||||
from pyqtgraph import mkBrush, mkPen
|
||||
from pyqtgraph.Qt import QtCore, QtWidgets
|
||||
from qtpy.QtCore import Signal, Slot
|
||||
from qtpy.QtWidgets import QTableWidgetItem, QVBoxLayout
|
||||
from pyqtgraph.Qt import QtCore, QtWidgets, uic
|
||||
from pyqtgraph.Qt.QtCore import pyqtSignal
|
||||
from qtpy.QtCore import Slot as pyqtSlot
|
||||
from qtpy.QtWidgets import QTableWidgetItem
|
||||
|
||||
from bec_widgets.utils import Colors, Crosshair, UILoader
|
||||
from bec_widgets.utils import Colors, Crosshair
|
||||
from bec_widgets.utils.bec_dispatcher import BECDispatcher
|
||||
|
||||
|
||||
class StreamPlot(QtWidgets.QWidget):
|
||||
update_signal = Signal()
|
||||
roi_signal = Signal(tuple)
|
||||
update_signal = pyqtSignal()
|
||||
roi_signal = pyqtSignal(tuple)
|
||||
|
||||
def __init__(self, name="", y_value_list=["gauss_bpm"], client=None, parent=None) -> None:
|
||||
"""
|
||||
@@ -38,7 +39,7 @@ class StreamPlot(QtWidgets.QWidget):
|
||||
pg.setConfigOption("background", "w")
|
||||
pg.setConfigOption("foreground", "k")
|
||||
current_path = os.path.dirname(__file__)
|
||||
self.ui = UILoader().load_ui(os.path.join(current_path, "line_plot.ui"), self)
|
||||
uic.loadUi(os.path.join(current_path, "line_plot.ui"), self)
|
||||
|
||||
self._idle_time = 100
|
||||
self.connector = RedisConnector(["localhost:6379"])
|
||||
@@ -81,9 +82,6 @@ class StreamPlot(QtWidgets.QWidget):
|
||||
|
||||
# LabelItem for ROI
|
||||
self.label_plot = pg.LabelItem(justify="center")
|
||||
self.glw_plot_layout = QVBoxLayout(self.ui.glw_plot_placeholder)
|
||||
self.glw_plot = pg.GraphicsLayoutWidget()
|
||||
self.glw_plot_layout.addWidget(self.glw_plot)
|
||||
self.glw_plot.addItem(self.label_plot)
|
||||
self.label_plot.setText("ROI region")
|
||||
|
||||
@@ -114,9 +112,6 @@ class StreamPlot(QtWidgets.QWidget):
|
||||
|
||||
# Label for coordinates moved
|
||||
self.label_image_moved = pg.LabelItem(justify="center")
|
||||
self.glw_image_layout = QVBoxLayout(self.ui.glw_image_placeholder)
|
||||
self.glw_image = pg.GraphicsLayoutWidget()
|
||||
self.glw_plot_layout.addWidget(self.glw_image)
|
||||
self.glw_image.addItem(self.label_image_moved)
|
||||
self.label_image_moved.setText("Actual coordinates (X, Y)")
|
||||
|
||||
@@ -226,10 +221,10 @@ class StreamPlot(QtWidgets.QWidget):
|
||||
|
||||
def init_table(self):
|
||||
# Init number of rows in table according to n of devices
|
||||
self.ui.cursor_table.setRowCount(len(self.y_value_list))
|
||||
self.cursor_table.setRowCount(len(self.y_value_list))
|
||||
# self.table.setHorizontalHeaderLabels(["(X, Y) - Moved", "(X, Y) - Clicked"]) #TODO can be dynamic
|
||||
self.ui.cursor_table.setVerticalHeaderLabels(self.y_value_list)
|
||||
self.ui.cursor_table.resizeColumnsToContents()
|
||||
self.cursor_table.setVerticalHeaderLabels(self.y_value_list)
|
||||
self.cursor_table.resizeColumnsToContents()
|
||||
|
||||
def update_table(self, table_widget, x, y_values):
|
||||
for i, y in enumerate(y_values):
|
||||
@@ -292,13 +287,13 @@ class StreamPlot(QtWidgets.QWidget):
|
||||
|
||||
self.update_signal.emit()
|
||||
|
||||
@Slot(dict, dict)
|
||||
@pyqtSlot(dict, dict)
|
||||
def on_dap_update(self, data: dict, metadata: dict):
|
||||
flipped_data = self.flip_even_rows(data["data"]["z"])
|
||||
|
||||
self.img.setImage(flipped_data)
|
||||
|
||||
@Slot(dict, dict)
|
||||
@pyqtSlot(dict, dict)
|
||||
def new_proj(self, content: dict, _metadata: dict):
|
||||
proj_nr = content["signals"]["proj_nr"]
|
||||
endpoint = f"px_stream/projection_{proj_nr}/metadata"
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
"""PySide6 port of the Qt Designer taskmenuextension example from Qt v6.x"""
|
||||
|
||||
import sys
|
||||
|
||||
from bec_ipython_client.main import BECIPythonClient
|
||||
from PySide6.QtWidgets import QApplication
|
||||
from tictactoe import TicTacToe
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
window = TicTacToe()
|
||||
window.state = "-X-XO----"
|
||||
window.show()
|
||||
sys.exit(app.exec())
|
||||
@@ -1,24 +0,0 @@
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
from PySide6.scripts.pyside_tool import designer
|
||||
|
||||
import bec_widgets
|
||||
|
||||
|
||||
def main():
|
||||
# os.environ["PYSIDE_DESIGNER_PLUGINS"] = os.path.join(
|
||||
# "/Users/janwyzula/PSI/bec_widgets/bec_widgets/plugin"
|
||||
# )
|
||||
os.environ["PYSIDE_DESIGNER_PLUGINS"] = os.path.join(
|
||||
os.path.dirname(bec_widgets.__file__), "widgets/motor_control/selection"
|
||||
)
|
||||
# os.environ["PYTHONFRAMEWORKPREFIX"] = os.path.join(
|
||||
# os.path.dirname(bec_widgets.__file__), "widgets/motor_control/selection"
|
||||
# )
|
||||
designer()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,12 +0,0 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||
from tictactoe import TicTacToe
|
||||
from tictactoeplugin import TicTacToePlugin
|
||||
|
||||
# Set PYSIDE_DESIGNER_PLUGINS to point to this directory and load the plugin
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
QPyDesignerCustomWidgetCollection.addCustomWidget(TicTacToePlugin())
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"files": ["tictactoe.py", "main.py", "registertictactoe.py", "tictactoeplugin.py",
|
||||
"tictactoetaskmenu.py"]
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
from PySide6.QtCore import Property, QPoint, QRect, QSize, Qt, Slot
|
||||
from PySide6.QtGui import QPainter, QPen
|
||||
from PySide6.QtWidgets import QWidget
|
||||
|
||||
EMPTY = "-"
|
||||
CROSS = "X"
|
||||
NOUGHT = "O"
|
||||
DEFAULT_STATE = "---------"
|
||||
|
||||
|
||||
class TicTacToe(QWidget):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self._state = DEFAULT_STATE
|
||||
self._turn_number = 0
|
||||
|
||||
def minimumSizeHint(self):
|
||||
return QSize(200, 200)
|
||||
|
||||
def sizeHint(self):
|
||||
return QSize(200, 200)
|
||||
|
||||
def setState(self, new_state):
|
||||
self._turn_number = 0
|
||||
self._state = DEFAULT_STATE
|
||||
for position in range(min(9, len(new_state))):
|
||||
mark = new_state[position]
|
||||
if mark == CROSS or mark == NOUGHT:
|
||||
self._turn_number += 1
|
||||
self._change_state_at(position, mark)
|
||||
position += 1
|
||||
self.update()
|
||||
|
||||
def state(self):
|
||||
return self._state
|
||||
|
||||
@Slot()
|
||||
def clear_board(self):
|
||||
self._state = DEFAULT_STATE
|
||||
self._turn_number = 0
|
||||
self.update()
|
||||
|
||||
def _change_state_at(self, pos, new_state):
|
||||
self._state = self._state[:pos] + new_state + self._state[pos + 1 :]
|
||||
|
||||
def mousePressEvent(self, event):
|
||||
if self._turn_number == 9:
|
||||
self.clear_board()
|
||||
return
|
||||
for position in range(9):
|
||||
cell = self._cell_rect(position)
|
||||
if cell.contains(event.position().toPoint()):
|
||||
if self._state[position] == EMPTY:
|
||||
new_state = CROSS if self._turn_number % 2 == 0 else NOUGHT
|
||||
self._change_state_at(position, new_state)
|
||||
self._turn_number += 1
|
||||
self.update()
|
||||
|
||||
def paintEvent(self, event):
|
||||
with QPainter(self) as painter:
|
||||
painter.setRenderHint(QPainter.Antialiasing)
|
||||
|
||||
painter.setPen(QPen(Qt.darkGreen, 1))
|
||||
painter.drawLine(self._cell_width(), 0, self._cell_width(), self.height())
|
||||
painter.drawLine(2 * self._cell_width(), 0, 2 * self._cell_width(), self.height())
|
||||
painter.drawLine(0, self._cell_height(), self.width(), self._cell_height())
|
||||
painter.drawLine(0, 2 * self._cell_height(), self.width(), 2 * self._cell_height())
|
||||
|
||||
painter.setPen(QPen(Qt.darkBlue, 2))
|
||||
|
||||
for position in range(9):
|
||||
cell = self._cell_rect(position)
|
||||
if self._state[position] == CROSS:
|
||||
painter.drawLine(cell.topLeft(), cell.bottomRight())
|
||||
painter.drawLine(cell.topRight(), cell.bottomLeft())
|
||||
elif self._state[position] == NOUGHT:
|
||||
painter.drawEllipse(cell)
|
||||
|
||||
painter.setPen(QPen(Qt.yellow, 3))
|
||||
|
||||
for position in range(0, 8, 3):
|
||||
if (
|
||||
self._state[position] != EMPTY
|
||||
and self._state[position + 1] == self._state[position]
|
||||
and self._state[position + 2] == self._state[position]
|
||||
):
|
||||
y = self._cell_rect(position).center().y()
|
||||
painter.drawLine(0, y, self.width(), y)
|
||||
self._turn_number = 9
|
||||
|
||||
for position in range(3):
|
||||
if (
|
||||
self._state[position] != EMPTY
|
||||
and self._state[position + 3] == self._state[position]
|
||||
and self._state[position + 6] == self._state[position]
|
||||
):
|
||||
x = self._cell_rect(position).center().x()
|
||||
painter.drawLine(x, 0, x, self.height())
|
||||
self._turn_number = 9
|
||||
|
||||
if (
|
||||
self._state[0] != EMPTY
|
||||
and self._state[4] == self._state[0]
|
||||
and self._state[8] == self._state[0]
|
||||
):
|
||||
painter.drawLine(0, 0, self.width(), self.height())
|
||||
self._turn_number = 9
|
||||
|
||||
if (
|
||||
self._state[2] != EMPTY
|
||||
and self._state[4] == self._state[2]
|
||||
and self._state[6] == self._state[2]
|
||||
):
|
||||
painter.drawLine(0, self.height(), self.width(), 0)
|
||||
self._turn_number = 9
|
||||
|
||||
def _cell_rect(self, position):
|
||||
h_margin = self.width() / 30
|
||||
v_margin = self.height() / 30
|
||||
row = int(position / 3)
|
||||
column = position - 3 * row
|
||||
pos = QPoint(column * self._cell_width() + h_margin, row * self._cell_height() + v_margin)
|
||||
size = QSize(self._cell_width() - 2 * h_margin, self._cell_height() - 2 * v_margin)
|
||||
return QRect(pos, size)
|
||||
|
||||
def _cell_width(self):
|
||||
return self.width() / 3
|
||||
|
||||
def _cell_height(self):
|
||||
return self.height() / 3
|
||||
|
||||
state = Property(str, state, setState)
|
||||
@@ -1,68 +0,0 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
from PySide6.QtDesigner import QDesignerCustomWidgetInterface
|
||||
from PySide6.QtGui import QIcon
|
||||
from tictactoe import TicTacToe
|
||||
from tictactoetaskmenu import TicTacToeTaskMenuFactory
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
<widget class='TicTacToe' name='ticTacToe'>
|
||||
<property name='geometry'>
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>200</width>
|
||||
<height>200</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name='state'>
|
||||
<string>-X-XO----</string>
|
||||
</property>
|
||||
</widget>
|
||||
</ui>
|
||||
"""
|
||||
|
||||
|
||||
class TicTacToePlugin(QDesignerCustomWidgetInterface):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._form_editor = None
|
||||
|
||||
def createWidget(self, parent):
|
||||
t = TicTacToe(parent)
|
||||
return t
|
||||
|
||||
def domXml(self):
|
||||
return DOM_XML
|
||||
|
||||
def group(self):
|
||||
return ""
|
||||
|
||||
def icon(self):
|
||||
return QIcon()
|
||||
|
||||
def includeFile(self):
|
||||
return "tictactoe"
|
||||
|
||||
def initialize(self, form_editor):
|
||||
self._form_editor = form_editor
|
||||
manager = form_editor.extensionManager()
|
||||
iid = TicTacToeTaskMenuFactory.task_menu_iid()
|
||||
manager.registerExtensions(TicTacToeTaskMenuFactory(manager), iid)
|
||||
|
||||
def isContainer(self):
|
||||
return False
|
||||
|
||||
def isInitialized(self):
|
||||
return self._form_editor is not None
|
||||
|
||||
def name(self):
|
||||
return "TicTacToe"
|
||||
|
||||
def toolTip(self):
|
||||
return "Tic Tac Toe Example, demonstrating class QDesignerTaskMenuExtension (Python)"
|
||||
|
||||
def whatsThis(self):
|
||||
return self.toolTip()
|
||||
@@ -1,67 +0,0 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
from PySide6.QtCore import Slot
|
||||
from PySide6.QtDesigner import QExtensionFactory, QPyDesignerTaskMenuExtension
|
||||
from PySide6.QtGui import QAction
|
||||
from PySide6.QtWidgets import QDialog, QDialogButtonBox, QVBoxLayout
|
||||
from tictactoe import TicTacToe
|
||||
|
||||
|
||||
class TicTacToeDialog(QDialog):
|
||||
def __init__(self, parent):
|
||||
super().__init__(parent)
|
||||
layout = QVBoxLayout(self)
|
||||
self._ticTacToe = TicTacToe(self)
|
||||
layout.addWidget(self._ticTacToe)
|
||||
button_box = QDialogButtonBox(
|
||||
QDialogButtonBox.Ok | QDialogButtonBox.Cancel | QDialogButtonBox.Reset
|
||||
)
|
||||
button_box.accepted.connect(self.accept)
|
||||
button_box.rejected.connect(self.reject)
|
||||
reset_button = button_box.button(QDialogButtonBox.Reset)
|
||||
reset_button.clicked.connect(self._ticTacToe.clear_board)
|
||||
layout.addWidget(button_box)
|
||||
|
||||
def set_state(self, new_state):
|
||||
self._ticTacToe.setState(new_state)
|
||||
|
||||
def state(self):
|
||||
return self._ticTacToe.state
|
||||
|
||||
|
||||
class TicTacToeTaskMenu(QPyDesignerTaskMenuExtension):
|
||||
def __init__(self, ticTacToe, parent):
|
||||
super().__init__(parent)
|
||||
self._ticTacToe = ticTacToe
|
||||
self._edit_state_action = QAction("Edit State...", None)
|
||||
self._edit_state_action.triggered.connect(self._edit_state)
|
||||
|
||||
def taskActions(self):
|
||||
return [self._edit_state_action]
|
||||
|
||||
def preferredEditAction(self):
|
||||
return self._edit_state_action
|
||||
|
||||
@Slot()
|
||||
def _edit_state(self):
|
||||
dialog = TicTacToeDialog(self._ticTacToe)
|
||||
dialog.set_state(self._ticTacToe.state)
|
||||
if dialog.exec() == QDialog.Accepted:
|
||||
self._ticTacToe.state = dialog.state()
|
||||
|
||||
|
||||
class TicTacToeTaskMenuFactory(QExtensionFactory):
|
||||
def __init__(self, extension_manager):
|
||||
super().__init__(extension_manager)
|
||||
|
||||
@staticmethod
|
||||
def task_menu_iid():
|
||||
return "org.qt-project.Qt.Designer.TaskMenu"
|
||||
|
||||
def createExtension(self, object, iid, parent):
|
||||
if iid != TicTacToeTaskMenuFactory.task_menu_iid():
|
||||
return None
|
||||
if object.__class__.__name__ != "TicTacToe":
|
||||
return None
|
||||
return TicTacToeTaskMenu(object, parent)
|
||||
@@ -7,5 +7,4 @@ from .crosshair import Crosshair
|
||||
from .entry_validator import EntryValidator
|
||||
from .layout_manager import GridLayoutManager
|
||||
from .rpc_decorator import register_rpc_methods, rpc_public
|
||||
from .ui_loader import UILoader
|
||||
from .validator_delegate import DoubleValidationDelegate
|
||||
|
||||
@@ -8,13 +8,13 @@ from qtpy.QtCore import Signal as pyqtSignal
|
||||
|
||||
class Crosshair(QObject):
|
||||
# Signal for 1D plot
|
||||
coordinatesChanged1D = pyqtSignal(tuple)
|
||||
coordinatesClicked1D = pyqtSignal(tuple)
|
||||
coordinatesChanged1D = pyqtSignal(float, list)
|
||||
coordinatesClicked1D = pyqtSignal(float, list)
|
||||
# Signal for 2D plot
|
||||
coordinatesChanged2D = pyqtSignal(tuple)
|
||||
coordinatesClicked2D = pyqtSignal(tuple)
|
||||
coordinatesChanged2D = pyqtSignal(float, float)
|
||||
coordinatesClicked2D = pyqtSignal(float, float)
|
||||
|
||||
def __init__(self, plot_item: pg.PlotItem, precision: int = 3, parent=None):
|
||||
def __init__(self, plot_item: pg.PlotItem, precision: int = None, parent=None):
|
||||
"""
|
||||
Crosshair for 1D and 2D plots.
|
||||
|
||||
@@ -174,11 +174,10 @@ class Crosshair(QObject):
|
||||
if isinstance(item, pg.PlotDataItem):
|
||||
if x is None or all(v is None for v in y_values):
|
||||
return
|
||||
coordinance_to_emit = (
|
||||
self.coordinatesChanged1D.emit(
|
||||
round(x, self.precision),
|
||||
[round(y_val, self.precision) for y_val in y_values],
|
||||
)
|
||||
self.coordinatesChanged1D.emit(coordinance_to_emit)
|
||||
for i, y_val in enumerate(y_values):
|
||||
self.marker_moved_1d[i].setData(
|
||||
[x if not self.is_log_x else np.log10(x)],
|
||||
@@ -187,8 +186,7 @@ class Crosshair(QObject):
|
||||
elif isinstance(item, pg.ImageItem):
|
||||
if x is None or y_values is None:
|
||||
return
|
||||
coordinance_to_emit = (x, y_values)
|
||||
self.coordinatesChanged2D.emit(coordinance_to_emit)
|
||||
self.coordinatesChanged2D.emit(x, y_values)
|
||||
|
||||
def mouse_clicked(self, event):
|
||||
"""Handles the mouse clicked event, updating the crosshair position and emitting signals.
|
||||
@@ -211,11 +209,10 @@ class Crosshair(QObject):
|
||||
if isinstance(item, pg.PlotDataItem):
|
||||
if x is None or all(v is None for v in y_values):
|
||||
return
|
||||
coordinate_to_emit = (
|
||||
self.coordinatesClicked1D.emit(
|
||||
round(x, self.precision),
|
||||
[round(y_val, self.precision) for y_val in y_values],
|
||||
)
|
||||
self.coordinatesClicked1D.emit(coordinate_to_emit)
|
||||
for i, y_val in enumerate(y_values):
|
||||
for marker in self.marker_clicked_1d[i]:
|
||||
marker.setData(
|
||||
@@ -225,8 +222,7 @@ class Crosshair(QObject):
|
||||
elif isinstance(item, pg.ImageItem):
|
||||
if x is None or y_values is None:
|
||||
return
|
||||
coordinate_to_emit = (x, y_values)
|
||||
self.coordinatesClicked2D.emit(coordinate_to_emit)
|
||||
self.coordinatesClicked2D.emit(x, y_values)
|
||||
self.marker_2d.setPos([x, y_values])
|
||||
|
||||
def check_log(self):
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
from qtpy import QT_VERSION
|
||||
from qtpy.QtCore import QFile, QIODevice
|
||||
|
||||
|
||||
class UILoader:
|
||||
"""Universal UI loader for PyQt5, PyQt6, PySide2, and PySide6."""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
self.parent = parent
|
||||
if QT_VERSION.startswith("5"):
|
||||
# PyQt5 or PySide2
|
||||
from qtpy import uic
|
||||
|
||||
self.loader = uic.loadUi
|
||||
elif QT_VERSION.startswith("6"):
|
||||
# PyQt6 or PySide6
|
||||
try:
|
||||
from PySide6.QtUiTools import QUiLoader
|
||||
|
||||
self.loader = self.load_ui_pyside6
|
||||
except ImportError:
|
||||
from PyQt6.uic import loadUi
|
||||
|
||||
self.loader = loadUi
|
||||
|
||||
def load_ui_pyside6(self, ui_file, parent=None):
|
||||
"""
|
||||
Specific loader for PySide6 using QUiLoader.
|
||||
Args:
|
||||
ui_file(str): Path to the .ui file.
|
||||
parent(QWidget): Parent widget.
|
||||
|
||||
Returns:
|
||||
QWidget: The loaded widget.
|
||||
"""
|
||||
from PySide6.QtUiTools import QUiLoader
|
||||
|
||||
loader = QUiLoader(parent)
|
||||
file = QFile(ui_file)
|
||||
if not file.open(QIODevice.ReadOnly):
|
||||
raise IOError(f"Cannot open file: {ui_file}")
|
||||
widget = loader.load(file, parent)
|
||||
file.close()
|
||||
return widget
|
||||
|
||||
def load_ui(self, ui_file, parent=None):
|
||||
"""
|
||||
Universal UI loader method.
|
||||
Args:
|
||||
ui_file(str): Path to the .ui file.
|
||||
parent(QWidget): Parent widget.
|
||||
|
||||
Returns:
|
||||
QWidget: The loaded widget.
|
||||
"""
|
||||
if parent is None:
|
||||
parent = self.parent
|
||||
return self.loader(ui_file, parent)
|
||||
@@ -1,14 +0,0 @@
|
||||
from qtpy.QtWidgets import QComboBox
|
||||
|
||||
from bec_widgets.utils import BECConnector, ConnectionConfig
|
||||
|
||||
|
||||
class DeviceCombobox(BECConnector, QComboBox):
|
||||
def __init__(self, parent=None, client=None, config=None, gui_id=None):
|
||||
super().__init__(client=client, config=config, gui_id=gui_id)
|
||||
QComboBox.__init__(self, parent=parent)
|
||||
|
||||
self.get_bec_shortcuts()
|
||||
|
||||
def get_device(self):
|
||||
return getattr(self.dev, self.text().lower())
|
||||
115
bec_widgets/widgets/dock_area/dock/dock.py
Normal file
115
bec_widgets/widgets/dock_area/dock/dock.py
Normal file
@@ -0,0 +1,115 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, Literal, Optional
|
||||
from weakref import WeakValueDictionary
|
||||
|
||||
from pydantic import Field
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtWidgets import QGridLayout, QHBoxLayout, QVBoxLayout
|
||||
from qtpy.QtWidgets import QDockWidget, QSizePolicy
|
||||
|
||||
from bec_widgets.cli.rpc_wigdet_handler import RPCWidgetHandler
|
||||
from bec_widgets.utils import BECConnector, ConnectionConfig
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from qtpy.QtWidgets import QWidget
|
||||
|
||||
from bec_widgets.widgets.dock_area.dock_area import BECDockAreaAlt
|
||||
|
||||
|
||||
class DockConfig(ConnectionConfig):
|
||||
widgets: dict[str, ConnectionConfig] = Field({}, description="The widgets in the dock.")
|
||||
position: Literal["bottom", "top", "left", "right", "above", "below"] = Field(
|
||||
"bottom", description="The position of the dock."
|
||||
)
|
||||
parent_dock_area: Optional[str] = Field(
|
||||
None, description="The GUI ID of parent dock area of the dock."
|
||||
)
|
||||
|
||||
|
||||
class BECDockAlt(BECConnector, QDockWidget):
|
||||
USER_ACCESS = []
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parent: QWidget | None = None,
|
||||
parent_dock_area: BECDockAreaAlt | None = None,
|
||||
config: DockConfig | None = None,
|
||||
title: str | None = None, # TODO maybe rename to title
|
||||
client=None,
|
||||
gui_id: str | None = None,
|
||||
layout: Literal["horizontal", "vertical", "grid"] = "vertical",
|
||||
) -> None:
|
||||
if config is None:
|
||||
config = DockConfig(
|
||||
widget_class=self.__class__.__name__, parent_dock_area=parent_dock_area.gui_id
|
||||
)
|
||||
else:
|
||||
if isinstance(config, dict):
|
||||
config = DockConfig(**config)
|
||||
super().__init__(client=client, config=config, gui_id=gui_id)
|
||||
QDockWidget.__init__(self, title, parent)
|
||||
|
||||
self._parent_dock_area = parent_dock_area
|
||||
self._init_setup()
|
||||
self.setup_layout(layout)
|
||||
|
||||
self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
# self._grid_layout_manager = GridLayoutManager(self)
|
||||
# self._widget_handler.init()
|
||||
# self._grid_layout_manager.init
|
||||
|
||||
def _init_setup(self):
|
||||
self.setAllowedAreas(Qt.DockWidgetArea.AllDockWidgetAreas)
|
||||
self._widget_handler = RPCWidgetHandler()
|
||||
self._base_widget = QWidget()
|
||||
self._widgets = WeakValueDictionary()
|
||||
|
||||
def setup_layout(self, layout: Literal["horizontal", "vertical", "grid"] = "grid"):
|
||||
if layout == "horizontal":
|
||||
self.layout = QHBoxLayout()
|
||||
elif layout == "vertical":
|
||||
self.layout = QVBoxLayout()
|
||||
elif layout == "grid":
|
||||
self.layout = QGridLayout()
|
||||
self.layout.setContentsMargins(0, 0, 0, 0)
|
||||
self._base_widget.setLayout(self.layout)
|
||||
self.setWidget(self._base_widget)
|
||||
|
||||
def add_widget(self, widget: QWidget, row: int = None, col: int = None):
|
||||
|
||||
if isinstance(self.layout, QVBoxLayout) or isinstance(self.layout, QHBoxLayout):
|
||||
self.layout.addWidget(widget)
|
||||
if row or col:
|
||||
print(f"Warning: row and column are not used in this layout - {self.layout}")
|
||||
elif isinstance(self.layout, QGridLayout):
|
||||
if row is None:
|
||||
row = self.layout.rowCount()
|
||||
if col is None:
|
||||
col = self.layout.columnCount()
|
||||
self.layout.addWidget(widget, row, col)
|
||||
|
||||
return widget
|
||||
|
||||
def add_widget_bec(self, widget_type: str, row: int = None, col: int = None):
|
||||
widget = self._widget_handler.create_widget(widget_type)
|
||||
self.add_widget(widget, row, col)
|
||||
return widget
|
||||
|
||||
def remove_widget(self, widget_id: str):
|
||||
widget = self.widgets.pop(widget_id)
|
||||
widget.deleteLater()
|
||||
return widget
|
||||
|
||||
def cleanup(self):
|
||||
"""
|
||||
Clean up the dock, including all its widgets.
|
||||
"""
|
||||
for widget in self.widgets:
|
||||
if hasattr(widget, "cleanup"):
|
||||
widget.cleanup()
|
||||
super().cleanup()
|
||||
|
||||
def close(self):
|
||||
self.cleanup()
|
||||
super().close()
|
||||
82
bec_widgets/widgets/dock_area/dock_area.py
Normal file
82
bec_widgets/widgets/dock_area/dock_area.py
Normal file
@@ -0,0 +1,82 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Literal
|
||||
from weakref import WeakValueDictionary
|
||||
|
||||
from pydantic import Field
|
||||
from qtpy.QtCore import Qt
|
||||
from qtpy.QtWidgets import QMainWindow, QWidget
|
||||
|
||||
from bec_widgets.utils import BECConnector, ConnectionConfig, WidgetContainerUtils
|
||||
from bec_widgets.widgets.dock_area.dock.dock import BECDockAlt, DockConfig
|
||||
|
||||
|
||||
class DockAreaConfig(ConnectionConfig):
|
||||
docks: dict[str, DockConfig] = Field({}, description="The docks in the dock area.")
|
||||
|
||||
|
||||
class BECDockAreaAlt(BECConnector, QMainWindow):
|
||||
USER_ACCESS = []
|
||||
positions = {
|
||||
"top": Qt.DockWidgetArea.TopDockWidgetArea,
|
||||
"bottom": Qt.DockWidgetArea.BottomDockWidgetArea,
|
||||
"left": Qt.DockWidgetArea.LeftDockWidgetArea,
|
||||
"right": Qt.DockWidgetArea.RightDockWidgetArea,
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
parent: QWidget | None = None,
|
||||
config: DockAreaConfig | None = None,
|
||||
client=None,
|
||||
gui_id: str = None,
|
||||
) -> None:
|
||||
if config is None:
|
||||
config = DockAreaConfig(widget_class=self.__class__.__name__)
|
||||
else:
|
||||
if isinstance(config, dict):
|
||||
config = DockAreaConfig(**config)
|
||||
self.config = config
|
||||
super().__init__(client=client, config=config, gui_id=gui_id)
|
||||
QMainWindow.__init__(self, parent=parent)
|
||||
# TODO experimetn with the options and features
|
||||
# self.setDockNestingEnabled(True)
|
||||
# self.setDockOptions(
|
||||
# QMainWindow.DockOption.AllowTabbedDocks | QMainWindow.DockOption.AllowNestedDocks
|
||||
# )
|
||||
self._instructions_visible = True # TODO do not know how to translate yet to native qt
|
||||
|
||||
self._docks = WeakValueDictionary()
|
||||
|
||||
@property
|
||||
def docks(self) -> dict:
|
||||
"""
|
||||
Get the docks in the dock area.
|
||||
Returns:
|
||||
dock_dict(dict): The docks in the dock area.
|
||||
"""
|
||||
return dict(self._docks)
|
||||
|
||||
@docks.setter
|
||||
def docks(self, value: dict):
|
||||
self._docks = WeakValueDictionary(value)
|
||||
|
||||
def add_dock(
|
||||
self,
|
||||
title: str = None,
|
||||
layout: Literal["horizontal", "vertical", "grid"] = "vertical",
|
||||
prefix: str = "dock",
|
||||
position: Literal["top", "bottom", "left", "right"] = "bottom",
|
||||
):
|
||||
|
||||
if title is None:
|
||||
title = WidgetContainerUtils.generate_unique_widget_id(self._docks, prefix=prefix)
|
||||
|
||||
if title in set(self.docks.keys()):
|
||||
raise ValueError(f"Dock with name {title} already exists.")
|
||||
|
||||
dock = BECDockAlt(title=title, parent=self, parent_dock_area=self, layout=layout)
|
||||
self.addDockWidget(self.positions[position], dock)
|
||||
self._docks[title] = dock
|
||||
|
||||
return dock
|
||||
@@ -16,7 +16,6 @@ from qtpy.QtWidgets import (
|
||||
QTableWidgetItem,
|
||||
)
|
||||
|
||||
from bec_widgets.utils import UILoader
|
||||
from bec_widgets.widgets.motor_control.motor_control import MotorControlWidget
|
||||
|
||||
|
||||
@@ -38,25 +37,25 @@ class MotorCoordinateTable(MotorControlWidget):
|
||||
def _load_ui(self):
|
||||
"""Load the UI for the coordinate table."""
|
||||
current_path = os.path.dirname(__file__)
|
||||
self.ui = UILoader().load_ui(os.path.join(current_path, "motor_table.ui"), self)
|
||||
uic.loadUi(os.path.join(current_path, "motor_table.ui"), self)
|
||||
|
||||
def _init_ui(self):
|
||||
"""Initialize the UI"""
|
||||
# Setup table behaviour
|
||||
self._setup_table()
|
||||
self.ui.table.setSelectionBehavior(QTableWidget.SelectRows)
|
||||
self.table.setSelectionBehavior(QTableWidget.SelectRows)
|
||||
|
||||
# for tag columns default tag
|
||||
self.tag_counter = 1
|
||||
|
||||
# Connect signals and slots
|
||||
self.ui.checkBox_resize_auto.stateChanged.connect(self.resize_table_auto)
|
||||
self.ui.comboBox_mode.currentIndexChanged.connect(self.mode_switch)
|
||||
self.checkBox_resize_auto.stateChanged.connect(self.resize_table_auto)
|
||||
self.comboBox_mode.currentIndexChanged.connect(self.mode_switch)
|
||||
|
||||
# Keyboard shortcuts for deleting a row
|
||||
self.delete_shortcut = QShortcut(QKeySequence(Qt.Key_Delete), self.ui.table)
|
||||
self.delete_shortcut = QShortcut(QKeySequence(Qt.Key_Delete), self.table)
|
||||
self.delete_shortcut.activated.connect(self.delete_selected_row)
|
||||
self.backspace_shortcut = QShortcut(QKeySequence(Qt.Key_Backspace), self.ui.table)
|
||||
self.backspace_shortcut = QShortcut(QKeySequence(Qt.Key_Backspace), self.table)
|
||||
self.backspace_shortcut.activated.connect(self.delete_selected_row)
|
||||
|
||||
# Warning message for mode switch enable/disable
|
||||
@@ -84,13 +83,13 @@ class MotorCoordinateTable(MotorControlWidget):
|
||||
self.mode = self.config["motor_control"].get("mode", "Individual")
|
||||
|
||||
# Set combobox to default mode
|
||||
self.ui.comboBox_mode.setCurrentText(self.mode)
|
||||
self.comboBox_mode.setCurrentText(self.mode)
|
||||
|
||||
self._init_ui()
|
||||
|
||||
def _setup_table(self):
|
||||
"""Setup the table with appropriate headers and configurations."""
|
||||
mode = self.ui.comboBox_mode.currentText()
|
||||
mode = self.comboBox_mode.currentText()
|
||||
|
||||
if mode == "Individual":
|
||||
self._setup_individual_mode()
|
||||
@@ -102,14 +101,14 @@ class MotorCoordinateTable(MotorControlWidget):
|
||||
|
||||
def _setup_individual_mode(self):
|
||||
"""Setup the table for individual mode."""
|
||||
self.ui.table.setColumnCount(5)
|
||||
self.ui.table.setHorizontalHeaderLabels(["Show", "Move", "Tag", "X", "Y"])
|
||||
self.ui.table.verticalHeader().setVisible(False)
|
||||
self.table.setColumnCount(5)
|
||||
self.table.setHorizontalHeaderLabels(["Show", "Move", "Tag", "X", "Y"])
|
||||
self.table.verticalHeader().setVisible(False)
|
||||
|
||||
def _setup_start_stop_mode(self):
|
||||
"""Setup the table for start/stop mode."""
|
||||
self.ui.table.setColumnCount(8)
|
||||
self.ui.table.setHorizontalHeaderLabels(
|
||||
self.table.setColumnCount(8)
|
||||
self.table.setHorizontalHeaderLabels(
|
||||
[
|
||||
"Show",
|
||||
"Move [start]",
|
||||
@@ -121,15 +120,15 @@ class MotorCoordinateTable(MotorControlWidget):
|
||||
"Y [end]",
|
||||
]
|
||||
)
|
||||
self.ui.table.verticalHeader().setVisible(False)
|
||||
self.table.verticalHeader().setVisible(False)
|
||||
# Set flag to track if the coordinate is stat or the end of the entry
|
||||
self.is_next_entry_end = False
|
||||
|
||||
def mode_switch(self):
|
||||
"""Switch between individual and start/stop mode."""
|
||||
last_selected_index = self.ui.comboBox_mode.currentIndex()
|
||||
last_selected_index = self.comboBox_mode.currentIndex()
|
||||
|
||||
if self.ui.table.rowCount() > 0 and self.warning_message is True:
|
||||
if self.table.rowCount() > 0 and self.warning_message is True:
|
||||
msgBox = QMessageBox()
|
||||
msgBox.setIcon(QMessageBox.Critical)
|
||||
msgBox.setText(
|
||||
@@ -139,9 +138,9 @@ class MotorCoordinateTable(MotorControlWidget):
|
||||
returnValue = msgBox.exec()
|
||||
|
||||
if returnValue is QMessageBox.Cancel:
|
||||
self.ui.comboBox_mode.blockSignals(True) # Block signals
|
||||
self.ui.comboBox_mode.setCurrentIndex(last_selected_index)
|
||||
self.ui.comboBox_mode.blockSignals(False) # Unblock signals
|
||||
self.comboBox_mode.blockSignals(True) # Block signals
|
||||
self.comboBox_mode.setCurrentIndex(last_selected_index)
|
||||
self.comboBox_mode.blockSignals(False) # Unblock signals
|
||||
return
|
||||
|
||||
# Wipe table
|
||||
@@ -171,7 +170,7 @@ class MotorCoordinateTable(MotorControlWidget):
|
||||
y(float): Y coordinate.
|
||||
"""
|
||||
|
||||
mode = self.ui.comboBox_mode.currentText()
|
||||
mode = self.comboBox_mode.currentText()
|
||||
if mode == "Individual":
|
||||
checkbox_pos = 0
|
||||
button_pos = 1
|
||||
@@ -182,8 +181,8 @@ class MotorCoordinateTable(MotorControlWidget):
|
||||
color = "green"
|
||||
|
||||
# Add new row -> new entry
|
||||
row_count = self.ui.table.rowCount()
|
||||
self.ui.table.insertRow(row_count)
|
||||
row_count = self.table.rowCount()
|
||||
self.table.insertRow(row_count)
|
||||
|
||||
# Add Widgets
|
||||
self._add_widgets(
|
||||
@@ -214,8 +213,8 @@ class MotorCoordinateTable(MotorControlWidget):
|
||||
color = "blue"
|
||||
|
||||
# Add new row -> new entry
|
||||
row_count = self.ui.table.rowCount()
|
||||
self.ui.table.insertRow(row_count)
|
||||
row_count = self.table.rowCount()
|
||||
self.table.insertRow(row_count)
|
||||
|
||||
# Add Widgets
|
||||
self._add_widgets(
|
||||
@@ -237,7 +236,7 @@ class MotorCoordinateTable(MotorControlWidget):
|
||||
|
||||
elif self.is_next_entry_end is True: # It is the end position of the entry
|
||||
print("End position")
|
||||
row_count = self.ui.table.rowCount() - 1 # Current row
|
||||
row_count = self.table.rowCount() - 1 # Current row
|
||||
button_pos = 2
|
||||
x_pos = 6
|
||||
y_pos = 7
|
||||
@@ -295,7 +294,7 @@ class MotorCoordinateTable(MotorControlWidget):
|
||||
# Add widgets
|
||||
self._add_checkbox(row, checkBox_pos, x_pos, y_pos)
|
||||
self._add_move_button(row, button_pos, x_pos, y_pos)
|
||||
self.ui.table.setItem(row, tag_pos, QTableWidgetItem(tag))
|
||||
self.table.setItem(row, tag_pos, QTableWidgetItem(tag))
|
||||
self._add_line_edit(x, row, x_pos, x_pos, y_pos, coordinate_reference, color)
|
||||
self._add_line_edit(y, row, y_pos, x_pos, y_pos, coordinate_reference, color)
|
||||
|
||||
@@ -303,10 +302,10 @@ class MotorCoordinateTable(MotorControlWidget):
|
||||
self.emit_plot_coordinates(x_pos, y_pos, coordinate_reference, color)
|
||||
|
||||
# Connect item edit to emit coordinates
|
||||
self.ui.table.itemChanged.connect(
|
||||
self.table.itemChanged.connect(
|
||||
lambda: print(f"item changed from {coordinate_reference} slot \n {x}-{y}-{color}")
|
||||
)
|
||||
self.ui.table.itemChanged.connect(
|
||||
self.table.itemChanged.connect(
|
||||
lambda: self.emit_plot_coordinates(x_pos, y_pos, coordinate_reference, color)
|
||||
)
|
||||
|
||||
@@ -322,7 +321,7 @@ class MotorCoordinateTable(MotorControlWidget):
|
||||
show_checkbox = QCheckBox()
|
||||
show_checkbox.setChecked(True)
|
||||
show_checkbox.stateChanged.connect(lambda: self.emit_plot_coordinates(x_pos, y_pos))
|
||||
self.ui.table.setCellWidget(row, checkBox_pos, show_checkbox)
|
||||
self.table.setCellWidget(row, checkBox_pos, show_checkbox)
|
||||
|
||||
def _add_move_button(self, row: int, button_pos: int, x_pos: int, y_pos: int) -> None:
|
||||
"""
|
||||
@@ -335,7 +334,7 @@ class MotorCoordinateTable(MotorControlWidget):
|
||||
"""
|
||||
move_button = QPushButton("Move")
|
||||
move_button.clicked.connect(lambda: self.handle_move_button_click(x_pos, y_pos))
|
||||
self.ui.table.setCellWidget(row, button_pos, move_button)
|
||||
self.table.setCellWidget(row, button_pos, move_button)
|
||||
|
||||
def _add_line_edit(
|
||||
self,
|
||||
@@ -368,7 +367,7 @@ class MotorCoordinateTable(MotorControlWidget):
|
||||
edit.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||
|
||||
# Add line edit to the table
|
||||
self.ui.table.setCellWidget(row, line_pos, edit)
|
||||
self.table.setCellWidget(row, line_pos, edit)
|
||||
edit.textChanged.connect(
|
||||
lambda: self.emit_plot_coordinates(x_pos, y_pos, coordinate_reference, color)
|
||||
)
|
||||
@@ -376,10 +375,10 @@ class MotorCoordinateTable(MotorControlWidget):
|
||||
def wipe_motor_map_coordinates(self):
|
||||
"""Wipe the motor map coordinates."""
|
||||
try:
|
||||
self.ui.table.itemChanged.disconnect() # Disconnect all previous connections
|
||||
self.table.itemChanged.disconnect() # Disconnect all previous connections
|
||||
except TypeError:
|
||||
print("No previous connections to disconnect")
|
||||
self.ui.table.setRowCount(0)
|
||||
self.table.setRowCount(0)
|
||||
reference_tags = ["Individual", "Start", "Stop"]
|
||||
for reference_tag in reference_tags:
|
||||
self.plot_coordinates_signal.emit([], reference_tag, "green")
|
||||
@@ -392,7 +391,7 @@ class MotorCoordinateTable(MotorControlWidget):
|
||||
y_pos(int): Y position of the coordinate.
|
||||
"""
|
||||
button = self.sender()
|
||||
row = self.ui.table.indexAt(button.pos()).row()
|
||||
row = self.table.indexAt(button.pos()).row()
|
||||
|
||||
x = self.get_coordinate(row, x_pos)
|
||||
y = self.get_coordinate(row, y_pos)
|
||||
@@ -411,8 +410,8 @@ class MotorCoordinateTable(MotorControlWidget):
|
||||
f"Emitting plot coordinates: x_pos={x_pos}, y_pos={y_pos}, reference_tag={reference_tag}, color={color}"
|
||||
)
|
||||
coordinates = []
|
||||
for row in range(self.ui.table.rowCount()):
|
||||
show = self.ui.table.cellWidget(row, 0).isChecked()
|
||||
for row in range(self.table.rowCount()):
|
||||
show = self.table.cellWidget(row, 0).isChecked()
|
||||
x = self.get_coordinate(row, x_pos)
|
||||
y = self.get_coordinate(row, y_pos)
|
||||
|
||||
@@ -428,27 +427,27 @@ class MotorCoordinateTable(MotorControlWidget):
|
||||
Returns:
|
||||
float: Value of the coordinate.
|
||||
"""
|
||||
edit = self.ui.table.cellWidget(row, column)
|
||||
edit = self.table.cellWidget(row, column)
|
||||
value = float(edit.text()) if edit and edit.text() != "" else None
|
||||
if value:
|
||||
return value
|
||||
|
||||
def delete_selected_row(self):
|
||||
"""Delete the selected row from the table."""
|
||||
selected_rows = self.ui.table.selectionModel().selectedRows()
|
||||
selected_rows = self.table.selectionModel().selectedRows()
|
||||
for row in selected_rows:
|
||||
self.ui.table.removeRow(row.row())
|
||||
if self.ui.comboBox_mode.currentText() == "Start/Stop":
|
||||
self.table.removeRow(row.row())
|
||||
if self.comboBox_mode.currentText() == "Start/Stop":
|
||||
self.emit_plot_coordinates(x_pos=4, y_pos=5, reference_tag="Start", color="blue")
|
||||
self.emit_plot_coordinates(x_pos=6, y_pos=7, reference_tag="Stop", color="red")
|
||||
self.is_next_entry_end = False
|
||||
elif self.ui.comboBox_mode.currentText() == "Individual":
|
||||
elif self.comboBox_mode.currentText() == "Individual":
|
||||
self.emit_plot_coordinates(x_pos=3, y_pos=4, reference_tag="Individual", color="green")
|
||||
|
||||
def resize_table_auto(self):
|
||||
"""Resize the table to fit the contents."""
|
||||
if self.ui.checkBox_resize_auto.isChecked():
|
||||
self.ui.table.resizeColumnsToContents()
|
||||
if self.checkBox_resize_auto.isChecked():
|
||||
self.table.resizeColumnsToContents()
|
||||
|
||||
def move_motor(self, x: float, y: float) -> None:
|
||||
"""
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
import os
|
||||
|
||||
from qtpy.QtWidgets import QWidget
|
||||
from qtpy import uic
|
||||
from qtpy.QtCore import Signal as pyqtSignal
|
||||
from qtpy.QtCore import Slot as pyqtSlot
|
||||
|
||||
from bec_widgets.utils import UILoader
|
||||
from bec_widgets.widgets.motor_control.motor_control import MotorControlWidget, MotorControlErrors
|
||||
from bec_widgets.widgets.motor_control.motor_control import MotorControlWidget
|
||||
|
||||
|
||||
class MotorControlAbsolute(MotorControlWidget):
|
||||
@@ -25,26 +23,26 @@ class MotorControlAbsolute(MotorControlWidget):
|
||||
def _load_ui(self):
|
||||
"""Load the UI from the .ui file."""
|
||||
current_path = os.path.dirname(__file__)
|
||||
self.ui = UILoader().load_ui(os.path.join(current_path, "movement_absolute.ui"), self)
|
||||
uic.loadUi(os.path.join(current_path, "movement_absolute.ui"), self)
|
||||
|
||||
def _init_ui(self):
|
||||
"""Initialize the UI."""
|
||||
|
||||
# Check if there are any motors connected
|
||||
if self.motor_x is None or self.motor_y is None:
|
||||
self.ui.motorControl_absolute.setEnabled(False)
|
||||
self.motorControl_absolute.setEnabled(False)
|
||||
return
|
||||
|
||||
# Move to absolute coordinates
|
||||
self.ui.pushButton_go_absolute.clicked.connect(
|
||||
self.pushButton_go_absolute.clicked.connect(
|
||||
lambda: self.move_motor_absolute(
|
||||
self.ui.spinBox_absolute_x.value(), self.ui.spinBox_absolute_y.value()
|
||||
self.spinBox_absolute_x.value(), self.spinBox_absolute_y.value()
|
||||
)
|
||||
)
|
||||
|
||||
self.ui.pushButton_set.clicked.connect(self.save_absolute_coordinates)
|
||||
self.ui.pushButton_save.clicked.connect(self.save_current_coordinates)
|
||||
self.ui.pushButton_stop.clicked.connect(self.motor_thread.stop_movement)
|
||||
self.pushButton_set.clicked.connect(self.save_absolute_coordinates)
|
||||
self.pushButton_save.clicked.connect(self.save_current_coordinates)
|
||||
self.pushButton_stop.clicked.connect(self.motor_thread.stop_movement)
|
||||
|
||||
# Enable/Disable GUI
|
||||
self.motor_thread.lock_gui.connect(self.enable_motor_controls)
|
||||
@@ -82,11 +80,11 @@ class MotorControlAbsolute(MotorControlWidget):
|
||||
"""
|
||||
|
||||
# Disable or enable all controls within the motorControl_absolute group box
|
||||
for widget in self.ui.motorControl_absolute.findChildren(QWidget):
|
||||
for widget in self.motorControl_absolute.findChildren(QWidget):
|
||||
widget.setEnabled(enable)
|
||||
|
||||
# Enable the pushButton_stop if the motor is moving
|
||||
self.ui.pushButton_stop.setEnabled(True)
|
||||
self.pushButton_stop.setEnabled(True)
|
||||
|
||||
@pyqtSlot(str, str)
|
||||
def change_motors(self, motor_x: str, motor_y: str):
|
||||
@@ -111,8 +109,8 @@ class MotorControlAbsolute(MotorControlWidget):
|
||||
"""
|
||||
self.precision = precision
|
||||
self.config["motor_control"]["precision"] = precision
|
||||
self.ui.spinBox_absolute_x.setDecimals(precision)
|
||||
self.ui.spinBox_absolute_y.setDecimals(precision)
|
||||
self.spinBox_absolute_x.setDecimals(precision)
|
||||
self.spinBox_absolute_y.setDecimals(precision)
|
||||
|
||||
def move_motor_absolute(self, x: float, y: float) -> None:
|
||||
"""
|
||||
@@ -124,32 +122,32 @@ class MotorControlAbsolute(MotorControlWidget):
|
||||
# self._enable_motor_controls(False)
|
||||
target_coordinates = (x, y)
|
||||
self.motor_thread.move_absolute(self.motor_x, self.motor_y, target_coordinates)
|
||||
if self.ui.checkBox_save_with_go.isChecked():
|
||||
if self.checkBox_save_with_go.isChecked():
|
||||
self.save_absolute_coordinates()
|
||||
|
||||
def _init_keyboard_shortcuts(self):
|
||||
"""Initialize the keyboard shortcuts."""
|
||||
# Go absolute button
|
||||
self.ui.pushButton_go_absolute.setShortcut("Ctrl+G")
|
||||
self.ui.pushButton_go_absolute.setToolTip("Ctrl+G")
|
||||
self.pushButton_go_absolute.setShortcut("Ctrl+G")
|
||||
self.pushButton_go_absolute.setToolTip("Ctrl+G")
|
||||
|
||||
# Set absolute coordinates
|
||||
self.ui.pushButton_set.setShortcut("Ctrl+D")
|
||||
self.ui.pushButton_set.setToolTip("Ctrl+D")
|
||||
self.pushButton_set.setShortcut("Ctrl+D")
|
||||
self.pushButton_set.setToolTip("Ctrl+D")
|
||||
|
||||
# Save Current coordinates
|
||||
self.ui.pushButton_save.setShortcut("Ctrl+S")
|
||||
self.ui.pushButton_save.setToolTip("Ctrl+S")
|
||||
self.pushButton_save.setShortcut("Ctrl+S")
|
||||
self.pushButton_save.setToolTip("Ctrl+S")
|
||||
|
||||
# Stop Button
|
||||
self.ui.pushButton_stop.setShortcut("Ctrl+X")
|
||||
self.ui.pushButton_stop.setToolTip("Ctrl+X")
|
||||
self.pushButton_stop.setShortcut("Ctrl+X")
|
||||
self.pushButton_stop.setToolTip("Ctrl+X")
|
||||
|
||||
def save_absolute_coordinates(self):
|
||||
"""Emit the setup coordinates from the spinboxes"""
|
||||
|
||||
x, y = round(self.ui.spinBox_absolute_x.value(), self.precision), round(
|
||||
self.ui.spinBox_absolute_y.value(), self.precision
|
||||
x, y = round(self.spinBox_absolute_x.value(), self.precision), round(
|
||||
self.spinBox_absolute_y.value(), self.precision
|
||||
)
|
||||
self.coordinates_signal.emit((x, y))
|
||||
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import qdarktheme
|
||||
from qtpy.QtWidgets import QApplication
|
||||
|
||||
from bec_widgets.utils.bec_dispatcher import BECDispatcher
|
||||
from bec_widgets.widgets.motor_control.selection.selection import MotorControlSelection
|
||||
|
||||
CONFIG_DEFAULT = {
|
||||
"motor_control": {
|
||||
"motor_x": "samx",
|
||||
"motor_y": "samy",
|
||||
"step_size_x": 3,
|
||||
"step_size_y": 5,
|
||||
"precision": 4,
|
||||
"step_x_y_same": False,
|
||||
"move_with_arrows": False,
|
||||
}
|
||||
}
|
||||
if __name__ == "__main__":
|
||||
bec_dispatcher = BECDispatcher()
|
||||
# BECclient global variables
|
||||
client = bec_dispatcher.client
|
||||
client.start()
|
||||
|
||||
app = QApplication([])
|
||||
qdarktheme.setup_theme("auto")
|
||||
motor_control = MotorControlSelection(client=client, config=CONFIG_DEFAULT)
|
||||
|
||||
window = motor_control
|
||||
window.show()
|
||||
app.exec()
|
||||
@@ -1,13 +0,0 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
from selection import MotorControlSelection
|
||||
from selectionplugin import MotorControlSelectionPlugin
|
||||
|
||||
from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
|
||||
|
||||
# Set PYSIDE_DESIGNER_PLUGINS to point to this directory and load the plugin
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
QPyDesignerCustomWidgetCollection.addCustomWidget(MotorControlSelectionPlugin())
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"files": ["selection.py", "motor_selection_launch.py", "registertictactoe.py", "tictactoeplugin.py",
|
||||
"tictactoetaskmenu.py"]
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
# Copyright (C) 2022 The Qt Company Ltd.
|
||||
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
|
||||
|
||||
from selection import MotorControlSelection
|
||||
|
||||
from PySide6.QtGui import QIcon
|
||||
from PySide6.QtDesigner import QDesignerCustomWidgetInterface
|
||||
|
||||
|
||||
DOM_XML = """
|
||||
<ui language='c++'>
|
||||
<widget class='MotorControlSelection' name='selection'>
|
||||
</widget>
|
||||
</ui>
|
||||
"""
|
||||
|
||||
|
||||
class MotorControlSelectionPlugin(QDesignerCustomWidgetInterface):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._form_editor = None
|
||||
|
||||
def createWidget(self, parent):
|
||||
t = MotorControlSelection(parent)
|
||||
return t
|
||||
|
||||
def domXml(self):
|
||||
return DOM_XML
|
||||
|
||||
def group(self):
|
||||
return ""
|
||||
|
||||
def icon(self):
|
||||
return QIcon()
|
||||
|
||||
def includeFile(self):
|
||||
return "selection"
|
||||
|
||||
def initialize(self, form_editor):
|
||||
self._form_editor = form_editor
|
||||
# manager = form_editor.extensionManager()
|
||||
# iid = TicTacToeTaskMenuFactory.task_menu_iid()
|
||||
# manager.registerExtensions(TicTacToeTaskMenuFactory(manager), iid)
|
||||
|
||||
def isContainer(self):
|
||||
return False
|
||||
|
||||
def isInitialized(self):
|
||||
return self._form_editor is not None
|
||||
|
||||
def name(self):
|
||||
return "MotorControlSelection"
|
||||
|
||||
def toolTip(self):
|
||||
return "MotorControl Selection Example for BEC Widgets"
|
||||
|
||||
def whatsThis(self):
|
||||
return self.toolTip()
|
||||
@@ -38,7 +38,6 @@ dev = [
|
||||
]
|
||||
pyqt5 = ["PyQt5>=5.9"]
|
||||
pyqt6 = ["PyQt6>=6.7"]
|
||||
pyside6 = ["PySide6>=6.7"]
|
||||
|
||||
[project.urls]
|
||||
"Bug Tracker" = "https://gitlab.psi.ch/bec/bec_widgets/issues"
|
||||
|
||||
@@ -420,11 +420,11 @@ def test_delete_selected_row(motor_coordinate_table):
|
||||
motor_coordinate_table.add_coordinate((3.0, 4.0))
|
||||
|
||||
# Select the row
|
||||
motor_coordinate_table.ui.table.selectRow(0)
|
||||
motor_coordinate_table.table.selectRow(0)
|
||||
|
||||
# Delete the selected row
|
||||
motor_coordinate_table.delete_selected_row()
|
||||
assert motor_coordinate_table.ui.table.rowCount() == 1
|
||||
assert motor_coordinate_table.table.rowCount() == 1
|
||||
|
||||
|
||||
def test_add_coordinate_and_table_update(motor_coordinate_table):
|
||||
@@ -433,24 +433,20 @@ def test_add_coordinate_and_table_update(motor_coordinate_table):
|
||||
|
||||
# Add coordinate in Individual mode
|
||||
motor_coordinate_table.add_coordinate((1.0, 2.0))
|
||||
assert motor_coordinate_table.ui.table.rowCount() == 1
|
||||
assert motor_coordinate_table.table.rowCount() == 1
|
||||
|
||||
# Check if the coordinates match
|
||||
x_item_individual = motor_coordinate_table.ui.table.cellWidget(
|
||||
0, 3
|
||||
) # Assuming X is in column 3
|
||||
y_item_individual = motor_coordinate_table.ui.table.cellWidget(
|
||||
0, 4
|
||||
) # Assuming Y is in column 4
|
||||
x_item_individual = motor_coordinate_table.table.cellWidget(0, 3) # Assuming X is in column 3
|
||||
y_item_individual = motor_coordinate_table.table.cellWidget(0, 4) # Assuming Y is in column 4
|
||||
assert float(x_item_individual.text()) == 1.0
|
||||
assert float(y_item_individual.text()) == 2.0
|
||||
|
||||
# Switch to Start/Stop and add coordinates
|
||||
motor_coordinate_table.ui.comboBox_mode.setCurrentIndex(1) # Switch mode
|
||||
motor_coordinate_table.comboBox_mode.setCurrentIndex(1) # Switch mode
|
||||
|
||||
motor_coordinate_table.add_coordinate((3.0, 4.0))
|
||||
motor_coordinate_table.add_coordinate((5.0, 6.0))
|
||||
assert motor_coordinate_table.ui.table.rowCount() == 1
|
||||
assert motor_coordinate_table.table.rowCount() == 1
|
||||
|
||||
|
||||
def test_plot_coordinates_signal(motor_coordinate_table):
|
||||
@@ -470,26 +466,26 @@ def test_plot_coordinates_signal(motor_coordinate_table):
|
||||
assert received
|
||||
|
||||
|
||||
# def test_move_motor_action(motor_coordinate_table,qtbot):#TODO enable again after table refactor
|
||||
# # Add a coordinate
|
||||
# motor_coordinate_table.add_coordinate((1.0, 2.0))
|
||||
#
|
||||
# # Mock the motor thread move_absolute function
|
||||
# motor_coordinate_table.motor_thread.move_absolute = MagicMock()
|
||||
#
|
||||
# # Trigger the move action
|
||||
# move_button = motor_coordinate_table.table.cellWidget(0, 1)
|
||||
# move_button.click()
|
||||
#
|
||||
# motor_coordinate_table.motor_thread.move_absolute.assert_called_with(
|
||||
# motor_coordinate_table.motor_x, motor_coordinate_table.motor_y, (1.0, 2.0)
|
||||
# )
|
||||
def test_move_motor_action(motor_coordinate_table):
|
||||
# Add a coordinate
|
||||
motor_coordinate_table.add_coordinate((1.0, 2.0))
|
||||
|
||||
# Mock the motor thread move_absolute function
|
||||
motor_coordinate_table.motor_thread.move_absolute = MagicMock()
|
||||
|
||||
# Trigger the move action
|
||||
move_button = motor_coordinate_table.table.cellWidget(0, 1)
|
||||
move_button.click()
|
||||
|
||||
motor_coordinate_table.motor_thread.move_absolute.assert_called_with(
|
||||
motor_coordinate_table.motor_x, motor_coordinate_table.motor_y, (1.0, 2.0)
|
||||
)
|
||||
|
||||
|
||||
def test_plot_coordinates_signal_individual(motor_coordinate_table, qtbot):
|
||||
motor_coordinate_table.warning_message = False
|
||||
motor_coordinate_table.set_precision(3)
|
||||
motor_coordinate_table.ui.comboBox_mode.setCurrentIndex(0)
|
||||
motor_coordinate_table.comboBox_mode.setCurrentIndex(0)
|
||||
|
||||
# This list will store the signals emitted during the test
|
||||
emitted_signals = []
|
||||
@@ -510,8 +506,8 @@ def test_plot_coordinates_signal_individual(motor_coordinate_table, qtbot):
|
||||
assert len(coordinates) > 0, "Coordinates list is empty."
|
||||
assert reference_tag == "Individual"
|
||||
assert color == "green"
|
||||
assert motor_coordinate_table.ui.table.cellWidget(0, 3).text() == "1.000"
|
||||
assert motor_coordinate_table.ui.table.cellWidget(0, 4).text() == "2.000"
|
||||
assert motor_coordinate_table.table.cellWidget(0, 3).text() == "1.000"
|
||||
assert motor_coordinate_table.table.cellWidget(0, 4).text() == "2.000"
|
||||
|
||||
|
||||
#######################################################
|
||||
|
||||
Reference in New Issue
Block a user