UI modules

Main window widget — assembles the tabbed layout.

class stoner_measurement.ui.main_window.MainWindow(plugin_manager: PluginManager, parent: QWidget | None = None)[source]

Bases: QWidget

Central widget that provides the tabbed layout.

Contains two tabs:

  • Measurement — the three-panel layout (DockPanel | PlotWidget | ConfigPanel).

  • Script Editor — a Python editor and interactive console.

Layout of the Measurement tab (left → right):

  • DockPanel — 25 % of width, instrument / sequence control.

  • PlotWidget — 50 % of width, PyQtGraph plotting area.

  • ConfigPanel — 25 % of width, tabbed configuration.

Args:
plugin_manager (PluginManager):

Shared plugin manager instance.

Keyword Parameters:
parent (QWidget | None):

Optional parent widget.

Attributes:

dock_panel (DockPanel): Left panel of the Measurement tab. plot_widget (PlotWidget): Central plot in the Measurement tab. config_panel (ConfigPanel): Right configuration panel. script_tab (ScriptTab): The Script Editor tab widget.

property config_panel: ConfigPanel

Right configuration panel (Measurement tab).

Returns:
(ConfigPanel):

The configuration panel widget.

property dock_panel: DockPanel

Left dock panel (Measurement tab).

Returns:
(DockPanel):

The dock panel widget.

property plot_widget: PlotWidget

Central plot widget (Measurement tab).

Returns:
(PlotWidget):

The plot widget.

resizeEvent(event) None[source]

Maintain 25 / 50 / 25 proportions when the window is resized.

property script_tab: ScriptTab

The Script Editor tab widget.

Returns:
(ScriptTab):

The script tab widget.

property tabs: QTabWidget

The top-level tab widget containing all tabs.

Returns:
(QTabWidget):

The tab widget.

Dock panel — left 25 % of the main window.

Provides plugin listing, sequence building controls, a run button, and a monitoring section where plugins can display live status widgets. The sequence list is a tree widget that supports drag-and-drop reordering and arbitrarily deep sub-sequence nesting for SequencePlugin items (including StateControlPlugin), enabling multi-dimensional measurement scans. Plugins may also be dragged directly from the Available sequence commands list into the sequence tree to insert new steps at any position or into a sub-sequence.

class stoner_measurement.ui.dock_panel.DockPanel(plugin_manager: PluginManager, parent: QWidget | None = None)[source]

Bases: QWidget

Left panel containing instrument, sequence controls, and monitoring widgets.

Plugins may contribute a live-status widget via monitor_widget(). Those widgets are displayed in a dedicated Monitoring section at the bottom of this panel and are removed automatically when the plugin is unregistered.

When the user clicks a step in the Sequence Steps tree, the plugin_selected signal is emitted with the corresponding plugin instance so that the configuration panel can update itself accordingly.

The sequence tree supports drag-and-drop:

  • Drag a step above or below another to reorder.

  • Drag any step onto a SequencePlugin item (e.g. a StateControlPlugin) to nest it as a sub-step (shown with indentation). This includes dragging one sequence-plugin step onto another, enabling multi-dimensional measurement scans at arbitrary depth.

  • Drag a sub-step to the top level to promote it.

  • Drag a plugin from the Available sequence commands list and drop it above, below, or onto any existing step (or onto the empty area below all steps) to insert a brand-new step instance at that position. Dropping onto a SequencePlugin item adds the new step as the last child of that sub-sequence.

Attributes:
sequence_steps (list[_SequenceStep]):

The current sequence steps as a recursive structure. Each element is either a plugin instance (for a leaf step with no sub-steps) or a (plugin_instance, [sub-steps…]) tuple for a SequencePlugin that has nested children. The inner list follows the same _SequenceStep structure, so nesting may be arbitrarily deep.

Args:
plugin_manager (PluginManager):

The application PluginManager instance — used to populate the available-plugins list and to manage monitoring widgets.

Keyword Parameters:
parent (QWidget | None):

