Compare commits

...

1 Commits

Author SHA1 Message Date
wakonig_k d4ff0bfac4 feat(screenshot): add option to upload to SciLog 2026-06-13 12:42:48 +02:00
3 changed files with 117 additions and 7 deletions
+45
View File
@@ -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, width="80%")
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
+39 -4
View File
@@ -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(