fix(ide-explorer): add tooltips for scripts and macros sections with help popups

This commit is contained in:
2026-07-14 16:59:40 +02:00
committed by wakonig_k
parent cfa38e549f
commit e4159b9787
3 changed files with 105 additions and 5 deletions
@@ -3,9 +3,18 @@ from __future__ import annotations
from bec_qthemes import material_icon
from qtpy.QtCore import QMimeData, Qt, Signal
from qtpy.QtGui import QDrag
from qtpy.QtWidgets import QHBoxLayout, QPushButton, QSizePolicy, QToolButton, QVBoxLayout, QWidget
from qtpy.QtWidgets import (
QHBoxLayout,
QLabel,
QPushButton,
QSizePolicy,
QToolButton,
QVBoxLayout,
QWidget,
)
from bec_widgets.utils.error_popups import SafeProperty
from bec_widgets.widgets.containers.main_window.addons.hover_widget import WidgetTooltip
class CollapsibleSection(QWidget):
@@ -34,6 +43,9 @@ class CollapsibleSection(QWidget):
super().__init__(parent=parent)
self.title = title
self.content_widget = None
self._help_text = tooltip
self._help_tooltip: WidgetTooltip | None = None
self.destroyed.connect(self._cleanup_help_tooltip)
self.setAcceptDrops(True)
self._expanded = True
@@ -58,13 +70,24 @@ class CollapsibleSection(QWidget):
self.header_button.mouseMoveEvent = self._header_mouse_move_event
self.header_button.dragEnterEvent = self._header_drag_enter_event
self.header_button.dropEvent = self._header_drop_event
if tooltip:
self.header_button.setToolTip(tooltip)
self.drag_start_position = None
# Add header to layout
header_layout.addWidget(self.header_button)
self.header_info_button = QToolButton()
self.header_info_button.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed)
self.header_info_button.setFixedSize(20, 20)
self.header_info_button.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
self.header_info_button.setAutoRaise(True)
self.header_info_button.setEnabled(bool(self._help_text))
self.header_info_button.setVisible(bool(self._help_text))
self.header_info_button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
self.header_info_button.setIcon(material_icon("info", convert_to_pixmap=False))
if self._help_text:
self.header_info_button.setToolTip("Show help")
self.header_info_button.clicked.connect(self._toggle_help_tooltip)
header_layout.addWidget(self.header_info_button)
header_layout.addStretch()
# Add button in header (icon-only)
@@ -146,6 +169,37 @@ class CollapsibleSection(QWidget):
"""
self.header_add_button.clicked.connect(slot)
def closeEvent(self, event):
self._cleanup_help_tooltip()
super().closeEvent(event)
def _toggle_help_tooltip(self):
"""Show or hide the section help popover near the info button."""
if not self._help_text:
return
if self._help_tooltip is not None and self._help_tooltip.isVisible():
self._help_tooltip.hide()
return
if self._help_tooltip is None:
help_label = QLabel(self._help_text)
help_label.setWordWrap(True)
help_label.setMinimumWidth(220)
help_label.setMaximumWidth(320)
self._help_tooltip = WidgetTooltip(help_label)
anchor = self.header_info_button.mapToGlobal(self.header_info_button.rect().center())
self._help_tooltip.show_near(anchor)
def _cleanup_help_tooltip(self, *_args):
"""Close and release any help popup owned by this section."""
if self._help_tooltip is None:
return
self._help_tooltip.close()
self._help_tooltip.deleteLater()
self._help_tooltip = None
def _header_mouse_press_event(self, event):
"""Handle mouse press on header for drag start"""
if event.button() == Qt.MouseButton.LeftButton:
@@ -78,7 +78,15 @@ class IDEExplorer(BECWidget, QWidget):
self._remove_section(section)
def add_script_section(self):
section = CollapsibleSection(parent=self, title="SCRIPTS", indentation=0)
section = CollapsibleSection(
parent=self,
title="SCRIPTS",
indentation=0,
tooltip="Scripts are executable Python files for scan workflows, "
"automation, and quick experiments.\n"
"In contrast to macros, scripts can contain executable code outside of function definitions.\n"
"Scripts can be run directly from the IDE by clicking the 'Run' button in the toolbar.",
)
script_explorer = Explorer(parent=self)
local_script_dir = os.path.expanduser(
@@ -120,7 +128,10 @@ class IDEExplorer(BECWidget, QWidget):
title="MACROS",
indentation=0,
show_add_button=True,
tooltip="Macros are reusable functions that can be called from scripts or the console.",
tooltip="Macros are reusable Python functions that can be called from scripts or the console.\n"
"All macros are automatically loaded and available for use. As a result,\n"
"macros must not have executable code outside of function definitions to\n"
"avoid unintended execution when imported.",
)
section.header_add_button.setIcon(
material_icon("refresh", size=(20, 20), convert_to_pixmap=False)
+35
View File
@@ -24,6 +24,41 @@ def test_ide_explorer_initialization(ide_explorer):
assert ide_explorer.main_explorer.sections[0].title == "SCRIPTS"
def test_script_section_has_info_tooltip(ide_explorer):
"""Scripts section exposes click-to-open help on the info icon."""
scripts_section = ide_explorer.main_explorer.get_section("SCRIPTS")
assert not scripts_section.header_button.toolTip()
assert not scripts_section.header_info_button.isHidden()
assert scripts_section.header_info_button.isEnabled()
assert scripts_section.header_info_button.toolTip() == "Show help"
def test_macro_section_has_info_tooltip(ide_explorer):
"""Macros section exposes click-to-open help on the info icon."""
macros_section = ide_explorer.main_explorer.get_section("MACROS")
assert not macros_section.header_button.toolTip()
assert not macros_section.header_info_button.isHidden()
assert macros_section.header_info_button.isEnabled()
assert macros_section.header_info_button.toolTip() == "Show help"
def test_script_section_info_button_opens_help_popup(ide_explorer, qtbot):
"""Clicking the info button should show the styled help popup."""
scripts_section = ide_explorer.main_explorer.get_section("SCRIPTS")
scripts_section.header_info_button.click()
qtbot.waitUntil(
lambda: scripts_section._help_tooltip is not None
and scripts_section._help_tooltip.isVisible(),
timeout=1000,
)
assert scripts_section._help_tooltip.content.text().startswith("Scripts are executable")
scripts_section._cleanup_help_tooltip()
def test_ide_explorer_add_local_script(ide_explorer, qtbot, tmpdir):
local_script_section = ide_explorer.main_explorer.get_section(
"SCRIPTS"