diff --git a/bec_widgets/examples/jupyter_console/jupyter_console_window.py b/bec_widgets/examples/jupyter_console/jupyter_console_window.py
index 0d080f61..c64c1e0c 100644
--- a/bec_widgets/examples/jupyter_console/jupyter_console_window.py
+++ b/bec_widgets/examples/jupyter_console/jupyter_console_window.py
@@ -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()
diff --git a/bec_widgets/examples/jupyter_console/jupyter_console_window.ui b/bec_widgets/examples/jupyter_console/jupyter_console_window.ui
index f0098b09..6abdf3b1 100644
--- a/bec_widgets/examples/jupyter_console/jupyter_console_window.ui
+++ b/bec_widgets/examples/jupyter_console/jupyter_console_window.ui
@@ -13,13 +13,37 @@
Plotting Console
-
+
-
Qt::Horizontal
-
+
+
+ 0
+
+
+
+ BECDock
+
+
+
-
+
+
+
+
+
+
+ BECFigure
+
+
+ -
+
+
+
+
+
diff --git a/bec_widgets/examples/jupyter_console/terminal_icon.png b/bec_widgets/examples/jupyter_console/terminal_icon.png
new file mode 100644
index 00000000..79e48272
Binary files /dev/null and b/bec_widgets/examples/jupyter_console/terminal_icon.png differ
diff --git a/bec_widgets/widgets/__init__.py b/bec_widgets/widgets/__init__.py
index e66c1f8c..ff683d61 100644
--- a/bec_widgets/widgets/__init__.py
+++ b/bec_widgets/widgets/__init__.py
@@ -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
diff --git a/bec_widgets/widgets/dock/__init__.py b/bec_widgets/widgets/dock/__init__.py
new file mode 100644
index 00000000..0579eee6
--- /dev/null
+++ b/bec_widgets/widgets/dock/__init__.py
@@ -0,0 +1 @@
+from .dock_area import BECDockArea, BECDock
diff --git a/bec_widgets/widgets/dock/dock_area.py b/bec_widgets/widgets/dock/dock_area.py
new file mode 100644
index 00000000..6b25edb0
--- /dev/null
+++ b/bec_widgets/widgets/dock/dock_area.py
@@ -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])
diff --git a/bec_widgets/widgets/plots/image.py b/bec_widgets/widgets/plots/image.py
index 6b39797e..c6b2a40c 100644
--- a/bec_widgets/widgets/plots/image.py
+++ b/bec_widgets/widgets/plots/image.py
@@ -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)