mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-06-15 17:40:57 +02:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2b22a7065c | |||
| 9146c5194e | |||
| 5fde0c6efc | |||
| 68903fc6ae |
@@ -1,6 +1,22 @@
|
||||
# CHANGELOG
|
||||
|
||||
|
||||
## v3.16.0 (2026-06-15)
|
||||
|
||||
### Features
|
||||
|
||||
- **screenshot**: Add option to upload to SciLog
|
||||
([`9146c51`](https://github.com/bec-project/bec_widgets/commit/9146c5194e0871b6f99a1b985a6d6b2e3fdd409c))
|
||||
|
||||
|
||||
## v3.15.1 (2026-06-13)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
- **curve_tree**: Update header labels to reflect device and signal columns
|
||||
([`68903fc`](https://github.com/bec-project/bec_widgets/commit/68903fc6ae2ffd3ac7b5394ba6cf9a4b2ce745e5))
|
||||
|
||||
|
||||
## v3.15.0 (2026-06-12)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@@ -304,6 +306,49 @@ class BECWidget(BECConnector):
|
||||
buf.close()
|
||||
return ba
|
||||
|
||||
@SafeSlot(popup_error=True)
|
||||
@rpc_timeout(None)
|
||||
def screenshot_to_scilog(self) -> None:
|
||||
"""
|
||||
Take a screenshot of the widget and send it to SciLog through BEC messaging services.
|
||||
"""
|
||||
if not isinstance(self, QWidget):
|
||||
raise RuntimeError("Cannot take screenshot of non-QWidget instance")
|
||||
|
||||
messaging = getattr(self.client, "messaging", None)
|
||||
if messaging is None:
|
||||
raise RuntimeError("BEC messaging services are not available on the current client.")
|
||||
|
||||
scilog = messaging.scilog
|
||||
if not getattr(scilog, "_enabled", False):
|
||||
# We currently don't expose a public method to check if SciLog is enabled,
|
||||
# so we play defensive and check for an internal _enabled attribute that
|
||||
# should be True when SciLog is enabled.
|
||||
raise RuntimeError(
|
||||
"SciLog is not enabled for the current client, cannot send screenshot."
|
||||
)
|
||||
|
||||
pixmap: QPixmap = self.grab()
|
||||
if pixmap.isNull():
|
||||
raise RuntimeError("Failed to capture screenshot.")
|
||||
|
||||
tmp_name: str | None = None
|
||||
|
||||
try:
|
||||
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp_file:
|
||||
tmp_name = tmp_file.name
|
||||
|
||||
if not pixmap.save(tmp_name, "PNG"):
|
||||
raise RuntimeError("Failed to save screenshot to a temporary file.")
|
||||
|
||||
msg = messaging.scilog.new()
|
||||
msg.add_attachment(tmp_name)
|
||||
msg.send()
|
||||
logger.info("Screenshot sent to SciLog")
|
||||
finally:
|
||||
if tmp_name and os.path.exists(tmp_name):
|
||||
os.unlink(tmp_name)
|
||||
|
||||
def attach(self):
|
||||
dock = WidgetHierarchy.find_ancestor(self, QtAds.CDockWidget)
|
||||
if dock is None:
|
||||
|
||||
@@ -32,6 +32,7 @@ from bec_widgets.utils.rpc_widget_handler import widget_handler
|
||||
from bec_widgets.utils.toolbars.actions import (
|
||||
ExpandableMenuAction,
|
||||
MaterialIconAction,
|
||||
SwitchableToolBarAction,
|
||||
WidgetAction,
|
||||
)
|
||||
from bec_widgets.utils.toolbars.bundles import ToolbarBundle
|
||||
@@ -470,10 +471,34 @@ class BECDockArea(DockAreaWidget):
|
||||
self._profile_management_enabled
|
||||
)
|
||||
self.toolbar.components.add_safe(
|
||||
"screenshot",
|
||||
"screenshot_save",
|
||||
MaterialIconAction(icon_name="photo_camera", tooltip="Take Screenshot", parent=self),
|
||||
)
|
||||
self.toolbar.components.get_action("screenshot").action.setVisible(
|
||||
self.toolbar.components.add_safe(
|
||||
"screenshot_to_scilog",
|
||||
MaterialIconAction(
|
||||
icon_name="add_a_photo", tooltip="Send Screenshot to SciLog", parent=self
|
||||
),
|
||||
)
|
||||
self.toolbar.components.add_safe(
|
||||
"screenshot",
|
||||
SwitchableToolBarAction(
|
||||
actions={
|
||||
"save": self.toolbar.components.get_action_reference("screenshot_save")(),
|
||||
"scilog": self.toolbar.components.get_action_reference(
|
||||
"screenshot_to_scilog"
|
||||
)(),
|
||||
},
|
||||
initial_action="save",
|
||||
tooltip="Screenshot Actions",
|
||||
checkable=False,
|
||||
parent=self.toolbar,
|
||||
),
|
||||
)
|
||||
self.toolbar.components.get_action("screenshot_save").action.setVisible(
|
||||
self._profile_management_enabled
|
||||
)
|
||||
self.toolbar.components.get_action("screenshot_to_scilog").action.setVisible(
|
||||
self._profile_management_enabled
|
||||
)
|
||||
dark_mode_action = WidgetAction(
|
||||
@@ -542,7 +567,12 @@ class BECDockArea(DockAreaWidget):
|
||||
_connect_flat_actions(self._ACTION_MAPPINGS["menu_utils"])
|
||||
|
||||
self.toolbar.components.get_action("attach_all").action.triggered.connect(self.attach_all)
|
||||
self.toolbar.components.get_action("screenshot").action.triggered.connect(self.screenshot)
|
||||
self.toolbar.components.get_action("screenshot_save").action.triggered.connect(
|
||||
self.screenshot
|
||||
)
|
||||
self.toolbar.components.get_action("screenshot_to_scilog").action.triggered.connect(
|
||||
self.screenshot_to_scilog
|
||||
)
|
||||
|
||||
def _new_plugin_widget(self, widget_type: str, toolbar_action: MaterialIconAction) -> None:
|
||||
# Created as helper method for simple tests
|
||||
|
||||
@@ -552,7 +552,7 @@ class CurveTree(BECWidget, QWidget):
|
||||
self.tree = QTreeWidget()
|
||||
self.tree.setColumnCount(8)
|
||||
self.tree.setHeaderLabels(
|
||||
["Actions", "Name", "Entry", "Scan #", "Color", "Style", "Width", "Symbol"]
|
||||
["Actions", "Device", "Signal", "Scan #", "Color", "Style", "Width", "Symbol"]
|
||||
)
|
||||
|
||||
header = self.tree.header()
|
||||
|
||||
+3
-1
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "bec_widgets"
|
||||
version = "3.15.0"
|
||||
version = "3.16.0"
|
||||
description = "BEC Widgets"
|
||||
requires-python = ">=3.11"
|
||||
classifiers = [
|
||||
@@ -76,6 +76,8 @@ qtermwidget = ["pyside6_qtermwidget"]
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
@@ -8,7 +8,7 @@ from unittest.mock import MagicMock, patch
|
||||
import pytest
|
||||
from qtpy.QtCore import QSettings, Qt, QTimer
|
||||
from qtpy.QtGui import QPixmap
|
||||
from qtpy.QtWidgets import QDialog, QMessageBox, QWidget
|
||||
from qtpy.QtWidgets import QDialog, QMessageBox, QToolButton, QWidget
|
||||
|
||||
import bec_widgets.widgets.containers.dock_area.basic_dock_area as basic_dock_module
|
||||
import bec_widgets.widgets.containers.dock_area.dock_area as dock_area_module
|
||||
@@ -987,9 +987,9 @@ class TestToolbarFunctionality:
|
||||
mock_screenshot = mock.MagicMock()
|
||||
mock_grab.return_value = mock_screenshot
|
||||
|
||||
# Trigger the screenshot action
|
||||
action = advanced_dock_area.toolbar.components.get_action("screenshot").action
|
||||
action.trigger()
|
||||
# Trigger the screenshot main button
|
||||
action = advanced_dock_area.toolbar.components.get_action("screenshot")
|
||||
action.main_button.click()
|
||||
|
||||
# Verify the dialog was called
|
||||
mock_dialog.assert_called_once()
|
||||
@@ -1000,6 +1000,41 @@ class TestToolbarFunctionality:
|
||||
# Verify save was called with the filename
|
||||
mock_screenshot.save.assert_called_once_with(str(screenshot_path))
|
||||
|
||||
def test_screenshot_button_has_scilog_dropdown(self, advanced_dock_area):
|
||||
"""Test screenshot toolbar button exposes a SciLog dropdown option."""
|
||||
action = advanced_dock_area.toolbar.components.get_action("screenshot")
|
||||
button = action.main_button
|
||||
|
||||
assert isinstance(button, QToolButton)
|
||||
assert button.popupMode() == QToolButton.ToolButtonPopupMode.MenuButtonPopup
|
||||
assert button.menu() is not None
|
||||
assert [menu_action.text() for menu_action in button.menu().actions()] == [
|
||||
"Take Screenshot",
|
||||
"Send Screenshot to SciLog",
|
||||
]
|
||||
|
||||
def test_screenshot_to_scilog_action(self, advanced_dock_area):
|
||||
"""Test sending a screenshot through the BEC SciLog messaging service."""
|
||||
mock_message = mock.MagicMock()
|
||||
mock_message.add_attachment.return_value = mock_message
|
||||
advanced_dock_area.client.messaging.scilog.new = mock.MagicMock(return_value=mock_message)
|
||||
advanced_dock_area.client.messaging.scilog._enabled = True
|
||||
|
||||
with mock.patch.object(advanced_dock_area, "grab") as mock_grab:
|
||||
mock_screenshot = mock.MagicMock()
|
||||
mock_screenshot.isNull.return_value = False
|
||||
mock_screenshot.save.return_value = True
|
||||
mock_grab.return_value = mock_screenshot
|
||||
|
||||
action = advanced_dock_area.toolbar.components.get_action("screenshot")
|
||||
assert action.main_button is not None
|
||||
scilog_menu_action = action.main_button.menu().actions()[1]
|
||||
scilog_menu_action.trigger()
|
||||
|
||||
advanced_dock_area.client.messaging.scilog.new.assert_called_once_with()
|
||||
mock_message.add_attachment.assert_called_once()
|
||||
mock_message.send.assert_called_once_with()
|
||||
|
||||
def test_plugin_toolbar_actions_empty_when_no_plugins(self, clear_plugin_toolbar_actions_cache):
|
||||
"""Test that no plugin toolbar actions are produced when no plugin widgets exist."""
|
||||
with patch(
|
||||
|
||||
Reference in New Issue
Block a user