Optional Qt parent widget.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> from stoner_measurement.core.plugin_manager import PluginManager
>>> pm = PluginManager()
>>> panel = DockPanel(plugin_manager=pm)
>>> panel.sequence_steps
[]
add_monitor_widget(plugin_name: str, widget: QWidget) None[source]

Add a monitoring widget for the named plugin.

If a monitoring widget for plugin_name is already present this call is a no-op.

Args:
plugin_name (str):

Unique identifier for the owning plugin.

widget (QWidget):

The widget to display in the monitoring section.

Examples:
>>> from PyQt6.QtWidgets import QApplication, QLabel
>>> _ = QApplication.instance() or QApplication([])
>>> from stoner_measurement.core.plugin_manager import PluginManager
>>> pm = PluginManager()
>>> panel = DockPanel(plugin_manager=pm)
>>> panel.add_monitor_widget("test", QLabel("Status: OK"))
>>> "test" in panel.monitor_widgets
True
copy_selected_step() bool[source]

Copy the currently selected sequence step(s) to the internal clipboard.

All selected steps (including any sub-steps) are serialised to JSON and stored in _clipboard_step_json. When a selected item’s parent is also selected the child is omitted — its data is already captured inside the parent’s serialised sub-steps. Returns False when nothing is selected.

Returns:
(bool):

True if at least one step was copied, False if nothing was selected.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> from stoner_measurement.core.plugin_manager import PluginManager
>>> from stoner_measurement.plugins.trace import DummyPlugin
>>> pm = PluginManager()
>>> panel = DockPanel(plugin_manager=pm)
>>> panel.load_sequence([DummyPlugin()])
>>> panel._sequence_tree.setCurrentItem(panel._sequence_tree.topLevelItem(0))
>>> panel.copy_selected_step()
True
>>> panel.has_clipboard_step
True
cut_selected_step() bool[source]

Cut the currently selected sequence step(s) to the internal clipboard.

Equivalent to copy_selected_step() followed by removing all selected steps from the tree. Returns False when nothing is selected.

Returns:
(bool):

True if at least one step was cut, False if nothing was selected.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> from stoner_measurement.core.plugin_manager import PluginManager
>>> from stoner_measurement.plugins.trace import DummyPlugin
>>> pm = PluginManager()
>>> panel = DockPanel(plugin_manager=pm)
>>> panel.load_sequence([DummyPlugin()])
>>> panel._sequence_tree.setCurrentItem(panel._sequence_tree.topLevelItem(0))
>>> panel.cut_selected_step()
True
>>> panel._sequence_tree.topLevelItemCount()
0
disabled_action_label() str[source]

Return the appropriate label for the disable/enable toggle action.

Inspects the currently selected sequence steps and returns "Enable Plugin(s)" when all selected plugins are disabled, or "Disable Plugin(s)" otherwise (including when the selection is empty). The plural suffix is omitted when exactly one item is selected.

Returns:
(str):

The action label to display in the context menu or Edit menu.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> from stoner_measurement.core.plugin_manager import PluginManager
>>> pm = PluginManager()
>>> panel = DockPanel(plugin_manager=pm)
>>> panel.disabled_action_label()
'Disable Plugin'
property has_clipboard_step: bool

True when the internal sequence-step clipboard contains data.

Returns:
(bool):

Whether there is a copied/cut step ready to paste.

load_sequence(steps: list[_SequenceStep]) None[source]

Replace the current sequence tree with steps.

All existing steps are removed and their plugin references released before the new steps are inserted. Each plugin is added to _step_plugins so that it is not garbage-collected, and its instance_name_changed signal (if present) is wired to the tree’s label-update handler.

This method is the programmatic counterpart of the interactive drag-and-drop sequence builder. Use it when loading a sequence from a file (see stoner_measurement.core.serializer).

Args:
steps (list[_SequenceStep]):

