1
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-12-31 19:11:18 +01:00

feat(widgets/dock): BECDockArea as a container for BECDocks

This commit is contained in:
2024-04-16 15:27:06 +02:00
parent 77ff7962cc
commit b51e1dc98a
7 changed files with 236 additions and 7 deletions

View File

@@ -2,13 +2,16 @@ import os
import numpy as np
import pyqtgraph as pg
from pyqtgraph.Qt import uic
from PyQt6.QtCore import QSize
from PyQt6.QtGui import QIcon
from PyQt6.QtWidgets import QMainWindow
from pyqtgraph.Qt import uic, QtWidgets
from qtconsole.inprocess import QtInProcessKernelManager
from qtconsole.rich_jupyter_widget import RichJupyterWidget
from qtpy.QtWidgets import QApplication, QVBoxLayout, QWidget
from bec_widgets.utils import BECDispatcher
from bec_widgets.widgets import BECFigure
from bec_widgets.widgets import BECFigure, BECDockArea
class JupyterConsoleWidget(RichJupyterWidget): # pragma: no cover:
@@ -47,9 +50,14 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
self.console.kernel_manager.kernel.shell.push(
{
"fig": self.figure,
"dock": self.dock,
"w1": self.w1,
"w2": self.w2,
"w3": self.w3,
"d1": self.d1,
"d2": self.d2,
"d3": self.d3,
"label_2": self.label_2,
"bec": self.figure.client,
"scans": self.figure.client.scans,
"dev": self.figure.client.device_manager.devices,
@@ -62,9 +70,16 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
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.dock_placeholder)
self.dock = BECDockArea(gui_id="remote")
self.dock_layout.addWidget(self.dock)
# add stuff to figure
self._init_figure()
# init dock for testing
self._init_dock()
self.console_layout = QVBoxLayout(self.widget_console)
self.console = JupyterConsoleWidget()
self.console_layout.addWidget(self.console)
@@ -86,6 +101,19 @@ class JupyterConsoleWindow(QWidget): # pragma: no cover:
self.w1.add_curve_scan("samx", "samy", "bpm3a", pen_style="dash")
self.c1 = self.w1.get_config()
def _init_dock(self):
self.button_1 = QtWidgets.QPushButton("Button 1 ")
self.label_1 = QtWidgets.QLabel("some scan info label with useful information")
self.label_2 = QtWidgets.QLabel("label which is added separately")
self.d1 = self.dock.add_dock(widget=self.button_1, position="left")
self.d2 = self.dock.add_dock(widget=self.label_1, position="right")
self.d3 = self.dock.plot(x_name="samx", y_name="bpm4d")
self.d4 = self.dock.image(monitor="eiger")
self.d4.set_vrange(0, 100)
if __name__ == "__main__": # pragma: no cover
import sys
@@ -96,6 +124,10 @@ if __name__ == "__main__": # pragma: no cover
app = QApplication(sys.argv)
app.setApplicationName("Jupyter Console")
app.setApplicationDisplayName("Jupyter Console")
icon = QIcon()
icon.addFile("terminal_icon.png", size=QSize(48, 48))
app.setWindowIcon(icon)
win = JupyterConsoleWindow()
win.show()

View File

@@ -13,13 +13,37 @@
<property name="windowTitle">
<string>Plotting Console</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QWidget" name="glw" native="true"/>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab_1">
<attribute name="title">
<string>BECDock</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QWidget" name="dock_placeholder" native="true"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>BECFigure</string>
</attribute>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QWidget" name="glw" native="true"/>
</item>
</layout>
</widget>
</widget>
<widget class="QWidget" name="widget_console" native="true"/>
</widget>
</item>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@@ -11,3 +11,4 @@ from .motor_control import (
from .motor_map import MotorMap
from .plots import BECCurve, BECMotorMap, BECWaveform
from .scan_control import ScanControl
from .dock import BECDockArea, BECDock

View File

@@ -0,0 +1 @@
from .dock_area import BECDockArea, BECDock

View File

@@ -0,0 +1,173 @@
import itertools
import warnings
from collections import defaultdict
from typing import Optional, Literal
import pyqtgraph as pg
from PyQt6.QtWidgets import QWidget
from pydantic import Field
from pyqtgraph import QtWidgets
from pyqtgraph.dockarea.DockArea import DockArea, Dock
from bec_widgets.utils import BECConnector, ConnectionConfig
from bec_widgets.widgets import BECWaveform, BECFigure, BECMotorMap
from bec_widgets.widgets.plots import BECImageShow
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 DockAreaConfig(ConnectionConfig):
docks: dict[str, DockConfig] = Field({}, description="The docks in the dock area.")
class BECDock(BECConnector, Dock):
def __init__(
self,
parent: Optional[QWidget] = None,
parent_dock_area: Optional["BECDockArea"] = None,
config: Optional[
DockConfig
] = None, # TODO ATM connection config -> will be changed when I will know what I want to use there
name: Optional[str] = None,
client=None,
gui_id: Optional[str] = None,
**kwargs,
) -> 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)
self.config = config
super().__init__(client=client, config=config, gui_id=gui_id)
Dock.__init__(self, name=name, **kwargs)
self.parent_dock_area = parent_dock_area
self.sigClosed.connect(self._remove_from_dock_area) # TODO test if it works
def _remove_from_dock_area(self):
"""Remove this dock from the DockArea it lives inside."""
self.parent_dock_area.docks.pop(self.name()) # TODO test if works
# def close(self):
# """Remove this dock from the DockArea it lives inside."""
# if self._container is None:
# warnings.warn(
# f"Cannot close dock {self} because it is not open.", RuntimeWarning, stacklevel=2
# )
# return
#
# self.setParent(None)
# QtWidgets.QLabel.close(self.label)
# self.label.setParent(None)
# self._container.apoptose()
# self._container = None
# self.sigClosed.emit(self)
# # TODO add remove from dict from DockArea
class BECDockArea(BECConnector, DockArea):
USER_ACCESS = []
def __init__(
self,
parent: Optional[QWidget] = None,
config: Optional[DockAreaConfig] = None,
client=None,
gui_id: Optional[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)
DockArea.__init__(self, parent=parent)
self._docks = defaultdict(dict) # TODO check how is the pyqtgraph .docks implemented
self._last_state = None # TOOD not sure if this will ever work
def figure(self, name: str = None) -> BECFigure:
figure = BECFigure(gui_id="remote")
self.add_dock(name=name, widget=figure, prefix="figure")
return figure
def plot(
self,
x_name: str = None,
y_name: str = None,
name: str = None,
) -> BECWaveform:
figure = BECFigure(gui_id="remote")
self.add_dock(name=name, widget=figure, prefix="plot")
plot = figure.plot(x_name, y_name)
return plot
def image(self, monitor: str = "eiger", name: str = None) -> BECImageShow:
figure = BECFigure(gui_id="remote")
self.add_dock(name=name, widget=figure, prefix="image")
image = figure.image(monitor)
return image
def motor_map(self, x_name: str = None, y_name: str = None, name: str = None) -> BECMotorMap:
figure = BECFigure(gui_id="remote")
self.add_dock(name=name, widget=figure, prefix="motor_map")
motor_map = figure.motor_map(x_name, y_name)
return motor_map
def add_dock(
self,
name: str = None,
widget: QWidget = None,
position: Literal["bottom", "top", "left", "right", "above", "below"] = None,
relative_to: Optional[BECDock] = None,
prefix: str = "dock",
) -> BECDock:
if name is None:
name = self._generate_unique_dock_id(prefix)
if name in set(self.docks.keys()):
raise ValueError(f"Dock with name {name} already exists.")
if position is None:
position = "bottom"
dock = BECDock(name=name, parent_dock_area=self, closable=True)
dock.config.position = position
self.config.docks[name] = dock.config
self.addDock(dock, position)
if widget is not None:
dock.addWidget(widget)
return dock
def _generate_unique_dock_id(
self, prefix: str = "widget"
): # TODO can be taken directly from BECFigure or made some mixin from it
"""Generate a unique dock id."""
existing_ids = set(self.docks.keys())
for i in itertools.count(1):
dock_id = f"{prefix}_{i}"
if dock_id not in existing_ids:
return dock_id
def _remove_dock_by_id(self, dock_id: str):
...
# TODO implement
# self.removeDock(self.docks[dock_id])

View File

@@ -358,7 +358,7 @@ class BECImageShow(BECPlotBase):
thread.start()
def find_widget_by_id(self, item_id: str) -> BECImageItem:
def find_widget_by_id(self, item_id: str) -> BECImageItem: # todo can be done in mixin
"""
Find the widget by its gui_id.
Args:
@@ -719,10 +719,8 @@ class BECImageShow(BECPlotBase):
processing_config = image_to_update.config.processing
self.processor.set_config(processing_config)
if self.use_threading:
print("using threaded version")
self._create_thread_worker(device, data)
else:
print("using NON-threaded version")
data = self.processor.process_image(data)
self.update_image(device, data)