Source code for pydm.widgets.qtplugin_base

"""Module to define a parent qtdesigner plugin class.

Please continue to name your qtdesigner plugin modules using the convention
modulename.py          <--- defines the widget
modulename_qtplugin.py <--- imports this module + the widget

However, a majority of the builtin plugins are defined in qtplugins.py
adjacent to this module.

NOTE: PyDMDesignerPlugin is a valid plugin, so designer will try to pick it up
      and instantiate it if you import it into another module's namespace. You
      will need to avoid having it present in the global namespace of any
      module that defines a qtplugin.

If you do not heed this warning, you will get a one-line traceback:
TypeError: __init__() takes exactly 3 arguments (1 given)
for each PyDMDesignerPlugin that Qt Designer tries to use. This will not
affect any of your widgets, but it will be annoying.

"""
import enum
import inspect
import logging
from typing import Dict, List, Optional, Type

import entrypoints
from qtpy import QtCore, QtDesigner, QtGui, QtWidgets

from .. import config
from ..qtdesigner import DesignerHooks
from .qtplugin_extensions import PyDMExtensionFactory

logger = logging.getLogger(__name__)


[docs]class WidgetCategory(str, enum.Enum): """Categories for PyDM Widgets in the Qt Designer.""" #: Widgets which can contain other widgets. CONTAINER = "PyDM Container Widgets" #: Widgets which contain displays. DISPLAY = "PyDM Display Widgets" #: Widgets which take user input. INPUT = "PyDM Input Widgets" #: Widgets which plot data. PLOT = "PyDM Plot Widgets" #: Widgets which draw things. DRAWING = "PyDM Drawing Widgets" #: Widgets which don't fit into any other category. MISC = "PyDM Misc. Widgets"
[docs]def qtplugin_factory( cls: Type[QtWidgets.QWidget], is_container: bool = False, group: str = "PyDM Widgets", extensions: Optional[List[Type]] = None, icon: Optional[QtGui.QIcon] = None, ) -> Type["PyDMDesignerPlugin"]: """ Helper function to create a generic PyDMDesignerPlugin class. Parameters ---------- cls : QWidget subclass The widget class. is_container : bool, optional True if this widget can contain other widgets (as in a Frame). This will also affect whether or not the widget can be used as the top-level widget of a display. Defaults to False. extensions : list of extension classes, optional Extension classes to use with the widget. icon : QtGui.QIcon, optional An icon to use with the widget in the designer. Consider using the PyDM-provided fontawesome ``ifont`` here for simplicity. """ class Plugin(PyDMDesignerPlugin): __doc__ = "PyDMDesigner Plugin for {}".format(cls.__name__) def __init__(self): super(Plugin, self).__init__(cls, is_container, group, extensions, icon) return Plugin
class PyDMDesignerPlugin(QtDesigner.QPyDesignerCustomWidgetPlugin): """ Parent class to standardize how pydm plugins are accessed in qt designer. All functions have default returns that can be overriden as necessary. """ def __init__( self, cls: Type[QtWidgets.QWidget], is_container: bool = False, group: str = "PyDM Widgets", extensions: Optional[List[Type]] = None, icon: Optional[QtGui.QIcon] = None, ): """ Set up the plugin using the class info in cls :param cls: Class of the widget to use :type cls: QWidget """ QtDesigner.QPyDesignerCustomWidgetPlugin.__init__(self) self.initialized = False self.is_container = is_container self.cls = cls self._group = group if icon is None and QtWidgets.QApplication.instance() is not None: get_designer_icon = getattr(cls, "get_designer_icon", None) if get_designer_icon is not None: icon = get_designer_icon() if icon is None: icon = QtGui.QIcon() self._icon = icon self.extensions = extensions self.manager = None def initialize(self, core): """ Override this function if you need special initialization instructions. Make sure you don't neglect to set the self.initialized flag to True after a successful initialization. :param core: form editor interface to use in the initialization :type core: QDesignerFormEditorInterface """ if self.initialized: return self.initialized = True designer_hooks = DesignerHooks() designer_hooks.form_editor = core if self.extensions is not None and len(self.extensions) > 0: self.manager = core.extensionManager() if self.manager: factory = PyDMExtensionFactory(parent=self.manager) self.manager.registerExtensions(factory, "org.qt-project.Qt.Designer.TaskMenu") # Qt5 def isInitialized(self): """ Return True if initialize function has been called successfully. """ return self.initialized def createWidget(self, parent): """ Instantiate a widget with the given parent. :param parent: Parent widget of instantiated widget :type parent: QWidget """ w = self.cls(parent=parent) try: setattr(w, "extensions", self.extensions) w.init_for_designer() except (AttributeError, NameError): pass return w def name(self): """ Return the class name of the widget. """ return self.cls.__name__ def group(self): """ Return a common group name so all PyDM Widgets are together in Qt Designer. """ return self._group def toolTip(self): """ A short description to pop up on mouseover. If we leave this as an empty string, we'll have no tooltip by default and can override this on a case-by-case basis. """ return "" def whatsThis(self): """ A longer description of the widget for Qt Designer. By default, this is the entire class docstring. """ return "" def isContainer(self): """ Return True if this widget can contain other widgets. """ return self.is_container def icon(self): """ Return a QIcon to represent this widget in Qt Designer. """ return QtGui.QIcon(self._icon.pixmap(QtCore.QSize(32, 32))) def domXml(self): """ XML Description of the widget's properties. """ return ( '<widget class="{0}" name="{0}">\n' ' <property name="toolTip" >\n' " <string>{1}</string>\n" " </property>\n" "</widget>\n" ).format(self.name(), self.toolTip()) def includeFile(self): """ Include the class module for the generated qt code """ return self.cls.__module__ def create_designer_widget_from_widget( widget_cls: Type[QtWidgets.QWidget], ) -> Type[PyDMDesignerPlugin]: """ Get a designable widget class. Accepts either user-provided :class:`PyDMDesignerPlugin` subclasses or :class:`QWidget` subclasses. In the case of :class:`QWidget` subclasses, designer-specific settings may be specified on a class attribute. These arguments should match what :func:`qtplugin_factory` expects. """ if not inspect.isclass(widget_cls): raise ValueError(f"Expected a class, got a {type(widget_cls).__name__}") if issubclass(widget_cls, PyDMDesignerPlugin): return widget_cls if issubclass(widget_cls, QtWidgets.QWidget): designer_kwargs = getattr(widget_cls, "_qt_designer_", None) or {} return qtplugin_factory(widget_cls, **designer_kwargs) raise ValueError(f"Expected a PyDMDesignerPlugin or a QWidget subclass, " f"got a {type(widget_cls).__name__}") def get_widgets_from_entrypoints(key: str = config.ENTRYPOINT_WIDGET) -> Dict[str, Type[PyDMDesignerPlugin]]: """ Get all widgets from entrypoint definitions. Parameters ---------- key : str, optional The entrypoint key. Defaults to ``pydm.config.ENTRYPOINT_WIDGET``. Returns ------- widgets : dict Dictionary of class name to ``PyDMDesignerPlugin`` subclass. """ widgets = {} for entry in entrypoints.get_group_all(key): logger.debug("Found widget from entrypoint: %s", entry.name) try: plugin_cls = entry.load() except Exception as ex: logger.exception("Failed to load %s entry %s: %s", key, entry.name, ex) continue try: designer_widget = create_designer_widget_from_widget(plugin_cls) except Exception: logger.warning("Invalid widget class specified in entrypoint " "%s: %s", entry.name, plugin_cls) else: widgets[entry.name] = designer_widget return widgets