Sequence steps in the same nested format returned by sequence_steps.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> from stoner_measurement.core.plugin_manager import PluginManager
>>> from stoner_measurement.plugins.trace import DummyPlugin
>>> pm = PluginManager()
>>> panel = DockPanel(plugin_manager=pm)
>>> plugin = DummyPlugin()
>>> panel.load_sequence([plugin])
>>> len(panel.sequence_steps)
1
property monitor_widgets: dict[str, QWidget]

Mapping of plugin name → currently displayed monitoring widget.

paste_step() bool[source]

Paste step(s) from the internal clipboard into the sequence tree.

All steps in the clipboard are inserted immediately after the current item (at the same level of nesting), preserving their original order. When nothing is selected every step is appended at the top level. Instance names are adjusted using _compute_paste_name() to avoid collisions with existing names. All newly inserted items are selected after the paste.

Returns:
(bool):

True if at least one step was pasted, False when the clipboard is empty.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> from stoner_measurement.core.plugin_manager import PluginManager
>>> from stoner_measurement.plugins.trace import DummyPlugin
>>> pm = PluginManager()
>>> panel = DockPanel(plugin_manager=pm)
>>> panel.load_sequence([DummyPlugin()])
>>> panel._sequence_tree.setCurrentItem(panel._sequence_tree.topLevelItem(0))
>>> panel.copy_selected_step()
True
>>> panel.paste_step()
True
>>> panel._sequence_tree.topLevelItemCount()
2
plugin_selected

Emitted with the plugin instance when exactly one sequence step is selected, or None when the selection is cleared or multiple steps are selected simultaneously.

remove_monitor_widget(plugin_name: str) None[source]

Remove the monitoring widget registered for plugin_name.

If no widget is registered for plugin_name this call is a no-op.

Args:
plugin_name (str):

Unique identifier for the owning plugin.

Examples:
>>> from PyQt6.QtWidgets import QApplication, QLabel
>>> _ = QApplication.instance() or QApplication([])
>>> from stoner_measurement.core.plugin_manager import PluginManager
>>> pm = PluginManager()
>>> panel = DockPanel(plugin_manager=pm)
>>> panel.add_monitor_widget("test", QLabel("Status: OK"))
>>> panel.remove_monitor_widget("test")
>>> "test" in panel.monitor_widgets
False
property sequence_steps: list[_SequenceStep]

Return the current sequence steps as a (possibly nested) list.

Each element is either:

  • a plugin instance for a step that has no sub-steps, or

  • a (plugin_instance, [sub-steps…]) tuple for a SequencePlugin step that has at least one nested child. The inner list follows the same structure recursively, allowing arbitrarily deep nesting for multi-dimensional measurement scans.

Returns:
(list[_SequenceStep]):

Ordered sequence of step descriptors.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> from stoner_measurement.core.plugin_manager import PluginManager
>>> pm = PluginManager()
>>> panel = DockPanel(plugin_manager=pm)
>>> panel.sequence_steps
[]
toggle_disable_selected_steps() None[source]

Toggle the disabled state of all currently selected sequence steps.

If any selected plugin is currently enabled, all selected plugins are disabled. If all selected plugins are already disabled, they are all re-enabled. The visual appearance of each affected tree item is updated immediately to reflect the new state.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> from stoner_measurement.core.plugin_manager import PluginManager
>>> from stoner_measurement.plugins.trace import DummyPlugin
>>> pm = PluginManager()
>>> panel = DockPanel(plugin_manager=pm)
>>> plugin = DummyPlugin()
>>> panel.load_sequence([plugin])
>>> panel._sequence_tree.setCurrentItem(panel._sequence_tree.topLevelItem(0))
>>> plugin.disabled
False
>>> panel.toggle_disable_selected_steps()
>>> plugin.disabled
True
>>> panel.toggle_disable_selected_steps()
>>> plugin.disabled
False

Central PyQtGraph plotting widget — middle 50 % of the main window.

Supports multiple named traces (each with its own colour) and multiple independent x- and y-axes implemented via linked pyqtgraph.ViewBox instances.

class stoner_measurement.ui.plot_widget.PlotWidget(*args: Any, **kwargs: Any)[source]

Bases: QWidget

