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:
@@ -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()
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
BIN
bec_widgets/examples/jupyter_console/terminal_icon.png
Normal file
BIN
bec_widgets/examples/jupyter_console/terminal_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.8 KiB |
@@ -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
|
||||
|
||||
1
bec_widgets/widgets/dock/__init__.py
Normal file
1
bec_widgets/widgets/dock/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .dock_area import BECDockArea, BECDock
|
||||
173
bec_widgets/widgets/dock/dock_area.py
Normal file
173
bec_widgets/widgets/dock/dock_area.py
Normal 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])
|
||||
@@ -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)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user