Source code for magicgui.widgets._bases.widget
from __future__ import annotations
import inspect
import sys
from contextlib import contextmanager
from typing import TYPE_CHECKING, Any, ForwardRef
from magicgui.application import use_app
from magicgui.events import Signal
from magicgui.widgets import _protocols
BUILDING_DOCS = sys.argv[-2:] == ["build", "docs"]
if BUILDING_DOCS:
import numpy as np
if TYPE_CHECKING:
from weakref import ReferenceType
import numpy as np # noqa
from magicgui.widgets._concrete import _LabeledWidget
[docs]class Widget:
"""Basic Widget, wrapping a class that implements WidgetProtocol.
Parameters
----------
widget_type : Type[WidgetProtocol]
A class implementing a widget protocol. Will be instantiated during __init__.
name : str, optional
The name of the parameter represented by this widget. by default ""
annotation : Any, optional
The type annotation for the parameter represented by the widget, by default
``None``
label : str
A string to use for an associated Label widget (if this widget is being
shown in a :class:`~magicgui.widgets.Container` widget, and labels are on).
By default, ``name`` will be used. Note: ``name`` refers the name of the
parameter, as might be used in a signature, whereas label is just the label
for that widget in the GUI.
tooltip : str, optional
A tooltip to display when hovering over the widget.
visible : bool, optional
Whether the widget is visible, by default ``True``.
backend_kwargs : dict, optional
keyword argument to pass to the backend widget constructor.
"""
_widget: _protocols.WidgetProtocol
# if this widget becomes owned by a labeled widget
_labeled_widget_ref: ReferenceType[_LabeledWidget] | None = None
parent_changed = Signal(object)
label_changed = Signal(str)
def __init__(
self,
widget_type: type[_protocols.WidgetProtocol],
name: str = "",
annotation: Any = None,
label: str = None,
tooltip: str | None = None,
visible: bool | None = None,
enabled: bool = True,
gui_only=False,
backend_kwargs=dict(),
**extra,
):
# for ipywidgets API compatibility
label = label or extra.pop("description", None)
if extra:
raise TypeError(
f"{type(self).__name__} got an unexpected "
f"keyword argument: {', '.join(extra)}"
)
_prot = self.__class__.__annotations__["_widget"]
if not isinstance(_prot, str):
_prot = _prot.__name__
prot = getattr(_protocols, _prot.replace("_protocols.", ""))
_protocols.assert_protocol(widget_type, prot)
self.__magicgui_app__ = use_app()
assert self.__magicgui_app__.native
self._widget = widget_type(**backend_kwargs)
self.name: str = name
self.param_kind = inspect.Parameter.POSITIONAL_OR_KEYWORD
self._label = label
self.tooltip = tooltip
self.enabled = enabled
self.annotation: Any = annotation
self.gui_only = gui_only
self._widget._mgui_bind_parent_change_callback(self._emit_parent)
# put the magicgui widget on the native object...may cause error on some backend
self.native._magic_widget = self
self._post_init()
self._visible: bool = False
self._explicitly_hidden: bool = False
if visible is not None:
self.visible = visible
@property
def annotation(self):
"""Return type annotation for the parameter represented by the widget.
ForwardRefs will be resolve when setting the annotation.
"""
return self._annotation
@annotation.setter
def annotation(self, value):
if isinstance(value, (str, ForwardRef)):
from magicgui.type_map import _evaluate_forwardref
value = _evaluate_forwardref(value)
self._annotation = value
@property
def param_kind(self) -> inspect._ParameterKind:
"""Return :attr:`inspect.Parameter.kind` represented by this widget.
Used in building signatures from multiple widgets, by default
:attr:`~inspect.Parameter.POSITIONAL_OR_KEYWORD`
"""
return self._param_kind
@param_kind.setter
def param_kind(self, kind: str | inspect._ParameterKind):
if isinstance(kind, str):
kind = inspect._ParameterKind[kind.upper()]
if not isinstance(kind, inspect._ParameterKind):
raise TypeError(
"'param_kind' must be either a string or a inspect._ParameterKind."
)
self._param_kind: inspect._ParameterKind = kind
def _post_init(self):
pass
@property
def options(self) -> dict:
"""Return options currently being used in this widget."""
return {"enabled": self.enabled, "visible": self.visible}
@property
def native(self):
"""Return native backend widget."""
return self._widget._mgui_get_native_widget()
@property
def enabled(self) -> bool:
"""Whether widget is enabled (editable)."""
return self._widget._mgui_get_enabled()
@enabled.setter
def enabled(self, value: bool):
self._widget._mgui_set_enabled(value)
@property
def parent(self) -> Widget:
"""Return the parent widget."""
return self._widget._mgui_get_parent()
@parent.setter
def parent(self, value: Widget):
self._widget._mgui_set_parent(value)
@property
def widget_type(self) -> str:
"""Return type of widget."""
return self.__class__.__name__
@property
def label(self):
"""Return a label to use for this widget when present in Containers."""
if self._label is None:
return self.name.replace("_", " ")
return self._label
@label.setter
def label(self, value):
self._label = value
self.label_changed.emit(value)
@property
def width(self) -> int:
"""Return the current width of the widget."""
return self._widget._mgui_get_width()
@width.setter
def width(self, value: int) -> None:
"""Set the minimum allowable width of the widget."""
self._widget._mgui_set_width(value)
@property
def min_width(self) -> int:
"""Get the minimum width of the widget."""
return self._widget._mgui_get_min_width()
@min_width.setter
def min_width(self, value: int) -> None:
"""Set the minimum width of the widget."""
self._widget._mgui_set_min_width(value)
@property
def max_width(self) -> int:
"""Get the maximum width of the widget."""
return self._widget._mgui_get_max_width()
@max_width.setter
def max_width(self, value: int) -> None:
"""Set the maximum width of the widget."""
self._widget._mgui_set_max_width(value)
@property
def height(self) -> int:
"""Return the current height of the widget."""
return self._widget._mgui_get_height()
@height.setter
def height(self, value: int) -> None:
"""Set the minimum allowable height of the widget."""
self._widget._mgui_set_height(value)
@property
def min_height(self) -> int:
"""Get the minimum height of the widget."""
return self._widget._mgui_get_min_height()
@min_height.setter
def min_height(self, value: int) -> None:
"""Set the minimum height of the widget."""
self._widget._mgui_set_min_height(value)
@property
def max_height(self) -> int:
"""Get the maximum height of the widget."""
return self._widget._mgui_get_max_height()
@max_height.setter
def max_height(self, value: int) -> None:
"""Set the maximum height of the widget."""
self._widget._mgui_set_max_height(value)
@property
def tooltip(self) -> str | None:
"""Get the tooltip for this widget."""
return self._widget._mgui_get_tooltip() or None
@tooltip.setter
def tooltip(self, value: str | None) -> None:
"""Set the tooltip for this widget."""
return self._widget._mgui_set_tooltip(value)
def _labeled_widget(self) -> _LabeledWidget | None:
"""Return _LabeledWidget container, if applicable."""
return self._labeled_widget_ref() if self._labeled_widget_ref else None
@property
def visible(self) -> bool:
"""Return whether widget is visible."""
return self._widget._mgui_get_visible()
@visible.setter
def visible(self, value: bool):
"""Set widget visibility.
``widget.show()`` is an alias for ``widget.visible = True``
``widget.hide()`` is an alias for ``widget.visible = False``
"""
if value is None:
return
self._widget._mgui_set_visible(value)
self._explicitly_hidden = not value
labeled_widget = self._labeled_widget()
if labeled_widget is not None:
labeled_widget.visible = value
[docs] def show(self, run=False):
"""Show widget.
alias for ``widget.visible = True``
Parameters
----------
run : bool, optional
Whether to start the application event loop, by default False
"""
self.visible = True
if run:
self.__magicgui_app__.run()
return self # useful for generating repr in sphinx
[docs] @contextmanager
def shown(self):
"""Context manager to show the widget."""
try:
self.show()
yield self.__magicgui_app__.__enter__()
finally:
self.__magicgui_app__.__exit__()
[docs] def hide(self):
"""Hide widget.
alias for ``widget.visible = False``
"""
self.visible = False
[docs] def render(self) -> np.ndarray:
"""Return an RGBA (MxNx4) numpy array bitmap of the rendered widget."""
return self._widget._mgui_render()
def __repr__(self) -> str:
"""Return representation of widget of instsance."""
return f"{self.widget_type}(annotation={self.annotation!r}, name={self.name!r})"
def _repr_png_(self):
"""Return PNG representation of the widget for QtConsole."""
from io import BytesIO
try:
from imageio import imsave
except ImportError:
print(
"(For a nicer magicgui widget representation in "
"Jupyter, please `pip install imageio`)"
)
return None
with BytesIO() as file_obj:
imsave(file_obj, self.render(), format="png")
file_obj.seek(0)
return file_obj.read()
def _emit_parent(self, *_):
self.parent_changed.emit(self.parent)