PyQtGraph-based plot area for displaying measurement data.

The widget supports:

  • Named traces — each trace is an independent pyqtgraph.PlotDataItem with its own colour. Traces are created on first use and can be updated point-by-point or in bulk.

  • Multiple axes — additional y-axes (left or right) and x-axes (top or bottom) can be added, and individual traces can be assigned to any axis pair.

Attributes:
pg_widget (pg.PlotWidget):

The underlying pyqtgraph.PlotWidget.

Keyword Parameters:
parent (QWidget | None):

Optional Qt parent widget.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> widget = PlotWidget()
>>> widget.append_point("my_trace", 1.0, 2.0)
>>> widget.x_data("my_trace")
[1.0]
add_x_axis(name: str, label: str, position: Literal['bottom', 'top'] = 'top') None[source]

Add a new x-axis with an independent pyqtgraph.ViewBox.

The new ViewBox is linked to the main plot’s y-axis so that panning and zooming in y remain synchronised.

Args:
name (str):

Unique identifier for the new axis.

label (str):

Text label shown on the axis.

Keyword Parameters:
position (Literal[“bottom”, “top”]):

Position of the axis relative to the plot. Defaults to "top".

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> widget = PlotWidget()
>>> widget.add_x_axis("freq", "Frequency (Hz)", position="top")
>>> "freq" in widget.axis_names
True
add_y_axis(name: str, label: str, side: Literal['left', 'right'] = 'right') None[source]

Add a new y-axis with an independent pyqtgraph.ViewBox.

The new ViewBox is linked to the main plot’s x-axis so that panning and zooming in x remain synchronised across all y-axes.

Args:
name (str):

Unique identifier for the new axis.

label (str):

Text label shown on the axis.

Keyword Parameters:
side (Literal[“left”, “right”]):

Side of the plot on which the axis appears. Defaults to "right".

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> widget = PlotWidget()
>>> widget.add_y_axis("temperature", "Temperature (K)", side="right")
>>> "temperature" in widget.axis_names
True
append_point(trace_name: str, x: float, y: float) None

Append a single (x, y) data point to the named trace.

The trace is created automatically if it does not already exist.

Args:
trace_name (str):

Name of the trace to update.

x (float):

Horizontal axis value.

y (float):

Vertical axis value.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> widget = PlotWidget()
>>> widget.append_point("sig", 0.0, 1.0)
>>> widget.x_data("sig")
[0.0]
assign_trace_axes(trace_name: str, x_axis: str = 'bottom', y_axis: str = 'left') None

Assign a trace to a specific pair of axes.

The trace is moved from its current pyqtgraph.ViewBox to the ViewBox associated with y_axis. This controls which axis range is used when the trace is rendered.

Args:
trace_name (str):

Name of the trace to reassign.

Keyword Parameters:
x_axis (str):

Name of the x-axis to use (must have been created via add_x_axis() or be "bottom"). Defaults to "bottom".

y_axis (str):

Name of the y-axis to use (must have been created via add_y_axis() or be "left"). Defaults to "left".

Raises:
KeyError:

If trace_name, x_axis, or y_axis are not registered.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> widget = PlotWidget()
>>> widget.add_y_axis("temp", "Temperature (K)")
>>> widget.append_point("sig", 0.0, 300.0)
>>> widget.assign_trace_axes("sig", y_axis="temp")
property axis_names: list[str]

Sorted list of all registered axis names (x and y combined).

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> widget = PlotWidget()
>>> sorted(widget.axis_names)
['bottom', 'left']
clear_all() None[source]

Remove all traces and their data.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> widget = PlotWidget()
>>> widget.append_point("a", 1.0, 2.0)
>>> widget.clear_all()
>>> widget.trace_names
[]
ensure_x_axis(name: str, label: str = '') None[source]

Ensure an x-axis with name exists, creating it at the top if absent.

Args:
name (str):

Identifier for the x-axis. One of the default "bottom" axis names or a custom name.

Keyword Parameters:
label (str):

Text label shown on the axis. Defaults to name when empty.

ensure_y_axis(name: str, label: str = '') None[source]

Ensure a y-axis with name exists, creating it on the right if absent.

If the axis already exists this is a no-op. If it does not exist a new right-hand y-axis is added using add_y_axis(), with label as the displayed axis label (falling back to name when label is empty).

This is intended for use by command plugins that direct trace data to a named axis — they call this method before assign_trace_axes() so that axes are created on demand without requiring the user to add them manually first.

Args:
name (str):

Identifier for the y-axis. One of the default "left" axis names or a custom name.

Keyword Parameters:
label (str):

Text label shown on the axis. Defaults to name when empty.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> widget = PlotWidget()
>>> widget.ensure_y_axis("temp", "Temperature (K)")
>>> "temp" in widget.axis_names
True
>>> widget.ensure_y_axis("temp")  # idempotent
>>> widget.axis_names.count("temp")
1
is_busy_for_data() bool[source]

Return True when queued plot data updates are still pending.

mark_data_update_queued() None

Record that a plot data update has been queued for processing.

property pg_widget: pyqtgraph.PlotWidget

The underlying pyqtgraph.PlotWidget.

remove_trace(trace_name: str) None[source]

Remove a named trace and all its data.

If trace_name does not exist this call is a no-op.

Args:
trace_name (str):

Name of the trace to remove.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> widget = PlotWidget()
>>> widget.append_point("sig", 0.0, 1.0)
>>> widget.remove_trace("sig")
>>> widget.trace_names
[]
set_default_axis_labels(x_label: str, y_label: str) None

Update the default bottom and left axis labels.

Called by PlotTraceCommand when trace metadata (names and units from TraceData) is available so that the plot axes reflect the physical quantities being displayed.

Args:
x_label (str):

Label for the bottom (x) axis. If empty the axis label is left unchanged.

y_label (str):

Label for the left (y) axis. If empty the axis label is left unchanged.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> widget = PlotWidget()
>>> widget.set_default_axis_labels("Current (A)", "Voltage (V)")
>>> widget._pg_widget.getPlotItem().getAxis("bottom").labelText
'Current (A)'
set_trace(trace_name: str, x_data: Sequence[float], y_data: Sequence[float]) None

Replace the complete data series for a named trace.

The trace is created automatically if it does not already exist.

Args:
trace_name (str):

Name of the trace to update.

x_data (Sequence[float]):

New horizontal axis data.

y_data (Sequence[float]):

New vertical axis data.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> widget = PlotWidget()
>>> widget.set_trace("sig", [0.0, 1.0], [2.0, 3.0])
>>> widget.y_data("sig")
[2.0, 3.0]
set_trace_style(trace_name: str, colour: str | None = None, line_style: str | None = None, point_style: str | None = None, line_width: float | None = None, point_size: float | None = None) None[source]

Set visual style properties for a trace.

Args:
trace_name (str):

Name of the trace to style.

Keyword Parameters:
colour (str | None):

Colour string accepted by pyqtgraph (e.g. "#ff0000"). If None, the trace’s existing colour is preserved.

line_style (str):

One of "solid", "dash", "dot", "dash-dot", or "none".

point_style (str):

One of "none", "circle", "square", "triangle", "diamond", "plus", or "cross".

line_width (float | None):

Width of the plotted line. If None the existing width is preserved.

point_size (float | None):

Size of plotted points. If None the existing size is preserved.

Raises:
ValueError:

If line_style or point_style are unknown values.

property trace_names: list[str]

Sorted list of currently registered trace names.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> widget = PlotWidget()
>>> widget.append_point("b", 0.0, 1.0)
>>> widget.append_point("a", 0.0, 1.0)
>>> widget.trace_names
['a', 'b']
x_data(trace_name: str = 'default') list[float][source]

Return the horizontal axis data for trace_name.

Args:
trace_name (str):

Name of the trace. Defaults to "default".

Returns:
(list[float]):

Copy of the x data list.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> widget = PlotWidget()
>>> widget.append_point("sig", 1.0, 2.0)
>>> widget.x_data("sig")
[1.0]
y_data(trace_name: str = 'default') list[float][source]

Return the vertical axis data for trace_name.

Args:
trace_name (str):

Name of the trace. Defaults to "default".

Returns:
(list[float]):

Copy of the y data list.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> widget = PlotWidget()
>>> widget.append_point("sig", 1.0, 2.0)
>>> widget.y_data("sig")
[2.0]

Configuration panel — right 25 % of the main window.

A QTabWidget that displays the configuration tabs of whichever plugin is currently selected in the sequence editor. Tabs are shown by calling ConfigPanel.show_plugin() and cleared when no step is selected.

class stoner_measurement.ui.config_panel.ConfigPanel(*args: Any, **kwargs: Any)[source]

Bases: QWidget

Right-hand tabbed configuration panel.

Displays the configuration tabs of the plugin that is currently selected in the sequence editor. Call show_plugin() to load a plugin’s tabs or pass None to return to the idle placeholder.

When the plugin manager notifies that a plugin has been removed, show_plugin() is called with None automatically if the removed plugin was the one currently being displayed.

Attributes:
tabs (QTabWidget):

The underlying tab widget.

Args:
plugin_manager (PluginManager):

The application PluginManager instance — used to detect when the currently displayed plugin is unregistered.

Keyword Parameters:
parent (QWidget | None):

Optional Qt parent widget.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> from stoner_measurement.core.plugin_manager import PluginManager
>>> pm = PluginManager()
>>> panel = ConfigPanel(plugin_manager=pm)
>>> panel.tabs.count()
0
commit_pending_changes() None[source]

Commit any pending edits in the currently displayed configuration tabs.

Some input widgets (e.g. QLineEdit) only apply their value to the plugin when the widget loses focus or the user presses Return. Toolbar and menu actions that do not take keyboard focus (the default Qt behaviour for toolbar buttons) would otherwise bypass this mechanism, so unsaved text would not reach the plugin before the action executes.

This method inspects the application-wide focus widget. If it is a descendant of this panel’s tab widget it is explicitly cleared of focus, which causes Qt to emit the editingFinished signal on any focused QLineEdit and flush the edit to the plugin before the action proceeds.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> from stoner_measurement.core.plugin_manager import PluginManager
>>> pm = PluginManager()
>>> panel = ConfigPanel(plugin_manager=pm)
>>> panel.commit_pending_changes()  # no-op when nothing is focused
show_placeholder() None[source]

Display a centred ‘no step selected’ message in the panel.

Convenience wrapper around show_plugin(None) that also adds a single informational tab so the panel does not appear completely empty.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> from stoner_measurement.core.plugin_manager import PluginManager
>>> pm = PluginManager()
>>> panel = ConfigPanel(plugin_manager=pm)
>>> panel.show_placeholder()
>>> panel.tabs.count()
1
show_plugin(plugin: BasePlugin | None) None[source]

Display the configuration tabs for plugin, replacing any currently shown tabs.

Tab widgets are sourced from config_tabs(). Because TracePlugin caches its tab widgets, user-edited state is preserved when a plugin is deselected and re-selected in the sequence editor.

Passing None removes all tabs and shows an empty panel.

Args:
plugin (BasePlugin | None):

The plugin whose tabs should be displayed, or None to clear the panel.

Examples:
>>> from PyQt6.QtWidgets import QApplication
>>> _ = QApplication.instance() or QApplication([])
>>> from stoner_measurement.core.plugin_manager import PluginManager
>>> from stoner_measurement.plugins.trace import DummyPlugin
>>> pm = PluginManager()
>>> panel = ConfigPanel(plugin_manager=pm)
>>> plugin = DummyPlugin()
>>> panel.show_plugin(plugin)
>>> panel.tabs.count()
3
>>> panel.show_plugin(None)
>>> panel.tabs.count()
0
property tabs: PyQt6.QtWidgets.QTabWidget

The underlying QTabWidget.