diff --git a/index.html b/index.html index 508504ef4..826c01ffa 100644 --- a/index.html +++ b/index.html @@ -1,9 +1,9 @@ - + -

Go to the default documentation.

+

Go to the default documentation.

\ No newline at end of file diff --git a/master/.buildinfo b/master/.buildinfo index 0e2cf77be..9045ebf5c 100644 --- a/master/.buildinfo +++ b/master/.buildinfo @@ -1,4 +1,4 @@ # Sphinx build info version 1 # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. -config: 1972ab311ac2e2a237fa1a55ef899cc9 +config: 6669a80fb98cbe78ffb6124512fb3cfd tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/master/_modules/index.html b/master/_modules/index.html index 3dc982de5..7f08653e3 100644 --- a/master/_modules/index.html +++ b/master/_modules/index.html @@ -3,7 +3,7 @@ - Overview: module code — Typhos 2.4.1.dev54+g553e3c5 documentation + Overview: module code — Typhos 2.4.1 documentation + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+ + +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/_modules/typhos/alarm.html b/v2.4.1/_modules/typhos/alarm.html new file mode 100644 index 000000000..65719dd48 --- /dev/null +++ b/v2.4.1/_modules/typhos/alarm.html @@ -0,0 +1,579 @@ + + + + + + typhos.alarm — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for typhos.alarm

+"""
+Module to define alarm summary frameworks and widgets.
+"""
+import enum
+import logging
+import os
+from collections import defaultdict
+from dataclasses import dataclass
+from functools import partial
+
+from ophyd.device import Kind
+from ophyd.signal import EpicsSignalBase
+from pydm.widgets.base import PyDMPrimitiveWidget
+from pydm.widgets.channel import PyDMChannel
+from pydm.widgets.drawing import (PyDMDrawing, PyDMDrawingCircle,
+                                  PyDMDrawingEllipse, PyDMDrawingPolygon,
+                                  PyDMDrawingRectangle, PyDMDrawingTriangle)
+from qtpy import QtCore, QtGui, QtWidgets
+from qtpy.QtCore import Qt
+
+from .plugins import register_signal
+from .utils import (TyphosObject, channel_from_signal,
+                    get_all_signals_from_device, pyqt_class_from_enum)
+from .widgets import HappiChannel
+
+logger = logging.getLogger(__name__)
+
+
+class KindLevel(enum.IntEnum):
+    """Options for TyphosAlarm.kindLevel."""
+    HINTED = 0
+    NORMAL = 1
+    CONFIG = 2
+    OMITTED = 3
+
+
+class AlarmLevel(enum.IntEnum):
+    """
+    Possible summary alarm states for a device.
+
+    These are also the valuess emitted from TyphosAlarm.alarm_changed.
+    """
+    NO_ALARM = 0
+    MINOR = 1
+    MAJOR = 2
+    INVALID = 3
+    DISCONNECTED = 4
+
+
+_KindLevel = pyqt_class_from_enum(KindLevel)
+_AlarmLevel = pyqt_class_from_enum(AlarmLevel)
+
+
+# Define behavior for the user's Kind selection.
+KIND_FILTERS = {
+    KindLevel.HINTED:
+        (lambda walk: walk.item.kind == Kind.hinted),
+    KindLevel.NORMAL:
+        (lambda walk: walk.item.kind in (Kind.hinted, Kind.normal)),
+    KindLevel.CONFIG:
+        (lambda walk: walk.item.kind != Kind.omitted),
+    KindLevel.OMITTED:
+        (lambda walk: True),
+}
+
+
+@dataclass
+class SignalInfo:
+    address: str
+    channel: PyDMChannel
+    signal_name: str
+    connected: bool
+    severity: int
+
+    @property
+    def alarm(self) -> AlarmLevel:
+        if not self.connected:
+            return AlarmLevel.DISCONNECTED
+        else:
+            return AlarmLevel(self.severity)
+
+    def describe(self) -> str:
+        alarm = self.alarm
+        if alarm == AlarmLevel.NO_ALARM:
+            desc = f'{self.address} has no alarm'
+        elif alarm in (AlarmLevel.DISCONNECTED, AlarmLevel.INVALID):
+            desc = f'{self.address} is {alarm.name}'
+        else:
+            desc = f'{self.address} has a {alarm.name} alarm'
+        if self.signal_name:
+            return f'{desc} ({self.signal_name})'
+        else:
+            return desc
+
+
+class TyphosAlarm(TyphosObject, PyDMDrawing, _KindLevel, _AlarmLevel):
+    """
+    Class that holds logic and routines common to all Typhos Alarm widgets.
+
+    Overall, these classes exist to summarize alarm states from Ophyd Devices
+    and change the colors on indicator widgets appropriately.
+
+    We will consider a subset of the signals that is of KindLevel and above and
+    summarize state based on the "worst" alarm we see as defined by AlarmLevel.
+    """
+    QtCore.Q_ENUMS(_KindLevel)
+    QtCore.Q_ENUMS(_AlarmLevel)
+
+    KindLevel = KindLevel
+    AlarmLevel = AlarmLevel
+
+    _qt_designer_ = {
+        "group": "Typhos Alarm Widgets",
+        "is_container": False,
+    }
+
+    alarm_changed = QtCore.Signal(_AlarmLevel)
+
+    def __init__(self, *args, **kwargs):
+        self._kind_level = KindLevel.HINTED
+        super().__init__(*args, **kwargs)
+        # Default drawing properties, can override if needed
+        self.penWidth = 2
+        self.penColor = QtGui.QColor('black')
+        self.penStyle = Qt.SolidLine
+        self.reset_alarm_state()
+        self.alarm_changed.connect(self.set_alarm_color)
+
+    @QtCore.Property(_KindLevel)
+    def kindLevel(self):
+        """
+        Determines which signals to include in the alarm summary.
+
+        If this is "hinted", only include hinted signals.
+        If this is "normal", include normal and hinted signals.
+        If this is "config", include everything except for omitted signals
+        If this is "omitted", include all signals
+        """
+        return self._kind_level
+
+    @kindLevel.setter
+    def kindLevel(self, kind_level):
+        # We must update the alarm config to add/remove PVs as appropriate.
+        self._kind_level = kind_level
+        self.update_alarm_config()
+
+    @QtCore.Property(str)
+    def channel(self):
+        """
+        The channel address to use for this widget.
+
+        If this is a happi:// channel, we'll create the device and
+        add it to this widget.
+
+        If this is a ca:// channel, we'll connect to the PV and include its
+        alarm information in the evaluation of this widget.
+
+        There is an assumption that you'll either be using this via one of the
+        channel options or by using "add_device" one or more times. There may
+        be some strange behavior if you try to set up this widget using both
+        approaches at the same time.
+        """
+        if self._channel:
+            return str(self._channel)
+        return None
+
+    @channel.setter
+    def channel(self, value):
+        if self._channel != value:
+            # Remove old connection
+            if self._channels:
+                for channel in self._channels:
+                    if hasattr(channel, 'disconnect'):
+                        channel.disconnect()
+                    if channel in self.signal_info:
+                        del self.signal_info[channel]
+                self._channels.clear()
+            # Load new channel
+            self._channel = str(value)
+            if 'happi://' in self._channel:
+                channel = HappiChannel(
+                    address=self._channel,
+                    tx_slot=self._tx,
+                )
+            else:
+                channel = PyDMChannel(
+                    address=self._channel,
+                    connection_slot=partial(self.update_connection,
+                                            addr=self._channel),
+                    severity_slot=partial(self.update_severity,
+                                          addr=self._channel),
+                )
+                self.signal_info[self._channel] = SignalInfo(
+                    address=self._channel,
+                    channel=channel,
+                    signal_name='',
+                    connected=False,
+                    severity=AlarmLevel.INVALID,
+                )
+            self._channels = [channel]
+            # Connect the channel to the HappiPlugin
+            if hasattr(channel, 'connect'):
+                channel.connect()
+
+    def _tx(self, value):
+        """Receive information from happi channel"""
+        self.add_device(value['obj'])
+
+    def reset_alarm_state(self):
+        self.signal_info = {}
+        self.device_info = defaultdict(list)
+        self.alarm_summary = AlarmLevel.DISCONNECTED
+        self.set_alarm_color(AlarmLevel.DISCONNECTED)
+
+    def channels(self):
+        """
+        Let pydm know about our pydm channels.
+        """
+        ch = list(self._channels)
+        for info in self.signal_info.values():
+            ch.append(info.channel)
+        return ch
+
+    def add_device(self, device):
+        """
+        Initialize our alarm handling when adding a device.
+        """
+        super().add_device(device)
+        self.setup_alarm_config(device)
+
+    def clear_all_alarm_configs(self):
+        """
+        Reset this widget down to the "no alarm handling" state.
+        """
+        for ch in (info.channel for info in self.signal_info.values()):
+            ch.disconnect()
+        self.reset_alarm_state()
+
+    def setup_alarm_config(self, device):
+        """
+        Add a device to the alarm summary.
+
+        This will pick PVs based on the device kind and the configured kind
+        level, configuring the PyDMChannels to update our alarm state and
+        color when we get updates from our PVs.
+        """
+        sigs = get_all_signals_from_device(
+            device,
+            filter_by=KIND_FILTERS[self._kind_level]
+        )
+        channel_addrs = [channel_from_signal(sig) for sig in sigs]
+        for sig in sigs:
+            if not isinstance(sig, EpicsSignalBase):
+                register_signal(sig)
+        channels = [
+            PyDMChannel(
+                address=addr,
+                connection_slot=partial(self.update_connection, addr=addr),
+                severity_slot=partial(self.update_severity, addr=addr),
+            )
+            for addr in channel_addrs]
+
+        for ch, sig in zip(channels, sigs):
+            info = SignalInfo(
+                address=ch.address,
+                channel=ch,
+                signal_name=sig.dotted_name,
+                connected=False,
+                severity=AlarmLevel.INVALID,
+            )
+            self.signal_info[ch.address] = info
+            self.device_info[device.name].append(info)
+            ch.connect()
+
+        all_channels = self.channels()
+        if all_channels:
+            logger.debug(
+                f'Finished setup of alarm config for device {device.name} on '
+                f'alarm widget with channel {all_channels[0]}.'
+            )
+        else:
+            logger.warning(
+                f'Tried to set up alarm config for device {device.name}, but '
+                'did not configure any channels! Check your kindLevel!'
+            )
+
+    def update_alarm_config(self):
+        """
+        Clean up the existing alarm config and create a new one.
+
+        This must be called when settings like KindLevel are changed so we can
+        re-evaluate them.
+        """
+        self.clear_all_alarm_configs()
+        for dev in self.devices:
+            self.setup_alarm_config(dev)
+
+    def update_connection(self, connected, addr):
+        """Slot that will be called when a PV connects or disconnects."""
+        self.signal_info[addr].connected = connected
+        self.update_current_alarm()
+
+    def update_severity(self, severity, addr):
+        """Slot that will be called when a PV's alarm severity changes."""
+        self.signal_info[addr].severity = severity
+        self.update_current_alarm()
+
+    def update_current_alarm(self):
+        """
+        Check what the current worst available alarm state is.
+
+        If the alarm state is different than the last time we checked,
+        emit the "alarm_changed" signal. This signal is configured at
+        init to change the color of this widget.
+        """
+        if not self.signal_info:
+            new_alarm = AlarmLevel.INVALID
+        else:
+            new_alarm = max(info.alarm for info in self.signal_info.values())
+        if new_alarm != self.alarm_summary:
+            try:
+                self.alarm_changed.emit(new_alarm)
+            except RuntimeError:
+                # Widget was destroyed and not properly cleaned up
+                logger.debug('Dangling reference to alarm widget!')
+                return
+            else:
+                logger.debug(
+                    f'Updated alarm from {self.alarm_summary} to {new_alarm} '
+                    f'on alarm widget with channel {self.channels()[0]}'
+                )
+
+        self.alarm_summary = new_alarm
+
+    def set_alarm_color(self, alarm_level):
+        """
+        Change the alarm color to the shade defined by the current alarm level.
+        """
+        self.setStyleSheet(indicator_stylesheet(self.__class__, alarm_level))
+
+    def eventFilter(self, obj, event):
+        """
+        Extra handling for showing the user which alarms are alarming.
+
+        We'll show this information on mouseover if anything is disconnected or
+        in an alarm state, unless the user middle-clicks, which will have the
+        default PyDM behavior of showing all the channels and copying them to
+        clipboard.
+        """
+        # super() doesn't work here, some strange pyqt thing
+        default_pydm_event = PyDMPrimitiveWidget.eventFilter(self, obj, event)
+        if default_pydm_event:
+            return True
+        if event.type() == QtCore.QEvent.Enter:
+            alarming = self.show_alarm_tooltip(event)
+            return alarming
+        return False
+
+    def show_alarm_tooltip(self, event):
+        """
+        Show a tooltip that reveals which channels are alarmed or disconnected.
+        """
+        tooltip_lines = []
+
+        # Start with the channel field, just show the status.
+        if self.channel in self.signal_info:
+            info = self.signal_info[self.channel]
+            tooltip_lines.append(f'Channel {info.describe()}')
+
+        # Handle each device
+        for name, device_info_list in self.device_info.items():
+            # At least show the device name
+            tooltip_lines.append(f'Device {name}')
+            has_alarm = False
+            for info in device_info_list:
+                if info.alarm != AlarmLevel.NO_ALARM:
+                    if not has_alarm:
+                        has_alarm = True
+                        tooltip_lines.append('-' * 2 * len(tooltip_lines[-1]))
+                    tooltip_lines.append(info.describe())
+
+        if tooltip_lines:
+            tooltip = os.linesep.join(tooltip_lines)
+            QtWidgets.QToolTip.showText(
+                self.mapToGlobal(
+                    QtCore.QPoint(event.x() + 10, event.y())),
+                tooltip,
+                self,
+            )
+
+        # Return True if we showed something
+        return bool(tooltip_lines)
+
+
+# Subclass an re-introduce properties as needed
+# Each of these must be included for these to work in designer
+
+class TyphosAlarmCircle(TyphosAlarm, PyDMDrawingCircle):
+    QtCore.Q_ENUMS(_KindLevel)
+    kindLevel = TyphosAlarm.kindLevel
+
+
+class TyphosAlarmRectangle(TyphosAlarm, PyDMDrawingRectangle):
+    QtCore.Q_ENUMS(_KindLevel)
+    kindLevel = TyphosAlarm.kindLevel
+
+
+class TyphosAlarmTriangle(TyphosAlarm, PyDMDrawingTriangle):
+    QtCore.Q_ENUMS(_KindLevel)
+    kindLevel = TyphosAlarm.kindLevel
+
+
+class TyphosAlarmEllipse(TyphosAlarm, PyDMDrawingEllipse):
+    QtCore.Q_ENUMS(_KindLevel)
+    kindLevel = TyphosAlarm.kindLevel
+
+
+class TyphosAlarmPolygon(TyphosAlarm, PyDMDrawingPolygon):
+    QtCore.Q_ENUMS(_KindLevel)
+    kindLevel = TyphosAlarm.kindLevel
+    numberOfPoints = PyDMDrawingPolygon.numberOfPoints
+
+
+def indicator_stylesheet(shape_cls, alarm):
+    """
+    Create the indicator stylesheet that will modify a PyDMDrawing's color.
+
+    Parameters
+    ----------
+    shape_cls : type
+        The PyDMDrawing widget subclass.
+
+    alarm : int
+        The value from AlarmLevel
+
+    Returns
+    -------
+    indicator_stylesheet : str
+        The correctly colored stylesheet to apply to the widget.
+    """
+    base = (
+        f'{shape_cls.__name__} '
+        '{border: none; '
+        ' background: transparent;'
+        ' qproperty-brush: rgba'
+    )
+
+    if alarm == AlarmLevel.DISCONNECTED:
+        return base + '(255,255,255,255);}'
+    elif alarm == AlarmLevel.NO_ALARM:
+        return base + '(0,255,0,255);}'
+    elif alarm == AlarmLevel.MINOR:
+        return base + '(255,255,0,255);}'
+    elif alarm == AlarmLevel.MAJOR:
+        return base + '(255,0,0,255);}'
+    elif alarm == AlarmLevel.INVALID:
+        return base + '(255,0,255,255);}'
+    else:
+        raise ValueError(f'Recieved invalid alarm level {alarm}')
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/_modules/typhos/cache.html b/v2.4.1/_modules/typhos/cache.html new file mode 100644 index 000000000..913f9a7b6 --- /dev/null +++ b/v2.4.1/_modules/typhos/cache.html @@ -0,0 +1,466 @@ + + + + + + typhos.cache — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for typhos.cache

+import fnmatch
+import functools
+import logging
+import os
+import pathlib
+import re
+import time
+
+from qtpy import QtCore
+
+from . import utils
+from .widgets import SignalWidgetInfo
+
+logger = logging.getLogger(__name__)
+
+
+# Global cache state. Do not use these directly, but instead use
+# `get_global_describe_cache()` and `get_global_widget_type_cache()` below.
+_GLOBAL_WIDGET_TYPE_CACHE = None
+_GLOBAL_DESCRIBE_CACHE = None
+_GLOBAL_DISPLAY_PATH_CACHE = None
+
+
+
[docs]def get_global_describe_cache(): + """Get the _GlobalDescribeCache singleton.""" + global _GLOBAL_DESCRIBE_CACHE + if _GLOBAL_DESCRIBE_CACHE is None: + _GLOBAL_DESCRIBE_CACHE = _GlobalDescribeCache() + return _GLOBAL_DESCRIBE_CACHE
+ + +
[docs]def get_global_widget_type_cache(): + """Get the _GlobalWidgetTypeCache singleton.""" + global _GLOBAL_WIDGET_TYPE_CACHE + if _GLOBAL_WIDGET_TYPE_CACHE is None: + _GLOBAL_WIDGET_TYPE_CACHE = _GlobalWidgetTypeCache() + return _GLOBAL_WIDGET_TYPE_CACHE
+ + +
[docs]def get_global_display_path_cache(): + """Get the _GlobalDisplayPathCache singleton.""" + global _GLOBAL_DISPLAY_PATH_CACHE + if _GLOBAL_DISPLAY_PATH_CACHE is None: + _GLOBAL_DISPLAY_PATH_CACHE = _GlobalDisplayPathCache() + return _GLOBAL_DISPLAY_PATH_CACHE
+ + +
[docs]class _GlobalDescribeCache(QtCore.QObject): + """ + Cache of ophyd object descriptions. + + ``obj.describe()`` is called in a thread from the global QThreadPool, and + new results are marked by the Signal ``new_description``. + + To access a description, call :meth:`.get`. If available, it will be + returned immediately. Otherwise, wait for the ``new_description`` Signal. + + Attributes + ---------- + connect_thread : :class:`ObjectConnectionMonitorThread` + The thread which monitors connection status. + + cache : dict + The cache holding descriptions, keyed on ``obj``. + """ + + new_description = QtCore.Signal(object, dict) + + def __init__(self): + super().__init__() + self._in_process = set() + self.cache = {} + + self.connect_thread = utils.ObjectConnectionMonitorThread(parent=self) + self.connect_thread.connection_update.connect( + self._connection_update, QtCore.Qt.QueuedConnection) + + self.connect_thread.start() + +
[docs] def clear(self): + """Clear the cache.""" + self.connect_thread.clear() + self.cache.clear() + self._in_process.clear()
+ + def _describe(self, obj): + """Retrieve the description of ``obj``.""" + try: + return obj.describe()[obj.name] + except Exception: + logger.error("Unable to connect to %r during widget creation", + obj.name) + logger.debug("Unable to connect to %r during widget creation", + obj.name, exc_info=True) + return {} + + def _worker_describe(self, obj): + """ + This is the worker thread method that gets run in the thread pool. + + It calls describe, updates the cache, and emits a signal when done. + """ + try: + self.cache[obj] = desc = self._describe(obj) + self.new_description.emit(obj, desc) + except Exception as ex: + logger.exception('Worker describe failed: %s', ex) + finally: + self._in_process.remove(obj) + + @QtCore.Slot(object, bool, dict) + def _connection_update(self, obj, connected, metadata): + """ + A connection callback from the connection monitor thread. + """ + if not connected: + return + elif self.cache.get(obj) or obj in self._in_process: + return + + self._in_process.add(obj) + func = functools.partial(self._worker_describe, obj) + QtCore.QThreadPool.globalInstance().start( + utils.ThreadPoolWorker(func) + ) + +
[docs] def get(self, obj): + """ + To access a description, call this method. If available, it will be + returned immediately. Otherwise, upon connection and successful + ``describe()`` call, the ``new_description`` Signal will be emitted. + + Parameters + ---------- + obj : :class:`ophyd.OphydObj` + The object to get the description of. + + Returns + ------- + desc : dict or None + If available in the cache, the description will be returned. + """ + try: + return self.cache[obj] + except KeyError: + # Add the object, waiting for a connection update to determine + # widget types + self.connect_thread.add_object(obj)
+ + +
[docs]class _GlobalWidgetTypeCache(QtCore.QObject): + """ + Cache of ophyd object Typhos widget types. + + ``obj.describe()`` is called using :class:`_GlobalDescribeCache` and are + therefore threaded and run in the background. New results are marked by + the Signal ``widgets_determined``. + + To access a set of widget types, call :meth:`.get`. If available, it will + be returned immediately. Otherwise, wait for the ``widgets_determined`` + Signal. + + Attributes + ---------- + describe_cache : :class:`_GlobalDescribeCache` + The describe cache, used for determining widget types. + + cache : dict + The cache holding widget type information. + Keyed on ``obj``, the values are :class:`SignalWidgetInfo` tuples. + """ + + widgets_determined = QtCore.Signal(object, SignalWidgetInfo) + + def __init__(self): + super().__init__() + self.cache = {} + self.describe_cache = get_global_describe_cache() + self.describe_cache.new_description.connect(self._new_description, + QtCore.Qt.QueuedConnection) + +
[docs] def clear(self): + """Clear the cache.""" + self.cache.clear()
+ + @QtCore.Slot(object, dict) + def _new_description(self, obj, desc): + """New description: determine widget types and update the cache.""" + if not desc: + # Marks an error in retrieving the description + # TODO: show error widget or some default widget? + return + + item = SignalWidgetInfo.from_signal(obj, desc) + logger.debug('Determined widgets for %s: %s', obj.name, item) + self.cache[obj] = item + self.widgets_determined.emit(obj, item) + +
[docs] def get(self, obj): + """ + To access widget types, call this method. If available, it will be + returned immediately. Otherwise, upon connection and successful + ``describe()`` call, the ``widgets_determined`` Signal will be emitted. + + Parameters + ---------- + obj : :class:`ophyd.OphydObj` + The object to get the widget types. + + Returns + ------- + desc : :class:`SignalWidgetInfo` or None + If available in the cache, the information will be returned. + """ + try: + return self.cache[obj] + except KeyError: + # Add the signal, waiting for a connection update to determine + # widget types + desc = self.describe_cache.get(obj) + if desc is not None: + # In certain scenarios (such as testing) this might happen + self._new_description(obj, desc)
+ + +# The default stale cached_path threshold time, in seconds: +TYPHOS_DISPLAY_PATH_CACHE_TIME = int( + os.environ.get('TYPHOS_DISPLAY_PATH_CACHE_TIME', '600') +) + + +class _CachedPath: + """ + A wrapper around pathlib.Path to support repeated globbing. + + Parameters + ---------- + path : pathlib.Path + The path to cache. + + Attributes + ---------- + path : pathlib.Path + The underlying path. + cache : list + The cache of filenames. + _update_time : float + The last time the cache was updated. + stale_threshold : float, optional + The time (in seconds) after which to update the path cache. This + happens on the next glob, and not on a timer-basis. + """ + + def __init__(self, path, *, + stale_threshold=TYPHOS_DISPLAY_PATH_CACHE_TIME): + self.path = pathlib.Path(path) + self.cache = None + self._update_time = None + self.stale_threshold = stale_threshold + + @classmethod + def from_path(cls, path, **kwargs): + """ + Get a cached path, if not already cached. + + Parameters + ---------- + path : :class:`pathlib.Path` or :class:`_CachedPath` + The paths to cache, if not already cached. + """ + if isinstance(path, (cls, _GlobalDisplayPathCache)): + # Already a _CachedPath + return path + return cls(path, **kwargs) + + def __hash__(self): + # Keep the hash the same as the internal path for set()/dict() usage + return hash(self.path) + + @property + def time_since_last_update(self): + """Time (in seconds) since the last update.""" + if self._update_time is None: + return 0 + return time.monotonic() - self._update_time + + def update(self): + """Update the file list.""" + self.cache = os.listdir(self.path) + self._update_time = time.monotonic() + + def glob(self, pattern): + """Glob a pattern.""" + if self.cache is None: + self.update() + elif self.time_since_last_update > self.stale_threshold: + self.update() + + if any(c in pattern for c in '*?['): + # Convert from glob syntax -> regular expression + # And compile it for repeated usage. + regex = re.compile(fnmatch.translate(pattern)) + for path in self.cache: + if regex.match(path): + yield self.path / path + else: + # No globbing syntax: only check if file is in the list + if pattern in self.cache: + yield self.path / pattern + + +
[docs]class _GlobalDisplayPathCache: + """ + A cache for all configured display paths. + + All paths from `utils.DISPLAY_PATHS` will be included: + 1. Environment variable ``PYDM_DISPLAYS_PATH``. + 2. Typhos package built-in paths. + """ + + def __init__(self): + self.paths = [] + for path in utils.DISPLAY_PATHS: + self.add_path(path) + +
[docs] def update(self): + """Force a reload of all paths in the cache.""" + logger.debug('Clearing global path cache.') + for path in self.paths: + path.cache = None
+ +
[docs] def add_path(self, path): + """ + Add a path to be searched during ``glob``. + + Parameters + ---------- + path : pathlib.Path or str + The path to add. + """ + logger.debug('Path added to _GlobalDisplayPathCache: %s', path) + path = pathlib.Path(path).expanduser().resolve() + path = _CachedPath( + path, stale_threshold=TYPHOS_DISPLAY_PATH_CACHE_TIME) + if path not in self.paths: + self.paths.append(path)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/_modules/typhos/display.html b/v2.4.1/_modules/typhos/display.html new file mode 100644 index 000000000..8626a6c3a --- /dev/null +++ b/v2.4.1/_modules/typhos/display.html @@ -0,0 +1,1798 @@ + + + + + + typhos.display — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for typhos.display

+"""Contains the main display widget used for representing an entire device."""
+
+import enum
+import inspect
+import logging
+import os
+import pathlib
+import webbrowser
+from typing import Optional, Union
+
+import ophyd
+import pcdsutils
+import pydm.display
+import pydm.exception
+import pydm.utilities
+from pcdsutils.qt import forward_property
+from qtpy import QtCore, QtGui, QtWidgets
+from qtpy.QtCore import Q_ENUMS, Property, Qt, Slot
+
+from . import cache
+from . import panel as typhos_panel
+from . import utils, web, widgets
+from .jira import TyphosJiraIssueWidget
+from .plugins.core import register_signal
+
+logger = logging.getLogger(__name__)
+
+
+class DisplayTypes(enum.IntEnum):
+    """Enumeration of template types that can be used in displays."""
+
+    embedded_screen = 0
+    detailed_screen = 1
+    engineering_screen = 2
+
+
+_DisplayTypes = utils.pyqt_class_from_enum(DisplayTypes)
+DisplayTypes.names = [view.name for view in DisplayTypes]
+
+
+class ScrollOptions(enum.IntEnum):
+    """Enumeration of scrollable options for displays."""
+
+    auto = 0
+    scrollbar = 1
+    no_scroll = 2
+
+
+_ScrollOptions = utils.pyqt_class_from_enum(ScrollOptions)
+ScrollOptions.names = [view.name for view in ScrollOptions]
+
+
+DEFAULT_TEMPLATES = {
+    name: [(utils.ui_dir / 'core' / f'{name}.ui').resolve()]
+    for name in DisplayTypes.names
+}
+
+DETAILED_TREE_TEMPLATE = (utils.ui_dir / 'core' / 'detailed_tree.ui').resolve()
+DEFAULT_TEMPLATES['detailed_screen'].append(DETAILED_TREE_TEMPLATE)
+
+DEFAULT_TEMPLATES_FLATTEN = [f for _, files in DEFAULT_TEMPLATES.items()
+                             for f in files]
+
+
+
[docs]def normalize_display_type( + display_type: Union[DisplayTypes, str, int] +) -> DisplayTypes: + """ + Normalize a given display type. + + Parameters + ---------- + display_type : DisplayTypes, str, or int + The display type. + + Returns + ------- + display_type : DisplayTypes + The normalized :class:`DisplayTypes`. + + Raises + ------ + ValueError + If the input cannot be made a :class:`DisplayTypes`. + """ + try: + return DisplayTypes(display_type) + except ValueError: + try: + return DisplayTypes[display_type] + except KeyError: + raise ValueError( + f'Unrecognized display type: {display_type}' + )
+ + +def normalize_scroll_option( + scroll_option: Union[ScrollOptions, str, int] +) -> ScrollOptions: + """ + Normalize a given scroll option. + + Parameters + ---------- + display_type : ScrollOptions, str, or int + The display type. + + Returns + ------- + display_type : ScrollOptions + The normalized :class:`ScrollOptions`. + + Raises + ------ + ValueError + If the input cannot be made a :class:`ScrollOptions`. + """ + try: + return ScrollOptions(scroll_option) + except ValueError: + try: + return ScrollOptions[scroll_option] + except KeyError: + raise ValueError( + f'Unrecognized scroll option: {scroll_option}' + ) + + +
[docs]class TyphosToolButton(QtWidgets.QToolButton): + """ + Base class for tool buttons used in the TyphosDisplaySwitcher. + + Parameters + ---------- + icon : QIcon or str, optional + See :meth:`.get_icon` for options. + + parent : QtWidgets.QWidget, optional + The parent widget. + + Attributes + ---------- + DEFAULT_ICON : str + The default icon from fontawesome to use. + """ + + DEFAULT_ICON = 'circle' + + def __init__(self, icon=None, *, parent=None): + super().__init__(parent=parent) + + self.setContextMenuPolicy(Qt.DefaultContextMenu) + self.contextMenuEvent = self.open_context_menu + self.clicked.connect(self._clicked) + self.setIcon(self.get_icon(icon)) + self.setMinimumSize(24, 24) + + def _clicked(self): + """Clicked callback: override in a subclass.""" + menu = self.generate_context_menu() + if menu: + menu.exec_(QtGui.QCursor.pos()) + +
[docs] def generate_context_menu(self): + """Context menu request: override in subclasses.""" + return None
+ +
[docs] @classmethod + def get_icon(cls, icon=None): + """ + Get a QIcon, if specified, or fall back to the default. + + Parameters + ---------- + icon : str or QtGui.QIcon + If a string, assume it is from fontawesome. + Otherwise, use the icon instance as-is. + """ + icon = icon or cls.DEFAULT_ICON + if isinstance(icon, str): + return pydm.utilities.IconFont().icon(icon) + return icon
+ +
[docs] def open_context_menu(self, ev): + """ + Open the instance-specific context menu. + + Parameters + ---------- + ev : QEvent + """ + menu = self.generate_context_menu() + if menu: + menu.exec_(self.mapToGlobal(ev.pos()))
+ + +
[docs]class TyphosDisplayConfigButton(TyphosToolButton): + """ + The configuration button used in the :class:`TyphosDisplaySwitcher`. + + This uses the common "vertical ellipse" icon by default. + """ + + DEFAULT_ICON = 'ellipsis-v' + + _kind_to_property = typhos_panel.TyphosSignalPanel._kind_to_property + + def __init__(self, icon=None, *, parent=None): + super().__init__(icon=icon, parent=parent) + self.setPopupMode(self.InstantPopup) + self.setArrowType(Qt.NoArrow) + self.templates = None + self.device_display = None + +
[docs] def set_device_display(self, device_display): + """Typhos callback: set the :class:`TyphosDeviceDisplay`.""" + self.device_display = device_display
+ +
[docs] def create_kind_filter_menu(self, panels, base_menu, *, only): + """ + Create the "Kind" filter menu. + + Parameters + ---------- + panels : list of TyphosSignalPanel + The panels to filter upon triggering of menu actions. + + base_menu : QMenu + The menu to add actions to. + + only : bool + False - create "Show Kind" actions. + True - create "Show only Kind" actions. + """ + for kind, prop in self._kind_to_property.items(): + def selected(new_value, *, prop=prop): + if only: + # Show *only* the specific kind for all panels + for kind, current_prop in self._kind_to_property.items(): + visible = (current_prop == prop) + for panel in panels: + setattr(panel, current_prop, visible) + else: + # Toggle visibility of the specific kind for all panels + for panel in panels: + setattr(panel, prop, new_value) + self.hide_empty() + + title = f'Show only &{kind}' if only else f'Show &{kind}' + action = base_menu.addAction(title) + if not only: + action.setCheckable(True) + action.setChecked(all(getattr(panel, prop) + for panel in panels)) + action.triggered.connect(selected)
+ +
[docs] def create_name_filter_menu(self, panels, base_menu): + """ + Create the name-based filtering menu. + + Parameters + ---------- + panels : list of TyphosSignalPanel + The panels to filter upon triggering of menu actions. + + base_menu : QMenu + The menu to add actions to. + """ + def text_filter_updated(): + text = line_edit.text().strip() + for panel in panels: + panel.nameFilter = text + self.hide_empty() + + line_edit = QtWidgets.QLineEdit() + + filters = list({panel.nameFilter for panel in panels + if panel.nameFilter}) + if len(filters) == 1: + line_edit.setText(filters[0]) + else: + line_edit.setPlaceholderText('/ '.join(filters)) + + line_edit.editingFinished.connect(text_filter_updated) + line_edit.setObjectName('menu_action') + + action = base_menu.addAction('Filter by name:') + action.setEnabled(False) + + action = QtWidgets.QWidgetAction(self) + action.setDefaultWidget(line_edit) + base_menu.addAction(action)
+ +
[docs] def hide_empty(self, search=True): + """ + Wrap hide_empty calls for use with search functions and action clicks. + + Parameters + ---------- + search : bool + Whether or not this method is being called from a search/filter + method. + """ + if self.device_display.hideEmpty: + if search: + show_empty(self.device_display) + hide_empty(self.device_display, process_widget=False)
+ +
[docs] def create_hide_empty_menu(self, panels, base_menu): + """ + Create the hide empty filtering menu. + + Parameters + ---------- + panels : list of TyphosSignalPanel + The panels to filter upon triggering of menu actions. + + base_menu : QMenu + The menu to add actions to. + """ + def handle_menu(checked): + self.device_display.hideEmpty = checked + + if not checked: + # Force a reboot of the filters + # since we no longer can figure what was supposed to be + # visible or not + for p in panels: + p._update_panel() + show_empty(self.device_display) + else: + self.hide_empty(search=False) + + action = base_menu.addAction('Hide Empty Panels') + action.setCheckable(True) + action.setChecked(self.device_display.hideEmpty) + action.triggered.connect(handle_menu)
+ +
[docs] def generate_context_menu(self): + """ + Generate the custom context menu. + + .. code:: + + Embedded + Detailed + Engineering + ------------- + Refresh templates + ------------- + Kind filter > Show hinted + ... + Show only hinted + Filter by name + Hide Empty Panels + """ + base_menu = QtWidgets.QMenu(parent=self) + + display = self.device_display + if not display: + return base_menu + + base_menu.addSection('Templates') + display._generate_template_menu(base_menu) + + panels = display.findChildren(typhos_panel.TyphosSignalPanel) or [] + if not panels: + return base_menu + + base_menu.addSection('Filters') + filter_menu = base_menu.addMenu("&Kind filter") + self.create_kind_filter_menu(panels, filter_menu, only=False) + filter_menu.addSeparator() + self.create_kind_filter_menu(panels, filter_menu, only=True) + + self.create_name_filter_menu(panels, base_menu) + + base_menu.addSeparator() + self.create_hide_empty_menu(panels, base_menu) + + if utils.DEBUG_MODE: + base_menu.addSection('Debug') + action = base_menu.addAction('&Copy to clipboard') + action.triggered.connect(display.copy_to_clipboard) + + return base_menu
+ + +
[docs]class TyphosDisplaySwitcherButton(TyphosToolButton): + """A button which switches the TyphosDeviceDisplay template on click.""" + + template_selected = QtCore.Signal(pathlib.Path) + + icons = {'embedded_screen': 'compress', + 'detailed_screen': 'braille', + 'engineering_screen': 'cogs' + } + + def __init__(self, display_type, *, parent=None): + super().__init__(icon=self.icons[display_type], parent=parent) + self.templates = None + + def _clicked(self): + """Clicked callback - set the template.""" + if self.templates is None: + logger.warning('set_device_display not called on %s', self) + return + + try: + template = self.templates[0] + except IndexError: + return + + self.template_selected.emit(template) + +
[docs] def generate_context_menu(self): + """Context menu request.""" + if not self.templates: + return + + menu = QtWidgets.QMenu(parent=self) + for template in self.templates: + def selected(*, template=template): + self.template_selected.emit(template) + + action = menu.addAction(template.name) + action.triggered.connect(selected) + + return menu
+ + +
[docs]class TyphosDisplaySwitcher(QtWidgets.QFrame, widgets.TyphosDesignerMixin): + """Display switcher set of buttons for use with a TyphosDeviceDisplay.""" + + template_selected = QtCore.Signal(pathlib.Path) + + def __init__(self, parent=None, **kwargs): + # Intialize background variable + super().__init__(parent=None) + + self.device_display = None + self.buttons = {} + layout = QtWidgets.QHBoxLayout() + self.setLayout(layout) + layout.setSpacing(0) + layout.setContentsMargins(0, 0, 0, 0) + + self.setContextMenuPolicy(Qt.DefaultContextMenu) + self.contextMenuEvent = self.open_context_menu + + if parent: + self.setParent(parent) + + self._create_ui() + + def _create_ui(self): + layout = self.layout() + self.buttons.clear() + self.help_button = None + self.config_button = None + + self.help_toggle_button = TyphosHelpToggleButton() + layout.addWidget(self.help_toggle_button, 0, Qt.AlignRight) + + for template_type in DisplayTypes.names: + button = TyphosDisplaySwitcherButton(template_type) + self.buttons[template_type] = button + button.template_selected.connect(self._template_selected) + layout.addWidget(button, 0, Qt.AlignRight) + + friendly_name = template_type.replace('_', ' ') + button.setToolTip(f'Switch to {friendly_name}') + + self.config_button = TyphosDisplayConfigButton() + layout.addWidget(self.config_button, 0, Qt.AlignRight) + self.config_button.setToolTip('Display settings...') + + def _template_selected(self, template): + """Template selected hook.""" + self.template_selected.emit(template) + if self.device_display is not None: + self.device_display.force_template = template + +
[docs] def set_device_display(self, display): + """Typhos hook for setting the associated device display.""" + self.device_display = display + + for template_type in self.buttons: + templates = display.templates.get(template_type, []) + self.buttons[template_type].templates = templates + self.config_button.set_device_display(display)
+ +
[docs] def add_device(self, device): + """Typhos hook for setting the associated device.""" + ...
+ + +
[docs]class TyphosTitleLabel(QtWidgets.QLabel): + """ + A label class intended for use as a standardized title. + + Attributes + ---------- + toggle_requested : QtCore.Signal + A Qt signal indicating that the user clicked on the title. By default, + this hides any nested panels underneath the title. + """ + + toggle_requested = QtCore.Signal() + +
[docs] def mousePressEvent(self, event): + """Overridden qt hook for a mouse press.""" + if event.button() == Qt.LeftButton: + self.toggle_requested.emit() + + super().mousePressEvent(event)
+ + +class TyphosHelpToggleButton(TyphosToolButton): + """ + A standard button used to toggle help information display. + + Attributes + ---------- + pop_out : QtCore.Signal + A Qt signal indicating a request to pop out the help widget. + + open_in_browser : QtCore.Signal + A Qt signal indicating a request to open the help in a browser. + + open_python_docs : QtCore.Signal + A Qt signal indicating a request to open the Python docstring + information. + + report_jira_issue : QtCore.Signal + A Qt signal indicating a request to open the Jira issue reporting + widget. + + toggle_help : QtCore.Signal + A Qt signal indicating a request to toggle the related help display + frame. + """ + pop_out = QtCore.Signal() + open_in_browser = QtCore.Signal() + open_python_docs = QtCore.Signal() + report_jira_issue = QtCore.Signal() + toggle_help = QtCore.Signal(bool) + + def __init__(self, icon="question", parent=None): + super().__init__(icon, parent=parent) + self.setCheckable(True) + + def _clicked(self): + """Hook for QToolButton.clicked.""" + self.toggle_help.emit(self.isChecked()) + + def generate_context_menu(self): + menu = QtWidgets.QMenu(parent=self) + + if utils.HELP_WEB_ENABLED: + pop_out_docs = menu.addAction("Pop &out documentation...") + pop_out_docs.triggered.connect(self.pop_out.emit) + + open_in_browser = menu.addAction("Open in &browser...") + open_in_browser.triggered.connect(self.open_in_browser.emit) + + open_python_docs = menu.addAction("Open &Python docs...") + open_python_docs.triggered.connect(self.open_python_docs.emit) + + def toggle(): + self.setChecked(not self.isChecked()) + self._clicked() + + if utils.HELP_WEB_ENABLED: + toggle_help = menu.addAction("Toggle &help") + toggle_help.triggered.connect(toggle) + + if utils.JIRA_URL: + menu.addSeparator() + report_issue = menu.addAction("&Report Jira issue...") + report_issue.triggered.connect(self.report_jira_issue.emit) + + return menu + + +class TyphosHelpFrame(QtWidgets.QFrame, widgets.TyphosDesignerMixin): + """ + A frame for help information display. + + Attributes + ---------- + tooltip_updated : QtCore.Signal + A signal indicating the help tooltip has changed. + """ + tooltip_updated = QtCore.Signal(str) + + def __init__(self, parent=None): + super().__init__(parent=parent) + self.help = None + self.help_web_view = None + self._delete_timer = None + self.python_docs_browser = None + + self.setContentsMargins(0, 0, 0, 0) + + layout = QtWidgets.QVBoxLayout() + self.setLayout(layout) + self.devices = [] + self._jira_widget = None + + def new_jira_widget(self): + """Open a new Jira issue reporting widget.""" + device = self.devices[0] if self.devices else None + self._jira_widget = TyphosJiraIssueWidget(device=device) + self._jira_widget.show() + + def open_in_browser(self, new=0, autoraise=True): + """ + Open the associated help documentation in the browser. + + Parameters + ---------- + new : int, optional + 0: the same browser window (the default). + 1: a new browser window. + 2: a new browser page ("tab"). + + autoraise : bool, optional + If possible, autoraise raises the window (the default) or not. + """ + return webbrowser.open( + self.help_url.toString(), new=new, autoraise=autoraise + ) + + def open_python_docs(self, show: bool = True): + """Open the Python docstring information in a new window.""" + if self.python_docs_browser is not None: + if show: + self.python_docs_browser.show() + self.python_docs_browser.raise_() + else: + self.python_docs_browser.hide() + return + + if not show: + return + + self.python_docs_browser = QtWidgets.QTextBrowser() + help_document = QtGui.QTextDocument() + contents = self._tooltip or "Unset" + first_line = contents.splitlines()[0] + # TODO: later versions of qt will support setMarkdown + help_document.setPlainText(contents) + self.python_docs_browser.setWindowTitle(first_line) + font = QtGui.QFont("Monospace") + font.setStyleHint(QtGui.QFont.TypeWriter) + # font.setStyleHint(QtGui.QFont.Monospace) + self.python_docs_browser.setFont(font) + self.python_docs_browser.setDocument(help_document) + self.python_docs_browser.show() + return self.python_docs_browser + + def _get_tooltip(self): + """Update the tooltip based on device information.""" + tooltip = [] + # BUG: I'm seeing two devices in `self.devices` for + # $ typhos --fake-device 'ophyd.EpicsMotor[{"prefix":"b"}]' + for device in sorted( + set(self.devices), + key=lambda dev: self.devices.index(dev) + ): + heading = device.name or type(device).__name__ + tooltip.extend([ + heading, + "-" * len(heading), + "" + ]) + + tooltip.append( + inspect.getdoc(device) or + inspect.getdoc(type(device)) or + "No docstring" + ) + tooltip.append("") + + return "\n".join(tooltip) + + def add_device(self, device): + self.devices.append(device) + + self._tooltip = self._get_tooltip() + self.tooltip_updated.emit(self._tooltip) + + self.setWindowTitle(f"Help: {device.name}") + + @property + def help_url(self): + """The full help URL, generated from ``TYPHOS_HELP_URL``.""" + if not self.devices or not utils.HELP_WEB_ENABLED: + return QtCore.QUrl("about:blank") + + device, *_ = self.devices + try: + device_url = utils.HELP_URL.format(device=device) + except Exception: + logger.exception("Failed to format confluence URL for device %s", + device) + return QtCore.QUrl("about:blank") + + return QtCore.QUrl(device_url) + + def show_help(self): + """Show the help information in a QWebEngineView.""" + if web.TyphosWebEngineView is None: + logger.error( + "Failed to import QWebEngineView; " + "help view is unavailable." + ) + return + + if self.help_web_view: + self.help_web_view.show() + return + + self.help_web_view = web.TyphosWebEngineView() + self.help_web_view.page().setUrl(self.help_url) + + self.help_web_view.setEnabled(True) + self.help_web_view.setMinimumSize(QtCore.QSize(100, 400)) + + self.layout().addWidget(self.help_web_view) + + def hide_help(self): + """Hide the help information QWebEngineView.""" + if not self.help_web_view: + return + self.help_web_view.hide() + if self._delete_timer is None: + self._delete_timer = QtCore.QTimer() + self._delete_timer.setInterval(20000) + self._delete_timer.setSingleShot(True) + self._delete_timer.timeout.connect(self._delete_help_if_hidden) + self._delete_timer.start() + + def _delete_help_if_hidden(self): + """ + Slowly react to the help display removal, as setting it back up can be + slow and painful. + """ + self._delete_timer = None + if self.help_web_view and not self.help_web_view.isVisible(): + self.layout().removeWidget(self.help_web_view) + self.help_web_view.deleteLater() + self.help_web_view = None + + def toggle_help(self, show): + """ + Toggle the visibility of the help information QWebEngineView. + + Parameters + ---------- + show : bool + Show the help (True) or hide it (False). + """ + if not self.devices: + logger.warning("No devices added -> no help") + return + + if show: + self.show_help() + else: + self.hide_help() + + +
[docs]class TyphosDisplayTitle(QtWidgets.QFrame, widgets.TyphosDesignerMixin): + """ + Standardized Typhos Device Display title. + + Parameters + ---------- + title : str, optional + The initial title text, which may contain macros. + + show_switcher : bool, optional + Show the :class:`TyphosDisplaySwitcher`. + + show_underline : bool, optional + Show the underline separator. + + parent : QtWidgets.QWidget, optional + The parent widget. + """ + + def __init__(self, title='${name}', *, show_switcher=True, + show_underline=True, parent=None): + self._show_underline = show_underline + self._show_switcher = show_switcher + super().__init__(parent=parent) + + self.label = TyphosTitleLabel(title) + self.switcher = TyphosDisplaySwitcher() + + self.underline = QtWidgets.QFrame() + self.underline.setFrameShape(self.underline.HLine) + self.underline.setFrameShadow(self.underline.Plain) + self.underline.setLineWidth(10) + + self.grid_layout = QtWidgets.QGridLayout() + self.grid_layout.addWidget(self.label, 0, 0) + self.grid_layout.addWidget(self.switcher, 0, 1, Qt.AlignRight) + self.grid_layout.addWidget(self.underline, 1, 0, 1, 2) + + self.help = TyphosHelpFrame() + if utils.HELP_WEB_ENABLED: + # Toggle the help web view if we have documentation to show + self.switcher.help_toggle_button.toggle_help.connect( + self.toggle_help + ) + else: + # Otherwise, open the python docs as a fallback + self.switcher.help_toggle_button.toggle_help.connect( + self.help.open_python_docs + ) + self.switcher.help_toggle_button.pop_out.connect(self.pop_out_help) + self.switcher.help_toggle_button.open_in_browser.connect( + self.help.open_in_browser + ) + self.switcher.help_toggle_button.open_python_docs.connect( + self.help.open_python_docs + ) + self.switcher.help_toggle_button.report_jira_issue.connect( + self.help.new_jira_widget + ) + self.help.tooltip_updated.connect( + self.switcher.help_toggle_button.setToolTip + ) + + self.grid_layout.addWidget(self.help, 2, 0, 1, 2) + + self.grid_layout.setSizeConstraint(self.grid_layout.SetMinimumSize) + self.setLayout(self.grid_layout) + + # Set the property: + self.show_switcher = show_switcher + self.show_underline = show_underline + +
[docs] def toggle_help(self, show): + """Toggle the help visibility.""" + if self.help is None: + return + + self.help.toggle_help(show) + if self.help.parent() is None: + self.grid_layout.addWidget(self.help, 2, 0, 1, 2)
+ +
[docs] def pop_out_help(self): + """Pop out the help widget.""" + if self.help is None: + return + + self.help.setParent(None) + self.switcher.help_toggle_button.setChecked(True) + self.help.show_help() + self.help.show() + self.help.raise_()
+ + @Property(bool) + def show_switcher(self): + """Get or set whether to show the display switcher.""" + return self._show_switcher + + @show_switcher.setter + def show_switcher(self, value): + self._show_switcher = bool(value) + self.switcher.setVisible(self._show_switcher) + +
[docs] def add_device(self, device): + """Typhos hook for setting the associated device.""" + if not self.label.text(): + self.label.setText(device.name) + + if self.help is not None: + self.help.add_device(device)
+ + @QtCore.Property(bool) + def show_underline(self): + """Get or set whether to show the underline.""" + return self._show_underline + + @show_underline.setter + def show_underline(self, value): + self._show_underline = bool(value) + self.underline.setVisible(self._show_underline) + +
[docs] def set_device_display(self, display): + """Typhos callback: set the :class:`TyphosDeviceDisplay`.""" + self.device_display = display + + def toggle(): + toggle_display(display.display_widget) + + self.label.toggle_requested.connect(toggle)
+ + # Make designable properties from the title label available here as well + label_alignment = forward_property('label', QtWidgets.QLabel, 'alignment') + label_font = forward_property('label', QtWidgets.QLabel, 'font') + label_indent = forward_property('label', QtWidgets.QLabel, 'indent') + label_margin = forward_property('label', QtWidgets.QLabel, 'margin') + label_openExternalLinks = forward_property('label', QtWidgets.QLabel, + 'openExternalLinks') + label_pixmap = forward_property('label', QtWidgets.QLabel, 'pixmap') + label_text = forward_property('label', QtWidgets.QLabel, 'text') + label_textFormat = forward_property('label', QtWidgets.QLabel, + 'textFormat') + label_textInteractionFlags = forward_property('label', QtWidgets.QLabel, + 'textInteractionFlags') + label_wordWrap = forward_property('label', QtWidgets.QLabel, 'wordWrap') + + # Make designable properties from the grid_layout + layout_margin = forward_property('grid_layout', QtWidgets.QHBoxLayout, + 'margin') + layout_spacing = forward_property('grid_layout', QtWidgets.QHBoxLayout, + 'spacing') + + # Make designable properties from the underline + underline_palette = forward_property('underline', QtWidgets.QFrame, + 'palette') + underline_styleSheet = forward_property('underline', QtWidgets.QFrame, + 'styleSheet') + underline_lineWidth = forward_property('underline', QtWidgets.QFrame, + 'lineWidth') + underline_midLineWidth = forward_property('underline', QtWidgets.QFrame, + 'midLineWidth')
+ + +
[docs]class TyphosDeviceDisplay(utils.TyphosBase, widgets.TyphosDesignerMixin, + _DisplayTypes): + """ + Main display for a single ophyd Device. + + This contains the widgets for all of the root devices signals, and any + methods you would like to display. By typhos convention, the base + initialization sets up the widgets and the :meth:`.from_device` class + method will automatically populate the resulting display. + + Parameters + ---------- + parent : QWidget, optional + The parent widget. + + scrollable : bool, optional + Semi-deprecated parameter. Use scroll_option instead. + If ``True``, put the loaded template into a :class:`QScrollArea`. + If ``False``, the display widget will go directly in this widget's + layout. + If omitted, scroll_option is used instead. + + composite_heuristics : bool, optional + Enable composite heuristics, which may change the suggested detailed + screen based on the contents of the added device. See also + :meth:`.suggest_composite_screen`. + + embedded_templates : list, optional + List of embedded templates to use in addition to those found on disk. + + detailed_templates : list, optional + List of detailed templates to use in addition to those found on disk. + + engineering_templates : list, optional + List of engineering templates to use in addition to those found on + disk. + + display_type : DisplayTypes, str, or int, optional + The default display type. + + scroll_option : ScrollOptions, str, or int, optional + The scroll behavior. + + nested : bool, optional + An optional annotation for a display that may be nested inside another. + """ + + # Template types and defaults + Q_ENUMS(_DisplayTypes) + TemplateEnum = DisplayTypes # For convenience + + device_count_threshold = 0 + signal_count_threshold = 30 + + def __init__( + self, + parent: Optional[QtWidgets.QWidget] = None, + *, + scrollable: Optional[bool] = None, + composite_heuristics: bool = True, + embedded_templates: Optional[list[str]] = None, + detailed_templates: Optional[list[str]] = None, + engineering_templates: Optional[list[str]] = None, + display_type: Union[DisplayTypes, str, int] = 'detailed_screen', + scroll_option: Union[ScrollOptions, str, int] = 'auto', + nested: bool = False, + ): + self._composite_heuristics = composite_heuristics + self._current_template = None + self._forced_template = '' + self._macros = {} + self._display_widget = None + self._scroll_option = ScrollOptions.no_scroll + self._searched = False + self._hide_empty = False + self._nested = nested + + self.templates = {name: [] for name in DisplayTypes.names} + self._display_type = normalize_display_type(display_type) + + instance_templates = { + 'embedded_screen': embedded_templates or [], + 'detailed_screen': detailed_templates or [], + 'engineering_screen': engineering_templates or [], + } + for view, path_list in instance_templates.items(): + paths = [pathlib.Path(p).expanduser().resolve() for p in path_list] + self.templates[view].extend(paths) + + self._scroll_area = QtWidgets.QScrollArea() + self._scroll_area.setAlignment(Qt.AlignTop) + self._scroll_area.setObjectName('scroll_area') + self._scroll_area.setFrameShape(QtWidgets.QFrame.StyledPanel) + self._scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) + self._scroll_area.setWidgetResizable(True) + self._scroll_area.setFrameStyle(QtWidgets.QFrame.NoFrame) + + super().__init__(parent=parent) + + layout = QtWidgets.QHBoxLayout() + self.setLayout(layout) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self._scroll_area) + + if scrollable is None: + self.scroll_option = scroll_option + else: + if scrollable: + self.scroll_option = ScrollOptions.scrollbar + else: + self.scroll_option = ScrollOptions.no_scroll + + @Property(bool) + def composite_heuristics(self): + """Allow composite screen to be suggested first by heuristics.""" + return self._composite_heuristics + + @composite_heuristics.setter + def composite_heuristics(self, composite_heuristics): + self._composite_heuristics = bool(composite_heuristics) + + @Property(_ScrollOptions) + def scroll_option(self) -> ScrollOptions: + """Place the display in a scrollable area.""" + return self._scroll_option + + @scroll_option.setter + def scroll_option(self, scrollable: ScrollOptions): + # Switch the scroll area behavior + opt = normalize_scroll_option(scrollable) + if opt == self._scroll_option: + return + + self._scroll_option = opt + self._move_display_to_layout(self._display_widget) + + @Property(bool) + def hideEmpty(self): + """Toggle hiding or showing empty panels.""" + return self._hide_empty + + @hideEmpty.setter + def hideEmpty(self, checked): + if checked != self._hide_empty: + self._hide_empty = checked + + def _move_display_to_layout(self, widget): + if not widget: + return + + widget.setParent(None) + if self.scroll_option == ScrollOptions.auto: + if self.display_type == DisplayTypes.embedded_screen: + scrollable = False + else: + scrollable = True + elif self.scroll_option == ScrollOptions.scrollbar: + scrollable = True + elif self.scroll_option == ScrollOptions.no_scroll: + scrollable = False + else: + scrollable = True + + if scrollable: + self._scroll_area.setWidget(widget) + else: + self.layout().addWidget(widget) + + self._scroll_area.setVisible(scrollable) + + def _generate_template_menu(self, base_menu): + """Generate the template switcher menu, adding it to ``base_menu``.""" + for view, filenames in self.templates.items(): + if view.endswith('_screen'): + view = view.split('_screen')[0] + menu = base_menu.addMenu(view.capitalize()) + + for filename in filenames: + def switch_template(*, filename=filename): + self.force_template = filename + + action = menu.addAction(os.path.split(filename)[-1]) + action.triggered.connect(switch_template) + + refresh_action = base_menu.addAction("Refresh Templates") + refresh_action.triggered.connect(self._refresh_templates) + + def _refresh_templates(self): + """Context menu 'Refresh Templates' clicked.""" + # Force an update of the display cache. + cache.get_global_display_path_cache().update() + self.search_for_templates() + self.load_best_template() + + @property + def current_template(self): + """Get the current template being displayed.""" + return self._current_template + + @Property(_DisplayTypes) + def display_type(self): + """Get or set the current display type.""" + return self._display_type + + @display_type.setter + def display_type(self, value): + value = normalize_display_type(value) + if self._display_type != value: + self._display_type = value + self.load_best_template() + + @property + def macros(self): + """Get or set the macros for the display.""" + return dict(self._macros) + + @macros.setter + def macros(self, macros): + self._macros.clear() + self._macros.update(**(macros or {})) + + # If any display macros are specified, re-search for templates: + if any(view in self._macros for view in DisplayTypes.names): + self.search_for_templates() + + @Property(str, designable=False) + def device_class(self): + """Get the full class with module name of loaded device.""" + device = self.device + cls = self.device.__class__ + return f'{cls.__module__}.{cls.__name__}' if device else '' + + @Property(str, designable=False) + def device_name(self): + """Get the name of the loaded device.""" + device = self.device + return device.name if device else '' + + @property + def device(self): + """Get the device associated with this Device Display.""" + try: + device, = self.devices + return device + except ValueError: + ... + +
[docs] def get_best_template(self, display_type, macros): + """ + Get the best template for the given display type. + + Parameters + ---------- + display_type : DisplayTypes, str, or int + The display type. + + macros : dict + Macros to use when loading the template. + """ + display_type = normalize_display_type(display_type).name + + templates = self.templates[display_type] + if templates: + return templates[0] + + logger.warning("No templates available for display type: %s", + self._display_type)
+ + def _remove_display(self): + """Remove the display widget, readying for a new template.""" + display_widget = self._display_widget + if display_widget: + if self._scroll_area.widget(): + self._scroll_area.takeWidget() + self.layout().removeWidget(display_widget) + display_widget.deleteLater() + + self._display_widget = None + +
[docs] def load_best_template(self): + """Load the best available template for the current display type.""" + if self.layout() is None: + # If we are not fully initialized yet do not try and add anything + # to the layout. This will happen if the QApplication has a + # stylesheet that forces a template prior to the creation of this + # display + return + + if not self._searched: + self.search_for_templates() + + self._remove_display() + + template = (self._forced_template or + self.get_best_template(self._display_type, self.macros)) + + if not template: + widget = QtWidgets.QWidget() + template = None + else: + template = pathlib.Path(template) + try: + widget = self._load_template(template) + except Exception as ex: + logger.exception("Unable to load file %r", template) + # If we have a previously defined template + if self._current_template is not None: + # Fallback to it so users have a choice + try: + widget = self._load_template(self._current_template) + except Exception: + logger.exception( + "Failed to fall back to previous template: %s", + self._current_template + ) + template = None + widget = None + + pydm.exception.raise_to_operator(ex) + else: + widget = QtWidgets.QWidget() + template = None + + if widget: + widget.setObjectName('display_widget') + + if widget.layout() is None and widget.minimumSize().width() == 0: + # If the widget has no layout, use a fixed size for it. + # Without this, the widget may not display at all. + widget.setMinimumSize(widget.size()) + + self._display_widget = widget + self._current_template = template + + def size_hint(*args, **kwargs): + return widget.size() + + # sizeHint is not defined so we suggest the widget size + widget.sizeHint = size_hint + + # We should _move_display_to_layout as soon as it is created. This + # allow us to speed up since if the widget is too complex it takes + # seconds to set it to the QScrollArea + self._move_display_to_layout(self._display_widget) + + self._update_children() + utils.reload_widget_stylesheet(self)
+ + @property + def display_widget(self): + """Get the widget generated from the template.""" + return self._display_widget + + @staticmethod + def _get_templates_from_macros(macros): + ret = {} + paths = cache.get_global_display_path_cache().paths + for display_type in DisplayTypes.names: + ret[display_type] = None + try: + value = macros[display_type] + except KeyError: + ... + else: + if not value: + continue + try: + value = pathlib.Path(value) + except ValueError as ex: + logger.debug('Invalid path specified in macro: %s=%s', + display_type, value, exc_info=ex) + else: + ret[display_type] = list(utils.find_file_in_paths( + value, paths=paths)) + + return ret + + def _load_template(self, filename): + """Load template from file and return the widget.""" + filename = pathlib.Path(filename) + loader = (pydm.display.load_py_file if filename.suffix == '.py' + else pydm.display.load_ui_file) + + logger.debug('Load template using %s: %r', loader.__name__, filename) + return loader(str(filename), macros=self._macros) + + def _update_children(self): + """Notify child widgets of this device display + the device.""" + device = self.device + display = self._display_widget + designer = display.findChildren(widgets.TyphosDesignerMixin) or [] + bases = display.findChildren(utils.TyphosBase) or [] + + for widget in set(bases + designer): + if device and hasattr(widget, 'add_device'): + widget.add_device(device) + + if hasattr(widget, 'set_device_display'): + widget.set_device_display(self) + + @Property(str) + def force_template(self): + """Force a specific template.""" + return self._forced_template + + @force_template.setter + def force_template(self, value): + if value != self._forced_template: + self._forced_template = value + self.load_best_template() + + @staticmethod + def _build_macros_from_device(device, macros=None): + result = {} + if hasattr(device, 'md'): + if isinstance(device.md, dict): + result = dict(device.md) + else: + result = dict(device.md.post()) + + if 'name' not in result: + result['name'] = device.name + if 'prefix' not in result and hasattr(device, 'prefix'): + result['prefix'] = device.prefix + + result.update(**(macros or {})) + return result + +
[docs] def add_device(self, device, macros=None): + """ + Add a Device and signals to the TyphosDeviceDisplay. + + The full dictionary of macros is built with the following order of + precedence:: + + 1. Macros from the device metadata itself. + 2. If available, `name`, and `prefix` will be added from the device. + 3. The argument ``macros`` is then used to fill/update the final + macro dictionary. + + This will also register the device's signals in the sig:// plugin. + This means that any templates can refer to their device's signals by + name. + + Parameters + ---------- + device : ophyd.Device + The device to add. + + macros : dict, optional + Additional macros to use/replace the defaults. + """ + # We only allow one device at a time + if self.devices: + logger.debug("Removing devices %r", self.devices) + self.devices.clear() + # Add the device to the cache + super().add_device(device) + logger.debug("Registering signals from device %s", device.name) + for component_walk in device.walk_signals(): + register_signal(component_walk.item) + self._searched = False + self.macros = self._build_macros_from_device(device, macros=macros) + self.load_best_template()
+ +
[docs] def search_for_templates(self): + """Search the filesystem for device-specific templates.""" + device = self.device + if not device: + logger.debug('Cannot search for templates without device') + return + + self._searched = True + cls = device.__class__ + + logger.debug('Searching for templates for %s', cls.__name__) + macro_templates = self._get_templates_from_macros(self._macros) + + paths = cache.get_global_display_path_cache().paths + for display_type in DisplayTypes.names: + view = display_type + if view.endswith('_screen'): + view = view.split('_screen')[0] + + template_list = self.templates[display_type] + template_list.clear() + + # 1. Highest priority: macros + for template in set(macro_templates[display_type] or []): + template_list.append(template) + logger.debug('Adding macro template %s: %s (total=%d)', + display_type, template, len(template_list)) + + # 2. Composite heuristics, if enabled + if self._composite_heuristics and view == 'detailed': + if self.suggest_composite_screen(cls): + template_list.append(DETAILED_TREE_TEMPLATE) + + # 3. Templates based on class hierarchy names + filenames = utils.find_templates_for_class(cls, view, paths) + for filename in filenames: + if filename not in template_list: + template_list.append(filename) + logger.debug('Found new template %s: %s (total=%d)', + display_type, filename, len(template_list)) + + # 4. Default templates + template_list.extend( + [templ for templ in DEFAULT_TEMPLATES[display_type] + if templ not in template_list] + )
+ +
[docs] @classmethod + def suggest_composite_screen(cls, device_cls): + """ + Suggest to use the composite screen for the given class. + + Returns + ------- + composite : bool + If True, favor the composite screen. + """ + num_devices = 0 + num_signals = 0 + for attr, component in utils._get_top_level_components(device_cls): + num_devices += issubclass(component.cls, ophyd.Device) + num_signals += issubclass(component.cls, ophyd.Signal) + + specific_screens = cls._get_specific_screens(device_cls) + if (len(specific_screens) or + (num_devices <= cls.device_count_threshold and + num_signals >= cls.signal_count_threshold)): + # 1. There's a custom screen - we probably should use them + # 2. There aren't many devices, so the composite display isn't + # useful + # 3. There are many signals, which should be broken up somehow + composite = False + else: + # 1. No custom screen, or + # 2. Many devices or a relatively small number of signals + composite = True + + logger.debug( + '%s screens=%s num_signals=%d num_devices=%d -> composite=%s', + device_cls, specific_screens, num_signals, num_devices, composite + ) + return composite
+ +
[docs] @classmethod + def from_device(cls, device, template=None, macros=None, **kwargs): + """ + Create a new TyphosDeviceDisplay from a Device. + + Loads the signals in to the appropriate positions and sets the title to + a cleaned version of the device name + + Parameters + ---------- + device : ophyd.Device + + template : str, optional + Set the ``display_template``. + + macros : dict, optional + Macro substitutions to be placed in template. + + **kwargs + Passed to the class init. + """ + display = cls(**kwargs) + # Reset the template if provided + if template: + display.force_template = template + # Add the device + display.add_device(device, macros=macros) + return display
+ +
[docs] @classmethod + def from_class(cls, klass, *, template=None, macros=None, **kwargs): + """ + Create a new TyphosDeviceDisplay from a Device class. + + Loads the signals in to the appropriate positions and sets the title to + a cleaned version of the device name. + + Parameters + ---------- + klass : str or class + + template : str, optional + Set the ``display_template``. + + macros : dict, optional + Macro substitutions to be placed in template. + + **kwargs + Extra arguments are used at device instantiation. + + Returns + ------- + TyphosDeviceDisplay + """ + try: + obj = pcdsutils.utils.get_instance_by_name(klass, **kwargs) + except Exception: + logger.exception('Failed to generate TyphosDeviceDisplay from ' + 'class %s', klass) + return None + + return cls.from_device(obj, template=template, macros=macros)
+ + @classmethod + def _get_specific_screens(cls, device_cls): + """ + Get the list of specific screens for a given device class. + + That is, screens that are not default Typhos-provided screens. + """ + paths = cache.get_global_display_path_cache().paths + return [ + template + for template in utils.find_templates_for_class( + device_cls, "detailed", paths + ) + if not utils.is_standard_template(template) + ] + +
[docs] def to_image(self): + """ + Return the entire display as a QtGui.QImage. + + Returns + ------- + QtGui.QImage + The display, as an image. + """ + if self._display_widget is not None: + return utils.widget_to_image(self._display_widget)
+ +
[docs] @Slot() + def copy_to_clipboard(self): + """Copy the display image to the clipboard.""" + image = self.to_image() + if image is not None: + clipboard = QtGui.QGuiApplication.clipboard() + clipboard.setImage(image)
+ + @Slot(object) + def _tx(self, value): + """Receive information from happi channel.""" + self.add_device(value['obj'], macros=value['md']) + + def __repr__(self): + """Get a custom representation for TyphosDeviceDisplay.""" + return ( + f'<{self.__class__.__name__} at {hex(id(self))} ' + f'device={self.device_class}[{self.device_name!r}] ' + f'nested={self._nested}' + f'>' + )
+ + +
[docs]def toggle_display(widget, force_state=None): + """ + Toggle the visibility of all :class:`TyphosSignalPanel` in a display. + + Parameters + ---------- + widget : QWidget + The widget in which to look for Panels. + + force_state : bool + If set to True or False, it will change visibility to the value of + force_state. + If not set or set to None, it will flip the current panels state. + """ + panels = widget.findChildren(typhos_panel.TyphosSignalPanel) or [] + visible = all(panel.isVisible() for panel in panels) + + state = not visible + if force_state is not None: + state = force_state + + for panel in panels: + panel.setVisible(state)
+ + +
[docs]def show_empty(widget): + """ + Recursively shows all panels and widgets, empty or not. + + Parameters + ---------- + widget : QWidget + """ + children = widget.findChildren(TyphosDeviceDisplay) or [] + for ch in children: + show_empty(ch) + widget.setVisible(True) + toggle_display(widget, force_state=True)
+ + +
[docs]def hide_empty(widget, process_widget=True): + """ + Recursively hide empty panels and widgets. + + Parameters + ---------- + widget : QWidget + The widget in which to start the recursive search. + + process_widget : bool + Whether or not to process the visibility for the widget. + This is useful since we don't want to hide the top-most + widget otherwise users can't change the visibility back on. + """ + def process(item, recursive=True): + if isinstance(item, TyphosDeviceDisplay) and recursive: + hide_empty(item) + elif isinstance(item, typhos_panel.TyphosSignalPanel): + if recursive: + hide_empty(item) + visible = bool(item._panel_layout.visible_elements) + item.setVisible(visible) + + if isinstance(widget, TyphosDeviceDisplay): + # Check if the template at this display is one of the defaults + # otherwise we are not sure if we can safely change it. + + if widget.current_template not in DEFAULT_TEMPLATES_FLATTEN: + logger.info("Can't hide empty entries in non built-in templates") + return + + children = widget.findChildren(utils.TyphosBase) or [] + for w in children: + process(w) + + if process_widget: + if isinstance(widget, TyphosDeviceDisplay): + overall_status = any(w.isVisible() for w in children) + elif isinstance(widget, typhos_panel.TyphosSignalPanel): + overall_status = bool(widget._panel_layout.visible_elements) + widget.setVisible(overall_status)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/_modules/typhos/func.html b/v2.4.1/_modules/typhos/func.html new file mode 100644 index 000000000..37afd51fe --- /dev/null +++ b/v2.4.1/_modules/typhos/func.html @@ -0,0 +1,729 @@ + + + + + + typhos.func — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for typhos.func

+"""
+Display an arbitrary Python function inside our PyQt UI.
+
+The class :class:`.FunctionDisplay` uses the function annotation language
+described in PEP 3107 to automatically create a widget based on the arguments
+and keywords contained within.
+
+To keep track of parameter information subclassed versions of QWidgets are
+instantiated. Each one is expected to keep track of the parameter it controls
+with the attribute ``parameter``, and each one should return the present value
+with the correct type with the method `get_param_value``. There may be cases
+where these widgets find that the user has entered inappropriate values, in
+this case they should return np.nan to halt the function from being called.
+"""
+import inspect
+import logging
+from functools import partial
+
+import numpy as np
+from numpydoc import docscrape
+from qtpy.QtCore import Property, QSize, Qt, Slot
+from qtpy.QtGui import QFont
+from qtpy.QtWidgets import (QCheckBox, QGroupBox, QHBoxLayout, QLabel,
+                            QLineEdit, QPushButton, QSizePolicy, QSpacerItem,
+                            QVBoxLayout, QWidget)
+
+from .status import TyphosStatusThread
+from .utils import clean_attr, raise_to_operator
+from .widgets import TogglePanel, TyphosDesignerMixin
+
+logger = logging.getLogger(__name__)
+
+
+class ParamWidget(QWidget):
+    """
+    Generic Parameter Widget.
+
+    This creates the QLabel for the parameter and defines the interface
+    required for subclasses of the ParamWidget.
+    """
+    def __init__(self, parameter, default=inspect._empty, parent=None):
+        super().__init__(parent=parent)
+        # Store parameter information
+        self.parameter = parameter
+        self.default = default
+        self.setLayout(QHBoxLayout())
+        # Create our label
+        self.param_label = QLabel(parent=self)
+        self.param_label.setText(clean_attr(parameter))
+        self.layout().addWidget(self.param_label)
+        # Indicate required parameters in bold font
+        if default == inspect._empty:
+            logger.debug("Inferring that %s has no default", parameter)
+            bold = QFont()
+            bold.setBold(True)
+            self.param_label.setFont(bold)
+
+    def get_param_value(self):
+        """Must be redefined by subclasses"""
+        raise NotImplementedError
+
+
+class ParamCheckBox(ParamWidget):
+    """
+    QCheckBox for operator control of boolean values.
+
+    Parameters
+    ----------
+    parameter : str
+        Name of parameter this widget controls.
+
+    default : bool, optional
+        Default state of the box.
+
+    parent : QWidget, optional
+    """
+    def __init__(self, parameter, default=inspect._empty, parent=None):
+        super().__init__(parameter, default=default, parent=parent)
+        self.param_control = QCheckBox(parent=self)
+        self.layout().addWidget(self.param_control)
+        # Configure default QCheckBox position
+        if default != inspect._empty:
+            self.param_control.setChecked(default)
+
+    def get_param_value(self):
+        """
+        Return the checked state of the QCheckBox.
+        """
+        return self.param_control.isChecked()
+
+
+class ParamLineEdit(ParamWidget):
+    """
+    QLineEdit for typed user entry control.
+
+    Parameter
+    ---------
+    parameter : str
+        Name of parameter this widget controls.
+
+    _type : type
+        Type to convert the text to before sending it to the function. All
+        values are initially `QString`s and then they are converted to the
+        specified type. If this raises a ``ValueError`` due to an improperly
+        entered value a ``np.nan`` is returned.
+
+    default : bool, optional
+        Default text for the QLineEdit. This if automatically populated into
+        the QLineEdit field and it is also set as the ``placeHolderText``.
+
+    parent : QWidget, optional
+    """
+    def __init__(self, parameter, _type, default='', parent=None):
+        super().__init__(parameter, default=default, parent=parent)
+        # Store type information
+        self._type = _type
+        # Create our LineEdit
+        # Set our default text
+        self.param_edit = QLineEdit(parent=self)
+        self.param_edit.setAlignment(Qt.AlignCenter)
+        self.layout().addWidget(self.param_edit)
+        # Configure default text of LineEdit
+        # If there is no default, still give some placeholder text
+        # to indicate the type of the command needed
+        if default != inspect._empty:
+            self.param_edit.setText(str(self.default))
+            self.param_edit.setPlaceholderText(str(self.default))
+        elif self._type in (int, float):
+            self.param_edit.setPlaceholderText(str(self._type(0.0)))
+
+    def get_param_value(self):
+        """
+        Return the current value of the QLineEdit converted to :attr:`._type`.
+        """
+        # Cast the current text into our type
+        try:
+            val = self._type(self.param_edit.text())
+        # If not possible, capture the exception and report `np.nan`
+        except ValueError:
+            logger.exception("Could not convert text to %r",
+                             self._type.__name__)
+            val = np.nan
+        return val
+
+
+def parse_numpy_docstring(docstring):
+    '''
+    Parse a numpy docstring for summary and parameter information.
+
+    Parameters
+    ----------
+    docstring : str
+        Docstring to parse.
+
+    Returns
+    -------
+    info : dict
+        info['summary'] is a string summary.
+        info['params'] is a dictionary of parameter name to a list of
+        description lines.
+    '''
+    info = {}
+    parsed = docscrape.NumpyDocString(docstring)
+    info['summary'] = '\n'.join(parsed['Summary'])
+    params = parsed['Parameters']
+
+    # numpydoc v0.8.0 uses just a tuple for parameters, but later versions use
+    # a namedtuple.  here, only assume a tuple:
+    info['params'] = {name: lines for name, type_, lines in params}
+    return info
+
+
+class FunctionDisplay(QGroupBox):
+    """
+    Display controls for an annotated function in a QGroupBox.
+
+    In order to display function arguments in the user interface, the class
+    must be aware of what the type is of each of the parameters. Instead of
+    requiring a user to enter this information manually, the class takes
+    advantage of the function annotation language described in PEP 3107. This
+    allows us to quickly create the appropriate widget for the given parameter
+    based on the type.
+
+    If a function parameter is not given an annotation, we will attempt to
+    infer it from the default value if available. If this is not possible, and
+    the type is not specified in the ``annotations`` dictionary an exception
+    will be raised.
+
+    The created user interface consists of a button to execute the function,
+    the required parameters are always displayed beneath the button, and
+    a :class:`.TogglePanel` object that toggles the view of the optional
+    parameters below.
+
+    Attributes
+    ----------
+    accepted_types : list
+        List of types FunctionDisplay can create widgets for.
+
+    Parameters
+    ----------
+    func : callable
+
+    name : str, optional
+        Name to label the box with, by default this will be the function
+        meeting.
+
+    annotations : dict, optional
+        If the function your are creating a display for is not annotated, you
+        may manually supply types for parameters by passing in a dictionary of
+        name to type mapping.
+
+    hide_params : list, optional
+        List of parameters to exclude from the display. These should have
+        appropriate defaults. By default, ``self``, ``args`` and ``kwargs`` are
+        all excluded.
+
+    parent : QWidget, optional
+    """
+    accepted_types = [bool, str, int, float]
+
+    def __init__(self, func, name=None, annotations=None,
+                 hide_params=None, parent=None):
+        # Function information
+        self.func = func
+        self.signature = inspect.signature(func)
+        self.name = name or self.func.__name__
+        # Initialize parent
+        super().__init__(f'{clean_attr(self.name)} Parameters',
+                         parent=parent)
+        # Ignore certain parameters, args and kwargs by default
+        self.hide_params = ['self', 'args', 'kwargs']
+        if hide_params:
+            self.hide_params.extend(hide_params)
+        # Create basic layout
+        self._layout = QVBoxLayout()
+        self._layout.setSpacing(2)
+        self.setLayout(self._layout)
+        # Create an empty list to fill later with parameter widgets
+        self.param_controls = list()
+        # Add our button to execute the function
+        self.execute_button = QPushButton()
+
+        self.docs = {'summary': func.__doc__ or '',
+                     'params': {}
+                     }
+
+        if func.__doc__ is not None:
+            try:
+                self.docs.update(**parse_numpy_docstring(func.__doc__))
+            except Exception as ex:
+                logger.warning('Unable to parse docstring for function %s: %s',
+                               name, ex, exc_info=ex)
+
+        self.execute_button.setToolTip(self.docs['summary'])
+
+        self.execute_button.setText(clean_attr(self.name))
+        self.execute_button.clicked.connect(self.execute)
+        self._layout.addWidget(self.execute_button)
+        # Add a panel for the optional parameters
+        self.optional = TogglePanel("Optional Parameters")
+        self.optional.contents = QWidget()
+        self.optional.contents.setLayout(QVBoxLayout())
+        self.optional.contents.layout().setSpacing(2)
+        self.optional.layout().addWidget(self.optional.contents)
+        self.optional.show_contents(False)
+        self._layout.addWidget(self.optional)
+        self._layout.addItem(QSpacerItem(10, 5, vPolicy=QSizePolicy.Expanding))
+        # Create parameters from function signature
+        annotations = annotations or dict()
+        for param in [param for param in self.signature.parameters.values()
+                      if param.name not in self.hide_params]:
+            logger.debug("Adding parameter %s ", param.name)
+            # See if we received a manual annotation for this parameter
+            if param.name in annotations:
+                _type = annotations[param.name]
+                logger.debug("Found manually specified type %r",
+                             _type.__name__)
+            # Try and get the type from the function annotation
+            elif param.annotation != inspect._empty:
+                _type = param.annotation
+                logger.debug("Found annotated type %r ",
+                             _type.__name__)
+            # Try and get the type from the default value
+            elif param.default != inspect._empty:
+                _type = type(param.default)
+                logger.debug("Gathered type %r from parameter default ",
+                             _type.__name__)
+            # If we don't have a default value or an annotation,
+            # we can not make a widget for this parameter. Since
+            # this is a required variable (no default), the function
+            # will not work without it. Raise an Exception
+            else:
+                raise TypeError("Parameter {} has an unspecified "
+                                "type".format(param.name))
+
+            # Add our parameter
+            self.add_parameter(param.name, _type, default=param.default)
+        # Hide optional parameter widget if there are no such parameters
+        if not self.optional_params:
+            self.optional.hide()
+
+    @property
+    def required_params(self):
+        """
+        Required parameters.
+        """
+        parameters = self.signature.parameters
+        return [param.parameter for param in self.param_controls
+                if parameters[param.parameter].default == inspect._empty]
+
+    @property
+    def optional_params(self):
+        """
+        Optional parameters.
+        """
+        parameters = self.signature.parameters
+        return [param.parameter for param in self.param_controls
+                if parameters[param.parameter].default != inspect._empty]
+
+    @Slot()
+    def execute(self):
+        """
+        Execute :attr:`.func`.
+
+        This takes the parameters configured by the :attr:`.param_controls`
+        widgets and passes them into the given callable. All generated
+        exceptions are captured and logged.
+        """
+        logger.info("Executing %s ...", self.name)
+        # If our function does not take any argument
+        # just pass it on. Otherwise, collect information
+        # from the appropriate widgets
+        if not self.signature.parameters:
+            func = self.func
+        else:
+            kwargs = dict()
+            # Gather information from parameter widgets
+            for button in self.param_controls:
+                logger.debug("Gathering parameters for %s ...",
+                             button.parameter)
+                val = button.get_param_value()
+                logger.debug("Received %s", val)
+                # Watch for NaN values returned from widgets
+                # This indicates that there was improper information given
+                if np.isnan(val):
+                    logger.error("Invalid information supplied for %s "
+                                 "parameter", button.parameter)
+                    return
+                kwargs[button.parameter] = val
+            # Button up function call with partial to try below
+            func = partial(self.func, **kwargs)
+        try:
+            # Execute our function
+            func()
+        except Exception:
+            logger.exception("Exception while executing function")
+        else:
+            logger.info("Operation Complete")
+
+    def add_parameter(self, name, _type, default=inspect._empty, tooltip=None):
+        """
+        Add a parameter to the function display.
+
+        Parameters
+        ----------
+        name : str
+            Parameter name.
+
+        _type : type
+            Type of variable that we are expecting the user to input.
+
+        default : any, optional
+            Default value for the parameter.
+
+        tooltip : str, optional
+            Tooltip to use for the control widget.  If not specified, docstring
+            parameter information will be used if available to generate a
+            default.
+
+        Returns
+        -------
+        widget : QWidget
+            The generated widget.
+        """
+        if tooltip is None:
+            tooltip_header = f'{name} - {_type.__name__}'
+            tooltip = [
+                tooltip_header,
+                '-' * len(tooltip_header)
+            ]
+
+            if default != inspect._empty:
+                tooltip.append(f'Default: {default}')
+
+            try:
+                doc_param = self.docs['params'][name]
+            except KeyError:
+                logger.debug('Parameter information is not available '
+                             'for %s(%s)', self.name, name)
+            else:
+                if doc_param:
+                    tooltip.extend(doc_param)
+
+            # If the tooltip is just the header, remove the dashes underneath:
+            if len(tooltip) == 2:
+                tooltip = tooltip[:1]
+            tooltip = '\n'.join(tooltip)
+
+        # Create our parameter control widget
+        # QCheckBox field
+        if _type == bool:
+            cntrl = ParamCheckBox(name, default=default)
+        else:
+            # Check if this is a valid type
+            if _type not in self.accepted_types:
+                raise TypeError("Parameter {} has type {} which can not "
+                                "be represented in a widget"
+                                "".format(name, _type.__name__))
+            # Create our QLineEdit
+            cntrl = ParamLineEdit(name, default=default, _type=_type)
+        # Add our button to the widget
+        # If it is required add it above the panel so that it is always
+        # visisble. Otherwise, add it to the hideable panel
+        self.param_controls.append(cntrl)
+        if default == inspect._empty:
+            self.layout().insertWidget(len(self.required_params), cntrl)
+        else:
+            # If this is the first optional parameter,
+            # show the whole optional panel
+            if self.optional.isHidden():
+                self.optional.show()
+            # Add the control widget to our contents
+            self.optional.contents.layout().addWidget(cntrl)
+
+        cntrl.param_label.setToolTip(tooltip)
+        return cntrl
+
+    def sizeHint(self):
+        """Suggested size."""
+        return QSize(175, 100)
+
+
+
[docs]class FunctionPanel(TogglePanel): + """ + Function Panel. + + Similar to :class:`.SignalPanel` but instead displays a set of function + widgets arranged in a row. Each provided method has a + :class:`.FunctionDisplay` generated for it an added to the layout. + + Parameters + ---------- + methods : list of callables, optional + List of callables to add to the FunctionPanel. + + parent : QWidget + """ + def __init__(self, methods=None, parent=None): + # Initialize parent + super().__init__("Functions", parent=parent) + self.contents = QWidget() + self.layout().addWidget(self.contents) + # Create Layout + self.contents.setLayout(QHBoxLayout()) + # Add two spacers to center our functions without + # expanding them + self.contents.layout().addItem(QSpacerItem(10, 20)) + self.contents.layout().addItem(QSpacerItem(10, 20)) + # Add methods + methods = methods or list() + self.methods = dict() + for method in methods: + self.add_method(method) + +
[docs] def add_method(self, func, *args, **kwargs): + """ + Add a :class:`.FunctionDisplay`. + + Parameters + ---------- + func : callable + Annotated callable function. + + args, kwargs: + All additional parameters are passed directly to the + :class:`.FunctionDisplay` constructor. + """ + # Create method display + func_name = kwargs.get('name', func.__name__) + logger.debug("Adding method %s ...", func_name) + widget = FunctionDisplay(func, *args, **kwargs) + # Store for posterity + self.methods[func_name] = widget + # Add to panel. Make sure that if this is + # the first added method that the panel is visible + self.show_contents(True) + self.contents.layout().insertWidget(len(self.methods), + widget)
+ + +
[docs]class TyphosMethodButton(QPushButton, TyphosDesignerMixin): + """ + QPushButton to access a method of a Device. + + The function provided by the loaded device and the :attr:`.method_name` + will be run when the button is clicked. If ``use_status`` is set to True, + the button will be disabled while the ``Status`` object is active. + """ + _min_visible_operation = 0.1 + _max_allowed_operation = 10.0 + + def __init__(self, parent=None): + self._method = '' + self._use_status = False + super().__init__(parent=parent) + self._status_thread = None + self.clicked.connect(self.execute) + self.devices = list() + +
[docs] def add_device(self, device): + """ + Add a new device to the widget. + + Parameters + ---------- + device : ophyd.Device + """ + logger.debug("Adding device %s ...", device.name) + self.devices.append(device)
+ + @Property(str) + def method_name(self): + """Name of method on provided Device to execute.""" + return self._method + + @method_name.setter + def method_name(self, value): + self._method = value + + @Property(bool) + def use_status(self): + """ + Use the status to enable and disable the button. + """ + return self._use_status + + @use_status.setter + def use_status(self, value): + self._use_status = value + +
[docs] @Slot() + def execute(self): + """Execute the method given by ``method_name``.""" + if not self.devices: + logger.error("No device loaded into the object") + return + device = self.devices[0] + logger.debug("Grabbing method %r from %r ...", + self.method_name, device.name) + try: + method = getattr(device, self.method_name) + logger.debug("Executing method ...") + status = method() + except Exception as exc: + logger.exception("Error executing method %r.", + self.method_name) + raise_to_operator(exc) + return + if self.use_status: + logger.debug("Tearing down any old status threads ...") + if self._status_thread and self._status_thread.isRunning(): + # We should usually never reach this line of code because the + # button should be disabled while the status object is not + # done. However, it is good to catch this to make sure that we + # only have one active thread at a time + logger.debug("Removing running TyphosStatusThread!") + self._status_thread.disconnect() + + self._status_thread = None + logger.debug("Setting up new status thread ...") + self._status_thread = TyphosStatusThread( + status, start_delay=self._min_visible_operation, + timeout=self._max_allowed_operation, + parent=self, + ) + + def status_started(): + self.setEnabled(False) + + def status_finished(result): + self.setEnabled(True) + + self._status_thread.status_started.connect(status_started) + self._status_thread.status_finished.connect(status_finished) + + # Connect the finished signal so that even in the worst case + # scenario, we re-enable the button. Almost always the button will + # be ended by the status_finished signal + self._status_thread.finished.connect(partial(status_finished, + True)) + logger.debug("Starting TyphosStatusThread ...") + self._status_thread.start()
+ +
[docs] @classmethod + def from_device(cls, device, parent=None): + """Create a TyphosMethodButton from a device.""" + instance = cls(parent=parent) + instance.add_device(device) + return instance
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/_modules/typhos/panel.html b/v2.4.1/_modules/typhos/panel.html new file mode 100644 index 000000000..a0734863a --- /dev/null +++ b/v2.4.1/_modules/typhos/panel.html @@ -0,0 +1,1006 @@ + + + + + + typhos.panel — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for typhos.panel

+"""
+Layouts and container widgets that show a "panel" of signals.
+
+Layouts:
+    * :class:`SignalPanel`
+    * :class:`CompositeSignalPanel`
+
+Container widgets:
+    * :class:`TyphosSignalPanel`
+    * :class:`TyphosCompositeSignalPanel`
+"""
+
+import functools
+import logging
+from functools import partial
+
+import ophyd
+from ophyd import Kind
+from ophyd.signal import EpicsSignal, EpicsSignalRO
+from qtpy import QtCore, QtWidgets
+from qtpy.QtCore import Q_ENUMS, Property
+
+from . import display, utils
+from .cache import get_global_widget_type_cache
+from .utils import TyphosBase
+from .widgets import SignalWidgetInfo, TyphosDesignerMixin
+
+logger = logging.getLogger(__name__)
+
+
+class SignalOrder:
+    """
+    Options for sorting signals.
+
+    This can be used as a base class for subclasses of
+    :class:`QtWidgets.QWidget`, allowing this to be used in
+    :class:`QtCore.Property` and therefore in the Qt designer.
+    """
+
+    byKind = 0
+    byName = 1
+
+
+DEFAULT_KIND_ORDER = (Kind.hinted, Kind.normal, Kind.config, Kind.omitted)
+
+
+def _get_component_sorter(signal_order, *, kind_order=None):
+    """
+    Get a sorting function for :class:`ophyd.device.ComponentWalk` entries.
+
+    Parameters
+    ----------
+    signal_order : SignalOrder
+        Order for signals.
+
+    kind_order : list, optional
+        Order for Kinds, defaulting to ``DEFAULT_KIND_ORDER``.
+    """
+    kind_order = kind_order or DEFAULT_KIND_ORDER
+
+    def kind_sorter(walk):
+        """Sort by kind."""
+        return (kind_order.index(walk.item.kind), walk.dotted_name)
+
+    def name_sorter(walk):
+        """Sort by name."""
+        return walk.dotted_name
+
+    return {SignalOrder.byKind: kind_sorter,
+            SignalOrder.byName: name_sorter
+            }.get(signal_order, name_sorter)
+
+
+class SignalPanelRowLabel(QtWidgets.QLabel):
+    """
+    A row label for a signal panel.
+
+    This subclass does not contain any special functionality currently, but
+    remains a special class for ease of stylesheet configuration and label
+    disambiguation.
+    """
+
+
+
[docs]class SignalPanel(QtWidgets.QGridLayout): + """ + Basic panel layout for :class:`ophyd.Signal` and other ophyd objects. + + This panel does not support hierarchical display of signals; rather, it + flattens a device hierarchy showing all signals in the same area. + + Parameters + ---------- + signals : OrderedDict, optional + Signals to include in the panel. + Parent of panel. + + Attributes + ---------- + loading_complete : QtCore.Signal + A signal indicating that loading of the panel has completed. + + NUM_COLS : int + The number of columns in the layout. + + COL_LABEL : int + The column number for the row label. + + COL_READBACK : int + The column number for the readback widget. + + COL_SETPOINT : int + The column number for the setpoint widget. + + See also + -------- + :class:`CompositeSignalPanel`. + """ + + NUM_COLS = 3 + COL_LABEL = 0 + COL_READBACK = 1 + COL_SETPOINT = 2 + + loading_complete = QtCore.Signal(list) + + def __init__(self, signals=None): + super().__init__() + + self.signal_name_to_info = {} + self._row_count = 0 + self._devices = [] + + # Make sure setpoint/readback share space evenly + self.setColumnStretch(self.COL_READBACK, 1) + self.setColumnStretch(self.COL_SETPOINT, 1) + + get_global_widget_type_cache().widgets_determined.connect( + self._got_signal_widget_info, QtCore.Qt.QueuedConnection) + + if signals: + for name, sig in signals.items(): + self.add_signal(sig, name) + + @property + def signals(self): + """ + Get all instantiated signals, omitting components. + + Returns + ------- + signals : dict + With the form: ``{signal_name: signal}``. + """ + return { + name: info['signal'] + for name, info in list(self.signal_name_to_info.items()) + if info['signal'] is not None + } + + @property + def visible_signals(self): + """ + Get all signals visible according to filters, omitting components. + + Returns + ------- + signals : dict + With the form: ``{signal_name: signal}``. + """ + return { + name: info['signal'] + for name, info in list(self.signal_name_to_info.items()) + if info['signal'] is not None and info['visible'] + } + + visible_elements = visible_signals + + @property + def row_count(self): + """Get the number of filled-in rows.""" + return self._row_count + + @QtCore.Slot(object, SignalWidgetInfo) + def _got_signal_widget_info(self, obj, info): + """ + Slot: Received information on how to make widgets for ``obj``. + + Parameters + ---------- + obj : ophyd.OphydObj + The object that corresponds to the given widget information. + + info : SignalWidgetInfo + The associated widget information. + """ + try: + sig_info = self.signal_name_to_info[obj.name] + except KeyError: + return + + if sig_info['widget_info'] is not None: + # Only add widgets on the first callback + # TODO: debug why multiple calls happen + return + + sig_info['widget_info'] = info + row = sig_info['row'] + + # Remove the 'loading...' animation if it's there + item = self.itemAtPosition(row, self.COL_SETPOINT) + if item: + val_widget = item.widget() + if isinstance(val_widget, utils.TyphosLoading): + self.removeItem(item) + val_widget.deleteLater() + + widgets = [None] + if info.read_cls is not None: + widgets.append(info.read_cls(**info.read_kwargs)) + + if info.write_cls is not None: + widgets.append(info.write_cls(**info.write_kwargs)) + + self._update_row(row, widgets) + + visible = sig_info['visible'] + for widget in widgets[1:]: + widget.setVisible(visible) + + signal_pairs = list(self.signal_name_to_info.items()) + if all(sig_info['widget_info'] is not None + for _, sig_info in signal_pairs): + self.loading_complete.emit([name for name, _ in signal_pairs]) + + def _create_row_label(self, attr, dotted_name, tooltip): + """Create a row label (i.e., the one used to display the name).""" + label_text = self.label_text_from_attribute(attr, dotted_name) + label = SignalPanelRowLabel(label_text) + label.setObjectName(dotted_name) + if tooltip is not None: + label.setToolTip(tooltip) + return label + +
[docs] def add_signal(self, signal, name=None, *, tooltip=None): + """ + Add a signal to the panel. + + The type of widget control that is drawn is dependent on + :attr:`_read_pv`, and :attr:`_write_pv`. attributes. + + If widget information for the given signal is available in the global + cache, the widgets will be created immediately. Otherwise, a row will + be reserved and widgets created upon signal connection and background + description callback. + + Parameters + ---------- + signal : EpicsSignal, EpicsSignalRO + Signal to create a widget. + + name : str, optional + The name to be used for the row label. This defaults to + ``signal.name``. + + Returns + ------- + row : int + Row number that the signal information was added to in the + `SignalPanel.layout()``. + """ + name = name or signal.name + if signal.name in self.signal_name_to_info: + return + + logger.debug("Adding signal %s (%s)", signal.name, name) + + label = self._create_row_label(name, name, tooltip) + loading = utils.TyphosLoading( + timeout_message='Connection timed out.' + ) + + loading_tooltip = ['Connecting to:'] + list({ + getattr(signal, attr) + for attr in ('setpoint_pvname', 'pvname') if hasattr(signal, attr) + }) + loading.setToolTip('\n'.join(loading_tooltip)) + + row = self.add_row(label, loading) + self.signal_name_to_info[signal.name] = dict( + row=row, + signal=signal, + component=None, + widget_info=None, + create_signal=None, + visible=True, + ) + + self._connect_signal(signal) + return row
+ + def _connect_signal(self, signal): + """Instantiate widgets for the given signal using the global cache.""" + monitor = get_global_widget_type_cache() + item = monitor.get(signal) + if item is not None: + self._got_signal_widget_info(signal, item) + # else: - this will happen during a callback + + def _add_component(self, device, attr, dotted_name, component): + """ + Add a component which may be instantiated later. + + Parameters + ---------- + device : ophyd.Device + The parent device for the component. + + attr : str + The attribute name of the component. + + dotted_name : str + The full dotted name of the component. + + component : ophyd.Component + The component itself. + """ + if dotted_name in self.signal_name_to_info: + return + + logger.debug("Adding component %s", dotted_name) + + label = self._create_row_label( + attr, dotted_name, tooltip=component.doc or '') + row = self.add_row(label, None) # utils.TyphosLoading()) + self.signal_name_to_info[dotted_name] = dict( + row=row, + signal=None, + widget_info=None, + component=component, + create_signal=functools.partial(getattr, device, dotted_name), + visible=False, + ) + + return row + +
[docs] def label_text_from_attribute(self, attr, dotted_name): + """ + Get label text for a given attribute. + + For a basic signal panel, use the full dotted name. This is because + this panel flattens the device hierarchy, and using only the last + attribute name may lead to ambiguity or name clashes. + """ + return dotted_name
+ +
[docs] def add_row(self, *widgets, **kwargs): + """ + Add ``widgets`` to the next row. + + If fewer than ``NUM_COLS`` widgets are given, the last widget will be + adjusted automatically to span the remaining columns. + + Parameters + ---------- + *widgets + List of :class:`QtWidgets.QWidget`. + + Returns + ------- + row : int + The row number. + """ + row = self._row_count + self._row_count += 1 + + if widgets: + self._update_row(row, widgets, **kwargs) + + return row
+ + def _update_row(self, row, widgets, **kwargs): + """ + Update ``row`` to contain ``widgets``. + + If fewer widgets than ``NUM_COLS`` are given, the last widget will be + adjusted automatically to span the remaining columns. + + Parameters + ---------- + row : int + The row number. + + widgets : list of :class:`QtWidgets.QWidget` + If ``None`` is found, the cell will be skipped. + + **kwargs + Passed into ``addWidget``. + """ + for col, item in enumerate(widgets[:-1]): + if item is not None: + self.addWidget(item, row, col, **kwargs) + + last_widget = widgets[-1] + if last_widget is not None: + # Column-span the last widget over the remaining columns: + last_column = len(widgets) - 1 + colspan = self.NUM_COLS - last_column + self.addWidget(last_widget, row, last_column, 1, colspan, **kwargs) + +
[docs] def add_pv(self, read_pv, name, write_pv=None): + """ + Add a row, given PV names. + + Parameters + --------- + read_pv : str + The readback PV name. + + name : str + Name of signal to display. + + write_pv : str, optional + The setpoint PV name. + + Returns + ------- + row : int + Row number that the signal information was added to in the + `SignalPanel.layout()``. + """ + logger.debug("Adding PV %s", name) + # Configure optional write PV settings + if write_pv: + sig = EpicsSignal(read_pv, name=name, write_pv=write_pv) + else: + sig = EpicsSignalRO(read_pv, name=name) + return self.add_signal(sig, name)
+ + @staticmethod + def _apply_name_filter(filter_by, *items): + """ + Apply the name filter. + + Parameters + ---------- + filter_by : str + The name filter text. + + *items + A list of strings to check for matches with. + """ + if not filter_by: + return True + + return any(filter_by in item for item in items) + + def _should_show(self, kind, name, *, kinds, name_filter): + """ + Based on the filter settings, indicate if ``signal`` should be shown. + + Parameters + ---------- + kind : ophyd.Kind + The kind of the signal. + + name : str + The name of the signal. + + kinds : list of :class:`ophyd.Kind` + Kinds that should be shown. + + name_filter : str + Name filter text. + + Returns + ------- + should_show : bool + """ + if kind not in kinds: + return False + return self._apply_name_filter(name_filter, name) + + def _set_visible(self, signal_name, visible): + """ + Change the visibility of ``signal_name`` to ``visible``. + + Parameters + ---------- + signal_name : str + The signal name to change the visibility of. + + visible : bool + Change the visibility of the row to this. + """ + info = self.signal_name_to_info[signal_name] + info['visible'] = bool(visible) + row = info['row'] + for col in range(self.NUM_COLS): + item = self.itemAtPosition(row, col) + if item: + widget = item.widget() + if widget is not None: + widget.setVisible(visible) + + if not visible or info['signal'] is not None: + return + + # Create the signal if we're displaying it for the first time. + create_func = info['create_signal'] + if create_func is None: + # A signal we shouldn't try to create again + return + + try: + info['signal'] = signal = create_func() + except Exception as ex: + logger.exception('Failed to create signal %s: %s', signal_name, ex) + # Stop it from another attempt + info['create_signal'] = None + return + + logger.debug('Instantiating a not-yet-created signal from a ' + 'component: %s', signal.name) + if signal.name != signal_name: + # This is, for better or worse, possible; does not support the case + # of changing the name after __init__ + self.signal_name_to_info[signal.name] = info + del self.signal_name_to_info[signal_name] + self._connect_signal(signal) + +
[docs] def filter_signals(self, kinds, name_filter=None): + """ + Filter signals based on the given kinds. + + Parameters + ---------- + kinds : list of :class:`ophyd.Kind` + List of kinds to show. + + name_filter : str, optional + Additionally filter signals by name. + """ + for name, info in list(self.signal_name_to_info.items()): + item = info['signal'] or info['component'] + visible = self._should_show(item.kind, name, + kinds=kinds, name_filter=name_filter) + self._set_visible(name, visible) + + self.update()
+ # utils.dump_grid_layout(self) + + @property + def _filter_settings(self): + """Get the current filter settings from the owner widget.""" + return self.parent().filter_settings + +
[docs] def add_device(self, device): + """Typhos hook for adding a new device.""" + self.clear() + self._devices.append(device) + + sorter = _get_component_sorter(self.parent().sortBy) + non_devices = [ + walk + for walk in sorted(device.walk_components(), key=sorter) + if not issubclass(walk.item.cls, ophyd.Device) + ] + + for walk in non_devices: + self._maybe_add_signal(device, walk.item.attr, walk.dotted_name, + walk.item) + + self.setSizeConstraint(self.SetMinimumSize)
+ + def _maybe_add_signal(self, device, attr, dotted_name, component): + """ + With the filter settings, add either the signal or a component stub. + + If the component does not match the current filter settings, a + stub will be added that can be filled in later should the filter + settings change. + + If the component matches the current filter settings, it will be + instantiated and widgets will be added when the signal is connected. + + Parameters + ---------- + device : ophyd.Device + The device owner. + + attr : str + The signal's attribute name. + + dotted_name : str + The signal's dotted name. + + component : ophyd.Component + The component class used to generate the instance. + """ + if component.lazy: + kind = component.kind + else: + try: + signal = getattr(device, dotted_name) + except Exception as ex: + logger.warning('Failed to get signal %r from device %s: %s', + dotted_name, device.name, ex, exc_info=True) + return + + kind = signal.kind + + if self._should_show(kind, dotted_name, **self._filter_settings): + try: + with ophyd.do_not_wait_for_lazy_connection(device): + signal = getattr(device, dotted_name) + except Exception as ex: + logger.warning('Failed to get signal %r from device %s: %s', + dotted_name, device.name, ex, exc_info=True) + return + + return self.add_signal(signal, name=attr, tooltip=component.doc) + + return self._add_component(device, attr, dotted_name, component) + +
[docs] def clear(self): + """Clear the SignalPanel.""" + logger.debug("Clearing layout %r ...", self) + utils.clear_layout(self) + self._devices.clear() + self.signal_name_to_info.clear()
+ + +
[docs]class TyphosSignalPanel(TyphosBase, TyphosDesignerMixin, SignalOrder): + """ + Panel of Signals for a given device, using :class:`SignalPanel`. + + Parameters + ---------- + parent : QtWidgets.QWidget, optional + The parent widget. + + init_channel : str, optional + The PyDM channel with which to initialize the widget. + """ + + Q_ENUMS(SignalOrder) # Necessary for display in Designer + SignalOrder = SignalOrder # For convenience + # From top of page to bottom + kind_order = (Kind.hinted, Kind.normal, + Kind.config, Kind.omitted) + _panel_class = SignalPanel + updated = QtCore.Signal() + + _kind_to_property = { + 'hinted': 'showHints', + 'normal': 'showNormal', + 'config': 'showConfig', + 'omitted': 'showOmitted', + } + + def __init__(self, parent=None, init_channel=None): + super().__init__(parent=parent) + # Create a SignalPanel layout to be modified later + self._panel_layout = self._panel_class() + self.setLayout(self._panel_layout) + self._name_filter = '' + # Add default Kind values + self._kinds = dict.fromkeys([kind.name for kind in Kind], True) + self._signal_order = SignalOrder.byKind + + self.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu) + self.contextMenuEvent = self.open_context_menu + + def _get_kind(self, kind): + """Property getter for show[kind].""" + return self._kinds[kind] + + def _set_kind(self, value, kind): + """Property setter for show[kind] = value.""" + # If we have a new value store it + if value != self._kinds[kind]: + # Store it internally + self._kinds[kind] = value + # Remodify the layout for the new Kind + self._update_panel() + + @property + def filter_settings(self): + """Get the filter settings dictionary.""" + return dict( + name_filter=self.nameFilter, + kinds=self.show_kinds, + ) + + def _update_panel(self): + """Apply filters and emit the update signal.""" + self._panel_layout.filter_signals(**self.filter_settings) + self.updated.emit() + + @property + def show_kinds(self): + """Get a list of the :class:`ophyd.Kind` that should be shown.""" + return [kind for kind in Kind if self._kinds[kind.name]] + + # Kind Configuration pyqtProperty + showHints = Property(bool, + partial(_get_kind, kind='hinted'), + partial(_set_kind, kind='hinted'), + doc='Show ophyd.Kind.hinted signals') + showNormal = Property(bool, + partial(_get_kind, kind='normal'), + partial(_set_kind, kind='normal'), + doc='Show ophyd.Kind.normal signals') + showConfig = Property(bool, + partial(_get_kind, kind='config'), + partial(_set_kind, kind='config'), + doc='Show ophyd.Kind.config signals') + showOmitted = Property(bool, + partial(_get_kind, kind='omitted'), + partial(_set_kind, kind='omitted'), + doc='Show ophyd.Kind.omitted signals') + + @Property(str) + def nameFilter(self): + """Get or set the current name filter.""" + return self._name_filter + + @nameFilter.setter + def nameFilter(self, name_filter): + if name_filter != self._name_filter: + self._name_filter = name_filter.strip() + self._update_panel() + + @Property(SignalOrder) + def sortBy(self): + """Get or set the order that the signals will be placed in layout.""" + return self._signal_order + + @sortBy.setter + def sortBy(self, value): + if value != self._signal_order: + self._signal_order = value + self._update_panel() + +
[docs] def add_device(self, device): + """Typhos hook for adding a new device.""" + self.devices.clear() + super().add_device(device) + # Configure the layout for the new device + self._panel_layout.add_device(device) + self._update_panel()
+ +
[docs] def set_device_display(self, display): + """Typhos hook for when the TyphosDeviceDisplay is associated.""" + self.display = display
+ +
[docs] def generate_context_menu(self): + """Generate a context menu for this TyphosSignalPanel.""" + menu = QtWidgets.QMenu(parent=self) + menu.addSection('Kinds') + for kind, property_name in self._kind_to_property.items(): + def selected(new_value, *, name=property_name): + setattr(self, name, new_value) + + action = menu.addAction('Show &' + kind) + action.setCheckable(True) + action.setChecked(getattr(self, property_name)) + action.triggered.connect(selected) + return menu
+ +
[docs] def open_context_menu(self, ev): + """ + Open a context menu when the Default Context Menu is requested. + + Parameters + ---------- + ev : QEvent + """ + menu = self.generate_context_menu() + menu.exec_(self.mapToGlobal(ev.pos()))
+ + +
[docs]class CompositeSignalPanel(SignalPanel): + """ + Composite panel layout for :class:`ophyd.Signal` and other ophyd objects. + + Contrasted to :class:`SignalPanel`, this class retains the hierarchy built + into an :class:`ophyd.Device` hierarchy. Individual signals mix in with + sub-device displays, which may or may not have custom screens. + + Attributes + ---------- + loading_complete : QtCore.Signal + A signal indicating that loading of the panel has completed. + + NUM_COLS : int + The number of columns in the layout. + + COL_LABEL : int + The column number for the row label. + + COL_READBACK : int + The column number for the readback widget. + + COL_SETPOINT : int + The column number for the setpoint widget. + """ + + _qt_designer_ = { + "group": "Typhos Widgets", + "is_container": False, + } + + def __init__(self): + super().__init__(signals=None) + self._containers = {} + +
[docs] def label_text_from_attribute(self, attr, dotted_name): + """Get label text for a given attribute.""" + # For a hierarchical signal panel, use only the attribute name. + return attr
+ +
[docs] def add_sub_device(self, device, name): + """ + Add a sub-device to the next row. + + Parameters + ---------- + device : ophyd.Device + The device to add. + + name : str + The name/label to go with the device. + """ + logger.debug('%s adding sub-device: %s (%s)', self.__class__.__name__, + device.name, device.__class__.__name__) + container = display.TyphosDeviceDisplay(scrollable=False, + composite_heuristics=True, + nested=True) + self._containers[name] = container + self.add_row(container) + container.add_device(device)
+ +
[docs] def add_device(self, device): + """Typhos hook for adding a new device.""" + # TODO: note that this does not call super + # super().add_device(device) + self._devices.append(device) + + logger.debug('%s signals from device: %s', self.__class__.__name__, + device.name) + + for attr, component in utils._get_top_level_components(type(device)): + dotted_name = f'{device.name}.{attr}' + if issubclass(component.cls, ophyd.Device): + sub_device = getattr(device, attr) + self.add_sub_device(sub_device, name=dotted_name) + else: + self._maybe_add_signal(device, attr, attr, component)
+ + @property + def visible_elements(self): + """Return all visible signals and components.""" + sigs = self.visible_signals + containers = { + name: cont + for name, cont in self._containers.items() if cont.isVisible() + } + sigs.update(containers) + return sigs
+ + +
[docs]class TyphosCompositeSignalPanel(TyphosSignalPanel): + """ + Hierarchical panel for a device, using :class:`CompositeSignalPanel`. + + Parameters + ---------- + parent : QtWidgets.QWidget, optional + The parent widget. + + init_channel : str, optional + The PyDM channel with which to initialize the widget. + """ + + _panel_class = CompositeSignalPanel
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/_modules/typhos/plugins/core.html b/v2.4.1/_modules/typhos/plugins/core.html new file mode 100644 index 000000000..508cf7a27 --- /dev/null +++ b/v2.4.1/_modules/typhos/plugins/core.html @@ -0,0 +1,393 @@ + + + + + + typhos.plugins.core — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for typhos.plugins.core

+"""
+Module Docstring
+"""
+import logging
+
+import numpy as np
+from ophyd.utils.epics_pvs import AlarmSeverity, _type_map
+from pydm.data_plugins.plugin import PyDMConnection, PyDMPlugin
+from qtpy.QtCore import Qt, Slot
+
+from ..utils import raise_to_operator
+
+logger = logging.getLogger(__name__)
+
+signal_registry = dict()
+
+
+
[docs]def register_signal(signal): + """ + Add a new Signal to the registry. + + The Signal object is kept within ``signal_registry`` for reference by name + in the :class:`.SignalConnection`. Signals can be added multiple times, + but only the first register_signal call for each unique signal name + has any effect. + + Signals can be referenced by their ``name`` attribute or by their + full dotted path starting from the parent's name. + """ + # Pick all the name aliases (name, dotted path) + if signal is signal.root: + names = (signal.name,) + else: + # .dotted_name does not include the root device's name + names = ( + signal.name, + '.'.join((signal.root.name, signal.dotted_name)), + ) + # Warn the user if they are adding twice + for name in names: + if name in signal_registry: + # Case 1: harmless re-add + if signal_registry[name] is signal: + logger.debug( + "The signal named %s is already registered!", + name, + ) + # Case 2: harmful overwrite! Name collision! + else: + logger.warning( + "A different signal named %s is already registered!", + name, + ) + return + logger.debug("Registering signal with names %s", names) + for name in names: + signal_registry[name] = signal
+ + +
[docs]class SignalConnection(PyDMConnection): + """ + Connection to monitor an Ophyd Signal. + + This is meant as a generalized connection to any type of Ophyd Signal. It + handles reporting updates to listeners as well as pushing new values that + users request in the PyDM interface back to the underlying signal. + + The signal `data_type` is used to inform PyDM on the Python type that the + signal will expect and emit. It is expected that this type is static + through the execution of the application. + + Attributes + ---------- + signal : ophyd.Signal + Stored signal object. + """ + supported_types = [int, float, str, np.ndarray] + + def __init__(self, channel, address, protocol=None, parent=None): + # Create base connection + super().__init__(channel, address, protocol=protocol, parent=parent) + self.signal_type = None + # Collect our signal + self.signal = signal_registry[address] + # Subscribe to updates from Ophyd + self.value_cid = self.signal.subscribe( + self.send_new_value, + event_type=self.signal.SUB_VALUE, + ) + self.meta_cid = self.signal.subscribe( + self.send_new_meta, + event_type=self.signal.SUB_META, + ) + # Add listener + self.add_listener(channel) + +
[docs] def cast(self, value): + """ + Cast a value to the correct Python type based on ``signal_type``. + + If ``signal_type`` is not set, the result of ``ophyd.Signal.describe`` + is used to determine what the correct Python type for value is. We need + to be aware of the correct Python type so that we can emit the value + through the correct signal and convert values returned by the widget to + the correct type before handing them to Ophyd Signal. + """ + # If this is the first time we are receiving a new value note the type + # We make the assumption that signals do not change types during a + # connection + if not self.signal_type: + dtype = self.signal.describe()[self.signal.name]['dtype'] + # Only way this raises a KeyError is if ophyd is confused + self.signal_type = _type_map[dtype][0] + logger.debug("Found signal type %r for %r. Using Python type %r", + dtype, self.signal.name, self.signal_type) + + logger.debug("Casting %r to %r", value, self.signal_type) + if self.signal_type is np.ndarray: + value = np.asarray(value) + else: + value = self.signal_type(value) + return value
+ +
[docs] @Slot(int) + @Slot(float) + @Slot(str) + @Slot(np.ndarray) + def put_value(self, new_val): + """ + Pass a value from the UI to Signal. + + We are not guaranteed that this signal is writeable so catch exceptions + if they are created. We attempt to cast the received value into the + reported type of the signal unless it is of type ``np.ndarray``. + """ + try: + new_val = self.cast(new_val) + logger.debug("Putting value %r to %r", new_val, self.address) + self.signal.put(new_val) + except Exception as exc: + logger.exception("Unable to put %r to %s", new_val, self.address) + raise_to_operator(exc)
+ +
[docs] def send_new_value(self, value=None, **kwargs): + """ + Update the UI with a new value from the Signal. + """ + try: + value = self.cast(value) + self.new_value_signal[self.signal_type].emit(value) + except Exception: + logger.exception("Unable to update %r with value %r.", + self.signal.name, value)
+ +
[docs] def send_new_meta( + self, + connected=None, + write_access=None, + severity=None, + precision=None, + units=None, + enum_strs=None, + **kwargs + ): + """ + Update the UI with new metadata from the Signal. + + Signal metadata updates always send all available metadata, so + default values to this function will not be sent ever if the signal + has valid data there. + + We default missing metadata to None and skip emitting in general, + but for severity we default to NO_ALARM for UI purposes. We don't + want the UI to assume that anything is in an alarm state. + """ + # Only emit the non-None values + if connected is not None: + self.connection_state_signal.emit(connected) + if write_access is not None: + self.write_access_signal.emit(write_access) + if precision is not None: + self.prec_signal.emit(precision) + if units is not None: + self.unit_signal.emit(units) + if enum_strs is not None: + self.enum_strings_signal.emit(enum_strs) + + # Special handling for severity + if severity is None: + severity = AlarmSeverity.NO_ALARM + self.new_severity_signal.emit(severity)
+ +
[docs] def add_listener(self, channel): + """ + Add a listener channel to this connection. + + This attaches values input by the user to the `send_new_value` function + in order to update the Signal object in addition to the default setup + performed in PyDMConnection. + """ + # Perform the default connection setup + logger.debug("Adding %r ...", channel) + super().add_listener(channel) + try: + # Gather the current value + signal_val = self.signal.get() + # Gather metadata + signal_meta = self.signal.metadata + except Exception: + logger.exception("Failed to gather proper information " + "from signal %r to initialize %r", + self.signal.name, channel) + return + # Report new value + self.send_new_value(signal_val) + self.send_new_meta(**signal_meta) + # If the channel is used for writing to PVs, hook it up to the + # 'put' methods. + if channel.value_signal is not None: + for _typ in self.supported_types: + try: + val_sig = channel.value_signal[_typ] + val_sig.connect(self.put_value, Qt.QueuedConnection) + except KeyError: + logger.debug("%s has no value_signal for type %s", + channel.address, _typ)
+ +
[docs] def remove_listener(self, channel, destroying=False, **kwargs): + """ + Remove a listener channel from this connection. + + This removes the `send_new_value` connections from the channel in + addition to the default disconnection performed in PyDMConnection. + """ + logger.debug("Removing %r ...", channel) + # Disconnect put_value from outgoing channel + if channel.value_signal is not None and not destroying: + for _typ in self.supported_types: + try: + channel.value_signal[_typ].disconnect(self.put_value) + except (KeyError, TypeError): + logger.debug("Unable to disconnect value_signal from %s " + "for type %s", channel.address, _typ) + # Disconnect any other signals + super().remove_listener(channel, destroying=destroying, **kwargs) + logger.debug("Successfully removed %r", channel)
+ +
[docs] def close(self): + """Unsubscribe from the Ophyd signal.""" + self.signal.unsubscribe(self.value_cid) + self.signal.unsubscribe(self.meta_cid)
+ + +
[docs]class SignalPlugin(PyDMPlugin): + """Plugin registered with PyDM to handle SignalConnection.""" + protocol = 'sig' + connection_class = SignalConnection + + def add_connection(self, channel): + """Add a connection to a channel.""" + try: + # Add a PyDMConnection for the channel + super().add_connection(channel) + # There is a chance that we raise an Exception on creation. If so, + # don't add this to our list of good to go connections. The next + # attempt we try again. + except KeyError: + logger.error("Unable to find signal for %r in signal registry." + "Use typhos.plugins.register_signal()", + channel) + except Exception: + logger.exception("Unable to create a connection to %r", + channel)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/_modules/typhos/plugins/happi.html b/v2.4.1/_modules/typhos/plugins/happi.html new file mode 100644 index 000000000..5245db457 --- /dev/null +++ b/v2.4.1/_modules/typhos/plugins/happi.html @@ -0,0 +1,203 @@ + + + + + + typhos.plugins.happi — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for typhos.plugins.happi

+import logging
+
+from happi import Client
+from happi.errors import SearchError
+from happi.loader import from_container
+from pydm.data_plugins.plugin import PyDMConnection, PyDMPlugin
+from qtpy import QtCore
+
+
+class HappiClientState:
+    client = None
+
+
+logger = logging.getLogger(__name__)
+
+
+
[docs]def register_client(client): + """ + Register a Happi Client to be used with the DataPlugin. + + This is not required to be called by the user, if your environment is setup + such that :meth:`happi.Client.from_config` will return the desired client. + """ + HappiClientState.client = client
+ + +
[docs]class HappiConnection(PyDMConnection): + """A PyDMConnection to the Happi Database.""" + tx = QtCore.Signal(dict) + + def __init__(self, channel, address, protocol=None, parent=None): + super().__init__(channel, address, protocol=protocol, parent=parent) + self.add_listener(channel) + +
[docs] def add_listener(self, channel): + """Add a new channel to the existing connection.""" + super().add_listener(channel) + # Connect our channel to the signal + self.tx.connect(channel.tx_slot, QtCore.Qt.QueuedConnection) + logger.debug("Loading %r from happi Client", channel) + if '.' in self.address: + device, child = self.address.split('.', 1) + else: + device, child = self.address, None + # Load the device from the Client + md = HappiClientState.client.find_item(name=device) + obj = from_container(md) + md = md.post() + # If we have a child grab it + if child: + logger.debug("Retrieving child %r from %r", + child, obj.name) + obj = getattr(obj, child) + md = {'name': obj.name} + # Send the device and metdata to all of our subscribers + self.tx.emit({'obj': obj, 'md': md})
+ +
[docs] def remove_listener(self, channel, destroying=False, **kwargs): + """Remove a channel from the database connection.""" + super().remove_listener(channel, destroying=destroying, **kwargs) + if not destroying: + self.tx.disconnect(channel.tx_slot)
+ + +
[docs]class HappiPlugin(PyDMPlugin): + protocol = 'happi' + connection_class = HappiConnection + + def add_connection(self, channel): + # If we haven't made a Client by the time we need the Plugin. Try + # and load one from configuration file + if not HappiClientState.client: + register_client(Client.from_config()) + try: + super().add_connection(channel) + except SearchError: + logger.error("Unable to find device for %r in happi database.", + channel) + except AttributeError as exc: + logger.exception("Invalid attribute %r for address %r", + exc, channel.address) + except Exception: + logger.exception("Unable to load %r from happi", channel.address)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/_modules/typhos/positioner.html b/v2.4.1/_modules/typhos/positioner.html new file mode 100644 index 000000000..99fad5206 --- /dev/null +++ b/v2.4.1/_modules/typhos/positioner.html @@ -0,0 +1,796 @@ + + + + + + typhos.positioner — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for typhos.positioner

+import logging
+import os.path
+import threading
+
+from pydm.widgets.channel import PyDMChannel
+from qtpy import QtCore, QtWidgets, uic
+
+from . import utils, widgets
+from .alarm import KindLevel, _KindLevel
+from .status import TyphosStatusThread
+
+logger = logging.getLogger(__name__)
+
+
+
[docs]class TyphosPositionerWidget( + utils.TyphosBase, + widgets.TyphosDesignerMixin, + _KindLevel, +): + """ + Widget to interact with a :class:`ophyd.Positioner`. + + Standard positioner motion requires a large amount of context for + operators. For most motors, it may not be enough to simply have a text + field where setpoints can be punched in. Instead, information like soft + limits and hardware limit switches are crucial for a full understanding of + the position and behavior of a motor. The widget will work with any object + that implements the method ``set``, however to get other relevant + information, we see if we can find other useful signals. Below is a table + of attributes that the widget looks for to inform screen design. + + ============== =========================================================== + Widget Attribute Selection + ============== =========================================================== + User Readback The ``readback_attribute`` property is used, which defaults + to ``user_readback``. Linked to UI element + ``user_readback``. + + User Setpoint The ``setpoint_attribute`` property is used, which defaults + to ``user_setpoint``. Linked to UI element + ``user_setpoint``. + + Limit Switches The ``low_limit_switch_attribute`` and + ``high_limit_switch_attribute`` properties are used, which + default to ``low_limit_switch`` and ``high_limit_switch``, + respectively. + + Soft Limits The ``low_limit_travel_attribute`` and + ``high_limit_travel_attribute`` properties are used, which + default to ``low_limit_travel`` and ``high_limit_travel``, + respectively. As a fallback, the ``limit`` property on the + device may be queried directly. + + Set and Tweak Both of these methods simply use ``Device.set`` which is + expected to take a ``float`` and return a ``status`` object + that indicates the motion completeness. Must be implemented. + + Stop ``Device.stop()``, if available, otherwise hide the button. + If you have a non-functional ``stop`` method inherited from + a parent device, you can hide it from ``typhos`` by + overriding it with a property that raises + ``AttributeError`` on access. + + Move Indicator The ``moving_attribute`` property is used, which defaults + to ``motor_is_moving``. Linked to UI element + ``moving_indicator``. + + Error Message The ``error_message_attribute`` property is used, which + defaults to ``error_message``. Linked to UI element + ``error_label``. + + Clear Error ``Device.clear_error()``, if applicable. This also clears + any visible error messages from the status returned by + ``Device.set``. + + Alarm Circle Uses the ``TyphosAlarmCircle`` widget to summarize the + alarm state of all of the device's ``normal`` and + ``hinted`` signals. + ============== =========================================================== + """ + QtCore.Q_ENUMS(_KindLevel) + KindLevel = KindLevel + + ui_template = os.path.join(utils.ui_dir, 'widgets', 'positioner.ui') + _readback_attr = 'user_readback' + _setpoint_attr = 'user_setpoint' + _low_limit_switch_attr = 'low_limit_switch' + _high_limit_switch_attr = 'high_limit_switch' + _low_limit_travel_attr = 'low_limit_travel' + _high_limit_travel_attr = 'high_limit_travel' + _velocity_attr = 'velocity' + _acceleration_attr = 'acceleration' + _moving_attr = 'motor_is_moving' + _error_message_attr = 'error_message' + _min_visible_operation = 0.1 + + def __init__(self, parent=None): + self._moving = False + self._last_move = None + self._readback = None + self._setpoint = None + self._status_thread = None + self._initialized = False + self._moving_channel = None + + super().__init__(parent=parent) + + self.ui = uic.loadUi(self.ui_template, self) + self.ui.tweak_positive.clicked.connect(self.positive_tweak) + self.ui.tweak_negative.clicked.connect(self.negative_tweak) + self.ui.stop_button.clicked.connect(self.stop) + self.ui.clear_error_button.clicked.connect(self.clear_error) + + self.ui.alarm_circle.kindLevel = self.ui.alarm_circle.NORMAL + self.ui.alarm_circle.alarm_changed.connect(self.update_alarm_text) + + self.show_expert_button = False + self._after_set_moving(False) + + def _clear_status_thread(self): + """Clear a previous status thread.""" + if self._status_thread is None: + return + + logger.debug("Clearing current active status") + self._status_thread.disconnect() + self._status_thread = None + + def _start_status_thread(self, status, timeout): + """Start the status monitoring thread for the given status object.""" + self._status_thread = thread = TyphosStatusThread( + status, start_delay=self._min_visible_operation, + timeout=timeout, + parent=self, + ) + thread.status_started.connect(self.move_changed) + thread.status_finished.connect(self._status_finished) + thread.start() + + def _get_timeout(self, set_position, settle_time): + """Use positioner's configuration to select a timeout.""" + pos_sig = getattr(self.device, self._readback_attr, None) + vel_sig = getattr(self.device, self._velocity_attr, None) + acc_sig = getattr(self.device, self._acceleration_attr, None) + # Not enough info == no timeout + if pos_sig is None or vel_sig is None: + return None + delta = pos_sig.get() - set_position + speed = vel_sig.get() + # Bad speed == no timeout + if speed == 0: + return None + # Bad acceleration == ignore acceleration + if acc_sig is None: + acc_time = 0 + else: + acc_time = acc_sig.get() + # This time is always greater than the kinematic calc + return abs(delta/speed) + 2 * abs(acc_time) + abs(settle_time) + + def _set(self, value): + """Inner `set` routine - call device.set() and monitor the status.""" + self._clear_status_thread() + self._last_move = None + if isinstance(self.ui.set_value, widgets.NoScrollComboBox): + set_position = value + else: + set_position = float(value) + + try: + timeout = self._get_timeout(set_position, 5) + except Exception: + # Something went wrong, just run without a timeout. + logger.exception('Unable to estimate motor timeout.') + timeout = None + logger.debug("Setting device %r to %r with timeout %r", + self.device, value, timeout) + try: + status = self.device.set(set_position) + except Exception as exc: + # Treat this exception as a status to use normal error reporting + # Usually this is e.g. limits error + self._status_finished(exc) + else: + # Send timeout through thread because status timeout stops the move + self._start_status_thread(status, timeout) + + @QtCore.Slot(int) + def combo_set(self, index): + self.set() + +
[docs] @QtCore.Slot() + def set(self): + """Set the device to the value configured by ``ui.set_value``""" + if not self.device: + return + + try: + if isinstance(self.ui.set_value, widgets.NoScrollComboBox): + value = self.ui.set_value.currentText() + else: + value = self.ui.set_value.text() + self._set(value) + except Exception as exc: + logger.exception("Error setting %r to %r", self.devices, value) + self._last_move = False + utils.reload_widget_stylesheet(self, cascade=True) + utils.raise_to_operator(exc)
+ +
[docs] def tweak(self, offset): + """Tweak by the given ``offset``.""" + try: + setpoint = self._get_position() + float(offset) + except Exception: + logger.exception('Tweak failed') + return + + self.ui.set_value.setText(str(setpoint)) + self.set()
+ +
[docs] @QtCore.Slot() + def positive_tweak(self): + """Tweak positive by the amount listed in ``ui.tweak_value``""" + try: + self.tweak(float(self.tweak_value.text())) + except Exception: + logger.exception('Tweak failed')
+ +
[docs] @QtCore.Slot() + def negative_tweak(self): + """Tweak negative by the amount listed in ``ui.tweak_value``""" + try: + self.tweak(-float(self.tweak_value.text())) + except Exception: + logger.exception('Tweak failed')
+ +
[docs] @QtCore.Slot() + def stop(self): + """Stop device""" + for device in self.devices: + # success=True means expected stop + device.stop(success=True)
+ +
[docs] @QtCore.Slot() + def clear_error(self): + """ + Clear the error messages from the device and screen. + + The device may have errors in the IOC. These will be cleared by calling + the clear_error method. + + The screen may have errors from the status of the last move. These will + be cleared from view. + """ + for device in self.devices: + clear_error_in_background(device) + self._set_status_text('') + # This variable holds True if last move was good, False otherwise + # It also controls whether or not we have a red box on the widget + # False = Red, True = Green, None = no box (in motion is yellow) + if not self._last_move: + self._last_move = None + utils.reload_widget_stylesheet(self, cascade=True)
+ + def _get_position(self): + if not self._readback: + raise Exception("No Device configured for widget!") + return self._readback.get() + + @utils.linked_attribute('readback_attribute', 'ui.user_readback', True) + def _link_readback(self, signal, widget): + """Link the positioner readback with the ui element.""" + self._readback = signal + + @utils.linked_attribute('setpoint_attribute', 'ui.user_setpoint', True) + def _link_setpoint(self, signal, widget): + """Link the positioner setpoint with the ui element.""" + self._setpoint = signal + if signal is not None: + # Seed the set_value text with the user_setpoint channel value. + if hasattr(widget, 'textChanged'): + widget.textChanged.connect(self._user_setpoint_update) + + @utils.linked_attribute('low_limit_switch_attribute', + 'ui.low_limit_switch', True) + def _link_low_limit_switch(self, signal, widget): + """Link the positioner lower limit switch with the ui element.""" + if signal is None: + widget.hide() + + @utils.linked_attribute('high_limit_switch_attribute', + 'ui.high_limit_switch', True) + def _link_high_limit_switch(self, signal, widget): + """Link the positioner high limit switch with the ui element.""" + if signal is None: + widget.hide() + + @utils.linked_attribute('low_limit_travel_attribute', 'ui.low_limit', True) + def _link_low_travel(self, signal, widget): + """Link the positioner lower travel limit with the ui element.""" + return signal is not None + + @utils.linked_attribute('high_limit_travel_attribute', 'ui.high_limit', + True) + def _link_high_travel(self, signal, widget): + """Link the positioner high travel limit with the ui element.""" + return signal is not None + + def _link_limits_by_limits_attr(self): + """Link limits by using ``device.limits``.""" + device = self.device + try: + low_limit, high_limit = device.limits + except Exception: + ... + else: + if low_limit < high_limit: + self.ui.low_limit.setText(str(low_limit)) + self.ui.high_limit.setText(str(high_limit)) + return + + # If not found or invalid, hide them: + self.ui.low_limit.hide() + self.ui.high_limit.hide() + + @utils.linked_attribute('moving_attribute', 'ui.moving_indicator', True) + def _link_moving(self, signal, widget): + """Link the positioner moving indicator with the ui element.""" + if signal is None: + widget.hide() + return False + widget.show() + # Additional handling for updating self.moving + if self._moving_channel is not None: + self._moving_channel.disconnect() + chname = utils.channel_from_signal(signal) + self._moving_channel = PyDMChannel( + address=chname, + value_slot=self._set_moving, + ) + self._moving_channel.connect() + return True + + @utils.linked_attribute('error_message_attribute', 'ui.error_label', True) + def _link_error_message(self, signal, widget): + """Link the IOC error message with the ui element.""" + if signal is None: + widget.hide() + + def _define_setpoint_widget(self): + """ + Leverage information at describe to define whether to use a + PyDMLineEdit or a PyDMEnumCombobox as setpoint widget. + """ + try: + setpoint_signal = getattr(self.device, self.setpoint_attribute) + selection = setpoint_signal.enum_strs is not None + except Exception: + selection = False + + if selection: + self.ui.set_value = widgets.NoScrollComboBox() + self.ui.set_value.addItems(setpoint_signal.enum_strs) + # Activated signal triggers only when the user selects an option + self.ui.set_value.activated.connect(self.set) + self.ui.set_value.setSizePolicy( + QtWidgets.QSizePolicy.Expanding, + QtWidgets.QSizePolicy.Fixed, + ) + self.ui.set_value.setMinimumContentsLength(20) + self.ui.tweak_widget.setVisible(False) + else: + self.ui.set_value = QtWidgets.QLineEdit() + self.ui.set_value.setAlignment(QtCore.Qt.AlignCenter) + self.ui.set_value.returnPressed.connect(self.set) + + self.ui.setpoint_layout.addWidget(self.ui.set_value) + + @property + def device(self): + """The associated device.""" + try: + return self.devices[0] + except Exception: + ... + +
[docs] def add_device(self, device): + """Add a device to the widget""" + # Add device to cache + self.devices.clear() # only one device allowed + super().add_device(device) + + self._define_setpoint_widget() + self._link_readback() + self._link_setpoint() + self._link_low_limit_switch() + self._link_high_limit_switch() + + # If the stop method is missing, hide the button + try: + device.stop + self.ui.stop_button.show() + except AttributeError: + self.ui.stop_button.hide() + + if not (self._link_low_travel() and self._link_high_travel()): + self._link_limits_by_limits_attr() + + if self._link_moving(): + self.ui.moving_indicator_label.show() + else: + self.ui.moving_indicator_label.hide() + + self._link_error_message() + + if self.show_expert_button: + self.ui.expert_button.devices.clear() + self.ui.expert_button.add_device(device) + + self.ui.alarm_circle.clear_all_alarm_configs() + self.ui.alarm_circle.add_device(device)
+ + @QtCore.Property(bool, designable=False) + def moving(self): + """ + Current state of widget + + This will lag behind the actual state of the positioner in order to + prevent unnecessary rapid movements + """ + return self._moving + + @moving.setter + def moving(self, value): + if value != self._moving: + self._moving = value + self._after_set_moving(value) + + def _after_set_moving(self, value): + """ + Common updates needed after a change to the moving state. + + This is pulled out as a separate method because we need + to initialize the label here during __init__ without + modifying self.moving. + """ + utils.reload_widget_stylesheet(self, cascade=True) + if value: + self.ui.moving_indicator_label.setText('moving') + else: + self.ui.moving_indicator_label.setText('done') + + def _set_moving(self, value): + """ + Slot for updating the self.moving property. + + This is used e.g. in updating the moving state when the + motor starts moving in EPICS but not by the request of + this widget. + """ + self.moving = bool(value) + + @QtCore.Property(bool, designable=False) + def successful_move(self): + """The last requested move was successful""" + return self._last_move is True + + @QtCore.Property(bool, designable=False) + def failed_move(self): + """The last requested move failed""" + return self._last_move is False + + @QtCore.Property(str, designable=True) + def readback_attribute(self): + """The attribute name for the readback signal.""" + return self._readback_attr + + @readback_attribute.setter + def readback_attribute(self, value): + self._readback_attr = value + + @QtCore.Property(str, designable=True) + def setpoint_attribute(self): + """The attribute name for the setpoint signal.""" + return self._setpoint_attr + + @setpoint_attribute.setter + def setpoint_attribute(self, value): + self._setpoint_attr = value + + @QtCore.Property(str, designable=True) + def low_limit_switch_attribute(self): + """The attribute name for the low limit switch signal.""" + return self._low_limit_switch_attr + + @low_limit_switch_attribute.setter + def low_limit_switch_attribute(self, value): + self._low_limit_switch_attr = value + + @QtCore.Property(str, designable=True) + def high_limit_switch_attribute(self): + """The attribute name for the high limit switch signal.""" + return self._high_limit_switch_attr + + @high_limit_switch_attribute.setter + def high_limit_switch_attribute(self, value): + self._high_limit_switch_attr = value + + @QtCore.Property(str, designable=True) + def low_limit_travel_attribute(self): + """The attribute name for the low limit signal.""" + return self._low_limit_travel_attr + + @low_limit_travel_attribute.setter + def low_limit_travel_attribute(self, value): + self._low_limit_travel_attr = value + + @QtCore.Property(str, designable=True) + def high_limit_travel_attribute(self): + """The attribute name for the high (soft) limit travel signal.""" + return self._high_limit_travel_attr + + @high_limit_travel_attribute.setter + def high_limit_travel_attribute(self, value): + self._high_limit_travel_attr = value + + @QtCore.Property(str, designable=True) + def velocity_attribute(self): + """The attribute name for the velocity signal.""" + return self._velocity_attr + + @velocity_attribute.setter + def velocity_attribute(self, value): + self._velocity_attr = value + + @QtCore.Property(str, designable=True) + def acceleration_attribute(self): + """The attribute name for the acceleration time signal.""" + return self._acceleration_attr + + @acceleration_attribute.setter + def acceleration_attribute(self, value): + self._acceleration_attr = value + + @QtCore.Property(str, designable=True) + def moving_attribute(self): + """The attribute name for the motor moving indicator.""" + return self._moving_attr + + @moving_attribute.setter + def moving_attribute(self, value): + self._moving_attr = value + + @QtCore.Property(str, designable=True) + def error_message_attribute(self): + """The attribute name for the IOC error message label.""" + return self._error_message_attr + + @error_message_attribute.setter + def error_message_attribute(self, value): + self._error_message_attr = value + + @QtCore.Property(bool, designable=True) + def show_expert_button(self): + """ + If True, show the expert button. + + The expert button opens a full suite for the device. + You typically want this False when you're already inside the + suite that the button would open. + You typically want this True when you're using the positioner widget + inside of an unrelated screen. + This will default to False. + """ + return self._show_expert_button + + @show_expert_button.setter + def show_expert_button(self, show): + self._show_expert_button = show + if show: + self.ui.expert_button.show() + else: + self.ui.expert_button.hide() + + @QtCore.Property(_KindLevel, designable=True) + def alarmKindLevel(self) -> KindLevel: + return self.ui.alarm_circle.kindLevel + + @alarmKindLevel.setter + def alarmKindLevel(self, kind_level: KindLevel): + if kind_level != self.alarmKindLevel: + self.ui.alarm_circle.kindLevel = kind_level + +
[docs] def move_changed(self): + """Called when a move is begun""" + logger.debug("Begin showing move in TyphosPositionerWidget") + self.moving = True
+ + def _set_status_text(self, text, *, max_length=60): + """Set the status text label to ``text``.""" + if len(text) >= max_length: + self.ui.status_label.setToolTip(text) + text = text[:max_length] + '...' + else: + self.ui.status_label.setToolTip('') + + self.ui.status_label.setText(text) + + def _status_finished(self, result): + """Called when a move is complete.""" + if isinstance(result, Exception): + text = f'<b>{result.__class__.__name__}</b> {result}' + else: + text = '' + + self._set_status_text(text) + + success = not isinstance(result, Exception) + logger.debug("Completed move in TyphosPositionerWidget (result=%r)", + result) + self._last_move = success + self.moving = False + + @QtCore.Slot(str) + def _user_setpoint_update(self, text): + """Qt slot - indicating the ``user_setpoint`` widget text changed.""" + try: + text = text.strip().split(' ')[0] + text = text.strip() + except Exception: + return + + # Update set_value if it's not being edited. + if not self.ui.set_value.hasFocus(): + if isinstance(self.ui.set_value, widgets.NoScrollComboBox): + try: + idx = int(text) + self.ui.set_value.setCurrentIndex(idx) + self._initialized = True + except ValueError: + logger.debug('Failed to convert value to int. %s', text) + else: + self._initialized = True + self.ui.set_value.setText(text) + +
[docs] def update_alarm_text(self, alarm_level): + """ + Label the alarm circle with a short text bit. + """ + alarms = self.ui.alarm_circle.AlarmLevel + if alarm_level == alarms.NO_ALARM: + text = 'no alarm' + elif alarm_level == alarms.MINOR: + text = 'minor' + elif alarm_level == alarms.MAJOR: + text = 'major' + elif alarm_level == alarms.DISCONNECTED: + text = 'no conn' + else: + text = 'invalid' + self.ui.alarm_label.setText(text)
+ + +def clear_error_in_background(device): + def inner(): + try: + device.clear_error() + except AttributeError: + pass + except Exception: + msg = "Could not clear error!" + logger.error(msg) + logger.debug(msg, exc_info=True) + + td = threading.Thread(target=inner) + td.start() +
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/_modules/typhos/suite.html b/v2.4.1/_modules/typhos/suite.html new file mode 100644 index 000000000..7908415a0 --- /dev/null +++ b/v2.4.1/_modules/typhos/suite.html @@ -0,0 +1,940 @@ + + + + + + typhos.suite — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for typhos.suite

+"""
+The high-level Typhos Suite, which bundles tools and panels.
+"""
+from __future__ import annotations
+
+import logging
+import os
+import textwrap
+from functools import partial
+
+import ophyd
+import pcdsutils.qt
+from ophyd import Device
+from pyqtgraph import parametertree
+from pyqtgraph.parametertree import parameterTypes as ptypes
+from qtpy import QtCore, QtGui, QtWidgets
+
+from . import utils, widgets
+from .display import DisplayTypes, ScrollOptions, TyphosDeviceDisplay
+from .tools import TyphosConsole, TyphosLogDisplay, TyphosTimePlot
+from .utils import TyphosBase, clean_attr, clean_name, flatten_tree, save_suite
+
+logger = logging.getLogger(__name__)
+# Use non-None sentinel value since None means no tools
+DEFAULT_TOOLS = object()
+
+
+class SidebarParameter(parametertree.Parameter):
+    """
+    Parameter to hold information for the sidebar.
+
+    Attributes
+    ----------
+    itemClass : type
+        The class to be used for the parameter.
+
+    sigOpen : QtCore.Signal
+        A signal indicating an open request for the parameter.
+
+    sigHide : QtCore.Signal
+        A signal indicating an hide request for the parameter.
+
+    sigEmbed : QtCore.Signal
+        A signal indicating an embed request for the parameter.
+    """
+
+    itemClass = widgets.TyphosSidebarItem
+    sigOpen = QtCore.Signal(object)
+    sigHide = QtCore.Signal(object)
+    sigEmbed = QtCore.Signal(object)
+
+    def __init__(self, devices=None, embeddable=None, **opts):
+        super().__init__(**opts)
+        self.embeddable = embeddable
+        self.devices = list(devices) if devices else []
+
+    def has_device(self, device: ophyd.Device):
+        """
+        Determine if this parameter contains the given device.
+
+        Parameters
+        ----------
+        device : ophyd.OphydObj or str
+            The device or its name.
+
+        Returns
+        -------
+        has_device : bool
+        """
+        return any(
+            (device in self.devices,
+             device in getattr(self.value(), 'devices', []),
+             self.name() == device,
+             isinstance(device, str) and self.name() == clean_attr(device),
+             )
+        )
+
+
+class LazySubdisplay(QtWidgets.QWidget):
+    """
+    A lazy subdisplay which only is instantiated when shown in the suite.
+
+    Supports devices by way of ``add_device``.
+
+    Parameters
+    ----------
+    widget_cls : QtWidgets.QWidget subclass
+        The widget class to instantiate.
+    """
+
+    widget_cls: type[QtWidgets.QWidget]
+    widget: QtWidgets.QWidget | None
+    devices: list[ophyd.Device]
+
+    def __init__(self, widget_cls: type[QtWidgets.QWidget]):
+        super().__init__()
+        self.widget_cls = widget_cls
+        self.widget = None
+
+        self.setVisible(False)
+        self.setLayout(QtWidgets.QVBoxLayout())
+        self.devices = []
+
+    def add_device(self, device: ophyd.Device):
+        """Hook for adding a device from the suite."""
+        self.devices.append(device)
+
+    def hideEvent(self, event: QtGui.QHideEvent):
+        """Hook for when the tool is hidden."""
+        return super().hideEvent(event)
+
+    def _create_widget(self):
+        """Make the widget no longer lazy."""
+        if self.widget is not None:
+            return
+
+        self.widget = self.widget_cls()
+        self.layout().addWidget(self.widget)
+        self.setSizePolicy(self.widget.sizePolicy())
+
+        if hasattr(self.widget, "add_device"):
+            for device in self.devices:
+                self.widget.add_device(device)
+
+    def showEvent(self, event: QtGui.QShowEvent):
+        """Hook for when the tool is shown in the suite."""
+        if self.widget is None:
+            self._create_widget()
+
+        return super().showEvent(event)
+
+    def minimumSizeHint(self):
+        """Minimum size hint forwarder from the embedded widget."""
+        if self.widget is not None:
+            return self.widget.minimumSizeHint()
+        return self.sizeHint()
+
+    def sizeHint(self):
+        """Size hint forwarder from the embedded widget."""
+        if self.widget is not None:
+            return self.widget.sizeHint()
+        return QtCore.QSize(100, 100)
+
+
+class DeviceParameter(SidebarParameter):
+    """
+    Parameter to hold information on an Ophyd Device.
+
+    Parameters
+    ----------
+    device : ophyd.Device
+        The device instance.
+
+    subdevices : bool, optional
+        Include child parameters for sub devices of ``device``.
+
+    **opts
+        Passed to super().__init__.
+    """
+
+    itemClass = widgets.TyphosSidebarItem
+
+    def __init__(self, device, subdevices=True, **opts):
+        # Set options for parameter
+        opts['name'] = clean_name(device, strip_parent=device.root)
+        self.device = device
+        opts['expanded'] = False
+        # Grab children from the given device
+        children = list()
+        if subdevices:
+            for child in device._sub_devices:
+                subdevice = getattr(device, child)
+                if subdevice._sub_devices:
+                    # If that device has children, make sure they are also
+                    # displayed further in the tree
+                    children.append(
+                        DeviceParameter(subdevice, subdevices=False)
+                    )
+                else:
+                    # Otherwise just make a regular parameter out of it
+                    child_name = clean_name(subdevice,
+                                            strip_parent=subdevice.root)
+                    param = SidebarParameter(
+                        value=partial(TyphosDeviceDisplay.from_device,
+                                      subdevice),
+                        name=child_name,
+                        embeddable=True,
+                        devices=[subdevice],
+                    )
+                    children.append(param)
+
+        opts['children'] = children
+        super().__init__(
+            value=partial(TyphosDeviceDisplay.from_device, device),
+            embeddable=opts.pop('embeddable', True),
+            devices=[device],
+            **opts
+        )
+
+
+
[docs]class TyphosSuite(TyphosBase): + """ + This suite combines tools and devices into a single widget. + + A :class:`ParameterTree` is contained in a :class:`~pcdsutils.qt.QPopBar` + which shows tools and the hierarchy of a device along with options to + show or hide them. + + Parameters + ---------- + parent : QWidget, optional + + pin : bool, optional + Pin the parameter tree on startup. + + content_layout : QLayout, optional + Sets the layout for when we have multiple subdisplays + open in the suite. This will have a horizontal layout by + default but can be changed as needed for the use case. + + default_display_type : DisplayType, optional + DisplayType enum that determines the type of display to open when we + add a device to the suite. Defaults to DisplayType.detailed_screen. + + scroll_option : ScrollOptions, optional + ScrollOptions enum that determines the behavior of scrollbars + in the suite. Default is ScrollOptions.auto, which enables + scrollbars for detailed and engineering screens but not for + embedded displays. + + Attributes + ---------- + default_tools : dict + The default tools to use in the suite. In the form of + ``{'tool_name': ToolClass}``. + """ + + DEFAULT_TITLE = 'Typhos Suite' + DEFAULT_TITLE_DEVICE = 'Typhos Suite - {device.name}' + + default_tools = {'Log': TyphosLogDisplay, + 'StripTool': TyphosTimePlot, + 'Console': TyphosConsole} + + def __init__( + self, + parent: QtWidgets.QWidget | None = None, + *, + pin: bool = False, + content_layout: QtWidgets.QLayout | None = None, + default_display_type: DisplayTypes = DisplayTypes.detailed_screen, + scroll_option: ScrollOptions = ScrollOptions.auto, + ): + super().__init__(parent=parent) + + self._update_title() + + self._tree = parametertree.ParameterTree(parent=self, showHeader=False) + self._tree.setAlternatingRowColors(False) + self._save_action = ptypes.ActionParameter(name='Save Suite') + self._tree.addParameters(self._save_action) + self._save_action.sigActivated.connect(self.save) + + self._bar = pcdsutils.qt.QPopBar(title='Suite', parent=self, + widget=self._tree, pin=pin) + + self._tree.setSizePolicy( + QtWidgets.QSizePolicy.MinimumExpanding, + QtWidgets.QSizePolicy.MinimumExpanding + ) + self._tree.setMinimumSize(250, 150) + + self._content_frame = QtWidgets.QFrame(self) + self._content_frame.setObjectName("content") + self._content_frame.setFrameShape(QtWidgets.QFrame.StyledPanel) + + # Content frame layout: configurable + # Defaults to [content] [content] [content] ... in one line + if content_layout is None: + content_layout = QtWidgets.QHBoxLayout() + self._content_frame.setLayout(content_layout) + + # Horizontal box layout: [PopBar] [Content Frame] + layout = QtWidgets.QHBoxLayout() + self.setLayout(layout) + layout.setSpacing(1) + layout.setContentsMargins(0, 0, 0, 0) + layout.addWidget(self._bar) + layout.addWidget(self._content_frame) + + self.embedded_dock = None + self.default_display_type = default_display_type + self.scroll_option = scroll_option + +
[docs] def add_subdisplay(self, name, display, category): + """ + Add an arbitrary widget to the tree of available widgets and tools. + + Parameters + ---------- + name : str + Name to be displayed in the tree + + display : QWidget + QWidget to show in the dock when expanded. + + category : str + The top level group to place the controls under in the tree. If the + category does not exist, a new one will be made + """ + logger.debug("Adding widget %r with %r to %r ...", + name, display, category) + # Create our parameter + parameter = SidebarParameter(value=display, name=name) + self._add_to_sidebar(parameter, category)
+ +
[docs] def add_lazy_subdisplay( + self, name: str, display_class: type[QtWidgets.QWidget], category: str + ): + """ + Add an arbitrary widget to the tree of available widgets and tools. + + Parameters + ---------- + name : str + Name to be displayed in the tree + + display_class : subclass of QWidget + QWidget class to show in the dock when expanded. + + category : str + The top level group to place the controls under in the tree. If the + category does not exist, a new one will be made + """ + logger.debug("Adding lazy subdisplay %r with %r to %r ...", + name, display_class, category) + # Create our parameter + parameter = SidebarParameter( + value=LazySubdisplay(display_class), + name=name + ) + self._add_to_sidebar(parameter, category)
+ + @property + def top_level_groups(self): + """ + Get top-level groups. + + This is of the form: + + .. code:: python + + {'name': QGroupParameterItem} + """ + root = self._tree.invisibleRootItem() + return {root.child(idx).param.name(): + root.child(idx).param + for idx in range(root.childCount())} + +
[docs] def add_tool(self, name: str, tool: type[QtWidgets.QWidget]): + """ + Add a widget to the toolbar. + + Shortcut for: + + .. code:: python + + suite.add_subdisplay(name, tool, category='Tools') + + Parameters + ---------- + name : str + Name of tool to be displayed in sidebar + + tool : QWidget + Widget to be added to ``.ui.subdisplay`` + """ + self.add_lazy_subdisplay(name, tool, "Tools")
+ +
[docs] def get_subdisplay(self, display): + """ + Get a subdisplay by name or contained device. + + Parameters + ---------- + display :str or Device + Name of screen or device + + Returns + ------- + widget : QWidget + Widget that is a member of the :attr:`.ui.subdisplay` + + Example + ------- + .. code:: python + + suite.get_subdisplay(my_device.x) + suite.get_subdisplay('My Tool') + """ + if not isinstance(display, SidebarParameter): + for group in self.top_level_groups.values(): + tree = flatten_tree(group) + matches = [ + param for param in tree + if hasattr(param, 'has_device') and + param.has_device(display) + ] + + if matches: + display = matches[0] + break + + if not isinstance(display, SidebarParameter): + # If we got here we can't find the subdisplay + raise ValueError(f"Unable to find subdisplay {display}") + + subdisplay = display.value() + if isinstance(subdisplay, partial): + subdisplay = subdisplay() + display.setValue(subdisplay) + return subdisplay
+ +
[docs] @QtCore.Slot(str) + @QtCore.Slot(object) + def show_subdisplay(self, widget): + """ + Open a display in the dock system. + + Parameters + ---------- + widget: QWidget, SidebarParameter or str + If given a ``SidebarParameter`` from the tree, the widget will be + shown and the sidebar item update. Otherwise, the information is + passed to :meth:`.get_subdisplay` + """ + # Grab true widget + if not isinstance(widget, QtWidgets.QWidget): + widget = self.get_subdisplay(widget) + # Setup the dock + dock = widgets.SubDisplay(self) + # Set sidebar properly + self._show_sidebar(widget, dock) + # Add the widget to the dock + logger.debug("Showing widget %r ...", widget) + if hasattr(widget, 'scroll_option'): + widget.scroll_option = self.scroll_option + if hasattr(widget, "display_type"): + # Setting a display_type implicitly loads the best template. + widget.display_type = self.default_display_type + dock.setWidget(widget) + + # Add to layout + content_layout = self._content_frame.layout() + content_layout.addWidget(dock) + if isinstance(content_layout, QtWidgets.QGridLayout): + self._content_frame.layout().setAlignment( + dock, QtCore.Qt.AlignHCenter + ) + self._content_frame.layout().setAlignment( + dock, QtCore.Qt.AlignTop + )
+ +
[docs] @QtCore.Slot(str) + @QtCore.Slot(object) + def embed_subdisplay(self, widget): + """Embed a display in the dock system.""" + # Grab the relevant display + if not self.embedded_dock: + self.embedded_dock = widgets.SubDisplay() + self.embedded_dock.setWidget(QtWidgets.QWidget()) + self.embedded_dock.widget().setLayout(QtWidgets.QVBoxLayout()) + self.embedded_dock.widget().layout().addStretch(1) + self._content_frame.layout().addWidget(self.embedded_dock) + + if not isinstance(widget, QtWidgets.QWidget): + widget = self.get_subdisplay(widget) + # Set sidebar properly + self._show_sidebar(widget, self.embedded_dock) + # Set our widget to be embedded + widget.setVisible(True) + widget.display_type = widget.embedded_screen + widget_count = self.embedded_dock.widget().layout().count() + self.embedded_dock.widget().layout().insertWidget(widget_count - 1, + widget)
+ +
[docs] @QtCore.Slot() + @QtCore.Slot(object) + def hide_subdisplay(self, widget): + """ + Hide a visible subdisplay. + + Parameters + ---------- + widget: SidebarParameter or Subdisplay + If you give a SidebarParameter, we will find the corresponding + widget and hide it. If the widget provided to us is inside a + DockWidget we will close that, otherwise the widget is just hidden. + """ + if not isinstance(widget, QtWidgets.QWidget): + widget = self.get_subdisplay(widget) + sidebar = self._get_sidebar(widget) + if sidebar: + for item in sidebar.items: + item._mark_hidden() + else: + logger.warning("Unable to find sidebar item for %r", widget) + # Make sure the actual widget is hidden + logger.debug("Hiding widget %r ...", widget) + if isinstance(widget.parent(), QtWidgets.QDockWidget): + logger.debug("Closing dock ...") + widget.parent().close() + # Hide the full dock if this is the last widget + elif (self.embedded_dock + and widget.parent() == self.embedded_dock.widget()): + logger.debug("Removing %r from embedded widget layout ...", + widget) + self.embedded_dock.widget().layout().removeWidget(widget) + widget.hide() + if self.embedded_dock.widget().layout().count() == 1: + logger.debug("Closing embedded layout ...") + self.embedded_dock.close() + self.embedded_dock = None + else: + widget.hide()
+ +
[docs] @QtCore.Slot() + def hide_subdisplays(self): + """Hide all open displays.""" + # Grab children from devices + for group in self.top_level_groups.values(): + for param in flatten_tree(group)[1:]: + self.hide_subdisplay(param)
+ + @property + def tools(self): + """Tools loaded into the suite.""" + if 'Tools' in self.top_level_groups: + return [param.value() + for param in self.top_level_groups['Tools'].childs] + return [] + + def _update_title(self, device=None): + """ + Update the window title, optionally with a device. + + Parameters + ---------- + device : ophyd.Device, optional + Device to indicate in the title. + """ + title_fmt = (self.DEFAULT_TITLE if device is None + else self.DEFAULT_TITLE_DEVICE) + + self.setWindowTitle(title_fmt.format(self=self, device=device)) + +
[docs] def add_device(self, device, children=True, category='Devices'): + """ + Add a device to the suite. + + Parameters + ---------- + device: ophyd.Device + The device to add. + + children: bool, optional + Also add any ``subdevices`` of this device to the suite as well. + + category: str, optional + Category of device. By default, all devices will just be added to + the "Devices" group + """ + + super().add_device(device) + self._update_title(device) + # Create DeviceParameter and add to top level category + dev_param = DeviceParameter(device, subdevices=children) + self._add_to_sidebar(dev_param, category) + # Grab children + for child in flatten_tree(dev_param)[1:]: + self._add_to_sidebar(child) + # Add a device to all the tool displays + for tool in self.tools: + try: + tool.add_device(device) + except Exception: + logger.exception("Unable to add %s to tool %s", + device.name, type(tool))
+ +
[docs] @classmethod + def from_device( + cls, + device: Device, + parent: QtWidgets.QWidget | None = None, + tools: dict[str, type] | None | DEFAULT_TOOLS = DEFAULT_TOOLS, + pin: bool = False, + content_layout: QtWidgets.QLayout | None = None, + default_display_type: DisplayTypes = DisplayTypes.detailed_screen, + scroll_option: ScrollOptions = ScrollOptions.auto, + show_displays: bool = True, + **kwargs, + ) -> TyphosSuite: + """ + Create a new :class:`TyphosSuite` from an :class:`ophyd.Device`. + + Parameters + ---------- + device : ophyd.Device + The device to use. + + children : bool, optional + Choice to include child Device components + + parent : QWidget + + tools : dict, optional + Tools to load for the object. ``dict`` should be name, class pairs. + By default these will be ``.default_tools``, but ``None`` can be + passed to avoid tool loading completely. + + pin : bool, optional + Pin the parameter tree on startup. + + content_layout : QLayout, optional + Sets the layout for when we have multiple subdisplays + open in the suite. This will have a horizontal layout by + default but can be changed as needed for the use case. + + default_display_type : DisplayTypes, optional + DisplayTypes enum that determines the type of display to open when + we add a device to the suite. Defaults to + DisplayTypes.detailed_screen. + + scroll_option : ScrollOptions, optional + ScrollOptions enum that determines the behavior of scrollbars + in the suite. Default is ScrollOptions.auto, which enables + scrollbars for detailed and engineering screens but not for + embedded displays. + + show_displays : bool, optional + If True (default), open all the included device displays. + If False, do not open any of the displays. + + **kwargs : + Passed to :meth:`TyphosSuite.add_device` + """ + return cls.from_devices([device], parent=parent, tools=tools, pin=pin, + content_layout=content_layout, + default_display_type=default_display_type, + scroll_option=scroll_option, + show_displays=show_displays, + **kwargs)
+ +
[docs] @classmethod + def from_devices( + cls, + devices: list[Device], + parent: QtWidgets.QWidget | None = None, + tools: dict[str, type] | None | DEFAULT_TOOLS = DEFAULT_TOOLS, + pin: bool = False, + content_layout: QtWidgets.QLayout | None = None, + default_display_type: DisplayTypes = DisplayTypes.detailed_screen, + scroll_option: ScrollOptions = ScrollOptions.auto, + show_displays: bool = True, + **kwargs, + ) -> TyphosSuite: + """ + Create a new TyphosSuite from an iterator of :class:`ophyd.Device` + + Parameters + ---------- + device : ophyd.Device + + children : bool, optional + Choice to include child Device components + + parent : QWidget + + tools : dict, optional + Tools to load for the object. ``dict`` should be name, class pairs. + By default these will be ``.default_tools``, but ``None`` can be + passed to avoid tool loading completely. + + pin : bool, optional + Pin the parameter tree on startup. + + content_layout : QLayout, optional + Sets the layout for when we have multiple subdisplays + open in the suite. This will have a horizontal layout by + default but can be changed as needed for the use case. + + default_display_type : DisplayTypes, optional + DisplayTypes enum that determines the type of display to open when + we add a device to the suite. Defaults to + DisplayTypes.detailed_screen. + + scroll_option : ScrollOptions, optional + ScrollOptions enum that determines the behavior of scrollbars + in the suite. Default is ScrollOptions.auto, which enables + scrollbars for detailed and engineering screens but not for + embedded displays. + + show_displays : bool, optional + If True (default), open all the included device displays. + If False, do not open any of the displays. + + **kwargs : + Passed to :meth:`TyphosSuite.add_device` + """ + suite = cls( + parent=parent, + pin=pin, + content_layout=content_layout, + default_display_type=default_display_type, + scroll_option=scroll_option, + ) + if tools is not None: + logger.info("Loading Tools ...") + if tools is DEFAULT_TOOLS: + logger.debug("Using default TyphosSuite tools ...") + tools = cls.default_tools + for name, tool in tools.items(): + try: + suite.add_tool(name, tool) + except Exception: + logger.exception("Unable to load %s", type(tool)) + + logger.info("Adding devices ...") + for device in devices: + try: + suite.add_device(device, **kwargs) + if show_displays: + suite.show_subdisplay(device) + except Exception: + logger.exception("Unable to add %r to TyphosSuite", + device.name) + return suite
+ +
[docs] def save(self): + """ + Save suite settings to a file using :meth:`typhos.utils.save_suite`. + + A ``QFileDialog`` will be used to query the user for the desired + location of the created Python file + + The template will be of the form: + + .. code:: + """ + # Note: the above docstring is appended below + + logger.debug("Requesting file location for saved TyphosSuite") + root_dir = os.getcwd() + filename = QtWidgets.QFileDialog.getSaveFileName( + self, 'Save TyphosSuite', root_dir, "Python (*.py)") + if filename: + try: + with open(filename[0], 'w+') as handle: + save_suite(self, handle) + except Exception as exc: + logger.exception("Failed to save TyphosSuite") + utils.raise_to_operator(exc) + else: + logger.debug("No filename chosen")
+ + # Add the template to the docstring + save.__doc__ += textwrap.indent('\n' + utils.saved_template, '\t\t') + + def _get_sidebar(self, widget): + items = {} + for group in self.top_level_groups.values(): + for item in flatten_tree(group): + items[item.value()] = item + return items.get(widget) + + def _show_sidebar(self, widget, dock): + sidebar = self._get_sidebar(widget) + if sidebar: + for item in sidebar.items: + item._mark_shown() + # Make sure we react if the dock is closed outside of our menu + self._connect_partial_weakly( + dock, dock.closing, self.hide_subdisplay, sidebar + ) + else: + logger.warning("Unable to find sidebar item for %r", widget) + + def _add_to_sidebar(self, parameter, category=None): + """Add an item to the sidebar, connecting necessary signals.""" + if category: + # Create or grab our category + if category in self.top_level_groups: + group = self.top_level_groups[category] + else: + logger.debug("Creating new category %r ...", category) + group = ptypes.GroupParameter(name=category) + self._tree.addParameters(group) + self._tree.sortItems(0, QtCore.Qt.AscendingOrder) + logger.debug("Adding %r to category %r ...", + parameter.name(), group.name()) + group.addChild(parameter) + + widget = parameter.value() + if isinstance(widget, QtWidgets.QWidget): + # Setup window to have a parent + widget.setParent(self) + widget.setHidden(True) + + logger.debug("Connecting parameter signals ...") + self._connect_partial_weakly( + parameter, parameter.sigOpen, self.show_subdisplay, parameter + ) + self._connect_partial_weakly( + parameter, parameter.sigHide, self.hide_subdisplay, parameter + ) + if parameter.embeddable: + self._connect_partial_weakly( + parameter, parameter.sigEmbed, self.embed_subdisplay, parameter + ) + return parameter
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/_modules/typhos/textedit.html b/v2.4.1/_modules/typhos/textedit.html new file mode 100644 index 000000000..4f264a7e4 --- /dev/null +++ b/v2.4.1/_modules/typhos/textedit.html @@ -0,0 +1,274 @@ + + + + + + typhos.textedit — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for typhos.textedit

+"""
+Multiline text edit widget.
+
+Variety support pending:
+- Text format
+"""
+import logging
+
+import numpy as np
+from pydm.widgets.base import PyDMWritableWidget
+from qtpy import QtWidgets
+
+from . import variety
+
+logger = logging.getLogger(__name__)
+
+
+
[docs]@variety.uses_key_handlers +@variety.use_for_variety_write('text-multiline') +class TyphosTextEdit(QtWidgets.QWidget, PyDMWritableWidget): + """ + A writable, multiline text editor with support for PyDM Channels. + + Parameters + ---------- + parent : QWidget + The parent widget. + + init_channel : str, optional + The channel to be used by the widget. + """ + + def __init__(self, parent=None, init_channel=None, variety_metadata=None, + ophyd_signal=None): + + self._display_text = None + self._encoding = "utf-8" + self._delimiter = '\n' + self._ophyd_signal = ophyd_signal + self._format = 'plain' + self._raw_value = None + + QtWidgets.QWidget.__init__(self, parent) + PyDMWritableWidget.__init__(self, init_channel=init_channel) + # superclasses do *not* support cooperative init: + # super().__init__(self, parent=parent, init_channel=init_channel) + + self._setup_ui() + self.variety_metadata = variety_metadata + + def _setup_ui(self): + layout = QtWidgets.QVBoxLayout() + self.setLayout(layout) + + self._text_edit = QtWidgets.QTextEdit() + self._send_button = QtWidgets.QPushButton('Send') + self._send_button.clicked.connect(self._send_clicked) + + self._revert_button = QtWidgets.QPushButton('Revert') + self._revert_button.clicked.connect(self._revert_clicked) + + self._button_layout = QtWidgets.QHBoxLayout() + + self._button_layout.addWidget(self._revert_button) + self._button_layout.addWidget(self._send_button) + + layout.addWidget(self._text_edit) + layout.addLayout(self._button_layout) + + def _revert_clicked(self): + self._set_text(self._display_text) + + def _send_clicked(self): + self.send_value() + +
[docs] def value_changed(self, value): + """Receive and update the TyphosTextEdit for a new channel value.""" + self._raw_value = value + super().value_changed(self._from_wire(value)) + self.set_display()
+ + def _to_wire(self, text=None): + """TextEdit text -> numpy array.""" + if text is None: + # text-format: toMarkdown, toHtml + text = self._text_edit.toPlainText() + + text = self._delimiter.join(text.splitlines()) + return np.array(list(text.encode(self._encoding)), + dtype=np.uint8) + + def _from_wire(self, value): + """numpy array/string/bytes -> string.""" + if isinstance(value, (list, np.ndarray)): + return bytes(value).decode(self._encoding) + return value + + def _set_text(self, text): + return self._text_edit.setText(text) + +
[docs] def send_value(self): + """Emit a :attr:`send_value_signal` to update channel value.""" + send_value = self._to_wire() + + try: + self.send_value_signal[np.ndarray].emit(send_value) + except ValueError: + logger.exception( + "send_value error %r with type %r and format %r (widget %r).", + send_value, self.channeltype, self._display_format_type, + self.objectName() + ) + + self._text_edit.document().setModified(False)
+ +
[docs] def write_access_changed(self, new_write_access): + """ + Change the TyphosTextEdit to read only if write access is denied + """ + super().write_access_changed(new_write_access) + self._text_edit.setReadOnly(not new_write_access) + self._send_button.setVisible(new_write_access) + self._revert_button.setVisible(new_write_access)
+ +
[docs] def set_display(self): + """Set the text display of the TyphosTextEdit.""" + if self.value is None or self._text_edit.document().isModified(): + return + + self._display_text = str(self.value) + self._set_text(self._display_text)
+ + variety_metadata = variety.create_variety_property() + + def _reinterpret_text(self): + """Re-interpret the raw value, if formatting and such change.""" + if self._raw_value is not None: + self.value_changed(self._raw_value) + + @variety.key_handler('delimiter') + def _variety_key_handler_delimiter(self, delimiter): + self._delimiter = delimiter + + @variety.key_handler('encoding') + def _variety_key_handler_encoding(self, encoding): + self._encoding = encoding + self._reinterpret_text() + + @variety.key_handler('format') + def _variety_key_handler_format(self, format_): + self._format = format_ + if format_ != 'plain': + logger.warning('Non-plain formats not yet implemented.') + self._reinterpret_text()
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/_modules/typhos/tools/console.html b/v2.4.1/_modules/typhos/tools/console.html new file mode 100644 index 000000000..6bfd268ef --- /dev/null +++ b/v2.4.1/_modules/typhos/tools/console.html @@ -0,0 +1,321 @@ + + + + + + typhos.tools.console — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for typhos.tools.console

+import logging
+import threading
+
+from qtconsole.manager import QtKernelManager
+from qtconsole.rich_jupyter_widget import RichJupyterWidget
+from qtpy import QtCore, QtWidgets
+
+from .. import utils
+
+logger = logging.getLogger(__name__)
+
+
+def _make_jupyter_widget_with_kernel(kernel_name):
+    """
+    Start a kernel, connect to it, and create a RichJupyterWidget to use it.
+
+    Parameters
+    ----------
+    kernel_name : str
+        Kernel name to use.
+    """
+    kernel_manager = QtKernelManager(kernel_name=kernel_name)
+    kernel_manager.start_kernel()
+
+    kernel_client = kernel_manager.client()
+    kernel_client.start_channels()
+
+    jupyter_widget = RichJupyterWidget()
+    jupyter_widget.kernel_manager = kernel_manager
+    jupyter_widget.kernel_client = kernel_client
+    return jupyter_widget
+
+
+
[docs]class TyphosConsole(utils.TyphosBase): + """ + IPython Widget for Typhos Display. + + This widget handles starting a ``JupyterKernel`` and connecting an IPython + console in which the user can type Python commands. It is important to note + that the kernel in which commands are executed is a completely separate + process. This protects the user against locking themselves out of the GUI, + but makes it difficult to pass the Device.. + + To get around this caveat, this widget uses ``happi`` to pass the Device + between the processes. This is not a strict requirement, but if ``happi`` + is not installed, users will need to create a custom ``add_device`` method + if they want their devices loaded in both the GUI and console. + """ + + device_added = QtCore.Signal(object) + kernel_ready = QtCore.Signal() + kernel_shut_down = QtCore.Signal() + +
[docs] def __init__(self, parent=None): + super().__init__(parent=parent) + self._shutting_down = False + + # Setup widget + self.jupyter_widget = _make_jupyter_widget_with_kernel('python3') + self.jupyter_widget.syntax_style = 'monokai' + self.jupyter_widget.set_default_style(colors='Linux') + self.jupyter_widget.kernel_manager.kernel_restarted.connect( + self._handle_kernel_restart + ) + + # Setup kernel readiness checks + self._ready_lock = threading.Lock() + self._kernel_is_ready = False + self._pending_devices = [] + self._pending_commands = [] + + self._device_history = set() + + self._check_readiness_timer = QtCore.QTimer() + self._check_readiness_timer.setInterval(100) + self._check_readiness_timer.timeout.connect(self._wait_for_readiness) + self._check_readiness_timer.start() + self.kernel_ready.connect(self._add_pending_devices) + + # Set the layout + self.setLayout(QtWidgets.QHBoxLayout()) + self.layout().setContentsMargins(0, 0, 0, 0) + self.layout().addWidget(self.jupyter_widget) + + # Ensure we shutdown the kernel + app = QtWidgets.QApplication.instance() + app.aboutToQuit.connect(lambda: self.shutdown(block=True)) + + self.device_added.connect(self._add_device_history)
+ + @property + def kernel_is_ready(self): + """Is the Jupyter kernel ready?""" + return self.kernel_is_alive and self._kernel_is_read + + @property + def kernel_is_alive(self): + """Is the Jupyter kernel alive and not shutting down?""" + return (self.jupyter_widget.kernel_manager.is_alive() and + not self._shutting_down) + + def _add_pending_devices(self): + """Add devices that were requested prior to the kernel being ready.""" + with self._ready_lock: + self._kernel_is_ready = True + + for command in self._pending_commands: + self.execute(command) + + for device in self._pending_devices: + self._add_device(device) + + self._pending_commands = [] + self._pending_devices = [] + + def _wait_for_readiness(self): + """Wait for the kernel to show the prompt.""" + + def looks_ready(text): + return any(line.startswith('In ') for line in text.splitlines()) + + if looks_ready(self._plain_text): + self.kernel_ready.emit() + self._check_readiness_timer.stop() + + def sizeHint(self): + default = super().sizeHint() + default.setWidth(600) + return default + + def shutdown(self, *, block=False): + """Shutdown the Jupyter Kernel.""" + client = self.jupyter_widget.kernel_client + if self._shutting_down: + logger.debug("Kernel is already shutting down") + return + + self._shutting_down = True + logger.debug("Stopping Jupyter Client") + + def cleanup(): + self.jupyter_widget.kernel_manager.shutdown_kernel() + self.kernel_shut_down.emit() + + client.stop_channels() + if block: + cleanup() + else: + QtCore.QTimer.singleShot(0, cleanup) + + def add_device(self, device): + # Add the device after a short delay to allow the console widget time + # to get initialized + with self._ready_lock: + if not self._kernel_is_ready: + self._pending_devices.append(device) + return + + self._add_device(device) + + @property + def _plain_text(self): + """ + Text in the console. + """ + return self.jupyter_widget._control.toPlainText() + + def execute(self, script, *, echo=True, check_readiness=True): + """ + Execute some code in the console. + """ + if echo: + # Can't seem to get `interactive` or `hidden=False` working: + script = '\n'.join((f"print({repr(script)})", script)) + + if check_readiness: + with self._ready_lock: + if not self._kernel_is_ready: + self._pending_commands.append(script) + return + + self.jupyter_widget.kernel_client.execute(script) + + def _add_device(self, device): + try: + script = utils.code_from_device(device) + self.execute(script) + except Exception: + # Prevent traceback from being displayed + logger.error("Unable to add device %r to TyphosConsole.", + device.name) + else: + self.device_added.emit(device) + + def _handle_kernel_restart(self): + logger.debug('Kernel was restarted.') + for dev in self._device_history: + self.add_device(dev) + + def _add_device_history(self, device): + self._device_history.add(device)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/_modules/typhos/tools/log.html b/v2.4.1/_modules/typhos/tools/log.html new file mode 100644 index 000000000..8aef1cf69 --- /dev/null +++ b/v2.4.1/_modules/typhos/tools/log.html @@ -0,0 +1,153 @@ + + + + + + typhos.tools.log — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for typhos.tools.log

+import logging
+
+from pydm.widgets.logdisplay import PyDMLogDisplay
+from qtpy.QtWidgets import QVBoxLayout
+
+from ..utils import TyphosBase
+
+
+
[docs]class TyphosLogDisplay(TyphosBase): + """Typhos Logging Display.""" +
[docs] def __init__(self, level=logging.INFO, parent=None): + super().__init__(parent=parent) + # Set the logname to be non-existant so that we do not attach to the + # root logger. This causes issue if this widget is closed before the + # end of the Python session. For the long term this issue will be + # resolved with https://github.com/slaclab/pydm/issues/474 + self.logdisplay = PyDMLogDisplay(logname='not_set', level=level, + parent=self) + self.setLayout(QVBoxLayout()) + self.layout().addWidget(self.logdisplay)
+ + def add_device(self, device): + """Add a device to the logging display.""" + super().add_device(device) + # If this is the first device + if len(self.devices) == 1: + self.logdisplay.logName = device.log.name + # If we have already attached a device, just set it to NOTSET to let + # the existing handler do all the filtering + else: + device.log.setLevel(logging.NOTSET) + logger = getattr(device.log, 'logger', device.log) + logger.addHandler(self.logdisplay.handler)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/_modules/typhos/tools/plot.html b/v2.4.1/_modules/typhos/tools/plot.html new file mode 100644 index 000000000..f921a1e1a --- /dev/null +++ b/v2.4.1/_modules/typhos/tools/plot.html @@ -0,0 +1,303 @@ + + + + + + typhos.tools.plot — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for typhos.tools.plot

+"""
+Typhos Plotting Interface
+"""
+import logging
+
+from qtpy import QtCore, QtGui
+from qtpy.QtCore import Qt, Slot
+from qtpy.QtWidgets import (QComboBox, QHBoxLayout, QLabel, QPushButton,
+                            QVBoxLayout)
+from timechart.displays.main_display import TimeChartDisplay
+from timechart.utilities.utils import random_color
+
+from .. import utils
+from ..cache import get_global_describe_cache
+
+logger = logging.getLogger(__name__)
+
+
+
[docs]class TyphosTimePlot(utils.TyphosBase): + """ + Generalized widget for plotting Ophyd signals. + + This widget is a ``TimeChartDisplay`` wrapped with some convenient + functions for adding signals by their attribute name. + + Parameters + ---------- + parent : QWidget + """ + +
[docs] def __init__(self, parent=None): + super().__init__(parent=parent) + # Setup layout + self.setLayout(QVBoxLayout()) + self.layout().setContentsMargins(2, 2, 2, 2) + + self._model = QtGui.QStandardItemModel() + self._proxy_model = QtCore.QSortFilterProxyModel() + self._proxy_model.setSourceModel(self._model) + + self._available_signals = {} + + self.signal_combo = QComboBox() + self.signal_combo.setModel(self._proxy_model) + self.signal_combo_label = QLabel('Available Signals: ') + + self.signal_create = QPushButton('Connect') + self.signal_combo_layout = QHBoxLayout() + self.signal_combo_layout.addWidget(self.signal_combo_label, 0) + self.signal_combo_layout.addWidget(self.signal_combo, 1) + self.signal_combo_layout.addWidget(self.signal_create, 0) + self.signal_create.clicked.connect(self.creation_requested) + self.layout().addLayout(self.signal_combo_layout) + # Add timechart + self.timechart = TimeChartDisplay(show_pv_add_panel=False) + self.layout().addWidget(self.timechart) + cache = get_global_describe_cache() + cache.new_description.connect(self._new_description, + Qt.QueuedConnection)
+ + @property + def channel_to_curve(self): + """ + A dictionary of channel_name to curve. + """ + return dict(self.timechart.channel_map) + + def add_available_signal(self, signal, name): + """ + Add an Ophyd signal to the list of available channels. + + If the Signal is not an EPICS Signal object you are responsible for + registering this yourself, if not already done. + + Parameters + ---------- + signal : ophyd.Signal + + name : str + Alias for signal to display in QComboBox. + + Raises + ------ + ValueError + If a signal of the same name already is available. + """ + if name in self._available_signals: + raise ValueError('Signal already available') + + channel = utils.channel_from_signal(signal) + self._available_signals[name] = (signal, channel) + item = QtGui.QStandardItem(name) + item.setData(channel, Qt.UserRole) + self._model.appendRow(item) + self._model.sort(0) + + def add_curve(self, channel, name=None, color=None, **kwargs): + """ + Add a curve to the plot. + + Parameters + ---------- + channel : str + PyDMChannel address. + + name : str, optional + Name of TimePlotCurveItem. If None is given, the ``channel`` is + used. + + color : QColor, optional + Color to display line in plot. If None is given, a QColor will be + chosen randomly. + + **kwargs + Passed to :meth:`timechart.add_y_channel`. + """ + name = name or channel + # Create a random color if None is supplied + if not color: + color = random_color() + logger.debug("Adding %s to plot ...", channel) + self.timechart.add_y_channel(pv_name=channel, curve_name=name, + color=color, **kwargs) + + @Slot() + def remove_curve(self, name): + """ + Remove a curve from the plot. + + Parameters + ---------- + name : str + Name of the curve to remove. This should match the name given + during the call of :meth:`.add_curve`. + """ + logger.debug("Removing %s from TyphosTimePlot ...", name) + self.timechart.remove_curve(name) + + @Slot() + def creation_requested(self): + """ + Reaction to ``create_button`` press. + + Observes the state of the selection widgets and makes the appropriate + call to :meth:`.add_curve`. + """ + # Find requested channel + name = self.signal_combo.currentText() + idx = self.signal_combo.currentIndex() + channel = self.signal_combo.itemData(idx) + # Add to the plot + self.add_curve(channel, name=name) + + @Slot(object, dict) + def _new_description(self, signal, desc): + name = f'{signal.root.name}.{signal.dotted_name}' + if 'dtype' not in desc: + # Marks an error in retrieving the description + logger.debug("Ignoring signal without description %s", name) + return + + # Only include scalars + if desc['dtype'] not in ('integer', 'number'): + logger.debug("Ignoring non-scalar signal %s", name) + return + + # Add to list of available signal + try: + self.add_available_signal(signal, name) + except ValueError: + # Signal already added + return + + def add_device(self, device): + """Add a device and it's component signals to the plot.""" + super().add_device(device) + + cache = get_global_describe_cache() + for signal in utils.get_all_signals_from_device(device, + include_lazy=False): + desc = cache.get(signal) + if desc is not None: + self._new_description(signal, desc)
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/_modules/typhos/tweakable.html b/v2.4.1/_modules/typhos/tweakable.html new file mode 100644 index 000000000..52518d894 --- /dev/null +++ b/v2.4.1/_modules/typhos/tweakable.html @@ -0,0 +1,208 @@ + + + + + + typhos.tweakable — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for typhos.tweakable

+"""
+Tweakable value widget.
+
+Variety support pending:
+- everything
+"""
+import logging
+
+import qtpy
+from qtpy import QtCore
+
+from . import utils, variety
+
+logger = logging.getLogger(__name__)
+
+
+
[docs]@variety.uses_key_handlers +@variety.use_for_variety_write('scalar-tweakable') +class TyphosTweakable(utils.TyphosBase): + # TODO rearrange package: widgets.TyphosDesignerMixin): + """ + Widget for a tweakable scalar. + + Parameters + ---------- + parent : QWidget + The parent widget. + + init_channel : str, optional + The channel to be used by the widget. + + Notes + ----- + """ + + ui_template = utils.ui_dir / 'widgets' / 'tweakable.ui' + _readback_attr = 'readback' + _setpoint_attr = 'setpoint' + + def __init__(self, parent=None, init_channel=None, variety_metadata=None, + ophyd_signal=None): + + self._ophyd_signal = ophyd_signal + super().__init__(parent=parent) + + self.ui = qtpy.uic.loadUi(str(self.ui_template), self) + self.ui.readback.channel = init_channel + self.ui.setpoint.channel = init_channel + self.ui.tweak_positive.clicked.connect(self.positive_tweak) + self.ui.tweak_negative.clicked.connect(self.negative_tweak) + + self.variety_metadata = variety_metadata + + variety_metadata = variety.create_variety_property() + + def _update_variety_metadata(self, *, display_format=None, **kwargs): + display_format = variety.get_display_format(display_format) + self.ui.readback.displayFormat = display_format + self.ui.setpoint.displayFormat = display_format + + variety._warn_unhandled_kwargs(self, kwargs) + +
[docs] def tweak(self, offset): + """Tweak by the given ``offset``.""" + try: + setpoint = float(self.readback.text()) + float(offset) + except Exception: + logger.exception('Tweak failed') + return + + self.ui.setpoint.setText(str(setpoint)) + self.ui.setpoint.send_value()
+ +
[docs] @QtCore.Slot() + def positive_tweak(self): + """Tweak positive by the amount listed in ``ui.tweak_value``""" + try: + self.tweak(float(self.tweak_value.text())) + except Exception: + logger.exception('Tweak failed')
+ +
[docs] @QtCore.Slot() + def negative_tweak(self): + """Tweak negative by the amount listed in ``ui.tweak_value``""" + try: + self.tweak(-float(self.tweak_value.text())) + except Exception: + logger.exception('Tweak failed')
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/_modules/typhos/utils.html b/v2.4.1/_modules/typhos/utils.html new file mode 100644 index 000000000..9c0aae592 --- /dev/null +++ b/v2.4.1/_modules/typhos/utils.html @@ -0,0 +1,1773 @@ + + + + + + typhos.utils — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for typhos.utils

+"""
+Utility functions for typhos
+"""
+from __future__ import annotations
+
+import atexit
+import collections
+import contextlib
+import functools
+import importlib.util
+import inspect
+import io
+import json
+import logging
+import operator
+import os
+import pathlib
+import random
+import re
+import threading
+import weakref
+from types import MethodType
+from typing import Iterable
+
+import entrypoints
+import ophyd
+import ophyd.sim
+from ophyd import Device
+from ophyd.signal import EpicsSignalBase, EpicsSignalRO
+from pydm.config import STYLESHEET as PYDM_USER_STYLESHEET
+from pydm.config import STYLESHEET_INCLUDE_DEFAULT as PYDM_INCLUDE_DEFAULT
+from pydm.exception import raise_to_operator  # noqa
+from pydm.utilities.stylesheet import \
+    GLOBAL_STYLESHEET as PYDM_DEFAULT_STYLESHEET
+from pydm.widgets.base import PyDMWritableWidget
+from qtpy import QtCore, QtGui, QtWidgets
+from qtpy.QtCore import QSize
+from qtpy.QtGui import QColor, QMovie, QPainter
+from qtpy.QtWidgets import QWidget
+
+from typhos import plugins
+
+try:
+    import happi
+except ImportError:
+    happi = None
+
+logger = logging.getLogger(__name__)
+
+# Entry point for directories of custom widgets
+# Must be one of:
+# - str
+# - pathlib.Path
+# - list of such objects
+TYPHOS_ENTRY_POINT_KEY = 'typhos.ui'
+MODULE_PATH = pathlib.Path(__file__).parent.resolve()
+ui_dir = MODULE_PATH / 'ui'
+ui_core_dir = ui_dir / 'core'
+
+GrabKindItem = collections.namedtuple('GrabKindItem',
+                                      ('attr', 'component', 'signal'))
+DEBUG_MODE = bool(os.environ.get('TYPHOS_DEBUG', False))
+
+# Help settings:
+# TYPHOS_HELP_URL (str): The help URL format string
+HELP_URL = os.environ.get('TYPHOS_HELP_URL', "").strip()
+HELP_WEB_ENABLED = bool(HELP_URL.strip())
+
+# TYPHOS_HELP_HEADERS (json): headers to pass to HELP_URL
+HELP_HEADERS = json.loads(os.environ.get('TYPHOS_HELP_HEADERS', "") or "{}")
+HELP_HEADERS_HOSTS = os.environ.get("TYPHOS_HELP_HEADERS_HOSTS", "").split(",")
+
+# TYPHOS_HELP_TOKEN (str): An optional token for the bearer authentication
+# scheme - e.g., personal access tokens with Confluence
+HELP_TOKEN = os.environ.get('TYPHOS_HELP_TOKEN', None)
+if HELP_TOKEN:
+    HELP_HEADERS["Authorization"] = f"Bearer {HELP_TOKEN}"
+
+# TYPHOS_JIRA_URL (str): The jira REST API collector URL
+JIRA_URL = os.environ.get('TYPHOS_JIRA_URL', "").strip()
+# TYPHOS_JIRA_HEADERS (json): headers to pass to JIRA_URL
+JIRA_HEADERS = json.loads(
+    os.environ.get('TYPHOS_JIRA_HEADERS', '{"X-Atlassian-Token": "no-check"}')
+    or "{}"
+)
+# TYPHOS_JIRA_TOKEN (str): An optional token for the bearer authentication
+# scheme - e.g., personal access tokens with Confluence
+JIRA_TOKEN = os.environ.get('TYPHOS_JIRA_TOKEN', None)
+# TYPHOS_JIRA_EMAIL_SUFFIX (str): The default e-mail address suffix
+JIRA_EMAIL_SUFFIX = os.environ.get('TYPHOS_JIRA_EMAIL_SUFFIX', "").strip()
+if JIRA_TOKEN:
+    JIRA_HEADERS["Authorization"] = f"Bearer {JIRA_TOKEN}"
+
+if happi is None:
+    logger.info("happi is not installed; some features may be unavailable")
+
+
+def _get_display_paths():
+    """
+    Get all display paths.
+
+    This includes, in order:
+
+    - The $PYDM_DISPLAYS_PATH environment variable
+    - The typhos.ui entry point
+    - typhos built-ins
+    """
+    paths = os.environ.get('PYDM_DISPLAYS_PATH', '')
+    for path in paths.split(os.pathsep):
+        path = pathlib.Path(path).expanduser().resolve()
+        if path.exists() and path.is_dir():
+            yield path
+
+    _entries = entrypoints.get_group_all(TYPHOS_ENTRY_POINT_KEY)
+    entry_objs = []
+
+    for entry in _entries:
+        try:
+            obj = entry.load()
+        except Exception:
+            msg = (f'Failed to load {TYPHOS_ENTRY_POINT_KEY} '
+                   f'entry: {entry.name}.')
+            logger.error(msg)
+            logger.debug(msg, exc_info=True)
+            continue
+        if isinstance(obj, list):
+            entry_objs.extend(obj)
+        else:
+            entry_objs.append(obj)
+
+    for obj in entry_objs:
+        try:
+            yield pathlib.Path(obj)
+        except Exception:
+            msg = (f'{TYPHOS_ENTRY_POINT_KEY} entry point '
+                   f'{entry.name}: {obj} is not a valid path!')
+            logger.error(msg)
+            logger.debug(msg, exc_info=True)
+
+    yield ui_dir / 'core'
+    yield ui_dir / 'devices'
+
+
+DISPLAY_PATHS = list(_get_display_paths())
+
+
+if hasattr(ophyd.signal, 'SignalRO'):
+    SignalRO = ophyd.signal.SignalRO
+else:
+    # SignalRO was re-introduced to ophyd.signal in December 2019 (1f83a055).
+    # If unavailable, fall back to our previous definition:
+    class SignalRO(ophyd.sim.SynSignalRO):
+        def __init__(self, value=0, *args, **kwargs):
+            self._value = value
+            super().__init__(*args, **kwargs)
+            self._metadata.update(
+                connected=True,
+                write_access=False,
+            )
+
+        def get(self):
+            return self._value
+
+
+
[docs]def channel_from_signal(signal, read=True): + """ + Create a PyDM address from arbitrary signal type + """ + if isinstance(signal, EpicsSignalBase): + if read: + # For readback widgets, focus on the _read_pv only: + attrs = ["_read_pv"] + else: + # For setpoint widgets, _write_pv may exist (and differ) from + # _read_pv, try it first: + attrs = ["_write_pv", "_read_pv"] + + # Some customizations of EpicsSignalBase may have different attributes. + # Try the attributes, but don't fail if they are not present: + for attr in attrs: + pv_instance = getattr(signal, attr, None) + pvname = getattr(pv_instance, "pvname", None) + if pvname is not None and isinstance(pvname, str): + return channel_name(pvname) + + return channel_name(signal.name, protocol='sig')
+ + +
[docs]def is_signal_ro(signal): + """ + Return whether the signal is read-only, based on its class. + + In the future this may be easier to do through improvements to + introspection in the ophyd library. Until that day we need to check classes + """ + return isinstance(signal, (SignalRO, EpicsSignalRO, ophyd.sim.SynSignalRO))
+ + +
[docs]def channel_name(pv, protocol='ca'): + """ + Create a valid PyDM channel from a PV name + """ + return protocol + '://' + pv
+ + +
[docs]def clean_attr(attr): + """ + Create a nicer, human readable alias from a Python attribute name + """ + return attr.replace('.', ' ').replace('_', ' ')
+ + +
[docs]def clean_name(device, strip_parent=True): + """ + Create a human readable name for a device + + Parameters + ---------- + device: ophyd.Device + + strip_parent: bool or Device + Remove the parent name of the device from name. If strip_parent is + True, the name of the direct parent of the device is stripped. If a + device is provided the name of that device is used. This allows + specification for removal at any point of the device schema + """ + name = device.name + if strip_parent and device.parent: + if isinstance(strip_parent, Device): + parent_name = strip_parent.name + else: + parent_name = device.parent.name + name = name.replace(parent_name + '_', '') + # Return the cleaned alias + return clean_attr(name)
+ + +
[docs]def use_stylesheet( + dark: bool = False, + widget: QtWidgets.QWidget | None = None, +) -> None: + """ + Use the Typhos stylesheet + + This is no longer used directly in typhos in favor of + apply_standard_stylesheets. + + This can still be used if you want the legacy behavior of ignoring PyDM + environment variables. The function is unchanged. + + Parameters + ---------- + dark: bool, optional + Whether or not to use the QDarkStyleSheet theme. By default the light + theme is chosen. + """ + # Dark Style + if dark: + import qdarkstyle + style = qdarkstyle.load_stylesheet_pyqt5() + # Light Style + else: + # Load the path to the file + style_path = os.path.join(ui_dir, 'style.qss') + if not os.path.exists(style_path): + raise OSError("Unable to find Typhos stylesheet in {}" + "".format(style_path)) + # Load the stylesheet from the file + with open(style_path) as handle: + style = handle.read() + if widget is None: + widget = QtWidgets.QApplication.instance() + # We can set Fusion style if it is an application + if isinstance(widget, QtWidgets.QApplication): + widget.setStyle(QtWidgets.QStyleFactory.create('Fusion')) + + # Set Stylesheet + widget.setStyleSheet(style)
+ + +
[docs]def compose_stylesheets(stylesheets: Iterable[str | pathlib.Path]) -> str: + """ + Combine multiple qss stylesheets into one qss stylesheet. + + If two stylesheets make conflicting specifications, the one passed into + this function first will take priority. + + This is accomplished by placing the text from the highest-priority + stylesheets at the bottom of the combined stylesheet. Stylesheets are + evaluated in order from top to bottom, and newer elements on the bottom + will override elements at the top. + + Parameters + ---------- + stylesheets : iterable of str or pathlib.Path + An itetable, such as a list, of the stylesheets to combine. + Each element can either be a fully-loaded stylesheet or a full path to + a stylesheet. Stylesheet paths must end in the .qss suffix. + In the unlikely event that a string is both a valid path + and a valid stylesheet, it will be interpretted as a path, + even if no file exists at that path. + + Returns + ------- + composed_style : str + A string suitable for passing into QWidget.setStylesheet that + incorporates all of the input stylesheets. + + Raises + ------ + OSError + If any error is encountered while reading a file + TypeError + If the input is not a valid type + """ + style_parts = [] + for sheet in stylesheets: + path = pathlib.Path(sheet) + if isinstance(sheet, pathlib.Path) or path.suffix == ".qss": + with path.open() as fd: + style_parts.append(fd.read()) + elif isinstance(sheet, str): + style_parts.append(sheet) + else: + raise TypeError(f"Invalid input {sheet} of type {type(sheet)}") + return "\n".join(reversed(style_parts))
+ + +
[docs]def apply_standard_stylesheets( + dark: bool = False, + paths: Iterable[str] | None = None, + include_pydm: bool = True, + widget: QtWidgets.QWidget | None = None, +) -> None: + """ + Apply all the stylesheets at once, along with the Fusion style. + + Applies stylesheets with the following priority order: + - Any existing stylesheet data on the widget + - User stylesheets in the paths argument + - User stylesheets in PYDM_STYLESHEET (which behaves as a path) + - Typhos's stylesheet (either the dark or the light variant) + - PyDM's built-in stylesheet, if PYDM_STYLESHEET_INCLUDE_DEFAULT is set. + + The Fusion style can only be applied to a QApplication. + + Parameters + ---------- + dark : bool, optional + Whether or not to use the QDarkStyleSheet theme. By default the light + theme is chosen. + paths : iterable of str, optional + User-provided paths to stylesheets to apply. + include_pydm : bool, optional + Whether or not to use the stylesheets defined in the pydm environment + variables. Defaults to True. + widget : QWidget, optional + The widget to apply the stylesheet to. + If omitted, apply to the whole QApplication. + """ + if isinstance(widget, QtWidgets.QApplication): + widget.setStyle(QtWidgets.QStyleFactory.create('Fusion')) + + stylesheets = [] + + if widget is None: + widget = QtWidgets.QApplication.instance() + stylesheets.append(widget.styleSheet()) + + if paths is not None: + stylesheets.extend(paths) + + if include_pydm and PYDM_USER_STYLESHEET: + stylesheets.extend(PYDM_USER_STYLESHEET.split(os.pathsep)) + + if dark: + import qdarkstyle + stylesheets.append(qdarkstyle.load_stylesheet_pyqt5()) + else: + stylesheets.append(ui_dir / 'style.qss') + + if include_pydm and PYDM_INCLUDE_DEFAULT: + stylesheets.append(PYDM_DEFAULT_STYLESHEET) + + widget.setStyleSheet(compose_stylesheets(stylesheets))
+ + +
[docs]def random_color(): + """Return a random hex color description""" + return QColor(random.randint(0, 255), + random.randint(0, 255), + random.randint(0, 255))
+ + +
[docs]class TyphosLoading(QtWidgets.QLabel): + """ + A QLabel with an animation for loading status. + + Attributes + ---------- + LOADING_TIMEOUT_MS : int + The timeout value in milliseconds for when to stop the animation + and replace it with a default timeout message. + + """ + LOADING_TIMEOUT_MS = 10000 + loading_gif = None + + def __init__(self, timeout_message, *, parent=None, **kwargs): + self.timeout_message = timeout_message + super().__init__(parent=parent, **kwargs) + self._icon_size = QSize(32, 32) + if TyphosLoading.loading_gif is None: + loading_path = os.path.join(ui_dir, 'loading.gif') + TyphosLoading.loading_gif = QMovie(loading_path) + self._animation = TyphosLoading.loading_gif + self._animation.setScaledSize(self._icon_size) + self.setMovie(self._animation) + self._animation.start() + if self.LOADING_TIMEOUT_MS > 0: + QtCore.QTimer.singleShot(self.LOADING_TIMEOUT_MS, + self._handle_timeout) + + self.setContextMenuPolicy(QtCore.Qt.DefaultContextMenu) + +
[docs] def contextMenuEvent(self, event): + menu = QtWidgets.QMenu(parent=self) + + def copy_to_clipboard(*, text): + clipboard = QtWidgets.QApplication.instance().clipboard() + clipboard.setText(text) + + menu.addSection('Copy to clipboard') + action = menu.addAction('&All') + action.triggered.connect(functools.partial(copy_to_clipboard, + text=self.toolTip())) + menu.addSeparator() + + for line in self.toolTip().splitlines(): + action = menu.addAction(line) + action.triggered.connect( + functools.partial(copy_to_clipboard, text=line) + ) + + menu.exec_(self.mapToGlobal(event.pos()))
+ + def _handle_timeout(self): + self._animation.stop() + self.setMovie(None) + self.setText(self.timeout_message) + + @property + def iconSize(self): + return self._icon_size + + @iconSize.setter + def iconSize(self, size): + self._icon_size = size + self._animation.setScaledSize(self._icon_size)
+ + +class TyphosObject: + def __init__(self, *args, **kwargs): + self.devices = list() + super().__init__(*args, **kwargs) + + def add_device(self, device): + """ + Add a new device to the widget + + Parameters + ---------- + device : ophyd.Device + """ + logger.debug("Adding device %s ...", device.name) + self.devices.append(device) + + def paintEvent(self, event): + # This is necessary because by default QWidget ignores stylesheets + # https://wiki.qt.io/How_to_Change_the_Background_Color_of_QWidget + opt = QtWidgets.QStyleOption() + opt.initFrom(self) + painter = QPainter() + painter.begin(self) + self.style().drawPrimitive(QtWidgets.QStyle.PE_Widget, opt, painter, + self) + super().paintEvent(event) + + @classmethod + def from_device(cls, device, parent=None, **kwargs): + """ + Create a new instance of the widget for a Device + + Shortcut for: + + .. code:: + + tool = TyphosBase(parent=parent) + tool.add_device(device) + + Parameters + ---------- + device: ophyd.Device + + parent: QWidget + """ + instance = cls(parent=parent, **kwargs) + instance.add_device(device) + return instance + + +
[docs]class WeakPartialMethodSlot: + """ + A PyQt-compatible slot for a partial method. + + This utility handles deleting the connection when the method class instance + gets garbage collected. This avoids cycles in the garbage collector + that would prevent the instance from being garbage collected prior to the + program exiting. + + Parameters + ---------- + signal_owner : QtCore.QObject + The owner of the signal. + signal : QtCore.Signal + The signal instance itself. + method : instance method + The method slot to call when the signal fires. + *args : + Arguments to pass to the method. + **kwargs : + Keyword arguments to pass to the method. + """ + def __init__( + self, + signal_owner: QtCore.QObject, + signal: QtCore.Signal, + method: MethodType, + *args, + **kwargs + ): + self.signal = signal + self.signal.connect(self._call, QtCore.Qt.QueuedConnection) + self.method = weakref.WeakMethod(method) + self._method_finalizer = weakref.finalize( + method.__self__, self._method_destroyed + ) + self._signal_finalizer = weakref.finalize( + signal_owner, self._signal_destroyed + ) + self.partial_args = args + self.partial_kwargs = kwargs + + def _signal_destroyed(self): + """Callback: the owner of the signal was destroyed; clean up.""" + if self.signal is None: + return + + self.method = None + self.partial_args = [] + self.partial_kwargs = {} + self.signal = None + + def _method_destroyed(self): + """Callback: the owner of the method was destroyed; clean up.""" + if self.signal is None: + return + + self.method = None + self.partial_args = [] + self.partial_kwargs = {} + try: + self.signal.disconnect(self._call) + except Exception: + ... + self.signal = None + + def _call(self, *new_args): + """ + PyQt callback slot which handles the internal WeakMethod. + + This method currently throws away arguments passed in from the signal. + This is for backward-compatibility to how the previous + `partial()`-using implementation worked. + + If reused beyond the TyphosSuite, this class may need revisiting in the + future. + """ + method = self.method() + if method is None: + self._method_destroyed() + return + + return method(*self.partial_args, **self.partial_kwargs)
+ + +
[docs]class TyphosBase(TyphosObject, QWidget): + """Base widget for all Typhos widgets that interface with devices""" + + _weak_partials_: list[WeakPartialMethodSlot] + + def __init__(self, *args, **kwargs): + self._weak_partials_ = [] + super().__init__(*args, **kwargs) + + def _connect_partial_weakly( + self, + signal_owner: QtCore.QObject, + signal: QtCore.Signal, + method: MethodType, + *args, + **kwargs + ): + """ + Connect the provided signal to an instance method via + WeakPartialMethodSlot. + + Parameters + ---------- + signal_owner : QtCore.QObject + The owner of the signal. + signal : QtCore.Signal + The signal instance itself. + method : instance method + The method slot to call when the signal fires. + *args : + Arguments to pass to the method. + **kwargs : + Keyword arguments to pass to the method. + """ + slot = WeakPartialMethodSlot( + signal_owner, signal, method, *args, **kwargs + ) + self._weak_partials_.append(slot)
+ + +
[docs]def make_identifier(name): + """Make a Python string into a valid Python identifier""" + # That was easy + if name.isidentifier(): + return name + # Lowercase + name = name.lower() + # Leading / following whitespace + name = name.strip() + # Intermediate whitespace should be underscores + name = re.sub('[\\s\\t\\n]+', '_', name) + # Remove invalid characters + name = re.sub('[^0-9a-zA-Z_]', '', name) + # Remove leading characters until we find a letter or an underscore + name = re.sub('^[^a-zA-Z_]+', '', name) + return name
+ + +
[docs]def flatten_tree(param): + """Flatten a tree of parameters""" + tree = [param] + for child in param.childs: + tree.extend(flatten_tree(child)) + return tree
+ + +
[docs]def clear_layout(layout): + """Clear a QLayout""" + while layout.count(): + child = layout.takeAt(0) + if child.widget(): + child.widget().deleteLater() + elif child.layout(): + clear_layout(child.layout())
+ + +
[docs]def reload_widget_stylesheet(widget, cascade=False): + """Reload the stylesheet of the provided widget""" + widget.style().unpolish(widget) + widget.style().polish(widget) + widget.update() + if cascade: + for child in widget.children(): + if isinstance(child, QWidget): + reload_widget_stylesheet(child, cascade=True)
+ + +
[docs]def save_suite(suite, file_or_buffer): + """ + Create a file capable of relaunching the TyphosSuite + + Parameters + ---------- + suite: TyphosSuite + + file_or_buffer : str or file-like + Either a path to the file or a handle that supports ``write`` + """ + # Accept file-like objects or a handle + if isinstance(file_or_buffer, str): + handle = open(file_or_buffer, 'w+') + else: + handle = file_or_buffer + logger.debug("Saving TyphosSuite contents to %r", handle) + devices = [device.name for device in suite.devices] + handle.write(saved_template.format(devices=devices))
+ + +
[docs]def load_suite(path, cfg=None): + """" + Load a file saved via Typhos + + Parameters + ---------- + path: str + Path to file describing the ``TyphosSuite``. This needs to be of the + format created by the :meth:`.save_suite` function. + + cfg: str, optional + Location of happi configuration file to use to load devices. If not + entered the ``$HAPPI_CFG`` environment variable will be used. + Returns + ------- + suite: TyphosSuite + """ + logger.info("Importing TyphosSuite from file %r ...", path) + module_name = pathlib.Path(path).name.replace('.py', '') + spec = importlib.util.spec_from_file_location(module_name, + path) + suite_module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(suite_module) + if hasattr(suite_module, 'create_suite'): + logger.debug("Executing create_suite method from %r", suite_module) + return suite_module.create_suite(cfg=cfg) + else: + raise AttributeError("Imported module has no 'create_suite' method!")
+ + +saved_template = """\ +import sys +import typhos.cli + +devices = {devices} + +def create_suite(cfg=None): + return typhos.cli.create_suite(devices, cfg=cfg) + +if __name__ == '__main__': + typhos.cli.typhos_cli(devices + sys.argv[1:]) +""" + + +
[docs]@contextlib.contextmanager +def no_device_lazy_load(): + ''' + Context manager which disables the ophyd.device.Device + `lazy_wait_for_connection` behavior and later restore its value. + ''' + old_val = Device.lazy_wait_for_connection + try: + Device.lazy_wait_for_connection = False + yield + finally: + Device.lazy_wait_for_connection = old_val
+ + +
[docs]def pyqt_class_from_enum(enum): + ''' + Create an inheritable base class from a Python Enum, which can also be used + for Q_ENUMS. + ''' + enum_dict = {item.name: item.value for item in list(enum)} + return type(enum.__name__, (object, ), enum_dict)
+ + +def _get_template_filenames_for_class(class_, view_type, *, include_mro=True): + ''' + Yields all possible template filenames that can be used for the class, in + order of priority, including those in the class MRO. + + This does not include the file extension, to be appended by the caller. + ''' + for cls in class_.mro(): + module = cls.__module__ + name = cls.__name__ + yield f'{module}.{name}.{view_type}' + yield f'{name}.{view_type}' + yield f'{name}' + + if not include_mro: + break + + +
[docs]def remove_duplicate_items(list_): + 'Return a de-duplicated list/tuple of items in `list_`, retaining order' + cls = type(list_) + return cls(sorted(set(list_), key=list_.index))
+ + +
[docs]def is_standard_template(template): + """ + Is the template a core one provided with typhos? + + Parameters + ---------- + template : str or pathlib.Path + """ + common_path = pathlib.Path(os.path.commonpath((template, ui_core_dir))) + return common_path == ui_core_dir
+ + +
[docs]def find_templates_for_class(cls, view_type, paths, *, extensions=None, + include_mro=True): + ''' + Given a class `cls` and a view type (such as 'detailed'), search `paths` + for potential templates to show. + + Parameters + ---------- + cls : class + Search for templates with this class name + view_type : {'detailed', 'engineering', 'embedded'} + The view type + paths : iterable + Iterable of paths to be expanded, de-duplicated, and searched + extensions : str or list, optional + The template filename extension (default is ``'.ui'`` or ``'.py'``) + include_mro : bool, optional + Include superclasses - those in the MRO - of ``cls`` as well + + Yields + ------ + path : pathlib.Path + A matching path, ordered from most-to-least specific. + ''' + if not inspect.isclass(cls): + cls = type(cls) + + if not extensions: + extensions = ['.py', '.ui'] + elif isinstance(extensions, str): + extensions = [extensions] + + from .cache import _CachedPath + paths = remove_duplicate_items( + [_CachedPath.from_path(p) for p in paths] + ) + + for candidate_filename in _get_template_filenames_for_class( + cls, view_type, include_mro=include_mro): + for extension in extensions: + for path in paths: + for match in path.glob(candidate_filename + extension): + if match.is_file(): + yield match
+ + +
[docs]def find_file_in_paths(filename, *, paths=None): + ''' + Search for filename ``filename`` in the list of paths ``paths`` + + Parameters + ---------- + filename : str or pathlib.Path + The filename + paths : list or iterable, optional + List of paths to search. Defaults to DISPLAY_PATHS. + + Yields + ------ + All filenames that match in the given paths + ''' + if paths is None: + paths = DISPLAY_PATHS + + if isinstance(filename, pathlib.Path): + if filename.is_absolute(): + if filename.exists(): + yield filename + return + + filename = filename.name + + from .cache import _CachedPath + paths = remove_duplicate_items( + [_CachedPath.from_path(p) for p in paths] + ) + + for path in paths: + for match in path.glob(filename): + if match.is_file(): + yield match
+ + +
[docs]def get_device_from_fake_class(cls): + """ + Return the non-fake class, given a fake class + + That is:: + + fake_cls = ophyd.sim.make_fake_device(cls) + get_device_from_fake_class(fake_cls) # -> cls + + Parameters + ---------- + cls : type + The fake class + """ + bases = cls.__bases__ + if not bases or len(bases) != 1: + raise ValueError('Not a fake class based on inheritance') + + actual_class, = bases + + if actual_class not in ophyd.sim.fake_device_cache: + raise ValueError('Not a fake class (ophyd.sim does not know about it)') + + return actual_class
+ + +
[docs]def is_fake_device_class(cls): + """ + Is ``cls`` a fake device from :func:`ophyd.sim.make_fake_device`? + """ + try: + get_device_from_fake_class(cls) + except ValueError: + return False + return True
+ + +
[docs]def code_from_device_repr(device): + """ + Return code to create a device from its ``repr`` information. + + Parameters + ---------- + device : ophyd.Device + """ + try: + module = device.__module__ + except AttributeError: + raise ValueError('Device class must be in a module') from None + + class_name = device.__class__.__name__ + if module == '__main__': + raise ValueError('Device class must be in a module') + + cls = device.__class__ + is_fake = is_fake_device_class(cls) + + full_class_name = f'{module}.{class_name}' + kwargs = '\n '.join(f'{k}={v!r},' for k, v in device._repr_info()) + logger.debug('%r fully qualified Device class: %r', device.name, + full_class_name) + if is_fake: + actual_class = get_device_from_fake_class(cls) + actual_name = f'{actual_class.__module__}.{actual_class.__name__}' + logger.debug('%r fully qualified Device class is fake, based on: %r', + device.name, actual_class) + return f'''\ +import ophyd.sim +import pcdsutils + +{actual_class.__name__} = pcdsutils.utils.import_helper({actual_name!r}) +{class_name} = ophyd.sim.make_fake_device({actual_class.__name__}) +{device.name} = {class_name}( + {kwargs} +) +ophyd.sim.clear_fake_device({device.name}) +''' + + return f'''\ +import pcdsutils + +{class_name} = pcdsutils.utils.import_helper({full_class_name!r}) +{device.name} = {class_name}( + {kwargs} +) +'''
+ + +
[docs]def code_from_device(device): + """ + Generate code required to load ``device`` in another process + """ + is_fake = is_fake_device_class(device.__class__) + if happi is None or not hasattr(device, 'md') or is_fake: + return code_from_device_repr(device) + + happi_name = device.md.name + return f'''\ +import happi +from happi.loader import from_container +client = happi.Client.from_config() +md = client.find_item(name="{happi_name}") +{device.name} = from_container(md) +'''
+ + +
[docs]@contextlib.contextmanager +def subscription_context(*objects, callback, event_type=None, run=True): + ''' + [Context manager] Subscribe to a specific event from all objects + + Unsubscribes all signals before exiting + + Parameters + ---------- + *objects : ophyd.OphydObj + Ophyd objects (signals) to monitor + callback : callable + Callback to run, with same signature as that of + :meth:`ophyd.OphydObj.subscribe`. + event_type : str, optional + The event type to subscribe to + run : bool, optional + Run the previously cached subscription immediately + ''' + obj_to_cid = {} + try: + for obj in objects: + try: + obj_to_cid[obj] = obj.subscribe(callback, + event_type=event_type, run=run) + except Exception: + logger.exception('Failed to subscribe to object %s', obj.name) + yield dict(obj_to_cid) + finally: + for obj, cid in obj_to_cid.items(): + try: + obj.unsubscribe(cid) + except KeyError: + # It's possible that when the object is being torn down, or + # destroyed that this has already been done. + ...
+ + +
[docs]def get_all_signals_from_device(device, include_lazy=False, filter_by=None): + ''' + Get all signals in a given device + + Parameters + ---------- + device : ophyd.Device + ophyd Device to monitor + include_lazy : bool, optional + Include lazy signals as well + filter_by : callable, optional + Filter signals, with signature ``callable(ophyd.Device.ComponentWalk)`` + ''' + if not filter_by: + def filter_by(walk): + return True + + def _get_signals(): + return [ + walk.item + for walk in device.walk_signals(include_lazy=include_lazy) + if filter_by(walk) + ] + + if not include_lazy: + return _get_signals() + + with no_device_lazy_load(): + return _get_signals()
+ + +
[docs]@contextlib.contextmanager +def subscription_context_device(device, callback, event_type=None, run=True, *, + include_lazy=False, filter_by=None): + ''' + [Context manager] Subscribe to ``event_type`` from signals in ``device`` + + Unsubscribes all signals before exiting + + Parameters + ---------- + device : ophyd.Device + ophyd Device to monitor + callback : callable + Callback to run, with same signature as that of + :meth:`ophyd.OphydObj.subscribe` + event_type : str, optional + The event type to subscribe to + run : bool, optional + Run the previously cached subscription immediately + include_lazy : bool, optional + Include lazy signals as well + filter_by : callable, optional + Filter signals, with signature ``callable(ophyd.Device.ComponentWalk)`` + ''' + signals = get_all_signals_from_device(device, include_lazy=include_lazy) + with subscription_context(*signals, callback=callback, + event_type=event_type, run=run) as obj_to_cid: + yield obj_to_cid
+ + +class _ConnectionStatus: + def __init__(self, callback): + self.connected = set() + self.callback = callback + self.lock = threading.Lock() + # NOTE: this will be set externally + self.obj_to_cid = {} + self.objects = set() + + def clear(self): + for obj in list(self.objects): + self.remove_object(obj) + + def _run_callback_hack_on_object(self, obj): + ''' + HACK: peek into ophyd objects to see if they're connected but have + never run metadata callbacks + + This is part of an ongoing ophyd issue and may be removed in the + future. + ''' + if obj not in self.objects: + return + + if obj.connected and obj._args_cache.get('meta') is None: + md = dict(obj.metadata) + if 'connected' not in md: + md['connected'] = True + self._connection_callback(obj=obj, **md) + + def add_object(self, obj): + 'Add an additional object to be monitored' + with self.lock: + if obj in self.objects: + return + + self.objects.add(obj) + try: + self.obj_to_cid[obj] = obj.subscribe( + self._connection_callback, event_type='meta', run=True) + except Exception: + logger.exception('Failed to subscribe to object: %s', obj.name) + self.objects.remove(obj) + else: + self._run_callback_hack_on_object(obj) + + def remove_object(self, obj): + 'Remove an object from being monitored - no more callbacks' + with self.lock: + if obj in self.connected: + self.connected.remove(obj) + + self.objects.remove(obj) + cid = self.obj_to_cid.pop(obj) + try: + obj.unsubscribe(cid) + except KeyError: + # It's possible that when the object is being torn down, or + # destroyed that this has already been done. + ... + + def _connection_callback(self, *, obj, connected, **kwargs): + with self.lock: + if obj not in self.objects: + # May have been removed + return + + if connected and obj not in self.connected: + self.connected.add(obj) + elif not connected and obj in self.connected: + self.connected.remove(obj) + else: + return + + logger.debug('Connection update: %r (obj=%s connected=%s kwargs=%r)', + self, obj.name, connected, kwargs) + self.callback(obj=obj, connected=connected, **kwargs) + + def __repr__(self): + return ( + f'<{self.__class__.__name__} connected={len(self.connected)} ' + f'objects={len(self.objects)}>' + ) + + +
[docs]@contextlib.contextmanager +def connection_status_monitor(*signals, callback): + ''' + [Context manager] Monitor connection status from a number of signals + + Filters out any other metadata updates, only calling once + connected/disconnected + + Parameters + ---------- + *signals : ophyd.OphydObj + Signals to monitor + callback : callable + Callback to run, with same signature as that of + :meth:`ophyd.OphydObj.subscribe`. ``obj`` and ``connected`` are + guaranteed kwargs. + ''' + + status = _ConnectionStatus(callback) + + with subscription_context(*signals, callback=status._connection_callback, + event_type='meta', run=True + ) as status.obj_to_cid: + for sig in signals: + status._run_callback_hack_on_object(sig) + + yield status
+ + +
[docs]class DeviceConnectionMonitorThread(QtCore.QThread): + ''' + Monitor connection status in a background thread + + Parameters + ---------- + device : ophyd.Device + The device to grab signals from + include_lazy : bool, optional + Include lazy signals as well + + Attributes + ---------- + connection_update : QtCore.Signal + Connection update signal with signature:: + + (signal, connected, metadata_dict) + ''' + + connection_update = QtCore.Signal(object, bool, dict) + + def __init__(self, device, include_lazy=False, **kwargs): + super().__init__(**kwargs) + self.device = device + self.include_lazy = include_lazy + self._update_event = threading.Event() + + atexit.register(self.stop) + +
[docs] def stop(self, *, wait_ms: int = 1000): + """ + Stop the background thread and clean up. + + Parameters + ---------- + wait_ms : int, optional + Time to wait for the background thread to exit. Set to 0 to + disable. + """ + if not self.isRunning(): + return + + self.requestInterruption() + if wait_ms > 0: + self.wait(msecs=wait_ms)
+ + def callback(self, obj, connected, **kwargs): + self._update_event.set() + self.connection_update.emit(obj, connected, kwargs) + +
[docs] def run(self): + signals = get_all_signals_from_device( + self.device, include_lazy=self.include_lazy) + + with connection_status_monitor(*signals, callback=self.callback): + while not self.isInterruptionRequested(): + self._update_event.clear() + self._update_event.wait(timeout=0.25)
+ + +
[docs]class ObjectConnectionMonitorThread(QtCore.QThread): + ''' + Monitor connection status in a background thread + + Attributes + ---------- + connection_update : QtCore.Signal + Connection update signal with signature:: + + (signal, connected, metadata_dict) + ''' + + connection_update = QtCore.Signal(object, bool, dict) + + def __init__(self, objects=None, **kwargs): + super().__init__(**kwargs) + self._init_objects = list(objects or []) + self.status = None + self.lock = threading.Lock() + self._update_event = threading.Event() + + atexit.register(self.stop) + +
[docs] def stop(self, *, wait_ms: int = 1000): + """ + Stop the background thread and clean up. + + Parameters + ---------- + wait_ms : int, optional + Time to wait for the background thread to exit. Set to 0 to + disable. + """ + if not self.isRunning(): + return + + self.requestInterruption() + if wait_ms > 0: + self.wait(msecs=wait_ms)
+ + def clear(self): + if self.status: + self.status.clear() + + def add_object(self, obj): + with self.lock: + # If the thread hasn't started yet, add it to the list + if self.status is None: + self._init_objects.append(obj) + return + + self.status.add_object(obj) + + def remove_object(self, obj): + with self.lock: + # If the thread hasn't started yet, remove it prior to monitoring + if self.status is None: + self._init_objects.remove(obj) + return + + self.status.remove_object(obj) + + def callback(self, obj, connected, **kwargs): + self._update_event.set() + self.connection_update.emit(obj, connected, kwargs) + +
[docs] def run(self): + self.lock.acquire() + try: + with connection_status_monitor( + *self._init_objects, + callback=self.callback) as self.status: + self._init_objects.clear() + self.lock.release() + while not self.isInterruptionRequested(): + self._update_event.clear() + self._update_event.wait(timeout=0.25) + finally: + if self.lock.locked(): + self.lock.release()
+ + +
[docs]class ThreadPoolWorker(QtCore.QRunnable): + ''' + Worker thread helper + + Parameters + ---------- + func : callable + The function to call during :meth:`.run` + *args + Arguments for the function call + **kwargs + Keyword rarguments for the function call + ''' + + def __init__(self, func, *args, **kwargs): + super().__init__() + self.func = func + self.args = args + self.kwargs = kwargs + +
[docs] @QtCore.Slot() + def run(self): + try: + self.func(*self.args, **self.kwargs) + except Exception: + logger.exception('Failed to run %s(*%s, **%r) in thread pool', + self.func, self.args, self.kwargs)
+ + +def _get_top_level_components(device_cls): + """Get all top-level components from a device class.""" + return list(device_cls._sig_attrs.items()) + + +
[docs]def find_parent_with_class(widget, cls=QWidget): + """ + Finds the first parent of a widget that is an instance of ``klass`` + + Parameters + ---------- + widget : QWidget + The widget from which to start the search + cls : type, optional + The class which the parent must be an instance of + + """ + parent = widget + while parent is not None: + if isinstance(parent, cls): + return parent + parent = parent.parent() + return None
+ + +
[docs]def dump_grid_layout(layout, rows=None, cols=None, *, cell_width=60): + """ + Dump the layout of a :class:`QtWidgets.QGridLayout` to ``file``. + + Parameters + ---------- + layout : QtWidgets.QGridLayout + The layout + rows : int + Number of rows to iterate over + cols : int + Number of columns to iterate over + + Returns + ------- + table : str + The text for the summary table + """ + rows = rows or layout.rowCount() + cols = cols or layout.columnCount() + + separator = '-' * ((cell_width + 4) * cols) + cell = ' {:<%ds}' % cell_width + + def get_text(item): + if not item: + return '' + + entry = item.widget() or item.layout() + visible = entry is None or entry.isVisible() + if isinstance(entry, QtWidgets.QLabel): + entry = f'<QLabel {entry.text()!r}>' + + if not visible: + entry = f'(invis) {entry}' + return entry + + with io.StringIO() as file: + print(separator, file=file) + for row in range(rows): + print('|', end='', file=file) + for col in range(cols): + item = get_text(layout.itemAtPosition(row, col)) + print(cell.format(str(item)), end=' |', file=file) + + print(file=file) + + print(separator, file=file) + return file.getvalue()
+ + +
[docs]@contextlib.contextmanager +def nullcontext(): + """Stand-in for py3.7's contextlib.nullcontext""" + yield
+ + +
[docs]def get_component(obj): + """ + Get the component that made the given object. + + Parameters + ---------- + obj : ophyd.OphydItem + The ophyd item for which to get the component. + + Returns + ------- + component : ophyd.Component + The component, if available. + """ + if obj.parent is None: + return None + + return getattr(type(obj.parent), obj.attr_name, None)
+ + +
[docs]def get_variety_metadata(cpt): + """ + Get "variety" metadata from a component or signal. + + Parameters + ---------- + cpt : ophyd.Component or ophyd.OphydItem + The component / ophyd item to get the metadata for. + + Returns + ------- + metadata : dict + The metadata, if set. Otherwise an empty dictionary. This metadata is + guaranteed to be valid according to the known schemas. + """ + if not isinstance(cpt, ophyd.Component): + cpt = get_component(cpt) + + return getattr(cpt, '_variety_metadata', {})
+ + +
[docs]def widget_to_image(widget, fill_color=QtCore.Qt.transparent): + """ + Paint the given widget in a new QtGui.QImage. + + Returns + ------- + QtGui.QImage + The display, as an image. + """ + image = QtGui.QImage(widget.width(), widget.height(), + QtGui.QImage.Format_ARGB32_Premultiplied) + + image.fill(fill_color) + pixmap = QtGui.QPixmap(image) + + painter = QtGui.QPainter(pixmap) + widget.render(image) + painter.end() + return image
+ + +_connect_slots_unpatched = None + + +
[docs]def patch_connect_slots(): + """ + Patches QtCore.QMetaObject.connectSlotsByName to catch SystemErrors. + """ + global _connect_slots_unpatched + + if _connect_slots_unpatched is not None: + return + + # TODO there could be a version check here if we can isolate it + + _connect_slots_unpatched = QtCore.QMetaObject.connectSlotsByName + + def connect_slots_patch(top_level_widget): + try: + return _connect_slots_unpatched(top_level_widget) + except SystemError as ex: + logger.debug( + "Eating system error. This may possibly be solved by either " + "downgrading Python or upgrading pyqt5 to >= 5.13.1. " + "For further discussion, see " + "https://github.com/pcdshub/typhos/issues/354", + exc_info=ex + ) + + QtCore.QMetaObject.connectSlotsByName = connect_slots_patch
+ + + + + +
[docs]def linked_attribute(property_attr, widget_attr, hide_unavailable=False): + """ + Decorator which connects a device signal with a widget. + + Retrieves the signal from the device, registers it with PyDM, and sets the + widget channel. + + Parameters + ---------- + property_attr : str + This is one level of indirection, allowing for the component attribute + to be configurable by way of designable properties. + In short, this looks like: + ``getattr(self.device, getattr(self, property_attr))`` + The component attribute name may include multiple levels (e.g., + ``'cpt1.cpt2.low_limit'``). + + widget_attr : str + The attribute name of the widget, referenced from ``self``. + The component attribute name may include multiple levels (e.g., + ``'ui.low_limit'``). + + hide_unavailable : bool + Whether or not to hide widgets for which the device signal is not + available + """ + get_widget_attr = operator.attrgetter(widget_attr) + + def wrapper(func): + @functools.wraps(func) + def wrapped(self): + widget = get_widget_attr(self) + device_attr = getattr(self, property_attr) + get_device_attr = operator.attrgetter(device_attr) + + try: + signal = get_device_attr(self.device) + except AttributeError: + signal = None + else: + # Fall short of an `isinstance(signal, OphydObj) check here: + try: + link_signal_to_widget(signal, widget) + except Exception: + logger.exception( + 'device.%s => self.%s (signal: %s widget: %s)', + device_attr, widget_attr, signal, widget) + signal = None + else: + logger.debug('device.%s => self.%s (signal=%s widget=%s)', + device_attr, widget_attr, signal, widget) + + if signal is None and hide_unavailable: + widget.setVisible(False) + + return func(self, signal, widget) + + return wrapped + return wrapper
+ + +
[docs]def raise_window(widget): + """ + Bring a widget's window into focus and on top of the window stack. + + If the window is minimized, unminimize it. + + Different window managers respond differently to the various + methods called here, the chosen sequence was intended for + good behavior on as many systems as possible. + """ + window = widget.window() + window.hide() + window.show() + if window.isMinimized(): + window.showNormal() + window.raise_() + window.activateWindow() + window.setFocus()
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/_modules/typhos/widgets.html b/v2.4.1/_modules/typhos/widgets.html new file mode 100644 index 000000000..e73242d0b --- /dev/null +++ b/v2.4.1/_modules/typhos/widgets.html @@ -0,0 +1,1372 @@ + + + + + + typhos.widgets — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +

Source code for typhos.widgets

+"""
+Typhos widgets and related utilities.
+"""
+
+import collections
+import datetime
+import inspect
+import logging
+
+import numpy as np
+import pydm
+import pydm.widgets
+import pydm.widgets.base
+import pydm.widgets.byte
+import pydm.widgets.enum_button
+import qtawesome as qta
+from ophyd.signal import EpicsSignalBase
+from pydm.widgets.display_format import DisplayFormat
+from pyqtgraph.parametertree import ParameterItem
+from qtpy import QtGui, QtWidgets
+from qtpy.QtCore import Property, QObject, QSize, Qt, Signal, Slot
+from qtpy.QtWidgets import (QAction, QDialog, QDockWidget, QPushButton,
+                            QToolBar, QVBoxLayout, QWidget)
+
+from . import plugins, utils, variety
+from .textedit import TyphosTextEdit  # noqa: F401
+from .tweakable import TyphosTweakable  # noqa: F401
+from .variety import use_for_variety_read, use_for_variety_write
+
+logger = logging.getLogger(__name__)
+
+EXPONENTIAL_UNITS = ['mtorr', 'torr', 'kpa', 'pa']
+
+
+
[docs]class SignalWidgetInfo( + collections.namedtuple( + 'SignalWidgetInfo', + 'read_cls read_kwargs write_cls write_kwargs' + )): + """ + Provides information on how to create signal widgets: class and kwargs. + + Parameters + ---------- + read_cls : type + The readback widget class. + + read_kwargs : dict + The readback widget initialization keyword arguments. + + write_cls : type + The setpoint widget class. + + write_kwargs : dict + The setpoint widget initialization keyword arguments. + """ + +
[docs] @classmethod + def from_signal(cls, obj, desc=None): + """ + Create a `SignalWidgetInfo` given an object and its description. + + Parameters + ---------- + obj : :class:`ophyd.OphydObj` + The object + + desc : dict, optional + The object description, if available. + """ + if desc is None: + desc = obj.describe() + + read_cls, read_kwargs = widget_type_from_description( + obj, desc, read_only=True) + + is_read_only = utils.is_signal_ro(obj) or ( + read_cls is not None and issubclass(read_cls, SignalDialogButton)) + + if is_read_only: + write_cls = None + write_kwargs = {} + else: + write_cls, write_kwargs = widget_type_from_description(obj, desc) + + return cls(read_cls, read_kwargs, write_cls, write_kwargs)
+ + +class TogglePanel(QWidget): + """ + Generic Panel Widget + + Displays a widget below QPushButton that hides and shows the contents. It + is up to subclasses to re-point the attribute :attr:`.contents` to the + widget whose visibility you would like to toggle. + + By default, it is assumed that the Panel is initialized with the + :attr:`.contents` widget as visible, however the contents will be hidden + and the button synced to the proper position if :meth:`.show_contents` is + called after instance creation + + Parameters + ---------- + title : str + Title of Panel. This will be the text on the QPushButton + + parent : QWidget + + Attributes + ---------- + contents : QWidget + Widget whose visibility is controlled via the QPushButton + """ + def __init__(self, title, parent=None): + super().__init__(parent=parent) + # Create Widget Infrastructure + self.title = title + self.setLayout(QVBoxLayout()) + self.layout().setContentsMargins(2, 2, 2, 2) + self.layout().setSpacing(5) + # Create button control + # Assuming widget is visible, set the button as checked + self.contents = None + self.hide_button = QPushButton(self.title) + self.hide_button.setCheckable(True) + self.hide_button.setChecked(True) + self.layout().addWidget(self.hide_button) + self.hide_button.clicked.connect(self.show_contents) + + @Slot(bool) + def show_contents(self, show): + """ + Show the contents of the Widget + + Hides the :attr:`.contents` QWidget and sets the :attr:`.hide_button` + to the proper status to indicate whether the widget is hidden or not + + Parameters + ---------- + show : bool + """ + # Configure our button in case this slot was called elsewhere + self.hide_button.setChecked(show) + # Show or hide the widget if the contents exist + if self.contents: + if show: + self.show() + self.contents.show() + else: + self.contents.hide() + + +
[docs]@use_for_variety_write('enum') +@use_for_variety_write('text-enum') +class TyphosComboBox(pydm.widgets.PyDMEnumComboBox): + """ + Notes + ----- + """ + def __init__(self, *args, variety_metadata=None, ophyd_signal=None, + **kwargs): + super().__init__(*args, **kwargs) + self.ophyd_signal = ophyd_signal + self._ophyd_enum_strings = None + self._md_sub = ophyd_signal.subscribe( + self._metadata_update, event_type="meta" + ) + + def __dtor__(self): + """PyQt5 destructor hook.""" + if self._md_sub is not None: + self.ophyd_signal.unsubscribe(self._md_sub) + self._md_sub = None + + def _metadata_update(self, enum_strs=None, **kwargs): + if enum_strs: + self._ophyd_enum_strings = tuple(enum_strs) + self.enum_strings_changed(enum_strs) + +
[docs] def enum_strings_changed(self, new_enum_strings): + current_idx = self.currentIndex() + super().enum_strings_changed( + tuple(self._ophyd_enum_strings or new_enum_strings) + ) + self.value_changed(current_idx)
+ +
[docs] def wheelEvent(self, event: QtGui.QWheelEvent): + event.ignore()
+ + +class NoScrollComboBox(QtWidgets.QComboBox): + """ + A combobox disconnected from direct EPICS/ophyd with scrolling ignored. + """ + def wheelEvent(self, event: QtGui.QWheelEvent): + event.ignore() + + +
[docs]@use_for_variety_write('scalar') +@use_for_variety_write('text') +class TyphosLineEdit(pydm.widgets.PyDMLineEdit): + """ + Reimplementation of PyDMLineEdit to set some custom defaults + + Notes + ----- + """ + def __init__(self, *args, display_format=None, **kwargs): + self._channel = None + self._setpoint_history_count = 5 + self._setpoint_history = collections.deque( + [], self._setpoint_history_count) + + super().__init__(*args, **kwargs) + self.showUnits = True + if display_format is not None: + self.displayFormat = display_format + + @property + def setpoint_history(self): + """ + History of setpoints, as a dictionary of {setpoint: timestamp} + """ + return dict(self._setpoint_history) + + @Property(int, designable=True) + def setpointHistoryCount(self): + """ + Number of items to show in the context menu "setpoint history" + """ + return self._setpoint_history_count + + @setpointHistoryCount.setter + def setpointHistoryCount(self, value): + self._setpoint_history_count = max((0, int(value))) + self._setpoint_history = collections.deque( + self._setpoint_history, self._setpoint_history_count) + + def _remove_history_item_by_value(self, remove_value): + """ + Remove an item from the history buffer by value + """ + new_history = [(value, ts) for value, ts in self._setpoint_history + if value != remove_value] + self._setpoint_history = collections.deque( + new_history, self._setpoint_history_count) + + def _add_history_item(self, value, *, timestamp=None): + """ + Add an item to the history buffer + """ + if value in dict(self._setpoint_history): + # Push this value to the end of the list as most-recently used + self._remove_history_item_by_value(value) + + self._setpoint_history.append( + (value, timestamp or datetime.datetime.now()) + ) + +
[docs] def send_value(self): + """ + Update channel value while recording setpoint history + """ + value = self.text().strip() + retval = super().send_value() + self._add_history_item(value) + return retval
+ + def _create_history_menu(self): + if not self._setpoint_history: + return None + + history_menu = QtWidgets.QMenu("&History") + font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont) + history_menu.setFont(font) + + max_len = max(len(value) + for value, timestamp in self._setpoint_history) + + # Pad values such that timestamp lines up: + # (Value) @ (Timestamp) + action_format = '{value:<%d} @ {timestamp}' % (max_len + 1) + + for value, timestamp in reversed(self._setpoint_history): + timestamp = timestamp.strftime('%m/%d %H:%M') + action = history_menu.addAction( + action_format.format(value=value, timestamp=timestamp)) + + def history_selected(*, value=value): + self.setText(str(value)) + + action.triggered.connect(history_selected) + + return history_menu + +
[docs] def widget_ctx_menu(self): + menu = super().widget_ctx_menu() + if self._setpoint_history_count > 0: + self._history_menu = self._create_history_menu() + if self._history_menu is not None: + menu.addSeparator() + menu.addMenu(self._history_menu) + + return menu
+ +
[docs] def unit_changed(self, new_unit): + """ + Callback invoked when the Channel has new unit value. + This callback also triggers an update_format_string call so the + new unit value is considered if ```showUnits``` is set. + + Parameters + ---------- + new_unit : str + The new unit + """ + if self._unit == new_unit: + return + + super().unit_changed(new_unit) + default = (self.displayFormat == DisplayFormat.Default) + if new_unit.lower() in EXPONENTIAL_UNITS and default: + self.displayFormat = DisplayFormat.Exponential
+ + +
[docs]@use_for_variety_read('array-nd') +@use_for_variety_read('command-enum') +@use_for_variety_read('command-setpoint-tracks-readback') +@use_for_variety_read('enum') +@use_for_variety_read('scalar') +@use_for_variety_read('scalar-range') +@use_for_variety_read('scalar-tweakable') +@use_for_variety_read('text') +@use_for_variety_read('text-enum') +@use_for_variety_read('text-multiline') +@use_for_variety_write('array-nd') +class TyphosLabel(pydm.widgets.PyDMLabel): + """ + Reimplementation of PyDMLabel to set some custom defaults + + Notes + ----- + """ + def __init__( + self, *args, display_format=None, ophyd_signal=None, **kwargs + ): + super().__init__(*args, **kwargs) + self.setAlignment(Qt.AlignCenter) + self.setSizePolicy(QtWidgets.QSizePolicy.Preferred, + QtWidgets.QSizePolicy.Maximum) + self.showUnits = True + if display_format is not None: + self.displayFormat = display_format + + self.ophyd_signal = ophyd_signal + self._ophyd_enum_strings = None + self._md_sub = ophyd_signal.subscribe( + self._metadata_update, event_type="meta" + ) + + def __dtor__(self): + """PyQt5 destructor hook.""" + if self._md_sub is not None: + self.ophyd_signal.unsubscribe(self._md_sub) + self._md_sub = None + + def _metadata_update(self, enum_strs=None, **kwargs): + if enum_strs: + self._ophyd_enum_strings = tuple(enum_strs) + self.enum_strings_changed(enum_strs) + +
[docs] def enum_strings_changed(self, new_enum_strings): + super().enum_strings_changed( + tuple(self._ophyd_enum_strings or new_enum_strings) + )
+ +
[docs] def unit_changed(self, new_unit): + """ + Callback invoked when the Channel has new unit value. + This callback also triggers an update_format_string call so the + new unit value is considered if ```showUnits``` is set. + + Parameters + ---------- + new_unit : str + The new unit + """ + if self._unit == new_unit: + return + + super().unit_changed(new_unit) + default = (self.displayFormat == DisplayFormat.Default) + if new_unit.lower() in EXPONENTIAL_UNITS and default: + self.displayFormat = DisplayFormat.Exponential
+ + +
[docs]class TyphosSidebarItem(ParameterItem): + """ + Class to display a Device or Tool in the sidebar + + Notes + ----- + """ + def __init__(self, param, depth): + super().__init__(param, depth) + # Configure a QToolbar + self.toolbar = QToolBar() + self.toolbar.setToolButtonStyle(Qt.ToolButtonIconOnly) + self.toolbar.setIconSize(QSize(15, 15)) + # Setup the action to open the widget + self.open_action = QAction( + qta.icon('fa5s.square', color='green'), 'Open', self.toolbar) + self.open_action.triggered.connect(self.open_requested) + # Setup the action to embed the widget + self.embed_action = QAction( + qta.icon('fa5s.th-large', color='yellow'), 'Embed', self.toolbar) + self.embed_action.triggered.connect(self.embed_requested) + # Setup the action to hide the widget + self.hide_action = QAction( + qta.icon('fa5s.times-circle', color='red'), 'Close', self.toolbar) + self.hide_action.triggered.connect(self.hide_requested) + self.hide_action.setEnabled(False) + # Add actions to toolbars + self.toolbar.addAction(self.open_action) + self.toolbar.addAction(self.hide_action) + if self.param.embeddable: + self.toolbar.insertAction(self.hide_action, self.embed_action) + +
[docs] def open_requested(self, triggered): + """Request to open display for sidebar item""" + self.param.sigOpen.emit(self) + self._mark_shown()
+ +
[docs] def embed_requested(self, triggered): + """Request to open embedded display for sidebar item""" + self.param.sigEmbed.emit(self) + self._mark_shown()
+ +
[docs] def hide_requested(self, triggered): + """Request to hide display for sidebar item""" + self.param.sigHide.emit(self) + self._mark_hidden()
+ + def _mark_shown(self): + self.open_action.setEnabled(False) + self.embed_action.setEnabled(False) + self.hide_action.setEnabled(True) + + def _mark_hidden(self): + self.open_action.setEnabled(True) + self.embed_action.setEnabled(True) + self.hide_action.setEnabled(False) + +
[docs] def treeWidgetChanged(self): + """Update the widget when add to a QTreeWidget""" + super().treeWidgetChanged() + tree = self.treeWidget() + if tree is None: + return + tree.setItemWidget(self, 1, self.toolbar)
+ + +
[docs]class SubDisplay(QDockWidget): + """QDockWidget modified to emit a signal when closed""" + closing = Signal() + +
[docs] def closeEvent(self, evt): + self.closing.emit() + super().closeEvent(evt)
+ + +class HappiChannel(pydm.widgets.channel.PyDMChannel, QObject): + """ + PyDMChannel to transport Device Information + + Parameters + ---------- + tx_slot: callable + Slot on widget to accept a dictionary of both the device and metadata + information + """ + + def __init__(self, *, tx_slot, **kwargs): + super().__init__(**kwargs) + QObject.__init__(self) + self._tx_slot = tx_slot + self._last_md = None + + @Slot(dict) + def tx_slot(self, value): + """Transmission Slot""" + # Do not fire twice for the same device + if not self._last_md or self._last_md != value['md']: + self._last_md = value['md'] + self._tx_slot(value) + else: + logger.debug("HappiChannel %r received same device. " + "Ignoring for now ...", self) + + +
[docs]class TyphosDesignerMixin(pydm.widgets.base.PyDMWidget): + """ + A mixin class used to display Typhos widgets in the Qt designer. + """ + + _qt_designer_ = { + "group": "Typhos Widgets", + "is_container": False, + } + + # Unused properties that we don't want visible in designer + alarmSensitiveBorder = Property(bool, designable=False) + alarmSensitiveContent = Property(bool, designable=False) + precisionFromPV = Property(bool, designable=False) + precision = Property(int, designable=False) + showUnits = Property(bool, designable=False) + + @Property(str) + def channel(self): + """The channel address to use for this widget""" + if self._channel: + return str(self._channel) + return None + + @channel.setter + def channel(self, value): + if self._channel != value: + # Remove old connection + if self._channels: + self._channels.clear() + for channel in self._channels: + if hasattr(channel, 'disconnect'): + channel.disconnect() + # Load new channel + self._channel = str(value) + channel = HappiChannel(address=self._channel, + tx_slot=self._tx) + self._channels = [channel] + # Connect the channel to the HappiPlugin + if hasattr(channel, 'connect'): + channel.connect() + + @Slot(object) + def _tx(self, value): + """Receive information from happi channel""" + self.add_device(value['obj'])
+ + +
[docs]class SignalDialogButton(QPushButton): + """QPushButton to launch a QDialog with a PyDMWidget""" + text = NotImplemented + icon = NotImplemented + parent_widget_class = QtWidgets.QWidget + + def __init__(self, init_channel, text=None, icon=None, parent=None): + self.text = text or self.text + self.icon = icon or self.icon + super().__init__(qta.icon(self.icon), self.text, parent=parent) + self.clicked.connect(self.show_dialog) + self.dialog = None + self.channel = init_channel + self.setIconSize(QSize(15, 15)) + +
[docs] def widget(self, channel): + """Return a widget created with channel""" + raise NotImplementedError
+ +
[docs] def show_dialog(self): + """Show the channel in a QDialog""" + # Dialog Creation + if not self.dialog: + logger.debug("Creating QDialog for %r", self.channel) + # Set up the QDialog + parent = utils.find_parent_with_class( + self, self.parent_widget_class) + self.dialog = QDialog(parent) + self.dialog.setWindowTitle(self.channel) + self.dialog.setLayout(QVBoxLayout()) + self.dialog.layout().setContentsMargins(2, 2, 2, 2) + # Add the widget + widget = self.widget() + self.dialog.layout().addWidget(widget) + # Handle a lost dialog + else: + logger.debug("Redisplaying QDialog for %r", self.channel) + self.dialog.close() + # Show the dialog + logger.debug("Showing QDialog for %r", self.channel) + self.dialog.show()
+ + +
[docs]@use_for_variety_read('array-image') +class ImageDialogButton(SignalDialogButton): + """ + QPushButton to show a 2-d array. + + Notes + ----- + """ + text = "Show Image" + icon = "fa5s.camera" + parent_widget_class = QtWidgets.QMainWindow + +
[docs] def widget(self): + """Create PyDMImageView""" + return pydm.widgets.PyDMImageView( + parent=self, image_channel=self.channel)
+ + +
[docs]@use_for_variety_read('array-timeseries') +@use_for_variety_read('array-histogram') # TODO: histogram settings? +class WaveformDialogButton(SignalDialogButton): + """ + QPushButton to show a 1-d array. + + Notes + ----- + """ + text = 'Show Waveform' + icon = "fa5s.chart-line" + parent_widget_class = QtWidgets.QMainWindow + +
[docs] def widget(self): + """Create PyDMWaveformPlot""" + return pydm.widgets.PyDMWaveformPlot( + init_y_channels=[self.channel], parent=self)
+ + +# @variety.uses_key_handlers +
[docs]@use_for_variety_write('command') +@use_for_variety_write('command-proc') +@use_for_variety_write('command-setpoint-tracks-readback') # TODO +class TyphosCommandButton(pydm.widgets.PyDMPushButton): + """ + A pushbutton widget which executes a command by sending a specific value. + + See Also + -------- + :class:`TyphosCommandEnumButton` + + Notes + ----- + """ + + default_label = 'Command' + + def __init__(self, *args, variety_metadata=None, ophyd_signal=None, + **kwargs): + super().__init__(*args, **kwargs) + self.ophyd_signal = ophyd_signal + self.variety_metadata = variety_metadata + self._forced_enum_strings = None + + variety_metadata = variety.create_variety_property() + +
[docs] def enum_strings_changed(self, new_enum_strings): + return super().enum_strings_changed( + self._forced_enum_strings or new_enum_strings)
+ + def _update_variety_metadata(self, *, value, enum_strings=None, + enum_dict=None, tags=None, **kwargs): + self.pressValue = value + enum_strings = variety.get_enum_strings(enum_strings, enum_dict) + if enum_strings is not None: + self._forced_enum_strings = tuple(enum_strings) + self.enum_strings_changed(None) # force an update + + tags = set(tags or {}) + + if 'protected' in tags: + self.passwordProtected = True + self.password = 'typhos' # ... yeah (TODO) + + if 'confirm' in tags: + self.showConfirmDialog = True + + variety._warn_unhandled_kwargs(self, kwargs) + + if not self.text(): + self.setText(self.default_label)
+ + +
[docs]@variety.uses_key_handlers +@use_for_variety_write('command-enum') +class TyphosCommandEnumButton(pydm.widgets.enum_button.PyDMEnumButton): + """ + A group of buttons which represent several command options. + + These options can come from directly from the control layer or can be + overridden with variety metadata. + + See Also + -------- + :class:`TyphosCommandButton` + + Notes + ----- + """ + + def __init__(self, *args, variety_metadata=None, ophyd_signal=None, + **kwargs): + super().__init__(*args, **kwargs) + self.ophyd_signal = ophyd_signal + self.variety_metadata = variety_metadata + self._forced_enum_strings = None + + variety_metadata = variety.create_variety_property() + +
[docs] def enum_strings_changed(self, new_enum_strings): + return super().enum_strings_changed( + self._forced_enum_strings or new_enum_strings)
+ + def _update_variety_metadata(self, *, value, enum_strings=None, + enum_dict=None, tags=None, **kwargs): + enum_strings = variety.get_enum_strings(enum_strings, enum_dict) + if enum_strings is not None: + self._forced_enum_strings = tuple(enum_strings) + self.enum_strings_changed(None) # force an update + + variety._warn_unhandled_kwargs(self, kwargs)
+ + +
[docs]@use_for_variety_read('bitmask') +@variety.uses_key_handlers +class TyphosByteIndicator(pydm.widgets.PyDMByteIndicator): + """ + Displays an integer value as individual, read-only bit indicators. + + Notes + ----- + """ + + def __init__(self, *args, variety_metadata=None, ophyd_signal=None, + **kwargs): + super().__init__(*args, **kwargs) + self.ophyd_signal = ophyd_signal + self.variety_metadata = variety_metadata + + variety_metadata = variety.create_variety_property() + + def _update_variety_metadata(self, *, bits, orientation, first_bit, style, + meaning=None, tags=None, **kwargs): + self.numBits = bits + self.orientation = { + 'horizontal': Qt.Horizontal, + 'vertical': Qt.Vertical, + }[orientation] + self.bigEndian = (first_bit == 'most-significant') + # TODO: labels do not display properly + # if meaning: + # self.labels = meaning[:bits] + # self.showLabels = True + variety._warn_unhandled_kwargs(self, kwargs) + + @variety.key_handler('style') + def _variety_key_handler_style(self, *, shape, on_color, off_color, + **kwargs): + """Variety hook for the sub-dictionary "style".""" + on_color = QtGui.QColor(on_color) + if on_color is not None: + self.onColor = on_color + + off_color = QtGui.QColor(off_color) + if off_color is not None: + self.offColor = off_color + + self.circles = (shape == 'circle') + + variety._warn_unhandled_kwargs(self, kwargs)
+ + +@use_for_variety_read('command') +@use_for_variety_read('command-proc') +class TyphosCommandIndicator(pydm.widgets.PyDMByteIndicator): + """Displays command status as a read-only bit indicator.""" + + def __init__(self, *args, ophyd_signal=None, **kwargs): + super().__init__(*args, **kwargs) + self.ophyd_signal = ophyd_signal + self.numBits = 1 + self.showLabels = False + self.circles = True + + +
[docs]class ClickableBitIndicator(pydm.widgets.byte.PyDMBitIndicator): + """A bit indicator that emits `clicked` when clicked.""" + clicked = Signal() + +
[docs] def mousePressEvent(self, event: QtGui.QMouseEvent): + super().mousePressEvent(event) + if event.button() == Qt.LeftButton: + self.clicked.emit()
+ + +
[docs]@use_for_variety_write('bitmask') +class TyphosByteSetpoint(TyphosByteIndicator, + pydm.widgets.base.PyDMWritableWidget): + """ + Displays an integer value as individual, toggleable bit indicators. + + Notes + ----- + """ + + def __init__(self, *args, variety_metadata=None, ophyd_signal=None, + **kwargs): + # NOTE: need to have these in the signature explicitly + super().__init__(*args, variety_metadata=variety_metadata, + ophyd_signal=ophyd_signal, **kwargs) + + self._requests_pending = {} + + def _get_setpoint_from_requests(self): + setpoint = self.value + for bit, request in self._requests_pending.items(): + mask = 1 << bit + if request: + setpoint |= mask + else: + setpoint &= ~mask + return setpoint + + def _bit_clicked(self, bit): + if bit in self._requests_pending: + old_value = self._requests_pending[bit] + else: + old_value = bool(self.value & (1 << bit)) + + self._requests_pending[bit] = not old_value + self.send_value_signal[int].emit(self._get_setpoint_from_requests()) + +
[docs] def value_changed(self, value): + """Receive and update the TyphosTextEdit for a new channel value.""" + for bit, request in list(self._requests_pending.items()): + mask = 1 << bit + is_set = bool(value & mask) + if is_set == request: + self._requests_pending.pop(bit, None) + + super().value_changed(value)
+ + @Property(int, designable=True) + def numBits(self): + """ + Number of bits to interpret. + + Re-implemented from PyDM to support changing of bit indicators. + """ + return self._num_bits + + @numBits.setter + def numBits(self, num_bits): + if num_bits < 1: + return + + self._num_bits = num_bits + for indicator in self._indicators: + indicator.deleteLater() + + self._indicators = [ + ClickableBitIndicator(parent=self, circle=self.circles) + for bit in range(self._num_bits) + ] + + for bit, indicator in enumerate(self._indicators): + def indicator_clicked(*, bit=bit): + self._bit_clicked(bit) + + indicator.clicked.connect(indicator_clicked) + + new_labels = [f"Bit {bit}" + for bit in range(len(self.labels), self._num_bits)] + self.labels = self.labels + new_labels
+ + +
[docs]@variety.uses_key_handlers +@use_for_variety_write('scalar-range') +class TyphosScalarRange(pydm.widgets.PyDMSlider): + """ + A slider widget which displays a scalar value with an explicit range. + + Notes + ----- + """ + + def __init__(self, *args, variety_metadata=None, ophyd_signal=None, + **kwargs): + super().__init__(*args, **kwargs) + self.ophyd_signal = ophyd_signal + self._delta_value = None + self._delta_signal = None + self._delta_signal_sub = None + self.variety_metadata = variety_metadata + + variety_metadata = variety.create_variety_property() + + def __dtor__(self): + """PyQt5 destructor hook""" + # Ensure our delta signal subscription is cleared: + if self._delta_signal is not None: + if self._delta_signal_sub is not None: + self._delta_signal.unsubscribe(self._delta_signal_sub) + self._delta_signal_sub = None + self.delta_signal = None + + @variety.key_handler('range') + def _variety_key_handler_range(self, value, source, **kwargs): + """Variety hook for the sub-dictionary "range".""" + if source == 'value': + if value is not None: + low, high = value + self.userMinimum = low + self.userMaximum = high + self.userDefinedLimits = True + # elif source == 'use_limits': + else: + variety._warn_unhandled(self, 'range.source', source) + + variety._warn_unhandled_kwargs(self, kwargs) + + @variety.key_handler('delta') + def _variety_key_handler_delta(self, source, value=None, signal=None, + **kwargs): + """Variety hook for the sub-dictionary "delta".""" + if source == 'value': + if value is not None: + self.delta_value = value + elif source == 'signal': + if signal is not None: + self.delta_signal = variety.get_referenced_signal(self, signal) + else: + variety._warn_unhandled(self, 'delta.source', source) + + # range_ = kwargs.pop('range') # unhandled + variety._warn_unhandled_kwargs(self, kwargs) + + @variety.key_handler('display_format') + def _variety_key_handler_display_format(self, value): + """Variety hook for the sub-dictionary "delta".""" + self.displayFormat = variety.get_display_format(value) + + @property + def delta_signal(self): + """Delta signal, used as the source for "delta_value".""" + return self._delta_signal + + @delta_signal.setter + def delta_signal(self, signal): + if self._delta_signal is not None: + self._delta_signal.unsubscribe(self._delta_signal_sub) + self._delta_signal_sub = None + + if signal is None: + return + + def update_delta(value, **kwargs): + self.delta_value = value + + self._delta_signal_sub = signal.subscribe(update_delta) + self._delta_signal = signal + + @Property(float, designable=True) + def delta_value(self): + """ + Delta value, an alternative to "num_points" provided by PyDMSlider. + + num_points is calculated using the current min/max and the delta value, + if set. + """ + return self._delta_value + + @delta_value.setter + def delta_value(self, value): + if value is None: + self._delta_value = None + return + if value <= 0.0: + return + + self._delta_value = value + if self.minimum is not None and self.maximum is not None: + self._mute_internal_slider_changes = True + try: + self.num_steps = (self.maximum - self.minimum) / value + except Exception: + logger.exception('Failed to set number of steps with ' + 'min=%s, max=%s, delta=%s', self.minimum, + self.maximum, value) + finally: + self._mute_internal_slider_changes = False + +
[docs] def connection_changed(self, connected): + ret = super().connection_changed(connected) + if connected: + self.delta_value = self._delta_value + return ret
+ + +
[docs]@variety.uses_key_handlers +@use_for_variety_write('array-tabular') +class TyphosArrayTable(pydm.widgets.PyDMWaveformTable): + """ + A table widget which reshapes and displays a given waveform value. + + Notes + ----- + """ + + def __init__(self, *args, variety_metadata=None, ophyd_signal=None, + **kwargs): + super().__init__(*args, **kwargs) + self.ophyd_signal = ophyd_signal + self.variety_metadata = variety_metadata + + variety_metadata = variety.create_variety_property() + +
[docs] def value_changed(self, value): + try: + len(value) + except TypeError: + logger.debug('Non-waveform value? %r', value) + return + + # shape = self.variety_metadata.get('shape') + # if shape is not None: + # expected_length = np.multiply.reduce(shape) + # if len(value) == expected_length: + # value = np.array(value).reshape(shape) + + return super().value_changed(value)
+ + def _calculate_size(self, padding=5): + width = self.verticalHeader().width() + padding + for col in range(self.columnCount()): + width += self.columnWidth(col) + + height = self.horizontalHeader().height() + padding + for row in range(self.rowCount()): + height += self.rowHeight(row) + + return QSize(width, height) + + def _update_variety_metadata(self, *, shape=None, tags=None, **kwargs): + if shape: + # TODO + columns = max(shape[0], 1) + rows = max(np.product(shape[1:]), 1) + self.setRowCount(rows) + self.setColumnCount(columns) + self.rowHeaderLabels = [f'{idx}' for idx in range(rows)] + self.columnHeaderLabels = [f'{idx}' for idx in range(columns)] + + if rows <= 5 and columns <= 5: + full_size = self._calculate_size() + self.setFixedSize(full_size) + + variety._warn_unhandled_kwargs(self, kwargs)
+ + +def _get_scalar_widget_class(desc, variety_md, read_only): + """ + From a given description, return the widget to use. + + Parameters + ---------- + desc : dict + The object description. + + variety_md : dict + The variety metadata. Currently unused. + + read_only : bool + Set if used for the readback widget. + """ + # Check for enum_strs, if so create a QCombobox + if read_only: + return TyphosLabel + + if 'enum_strs' in desc: + # Create a QCombobox if the widget has enum_strs + return TyphosComboBox + + # Otherwise a LineEdit will suffice + return TyphosLineEdit + + +def _get_ndimensional_widget_class(dimensions, desc, variety_md, read_only): + """ + From a given description and dimensionality, return the widget to use. + + Parameters + ---------- + dimensions : int + The number of dimensions (e.g., 0D or scalar, 1D array, ND array) + + desc : dict + The object description. + + variety_md : dict + The variety metadata. Currently unused. + + read_only : bool + Set if used for the readback widget. + """ + if dimensions == 0: + return _get_scalar_widget_class(desc, variety_md, read_only) + + return { + 1: WaveformDialogButton, + 2: ImageDialogButton + }.get(dimensions, TyphosLabel) + + +DIRECT_CONTROL_LAYERS = {"pyepics", "caproto"} + + +
[docs]def widget_type_from_description(signal, desc, read_only=False): + """ + Determine which widget class should be used for the given signal + + Parameters + ---------- + signal : ophyd.Signal + Signal object to determine widget class + + desc : dict + Previously recorded description from the signal + + read_only: bool, optional + Should the chosen widget class be read-only? + + Returns + ------- + widget_class : class + The class to use for the widget + kwargs : dict + Keyword arguments for the class + """ + use_pv_directly = ( + # We can use PyDM's data source directly with EpicsSignalBase: + isinstance(signal, EpicsSignalBase) and + # So long as its underlying control layer is a supported ophyd-provided + # one, at least. + getattr(signal.cl, "name", "") in DIRECT_CONTROL_LAYERS + ) + if use_pv_directly: + # Still re-route EpicsSignal through the ca:// plugin + pv = (signal._read_pv + if read_only else signal._write_pv) + init_channel = utils.channel_name(pv.pvname) + else: + # Register signal with plugin + plugins.register_signal(signal) + init_channel = utils.channel_name(signal.name, protocol='sig') + + variety_metadata = utils.get_variety_metadata(signal) + kwargs = { + 'init_channel': init_channel, + } + + if variety_metadata: + widget_cls = variety._get_widget_class_from_variety( + desc, variety_metadata, read_only) + else: + try: + dimensions = len(desc.get('shape', [])) + except TypeError: + dimensions = 0 + + widget_cls = _get_ndimensional_widget_class( + dimensions, desc, variety_metadata, read_only) + + if widget_cls is None: + return None, None + + if desc.get('dtype') == 'string' and widget_cls in (TyphosLabel, + TyphosLineEdit): + kwargs['display_format'] = DisplayFormat.String + + class_signature = inspect.signature(widget_cls) + if 'variety_metadata' in class_signature.parameters: + kwargs['variety_metadata'] = variety_metadata + + if 'ophyd_signal' in class_signature.parameters: + kwargs['ophyd_signal'] = signal + + return widget_cls, kwargs
+ + +
[docs]def determine_widget_type(signal, read_only=False): + """ + Determine which widget class should be used for the given signal. + + Parameters + ---------- + signal : ophyd.Signal + Signal object to determine widget class + + read_only: bool, optional + Should the chosen widget class be read-only? + + Returns + ------- + widget_class : class + The class to use for the widget + kwargs : dict + Keyword arguments for the class + """ + try: + desc = signal.describe()[signal.name] + except Exception: + logger.error("Unable to connect to %r during widget creation", + signal.name) + desc = {} + + return widget_type_from_description(signal, desc, read_only=read_only)
+ + +
[docs]def create_signal_widget(signal, read_only=False, tooltip=None): + """ + Factory for creating a PyDMWidget from a signal + + Parameters + ---------- + signal : ophyd.Signal + Signal object to create widget + + read_only: bool, optional + Whether this widget should be able to write back to the signal you + provided + + tooltip : str, optional + Tooltip to use for the widget + + Returns + ------- + widget : PyDMWidget + PyDMLabel, PyDMLineEdit, or PyDMEnumComboBox based on whether we should + be able to write back to the widget and if the signal has ``enum_strs`` + """ + widget_cls, kwargs = determine_widget_type(signal, read_only=read_only) + if widget_cls is None: + return + + logger.debug("Creating %s for %s", widget_cls, signal.name) + + widget = widget_cls(**kwargs) + widget.setObjectName(f'{signal.name}_{widget_cls.__name__}') + if tooltip is not None: + widget.setToolTip(tooltip) + + return widget
+
+ +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/_sources/basic_usage.rst.txt b/v2.4.1/_sources/basic_usage.rst.txt new file mode 100644 index 000000000..2fcca60be --- /dev/null +++ b/v2.4.1/_sources/basic_usage.rst.txt @@ -0,0 +1,312 @@ +============ +How it Works +============ +Typhos has three major building blocks that combine into the final display seen +by the operator: + +* **TyphosSuite** : The overall view for a Typhos window. It allows the + operator to view all of the loaded components and tools. +* **TyphosDeviceDisplay** : This is the widget created for a standard + ``ophyd.Device``. Signals can be organized based on their ``Kind`` and + description. +* **typhos.tools** : These are widgets that interface with external + applications. While you may have other GUIs for these systems, + ``typhos.tools`` are built especially to handle the handshaking between all + the information stored on your device and the tool you are interfacing with. + This saves your operator clicks and ensures consistency in use. + +All three of the widgets listed above share a similar API for creation. +Instantiating the object by itself handles loading the container widgets and +placing them in the correct place, but these do not accept ``ophyd.Device`` +arguments. The reason for this is to ensure that we can use all of the +``typhos`` screens as templates, and regardless or not of whether you have an +``ophyd.Device`` you can always populate the screens by hand. If you do in fact +have an ``ophyd.Device`` every class has an ``add_device`` method and +alternatively and be constructed using the ``from_device`` classmethod. + + +.. autoclass:: typhos.utils.TyphosBase + :members: + :noindex: + + +Interpreting a Device +===================== +Typhos interprets the internal structure of the ``ophyd.Device`` to create the +`PyDM` user interface, so the most intuitive way to configure the created +display is to include components on the device itself. This also has the advantage +of keeping your Python API and display in sync, making the transition from +using screens to using an IPython shell seamless. + +For the following applications we'll use the ``motor`` simulation contained +within ``ophyd`` itself. We also need to create a ``QApplication`` before we +create any widgets: + +.. ipython:: python + + from qtpy.QtWidgets import QApplication + app = QApplication([]) + + +.. ipython:: python + :suppress: + + from ophyd.sim import motor + + + +Using Happi +^^^^^^^^^^^ +While ``happi`` is not a requirement for using ``typhos``, it is recommended. +For more information, visit the `GitHub `_ +repository. The main purpose of the package is to store information on our +Ophyd objects so that we can load them in a variety of contexts. If you do not +use ``happi`` you will need to create your objects and displays in the same +process. + +From the command-line, using typhos and happi together is easy. For example, +to load an auto-generated typhos screen for your device named ``"my_device"`` +would only require the following: + +.. code:: bash + + $ typhos my_device + +typhos automatically configures the happi client, finds your device, and +creates an appropriate screen for it. + +If you are looking to integrate typhos at the Python source code level, +consider the following example which uses ``typhos`` with ``happi``: + +.. code:: python + + import happi + from typhos.plugins import register_client + + # Initialize a new JSON based client + client = happi.Client(path='db.json', initialize=True) + # Register this with typhos + register_client(client) + # Add a device to our new database + device = happi.Device(device_class='ophyd.sim.SynAxis', + prefix='Tst:Mtr', args=[], kwargs='{{name}}', + name='my_motor', beamline='TST') + client.add_device(device) + +In practice, it is not necessary to call :func:`.register_client` if you have +configured the ``$HAPPI_CFG`` environment variable such that +``happi.Client.from_config`` yields the desired client. + +We can now check that we can load the complete ``SynAxis`` object. + +.. code:: python + + motor = client.load_device(name='my_motor') + +Signals of Devices +^^^^^^^^^^^^^^^^^^ + +When making a custom screen, you can access signals associated with your device +in several ways, in order of suggested use: + +1. By using the typhos built-in "signal" plugin to connect to the signal with + the dotted ophyd name, just as you would use in an IPython session. + In the designer "channel" property, specify: ``sig://device_name.attr`` + with as many ``.attrs`` required to reach the signal from the top-level + device as needed. + For example, for a motor named "my_motor", you could use: + ``sig://my_motor.user_readback`` +2. An alternate signal name is available, that which is seen by the data + acquisition system (e.g., the databroker by way of bluesky). Generally, + characters seen as invalid for a MongoDB are replaced with an underscore + (``_``). To check a signal's name, see the ``.name`` property of that + signal. + For example, for a motor named "my_motor", you could use: + ``sig://my_motor_user_readback`` +3. By PV name directly. Assuming your signal is available through the + underlying control system (EPICS, for example), you could look and see which + PVs your signal talks to and use those directly. That is, + ``my_motor.user_readback.pvname`` would tell you which EPICS PV the user + readback uses. From there, you could set the widget's channel to use EPICS + Channel Access with ``ca://pv_name_here``. + + +Display Signals +^^^^^^^^^^^^^^^ +The first thing we'll talk about is showing a group of signals associated with +our ``motor`` object in a basic form called a +:class:`~typhos.TyphosSignalPanel`. Simply inspecting the device reveals a few +signals for us to display + +.. ipython:: python + + motor.component_names + +It is crucial that we understand the importance of these signals to the +operator. In order to glean this information from the object the ``kind`` +attributes are inspected. For more information see the `ophyd documentation +`_. A quick inspection of +the various attributes allows us to see how our signals are organized. + +.. ipython:: python + + # Most important signal(s) + motor.hints + # Important signals, all hints will be found here as well + motor.read() + # Configuration information + motor.read_configuration() + +The :class:`.TyphosSignalPanel` will render these, allowing us to select a +subset of the signals to display based on their kind. Below both the +``QtDesigner`` using ``happi`` and the corresponding ``Python`` code is shown +as well: + +.. ipython:: python + + from typhos import TyphosSignalPanel + panel = TyphosSignalPanel.from_device(motor) + +.. figure:: /_static/kind_panel.gif + :scale: 100% + :align: center + +Now, at first glance it may not be obvious, but there is a lot of information +here! We know which of these signals an operator will want to control and which +ones are purely meant to be read back. We also have these signals grouped by +their importance to operation, each with a terse human-readable description of +what the ``Signal`` represents. + +Filling Templates +^^^^^^^^^^^^^^^^^ +Taking this concept further, instead of filling a single panel +:class:`.TyphosDeviceDisplay` allows a template to be created with a multitude +of widgets and panels. ``Typhos`` will find widgets that accept devices, but do +not have any devices already. Typhos comes with some default templates, and you +can cycle between them by changing the ``display_type`` + +Once again, both the ``Python`` code and the ``QtDesigner`` use cases are +shown: + +.. ipython:: python + + from typhos import TyphosDeviceDisplay + display = TyphosDeviceDisplay.from_device(motor) + + +.. figure:: /_static/device_display.gif + :scale: 100% + :align: center + + +The TyphosSuite +=============== +A complete application can be made by loading the :class:`.TyphosSuite`. Below +is the complete code from start to finish required to create the suite. Look at +the ``TyphosSuited.default_tools`` to control which ``typhos.tools`` are +loaded. + +.. code:: python + + from ophyd.sim import motor + from qtpy.QtWidgets import QApplication + from typhos.suite import TyphosSuite + from typhos.utils import apply_standard_stylesheets + + # Create our application + app = QApplication([]) + apply_standard_stylesheets() # Optional + suite = TyphosSuite.from_device(motor) + + # Launch + suite.show() + app.exec_() + + +Using the StyleSheet +==================== +Typhos ships with two stylesheets to improve the look and feel of the widgets. +When invoking ``typhos`` from the CLI as normal, you can pass +the ``--dark`` flag to use the dark stylesheet instead of the light mode, +and a ``--stylesheet-add`` argument to use your own stylesheet in addition to Typhos's. +If you want to completely ignore Typhos's normal stylesheet loading and use your own, +you can pass the ``--stylesheet-override`` argument. You can pass these arguments +multiple times to include multiple stylesheets. + +Typhos also uses the same stylesheet environment variables as PyDM to load additional +stylesheets. The PyDM environment variables respected here are: + +- ``PYDM_STYLESHEET``, a path-like variable that should contain file paths to qss + stylesheets if set. +- ``PYDM_STYLESHEET_INCLUDE_DEFAULT``, which should be set to 1 to include the + default PyDM stylesheet or unset to not include it. + +The priority order for stylesheets in the case of conflicts is: + +1. The explicit ``styleSheet`` property on the display template +2. The style elements from ``--stylesheet-add`` +3. User stylesheets from ``PYDM_STYLESHEET_INCLUDE_DEFAULT`` +4. Typhos's stylesheet (either the dark or the light variant) +5. The built-in PyDM stylesheet + +Outside of the CLI, the stylesheets can be applied using :func:`typhos.apply_standard_stylesheets`. +This function also handles setting the "Fusion" ``QStyle`` which helps +make the interface have an operating system independent look and feel. + + +Using the Documentation Widget +============================== + +Typhos has a built-in documentation helper, which allows for the in-line +linking and display of a user-provided website. + +To inform Typhos how to load documentation specific to your facility, please +customize the following environment variables. + +1. ``TYPHOS_HELP_URL`` (str): The help URL format string. The help URL will + be formatted with the ophyd device pertinent to the display, such that you + may access its name, PV prefix, happi metadata (if available), and so on. + For example, if a Confluence server exists at + ``https://my-confluence-site.example.com/Controls/`` with document names + that match your devices, ``TYPHOS_HELP_URL`` should be set to + ``https://my-confluence-site.example.com/Controls/{device.name}``. + If, perhaps, only top-level devices are guaranteed to have documentation, + consider using: ``device.root.name`` instead in the format string. +2. ``TYPHOS_HELP_HEADERS`` (json): headers to pass to HELP_URL. This should be + in a JSON format, such as ``{"my_key":"my_value"}``. +3. ``TYPHOS_HELP_HEADERS_HOSTS`` (str): comma-delimited hosts that headers may + be sent to, aside from the host configured in ``TYPHOS_HELP_URL``. +4. ``TYPHOS_HELP_TOKEN`` (str): An optional token for the bearer authentication + scheme - e.g., personal access tokens with Confluence. This is a shortcut + to add a header ``"Authorization"`` with the value + ``"Bearer ${TYPHOS_HELP_TOKEN}"``. + + +Using the Jira Bug Reporting Widget +=================================== + +Typhos has an optional built-in widget to generate Jira user stories/bug +reports. + +A prerequisite to this support is, of course, a working Jira installation +and a pre-configured issue collector. + +1. ``TYPHOS_JIRA_URL`` (str): The Jira issue collector URL. This will resemble + ``https://jira.example.com/rest/collectors/1.0/template/custom/...``. +2. ``TYPHOS_JIRA_HEADERS`` (json): headers to pass to the Jira request, if + needed. This should be in a JSON format, such as ``{"my_key":"my_value"}``. +3. ``TYPHOS_JIRA_TOKEN`` (str): An optional token for the bearer authentication + scheme - e.g., personal access tokens with Confluence. This is a shortcut + to add a header ``"Authorization"`` with the value + ``"Bearer ${TYPHOS_JIRA_TOKEN}"``. +4. ``TYPHOS_JIRA_EMAIL_SUFFIX`` (str): the default e-mail suffix to put on + usernames, such as ``"@example.com"``. + + +Launching the Examples +====================== +There are example screens in the ``typhos.examples`` submodule. After +installing ``typhos``, you can launch them as follows: + +- ``python -m typhos.examples.panel`` +- ``python -m typhos.examples.positioner`` diff --git a/v2.4.1/_sources/cli.rst.txt b/v2.4.1/_sources/cli.rst.txt new file mode 100644 index 000000000..3045eebc6 --- /dev/null +++ b/v2.4.1/_sources/cli.rst.txt @@ -0,0 +1,4 @@ +Command Line Utilities +====================== + +.. automodule:: typhos.cli diff --git a/v2.4.1/_sources/connections.rst.txt b/v2.4.1/_sources/connections.rst.txt new file mode 100644 index 000000000..6035c7db0 --- /dev/null +++ b/v2.4.1/_sources/connections.rst.txt @@ -0,0 +1,60 @@ +======================= +Application Connections +======================= + +Ophyd Signals +============= +Typhos takes advantage of the flexible data plugin system contained within +``PyDM`` and the abstraction of the "control layer" within ``Ophyd``. In the +:class:`.SignalPanel`, objects signals are queried for their type. If these are +determined to be coming from ``EPICS`` the data plugin configured within +``PyDM`` is used directly, any other kind of signal goes through the generic +:class:`.SignalPlugin`. This uses the subscription system contained within +``Ophyd`` to keep widgets values updated. One caveat is that ``PyDM`` requires +that channels are specified by a string identifier. In the case of +``ophyd.Signal`` objects we want to ensure that these are passed by reference +to avoid duplicating objects. This means the workflow for adding these has one +more additonal step where the ``Signal`` is registered with the ``PyDM`` +plugin. + +.. code:: python + + from typhos.plugins import register_signal + + # Create an Ophyd Signal + my_signal = ophyd.Signal(name='this_signal') + # Register this with the Plugin + register_signal(my_signal) + # This signal is now available for use with PyDM widgets + PyDMWidget(channel='sig://this_signal') + +Note that this is all done for you if you use the :class:`.SignalPanel`, but +maybe useful if you would like to use the :class:`.SignalPlugin` directly. + +Inclusion of Metadata +--------------------- +In many cases just knowing the value of a signal is not enough to accurately +display it. Extra pieces of information such as the units and precision of +information can provide a richer operator experience. ``Typhos`` counts on this +information being available in the output of ``describe`` method of the signal. +If you want your child ``ophyd.Signal`` class to convey this information make +sure that it is expressed properly in the output of ``describe``. + +===================== =============== +Metadata Description Key +===================== =============== +Precision `"precision"` +Enumeration Strings `"enum_strs"` +Engineering Units `"units"` +===================== =============== + + +Happi Plugin +============ + +The PyDM Plugin interface makes no mandate about the type of signals that we +connect to our widgets. The :class:`.HappiPlugin` and corresponding +``HappiChannel`` contains alternative signals in order to send entire ``ophyd`` +objects from a stored database to our widgets. This is useful where we want to +populate a template with an entire devices signals instead of connecting +widgets one by one. diff --git a/v2.4.1/_sources/display.rst.txt b/v2.4.1/_sources/display.rst.txt new file mode 100644 index 000000000..fbd4514be --- /dev/null +++ b/v2.4.1/_sources/display.rst.txt @@ -0,0 +1,58 @@ +================== +Suite and Displays +================== + +Typhos has two major widgets that users are expected to interface with. The +first is the :class:`.TyphosDeviceDisplay`, which shows device information, and +:class:`.TyphosSuite` which contains multiple devices and tools. This is the +barebones implementation. No signals, or widgets are automatically populated in +the screen. In fact, by default most of the widgets will be hidden. You can +then manually add signals to the panels and plots, the panels will only show +themselves when you add PVs. + +TyphosSuite +=========== + +.. autoclass:: typhos.TyphosSuite + :members: + +TyphosDeviceDisplay +=================== + +.. autoclass:: typhos.TyphosDeviceDisplay + :members: + +Standardized Display Title +========================== + +.. autoclass:: typhos.display.TyphosDisplayTitle + :members: + +.. autoclass:: typhos.display.TyphosDisplaySwitcher + :members: + +.. autoclass:: typhos.display.TyphosTitleLabel + :members: + +Tool buttons +------------ + +.. autoclass:: typhos.display.TyphosToolButton + :members: + +.. autoclass:: typhos.display.TyphosDisplaySwitcherButton + :members: + +.. autoclass:: typhos.display.TyphosDisplayConfigButton + :members: + +Utilities +========= + +.. autofunction:: typhos.display.normalize_display_type + +.. autofunction:: typhos.display.hide_empty + +.. autofunction:: typhos.display.show_empty + +.. autofunction:: typhos.display.toggle_display diff --git a/v2.4.1/_sources/generated/typhos.tools.TyphosConsole.rst.txt b/v2.4.1/_sources/generated/typhos.tools.TyphosConsole.rst.txt new file mode 100644 index 000000000..424edfa16 --- /dev/null +++ b/v2.4.1/_sources/generated/typhos.tools.TyphosConsole.rst.txt @@ -0,0 +1,362 @@ +typhos.tools.TyphosConsole +========================== + +.. currentmodule:: typhos.tools + +.. autoclass:: TyphosConsole + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~TyphosConsole.__init__ + ~TyphosConsole.acceptDrops + ~TyphosConsole.accessibleDescription + ~TyphosConsole.accessibleName + ~TyphosConsole.actionEvent + ~TyphosConsole.actions + ~TyphosConsole.activateWindow + ~TyphosConsole.addAction + ~TyphosConsole.addActions + ~TyphosConsole.add_device + ~TyphosConsole.adjustSize + ~TyphosConsole.autoFillBackground + ~TyphosConsole.backgroundRole + ~TyphosConsole.baseSize + ~TyphosConsole.blockSignals + ~TyphosConsole.changeEvent + ~TyphosConsole.childAt + ~TyphosConsole.childEvent + ~TyphosConsole.children + ~TyphosConsole.childrenRect + ~TyphosConsole.childrenRegion + ~TyphosConsole.clearFocus + ~TyphosConsole.clearMask + ~TyphosConsole.close + ~TyphosConsole.closeEvent + ~TyphosConsole.colorCount + ~TyphosConsole.connectNotify + ~TyphosConsole.contentsMargins + ~TyphosConsole.contentsRect + ~TyphosConsole.contextMenuEvent + ~TyphosConsole.contextMenuPolicy + ~TyphosConsole.create + ~TyphosConsole.createWindowContainer + ~TyphosConsole.cursor + ~TyphosConsole.customEvent + ~TyphosConsole.deleteLater + ~TyphosConsole.depth + ~TyphosConsole.destroy + ~TyphosConsole.devType + ~TyphosConsole.devicePixelRatio + ~TyphosConsole.devicePixelRatioF + ~TyphosConsole.devicePixelRatioFScale + ~TyphosConsole.disconnect + ~TyphosConsole.disconnectNotify + ~TyphosConsole.dragEnterEvent + ~TyphosConsole.dragLeaveEvent + ~TyphosConsole.dragMoveEvent + ~TyphosConsole.dropEvent + ~TyphosConsole.dumpObjectInfo + ~TyphosConsole.dumpObjectTree + ~TyphosConsole.dynamicPropertyNames + ~TyphosConsole.effectiveWinId + ~TyphosConsole.ensurePolished + ~TyphosConsole.enterEvent + ~TyphosConsole.event + ~TyphosConsole.eventFilter + ~TyphosConsole.execute + ~TyphosConsole.find + ~TyphosConsole.findChild + ~TyphosConsole.findChildren + ~TyphosConsole.focusInEvent + ~TyphosConsole.focusNextChild + ~TyphosConsole.focusNextPrevChild + ~TyphosConsole.focusOutEvent + ~TyphosConsole.focusPolicy + ~TyphosConsole.focusPreviousChild + ~TyphosConsole.focusProxy + ~TyphosConsole.focusWidget + ~TyphosConsole.font + ~TyphosConsole.fontInfo + ~TyphosConsole.fontMetrics + ~TyphosConsole.foregroundRole + ~TyphosConsole.frameGeometry + ~TyphosConsole.frameSize + ~TyphosConsole.from_device + ~TyphosConsole.geometry + ~TyphosConsole.getContentsMargins + ~TyphosConsole.grab + ~TyphosConsole.grabGesture + ~TyphosConsole.grabKeyboard + ~TyphosConsole.grabMouse + ~TyphosConsole.grabShortcut + ~TyphosConsole.graphicsEffect + ~TyphosConsole.graphicsProxyWidget + ~TyphosConsole.hasFocus + ~TyphosConsole.hasHeightForWidth + ~TyphosConsole.hasMouseTracking + ~TyphosConsole.hasTabletTracking + ~TyphosConsole.height + ~TyphosConsole.heightForWidth + ~TyphosConsole.heightMM + ~TyphosConsole.hide + ~TyphosConsole.hideEvent + ~TyphosConsole.inherits + ~TyphosConsole.initPainter + ~TyphosConsole.inputMethodEvent + ~TyphosConsole.inputMethodHints + ~TyphosConsole.inputMethodQuery + ~TyphosConsole.insertAction + ~TyphosConsole.insertActions + ~TyphosConsole.installEventFilter + ~TyphosConsole.isActiveWindow + ~TyphosConsole.isAncestorOf + ~TyphosConsole.isEnabled + ~TyphosConsole.isEnabledTo + ~TyphosConsole.isFullScreen + ~TyphosConsole.isHidden + ~TyphosConsole.isLeftToRight + ~TyphosConsole.isMaximized + ~TyphosConsole.isMinimized + ~TyphosConsole.isModal + ~TyphosConsole.isRightToLeft + ~TyphosConsole.isSignalConnected + ~TyphosConsole.isVisible + ~TyphosConsole.isVisibleTo + ~TyphosConsole.isWidgetType + ~TyphosConsole.isWindow + ~TyphosConsole.isWindowModified + ~TyphosConsole.isWindowType + ~TyphosConsole.keyPressEvent + ~TyphosConsole.keyReleaseEvent + ~TyphosConsole.keyboardGrabber + ~TyphosConsole.killTimer + ~TyphosConsole.layout + ~TyphosConsole.layoutDirection + ~TyphosConsole.leaveEvent + ~TyphosConsole.locale + ~TyphosConsole.logicalDpiX + ~TyphosConsole.logicalDpiY + ~TyphosConsole.lower + ~TyphosConsole.mapFrom + ~TyphosConsole.mapFromGlobal + ~TyphosConsole.mapFromParent + ~TyphosConsole.mapTo + ~TyphosConsole.mapToGlobal + ~TyphosConsole.mapToParent + ~TyphosConsole.mask + ~TyphosConsole.maximumHeight + ~TyphosConsole.maximumSize + ~TyphosConsole.maximumWidth + ~TyphosConsole.metaObject + ~TyphosConsole.metric + ~TyphosConsole.minimumHeight + ~TyphosConsole.minimumSize + ~TyphosConsole.minimumSizeHint + ~TyphosConsole.minimumWidth + ~TyphosConsole.mouseDoubleClickEvent + ~TyphosConsole.mouseGrabber + ~TyphosConsole.mouseMoveEvent + ~TyphosConsole.mousePressEvent + ~TyphosConsole.mouseReleaseEvent + ~TyphosConsole.move + ~TyphosConsole.moveEvent + ~TyphosConsole.moveToThread + ~TyphosConsole.nativeEvent + ~TyphosConsole.nativeParentWidget + ~TyphosConsole.nextInFocusChain + ~TyphosConsole.normalGeometry + ~TyphosConsole.objectName + ~TyphosConsole.overrideWindowFlags + ~TyphosConsole.overrideWindowState + ~TyphosConsole.paintEngine + ~TyphosConsole.paintEvent + ~TyphosConsole.paintingActive + ~TyphosConsole.palette + ~TyphosConsole.parent + ~TyphosConsole.parentWidget + ~TyphosConsole.physicalDpiX + ~TyphosConsole.physicalDpiY + ~TyphosConsole.pos + ~TyphosConsole.previousInFocusChain + ~TyphosConsole.property + ~TyphosConsole.pyqtConfigure + ~TyphosConsole.raise_ + ~TyphosConsole.receivers + ~TyphosConsole.rect + ~TyphosConsole.releaseKeyboard + ~TyphosConsole.releaseMouse + ~TyphosConsole.releaseShortcut + ~TyphosConsole.removeAction + ~TyphosConsole.removeEventFilter + ~TyphosConsole.render + ~TyphosConsole.repaint + ~TyphosConsole.resize + ~TyphosConsole.resizeEvent + ~TyphosConsole.restoreGeometry + ~TyphosConsole.saveGeometry + ~TyphosConsole.screen + ~TyphosConsole.scroll + ~TyphosConsole.sender + ~TyphosConsole.senderSignalIndex + ~TyphosConsole.setAcceptDrops + ~TyphosConsole.setAccessibleDescription + ~TyphosConsole.setAccessibleName + ~TyphosConsole.setAttribute + ~TyphosConsole.setAutoFillBackground + ~TyphosConsole.setBackgroundRole + ~TyphosConsole.setBaseSize + ~TyphosConsole.setContentsMargins + ~TyphosConsole.setContextMenuPolicy + ~TyphosConsole.setCursor + ~TyphosConsole.setDisabled + ~TyphosConsole.setEnabled + ~TyphosConsole.setFixedHeight + ~TyphosConsole.setFixedSize + ~TyphosConsole.setFixedWidth + ~TyphosConsole.setFocus + ~TyphosConsole.setFocusPolicy + ~TyphosConsole.setFocusProxy + ~TyphosConsole.setFont + ~TyphosConsole.setForegroundRole + ~TyphosConsole.setGeometry + ~TyphosConsole.setGraphicsEffect + ~TyphosConsole.setHidden + ~TyphosConsole.setInputMethodHints + ~TyphosConsole.setLayout + ~TyphosConsole.setLayoutDirection + ~TyphosConsole.setLocale + ~TyphosConsole.setMask + ~TyphosConsole.setMaximumHeight + ~TyphosConsole.setMaximumSize + ~TyphosConsole.setMaximumWidth + ~TyphosConsole.setMinimumHeight + ~TyphosConsole.setMinimumSize + ~TyphosConsole.setMinimumWidth + ~TyphosConsole.setMouseTracking + ~TyphosConsole.setObjectName + ~TyphosConsole.setPalette + ~TyphosConsole.setParent + ~TyphosConsole.setProperty + ~TyphosConsole.setShortcutAutoRepeat + ~TyphosConsole.setShortcutEnabled + ~TyphosConsole.setSizeIncrement + ~TyphosConsole.setSizePolicy + ~TyphosConsole.setStatusTip + ~TyphosConsole.setStyle + ~TyphosConsole.setStyleSheet + ~TyphosConsole.setTabOrder + ~TyphosConsole.setTabletTracking + ~TyphosConsole.setToolTip + ~TyphosConsole.setToolTipDuration + ~TyphosConsole.setUpdatesEnabled + ~TyphosConsole.setVisible + ~TyphosConsole.setWhatsThis + ~TyphosConsole.setWindowFilePath + ~TyphosConsole.setWindowFlag + ~TyphosConsole.setWindowFlags + ~TyphosConsole.setWindowIcon + ~TyphosConsole.setWindowIconText + ~TyphosConsole.setWindowModality + ~TyphosConsole.setWindowModified + ~TyphosConsole.setWindowOpacity + ~TyphosConsole.setWindowRole + ~TyphosConsole.setWindowState + ~TyphosConsole.setWindowTitle + ~TyphosConsole.sharedPainter + ~TyphosConsole.show + ~TyphosConsole.showEvent + ~TyphosConsole.showFullScreen + ~TyphosConsole.showMaximized + ~TyphosConsole.showMinimized + ~TyphosConsole.showNormal + ~TyphosConsole.shutdown + ~TyphosConsole.signalsBlocked + ~TyphosConsole.size + ~TyphosConsole.sizeHint + ~TyphosConsole.sizeIncrement + ~TyphosConsole.sizePolicy + ~TyphosConsole.stackUnder + ~TyphosConsole.startTimer + ~TyphosConsole.statusTip + ~TyphosConsole.style + ~TyphosConsole.styleSheet + ~TyphosConsole.tabletEvent + ~TyphosConsole.testAttribute + ~TyphosConsole.thread + ~TyphosConsole.timerEvent + ~TyphosConsole.toolTip + ~TyphosConsole.toolTipDuration + ~TyphosConsole.tr + ~TyphosConsole.underMouse + ~TyphosConsole.ungrabGesture + ~TyphosConsole.unsetCursor + ~TyphosConsole.unsetLayoutDirection + ~TyphosConsole.unsetLocale + ~TyphosConsole.update + ~TyphosConsole.updateGeometry + ~TyphosConsole.updateMicroFocus + ~TyphosConsole.updatesEnabled + ~TyphosConsole.visibleRegion + ~TyphosConsole.whatsThis + ~TyphosConsole.wheelEvent + ~TyphosConsole.width + ~TyphosConsole.widthMM + ~TyphosConsole.winId + ~TyphosConsole.window + ~TyphosConsole.windowFilePath + ~TyphosConsole.windowFlags + ~TyphosConsole.windowHandle + ~TyphosConsole.windowIcon + ~TyphosConsole.windowIconText + ~TyphosConsole.windowModality + ~TyphosConsole.windowOpacity + ~TyphosConsole.windowRole + ~TyphosConsole.windowState + ~TyphosConsole.windowTitle + ~TyphosConsole.windowType + ~TyphosConsole.x + ~TyphosConsole.y + + + + + + .. rubric:: Attributes + + .. autosummary:: + + ~TyphosConsole.DrawChildren + ~TyphosConsole.DrawWindowBackground + ~TyphosConsole.IgnoreMask + ~TyphosConsole.PdmDepth + ~TyphosConsole.PdmDevicePixelRatio + ~TyphosConsole.PdmDevicePixelRatioScaled + ~TyphosConsole.PdmDpiX + ~TyphosConsole.PdmDpiY + ~TyphosConsole.PdmHeight + ~TyphosConsole.PdmHeightMM + ~TyphosConsole.PdmNumColors + ~TyphosConsole.PdmPhysicalDpiX + ~TyphosConsole.PdmPhysicalDpiY + ~TyphosConsole.PdmWidth + ~TyphosConsole.PdmWidthMM + ~TyphosConsole.customContextMenuRequested + ~TyphosConsole.destroyed + ~TyphosConsole.device_added + ~TyphosConsole.kernel_is_alive + ~TyphosConsole.kernel_is_ready + ~TyphosConsole.kernel_ready + ~TyphosConsole.kernel_shut_down + ~TyphosConsole.objectNameChanged + ~TyphosConsole.staticMetaObject + ~TyphosConsole.windowIconChanged + ~TyphosConsole.windowIconTextChanged + ~TyphosConsole.windowTitleChanged + + \ No newline at end of file diff --git a/v2.4.1/_sources/generated/typhos.tools.TyphosLogDisplay.rst.txt b/v2.4.1/_sources/generated/typhos.tools.TyphosLogDisplay.rst.txt new file mode 100644 index 000000000..077f6c3ad --- /dev/null +++ b/v2.4.1/_sources/generated/typhos.tools.TyphosLogDisplay.rst.txt @@ -0,0 +1,355 @@ +typhos.tools.TyphosLogDisplay +============================= + +.. currentmodule:: typhos.tools + +.. autoclass:: TyphosLogDisplay + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~TyphosLogDisplay.__init__ + ~TyphosLogDisplay.acceptDrops + ~TyphosLogDisplay.accessibleDescription + ~TyphosLogDisplay.accessibleName + ~TyphosLogDisplay.actionEvent + ~TyphosLogDisplay.actions + ~TyphosLogDisplay.activateWindow + ~TyphosLogDisplay.addAction + ~TyphosLogDisplay.addActions + ~TyphosLogDisplay.add_device + ~TyphosLogDisplay.adjustSize + ~TyphosLogDisplay.autoFillBackground + ~TyphosLogDisplay.backgroundRole + ~TyphosLogDisplay.baseSize + ~TyphosLogDisplay.blockSignals + ~TyphosLogDisplay.changeEvent + ~TyphosLogDisplay.childAt + ~TyphosLogDisplay.childEvent + ~TyphosLogDisplay.children + ~TyphosLogDisplay.childrenRect + ~TyphosLogDisplay.childrenRegion + ~TyphosLogDisplay.clearFocus + ~TyphosLogDisplay.clearMask + ~TyphosLogDisplay.close + ~TyphosLogDisplay.closeEvent + ~TyphosLogDisplay.colorCount + ~TyphosLogDisplay.connectNotify + ~TyphosLogDisplay.contentsMargins + ~TyphosLogDisplay.contentsRect + ~TyphosLogDisplay.contextMenuEvent + ~TyphosLogDisplay.contextMenuPolicy + ~TyphosLogDisplay.create + ~TyphosLogDisplay.createWindowContainer + ~TyphosLogDisplay.cursor + ~TyphosLogDisplay.customEvent + ~TyphosLogDisplay.deleteLater + ~TyphosLogDisplay.depth + ~TyphosLogDisplay.destroy + ~TyphosLogDisplay.devType + ~TyphosLogDisplay.devicePixelRatio + ~TyphosLogDisplay.devicePixelRatioF + ~TyphosLogDisplay.devicePixelRatioFScale + ~TyphosLogDisplay.disconnect + ~TyphosLogDisplay.disconnectNotify + ~TyphosLogDisplay.dragEnterEvent + ~TyphosLogDisplay.dragLeaveEvent + ~TyphosLogDisplay.dragMoveEvent + ~TyphosLogDisplay.dropEvent + ~TyphosLogDisplay.dumpObjectInfo + ~TyphosLogDisplay.dumpObjectTree + ~TyphosLogDisplay.dynamicPropertyNames + ~TyphosLogDisplay.effectiveWinId + ~TyphosLogDisplay.ensurePolished + ~TyphosLogDisplay.enterEvent + ~TyphosLogDisplay.event + ~TyphosLogDisplay.eventFilter + ~TyphosLogDisplay.find + ~TyphosLogDisplay.findChild + ~TyphosLogDisplay.findChildren + ~TyphosLogDisplay.focusInEvent + ~TyphosLogDisplay.focusNextChild + ~TyphosLogDisplay.focusNextPrevChild + ~TyphosLogDisplay.focusOutEvent + ~TyphosLogDisplay.focusPolicy + ~TyphosLogDisplay.focusPreviousChild + ~TyphosLogDisplay.focusProxy + ~TyphosLogDisplay.focusWidget + ~TyphosLogDisplay.font + ~TyphosLogDisplay.fontInfo + ~TyphosLogDisplay.fontMetrics + ~TyphosLogDisplay.foregroundRole + ~TyphosLogDisplay.frameGeometry + ~TyphosLogDisplay.frameSize + ~TyphosLogDisplay.from_device + ~TyphosLogDisplay.geometry + ~TyphosLogDisplay.getContentsMargins + ~TyphosLogDisplay.grab + ~TyphosLogDisplay.grabGesture + ~TyphosLogDisplay.grabKeyboard + ~TyphosLogDisplay.grabMouse + ~TyphosLogDisplay.grabShortcut + ~TyphosLogDisplay.graphicsEffect + ~TyphosLogDisplay.graphicsProxyWidget + ~TyphosLogDisplay.hasFocus + ~TyphosLogDisplay.hasHeightForWidth + ~TyphosLogDisplay.hasMouseTracking + ~TyphosLogDisplay.hasTabletTracking + ~TyphosLogDisplay.height + ~TyphosLogDisplay.heightForWidth + ~TyphosLogDisplay.heightMM + ~TyphosLogDisplay.hide + ~TyphosLogDisplay.hideEvent + ~TyphosLogDisplay.inherits + ~TyphosLogDisplay.initPainter + ~TyphosLogDisplay.inputMethodEvent + ~TyphosLogDisplay.inputMethodHints + ~TyphosLogDisplay.inputMethodQuery + ~TyphosLogDisplay.insertAction + ~TyphosLogDisplay.insertActions + ~TyphosLogDisplay.installEventFilter + ~TyphosLogDisplay.isActiveWindow + ~TyphosLogDisplay.isAncestorOf + ~TyphosLogDisplay.isEnabled + ~TyphosLogDisplay.isEnabledTo + ~TyphosLogDisplay.isFullScreen + ~TyphosLogDisplay.isHidden + ~TyphosLogDisplay.isLeftToRight + ~TyphosLogDisplay.isMaximized + ~TyphosLogDisplay.isMinimized + ~TyphosLogDisplay.isModal + ~TyphosLogDisplay.isRightToLeft + ~TyphosLogDisplay.isSignalConnected + ~TyphosLogDisplay.isVisible + ~TyphosLogDisplay.isVisibleTo + ~TyphosLogDisplay.isWidgetType + ~TyphosLogDisplay.isWindow + ~TyphosLogDisplay.isWindowModified + ~TyphosLogDisplay.isWindowType + ~TyphosLogDisplay.keyPressEvent + ~TyphosLogDisplay.keyReleaseEvent + ~TyphosLogDisplay.keyboardGrabber + ~TyphosLogDisplay.killTimer + ~TyphosLogDisplay.layout + ~TyphosLogDisplay.layoutDirection + ~TyphosLogDisplay.leaveEvent + ~TyphosLogDisplay.locale + ~TyphosLogDisplay.logicalDpiX + ~TyphosLogDisplay.logicalDpiY + ~TyphosLogDisplay.lower + ~TyphosLogDisplay.mapFrom + ~TyphosLogDisplay.mapFromGlobal + ~TyphosLogDisplay.mapFromParent + ~TyphosLogDisplay.mapTo + ~TyphosLogDisplay.mapToGlobal + ~TyphosLogDisplay.mapToParent + ~TyphosLogDisplay.mask + ~TyphosLogDisplay.maximumHeight + ~TyphosLogDisplay.maximumSize + ~TyphosLogDisplay.maximumWidth + ~TyphosLogDisplay.metaObject + ~TyphosLogDisplay.metric + ~TyphosLogDisplay.minimumHeight + ~TyphosLogDisplay.minimumSize + ~TyphosLogDisplay.minimumSizeHint + ~TyphosLogDisplay.minimumWidth + ~TyphosLogDisplay.mouseDoubleClickEvent + ~TyphosLogDisplay.mouseGrabber + ~TyphosLogDisplay.mouseMoveEvent + ~TyphosLogDisplay.mousePressEvent + ~TyphosLogDisplay.mouseReleaseEvent + ~TyphosLogDisplay.move + ~TyphosLogDisplay.moveEvent + ~TyphosLogDisplay.moveToThread + ~TyphosLogDisplay.nativeEvent + ~TyphosLogDisplay.nativeParentWidget + ~TyphosLogDisplay.nextInFocusChain + ~TyphosLogDisplay.normalGeometry + ~TyphosLogDisplay.objectName + ~TyphosLogDisplay.overrideWindowFlags + ~TyphosLogDisplay.overrideWindowState + ~TyphosLogDisplay.paintEngine + ~TyphosLogDisplay.paintEvent + ~TyphosLogDisplay.paintingActive + ~TyphosLogDisplay.palette + ~TyphosLogDisplay.parent + ~TyphosLogDisplay.parentWidget + ~TyphosLogDisplay.physicalDpiX + ~TyphosLogDisplay.physicalDpiY + ~TyphosLogDisplay.pos + ~TyphosLogDisplay.previousInFocusChain + ~TyphosLogDisplay.property + ~TyphosLogDisplay.pyqtConfigure + ~TyphosLogDisplay.raise_ + ~TyphosLogDisplay.receivers + ~TyphosLogDisplay.rect + ~TyphosLogDisplay.releaseKeyboard + ~TyphosLogDisplay.releaseMouse + ~TyphosLogDisplay.releaseShortcut + ~TyphosLogDisplay.removeAction + ~TyphosLogDisplay.removeEventFilter + ~TyphosLogDisplay.render + ~TyphosLogDisplay.repaint + ~TyphosLogDisplay.resize + ~TyphosLogDisplay.resizeEvent + ~TyphosLogDisplay.restoreGeometry + ~TyphosLogDisplay.saveGeometry + ~TyphosLogDisplay.screen + ~TyphosLogDisplay.scroll + ~TyphosLogDisplay.sender + ~TyphosLogDisplay.senderSignalIndex + ~TyphosLogDisplay.setAcceptDrops + ~TyphosLogDisplay.setAccessibleDescription + ~TyphosLogDisplay.setAccessibleName + ~TyphosLogDisplay.setAttribute + ~TyphosLogDisplay.setAutoFillBackground + ~TyphosLogDisplay.setBackgroundRole + ~TyphosLogDisplay.setBaseSize + ~TyphosLogDisplay.setContentsMargins + ~TyphosLogDisplay.setContextMenuPolicy + ~TyphosLogDisplay.setCursor + ~TyphosLogDisplay.setDisabled + ~TyphosLogDisplay.setEnabled + ~TyphosLogDisplay.setFixedHeight + ~TyphosLogDisplay.setFixedSize + ~TyphosLogDisplay.setFixedWidth + ~TyphosLogDisplay.setFocus + ~TyphosLogDisplay.setFocusPolicy + ~TyphosLogDisplay.setFocusProxy + ~TyphosLogDisplay.setFont + ~TyphosLogDisplay.setForegroundRole + ~TyphosLogDisplay.setGeometry + ~TyphosLogDisplay.setGraphicsEffect + ~TyphosLogDisplay.setHidden + ~TyphosLogDisplay.setInputMethodHints + ~TyphosLogDisplay.setLayout + ~TyphosLogDisplay.setLayoutDirection + ~TyphosLogDisplay.setLocale + ~TyphosLogDisplay.setMask + ~TyphosLogDisplay.setMaximumHeight + ~TyphosLogDisplay.setMaximumSize + ~TyphosLogDisplay.setMaximumWidth + ~TyphosLogDisplay.setMinimumHeight + ~TyphosLogDisplay.setMinimumSize + ~TyphosLogDisplay.setMinimumWidth + ~TyphosLogDisplay.setMouseTracking + ~TyphosLogDisplay.setObjectName + ~TyphosLogDisplay.setPalette + ~TyphosLogDisplay.setParent + ~TyphosLogDisplay.setProperty + ~TyphosLogDisplay.setShortcutAutoRepeat + ~TyphosLogDisplay.setShortcutEnabled + ~TyphosLogDisplay.setSizeIncrement + ~TyphosLogDisplay.setSizePolicy + ~TyphosLogDisplay.setStatusTip + ~TyphosLogDisplay.setStyle + ~TyphosLogDisplay.setStyleSheet + ~TyphosLogDisplay.setTabOrder + ~TyphosLogDisplay.setTabletTracking + ~TyphosLogDisplay.setToolTip + ~TyphosLogDisplay.setToolTipDuration + ~TyphosLogDisplay.setUpdatesEnabled + ~TyphosLogDisplay.setVisible + ~TyphosLogDisplay.setWhatsThis + ~TyphosLogDisplay.setWindowFilePath + ~TyphosLogDisplay.setWindowFlag + ~TyphosLogDisplay.setWindowFlags + ~TyphosLogDisplay.setWindowIcon + ~TyphosLogDisplay.setWindowIconText + ~TyphosLogDisplay.setWindowModality + ~TyphosLogDisplay.setWindowModified + ~TyphosLogDisplay.setWindowOpacity + ~TyphosLogDisplay.setWindowRole + ~TyphosLogDisplay.setWindowState + ~TyphosLogDisplay.setWindowTitle + ~TyphosLogDisplay.sharedPainter + ~TyphosLogDisplay.show + ~TyphosLogDisplay.showEvent + ~TyphosLogDisplay.showFullScreen + ~TyphosLogDisplay.showMaximized + ~TyphosLogDisplay.showMinimized + ~TyphosLogDisplay.showNormal + ~TyphosLogDisplay.signalsBlocked + ~TyphosLogDisplay.size + ~TyphosLogDisplay.sizeHint + ~TyphosLogDisplay.sizeIncrement + ~TyphosLogDisplay.sizePolicy + ~TyphosLogDisplay.stackUnder + ~TyphosLogDisplay.startTimer + ~TyphosLogDisplay.statusTip + ~TyphosLogDisplay.style + ~TyphosLogDisplay.styleSheet + ~TyphosLogDisplay.tabletEvent + ~TyphosLogDisplay.testAttribute + ~TyphosLogDisplay.thread + ~TyphosLogDisplay.timerEvent + ~TyphosLogDisplay.toolTip + ~TyphosLogDisplay.toolTipDuration + ~TyphosLogDisplay.tr + ~TyphosLogDisplay.underMouse + ~TyphosLogDisplay.ungrabGesture + ~TyphosLogDisplay.unsetCursor + ~TyphosLogDisplay.unsetLayoutDirection + ~TyphosLogDisplay.unsetLocale + ~TyphosLogDisplay.update + ~TyphosLogDisplay.updateGeometry + ~TyphosLogDisplay.updateMicroFocus + ~TyphosLogDisplay.updatesEnabled + ~TyphosLogDisplay.visibleRegion + ~TyphosLogDisplay.whatsThis + ~TyphosLogDisplay.wheelEvent + ~TyphosLogDisplay.width + ~TyphosLogDisplay.widthMM + ~TyphosLogDisplay.winId + ~TyphosLogDisplay.window + ~TyphosLogDisplay.windowFilePath + ~TyphosLogDisplay.windowFlags + ~TyphosLogDisplay.windowHandle + ~TyphosLogDisplay.windowIcon + ~TyphosLogDisplay.windowIconText + ~TyphosLogDisplay.windowModality + ~TyphosLogDisplay.windowOpacity + ~TyphosLogDisplay.windowRole + ~TyphosLogDisplay.windowState + ~TyphosLogDisplay.windowTitle + ~TyphosLogDisplay.windowType + ~TyphosLogDisplay.x + ~TyphosLogDisplay.y + + + + + + .. rubric:: Attributes + + .. autosummary:: + + ~TyphosLogDisplay.DrawChildren + ~TyphosLogDisplay.DrawWindowBackground + ~TyphosLogDisplay.IgnoreMask + ~TyphosLogDisplay.PdmDepth + ~TyphosLogDisplay.PdmDevicePixelRatio + ~TyphosLogDisplay.PdmDevicePixelRatioScaled + ~TyphosLogDisplay.PdmDpiX + ~TyphosLogDisplay.PdmDpiY + ~TyphosLogDisplay.PdmHeight + ~TyphosLogDisplay.PdmHeightMM + ~TyphosLogDisplay.PdmNumColors + ~TyphosLogDisplay.PdmPhysicalDpiX + ~TyphosLogDisplay.PdmPhysicalDpiY + ~TyphosLogDisplay.PdmWidth + ~TyphosLogDisplay.PdmWidthMM + ~TyphosLogDisplay.customContextMenuRequested + ~TyphosLogDisplay.destroyed + ~TyphosLogDisplay.objectNameChanged + ~TyphosLogDisplay.staticMetaObject + ~TyphosLogDisplay.windowIconChanged + ~TyphosLogDisplay.windowIconTextChanged + ~TyphosLogDisplay.windowTitleChanged + + \ No newline at end of file diff --git a/v2.4.1/_sources/generated/typhos.tools.TyphosTimePlot.rst.txt b/v2.4.1/_sources/generated/typhos.tools.TyphosTimePlot.rst.txt new file mode 100644 index 000000000..56ada956c --- /dev/null +++ b/v2.4.1/_sources/generated/typhos.tools.TyphosTimePlot.rst.txt @@ -0,0 +1,360 @@ +typhos.tools.TyphosTimePlot +=========================== + +.. currentmodule:: typhos.tools + +.. autoclass:: TyphosTimePlot + + + .. automethod:: __init__ + + + .. rubric:: Methods + + .. autosummary:: + + ~TyphosTimePlot.__init__ + ~TyphosTimePlot.acceptDrops + ~TyphosTimePlot.accessibleDescription + ~TyphosTimePlot.accessibleName + ~TyphosTimePlot.actionEvent + ~TyphosTimePlot.actions + ~TyphosTimePlot.activateWindow + ~TyphosTimePlot.addAction + ~TyphosTimePlot.addActions + ~TyphosTimePlot.add_available_signal + ~TyphosTimePlot.add_curve + ~TyphosTimePlot.add_device + ~TyphosTimePlot.adjustSize + ~TyphosTimePlot.autoFillBackground + ~TyphosTimePlot.backgroundRole + ~TyphosTimePlot.baseSize + ~TyphosTimePlot.blockSignals + ~TyphosTimePlot.changeEvent + ~TyphosTimePlot.childAt + ~TyphosTimePlot.childEvent + ~TyphosTimePlot.children + ~TyphosTimePlot.childrenRect + ~TyphosTimePlot.childrenRegion + ~TyphosTimePlot.clearFocus + ~TyphosTimePlot.clearMask + ~TyphosTimePlot.close + ~TyphosTimePlot.closeEvent + ~TyphosTimePlot.colorCount + ~TyphosTimePlot.connectNotify + ~TyphosTimePlot.contentsMargins + ~TyphosTimePlot.contentsRect + ~TyphosTimePlot.contextMenuEvent + ~TyphosTimePlot.contextMenuPolicy + ~TyphosTimePlot.create + ~TyphosTimePlot.createWindowContainer + ~TyphosTimePlot.creation_requested + ~TyphosTimePlot.cursor + ~TyphosTimePlot.customEvent + ~TyphosTimePlot.deleteLater + ~TyphosTimePlot.depth + ~TyphosTimePlot.destroy + ~TyphosTimePlot.devType + ~TyphosTimePlot.devicePixelRatio + ~TyphosTimePlot.devicePixelRatioF + ~TyphosTimePlot.devicePixelRatioFScale + ~TyphosTimePlot.disconnect + ~TyphosTimePlot.disconnectNotify + ~TyphosTimePlot.dragEnterEvent + ~TyphosTimePlot.dragLeaveEvent + ~TyphosTimePlot.dragMoveEvent + ~TyphosTimePlot.dropEvent + ~TyphosTimePlot.dumpObjectInfo + ~TyphosTimePlot.dumpObjectTree + ~TyphosTimePlot.dynamicPropertyNames + ~TyphosTimePlot.effectiveWinId + ~TyphosTimePlot.ensurePolished + ~TyphosTimePlot.enterEvent + ~TyphosTimePlot.event + ~TyphosTimePlot.eventFilter + ~TyphosTimePlot.find + ~TyphosTimePlot.findChild + ~TyphosTimePlot.findChildren + ~TyphosTimePlot.focusInEvent + ~TyphosTimePlot.focusNextChild + ~TyphosTimePlot.focusNextPrevChild + ~TyphosTimePlot.focusOutEvent + ~TyphosTimePlot.focusPolicy + ~TyphosTimePlot.focusPreviousChild + ~TyphosTimePlot.focusProxy + ~TyphosTimePlot.focusWidget + ~TyphosTimePlot.font + ~TyphosTimePlot.fontInfo + ~TyphosTimePlot.fontMetrics + ~TyphosTimePlot.foregroundRole + ~TyphosTimePlot.frameGeometry + ~TyphosTimePlot.frameSize + ~TyphosTimePlot.from_device + ~TyphosTimePlot.geometry + ~TyphosTimePlot.getContentsMargins + ~TyphosTimePlot.grab + ~TyphosTimePlot.grabGesture + ~TyphosTimePlot.grabKeyboard + ~TyphosTimePlot.grabMouse + ~TyphosTimePlot.grabShortcut + ~TyphosTimePlot.graphicsEffect + ~TyphosTimePlot.graphicsProxyWidget + ~TyphosTimePlot.hasFocus + ~TyphosTimePlot.hasHeightForWidth + ~TyphosTimePlot.hasMouseTracking + ~TyphosTimePlot.hasTabletTracking + ~TyphosTimePlot.height + ~TyphosTimePlot.heightForWidth + ~TyphosTimePlot.heightMM + ~TyphosTimePlot.hide + ~TyphosTimePlot.hideEvent + ~TyphosTimePlot.inherits + ~TyphosTimePlot.initPainter + ~TyphosTimePlot.inputMethodEvent + ~TyphosTimePlot.inputMethodHints + ~TyphosTimePlot.inputMethodQuery + ~TyphosTimePlot.insertAction + ~TyphosTimePlot.insertActions + ~TyphosTimePlot.installEventFilter + ~TyphosTimePlot.isActiveWindow + ~TyphosTimePlot.isAncestorOf + ~TyphosTimePlot.isEnabled + ~TyphosTimePlot.isEnabledTo + ~TyphosTimePlot.isFullScreen + ~TyphosTimePlot.isHidden + ~TyphosTimePlot.isLeftToRight + ~TyphosTimePlot.isMaximized + ~TyphosTimePlot.isMinimized + ~TyphosTimePlot.isModal + ~TyphosTimePlot.isRightToLeft + ~TyphosTimePlot.isSignalConnected + ~TyphosTimePlot.isVisible + ~TyphosTimePlot.isVisibleTo + ~TyphosTimePlot.isWidgetType + ~TyphosTimePlot.isWindow + ~TyphosTimePlot.isWindowModified + ~TyphosTimePlot.isWindowType + ~TyphosTimePlot.keyPressEvent + ~TyphosTimePlot.keyReleaseEvent + ~TyphosTimePlot.keyboardGrabber + ~TyphosTimePlot.killTimer + ~TyphosTimePlot.layout + ~TyphosTimePlot.layoutDirection + ~TyphosTimePlot.leaveEvent + ~TyphosTimePlot.locale + ~TyphosTimePlot.logicalDpiX + ~TyphosTimePlot.logicalDpiY + ~TyphosTimePlot.lower + ~TyphosTimePlot.mapFrom + ~TyphosTimePlot.mapFromGlobal + ~TyphosTimePlot.mapFromParent + ~TyphosTimePlot.mapTo + ~TyphosTimePlot.mapToGlobal + ~TyphosTimePlot.mapToParent + ~TyphosTimePlot.mask + ~TyphosTimePlot.maximumHeight + ~TyphosTimePlot.maximumSize + ~TyphosTimePlot.maximumWidth + ~TyphosTimePlot.metaObject + ~TyphosTimePlot.metric + ~TyphosTimePlot.minimumHeight + ~TyphosTimePlot.minimumSize + ~TyphosTimePlot.minimumSizeHint + ~TyphosTimePlot.minimumWidth + ~TyphosTimePlot.mouseDoubleClickEvent + ~TyphosTimePlot.mouseGrabber + ~TyphosTimePlot.mouseMoveEvent + ~TyphosTimePlot.mousePressEvent + ~TyphosTimePlot.mouseReleaseEvent + ~TyphosTimePlot.move + ~TyphosTimePlot.moveEvent + ~TyphosTimePlot.moveToThread + ~TyphosTimePlot.nativeEvent + ~TyphosTimePlot.nativeParentWidget + ~TyphosTimePlot.nextInFocusChain + ~TyphosTimePlot.normalGeometry + ~TyphosTimePlot.objectName + ~TyphosTimePlot.overrideWindowFlags + ~TyphosTimePlot.overrideWindowState + ~TyphosTimePlot.paintEngine + ~TyphosTimePlot.paintEvent + ~TyphosTimePlot.paintingActive + ~TyphosTimePlot.palette + ~TyphosTimePlot.parent + ~TyphosTimePlot.parentWidget + ~TyphosTimePlot.physicalDpiX + ~TyphosTimePlot.physicalDpiY + ~TyphosTimePlot.pos + ~TyphosTimePlot.previousInFocusChain + ~TyphosTimePlot.property + ~TyphosTimePlot.pyqtConfigure + ~TyphosTimePlot.raise_ + ~TyphosTimePlot.receivers + ~TyphosTimePlot.rect + ~TyphosTimePlot.releaseKeyboard + ~TyphosTimePlot.releaseMouse + ~TyphosTimePlot.releaseShortcut + ~TyphosTimePlot.removeAction + ~TyphosTimePlot.removeEventFilter + ~TyphosTimePlot.remove_curve + ~TyphosTimePlot.render + ~TyphosTimePlot.repaint + ~TyphosTimePlot.resize + ~TyphosTimePlot.resizeEvent + ~TyphosTimePlot.restoreGeometry + ~TyphosTimePlot.saveGeometry + ~TyphosTimePlot.screen + ~TyphosTimePlot.scroll + ~TyphosTimePlot.sender + ~TyphosTimePlot.senderSignalIndex + ~TyphosTimePlot.setAcceptDrops + ~TyphosTimePlot.setAccessibleDescription + ~TyphosTimePlot.setAccessibleName + ~TyphosTimePlot.setAttribute + ~TyphosTimePlot.setAutoFillBackground + ~TyphosTimePlot.setBackgroundRole + ~TyphosTimePlot.setBaseSize + ~TyphosTimePlot.setContentsMargins + ~TyphosTimePlot.setContextMenuPolicy + ~TyphosTimePlot.setCursor + ~TyphosTimePlot.setDisabled + ~TyphosTimePlot.setEnabled + ~TyphosTimePlot.setFixedHeight + ~TyphosTimePlot.setFixedSize + ~TyphosTimePlot.setFixedWidth + ~TyphosTimePlot.setFocus + ~TyphosTimePlot.setFocusPolicy + ~TyphosTimePlot.setFocusProxy + ~TyphosTimePlot.setFont + ~TyphosTimePlot.setForegroundRole + ~TyphosTimePlot.setGeometry + ~TyphosTimePlot.setGraphicsEffect + ~TyphosTimePlot.setHidden + ~TyphosTimePlot.setInputMethodHints + ~TyphosTimePlot.setLayout + ~TyphosTimePlot.setLayoutDirection + ~TyphosTimePlot.setLocale + ~TyphosTimePlot.setMask + ~TyphosTimePlot.setMaximumHeight + ~TyphosTimePlot.setMaximumSize + ~TyphosTimePlot.setMaximumWidth + ~TyphosTimePlot.setMinimumHeight + ~TyphosTimePlot.setMinimumSize + ~TyphosTimePlot.setMinimumWidth + ~TyphosTimePlot.setMouseTracking + ~TyphosTimePlot.setObjectName + ~TyphosTimePlot.setPalette + ~TyphosTimePlot.setParent + ~TyphosTimePlot.setProperty + ~TyphosTimePlot.setShortcutAutoRepeat + ~TyphosTimePlot.setShortcutEnabled + ~TyphosTimePlot.setSizeIncrement + ~TyphosTimePlot.setSizePolicy + ~TyphosTimePlot.setStatusTip + ~TyphosTimePlot.setStyle + ~TyphosTimePlot.setStyleSheet + ~TyphosTimePlot.setTabOrder + ~TyphosTimePlot.setTabletTracking + ~TyphosTimePlot.setToolTip + ~TyphosTimePlot.setToolTipDuration + ~TyphosTimePlot.setUpdatesEnabled + ~TyphosTimePlot.setVisible + ~TyphosTimePlot.setWhatsThis + ~TyphosTimePlot.setWindowFilePath + ~TyphosTimePlot.setWindowFlag + ~TyphosTimePlot.setWindowFlags + ~TyphosTimePlot.setWindowIcon + ~TyphosTimePlot.setWindowIconText + ~TyphosTimePlot.setWindowModality + ~TyphosTimePlot.setWindowModified + ~TyphosTimePlot.setWindowOpacity + ~TyphosTimePlot.setWindowRole + ~TyphosTimePlot.setWindowState + ~TyphosTimePlot.setWindowTitle + ~TyphosTimePlot.sharedPainter + ~TyphosTimePlot.show + ~TyphosTimePlot.showEvent + ~TyphosTimePlot.showFullScreen + ~TyphosTimePlot.showMaximized + ~TyphosTimePlot.showMinimized + ~TyphosTimePlot.showNormal + ~TyphosTimePlot.signalsBlocked + ~TyphosTimePlot.size + ~TyphosTimePlot.sizeHint + ~TyphosTimePlot.sizeIncrement + ~TyphosTimePlot.sizePolicy + ~TyphosTimePlot.stackUnder + ~TyphosTimePlot.startTimer + ~TyphosTimePlot.statusTip + ~TyphosTimePlot.style + ~TyphosTimePlot.styleSheet + ~TyphosTimePlot.tabletEvent + ~TyphosTimePlot.testAttribute + ~TyphosTimePlot.thread + ~TyphosTimePlot.timerEvent + ~TyphosTimePlot.toolTip + ~TyphosTimePlot.toolTipDuration + ~TyphosTimePlot.tr + ~TyphosTimePlot.underMouse + ~TyphosTimePlot.ungrabGesture + ~TyphosTimePlot.unsetCursor + ~TyphosTimePlot.unsetLayoutDirection + ~TyphosTimePlot.unsetLocale + ~TyphosTimePlot.update + ~TyphosTimePlot.updateGeometry + ~TyphosTimePlot.updateMicroFocus + ~TyphosTimePlot.updatesEnabled + ~TyphosTimePlot.visibleRegion + ~TyphosTimePlot.whatsThis + ~TyphosTimePlot.wheelEvent + ~TyphosTimePlot.width + ~TyphosTimePlot.widthMM + ~TyphosTimePlot.winId + ~TyphosTimePlot.window + ~TyphosTimePlot.windowFilePath + ~TyphosTimePlot.windowFlags + ~TyphosTimePlot.windowHandle + ~TyphosTimePlot.windowIcon + ~TyphosTimePlot.windowIconText + ~TyphosTimePlot.windowModality + ~TyphosTimePlot.windowOpacity + ~TyphosTimePlot.windowRole + ~TyphosTimePlot.windowState + ~TyphosTimePlot.windowTitle + ~TyphosTimePlot.windowType + ~TyphosTimePlot.x + ~TyphosTimePlot.y + + + + + + .. rubric:: Attributes + + .. autosummary:: + + ~TyphosTimePlot.DrawChildren + ~TyphosTimePlot.DrawWindowBackground + ~TyphosTimePlot.IgnoreMask + ~TyphosTimePlot.PdmDepth + ~TyphosTimePlot.PdmDevicePixelRatio + ~TyphosTimePlot.PdmDevicePixelRatioScaled + ~TyphosTimePlot.PdmDpiX + ~TyphosTimePlot.PdmDpiY + ~TyphosTimePlot.PdmHeight + ~TyphosTimePlot.PdmHeightMM + ~TyphosTimePlot.PdmNumColors + ~TyphosTimePlot.PdmPhysicalDpiX + ~TyphosTimePlot.PdmPhysicalDpiY + ~TyphosTimePlot.PdmWidth + ~TyphosTimePlot.PdmWidthMM + ~TyphosTimePlot.channel_to_curve + ~TyphosTimePlot.customContextMenuRequested + ~TyphosTimePlot.destroyed + ~TyphosTimePlot.objectNameChanged + ~TyphosTimePlot.staticMetaObject + ~TyphosTimePlot.windowIconChanged + ~TyphosTimePlot.windowIconTextChanged + ~TyphosTimePlot.windowTitleChanged + + \ No newline at end of file diff --git a/v2.4.1/_sources/index.rst.txt b/v2.4.1/_sources/index.rst.txt new file mode 100644 index 000000000..3c10c0e28 --- /dev/null +++ b/v2.4.1/_sources/index.rst.txt @@ -0,0 +1,57 @@ +====== +Typhos +====== + +EPICS is a flexible and powerful controls system to access to experimental +information, however, the relation and meaning of process variables is often +obscure. Many of the user interfaces for EPICS information reflect this, as +walls of buttons and flashing lights bombard the user with little thought to +structure or cohesion. + +Typhos addresses this by providing an automated way to generate screens based +on a provided hierarchy of devices and signals. Built using PyDM, a PyQt based +display manager developed at SLAC National Laboratory, Typhos utilizes a large +toolkit of widgets to display EPICS information. For each process variable, a +corresponding widget is created based on; the importance to the average +operator, the type of value the EPICS PV will return, and whether a user should +be allowed to write to the variable. These widgets are then placed in a +convenient tab-based system to only show the necessary information for basic +function, but still allow access to more advanced signals. + +Instead of reinventing a new way to specify device structures, Typhos uses +`Ophyd`, a library to abstract EPICS information into consistently structured +Python objects. Originally built for scripting experimental procedures at +NSLS-II, Ophyd represents devices as combinations of components which are +either signals or nested devices. Then, either at runtime or by using the +defaults of the representative Python class, these signals are sorted into +different categories based on their relevance to operators. Typhos uses this +information to craft user interfaces. + +Related Projects +---------------- +- `pydm `_ +- `ophyd `_ + +.. toctree:: + :maxdepth: 2 + :caption: User Documentation + :hidden: + + basic_usage.rst + tools.rst + save.rst + connections.rst + python_methods.rst + templates.rst + release_notes.rst + +.. toctree:: + :maxdepth: 3 + :caption: Developer Documentation + :hidden: + + cli.rst + display.rst + widgets.rst + plugins.rst + utils.rst diff --git a/v2.4.1/_sources/plugins.rst.txt b/v2.4.1/_sources/plugins.rst.txt new file mode 100644 index 000000000..4d0f04793 --- /dev/null +++ b/v2.4.1/_sources/plugins.rst.txt @@ -0,0 +1,25 @@ +============================ +Typhos Data Plugins for PyDM +============================ + +SignalPlugin +============ +.. autofunction:: typhos.plugins.register_signal + +.. autoclass:: typhos.plugins.SignalConnection + :members: + +.. autoclass:: typhos.plugins.SignalPlugin + + +HappiPlugin +=========== +These functions will not be imported if ``happi`` is not installed in the +current Python environment + +.. autofunction:: typhos.plugins.register_client + +.. autoclass:: typhos.plugins.HappiConnection + :members: + +.. autoclass:: typhos.plugins.HappiPlugin diff --git a/v2.4.1/_sources/python_methods.rst.txt b/v2.4.1/_sources/python_methods.rst.txt new file mode 100644 index 000000000..14c82382e --- /dev/null +++ b/v2.4.1/_sources/python_methods.rst.txt @@ -0,0 +1,43 @@ +===================== +Including Python Code +===================== + + +Adding Methods +============== +Each :class:`.TyphosDeviceDisplay` has an :attr:`.method_panel`. You can add methods +manually or pass them in via the constructor. In order to make the appropriate +widgets, the function signature is examined. For example lets make a mock +function: + +.. code:: python + + def foo(a: int, b: int, c: bool=False, d: float=3.14, e: bool=False): + pass + +When you add the method to the panel the Python ``inspect`` module looks for +type annotations for each parameter. It also determines which parameters are +optional and which are not. Boolean variables are given QCheckboxes, while +others are given QLineEdits for entry. Optional keywords are also hidden from +the user unless they choose to expand the tab. Using +:meth:`.FunctionPanel.add_method` would look like this: + +.. code:: python + + panel.add_method(foo, hide_params=['e']) + + +|function| |expanded| + + +.. |function| image:: _static/function.png + :width: 40 % + +.. |expanded| image:: _static/expanded.jpg + :width: 40 % + + +If you don't want to annotate your function as above, Typhos will attempt to +guess the type of optional variables via their default value. You can also pass +in an `annotations` dictionary that fulfills the indicates the type of each +variable. diff --git a/v2.4.1/_sources/release_notes.rst.txt b/v2.4.1/_sources/release_notes.rst.txt new file mode 100644 index 000000000..c0745b581 --- /dev/null +++ b/v2.4.1/_sources/release_notes.rst.txt @@ -0,0 +1,771 @@ +================= + Release History +================= + +v2.4.1 (2023-4-4) +================= + +Description +----------- +This is a bugfix and maintenance/CI release. + +Bugfixes +-------- +- Include the normal PyDM stylesheets in the loading process. + Previously, this was leading to unexpected behavior. + +Maintenance +----------- +- Fix an issue related to a deleted flake8 mirror. +- Migrates from Travis CI to GitHub Actions for continuous integration testing, and documentation deployment. +- Updates typhos to use setuptools-scm, replacing versioneer, as its version-string management tool of choice. +- Syntax has been updated to Python 3.9+ via ``pyupgrade``. +- typhos has migrated to modern ``pyproject.toml``, replacing ``setup.py``. +- Sphinx 6.0 now supported for documentation building. + +Contributors +------------ +- tangkong +- zllentz + + +v2.4.0 (2022-11-4) +================== + +Description +----------- +This is a small release with features for improving the usage +and configurability of the ``PositionerWidget``. + +Features +-------- +- Report errors raised during the execution of positioner + ``set`` commands in the positioner widget instead of in a pop-up. + This makes it easier to keep track of which positioner widget + is associated with which error and makes it less likely that the + message will be missed or lost on large monitors. +- Add a designer property to ``PositionerWidget``, ``alarmKindLevel``, + to configure the enclosed alarm widget's ``kindLevel`` property in + designer. This was previously only configurable in code. + +Contributors +------------ +- zllentz + + +v2.3.3 (2022-10-20) +=================== + +Description +----------- +This is a small release with bugfixes and maintenance. + +Bugfixes +-------- +- Do not wait for lazy signals when creating a SignalPanel. + This was causing long setup times in some applications. +- Call stop with success=True in the positioner widget to avoid causing + our own UnknownStatusError, which was then displayed to the user. + +Maintenance +----------- +- Add cleanup for background threads. +- Add replacement for functools.partial usage in methods as + this was preventing TyphosSuite from getting garbage collected. +- Removes custom designer widget plugin, + instead relying on PyDM's own mechanism +- Use pydm's data plugin entrypoint to include the sig and happi channels. +- Prevent TyphosStatusThread objects from being orphaned. + +Contributors +------------ +- klauer +- tangkong +- zllentz + + +v2.3.2 (2022-07-28) +=================== + +Description +----------- +This is a bugfix and maintenance release. + +Fixes +----- +- Fix various instances of clipping in the positioner widget. +- Show Python documentation when no web help is available. +- Fix issues with suite sidebar width. +- Lazy load all tools to improve performance. +- Fix the profiler to also profile class methods. +- Use cached paths for finding class templates. +- Properly handle various deprecations and deprecation warnings. +- Fix usage of deprecated methods in happi (optional dependency). + +Maintenance +----------- +- Log "unable to add device" without the traceback, which was previously unhelpful. +- Pin pyqt at 5.12 for test suite incompatibility in newer versions. +- Ensure that test.qss test suite artifact is cleaned up properly. +- Fix the broken test suite. +- Pin jinja2 at <3.1 in CI builds for sphinx <4.0.0 compatibility + +Contributors +------------ +- anleslac +- klauer +- zllentz + + +v2.3.1 (2022-05-02) +=================== + +Description +----------- +This is a small bugfix release. + +Fixes +----- +- Fix an issue where the configuration menu would be defunct for + custom template screens. + +Maintenance +----------- +- Add some additional documentation about sig:// and cli usage. +- Configure and satisfy the repository's own pre-commit checks. +- Update versioneer install to current latest. + +Contributors +------------ +- klauer +- zllentz + + +v2.3.0 (2022-03-31) +=================== + +Description +----------- +This is a small release with fixes and features that were implemented +last month. + +Features +-------- +- Add the option to hide displays in the suite at launch, + rather than automatically showing all of them. +- Allow the sig:// protocol to be used in typhos templates by + automatically registering all of a device's signals at launch. + +Fixes +----- +- Fix an issue where an assumption about the nature of EpicsSignal + object was breaking when using PytmcSignal objects from pcdsdevices. +- Make a workaround for a C++ wrapped exception that could happen + in specific orders of loading and unloading typhos alarm widgets. + + +v2.2.1 (2022-02-07) +=================== + +Description +----------- +This is a small bugfix release that was deployed as a hotfix +to avoid accidental moves. + +Fixes +----- +- Disable scroll wheel interaction with positioner combo boxes. + This created a situation where operators were accidentally + requesting moves while trying to scroll past the control box. + This was previously fixed for the typhos combo boxes found on + the various automatically generated panels in v1.1.0, but not + for the positioner combo boxes. + + +v2.2.0 (2021-11-30) +=================== + +Description +----------- +This is a feature and bugfix release to extend the customizability of +typhos suites and launcher scrips, to fix various issues in control +layer and enum handling, and to do some necessary CI maintenance. + +Enhancements / What's new +------------------------- +* Add suite options for layouts, display types, scrollbars, and + starting window size. These are all also available as CLI arguments, + with the intention of augmenting typhos suite launcher scripts. + Here are some examples: + + * ``--layout grid --cols 3``: lays out the device displays in a 3-column + grid + * ``--layout flow``: lays out the device displays in a grid that adjusts + dynamically as the window is resized. + * ``--display-type embed``: starts all device displays in their embedded + state + * ``--size 1000,1000``: sets a starting size of 1000 width, 1000 height for + the suite window. + + See `#450 `_ + +Fixes +----- +* Respect ophyd signal enum_strs and metadata updates. Previously, these were + ignored, but these can update during the lifetime of a screen and should be + used. (`#459 `_) +* Identify signals that use non-EPICS control layers and handle them + appropriately. Previously, these would be misidentified as EPICS signals + and handled using the ca:// PyDM plugin, which was not correct. + (`#463 `_) +* Fix an issue where get_native_methods could fail. This was not observed + in the field, but it broke the test suite. + (`#464 `_) + +Maintenance +----------- +* Fix various issues related to the test suite stability. + + +v2.1.0 (2021-10-18) +=================== + +Description +----------- +This is a minor feature release of typhos. + +Enhancements / What's new +------------------------- +* Added option to pop out documentation frame + (`#458 `_) + +Fixes +----- +* Fixed authorization headers on Typhos help widget redirect + (`#457 `_) + + * This allows for the latest Confluence to work with Personal + Access Tokens while navigating through the page + +Maintenance +----------- +* Reduced javascript log message spam from the web view widget + (part of `#457 `_) +* Reduced log message spam from variety metadata handling + (part of `#457 `_) +* Fixed compatibility with pyqtgraph v0.12.3 +* Web-related widgets are now in a new submodule `typhos.web`. + + +v2.0.0 (2021-08-05) +=================== + +Description +----------- +This is a feature update with backwards-incompatible changes, namely the +removal and relocation of the LCLS typhos templates. + +API Breaks +---------- +All device templates except for the ``PositionerBase`` template have been +moved from typhos to pcdsdevices, which is where their device classes +are defined. This will break LCLS environments that update typhos without +also updating pcdsdevices, but will not affect environments outside of LCLS. + +Enhancements / What's New +------------------------- +- Add the ``TyphosRelatedSuiteButton``, a ``QPushButton`` that will open a device's + typhos screen. This can be included in embedded widgets or placed on + traditional hand-crafted pydm screens as a quick way to open the typhos + expert screen. +- Add the typhos help widget, which is a new addition to the display switcher + that is found in all built-in typhos templates. Check out the ``?`` button! + See the docs for information on how to configure this. + The main features implemented here are: + + - View the class docstring from inside the typhos window + - Open site-specific web documentation in a browser + - Report bugs directly from the typhos screen + +- Expand the ``PositionerWidget`` with aesthetic updates and more features: + + - Show driver-specific error messages from the IOC + - Add a "clear error" button that can be linked to IOC-specific error + reset routines by adding a ``clear_error`` method to your positioner + class. This will also clear status errors returned from the positioner's + set routine from the display. + - Add a moving/done_moving indicator (for ``EpicsMotor``, uses the ``.MOVN`` field) + - Add an optional ``TyphosRelatedSuite`` button + - Allow the ``stop`` button to be removed if the ``stop`` method is missing or + otherwise raises an ``AttributeError`` on access + - Add an alarm indicator + +- Add the ``typhos.ui`` entry point. This allows a module to notify typhos that + it should check specified directories for custom typhos templates. To be + used by typhos, the entry point should load a ``str``, ``pathlib.Path``, or ``list`` + of such objects. +- Move the examples submodule into the ``typhos.examples`` submodule, so we can + launch the examples by way of e.g. ``typhos -m typhos.examples.positioner``. +- For the alarm indicator widgets, allow the pen width, pen color, and + pen style to be customized. + +Compatibility / Fixes +--------------------- +- Find a better fix for the issue where the positioner combobox widget would + put to the PV on startup and on IOC reboot + (see ``v1.1.0`` note about a hacky workaround). +- Fix the issue where the positioner combobox widget could not be used to + move to the last position selected. +- Fix an issue where a positioner status that was marked as failed immediately + would show as an unknown error, even if it had an associated exception + with useful error text. + +Docs / Testing +-------------- +- Add documentation for all features included in this update +- Add documentation for how to create custom ``typhos`` templates + + +v1.2.0 (2021-07-09) +=================== + +Description +----------- +This is a feature update intended for use in lucid, but it may also be useful +elsewhere. + +Enhancements / What's New +------------------------- +Add a handful of new widgets for indicating device alarm state. These will +change color based on the most severe alarm found among the device's signals. +Their shapes correlate with the available shapes of PyDMDrawingWidget: + +- TyphosAlarmCircle +- TyphosAlarmRectangle +- TyphosAlarmTriangle +- TyphosAlarmEllipse +- TyphosAlarmPolygon + +Compatibility / Fixes +--------------------- +- Add a sigint handler to avoid annoying behavior when closing with Ctrl-C on + macOS. +- Increase some timeouts to improve unit test consistency. + + +v1.1.6 (2021-04-05) +=================== + +Description +----------- +This is maintenance/compatibility release for pydm v1.11.0. + +Compatibility / Fixes +--------------------- +- Internal fixes regarding error handling and input sanitization. + Some subtle issues cropped up here in the update to pydm v1.11.0. +- Fix issue where the test suite would freeze when pydm displays + an exception to the user. + + +v1.1.5 (2020-04-02) +=================== + +Description +----------- +This is a maintenance release + +Compatibility / Fixes +--------------------- +- Fix an issue where certain data files were not included in the package + build. + + +v1.1.4 (2020-02-26) +=================== + +Description +----------- +This is a bugfix release + +Compatibility / Fixes +--------------------- +- Fix returning issue where certain devices could fail to load with a + "dictionary changed during iteration" error. +- Fix issue where the documentation was not building properly. + + +v1.1.3 (2020-02-10) +=================== + +Description +----------- +This is a minor screen inclusion release. + +Enhancements / What's New +------------------------- +- Add a screen for AT1K4. This, and similar screens, should be moved out of + typhos and into an LCLS-specific landing-zone, but this is not ready yet. + + +v1.1.2 (2020-12-22) +=================== + +Description +----------- +This is a minor bugfix release. + +Compatibility / Fixes +--------------------- +- Fix issue where ``SignalRO`` from ``ophyd`` was not showing as read-only. +- Update the AT2L0 screen to not have a redundant calculation dialog as per + request. + + +v1.1.1 (2020-08-19) +=================== + +Description +----------- +This is a bugfix release. Please use this instead of v1.1.0. + +Compatibility / Fixes +--------------------- +- Fix issue with ui files not being included in the manifest +- Fix issue with profiler failing on tests submodule + + +v1.1.0 (2020-08-18) +=================== + +Description +----------- +This is a big release with many fixes and features. + +Enhancements / What's New +------------------------- +- Make Typhos aware of variety metadata and assign appropriate widgets based + on the variety metadata assigned in pcdsdevices. +- Split templates into three categories: core, devices, and widgets. + Core templates are the main typhos display templates, e.g. detailed_tree. + Devices templates are templates tailored for specific device classes. + Widgets templates define special typhos widgets like tweakable, positioner, + etc. +- Add attenuator calculator screens. These may be moved to another repo in a + future release. +- Add information to loading widgets indicating timeout details. + +Compatibility / fixes +--------------------- +- Fix issue with comboboxes being set on mouse scroll. +- Allow loading classes from cli with numbers in the name. +- Fix issue with legacy codepath used in lightpath. +- Fix issue with widget UnboundLocalError. +- Hacky workaround for issue with newer versions of Python. +- Hacky workaround for issue where positioner widget puts on startup. +- Fix issue with unset _channel member. +- Fix issue with typhos creating and installing a tests package separate + from the main typhos package. + +Docs / Testing +-------------- +- Add variety testing IOC. +- Add doctr_versions_menu extension to properly render version menu. +- Fix issues with failing benchmark tests + + +v1.0.2 (2020-07-01) +=================== + +Description +----------- + +A bug fix and package maintenance release. + +Enhancements / What's New +------------------------- +- PositionerWidget moves set their timeouts based on expected + velocity and acceleration, rather than a flat 10 seconds. + +Compatibility / fixes +--------------------- +- Ensure that widgets with no layout or minimum size are still displayed. +- Update local conda recipe to match conda-forge. +- Update CI to used shared configurations. + + +v1.0.1 (2020-05-20) +=================== + +Description +----------- + +A bug fix release with a minor addition. + +Enhancements / What's New +------------------------- +- TyphosLoading now takes in a timeout value to switch the animation + with a text message stating that the operation timed-out after X + seconds. + + +Compatibility / fixes +--------------------- + +- Combobox widgets were appearing when switching or refreshing templates. + + +v1.0.0 (2020-05-18) +=================== + +Description +----------- + +A major new feature release with added views for complex devices and +simplified configurability. + +As planned, the deprecated import name ``typhon`` and the ``typhon`` +command-line tool have been removed. + +Enhancements / What's New +------------------------- + +- Panels: New ``TyphosCompositeSignalPanel``, which composes multiple + ``TyphosDisplay``\ s in a tree-like view. +- Benchmarking: new profiling tools accessible in the command-line + ``typhos`` tool, allowing for per-line profiling of standardized + devices. (``--benchmark``) +- Template discovery: templates are discovered based on screen macros + and class inheritance structure, with the fallback of built-in + templates. +- New command-line options for testing with mock devices + (``--fake-device``). +- Performance: Major performance improvements by way of background + threading of signal description determination, display path caching, + and connection status monitoring to reduce GUI thread blocking. +- Display: Adds a "display switcher" tool for easy access to different + screen types. +- Display: Adds a "configuration" button to displays. +- Filtering: Filter panel contents by kinds. +- Filtering: Filter panel contents by signal names. +- Setpoint history: a history of previous setpoints has been added to + the context menu in ``TyphosLineEdit``. +- Positioner widgets have been redesigned to be less magical and more fault- + tolerant. Adds designable properties that allow for specification of + attribute names. +- Anything that inherits from ``PositionerBase`` will have the template as an + option (``EpicsMotor``, ``PCDSMotorBase``, etc.) +- Reworked default templates to remove the ``miscellaneous`` panel. Omitted + signals may still be shown by way of panel context menus or configuration + menus. + +Compatibility / fixes +--------------------- + +- Python 3.8 is now being included in the test suite. +- Happi is now completely optional. +- Popped-out widgets such as plots will persist even when the parent + display is closed. +- Font sizes should be more consistent on various DPI displays. +- Module ``typhos.signal`` has been renamed to ``typhos.panel``. +- ``TyphosTimePlot`` no longer automatically adds signals to the plot. +- Removed internally-used ``typhos.utils.grab_kind``. +- OSX layout of ``TyphosSuite`` should be improved using the unified title and + toolbar. + +v0.7.0 (2020-03-09) +=================== + +- Fix docs deployment +- Add “loading in progress” gif +- Fix sorting of signals +- Automatically choose exponential format based on engineering units +- Fix lazy loading in ophyd 1.4 +- Save images of widgets when running tests +- Add a new “PopBar” which pops in the device tree in the suite +- Clean up the codebase - sort all imports + fix style +- Relocate SignalRO to a single spot + + +v0.6.0 (2020-01-09) +=================== + +Description +----------- + +This release is dedicated to the renaming of the package from ``Typhon`` +to ``Typhos``. The main reason for the renaming is a naming conflict at +PyPI that is now addressed. + +Compatibility +------------- + +This release is still compatible and will throw some DeprecationWarnings +when ``typhon`` is used. The only incompatible piece is for Qt +Stylesheets. You will need to add the ``typhos`` equivalents to your +custom stylesheets if you ever created one. + +**This is the first release with the backwards compatibility for typhon. +In two releases time it will be removed.** + + +v0.5.0 (2019-09-18) +=================== + +Description +----------- + +It was a long time since the latest release of ``Typhon``. It is time +for a new one. Next releases will have again the beautiful and +descriptive messages for enhancements, bug fixes and etc. + +What’s New +---------- + +A lot. + + +v0.2.1 (2018-09-28) +=================== + +Description +----------- + +This is a minor release of the ``Typhon`` library. No major features +were added, but instead the library was made more stable and utilitarian +for use in other programs. This includes making sure that any calls to a +signal’s values or metadata are capable of handling disconnections. It +also moves some of the methods that were hidden in larger classes or +functions into smaller, more useful methods. + +Enhancements +~~~~~~~~~~~~ + +- ``SignalPlugin`` now transmits all the metadata that is guaranteed to + be present from the base ``Signal`` object. This includes + ``enum_strs``, ``precision``, and ``units`` + (`#92 `__) +- ``DeviceDisplay`` now has an optional argument ``children``. This + makes it possible to ignore a ``Device`` components when creating the + display (`#96 `__) +- The following utility functions have been created to ensure that a + uniform approach is taken for\ ``Device`` introspection: + ``is_signal_ro``, ``grab_hints`` + (`#98 `__) + +Maintenance +~~~~~~~~~~~ + +- Catch exceptions when requesting information from a ``Signal`` in + case of disconnection, e.t.c + (`#91 `__, + `#92 `__) +- The library now imports entirely from the ``qtpy`` compatibility + layer (`#94 `__) + +Deprecations +~~~~~~~~~~~~ + +- The ``title`` command in ``SignalPanel`` was no longer used. It is + still accepted in this release, but will dropped in the next major + release (`#90 `__) + + +v0.2.0 (2018-06-27) +=================== + +Description +----------- + +This ``Typhon`` release marks the transition from prototype to a stable +library. There was a variety of API breaks and deprecations after +``v0.1.0`` as many of the names and functions were not future-proof. + +Enhancements +~~~~~~~~~~~~ + +- ``Typhon`` is now available on the ``pcds-tag`` Anaconda channel + (`#45 `__) +- ``Typhon`` now installs a special data plugin for ``PyDM`` called + ``SignalPlugin``. This uses the generic ``ophyd.Signal`` methods to + communicate information to PyDM widgets. + (`#63 `__) +- ``Typhon`` now supports two different stylesheets a “light” and + “dark” mode. These are not activated by default, but instead can be + accessed via ``use_stylesheet`` function + (`#61 `__, + `#89 `__) +- There is now a sidebar to the ``DeviceDisplay`` that makes adding + devices and tools easier. The ``add_subdisplay`` function still works + but it is preferable to use the more specific ``add_tool`` and + ``add_subdevice``. + (`#61 `__) +- ``Typhon`` will automaticaly create a ``PyDMLogDisplay`` to show the + output of the ``logging.Logger`` object attached to each + ``ophyd.Device`` + (`#70 `__) +- ``Typhon`` now creates a ``PyDMTimePlot`` with the “hinted” + attributes of the Device. This can be configured at runtime to have + fewer or more signals + (`#73 `__) + +API Changes +~~~~~~~~~~~ + +- All of the ``Panel`` objects have been moved to different files. + ``SignalPanel`` now resides in ``typhon.signal`` while the base + ``Panel`` that is no longer used to display signals is in the generic + ``typhon.widgets`` renamed as ``TogglePanel`` + (`#50 `__) + +Deprecations +~~~~~~~~~~~~ + +- ``RotatingImage`` has been removed as it is no longer used by the + library (`#58 `__) +- ``ComponentButton`` has been removed as it is no longer used by the + library(`#58 `__) +- The base ``DeviceDisplay`` no longer has a plot. The + ``add_pv_to_plot`` function has been completely removed. + (`#58 `__) + +Dependencies +~~~~~~~~~~~~ + +- ``TyphonDisplay`` requires ``ophyd >= 1.2.0``. The ``PyDMLogDisplay`` + tool is attached to the ``Device.log`` that is now present on all + ``ophyd`` devices. + (`#53 `__) +- ``pydm >= 1.2.0`` due to various bug fixes and widget additions + (`#63 `__) +- ``QDarkStyleSheet`` is now included in the recipe to provide dark + stylesheet support. + (`#89 `__) + +Bug Fixes +~~~~~~~~~ + +- ``SignalPanel`` previously did not account for the fact that ``read`` + and ``configuration`` attributes could be devices themselves + (`#42 `__) +- ``SignalPanel`` no longer assumes that all signals are + ``EpicsSignal`` objects + (`#71 `__) + + +v0.1.0 (2017-12-15) +=================== + +The initial release of Typhon. This serves as a proof of concept for the +automation of PyDM screen building as informed by the structure of an +Ophyd Device. + +Features +-------- + +- Generate a full ``DeviceDisplay`` with all of the device signals and + sub-devices available +- Include methods from the ophyd Device in the User Interface, + automatically parse the arguments to make a widget representation of + the function +- Include ``png`` images associated with devices and sub-devices diff --git a/v2.4.1/_sources/save.rst.txt b/v2.4.1/_sources/save.rst.txt new file mode 100644 index 000000000..585e341dc --- /dev/null +++ b/v2.4.1/_sources/save.rst.txt @@ -0,0 +1,49 @@ +################## +Saving and Loading +################## +:class:`.TyphosSuite` objects can be stored for later use. The devices that +were loaded into the suite via :meth:`.TyphosSuite.add_device` will be added +once again assuming that they are stored in a ``happi`` database. + +.. automethod:: typhos.TyphosSuite.save + :noindex: + +There are two major ways to use this created file: + +1. Execute the Python file from the command line. This will route the call + through the standard :mod:`typhos.cli` meaning all options + described there are also available. + +.. code:: bash + + $ python saved_suite.py + + +2. The ``create_suite`` method generated in the saved file can be used to + re-create the :class:`.TyphosSuite` in an already running Python process. + Typhos provides the :func:`.load_suite` function to import the provided + Python file and execute the stored ``create_suite`` method. This is useful + if you want to use the file to embed a saved :class:`.TyphosSuite` inside + another PyQt window for instance, or load multiple suites at once. + + +.. code:: python + + from qtpy.QtWidgets import QApplication + from typhos import load_suite + + app = QApplication([]) + saved_suite = load_suite('saved_suite.py') + + saved_suite.show() + app.exec_() + + +.. note:: + + The saved file only stores a reference to the devices loaded into the + ``TyphosSuite`` by name. It is assumed that these devices will be available + under the same name via the configured ``happi`` database when + ``load_suite`` is called. If the device has a different name in the database + or you have configured a different ``happi`` database to be used your + devices will not be loaded properly. diff --git a/v2.4.1/_sources/templates.rst.txt b/v2.4.1/_sources/templates.rst.txt new file mode 100644 index 000000000..4660cb774 --- /dev/null +++ b/v2.4.1/_sources/templates.rst.txt @@ -0,0 +1,77 @@ +================ +Custom Templates +================ +Typhos ships with a handful of built-in templates. You can see these when you +browse the ``typhos/ui/core`` and ``typhos/ui/devices`` directories. + +.. note:: + + This repo originally had a large number of LCLS-specific device templates. + These have been moved to pcdsdevices.ui. + + +You can define your own templates outside of typhos to customize the behavior +of the module when launching screens. These can be done generically, to +replace the default templates, or per-class, to replace the templates in +specific cases. + + +Template Creation +================= +Templates are ``.ui`` files created in qt designer. These are largely just +normal pydm displays, with extra macro substitutions. See the +:ref:`pydm tutorial ` +for more guidance on using the designer. + + +Template Substitutions +---------------------- +All the information found in ``happi`` will be loaded as a pydm macro into the +template. It does this by checking for attributes on the ``device.md`` +namespace object. + +If no ``device.md`` object is found, we will still include ``device.name`` +as the ``name`` macro and ``device.prefix`` as the ``prefix`` macro. + +The upshot of this is that you can include ``${name}``, ``${prefix}``, and +other keys from the happi database in your template and they will be +filled in from the device database on load. + + +Template Filenames +------------------ +To replace a default template, create a template with exactly the same name. + +To create a template for a class, name it based on the class name +and the template type, e.g.: + +- PositionerBase.embedded.ui +- PositionerBase.detailed.ui +- PositionerBase.engineering.ui + +Note that we'll check an object class's mro() when deciding which template to +use- this is why all PositionerBase subclasses use the built-in +PositionerBase.detailed.ui template by default. + +In this way you can create one template for a set of related classes. + + +Template Discovery +------------------ +There are currently three places that typhos checks for templates. +In order of priority: + +1. Check the paths defined by the ``PYDM_DISPLAYS_PATH`` environment variable. +2. Check any paths defined by the ``typhos.ui`` package entry point. +3. Check the built-in paths (core and devices) + +With that in mind, there are two recommended workflows for running typhos with +custom templates: + +1. Create a repository to store your screens, and set ``PYDM_DISPLAYS_PATH`` + to point to your repository clone's screens directory. This path works + exactly like any other ``PATH`` variable in linux. +2. Create a module that defines the ``typhos.ui`` entry point. This entry + point is expecting to find a ``str``, ``pathlib.Path``, or ``list`` of + such objects at your entry point. One such example of how to do this can + be found `here `_ diff --git a/v2.4.1/_sources/tools.rst.txt b/v2.4.1/_sources/tools.rst.txt new file mode 100644 index 000000000..84a55a3c8 --- /dev/null +++ b/v2.4.1/_sources/tools.rst.txt @@ -0,0 +1,31 @@ +############### +Supported Tools +############### + +In experimental environments there are a variety of external tools and +applications that are critical to day to day operation. Typhos hopes to +integrate many of these services into the :class:`.TyphosDeviceDisplay` for +ease of operation. This approach has two advantages; the first is that getting +to helpful tools requires fewer clicks and therefore less time, secondly, if we +assume that the context in which they want to use the external tool includes +this device, we can pre-populate many of the fields for them. + +All of the tools in ``typhos`` follow a basic pattern. Each one can be +instantiated as a standalone widget with no ``ophyd`` or ``Device`` required. The +intention is that these tools could be used in a separate application where the +underlying information is in a different form. However, in order to make these +objects easier to interface with ``ophyd`` objects the methods +:meth:`.TyphosTool.from_device` and :meth:`.TyphosTool.add_device` are +available. These automatically populate fields according to device structures. + +Tool Classes +============ + +.. currentmodule:: typhos.tools + +.. autosummary:: + :toctree: generated + + TyphosConsole + TyphosLogDisplay + TyphosTimePlot diff --git a/v2.4.1/_sources/utils.rst.txt b/v2.4.1/_sources/utils.rst.txt new file mode 100644 index 000000000..163e9c94c --- /dev/null +++ b/v2.4.1/_sources/utils.rst.txt @@ -0,0 +1,37 @@ +################# +Utility Functions +################# + +.. automodule:: typhos.utils + :members: + + +############### +Cache Utilities +############### + +Path caching +============ + +.. autofunction:: typhos.cache.get_global_display_path_cache + +.. autoclass:: typhos.cache._GlobalDisplayPathCache + :members: + + +Ophyd Object Description Caching +================================ + +.. autofunction:: typhos.cache.get_global_describe_cache + +.. autoclass:: typhos.cache._GlobalDescribeCache + :members: + + +Ophyd Object to Widget Type Cache +================================= + +.. autofunction:: typhos.cache.get_global_widget_type_cache + +.. autoclass:: typhos.cache._GlobalWidgetTypeCache + :members: diff --git a/v2.4.1/_sources/widgets.rst.txt b/v2.4.1/_sources/widgets.rst.txt new file mode 100644 index 000000000..3887df7aa --- /dev/null +++ b/v2.4.1/_sources/widgets.rst.txt @@ -0,0 +1,130 @@ +======= +Widgets +======= +Typhos uses a few custom widgets to create a clean and concise user interface. +While most users should not be interacting with these directly, there may be a +need if a user opts to create their display by hand instead of automatically +generating one. + + +Determining widget types +======================== + +If you would just like a widget for an ``ophyd.Signal``, there +is a function available: + +.. autofunction:: typhos.widgets.create_signal_widget + +.. autoclass:: typhos.widgets.SignalWidgetInfo + :members: + +.. autofunction:: typhos.widgets.widget_type_from_description + +.. autofunction:: typhos.widgets.determine_widget_type + + +Panels +====== +One of the major design principles of Typhos is that users should be able to +see what they need and hide one they don't. Thefore, many of the widget +implementations are placed in "Panels" these consist of QPushButton header that +hides and shows the contents. Each variation in Typhos is documented below. + + +Basic Signal Panels +------------------- + +.. autoclass:: typhos.panel.SignalPanel + :members: + +.. autoclass:: typhos.TyphosSignalPanel + :members: + + +Composite Signal Panels +----------------------- + +.. autoclass:: typhos.panel.CompositeSignalPanel + :members: + +.. autoclass:: typhos.TyphosCompositeSignalPanel + :members: + + +TyphosPositionerWidget +====================== + +.. autoclass:: typhos.TyphosPositionerWidget + :members: + + +Functions and Methods +===================== + +.. autoclass:: typhos.func.FunctionPanel + :members: + +.. autoclass:: typhos.TyphosMethodButton + :members: + + +Miscellaneous +============= + +.. autoclass:: typhos.widgets.ClickableBitIndicator + :members: + +.. autoclass:: typhos.widgets.ImageDialogButton + :members: + +.. autoclass:: typhos.widgets.SignalDialogButton + :members: + +.. autoclass:: typhos.widgets.SubDisplay + :members: + +.. autoclass:: typhos.widgets.TyphosArrayTable + :members: + +.. autoclass:: typhos.widgets.TyphosByteIndicator + :members: + +.. autoclass:: typhos.widgets.TyphosByteSetpoint + :members: + +.. autoclass:: typhos.widgets.TyphosComboBox + :members: + +.. autoclass:: typhos.widgets.TyphosCommandButton + :members: + +.. autoclass:: typhos.widgets.TyphosCommandEnumButton + :members: + +.. autoclass:: typhos.widgets.TyphosLabel + :members: + +.. autoclass:: typhos.widgets.TyphosLineEdit + :members: + +.. autoclass:: typhos.widgets.TyphosScalarRange + :members: + +.. autoclass:: typhos.widgets.TyphosSidebarItem + :members: + +.. autoclass:: typhos.tweakable.TyphosTweakable + :members: + +.. autoclass:: typhos.textedit.TyphosTextEdit + :members: + +.. autoclass:: typhos.widgets.WaveformDialogButton + :members: + + +Designer +======== + +.. autoclass:: typhos.widgets.TyphosDesignerMixin + :members: diff --git a/v2.4.1/_static/_sphinx_javascript_frameworks_compat.js b/v2.4.1/_static/_sphinx_javascript_frameworks_compat.js new file mode 100644 index 000000000..81415803e --- /dev/null +++ b/v2.4.1/_static/_sphinx_javascript_frameworks_compat.js @@ -0,0 +1,123 @@ +/* Compatability shim for jQuery and underscores.js. + * + * Copyright Sphinx contributors + * Released under the two clause BSD licence + */ + +/** + * small helper function to urldecode strings + * + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL + */ +jQuery.urldecode = function(x) { + if (!x) { + return x + } + return decodeURIComponent(x.replace(/\+/g, ' ')); +}; + +/** + * small helper function to urlencode strings + */ +jQuery.urlencode = encodeURIComponent; + +/** + * This function returns the parsed url parameters of the + * current request. Multiple values per key are supported, + * it will always return arrays of strings for the value parts. + */ +jQuery.getQueryParameters = function(s) { + if (typeof s === 'undefined') + s = document.location.search; + var parts = s.substr(s.indexOf('?') + 1).split('&'); + var result = {}; + for (var i = 0; i < parts.length; i++) { + var tmp = parts[i].split('=', 2); + var key = jQuery.urldecode(tmp[0]); + var value = jQuery.urldecode(tmp[1]); + if (key in result) + result[key].push(value); + else + result[key] = [value]; + } + return result; +}; + +/** + * highlight a given string on a jquery object by wrapping it in + * span elements with the given class name. + */ +jQuery.fn.highlightText = function(text, className) { + function highlight(node, addItems) { + if (node.nodeType === 3) { + var val = node.nodeValue; + var pos = val.toLowerCase().indexOf(text); + if (pos >= 0 && + !jQuery(node.parentNode).hasClass(className) && + !jQuery(node.parentNode).hasClass("nohighlight")) { + var span; + var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.className = className; + } + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + node.parentNode.insertBefore(span, node.parentNode.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling)); + node.nodeValue = val.substr(0, pos); + if (isInSVG) { + var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); + var bbox = node.parentElement.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute('class', className); + addItems.push({ + "parent": node.parentNode, + "target": rect}); + } + } + } + else if (!jQuery(node).is("button, select, textarea")) { + jQuery.each(node.childNodes, function() { + highlight(this, addItems); + }); + } + } + var addItems = []; + var result = this.each(function() { + highlight(this, addItems); + }); + for (var i = 0; i < addItems.length; ++i) { + jQuery(addItems[i].parent).before(addItems[i].target); + } + return result; +}; + +/* + * backward compatibility for jQuery.browser + * This will be supported until firefox bug is fixed. + */ +if (!jQuery.browser) { + jQuery.uaMatch = function(ua) { + ua = ua.toLowerCase(); + + var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || + /(webkit)[ \/]([\w.]+)/.exec(ua) || + /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || + /(msie) ([\w.]+)/.exec(ua) || + ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || + []; + + return { + browser: match[ 1 ] || "", + version: match[ 2 ] || "0" + }; + }; + jQuery.browser = {}; + jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; +} diff --git a/v2.4.1/_static/basic.css b/v2.4.1/_static/basic.css new file mode 100644 index 000000000..7577acb1a --- /dev/null +++ b/v2.4.1/_static/basic.css @@ -0,0 +1,903 @@ +/* + * basic.css + * ~~~~~~~~~ + * + * Sphinx stylesheet -- basic theme. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ + +/* -- main layout ----------------------------------------------------------- */ + +div.clearer { + clear: both; +} + +div.section::after { + display: block; + content: ''; + clear: left; +} + +/* -- relbar ---------------------------------------------------------------- */ + +div.related { + width: 100%; + font-size: 90%; +} + +div.related h3 { + display: none; +} + +div.related ul { + margin: 0; + padding: 0 0 0 10px; + list-style: none; +} + +div.related li { + display: inline; +} + +div.related li.right { + float: right; + margin-right: 5px; +} + +/* -- sidebar --------------------------------------------------------------- */ + +div.sphinxsidebarwrapper { + padding: 10px 5px 0 10px; +} + +div.sphinxsidebar { + float: left; + width: 230px; + margin-left: -100%; + font-size: 90%; + word-wrap: break-word; + overflow-wrap : break-word; +} + +div.sphinxsidebar ul { + list-style: none; +} + +div.sphinxsidebar ul ul, +div.sphinxsidebar ul.want-points { + margin-left: 20px; + list-style: square; +} + +div.sphinxsidebar ul ul { + margin-top: 0; + margin-bottom: 0; +} + +div.sphinxsidebar form { + margin-top: 10px; +} + +div.sphinxsidebar input { + border: 1px solid #98dbcc; + font-family: sans-serif; + font-size: 1em; +} + +div.sphinxsidebar #searchbox form.search { + overflow: hidden; +} + +div.sphinxsidebar #searchbox input[type="text"] { + float: left; + width: 80%; + padding: 0.25em; + box-sizing: border-box; +} + +div.sphinxsidebar #searchbox input[type="submit"] { + float: left; + width: 20%; + border-left: none; + padding: 0.25em; + box-sizing: border-box; +} + + +img { + border: 0; + max-width: 100%; +} + +/* -- search page ----------------------------------------------------------- */ + +ul.search { + margin: 10px 0 0 20px; + padding: 0; +} + +ul.search li { + padding: 5px 0 5px 20px; + background-image: url(file.png); + background-repeat: no-repeat; + background-position: 0 7px; +} + +ul.search li a { + font-weight: bold; +} + +ul.search li p.context { + color: #888; + margin: 2px 0 0 30px; + text-align: left; +} + +ul.keywordmatches li.goodmatch a { + font-weight: bold; +} + +/* -- index page ------------------------------------------------------------ */ + +table.contentstable { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +table.contentstable p.biglink { + line-height: 150%; +} + +a.biglink { + font-size: 1.3em; +} + +span.linkdescr { + font-style: italic; + padding-top: 5px; + font-size: 90%; +} + +/* -- general index --------------------------------------------------------- */ + +table.indextable { + width: 100%; +} + +table.indextable td { + text-align: left; + vertical-align: top; +} + +table.indextable ul { + margin-top: 0; + margin-bottom: 0; + list-style-type: none; +} + +table.indextable > tbody > tr > td > ul { + padding-left: 0em; +} + +table.indextable tr.pcap { + height: 10px; +} + +table.indextable tr.cap { + margin-top: 10px; + background-color: #f2f2f2; +} + +img.toggler { + margin-right: 3px; + margin-top: 3px; + cursor: pointer; +} + +div.modindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +div.genindex-jumpbox { + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + margin: 1em 0 1em 0; + padding: 0.4em; +} + +/* -- domain module index --------------------------------------------------- */ + +table.modindextable td { + padding: 2px; + border-collapse: collapse; +} + +/* -- general body styles --------------------------------------------------- */ + +div.body { + min-width: 360px; + max-width: 800px; +} + +div.body p, div.body dd, div.body li, div.body blockquote { + -moz-hyphens: auto; + -ms-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; +} + +a.headerlink { + visibility: hidden; +} + +h1:hover > a.headerlink, +h2:hover > a.headerlink, +h3:hover > a.headerlink, +h4:hover > a.headerlink, +h5:hover > a.headerlink, +h6:hover > a.headerlink, +dt:hover > a.headerlink, +caption:hover > a.headerlink, +p.caption:hover > a.headerlink, +div.code-block-caption:hover > a.headerlink { + visibility: visible; +} + +div.body p.caption { + text-align: inherit; +} + +div.body td { + text-align: left; +} + +.first { + margin-top: 0 !important; +} + +p.rubric { + margin-top: 30px; + font-weight: bold; +} + +img.align-left, figure.align-left, .figure.align-left, object.align-left { + clear: left; + float: left; + margin-right: 1em; +} + +img.align-right, figure.align-right, .figure.align-right, object.align-right { + clear: right; + float: right; + margin-left: 1em; +} + +img.align-center, figure.align-center, .figure.align-center, object.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +img.align-default, figure.align-default, .figure.align-default { + display: block; + margin-left: auto; + margin-right: auto; +} + +.align-left { + text-align: left; +} + +.align-center { + text-align: center; +} + +.align-default { + text-align: center; +} + +.align-right { + text-align: right; +} + +/* -- sidebars -------------------------------------------------------------- */ + +div.sidebar, +aside.sidebar { + margin: 0 0 0.5em 1em; + border: 1px solid #ddb; + padding: 7px; + background-color: #ffe; + width: 40%; + float: right; + clear: right; + overflow-x: auto; +} + +p.sidebar-title { + font-weight: bold; +} + +nav.contents, +aside.topic, +div.admonition, div.topic, blockquote { + clear: left; +} + +/* -- topics ---------------------------------------------------------------- */ + +nav.contents, +aside.topic, +div.topic { + border: 1px solid #ccc; + padding: 7px; + margin: 10px 0 10px 0; +} + +p.topic-title { + font-size: 1.1em; + font-weight: bold; + margin-top: 10px; +} + +/* -- admonitions ----------------------------------------------------------- */ + +div.admonition { + margin-top: 10px; + margin-bottom: 10px; + padding: 7px; +} + +div.admonition dt { + font-weight: bold; +} + +p.admonition-title { + margin: 0px 10px 5px 0px; + font-weight: bold; +} + +div.body p.centered { + text-align: center; + margin-top: 25px; +} + +/* -- content of sidebars/topics/admonitions -------------------------------- */ + +div.sidebar > :last-child, +aside.sidebar > :last-child, +nav.contents > :last-child, +aside.topic > :last-child, +div.topic > :last-child, +div.admonition > :last-child { + margin-bottom: 0; +} + +div.sidebar::after, +aside.sidebar::after, +nav.contents::after, +aside.topic::after, +div.topic::after, +div.admonition::after, +blockquote::after { + display: block; + content: ''; + clear: both; +} + +/* -- tables ---------------------------------------------------------------- */ + +table.docutils { + margin-top: 10px; + margin-bottom: 10px; + border: 0; + border-collapse: collapse; +} + +table.align-center { + margin-left: auto; + margin-right: auto; +} + +table.align-default { + margin-left: auto; + margin-right: auto; +} + +table caption span.caption-number { + font-style: italic; +} + +table caption span.caption-text { +} + +table.docutils td, table.docutils th { + padding: 1px 8px 1px 5px; + border-top: 0; + border-left: 0; + border-right: 0; + border-bottom: 1px solid #aaa; +} + +th { + text-align: left; + padding-right: 5px; +} + +table.citation { + border-left: solid 1px gray; + margin-left: 1px; +} + +table.citation td { + border-bottom: none; +} + +th > :first-child, +td > :first-child { + margin-top: 0px; +} + +th > :last-child, +td > :last-child { + margin-bottom: 0px; +} + +/* -- figures --------------------------------------------------------------- */ + +div.figure, figure { + margin: 0.5em; + padding: 0.5em; +} + +div.figure p.caption, figcaption { + padding: 0.3em; +} + +div.figure p.caption span.caption-number, +figcaption span.caption-number { + font-style: italic; +} + +div.figure p.caption span.caption-text, +figcaption span.caption-text { +} + +/* -- field list styles ----------------------------------------------------- */ + +table.field-list td, table.field-list th { + border: 0 !important; +} + +.field-list ul { + margin: 0; + padding-left: 1em; +} + +.field-list p { + margin: 0; +} + +.field-name { + -moz-hyphens: manual; + -ms-hyphens: manual; + -webkit-hyphens: manual; + hyphens: manual; +} + +/* -- hlist styles ---------------------------------------------------------- */ + +table.hlist { + margin: 1em 0; +} + +table.hlist td { + vertical-align: top; +} + +/* -- object description styles --------------------------------------------- */ + +.sig { + font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace; +} + +.sig-name, code.descname { + background-color: transparent; + font-weight: bold; +} + +.sig-name { + font-size: 1.1em; +} + +code.descname { + font-size: 1.2em; +} + +.sig-prename, code.descclassname { + background-color: transparent; +} + +.optional { + font-size: 1.3em; +} + +.sig-paren { + font-size: larger; +} + +.sig-param.n { + font-style: italic; +} + +/* C++ specific styling */ + +.sig-inline.c-texpr, +.sig-inline.cpp-texpr { + font-family: unset; +} + +.sig.c .k, .sig.c .kt, +.sig.cpp .k, .sig.cpp .kt { + color: #0033B3; +} + +.sig.c .m, +.sig.cpp .m { + color: #1750EB; +} + +.sig.c .s, .sig.c .sc, +.sig.cpp .s, .sig.cpp .sc { + color: #067D17; +} + + +/* -- other body styles ----------------------------------------------------- */ + +ol.arabic { + list-style: decimal; +} + +ol.loweralpha { + list-style: lower-alpha; +} + +ol.upperalpha { + list-style: upper-alpha; +} + +ol.lowerroman { + list-style: lower-roman; +} + +ol.upperroman { + list-style: upper-roman; +} + +:not(li) > ol > li:first-child > :first-child, +:not(li) > ul > li:first-child > :first-child { + margin-top: 0px; +} + +:not(li) > ol > li:last-child > :last-child, +:not(li) > ul > li:last-child > :last-child { + margin-bottom: 0px; +} + +ol.simple ol p, +ol.simple ul p, +ul.simple ol p, +ul.simple ul p { + margin-top: 0; +} + +ol.simple > li:not(:first-child) > p, +ul.simple > li:not(:first-child) > p { + margin-top: 0; +} + +ol.simple p, +ul.simple p { + margin-bottom: 0; +} + +aside.footnote > span, +div.citation > span { + float: left; +} +aside.footnote > span:last-of-type, +div.citation > span:last-of-type { + padding-right: 0.5em; +} +aside.footnote > p { + margin-left: 2em; +} +div.citation > p { + margin-left: 4em; +} +aside.footnote > p:last-of-type, +div.citation > p:last-of-type { + margin-bottom: 0em; +} +aside.footnote > p:last-of-type:after, +div.citation > p:last-of-type:after { + content: ""; + clear: both; +} + +dl.field-list { + display: grid; + grid-template-columns: fit-content(30%) auto; +} + +dl.field-list > dt { + font-weight: bold; + word-break: break-word; + padding-left: 0.5em; + padding-right: 5px; +} + +dl.field-list > dd { + padding-left: 0.5em; + margin-top: 0em; + margin-left: 0em; + margin-bottom: 0em; +} + +dl { + margin-bottom: 15px; +} + +dd > :first-child { + margin-top: 0px; +} + +dd ul, dd table { + margin-bottom: 10px; +} + +dd { + margin-top: 3px; + margin-bottom: 10px; + margin-left: 30px; +} + +dl > dd:last-child, +dl > dd:last-child > :last-child { + margin-bottom: 0; +} + +dt:target, span.highlighted { + background-color: #fbe54e; +} + +rect.highlighted { + fill: #fbe54e; +} + +dl.glossary dt { + font-weight: bold; + font-size: 1.1em; +} + +.versionmodified { + font-style: italic; +} + +.system-message { + background-color: #fda; + padding: 5px; + border: 3px solid red; +} + +.footnote:target { + background-color: #ffa; +} + +.line-block { + display: block; + margin-top: 1em; + margin-bottom: 1em; +} + +.line-block .line-block { + margin-top: 0; + margin-bottom: 0; + margin-left: 1.5em; +} + +.guilabel, .menuselection { + font-family: sans-serif; +} + +.accelerator { + text-decoration: underline; +} + +.classifier { + font-style: oblique; +} + +.classifier:before { + font-style: normal; + margin: 0 0.5em; + content: ":"; + display: inline-block; +} + +abbr, acronym { + border-bottom: dotted 1px; + cursor: help; +} + +/* -- code displays --------------------------------------------------------- */ + +pre { + overflow: auto; + overflow-y: hidden; /* fixes display issues on Chrome browsers */ +} + +pre, div[class*="highlight-"] { + clear: both; +} + +span.pre { + -moz-hyphens: none; + -ms-hyphens: none; + -webkit-hyphens: none; + hyphens: none; + white-space: nowrap; +} + +div[class*="highlight-"] { + margin: 1em 0; +} + +td.linenos pre { + border: 0; + background-color: transparent; + color: #aaa; +} + +table.highlighttable { + display: block; +} + +table.highlighttable tbody { + display: block; +} + +table.highlighttable tr { + display: flex; +} + +table.highlighttable td { + margin: 0; + padding: 0; +} + +table.highlighttable td.linenos { + padding-right: 0.5em; +} + +table.highlighttable td.code { + flex: 1; + overflow: hidden; +} + +.highlight .hll { + display: block; +} + +div.highlight pre, +table.highlighttable pre { + margin: 0; +} + +div.code-block-caption + div { + margin-top: 0; +} + +div.code-block-caption { + margin-top: 1em; + padding: 2px 5px; + font-size: small; +} + +div.code-block-caption code { + background-color: transparent; +} + +table.highlighttable td.linenos, +span.linenos, +div.highlight span.gp { /* gp: Generic.Prompt */ + user-select: none; + -webkit-user-select: text; /* Safari fallback only */ + -webkit-user-select: none; /* Chrome/Safari */ + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* IE10+ */ +} + +div.code-block-caption span.caption-number { + padding: 0.1em 0.3em; + font-style: italic; +} + +div.code-block-caption span.caption-text { +} + +div.literal-block-wrapper { + margin: 1em 0; +} + +code.xref, a code { + background-color: transparent; + font-weight: bold; +} + +h1 code, h2 code, h3 code, h4 code, h5 code, h6 code { + background-color: transparent; +} + +.viewcode-link { + float: right; +} + +.viewcode-back { + float: right; + font-family: sans-serif; +} + +div.viewcode-block:target { + margin: -1px -10px; + padding: 0 10px; +} + +/* -- math display ---------------------------------------------------------- */ + +img.math { + vertical-align: middle; +} + +div.body div.math p { + text-align: center; +} + +span.eqno { + float: right; +} + +span.eqno a.headerlink { + position: absolute; + z-index: 1; +} + +div.math:hover a.headerlink { + visibility: visible; +} + +/* -- printout stylesheet --------------------------------------------------- */ + +@media print { + div.document, + div.documentwrapper, + div.bodywrapper { + margin: 0 !important; + width: 100%; + } + + div.sphinxsidebar, + div.related, + div.footer, + #top-link { + display: none; + } +} \ No newline at end of file diff --git a/v2.4.1/_static/css/badge_only.css b/v2.4.1/_static/css/badge_only.css new file mode 100644 index 000000000..c718cee44 --- /dev/null +++ b/v2.4.1/_static/css/badge_only.css @@ -0,0 +1 @@ +.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}@font-face{font-family:FontAwesome;font-style:normal;font-weight:400;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#FontAwesome) format("svg")}.fa:before{font-family:FontAwesome;font-style:normal;font-weight:400;line-height:1}.fa:before,a .fa{text-decoration:inherit}.fa:before,a .fa,li .fa{display:inline-block}li .fa-large:before{width:1.875em}ul.fas{list-style-type:none;margin-left:2em;text-indent:-.8em}ul.fas li .fa{width:.8em}ul.fas li .fa-large:before{vertical-align:baseline}.fa-book:before,.icon-book:before{content:"\f02d"}.fa-caret-down:before,.icon-caret-down:before{content:"\f0d7"}.fa-caret-up:before,.icon-caret-up:before{content:"\f0d8"}.fa-caret-left:before,.icon-caret-left:before{content:"\f0d9"}.fa-caret-right:before,.icon-caret-right:before{content:"\f0da"}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60}.rst-versions .rst-current-version:after{clear:both;content:"";display:block}.rst-versions .rst-current-version .fa{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}} \ No newline at end of file diff --git a/v2.4.1/_static/css/fonts/Roboto-Slab-Bold.woff b/v2.4.1/_static/css/fonts/Roboto-Slab-Bold.woff new file mode 100644 index 000000000..6cb600001 Binary files /dev/null and b/v2.4.1/_static/css/fonts/Roboto-Slab-Bold.woff differ diff --git a/v2.4.1/_static/css/fonts/Roboto-Slab-Bold.woff2 b/v2.4.1/_static/css/fonts/Roboto-Slab-Bold.woff2 new file mode 100644 index 000000000..7059e2314 Binary files /dev/null and b/v2.4.1/_static/css/fonts/Roboto-Slab-Bold.woff2 differ diff --git a/v2.4.1/_static/css/fonts/Roboto-Slab-Regular.woff b/v2.4.1/_static/css/fonts/Roboto-Slab-Regular.woff new file mode 100644 index 000000000..f815f63f9 Binary files /dev/null and b/v2.4.1/_static/css/fonts/Roboto-Slab-Regular.woff differ diff --git a/v2.4.1/_static/css/fonts/Roboto-Slab-Regular.woff2 b/v2.4.1/_static/css/fonts/Roboto-Slab-Regular.woff2 new file mode 100644 index 000000000..f2c76e5bd Binary files /dev/null and b/v2.4.1/_static/css/fonts/Roboto-Slab-Regular.woff2 differ diff --git a/v2.4.1/_static/css/fonts/fontawesome-webfont.eot b/v2.4.1/_static/css/fonts/fontawesome-webfont.eot new file mode 100644 index 000000000..e9f60ca95 Binary files /dev/null and b/v2.4.1/_static/css/fonts/fontawesome-webfont.eot differ diff --git a/v2.4.1/_static/css/fonts/fontawesome-webfont.svg b/v2.4.1/_static/css/fonts/fontawesome-webfont.svg new file mode 100644 index 000000000..855c845e5 --- /dev/null +++ b/v2.4.1/_static/css/fonts/fontawesome-webfont.svg @@ -0,0 +1,2671 @@ + + + + +Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + By ,,, +Copyright Dave Gandy 2016. All rights reserved. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/v2.4.1/_static/css/fonts/fontawesome-webfont.ttf b/v2.4.1/_static/css/fonts/fontawesome-webfont.ttf new file mode 100644 index 000000000..35acda2fa Binary files /dev/null and b/v2.4.1/_static/css/fonts/fontawesome-webfont.ttf differ diff --git a/v2.4.1/_static/css/fonts/fontawesome-webfont.woff b/v2.4.1/_static/css/fonts/fontawesome-webfont.woff new file mode 100644 index 000000000..400014a4b Binary files /dev/null and b/v2.4.1/_static/css/fonts/fontawesome-webfont.woff differ diff --git a/v2.4.1/_static/css/fonts/fontawesome-webfont.woff2 b/v2.4.1/_static/css/fonts/fontawesome-webfont.woff2 new file mode 100644 index 000000000..4d13fc604 Binary files /dev/null and b/v2.4.1/_static/css/fonts/fontawesome-webfont.woff2 differ diff --git a/v2.4.1/_static/css/fonts/lato-bold-italic.woff b/v2.4.1/_static/css/fonts/lato-bold-italic.woff new file mode 100644 index 000000000..88ad05b9f Binary files /dev/null and b/v2.4.1/_static/css/fonts/lato-bold-italic.woff differ diff --git a/v2.4.1/_static/css/fonts/lato-bold-italic.woff2 b/v2.4.1/_static/css/fonts/lato-bold-italic.woff2 new file mode 100644 index 000000000..c4e3d804b Binary files /dev/null and b/v2.4.1/_static/css/fonts/lato-bold-italic.woff2 differ diff --git a/v2.4.1/_static/css/fonts/lato-bold.woff b/v2.4.1/_static/css/fonts/lato-bold.woff new file mode 100644 index 000000000..c6dff51f0 Binary files /dev/null and b/v2.4.1/_static/css/fonts/lato-bold.woff differ diff --git a/v2.4.1/_static/css/fonts/lato-bold.woff2 b/v2.4.1/_static/css/fonts/lato-bold.woff2 new file mode 100644 index 000000000..bb195043c Binary files /dev/null and b/v2.4.1/_static/css/fonts/lato-bold.woff2 differ diff --git a/v2.4.1/_static/css/fonts/lato-normal-italic.woff b/v2.4.1/_static/css/fonts/lato-normal-italic.woff new file mode 100644 index 000000000..76114bc03 Binary files /dev/null and b/v2.4.1/_static/css/fonts/lato-normal-italic.woff differ diff --git a/v2.4.1/_static/css/fonts/lato-normal-italic.woff2 b/v2.4.1/_static/css/fonts/lato-normal-italic.woff2 new file mode 100644 index 000000000..3404f37e2 Binary files /dev/null and b/v2.4.1/_static/css/fonts/lato-normal-italic.woff2 differ diff --git a/v2.4.1/_static/css/fonts/lato-normal.woff b/v2.4.1/_static/css/fonts/lato-normal.woff new file mode 100644 index 000000000..ae1307ff5 Binary files /dev/null and b/v2.4.1/_static/css/fonts/lato-normal.woff differ diff --git a/v2.4.1/_static/css/fonts/lato-normal.woff2 b/v2.4.1/_static/css/fonts/lato-normal.woff2 new file mode 100644 index 000000000..3bf984332 Binary files /dev/null and b/v2.4.1/_static/css/fonts/lato-normal.woff2 differ diff --git a/v2.4.1/_static/css/theme.css b/v2.4.1/_static/css/theme.css new file mode 100644 index 000000000..c03c88f06 --- /dev/null +++ b/v2.4.1/_static/css/theme.css @@ -0,0 +1,4 @@ +html{box-sizing:border-box}*,:after,:before{box-sizing:inherit}article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}[hidden],audio:not([controls]){display:none}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}blockquote{margin:0}dfn{font-style:italic}ins{background:#ff9;text-decoration:none}ins,mark{color:#000}mark{background:#ff0;font-style:italic;font-weight:700}.rst-content code,.rst-content tt,code,kbd,pre,samp{font-family:monospace,serif;_font-family:courier new,monospace;font-size:1em}pre{white-space:pre}q{quotes:none}q:after,q:before{content:"";content:none}small{font-size:85%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}dl,ol,ul{margin:0;padding:0;list-style:none;list-style-image:none}li{list-style:none}dd{margin:0}img{border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;max-width:100%}svg:not(:root){overflow:hidden}figure,form{margin:0}label{cursor:pointer}button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}button,input{line-height:normal}button,input[type=button],input[type=reset],input[type=submit]{cursor:pointer;-webkit-appearance:button;*overflow:visible}button[disabled],input[disabled]{cursor:default}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}textarea{resize:vertical}table{border-collapse:collapse;border-spacing:0}td{vertical-align:top}.chromeframe{margin:.2em 0;background:#ccc;color:#000;padding:.2em 0}.ir{display:block;border:0;text-indent:-999em;overflow:hidden;background-color:transparent;background-repeat:no-repeat;text-align:left;direction:ltr;*line-height:0}.ir br{display:none}.hidden{display:none!important;visibility:hidden}.visuallyhidden{border:0;clip:rect(0 0 0 0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.visuallyhidden.focusable:active,.visuallyhidden.focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}.invisible{visibility:hidden}.relative{position:relative}big,small{font-size:100%}@media print{body,html,section{background:none!important}*{box-shadow:none!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}a,a:visited{text-decoration:underline}.ir a:after,a[href^="#"]:after,a[href^="javascript:"]:after{content:""}blockquote,pre{page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}.rst-content .toctree-wrapper>p.caption,h2,h3,p{orphans:3;widows:3}.rst-content .toctree-wrapper>p.caption,h2,h3{page-break-after:avoid}}.btn,.fa:before,.icon:before,.rst-content .admonition,.rst-content .admonition-title:before,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .code-block-caption .headerlink:before,.rst-content .danger,.rst-content .eqno .headerlink:before,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-alert,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before,input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{-webkit-font-smoothing:antialiased}.clearfix{*zoom:1}.clearfix:after,.clearfix:before{display:table;content:""}.clearfix:after{clear:both}/*! + * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:FontAwesome;src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713);src:url(fonts/fontawesome-webfont.eot?674f50d287a8c48dc19ba404d20fe713?#iefix&v=4.7.0) format("embedded-opentype"),url(fonts/fontawesome-webfont.woff2?af7ae505a9eed503f8b8e6982036873e) format("woff2"),url(fonts/fontawesome-webfont.woff?fee66e712a8a08eef5805a46892932ad) format("woff"),url(fonts/fontawesome-webfont.ttf?b06871f281fee6b241d60582ae9369b9) format("truetype"),url(fonts/fontawesome-webfont.svg?912ec66d7572ff821749319396470bde#fontawesomeregular) format("svg");font-weight:400;font-style:normal}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14286em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14286em;width:2.14286em;top:.14286em;text-align:center}.fa-li.fa-lg{left:-1.85714em}.fa-border{padding:.2em .25em .15em;border:.08em solid #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa-pull-left.icon,.fa.fa-pull-left,.rst-content .code-block-caption .fa-pull-left.headerlink,.rst-content .eqno .fa-pull-left.headerlink,.rst-content .fa-pull-left.admonition-title,.rst-content code.download span.fa-pull-left:first-child,.rst-content dl dt .fa-pull-left.headerlink,.rst-content h1 .fa-pull-left.headerlink,.rst-content h2 .fa-pull-left.headerlink,.rst-content h3 .fa-pull-left.headerlink,.rst-content h4 .fa-pull-left.headerlink,.rst-content h5 .fa-pull-left.headerlink,.rst-content h6 .fa-pull-left.headerlink,.rst-content p .fa-pull-left.headerlink,.rst-content table>caption .fa-pull-left.headerlink,.rst-content tt.download span.fa-pull-left:first-child,.wy-menu-vertical li.current>a button.fa-pull-left.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-left.toctree-expand,.wy-menu-vertical li button.fa-pull-left.toctree-expand{margin-right:.3em}.fa-pull-right.icon,.fa.fa-pull-right,.rst-content .code-block-caption .fa-pull-right.headerlink,.rst-content .eqno .fa-pull-right.headerlink,.rst-content .fa-pull-right.admonition-title,.rst-content code.download span.fa-pull-right:first-child,.rst-content dl dt .fa-pull-right.headerlink,.rst-content h1 .fa-pull-right.headerlink,.rst-content h2 .fa-pull-right.headerlink,.rst-content h3 .fa-pull-right.headerlink,.rst-content h4 .fa-pull-right.headerlink,.rst-content h5 .fa-pull-right.headerlink,.rst-content h6 .fa-pull-right.headerlink,.rst-content p .fa-pull-right.headerlink,.rst-content table>caption .fa-pull-right.headerlink,.rst-content tt.download span.fa-pull-right:first-child,.wy-menu-vertical li.current>a button.fa-pull-right.toctree-expand,.wy-menu-vertical li.on a button.fa-pull-right.toctree-expand,.wy-menu-vertical li button.fa-pull-right.toctree-expand{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left,.pull-left.icon,.rst-content .code-block-caption .pull-left.headerlink,.rst-content .eqno .pull-left.headerlink,.rst-content .pull-left.admonition-title,.rst-content code.download span.pull-left:first-child,.rst-content dl dt .pull-left.headerlink,.rst-content h1 .pull-left.headerlink,.rst-content h2 .pull-left.headerlink,.rst-content h3 .pull-left.headerlink,.rst-content h4 .pull-left.headerlink,.rst-content h5 .pull-left.headerlink,.rst-content h6 .pull-left.headerlink,.rst-content p .pull-left.headerlink,.rst-content table>caption .pull-left.headerlink,.rst-content tt.download span.pull-left:first-child,.wy-menu-vertical li.current>a button.pull-left.toctree-expand,.wy-menu-vertical li.on a button.pull-left.toctree-expand,.wy-menu-vertical li button.pull-left.toctree-expand{margin-right:.3em}.fa.pull-right,.pull-right.icon,.rst-content .code-block-caption .pull-right.headerlink,.rst-content .eqno .pull-right.headerlink,.rst-content .pull-right.admonition-title,.rst-content code.download span.pull-right:first-child,.rst-content dl dt .pull-right.headerlink,.rst-content h1 .pull-right.headerlink,.rst-content h2 .pull-right.headerlink,.rst-content h3 .pull-right.headerlink,.rst-content h4 .pull-right.headerlink,.rst-content h5 .pull-right.headerlink,.rst-content h6 .pull-right.headerlink,.rst-content p .pull-right.headerlink,.rst-content table>caption .pull-right.headerlink,.rst-content tt.download span.pull-right:first-child,.wy-menu-vertical li.current>a button.pull-right.toctree-expand,.wy-menu-vertical li.on a button.pull-right.toctree-expand,.wy-menu-vertical li button.pull-right.toctree-expand{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s linear infinite;animation:fa-spin 2s linear infinite}.fa-pulse{-webkit-animation:fa-spin 1s steps(8) infinite;animation:fa-spin 1s steps(8) infinite}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scaleX(-1);-ms-transform:scaleX(-1);transform:scaleX(-1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scaleY(-1);-ms-transform:scaleY(-1);transform:scaleY(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:""}.fa-music:before{content:""}.fa-search:before,.icon-search:before{content:""}.fa-envelope-o:before{content:""}.fa-heart:before{content:""}.fa-star:before{content:""}.fa-star-o:before{content:""}.fa-user:before{content:""}.fa-film:before{content:""}.fa-th-large:before{content:""}.fa-th:before{content:""}.fa-th-list:before{content:""}.fa-check:before{content:""}.fa-close:before,.fa-remove:before,.fa-times:before{content:""}.fa-search-plus:before{content:""}.fa-search-minus:before{content:""}.fa-power-off:before{content:""}.fa-signal:before{content:""}.fa-cog:before,.fa-gear:before{content:""}.fa-trash-o:before{content:""}.fa-home:before,.icon-home:before{content:""}.fa-file-o:before{content:""}.fa-clock-o:before{content:""}.fa-road:before{content:""}.fa-download:before,.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{content:""}.fa-arrow-circle-o-down:before{content:""}.fa-arrow-circle-o-up:before{content:""}.fa-inbox:before{content:""}.fa-play-circle-o:before{content:""}.fa-repeat:before,.fa-rotate-right:before{content:""}.fa-refresh:before{content:""}.fa-list-alt:before{content:""}.fa-lock:before{content:""}.fa-flag:before{content:""}.fa-headphones:before{content:""}.fa-volume-off:before{content:""}.fa-volume-down:before{content:""}.fa-volume-up:before{content:""}.fa-qrcode:before{content:""}.fa-barcode:before{content:""}.fa-tag:before{content:""}.fa-tags:before{content:""}.fa-book:before,.icon-book:before{content:""}.fa-bookmark:before{content:""}.fa-print:before{content:""}.fa-camera:before{content:""}.fa-font:before{content:""}.fa-bold:before{content:""}.fa-italic:before{content:""}.fa-text-height:before{content:""}.fa-text-width:before{content:""}.fa-align-left:before{content:""}.fa-align-center:before{content:""}.fa-align-right:before{content:""}.fa-align-justify:before{content:""}.fa-list:before{content:""}.fa-dedent:before,.fa-outdent:before{content:""}.fa-indent:before{content:""}.fa-video-camera:before{content:""}.fa-image:before,.fa-photo:before,.fa-picture-o:before{content:""}.fa-pencil:before{content:""}.fa-map-marker:before{content:""}.fa-adjust:before{content:""}.fa-tint:before{content:""}.fa-edit:before,.fa-pencil-square-o:before{content:""}.fa-share-square-o:before{content:""}.fa-check-square-o:before{content:""}.fa-arrows:before{content:""}.fa-step-backward:before{content:""}.fa-fast-backward:before{content:""}.fa-backward:before{content:""}.fa-play:before{content:""}.fa-pause:before{content:""}.fa-stop:before{content:""}.fa-forward:before{content:""}.fa-fast-forward:before{content:""}.fa-step-forward:before{content:""}.fa-eject:before{content:""}.fa-chevron-left:before{content:""}.fa-chevron-right:before{content:""}.fa-plus-circle:before{content:""}.fa-minus-circle:before{content:""}.fa-times-circle:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before{content:""}.fa-check-circle:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before{content:""}.fa-question-circle:before{content:""}.fa-info-circle:before{content:""}.fa-crosshairs:before{content:""}.fa-times-circle-o:before{content:""}.fa-check-circle-o:before{content:""}.fa-ban:before{content:""}.fa-arrow-left:before{content:""}.fa-arrow-right:before{content:""}.fa-arrow-up:before{content:""}.fa-arrow-down:before{content:""}.fa-mail-forward:before,.fa-share:before{content:""}.fa-expand:before{content:""}.fa-compress:before{content:""}.fa-plus:before{content:""}.fa-minus:before{content:""}.fa-asterisk:before{content:""}.fa-exclamation-circle:before,.rst-content .admonition-title:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before{content:""}.fa-gift:before{content:""}.fa-leaf:before{content:""}.fa-fire:before,.icon-fire:before{content:""}.fa-eye:before{content:""}.fa-eye-slash:before{content:""}.fa-exclamation-triangle:before,.fa-warning:before{content:""}.fa-plane:before{content:""}.fa-calendar:before{content:""}.fa-random:before{content:""}.fa-comment:before{content:""}.fa-magnet:before{content:""}.fa-chevron-up:before{content:""}.fa-chevron-down:before{content:""}.fa-retweet:before{content:""}.fa-shopping-cart:before{content:""}.fa-folder:before{content:""}.fa-folder-open:before{content:""}.fa-arrows-v:before{content:""}.fa-arrows-h:before{content:""}.fa-bar-chart-o:before,.fa-bar-chart:before{content:""}.fa-twitter-square:before{content:""}.fa-facebook-square:before{content:""}.fa-camera-retro:before{content:""}.fa-key:before{content:""}.fa-cogs:before,.fa-gears:before{content:""}.fa-comments:before{content:""}.fa-thumbs-o-up:before{content:""}.fa-thumbs-o-down:before{content:""}.fa-star-half:before{content:""}.fa-heart-o:before{content:""}.fa-sign-out:before{content:""}.fa-linkedin-square:before{content:""}.fa-thumb-tack:before{content:""}.fa-external-link:before{content:""}.fa-sign-in:before{content:""}.fa-trophy:before{content:""}.fa-github-square:before{content:""}.fa-upload:before{content:""}.fa-lemon-o:before{content:""}.fa-phone:before{content:""}.fa-square-o:before{content:""}.fa-bookmark-o:before{content:""}.fa-phone-square:before{content:""}.fa-twitter:before{content:""}.fa-facebook-f:before,.fa-facebook:before{content:""}.fa-github:before,.icon-github:before{content:""}.fa-unlock:before{content:""}.fa-credit-card:before{content:""}.fa-feed:before,.fa-rss:before{content:""}.fa-hdd-o:before{content:""}.fa-bullhorn:before{content:""}.fa-bell:before{content:""}.fa-certificate:before{content:""}.fa-hand-o-right:before{content:""}.fa-hand-o-left:before{content:""}.fa-hand-o-up:before{content:""}.fa-hand-o-down:before{content:""}.fa-arrow-circle-left:before,.icon-circle-arrow-left:before{content:""}.fa-arrow-circle-right:before,.icon-circle-arrow-right:before{content:""}.fa-arrow-circle-up:before{content:""}.fa-arrow-circle-down:before{content:""}.fa-globe:before{content:""}.fa-wrench:before{content:""}.fa-tasks:before{content:""}.fa-filter:before{content:""}.fa-briefcase:before{content:""}.fa-arrows-alt:before{content:""}.fa-group:before,.fa-users:before{content:""}.fa-chain:before,.fa-link:before,.icon-link:before{content:""}.fa-cloud:before{content:""}.fa-flask:before{content:""}.fa-cut:before,.fa-scissors:before{content:""}.fa-copy:before,.fa-files-o:before{content:""}.fa-paperclip:before{content:""}.fa-floppy-o:before,.fa-save:before{content:""}.fa-square:before{content:""}.fa-bars:before,.fa-navicon:before,.fa-reorder:before{content:""}.fa-list-ul:before{content:""}.fa-list-ol:before{content:""}.fa-strikethrough:before{content:""}.fa-underline:before{content:""}.fa-table:before{content:""}.fa-magic:before{content:""}.fa-truck:before{content:""}.fa-pinterest:before{content:""}.fa-pinterest-square:before{content:""}.fa-google-plus-square:before{content:""}.fa-google-plus:before{content:""}.fa-money:before{content:""}.fa-caret-down:before,.icon-caret-down:before,.wy-dropdown .caret:before{content:""}.fa-caret-up:before{content:""}.fa-caret-left:before{content:""}.fa-caret-right:before{content:""}.fa-columns:before{content:""}.fa-sort:before,.fa-unsorted:before{content:""}.fa-sort-desc:before,.fa-sort-down:before{content:""}.fa-sort-asc:before,.fa-sort-up:before{content:""}.fa-envelope:before{content:""}.fa-linkedin:before{content:""}.fa-rotate-left:before,.fa-undo:before{content:""}.fa-gavel:before,.fa-legal:before{content:""}.fa-dashboard:before,.fa-tachometer:before{content:""}.fa-comment-o:before{content:""}.fa-comments-o:before{content:""}.fa-bolt:before,.fa-flash:before{content:""}.fa-sitemap:before{content:""}.fa-umbrella:before{content:""}.fa-clipboard:before,.fa-paste:before{content:""}.fa-lightbulb-o:before{content:""}.fa-exchange:before{content:""}.fa-cloud-download:before{content:""}.fa-cloud-upload:before{content:""}.fa-user-md:before{content:""}.fa-stethoscope:before{content:""}.fa-suitcase:before{content:""}.fa-bell-o:before{content:""}.fa-coffee:before{content:""}.fa-cutlery:before{content:""}.fa-file-text-o:before{content:""}.fa-building-o:before{content:""}.fa-hospital-o:before{content:""}.fa-ambulance:before{content:""}.fa-medkit:before{content:""}.fa-fighter-jet:before{content:""}.fa-beer:before{content:""}.fa-h-square:before{content:""}.fa-plus-square:before{content:""}.fa-angle-double-left:before{content:""}.fa-angle-double-right:before{content:""}.fa-angle-double-up:before{content:""}.fa-angle-double-down:before{content:""}.fa-angle-left:before{content:""}.fa-angle-right:before{content:""}.fa-angle-up:before{content:""}.fa-angle-down:before{content:""}.fa-desktop:before{content:""}.fa-laptop:before{content:""}.fa-tablet:before{content:""}.fa-mobile-phone:before,.fa-mobile:before{content:""}.fa-circle-o:before{content:""}.fa-quote-left:before{content:""}.fa-quote-right:before{content:""}.fa-spinner:before{content:""}.fa-circle:before{content:""}.fa-mail-reply:before,.fa-reply:before{content:""}.fa-github-alt:before{content:""}.fa-folder-o:before{content:""}.fa-folder-open-o:before{content:""}.fa-smile-o:before{content:""}.fa-frown-o:before{content:""}.fa-meh-o:before{content:""}.fa-gamepad:before{content:""}.fa-keyboard-o:before{content:""}.fa-flag-o:before{content:""}.fa-flag-checkered:before{content:""}.fa-terminal:before{content:""}.fa-code:before{content:""}.fa-mail-reply-all:before,.fa-reply-all:before{content:""}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:""}.fa-location-arrow:before{content:""}.fa-crop:before{content:""}.fa-code-fork:before{content:""}.fa-chain-broken:before,.fa-unlink:before{content:""}.fa-question:before{content:""}.fa-info:before{content:""}.fa-exclamation:before{content:""}.fa-superscript:before{content:""}.fa-subscript:before{content:""}.fa-eraser:before{content:""}.fa-puzzle-piece:before{content:""}.fa-microphone:before{content:""}.fa-microphone-slash:before{content:""}.fa-shield:before{content:""}.fa-calendar-o:before{content:""}.fa-fire-extinguisher:before{content:""}.fa-rocket:before{content:""}.fa-maxcdn:before{content:""}.fa-chevron-circle-left:before{content:""}.fa-chevron-circle-right:before{content:""}.fa-chevron-circle-up:before{content:""}.fa-chevron-circle-down:before{content:""}.fa-html5:before{content:""}.fa-css3:before{content:""}.fa-anchor:before{content:""}.fa-unlock-alt:before{content:""}.fa-bullseye:before{content:""}.fa-ellipsis-h:before{content:""}.fa-ellipsis-v:before{content:""}.fa-rss-square:before{content:""}.fa-play-circle:before{content:""}.fa-ticket:before{content:""}.fa-minus-square:before{content:""}.fa-minus-square-o:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before{content:""}.fa-level-up:before{content:""}.fa-level-down:before{content:""}.fa-check-square:before{content:""}.fa-pencil-square:before{content:""}.fa-external-link-square:before{content:""}.fa-share-square:before{content:""}.fa-compass:before{content:""}.fa-caret-square-o-down:before,.fa-toggle-down:before{content:""}.fa-caret-square-o-up:before,.fa-toggle-up:before{content:""}.fa-caret-square-o-right:before,.fa-toggle-right:before{content:""}.fa-eur:before,.fa-euro:before{content:""}.fa-gbp:before{content:""}.fa-dollar:before,.fa-usd:before{content:""}.fa-inr:before,.fa-rupee:before{content:""}.fa-cny:before,.fa-jpy:before,.fa-rmb:before,.fa-yen:before{content:""}.fa-rouble:before,.fa-rub:before,.fa-ruble:before{content:""}.fa-krw:before,.fa-won:before{content:""}.fa-bitcoin:before,.fa-btc:before{content:""}.fa-file:before{content:""}.fa-file-text:before{content:""}.fa-sort-alpha-asc:before{content:""}.fa-sort-alpha-desc:before{content:""}.fa-sort-amount-asc:before{content:""}.fa-sort-amount-desc:before{content:""}.fa-sort-numeric-asc:before{content:""}.fa-sort-numeric-desc:before{content:""}.fa-thumbs-up:before{content:""}.fa-thumbs-down:before{content:""}.fa-youtube-square:before{content:""}.fa-youtube:before{content:""}.fa-xing:before{content:""}.fa-xing-square:before{content:""}.fa-youtube-play:before{content:""}.fa-dropbox:before{content:""}.fa-stack-overflow:before{content:""}.fa-instagram:before{content:""}.fa-flickr:before{content:""}.fa-adn:before{content:""}.fa-bitbucket:before,.icon-bitbucket:before{content:""}.fa-bitbucket-square:before{content:""}.fa-tumblr:before{content:""}.fa-tumblr-square:before{content:""}.fa-long-arrow-down:before{content:""}.fa-long-arrow-up:before{content:""}.fa-long-arrow-left:before{content:""}.fa-long-arrow-right:before{content:""}.fa-apple:before{content:""}.fa-windows:before{content:""}.fa-android:before{content:""}.fa-linux:before{content:""}.fa-dribbble:before{content:""}.fa-skype:before{content:""}.fa-foursquare:before{content:""}.fa-trello:before{content:""}.fa-female:before{content:""}.fa-male:before{content:""}.fa-gittip:before,.fa-gratipay:before{content:""}.fa-sun-o:before{content:""}.fa-moon-o:before{content:""}.fa-archive:before{content:""}.fa-bug:before{content:""}.fa-vk:before{content:""}.fa-weibo:before{content:""}.fa-renren:before{content:""}.fa-pagelines:before{content:""}.fa-stack-exchange:before{content:""}.fa-arrow-circle-o-right:before{content:""}.fa-arrow-circle-o-left:before{content:""}.fa-caret-square-o-left:before,.fa-toggle-left:before{content:""}.fa-dot-circle-o:before{content:""}.fa-wheelchair:before{content:""}.fa-vimeo-square:before{content:""}.fa-try:before,.fa-turkish-lira:before{content:""}.fa-plus-square-o:before,.wy-menu-vertical li button.toctree-expand:before{content:""}.fa-space-shuttle:before{content:""}.fa-slack:before{content:""}.fa-envelope-square:before{content:""}.fa-wordpress:before{content:""}.fa-openid:before{content:""}.fa-bank:before,.fa-institution:before,.fa-university:before{content:""}.fa-graduation-cap:before,.fa-mortar-board:before{content:""}.fa-yahoo:before{content:""}.fa-google:before{content:""}.fa-reddit:before{content:""}.fa-reddit-square:before{content:""}.fa-stumbleupon-circle:before{content:""}.fa-stumbleupon:before{content:""}.fa-delicious:before{content:""}.fa-digg:before{content:""}.fa-pied-piper-pp:before{content:""}.fa-pied-piper-alt:before{content:""}.fa-drupal:before{content:""}.fa-joomla:before{content:""}.fa-language:before{content:""}.fa-fax:before{content:""}.fa-building:before{content:""}.fa-child:before{content:""}.fa-paw:before{content:""}.fa-spoon:before{content:""}.fa-cube:before{content:""}.fa-cubes:before{content:""}.fa-behance:before{content:""}.fa-behance-square:before{content:""}.fa-steam:before{content:""}.fa-steam-square:before{content:""}.fa-recycle:before{content:""}.fa-automobile:before,.fa-car:before{content:""}.fa-cab:before,.fa-taxi:before{content:""}.fa-tree:before{content:""}.fa-spotify:before{content:""}.fa-deviantart:before{content:""}.fa-soundcloud:before{content:""}.fa-database:before{content:""}.fa-file-pdf-o:before{content:""}.fa-file-word-o:before{content:""}.fa-file-excel-o:before{content:""}.fa-file-powerpoint-o:before{content:""}.fa-file-image-o:before,.fa-file-photo-o:before,.fa-file-picture-o:before{content:""}.fa-file-archive-o:before,.fa-file-zip-o:before{content:""}.fa-file-audio-o:before,.fa-file-sound-o:before{content:""}.fa-file-movie-o:before,.fa-file-video-o:before{content:""}.fa-file-code-o:before{content:""}.fa-vine:before{content:""}.fa-codepen:before{content:""}.fa-jsfiddle:before{content:""}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-ring:before,.fa-life-saver:before,.fa-support:before{content:""}.fa-circle-o-notch:before{content:""}.fa-ra:before,.fa-rebel:before,.fa-resistance:before{content:""}.fa-empire:before,.fa-ge:before{content:""}.fa-git-square:before{content:""}.fa-git:before{content:""}.fa-hacker-news:before,.fa-y-combinator-square:before,.fa-yc-square:before{content:""}.fa-tencent-weibo:before{content:""}.fa-qq:before{content:""}.fa-wechat:before,.fa-weixin:before{content:""}.fa-paper-plane:before,.fa-send:before{content:""}.fa-paper-plane-o:before,.fa-send-o:before{content:""}.fa-history:before{content:""}.fa-circle-thin:before{content:""}.fa-header:before{content:""}.fa-paragraph:before{content:""}.fa-sliders:before{content:""}.fa-share-alt:before{content:""}.fa-share-alt-square:before{content:""}.fa-bomb:before{content:""}.fa-futbol-o:before,.fa-soccer-ball-o:before{content:""}.fa-tty:before{content:""}.fa-binoculars:before{content:""}.fa-plug:before{content:""}.fa-slideshare:before{content:""}.fa-twitch:before{content:""}.fa-yelp:before{content:""}.fa-newspaper-o:before{content:""}.fa-wifi:before{content:""}.fa-calculator:before{content:""}.fa-paypal:before{content:""}.fa-google-wallet:before{content:""}.fa-cc-visa:before{content:""}.fa-cc-mastercard:before{content:""}.fa-cc-discover:before{content:""}.fa-cc-amex:before{content:""}.fa-cc-paypal:before{content:""}.fa-cc-stripe:before{content:""}.fa-bell-slash:before{content:""}.fa-bell-slash-o:before{content:""}.fa-trash:before{content:""}.fa-copyright:before{content:""}.fa-at:before{content:""}.fa-eyedropper:before{content:""}.fa-paint-brush:before{content:""}.fa-birthday-cake:before{content:""}.fa-area-chart:before{content:""}.fa-pie-chart:before{content:""}.fa-line-chart:before{content:""}.fa-lastfm:before{content:""}.fa-lastfm-square:before{content:""}.fa-toggle-off:before{content:""}.fa-toggle-on:before{content:""}.fa-bicycle:before{content:""}.fa-bus:before{content:""}.fa-ioxhost:before{content:""}.fa-angellist:before{content:""}.fa-cc:before{content:""}.fa-ils:before,.fa-shekel:before,.fa-sheqel:before{content:""}.fa-meanpath:before{content:""}.fa-buysellads:before{content:""}.fa-connectdevelop:before{content:""}.fa-dashcube:before{content:""}.fa-forumbee:before{content:""}.fa-leanpub:before{content:""}.fa-sellsy:before{content:""}.fa-shirtsinbulk:before{content:""}.fa-simplybuilt:before{content:""}.fa-skyatlas:before{content:""}.fa-cart-plus:before{content:""}.fa-cart-arrow-down:before{content:""}.fa-diamond:before{content:""}.fa-ship:before{content:""}.fa-user-secret:before{content:""}.fa-motorcycle:before{content:""}.fa-street-view:before{content:""}.fa-heartbeat:before{content:""}.fa-venus:before{content:""}.fa-mars:before{content:""}.fa-mercury:before{content:""}.fa-intersex:before,.fa-transgender:before{content:""}.fa-transgender-alt:before{content:""}.fa-venus-double:before{content:""}.fa-mars-double:before{content:""}.fa-venus-mars:before{content:""}.fa-mars-stroke:before{content:""}.fa-mars-stroke-v:before{content:""}.fa-mars-stroke-h:before{content:""}.fa-neuter:before{content:""}.fa-genderless:before{content:""}.fa-facebook-official:before{content:""}.fa-pinterest-p:before{content:""}.fa-whatsapp:before{content:""}.fa-server:before{content:""}.fa-user-plus:before{content:""}.fa-user-times:before{content:""}.fa-bed:before,.fa-hotel:before{content:""}.fa-viacoin:before{content:""}.fa-train:before{content:""}.fa-subway:before{content:""}.fa-medium:before{content:""}.fa-y-combinator:before,.fa-yc:before{content:""}.fa-optin-monster:before{content:""}.fa-opencart:before{content:""}.fa-expeditedssl:before{content:""}.fa-battery-4:before,.fa-battery-full:before,.fa-battery:before{content:""}.fa-battery-3:before,.fa-battery-three-quarters:before{content:""}.fa-battery-2:before,.fa-battery-half:before{content:""}.fa-battery-1:before,.fa-battery-quarter:before{content:""}.fa-battery-0:before,.fa-battery-empty:before{content:""}.fa-mouse-pointer:before{content:""}.fa-i-cursor:before{content:""}.fa-object-group:before{content:""}.fa-object-ungroup:before{content:""}.fa-sticky-note:before{content:""}.fa-sticky-note-o:before{content:""}.fa-cc-jcb:before{content:""}.fa-cc-diners-club:before{content:""}.fa-clone:before{content:""}.fa-balance-scale:before{content:""}.fa-hourglass-o:before{content:""}.fa-hourglass-1:before,.fa-hourglass-start:before{content:""}.fa-hourglass-2:before,.fa-hourglass-half:before{content:""}.fa-hourglass-3:before,.fa-hourglass-end:before{content:""}.fa-hourglass:before{content:""}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:""}.fa-hand-paper-o:before,.fa-hand-stop-o:before{content:""}.fa-hand-scissors-o:before{content:""}.fa-hand-lizard-o:before{content:""}.fa-hand-spock-o:before{content:""}.fa-hand-pointer-o:before{content:""}.fa-hand-peace-o:before{content:""}.fa-trademark:before{content:""}.fa-registered:before{content:""}.fa-creative-commons:before{content:""}.fa-gg:before{content:""}.fa-gg-circle:before{content:""}.fa-tripadvisor:before{content:""}.fa-odnoklassniki:before{content:""}.fa-odnoklassniki-square:before{content:""}.fa-get-pocket:before{content:""}.fa-wikipedia-w:before{content:""}.fa-safari:before{content:""}.fa-chrome:before{content:""}.fa-firefox:before{content:""}.fa-opera:before{content:""}.fa-internet-explorer:before{content:""}.fa-television:before,.fa-tv:before{content:""}.fa-contao:before{content:""}.fa-500px:before{content:""}.fa-amazon:before{content:""}.fa-calendar-plus-o:before{content:""}.fa-calendar-minus-o:before{content:""}.fa-calendar-times-o:before{content:""}.fa-calendar-check-o:before{content:""}.fa-industry:before{content:""}.fa-map-pin:before{content:""}.fa-map-signs:before{content:""}.fa-map-o:before{content:""}.fa-map:before{content:""}.fa-commenting:before{content:""}.fa-commenting-o:before{content:""}.fa-houzz:before{content:""}.fa-vimeo:before{content:""}.fa-black-tie:before{content:""}.fa-fonticons:before{content:""}.fa-reddit-alien:before{content:""}.fa-edge:before{content:""}.fa-credit-card-alt:before{content:""}.fa-codiepie:before{content:""}.fa-modx:before{content:""}.fa-fort-awesome:before{content:""}.fa-usb:before{content:""}.fa-product-hunt:before{content:""}.fa-mixcloud:before{content:""}.fa-scribd:before{content:""}.fa-pause-circle:before{content:""}.fa-pause-circle-o:before{content:""}.fa-stop-circle:before{content:""}.fa-stop-circle-o:before{content:""}.fa-shopping-bag:before{content:""}.fa-shopping-basket:before{content:""}.fa-hashtag:before{content:""}.fa-bluetooth:before{content:""}.fa-bluetooth-b:before{content:""}.fa-percent:before{content:""}.fa-gitlab:before,.icon-gitlab:before{content:""}.fa-wpbeginner:before{content:""}.fa-wpforms:before{content:""}.fa-envira:before{content:""}.fa-universal-access:before{content:""}.fa-wheelchair-alt:before{content:""}.fa-question-circle-o:before{content:""}.fa-blind:before{content:""}.fa-audio-description:before{content:""}.fa-volume-control-phone:before{content:""}.fa-braille:before{content:""}.fa-assistive-listening-systems:before{content:""}.fa-american-sign-language-interpreting:before,.fa-asl-interpreting:before{content:""}.fa-deaf:before,.fa-deafness:before,.fa-hard-of-hearing:before{content:""}.fa-glide:before{content:""}.fa-glide-g:before{content:""}.fa-sign-language:before,.fa-signing:before{content:""}.fa-low-vision:before{content:""}.fa-viadeo:before{content:""}.fa-viadeo-square:before{content:""}.fa-snapchat:before{content:""}.fa-snapchat-ghost:before{content:""}.fa-snapchat-square:before{content:""}.fa-pied-piper:before{content:""}.fa-first-order:before{content:""}.fa-yoast:before{content:""}.fa-themeisle:before{content:""}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:""}.fa-fa:before,.fa-font-awesome:before{content:""}.fa-handshake-o:before{content:""}.fa-envelope-open:before{content:""}.fa-envelope-open-o:before{content:""}.fa-linode:before{content:""}.fa-address-book:before{content:""}.fa-address-book-o:before{content:""}.fa-address-card:before,.fa-vcard:before{content:""}.fa-address-card-o:before,.fa-vcard-o:before{content:""}.fa-user-circle:before{content:""}.fa-user-circle-o:before{content:""}.fa-user-o:before{content:""}.fa-id-badge:before{content:""}.fa-drivers-license:before,.fa-id-card:before{content:""}.fa-drivers-license-o:before,.fa-id-card-o:before{content:""}.fa-quora:before{content:""}.fa-free-code-camp:before{content:""}.fa-telegram:before{content:""}.fa-thermometer-4:before,.fa-thermometer-full:before,.fa-thermometer:before{content:""}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:""}.fa-thermometer-2:before,.fa-thermometer-half:before{content:""}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:""}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:""}.fa-shower:before{content:""}.fa-bath:before,.fa-bathtub:before,.fa-s15:before{content:""}.fa-podcast:before{content:""}.fa-window-maximize:before{content:""}.fa-window-minimize:before{content:""}.fa-window-restore:before{content:""}.fa-times-rectangle:before,.fa-window-close:before{content:""}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:""}.fa-bandcamp:before{content:""}.fa-grav:before{content:""}.fa-etsy:before{content:""}.fa-imdb:before{content:""}.fa-ravelry:before{content:""}.fa-eercast:before{content:""}.fa-microchip:before{content:""}.fa-snowflake-o:before{content:""}.fa-superpowers:before{content:""}.fa-wpexplorer:before{content:""}.fa-meetup:before{content:""}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.fa,.icon,.rst-content .admonition-title,.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content code.download span:first-child,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink,.rst-content tt.download span:first-child,.wy-dropdown .caret,.wy-inline-validate.wy-inline-validate-danger .wy-input-context,.wy-inline-validate.wy-inline-validate-info .wy-input-context,.wy-inline-validate.wy-inline-validate-success .wy-input-context,.wy-inline-validate.wy-inline-validate-warning .wy-input-context,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li button.toctree-expand{font-family:inherit}.fa:before,.icon:before,.rst-content .admonition-title:before,.rst-content .code-block-caption .headerlink:before,.rst-content .eqno .headerlink:before,.rst-content code.download span:first-child:before,.rst-content dl dt .headerlink:before,.rst-content h1 .headerlink:before,.rst-content h2 .headerlink:before,.rst-content h3 .headerlink:before,.rst-content h4 .headerlink:before,.rst-content h5 .headerlink:before,.rst-content h6 .headerlink:before,.rst-content p.caption .headerlink:before,.rst-content p .headerlink:before,.rst-content table>caption .headerlink:before,.rst-content tt.download span:first-child:before,.wy-dropdown .caret:before,.wy-inline-validate.wy-inline-validate-danger .wy-input-context:before,.wy-inline-validate.wy-inline-validate-info .wy-input-context:before,.wy-inline-validate.wy-inline-validate-success .wy-input-context:before,.wy-inline-validate.wy-inline-validate-warning .wy-input-context:before,.wy-menu-vertical li.current>a button.toctree-expand:before,.wy-menu-vertical li.on a button.toctree-expand:before,.wy-menu-vertical li button.toctree-expand:before{font-family:FontAwesome;display:inline-block;font-style:normal;font-weight:400;line-height:1;text-decoration:inherit}.rst-content .code-block-caption a .headerlink,.rst-content .eqno a .headerlink,.rst-content a .admonition-title,.rst-content code.download a span:first-child,.rst-content dl dt a .headerlink,.rst-content h1 a .headerlink,.rst-content h2 a .headerlink,.rst-content h3 a .headerlink,.rst-content h4 a .headerlink,.rst-content h5 a .headerlink,.rst-content h6 a .headerlink,.rst-content p.caption a .headerlink,.rst-content p a .headerlink,.rst-content table>caption a .headerlink,.rst-content tt.download a span:first-child,.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand,.wy-menu-vertical li a button.toctree-expand,a .fa,a .icon,a .rst-content .admonition-title,a .rst-content .code-block-caption .headerlink,a .rst-content .eqno .headerlink,a .rst-content code.download span:first-child,a .rst-content dl dt .headerlink,a .rst-content h1 .headerlink,a .rst-content h2 .headerlink,a .rst-content h3 .headerlink,a .rst-content h4 .headerlink,a .rst-content h5 .headerlink,a .rst-content h6 .headerlink,a .rst-content p.caption .headerlink,a .rst-content p .headerlink,a .rst-content table>caption .headerlink,a .rst-content tt.download span:first-child,a .wy-menu-vertical li button.toctree-expand{display:inline-block;text-decoration:inherit}.btn .fa,.btn .icon,.btn .rst-content .admonition-title,.btn .rst-content .code-block-caption .headerlink,.btn .rst-content .eqno .headerlink,.btn .rst-content code.download span:first-child,.btn .rst-content dl dt .headerlink,.btn .rst-content h1 .headerlink,.btn .rst-content h2 .headerlink,.btn .rst-content h3 .headerlink,.btn .rst-content h4 .headerlink,.btn .rst-content h5 .headerlink,.btn .rst-content h6 .headerlink,.btn .rst-content p .headerlink,.btn .rst-content table>caption .headerlink,.btn .rst-content tt.download span:first-child,.btn .wy-menu-vertical li.current>a button.toctree-expand,.btn .wy-menu-vertical li.on a button.toctree-expand,.btn .wy-menu-vertical li button.toctree-expand,.nav .fa,.nav .icon,.nav .rst-content .admonition-title,.nav .rst-content .code-block-caption .headerlink,.nav .rst-content .eqno .headerlink,.nav .rst-content code.download span:first-child,.nav .rst-content dl dt .headerlink,.nav .rst-content h1 .headerlink,.nav .rst-content h2 .headerlink,.nav .rst-content h3 .headerlink,.nav .rst-content h4 .headerlink,.nav .rst-content h5 .headerlink,.nav .rst-content h6 .headerlink,.nav .rst-content p .headerlink,.nav .rst-content table>caption .headerlink,.nav .rst-content tt.download span:first-child,.nav .wy-menu-vertical li.current>a button.toctree-expand,.nav .wy-menu-vertical li.on a button.toctree-expand,.nav .wy-menu-vertical li button.toctree-expand,.rst-content .btn .admonition-title,.rst-content .code-block-caption .btn .headerlink,.rst-content .code-block-caption .nav .headerlink,.rst-content .eqno .btn .headerlink,.rst-content .eqno .nav .headerlink,.rst-content .nav .admonition-title,.rst-content code.download .btn span:first-child,.rst-content code.download .nav span:first-child,.rst-content dl dt .btn .headerlink,.rst-content dl dt .nav .headerlink,.rst-content h1 .btn .headerlink,.rst-content h1 .nav .headerlink,.rst-content h2 .btn .headerlink,.rst-content h2 .nav .headerlink,.rst-content h3 .btn .headerlink,.rst-content h3 .nav .headerlink,.rst-content h4 .btn .headerlink,.rst-content h4 .nav .headerlink,.rst-content h5 .btn .headerlink,.rst-content h5 .nav .headerlink,.rst-content h6 .btn .headerlink,.rst-content h6 .nav .headerlink,.rst-content p .btn .headerlink,.rst-content p .nav .headerlink,.rst-content table>caption .btn .headerlink,.rst-content table>caption .nav .headerlink,.rst-content tt.download .btn span:first-child,.rst-content tt.download .nav span:first-child,.wy-menu-vertical li .btn button.toctree-expand,.wy-menu-vertical li.current>a .btn button.toctree-expand,.wy-menu-vertical li.current>a .nav button.toctree-expand,.wy-menu-vertical li .nav button.toctree-expand,.wy-menu-vertical li.on a .btn button.toctree-expand,.wy-menu-vertical li.on a .nav button.toctree-expand{display:inline}.btn .fa-large.icon,.btn .fa.fa-large,.btn .rst-content .code-block-caption .fa-large.headerlink,.btn .rst-content .eqno .fa-large.headerlink,.btn .rst-content .fa-large.admonition-title,.btn .rst-content code.download span.fa-large:first-child,.btn .rst-content dl dt .fa-large.headerlink,.btn .rst-content h1 .fa-large.headerlink,.btn .rst-content h2 .fa-large.headerlink,.btn .rst-content h3 .fa-large.headerlink,.btn .rst-content h4 .fa-large.headerlink,.btn .rst-content h5 .fa-large.headerlink,.btn .rst-content h6 .fa-large.headerlink,.btn .rst-content p .fa-large.headerlink,.btn .rst-content table>caption .fa-large.headerlink,.btn .rst-content tt.download span.fa-large:first-child,.btn .wy-menu-vertical li button.fa-large.toctree-expand,.nav .fa-large.icon,.nav .fa.fa-large,.nav .rst-content .code-block-caption .fa-large.headerlink,.nav .rst-content .eqno .fa-large.headerlink,.nav .rst-content .fa-large.admonition-title,.nav .rst-content code.download span.fa-large:first-child,.nav .rst-content dl dt .fa-large.headerlink,.nav .rst-content h1 .fa-large.headerlink,.nav .rst-content h2 .fa-large.headerlink,.nav .rst-content h3 .fa-large.headerlink,.nav .rst-content h4 .fa-large.headerlink,.nav .rst-content h5 .fa-large.headerlink,.nav .rst-content h6 .fa-large.headerlink,.nav .rst-content p .fa-large.headerlink,.nav .rst-content table>caption .fa-large.headerlink,.nav .rst-content tt.download span.fa-large:first-child,.nav .wy-menu-vertical li button.fa-large.toctree-expand,.rst-content .btn .fa-large.admonition-title,.rst-content .code-block-caption .btn .fa-large.headerlink,.rst-content .code-block-caption .nav .fa-large.headerlink,.rst-content .eqno .btn .fa-large.headerlink,.rst-content .eqno .nav .fa-large.headerlink,.rst-content .nav .fa-large.admonition-title,.rst-content code.download .btn span.fa-large:first-child,.rst-content code.download .nav span.fa-large:first-child,.rst-content dl dt .btn .fa-large.headerlink,.rst-content dl dt .nav .fa-large.headerlink,.rst-content h1 .btn .fa-large.headerlink,.rst-content h1 .nav .fa-large.headerlink,.rst-content h2 .btn .fa-large.headerlink,.rst-content h2 .nav .fa-large.headerlink,.rst-content h3 .btn .fa-large.headerlink,.rst-content h3 .nav .fa-large.headerlink,.rst-content h4 .btn .fa-large.headerlink,.rst-content h4 .nav .fa-large.headerlink,.rst-content h5 .btn .fa-large.headerlink,.rst-content h5 .nav .fa-large.headerlink,.rst-content h6 .btn .fa-large.headerlink,.rst-content h6 .nav .fa-large.headerlink,.rst-content p .btn .fa-large.headerlink,.rst-content p .nav .fa-large.headerlink,.rst-content table>caption .btn .fa-large.headerlink,.rst-content table>caption .nav .fa-large.headerlink,.rst-content tt.download .btn span.fa-large:first-child,.rst-content tt.download .nav span.fa-large:first-child,.wy-menu-vertical li .btn button.fa-large.toctree-expand,.wy-menu-vertical li .nav button.fa-large.toctree-expand{line-height:.9em}.btn .fa-spin.icon,.btn .fa.fa-spin,.btn .rst-content .code-block-caption .fa-spin.headerlink,.btn .rst-content .eqno .fa-spin.headerlink,.btn .rst-content .fa-spin.admonition-title,.btn .rst-content code.download span.fa-spin:first-child,.btn .rst-content dl dt .fa-spin.headerlink,.btn .rst-content h1 .fa-spin.headerlink,.btn .rst-content h2 .fa-spin.headerlink,.btn .rst-content h3 .fa-spin.headerlink,.btn .rst-content h4 .fa-spin.headerlink,.btn .rst-content h5 .fa-spin.headerlink,.btn .rst-content h6 .fa-spin.headerlink,.btn .rst-content p .fa-spin.headerlink,.btn .rst-content table>caption .fa-spin.headerlink,.btn .rst-content tt.download span.fa-spin:first-child,.btn .wy-menu-vertical li button.fa-spin.toctree-expand,.nav .fa-spin.icon,.nav .fa.fa-spin,.nav .rst-content .code-block-caption .fa-spin.headerlink,.nav .rst-content .eqno .fa-spin.headerlink,.nav .rst-content .fa-spin.admonition-title,.nav .rst-content code.download span.fa-spin:first-child,.nav .rst-content dl dt .fa-spin.headerlink,.nav .rst-content h1 .fa-spin.headerlink,.nav .rst-content h2 .fa-spin.headerlink,.nav .rst-content h3 .fa-spin.headerlink,.nav .rst-content h4 .fa-spin.headerlink,.nav .rst-content h5 .fa-spin.headerlink,.nav .rst-content h6 .fa-spin.headerlink,.nav .rst-content p .fa-spin.headerlink,.nav .rst-content table>caption .fa-spin.headerlink,.nav .rst-content tt.download span.fa-spin:first-child,.nav .wy-menu-vertical li button.fa-spin.toctree-expand,.rst-content .btn .fa-spin.admonition-title,.rst-content .code-block-caption .btn .fa-spin.headerlink,.rst-content .code-block-caption .nav .fa-spin.headerlink,.rst-content .eqno .btn .fa-spin.headerlink,.rst-content .eqno .nav .fa-spin.headerlink,.rst-content .nav .fa-spin.admonition-title,.rst-content code.download .btn span.fa-spin:first-child,.rst-content code.download .nav span.fa-spin:first-child,.rst-content dl dt .btn .fa-spin.headerlink,.rst-content dl dt .nav .fa-spin.headerlink,.rst-content h1 .btn .fa-spin.headerlink,.rst-content h1 .nav .fa-spin.headerlink,.rst-content h2 .btn .fa-spin.headerlink,.rst-content h2 .nav .fa-spin.headerlink,.rst-content h3 .btn .fa-spin.headerlink,.rst-content h3 .nav .fa-spin.headerlink,.rst-content h4 .btn .fa-spin.headerlink,.rst-content h4 .nav .fa-spin.headerlink,.rst-content h5 .btn .fa-spin.headerlink,.rst-content h5 .nav .fa-spin.headerlink,.rst-content h6 .btn .fa-spin.headerlink,.rst-content h6 .nav .fa-spin.headerlink,.rst-content p .btn .fa-spin.headerlink,.rst-content p .nav .fa-spin.headerlink,.rst-content table>caption .btn .fa-spin.headerlink,.rst-content table>caption .nav .fa-spin.headerlink,.rst-content tt.download .btn span.fa-spin:first-child,.rst-content tt.download .nav span.fa-spin:first-child,.wy-menu-vertical li .btn button.fa-spin.toctree-expand,.wy-menu-vertical li .nav button.fa-spin.toctree-expand{display:inline-block}.btn.fa:before,.btn.icon:before,.rst-content .btn.admonition-title:before,.rst-content .code-block-caption .btn.headerlink:before,.rst-content .eqno .btn.headerlink:before,.rst-content code.download span.btn:first-child:before,.rst-content dl dt .btn.headerlink:before,.rst-content h1 .btn.headerlink:before,.rst-content h2 .btn.headerlink:before,.rst-content h3 .btn.headerlink:before,.rst-content h4 .btn.headerlink:before,.rst-content h5 .btn.headerlink:before,.rst-content h6 .btn.headerlink:before,.rst-content p .btn.headerlink:before,.rst-content table>caption .btn.headerlink:before,.rst-content tt.download span.btn:first-child:before,.wy-menu-vertical li button.btn.toctree-expand:before{opacity:.5;-webkit-transition:opacity .05s ease-in;-moz-transition:opacity .05s ease-in;transition:opacity .05s ease-in}.btn.fa:hover:before,.btn.icon:hover:before,.rst-content .btn.admonition-title:hover:before,.rst-content .code-block-caption .btn.headerlink:hover:before,.rst-content .eqno .btn.headerlink:hover:before,.rst-content code.download span.btn:first-child:hover:before,.rst-content dl dt .btn.headerlink:hover:before,.rst-content h1 .btn.headerlink:hover:before,.rst-content h2 .btn.headerlink:hover:before,.rst-content h3 .btn.headerlink:hover:before,.rst-content h4 .btn.headerlink:hover:before,.rst-content h5 .btn.headerlink:hover:before,.rst-content h6 .btn.headerlink:hover:before,.rst-content p .btn.headerlink:hover:before,.rst-content table>caption .btn.headerlink:hover:before,.rst-content tt.download span.btn:first-child:hover:before,.wy-menu-vertical li button.btn.toctree-expand:hover:before{opacity:1}.btn-mini .fa:before,.btn-mini .icon:before,.btn-mini .rst-content .admonition-title:before,.btn-mini .rst-content .code-block-caption .headerlink:before,.btn-mini .rst-content .eqno .headerlink:before,.btn-mini .rst-content code.download span:first-child:before,.btn-mini .rst-content dl dt .headerlink:before,.btn-mini .rst-content h1 .headerlink:before,.btn-mini .rst-content h2 .headerlink:before,.btn-mini .rst-content h3 .headerlink:before,.btn-mini .rst-content h4 .headerlink:before,.btn-mini .rst-content h5 .headerlink:before,.btn-mini .rst-content h6 .headerlink:before,.btn-mini .rst-content p .headerlink:before,.btn-mini .rst-content table>caption .headerlink:before,.btn-mini .rst-content tt.download span:first-child:before,.btn-mini .wy-menu-vertical li button.toctree-expand:before,.rst-content .btn-mini .admonition-title:before,.rst-content .code-block-caption .btn-mini .headerlink:before,.rst-content .eqno .btn-mini .headerlink:before,.rst-content code.download .btn-mini span:first-child:before,.rst-content dl dt .btn-mini .headerlink:before,.rst-content h1 .btn-mini .headerlink:before,.rst-content h2 .btn-mini .headerlink:before,.rst-content h3 .btn-mini .headerlink:before,.rst-content h4 .btn-mini .headerlink:before,.rst-content h5 .btn-mini .headerlink:before,.rst-content h6 .btn-mini .headerlink:before,.rst-content p .btn-mini .headerlink:before,.rst-content table>caption .btn-mini .headerlink:before,.rst-content tt.download .btn-mini span:first-child:before,.wy-menu-vertical li .btn-mini button.toctree-expand:before{font-size:14px;vertical-align:-15%}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning,.wy-alert{padding:12px;line-height:24px;margin-bottom:24px;background:#e7f2fa}.rst-content .admonition-title,.wy-alert-title{font-weight:700;display:block;color:#fff;background:#6ab0de;padding:6px 12px;margin:-12px -12px 12px}.rst-content .danger,.rst-content .error,.rst-content .wy-alert-danger.admonition,.rst-content .wy-alert-danger.admonition-todo,.rst-content .wy-alert-danger.attention,.rst-content .wy-alert-danger.caution,.rst-content .wy-alert-danger.hint,.rst-content .wy-alert-danger.important,.rst-content .wy-alert-danger.note,.rst-content .wy-alert-danger.seealso,.rst-content .wy-alert-danger.tip,.rst-content .wy-alert-danger.warning,.wy-alert.wy-alert-danger{background:#fdf3f2}.rst-content .danger .admonition-title,.rst-content .danger .wy-alert-title,.rst-content .error .admonition-title,.rst-content .error .wy-alert-title,.rst-content .wy-alert-danger.admonition-todo .admonition-title,.rst-content .wy-alert-danger.admonition-todo .wy-alert-title,.rst-content .wy-alert-danger.admonition .admonition-title,.rst-content .wy-alert-danger.admonition .wy-alert-title,.rst-content .wy-alert-danger.attention .admonition-title,.rst-content .wy-alert-danger.attention .wy-alert-title,.rst-content .wy-alert-danger.caution .admonition-title,.rst-content .wy-alert-danger.caution .wy-alert-title,.rst-content .wy-alert-danger.hint .admonition-title,.rst-content .wy-alert-danger.hint .wy-alert-title,.rst-content .wy-alert-danger.important .admonition-title,.rst-content .wy-alert-danger.important .wy-alert-title,.rst-content .wy-alert-danger.note .admonition-title,.rst-content .wy-alert-danger.note .wy-alert-title,.rst-content .wy-alert-danger.seealso .admonition-title,.rst-content .wy-alert-danger.seealso .wy-alert-title,.rst-content .wy-alert-danger.tip .admonition-title,.rst-content .wy-alert-danger.tip .wy-alert-title,.rst-content .wy-alert-danger.warning .admonition-title,.rst-content .wy-alert-danger.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-danger .admonition-title,.wy-alert.wy-alert-danger .rst-content .admonition-title,.wy-alert.wy-alert-danger .wy-alert-title{background:#f29f97}.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .warning,.rst-content .wy-alert-warning.admonition,.rst-content .wy-alert-warning.danger,.rst-content .wy-alert-warning.error,.rst-content .wy-alert-warning.hint,.rst-content .wy-alert-warning.important,.rst-content .wy-alert-warning.note,.rst-content .wy-alert-warning.seealso,.rst-content .wy-alert-warning.tip,.wy-alert.wy-alert-warning{background:#ffedcc}.rst-content .admonition-todo .admonition-title,.rst-content .admonition-todo .wy-alert-title,.rst-content .attention .admonition-title,.rst-content .attention .wy-alert-title,.rst-content .caution .admonition-title,.rst-content .caution .wy-alert-title,.rst-content .warning .admonition-title,.rst-content .warning .wy-alert-title,.rst-content .wy-alert-warning.admonition .admonition-title,.rst-content .wy-alert-warning.admonition .wy-alert-title,.rst-content .wy-alert-warning.danger .admonition-title,.rst-content .wy-alert-warning.danger .wy-alert-title,.rst-content .wy-alert-warning.error .admonition-title,.rst-content .wy-alert-warning.error .wy-alert-title,.rst-content .wy-alert-warning.hint .admonition-title,.rst-content .wy-alert-warning.hint .wy-alert-title,.rst-content .wy-alert-warning.important .admonition-title,.rst-content .wy-alert-warning.important .wy-alert-title,.rst-content .wy-alert-warning.note .admonition-title,.rst-content .wy-alert-warning.note .wy-alert-title,.rst-content .wy-alert-warning.seealso .admonition-title,.rst-content .wy-alert-warning.seealso .wy-alert-title,.rst-content .wy-alert-warning.tip .admonition-title,.rst-content .wy-alert-warning.tip .wy-alert-title,.rst-content .wy-alert.wy-alert-warning .admonition-title,.wy-alert.wy-alert-warning .rst-content .admonition-title,.wy-alert.wy-alert-warning .wy-alert-title{background:#f0b37e}.rst-content .note,.rst-content .seealso,.rst-content .wy-alert-info.admonition,.rst-content .wy-alert-info.admonition-todo,.rst-content .wy-alert-info.attention,.rst-content .wy-alert-info.caution,.rst-content .wy-alert-info.danger,.rst-content .wy-alert-info.error,.rst-content .wy-alert-info.hint,.rst-content .wy-alert-info.important,.rst-content .wy-alert-info.tip,.rst-content .wy-alert-info.warning,.wy-alert.wy-alert-info{background:#e7f2fa}.rst-content .note .admonition-title,.rst-content .note .wy-alert-title,.rst-content .seealso .admonition-title,.rst-content .seealso .wy-alert-title,.rst-content .wy-alert-info.admonition-todo .admonition-title,.rst-content .wy-alert-info.admonition-todo .wy-alert-title,.rst-content .wy-alert-info.admonition .admonition-title,.rst-content .wy-alert-info.admonition .wy-alert-title,.rst-content .wy-alert-info.attention .admonition-title,.rst-content .wy-alert-info.attention .wy-alert-title,.rst-content .wy-alert-info.caution .admonition-title,.rst-content .wy-alert-info.caution .wy-alert-title,.rst-content .wy-alert-info.danger .admonition-title,.rst-content .wy-alert-info.danger .wy-alert-title,.rst-content .wy-alert-info.error .admonition-title,.rst-content .wy-alert-info.error .wy-alert-title,.rst-content .wy-alert-info.hint .admonition-title,.rst-content .wy-alert-info.hint .wy-alert-title,.rst-content .wy-alert-info.important .admonition-title,.rst-content .wy-alert-info.important .wy-alert-title,.rst-content .wy-alert-info.tip .admonition-title,.rst-content .wy-alert-info.tip .wy-alert-title,.rst-content .wy-alert-info.warning .admonition-title,.rst-content .wy-alert-info.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-info .admonition-title,.wy-alert.wy-alert-info .rst-content .admonition-title,.wy-alert.wy-alert-info .wy-alert-title{background:#6ab0de}.rst-content .hint,.rst-content .important,.rst-content .tip,.rst-content .wy-alert-success.admonition,.rst-content .wy-alert-success.admonition-todo,.rst-content .wy-alert-success.attention,.rst-content .wy-alert-success.caution,.rst-content .wy-alert-success.danger,.rst-content .wy-alert-success.error,.rst-content .wy-alert-success.note,.rst-content .wy-alert-success.seealso,.rst-content .wy-alert-success.warning,.wy-alert.wy-alert-success{background:#dbfaf4}.rst-content .hint .admonition-title,.rst-content .hint .wy-alert-title,.rst-content .important .admonition-title,.rst-content .important .wy-alert-title,.rst-content .tip .admonition-title,.rst-content .tip .wy-alert-title,.rst-content .wy-alert-success.admonition-todo .admonition-title,.rst-content .wy-alert-success.admonition-todo .wy-alert-title,.rst-content .wy-alert-success.admonition .admonition-title,.rst-content .wy-alert-success.admonition .wy-alert-title,.rst-content .wy-alert-success.attention .admonition-title,.rst-content .wy-alert-success.attention .wy-alert-title,.rst-content .wy-alert-success.caution .admonition-title,.rst-content .wy-alert-success.caution .wy-alert-title,.rst-content .wy-alert-success.danger .admonition-title,.rst-content .wy-alert-success.danger .wy-alert-title,.rst-content .wy-alert-success.error .admonition-title,.rst-content .wy-alert-success.error .wy-alert-title,.rst-content .wy-alert-success.note .admonition-title,.rst-content .wy-alert-success.note .wy-alert-title,.rst-content .wy-alert-success.seealso .admonition-title,.rst-content .wy-alert-success.seealso .wy-alert-title,.rst-content .wy-alert-success.warning .admonition-title,.rst-content .wy-alert-success.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-success .admonition-title,.wy-alert.wy-alert-success .rst-content .admonition-title,.wy-alert.wy-alert-success .wy-alert-title{background:#1abc9c}.rst-content .wy-alert-neutral.admonition,.rst-content .wy-alert-neutral.admonition-todo,.rst-content .wy-alert-neutral.attention,.rst-content .wy-alert-neutral.caution,.rst-content .wy-alert-neutral.danger,.rst-content .wy-alert-neutral.error,.rst-content .wy-alert-neutral.hint,.rst-content .wy-alert-neutral.important,.rst-content .wy-alert-neutral.note,.rst-content .wy-alert-neutral.seealso,.rst-content .wy-alert-neutral.tip,.rst-content .wy-alert-neutral.warning,.wy-alert.wy-alert-neutral{background:#f3f6f6}.rst-content .wy-alert-neutral.admonition-todo .admonition-title,.rst-content .wy-alert-neutral.admonition-todo .wy-alert-title,.rst-content .wy-alert-neutral.admonition .admonition-title,.rst-content .wy-alert-neutral.admonition .wy-alert-title,.rst-content .wy-alert-neutral.attention .admonition-title,.rst-content .wy-alert-neutral.attention .wy-alert-title,.rst-content .wy-alert-neutral.caution .admonition-title,.rst-content .wy-alert-neutral.caution .wy-alert-title,.rst-content .wy-alert-neutral.danger .admonition-title,.rst-content .wy-alert-neutral.danger .wy-alert-title,.rst-content .wy-alert-neutral.error .admonition-title,.rst-content .wy-alert-neutral.error .wy-alert-title,.rst-content .wy-alert-neutral.hint .admonition-title,.rst-content .wy-alert-neutral.hint .wy-alert-title,.rst-content .wy-alert-neutral.important .admonition-title,.rst-content .wy-alert-neutral.important .wy-alert-title,.rst-content .wy-alert-neutral.note .admonition-title,.rst-content .wy-alert-neutral.note .wy-alert-title,.rst-content .wy-alert-neutral.seealso .admonition-title,.rst-content .wy-alert-neutral.seealso .wy-alert-title,.rst-content .wy-alert-neutral.tip .admonition-title,.rst-content .wy-alert-neutral.tip .wy-alert-title,.rst-content .wy-alert-neutral.warning .admonition-title,.rst-content .wy-alert-neutral.warning .wy-alert-title,.rst-content .wy-alert.wy-alert-neutral .admonition-title,.wy-alert.wy-alert-neutral .rst-content .admonition-title,.wy-alert.wy-alert-neutral .wy-alert-title{color:#404040;background:#e1e4e5}.rst-content .wy-alert-neutral.admonition-todo a,.rst-content .wy-alert-neutral.admonition a,.rst-content .wy-alert-neutral.attention a,.rst-content .wy-alert-neutral.caution a,.rst-content .wy-alert-neutral.danger a,.rst-content .wy-alert-neutral.error a,.rst-content .wy-alert-neutral.hint a,.rst-content .wy-alert-neutral.important a,.rst-content .wy-alert-neutral.note a,.rst-content .wy-alert-neutral.seealso a,.rst-content .wy-alert-neutral.tip a,.rst-content .wy-alert-neutral.warning a,.wy-alert.wy-alert-neutral a{color:#2980b9}.rst-content .admonition-todo p:last-child,.rst-content .admonition p:last-child,.rst-content .attention p:last-child,.rst-content .caution p:last-child,.rst-content .danger p:last-child,.rst-content .error p:last-child,.rst-content .hint p:last-child,.rst-content .important p:last-child,.rst-content .note p:last-child,.rst-content .seealso p:last-child,.rst-content .tip p:last-child,.rst-content .warning p:last-child,.wy-alert p:last-child{margin-bottom:0}.wy-tray-container{position:fixed;bottom:0;left:0;z-index:600}.wy-tray-container li{display:block;width:300px;background:transparent;color:#fff;text-align:center;box-shadow:0 5px 5px 0 rgba(0,0,0,.1);padding:0 24px;min-width:20%;opacity:0;height:0;line-height:56px;overflow:hidden;-webkit-transition:all .3s ease-in;-moz-transition:all .3s ease-in;transition:all .3s ease-in}.wy-tray-container li.wy-tray-item-success{background:#27ae60}.wy-tray-container li.wy-tray-item-info{background:#2980b9}.wy-tray-container li.wy-tray-item-warning{background:#e67e22}.wy-tray-container li.wy-tray-item-danger{background:#e74c3c}.wy-tray-container li.on{opacity:1;height:56px}@media screen and (max-width:768px){.wy-tray-container{bottom:auto;top:0;width:100%}.wy-tray-container li{width:100%}}button{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;cursor:pointer;line-height:normal;-webkit-appearance:button;*overflow:visible}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}button[disabled]{cursor:default}.btn{display:inline-block;border-radius:2px;line-height:normal;white-space:nowrap;text-align:center;cursor:pointer;font-size:100%;padding:6px 12px 8px;color:#fff;border:1px solid rgba(0,0,0,.1);background-color:#27ae60;text-decoration:none;font-weight:400;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 2px -1px hsla(0,0%,100%,.5),inset 0 -2px 0 0 rgba(0,0,0,.1);outline-none:false;vertical-align:middle;*display:inline;zoom:1;-webkit-user-drag:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-transition:all .1s linear;-moz-transition:all .1s linear;transition:all .1s linear}.btn-hover{background:#2e8ece;color:#fff}.btn:hover{background:#2cc36b;color:#fff}.btn:focus{background:#2cc36b;outline:0}.btn:active{box-shadow:inset 0 -1px 0 0 rgba(0,0,0,.05),inset 0 2px 0 0 rgba(0,0,0,.1);padding:8px 12px 6px}.btn:visited{color:#fff}.btn-disabled,.btn-disabled:active,.btn-disabled:focus,.btn-disabled:hover,.btn:disabled{background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=40);opacity:.4;cursor:not-allowed;box-shadow:none}.btn::-moz-focus-inner{padding:0;border:0}.btn-small{font-size:80%}.btn-info{background-color:#2980b9!important}.btn-info:hover{background-color:#2e8ece!important}.btn-neutral{background-color:#f3f6f6!important;color:#404040!important}.btn-neutral:hover{background-color:#e5ebeb!important;color:#404040}.btn-neutral:visited{color:#404040!important}.btn-success{background-color:#27ae60!important}.btn-success:hover{background-color:#295!important}.btn-danger{background-color:#e74c3c!important}.btn-danger:hover{background-color:#ea6153!important}.btn-warning{background-color:#e67e22!important}.btn-warning:hover{background-color:#e98b39!important}.btn-invert{background-color:#222}.btn-invert:hover{background-color:#2f2f2f!important}.btn-link{background-color:transparent!important;color:#2980b9;box-shadow:none;border-color:transparent!important}.btn-link:active,.btn-link:hover{background-color:transparent!important;color:#409ad5!important;box-shadow:none}.btn-link:visited{color:#9b59b6}.wy-btn-group .btn,.wy-control .btn{vertical-align:middle}.wy-btn-group{margin-bottom:24px;*zoom:1}.wy-btn-group:after,.wy-btn-group:before{display:table;content:""}.wy-btn-group:after{clear:both}.wy-dropdown{position:relative;display:inline-block}.wy-dropdown-active .wy-dropdown-menu{display:block}.wy-dropdown-menu{position:absolute;left:0;display:none;float:left;top:100%;min-width:100%;background:#fcfcfc;z-index:100;border:1px solid #cfd7dd;box-shadow:0 2px 2px 0 rgba(0,0,0,.1);padding:12px}.wy-dropdown-menu>dd>a{display:block;clear:both;color:#404040;white-space:nowrap;font-size:90%;padding:0 12px;cursor:pointer}.wy-dropdown-menu>dd>a:hover{background:#2980b9;color:#fff}.wy-dropdown-menu>dd.divider{border-top:1px solid #cfd7dd;margin:6px 0}.wy-dropdown-menu>dd.search{padding-bottom:12px}.wy-dropdown-menu>dd.search input[type=search]{width:100%}.wy-dropdown-menu>dd.call-to-action{background:#e3e3e3;text-transform:uppercase;font-weight:500;font-size:80%}.wy-dropdown-menu>dd.call-to-action:hover{background:#e3e3e3}.wy-dropdown-menu>dd.call-to-action .btn{color:#fff}.wy-dropdown.wy-dropdown-up .wy-dropdown-menu{bottom:100%;top:auto;left:auto;right:0}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu{background:#fcfcfc;margin-top:2px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a{padding:6px 12px}.wy-dropdown.wy-dropdown-bubble .wy-dropdown-menu a:hover{background:#2980b9;color:#fff}.wy-dropdown.wy-dropdown-left .wy-dropdown-menu{right:0;left:auto;text-align:right}.wy-dropdown-arrow:before{content:" ";border-bottom:5px solid #f5f5f5;border-left:5px solid transparent;border-right:5px solid transparent;position:absolute;display:block;top:-4px;left:50%;margin-left:-3px}.wy-dropdown-arrow.wy-dropdown-arrow-left:before{left:11px}.wy-form-stacked select{display:block}.wy-form-aligned .wy-help-inline,.wy-form-aligned input,.wy-form-aligned label,.wy-form-aligned select,.wy-form-aligned textarea{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-form-aligned .wy-control-group>label{display:inline-block;vertical-align:middle;width:10em;margin:6px 12px 0 0;float:left}.wy-form-aligned .wy-control{float:left}.wy-form-aligned .wy-control label{display:block}.wy-form-aligned .wy-control select{margin-top:6px}fieldset{margin:0}fieldset,legend{border:0;padding:0}legend{width:100%;white-space:normal;margin-bottom:24px;font-size:150%;*margin-left:-7px}label,legend{display:block}label{margin:0 0 .3125em;color:#333;font-size:90%}input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle}.wy-control-group{margin-bottom:24px;max-width:1200px;margin-left:auto;margin-right:auto;*zoom:1}.wy-control-group:after,.wy-control-group:before{display:table;content:""}.wy-control-group:after{clear:both}.wy-control-group.wy-control-group-required>label:after{content:" *";color:#e74c3c}.wy-control-group .wy-form-full,.wy-control-group .wy-form-halves,.wy-control-group .wy-form-thirds{padding-bottom:12px}.wy-control-group .wy-form-full input[type=color],.wy-control-group .wy-form-full input[type=date],.wy-control-group .wy-form-full input[type=datetime-local],.wy-control-group .wy-form-full input[type=datetime],.wy-control-group .wy-form-full input[type=email],.wy-control-group .wy-form-full input[type=month],.wy-control-group .wy-form-full input[type=number],.wy-control-group .wy-form-full input[type=password],.wy-control-group .wy-form-full input[type=search],.wy-control-group .wy-form-full input[type=tel],.wy-control-group .wy-form-full input[type=text],.wy-control-group .wy-form-full input[type=time],.wy-control-group .wy-form-full input[type=url],.wy-control-group .wy-form-full input[type=week],.wy-control-group .wy-form-full select,.wy-control-group .wy-form-halves input[type=color],.wy-control-group .wy-form-halves input[type=date],.wy-control-group .wy-form-halves input[type=datetime-local],.wy-control-group .wy-form-halves input[type=datetime],.wy-control-group .wy-form-halves input[type=email],.wy-control-group .wy-form-halves input[type=month],.wy-control-group .wy-form-halves input[type=number],.wy-control-group .wy-form-halves input[type=password],.wy-control-group .wy-form-halves input[type=search],.wy-control-group .wy-form-halves input[type=tel],.wy-control-group .wy-form-halves input[type=text],.wy-control-group .wy-form-halves input[type=time],.wy-control-group .wy-form-halves input[type=url],.wy-control-group .wy-form-halves input[type=week],.wy-control-group .wy-form-halves select,.wy-control-group .wy-form-thirds input[type=color],.wy-control-group .wy-form-thirds input[type=date],.wy-control-group .wy-form-thirds input[type=datetime-local],.wy-control-group .wy-form-thirds input[type=datetime],.wy-control-group .wy-form-thirds input[type=email],.wy-control-group .wy-form-thirds input[type=month],.wy-control-group .wy-form-thirds input[type=number],.wy-control-group .wy-form-thirds input[type=password],.wy-control-group .wy-form-thirds input[type=search],.wy-control-group .wy-form-thirds input[type=tel],.wy-control-group .wy-form-thirds input[type=text],.wy-control-group .wy-form-thirds input[type=time],.wy-control-group .wy-form-thirds input[type=url],.wy-control-group .wy-form-thirds input[type=week],.wy-control-group .wy-form-thirds select{width:100%}.wy-control-group .wy-form-full{float:left;display:block;width:100%;margin-right:0}.wy-control-group .wy-form-full:last-child{margin-right:0}.wy-control-group .wy-form-halves{float:left;display:block;margin-right:2.35765%;width:48.82117%}.wy-control-group .wy-form-halves:last-child,.wy-control-group .wy-form-halves:nth-of-type(2n){margin-right:0}.wy-control-group .wy-form-halves:nth-of-type(odd){clear:left}.wy-control-group .wy-form-thirds{float:left;display:block;margin-right:2.35765%;width:31.76157%}.wy-control-group .wy-form-thirds:last-child,.wy-control-group .wy-form-thirds:nth-of-type(3n){margin-right:0}.wy-control-group .wy-form-thirds:nth-of-type(3n+1){clear:left}.wy-control-group.wy-control-group-no-input .wy-control,.wy-control-no-input{margin:6px 0 0;font-size:90%}.wy-control-no-input{display:inline-block}.wy-control-group.fluid-input input[type=color],.wy-control-group.fluid-input input[type=date],.wy-control-group.fluid-input input[type=datetime-local],.wy-control-group.fluid-input input[type=datetime],.wy-control-group.fluid-input input[type=email],.wy-control-group.fluid-input input[type=month],.wy-control-group.fluid-input input[type=number],.wy-control-group.fluid-input input[type=password],.wy-control-group.fluid-input input[type=search],.wy-control-group.fluid-input input[type=tel],.wy-control-group.fluid-input input[type=text],.wy-control-group.fluid-input input[type=time],.wy-control-group.fluid-input input[type=url],.wy-control-group.fluid-input input[type=week]{width:100%}.wy-form-message-inline{padding-left:.3em;color:#666;font-size:90%}.wy-form-message{display:block;color:#999;font-size:70%;margin-top:.3125em;font-style:italic}.wy-form-message p{font-size:inherit;font-style:italic;margin-bottom:6px}.wy-form-message p:last-child{margin-bottom:0}input{line-height:normal}input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;*overflow:visible}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:6px;display:inline-block;border:1px solid #ccc;font-size:80%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;box-shadow:inset 0 1px 3px #ddd;border-radius:0;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}input[type=datetime-local]{padding:.34375em .625em}input[disabled]{cursor:default}input[type=checkbox],input[type=radio]{padding:0;margin-right:.3125em;*height:13px;*width:13px}input[type=checkbox],input[type=radio],input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus{outline:0;outline:thin dotted\9;border-color:#333}input.no-focus:focus{border-color:#ccc!important}input[type=checkbox]:focus,input[type=file]:focus,input[type=radio]:focus{outline:thin dotted #333;outline:1px auto #129fea}input[type=color][disabled],input[type=date][disabled],input[type=datetime-local][disabled],input[type=datetime][disabled],input[type=email][disabled],input[type=month][disabled],input[type=number][disabled],input[type=password][disabled],input[type=search][disabled],input[type=tel][disabled],input[type=text][disabled],input[type=time][disabled],input[type=url][disabled],input[type=week][disabled]{cursor:not-allowed;background-color:#fafafa}input:focus:invalid,select:focus:invalid,textarea:focus:invalid{color:#e74c3c;border:1px solid #e74c3c}input:focus:invalid:focus,select:focus:invalid:focus,textarea:focus:invalid:focus{border-color:#e74c3c}input[type=checkbox]:focus:invalid:focus,input[type=file]:focus:invalid:focus,input[type=radio]:focus:invalid:focus{outline-color:#e74c3c}input.wy-input-large{padding:12px;font-size:100%}textarea{overflow:auto;vertical-align:top;width:100%;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif}select,textarea{padding:.5em .625em;display:inline-block;border:1px solid #ccc;font-size:80%;box-shadow:inset 0 1px 3px #ddd;-webkit-transition:border .3s linear;-moz-transition:border .3s linear;transition:border .3s linear}select{border:1px solid #ccc;background-color:#fff}select[multiple]{height:auto}select:focus,textarea:focus{outline:0}input[readonly],select[disabled],select[readonly],textarea[disabled],textarea[readonly]{cursor:not-allowed;background-color:#fafafa}input[type=checkbox][disabled],input[type=radio][disabled]{cursor:not-allowed}.wy-checkbox,.wy-radio{margin:6px 0;color:#404040;display:block}.wy-checkbox input,.wy-radio input{vertical-align:baseline}.wy-form-message-inline{display:inline-block;*display:inline;*zoom:1;vertical-align:middle}.wy-input-prefix,.wy-input-suffix{white-space:nowrap;padding:6px}.wy-input-prefix .wy-input-context,.wy-input-suffix .wy-input-context{line-height:27px;padding:0 8px;display:inline-block;font-size:80%;background-color:#f3f6f6;border:1px solid #ccc;color:#999}.wy-input-suffix .wy-input-context{border-left:0}.wy-input-prefix .wy-input-context{border-right:0}.wy-switch{position:relative;display:block;height:24px;margin-top:12px;cursor:pointer}.wy-switch:before{left:0;top:0;width:36px;height:12px;background:#ccc}.wy-switch:after,.wy-switch:before{position:absolute;content:"";display:block;border-radius:4px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.wy-switch:after{width:18px;height:18px;background:#999;left:-3px;top:-3px}.wy-switch span{position:absolute;left:48px;display:block;font-size:12px;color:#ccc;line-height:1}.wy-switch.active:before{background:#1e8449}.wy-switch.active:after{left:24px;background:#27ae60}.wy-switch.disabled{cursor:not-allowed;opacity:.8}.wy-control-group.wy-control-group-error .wy-form-message,.wy-control-group.wy-control-group-error>label{color:#e74c3c}.wy-control-group.wy-control-group-error input[type=color],.wy-control-group.wy-control-group-error input[type=date],.wy-control-group.wy-control-group-error input[type=datetime-local],.wy-control-group.wy-control-group-error input[type=datetime],.wy-control-group.wy-control-group-error input[type=email],.wy-control-group.wy-control-group-error input[type=month],.wy-control-group.wy-control-group-error input[type=number],.wy-control-group.wy-control-group-error input[type=password],.wy-control-group.wy-control-group-error input[type=search],.wy-control-group.wy-control-group-error input[type=tel],.wy-control-group.wy-control-group-error input[type=text],.wy-control-group.wy-control-group-error input[type=time],.wy-control-group.wy-control-group-error input[type=url],.wy-control-group.wy-control-group-error input[type=week],.wy-control-group.wy-control-group-error textarea{border:1px solid #e74c3c}.wy-inline-validate{white-space:nowrap}.wy-inline-validate .wy-input-context{padding:.5em .625em;display:inline-block;font-size:80%}.wy-inline-validate.wy-inline-validate-success .wy-input-context{color:#27ae60}.wy-inline-validate.wy-inline-validate-danger .wy-input-context{color:#e74c3c}.wy-inline-validate.wy-inline-validate-warning .wy-input-context{color:#e67e22}.wy-inline-validate.wy-inline-validate-info .wy-input-context{color:#2980b9}.rotate-90{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)}.rotate-180{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)}.rotate-270{-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)}.mirror{-webkit-transform:scaleX(-1);-moz-transform:scaleX(-1);-ms-transform:scaleX(-1);-o-transform:scaleX(-1);transform:scaleX(-1)}.mirror.rotate-90{-webkit-transform:scaleX(-1) rotate(90deg);-moz-transform:scaleX(-1) rotate(90deg);-ms-transform:scaleX(-1) rotate(90deg);-o-transform:scaleX(-1) rotate(90deg);transform:scaleX(-1) rotate(90deg)}.mirror.rotate-180{-webkit-transform:scaleX(-1) rotate(180deg);-moz-transform:scaleX(-1) rotate(180deg);-ms-transform:scaleX(-1) rotate(180deg);-o-transform:scaleX(-1) rotate(180deg);transform:scaleX(-1) rotate(180deg)}.mirror.rotate-270{-webkit-transform:scaleX(-1) rotate(270deg);-moz-transform:scaleX(-1) rotate(270deg);-ms-transform:scaleX(-1) rotate(270deg);-o-transform:scaleX(-1) rotate(270deg);transform:scaleX(-1) rotate(270deg)}@media only screen and (max-width:480px){.wy-form button[type=submit]{margin:.7em 0 0}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=text],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week],.wy-form label{margin-bottom:.3em;display:block}.wy-form input[type=color],.wy-form input[type=date],.wy-form input[type=datetime-local],.wy-form input[type=datetime],.wy-form input[type=email],.wy-form input[type=month],.wy-form input[type=number],.wy-form input[type=password],.wy-form input[type=search],.wy-form input[type=tel],.wy-form input[type=time],.wy-form input[type=url],.wy-form input[type=week]{margin-bottom:0}.wy-form-aligned .wy-control-group label{margin-bottom:.3em;text-align:left;display:block;width:100%}.wy-form-aligned .wy-control{margin:1.5em 0 0}.wy-form-message,.wy-form-message-inline,.wy-form .wy-help-inline{display:block;font-size:80%;padding:6px 0}}@media screen and (max-width:768px){.tablet-hide{display:none}}@media screen and (max-width:480px){.mobile-hide{display:none}}.float-left{float:left}.float-right{float:right}.full-width{width:100%}.rst-content table.docutils,.rst-content table.field-list,.wy-table{border-collapse:collapse;border-spacing:0;empty-cells:show;margin-bottom:24px}.rst-content table.docutils caption,.rst-content table.field-list caption,.wy-table caption{color:#000;font:italic 85%/1 arial,sans-serif;padding:1em 0;text-align:center}.rst-content table.docutils td,.rst-content table.docutils th,.rst-content table.field-list td,.rst-content table.field-list th,.wy-table td,.wy-table th{font-size:90%;margin:0;overflow:visible;padding:8px 16px}.rst-content table.docutils td:first-child,.rst-content table.docutils th:first-child,.rst-content table.field-list td:first-child,.rst-content table.field-list th:first-child,.wy-table td:first-child,.wy-table th:first-child{border-left-width:0}.rst-content table.docutils thead,.rst-content table.field-list thead,.wy-table thead{color:#000;text-align:left;vertical-align:bottom;white-space:nowrap}.rst-content table.docutils thead th,.rst-content table.field-list thead th,.wy-table thead th{font-weight:700;border-bottom:2px solid #e1e4e5}.rst-content table.docutils td,.rst-content table.field-list td,.wy-table td{background-color:transparent;vertical-align:middle}.rst-content table.docutils td p,.rst-content table.field-list td p,.wy-table td p{line-height:18px}.rst-content table.docutils td p:last-child,.rst-content table.field-list td p:last-child,.wy-table td p:last-child{margin-bottom:0}.rst-content table.docutils .wy-table-cell-min,.rst-content table.field-list .wy-table-cell-min,.wy-table .wy-table-cell-min{width:1%;padding-right:0}.rst-content table.docutils .wy-table-cell-min input[type=checkbox],.rst-content table.field-list .wy-table-cell-min input[type=checkbox],.wy-table .wy-table-cell-min input[type=checkbox]{margin:0}.wy-table-secondary{color:grey;font-size:90%}.wy-table-tertiary{color:grey;font-size:80%}.rst-content table.docutils:not(.field-list) tr:nth-child(2n-1) td,.wy-table-backed,.wy-table-odd td,.wy-table-striped tr:nth-child(2n-1) td{background-color:#f3f6f6}.rst-content table.docutils,.wy-table-bordered-all{border:1px solid #e1e4e5}.rst-content table.docutils td,.wy-table-bordered-all td{border-bottom:1px solid #e1e4e5;border-left:1px solid #e1e4e5}.rst-content table.docutils tbody>tr:last-child td,.wy-table-bordered-all tbody>tr:last-child td{border-bottom-width:0}.wy-table-bordered{border:1px solid #e1e4e5}.wy-table-bordered-rows td{border-bottom:1px solid #e1e4e5}.wy-table-bordered-rows tbody>tr:last-child td{border-bottom-width:0}.wy-table-horizontal td,.wy-table-horizontal th{border-width:0 0 1px;border-bottom:1px solid #e1e4e5}.wy-table-horizontal tbody>tr:last-child td{border-bottom-width:0}.wy-table-responsive{margin-bottom:24px;max-width:100%;overflow:auto}.wy-table-responsive table{margin-bottom:0!important}.wy-table-responsive table td,.wy-table-responsive table th{white-space:nowrap}a{color:#2980b9;text-decoration:none;cursor:pointer}a:hover{color:#3091d1}a:visited{color:#9b59b6}html{height:100%}body,html{overflow-x:hidden}body{font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;font-weight:400;color:#404040;min-height:100%;background:#edf0f2}.wy-text-left{text-align:left}.wy-text-center{text-align:center}.wy-text-right{text-align:right}.wy-text-large{font-size:120%}.wy-text-normal{font-size:100%}.wy-text-small,small{font-size:80%}.wy-text-strike{text-decoration:line-through}.wy-text-warning{color:#e67e22!important}a.wy-text-warning:hover{color:#eb9950!important}.wy-text-info{color:#2980b9!important}a.wy-text-info:hover{color:#409ad5!important}.wy-text-success{color:#27ae60!important}a.wy-text-success:hover{color:#36d278!important}.wy-text-danger{color:#e74c3c!important}a.wy-text-danger:hover{color:#ed7669!important}.wy-text-neutral{color:#404040!important}a.wy-text-neutral:hover{color:#595959!important}.rst-content .toctree-wrapper>p.caption,h1,h2,h3,h4,h5,h6,legend{margin-top:0;font-weight:700;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif}p{line-height:24px;font-size:16px;margin:0 0 24px}h1{font-size:175%}.rst-content .toctree-wrapper>p.caption,h2{font-size:150%}h3{font-size:125%}h4{font-size:115%}h5{font-size:110%}h6{font-size:100%}hr{display:block;height:1px;border:0;border-top:1px solid #e1e4e5;margin:24px 0;padding:0}.rst-content code,.rst-content tt,code{white-space:nowrap;max-width:100%;background:#fff;border:1px solid #e1e4e5;font-size:75%;padding:0 5px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#e74c3c;overflow-x:auto}.rst-content tt.code-large,code.code-large{font-size:90%}.rst-content .section ul,.rst-content .toctree-wrapper ul,.rst-content section ul,.wy-plain-list-disc,article ul{list-style:disc;line-height:24px;margin-bottom:24px}.rst-content .section ul li,.rst-content .toctree-wrapper ul li,.rst-content section ul li,.wy-plain-list-disc li,article ul li{list-style:disc;margin-left:24px}.rst-content .section ul li p:last-child,.rst-content .section ul li ul,.rst-content .toctree-wrapper ul li p:last-child,.rst-content .toctree-wrapper ul li ul,.rst-content section ul li p:last-child,.rst-content section ul li ul,.wy-plain-list-disc li p:last-child,.wy-plain-list-disc li ul,article ul li p:last-child,article ul li ul{margin-bottom:0}.rst-content .section ul li li,.rst-content .toctree-wrapper ul li li,.rst-content section ul li li,.wy-plain-list-disc li li,article ul li li{list-style:circle}.rst-content .section ul li li li,.rst-content .toctree-wrapper ul li li li,.rst-content section ul li li li,.wy-plain-list-disc li li li,article ul li li li{list-style:square}.rst-content .section ul li ol li,.rst-content .toctree-wrapper ul li ol li,.rst-content section ul li ol li,.wy-plain-list-disc li ol li,article ul li ol li{list-style:decimal}.rst-content .section ol,.rst-content .section ol.arabic,.rst-content .toctree-wrapper ol,.rst-content .toctree-wrapper ol.arabic,.rst-content section ol,.rst-content section ol.arabic,.wy-plain-list-decimal,article ol{list-style:decimal;line-height:24px;margin-bottom:24px}.rst-content .section ol.arabic li,.rst-content .section ol li,.rst-content .toctree-wrapper ol.arabic li,.rst-content .toctree-wrapper ol li,.rst-content section ol.arabic li,.rst-content section ol li,.wy-plain-list-decimal li,article ol li{list-style:decimal;margin-left:24px}.rst-content .section ol.arabic li ul,.rst-content .section ol li p:last-child,.rst-content .section ol li ul,.rst-content .toctree-wrapper ol.arabic li ul,.rst-content .toctree-wrapper ol li p:last-child,.rst-content .toctree-wrapper ol li ul,.rst-content section ol.arabic li ul,.rst-content section ol li p:last-child,.rst-content section ol li ul,.wy-plain-list-decimal li p:last-child,.wy-plain-list-decimal li ul,article ol li p:last-child,article ol li ul{margin-bottom:0}.rst-content .section ol.arabic li ul li,.rst-content .section ol li ul li,.rst-content .toctree-wrapper ol.arabic li ul li,.rst-content .toctree-wrapper ol li ul li,.rst-content section ol.arabic li ul li,.rst-content section ol li ul li,.wy-plain-list-decimal li ul li,article ol li ul li{list-style:disc}.wy-breadcrumbs{*zoom:1}.wy-breadcrumbs:after,.wy-breadcrumbs:before{display:table;content:""}.wy-breadcrumbs:after{clear:both}.wy-breadcrumbs>li{display:inline-block;padding-top:5px}.wy-breadcrumbs>li.wy-breadcrumbs-aside{float:right}.rst-content .wy-breadcrumbs>li code,.rst-content .wy-breadcrumbs>li tt,.wy-breadcrumbs>li .rst-content tt,.wy-breadcrumbs>li code{all:inherit;color:inherit}.breadcrumb-item:before{content:"/";color:#bbb;font-size:13px;padding:0 6px 0 3px}.wy-breadcrumbs-extra{margin-bottom:0;color:#b3b3b3;font-size:80%;display:inline-block}@media screen and (max-width:480px){.wy-breadcrumbs-extra,.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}@media print{.wy-breadcrumbs li.wy-breadcrumbs-aside{display:none}}html{font-size:16px}.wy-affix{position:fixed;top:1.618em}.wy-menu a:hover{text-decoration:none}.wy-menu-horiz{*zoom:1}.wy-menu-horiz:after,.wy-menu-horiz:before{display:table;content:""}.wy-menu-horiz:after{clear:both}.wy-menu-horiz li,.wy-menu-horiz ul{display:inline-block}.wy-menu-horiz li:hover{background:hsla(0,0%,100%,.1)}.wy-menu-horiz li.divide-left{border-left:1px solid #404040}.wy-menu-horiz li.divide-right{border-right:1px solid #404040}.wy-menu-horiz a{height:32px;display:inline-block;line-height:32px;padding:0 16px}.wy-menu-vertical{width:300px}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#55a5d9;height:32px;line-height:32px;padding:0 1.618em;margin:12px 0 0;display:block;font-weight:700;text-transform:uppercase;font-size:85%;white-space:nowrap}.wy-menu-vertical ul{margin-bottom:0}.wy-menu-vertical li.divide-top{border-top:1px solid #404040}.wy-menu-vertical li.divide-bottom{border-bottom:1px solid #404040}.wy-menu-vertical li.current{background:#e3e3e3}.wy-menu-vertical li.current a{color:grey;border-right:1px solid #c9c9c9;padding:.4045em 2.427em}.wy-menu-vertical li.current a:hover{background:#d6d6d6}.rst-content .wy-menu-vertical li tt,.wy-menu-vertical li .rst-content tt,.wy-menu-vertical li code{border:none;background:inherit;color:inherit;padding-left:0;padding-right:0}.wy-menu-vertical li button.toctree-expand{display:block;float:left;margin-left:-1.2em;line-height:18px;color:#4d4d4d;border:none;background:none;padding:0}.wy-menu-vertical li.current>a,.wy-menu-vertical li.on a{color:#404040;font-weight:700;position:relative;background:#fcfcfc;border:none;padding:.4045em 1.618em}.wy-menu-vertical li.current>a:hover,.wy-menu-vertical li.on a:hover{background:#fcfcfc}.wy-menu-vertical li.current>a:hover button.toctree-expand,.wy-menu-vertical li.on a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.current>a button.toctree-expand,.wy-menu-vertical li.on a button.toctree-expand{display:block;line-height:18px;color:#333}.wy-menu-vertical li.toctree-l1.current>a{border-bottom:1px solid #c9c9c9;border-top:1px solid #c9c9c9}.wy-menu-vertical .toctree-l1.current .toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .toctree-l11>ul{display:none}.wy-menu-vertical .toctree-l1.current .current.toctree-l2>ul,.wy-menu-vertical .toctree-l2.current .current.toctree-l3>ul,.wy-menu-vertical .toctree-l3.current .current.toctree-l4>ul,.wy-menu-vertical .toctree-l4.current .current.toctree-l5>ul,.wy-menu-vertical .toctree-l5.current .current.toctree-l6>ul,.wy-menu-vertical .toctree-l6.current .current.toctree-l7>ul,.wy-menu-vertical .toctree-l7.current .current.toctree-l8>ul,.wy-menu-vertical .toctree-l8.current .current.toctree-l9>ul,.wy-menu-vertical .toctree-l9.current .current.toctree-l10>ul,.wy-menu-vertical .toctree-l10.current .current.toctree-l11>ul{display:block}.wy-menu-vertical li.toctree-l3,.wy-menu-vertical li.toctree-l4{font-size:.9em}.wy-menu-vertical li.toctree-l2 a,.wy-menu-vertical li.toctree-l3 a,.wy-menu-vertical li.toctree-l4 a,.wy-menu-vertical li.toctree-l5 a,.wy-menu-vertical li.toctree-l6 a,.wy-menu-vertical li.toctree-l7 a,.wy-menu-vertical li.toctree-l8 a,.wy-menu-vertical li.toctree-l9 a,.wy-menu-vertical li.toctree-l10 a{color:#404040}.wy-menu-vertical li.toctree-l2 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l3 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l4 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l5 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l6 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l7 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l8 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l9 a:hover button.toctree-expand,.wy-menu-vertical li.toctree-l10 a:hover button.toctree-expand{color:grey}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a,.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a,.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a,.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a,.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a,.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a,.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a,.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{display:block}.wy-menu-vertical li.toctree-l2.current>a{padding:.4045em 2.427em}.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{padding:.4045em 1.618em .4045em 4.045em}.wy-menu-vertical li.toctree-l3.current>a{padding:.4045em 4.045em}.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{padding:.4045em 1.618em .4045em 5.663em}.wy-menu-vertical li.toctree-l4.current>a{padding:.4045em 5.663em}.wy-menu-vertical li.toctree-l4.current li.toctree-l5>a{padding:.4045em 1.618em .4045em 7.281em}.wy-menu-vertical li.toctree-l5.current>a{padding:.4045em 7.281em}.wy-menu-vertical li.toctree-l5.current li.toctree-l6>a{padding:.4045em 1.618em .4045em 8.899em}.wy-menu-vertical li.toctree-l6.current>a{padding:.4045em 8.899em}.wy-menu-vertical li.toctree-l6.current li.toctree-l7>a{padding:.4045em 1.618em .4045em 10.517em}.wy-menu-vertical li.toctree-l7.current>a{padding:.4045em 10.517em}.wy-menu-vertical li.toctree-l7.current li.toctree-l8>a{padding:.4045em 1.618em .4045em 12.135em}.wy-menu-vertical li.toctree-l8.current>a{padding:.4045em 12.135em}.wy-menu-vertical li.toctree-l8.current li.toctree-l9>a{padding:.4045em 1.618em .4045em 13.753em}.wy-menu-vertical li.toctree-l9.current>a{padding:.4045em 13.753em}.wy-menu-vertical li.toctree-l9.current li.toctree-l10>a{padding:.4045em 1.618em .4045em 15.371em}.wy-menu-vertical li.toctree-l10.current>a{padding:.4045em 15.371em}.wy-menu-vertical li.toctree-l10.current li.toctree-l11>a{padding:.4045em 1.618em .4045em 16.989em}.wy-menu-vertical li.toctree-l2.current>a,.wy-menu-vertical li.toctree-l2.current li.toctree-l3>a{background:#c9c9c9}.wy-menu-vertical li.toctree-l2 button.toctree-expand{color:#a3a3a3}.wy-menu-vertical li.toctree-l3.current>a,.wy-menu-vertical li.toctree-l3.current li.toctree-l4>a{background:#bdbdbd}.wy-menu-vertical li.toctree-l3 button.toctree-expand{color:#969696}.wy-menu-vertical li.current ul{display:block}.wy-menu-vertical li ul{margin-bottom:0;display:none}.wy-menu-vertical li ul li a{margin-bottom:0;color:#d9d9d9;font-weight:400}.wy-menu-vertical a{line-height:18px;padding:.4045em 1.618em;display:block;position:relative;font-size:90%;color:#d9d9d9}.wy-menu-vertical a:hover{background-color:#4e4a4a;cursor:pointer}.wy-menu-vertical a:hover button.toctree-expand{color:#d9d9d9}.wy-menu-vertical a:active{background-color:#2980b9;cursor:pointer;color:#fff}.wy-menu-vertical a:active button.toctree-expand{color:#fff}.wy-side-nav-search{display:block;width:300px;padding:.809em;margin-bottom:.809em;z-index:200;background-color:#2980b9;text-align:center;color:#fcfcfc}.wy-side-nav-search input[type=text]{width:100%;border-radius:50px;padding:6px 12px;border-color:#2472a4}.wy-side-nav-search img{display:block;margin:auto auto .809em;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-side-nav-search .wy-dropdown>a,.wy-side-nav-search>a{color:#fcfcfc;font-size:100%;font-weight:700;display:inline-block;padding:4px 6px;margin-bottom:.809em;max-width:100%}.wy-side-nav-search .wy-dropdown>a:hover,.wy-side-nav-search>a:hover{background:hsla(0,0%,100%,.1)}.wy-side-nav-search .wy-dropdown>a img.logo,.wy-side-nav-search>a img.logo{display:block;margin:0 auto;height:auto;width:auto;border-radius:0;max-width:100%;background:transparent}.wy-side-nav-search .wy-dropdown>a.icon img.logo,.wy-side-nav-search>a.icon img.logo{margin-top:.85em}.wy-side-nav-search>div.version{margin-top:-.4045em;margin-bottom:.809em;font-weight:400;color:hsla(0,0%,100%,.3)}.wy-nav .wy-menu-vertical header{color:#2980b9}.wy-nav .wy-menu-vertical a{color:#b3b3b3}.wy-nav .wy-menu-vertical a:hover{background-color:#2980b9;color:#fff}[data-menu-wrap]{-webkit-transition:all .2s ease-in;-moz-transition:all .2s ease-in;transition:all .2s ease-in;position:absolute;opacity:1;width:100%;opacity:0}[data-menu-wrap].move-center{left:0;right:auto;opacity:1}[data-menu-wrap].move-left{right:auto;left:-100%;opacity:0}[data-menu-wrap].move-right{right:-100%;left:auto;opacity:0}.wy-body-for-nav{background:#fcfcfc}.wy-grid-for-nav{position:absolute;width:100%;height:100%}.wy-nav-side{position:fixed;top:0;bottom:0;left:0;padding-bottom:2em;width:300px;overflow-x:hidden;overflow-y:hidden;min-height:100%;color:#9b9b9b;background:#343131;z-index:200}.wy-side-scroll{width:320px;position:relative;overflow-x:hidden;overflow-y:scroll;height:100%}.wy-nav-top{display:none;background:#2980b9;color:#fff;padding:.4045em .809em;position:relative;line-height:50px;text-align:center;font-size:100%;*zoom:1}.wy-nav-top:after,.wy-nav-top:before{display:table;content:""}.wy-nav-top:after{clear:both}.wy-nav-top a{color:#fff;font-weight:700}.wy-nav-top img{margin-right:12px;height:45px;width:45px;background-color:#2980b9;padding:5px;border-radius:100%}.wy-nav-top i{font-size:30px;float:left;cursor:pointer;padding-top:inherit}.wy-nav-content-wrap{margin-left:300px;background:#fcfcfc;min-height:100%}.wy-nav-content{padding:1.618em 3.236em;height:100%;max-width:800px;margin:auto}.wy-body-mask{position:fixed;width:100%;height:100%;background:rgba(0,0,0,.2);display:none;z-index:499}.wy-body-mask.on{display:block}footer{color:grey}footer p{margin-bottom:12px}.rst-content footer span.commit tt,footer span.commit .rst-content tt,footer span.commit code{padding:0;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:1em;background:none;border:none;color:grey}.rst-footer-buttons{*zoom:1}.rst-footer-buttons:after,.rst-footer-buttons:before{width:100%;display:table;content:""}.rst-footer-buttons:after{clear:both}.rst-breadcrumbs-buttons{margin-top:12px;*zoom:1}.rst-breadcrumbs-buttons:after,.rst-breadcrumbs-buttons:before{display:table;content:""}.rst-breadcrumbs-buttons:after{clear:both}#search-results .search li{margin-bottom:24px;border-bottom:1px solid #e1e4e5;padding-bottom:24px}#search-results .search li:first-child{border-top:1px solid #e1e4e5;padding-top:24px}#search-results .search li a{font-size:120%;margin-bottom:12px;display:inline-block}#search-results .context{color:grey;font-size:90%}.genindextable li>ul{margin-left:24px}@media screen and (max-width:768px){.wy-body-for-nav{background:#fcfcfc}.wy-nav-top{display:block}.wy-nav-side{left:-300px}.wy-nav-side.shift{width:85%;left:0}.wy-menu.wy-menu-vertical,.wy-side-nav-search,.wy-side-scroll{width:auto}.wy-nav-content-wrap{margin-left:0}.wy-nav-content-wrap .wy-nav-content{padding:1.618em}.wy-nav-content-wrap.shift{position:fixed;min-width:100%;left:85%;top:0;height:100%;overflow:hidden}}@media screen and (min-width:1100px){.wy-nav-content-wrap{background:rgba(0,0,0,.05)}.wy-nav-content{margin:0;background:#fcfcfc}}@media print{.rst-versions,.wy-nav-side,footer{display:none}.wy-nav-content-wrap{margin-left:0}}.rst-versions{position:fixed;bottom:0;left:0;width:300px;color:#fcfcfc;background:#1f1d1d;font-family:Lato,proxima-nova,Helvetica Neue,Arial,sans-serif;z-index:400}.rst-versions a{color:#2980b9;text-decoration:none}.rst-versions .rst-badge-small{display:none}.rst-versions .rst-current-version{padding:12px;background-color:#272525;display:block;text-align:right;font-size:90%;cursor:pointer;color:#27ae60;*zoom:1}.rst-versions .rst-current-version:after,.rst-versions .rst-current-version:before{display:table;content:""}.rst-versions .rst-current-version:after{clear:both}.rst-content .code-block-caption .rst-versions .rst-current-version .headerlink,.rst-content .eqno .rst-versions .rst-current-version .headerlink,.rst-content .rst-versions .rst-current-version .admonition-title,.rst-content code.download .rst-versions .rst-current-version span:first-child,.rst-content dl dt .rst-versions .rst-current-version .headerlink,.rst-content h1 .rst-versions .rst-current-version .headerlink,.rst-content h2 .rst-versions .rst-current-version .headerlink,.rst-content h3 .rst-versions .rst-current-version .headerlink,.rst-content h4 .rst-versions .rst-current-version .headerlink,.rst-content h5 .rst-versions .rst-current-version .headerlink,.rst-content h6 .rst-versions .rst-current-version .headerlink,.rst-content p .rst-versions .rst-current-version .headerlink,.rst-content table>caption .rst-versions .rst-current-version .headerlink,.rst-content tt.download .rst-versions .rst-current-version span:first-child,.rst-versions .rst-current-version .fa,.rst-versions .rst-current-version .icon,.rst-versions .rst-current-version .rst-content .admonition-title,.rst-versions .rst-current-version .rst-content .code-block-caption .headerlink,.rst-versions .rst-current-version .rst-content .eqno .headerlink,.rst-versions .rst-current-version .rst-content code.download span:first-child,.rst-versions .rst-current-version .rst-content dl dt .headerlink,.rst-versions .rst-current-version .rst-content h1 .headerlink,.rst-versions .rst-current-version .rst-content h2 .headerlink,.rst-versions .rst-current-version .rst-content h3 .headerlink,.rst-versions .rst-current-version .rst-content h4 .headerlink,.rst-versions .rst-current-version .rst-content h5 .headerlink,.rst-versions .rst-current-version .rst-content h6 .headerlink,.rst-versions .rst-current-version .rst-content p .headerlink,.rst-versions .rst-current-version .rst-content table>caption .headerlink,.rst-versions .rst-current-version .rst-content tt.download span:first-child,.rst-versions .rst-current-version .wy-menu-vertical li button.toctree-expand,.wy-menu-vertical li .rst-versions .rst-current-version button.toctree-expand{color:#fcfcfc}.rst-versions .rst-current-version .fa-book,.rst-versions .rst-current-version .icon-book{float:left}.rst-versions .rst-current-version.rst-out-of-date{background-color:#e74c3c;color:#fff}.rst-versions .rst-current-version.rst-active-old-version{background-color:#f1c40f;color:#000}.rst-versions.shift-up{height:auto;max-height:100%;overflow-y:scroll}.rst-versions.shift-up .rst-other-versions{display:block}.rst-versions .rst-other-versions{font-size:90%;padding:12px;color:grey;display:none}.rst-versions .rst-other-versions hr{display:block;height:1px;border:0;margin:20px 0;padding:0;border-top:1px solid #413d3d}.rst-versions .rst-other-versions dd{display:inline-block;margin:0}.rst-versions .rst-other-versions dd a{display:inline-block;padding:6px;color:#fcfcfc}.rst-versions.rst-badge{width:auto;bottom:20px;right:20px;left:auto;border:none;max-width:300px;max-height:90%}.rst-versions.rst-badge .fa-book,.rst-versions.rst-badge .icon-book{float:none;line-height:30px}.rst-versions.rst-badge.shift-up .rst-current-version{text-align:right}.rst-versions.rst-badge.shift-up .rst-current-version .fa-book,.rst-versions.rst-badge.shift-up .rst-current-version .icon-book{float:left}.rst-versions.rst-badge>.rst-current-version{width:auto;height:30px;line-height:30px;padding:0 6px;display:block;text-align:center}@media screen and (max-width:768px){.rst-versions{width:85%;display:none}.rst-versions.shift{display:block}}.rst-content .toctree-wrapper>p.caption,.rst-content h1,.rst-content h2,.rst-content h3,.rst-content h4,.rst-content h5,.rst-content h6{margin-bottom:24px}.rst-content img{max-width:100%;height:auto}.rst-content div.figure,.rst-content figure{margin-bottom:24px}.rst-content div.figure .caption-text,.rst-content figure .caption-text{font-style:italic}.rst-content div.figure p:last-child.caption,.rst-content figure p:last-child.caption{margin-bottom:0}.rst-content div.figure.align-center,.rst-content figure.align-center{text-align:center}.rst-content .section>a>img,.rst-content .section>img,.rst-content section>a>img,.rst-content section>img{margin-bottom:24px}.rst-content abbr[title]{text-decoration:none}.rst-content.style-external-links a.reference.external:after{font-family:FontAwesome;content:"\f08e";color:#b3b3b3;vertical-align:super;font-size:60%;margin:0 .2em}.rst-content blockquote{margin-left:24px;line-height:24px;margin-bottom:24px}.rst-content pre.literal-block{white-space:pre;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;display:block;overflow:auto}.rst-content div[class^=highlight],.rst-content pre.literal-block{border:1px solid #e1e4e5;overflow-x:auto;margin:1px 0 24px}.rst-content div[class^=highlight] div[class^=highlight],.rst-content pre.literal-block div[class^=highlight]{padding:0;border:none;margin:0}.rst-content div[class^=highlight] td.code{width:100%}.rst-content .linenodiv pre{border-right:1px solid #e6e9ea;margin:0;padding:12px;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;user-select:none;pointer-events:none}.rst-content div[class^=highlight] pre{white-space:pre;margin:0;padding:12px;display:block;overflow:auto}.rst-content div[class^=highlight] pre .hll{display:block;margin:0 -12px;padding:0 12px}.rst-content .linenodiv pre,.rst-content div[class^=highlight] pre,.rst-content pre.literal-block{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;font-size:12px;line-height:1.4}.rst-content div.highlight .gp,.rst-content div.highlight span.linenos{user-select:none;pointer-events:none}.rst-content div.highlight span.linenos{display:inline-block;padding-left:0;padding-right:12px;margin-right:12px;border-right:1px solid #e6e9ea}.rst-content .code-block-caption{font-style:italic;font-size:85%;line-height:1;padding:1em 0;text-align:center}@media print{.rst-content .codeblock,.rst-content div[class^=highlight],.rst-content div[class^=highlight] pre{white-space:pre-wrap}}.rst-content .admonition,.rst-content .admonition-todo,.rst-content .attention,.rst-content .caution,.rst-content .danger,.rst-content .error,.rst-content .hint,.rst-content .important,.rst-content .note,.rst-content .seealso,.rst-content .tip,.rst-content .warning{clear:both}.rst-content .admonition-todo .last,.rst-content .admonition-todo>:last-child,.rst-content .admonition .last,.rst-content .admonition>:last-child,.rst-content .attention .last,.rst-content .attention>:last-child,.rst-content .caution .last,.rst-content .caution>:last-child,.rst-content .danger .last,.rst-content .danger>:last-child,.rst-content .error .last,.rst-content .error>:last-child,.rst-content .hint .last,.rst-content .hint>:last-child,.rst-content .important .last,.rst-content .important>:last-child,.rst-content .note .last,.rst-content .note>:last-child,.rst-content .seealso .last,.rst-content .seealso>:last-child,.rst-content .tip .last,.rst-content .tip>:last-child,.rst-content .warning .last,.rst-content .warning>:last-child{margin-bottom:0}.rst-content .admonition-title:before{margin-right:4px}.rst-content .admonition table{border-color:rgba(0,0,0,.1)}.rst-content .admonition table td,.rst-content .admonition table th{background:transparent!important;border-color:rgba(0,0,0,.1)!important}.rst-content .section ol.loweralpha,.rst-content .section ol.loweralpha>li,.rst-content .toctree-wrapper ol.loweralpha,.rst-content .toctree-wrapper ol.loweralpha>li,.rst-content section ol.loweralpha,.rst-content section ol.loweralpha>li{list-style:lower-alpha}.rst-content .section ol.upperalpha,.rst-content .section ol.upperalpha>li,.rst-content .toctree-wrapper ol.upperalpha,.rst-content .toctree-wrapper ol.upperalpha>li,.rst-content section ol.upperalpha,.rst-content section ol.upperalpha>li{list-style:upper-alpha}.rst-content .section ol li>*,.rst-content .section ul li>*,.rst-content .toctree-wrapper ol li>*,.rst-content .toctree-wrapper ul li>*,.rst-content section ol li>*,.rst-content section ul li>*{margin-top:12px;margin-bottom:12px}.rst-content .section ol li>:first-child,.rst-content .section ul li>:first-child,.rst-content .toctree-wrapper ol li>:first-child,.rst-content .toctree-wrapper ul li>:first-child,.rst-content section ol li>:first-child,.rst-content section ul li>:first-child{margin-top:0}.rst-content .section ol li>p,.rst-content .section ol li>p:last-child,.rst-content .section ul li>p,.rst-content .section ul li>p:last-child,.rst-content .toctree-wrapper ol li>p,.rst-content .toctree-wrapper ol li>p:last-child,.rst-content .toctree-wrapper ul li>p,.rst-content .toctree-wrapper ul li>p:last-child,.rst-content section ol li>p,.rst-content section ol li>p:last-child,.rst-content section ul li>p,.rst-content section ul li>p:last-child{margin-bottom:12px}.rst-content .section ol li>p:only-child,.rst-content .section ol li>p:only-child:last-child,.rst-content .section ul li>p:only-child,.rst-content .section ul li>p:only-child:last-child,.rst-content .toctree-wrapper ol li>p:only-child,.rst-content .toctree-wrapper ol li>p:only-child:last-child,.rst-content .toctree-wrapper ul li>p:only-child,.rst-content .toctree-wrapper ul li>p:only-child:last-child,.rst-content section ol li>p:only-child,.rst-content section ol li>p:only-child:last-child,.rst-content section ul li>p:only-child,.rst-content section ul li>p:only-child:last-child{margin-bottom:0}.rst-content .section ol li>ol,.rst-content .section ol li>ul,.rst-content .section ul li>ol,.rst-content .section ul li>ul,.rst-content .toctree-wrapper ol li>ol,.rst-content .toctree-wrapper ol li>ul,.rst-content .toctree-wrapper ul li>ol,.rst-content .toctree-wrapper ul li>ul,.rst-content section ol li>ol,.rst-content section ol li>ul,.rst-content section ul li>ol,.rst-content section ul li>ul{margin-bottom:12px}.rst-content .section ol.simple li>*,.rst-content .section ol.simple li ol,.rst-content .section ol.simple li ul,.rst-content .section ul.simple li>*,.rst-content .section ul.simple li ol,.rst-content .section ul.simple li ul,.rst-content .toctree-wrapper ol.simple li>*,.rst-content .toctree-wrapper ol.simple li ol,.rst-content .toctree-wrapper ol.simple li ul,.rst-content .toctree-wrapper ul.simple li>*,.rst-content .toctree-wrapper ul.simple li ol,.rst-content .toctree-wrapper ul.simple li ul,.rst-content section ol.simple li>*,.rst-content section ol.simple li ol,.rst-content section ol.simple li ul,.rst-content section ul.simple li>*,.rst-content section ul.simple li ol,.rst-content section ul.simple li ul{margin-top:0;margin-bottom:0}.rst-content .line-block{margin-left:0;margin-bottom:24px;line-height:24px}.rst-content .line-block .line-block{margin-left:24px;margin-bottom:0}.rst-content .topic-title{font-weight:700;margin-bottom:12px}.rst-content .toc-backref{color:#404040}.rst-content .align-right{float:right;margin:0 0 24px 24px}.rst-content .align-left{float:left;margin:0 24px 24px 0}.rst-content .align-center{margin:auto}.rst-content .align-center:not(table){display:block}.rst-content .code-block-caption .headerlink,.rst-content .eqno .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink,.rst-content dl dt .headerlink,.rst-content h1 .headerlink,.rst-content h2 .headerlink,.rst-content h3 .headerlink,.rst-content h4 .headerlink,.rst-content h5 .headerlink,.rst-content h6 .headerlink,.rst-content p.caption .headerlink,.rst-content p .headerlink,.rst-content table>caption .headerlink{opacity:0;font-size:14px;font-family:FontAwesome;margin-left:.5em}.rst-content .code-block-caption .headerlink:focus,.rst-content .code-block-caption:hover .headerlink,.rst-content .eqno .headerlink:focus,.rst-content .eqno:hover .headerlink,.rst-content .toctree-wrapper>p.caption .headerlink:focus,.rst-content .toctree-wrapper>p.caption:hover .headerlink,.rst-content dl dt .headerlink:focus,.rst-content dl dt:hover .headerlink,.rst-content h1 .headerlink:focus,.rst-content h1:hover .headerlink,.rst-content h2 .headerlink:focus,.rst-content h2:hover .headerlink,.rst-content h3 .headerlink:focus,.rst-content h3:hover .headerlink,.rst-content h4 .headerlink:focus,.rst-content h4:hover .headerlink,.rst-content h5 .headerlink:focus,.rst-content h5:hover .headerlink,.rst-content h6 .headerlink:focus,.rst-content h6:hover .headerlink,.rst-content p.caption .headerlink:focus,.rst-content p.caption:hover .headerlink,.rst-content p .headerlink:focus,.rst-content p:hover .headerlink,.rst-content table>caption .headerlink:focus,.rst-content table>caption:hover .headerlink{opacity:1}.rst-content p a{overflow-wrap:anywhere}.rst-content .wy-table td p,.rst-content .wy-table td ul,.rst-content .wy-table th p,.rst-content .wy-table th ul,.rst-content table.docutils td p,.rst-content table.docutils td ul,.rst-content table.docutils th p,.rst-content table.docutils th ul,.rst-content table.field-list td p,.rst-content table.field-list td ul,.rst-content table.field-list th p,.rst-content table.field-list th ul{font-size:inherit}.rst-content .btn:focus{outline:2px solid}.rst-content table>caption .headerlink:after{font-size:12px}.rst-content .centered{text-align:center}.rst-content .sidebar{float:right;width:40%;display:block;margin:0 0 24px 24px;padding:24px;background:#f3f6f6;border:1px solid #e1e4e5}.rst-content .sidebar dl,.rst-content .sidebar p,.rst-content .sidebar ul{font-size:90%}.rst-content .sidebar .last,.rst-content .sidebar>:last-child{margin-bottom:0}.rst-content .sidebar .sidebar-title{display:block;font-family:Roboto Slab,ff-tisa-web-pro,Georgia,Arial,sans-serif;font-weight:700;background:#e1e4e5;padding:6px 12px;margin:-24px -24px 24px;font-size:100%}.rst-content .highlighted{background:#f1c40f;box-shadow:0 0 0 2px #f1c40f;display:inline;font-weight:700}.rst-content .citation-reference,.rst-content .footnote-reference{vertical-align:baseline;position:relative;top:-.4em;line-height:0;font-size:90%}.rst-content .citation-reference>span.fn-bracket,.rst-content .footnote-reference>span.fn-bracket{display:none}.rst-content .hlist{width:100%}.rst-content dl dt span.classifier:before{content:" : "}.rst-content dl dt span.classifier-delimiter{display:none!important}html.writer-html4 .rst-content table.docutils.citation,html.writer-html4 .rst-content table.docutils.footnote{background:none;border:none}html.writer-html4 .rst-content table.docutils.citation td,html.writer-html4 .rst-content table.docutils.citation tr,html.writer-html4 .rst-content table.docutils.footnote td,html.writer-html4 .rst-content table.docutils.footnote tr{border:none;background-color:transparent!important;white-space:normal}html.writer-html4 .rst-content table.docutils.citation td.label,html.writer-html4 .rst-content table.docutils.footnote td.label{padding-left:0;padding-right:0;vertical-align:top}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{display:grid;grid-template-columns:auto minmax(80%,95%)}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{display:inline-grid;grid-template-columns:max-content auto}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{display:grid;grid-template-columns:auto auto minmax(.65rem,auto) minmax(40%,95%)}html.writer-html5 .rst-content aside.citation>span.label,html.writer-html5 .rst-content aside.footnote>span.label,html.writer-html5 .rst-content div.citation>span.label{grid-column-start:1;grid-column-end:2}html.writer-html5 .rst-content aside.citation>span.backrefs,html.writer-html5 .rst-content aside.footnote>span.backrefs,html.writer-html5 .rst-content div.citation>span.backrefs{grid-column-start:2;grid-column-end:3;grid-row-start:1;grid-row-end:3}html.writer-html5 .rst-content aside.citation>p,html.writer-html5 .rst-content aside.footnote>p,html.writer-html5 .rst-content div.citation>p{grid-column-start:4;grid-column-end:5}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.field-list,html.writer-html5 .rst-content dl.footnote{margin-bottom:24px}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dt{padding-left:1rem}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.field-list>dd,html.writer-html5 .rst-content dl.field-list>dt,html.writer-html5 .rst-content dl.footnote>dd,html.writer-html5 .rst-content dl.footnote>dt{margin-bottom:0}html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{font-size:.9rem}html.writer-html5 .rst-content dl.citation>dt,html.writer-html5 .rst-content dl.footnote>dt{margin:0 .5rem .5rem 0;line-height:1.2rem;word-break:break-all;font-weight:400}html.writer-html5 .rst-content dl.citation>dt>span.brackets:before,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:before{content:"["}html.writer-html5 .rst-content dl.citation>dt>span.brackets:after,html.writer-html5 .rst-content dl.footnote>dt>span.brackets:after{content:"]"}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a{word-break:keep-all}html.writer-html5 .rst-content dl.citation>dt>span.fn-backref>a:not(:first-child):before,html.writer-html5 .rst-content dl.footnote>dt>span.fn-backref>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content dl.citation>dd,html.writer-html5 .rst-content dl.footnote>dd{margin:0 0 .5rem;line-height:1.2rem}html.writer-html5 .rst-content dl.citation>dd p,html.writer-html5 .rst-content dl.footnote>dd p{font-size:.9rem}html.writer-html5 .rst-content aside.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content div.citation{padding-left:1rem;padding-right:1rem;font-size:.9rem;line-height:1.2rem}html.writer-html5 .rst-content aside.citation p,html.writer-html5 .rst-content aside.footnote p,html.writer-html5 .rst-content div.citation p{font-size:.9rem;line-height:1.2rem;margin-bottom:12px}html.writer-html5 .rst-content aside.citation span.backrefs,html.writer-html5 .rst-content aside.footnote span.backrefs,html.writer-html5 .rst-content div.citation span.backrefs{text-align:left;font-style:italic;margin-left:.65rem;word-break:break-word;word-spacing:-.1rem;max-width:5rem}html.writer-html5 .rst-content aside.citation span.backrefs>a,html.writer-html5 .rst-content aside.footnote span.backrefs>a,html.writer-html5 .rst-content div.citation span.backrefs>a{word-break:keep-all}html.writer-html5 .rst-content aside.citation span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content aside.footnote span.backrefs>a:not(:first-child):before,html.writer-html5 .rst-content div.citation span.backrefs>a:not(:first-child):before{content:" "}html.writer-html5 .rst-content aside.citation span.label,html.writer-html5 .rst-content aside.footnote span.label,html.writer-html5 .rst-content div.citation span.label{line-height:1.2rem}html.writer-html5 .rst-content aside.citation-list,html.writer-html5 .rst-content aside.footnote-list,html.writer-html5 .rst-content div.citation-list{margin-bottom:24px}html.writer-html5 .rst-content dl.option-list kbd{font-size:.9rem}.rst-content table.docutils.footnote,html.writer-html4 .rst-content table.docutils.citation,html.writer-html5 .rst-content aside.footnote,html.writer-html5 .rst-content aside.footnote-list aside.footnote,html.writer-html5 .rst-content div.citation-list>div.citation,html.writer-html5 .rst-content dl.citation,html.writer-html5 .rst-content dl.footnote{color:grey}.rst-content table.docutils.footnote code,.rst-content table.docutils.footnote tt,html.writer-html4 .rst-content table.docutils.citation code,html.writer-html4 .rst-content table.docutils.citation tt,html.writer-html5 .rst-content aside.footnote-list aside.footnote code,html.writer-html5 .rst-content aside.footnote-list aside.footnote tt,html.writer-html5 .rst-content aside.footnote code,html.writer-html5 .rst-content aside.footnote tt,html.writer-html5 .rst-content div.citation-list>div.citation code,html.writer-html5 .rst-content div.citation-list>div.citation tt,html.writer-html5 .rst-content dl.citation code,html.writer-html5 .rst-content dl.citation tt,html.writer-html5 .rst-content dl.footnote code,html.writer-html5 .rst-content dl.footnote tt{color:#555}.rst-content .wy-table-responsive.citation,.rst-content .wy-table-responsive.footnote{margin-bottom:0}.rst-content .wy-table-responsive.citation+:not(.citation),.rst-content .wy-table-responsive.footnote+:not(.footnote){margin-top:24px}.rst-content .wy-table-responsive.citation:last-child,.rst-content .wy-table-responsive.footnote:last-child{margin-bottom:24px}.rst-content table.docutils th{border-color:#e1e4e5}html.writer-html5 .rst-content table.docutils th{border:1px solid #e1e4e5}html.writer-html5 .rst-content table.docutils td>p,html.writer-html5 .rst-content table.docutils th>p{line-height:1rem;margin-bottom:0;font-size:.9rem}.rst-content table.docutils td .last,.rst-content table.docutils td .last>:last-child{margin-bottom:0}.rst-content table.field-list,.rst-content table.field-list td{border:none}.rst-content table.field-list td p{line-height:inherit}.rst-content table.field-list td>strong{display:inline-block}.rst-content table.field-list .field-name{padding-right:10px;text-align:left;white-space:nowrap}.rst-content table.field-list .field-body{text-align:left}.rst-content code,.rst-content tt{color:#000;font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;padding:2px 5px}.rst-content code big,.rst-content code em,.rst-content tt big,.rst-content tt em{font-size:100%!important;line-height:normal}.rst-content code.literal,.rst-content tt.literal{color:#e74c3c;white-space:normal}.rst-content code.xref,.rst-content tt.xref,a .rst-content code,a .rst-content tt{font-weight:700;color:#404040;overflow-wrap:normal}.rst-content kbd,.rst-content pre,.rst-content samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace}.rst-content a code,.rst-content a tt{color:#2980b9}.rst-content dl{margin-bottom:24px}.rst-content dl dt{font-weight:700;margin-bottom:12px}.rst-content dl ol,.rst-content dl p,.rst-content dl table,.rst-content dl ul{margin-bottom:12px}.rst-content dl dd{margin:0 0 12px 24px;line-height:24px}.rst-content dl dd>ol:last-child,.rst-content dl dd>p:last-child,.rst-content dl dd>table:last-child,.rst-content dl dd>ul:last-child{margin-bottom:0}html.writer-html4 .rst-content dl:not(.docutils),html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple){margin-bottom:24px}html.writer-html4 .rst-content dl:not(.docutils)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{display:table;margin:6px 0;font-size:90%;line-height:normal;background:#e7f2fa;color:#2980b9;border-top:3px solid #6ab0de;padding:6px;position:relative}html.writer-html4 .rst-content dl:not(.docutils)>dt:before,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:before{color:#6ab0de}html.writer-html4 .rst-content dl:not(.docutils)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt{margin-bottom:6px;border:none;border-left:3px solid #ccc;background:#f0f0f0;color:#555}html.writer-html4 .rst-content dl:not(.docutils) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) dl:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt .headerlink{color:#404040;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils)>dt:first-child,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple)>dt:first-child{margin-top:0}html.writer-html4 .rst-content dl:not(.docutils) code.descclassname,html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descclassname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{background-color:transparent;border:none;padding:0;font-size:100%!important}html.writer-html4 .rst-content dl:not(.docutils) code.descname,html.writer-html4 .rst-content dl:not(.docutils) tt.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) code.descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) tt.descname{font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .optional,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .optional{display:inline-block;padding:0 4px;color:#000;font-weight:700}html.writer-html4 .rst-content dl:not(.docutils) .property,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .property{display:inline-block;padding-right:8px;max-width:100%}html.writer-html4 .rst-content dl:not(.docutils) .k,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .k{font-style:italic}html.writer-html4 .rst-content dl:not(.docutils) .descclassname,html.writer-html4 .rst-content dl:not(.docutils) .descname,html.writer-html4 .rst-content dl:not(.docutils) .sig-name,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descclassname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .descname,html.writer-html5 .rst-content dl[class]:not(.option-list):not(.field-list):not(.footnote):not(.citation):not(.glossary):not(.simple) .sig-name{font-family:SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,Courier,monospace;color:#000}.rst-content .viewcode-back,.rst-content .viewcode-link{display:inline-block;color:#27ae60;font-size:80%;padding-left:24px}.rst-content .viewcode-back{display:block;float:right}.rst-content p.rubric{margin-bottom:12px;font-weight:700}.rst-content code.download,.rst-content tt.download{background:inherit;padding:inherit;font-weight:400;font-family:inherit;font-size:inherit;color:inherit;border:inherit;white-space:inherit}.rst-content code.download span:first-child,.rst-content tt.download span:first-child{-webkit-font-smoothing:subpixel-antialiased}.rst-content code.download span:first-child:before,.rst-content tt.download span:first-child:before{margin-right:4px}.rst-content .guilabel{border:1px solid #7fbbe3;background:#e7f2fa;font-size:80%;font-weight:700;border-radius:4px;padding:2.4px 6px;margin:auto 2px}.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>.kbd,.rst-content :not(dl.option-list)>:not(dt):not(kbd):not(.kbd)>kbd{color:inherit;font-size:80%;background-color:#fff;border:1px solid #a6a6a6;border-radius:4px;box-shadow:0 2px grey;padding:2.4px 6px;margin:auto 0}.rst-content .versionmodified{font-style:italic}@media screen and (max-width:480px){.rst-content .sidebar{width:100%}}span[id*=MathJax-Span]{color:#404040}.math{text-align:center}@font-face{font-family:Lato;src:url(fonts/lato-normal.woff2?bd03a2cc277bbbc338d464e679fe9942) format("woff2"),url(fonts/lato-normal.woff?27bd77b9162d388cb8d4c4217c7c5e2a) format("woff");font-weight:400;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold.woff2?cccb897485813c7c256901dbca54ecf2) format("woff2"),url(fonts/lato-bold.woff?d878b6c29b10beca227e9eef4246111b) format("woff");font-weight:700;font-style:normal;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-bold-italic.woff2?0b6bb6725576b072c5d0b02ecdd1900d) format("woff2"),url(fonts/lato-bold-italic.woff?9c7e4e9eb485b4a121c760e61bc3707c) format("woff");font-weight:700;font-style:italic;font-display:block}@font-face{font-family:Lato;src:url(fonts/lato-normal-italic.woff2?4eb103b4d12be57cb1d040ed5e162e9d) format("woff2"),url(fonts/lato-normal-italic.woff?f28f2d6482446544ef1ea1ccc6dd5892) format("woff");font-weight:400;font-style:italic;font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:400;src:url(fonts/Roboto-Slab-Regular.woff2?7abf5b8d04d26a2cafea937019bca958) format("woff2"),url(fonts/Roboto-Slab-Regular.woff?c1be9284088d487c5e3ff0a10a92e58c) format("woff");font-display:block}@font-face{font-family:Roboto Slab;font-style:normal;font-weight:700;src:url(fonts/Roboto-Slab-Bold.woff2?9984f4a9bda09be08e83f2506954adbe) format("woff2"),url(fonts/Roboto-Slab-Bold.woff?bed5564a116b05148e3b3bea6fb1162a) format("woff");font-display:block} \ No newline at end of file diff --git a/v2.4.1/_static/device_display.gif b/v2.4.1/_static/device_display.gif new file mode 100644 index 000000000..67a85402f Binary files /dev/null and b/v2.4.1/_static/device_display.gif differ diff --git a/v2.4.1/_static/docs-versions-menu.js b/v2.4.1/_static/docs-versions-menu.js new file mode 100644 index 000000000..696095a6c --- /dev/null +++ b/v2.4.1/_static/docs-versions-menu.js @@ -0,0 +1,148 @@ +"use strict"; + +function getGhPagesCurrentFolder() { + // Extract version folder under the assumpgion that the URL is of the form + // https://.github.io///... + if (window.location.hostname.includes("github.io")){ + return window.location.pathname.split('/')[2]; + } +} + +function getRootUrl() { + // Return the "root" URL, i.e. everything before the current folder + // (getGhPagesCurrentFolder). On gh-pages, this includes the project name. + var root_url = window.location.origin; + if (window.location.hostname.includes("github.io")){ + root_url = root_url + '/' + window.location.pathname.split('/')[1]; + } + return root_url; +} + +function getGithubProjectUrl(){ + // Return the project url on Github, under the assumption that the current + // page is hosted on github-pages (https://.github.io//) + var root_url = getRootUrl(); + var match = root_url.match(/([\w\d-]+)\.github\.io\/([\w\d-]+)/) + if (match !== null){ + var username = match[1]; + var projectname = match[2]; + return "https://github.com/" + username + "/" + projectname; + } else { + return null + } +} + +function _addVersionsMenu(version_data) { + // The menu was reverse-engineered from the RTD websites, so it's very + // specific to the sphinx_rtd_theme + var folders = version_data["versions"]; + var root_url = getRootUrl(); + var current_url = document.URL; + var current_folder = getGhPagesCurrentFolder(); + if (current_folder === undefined) return; + var current_version = version_data["labels"][current_folder]; + var menu = document.createElement('div'); + menu.setAttribute('class', 'rst-versions'); + menu.setAttribute('data-toggle', 'rst-versions'); + menu.setAttribute('role', 'note'); + menu.setAttribute('aria-label', 'versions'); + var inner_html = + "" + + " Docs " + + "" + current_version + " " + + "" + + "" + + "
" + + "
" + + "
" + + "
Versions
"; + var i; + for (i in folders) { + var folder = folders[i]; + if (folder == current_folder){ + var inner_html = inner_html + "
" + current_version + "
"; + } else { + var inner_html = inner_html + "
" + version_data["labels"][folder] + "
"; + } + } + var downloads = version_data["downloads"][current_folder]; + if (downloads.length > 0){ + var inner_html = inner_html + + "
Downloads
"; + for (i in downloads) { + var download_label = downloads[i][0]; + var download_url = downloads[i][1]; + if (!(/^(https?|ftp):/.test(download_url))){ + if (!download_url.startsWith('/')){ + var download_url = '/' + download_url; + } + var download_url = root_url + download_url; + } + var inner_html = inner_html + "
" + + download_label + "
"; + } + } + var github_project_url = getGithubProjectUrl(); + if (github_project_url !== null && github_project_url.length > 0){ + var inner_html = inner_html + + "
On Github
" + + "
Project Home
" + + "
Issues
"; + } + var inner_html = inner_html + + "
" + + "
" + + "Generated by Docs Versions Menu" + + "" + + "
" + + "
"; + menu.innerHTML = inner_html; + var parent = document.body; + parent.insertBefore(menu, parent.lastChild); + + // Add a warning banner for dev/outdated versions + var warning; + var msg; + if (version_data["warnings"][current_folder].indexOf("outdated") >=0){ + warning = document.createElement('div'); + warning.setAttribute('class', 'admonition danger'); + msg = "This document is for an outdated version."; + } else if (version_data["warnings"][current_folder].indexOf("unreleased") >=0){ + warning = document.createElement('div'); + warning.setAttribute('class', 'admonition danger'); + msg = "This document is for an unreleased development version."; + } else if (version_data["warnings"][current_folder].indexOf("prereleased") >=0){ + warning = document.createElement('div'); + warning.setAttribute('class', 'admonition danger'); + msg = "This document is for a pre-release development version."; + } + if (warning !== undefined){ + if (version_data["latest"] !== null){ + msg = msg + " Documentation is available for the " + "latest public release." + } + warning.innerHTML = "

Note

" + + "

" + msg + "

"; + var parent = document.querySelector('div.body') + || document.querySelector('div.document') + || document.body; + parent.insertBefore(warning, parent.firstChild); + } + + +} + +function addVersionsMenu() { + // We assume that we can load versions.json from + // https://.github.io//versions.json + // That is, there's a path between the hostname and versions.json + var json_file = "/" + window.location.pathname.split("/")[1] + "/versions.json"; + $.getJSON(json_file, _addVersionsMenu); +} + +document.addEventListener('DOMContentLoaded', addVersionsMenu); \ No newline at end of file diff --git a/v2.4.1/_static/doctools.js b/v2.4.1/_static/doctools.js new file mode 100644 index 000000000..d06a71d75 --- /dev/null +++ b/v2.4.1/_static/doctools.js @@ -0,0 +1,156 @@ +/* + * doctools.js + * ~~~~~~~~~~~ + * + * Base JavaScript utilities for all Sphinx HTML documentation. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([ + "TEXTAREA", + "INPUT", + "SELECT", + "BUTTON", +]); + +const _ready = (callback) => { + if (document.readyState !== "loading") { + callback(); + } else { + document.addEventListener("DOMContentLoaded", callback); + } +}; + +/** + * Small JavaScript module for the documentation. + */ +const Documentation = { + init: () => { + Documentation.initDomainIndexTable(); + Documentation.initOnKeyListeners(); + }, + + /** + * i18n support + */ + TRANSLATIONS: {}, + PLURAL_EXPR: (n) => (n === 1 ? 0 : 1), + LOCALE: "unknown", + + // gettext and ngettext don't access this so that the functions + // can safely bound to a different name (_ = Documentation.gettext) + gettext: (string) => { + const translated = Documentation.TRANSLATIONS[string]; + switch (typeof translated) { + case "undefined": + return string; // no translation + case "string": + return translated; // translation exists + default: + return translated[0]; // (singular, plural) translation tuple exists + } + }, + + ngettext: (singular, plural, n) => { + const translated = Documentation.TRANSLATIONS[singular]; + if (typeof translated !== "undefined") + return translated[Documentation.PLURAL_EXPR(n)]; + return n === 1 ? singular : plural; + }, + + addTranslations: (catalog) => { + Object.assign(Documentation.TRANSLATIONS, catalog.messages); + Documentation.PLURAL_EXPR = new Function( + "n", + `return (${catalog.plural_expr})` + ); + Documentation.LOCALE = catalog.locale; + }, + + /** + * helper function to focus on search bar + */ + focusSearchBar: () => { + document.querySelectorAll("input[name=q]")[0]?.focus(); + }, + + /** + * Initialise the domain index toggle buttons + */ + initDomainIndexTable: () => { + const toggler = (el) => { + const idNumber = el.id.substr(7); + const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`); + if (el.src.substr(-9) === "minus.png") { + el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`; + toggledRows.forEach((el) => (el.style.display = "none")); + } else { + el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`; + toggledRows.forEach((el) => (el.style.display = "")); + } + }; + + const togglerElements = document.querySelectorAll("img.toggler"); + togglerElements.forEach((el) => + el.addEventListener("click", (event) => toggler(event.currentTarget)) + ); + togglerElements.forEach((el) => (el.style.display = "")); + if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler); + }, + + initOnKeyListeners: () => { + // only install a listener if it is really needed + if ( + !DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS && + !DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS + ) + return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.altKey || event.ctrlKey || event.metaKey) return; + + if (!event.shiftKey) { + switch (event.key) { + case "ArrowLeft": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const prevLink = document.querySelector('link[rel="prev"]'); + if (prevLink && prevLink.href) { + window.location.href = prevLink.href; + event.preventDefault(); + } + break; + case "ArrowRight": + if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break; + + const nextLink = document.querySelector('link[rel="next"]'); + if (nextLink && nextLink.href) { + window.location.href = nextLink.href; + event.preventDefault(); + } + break; + } + } + + // some keyboard layouts may need Shift to get / + switch (event.key) { + case "/": + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break; + Documentation.focusSearchBar(); + event.preventDefault(); + } + }); + }, +}; + +// quick alias for translations +const _ = Documentation.gettext; + +_ready(Documentation.init); diff --git a/v2.4.1/_static/documentation_options.js b/v2.4.1/_static/documentation_options.js new file mode 100644 index 000000000..fba58fdc6 --- /dev/null +++ b/v2.4.1/_static/documentation_options.js @@ -0,0 +1,14 @@ +var DOCUMENTATION_OPTIONS = { + URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), + VERSION: '2.4.1', + LANGUAGE: 'en', + COLLAPSE_INDEX: false, + BUILDER: 'html', + FILE_SUFFIX: '.html', + LINK_SUFFIX: '.html', + HAS_SOURCE: true, + SOURCELINK_SUFFIX: '.txt', + NAVIGATION_WITH_KEYS: false, + SHOW_SEARCH_SUMMARY: true, + ENABLE_SEARCH_SHORTCUTS: true, +}; \ No newline at end of file diff --git a/v2.4.1/_static/expanded.jpg b/v2.4.1/_static/expanded.jpg new file mode 100644 index 000000000..ac07e6940 Binary files /dev/null and b/v2.4.1/_static/expanded.jpg differ diff --git a/v2.4.1/_static/file.png b/v2.4.1/_static/file.png new file mode 100644 index 000000000..a858a410e Binary files /dev/null and b/v2.4.1/_static/file.png differ diff --git a/v2.4.1/_static/function.png b/v2.4.1/_static/function.png new file mode 100644 index 000000000..924ef3c87 Binary files /dev/null and b/v2.4.1/_static/function.png differ diff --git a/v2.4.1/_static/hydra.jpg b/v2.4.1/_static/hydra.jpg new file mode 100644 index 000000000..7baed50b1 Binary files /dev/null and b/v2.4.1/_static/hydra.jpg differ diff --git a/v2.4.1/_static/jquery.js b/v2.4.1/_static/jquery.js new file mode 100644 index 000000000..c4c6022f2 --- /dev/null +++ b/v2.4.1/_static/jquery.js @@ -0,0 +1,2 @@ +/*! jQuery v3.6.0 | (c) OpenJS Foundation and other contributors | jquery.org/license */ +!function(e,t){"use strict";"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(C,e){"use strict";var t=[],r=Object.getPrototypeOf,s=t.slice,g=t.flat?function(e){return t.flat.call(e)}:function(e){return t.concat.apply([],e)},u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return"function"==typeof e&&"number"!=typeof e.nodeType&&"function"!=typeof e.item},x=function(e){return null!=e&&e===e.window},E=C.document,c={type:!0,src:!0,nonce:!0,noModule:!0};function b(e,t,n){var r,i,o=(n=n||E).createElement("script");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?n[o.call(e)]||"object":typeof e}var f="3.6.0",S=function(e,t){return new S.fn.init(e,t)};function p(e){var t=!!e&&"length"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&("array"===n||0===t||"number"==typeof t&&0+~]|"+M+")"+M+"*"),U=new RegExp(M+"|>"),X=new RegExp(F),V=new RegExp("^"+I+"$"),G={ID:new RegExp("^#("+I+")"),CLASS:new RegExp("^\\.("+I+")"),TAG:new RegExp("^("+I+"|[*])"),ATTR:new RegExp("^"+W),PSEUDO:new RegExp("^"+F),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+M+"*(even|odd|(([+-]|)(\\d*)n|)"+M+"*(?:([+-]|)"+M+"*(\\d+)|))"+M+"*\\)|)","i"),bool:new RegExp("^(?:"+R+")$","i"),needsContext:new RegExp("^"+M+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+M+"*((?:-\\d)?\\d*)"+M+"*\\)|)(?=[^-]|$)","i")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ee=/[+~]/,te=new RegExp("\\\\[\\da-fA-F]{1,6}"+M+"?|\\\\([^\\r\\n\\f])","g"),ne=function(e,t){var n="0x"+e.slice(1)-65536;return t||(n<0?String.fromCharCode(n+65536):String.fromCharCode(n>>10|55296,1023&n|56320))},re=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,ie=function(e,t){return t?"\0"===e?"\ufffd":e.slice(0,-1)+"\\"+e.charCodeAt(e.length-1).toString(16)+" ":"\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&"fieldset"===e.nodeName.toLowerCase()},{dir:"parentNode",next:"legend"});try{H.apply(t=O.call(p.childNodes),p.childNodes),t[p.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],"string"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&(T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!N[t+" "]&&(!v||!v.test(t))&&(1!==p||"object"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&(U.test(t)||z.test(t))){(f=ee.test(t)&&ye(e.parentNode)||e)===e&&d.scope||((s=e.getAttribute("id"))?s=s.replace(re,ie):e.setAttribute("id",s=S)),o=(l=h(t)).length;while(o--)l[o]=(s?"#"+s:":scope")+" "+xe(l[o]);c=l.join(",")}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){N(t,!0)}finally{s===S&&e.removeAttribute("id")}}}return g(t.replace($,"$1"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+" ")>b.cacheLength&&delete e[r.shift()],e[t+" "]=n}}function le(e){return e[S]=!0,e}function ce(e){var t=C.createElement("fieldset");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){var n=e.split("|"),r=n.length;while(r--)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return("input"===t||"button"===t)&&e.type===n}}function ge(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){var n,r=a([],e.length,o),i=r.length;while(i--)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}for(e in d=se.support={},i=se.isXML=function(e){var t=e&&e.namespaceURI,n=e&&(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||"HTML")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:p;return r!=C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),p!=C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",oe,!1):n.attachEvent&&n.attachEvent("onunload",oe)),d.scope=ce(function(e){return a.appendChild(e).appendChild(C.createElement("div")),"undefined"!=typeof e.querySelectorAll&&!e.querySelectorAll(":scope fieldset div").length}),d.attributes=ce(function(e){return e.className="i",!e.getAttribute("className")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment("")),!e.getElementsByTagName("*").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=S,!C.getElementsByName||!C.getElementsByName(S).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute("id")===t}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return t&&t.value===n}},b.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode("id"))&&n.value===e)return[o];i=t.getElementsByName(e),r=0;while(o=i[r++])if((n=o.getAttributeNode("id"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&E)return t.getElementsByClassName(e)},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){var t;a.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&v.push("[*^$]="+M+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||v.push("\\["+M+"*(?:value|"+R+")"),e.querySelectorAll("[id~="+S+"-]").length||v.push("~="),(t=C.createElement("input")).setAttribute("name",""),e.appendChild(t),e.querySelectorAll("[name='']").length||v.push("\\["+M+"*name"+M+"*="+M+"*(?:''|\"\")"),e.querySelectorAll(":checked").length||v.push(":checked"),e.querySelectorAll("a#"+S+"+*").length||v.push(".#.+[+~]"),e.querySelectorAll("\\\f"),v.push("[\\r\\n\\f]")}),ce(function(e){e.innerHTML="";var t=C.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&v.push("name"+M+"*[*^$|!~]?="),2!==e.querySelectorAll(":enabled").length&&v.push(":enabled",":disabled"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(":disabled").length&&v.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),v.push(",.*:")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,"*"),c.call(e,"[s!='']:x"),s.push("!=",F)}),v=v.length&&new RegExp(v.join("|")),s=s.length&&new RegExp(s.join("|")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},j=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)==(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e==C||e.ownerDocument==p&&y(p,e)?-1:t==C||t.ownerDocument==p&&y(p,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e==C?-1:t==C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);n=e;while(n=n.parentNode)a.unshift(n);n=t;while(n=n.parentNode)s.unshift(n);while(a[r]===s[r])r++;return r?pe(a[r],s[r]):a[r]==p?-1:s[r]==p?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if(T(e),d.matchesSelector&&E&&!N[t+" "]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){N(t,!0)}return 0":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||"").replace(te,ne),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=m[e+" "];return t||(t=new RegExp("(^|"+M+")"+e+"("+M+"|$)"))&&m(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?"!="===r:!r||(t+="","="===r?t===i:"!="===r?t!==i:"^="===r?i&&0===t.indexOf(i):"*="===r?i&&-1:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function j(e,n,r){return m(n)?S.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?S.grep(e,function(e){return e===n!==r}):"string"!=typeof n?S.grep(e,function(e){return-1)[^>]*|#([\w-]+))$/;(S.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||D,"string"==typeof e){if(!(r="<"===e[0]&&">"===e[e.length-1]&&3<=e.length?[null,e,null]:q.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof S?t[0]:t,S.merge(this,S.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),N.test(r[1])&&S.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(S):S.makeArray(e,this)}).prototype=S.fn,D=S(E);var L=/^(?:parents|prev(?:Until|All))/,H={children:!0,contents:!0,next:!0,prev:!0};function O(e,t){while((e=e[t])&&1!==e.nodeType);return e}S.fn.extend({has:function(e){var t=S(e,this),n=t.length;return this.filter(function(){for(var e=0;e\x20\t\r\n\f]*)/i,he=/^$|^module$|\/(?:java|ecma)script/i;ce=E.createDocumentFragment().appendChild(E.createElement("div")),(fe=E.createElement("input")).setAttribute("type","radio"),fe.setAttribute("checked","checked"),fe.setAttribute("name","t"),ce.appendChild(fe),y.checkClone=ce.cloneNode(!0).cloneNode(!0).lastChild.checked,ce.innerHTML="",y.noCloneChecked=!!ce.cloneNode(!0).lastChild.defaultValue,ce.innerHTML="",y.option=!!ce.lastChild;var ge={thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function ve(e,t){var n;return n="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):[],void 0===t||t&&A(e,t)?S.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;n",""]);var me=/<|&#?\w+;/;function xe(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;d\s*$/g;function je(e,t){return A(e,"table")&&A(11!==t.nodeType?t:t.firstChild,"tr")&&S(e).children("tbody")[0]||e}function De(e){return e.type=(null!==e.getAttribute("type"))+"/"+e.type,e}function qe(e){return"true/"===(e.type||"").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute("type"),e}function Le(e,t){var n,r,i,o,a,s;if(1===t.nodeType){if(Y.hasData(e)&&(s=Y.get(e).events))for(i in Y.remove(t,"handle events"),s)for(n=0,r=s[i].length;n").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on("load error",i=function(e){r.remove(),i=null,e&&t("error"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}});var _t,zt=[],Ut=/(=)\?(?=&|$)|\?\?/;S.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=zt.pop()||S.expando+"_"+wt.guid++;return this[e]=!0,e}}),S.ajaxPrefilter("json jsonp",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Ut.test(e.url)?"url":"string"==typeof e.data&&0===(e.contentType||"").indexOf("application/x-www-form-urlencoded")&&Ut.test(e.data)&&"data");if(a||"jsonp"===e.dataTypes[0])return r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Ut,"$1"+r):!1!==e.jsonp&&(e.url+=(Tt.test(e.url)?"&":"?")+e.jsonp+"="+r),e.converters["script json"]=function(){return o||S.error(r+" was not called"),o[0]},e.dataTypes[0]="json",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?S(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,zt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),"script"}),y.createHTMLDocument=((_t=E.implementation.createHTMLDocument("").body).innerHTML="
",2===_t.childNodes.length),S.parseHTML=function(e,t,n){return"string"!=typeof e?[]:("boolean"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument("")).createElement("base")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=N.exec(e))?[t.createElement(i[1])]:(i=xe([e],t,o),o&&o.length&&S(o).remove(),S.merge([],i.childNodes)));var r,i,o},S.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(" ");return-1").append(S.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},S.expr.pseudos.animated=function(t){return S.grep(S.timers,function(e){return t===e.elem}).length},S.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=S.css(e,"position"),c=S(e),f={};"static"===l&&(e.style.position="relative"),s=c.offset(),o=S.css(e,"top"),u=S.css(e,"left"),("absolute"===l||"fixed"===l)&&-1<(o+u).indexOf("auto")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,S.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),"using"in t?t.using.call(e,f):c.css(f)}},S.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){S.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if("fixed"===S.css(r,"position"))t=r.getBoundingClientRect();else{t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;while(e&&(e===n.body||e===n.documentElement)&&"static"===S.css(e,"position"))e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=S(e).offset()).top+=S.css(e,"borderTopWidth",!0),i.left+=S.css(e,"borderLeftWidth",!0))}return{top:t.top-i.top-S.css(r,"marginTop",!0),left:t.left-i.left-S.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){var e=this.offsetParent;while(e&&"static"===S.css(e,"position"))e=e.offsetParent;return e||re})}}),S.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(t,i){var o="pageYOffset"===i;S.fn[t]=function(e){return $(this,function(e,t,n){var r;if(x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n)return r?r[i]:e[t];r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n},t,e,arguments.length)}}),S.each(["top","left"],function(e,n){S.cssHooks[n]=Fe(y.pixelPosition,function(e,t){if(t)return t=We(e,n),Pe.test(t)?S(e).position()[n]+"px":t})}),S.each({Height:"height",Width:"width"},function(a,s){S.each({padding:"inner"+a,content:s,"":"outer"+a},function(r,o){S.fn[o]=function(e,t){var n=arguments.length&&(r||"boolean"!=typeof e),i=r||(!0===e||!0===t?"margin":"border");return $(this,function(e,t,n){var r;return x(e)?0===o.indexOf("outer")?e["inner"+a]:e.document.documentElement["client"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body["scroll"+a],r["scroll"+a],e.body["offset"+a],r["offset"+a],r["client"+a])):void 0===n?S.css(e,t,i):S.style(e,t,n,i)},s,n?e:void 0,n)}})}),S.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){S.fn[t]=function(e){return this.on(t,e)}}),S.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,"**"):this.off(t,e||"**",n)},hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),S.each("blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu".split(" "),function(e,n){S.fn[n]=function(e,t){return 0",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=y.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=y.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),y.elements=c+" "+a,j(b)}function f(a){var b=x[a[v]];return b||(b={},w++,a[v]=w,x[w]=b),b}function g(a,c,d){if(c||(c=b),q)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():u.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||t.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),q)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return y.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(y,b.frag)}function j(a){a||(a=b);var d=f(a);return!y.shivCSS||p||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),q||i(a,d),a}function k(a){for(var b,c=a.getElementsByTagName("*"),e=c.length,f=RegExp("^(?:"+d().join("|")+")$","i"),g=[];e--;)b=c[e],f.test(b.nodeName)&&g.push(b.applyElement(l(b)));return g}function l(a){for(var b,c=a.attributes,d=c.length,e=a.ownerDocument.createElement(A+":"+a.nodeName);d--;)b=c[d],b.specified&&e.setAttribute(b.nodeName,b.nodeValue);return e.style.cssText=a.style.cssText,e}function m(a){for(var b,c=a.split("{"),e=c.length,f=RegExp("(^|[\\s,>+~])("+d().join("|")+")(?=[[\\s,>+~#.:]|$)","gi"),g="$1"+A+"\\:$2";e--;)b=c[e]=c[e].split("}"),b[b.length-1]=b[b.length-1].replace(f,g),c[e]=b.join("}");return c.join("{")}function n(a){for(var b=a.length;b--;)a[b].removeNode()}function o(a){function b(){clearTimeout(g._removeSheetTimer),d&&d.removeNode(!0),d=null}var d,e,g=f(a),h=a.namespaces,i=a.parentWindow;return!B||a.printShived?a:("undefined"==typeof h[A]&&h.add(A),i.attachEvent("onbeforeprint",function(){b();for(var f,g,h,i=a.styleSheets,j=[],l=i.length,n=Array(l);l--;)n[l]=i[l];for(;h=n.pop();)if(!h.disabled&&z.test(h.media)){try{f=h.imports,g=f.length}catch(o){g=0}for(l=0;g>l;l++)n.push(f[l]);try{j.push(h.cssText)}catch(o){}}j=m(j.reverse().join("")),e=k(a),d=c(a,j)}),i.attachEvent("onafterprint",function(){n(e),clearTimeout(g._removeSheetTimer),g._removeSheetTimer=setTimeout(b,500)}),a.printShived=!0,a)}var p,q,r="3.7.3",s=a.html5||{},t=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,u=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,v="_html5shiv",w=0,x={};!function(){try{var a=b.createElement("a");a.innerHTML="",p="hidden"in a,q=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){p=!0,q=!0}}();var y={elements:s.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:r,shivCSS:s.shivCSS!==!1,supportsUnknownElements:q,shivMethods:s.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=y,j(b);var z=/^$|\b(?:all|print)\b/,A="html5shiv",B=!q&&function(){var c=b.documentElement;return!("undefined"==typeof b.namespaces||"undefined"==typeof b.parentWindow||"undefined"==typeof c.applyElement||"undefined"==typeof c.removeNode||"undefined"==typeof a.attachEvent)}();y.type+=" print",y.shivPrint=o,o(b),"object"==typeof module&&module.exports&&(module.exports=y)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/v2.4.1/_static/js/html5shiv.min.js b/v2.4.1/_static/js/html5shiv.min.js new file mode 100644 index 000000000..cd1c674f5 --- /dev/null +++ b/v2.4.1/_static/js/html5shiv.min.js @@ -0,0 +1,4 @@ +/** +* @preserve HTML5 Shiv 3.7.3 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed +*/ +!function(a,b){function c(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x",d.insertBefore(c.lastChild,d.firstChild)}function d(){var a=t.elements;return"string"==typeof a?a.split(" "):a}function e(a,b){var c=t.elements;"string"!=typeof c&&(c=c.join(" ")),"string"!=typeof a&&(a=a.join(" ")),t.elements=c+" "+a,j(b)}function f(a){var b=s[a[q]];return b||(b={},r++,a[q]=r,s[r]=b),b}function g(a,c,d){if(c||(c=b),l)return c.createElement(a);d||(d=f(c));var e;return e=d.cache[a]?d.cache[a].cloneNode():p.test(a)?(d.cache[a]=d.createElem(a)).cloneNode():d.createElem(a),!e.canHaveChildren||o.test(a)||e.tagUrn?e:d.frag.appendChild(e)}function h(a,c){if(a||(a=b),l)return a.createDocumentFragment();c=c||f(a);for(var e=c.frag.cloneNode(),g=0,h=d(),i=h.length;i>g;g++)e.createElement(h[g]);return e}function i(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return t.shivMethods?g(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+d().join().replace(/[\w\-:]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(t,b.frag)}function j(a){a||(a=b);var d=f(a);return!t.shivCSS||k||d.hasCSS||(d.hasCSS=!!c(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),l||i(a,d),a}var k,l,m="3.7.3-pre",n=a.html5||{},o=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,p=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,q="_html5shiv",r=0,s={};!function(){try{var a=b.createElement("a");a.innerHTML="",k="hidden"in a,l=1==a.childNodes.length||function(){b.createElement("a");var a=b.createDocumentFragment();return"undefined"==typeof a.cloneNode||"undefined"==typeof a.createDocumentFragment||"undefined"==typeof a.createElement}()}catch(c){k=!0,l=!0}}();var t={elements:n.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output picture progress section summary template time video",version:m,shivCSS:n.shivCSS!==!1,supportsUnknownElements:l,shivMethods:n.shivMethods!==!1,type:"default",shivDocument:j,createElement:g,createDocumentFragment:h,addElements:e};a.html5=t,j(b),"object"==typeof module&&module.exports&&(module.exports=t)}("undefined"!=typeof window?window:this,document); \ No newline at end of file diff --git a/v2.4.1/_static/js/theme.js b/v2.4.1/_static/js/theme.js new file mode 100644 index 000000000..1fddb6ee4 --- /dev/null +++ b/v2.4.1/_static/js/theme.js @@ -0,0 +1 @@ +!function(n){var e={};function t(i){if(e[i])return e[i].exports;var o=e[i]={i:i,l:!1,exports:{}};return n[i].call(o.exports,o,o.exports,t),o.l=!0,o.exports}t.m=n,t.c=e,t.d=function(n,e,i){t.o(n,e)||Object.defineProperty(n,e,{enumerable:!0,get:i})},t.r=function(n){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(n,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(n,"__esModule",{value:!0})},t.t=function(n,e){if(1&e&&(n=t(n)),8&e)return n;if(4&e&&"object"==typeof n&&n&&n.__esModule)return n;var i=Object.create(null);if(t.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:n}),2&e&&"string"!=typeof n)for(var o in n)t.d(i,o,function(e){return n[e]}.bind(null,o));return i},t.n=function(n){var e=n&&n.__esModule?function(){return n.default}:function(){return n};return t.d(e,"a",e),e},t.o=function(n,e){return Object.prototype.hasOwnProperty.call(n,e)},t.p="",t(t.s=0)}([function(n,e,t){t(1),n.exports=t(3)},function(n,e,t){(function(){var e="undefined"!=typeof window?window.jQuery:t(2);n.exports.ThemeNav={navBar:null,win:null,winScroll:!1,winResize:!1,linkScroll:!1,winPosition:0,winHeight:null,docHeight:null,isRunning:!1,enable:function(n){var t=this;void 0===n&&(n=!0),t.isRunning||(t.isRunning=!0,e((function(e){t.init(e),t.reset(),t.win.on("hashchange",t.reset),n&&t.win.on("scroll",(function(){t.linkScroll||t.winScroll||(t.winScroll=!0,requestAnimationFrame((function(){t.onScroll()})))})),t.win.on("resize",(function(){t.winResize||(t.winResize=!0,requestAnimationFrame((function(){t.onResize()})))})),t.onResize()})))},enableSticky:function(){this.enable(!0)},init:function(n){n(document);var e=this;this.navBar=n("div.wy-side-scroll:first"),this.win=n(window),n(document).on("click","[data-toggle='wy-nav-top']",(function(){n("[data-toggle='wy-nav-shift']").toggleClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift")})).on("click",".wy-menu-vertical .current ul li a",(function(){var t=n(this);n("[data-toggle='wy-nav-shift']").removeClass("shift"),n("[data-toggle='rst-versions']").toggleClass("shift"),e.toggleCurrent(t),e.hashChange()})).on("click","[data-toggle='rst-current-version']",(function(){n("[data-toggle='rst-versions']").toggleClass("shift-up")})),n("table.docutils:not(.field-list,.footnote,.citation)").wrap("
"),n("table.docutils.footnote").wrap("
"),n("table.docutils.citation").wrap("
"),n(".wy-menu-vertical ul").not(".simple").siblings("a").each((function(){var t=n(this);expand=n(''),expand.on("click",(function(n){return e.toggleCurrent(t),n.stopPropagation(),!1})),t.prepend(expand)}))},reset:function(){var n=encodeURI(window.location.hash)||"#";try{var e=$(".wy-menu-vertical"),t=e.find('[href="'+n+'"]');if(0===t.length){var i=$('.document [id="'+n.substring(1)+'"]').closest("div.section");0===(t=e.find('[href="#'+i.attr("id")+'"]')).length&&(t=e.find('[href="#"]'))}if(t.length>0){$(".wy-menu-vertical .current").removeClass("current").attr("aria-expanded","false"),t.addClass("current").attr("aria-expanded","true"),t.closest("li.toctree-l1").parent().addClass("current").attr("aria-expanded","true");for(let n=1;n<=10;n++)t.closest("li.toctree-l"+n).addClass("current").attr("aria-expanded","true");t[0].scrollIntoView()}}catch(n){console.log("Error expanding nav for anchor",n)}},onScroll:function(){this.winScroll=!1;var n=this.win.scrollTop(),e=n+this.winHeight,t=this.navBar.scrollTop()+(n-this.winPosition);n<0||e>this.docHeight||(this.navBar.scrollTop(t),this.winPosition=n)},onResize:function(){this.winResize=!1,this.winHeight=this.win.height(),this.docHeight=$(document).height()},hashChange:function(){this.linkScroll=!0,this.win.one("hashchange",(function(){this.linkScroll=!1}))},toggleCurrent:function(n){var e=n.closest("li");e.siblings("li.current").removeClass("current").attr("aria-expanded","false"),e.siblings().find("li.current").removeClass("current").attr("aria-expanded","false");var t=e.find("> ul li");t.length&&(t.removeClass("current").attr("aria-expanded","false"),e.toggleClass("current").attr("aria-expanded",(function(n,e){return"true"==e?"false":"true"})))}},"undefined"!=typeof window&&(window.SphinxRtdTheme={Navigation:n.exports.ThemeNav,StickyNav:n.exports.ThemeNav}),function(){for(var n=0,e=["ms","moz","webkit","o"],t=0;t0 + var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 + var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 + var s_v = "^(" + C + ")?" + v; // vowel in stem + + this.stemWord = function (w) { + var stem; + var suffix; + var firstch; + var origword = w; + + if (w.length < 3) + return w; + + var re; + var re2; + var re3; + var re4; + + firstch = w.substr(0,1); + if (firstch == "y") + w = firstch.toUpperCase() + w.substr(1); + + // Step 1a + re = /^(.+?)(ss|i)es$/; + re2 = /^(.+?)([^s])s$/; + + if (re.test(w)) + w = w.replace(re,"$1$2"); + else if (re2.test(w)) + w = w.replace(re2,"$1$2"); + + // Step 1b + re = /^(.+?)eed$/; + re2 = /^(.+?)(ed|ing)$/; + if (re.test(w)) { + var fp = re.exec(w); + re = new RegExp(mgr0); + if (re.test(fp[1])) { + re = /.$/; + w = w.replace(re,""); + } + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1]; + re2 = new RegExp(s_v); + if (re2.test(stem)) { + w = stem; + re2 = /(at|bl|iz)$/; + re3 = new RegExp("([^aeiouylsz])\\1$"); + re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re2.test(w)) + w = w + "e"; + else if (re3.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + else if (re4.test(w)) + w = w + "e"; + } + } + + // Step 1c + re = /^(.+?)y$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(s_v); + if (re.test(stem)) + w = stem + "i"; + } + + // Step 2 + re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step2list[suffix]; + } + + // Step 3 + re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + suffix = fp[2]; + re = new RegExp(mgr0); + if (re.test(stem)) + w = stem + step3list[suffix]; + } + + // Step 4 + re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; + re2 = /^(.+?)(s|t)(ion)$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + if (re.test(stem)) + w = stem; + } + else if (re2.test(w)) { + var fp = re2.exec(w); + stem = fp[1] + fp[2]; + re2 = new RegExp(mgr1); + if (re2.test(stem)) + w = stem; + } + + // Step 5 + re = /^(.+?)e$/; + if (re.test(w)) { + var fp = re.exec(w); + stem = fp[1]; + re = new RegExp(mgr1); + re2 = new RegExp(meq1); + re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); + if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) + w = stem; + } + re = /ll$/; + re2 = new RegExp(mgr1); + if (re.test(w) && re2.test(w)) { + re = /.$/; + w = w.replace(re,""); + } + + // and turn initial Y back to y + if (firstch == "y") + w = firstch.toLowerCase() + w.substr(1); + return w; + } +} + diff --git a/v2.4.1/_static/minus.png b/v2.4.1/_static/minus.png new file mode 100644 index 000000000..d96755fda Binary files /dev/null and b/v2.4.1/_static/minus.png differ diff --git a/v2.4.1/_static/plus.png b/v2.4.1/_static/plus.png new file mode 100644 index 000000000..7107cec93 Binary files /dev/null and b/v2.4.1/_static/plus.png differ diff --git a/v2.4.1/_static/pygments.css b/v2.4.1/_static/pygments.css new file mode 100644 index 000000000..691aeb82d --- /dev/null +++ b/v2.4.1/_static/pygments.css @@ -0,0 +1,74 @@ +pre { line-height: 125%; } +td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; } +td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } +.highlight .hll { background-color: #ffffcc } +.highlight { background: #eeffcc; } +.highlight .c { color: #408090; font-style: italic } /* Comment */ +.highlight .err { border: 1px solid #FF0000 } /* Error */ +.highlight .k { color: #007020; font-weight: bold } /* Keyword */ +.highlight .o { color: #666666 } /* Operator */ +.highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */ +.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */ +.highlight .cp { color: #007020 } /* Comment.Preproc */ +.highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */ +.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */ +.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */ +.highlight .gd { color: #A00000 } /* Generic.Deleted */ +.highlight .ge { font-style: italic } /* Generic.Emph */ +.highlight .gr { color: #FF0000 } /* Generic.Error */ +.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ +.highlight .gi { color: #00A000 } /* Generic.Inserted */ +.highlight .go { color: #333333 } /* Generic.Output */ +.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ +.highlight .gs { font-weight: bold } /* Generic.Strong */ +.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ +.highlight .gt { color: #0044DD } /* Generic.Traceback */ +.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ +.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ +.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ +.highlight .kp { color: #007020 } /* Keyword.Pseudo */ +.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ +.highlight .kt { color: #902000 } /* Keyword.Type */ +.highlight .m { color: #208050 } /* Literal.Number */ +.highlight .s { color: #4070a0 } /* Literal.String */ +.highlight .na { color: #4070a0 } /* Name.Attribute */ +.highlight .nb { color: #007020 } /* Name.Builtin */ +.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ +.highlight .no { color: #60add5 } /* Name.Constant */ +.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ +.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ +.highlight .ne { color: #007020 } /* Name.Exception */ +.highlight .nf { color: #06287e } /* Name.Function */ +.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ +.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ +.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ +.highlight .nv { color: #bb60d5 } /* Name.Variable */ +.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ +.highlight .w { color: #bbbbbb } /* Text.Whitespace */ +.highlight .mb { color: #208050 } /* Literal.Number.Bin */ +.highlight .mf { color: #208050 } /* Literal.Number.Float */ +.highlight .mh { color: #208050 } /* Literal.Number.Hex */ +.highlight .mi { color: #208050 } /* Literal.Number.Integer */ +.highlight .mo { color: #208050 } /* Literal.Number.Oct */ +.highlight .sa { color: #4070a0 } /* Literal.String.Affix */ +.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ +.highlight .sc { color: #4070a0 } /* Literal.String.Char */ +.highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ +.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ +.highlight .s2 { color: #4070a0 } /* Literal.String.Double */ +.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ +.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ +.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ +.highlight .sx { color: #c65d09 } /* Literal.String.Other */ +.highlight .sr { color: #235388 } /* Literal.String.Regex */ +.highlight .s1 { color: #4070a0 } /* Literal.String.Single */ +.highlight .ss { color: #517918 } /* Literal.String.Symbol */ +.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ +.highlight .fm { color: #06287e } /* Name.Function.Magic */ +.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ +.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ +.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ +.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ +.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */ \ No newline at end of file diff --git a/v2.4.1/_static/searchtools.js b/v2.4.1/_static/searchtools.js new file mode 100644 index 000000000..97d56a74d --- /dev/null +++ b/v2.4.1/_static/searchtools.js @@ -0,0 +1,566 @@ +/* + * searchtools.js + * ~~~~~~~~~~~~~~~~ + * + * Sphinx JavaScript utilities for the full-text search. + * + * :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. + * :license: BSD, see LICENSE for details. + * + */ +"use strict"; + +/** + * Simple result scoring code. + */ +if (typeof Scorer === "undefined") { + var Scorer = { + // Implement the following function to further tweak the score for each result + // The function takes a result array [docname, title, anchor, descr, score, filename] + // and returns the new score. + /* + score: result => { + const [docname, title, anchor, descr, score, filename] = result + return score + }, + */ + + // query matches the full name of an object + objNameMatch: 11, + // or matches in the last dotted part of the object name + objPartialMatch: 6, + // Additive scores depending on the priority of the object + objPrio: { + 0: 15, // used to be importantResults + 1: 5, // used to be objectResults + 2: -5, // used to be unimportantResults + }, + // Used when the priority is not in the mapping. + objPrioDefault: 0, + + // query found in title + title: 15, + partialTitle: 7, + // query found in terms + term: 5, + partialTerm: 2, + }; +} + +const _removeChildren = (element) => { + while (element && element.lastChild) element.removeChild(element.lastChild); +}; + +/** + * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping + */ +const _escapeRegExp = (string) => + string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string + +const _displayItem = (item, searchTerms) => { + const docBuilder = DOCUMENTATION_OPTIONS.BUILDER; + const docUrlRoot = DOCUMENTATION_OPTIONS.URL_ROOT; + const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX; + const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX; + const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY; + + const [docName, title, anchor, descr, score, _filename] = item; + + let listItem = document.createElement("li"); + let requestUrl; + let linkUrl; + if (docBuilder === "dirhtml") { + // dirhtml builder + let dirname = docName + "/"; + if (dirname.match(/\/index\/$/)) + dirname = dirname.substring(0, dirname.length - 6); + else if (dirname === "index/") dirname = ""; + requestUrl = docUrlRoot + dirname; + linkUrl = requestUrl; + } else { + // normal html builders + requestUrl = docUrlRoot + docName + docFileSuffix; + linkUrl = docName + docLinkSuffix; + } + let linkEl = listItem.appendChild(document.createElement("a")); + linkEl.href = linkUrl + anchor; + linkEl.dataset.score = score; + linkEl.innerHTML = title; + if (descr) + listItem.appendChild(document.createElement("span")).innerHTML = + " (" + descr + ")"; + else if (showSearchSummary) + fetch(requestUrl) + .then((responseData) => responseData.text()) + .then((data) => { + if (data) + listItem.appendChild( + Search.makeSearchSummary(data, searchTerms) + ); + }); + Search.output.appendChild(listItem); +}; +const _finishSearch = (resultCount) => { + Search.stopPulse(); + Search.title.innerText = _("Search Results"); + if (!resultCount) + Search.status.innerText = Documentation.gettext( + "Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories." + ); + else + Search.status.innerText = _( + `Search finished, found ${resultCount} page(s) matching the search query.` + ); +}; +const _displayNextItem = ( + results, + resultCount, + searchTerms +) => { + // results left, load the summary and display it + // this is intended to be dynamic (don't sub resultsCount) + if (results.length) { + _displayItem(results.pop(), searchTerms); + setTimeout( + () => _displayNextItem(results, resultCount, searchTerms), + 5 + ); + } + // search finished, update title and status message + else _finishSearch(resultCount); +}; + +/** + * Default splitQuery function. Can be overridden in ``sphinx.search`` with a + * custom function per language. + * + * The regular expression works by splitting the string on consecutive characters + * that are not Unicode letters, numbers, underscores, or emoji characters. + * This is the same as ``\W+`` in Python, preserving the surrogate pair area. + */ +if (typeof splitQuery === "undefined") { + var splitQuery = (query) => query + .split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu) + .filter(term => term) // remove remaining empty strings +} + +/** + * Search Module + */ +const Search = { + _index: null, + _queued_query: null, + _pulse_status: -1, + + htmlToText: (htmlString) => { + const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html'); + htmlElement.querySelectorAll(".headerlink").forEach((el) => { el.remove() }); + const docContent = htmlElement.querySelector('[role="main"]'); + if (docContent !== undefined) return docContent.textContent; + console.warn( + "Content block not found. Sphinx search tries to obtain it via '[role=main]'. Could you check your theme or template." + ); + return ""; + }, + + init: () => { + const query = new URLSearchParams(window.location.search).get("q"); + document + .querySelectorAll('input[name="q"]') + .forEach((el) => (el.value = query)); + if (query) Search.performSearch(query); + }, + + loadIndex: (url) => + (document.body.appendChild(document.createElement("script")).src = url), + + setIndex: (index) => { + Search._index = index; + if (Search._queued_query !== null) { + const query = Search._queued_query; + Search._queued_query = null; + Search.query(query); + } + }, + + hasIndex: () => Search._index !== null, + + deferQuery: (query) => (Search._queued_query = query), + + stopPulse: () => (Search._pulse_status = -1), + + startPulse: () => { + if (Search._pulse_status >= 0) return; + + const pulse = () => { + Search._pulse_status = (Search._pulse_status + 1) % 4; + Search.dots.innerText = ".".repeat(Search._pulse_status); + if (Search._pulse_status >= 0) window.setTimeout(pulse, 500); + }; + pulse(); + }, + + /** + * perform a search for something (or wait until index is loaded) + */ + performSearch: (query) => { + // create the required interface elements + const searchText = document.createElement("h2"); + searchText.textContent = _("Searching"); + const searchSummary = document.createElement("p"); + searchSummary.classList.add("search-summary"); + searchSummary.innerText = ""; + const searchList = document.createElement("ul"); + searchList.classList.add("search"); + + const out = document.getElementById("search-results"); + Search.title = out.appendChild(searchText); + Search.dots = Search.title.appendChild(document.createElement("span")); + Search.status = out.appendChild(searchSummary); + Search.output = out.appendChild(searchList); + + const searchProgress = document.getElementById("search-progress"); + // Some themes don't use the search progress node + if (searchProgress) { + searchProgress.innerText = _("Preparing search..."); + } + Search.startPulse(); + + // index already loaded, the browser was quick! + if (Search.hasIndex()) Search.query(query); + else Search.deferQuery(query); + }, + + /** + * execute search (requires search index to be loaded) + */ + query: (query) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + const allTitles = Search._index.alltitles; + const indexEntries = Search._index.indexentries; + + // stem the search terms and add them to the correct list + const stemmer = new Stemmer(); + const searchTerms = new Set(); + const excludedTerms = new Set(); + const highlightTerms = new Set(); + const objectTerms = new Set(splitQuery(query.toLowerCase().trim())); + splitQuery(query.trim()).forEach((queryTerm) => { + const queryTermLower = queryTerm.toLowerCase(); + + // maybe skip this "word" + // stopwords array is from language_data.js + if ( + stopwords.indexOf(queryTermLower) !== -1 || + queryTerm.match(/^\d+$/) + ) + return; + + // stem the word + let word = stemmer.stemWord(queryTermLower); + // select the correct list + if (word[0] === "-") excludedTerms.add(word.substr(1)); + else { + searchTerms.add(word); + highlightTerms.add(queryTermLower); + } + }); + + if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js + localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" ")) + } + + // console.debug("SEARCH: searching for:"); + // console.info("required: ", [...searchTerms]); + // console.info("excluded: ", [...excludedTerms]); + + // array of [docname, title, anchor, descr, score, filename] + let results = []; + _removeChildren(document.getElementById("search-progress")); + + const queryLower = query.toLowerCase(); + for (const [title, foundTitles] of Object.entries(allTitles)) { + if (title.toLowerCase().includes(queryLower) && (queryLower.length >= title.length/2)) { + for (const [file, id] of foundTitles) { + let score = Math.round(100 * queryLower.length / title.length) + results.push([ + docNames[file], + titles[file] !== title ? `${titles[file]} > ${title}` : title, + id !== null ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // search for explicit entries in index directives + for (const [entry, foundEntries] of Object.entries(indexEntries)) { + if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) { + for (const [file, id] of foundEntries) { + let score = Math.round(100 * queryLower.length / entry.length) + results.push([ + docNames[file], + titles[file], + id ? "#" + id : "", + null, + score, + filenames[file], + ]); + } + } + } + + // lookup as object + objectTerms.forEach((term) => + results.push(...Search.performObjectSearch(term, objectTerms)) + ); + + // lookup as search terms in fulltext + results.push(...Search.performTermsSearch(searchTerms, excludedTerms)); + + // let the scorer override scores with a custom scoring function + if (Scorer.score) results.forEach((item) => (item[4] = Scorer.score(item))); + + // now sort the results by score (in opposite order of appearance, since the + // display function below uses pop() to retrieve items) and then + // alphabetically + results.sort((a, b) => { + const leftScore = a[4]; + const rightScore = b[4]; + if (leftScore === rightScore) { + // same score: sort alphabetically + const leftTitle = a[1].toLowerCase(); + const rightTitle = b[1].toLowerCase(); + if (leftTitle === rightTitle) return 0; + return leftTitle > rightTitle ? -1 : 1; // inverted is intentional + } + return leftScore > rightScore ? 1 : -1; + }); + + // remove duplicate search results + // note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept + let seen = new Set(); + results = results.reverse().reduce((acc, result) => { + let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(','); + if (!seen.has(resultStr)) { + acc.push(result); + seen.add(resultStr); + } + return acc; + }, []); + + results = results.reverse(); + + // for debugging + //Search.lastresults = results.slice(); // a copy + // console.info("search results:", Search.lastresults); + + // print the results + _displayNextItem(results, results.length, searchTerms); + }, + + /** + * search for object names + */ + performObjectSearch: (object, objectTerms) => { + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const objects = Search._index.objects; + const objNames = Search._index.objnames; + const titles = Search._index.titles; + + const results = []; + + const objectSearchCallback = (prefix, match) => { + const name = match[4] + const fullname = (prefix ? prefix + "." : "") + name; + const fullnameLower = fullname.toLowerCase(); + if (fullnameLower.indexOf(object) < 0) return; + + let score = 0; + const parts = fullnameLower.split("."); + + // check for different match types: exact matches of full name or + // "last name" (i.e. last dotted part) + if (fullnameLower === object || parts.slice(-1)[0] === object) + score += Scorer.objNameMatch; + else if (parts.slice(-1)[0].indexOf(object) > -1) + score += Scorer.objPartialMatch; // matches in last name + + const objName = objNames[match[1]][2]; + const title = titles[match[0]]; + + // If more than one term searched for, we require other words to be + // found in the name/title/description + const otherTerms = new Set(objectTerms); + otherTerms.delete(object); + if (otherTerms.size > 0) { + const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase(); + if ( + [...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0) + ) + return; + } + + let anchor = match[3]; + if (anchor === "") anchor = fullname; + else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname; + + const descr = objName + _(", in ") + title; + + // add custom score for some objects according to scorer + if (Scorer.objPrio.hasOwnProperty(match[2])) + score += Scorer.objPrio[match[2]]; + else score += Scorer.objPrioDefault; + + results.push([ + docNames[match[0]], + fullname, + "#" + anchor, + descr, + score, + filenames[match[0]], + ]); + }; + Object.keys(objects).forEach((prefix) => + objects[prefix].forEach((array) => + objectSearchCallback(prefix, array) + ) + ); + return results; + }, + + /** + * search for full-text terms in the index + */ + performTermsSearch: (searchTerms, excludedTerms) => { + // prepare search + const terms = Search._index.terms; + const titleTerms = Search._index.titleterms; + const filenames = Search._index.filenames; + const docNames = Search._index.docnames; + const titles = Search._index.titles; + + const scoreMap = new Map(); + const fileMap = new Map(); + + // perform the search on the required terms + searchTerms.forEach((word) => { + const files = []; + const arr = [ + { files: terms[word], score: Scorer.term }, + { files: titleTerms[word], score: Scorer.title }, + ]; + // add support for partial matches + if (word.length > 2) { + const escapedWord = _escapeRegExp(word); + Object.keys(terms).forEach((term) => { + if (term.match(escapedWord) && !terms[word]) + arr.push({ files: terms[term], score: Scorer.partialTerm }); + }); + Object.keys(titleTerms).forEach((term) => { + if (term.match(escapedWord) && !titleTerms[word]) + arr.push({ files: titleTerms[word], score: Scorer.partialTitle }); + }); + } + + // no match but word was a required one + if (arr.every((record) => record.files === undefined)) return; + + // found search word in contents + arr.forEach((record) => { + if (record.files === undefined) return; + + let recordFiles = record.files; + if (recordFiles.length === undefined) recordFiles = [recordFiles]; + files.push(...recordFiles); + + // set score for the word in each file + recordFiles.forEach((file) => { + if (!scoreMap.has(file)) scoreMap.set(file, {}); + scoreMap.get(file)[word] = record.score; + }); + }); + + // create the mapping + files.forEach((file) => { + if (fileMap.has(file) && fileMap.get(file).indexOf(word) === -1) + fileMap.get(file).push(word); + else fileMap.set(file, [word]); + }); + }); + + // now check if the files don't contain excluded terms + const results = []; + for (const [file, wordList] of fileMap) { + // check if all requirements are matched + + // as search terms with length < 3 are discarded + const filteredTermCount = [...searchTerms].filter( + (term) => term.length > 2 + ).length; + if ( + wordList.length !== searchTerms.size && + wordList.length !== filteredTermCount + ) + continue; + + // ensure that none of the excluded terms is in the search result + if ( + [...excludedTerms].some( + (term) => + terms[term] === file || + titleTerms[term] === file || + (terms[term] || []).includes(file) || + (titleTerms[term] || []).includes(file) + ) + ) + break; + + // select one (max) score for the file. + const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w])); + // add result to the result list + results.push([ + docNames[file], + titles[file], + "", + null, + score, + filenames[file], + ]); + } + return results; + }, + + /** + * helper function to return a node containing the + * search summary for a given text. keywords is a list + * of stemmed words. + */ + makeSearchSummary: (htmlText, keywords) => { + const text = Search.htmlToText(htmlText); + if (text === "") return null; + + const textLower = text.toLowerCase(); + const actualStartPosition = [...keywords] + .map((k) => textLower.indexOf(k.toLowerCase())) + .filter((i) => i > -1) + .slice(-1)[0]; + const startWithContext = Math.max(actualStartPosition - 120, 0); + + const top = startWithContext === 0 ? "" : "..."; + const tail = startWithContext + 240 < text.length ? "..." : ""; + + let summary = document.createElement("p"); + summary.classList.add("context"); + summary.textContent = top + text.substr(startWithContext, 240).trim() + tail; + + return summary; + }, +}; + +_ready(Search.init); diff --git a/v2.4.1/_static/sphinx_highlight.js b/v2.4.1/_static/sphinx_highlight.js new file mode 100644 index 000000000..aae669d7e --- /dev/null +++ b/v2.4.1/_static/sphinx_highlight.js @@ -0,0 +1,144 @@ +/* Highlighting utilities for Sphinx HTML documentation. */ +"use strict"; + +const SPHINX_HIGHLIGHT_ENABLED = true + +/** + * highlight a given string on a node by wrapping it in + * span elements with the given class name. + */ +const _highlight = (node, addItems, text, className) => { + if (node.nodeType === Node.TEXT_NODE) { + const val = node.nodeValue; + const parent = node.parentNode; + const pos = val.toLowerCase().indexOf(text); + if ( + pos >= 0 && + !parent.classList.contains(className) && + !parent.classList.contains("nohighlight") + ) { + let span; + + const closestNode = parent.closest("body, svg, foreignObject"); + const isInSVG = closestNode && closestNode.matches("svg"); + if (isInSVG) { + span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); + } else { + span = document.createElement("span"); + span.classList.add(className); + } + + span.appendChild(document.createTextNode(val.substr(pos, text.length))); + parent.insertBefore( + span, + parent.insertBefore( + document.createTextNode(val.substr(pos + text.length)), + node.nextSibling + ) + ); + node.nodeValue = val.substr(0, pos); + + if (isInSVG) { + const rect = document.createElementNS( + "http://www.w3.org/2000/svg", + "rect" + ); + const bbox = parent.getBBox(); + rect.x.baseVal.value = bbox.x; + rect.y.baseVal.value = bbox.y; + rect.width.baseVal.value = bbox.width; + rect.height.baseVal.value = bbox.height; + rect.setAttribute("class", className); + addItems.push({ parent: parent, target: rect }); + } + } + } else if (node.matches && !node.matches("button, select, textarea")) { + node.childNodes.forEach((el) => _highlight(el, addItems, text, className)); + } +}; +const _highlightText = (thisNode, text, className) => { + let addItems = []; + _highlight(thisNode, addItems, text, className); + addItems.forEach((obj) => + obj.parent.insertAdjacentElement("beforebegin", obj.target) + ); +}; + +/** + * Small JavaScript module for the documentation. + */ +const SphinxHighlight = { + + /** + * highlight the search words provided in localstorage in the text + */ + highlightSearchWords: () => { + if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight + + // get and clear terms from localstorage + const url = new URL(window.location); + const highlight = + localStorage.getItem("sphinx_highlight_terms") + || url.searchParams.get("highlight") + || ""; + localStorage.removeItem("sphinx_highlight_terms") + url.searchParams.delete("highlight"); + window.history.replaceState({}, "", url); + + // get individual terms from highlight string + const terms = highlight.toLowerCase().split(/\s+/).filter(x => x); + if (terms.length === 0) return; // nothing to do + + // There should never be more than one element matching "div.body" + const divBody = document.querySelectorAll("div.body"); + const body = divBody.length ? divBody[0] : document.querySelector("body"); + window.setTimeout(() => { + terms.forEach((term) => _highlightText(body, term, "highlighted")); + }, 10); + + const searchBox = document.getElementById("searchbox"); + if (searchBox === null) return; + searchBox.appendChild( + document + .createRange() + .createContextualFragment( + '" + ) + ); + }, + + /** + * helper function to hide the search marks again + */ + hideSearchWords: () => { + document + .querySelectorAll("#searchbox .highlight-link") + .forEach((el) => el.remove()); + document + .querySelectorAll("span.highlighted") + .forEach((el) => el.classList.remove("highlighted")); + localStorage.removeItem("sphinx_highlight_terms") + }, + + initEscapeListener: () => { + // only install a listener if it is really needed + if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return; + + document.addEventListener("keydown", (event) => { + // bail for input elements + if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return; + // bail with special keys + if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return; + if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) { + SphinxHighlight.hideSearchWords(); + event.preventDefault(); + } + }); + }, +}; + +_ready(SphinxHighlight.highlightSearchWords); +_ready(SphinxHighlight.initEscapeListener); diff --git a/v2.4.1/basic_usage.html b/v2.4.1/basic_usage.html new file mode 100644 index 000000000..5f52fc4d6 --- /dev/null +++ b/v2.4.1/basic_usage.html @@ -0,0 +1,426 @@ + + + + + + + How it Works — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

How it Works

+

Typhos has three major building blocks that combine into the final display seen +by the operator:

+
    +
  • TyphosSuite : The overall view for a Typhos window. It allows the +operator to view all of the loaded components and tools.

  • +
  • TyphosDeviceDisplay : This is the widget created for a standard +ophyd.Device. Signals can be organized based on their Kind and +description.

  • +
  • typhos.tools : These are widgets that interface with external +applications. While you may have other GUIs for these systems, +typhos.tools are built especially to handle the handshaking between all +the information stored on your device and the tool you are interfacing with. +This saves your operator clicks and ensures consistency in use.

  • +
+

All three of the widgets listed above share a similar API for creation. +Instantiating the object by itself handles loading the container widgets and +placing them in the correct place, but these do not accept ophyd.Device +arguments. The reason for this is to ensure that we can use all of the +typhos screens as templates, and regardless or not of whether you have an +ophyd.Device you can always populate the screens by hand. If you do in fact +have an ophyd.Device every class has an add_device method and +alternatively and be constructed using the from_device classmethod.

+
+
+class typhos.utils.TyphosBase(*args, **kwargs)[source]
+

Base widget for all Typhos widgets that interface with devices

+
+ +
+

Interpreting a Device

+

Typhos interprets the internal structure of the ophyd.Device to create the +PyDM user interface, so the most intuitive way to configure the created +display is to include components on the device itself. This also has the advantage +of keeping your Python API and display in sync, making the transition from +using screens to using an IPython shell seamless.

+

For the following applications we’ll use the motor simulation contained +within ophyd itself. We also need to create a QApplication before we +create any widgets:

+
In [1]: from qtpy.QtWidgets import QApplication
+
+In [2]: app = QApplication([])
+
+
+
+

Using Happi

+

While happi is not a requirement for using typhos, it is recommended. +For more information, visit the GitHub +repository. The main purpose of the package is to store information on our +Ophyd objects so that we can load them in a variety of contexts. If you do not +use happi you will need to create your objects and displays in the same +process.

+

From the command-line, using typhos and happi together is easy. For example, +to load an auto-generated typhos screen for your device named "my_device" +would only require the following:

+
$ typhos my_device
+
+
+

typhos automatically configures the happi client, finds your device, and +creates an appropriate screen for it.

+

If you are looking to integrate typhos at the Python source code level, +consider the following example which uses typhos with happi:

+
import happi
+from typhos.plugins import register_client
+
+# Initialize a new JSON based client
+client = happi.Client(path='db.json', initialize=True)
+# Register this with typhos
+register_client(client)
+# Add a device to our new database
+device = happi.Device(device_class='ophyd.sim.SynAxis',
+                      prefix='Tst:Mtr', args=[], kwargs='{{name}}',
+                      name='my_motor', beamline='TST')
+client.add_device(device)
+
+
+

In practice, it is not necessary to call register_client() if you have +configured the $HAPPI_CFG environment variable such that +happi.Client.from_config yields the desired client.

+

We can now check that we can load the complete SynAxis object.

+
motor = client.load_device(name='my_motor')
+
+
+
+
+

Signals of Devices

+

When making a custom screen, you can access signals associated with your device +in several ways, in order of suggested use:

+
    +
  1. By using the typhos built-in “signal” plugin to connect to the signal with +the dotted ophyd name, just as you would use in an IPython session. +In the designer “channel” property, specify: sig://device_name.attr +with as many .attrs required to reach the signal from the top-level +device as needed. +For example, for a motor named “my_motor”, you could use: +sig://my_motor.user_readback

  2. +
  3. An alternate signal name is available, that which is seen by the data +acquisition system (e.g., the databroker by way of bluesky). Generally, +characters seen as invalid for a MongoDB are replaced with an underscore +(_). To check a signal’s name, see the .name property of that +signal. +For example, for a motor named “my_motor”, you could use: +sig://my_motor_user_readback

  4. +
  5. By PV name directly. Assuming your signal is available through the +underlying control system (EPICS, for example), you could look and see which +PVs your signal talks to and use those directly. That is, +my_motor.user_readback.pvname would tell you which EPICS PV the user +readback uses. From there, you could set the widget’s channel to use EPICS +Channel Access with ca://pv_name_here.

  6. +
+
+
+

Display Signals

+

The first thing we’ll talk about is showing a group of signals associated with +our motor object in a basic form called a +TyphosSignalPanel. Simply inspecting the device reveals a few +signals for us to display

+
In [3]: motor.component_names
+Out[3]: ('readback', 'setpoint', 'velocity', 'acceleration', 'unused')
+
+
+

It is crucial that we understand the importance of these signals to the +operator. In order to glean this information from the object the kind +attributes are inspected. For more information see the ophyd documentation. A quick inspection of +the various attributes allows us to see how our signals are organized.

+
# Most important signal(s)
+In [4]: motor.hints
+Out[4]: {'fields': ['motor']}
+
+# Important signals, all hints will be found here as well
+In [5]: motor.read()
+Out[5]: 
+OrderedDict([('motor', {'value': 0, 'timestamp': 1680647506.9771109}),
+             ('motor_setpoint', {'value': 0, 'timestamp': 1680647506.977109})])
+
+# Configuration information
+In [6]: motor.read_configuration()
+Out[6]: 
+OrderedDict([('motor_velocity', {'value': 1, 'timestamp': 1680647506.9779942}),
+             ('motor_acceleration',
+              {'value': 1, 'timestamp': 1680647506.978023})])
+
+
+

The TyphosSignalPanel will render these, allowing us to select a +subset of the signals to display based on their kind. Below both the +QtDesigner using happi and the corresponding Python code is shown +as well:

+
In [7]: from typhos import TyphosSignalPanel
+
+In [8]: panel = TyphosSignalPanel.from_device(motor)
+
+
+
+_images/kind_panel.gif +
+

Now, at first glance it may not be obvious, but there is a lot of information +here! We know which of these signals an operator will want to control and which +ones are purely meant to be read back. We also have these signals grouped by +their importance to operation, each with a terse human-readable description of +what the Signal represents.

+
+
+

Filling Templates

+

Taking this concept further, instead of filling a single panel +TyphosDeviceDisplay allows a template to be created with a multitude +of widgets and panels. Typhos will find widgets that accept devices, but do +not have any devices already. Typhos comes with some default templates, and you +can cycle between them by changing the display_type

+

Once again, both the Python code and the QtDesigner use cases are +shown:

+
In [9]: from typhos import TyphosDeviceDisplay
+
+In [10]: display = TyphosDeviceDisplay.from_device(motor)
+
+
+
+_images/device_display.gif +
+
+
+
+

The TyphosSuite

+

A complete application can be made by loading the TyphosSuite. Below +is the complete code from start to finish required to create the suite. Look at +the TyphosSuited.default_tools to control which typhos.tools are +loaded.

+
from ophyd.sim import motor
+from qtpy.QtWidgets import QApplication
+from typhos.suite import TyphosSuite
+from typhos.utils import apply_standard_stylesheets
+
+# Create our application
+app = QApplication([])
+apply_standard_stylesheets()  # Optional
+suite = TyphosSuite.from_device(motor)
+
+# Launch
+suite.show()
+app.exec_()
+
+
+
+
+

Using the StyleSheet

+

Typhos ships with two stylesheets to improve the look and feel of the widgets. +When invoking typhos from the CLI as normal, you can pass +the --dark flag to use the dark stylesheet instead of the light mode, +and a --stylesheet-add argument to use your own stylesheet in addition to Typhos’s. +If you want to completely ignore Typhos’s normal stylesheet loading and use your own, +you can pass the --stylesheet-override argument. You can pass these arguments +multiple times to include multiple stylesheets.

+

Typhos also uses the same stylesheet environment variables as PyDM to load additional +stylesheets. The PyDM environment variables respected here are:

+
    +
  • PYDM_STYLESHEET, a path-like variable that should contain file paths to qss +stylesheets if set.

  • +
  • PYDM_STYLESHEET_INCLUDE_DEFAULT, which should be set to 1 to include the +default PyDM stylesheet or unset to not include it.

  • +
+

The priority order for stylesheets in the case of conflicts is:

+
    +
  1. The explicit styleSheet property on the display template

  2. +
  3. The style elements from --stylesheet-add

  4. +
  5. User stylesheets from PYDM_STYLESHEET_INCLUDE_DEFAULT

  6. +
  7. Typhos’s stylesheet (either the dark or the light variant)

  8. +
  9. The built-in PyDM stylesheet

  10. +
+

Outside of the CLI, the stylesheets can be applied using typhos.apply_standard_stylesheets(). +This function also handles setting the “Fusion” QStyle which helps +make the interface have an operating system independent look and feel.

+
+
+

Using the Documentation Widget

+

Typhos has a built-in documentation helper, which allows for the in-line +linking and display of a user-provided website.

+

To inform Typhos how to load documentation specific to your facility, please +customize the following environment variables.

+
    +
  1. TYPHOS_HELP_URL (str): The help URL format string. The help URL will +be formatted with the ophyd device pertinent to the display, such that you +may access its name, PV prefix, happi metadata (if available), and so on. +For example, if a Confluence server exists at +https://my-confluence-site.example.com/Controls/ with document names +that match your devices, TYPHOS_HELP_URL should be set to +https://my-confluence-site.example.com/Controls/{device.name}. +If, perhaps, only top-level devices are guaranteed to have documentation, +consider using: device.root.name instead in the format string.

  2. +
  3. TYPHOS_HELP_HEADERS (json): headers to pass to HELP_URL. This should be +in a JSON format, such as {"my_key":"my_value"}.

  4. +
  5. TYPHOS_HELP_HEADERS_HOSTS (str): comma-delimited hosts that headers may +be sent to, aside from the host configured in TYPHOS_HELP_URL.

  6. +
  7. TYPHOS_HELP_TOKEN (str): An optional token for the bearer authentication +scheme - e.g., personal access tokens with Confluence. This is a shortcut +to add a header "Authorization" with the value +"Bearer ${TYPHOS_HELP_TOKEN}".

  8. +
+
+
+

Using the Jira Bug Reporting Widget

+

Typhos has an optional built-in widget to generate Jira user stories/bug +reports.

+

A prerequisite to this support is, of course, a working Jira installation +and a pre-configured issue collector.

+
    +
  1. TYPHOS_JIRA_URL (str): The Jira issue collector URL. This will resemble +https://jira.example.com/rest/collectors/1.0/template/custom/....

  2. +
  3. TYPHOS_JIRA_HEADERS (json): headers to pass to the Jira request, if +needed. This should be in a JSON format, such as {"my_key":"my_value"}.

  4. +
  5. TYPHOS_JIRA_TOKEN (str): An optional token for the bearer authentication +scheme - e.g., personal access tokens with Confluence. This is a shortcut +to add a header "Authorization" with the value +"Bearer ${TYPHOS_JIRA_TOKEN}".

  6. +
  7. TYPHOS_JIRA_EMAIL_SUFFIX (str): the default e-mail suffix to put on +usernames, such as "@example.com".

  8. +
+
+
+

Launching the Examples

+

There are example screens in the typhos.examples submodule. After +installing typhos, you can launch them as follows:

+
    +
  • python -m typhos.examples.panel

  • +
  • python -m typhos.examples.positioner

  • +
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/cli.html b/v2.4.1/cli.html new file mode 100644 index 000000000..a0dc31977 --- /dev/null +++ b/v2.4.1/cli.html @@ -0,0 +1,204 @@ + + + + + + + Command Line Utilities — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Command Line Utilities

+

This module defines the typhos command line utility

+
usage: __main__.py [-h] [--layout LAYOUT] [--cols COLS]
+                   [--display-type DISPLAY_TYPE] [--scrollable SCROLLABLE]
+                   [--size SIZE] [--hide-displays] [--happi-cfg HAPPI_CFG]
+                   [--fake-device] [--version] [--verbose] [--dark]
+                   [--stylesheet-override STYLESHEET_OVERRIDE]
+                   [--stylesheet-add STYLESHEET_ADD]
+                   [--profile-modules [PROFILE_MODULES ...]]
+                   [--profile-output PROFILE_OUTPUT]
+                   [--benchmark [BENCHMARK ...]] [--exit-after EXIT_AFTER]
+                   [devices ...]
+
+Create a TyphosSuite for device/s stored in a Happi Database
+
+positional arguments:
+  devices               Device names to load in the TyphosSuite or class name
+                        with parameters on the format:
+                        package.ClassName[{"param1":"val1",...}]
+
+optional arguments:
+  -h, --help            show this help message and exit
+  --layout LAYOUT       Select a alternate layout for suites of many devices.
+                        Valid options are "horizontal" (default), "vertical",
+                        "grid", "flow", and any unique shortenings of those
+                        options.
+  --cols COLS           The number of columns to use for the grid layout if
+                        selected in the layout argument. This will have no
+                        effect for other layouts.
+  --display-type DISPLAY_TYPE
+                        The kind of display to open for each device at initial
+                        load. Valid options are "embedded", "detailed"
+                        (default), "engineering", and any unique shortenings
+                        of those options.
+  --scrollable SCROLLABLE
+                        Whether or not to include the scrollbar. Valid options
+                        are "auto", "true", "false", and any unique
+                        shortenings of those options. Selecting "auto" will
+                        include a scrollbar for non-embedded layouts.
+  --size SIZE           A starting x,y size for the typhos suite. Useful if
+                        the default size is not suitable for your application.
+                        Example: --size 1000,1000
+  --hide-displays       Option to start with subdisplays hidden instead of
+                        shown.
+  --happi-cfg HAPPI_CFG
+                        Location of happi configuration file if not specified
+                        by $HAPPI_CFG environment variable
+  --fake-device         Create fake devices with no EPICS connections. This
+                        does not yet work for happi devices. An example
+                        invocation: typhos --fake-device ophyd.EpicsMotor[]
+  --version, -V         Current version and location of Typhos installation.
+  --verbose, -v         Show the debug logging stream
+  --dark                Use the QDarkStyleSheet shipped with Typhos
+  --stylesheet-override STYLESHEET_OVERRIDE, --stylesheet STYLESHEET_OVERRIDE
+                        Override all built-in stylesheets, using this
+                        stylesheet instead.
+  --stylesheet-add STYLESHEET_ADD
+                        Include an additional stylesheet in the loading
+                        process. This stylesheet will take priority over all
+                        built-in stylesheets, but not over a template or
+                        widget's styleSheet property.
+  --profile-modules [PROFILE_MODULES ...]
+                        Submodules to profile during the execution. If no
+                        specific modules are specified, profiles all
+                        submodules of typhos. Turns on line profiling.
+  --profile-output PROFILE_OUTPUT
+                        Filename to output the profile results to. If omitted,
+                        prints results to stdout. Turns on line profiling.
+  --benchmark [BENCHMARK ...]
+                        Runs the specified benchmarking tests instead of
+                        launching a screen. If no specific tests are
+                        specified, runs all of them. Turns on line profiling.
+  --exit-after EXIT_AFTER
+                        (For profiling purposes) Exit typhos after the
+                        provided number of seconds
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/connections.html b/v2.4.1/connections.html new file mode 100644 index 000000000..56b432012 --- /dev/null +++ b/v2.4.1/connections.html @@ -0,0 +1,198 @@ + + + + + + + Application Connections — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Application Connections

+
+

Ophyd Signals

+

Typhos takes advantage of the flexible data plugin system contained within +PyDM and the abstraction of the “control layer” within Ophyd. In the +SignalPanel, objects signals are queried for their type. If these are +determined to be coming from EPICS the data plugin configured within +PyDM is used directly, any other kind of signal goes through the generic +SignalPlugin. This uses the subscription system contained within +Ophyd to keep widgets values updated. One caveat is that PyDM requires +that channels are specified by a string identifier. In the case of +ophyd.Signal objects we want to ensure that these are passed by reference +to avoid duplicating objects. This means the workflow for adding these has one +more additonal step where the Signal is registered with the PyDM +plugin.

+
from typhos.plugins import register_signal
+
+# Create an Ophyd Signal
+my_signal = ophyd.Signal(name='this_signal')
+# Register this with the Plugin
+register_signal(my_signal)
+# This signal is now available for use with PyDM widgets
+PyDMWidget(channel='sig://this_signal')
+
+
+

Note that this is all done for you if you use the SignalPanel, but +maybe useful if you would like to use the SignalPlugin directly.

+
+

Inclusion of Metadata

+

In many cases just knowing the value of a signal is not enough to accurately +display it. Extra pieces of information such as the units and precision of +information can provide a richer operator experience. Typhos counts on this +information being available in the output of describe method of the signal. +If you want your child ophyd.Signal class to convey this information make +sure that it is expressed properly in the output of describe.

+ + + + + + + + + + + + + + + + + +

Metadata

Description Key

Precision

“precision”

Enumeration Strings

“enum_strs”

Engineering Units

“units”

+
+
+
+

Happi Plugin

+

The PyDM Plugin interface makes no mandate about the type of signals that we +connect to our widgets. The HappiPlugin and corresponding +HappiChannel contains alternative signals in order to send entire ophyd +objects from a stored database to our widgets. This is useful where we want to +populate a template with an entire devices signals instead of connecting +widgets one by one.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/display.html b/v2.4.1/display.html new file mode 100644 index 000000000..6a0f1a813 --- /dev/null +++ b/v2.4.1/display.html @@ -0,0 +1,1039 @@ + + + + + + + Suite and Displays — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Suite and Displays

+

Typhos has two major widgets that users are expected to interface with. The +first is the TyphosDeviceDisplay, which shows device information, and +TyphosSuite which contains multiple devices and tools. This is the +barebones implementation. No signals, or widgets are automatically populated in +the screen. In fact, by default most of the widgets will be hidden. You can +then manually add signals to the panels and plots, the panels will only show +themselves when you add PVs.

+
+

TyphosSuite

+
+
+class typhos.TyphosSuite(parent: QtWidgets.QWidget | None = None, *, pin: bool = False, content_layout: QtWidgets.QLayout | None = None, default_display_type: DisplayTypes = DisplayTypes.detailed_screen, scroll_option: ScrollOptions = ScrollOptions.auto)[source]
+

This suite combines tools and devices into a single widget.

+

A ParameterTree is contained in a QPopBar +which shows tools and the hierarchy of a device along with options to +show or hide them.

+
+
Parameters:
+
    +
  • parent (QWidget, optional) –

  • +
  • pin (bool, optional) – Pin the parameter tree on startup.

  • +
  • content_layout (QLayout, optional) – Sets the layout for when we have multiple subdisplays +open in the suite. This will have a horizontal layout by +default but can be changed as needed for the use case.

  • +
  • default_display_type (DisplayType, optional) – DisplayType enum that determines the type of display to open when we +add a device to the suite. Defaults to DisplayType.detailed_screen.

  • +
  • scroll_option (ScrollOptions, optional) – ScrollOptions enum that determines the behavior of scrollbars +in the suite. Default is ScrollOptions.auto, which enables +scrollbars for detailed and engineering screens but not for +embedded displays.

  • +
+
+
+
+
+default_tools
+

The default tools to use in the suite. In the form of +{'tool_name': ToolClass}.

+
+
Type:
+

dict

+
+
+
+ +
+
+add_device(device, children=True, category='Devices')[source]
+

Add a device to the suite.

+
+
Parameters:
+
    +
  • device (ophyd.Device) – The device to add.

  • +
  • children (bool, optional) – Also add any subdevices of this device to the suite as well.

  • +
  • category (str, optional) – Category of device. By default, all devices will just be added to +the “Devices” group

  • +
+
+
+
+ +
+
+add_lazy_subdisplay(name: str, display_class: type[PyQt5.QtWidgets.QWidget], category: str)[source]
+

Add an arbitrary widget to the tree of available widgets and tools.

+
+
Parameters:
+
    +
  • name (str) – Name to be displayed in the tree

  • +
  • display_class (subclass of QWidget) – QWidget class to show in the dock when expanded.

  • +
  • category (str) – The top level group to place the controls under in the tree. If the +category does not exist, a new one will be made

  • +
+
+
+
+ +
+
+add_subdisplay(name, display, category)[source]
+

Add an arbitrary widget to the tree of available widgets and tools.

+
+
Parameters:
+
    +
  • name (str) – Name to be displayed in the tree

  • +
  • display (QWidget) – QWidget to show in the dock when expanded.

  • +
  • category (str) – The top level group to place the controls under in the tree. If the +category does not exist, a new one will be made

  • +
+
+
+
+ +
+
+add_tool(name: str, tool: type[PyQt5.QtWidgets.QWidget])[source]
+

Add a widget to the toolbar.

+

Shortcut for:

+
suite.add_subdisplay(name, tool, category='Tools')
+
+
+
+
Parameters:
+
    +
  • name (str) – Name of tool to be displayed in sidebar

  • +
  • tool (QWidget) – Widget to be added to .ui.subdisplay

  • +
+
+
+
+ +
+
+embed_subdisplay(widget)[source]
+

Embed a display in the dock system.

+
+ +
+
+classmethod from_device(device: Device, parent: QtWidgets.QWidget | None = None, tools: dict[str, type] | None | DEFAULT_TOOLS = <object object>, pin: bool = False, content_layout: QtWidgets.QLayout | None = None, default_display_type: DisplayTypes = DisplayTypes.detailed_screen, scroll_option: ScrollOptions = ScrollOptions.auto, show_displays: bool = True, **kwargs) TyphosSuite[source]
+

Create a new TyphosSuite from an ophyd.Device.

+
+
Parameters:
+
    +
  • device (ophyd.Device) – The device to use.

  • +
  • children (bool, optional) – Choice to include child Device components

  • +
  • parent (QWidget) –

  • +
  • tools (dict, optional) – Tools to load for the object. dict should be name, class pairs. +By default these will be .default_tools, but None can be +passed to avoid tool loading completely.

  • +
  • pin (bool, optional) – Pin the parameter tree on startup.

  • +
  • content_layout (QLayout, optional) – Sets the layout for when we have multiple subdisplays +open in the suite. This will have a horizontal layout by +default but can be changed as needed for the use case.

  • +
  • default_display_type (DisplayTypes, optional) – DisplayTypes enum that determines the type of display to open when +we add a device to the suite. Defaults to +DisplayTypes.detailed_screen.

  • +
  • scroll_option (ScrollOptions, optional) – ScrollOptions enum that determines the behavior of scrollbars +in the suite. Default is ScrollOptions.auto, which enables +scrollbars for detailed and engineering screens but not for +embedded displays.

  • +
  • show_displays (bool, optional) – If True (default), open all the included device displays. +If False, do not open any of the displays.

  • +
  • **kwargs – Passed to TyphosSuite.add_device()

  • +
+
+
+
+ +
+
+classmethod from_devices(devices: list[Device], parent: QtWidgets.QWidget | None = None, tools: dict[str, type] | None | DEFAULT_TOOLS = <object object>, pin: bool = False, content_layout: QtWidgets.QLayout | None = None, default_display_type: DisplayTypes = DisplayTypes.detailed_screen, scroll_option: ScrollOptions = ScrollOptions.auto, show_displays: bool = True, **kwargs) TyphosSuite[source]
+

Create a new TyphosSuite from an iterator of ophyd.Device

+
+
Parameters:
+
    +
  • device (ophyd.Device) –

  • +
  • children (bool, optional) – Choice to include child Device components

  • +
  • parent (QWidget) –

  • +
  • tools (dict, optional) – Tools to load for the object. dict should be name, class pairs. +By default these will be .default_tools, but None can be +passed to avoid tool loading completely.

  • +
  • pin (bool, optional) – Pin the parameter tree on startup.

  • +
  • content_layout (QLayout, optional) – Sets the layout for when we have multiple subdisplays +open in the suite. This will have a horizontal layout by +default but can be changed as needed for the use case.

  • +
  • default_display_type (DisplayTypes, optional) – DisplayTypes enum that determines the type of display to open when +we add a device to the suite. Defaults to +DisplayTypes.detailed_screen.

  • +
  • scroll_option (ScrollOptions, optional) – ScrollOptions enum that determines the behavior of scrollbars +in the suite. Default is ScrollOptions.auto, which enables +scrollbars for detailed and engineering screens but not for +embedded displays.

  • +
  • show_displays (bool, optional) – If True (default), open all the included device displays. +If False, do not open any of the displays.

  • +
  • **kwargs – Passed to TyphosSuite.add_device()

  • +
+
+
+
+ +
+
+get_subdisplay(display)[source]
+

Get a subdisplay by name or contained device.

+
+
Parameters:
+

display (str or Device) – Name of screen or device

+
+
Returns:
+

widget – Widget that is a member of the ui.subdisplay

+
+
Return type:
+

QWidget

+
+
+

Example

+
suite.get_subdisplay(my_device.x)
+suite.get_subdisplay('My Tool')
+
+
+
+ +
+
+hide_subdisplay(widget)[source]
+

Hide a visible subdisplay.

+
+
Parameters:
+

widget (SidebarParameter or Subdisplay) – If you give a SidebarParameter, we will find the corresponding +widget and hide it. If the widget provided to us is inside a +DockWidget we will close that, otherwise the widget is just hidden.

+
+
+
+ +
+
+hide_subdisplays()[source]
+

Hide all open displays.

+
+ +
+
+save()[source]
+

Save suite settings to a file using typhos.utils.save_suite().

+

A QFileDialog will be used to query the user for the desired +location of the created Python file

+

The template will be of the form:

+
import sys
+import typhos.cli
+
+devices = {devices}
+
+def create_suite(cfg=None):
+    return typhos.cli.create_suite(devices, cfg=cfg)
+
+if __name__ == '__main__':
+    typhos.cli.typhos_cli(devices + sys.argv[1:])
+
+
+
+ +
+
+show_subdisplay(widget)[source]
+

Open a display in the dock system.

+
+
Parameters:
+

widget (QWidget, SidebarParameter or str) – If given a SidebarParameter from the tree, the widget will be +shown and the sidebar item update. Otherwise, the information is +passed to get_subdisplay()

+
+
+
+ +
+
+property tools
+

Tools loaded into the suite.

+
+ +
+
+property top_level_groups
+

Get top-level groups.

+

This is of the form:

+
{'name': QGroupParameterItem}
+
+
+
+ +
+ +
+
+

TyphosDeviceDisplay

+
+
+class typhos.TyphosDeviceDisplay(parent: QWidget | None = None, *, scrollable: bool | None = None, composite_heuristics: bool = True, embedded_templates: list[str] | None = None, detailed_templates: list[str] | None = None, engineering_templates: list[str] | None = None, display_type: DisplayTypes | str | int = 'detailed_screen', scroll_option: ScrollOptions | str | int = 'auto', nested: bool = False)[source]
+

Main display for a single ophyd Device.

+

This contains the widgets for all of the root devices signals, and any +methods you would like to display. By typhos convention, the base +initialization sets up the widgets and the from_device() class +method will automatically populate the resulting display.

+
+
Parameters:
+
    +
  • parent (QWidget, optional) – The parent widget.

  • +
  • scrollable (bool, optional) – Semi-deprecated parameter. Use scroll_option instead. +If True, put the loaded template into a QScrollArea. +If False, the display widget will go directly in this widget’s +layout. +If omitted, scroll_option is used instead.

  • +
  • composite_heuristics (bool, optional) – Enable composite heuristics, which may change the suggested detailed +screen based on the contents of the added device. See also +suggest_composite_screen().

  • +
  • embedded_templates (list, optional) – List of embedded templates to use in addition to those found on disk.

  • +
  • detailed_templates (list, optional) – List of detailed templates to use in addition to those found on disk.

  • +
  • engineering_templates (list, optional) – List of engineering templates to use in addition to those found on +disk.

  • +
  • display_type (DisplayTypes, str, or int, optional) – The default display type.

  • +
  • scroll_option (ScrollOptions, str, or int, optional) – The scroll behavior.

  • +
  • nested (bool, optional) – An optional annotation for a display that may be nested inside another.

  • +
+
+
+
+
+TemplateEnum
+

alias of DisplayTypes

+
+ +
+
+add_device(device, macros=None)[source]
+

Add a Device and signals to the TyphosDeviceDisplay.

+

The full dictionary of macros is built with the following order of +precedence:

+
1. Macros from the device metadata itself.
+2. If available, `name`, and `prefix` will be added from the device.
+3. The argument ``macros`` is then used to fill/update the final
+   macro dictionary.
+
+
+

This will also register the device’s signals in the sig:// plugin. +This means that any templates can refer to their device’s signals by +name.

+
+
Parameters:
+
    +
  • device (ophyd.Device) – The device to add.

  • +
  • macros (dict, optional) – Additional macros to use/replace the defaults.

  • +
+
+
+
+ +
+
+composite_heuristics
+

Allow composite screen to be suggested first by heuristics.

+
+ +
+
+copy_to_clipboard()[source]
+

Copy the display image to the clipboard.

+
+ +
+
+property current_template
+

Get the current template being displayed.

+
+ +
+
+property device
+

Get the device associated with this Device Display.

+
+ +
+
+device_class
+

Get the full class with module name of loaded device.

+
+ +
+
+device_name
+

Get the name of the loaded device.

+
+ +
+
+display_type
+

Get or set the current display type.

+
+ +
+
+property display_widget
+

Get the widget generated from the template.

+
+ +
+
+force_template
+

Force a specific template.

+
+ +
+
+classmethod from_class(klass, *, template=None, macros=None, **kwargs)[source]
+

Create a new TyphosDeviceDisplay from a Device class.

+

Loads the signals in to the appropriate positions and sets the title to +a cleaned version of the device name.

+
+
Parameters:
+
    +
  • klass (str or class) –

  • +
  • template (str, optional) – Set the display_template.

  • +
  • macros (dict, optional) – Macro substitutions to be placed in template.

  • +
  • **kwargs – Extra arguments are used at device instantiation.

  • +
+
+
Return type:
+

TyphosDeviceDisplay

+
+
+
+ +
+
+classmethod from_device(device, template=None, macros=None, **kwargs)[source]
+

Create a new TyphosDeviceDisplay from a Device.

+

Loads the signals in to the appropriate positions and sets the title to +a cleaned version of the device name

+
+
Parameters:
+
    +
  • device (ophyd.Device) –

  • +
  • template (str, optional) – Set the display_template.

  • +
  • macros (dict, optional) – Macro substitutions to be placed in template.

  • +
  • **kwargs – Passed to the class init.

  • +
+
+
+
+ +
+
+get_best_template(display_type, macros)[source]
+

Get the best template for the given display type.

+
+
Parameters:
+
    +
  • display_type (DisplayTypes, str, or int) – The display type.

  • +
  • macros (dict) – Macros to use when loading the template.

  • +
+
+
+
+ +
+
+hideEmpty
+

Toggle hiding or showing empty panels.

+
+ +
+
+load_best_template()[source]
+

Load the best available template for the current display type.

+
+ +
+
+property macros
+

Get or set the macros for the display.

+
+ +
+
+scroll_option
+

Place the display in a scrollable area.

+
+ +
+
+search_for_templates()[source]
+

Search the filesystem for device-specific templates.

+
+ +
+
+classmethod suggest_composite_screen(device_cls)[source]
+

Suggest to use the composite screen for the given class.

+
+
Returns:
+

composite – If True, favor the composite screen.

+
+
Return type:
+

bool

+
+
+
+ +
+
+to_image()[source]
+

Return the entire display as a QtGui.QImage.

+
+
Returns:
+

The display, as an image.

+
+
Return type:
+

QtGui.QImage

+
+
+
+ +
+ +
+
+

Standardized Display Title

+
+
+class typhos.display.TyphosDisplayTitle(title='${name}', *, show_switcher=True, show_underline=True, parent=None)[source]
+

Standardized Typhos Device Display title.

+
+
Parameters:
+
    +
  • title (str, optional) – The initial title text, which may contain macros.

  • +
  • show_switcher (bool, optional) – Show the TyphosDisplaySwitcher.

  • +
  • show_underline (bool, optional) – Show the underline separator.

  • +
  • parent (QtWidgets.QWidget, optional) – The parent widget.

  • +
+
+
+
+
+add_device(device)[source]
+

Typhos hook for setting the associated device.

+
+ +
+
+pop_out_help()[source]
+

Pop out the help widget.

+
+ +
+
+set_device_display(display)[source]
+

Typhos callback: set the TyphosDeviceDisplay.

+
+ +
+
+show_switcher
+

Get or set whether to show the display switcher.

+
+ +
+
+show_underline
+

Get or set whether to show the underline.

+
+ +
+
+toggle_help(show)[source]
+

Toggle the help visibility.

+
+ +
+ +
+
+class typhos.display.TyphosDisplaySwitcher(parent=None, **kwargs)[source]
+

Display switcher set of buttons for use with a TyphosDeviceDisplay.

+
+
+add_device(device)[source]
+

Typhos hook for setting the associated device.

+
+ +
+
+set_device_display(display)[source]
+

Typhos hook for setting the associated device display.

+
+ +
+ +
+
+class typhos.display.TyphosTitleLabel[source]
+

A label class intended for use as a standardized title.

+
+
+toggle_requested
+

A Qt signal indicating that the user clicked on the title. By default, +this hides any nested panels underneath the title.

+
+
Type:
+

QtCore.Signal

+
+
+
+ +
+
+mousePressEvent(event)[source]
+

Overridden qt hook for a mouse press.

+
+ +
+ +
+

Tool buttons

+
+
+class typhos.display.TyphosToolButton(icon=None, *, parent=None)[source]
+

Base class for tool buttons used in the TyphosDisplaySwitcher.

+
+
Parameters:
+
    +
  • icon (QIcon or str, optional) – See get_icon() for options.

  • +
  • parent (QtWidgets.QWidget, optional) – The parent widget.

  • +
+
+
+
+
+DEFAULT_ICON
+

The default icon from fontawesome to use.

+
+
Type:
+

str

+
+
+
+ +
+
+generate_context_menu()[source]
+

Context menu request: override in subclasses.

+
+ +
+
+classmethod get_icon(icon=None)[source]
+

Get a QIcon, if specified, or fall back to the default.

+
+
Parameters:
+

icon (str or QtGui.QIcon) – If a string, assume it is from fontawesome. +Otherwise, use the icon instance as-is.

+
+
+
+ +
+
+open_context_menu(ev)[source]
+

Open the instance-specific context menu.

+
+
Parameters:
+

ev (QEvent) –

+
+
+
+ +
+ +
+
+class typhos.display.TyphosDisplaySwitcherButton(display_type, *, parent=None)[source]
+

A button which switches the TyphosDeviceDisplay template on click.

+
+
+generate_context_menu()[source]
+

Context menu request.

+
+ +
+ +
+
+class typhos.display.TyphosDisplayConfigButton(icon=None, *, parent=None)[source]
+

The configuration button used in the TyphosDisplaySwitcher.

+

This uses the common “vertical ellipse” icon by default.

+
+
+create_hide_empty_menu(panels, base_menu)[source]
+

Create the hide empty filtering menu.

+
+
Parameters:
+
    +
  • panels (list of TyphosSignalPanel) – The panels to filter upon triggering of menu actions.

  • +
  • base_menu (QMenu) – The menu to add actions to.

  • +
+
+
+
+ +
+
+create_kind_filter_menu(panels, base_menu, *, only)[source]
+

Create the “Kind” filter menu.

+
+
Parameters:
+
    +
  • panels (list of TyphosSignalPanel) – The panels to filter upon triggering of menu actions.

  • +
  • base_menu (QMenu) – The menu to add actions to.

  • +
  • only (bool) – False - create “Show Kind” actions. +True - create “Show only Kind” actions.

  • +
+
+
+
+ +
+
+create_name_filter_menu(panels, base_menu)[source]
+

Create the name-based filtering menu.

+
+
Parameters:
+
    +
  • panels (list of TyphosSignalPanel) – The panels to filter upon triggering of menu actions.

  • +
  • base_menu (QMenu) – The menu to add actions to.

  • +
+
+
+
+ +
+
+generate_context_menu()[source]
+

Generate the custom context menu.

+
Embedded
+Detailed
+Engineering
+-------------
+Refresh templates
+-------------
+Kind filter > Show hinted
+              ...
+              Show only hinted
+Filter by name
+Hide Empty Panels
+
+
+
+ +
+
+hide_empty(search=True)[source]
+

Wrap hide_empty calls for use with search functions and action clicks.

+
+
Parameters:
+

search (bool) – Whether or not this method is being called from a search/filter +method.

+
+
+
+ +
+
+set_device_display(device_display)[source]
+

Typhos callback: set the TyphosDeviceDisplay.

+
+ +
+ +
+
+
+

Utilities

+
+
+typhos.display.normalize_display_type(display_type: DisplayTypes | str | int) DisplayTypes[source]
+

Normalize a given display type.

+
+
Parameters:
+

display_type (DisplayTypes, str, or int) – The display type.

+
+
Returns:
+

display_type – The normalized DisplayTypes.

+
+
Return type:
+

DisplayTypes

+
+
Raises:
+

ValueError – If the input cannot be made a DisplayTypes.

+
+
+
+ +
+
+typhos.display.hide_empty(widget, process_widget=True)[source]
+

Recursively hide empty panels and widgets.

+
+
Parameters:
+
    +
  • widget (QWidget) – The widget in which to start the recursive search.

  • +
  • process_widget (bool) – Whether or not to process the visibility for the widget. +This is useful since we don’t want to hide the top-most +widget otherwise users can’t change the visibility back on.

  • +
+
+
+
+ +
+
+typhos.display.show_empty(widget)[source]
+

Recursively shows all panels and widgets, empty or not.

+
+
Parameters:
+

widget (QWidget) –

+
+
+
+ +
+
+typhos.display.toggle_display(widget, force_state=None)[source]
+

Toggle the visibility of all TyphosSignalPanel in a display.

+
+
Parameters:
+
    +
  • widget (QWidget) – The widget in which to look for Panels.

  • +
  • force_state (bool) – If set to True or False, it will change visibility to the value of +force_state. +If not set or set to None, it will flip the current panels state.

  • +
+
+
+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/generated/typhos.tools.TyphosConsole.html b/v2.4.1/generated/typhos.tools.TyphosConsole.html new file mode 100644 index 000000000..5a060e5ac --- /dev/null +++ b/v2.4.1/generated/typhos.tools.TyphosConsole.html @@ -0,0 +1,1180 @@ + + + + + + + typhos.tools.TyphosConsole — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

typhos.tools.TyphosConsole

+
+
+class typhos.tools.TyphosConsole(parent=None)[source]
+

IPython Widget for Typhos Display.

+

This widget handles starting a JupyterKernel and connecting an IPython +console in which the user can type Python commands. It is important to note +that the kernel in which commands are executed is a completely separate +process. This protects the user against locking themselves out of the GUI, +but makes it difficult to pass the Device..

+

To get around this caveat, this widget uses happi to pass the Device +between the processes. This is not a strict requirement, but if happi +is not installed, users will need to create a custom add_device method +if they want their devices loaded in both the GUI and console.

+
+
+__init__(parent=None)[source]
+
+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

__init__([parent])

acceptDrops(self)

accessibleDescription(self)

accessibleName(self)

actionEvent(self, a0)

actions(self)

activateWindow(self)

addAction(self, action)

addActions(self, actions)

add_device(device)

Add a new device to the widget

adjustSize(self)

autoFillBackground(self)

backgroundRole(self)

baseSize(self)

blockSignals(self, b)

changeEvent(self, a0)

childAt(-> QWidget)

childEvent(self, a0)

children(self)

childrenRect(self)

childrenRegion(self)

clearFocus(self)

clearMask(self)

close(self)

closeEvent(self, a0)

colorCount(self)

connectNotify(self, signal)

contentsMargins(self)

contentsRect(self)

contextMenuEvent(self, a0)

contextMenuPolicy(self)

create(self[, window, initializeWindow, ...])

createWindowContainer(window[, parent, flags])

cursor(self)

customEvent(self, a0)

deleteLater(self)

depth(self)

destroy(self[, destroyWindow, destroySubWindows])

devType(self)

devicePixelRatio(self)

devicePixelRatioF(self)

devicePixelRatioFScale()

disconnect(-> bool)

disconnectNotify(self, signal)

dragEnterEvent(self, a0)

dragLeaveEvent(self, a0)

dragMoveEvent(self, a0)

dropEvent(self, a0)

dumpObjectInfo(self)

dumpObjectTree(self)

dynamicPropertyNames(self)

effectiveWinId(self)

ensurePolished(self)

enterEvent(self, a0)

event(self, a0)

eventFilter(self, a0, a1)

execute(script, *[, echo, check_readiness])

Execute some code in the console.

find(a0)

findChild(-> QObject)

findChildren(...)

focusInEvent(self, a0)

focusNextChild(self)

focusNextPrevChild(self, next)

focusOutEvent(self, a0)

focusPolicy(self)

focusPreviousChild(self)

focusProxy(self)

focusWidget(self)

font(self)

fontInfo(self)

fontMetrics(self)

foregroundRole(self)

frameGeometry(self)

frameSize(self)

from_device(device[, parent])

Create a new instance of the widget for a Device

geometry(self)

getContentsMargins(self)

grab(self[, rectangle])

grabGesture(self, type[, flags])

grabKeyboard(self)

grabMouse()

grabShortcut(self, key[, context])

graphicsEffect(self)

graphicsProxyWidget(self)

hasFocus(self)

hasHeightForWidth(self)

hasMouseTracking(self)

hasTabletTracking(self)

height(self)

heightForWidth(self, a0)

heightMM(self)

hide(self)

hideEvent(self, a0)

inherits(self, classname)

initPainter(self, painter)

inputMethodEvent(self, a0)

inputMethodHints(self)

inputMethodQuery(self, a0)

insertAction(self, before, action)

insertActions(self, before, actions)

installEventFilter(self, a0)

isActiveWindow(self)

isAncestorOf(self, child)

isEnabled(self)

isEnabledTo(self, a0)

isFullScreen(self)

isHidden(self)

isLeftToRight(self)

isMaximized(self)

isMinimized(self)

isModal(self)

isRightToLeft(self)

isSignalConnected(self, signal)

isVisible(self)

isVisibleTo(self, a0)

isWidgetType(self)

isWindow(self)

isWindowModified(self)

isWindowType(self)

keyPressEvent(self, a0)

keyReleaseEvent(self, a0)

keyboardGrabber()

killTimer(self, id)

layout(self)

layoutDirection(self)

leaveEvent(self, a0)

locale(self)

logicalDpiX(self)

logicalDpiY(self)

lower(self)

mapFrom(self, a0, a1)

mapFromGlobal(self, a0)

mapFromParent(self, a0)

mapTo(self, a0, a1)

mapToGlobal(self, a0)

mapToParent(self, a0)

mask(self)

maximumHeight(self)

maximumSize(self)

maximumWidth(self)

metaObject(self)

metric(self, a0)

minimumHeight(self)

minimumSize(self)

minimumSizeHint(self)

minimumWidth(self)

mouseDoubleClickEvent(self, a0)

mouseGrabber()

mouseMoveEvent(self, a0)

mousePressEvent(self, a0)

mouseReleaseEvent(self, a0)

move()

moveEvent(self, a0)

moveToThread(self, thread)

nativeEvent(self, eventType, message)

nativeParentWidget(self)

nextInFocusChain(self)

normalGeometry(self)

objectName(self)

overrideWindowFlags(self, type)

overrideWindowState(self, state)

paintEngine(self)

paintEvent(self, a0)

paintingActive(self)

palette(self)

parent(self)

parentWidget(self)

physicalDpiX(self)

physicalDpiY(self)

pos(self)

previousInFocusChain(self)

property(self, name)

pyqtConfigure(...)

Each keyword argument is either the name of a Qt property or a Qt signal.

raise_(self)

receivers(self, signal)

rect(self)

releaseKeyboard(self)

releaseMouse(self)

releaseShortcut(self, id)

removeAction(self, action)

removeEventFilter(self, a0)

render(, sourceRegion, flags, ...)

repaint(-> None + -> None)

resize()

resizeEvent(self, a0)

restoreGeometry(self, geometry)

saveGeometry(self)

screen(self)

scroll()

sender(self)

senderSignalIndex(self)

setAcceptDrops(self, on)

setAccessibleDescription(self, description)

setAccessibleName(self, name)

setAttribute(self, attribute[, on])

setAutoFillBackground(self, enabled)

setBackgroundRole(self, a0)

setBaseSize()

setContentsMargins()

setContextMenuPolicy(self, policy)

setCursor(self, a0)

setDisabled(self, a0)

setEnabled(self, a0)

setFixedHeight(self, h)

setFixedSize()

setFixedWidth(self, w)

setFocus()

setFocusPolicy(self, policy)

setFocusProxy(self, a0)

setFont(self, a0)

setForegroundRole(self, a0)

setGeometry()

setGraphicsEffect(self, effect)

setHidden(self, hidden)

setInputMethodHints(self, hints)

setLayout(self, a0)

setLayoutDirection(self, direction)

setLocale(self, locale)

setMask()

setMaximumHeight(self, maxh)

setMaximumSize()

setMaximumWidth(self, maxw)

setMinimumHeight(self, minh)

setMinimumSize()

setMinimumWidth(self, minw)

setMouseTracking(self, enable)

setObjectName(self, name)

setPalette(self, a0)

setParent()

setProperty(self, name, value)

setShortcutAutoRepeat(self, id[, enabled])

setShortcutEnabled(self, id[, enabled])

setSizeIncrement()

setSizePolicy()

setStatusTip(self, a0)

setStyle(self, a0)

setStyleSheet(self, styleSheet)

setTabOrder(a0, a1)

setTabletTracking(self, enable)

setToolTip(self, a0)

setToolTipDuration(self, msec)

setUpdatesEnabled(self, enable)

setVisible(self, visible)

setWhatsThis(self, a0)

setWindowFilePath(self, filePath)

setWindowFlag(self, a0[, on])

setWindowFlags(self, type)

setWindowIcon(self, icon)

setWindowIconText(self, a0)

setWindowModality(self, windowModality)

setWindowModified(self, a0)

setWindowOpacity(self, level)

setWindowRole(self, a0)

setWindowState(self, state)

setWindowTitle(self, a0)

sharedPainter(self)

show(self)

showEvent(self, a0)

showFullScreen(self)

showMaximized(self)

showMinimized(self)

showNormal(self)

shutdown(*[, block])

Shutdown the Jupyter Kernel.

signalsBlocked(self)

size(self)

sizeHint(self)

sizeIncrement(self)

sizePolicy(self)

stackUnder(self, a0)

startTimer(self, interval[, timerType])

statusTip(self)

style(self)

styleSheet(self)

tabletEvent(self, a0)

testAttribute(self, attribute)

thread(self)

timerEvent(self, a0)

toolTip(self)

toolTipDuration(self)

tr(self, sourceText[, disambiguation, n])

underMouse(self)

ungrabGesture(self, type)

unsetCursor(self)

unsetLayoutDirection(self)

unsetLocale(self)

update(-> None + -> None)

updateGeometry(self)

updateMicroFocus(self)

updatesEnabled(self)

visibleRegion(self)

whatsThis(self)

wheelEvent(self, a0)

width(self)

widthMM(self)

winId(self)

window(self)

windowFilePath(self)

windowFlags(self)

windowHandle(self)

windowIcon(self)

windowIconText(self)

windowModality(self)

windowOpacity(self)

windowRole(self)

windowState(self)

windowTitle(self)

windowType(self)

x(self)

y(self)

+

Attributes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

DrawChildren

DrawWindowBackground

IgnoreMask

PdmDepth

PdmDevicePixelRatio

PdmDevicePixelRatioScaled

PdmDpiX

PdmDpiY

PdmHeight

PdmHeightMM

PdmNumColors

PdmPhysicalDpiX

PdmPhysicalDpiY

PdmWidth

PdmWidthMM

customContextMenuRequested

QPoint) [signal]

destroyed

typing.Optional[QObject] = None) [signal]

device_added

kernel_is_alive

Is the Jupyter kernel alive and not shutting down?

kernel_is_ready

Is the Jupyter kernel ready?

kernel_ready

kernel_shut_down

objectNameChanged

str) [signal]

staticMetaObject

windowIconChanged

QIcon) [signal]

windowIconTextChanged

str) [signal]

windowTitleChanged

str) [signal]

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/generated/typhos.tools.TyphosLogDisplay.html b/v2.4.1/generated/typhos.tools.TyphosLogDisplay.html new file mode 100644 index 000000000..7c10ddac5 --- /dev/null +++ b/v2.4.1/generated/typhos.tools.TyphosLogDisplay.html @@ -0,0 +1,1150 @@ + + + + + + + typhos.tools.TyphosLogDisplay — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

typhos.tools.TyphosLogDisplay

+
+
+class typhos.tools.TyphosLogDisplay(level=20, parent=None)[source]
+

Typhos Logging Display.

+
+
+__init__(level=20, parent=None)[source]
+
+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

__init__([level, parent])

acceptDrops(self)

accessibleDescription(self)

accessibleName(self)

actionEvent(self, a0)

actions(self)

activateWindow(self)

addAction(self, action)

addActions(self, actions)

add_device(device)

Add a device to the logging display.

adjustSize(self)

autoFillBackground(self)

backgroundRole(self)

baseSize(self)

blockSignals(self, b)

changeEvent(self, a0)

childAt(-> QWidget)

childEvent(self, a0)

children(self)

childrenRect(self)

childrenRegion(self)

clearFocus(self)

clearMask(self)

close(self)

closeEvent(self, a0)

colorCount(self)

connectNotify(self, signal)

contentsMargins(self)

contentsRect(self)

contextMenuEvent(self, a0)

contextMenuPolicy(self)

create(self[, window, initializeWindow, ...])

createWindowContainer(window[, parent, flags])

cursor(self)

customEvent(self, a0)

deleteLater(self)

depth(self)

destroy(self[, destroyWindow, destroySubWindows])

devType(self)

devicePixelRatio(self)

devicePixelRatioF(self)

devicePixelRatioFScale()

disconnect(-> bool)

disconnectNotify(self, signal)

dragEnterEvent(self, a0)

dragLeaveEvent(self, a0)

dragMoveEvent(self, a0)

dropEvent(self, a0)

dumpObjectInfo(self)

dumpObjectTree(self)

dynamicPropertyNames(self)

effectiveWinId(self)

ensurePolished(self)

enterEvent(self, a0)

event(self, a0)

eventFilter(self, a0, a1)

find(a0)

findChild(-> QObject)

findChildren(...)

focusInEvent(self, a0)

focusNextChild(self)

focusNextPrevChild(self, next)

focusOutEvent(self, a0)

focusPolicy(self)

focusPreviousChild(self)

focusProxy(self)

focusWidget(self)

font(self)

fontInfo(self)

fontMetrics(self)

foregroundRole(self)

frameGeometry(self)

frameSize(self)

from_device(device[, parent])

Create a new instance of the widget for a Device

geometry(self)

getContentsMargins(self)

grab(self[, rectangle])

grabGesture(self, type[, flags])

grabKeyboard(self)

grabMouse()

grabShortcut(self, key[, context])

graphicsEffect(self)

graphicsProxyWidget(self)

hasFocus(self)

hasHeightForWidth(self)

hasMouseTracking(self)

hasTabletTracking(self)

height(self)

heightForWidth(self, a0)

heightMM(self)

hide(self)

hideEvent(self, a0)

inherits(self, classname)

initPainter(self, painter)

inputMethodEvent(self, a0)

inputMethodHints(self)

inputMethodQuery(self, a0)

insertAction(self, before, action)

insertActions(self, before, actions)

installEventFilter(self, a0)

isActiveWindow(self)

isAncestorOf(self, child)

isEnabled(self)

isEnabledTo(self, a0)

isFullScreen(self)

isHidden(self)

isLeftToRight(self)

isMaximized(self)

isMinimized(self)

isModal(self)

isRightToLeft(self)

isSignalConnected(self, signal)

isVisible(self)

isVisibleTo(self, a0)

isWidgetType(self)

isWindow(self)

isWindowModified(self)

isWindowType(self)

keyPressEvent(self, a0)

keyReleaseEvent(self, a0)

keyboardGrabber()

killTimer(self, id)

layout(self)

layoutDirection(self)

leaveEvent(self, a0)

locale(self)

logicalDpiX(self)

logicalDpiY(self)

lower(self)

mapFrom(self, a0, a1)

mapFromGlobal(self, a0)

mapFromParent(self, a0)

mapTo(self, a0, a1)

mapToGlobal(self, a0)

mapToParent(self, a0)

mask(self)

maximumHeight(self)

maximumSize(self)

maximumWidth(self)

metaObject(self)

metric(self, a0)

minimumHeight(self)

minimumSize(self)

minimumSizeHint(self)

minimumWidth(self)

mouseDoubleClickEvent(self, a0)

mouseGrabber()

mouseMoveEvent(self, a0)

mousePressEvent(self, a0)

mouseReleaseEvent(self, a0)

move()

moveEvent(self, a0)

moveToThread(self, thread)

nativeEvent(self, eventType, message)

nativeParentWidget(self)

nextInFocusChain(self)

normalGeometry(self)

objectName(self)

overrideWindowFlags(self, type)

overrideWindowState(self, state)

paintEngine(self)

paintEvent(self, a0)

paintingActive(self)

palette(self)

parent(self)

parentWidget(self)

physicalDpiX(self)

physicalDpiY(self)

pos(self)

previousInFocusChain(self)

property(self, name)

pyqtConfigure(...)

Each keyword argument is either the name of a Qt property or a Qt signal.

raise_(self)

receivers(self, signal)

rect(self)

releaseKeyboard(self)

releaseMouse(self)

releaseShortcut(self, id)

removeAction(self, action)

removeEventFilter(self, a0)

render(, sourceRegion, flags, ...)

repaint(-> None + -> None)

resize()

resizeEvent(self, a0)

restoreGeometry(self, geometry)

saveGeometry(self)

screen(self)

scroll()

sender(self)

senderSignalIndex(self)

setAcceptDrops(self, on)

setAccessibleDescription(self, description)

setAccessibleName(self, name)

setAttribute(self, attribute[, on])

setAutoFillBackground(self, enabled)

setBackgroundRole(self, a0)

setBaseSize()

setContentsMargins()

setContextMenuPolicy(self, policy)

setCursor(self, a0)

setDisabled(self, a0)

setEnabled(self, a0)

setFixedHeight(self, h)

setFixedSize()

setFixedWidth(self, w)

setFocus()

setFocusPolicy(self, policy)

setFocusProxy(self, a0)

setFont(self, a0)

setForegroundRole(self, a0)

setGeometry()

setGraphicsEffect(self, effect)

setHidden(self, hidden)

setInputMethodHints(self, hints)

setLayout(self, a0)

setLayoutDirection(self, direction)

setLocale(self, locale)

setMask()

setMaximumHeight(self, maxh)

setMaximumSize()

setMaximumWidth(self, maxw)

setMinimumHeight(self, minh)

setMinimumSize()

setMinimumWidth(self, minw)

setMouseTracking(self, enable)

setObjectName(self, name)

setPalette(self, a0)

setParent()

setProperty(self, name, value)

setShortcutAutoRepeat(self, id[, enabled])

setShortcutEnabled(self, id[, enabled])

setSizeIncrement()

setSizePolicy()

setStatusTip(self, a0)

setStyle(self, a0)

setStyleSheet(self, styleSheet)

setTabOrder(a0, a1)

setTabletTracking(self, enable)

setToolTip(self, a0)

setToolTipDuration(self, msec)

setUpdatesEnabled(self, enable)

setVisible(self, visible)

setWhatsThis(self, a0)

setWindowFilePath(self, filePath)

setWindowFlag(self, a0[, on])

setWindowFlags(self, type)

setWindowIcon(self, icon)

setWindowIconText(self, a0)

setWindowModality(self, windowModality)

setWindowModified(self, a0)

setWindowOpacity(self, level)

setWindowRole(self, a0)

setWindowState(self, state)

setWindowTitle(self, a0)

sharedPainter(self)

show(self)

showEvent(self, a0)

showFullScreen(self)

showMaximized(self)

showMinimized(self)

showNormal(self)

signalsBlocked(self)

size(self)

sizeHint(self)

sizeIncrement(self)

sizePolicy(self)

stackUnder(self, a0)

startTimer(self, interval[, timerType])

statusTip(self)

style(self)

styleSheet(self)

tabletEvent(self, a0)

testAttribute(self, attribute)

thread(self)

timerEvent(self, a0)

toolTip(self)

toolTipDuration(self)

tr(self, sourceText[, disambiguation, n])

underMouse(self)

ungrabGesture(self, type)

unsetCursor(self)

unsetLayoutDirection(self)

unsetLocale(self)

update(-> None + -> None)

updateGeometry(self)

updateMicroFocus(self)

updatesEnabled(self)

visibleRegion(self)

whatsThis(self)

wheelEvent(self, a0)

width(self)

widthMM(self)

winId(self)

window(self)

windowFilePath(self)

windowFlags(self)

windowHandle(self)

windowIcon(self)

windowIconText(self)

windowModality(self)

windowOpacity(self)

windowRole(self)

windowState(self)

windowTitle(self)

windowType(self)

x(self)

y(self)

+

Attributes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

DrawChildren

DrawWindowBackground

IgnoreMask

PdmDepth

PdmDevicePixelRatio

PdmDevicePixelRatioScaled

PdmDpiX

PdmDpiY

PdmHeight

PdmHeightMM

PdmNumColors

PdmPhysicalDpiX

PdmPhysicalDpiY

PdmWidth

PdmWidthMM

customContextMenuRequested

QPoint) [signal]

destroyed

typing.Optional[QObject] = None) [signal]

objectNameChanged

str) [signal]

staticMetaObject

windowIconChanged

QIcon) [signal]

windowIconTextChanged

str) [signal]

windowTitleChanged

str) [signal]

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/generated/typhos.tools.TyphosTimePlot.html b/v2.4.1/generated/typhos.tools.TyphosTimePlot.html new file mode 100644 index 000000000..79850e7b7 --- /dev/null +++ b/v2.4.1/generated/typhos.tools.TyphosTimePlot.html @@ -0,0 +1,1172 @@ + + + + + + + typhos.tools.TyphosTimePlot — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

typhos.tools.TyphosTimePlot

+
+
+class typhos.tools.TyphosTimePlot(parent=None)[source]
+

Generalized widget for plotting Ophyd signals.

+

This widget is a TimeChartDisplay wrapped with some convenient +functions for adding signals by their attribute name.

+
+
Parameters:
+

parent (QWidget) –

+
+
+
+
+__init__(parent=None)[source]
+
+ +

Methods

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

__init__([parent])

acceptDrops(self)

accessibleDescription(self)

accessibleName(self)

actionEvent(self, a0)

actions(self)

activateWindow(self)

addAction(self, action)

addActions(self, actions)

add_available_signal(signal, name)

Add an Ophyd signal to the list of available channels.

add_curve(channel[, name, color])

Add a curve to the plot.

add_device(device)

Add a device and it's component signals to the plot.

adjustSize(self)

autoFillBackground(self)

backgroundRole(self)

baseSize(self)

blockSignals(self, b)

changeEvent(self, a0)

childAt(-> QWidget)

childEvent(self, a0)

children(self)

childrenRect(self)

childrenRegion(self)

clearFocus(self)

clearMask(self)

close(self)

closeEvent(self, a0)

colorCount(self)

connectNotify(self, signal)

contentsMargins(self)

contentsRect(self)

contextMenuEvent(self, a0)

contextMenuPolicy(self)

create(self[, window, initializeWindow, ...])

createWindowContainer(window[, parent, flags])

creation_requested()

Reaction to create_button press.

cursor(self)

customEvent(self, a0)

deleteLater(self)

depth(self)

destroy(self[, destroyWindow, destroySubWindows])

devType(self)

devicePixelRatio(self)

devicePixelRatioF(self)

devicePixelRatioFScale()

disconnect(-> bool)

disconnectNotify(self, signal)

dragEnterEvent(self, a0)

dragLeaveEvent(self, a0)

dragMoveEvent(self, a0)

dropEvent(self, a0)

dumpObjectInfo(self)

dumpObjectTree(self)

dynamicPropertyNames(self)

effectiveWinId(self)

ensurePolished(self)

enterEvent(self, a0)

event(self, a0)

eventFilter(self, a0, a1)

find(a0)

findChild(-> QObject)

findChildren(...)

focusInEvent(self, a0)

focusNextChild(self)

focusNextPrevChild(self, next)

focusOutEvent(self, a0)

focusPolicy(self)

focusPreviousChild(self)

focusProxy(self)

focusWidget(self)

font(self)

fontInfo(self)

fontMetrics(self)

foregroundRole(self)

frameGeometry(self)

frameSize(self)

from_device(device[, parent])

Create a new instance of the widget for a Device

geometry(self)

getContentsMargins(self)

grab(self[, rectangle])

grabGesture(self, type[, flags])

grabKeyboard(self)

grabMouse()

grabShortcut(self, key[, context])

graphicsEffect(self)

graphicsProxyWidget(self)

hasFocus(self)

hasHeightForWidth(self)

hasMouseTracking(self)

hasTabletTracking(self)

height(self)

heightForWidth(self, a0)

heightMM(self)

hide(self)

hideEvent(self, a0)

inherits(self, classname)

initPainter(self, painter)

inputMethodEvent(self, a0)

inputMethodHints(self)

inputMethodQuery(self, a0)

insertAction(self, before, action)

insertActions(self, before, actions)

installEventFilter(self, a0)

isActiveWindow(self)

isAncestorOf(self, child)

isEnabled(self)

isEnabledTo(self, a0)

isFullScreen(self)

isHidden(self)

isLeftToRight(self)

isMaximized(self)

isMinimized(self)

isModal(self)

isRightToLeft(self)

isSignalConnected(self, signal)

isVisible(self)

isVisibleTo(self, a0)

isWidgetType(self)

isWindow(self)

isWindowModified(self)

isWindowType(self)

keyPressEvent(self, a0)

keyReleaseEvent(self, a0)

keyboardGrabber()

killTimer(self, id)

layout(self)

layoutDirection(self)

leaveEvent(self, a0)

locale(self)

logicalDpiX(self)

logicalDpiY(self)

lower(self)

mapFrom(self, a0, a1)

mapFromGlobal(self, a0)

mapFromParent(self, a0)

mapTo(self, a0, a1)

mapToGlobal(self, a0)

mapToParent(self, a0)

mask(self)

maximumHeight(self)

maximumSize(self)

maximumWidth(self)

metaObject(self)

metric(self, a0)

minimumHeight(self)

minimumSize(self)

minimumSizeHint(self)

minimumWidth(self)

mouseDoubleClickEvent(self, a0)

mouseGrabber()

mouseMoveEvent(self, a0)

mousePressEvent(self, a0)

mouseReleaseEvent(self, a0)

move()

moveEvent(self, a0)

moveToThread(self, thread)

nativeEvent(self, eventType, message)

nativeParentWidget(self)

nextInFocusChain(self)

normalGeometry(self)

objectName(self)

overrideWindowFlags(self, type)

overrideWindowState(self, state)

paintEngine(self)

paintEvent(self, a0)

paintingActive(self)

palette(self)

parent(self)

parentWidget(self)

physicalDpiX(self)

physicalDpiY(self)

pos(self)

previousInFocusChain(self)

property(self, name)

pyqtConfigure(...)

Each keyword argument is either the name of a Qt property or a Qt signal.

raise_(self)

receivers(self, signal)

rect(self)

releaseKeyboard(self)

releaseMouse(self)

releaseShortcut(self, id)

removeAction(self, action)

removeEventFilter(self, a0)

remove_curve(name)

Remove a curve from the plot.

render(, sourceRegion, flags, ...)

repaint(-> None + -> None)

resize()

resizeEvent(self, a0)

restoreGeometry(self, geometry)

saveGeometry(self)

screen(self)

scroll()

sender(self)

senderSignalIndex(self)

setAcceptDrops(self, on)

setAccessibleDescription(self, description)

setAccessibleName(self, name)

setAttribute(self, attribute[, on])

setAutoFillBackground(self, enabled)

setBackgroundRole(self, a0)

setBaseSize()

setContentsMargins()

setContextMenuPolicy(self, policy)

setCursor(self, a0)

setDisabled(self, a0)

setEnabled(self, a0)

setFixedHeight(self, h)

setFixedSize()

setFixedWidth(self, w)

setFocus()

setFocusPolicy(self, policy)

setFocusProxy(self, a0)

setFont(self, a0)

setForegroundRole(self, a0)

setGeometry()

setGraphicsEffect(self, effect)

setHidden(self, hidden)

setInputMethodHints(self, hints)

setLayout(self, a0)

setLayoutDirection(self, direction)

setLocale(self, locale)

setMask()

setMaximumHeight(self, maxh)

setMaximumSize()

setMaximumWidth(self, maxw)

setMinimumHeight(self, minh)

setMinimumSize()

setMinimumWidth(self, minw)

setMouseTracking(self, enable)

setObjectName(self, name)

setPalette(self, a0)

setParent()

setProperty(self, name, value)

setShortcutAutoRepeat(self, id[, enabled])

setShortcutEnabled(self, id[, enabled])

setSizeIncrement()

setSizePolicy()

setStatusTip(self, a0)

setStyle(self, a0)

setStyleSheet(self, styleSheet)

setTabOrder(a0, a1)

setTabletTracking(self, enable)

setToolTip(self, a0)

setToolTipDuration(self, msec)

setUpdatesEnabled(self, enable)

setVisible(self, visible)

setWhatsThis(self, a0)

setWindowFilePath(self, filePath)

setWindowFlag(self, a0[, on])

setWindowFlags(self, type)

setWindowIcon(self, icon)

setWindowIconText(self, a0)

setWindowModality(self, windowModality)

setWindowModified(self, a0)

setWindowOpacity(self, level)

setWindowRole(self, a0)

setWindowState(self, state)

setWindowTitle(self, a0)

sharedPainter(self)

show(self)

showEvent(self, a0)

showFullScreen(self)

showMaximized(self)

showMinimized(self)

showNormal(self)

signalsBlocked(self)

size(self)

sizeHint(self)

sizeIncrement(self)

sizePolicy(self)

stackUnder(self, a0)

startTimer(self, interval[, timerType])

statusTip(self)

style(self)

styleSheet(self)

tabletEvent(self, a0)

testAttribute(self, attribute)

thread(self)

timerEvent(self, a0)

toolTip(self)

toolTipDuration(self)

tr(self, sourceText[, disambiguation, n])

underMouse(self)

ungrabGesture(self, type)

unsetCursor(self)

unsetLayoutDirection(self)

unsetLocale(self)

update(-> None + -> None)

updateGeometry(self)

updateMicroFocus(self)

updatesEnabled(self)

visibleRegion(self)

whatsThis(self)

wheelEvent(self, a0)

width(self)

widthMM(self)

winId(self)

window(self)

windowFilePath(self)

windowFlags(self)

windowHandle(self)

windowIcon(self)

windowIconText(self)

windowModality(self)

windowOpacity(self)

windowRole(self)

windowState(self)

windowTitle(self)

windowType(self)

x(self)

y(self)

+

Attributes

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

DrawChildren

DrawWindowBackground

IgnoreMask

PdmDepth

PdmDevicePixelRatio

PdmDevicePixelRatioScaled

PdmDpiX

PdmDpiY

PdmHeight

PdmHeightMM

PdmNumColors

PdmPhysicalDpiX

PdmPhysicalDpiY

PdmWidth

PdmWidthMM

channel_to_curve

A dictionary of channel_name to curve.

customContextMenuRequested

QPoint) [signal]

destroyed

typing.Optional[QObject] = None) [signal]

objectNameChanged

str) [signal]

staticMetaObject

windowIconChanged

QIcon) [signal]

windowIconTextChanged

str) [signal]

windowTitleChanged

str) [signal]

+
+ +
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/genindex.html b/v2.4.1/genindex.html new file mode 100644 index 000000000..88ca0c63f --- /dev/null +++ b/v2.4.1/genindex.html @@ -0,0 +1,1023 @@ + + + + + + Index — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Index

+ +
+ _ + | A + | C + | D + | E + | F + | G + | H + | I + | L + | M + | N + | O + | P + | R + | S + | T + | U + | V + | W + +
+

_

+ + + +
+ +

A

+ + + +
+ +

C

+ + + +
+ +

D

+ + + +
+ +

E

+ + + +
+ +

F

+ + + +
+ +

G

+ + + +
+ +

H

+ + + +
+ +

I

+ + + +
+ +

L

+ + + +
+ +

M

+ + + +
+ +

N

+ + + +
+ +

O

+ + + +
+ +

P

+ + + +
+ +

R

+ + + +
+ +

S

+ + + +
+ +

T

+ + + +
+ +

U

+ + + +
+ +

V

+ + + +
+ +

W

+ + + +
+ + + +
+
+
+ +
+ +
+

© Copyright 2023, SLAC National Accelerator Laboratory.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/index.html b/v2.4.1/index.html new file mode 100644 index 000000000..54c94ab00 --- /dev/null +++ b/v2.4.1/index.html @@ -0,0 +1,159 @@ + + + + + + + Typhos — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Typhos

+

EPICS is a flexible and powerful controls system to access to experimental +information, however, the relation and meaning of process variables is often +obscure. Many of the user interfaces for EPICS information reflect this, as +walls of buttons and flashing lights bombard the user with little thought to +structure or cohesion.

+

Typhos addresses this by providing an automated way to generate screens based +on a provided hierarchy of devices and signals. Built using PyDM, a PyQt based +display manager developed at SLAC National Laboratory, Typhos utilizes a large +toolkit of widgets to display EPICS information. For each process variable, a +corresponding widget is created based on; the importance to the average +operator, the type of value the EPICS PV will return, and whether a user should +be allowed to write to the variable. These widgets are then placed in a +convenient tab-based system to only show the necessary information for basic +function, but still allow access to more advanced signals.

+

Instead of reinventing a new way to specify device structures, Typhos uses +Ophyd, a library to abstract EPICS information into consistently structured +Python objects. Originally built for scripting experimental procedures at +NSLS-II, Ophyd represents devices as combinations of components which are +either signals or nested devices. Then, either at runtime or by using the +defaults of the representative Python class, these signals are sorted into +different categories based on their relevance to operators. Typhos uses this +information to craft user interfaces.

+ +
+ + +
+
+
+ +
+ +
+

© Copyright 2023, SLAC National Accelerator Laboratory.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/objects.inv b/v2.4.1/objects.inv new file mode 100644 index 000000000..54063ab06 Binary files /dev/null and b/v2.4.1/objects.inv differ diff --git a/v2.4.1/plugins.html b/v2.4.1/plugins.html new file mode 100644 index 000000000..90246bab6 --- /dev/null +++ b/v2.4.1/plugins.html @@ -0,0 +1,297 @@ + + + + + + + Typhos Data Plugins for PyDM — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Typhos Data Plugins for PyDM

+
+

SignalPlugin

+
+
+typhos.plugins.register_signal(signal)[source]
+

Add a new Signal to the registry.

+

The Signal object is kept within signal_registry for reference by name +in the SignalConnection. Signals can be added multiple times, +but only the first register_signal call for each unique signal name +has any effect.

+

Signals can be referenced by their name attribute or by their +full dotted path starting from the parent’s name.

+
+ +
+
+class typhos.plugins.SignalConnection(channel, address, protocol=None, parent=None)[source]
+

Connection to monitor an Ophyd Signal.

+

This is meant as a generalized connection to any type of Ophyd Signal. It +handles reporting updates to listeners as well as pushing new values that +users request in the PyDM interface back to the underlying signal.

+

The signal data_type is used to inform PyDM on the Python type that the +signal will expect and emit. It is expected that this type is static +through the execution of the application.

+
+
+signal
+

Stored signal object.

+
+
Type:
+

ophyd.Signal

+
+
+
+ +
+
+add_listener(channel)[source]
+

Add a listener channel to this connection.

+

This attaches values input by the user to the send_new_value function +in order to update the Signal object in addition to the default setup +performed in PyDMConnection.

+
+ +
+
+cast(value)[source]
+

Cast a value to the correct Python type based on signal_type.

+

If signal_type is not set, the result of ophyd.Signal.describe +is used to determine what the correct Python type for value is. We need +to be aware of the correct Python type so that we can emit the value +through the correct signal and convert values returned by the widget to +the correct type before handing them to Ophyd Signal.

+
+ +
+
+close()[source]
+

Unsubscribe from the Ophyd signal.

+
+ +
+
+put_value(new_val)[source]
+

Pass a value from the UI to Signal.

+

We are not guaranteed that this signal is writeable so catch exceptions +if they are created. We attempt to cast the received value into the +reported type of the signal unless it is of type np.ndarray.

+
+ +
+
+remove_listener(channel, destroying=False, **kwargs)[source]
+

Remove a listener channel from this connection.

+

This removes the send_new_value connections from the channel in +addition to the default disconnection performed in PyDMConnection.

+
+ +
+
+send_new_meta(connected=None, write_access=None, severity=None, precision=None, units=None, enum_strs=None, **kwargs)[source]
+

Update the UI with new metadata from the Signal.

+

Signal metadata updates always send all available metadata, so +default values to this function will not be sent ever if the signal +has valid data there.

+

We default missing metadata to None and skip emitting in general, +but for severity we default to NO_ALARM for UI purposes. We don’t +want the UI to assume that anything is in an alarm state.

+
+ +
+
+send_new_value(value=None, **kwargs)[source]
+

Update the UI with a new value from the Signal.

+
+ +
+ +
+
+class typhos.plugins.SignalPlugin[source]
+

Plugin registered with PyDM to handle SignalConnection.

+
+ +
+
+

HappiPlugin

+

These functions will not be imported if happi is not installed in the +current Python environment

+
+
+typhos.plugins.register_client(client)[source]
+

Register a Happi Client to be used with the DataPlugin.

+

This is not required to be called by the user, if your environment is setup +such that happi.Client.from_config() will return the desired client.

+
+ +
+
+class typhos.plugins.HappiConnection(channel, address, protocol=None, parent=None)[source]
+

A PyDMConnection to the Happi Database.

+
+
+add_listener(channel)[source]
+

Add a new channel to the existing connection.

+
+ +
+
+remove_listener(channel, destroying=False, **kwargs)[source]
+

Remove a channel from the database connection.

+
+ +
+ +
+
+class typhos.plugins.HappiPlugin[source]
+
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/py-modindex.html b/v2.4.1/py-modindex.html new file mode 100644 index 000000000..e9a80943b --- /dev/null +++ b/v2.4.1/py-modindex.html @@ -0,0 +1,149 @@ + + + + + + Python Module Index — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + +

Python Module Index

+ +
+ t +
+ + + + + + + + + + + + + +
 
+ t
+ typhos +
    + typhos.cli +
    + typhos.utils +
+ + +
+
+
+ +
+ +
+

© Copyright 2023, SLAC National Accelerator Laboratory.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/python_methods.html b/v2.4.1/python_methods.html new file mode 100644 index 000000000..a89d01e77 --- /dev/null +++ b/v2.4.1/python_methods.html @@ -0,0 +1,156 @@ + + + + + + + Including Python Code — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Including Python Code

+
+

Adding Methods

+

Each TyphosDeviceDisplay has an method_panel. You can add methods +manually or pass them in via the constructor. In order to make the appropriate +widgets, the function signature is examined. For example lets make a mock +function:

+
def foo(a: int, b: int, c: bool=False, d: float=3.14, e: bool=False):
+    pass
+
+
+

When you add the method to the panel the Python inspect module looks for +type annotations for each parameter. It also determines which parameters are +optional and which are not. Boolean variables are given QCheckboxes, while +others are given QLineEdits for entry. Optional keywords are also hidden from +the user unless they choose to expand the tab. Using +FunctionPanel.add_method() would look like this:

+
panel.add_method(foo, hide_params=['e'])
+
+
+

function expanded

+

If you don’t want to annotate your function as above, Typhos will attempt to +guess the type of optional variables via their default value. You can also pass +in an annotations dictionary that fulfills the indicates the type of each +variable.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/release_notes.html b/v2.4.1/release_notes.html new file mode 100644 index 000000000..c6b2e57f5 --- /dev/null +++ b/v2.4.1/release_notes.html @@ -0,0 +1,1120 @@ + + + + + + + Release History — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Release History

+
+

v2.4.1 (2023-4-4)

+
+

Description

+

This is a bugfix and maintenance/CI release.

+
+
+

Bugfixes

+
    +
  • Include the normal PyDM stylesheets in the loading process. +Previously, this was leading to unexpected behavior.

  • +
+
+
+

Maintenance

+
    +
  • Fix an issue related to a deleted flake8 mirror.

  • +
  • Migrates from Travis CI to GitHub Actions for continuous integration testing, and documentation deployment.

  • +
  • Updates typhos to use setuptools-scm, replacing versioneer, as its version-string management tool of choice.

  • +
  • Syntax has been updated to Python 3.9+ via pyupgrade.

  • +
  • typhos has migrated to modern pyproject.toml, replacing setup.py.

  • +
  • Sphinx 6.0 now supported for documentation building.

  • +
+
+
+

Contributors

+
    +
  • tangkong

  • +
  • zllentz

  • +
+
+
+
+

v2.4.0 (2022-11-4)

+
+

Description

+

This is a small release with features for improving the usage +and configurability of the PositionerWidget.

+
+
+

Features

+
    +
  • Report errors raised during the execution of positioner +set commands in the positioner widget instead of in a pop-up. +This makes it easier to keep track of which positioner widget +is associated with which error and makes it less likely that the +message will be missed or lost on large monitors.

  • +
  • Add a designer property to PositionerWidget, alarmKindLevel, +to configure the enclosed alarm widget’s kindLevel property in +designer. This was previously only configurable in code.

  • +
+
+
+

Contributors

+
    +
  • zllentz

  • +
+
+
+
+

v2.3.3 (2022-10-20)

+
+

Description

+

This is a small release with bugfixes and maintenance.

+
+
+

Bugfixes

+
    +
  • Do not wait for lazy signals when creating a SignalPanel. +This was causing long setup times in some applications.

  • +
  • Call stop with success=True in the positioner widget to avoid causing +our own UnknownStatusError, which was then displayed to the user.

  • +
+
+
+

Maintenance

+
    +
  • Add cleanup for background threads.

  • +
  • Add replacement for functools.partial usage in methods as +this was preventing TyphosSuite from getting garbage collected.

  • +
  • Removes custom designer widget plugin, +instead relying on PyDM’s own mechanism

  • +
  • Use pydm’s data plugin entrypoint to include the sig and happi channels.

  • +
  • Prevent TyphosStatusThread objects from being orphaned.

  • +
+
+
+

Contributors

+
    +
  • klauer

  • +
  • tangkong

  • +
  • zllentz

  • +
+
+
+
+

v2.3.2 (2022-07-28)

+
+

Description

+

This is a bugfix and maintenance release.

+
+
+

Fixes

+
    +
  • Fix various instances of clipping in the positioner widget.

  • +
  • Show Python documentation when no web help is available.

  • +
  • Fix issues with suite sidebar width.

  • +
  • Lazy load all tools to improve performance.

  • +
  • Fix the profiler to also profile class methods.

  • +
  • Use cached paths for finding class templates.

  • +
  • Properly handle various deprecations and deprecation warnings.

  • +
  • Fix usage of deprecated methods in happi (optional dependency).

  • +
+
+
+

Maintenance

+
    +
  • Log “unable to add device” without the traceback, which was previously unhelpful.

  • +
  • Pin pyqt at 5.12 for test suite incompatibility in newer versions.

  • +
  • Ensure that test.qss test suite artifact is cleaned up properly.

  • +
  • Fix the broken test suite.

  • +
  • Pin jinja2 at <3.1 in CI builds for sphinx <4.0.0 compatibility

  • +
+
+
+

Contributors

+
    +
  • anleslac

  • +
  • klauer

  • +
  • zllentz

  • +
+
+
+
+

v2.3.1 (2022-05-02)

+
+

Description

+

This is a small bugfix release.

+
+
+

Fixes

+
    +
  • Fix an issue where the configuration menu would be defunct for +custom template screens.

  • +
+
+
+

Maintenance

+
    +
  • Add some additional documentation about sig:// and cli usage.

  • +
  • Configure and satisfy the repository’s own pre-commit checks.

  • +
  • Update versioneer install to current latest.

  • +
+
+
+

Contributors

+
    +
  • klauer

  • +
  • zllentz

  • +
+
+
+
+

v2.3.0 (2022-03-31)

+
+

Description

+

This is a small release with fixes and features that were implemented +last month.

+
+
+

Features

+
    +
  • Add the option to hide displays in the suite at launch, +rather than automatically showing all of them.

  • +
  • Allow the sig:// protocol to be used in typhos templates by +automatically registering all of a device’s signals at launch.

  • +
+
+
+

Fixes

+
    +
  • Fix an issue where an assumption about the nature of EpicsSignal +object was breaking when using PytmcSignal objects from pcdsdevices.

  • +
  • Make a workaround for a C++ wrapped exception that could happen +in specific orders of loading and unloading typhos alarm widgets.

  • +
+
+
+
+

v2.2.1 (2022-02-07)

+
+

Description

+

This is a small bugfix release that was deployed as a hotfix +to avoid accidental moves.

+
+
+

Fixes

+
    +
  • Disable scroll wheel interaction with positioner combo boxes. +This created a situation where operators were accidentally +requesting moves while trying to scroll past the control box. +This was previously fixed for the typhos combo boxes found on +the various automatically generated panels in v1.1.0, but not +for the positioner combo boxes.

  • +
+
+
+
+

v2.2.0 (2021-11-30)

+
+

Description

+

This is a feature and bugfix release to extend the customizability of +typhos suites and launcher scrips, to fix various issues in control +layer and enum handling, and to do some necessary CI maintenance.

+
+
+

Enhancements / What’s new

+
    +
  • Add suite options for layouts, display types, scrollbars, and +starting window size. These are all also available as CLI arguments, +with the intention of augmenting typhos suite launcher scripts. +Here are some examples:

    +
      +
    • --layout grid --cols 3: lays out the device displays in a 3-column +grid

    • +
    • --layout flow: lays out the device displays in a grid that adjusts +dynamically as the window is resized.

    • +
    • --display-type embed: starts all device displays in their embedded +state

    • +
    • --size 1000,1000: sets a starting size of 1000 width, 1000 height for +the suite window.

    • +
    +

    See #450

    +
  • +
+
+
+

Fixes

+
    +
  • Respect ophyd signal enum_strs and metadata updates. Previously, these were +ignored, but these can update during the lifetime of a screen and should be +used. (#459)

  • +
  • Identify signals that use non-EPICS control layers and handle them +appropriately. Previously, these would be misidentified as EPICS signals +and handled using the ca:// PyDM plugin, which was not correct. +(#463)

  • +
  • Fix an issue where get_native_methods could fail. This was not observed +in the field, but it broke the test suite. +(#464)

  • +
+
+
+

Maintenance

+
    +
  • Fix various issues related to the test suite stability.

  • +
+
+
+
+

v2.1.0 (2021-10-18)

+
+

Description

+

This is a minor feature release of typhos.

+
+
+

Enhancements / What’s new

+
    +
  • Added option to pop out documentation frame +(#458)

  • +
+
+
+

Fixes

+
    +
  • Fixed authorization headers on Typhos help widget redirect +(#457)

    +
      +
    • This allows for the latest Confluence to work with Personal +Access Tokens while navigating through the page

    • +
    +
  • +
+
+
+

Maintenance

+
    +
  • Reduced javascript log message spam from the web view widget +(part of #457)

  • +
  • Reduced log message spam from variety metadata handling +(part of #457)

  • +
  • Fixed compatibility with pyqtgraph v0.12.3

  • +
  • Web-related widgets are now in a new submodule typhos.web.

  • +
+
+
+
+

v2.0.0 (2021-08-05)

+
+

Description

+

This is a feature update with backwards-incompatible changes, namely the +removal and relocation of the LCLS typhos templates.

+
+
+

API Breaks

+

All device templates except for the PositionerBase template have been +moved from typhos to pcdsdevices, which is where their device classes +are defined. This will break LCLS environments that update typhos without +also updating pcdsdevices, but will not affect environments outside of LCLS.

+
+
+

Enhancements / What’s New

+
    +
  • Add the TyphosRelatedSuiteButton, a QPushButton that will open a device’s +typhos screen. This can be included in embedded widgets or placed on +traditional hand-crafted pydm screens as a quick way to open the typhos +expert screen.

  • +
  • Add the typhos help widget, which is a new addition to the display switcher +that is found in all built-in typhos templates. Check out the ? button! +See the docs for information on how to configure this. +The main features implemented here are:

    +
      +
    • View the class docstring from inside the typhos window

    • +
    • Open site-specific web documentation in a browser

    • +
    • Report bugs directly from the typhos screen

    • +
    +
  • +
  • Expand the PositionerWidget with aesthetic updates and more features:

    +
      +
    • Show driver-specific error messages from the IOC

    • +
    • Add a “clear error” button that can be linked to IOC-specific error +reset routines by adding a clear_error method to your positioner +class. This will also clear status errors returned from the positioner’s +set routine from the display.

    • +
    • Add a moving/done_moving indicator (for EpicsMotor, uses the .MOVN field)

    • +
    • Add an optional TyphosRelatedSuite button

    • +
    • Allow the stop button to be removed if the stop method is missing or +otherwise raises an AttributeError on access

    • +
    • Add an alarm indicator

    • +
    +
  • +
  • Add the typhos.ui entry point. This allows a module to notify typhos that +it should check specified directories for custom typhos templates. To be +used by typhos, the entry point should load a str, pathlib.Path, or list +of such objects.

  • +
  • Move the examples submodule into the typhos.examples submodule, so we can +launch the examples by way of e.g. typhos -m typhos.examples.positioner.

  • +
  • For the alarm indicator widgets, allow the pen width, pen color, and +pen style to be customized.

  • +
+
+
+

Compatibility / Fixes

+
    +
  • Find a better fix for the issue where the positioner combobox widget would +put to the PV on startup and on IOC reboot +(see v1.1.0 note about a hacky workaround).

  • +
  • Fix the issue where the positioner combobox widget could not be used to +move to the last position selected.

  • +
  • Fix an issue where a positioner status that was marked as failed immediately +would show as an unknown error, even if it had an associated exception +with useful error text.

  • +
+
+
+

Docs / Testing

+
    +
  • Add documentation for all features included in this update

  • +
  • Add documentation for how to create custom typhos templates

  • +
+
+
+
+

v1.2.0 (2021-07-09)

+
+

Description

+

This is a feature update intended for use in lucid, but it may also be useful +elsewhere.

+
+
+

Enhancements / What’s New

+

Add a handful of new widgets for indicating device alarm state. These will +change color based on the most severe alarm found among the device’s signals. +Their shapes correlate with the available shapes of PyDMDrawingWidget:

+
    +
  • TyphosAlarmCircle

  • +
  • TyphosAlarmRectangle

  • +
  • TyphosAlarmTriangle

  • +
  • TyphosAlarmEllipse

  • +
  • TyphosAlarmPolygon

  • +
+
+
+

Compatibility / Fixes

+
    +
  • Add a sigint handler to avoid annoying behavior when closing with Ctrl-C on +macOS.

  • +
  • Increase some timeouts to improve unit test consistency.

  • +
+
+
+
+

v1.1.6 (2021-04-05)

+
+

Description

+

This is maintenance/compatibility release for pydm v1.11.0.

+
+
+

Compatibility / Fixes

+
    +
  • Internal fixes regarding error handling and input sanitization. +Some subtle issues cropped up here in the update to pydm v1.11.0.

  • +
  • Fix issue where the test suite would freeze when pydm displays +an exception to the user.

  • +
+
+
+
+

v1.1.5 (2020-04-02)

+
+

Description

+

This is a maintenance release

+
+
+

Compatibility / Fixes

+
    +
  • Fix an issue where certain data files were not included in the package +build.

  • +
+
+
+
+

v1.1.4 (2020-02-26)

+
+

Description

+

This is a bugfix release

+
+
+

Compatibility / Fixes

+
    +
  • Fix returning issue where certain devices could fail to load with a +“dictionary changed during iteration” error.

  • +
  • Fix issue where the documentation was not building properly.

  • +
+
+
+
+

v1.1.3 (2020-02-10)

+
+

Description

+

This is a minor screen inclusion release.

+
+
+

Enhancements / What’s New

+
    +
  • Add a screen for AT1K4. This, and similar screens, should be moved out of +typhos and into an LCLS-specific landing-zone, but this is not ready yet.

  • +
+
+
+
+

v1.1.2 (2020-12-22)

+
+

Description

+

This is a minor bugfix release.

+
+
+

Compatibility / Fixes

+
    +
  • Fix issue where SignalRO from ophyd was not showing as read-only.

  • +
  • Update the AT2L0 screen to not have a redundant calculation dialog as per +request.

  • +
+
+
+
+

v1.1.1 (2020-08-19)

+
+

Description

+

This is a bugfix release. Please use this instead of v1.1.0.

+
+
+

Compatibility / Fixes

+
    +
  • Fix issue with ui files not being included in the manifest

  • +
  • Fix issue with profiler failing on tests submodule

  • +
+
+
+
+

v1.1.0 (2020-08-18)

+
+

Description

+

This is a big release with many fixes and features.

+
+
+

Enhancements / What’s New

+
    +
  • Make Typhos aware of variety metadata and assign appropriate widgets based +on the variety metadata assigned in pcdsdevices.

  • +
  • Split templates into three categories: core, devices, and widgets. +Core templates are the main typhos display templates, e.g. detailed_tree. +Devices templates are templates tailored for specific device classes. +Widgets templates define special typhos widgets like tweakable, positioner, +etc.

  • +
  • Add attenuator calculator screens. These may be moved to another repo in a +future release.

  • +
  • Add information to loading widgets indicating timeout details.

  • +
+
+
+

Compatibility / fixes

+
    +
  • Fix issue with comboboxes being set on mouse scroll.

  • +
  • Allow loading classes from cli with numbers in the name.

  • +
  • Fix issue with legacy codepath used in lightpath.

  • +
  • Fix issue with widget UnboundLocalError.

  • +
  • Hacky workaround for issue with newer versions of Python.

  • +
  • Hacky workaround for issue where positioner widget puts on startup.

  • +
  • Fix issue with unset _channel member.

  • +
  • Fix issue with typhos creating and installing a tests package separate +from the main typhos package.

  • +
+
+
+

Docs / Testing

+
    +
  • Add variety testing IOC.

  • +
  • Add doctr_versions_menu extension to properly render version menu.

  • +
  • Fix issues with failing benchmark tests

  • +
+
+
+
+

v1.0.2 (2020-07-01)

+
+

Description

+

A bug fix and package maintenance release.

+
+
+

Enhancements / What’s New

+
    +
  • PositionerWidget moves set their timeouts based on expected +velocity and acceleration, rather than a flat 10 seconds.

  • +
+
+
+

Compatibility / fixes

+
    +
  • Ensure that widgets with no layout or minimum size are still displayed.

  • +
  • Update local conda recipe to match conda-forge.

  • +
  • Update CI to used shared configurations.

  • +
+
+
+
+

v1.0.1 (2020-05-20)

+
+

Description

+

A bug fix release with a minor addition.

+
+
+

Enhancements / What’s New

+
    +
  • TyphosLoading now takes in a timeout value to switch the animation +with a text message stating that the operation timed-out after X +seconds.

  • +
+
+
+

Compatibility / fixes

+
    +
  • Combobox widgets were appearing when switching or refreshing templates.

  • +
+
+
+
+

v1.0.0 (2020-05-18)

+
+

Description

+

A major new feature release with added views for complex devices and +simplified configurability.

+

As planned, the deprecated import name typhon and the typhon +command-line tool have been removed.

+
+
+

Enhancements / What’s New

+
    +
  • Panels: New TyphosCompositeSignalPanel, which composes multiple +TyphosDisplays in a tree-like view.

  • +
  • Benchmarking: new profiling tools accessible in the command-line +typhos tool, allowing for per-line profiling of standardized +devices. (--benchmark)

  • +
  • Template discovery: templates are discovered based on screen macros +and class inheritance structure, with the fallback of built-in +templates.

  • +
  • New command-line options for testing with mock devices +(--fake-device).

  • +
  • Performance: Major performance improvements by way of background +threading of signal description determination, display path caching, +and connection status monitoring to reduce GUI thread blocking.

  • +
  • Display: Adds a “display switcher” tool for easy access to different +screen types.

  • +
  • Display: Adds a “configuration” button to displays.

  • +
  • Filtering: Filter panel contents by kinds.

  • +
  • Filtering: Filter panel contents by signal names.

  • +
  • Setpoint history: a history of previous setpoints has been added to +the context menu in TyphosLineEdit.

  • +
  • Positioner widgets have been redesigned to be less magical and more fault- +tolerant. Adds designable properties that allow for specification of +attribute names.

  • +
  • Anything that inherits from PositionerBase will have the template as an +option (EpicsMotor, PCDSMotorBase, etc.)

  • +
  • Reworked default templates to remove the miscellaneous panel. Omitted +signals may still be shown by way of panel context menus or configuration +menus.

  • +
+
+
+

Compatibility / fixes

+
    +
  • Python 3.8 is now being included in the test suite.

  • +
  • Happi is now completely optional.

  • +
  • Popped-out widgets such as plots will persist even when the parent +display is closed.

  • +
  • Font sizes should be more consistent on various DPI displays.

  • +
  • Module typhos.signal has been renamed to typhos.panel.

  • +
  • TyphosTimePlot no longer automatically adds signals to the plot.

  • +
  • Removed internally-used typhos.utils.grab_kind.

  • +
  • OSX layout of TyphosSuite should be improved using the unified title and +toolbar.

  • +
+
+
+
+

v0.7.0 (2020-03-09)

+
    +
  • Fix docs deployment

  • +
  • Add “loading in progress” gif

  • +
  • Fix sorting of signals

  • +
  • Automatically choose exponential format based on engineering units

  • +
  • Fix lazy loading in ophyd 1.4

  • +
  • Save images of widgets when running tests

  • +
  • Add a new “PopBar” which pops in the device tree in the suite

  • +
  • Clean up the codebase - sort all imports + fix style

  • +
  • Relocate SignalRO to a single spot

  • +
+
+
+

v0.6.0 (2020-01-09)

+
+

Description

+

This release is dedicated to the renaming of the package from Typhon +to Typhos. The main reason for the renaming is a naming conflict at +PyPI that is now addressed.

+
+
+

Compatibility

+

This release is still compatible and will throw some DeprecationWarnings +when typhon is used. The only incompatible piece is for Qt +Stylesheets. You will need to add the typhos equivalents to your +custom stylesheets if you ever created one.

+

This is the first release with the backwards compatibility for typhon. +In two releases time it will be removed.

+
+
+
+

v0.5.0 (2019-09-18)

+
+

Description

+

It was a long time since the latest release of Typhon. It is time +for a new one. Next releases will have again the beautiful and +descriptive messages for enhancements, bug fixes and etc.

+
+
+

What’s New

+

A lot.

+
+
+
+

v0.2.1 (2018-09-28)

+
+

Description

+

This is a minor release of the Typhon library. No major features +were added, but instead the library was made more stable and utilitarian +for use in other programs. This includes making sure that any calls to a +signal’s values or metadata are capable of handling disconnections. It +also moves some of the methods that were hidden in larger classes or +functions into smaller, more useful methods.

+
+

Enhancements

+
    +
  • SignalPlugin now transmits all the metadata that is guaranteed to +be present from the base Signal object. This includes +enum_strs, precision, and units +(#92)

  • +
  • DeviceDisplay now has an optional argument children. This +makes it possible to ignore a Device components when creating the +display (#96)

  • +
  • The following utility functions have been created to ensure that a +uniform approach is taken forDevice introspection: +is_signal_ro, grab_hints +(#98)

  • +
+
+
+

Maintenance

+
    +
  • Catch exceptions when requesting information from a Signal in +case of disconnection, e.t.c +(#91, +#92)

  • +
  • The library now imports entirely from the qtpy compatibility +layer (#94)

  • +
+
+
+

Deprecations

+
    +
  • The title command in SignalPanel was no longer used. It is +still accepted in this release, but will dropped in the next major +release (#90)

  • +
+
+
+
+
+

v0.2.0 (2018-06-27)

+
+

Description

+

This Typhon release marks the transition from prototype to a stable +library. There was a variety of API breaks and deprecations after +v0.1.0 as many of the names and functions were not future-proof.

+
+

Enhancements

+
    +
  • Typhon is now available on the pcds-tag Anaconda channel +(#45)

  • +
  • Typhon now installs a special data plugin for PyDM called +SignalPlugin. This uses the generic ophyd.Signal methods to +communicate information to PyDM widgets. +(#63)

  • +
  • Typhon now supports two different stylesheets a “light” and +“dark” mode. These are not activated by default, but instead can be +accessed via use_stylesheet function +(#61, +#89)

  • +
  • There is now a sidebar to the DeviceDisplay that makes adding +devices and tools easier. The add_subdisplay function still works +but it is preferable to use the more specific add_tool and +add_subdevice. +(#61)

  • +
  • Typhon will automaticaly create a PyDMLogDisplay to show the +output of the logging.Logger object attached to each +ophyd.Device +(#70)

  • +
  • Typhon now creates a PyDMTimePlot with the “hinted” +attributes of the Device. This can be configured at runtime to have +fewer or more signals +(#73)

  • +
+
+
+

API Changes

+
    +
  • All of the Panel objects have been moved to different files. +SignalPanel now resides in typhon.signal while the base +Panel that is no longer used to display signals is in the generic +typhon.widgets renamed as TogglePanel +(#50)

  • +
+
+
+

Deprecations

+
    +
  • RotatingImage has been removed as it is no longer used by the +library (#58)

  • +
  • ComponentButton has been removed as it is no longer used by the +library(#58)

  • +
  • The base DeviceDisplay no longer has a plot. The +add_pv_to_plot function has been completely removed. +(#58)

  • +
+
+
+

Dependencies

+
    +
  • TyphonDisplay requires ophyd >= 1.2.0. The PyDMLogDisplay +tool is attached to the Device.log that is now present on all +ophyd devices. +(#53)

  • +
  • pydm >= 1.2.0 due to various bug fixes and widget additions +(#63)

  • +
  • QDarkStyleSheet is now included in the recipe to provide dark +stylesheet support. +(#89)

  • +
+
+
+

Bug Fixes

+
    +
  • SignalPanel previously did not account for the fact that read +and configuration attributes could be devices themselves +(#42)

  • +
  • SignalPanel no longer assumes that all signals are +EpicsSignal objects +(#71)

  • +
+
+
+
+
+

v0.1.0 (2017-12-15)

+

The initial release of Typhon. This serves as a proof of concept for the +automation of PyDM screen building as informed by the structure of an +Ophyd Device.

+
+

Features

+
    +
  • Generate a full DeviceDisplay with all of the device signals and +sub-devices available

  • +
  • Include methods from the ophyd Device in the User Interface, +automatically parse the arguments to make a widget representation of +the function

  • +
  • Include png images associated with devices and sub-devices

  • +
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/save.html b/v2.4.1/save.html new file mode 100644 index 000000000..5493c4e34 --- /dev/null +++ b/v2.4.1/save.html @@ -0,0 +1,188 @@ + + + + + + + Saving and Loading — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Saving and Loading

+

TyphosSuite objects can be stored for later use. The devices that +were loaded into the suite via TyphosSuite.add_device() will be added +once again assuming that they are stored in a happi database.

+
+
+TyphosSuite.save()[source]
+

Save suite settings to a file using typhos.utils.save_suite().

+

A QFileDialog will be used to query the user for the desired +location of the created Python file

+

The template will be of the form:

+
import sys
+import typhos.cli
+
+devices = {devices}
+
+def create_suite(cfg=None):
+    return typhos.cli.create_suite(devices, cfg=cfg)
+
+if __name__ == '__main__':
+    typhos.cli.typhos_cli(devices + sys.argv[1:])
+
+
+
+ +

There are two major ways to use this created file:

+
    +
  1. Execute the Python file from the command line. This will route the call +through the standard typhos.cli meaning all options +described there are also available.

  2. +
+
$ python saved_suite.py
+
+
+
    +
  1. The create_suite method generated in the saved file can be used to +re-create the TyphosSuite in an already running Python process. +Typhos provides the load_suite() function to import the provided +Python file and execute the stored create_suite method. This is useful +if you want to use the file to embed a saved TyphosSuite inside +another PyQt window for instance, or load multiple suites at once.

  2. +
+
from qtpy.QtWidgets import QApplication
+from typhos import load_suite
+
+app = QApplication([])
+saved_suite = load_suite('saved_suite.py')
+
+saved_suite.show()
+app.exec_()
+
+
+
+

Note

+

The saved file only stores a reference to the devices loaded into the +TyphosSuite by name. It is assumed that these devices will be available +under the same name via the configured happi database when +load_suite is called. If the device has a different name in the database +or you have configured a different happi database to be used your +devices will not be loaded properly.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/search.html b/v2.4.1/search.html new file mode 100644 index 000000000..3bb4ca368 --- /dev/null +++ b/v2.4.1/search.html @@ -0,0 +1,139 @@ + + + + + + Search — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+
    +
  • + +
  • +
  • +
+
+
+
+
+ + + + +
+ +
+ +
+
+
+ +
+ +
+

© Copyright 2023, SLAC National Accelerator Laboratory.

+
+ + Built with Sphinx using a + theme + provided by Read the Docs. + + +
+
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/v2.4.1/searchindex.js b/v2.4.1/searchindex.js new file mode 100644 index 000000000..1508323f4 --- /dev/null +++ b/v2.4.1/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"docnames": ["basic_usage", "cli", "connections", "display", "generated/typhos.tools.TyphosConsole", "generated/typhos.tools.TyphosLogDisplay", "generated/typhos.tools.TyphosTimePlot", "index", "plugins", "python_methods", "release_notes", "save", "templates", "tools", "utils", "widgets"], "filenames": ["basic_usage.rst", "cli.rst", "connections.rst", "display.rst", "generated/typhos.tools.TyphosConsole.rst", "generated/typhos.tools.TyphosLogDisplay.rst", "generated/typhos.tools.TyphosTimePlot.rst", "index.rst", "plugins.rst", "python_methods.rst", "release_notes.rst", "save.rst", "templates.rst", "tools.rst", "utils.rst", "widgets.rst"], "titles": ["How it Works", "Command Line Utilities", "Application Connections", "Suite and Displays", "typhos.tools.TyphosConsole", "typhos.tools.TyphosLogDisplay", "typhos.tools.TyphosTimePlot", "Typhos", "Typhos Data Plugins for PyDM", "Including Python Code", "Release History", "Saving and Loading", "Custom Templates", "Supported Tools", "Utility Functions", "Widgets"], "terms": {"typho": [0, 1, 2, 3, 9, 10, 11, 12, 13, 14, 15], "ha": [0, 2, 3, 8, 9, 10, 11, 13, 15], "three": [0, 10, 12], "major": [0, 3, 10, 11, 15], "build": [0, 10], "block": [0, 10], "combin": [0, 3, 7, 14], "final": [0, 3], "seen": 0, "oper": [0, 2, 7, 10, 13, 15], "overal": 0, "view": [0, 10, 14, 15], "window": [0, 10, 11, 14], "It": [0, 4, 8, 9, 10, 11, 12], "allow": [0, 3, 7, 10, 14, 15], "all": [0, 1, 2, 3, 8, 10, 11, 12, 13, 14, 15], "load": [0, 1, 3, 4, 10, 12, 14, 15], "compon": [0, 3, 7, 10, 14, 15], "tool": [0, 10, 15], "typhosdevicedisplai": [0, 9, 13, 15], "thi": [0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "i": [0, 1, 2, 3, 4, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], "creat": [0, 1, 2, 3, 4, 7, 8, 10, 11, 12, 14, 15], "standard": [0, 10, 11, 15], "ophyd": [0, 1, 3, 6, 7, 8, 10, 13, 15], "can": [0, 2, 3, 4, 8, 9, 10, 11, 12, 13, 14, 15], "organ": 0, "base": [0, 3, 7, 8, 10, 12, 14, 15], "kind": [0, 1, 2, 3, 10, 15], "descript": [0, 2, 15], "These": [0, 7, 8, 10, 12, 13, 15], "ar": [0, 1, 2, 3, 4, 7, 8, 9, 10, 11, 12, 13, 14, 15], "interfac": [0, 2, 3, 7, 8, 10, 13, 14, 15], "extern": [0, 13], "applic": [0, 1, 8, 10, 13, 15], "while": [0, 9, 10, 14, 15], "you": [0, 2, 3, 9, 10, 11, 12, 14, 15], "mai": [0, 3, 10, 14, 15], "have": [0, 1, 3, 10, 11, 12, 15], "other": [0, 1, 2, 9, 10, 12, 14, 15], "gui": [0, 4, 10], "system": [0, 2, 3, 7, 14], "built": [0, 1, 3, 7, 10, 12, 14, 15], "especi": 0, "handl": [0, 4, 8, 10, 14], "handshak": 0, "between": [0, 4], "inform": [0, 2, 3, 7, 8, 10, 12, 13, 14, 15], "store": [0, 1, 2, 8, 11, 12], "your": [0, 1, 2, 8, 9, 10, 11, 12], "save": [0, 3, 10, 14], "click": [0, 3, 13, 15], "ensur": [0, 2, 10], "consist": [0, 7, 10, 15], "list": [0, 3, 10, 12, 14, 15], "abov": [0, 9], "share": [0, 10], "similar": [0, 10, 15], "api": 0, "creation": 0, "instanti": [0, 3, 13, 15], "object": [0, 2, 3, 7, 8, 10, 11, 12, 13, 15], "itself": [0, 3, 14], "contain": [0, 2, 3], "place": [0, 3, 7, 10, 12, 14, 15], "them": [0, 1, 3, 8, 9, 10, 13], "correct": [0, 8, 10], "do": [0, 3, 10, 12, 14], "accept": [0, 10], "argument": [0, 1, 3, 10, 14, 15], "reason": [0, 10], "we": [0, 2, 3, 8, 10, 12, 13, 14, 15], "screen": [0, 1, 3, 7, 10, 12, 15], "regardless": 0, "whether": [0, 1, 3, 7, 14, 15], "an": [0, 1, 2, 3, 4, 7, 8, 9, 10, 11, 12, 14, 15], "alwai": [0, 8], "popul": [0, 2, 3, 13, 15], "hand": [0, 8, 10, 12, 15], "If": [0, 1, 2, 3, 8, 9, 11, 12, 14, 15], "fact": [0, 3, 10], "everi": 0, "class": [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 15], "add_devic": [0, 3, 4, 11, 13, 15], "method": [0, 2, 3, 4, 5, 6, 10, 11, 13, 14], "altern": [0, 1, 2, 15], "construct": 0, "from_devic": [0, 3, 13, 15], "classmethod": [0, 3, 15], "util": [0, 7, 10, 11], "typhosbas": [0, 14], "arg": [0, 14, 15], "kwarg": [0, 3, 8, 14, 15], "sourc": [0, 3, 4, 5, 6, 8, 11, 14, 15], "intern": [0, 10], "structur": [0, 7, 10, 13], "pydm": [0, 2, 7, 10, 12, 14, 15], "user": [0, 3, 4, 7, 8, 9, 10, 11, 14, 15], "so": [0, 8, 10, 15], "most": [0, 3, 10, 14, 15], "intuit": 0, "wai": [0, 7, 10, 11, 12, 14], "configur": [0, 1, 2, 3, 10, 11, 14, 15], "includ": [0, 1, 3, 10, 12, 13, 14, 15], "also": [0, 3, 9, 10, 11, 14, 15], "advantag": [0, 2, 13], "keep": [0, 2, 10], "python": [0, 3, 4, 7, 8, 10, 11, 14], "sync": 0, "make": [0, 2, 4, 9, 10, 13, 14], "transit": [0, 10], "from": [0, 2, 3, 8, 9, 10, 11, 12, 14, 15], "ipython": [0, 4], "shell": 0, "seamless": 0, "For": [0, 1, 7, 9, 10, 15], "follow": [0, 3, 10, 13, 14], "ll": [0, 12], "motor": [0, 15], "simul": 0, "within": [0, 2, 8], "need": [0, 3, 4, 8, 10, 14, 15], "qapplic": [0, 11, 14], "befor": [0, 8, 14], "ani": [0, 1, 2, 3, 8, 10, 12, 14, 15], "In": [0, 2, 3, 9, 10, 12, 13, 14], "1": [0, 3, 11, 14, 15], "qtpy": [0, 10, 11], "qtwidget": [0, 3, 11, 14, 15], "import": [0, 2, 3, 4, 7, 8, 10, 11], "2": [0, 3, 14, 15], "app": [0, 11], "requir": [0, 2, 4, 8, 10, 13, 14, 15], "recommend": [0, 12], "more": [0, 2, 7, 10, 12], "visit": 0, "github": [0, 10], "repositori": [0, 10, 12], "main": [0, 3, 10], "purpos": [0, 1, 8], "packag": [0, 1, 10, 12, 14], "our": [0, 2, 10], "varieti": [0, 10, 13, 14, 15], "context": [0, 3, 10, 13, 14, 15], "same": [0, 11, 12, 14, 15], "process": [0, 1, 3, 4, 7, 10, 11, 14], "command": [0, 4, 10, 11, 15], "line": [0, 10, 11], "togeth": 0, "easi": [0, 10], "auto": [0, 1, 3], "gener": [0, 2, 3, 6, 7, 8, 10, 11, 12, 14, 15], "name": [0, 1, 2, 3, 6, 8, 10, 11, 12, 14, 15], "my_devic": [0, 3], "would": [0, 2, 3, 9, 10, 14, 15], "onli": [0, 3, 7, 8, 10, 11, 14, 15], "automat": [0, 3, 10, 13, 15], "client": [0, 8], "find": [0, 3, 10, 12, 14, 15], "appropri": [0, 3, 9, 10], "look": [0, 3, 9, 14, 15], "integr": [0, 10, 13], "code": [0, 10, 14], "level": [0, 3, 5, 14], "consid": [0, 15], "which": [0, 3, 4, 7, 9, 10, 12, 13, 14, 15], "plugin": [0, 3, 10], "register_cli": [0, 8], "initi": [0, 1, 3, 10, 15], "new": [0, 3, 7, 8, 14, 15], "json": 0, "path": [0, 8, 10, 12], "db": 0, "true": [0, 1, 3, 10, 14, 15], "regist": [0, 2, 3, 8, 10, 14], "add": [0, 1, 3, 8, 9, 10, 14, 15], "databas": [0, 1, 2, 8, 11, 12], "device_class": [0, 3], "sim": [0, 14], "synaxi": 0, "prefix": [0, 3, 12], "tst": 0, "mtr": 0, "my_motor": 0, "beamlin": 0, "practic": 0, "necessari": [0, 7, 10], "call": [0, 3, 8, 10, 11, 14, 15], "happi_cfg": [0, 1, 14], "environ": [0, 1, 8, 10, 12, 13, 14], "variabl": [0, 1, 7, 9, 12, 14], "from_config": [0, 8], "yield": [0, 14], "desir": [0, 3, 8, 11], "now": [0, 2, 10], "check": [0, 10, 12, 14], "complet": [0, 3, 4, 10, 15], "load_devic": 0, "when": [0, 3, 9, 10, 11, 12, 14, 15], "custom": [0, 3, 4, 10, 15], "access": [0, 7, 10, 14, 15], "associ": [0, 3, 10, 15], "sever": [0, 8, 10, 15], "order": [0, 2, 3, 8, 9, 10, 12, 13, 14, 15], "suggest": [0, 3], "By": [0, 3, 14], "connect": [0, 1, 4, 8, 10, 14, 15], "dot": [0, 8, 15], "just": [0, 2, 3, 12, 15], "session": 0, "design": [0, 10, 12, 14], "channel": [0, 2, 8, 10, 14, 15], "properti": [0, 1, 3, 10, 14, 15], "specifi": [0, 1, 2, 3, 7, 10], "sig": [0, 2, 3, 10], "device_nam": [0, 3], "attr": [0, 14, 15], "mani": [0, 1, 2, 7, 10, 13, 14, 15], "reach": 0, "top": [0, 3, 14], "could": [0, 10, 13], "user_readback": [0, 15], "avail": [0, 2, 3, 8, 10, 11, 13, 14, 15], "data": [0, 2, 10, 14], "acquisit": 0, "e": [0, 9, 10, 12, 14, 15], "g": [0, 10, 12, 14], "databrok": 0, "blueski": 0, "charact": 0, "invalid": 0, "mongodb": 0, "replac": [0, 3, 10, 12, 14], "underscor": 0, "_": 0, "To": [0, 4, 10, 12, 14], "": [0, 1, 3, 8, 12, 14, 15], "see": [0, 3, 10, 12, 15], "my_motor_user_readback": 0, "pv": [0, 3, 7, 10, 14, 15], "directli": [0, 2, 3, 10, 14, 15], "assum": [0, 3, 8, 10, 11, 13], "through": [0, 2, 8, 10, 11, 14], "underli": [0, 8, 13], "control": [0, 2, 3, 7, 10, 15], "epic": [0, 1, 2, 7, 10], "talk": 0, "those": [0, 1, 3, 14], "That": [0, 14], "pvname": 0, "tell": 0, "readback": [0, 15], "set": [0, 3, 8, 10, 11, 12, 14, 15], "ca": [0, 10, 14], "pv_name_her": 0, "first": [0, 3, 8, 10, 13, 14], "thing": 0, "about": [0, 2, 10], "show": [0, 1, 3, 7, 10, 11, 14, 15], "group": [0, 3, 15], "basic": [0, 7, 13], "form": [0, 3, 11, 13, 15], "typhossignalpanel": [0, 3, 15], "simpli": [0, 15], "inspect": [0, 9], "reveal": 0, "few": [0, 15], "u": [0, 3], "3": [0, 3, 9], "component_nam": 0, "out": [0, 3, 4, 10, 14], "setpoint": [0, 10, 15], "veloc": [0, 10, 15], "acceler": [0, 10, 15], "unus": 0, "crucial": [0, 15], "understand": [0, 15], "glean": 0, "attribut": [0, 4, 5, 6, 8, 10, 12, 14, 15], "A": [0, 1, 3, 8, 10, 11, 14, 15], "quick": [0, 10], "variou": [0, 10, 14], "4": 0, "hint": [0, 3, 10, 15], "field": [0, 10, 13, 14, 15], "found": [0, 3, 10, 12], "here": [0, 10, 12, 14], "well": [0, 3, 8, 14], "5": 0, "read": [0, 10, 14, 15], "ordereddict": [0, 15], "valu": [0, 2, 3, 7, 8, 9, 10, 14, 15], "0": [0, 14, 15], "timestamp": [0, 15], "1680647506": 0, "9771109": 0, "motor_setpoint": 0, "977109": 0, "6": 0, "read_configur": 0, "motor_veloc": 0, "9779942": 0, "motor_acceler": 0, "978023": 0, "render": [0, 10], "select": [0, 1, 10, 15], "subset": 0, "below": [0, 15], "both": [0, 4, 14, 15], "qtdesign": 0, "correspond": [0, 2, 3, 7], "shown": [0, 1, 3, 10, 15], "7": [0, 14], "8": [0, 10], "panel": [0, 3, 9, 10], "glanc": 0, "obviou": 0, "lot": [0, 10], "know": [0, 2], "want": [0, 2, 3, 4, 8, 9, 11, 13, 14, 15], "ones": 0, "pure": 0, "meant": [0, 8], "back": [0, 3, 8, 15], "each": [0, 1, 7, 8, 9, 10, 13, 14, 15], "ters": 0, "human": [0, 14], "readabl": [0, 14], "what": [0, 8, 15], "repres": [0, 7, 15], "take": [0, 1, 2, 10, 14, 15], "concept": [0, 10], "further": 0, "instead": [0, 1, 2, 3, 7, 10, 15], "singl": [0, 3, 10], "multitud": 0, "alreadi": [0, 11, 15], "come": [0, 2, 15], "some": [0, 6, 10, 15], "default": [0, 1, 3, 7, 8, 9, 10, 12, 14, 15], "cycl": [0, 14], "chang": [0, 3, 15], "display_typ": [0, 1, 3], "onc": [0, 11, 14], "again": [0, 10, 11], "case": [0, 2, 3, 10, 12], "9": [0, 10], "10": 0, "made": [0, 3, 10, 14], "start": [0, 1, 3, 4, 8, 10, 14], "finish": 0, "suit": [0, 1, 10, 11, 14, 15], "default_tool": [0, 3], "apply_standard_stylesheet": [0, 14], "option": [0, 1, 3, 9, 10, 11, 14, 15], "exec_": [0, 11], "ship": [0, 1, 12], "two": [0, 3, 10, 11, 12, 13, 14], "improv": [0, 10, 14], "feel": 0, "invok": [0, 15], "cli": [0, 3, 10, 11], "normal": [0, 3, 10, 12, 15], "pass": [0, 2, 3, 4, 8, 9, 14, 15], "dark": [0, 1, 10, 14], "flag": 0, "light": [0, 7, 10, 14], "mode": [0, 10], "own": [0, 10, 12], "addit": [0, 1, 3, 8, 10, 15], "ignor": [0, 10, 14], "overrid": [0, 1, 3, 14, 15], "multipl": [0, 3, 8, 10, 11, 14], "time": [0, 8, 10, 13, 14, 15], "respect": [0, 10, 15], "pydm_stylesheet": [0, 14], "like": [0, 2, 3, 9, 10, 12, 14, 15], "should": [0, 3, 7, 10, 15], "file": [0, 1, 3, 10, 11, 12, 14], "qss": [0, 10, 14], "pydm_stylesheet_include_default": [0, 14], "unset": [0, 10], "prioriti": [0, 1, 12, 14], "conflict": [0, 10, 14], "explicit": [0, 15], "style": [0, 10, 14], "element": [0, 14, 15], "either": [0, 7, 14], "variant": [0, 14], "outsid": [0, 10, 12], "appli": [0, 14], "function": [0, 3, 6, 7, 8, 9, 10, 11], "fusion": [0, 14], "qstyle": 0, "help": [0, 1, 3, 10, 13], "independ": 0, "helper": [0, 14], "link": [0, 10, 15], "provid": [0, 1, 2, 3, 7, 10, 11, 14, 15], "websit": 0, "specif": [0, 1, 3, 10, 12, 14, 15], "facil": 0, "pleas": [0, 10], "typhos_help_url": 0, "str": [0, 3, 10, 12, 14, 15], "url": 0, "format": [0, 1, 10, 14], "string": [0, 2, 3, 10, 14], "pertin": 0, "its": [0, 10, 14, 15], "metadata": [0, 3, 8, 10, 14, 15], "confluenc": [0, 10], "server": 0, "exist": [0, 3, 8, 14], "http": 0, "my": [0, 3], "site": [0, 10], "com": 0, "match": [0, 10, 14], "perhap": 0, "guarante": [0, 8, 10, 14], "root": [0, 3], "typhos_help_head": 0, "header": [0, 10, 15], "help_url": 0, "my_kei": 0, "my_valu": 0, "typhos_help_headers_host": 0, "comma": 0, "delimit": 0, "host": 0, "sent": [0, 8], "asid": 0, "typhos_help_token": 0, "token": [0, 10], "bearer": 0, "authent": 0, "scheme": 0, "person": [0, 10], "shortcut": [0, 3], "author": [0, 10], "stori": 0, "prerequisit": 0, "support": [0, 10, 14, 15], "cours": 0, "instal": [0, 1, 4, 8, 10], "pre": [0, 10, 13], "issu": [0, 10], "collector": [0, 14], "typhos_jira_url": 0, "resembl": 0, "rest": 0, "typhos_jira_head": 0, "request": [0, 3, 8, 10, 15], "typhos_jira_token": 0, "typhos_jira_email_suffix": 0, "mail": 0, "suffix": [0, 14], "put": [0, 3, 10], "usernam": 0, "There": [0, 10, 11, 12], "submodul": [0, 1, 10], "after": [0, 1, 10], "m": [0, 10], "position": [0, 10, 15], "modul": [1, 3, 9, 10, 12], "defin": [1, 10, 12, 14], "usag": [1, 10], "__main__": [1, 3, 11], "py": [1, 10, 11, 14], "h": 1, "layout": [1, 3, 10, 14, 15], "col": [1, 10, 14], "displai": [1, 2, 4, 5, 7, 10, 12, 14, 15], "type": [1, 2, 3, 4, 7, 8, 9, 10, 12], "scrollabl": [1, 3], "size": [1, 10], "hide": [1, 3, 10, 14, 15], "happi": [1, 4, 8, 10, 11, 12, 14], "cfg": [1, 3, 11, 14], "fake": [1, 10, 14], "devic": [1, 2, 3, 4, 7, 10, 11, 12, 13, 14, 15], "version": [1, 3, 10], "verbos": 1, "stylesheet": [1, 10, 14], "stylesheet_overrid": 1, "stylesheet_add": 1, "profil": [1, 10], "profile_modul": 1, "output": [1, 2, 10], "profile_output": 1, "benchmark": [1, 10], "exit": [1, 14], "exit_aft": 1, "typhossuit": [1, 10, 11, 14], "posit": [1, 3, 10, 15], "paramet": [1, 3, 6, 9, 14, 15], "classnam": 1, "param1": 1, "val1": 1, "messag": [1, 10, 14, 15], "valid": [1, 8, 14], "horizont": [1, 3], "vertic": [1, 3], "grid": [1, 10], "flow": [1, 10], "uniqu": [1, 8], "shorten": 1, "The": [1, 2, 3, 8, 10, 11, 12, 13, 14, 15], "number": [1, 10, 12, 14, 15], "column": [1, 10, 14, 15], "us": [1, 2, 3, 4, 7, 8, 9, 10, 11, 12, 13, 14, 15], "effect": [1, 8], "open": [1, 3, 10, 15], "embed": [1, 3, 10, 12, 14, 15], "detail": [1, 3, 10, 12, 14], "engin": [1, 2, 3, 10, 12, 14], "scrollbar": [1, 3, 10], "fals": [1, 3, 8, 9, 14, 15], "non": [1, 10, 14, 15], "x": [1, 3, 10], "y": 1, "suitabl": [1, 14], "exampl": [1, 3, 9, 10, 12], "1000": [1, 10, 14], "subdisplai": [1, 3, 15], "hidden": [1, 3, 9, 10], "locat": [1, 3, 11, 14], "doe": [1, 3, 12, 15], "yet": [1, 10], "work": [1, 10, 12, 15], "invoc": 1, "epicsmotor": [1, 10], "v": 1, "current": [1, 3, 8, 10, 12, 15], "debug": 1, "log": [1, 5, 10], "stream": 1, "qdarkstylesheet": [1, 10, 14], "over": [1, 14], "templat": [1, 2, 3, 10, 11, 14], "widget": [1, 2, 3, 4, 6, 7, 8, 9, 10, 13], "dure": [1, 10, 14], "execut": [1, 4, 8, 10, 11, 15], "turn": 1, "filenam": [1, 14], "result": [1, 3, 8, 14], "omit": [1, 3, 10, 14, 15], "print": 1, "stdout": 1, "run": [1, 10, 11, 12, 14, 15], "test": 1, "launch": [1, 10, 12, 15], "second": [1, 10], "flexibl": [2, 7], "abstract": [2, 7], "layer": [2, 10, 15], "signalpanel": [2, 10, 15], "queri": [2, 3, 11, 15], "determin": [2, 3, 8, 9, 10, 14], "goe": 2, "signalplugin": [2, 10], "subscript": [2, 14], "updat": [2, 3, 8, 10, 14, 15], "One": [2, 12, 15], "caveat": [2, 4], "identifi": [2, 10, 14], "refer": [2, 3, 8, 11], "avoid": [2, 3, 10, 14], "duplic": [2, 14], "mean": [2, 3, 7, 11], "workflow": [2, 12], "ad": [2, 3, 6, 8, 10, 11, 15], "one": [2, 3, 10, 12, 13, 14, 15], "additon": 2, "step": 2, "where": [2, 10, 13, 15], "register_sign": [2, 8], "my_sign": 2, "this_sign": 2, "pydmwidget": [2, 15], "note": [2, 4, 10, 12, 15], "done": [2, 12], "mayb": 2, "enough": [2, 15], "accur": 2, "extra": [2, 3, 12], "piec": [2, 10], "unit": [2, 8, 10, 15], "precis": [2, 8, 10], "richer": 2, "experi": 2, "count": 2, "being": [2, 3, 10, 14], "describ": [2, 8, 11, 14], "child": [2, 3], "convei": 2, "sure": [2, 10], "express": 2, "properli": [2, 10, 11], "kei": [2, 12, 14], "enumer": 2, "enum_str": [2, 8, 10, 15], "mandat": 2, "happiplugin": 2, "happichannel": 2, "send": [2, 8, 15], "entir": [2, 3, 10], "expect": [3, 8, 10, 12, 15], "barebon": 3, "implement": [3, 10, 15], "No": [3, 10], "signal": [3, 6, 7, 8, 10, 14], "manual": [3, 9], "plot": [3, 6, 10], "themselv": [3, 4, 10], "parent": [3, 4, 5, 6, 8, 10, 14, 15], "qwidget": [3, 6, 14, 15], "none": [3, 4, 5, 6, 8, 11, 14, 15], "pin": [3, 10], "bool": [3, 9, 14, 15], "content_layout": 3, "qlayout": [3, 14], "default_display_typ": 3, "displaytyp": 3, "detailed_screen": 3, "scroll_opt": 3, "scrollopt": 3, "parametertre": 3, "qpopbar": 3, "hierarchi": [3, 7, 15], "along": [3, 14], "tree": [3, 10, 14], "startup": [3, 10], "enum": [3, 10, 14, 15], "behavior": [3, 10, 12, 14, 15], "enabl": [3, 15], "tool_nam": 3, "toolclass": 3, "dict": [3, 14, 15], "children": [3, 10], "categori": [3, 7, 10], "subdevic": 3, "add_lazy_subdisplai": 3, "display_class": 3, "pyqt5": [3, 14], "arbitrari": [3, 14], "subclass": [3, 12, 15], "dock": 3, "expand": [3, 9, 10, 14], "under": [3, 11], "add_subdisplai": [3, 10], "add_tool": [3, 10], "toolbar": [3, 10], "sidebar": [3, 10, 15], "ui": [3, 8, 10, 12, 14, 15], "embed_subdisplai": 3, "emb": [3, 10, 11], "show_displai": 3, "choic": [3, 10], "pair": 3, "iter": [3, 10, 14], "get_subdisplai": 3, "get": [3, 4, 10, 13, 14, 15], "return": [3, 7, 8, 10, 11, 14, 15], "member": [3, 10], "hide_subdisplai": 3, "visibl": [3, 15], "sidebarparamet": 3, "give": 3, "insid": [3, 10, 11, 15], "dockwidget": 3, "close": [3, 8, 10, 15], "otherwis": [3, 10, 14, 15], "save_suit": [3, 11, 14], "qfiledialog": [3, 11], "sy": [3, 11], "def": [3, 9, 11], "create_suit": [3, 11], "__name__": [3, 11], "typhos_cli": [3, 11], "argv": [3, 11], "show_subdisplai": 3, "given": [3, 9, 14, 15], "item": [3, 14, 15], "top_level_group": 3, "qgroupparameteritem": 3, "composite_heurist": 3, "embedded_templ": 3, "detailed_templ": 3, "engineering_templ": 3, "int": [3, 9, 14, 15], "nest": [3, 7], "convent": 3, "up": [3, 10, 14], "semi": 3, "deprec": 3, "qscrollarea": 3, "go": [3, 15], "composit": 3, "heurist": 3, "content": [3, 10, 15], "suggest_composite_screen": 3, "disk": 3, "scroll": [3, 10], "annot": [3, 9, 15], "anoth": [3, 10, 11, 14], "templateenum": 3, "alia": [3, 14, 15], "macro": [3, 10, 12], "full": [3, 8, 10, 14, 15], "dictionari": [3, 9, 10, 14, 15], "preced": 3, "fill": [3, 12, 15], "copy_to_clipboard": 3, "copi": 3, "imag": [3, 10, 14, 15], "clipboard": 3, "current_templ": 3, "display_widget": 3, "force_templ": 3, "forc": [3, 14], "from_class": 3, "klass": [3, 14], "clean": [3, 10, 14, 15], "display_templ": 3, "substitut": 3, "init": 3, "get_best_templ": 3, "best": 3, "hideempti": 3, "toggl": 3, "empti": [3, 14], "load_best_templ": 3, "area": [3, 15], "search_for_templ": 3, "search": [3, 14], "filesystem": 3, "device_cl": 3, "favor": [3, 14], "to_imag": 3, "qtgui": [3, 14], "qimag": [3, 14], "typhosdisplaytitl": 3, "show_switch": 3, "show_underlin": 3, "text": [3, 10, 14, 15], "typhosdisplayswitch": 3, "underlin": 3, "separ": [3, 4, 10, 13], "hook": [3, 15], "pop_out_help": 3, "pop": [3, 10], "set_device_displai": [3, 15], "callback": [3, 14, 15], "switcher": [3, 10], "toggle_help": 3, "typhostitlelabel": 3, "label": [3, 15], "intend": [3, 10, 14], "toggle_request": 3, "qt": [3, 10, 12, 15], "indic": [3, 9, 10, 15], "underneath": 3, "qtcore": [3, 14, 15], "mousepressev": [3, 15], "event": [3, 14, 15], "overridden": [3, 15], "mous": [3, 10], "press": 3, "typhostoolbutton": 3, "icon": [3, 15], "qicon": 3, "get_icon": 3, "default_icon": 3, "fontawesom": 3, "generate_context_menu": [3, 15], "menu": [3, 10, 15], "fall": 3, "instanc": [3, 10, 11, 14], "open_context_menu": [3, 15], "ev": [3, 14, 15], "qevent": [3, 15], "typhosdisplayswitcherbutton": 3, "switch": [3, 10, 15], "typhosdisplayconfigbutton": 3, "common": 3, "ellips": 3, "create_hide_empty_menu": 3, "base_menu": 3, "filter": [3, 10, 14, 15], "upon": [3, 14, 15], "trigger": [3, 15], "action": [3, 10], "qmenu": [3, 15], "create_kind_filter_menu": 3, "create_name_filter_menu": 3, "refresh": [3, 10], "hide_empti": 3, "wrap": [3, 6, 10], "device_displai": 3, "normalize_display_typ": 3, "rais": [3, 10, 14, 15], "valueerror": 3, "input": [3, 8, 10, 14], "cannot": 3, "process_widget": 3, "recurs": 3, "sinc": [3, 10], "don": [3, 8, 9, 15], "t": [3, 8, 9, 10, 15], "show_empti": 3, "toggle_displai": 3, "force_st": 3, "flip": 3, "state": [3, 8, 10, 15], "jupyterkernel": 4, "consol": 4, "kernel": 4, "protect": 4, "against": 4, "lock": 4, "difficult": 4, "around": 4, "strict": 4, "thei": [4, 8, 9, 11, 12, 13, 15], "__init__": [4, 5, 6], "20": 5, "timechartdisplai": 6, "conveni": [6, 7], "power": 7, "experiment": [7, 13], "howev": [7, 13, 15], "often": 7, "obscur": 7, "reflect": 7, "wall": 7, "button": [7, 10, 15], "flash": 7, "bombard": 7, "littl": 7, "thought": 7, "cohes": 7, "address": [7, 8, 10, 14, 15], "autom": [7, 10], "pyqt": [7, 10, 11, 14], "manag": [7, 10, 14], "develop": 7, "slac": 7, "nation": 7, "laboratori": 7, "larg": [7, 10, 12, 15], "toolkit": 7, "averag": 7, "write": [7, 14, 15], "tab": [7, 9], "still": [7, 10, 12, 14], "advanc": 7, "reinvent": 7, "librari": [7, 10, 14], "origin": [7, 12], "script": [7, 10], "procedur": 7, "nsl": 7, "ii": 7, "Then": 7, "runtim": [7, 10], "sort": [7, 10, 15], "differ": [7, 10, 11, 13, 14], "relev": [7, 15], "craft": [7, 10], "registri": 8, "kept": 8, "signal_registri": 8, "signalconnect": 8, "referenc": [8, 14], "protocol": [8, 10, 14], "monitor": [8, 10, 14], "report": [8, 10], "listen": 8, "push": 8, "data_typ": 8, "emit": [8, 14, 15], "static": 8, "add_listen": 8, "attach": [8, 10], "send_new_valu": 8, "setup": [8, 10], "perform": [8, 10], "pydmconnect": 8, "cast": 8, "signal_typ": 8, "awar": [8, 10], "convert": 8, "unsubscrib": [8, 14], "put_valu": 8, "new_val": 8, "writeabl": 8, "catch": [8, 10, 14], "except": [8, 10], "attempt": [8, 9], "receiv": [8, 15], "unless": [8, 9], "np": [8, 15], "ndarrai": [8, 15], "remove_listen": 8, "destroi": 8, "remov": [8, 10, 14], "disconnect": [8, 10, 14, 15], "send_new_meta": 8, "write_access": 8, "ever": [8, 10], "miss": [8, 10], "skip": 8, "no_alarm": 8, "anyth": [8, 10], "alarm": [8, 10, 15], "dataplugin": 8, "happiconnect": 8, "method_panel": 9, "via": [9, 10, 11, 14], "constructor": [9, 15], "signatur": [9, 14], "examin": 9, "let": 9, "mock": [9, 10], "foo": 9, "b": 9, "c": [9, 10], "d": [9, 15], "float": [9, 15], "14": 9, "boolean": 9, "qcheckbox": 9, "qlineedit": 9, "entri": [9, 10, 12], "keyword": [9, 14, 15], "choos": [9, 10], "functionpanel": [9, 15], "add_method": [9, 15], "hide_param": 9, "guess": 9, "fulfil": 9, "ci": 10, "previous": [10, 14, 15], "wa": [10, 14, 15], "lead": [10, 15], "unexpect": 10, "relat": [10, 12], "delet": [10, 14], "flake8": 10, "mirror": 10, "migrat": 10, "travi": 10, "continu": 10, "document": [10, 15], "deploy": 10, "setuptool": 10, "scm": 10, "syntax": 10, "been": [10, 12], "pyupgrad": 10, "modern": 10, "pyproject": 10, "toml": 10, "sphinx": 10, "tangkong": 10, "zllentz": 10, "small": 10, "positionerwidget": 10, "error": [10, 14, 15], "easier": [10, 13, 14], "track": [10, 15], "less": [10, 13], "lost": 10, "alarmkindlevel": 10, "enclos": 10, "kindlevel": [10, 15], "wait": [10, 14], "lazi": [10, 14], "caus": 10, "long": 10, "stop": [10, 14, 15], "success": [10, 14, 15], "unknownstatuserror": 10, "cleanup": 10, "background": [10, 14, 15], "thread": [10, 14], "functool": 10, "partial": [10, 14], "prevent": [10, 14, 15], "garbag": [10, 14], "collect": [10, 14], "reli": 10, "mechan": 10, "entrypoint": 10, "typhosstatusthread": 10, "orphan": 10, "klauer": 10, "clip": 10, "web": 10, "width": 10, "cach": [10, 15], "warn": 10, "unabl": 10, "without": 10, "traceback": 10, "unhelp": 10, "incompat": 10, "newer": [10, 14], "artifact": 10, "broken": 10, "jinja2": 10, "anleslac": 10, "defunct": 10, "satisfi": 10, "commit": 10, "latest": 10, "were": [10, 11], "last": [10, 15], "month": 10, "rather": [10, 15], "than": [10, 15], "assumpt": 10, "natur": 10, "epicssign": [10, 15], "pytmcsign": 10, "pcdsdevic": [10, 12], "workaround": 10, "happen": 10, "unload": 10, "deploi": 10, "hotfix": 10, "accident": 10, "move": [10, 12, 15], "disabl": [10, 14, 15], "wheel": 10, "interact": [10, 15], "combo": 10, "box": 10, "situat": 10, "try": 10, "past": 10, "extend": 10, "customiz": 10, "launcher": 10, "scrip": 10, "intent": [10, 13], "augment": 10, "lai": 10, "adjust": [10, 15], "dynam": 10, "resiz": 10, "height": 10, "450": 10, "lifetim": 10, "459": 10, "misidentifi": 10, "463": 10, "get_native_method": 10, "fail": [10, 15], "observ": 10, "broke": 10, "464": 10, "stabil": 10, "minor": 10, "frame": 10, "458": 10, "redirect": 10, "457": 10, "navig": 10, "page": 10, "reduc": 10, "javascript": 10, "spam": 10, "part": 10, "pyqtgraph": 10, "backward": 10, "reloc": 10, "lcl": [10, 12], "positionerbas": [10, 12], "affect": 10, "typhosrelatedsuitebutton": 10, "qpushbutton": [10, 15], "tradit": 10, "expert": [10, 15], "how": [10, 12, 15], "docstr": 10, "browser": 10, "aesthet": 10, "driver": 10, "ioc": [10, 15], "clear": [10, 14, 15], "reset": 10, "routin": 10, "clear_error": [10, 15], "statu": [10, 14, 15], "done_mov": 10, "movn": 10, "typhosrelatedsuit": 10, "attributeerror": [10, 15], "point": [10, 12, 14], "notifi": 10, "directori": [10, 12], "pathlib": [10, 12, 14], "pen": 10, "color": [10, 14], "better": 10, "combobox": 10, "reboot": 10, "hacki": 10, "mark": [10, 14], "immedi": [10, 14, 15], "unknown": 10, "even": [10, 14], "had": [10, 12], "lucid": 10, "elsewher": 10, "among": 10, "Their": 10, "shape": 10, "correl": 10, "pydmdrawingwidget": 10, "typhosalarmcircl": [10, 15], "typhosalarmrectangl": 10, "typhosalarmtriangl": 10, "typhosalarmellips": 10, "typhosalarmpolygon": 10, "sigint": 10, "handler": 10, "annoi": 10, "ctrl": 10, "maco": 10, "increas": 10, "timeout": [10, 14], "regard": 10, "sanit": 10, "subtl": 10, "crop": 10, "freez": 10, "certain": 10, "inclus": 10, "at1k4": 10, "land": 10, "zone": 10, "readi": 10, "signalro": 10, "at2l0": 10, "redund": 10, "calcul": [10, 15], "dialog": 10, "per": [10, 12], "manifest": 10, "big": 10, "assign": 10, "split": 10, "core": [10, 12, 14], "detailed_tre": 10, "tailor": 10, "special": 10, "tweakabl": [10, 15], "etc": 10, "attenu": 10, "repo": [10, 12], "futur": [10, 14], "legaci": [10, 14], "codepath": 10, "lightpath": 10, "unboundlocalerror": 10, "_channel": 10, "doctr_versions_menu": 10, "extens": [10, 14], "flat": 10, "minimum": 10, "local": 10, "conda": 10, "recip": 10, "forg": 10, "typhosload": [10, 14], "anim": [10, 14], "appear": 10, "complex": 10, "simplifi": 10, "As": [10, 15], "plan": 10, "typhon": 10, "typhoscompositesignalpanel": [10, 15], "compos": 10, "typhosdisplai": 10, "discoveri": 10, "discov": 10, "inherit": [10, 14, 15], "fallback": [10, 15], "previou": 10, "typhoslineedit": [10, 15], "redesign": 10, "magic": 10, "fault": 10, "toler": 10, "pcdsmotorbas": 10, "rework": 10, "miscellan": 10, "persist": 10, "font": 10, "dpi": 10, "renam": 10, "typhostimeplot": 10, "longer": [10, 14], "grab_kind": 10, "osx": 10, "unifi": 10, "titl": 10, "progress": 10, "gif": 10, "exponenti": 10, "popbar": 10, "codebas": 10, "spot": 10, "dedic": 10, "pypi": 10, "throw": 10, "deprecationwarn": 10, "equival": 10, "next": [10, 15], "beauti": 10, "stabl": 10, "utilitarian": 10, "program": [10, 14], "capabl": [10, 14], "larger": 10, "smaller": 10, "transmit": 10, "present": 10, "92": 10, "devicedisplai": 10, "possibl": [10, 14], "96": 10, "uniform": 10, "approach": [10, 13], "taken": 10, "introspect": [10, 14], "is_signal_ro": [10, 14], "grab_hint": 10, "98": 10, "91": 10, "94": 10, "drop": 10, "90": 10, "prototyp": 10, "proof": 10, "pcd": 10, "tag": 10, "anaconda": 10, "45": 10, "commun": 10, "63": 10, "activ": [10, 15], "use_stylesheet": [10, 14], "61": 10, "89": 10, "prefer": 10, "add_subdevic": 10, "automaticali": 10, "pydmlogdisplai": 10, "logger": 10, "70": 10, "pydmtimeplot": 10, "fewer": [10, 13, 15], "73": 10, "resid": 10, "togglepanel": 10, "50": 10, "rotatingimag": 10, "58": 10, "componentbutton": 10, "add_pv_to_plot": 10, "typhondisplai": 10, "53": 10, "due": 10, "did": 10, "account": 10, "42": 10, "71": 10, "serv": 10, "sub": [10, 15], "pars": 10, "represent": 10, "png": 10, "later": [11, 14], "rout": 11, "saved_suit": 11, "re": [11, 15], "load_suit": [11, 14], "brows": 12, "tutori": 12, "guidanc": 12, "md": 12, "namespac": 12, "upshot": 12, "exactli": 12, "mro": [12, 14], "decid": 12, "why": 12, "pydm_displays_path": [12, 14], "With": [12, 15], "mind": 12, "clone": 12, "linux": 12, "critic": 13, "dai": [13, 14], "hope": 13, "servic": 13, "eas": 13, "therefor": [13, 14, 15], "secondli": 13, "pattern": 13, "standalon": 13, "typhostool": 13, "accord": [13, 14, 15], "deviceconnectionmonitorthread": 14, "include_lazi": 14, "grab": 14, "connection_upd": 14, "metadata_dict": 14, "self": [14, 15], "wait_m": 14, "grabkinditem": 14, "objectconnectionmonitorthread": 14, "threadpoolwork": 14, "func": [14, 15], "worker": 14, "callabl": [14, 15], "rargument": 14, "timeout_messag": 14, "qlabel": 14, "loading_timeout_m": 14, "millisecond": 14, "contextmenuev": 14, "qcontextmenuev": 14, "weakpartialmethodslot": 14, "signal_own": 14, "qobject": 14, "pyqtsign": 14, "compat": 14, "slot": 14, "prior": 14, "owner": 14, "fire": 14, "include_pydm": 14, "behav": 14, "theme": 14, "chosen": [14, 15], "whole": 14, "channel_from_sign": 14, "channel_nam": 14, "clean_attr": 14, "nicer": 14, "clean_nam": 14, "strip_par": 14, "direct": 14, "strip": 14, "schema": 14, "clear_layout": 14, "code_from_devic": 14, "code_from_device_repr": 14, "repr": 14, "compose_stylesheet": 14, "accomplish": 14, "highest": 14, "bottom": 14, "evalu": 14, "itet": 14, "fulli": 14, "must": [14, 15], "end": 14, "unlik": 14, "interpret": [14, 15], "composed_styl": 14, "setstylesheet": 14, "incorpor": 14, "oserror": 14, "encount": 14, "typeerror": 14, "connection_status_monitor": 14, "ophydobj": [14, 15], "subscrib": 14, "obj": [14, 15], "dump_grid_layout": 14, "row": [14, 15], "cell_width": 14, "60": 14, "dump": 14, "qgridlayout": 14, "tabl": [14, 15], "summari": 14, "find_file_in_path": 14, "display_path": 14, "find_parent_with_class": 14, "cl": 14, "find_templates_for_class": 14, "view_typ": 14, "include_mro": 14, "potenti": 14, "de": 14, "superclass": 14, "least": 14, "flatten_tre": 14, "param": [14, 15], "flatten": [14, 15], "get_all_signals_from_devic": 14, "filter_bi": 14, "componentwalk": 14, "get_compon": 14, "ophyditem": 14, "get_device_from_fake_class": 14, "fake_cl": 14, "make_fake_devic": 14, "get_variety_metadata": 14, "cpt": 14, "known": 14, "is_fake_device_class": 14, "until": 14, "is_standard_templ": 14, "link_signal_to_widget": 14, "linked_attribut": 14, "property_attr": 14, "widget_attr": 14, "hide_unavail": 14, "decor": 14, "retriev": 14, "indirect": 14, "short": [14, 15], "getattr": 14, "cpt1": 14, "cpt2": 14, "low_limit": 14, "enter": 14, "make_identifi": 14, "no_device_lazy_load": 14, "lazy_wait_for_connect": 14, "restor": 14, "nullcontext": 14, "stand": 14, "py3": 14, "contextlib": 14, "patch_connect_slot": 14, "patch": 14, "qmetaobject": 14, "connectslotsbynam": 14, "systemerror": 14, "pyqt_class_from_enum": 14, "q_enum": 14, "raise_window": 14, "bring": 14, "focu": 14, "stack": 14, "minim": 14, "unminim": 14, "respond": 14, "sequenc": 14, "good": 14, "random_color": 14, "random": 14, "hex": 14, "reload_widget_stylesheet": 14, "cascad": 14, "reload": 14, "remove_duplicate_item": 14, "list_": 14, "tupl": [14, 15], "retain": [14, 15], "file_or_buff": 14, "relaunch": 14, "subscription_context": 14, "event_typ": 14, "subscription_context_devic": 14, "unchang": 14, "widget_to_imag": 14, "fill_color": 14, "19": 14, "paint": 14, "get_global_display_path_cach": 14, "_globaldisplaypathcach": 14, "singleton": 14, "add_path": 14, "glob": 14, "get_global_describe_cach": 14, "_globaldescribecach": 14, "global": [14, 15], "qthreadpool": 14, "new_descript": 14, "connect_thread": 14, "hold": 14, "desc": [14, 15], "get_global_widget_type_cach": 14, "_globalwidgettypecach": 14, "widgets_determin": 14, "describe_cach": 14, "signalwidgetinfo": [14, 15], "concis": 15, "opt": 15, "create_signal_widget": 15, "read_onli": 15, "tooltip": 15, "factori": 15, "abl": 15, "pydmlabel": 15, "pydmlineedit": 15, "pydmenumcombobox": 15, "read_cl": 15, "read_kwarg": 15, "write_cl": 15, "write_kwarg": 15, "from_sign": 15, "widget_type_from_descript": 15, "record": 15, "widget_class": 15, "determine_widget_typ": 15, "principl": 15, "thefor": 15, "variat": 15, "hierarch": 15, "loading_complet": 15, "num_col": 15, "col_label": 15, "col_readback": 15, "col_setpoint": 15, "compositesignalpanel": 15, "add_pv": 15, "read_pv": 15, "write_pv": 15, "add_row": 15, "span": 15, "remain": 15, "add_sign": 15, "drawn": 15, "depend": 15, "_read_pv": 15, "_write_pv": 15, "reserv": 15, "epicssignalro": 15, "filter_sign": 15, "name_filt": 15, "addition": 15, "label_text_from_attribut": 15, "dotted_nam": 15, "becaus": 15, "ambigu": 15, "clash": 15, "row_count": 15, "signal_nam": 15, "visible_el": 15, "visible_sign": 15, "init_channel": 15, "signalord": 15, "filter_set": 15, "namefilt": 15, "showconfig": 15, "config": 15, "showhint": 15, "shownorm": 15, "showomit": 15, "show_kind": 15, "sortbi": 15, "contrast": 15, "individu": 15, "mix": 15, "add_sub_devic": 15, "motion": 15, "amount": 15, "punch": 15, "soft": 15, "limit": 15, "hardwar": 15, "readback_attribut": 15, "setpoint_attribut": 15, "user_setpoint": 15, "low_limit_switch_attribut": 15, "high_limit_switch_attribut": 15, "low_limit_switch": 15, "high_limit_switch": 15, "low_limit_travel_attribut": 15, "high_limit_travel_attribut": 15, "low_limit_travel": 15, "high_limit_travel": 15, "tweak": 15, "moving_attribut": 15, "motor_is_mov": 15, "moving_ind": 15, "error_message_attribut": 15, "error_messag": 15, "error_label": 15, "circl": 15, "summar": 15, "typhosalarm": 15, "acceleration_attribut": 15, "failed_mov": 15, "high": 15, "travel": 15, "low": 15, "move_chang": 15, "begun": 15, "lag": 15, "behind": 15, "actual": 15, "unnecessari": 15, "rapid": 15, "movement": 15, "negative_tweak": 15, "neg": 15, "tweak_valu": 15, "positive_tweak": 15, "set_valu": 15, "show_expert_button": 15, "typic": 15, "unrel": 15, "successful_mov": 15, "offset": 15, "update_alarm_text": 15, "alarm_level": 15, "bit": 15, "velocity_attribut": 15, "arrang": 15, "functiondisplai": 15, "typhosmethodbutton": 15, "method_nam": 15, "use_statu": 15, "clickablebitind": 15, "a0": 15, "qmouseev": 15, "imagedialogbutton": 15, "arrai": 15, "parent_widget_class": 15, "qmainwindow": 15, "pydmimageview": 15, "signaldialogbutton": 15, "qdialog": 15, "notimpl": 15, "show_dialog": 15, "qdockwidget": 15, "modifi": 15, "closeev": 15, "qcloseev": 15, "typhosarrayt": 15, "variety_metadata": 15, "ophyd_sign": 15, "reshap": 15, "waveform": 15, "tabular": 15, "value_chang": 15, "new_waveform": 15, "typhosbyteind": 15, "integ": 15, "bitmask": 15, "typhosbytesetpoint": 15, "toggleabl": 15, "numbit": 15, "typhostextedit": 15, "typhoscombobox": 15, "enum_strings_chang": 15, "new_enum_str": 15, "broadcast": 15, "wheelev": 15, "qwheelev": 15, "typhoscommandbutton": 15, "pushbutton": 15, "typhoscommandenumbutton": 15, "proc": 15, "typhoslabel": 15, "display_format": 15, "reimplement": 15, "nd": 15, "multilin": 15, "scalar": 15, "rang": 15, "unit_chang": 15, "new_unit": 15, "update_format_str": 15, "showunit": 15, "send_valu": 15, "histori": 15, "setpointhistorycount": 15, "setpoint_histori": 15, "widget_ctx_menu": 15, "fetch": 15, "assemble_tools_menu": 15, "typhosscalarrang": 15, "slider": 15, "connection_chang": 15, "act": 15, "alarm_disconnect": 15, "delta_sign": 15, "delta": 15, "delta_valu": 15, "num_point": 15, "pydmslid": 15, "min": 15, "max": 15, "typhossidebaritem": 15, "depth": 15, "embed_request": 15, "hide_request": 15, "open_request": 15, "treewidgetchang": 15, "qtreewidget": 15, "typhostweak": 15, "textedit": 15, "writabl": 15, "editor": 15, "send_value_sign": 15, "set_displai": 15, "write_access_chang": 15, "new_write_access": 15, "deni": 15, "waveformdialogbutton": 15, "histogram": 15, "timeseri": 15, "pydmwaveformplot": 15, "typhosdesignermixin": 15, "mixin": 15, "alarmsensitivebord": 15, "alarmsensitivecont": 15}, "objects": {"typhos": [[15, 0, 1, "", "TyphosCompositeSignalPanel"], [3, 0, 1, "", "TyphosDeviceDisplay"], [15, 0, 1, "", "TyphosMethodButton"], [15, 0, 1, "", "TyphosPositionerWidget"], [15, 0, 1, "", "TyphosSignalPanel"], [3, 0, 1, "", "TyphosSuite"], [1, 5, 0, "-", "cli"], [14, 5, 0, "-", "utils"]], "typhos.TyphosDeviceDisplay": [[3, 1, 1, "", "TemplateEnum"], [3, 2, 1, "", "add_device"], [3, 1, 1, "", "composite_heuristics"], [3, 2, 1, "", "copy_to_clipboard"], [3, 3, 1, "", "current_template"], [3, 3, 1, "", "device"], [3, 1, 1, "", "device_class"], [3, 1, 1, "", "device_name"], [3, 1, 1, "", "display_type"], [3, 3, 1, "", "display_widget"], [3, 1, 1, "", "force_template"], [3, 2, 1, "", "from_class"], [3, 2, 1, "", "from_device"], [3, 2, 1, "", "get_best_template"], [3, 1, 1, "", "hideEmpty"], [3, 2, 1, "", "load_best_template"], [3, 3, 1, "", "macros"], [3, 1, 1, "", "scroll_option"], [3, 2, 1, "", "search_for_templates"], [3, 2, 1, "", "suggest_composite_screen"], [3, 2, 1, "", "to_image"]], "typhos.TyphosMethodButton": [[15, 2, 1, "", "add_device"], [15, 2, 1, "", "execute"], [15, 2, 1, "", "from_device"], [15, 1, 1, "", "method_name"], [15, 1, 1, "", "use_status"]], "typhos.TyphosPositionerWidget": [[15, 0, 1, "", "KindLevel"], [15, 1, 1, "", "acceleration_attribute"], [15, 2, 1, "", "add_device"], [15, 2, 1, "", "clear_error"], [15, 3, 1, "", "device"], [15, 1, 1, "", "error_message_attribute"], [15, 1, 1, "", "failed_move"], [15, 1, 1, "", "high_limit_switch_attribute"], [15, 1, 1, "", "high_limit_travel_attribute"], [15, 1, 1, "", "low_limit_switch_attribute"], [15, 1, 1, "", "low_limit_travel_attribute"], [15, 2, 1, "", "move_changed"], [15, 1, 1, "", "moving"], [15, 1, 1, "", "moving_attribute"], [15, 2, 1, "", "negative_tweak"], [15, 2, 1, "", "positive_tweak"], [15, 1, 1, "", "readback_attribute"], [15, 2, 1, "", "set"], [15, 1, 1, "", "setpoint_attribute"], [15, 1, 1, "", "show_expert_button"], [15, 2, 1, "", "stop"], [15, 1, 1, "", "successful_move"], [15, 2, 1, "", "tweak"], [15, 2, 1, "", "update_alarm_text"], [15, 1, 1, "", "velocity_attribute"]], "typhos.TyphosSignalPanel": [[15, 0, 1, "", "SignalOrder"], [15, 2, 1, "", "add_device"], [15, 3, 1, "", "filter_settings"], [15, 2, 1, "", "generate_context_menu"], [15, 1, 1, "", "nameFilter"], [15, 2, 1, "", "open_context_menu"], [15, 2, 1, "", "set_device_display"], [15, 1, 1, "", "showConfig"], [15, 1, 1, "", "showHints"], [15, 1, 1, "", "showNormal"], [15, 1, 1, "", "showOmitted"], [15, 3, 1, "", "show_kinds"], [15, 1, 1, "", "sortBy"]], "typhos.TyphosSuite": [[3, 2, 1, "", "add_device"], [3, 2, 1, "", "add_lazy_subdisplay"], [3, 2, 1, "", "add_subdisplay"], [3, 2, 1, "", "add_tool"], [3, 1, 1, "", "default_tools"], [3, 2, 1, "", "embed_subdisplay"], [3, 2, 1, "", "from_device"], [3, 2, 1, "", "from_devices"], [3, 2, 1, "", "get_subdisplay"], [3, 2, 1, "", "hide_subdisplay"], [3, 2, 1, "", "hide_subdisplays"], [3, 2, 1, "", "save"], [3, 2, 1, "", "show_subdisplay"], [3, 3, 1, "", "tools"], [3, 3, 1, "", "top_level_groups"]], "typhos.cache": [[14, 0, 1, "", "_GlobalDescribeCache"], [14, 0, 1, "", "_GlobalDisplayPathCache"], [14, 0, 1, "", "_GlobalWidgetTypeCache"], [14, 4, 1, "", "get_global_describe_cache"], [14, 4, 1, "", "get_global_display_path_cache"], [14, 4, 1, "", "get_global_widget_type_cache"]], "typhos.cache._GlobalDescribeCache": [[14, 1, 1, "", "cache"], [14, 2, 1, "", "clear"], [14, 1, 1, "", "connect_thread"], [14, 2, 1, "", "get"]], "typhos.cache._GlobalDisplayPathCache": [[14, 2, 1, "", "add_path"], [14, 2, 1, "", "update"]], "typhos.cache._GlobalWidgetTypeCache": [[14, 1, 1, "", "cache"], [14, 2, 1, "", "clear"], [14, 1, 1, "", "describe_cache"], [14, 2, 1, "", "get"]], "typhos.display": [[3, 0, 1, "", "TyphosDisplayConfigButton"], [3, 0, 1, "", "TyphosDisplaySwitcher"], [3, 0, 1, "", "TyphosDisplaySwitcherButton"], [3, 0, 1, "", "TyphosDisplayTitle"], [3, 0, 1, "", "TyphosTitleLabel"], [3, 0, 1, "", "TyphosToolButton"], [3, 4, 1, "", "hide_empty"], [3, 4, 1, "", "normalize_display_type"], [3, 4, 1, "", "show_empty"], [3, 4, 1, "", "toggle_display"]], "typhos.display.TyphosDisplayConfigButton": [[3, 2, 1, "", "create_hide_empty_menu"], [3, 2, 1, "", "create_kind_filter_menu"], [3, 2, 1, "", "create_name_filter_menu"], [3, 2, 1, "", "generate_context_menu"], [3, 2, 1, "", "hide_empty"], [3, 2, 1, "", "set_device_display"]], "typhos.display.TyphosDisplaySwitcher": [[3, 2, 1, "", "add_device"], [3, 2, 1, "", "set_device_display"]], "typhos.display.TyphosDisplaySwitcherButton": [[3, 2, 1, "", "generate_context_menu"]], "typhos.display.TyphosDisplayTitle": [[3, 2, 1, "", "add_device"], [3, 2, 1, "", "pop_out_help"], [3, 2, 1, "", "set_device_display"], [3, 1, 1, "", "show_switcher"], [3, 1, 1, "", "show_underline"], [3, 2, 1, "", "toggle_help"]], "typhos.display.TyphosTitleLabel": [[3, 2, 1, "", "mousePressEvent"], [3, 1, 1, "", "toggle_requested"]], "typhos.display.TyphosToolButton": [[3, 1, 1, "", "DEFAULT_ICON"], [3, 2, 1, "", "generate_context_menu"], [3, 2, 1, "", "get_icon"], [3, 2, 1, "", "open_context_menu"]], "typhos.func": [[15, 0, 1, "", "FunctionPanel"]], "typhos.func.FunctionPanel": [[15, 2, 1, "", "add_method"]], "typhos.panel": [[15, 0, 1, "", "CompositeSignalPanel"], [15, 0, 1, "", "SignalPanel"]], "typhos.panel.CompositeSignalPanel": [[15, 1, 1, "", "COL_LABEL"], [15, 1, 1, "", "COL_READBACK"], [15, 1, 1, "", "COL_SETPOINT"], [15, 1, 1, "", "NUM_COLS"], [15, 2, 1, "", "add_device"], [15, 2, 1, "", "add_sub_device"], [15, 2, 1, "", "label_text_from_attribute"], [15, 1, 1, "", "loading_complete"], [15, 3, 1, "", "visible_elements"]], "typhos.panel.SignalPanel": [[15, 1, 1, "", "COL_LABEL"], [15, 1, 1, "", "COL_READBACK"], [15, 1, 1, "", "COL_SETPOINT"], [15, 1, 1, "", "NUM_COLS"], [15, 2, 1, "", "add_device"], [15, 2, 1, "", "add_pv"], [15, 2, 1, "", "add_row"], [15, 2, 1, "", "add_signal"], [15, 2, 1, "", "clear"], [15, 2, 1, "", "filter_signals"], [15, 2, 1, "", "label_text_from_attribute"], [15, 1, 1, "", "loading_complete"], [15, 3, 1, "", "row_count"], [15, 3, 1, "", "signals"], [15, 3, 1, "", "visible_elements"], [15, 3, 1, "", "visible_signals"]], "typhos.plugins": [[8, 0, 1, "", "HappiConnection"], [8, 0, 1, "", "HappiPlugin"], [8, 0, 1, "", "SignalConnection"], [8, 0, 1, "", "SignalPlugin"], [8, 4, 1, "", "register_client"], [8, 4, 1, "", "register_signal"]], "typhos.plugins.HappiConnection": [[8, 2, 1, "", "add_listener"], [8, 2, 1, "", "remove_listener"]], "typhos.plugins.SignalConnection": [[8, 2, 1, "", "add_listener"], [8, 2, 1, "", "cast"], [8, 2, 1, "", "close"], [8, 2, 1, "", "put_value"], [8, 2, 1, "", "remove_listener"], [8, 2, 1, "", "send_new_meta"], [8, 2, 1, "", "send_new_value"], [8, 1, 1, "", "signal"]], "typhos.textedit": [[15, 0, 1, "", "TyphosTextEdit"]], "typhos.textedit.TyphosTextEdit": [[15, 2, 1, "", "send_value"], [15, 2, 1, "", "set_display"], [15, 2, 1, "", "value_changed"], [15, 3, 1, "", "variety_metadata"], [15, 2, 1, "", "write_access_changed"]], "typhos.tools": [[4, 0, 1, "", "TyphosConsole"], [5, 0, 1, "", "TyphosLogDisplay"], [6, 0, 1, "", "TyphosTimePlot"]], "typhos.tools.TyphosConsole": [[4, 2, 1, "", "__init__"]], "typhos.tools.TyphosLogDisplay": [[5, 2, 1, "", "__init__"]], "typhos.tools.TyphosTimePlot": [[6, 2, 1, "", "__init__"]], "typhos.tweakable": [[15, 0, 1, "", "TyphosTweakable"]], "typhos.tweakable.TyphosTweakable": [[15, 2, 1, "", "negative_tweak"], [15, 2, 1, "", "positive_tweak"], [15, 2, 1, "", "tweak"], [15, 3, 1, "", "variety_metadata"]], "typhos.utils": [[14, 0, 1, "", "DeviceConnectionMonitorThread"], [14, 0, 1, "", "GrabKindItem"], [14, 0, 1, "", "ObjectConnectionMonitorThread"], [14, 0, 1, "", "ThreadPoolWorker"], [14, 0, 1, "", "TyphosBase"], [14, 0, 1, "", "TyphosLoading"], [14, 0, 1, "", "WeakPartialMethodSlot"], [14, 4, 1, "", "apply_standard_stylesheets"], [14, 4, 1, "", "channel_from_signal"], [14, 4, 1, "", "channel_name"], [14, 4, 1, "", "clean_attr"], [14, 4, 1, "", "clean_name"], [14, 4, 1, "", "clear_layout"], [14, 4, 1, "", "code_from_device"], [14, 4, 1, "", "code_from_device_repr"], [14, 4, 1, "", "compose_stylesheets"], [14, 4, 1, "", "connection_status_monitor"], [14, 4, 1, "", "dump_grid_layout"], [14, 4, 1, "", "find_file_in_paths"], [14, 4, 1, "", "find_parent_with_class"], [14, 4, 1, "", "find_templates_for_class"], [14, 4, 1, "", "flatten_tree"], [14, 4, 1, "", "get_all_signals_from_device"], [14, 4, 1, "", "get_component"], [14, 4, 1, "", "get_device_from_fake_class"], [14, 4, 1, "", "get_variety_metadata"], [14, 4, 1, "", "is_fake_device_class"], [14, 4, 1, "", "is_signal_ro"], [14, 4, 1, "", "is_standard_template"], [14, 4, 1, "", "link_signal_to_widget"], [14, 4, 1, "", "linked_attribute"], [14, 4, 1, "", "load_suite"], [14, 4, 1, "", "make_identifier"], [14, 4, 1, "", "no_device_lazy_load"], [14, 4, 1, "", "nullcontext"], [14, 4, 1, "", "patch_connect_slots"], [14, 4, 1, "", "pyqt_class_from_enum"], [14, 4, 1, "", "raise_window"], [14, 4, 1, "", "random_color"], [14, 4, 1, "", "reload_widget_stylesheet"], [14, 4, 1, "", "remove_duplicate_items"], [14, 4, 1, "", "save_suite"], [14, 4, 1, "", "subscription_context"], [14, 4, 1, "", "subscription_context_device"], [14, 4, 1, "", "use_stylesheet"], [14, 4, 1, "", "widget_to_image"]], "typhos.utils.DeviceConnectionMonitorThread": [[14, 1, 1, "", "connection_update"], [14, 2, 1, "", "run"], [14, 2, 1, "", "stop"]], "typhos.utils.GrabKindItem": [[14, 1, 1, "", "attr"], [14, 1, 1, "", "component"], [14, 1, 1, "", "signal"]], "typhos.utils.ObjectConnectionMonitorThread": [[14, 1, 1, "", "connection_update"], [14, 2, 1, "", "run"], [14, 2, 1, "", "stop"]], "typhos.utils.ThreadPoolWorker": [[14, 2, 1, "", "run"]], "typhos.utils.TyphosLoading": [[14, 1, 1, "", "LOADING_TIMEOUT_MS"], [14, 2, 1, "", "contextMenuEvent"]], "typhos.widgets": [[15, 0, 1, "", "ClickableBitIndicator"], [15, 0, 1, "", "ImageDialogButton"], [15, 0, 1, "", "SignalDialogButton"], [15, 0, 1, "", "SignalWidgetInfo"], [15, 0, 1, "", "SubDisplay"], [15, 0, 1, "", "TyphosArrayTable"], [15, 0, 1, "", "TyphosByteIndicator"], [15, 0, 1, "", "TyphosByteSetpoint"], [15, 0, 1, "", "TyphosComboBox"], [15, 0, 1, "", "TyphosCommandButton"], [15, 0, 1, "", "TyphosCommandEnumButton"], [15, 0, 1, "", "TyphosDesignerMixin"], [15, 0, 1, "", "TyphosLabel"], [15, 0, 1, "", "TyphosLineEdit"], [15, 0, 1, "", "TyphosScalarRange"], [15, 0, 1, "", "TyphosSidebarItem"], [15, 0, 1, "", "WaveformDialogButton"], [15, 4, 1, "", "create_signal_widget"], [15, 4, 1, "", "determine_widget_type"], [15, 4, 1, "", "widget_type_from_description"]], "typhos.widgets.ClickableBitIndicator": [[15, 2, 1, "", "mousePressEvent"]], "typhos.widgets.ImageDialogButton": [[15, 1, 1, "", "parent_widget_class"], [15, 2, 1, "", "widget"]], "typhos.widgets.SignalDialogButton": [[15, 1, 1, "", "icon"], [15, 1, 1, "", "parent_widget_class"], [15, 2, 1, "", "show_dialog"], [15, 1, 1, "", "text"], [15, 2, 1, "", "widget"]], "typhos.widgets.SignalWidgetInfo": [[15, 2, 1, "", "from_signal"]], "typhos.widgets.SubDisplay": [[15, 2, 1, "", "closeEvent"]], "typhos.widgets.TyphosArrayTable": [[15, 2, 1, "", "value_changed"], [15, 3, 1, "", "variety_metadata"]], "typhos.widgets.TyphosByteIndicator": [[15, 3, 1, "", "variety_metadata"]], "typhos.widgets.TyphosByteSetpoint": [[15, 1, 1, "", "numBits"], [15, 2, 1, "", "value_changed"]], "typhos.widgets.TyphosComboBox": [[15, 2, 1, "", "enum_strings_changed"], [15, 2, 1, "", "wheelEvent"]], "typhos.widgets.TyphosCommandButton": [[15, 2, 1, "", "enum_strings_changed"], [15, 3, 1, "", "variety_metadata"]], "typhos.widgets.TyphosCommandEnumButton": [[15, 2, 1, "", "enum_strings_changed"], [15, 3, 1, "", "variety_metadata"]], "typhos.widgets.TyphosDesignerMixin": [[15, 1, 1, "", "alarmSensitiveBorder"], [15, 1, 1, "", "alarmSensitiveContent"], [15, 1, 1, "", "channel"]], "typhos.widgets.TyphosLabel": [[15, 2, 1, "", "enum_strings_changed"], [15, 2, 1, "", "unit_changed"]], "typhos.widgets.TyphosLineEdit": [[15, 2, 1, "", "send_value"], [15, 1, 1, "", "setpointHistoryCount"], [15, 3, 1, "", "setpoint_history"], [15, 2, 1, "", "unit_changed"], [15, 2, 1, "", "widget_ctx_menu"]], "typhos.widgets.TyphosScalarRange": [[15, 2, 1, "", "connection_changed"], [15, 3, 1, "", "delta_signal"], [15, 1, 1, "", "delta_value"], [15, 3, 1, "", "variety_metadata"]], "typhos.widgets.TyphosSidebarItem": [[15, 2, 1, "", "embed_requested"], [15, 2, 1, "", "hide_requested"], [15, 2, 1, "", "open_requested"], [15, 2, 1, "", "treeWidgetChanged"]], "typhos.widgets.WaveformDialogButton": [[15, 1, 1, "", "parent_widget_class"], [15, 2, 1, "", "widget"]]}, "objtypes": {"0": "py:class", "1": "py:attribute", "2": "py:method", "3": "py:property", "4": "py:function", "5": "py:module"}, "objnames": {"0": ["py", "class", "Python class"], "1": ["py", "attribute", "Python attribute"], "2": ["py", "method", "Python method"], "3": ["py", "property", "Python property"], "4": ["py", "function", "Python function"], "5": ["py", "module", "Python module"]}, "titleterms": {"how": 0, "work": 0, "interpret": 0, "devic": 0, "us": 0, "happi": [0, 2], "signal": [0, 2, 15], "displai": [0, 3], "fill": 0, "templat": [0, 12], "The": 0, "typhossuit": [0, 3], "stylesheet": 0, "document": 0, "widget": [0, 14, 15], "jira": 0, "bug": [0, 10], "report": 0, "launch": 0, "exampl": 0, "command": 1, "line": 1, "util": [1, 3, 14], "applic": 2, "connect": 2, "ophyd": [2, 14], "inclus": 2, "metadata": 2, "plugin": [2, 8], "suit": 3, "typhosdevicedisplai": 3, "standard": 3, "titl": 3, "tool": [3, 4, 5, 6, 13], "button": 3, "typho": [4, 5, 6, 7, 8], "typhosconsol": 4, "typhoslogdisplai": 5, "typhostimeplot": 6, "relat": 7, "project": 7, "data": 8, "pydm": 8, "signalplugin": 8, "happiplugin": 8, "includ": 9, "python": 9, "code": 9, "ad": 9, "method": [9, 15], "releas": 10, "histori": 10, "v2": 10, "4": 10, "1": 10, "2023": 10, "descript": [10, 14], "bugfix": 10, "mainten": 10, "contributor": 10, "0": 10, "2022": 10, "11": 10, "featur": 10, "3": 10, "10": 10, "20": 10, "2": 10, "07": 10, "28": 10, "fix": 10, "05": 10, "02": 10, "03": 10, "31": 10, "2021": 10, "30": 10, "enhanc": 10, "what": 10, "": 10, "new": 10, "18": 10, "08": 10, "api": 10, "break": 10, "compat": 10, "doc": 10, "test": 10, "v1": 10, "09": 10, "6": 10, "04": 10, "5": 10, "2020": 10, "26": 10, "12": 10, "22": 10, "19": 10, "01": 10, "v0": 10, "7": 10, "2019": 10, "2018": 10, "deprec": 10, "06": 10, "27": 10, "chang": 10, "depend": 10, "2017": 10, "15": 10, "save": 11, "load": 11, "custom": 12, "creation": 12, "substitut": 12, "filenam": 12, "discoveri": 12, "support": 13, "class": 13, "function": [14, 15], "cach": 14, "path": 14, "object": 14, "type": [14, 15], "determin": 15, "panel": 15, "basic": 15, "composit": 15, "typhospositionerwidget": 15, "miscellan": 15, "design": 15}, "envversion": {"sphinx.domains.c": 2, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 8, "sphinx.domains.index": 1, "sphinx.domains.javascript": 2, "sphinx.domains.math": 2, "sphinx.domains.python": 3, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.viewcode": 1, "sphinx": 57}, "alltitles": {"How it Works": [[0, "how-it-works"]], "Interpreting a Device": [[0, "interpreting-a-device"]], "Using Happi": [[0, "using-happi"]], "Signals of Devices": [[0, "signals-of-devices"]], "Display Signals": [[0, "display-signals"]], "Filling Templates": [[0, "filling-templates"]], "The TyphosSuite": [[0, "the-typhossuite"]], "Using the StyleSheet": [[0, "using-the-stylesheet"]], "Using the Documentation Widget": [[0, "using-the-documentation-widget"]], "Using the Jira Bug Reporting Widget": [[0, "using-the-jira-bug-reporting-widget"]], "Launching the Examples": [[0, "launching-the-examples"]], "Command Line Utilities": [[1, "module-typhos.cli"]], "Application Connections": [[2, "application-connections"]], "Ophyd Signals": [[2, "ophyd-signals"]], "Inclusion of Metadata": [[2, "inclusion-of-metadata"]], "Happi Plugin": [[2, "happi-plugin"]], "Suite and Displays": [[3, "suite-and-displays"]], "TyphosSuite": [[3, "typhossuite"]], "TyphosDeviceDisplay": [[3, "typhosdevicedisplay"]], "Standardized Display Title": [[3, "standardized-display-title"]], "Tool buttons": [[3, "tool-buttons"]], "Utilities": [[3, "utilities"]], "typhos.tools.TyphosConsole": [[4, "typhos-tools-typhosconsole"]], "typhos.tools.TyphosLogDisplay": [[5, "typhos-tools-typhoslogdisplay"]], "typhos.tools.TyphosTimePlot": [[6, "typhos-tools-typhostimeplot"]], "Typhos": [[7, "typhos"]], "Related Projects": [[7, "related-projects"]], "Typhos Data Plugins for PyDM": [[8, "typhos-data-plugins-for-pydm"]], "SignalPlugin": [[8, "signalplugin"]], "HappiPlugin": [[8, "happiplugin"]], "Including Python Code": [[9, "including-python-code"]], "Adding Methods": [[9, "adding-methods"]], "Release History": [[10, "release-history"]], "v2.4.1 (2023-4-4)": [[10, "v2-4-1-2023-4-4"]], "Description": [[10, "description"], [10, "id1"], [10, "id3"], [10, "id7"], [10, "id10"], [10, "id14"], [10, "id17"], [10, "id19"], [10, "id26"], [10, "id34"], [10, "id36"], [10, "id39"], [10, "id41"], [10, "id43"], [10, "id45"], [10, "id47"], [10, "id49"], [10, "id51"], [10, "id55"], [10, "id58"], [10, "id61"], [10, "id64"], [10, "id65"], [10, "id66"], [10, "id68"]], "Bugfixes": [[10, "bugfixes"], [10, "id4"]], "Maintenance": [[10, "maintenance"], [10, "id5"], [10, "id8"], [10, "id12"], [10, "id25"], [10, "id31"], [10, "id67"]], "Contributors": [[10, "contributors"], [10, "id2"], [10, "id6"], [10, "id9"], [10, "id13"]], "v2.4.0 (2022-11-4)": [[10, "v2-4-0-2022-11-4"]], "Features": [[10, "features"], [10, "id15"], [10, "id71"]], "v2.3.3 (2022-10-20)": [[10, "v2-3-3-2022-10-20"]], "v2.3.2 (2022-07-28)": [[10, "v2-3-2-2022-07-28"]], "Fixes": [[10, "fixes"], [10, "id11"], [10, "id16"], [10, "id18"], [10, "id21"], [10, "id29"]], "v2.3.1 (2022-05-02)": [[10, "v2-3-1-2022-05-02"]], "v2.3.0 (2022-03-31)": [[10, "v2-3-0-2022-03-31"]], "v2.2.1 (2022-02-07)": [[10, "v2-2-1-2022-02-07"]], "v2.2.0 (2021-11-30)": [[10, "v2-2-0-2021-11-30"]], "Enhancements / What\u2019s new": [[10, "enhancements-what-s-new"], [10, "id27"]], "v2.1.0 (2021-10-18)": [[10, "v2-1-0-2021-10-18"]], "v2.0.0 (2021-08-05)": [[10, "v2-0-0-2021-08-05"]], "API Breaks": [[10, "api-breaks"]], "Enhancements / What\u2019s New": [[10, "id35"], [10, "id37"], [10, "id46"], [10, "id52"], [10, "id56"], [10, "id59"], [10, "id62"]], "Compatibility / Fixes": [[10, "compatibility-fixes"], [10, "id38"], [10, "id40"], [10, "id42"], [10, "id44"], [10, "id48"], [10, "id50"]], "Docs / Testing": [[10, "docs-testing"], [10, "id54"]], "v1.2.0 (2021-07-09)": [[10, "v1-2-0-2021-07-09"]], "v1.1.6 (2021-04-05)": [[10, "v1-1-6-2021-04-05"]], "v1.1.5 (2020-04-02)": [[10, "v1-1-5-2020-04-02"]], "v1.1.4 (2020-02-26)": [[10, "v1-1-4-2020-02-26"]], "v1.1.3 (2020-02-10)": [[10, "v1-1-3-2020-02-10"]], "v1.1.2 (2020-12-22)": [[10, "v1-1-2-2020-12-22"]], "v1.1.1 (2020-08-19)": [[10, "v1-1-1-2020-08-19"]], "v1.1.0 (2020-08-18)": [[10, "v1-1-0-2020-08-18"]], "Compatibility / fixes": [[10, "id53"], [10, "id57"], [10, "id60"], [10, "id63"]], "v1.0.2 (2020-07-01)": [[10, "v1-0-2-2020-07-01"]], "v1.0.1 (2020-05-20)": [[10, "v1-0-1-2020-05-20"]], "v1.0.0 (2020-05-18)": [[10, "v1-0-0-2020-05-18"]], "v0.7.0 (2020-03-09)": [[10, "v0-7-0-2020-03-09"]], "v0.6.0 (2020-01-09)": [[10, "v0-6-0-2020-01-09"]], "Compatibility": [[10, "compatibility"]], "v0.5.0 (2019-09-18)": [[10, "v0-5-0-2019-09-18"]], "What\u2019s New": [[10, "whats-new"]], "v0.2.1 (2018-09-28)": [[10, "v0-2-1-2018-09-28"]], "Enhancements": [[10, "enhancements"], [10, "id69"]], "Deprecations": [[10, "deprecations"], [10, "id70"]], "v0.2.0 (2018-06-27)": [[10, "v0-2-0-2018-06-27"]], "API Changes": [[10, "api-changes"]], "Dependencies": [[10, "dependencies"]], "Bug Fixes": [[10, "bug-fixes"]], "v0.1.0 (2017-12-15)": [[10, "v0-1-0-2017-12-15"]], "Saving and Loading": [[11, "saving-and-loading"]], "Custom Templates": [[12, "custom-templates"]], "Template Creation": [[12, "template-creation"]], "Template Substitutions": [[12, "template-substitutions"]], "Template Filenames": [[12, "template-filenames"]], "Template Discovery": [[12, "template-discovery"]], "Supported Tools": [[13, "supported-tools"]], "Tool Classes": [[13, "tool-classes"]], "Utility Functions": [[14, "module-typhos.utils"]], "Cache Utilities": [[14, "cache-utilities"]], "Path caching": [[14, "path-caching"]], "Ophyd Object Description Caching": [[14, "ophyd-object-description-caching"]], "Ophyd Object to Widget Type Cache": [[14, "ophyd-object-to-widget-type-cache"]], "Widgets": [[15, "widgets"]], "Determining widget types": [[15, "determining-widget-types"]], "Panels": [[15, "panels"]], "Basic Signal Panels": [[15, "basic-signal-panels"]], "Composite Signal Panels": [[15, "composite-signal-panels"]], "TyphosPositionerWidget": [[15, "typhospositionerwidget"]], "Functions and Methods": [[15, "functions-and-methods"]], "Miscellaneous": [[15, "miscellaneous"]], "Designer": [[15, "designer"]]}, "indexentries": {"module": [[1, "module-typhos.cli"], [14, "module-typhos.utils"]], "typhos.cli": [[1, "module-typhos.cli"]], "default_icon (typhos.display.typhostoolbutton attribute)": [[3, "typhos.display.TyphosToolButton.DEFAULT_ICON"]], "templateenum (typhos.typhosdevicedisplay attribute)": [[3, "typhos.TyphosDeviceDisplay.TemplateEnum"]], "typhosdevicedisplay (class in typhos)": [[3, "typhos.TyphosDeviceDisplay"]], "typhosdisplayconfigbutton (class in typhos.display)": [[3, "typhos.display.TyphosDisplayConfigButton"]], "typhosdisplayswitcher (class in typhos.display)": [[3, "typhos.display.TyphosDisplaySwitcher"]], "typhosdisplayswitcherbutton (class in typhos.display)": [[3, "typhos.display.TyphosDisplaySwitcherButton"]], "typhosdisplaytitle (class in typhos.display)": [[3, "typhos.display.TyphosDisplayTitle"]], "typhossuite (class in typhos)": [[3, "typhos.TyphosSuite"]], "typhostitlelabel (class in typhos.display)": [[3, "typhos.display.TyphosTitleLabel"]], "typhostoolbutton (class in typhos.display)": [[3, "typhos.display.TyphosToolButton"]], "add_device() (typhos.typhosdevicedisplay method)": [[3, "typhos.TyphosDeviceDisplay.add_device"]], "add_device() (typhos.typhossuite method)": [[3, "typhos.TyphosSuite.add_device"]], "add_device() (typhos.display.typhosdisplayswitcher method)": [[3, "typhos.display.TyphosDisplaySwitcher.add_device"]], "add_device() (typhos.display.typhosdisplaytitle method)": [[3, "typhos.display.TyphosDisplayTitle.add_device"]], "add_lazy_subdisplay() (typhos.typhossuite method)": [[3, "typhos.TyphosSuite.add_lazy_subdisplay"]], "add_subdisplay() (typhos.typhossuite method)": [[3, "typhos.TyphosSuite.add_subdisplay"]], "add_tool() (typhos.typhossuite method)": [[3, "typhos.TyphosSuite.add_tool"]], "composite_heuristics (typhos.typhosdevicedisplay attribute)": [[3, "typhos.TyphosDeviceDisplay.composite_heuristics"]], "copy_to_clipboard() (typhos.typhosdevicedisplay method)": [[3, "typhos.TyphosDeviceDisplay.copy_to_clipboard"]], "create_hide_empty_menu() (typhos.display.typhosdisplayconfigbutton method)": [[3, "typhos.display.TyphosDisplayConfigButton.create_hide_empty_menu"]], "create_kind_filter_menu() (typhos.display.typhosdisplayconfigbutton method)": [[3, "typhos.display.TyphosDisplayConfigButton.create_kind_filter_menu"]], "create_name_filter_menu() (typhos.display.typhosdisplayconfigbutton method)": [[3, "typhos.display.TyphosDisplayConfigButton.create_name_filter_menu"]], "current_template (typhos.typhosdevicedisplay property)": [[3, "typhos.TyphosDeviceDisplay.current_template"]], "default_tools (typhos.typhossuite attribute)": [[3, "typhos.TyphosSuite.default_tools"]], "device (typhos.typhosdevicedisplay property)": [[3, "typhos.TyphosDeviceDisplay.device"]], "device_class (typhos.typhosdevicedisplay attribute)": [[3, "typhos.TyphosDeviceDisplay.device_class"]], "device_name (typhos.typhosdevicedisplay attribute)": [[3, "typhos.TyphosDeviceDisplay.device_name"]], "display_type (typhos.typhosdevicedisplay attribute)": [[3, "typhos.TyphosDeviceDisplay.display_type"]], "display_widget (typhos.typhosdevicedisplay property)": [[3, "typhos.TyphosDeviceDisplay.display_widget"]], "embed_subdisplay() (typhos.typhossuite method)": [[3, "typhos.TyphosSuite.embed_subdisplay"]], "force_template (typhos.typhosdevicedisplay attribute)": [[3, "typhos.TyphosDeviceDisplay.force_template"]], "from_class() (typhos.typhosdevicedisplay class method)": [[3, "typhos.TyphosDeviceDisplay.from_class"]], "from_device() (typhos.typhosdevicedisplay class method)": [[3, "typhos.TyphosDeviceDisplay.from_device"]], "from_device() (typhos.typhossuite class method)": [[3, "typhos.TyphosSuite.from_device"]], "from_devices() (typhos.typhossuite class method)": [[3, "typhos.TyphosSuite.from_devices"]], "generate_context_menu() (typhos.display.typhosdisplayconfigbutton method)": [[3, "typhos.display.TyphosDisplayConfigButton.generate_context_menu"]], "generate_context_menu() (typhos.display.typhosdisplayswitcherbutton method)": [[3, "typhos.display.TyphosDisplaySwitcherButton.generate_context_menu"]], "generate_context_menu() (typhos.display.typhostoolbutton method)": [[3, "typhos.display.TyphosToolButton.generate_context_menu"]], "get_best_template() (typhos.typhosdevicedisplay method)": [[3, "typhos.TyphosDeviceDisplay.get_best_template"]], "get_icon() (typhos.display.typhostoolbutton class method)": [[3, "typhos.display.TyphosToolButton.get_icon"]], "get_subdisplay() (typhos.typhossuite method)": [[3, "typhos.TyphosSuite.get_subdisplay"]], "hideempty (typhos.typhosdevicedisplay attribute)": [[3, "typhos.TyphosDeviceDisplay.hideEmpty"]], "hide_empty() (in module typhos.display)": [[3, "typhos.display.hide_empty"]], "hide_empty() (typhos.display.typhosdisplayconfigbutton method)": [[3, "typhos.display.TyphosDisplayConfigButton.hide_empty"]], "hide_subdisplay() (typhos.typhossuite method)": [[3, "typhos.TyphosSuite.hide_subdisplay"]], "hide_subdisplays() (typhos.typhossuite method)": [[3, "typhos.TyphosSuite.hide_subdisplays"]], "load_best_template() (typhos.typhosdevicedisplay method)": [[3, "typhos.TyphosDeviceDisplay.load_best_template"]], "macros (typhos.typhosdevicedisplay property)": [[3, "typhos.TyphosDeviceDisplay.macros"]], "mousepressevent() (typhos.display.typhostitlelabel method)": [[3, "typhos.display.TyphosTitleLabel.mousePressEvent"]], "normalize_display_type() (in module typhos.display)": [[3, "typhos.display.normalize_display_type"]], "open_context_menu() (typhos.display.typhostoolbutton method)": [[3, "typhos.display.TyphosToolButton.open_context_menu"]], "pop_out_help() (typhos.display.typhosdisplaytitle method)": [[3, "typhos.display.TyphosDisplayTitle.pop_out_help"]], "save() (typhos.typhossuite method)": [[3, "typhos.TyphosSuite.save"]], "scroll_option (typhos.typhosdevicedisplay attribute)": [[3, "typhos.TyphosDeviceDisplay.scroll_option"]], "search_for_templates() (typhos.typhosdevicedisplay method)": [[3, "typhos.TyphosDeviceDisplay.search_for_templates"]], "set_device_display() (typhos.display.typhosdisplayconfigbutton method)": [[3, "typhos.display.TyphosDisplayConfigButton.set_device_display"]], "set_device_display() (typhos.display.typhosdisplayswitcher method)": [[3, "typhos.display.TyphosDisplaySwitcher.set_device_display"]], "set_device_display() (typhos.display.typhosdisplaytitle method)": [[3, "typhos.display.TyphosDisplayTitle.set_device_display"]], "show_empty() (in module typhos.display)": [[3, "typhos.display.show_empty"]], "show_subdisplay() (typhos.typhossuite method)": [[3, "typhos.TyphosSuite.show_subdisplay"]], "show_switcher (typhos.display.typhosdisplaytitle attribute)": [[3, "typhos.display.TyphosDisplayTitle.show_switcher"]], "show_underline (typhos.display.typhosdisplaytitle attribute)": [[3, "typhos.display.TyphosDisplayTitle.show_underline"]], "suggest_composite_screen() (typhos.typhosdevicedisplay class method)": [[3, "typhos.TyphosDeviceDisplay.suggest_composite_screen"]], "to_image() (typhos.typhosdevicedisplay method)": [[3, "typhos.TyphosDeviceDisplay.to_image"]], "toggle_display() (in module typhos.display)": [[3, "typhos.display.toggle_display"]], "toggle_help() (typhos.display.typhosdisplaytitle method)": [[3, "typhos.display.TyphosDisplayTitle.toggle_help"]], "toggle_requested (typhos.display.typhostitlelabel attribute)": [[3, "typhos.display.TyphosTitleLabel.toggle_requested"]], "tools (typhos.typhossuite property)": [[3, "typhos.TyphosSuite.tools"]], "top_level_groups (typhos.typhossuite property)": [[3, "typhos.TyphosSuite.top_level_groups"]], "typhosconsole (class in typhos.tools)": [[4, "typhos.tools.TyphosConsole"]], "__init__() (typhos.tools.typhosconsole method)": [[4, "typhos.tools.TyphosConsole.__init__"]], "typhoslogdisplay (class in typhos.tools)": [[5, "typhos.tools.TyphosLogDisplay"]], "__init__() (typhos.tools.typhoslogdisplay method)": [[5, "typhos.tools.TyphosLogDisplay.__init__"]], "typhostimeplot (class in typhos.tools)": [[6, "typhos.tools.TyphosTimePlot"]], "__init__() (typhos.tools.typhostimeplot method)": [[6, "typhos.tools.TyphosTimePlot.__init__"]], "happiconnection (class in typhos.plugins)": [[8, "typhos.plugins.HappiConnection"]], "happiplugin (class in typhos.plugins)": [[8, "typhos.plugins.HappiPlugin"]], "signalconnection (class in typhos.plugins)": [[8, "typhos.plugins.SignalConnection"]], "signalplugin (class in typhos.plugins)": [[8, "typhos.plugins.SignalPlugin"]], "add_listener() (typhos.plugins.happiconnection method)": [[8, "typhos.plugins.HappiConnection.add_listener"]], "add_listener() (typhos.plugins.signalconnection method)": [[8, "typhos.plugins.SignalConnection.add_listener"]], "cast() (typhos.plugins.signalconnection method)": [[8, "typhos.plugins.SignalConnection.cast"]], "close() (typhos.plugins.signalconnection method)": [[8, "typhos.plugins.SignalConnection.close"]], "put_value() (typhos.plugins.signalconnection method)": [[8, "typhos.plugins.SignalConnection.put_value"]], "register_client() (in module typhos.plugins)": [[8, "typhos.plugins.register_client"]], "register_signal() (in module typhos.plugins)": [[8, "typhos.plugins.register_signal"]], "remove_listener() (typhos.plugins.happiconnection method)": [[8, "typhos.plugins.HappiConnection.remove_listener"]], "remove_listener() (typhos.plugins.signalconnection method)": [[8, "typhos.plugins.SignalConnection.remove_listener"]], "send_new_meta() (typhos.plugins.signalconnection method)": [[8, "typhos.plugins.SignalConnection.send_new_meta"]], "send_new_value() (typhos.plugins.signalconnection method)": [[8, "typhos.plugins.SignalConnection.send_new_value"]], "signal (typhos.plugins.signalconnection attribute)": [[8, "typhos.plugins.SignalConnection.signal"]], "deviceconnectionmonitorthread (class in typhos.utils)": [[14, "typhos.utils.DeviceConnectionMonitorThread"]], "grabkinditem (class in typhos.utils)": [[14, "typhos.utils.GrabKindItem"]], "loading_timeout_ms (typhos.utils.typhosloading attribute)": [[14, "typhos.utils.TyphosLoading.LOADING_TIMEOUT_MS"]], "objectconnectionmonitorthread (class in typhos.utils)": [[14, "typhos.utils.ObjectConnectionMonitorThread"]], "threadpoolworker (class in typhos.utils)": [[14, "typhos.utils.ThreadPoolWorker"]], "typhosbase (class in typhos.utils)": [[14, "typhos.utils.TyphosBase"]], "typhosloading (class in typhos.utils)": [[14, "typhos.utils.TyphosLoading"]], "weakpartialmethodslot (class in typhos.utils)": [[14, "typhos.utils.WeakPartialMethodSlot"]], "_globaldescribecache (class in typhos.cache)": [[14, "typhos.cache._GlobalDescribeCache"]], "_globaldisplaypathcache (class in typhos.cache)": [[14, "typhos.cache._GlobalDisplayPathCache"]], "_globalwidgettypecache (class in typhos.cache)": [[14, "typhos.cache._GlobalWidgetTypeCache"]], "add_path() (typhos.cache._globaldisplaypathcache method)": [[14, "typhos.cache._GlobalDisplayPathCache.add_path"]], "apply_standard_stylesheets() (in module typhos.utils)": [[14, "typhos.utils.apply_standard_stylesheets"]], "attr (typhos.utils.grabkinditem attribute)": [[14, "typhos.utils.GrabKindItem.attr"]], "cache (typhos.cache._globaldescribecache attribute)": [[14, "typhos.cache._GlobalDescribeCache.cache"]], "cache (typhos.cache._globalwidgettypecache attribute)": [[14, "typhos.cache._GlobalWidgetTypeCache.cache"]], "channel_from_signal() (in module typhos.utils)": [[14, "typhos.utils.channel_from_signal"]], "channel_name() (in module typhos.utils)": [[14, "typhos.utils.channel_name"]], "clean_attr() (in module typhos.utils)": [[14, "typhos.utils.clean_attr"]], "clean_name() (in module typhos.utils)": [[14, "typhos.utils.clean_name"]], "clear() (typhos.cache._globaldescribecache method)": [[14, "typhos.cache._GlobalDescribeCache.clear"]], "clear() (typhos.cache._globalwidgettypecache method)": [[14, "typhos.cache._GlobalWidgetTypeCache.clear"]], "clear_layout() (in module typhos.utils)": [[14, "typhos.utils.clear_layout"]], "code_from_device() (in module typhos.utils)": [[14, "typhos.utils.code_from_device"]], "code_from_device_repr() (in module typhos.utils)": [[14, "typhos.utils.code_from_device_repr"]], "component (typhos.utils.grabkinditem attribute)": [[14, "typhos.utils.GrabKindItem.component"]], "compose_stylesheets() (in module typhos.utils)": [[14, "typhos.utils.compose_stylesheets"]], "connect_thread (typhos.cache._globaldescribecache attribute)": [[14, "typhos.cache._GlobalDescribeCache.connect_thread"]], "connection_status_monitor() (in module typhos.utils)": [[14, "typhos.utils.connection_status_monitor"]], "connection_update (typhos.utils.deviceconnectionmonitorthread attribute)": [[14, "typhos.utils.DeviceConnectionMonitorThread.connection_update"]], "connection_update (typhos.utils.objectconnectionmonitorthread attribute)": [[14, "typhos.utils.ObjectConnectionMonitorThread.connection_update"]], "contextmenuevent() (typhos.utils.typhosloading method)": [[14, "typhos.utils.TyphosLoading.contextMenuEvent"]], "describe_cache (typhos.cache._globalwidgettypecache attribute)": [[14, "typhos.cache._GlobalWidgetTypeCache.describe_cache"]], "dump_grid_layout() (in module typhos.utils)": [[14, "typhos.utils.dump_grid_layout"]], "find_file_in_paths() (in module typhos.utils)": [[14, "typhos.utils.find_file_in_paths"]], "find_parent_with_class() (in module typhos.utils)": [[14, "typhos.utils.find_parent_with_class"]], "find_templates_for_class() (in module typhos.utils)": [[14, "typhos.utils.find_templates_for_class"]], "flatten_tree() (in module typhos.utils)": [[14, "typhos.utils.flatten_tree"]], "get() (typhos.cache._globaldescribecache method)": [[14, "typhos.cache._GlobalDescribeCache.get"]], "get() (typhos.cache._globalwidgettypecache method)": [[14, "typhos.cache._GlobalWidgetTypeCache.get"]], "get_all_signals_from_device() (in module typhos.utils)": [[14, "typhos.utils.get_all_signals_from_device"]], "get_component() (in module typhos.utils)": [[14, "typhos.utils.get_component"]], "get_device_from_fake_class() (in module typhos.utils)": [[14, "typhos.utils.get_device_from_fake_class"]], "get_global_describe_cache() (in module typhos.cache)": [[14, "typhos.cache.get_global_describe_cache"]], "get_global_display_path_cache() (in module typhos.cache)": [[14, "typhos.cache.get_global_display_path_cache"]], "get_global_widget_type_cache() (in module typhos.cache)": [[14, "typhos.cache.get_global_widget_type_cache"]], "get_variety_metadata() (in module typhos.utils)": [[14, "typhos.utils.get_variety_metadata"]], "is_fake_device_class() (in module typhos.utils)": [[14, "typhos.utils.is_fake_device_class"]], "is_signal_ro() (in module typhos.utils)": [[14, "typhos.utils.is_signal_ro"]], "is_standard_template() (in module typhos.utils)": [[14, "typhos.utils.is_standard_template"]], "link_signal_to_widget() (in module typhos.utils)": [[14, "typhos.utils.link_signal_to_widget"]], "linked_attribute() (in module typhos.utils)": [[14, "typhos.utils.linked_attribute"]], "load_suite() (in module typhos.utils)": [[14, "typhos.utils.load_suite"]], "make_identifier() (in module typhos.utils)": [[14, "typhos.utils.make_identifier"]], "no_device_lazy_load() (in module typhos.utils)": [[14, "typhos.utils.no_device_lazy_load"]], "nullcontext() (in module typhos.utils)": [[14, "typhos.utils.nullcontext"]], "patch_connect_slots() (in module typhos.utils)": [[14, "typhos.utils.patch_connect_slots"]], "pyqt_class_from_enum() (in module typhos.utils)": [[14, "typhos.utils.pyqt_class_from_enum"]], "raise_window() (in module typhos.utils)": [[14, "typhos.utils.raise_window"]], "random_color() (in module typhos.utils)": [[14, "typhos.utils.random_color"]], "reload_widget_stylesheet() (in module typhos.utils)": [[14, "typhos.utils.reload_widget_stylesheet"]], "remove_duplicate_items() (in module typhos.utils)": [[14, "typhos.utils.remove_duplicate_items"]], "run() (typhos.utils.deviceconnectionmonitorthread method)": [[14, "typhos.utils.DeviceConnectionMonitorThread.run"]], "run() (typhos.utils.objectconnectionmonitorthread method)": [[14, "typhos.utils.ObjectConnectionMonitorThread.run"]], "run() (typhos.utils.threadpoolworker method)": [[14, "typhos.utils.ThreadPoolWorker.run"]], "save_suite() (in module typhos.utils)": [[14, "typhos.utils.save_suite"]], "signal (typhos.utils.grabkinditem attribute)": [[14, "typhos.utils.GrabKindItem.signal"]], "stop() (typhos.utils.deviceconnectionmonitorthread method)": [[14, "typhos.utils.DeviceConnectionMonitorThread.stop"]], "stop() (typhos.utils.objectconnectionmonitorthread method)": [[14, "typhos.utils.ObjectConnectionMonitorThread.stop"]], "subscription_context() (in module typhos.utils)": [[14, "typhos.utils.subscription_context"]], "subscription_context_device() (in module typhos.utils)": [[14, "typhos.utils.subscription_context_device"]], "typhos.utils": [[14, "module-typhos.utils"]], "update() (typhos.cache._globaldisplaypathcache method)": [[14, "typhos.cache._GlobalDisplayPathCache.update"]], "use_stylesheet() (in module typhos.utils)": [[14, "typhos.utils.use_stylesheet"]], "widget_to_image() (in module typhos.utils)": [[14, "typhos.utils.widget_to_image"]], "col_label (typhos.panel.compositesignalpanel attribute)": [[15, "typhos.panel.CompositeSignalPanel.COL_LABEL"]], "col_label (typhos.panel.signalpanel attribute)": [[15, "typhos.panel.SignalPanel.COL_LABEL"]], "col_readback (typhos.panel.compositesignalpanel attribute)": [[15, "typhos.panel.CompositeSignalPanel.COL_READBACK"]], "col_readback (typhos.panel.signalpanel attribute)": [[15, "typhos.panel.SignalPanel.COL_READBACK"]], "col_setpoint (typhos.panel.compositesignalpanel attribute)": [[15, "typhos.panel.CompositeSignalPanel.COL_SETPOINT"]], "col_setpoint (typhos.panel.signalpanel attribute)": [[15, "typhos.panel.SignalPanel.COL_SETPOINT"]], "clickablebitindicator (class in typhos.widgets)": [[15, "typhos.widgets.ClickableBitIndicator"]], "compositesignalpanel (class in typhos.panel)": [[15, "typhos.panel.CompositeSignalPanel"]], "functionpanel (class in typhos.func)": [[15, "typhos.func.FunctionPanel"]], "imagedialogbutton (class in typhos.widgets)": [[15, "typhos.widgets.ImageDialogButton"]], "num_cols (typhos.panel.compositesignalpanel attribute)": [[15, "typhos.panel.CompositeSignalPanel.NUM_COLS"]], "num_cols (typhos.panel.signalpanel attribute)": [[15, "typhos.panel.SignalPanel.NUM_COLS"]], "signaldialogbutton (class in typhos.widgets)": [[15, "typhos.widgets.SignalDialogButton"]], "signalpanel (class in typhos.panel)": [[15, "typhos.panel.SignalPanel"]], "signalwidgetinfo (class in typhos.widgets)": [[15, "typhos.widgets.SignalWidgetInfo"]], "subdisplay (class in typhos.widgets)": [[15, "typhos.widgets.SubDisplay"]], "typhosarraytable (class in typhos.widgets)": [[15, "typhos.widgets.TyphosArrayTable"]], "typhosbyteindicator (class in typhos.widgets)": [[15, "typhos.widgets.TyphosByteIndicator"]], "typhosbytesetpoint (class in typhos.widgets)": [[15, "typhos.widgets.TyphosByteSetpoint"]], "typhoscombobox (class in typhos.widgets)": [[15, "typhos.widgets.TyphosComboBox"]], "typhoscommandbutton (class in typhos.widgets)": [[15, "typhos.widgets.TyphosCommandButton"]], "typhoscommandenumbutton (class in typhos.widgets)": [[15, "typhos.widgets.TyphosCommandEnumButton"]], "typhoscompositesignalpanel (class in typhos)": [[15, "typhos.TyphosCompositeSignalPanel"]], "typhosdesignermixin (class in typhos.widgets)": [[15, "typhos.widgets.TyphosDesignerMixin"]], "typhoslabel (class in typhos.widgets)": [[15, "typhos.widgets.TyphosLabel"]], "typhoslineedit (class in typhos.widgets)": [[15, "typhos.widgets.TyphosLineEdit"]], "typhosmethodbutton (class in typhos)": [[15, "typhos.TyphosMethodButton"]], "typhospositionerwidget (class in typhos)": [[15, "typhos.TyphosPositionerWidget"]], "typhospositionerwidget.kindlevel (class in typhos)": [[15, "typhos.TyphosPositionerWidget.KindLevel"]], "typhosscalarrange (class in typhos.widgets)": [[15, "typhos.widgets.TyphosScalarRange"]], "typhossidebaritem (class in typhos.widgets)": [[15, "typhos.widgets.TyphosSidebarItem"]], "typhossignalpanel (class in typhos)": [[15, "typhos.TyphosSignalPanel"]], "typhossignalpanel.signalorder (class in typhos)": [[15, "typhos.TyphosSignalPanel.SignalOrder"]], "typhostextedit (class in typhos.textedit)": [[15, "typhos.textedit.TyphosTextEdit"]], "typhostweakable (class in typhos.tweakable)": [[15, "typhos.tweakable.TyphosTweakable"]], "waveformdialogbutton (class in typhos.widgets)": [[15, "typhos.widgets.WaveformDialogButton"]], "acceleration_attribute (typhos.typhospositionerwidget attribute)": [[15, "typhos.TyphosPositionerWidget.acceleration_attribute"]], "add_device() (typhos.typhosmethodbutton method)": [[15, "typhos.TyphosMethodButton.add_device"]], "add_device() (typhos.typhospositionerwidget method)": [[15, "typhos.TyphosPositionerWidget.add_device"]], "add_device() (typhos.typhossignalpanel method)": [[15, "typhos.TyphosSignalPanel.add_device"]], "add_device() (typhos.panel.compositesignalpanel method)": [[15, "typhos.panel.CompositeSignalPanel.add_device"]], "add_device() (typhos.panel.signalpanel method)": [[15, "typhos.panel.SignalPanel.add_device"]], "add_method() (typhos.func.functionpanel method)": [[15, "typhos.func.FunctionPanel.add_method"]], "add_pv() (typhos.panel.signalpanel method)": [[15, "typhos.panel.SignalPanel.add_pv"]], "add_row() (typhos.panel.signalpanel method)": [[15, "typhos.panel.SignalPanel.add_row"]], "add_signal() (typhos.panel.signalpanel method)": [[15, "typhos.panel.SignalPanel.add_signal"]], "add_sub_device() (typhos.panel.compositesignalpanel method)": [[15, "typhos.panel.CompositeSignalPanel.add_sub_device"]], "alarmsensitiveborder (typhos.widgets.typhosdesignermixin attribute)": [[15, "typhos.widgets.TyphosDesignerMixin.alarmSensitiveBorder"]], "alarmsensitivecontent (typhos.widgets.typhosdesignermixin attribute)": [[15, "typhos.widgets.TyphosDesignerMixin.alarmSensitiveContent"]], "channel (typhos.widgets.typhosdesignermixin attribute)": [[15, "typhos.widgets.TyphosDesignerMixin.channel"]], "clear() (typhos.panel.signalpanel method)": [[15, "typhos.panel.SignalPanel.clear"]], "clear_error() (typhos.typhospositionerwidget method)": [[15, "typhos.TyphosPositionerWidget.clear_error"]], "closeevent() (typhos.widgets.subdisplay method)": [[15, "typhos.widgets.SubDisplay.closeEvent"]], "connection_changed() (typhos.widgets.typhosscalarrange method)": [[15, "typhos.widgets.TyphosScalarRange.connection_changed"]], "create_signal_widget() (in module typhos.widgets)": [[15, "typhos.widgets.create_signal_widget"]], "delta_signal (typhos.widgets.typhosscalarrange property)": [[15, "typhos.widgets.TyphosScalarRange.delta_signal"]], "delta_value (typhos.widgets.typhosscalarrange attribute)": [[15, "typhos.widgets.TyphosScalarRange.delta_value"]], "determine_widget_type() (in module typhos.widgets)": [[15, "typhos.widgets.determine_widget_type"]], "device (typhos.typhospositionerwidget property)": [[15, "typhos.TyphosPositionerWidget.device"]], "embed_requested() (typhos.widgets.typhossidebaritem method)": [[15, "typhos.widgets.TyphosSidebarItem.embed_requested"]], "enum_strings_changed() (typhos.widgets.typhoscombobox method)": [[15, "typhos.widgets.TyphosComboBox.enum_strings_changed"]], "enum_strings_changed() (typhos.widgets.typhoscommandbutton method)": [[15, "typhos.widgets.TyphosCommandButton.enum_strings_changed"]], "enum_strings_changed() (typhos.widgets.typhoscommandenumbutton method)": [[15, "typhos.widgets.TyphosCommandEnumButton.enum_strings_changed"]], "enum_strings_changed() (typhos.widgets.typhoslabel method)": [[15, "typhos.widgets.TyphosLabel.enum_strings_changed"]], "error_message_attribute (typhos.typhospositionerwidget attribute)": [[15, "typhos.TyphosPositionerWidget.error_message_attribute"]], "execute() (typhos.typhosmethodbutton method)": [[15, "typhos.TyphosMethodButton.execute"]], "failed_move (typhos.typhospositionerwidget attribute)": [[15, "typhos.TyphosPositionerWidget.failed_move"]], "filter_settings (typhos.typhossignalpanel property)": [[15, "typhos.TyphosSignalPanel.filter_settings"]], "filter_signals() (typhos.panel.signalpanel method)": [[15, "typhos.panel.SignalPanel.filter_signals"]], "from_device() (typhos.typhosmethodbutton class method)": [[15, "typhos.TyphosMethodButton.from_device"]], "from_signal() (typhos.widgets.signalwidgetinfo class method)": [[15, "typhos.widgets.SignalWidgetInfo.from_signal"]], "generate_context_menu() (typhos.typhossignalpanel method)": [[15, "typhos.TyphosSignalPanel.generate_context_menu"]], "hide_requested() (typhos.widgets.typhossidebaritem method)": [[15, "typhos.widgets.TyphosSidebarItem.hide_requested"]], "high_limit_switch_attribute (typhos.typhospositionerwidget attribute)": [[15, "typhos.TyphosPositionerWidget.high_limit_switch_attribute"]], "high_limit_travel_attribute (typhos.typhospositionerwidget attribute)": [[15, "typhos.TyphosPositionerWidget.high_limit_travel_attribute"]], "icon (typhos.widgets.signaldialogbutton attribute)": [[15, "typhos.widgets.SignalDialogButton.icon"]], "label_text_from_attribute() (typhos.panel.compositesignalpanel method)": [[15, "typhos.panel.CompositeSignalPanel.label_text_from_attribute"]], "label_text_from_attribute() (typhos.panel.signalpanel method)": [[15, "typhos.panel.SignalPanel.label_text_from_attribute"]], "loading_complete (typhos.panel.compositesignalpanel attribute)": [[15, "typhos.panel.CompositeSignalPanel.loading_complete"]], "loading_complete (typhos.panel.signalpanel attribute)": [[15, "typhos.panel.SignalPanel.loading_complete"]], "low_limit_switch_attribute (typhos.typhospositionerwidget attribute)": [[15, "typhos.TyphosPositionerWidget.low_limit_switch_attribute"]], "low_limit_travel_attribute (typhos.typhospositionerwidget attribute)": [[15, "typhos.TyphosPositionerWidget.low_limit_travel_attribute"]], "method_name (typhos.typhosmethodbutton attribute)": [[15, "typhos.TyphosMethodButton.method_name"]], "mousepressevent() (typhos.widgets.clickablebitindicator method)": [[15, "typhos.widgets.ClickableBitIndicator.mousePressEvent"]], "move_changed() (typhos.typhospositionerwidget method)": [[15, "typhos.TyphosPositionerWidget.move_changed"]], "moving (typhos.typhospositionerwidget attribute)": [[15, "typhos.TyphosPositionerWidget.moving"]], "moving_attribute (typhos.typhospositionerwidget attribute)": [[15, "typhos.TyphosPositionerWidget.moving_attribute"]], "namefilter (typhos.typhossignalpanel attribute)": [[15, "typhos.TyphosSignalPanel.nameFilter"]], "negative_tweak() (typhos.typhospositionerwidget method)": [[15, "typhos.TyphosPositionerWidget.negative_tweak"]], "negative_tweak() (typhos.tweakable.typhostweakable method)": [[15, "typhos.tweakable.TyphosTweakable.negative_tweak"]], "numbits (typhos.widgets.typhosbytesetpoint attribute)": [[15, "typhos.widgets.TyphosByteSetpoint.numBits"]], "open_context_menu() (typhos.typhossignalpanel method)": [[15, "typhos.TyphosSignalPanel.open_context_menu"]], "open_requested() (typhos.widgets.typhossidebaritem method)": [[15, "typhos.widgets.TyphosSidebarItem.open_requested"]], "parent_widget_class (typhos.widgets.imagedialogbutton attribute)": [[15, "typhos.widgets.ImageDialogButton.parent_widget_class"]], "parent_widget_class (typhos.widgets.signaldialogbutton attribute)": [[15, "typhos.widgets.SignalDialogButton.parent_widget_class"]], "parent_widget_class (typhos.widgets.waveformdialogbutton attribute)": [[15, "typhos.widgets.WaveformDialogButton.parent_widget_class"]], "positive_tweak() (typhos.typhospositionerwidget method)": [[15, "typhos.TyphosPositionerWidget.positive_tweak"]], "positive_tweak() (typhos.tweakable.typhostweakable method)": [[15, "typhos.tweakable.TyphosTweakable.positive_tweak"]], "readback_attribute (typhos.typhospositionerwidget attribute)": [[15, "typhos.TyphosPositionerWidget.readback_attribute"]], "row_count (typhos.panel.signalpanel property)": [[15, "typhos.panel.SignalPanel.row_count"]], "send_value() (typhos.textedit.typhostextedit method)": [[15, "typhos.textedit.TyphosTextEdit.send_value"]], "send_value() (typhos.widgets.typhoslineedit method)": [[15, "typhos.widgets.TyphosLineEdit.send_value"]], "set() (typhos.typhospositionerwidget method)": [[15, "typhos.TyphosPositionerWidget.set"]], "set_device_display() (typhos.typhossignalpanel method)": [[15, "typhos.TyphosSignalPanel.set_device_display"]], "set_display() (typhos.textedit.typhostextedit method)": [[15, "typhos.textedit.TyphosTextEdit.set_display"]], "setpointhistorycount (typhos.widgets.typhoslineedit attribute)": [[15, "typhos.widgets.TyphosLineEdit.setpointHistoryCount"]], "setpoint_attribute (typhos.typhospositionerwidget attribute)": [[15, "typhos.TyphosPositionerWidget.setpoint_attribute"]], "setpoint_history (typhos.widgets.typhoslineedit property)": [[15, "typhos.widgets.TyphosLineEdit.setpoint_history"]], "showconfig (typhos.typhossignalpanel attribute)": [[15, "typhos.TyphosSignalPanel.showConfig"]], "showhints (typhos.typhossignalpanel attribute)": [[15, "typhos.TyphosSignalPanel.showHints"]], "shownormal (typhos.typhossignalpanel attribute)": [[15, "typhos.TyphosSignalPanel.showNormal"]], "showomitted (typhos.typhossignalpanel attribute)": [[15, "typhos.TyphosSignalPanel.showOmitted"]], "show_dialog() (typhos.widgets.signaldialogbutton method)": [[15, "typhos.widgets.SignalDialogButton.show_dialog"]], "show_expert_button (typhos.typhospositionerwidget attribute)": [[15, "typhos.TyphosPositionerWidget.show_expert_button"]], "show_kinds (typhos.typhossignalpanel property)": [[15, "typhos.TyphosSignalPanel.show_kinds"]], "signals (typhos.panel.signalpanel property)": [[15, "typhos.panel.SignalPanel.signals"]], "sortby (typhos.typhossignalpanel attribute)": [[15, "typhos.TyphosSignalPanel.sortBy"]], "stop() (typhos.typhospositionerwidget method)": [[15, "typhos.TyphosPositionerWidget.stop"]], "successful_move (typhos.typhospositionerwidget attribute)": [[15, "typhos.TyphosPositionerWidget.successful_move"]], "text (typhos.widgets.signaldialogbutton attribute)": [[15, "typhos.widgets.SignalDialogButton.text"]], "treewidgetchanged() (typhos.widgets.typhossidebaritem method)": [[15, "typhos.widgets.TyphosSidebarItem.treeWidgetChanged"]], "tweak() (typhos.typhospositionerwidget method)": [[15, "typhos.TyphosPositionerWidget.tweak"]], "tweak() (typhos.tweakable.typhostweakable method)": [[15, "typhos.tweakable.TyphosTweakable.tweak"]], "unit_changed() (typhos.widgets.typhoslabel method)": [[15, "typhos.widgets.TyphosLabel.unit_changed"]], "unit_changed() (typhos.widgets.typhoslineedit method)": [[15, "typhos.widgets.TyphosLineEdit.unit_changed"]], "update_alarm_text() (typhos.typhospositionerwidget method)": [[15, "typhos.TyphosPositionerWidget.update_alarm_text"]], "use_status (typhos.typhosmethodbutton attribute)": [[15, "typhos.TyphosMethodButton.use_status"]], "value_changed() (typhos.textedit.typhostextedit method)": [[15, "typhos.textedit.TyphosTextEdit.value_changed"]], "value_changed() (typhos.widgets.typhosarraytable method)": [[15, "typhos.widgets.TyphosArrayTable.value_changed"]], "value_changed() (typhos.widgets.typhosbytesetpoint method)": [[15, "typhos.widgets.TyphosByteSetpoint.value_changed"]], "variety_metadata (typhos.textedit.typhostextedit property)": [[15, "typhos.textedit.TyphosTextEdit.variety_metadata"]], "variety_metadata (typhos.tweakable.typhostweakable property)": [[15, "typhos.tweakable.TyphosTweakable.variety_metadata"]], "variety_metadata (typhos.widgets.typhosarraytable property)": [[15, "typhos.widgets.TyphosArrayTable.variety_metadata"]], "variety_metadata (typhos.widgets.typhosbyteindicator property)": [[15, "typhos.widgets.TyphosByteIndicator.variety_metadata"]], "variety_metadata (typhos.widgets.typhoscommandbutton property)": [[15, "typhos.widgets.TyphosCommandButton.variety_metadata"]], "variety_metadata (typhos.widgets.typhoscommandenumbutton property)": [[15, "typhos.widgets.TyphosCommandEnumButton.variety_metadata"]], "variety_metadata (typhos.widgets.typhosscalarrange property)": [[15, "typhos.widgets.TyphosScalarRange.variety_metadata"]], "velocity_attribute (typhos.typhospositionerwidget attribute)": [[15, "typhos.TyphosPositionerWidget.velocity_attribute"]], "visible_elements (typhos.panel.compositesignalpanel property)": [[15, "typhos.panel.CompositeSignalPanel.visible_elements"]], "visible_elements (typhos.panel.signalpanel property)": [[15, "typhos.panel.SignalPanel.visible_elements"]], "visible_signals (typhos.panel.signalpanel property)": [[15, "typhos.panel.SignalPanel.visible_signals"]], "wheelevent() (typhos.widgets.typhoscombobox method)": [[15, "typhos.widgets.TyphosComboBox.wheelEvent"]], "widget() (typhos.widgets.imagedialogbutton method)": [[15, "typhos.widgets.ImageDialogButton.widget"]], "widget() (typhos.widgets.signaldialogbutton method)": [[15, "typhos.widgets.SignalDialogButton.widget"]], "widget() (typhos.widgets.waveformdialogbutton method)": [[15, "typhos.widgets.WaveformDialogButton.widget"]], "widget_ctx_menu() (typhos.widgets.typhoslineedit method)": [[15, "typhos.widgets.TyphosLineEdit.widget_ctx_menu"]], "widget_type_from_description() (in module typhos.widgets)": [[15, "typhos.widgets.widget_type_from_description"]], "write_access_changed() (typhos.textedit.typhostextedit method)": [[15, "typhos.textedit.TyphosTextEdit.write_access_changed"]]}}) \ No newline at end of file diff --git a/v2.4.1/templates.html b/v2.4.1/templates.html new file mode 100644 index 000000000..5541a8796 --- /dev/null +++ b/v2.4.1/templates.html @@ -0,0 +1,201 @@ + + + + + + + Custom Templates — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Custom Templates

+

Typhos ships with a handful of built-in templates. You can see these when you +browse the typhos/ui/core and typhos/ui/devices directories.

+
+

Note

+

This repo originally had a large number of LCLS-specific device templates. +These have been moved to pcdsdevices.ui.

+
+

You can define your own templates outside of typhos to customize the behavior +of the module when launching screens. These can be done generically, to +replace the default templates, or per-class, to replace the templates in +specific cases.

+
+

Template Creation

+

Templates are .ui files created in qt designer. These are largely just +normal pydm displays, with extra macro substitutions. See the +pydm tutorial +for more guidance on using the designer.

+
+

Template Substitutions

+

All the information found in happi will be loaded as a pydm macro into the +template. It does this by checking for attributes on the device.md +namespace object.

+

If no device.md object is found, we will still include device.name +as the name macro and device.prefix as the prefix macro.

+

The upshot of this is that you can include ${name}, ${prefix}, and +other keys from the happi database in your template and they will be +filled in from the device database on load.

+
+
+

Template Filenames

+

To replace a default template, create a template with exactly the same name.

+

To create a template for a class, name it based on the class name +and the template type, e.g.:

+
    +
  • PositionerBase.embedded.ui

  • +
  • PositionerBase.detailed.ui

  • +
  • PositionerBase.engineering.ui

  • +
+

Note that we’ll check an object class’s mro() when deciding which template to +use- this is why all PositionerBase subclasses use the built-in +PositionerBase.detailed.ui template by default.

+

In this way you can create one template for a set of related classes.

+
+
+

Template Discovery

+

There are currently three places that typhos checks for templates. +In order of priority:

+
    +
  1. Check the paths defined by the PYDM_DISPLAYS_PATH environment variable.

  2. +
  3. Check any paths defined by the typhos.ui package entry point.

  4. +
  5. Check the built-in paths (core and devices)

  6. +
+

With that in mind, there are two recommended workflows for running typhos with +custom templates:

+
    +
  1. Create a repository to store your screens, and set PYDM_DISPLAYS_PATH +to point to your repository clone’s screens directory. This path works +exactly like any other PATH variable in linux.

  2. +
  3. Create a module that defines the typhos.ui entry point. This entry +point is expecting to find a str, pathlib.Path, or list of +such objects at your entry point. One such example of how to do this can +be found here

  4. +
+
+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/tools.html b/v2.4.1/tools.html new file mode 100644 index 000000000..cfde05996 --- /dev/null +++ b/v2.4.1/tools.html @@ -0,0 +1,166 @@ + + + + + + + Supported Tools — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Supported Tools

+

In experimental environments there are a variety of external tools and +applications that are critical to day to day operation. Typhos hopes to +integrate many of these services into the TyphosDeviceDisplay for +ease of operation. This approach has two advantages; the first is that getting +to helpful tools requires fewer clicks and therefore less time, secondly, if we +assume that the context in which they want to use the external tool includes +this device, we can pre-populate many of the fields for them.

+

All of the tools in typhos follow a basic pattern. Each one can be +instantiated as a standalone widget with no ophyd or Device required. The +intention is that these tools could be used in a separate application where the +underlying information is in a different form. However, in order to make these +objects easier to interface with ophyd objects the methods +TyphosTool.from_device() and TyphosTool.add_device() are +available. These automatically populate fields according to device structures.

+
+

Tool Classes

+ + + + + + + + + + + + +

TyphosConsole([parent])

IPython Widget for Typhos Display.

TyphosLogDisplay([level, parent])

Typhos Logging Display.

TyphosTimePlot([parent])

Generalized widget for plotting Ophyd signals.

+
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/utils.html b/v2.4.1/utils.html new file mode 100644 index 000000000..12845fa90 --- /dev/null +++ b/v2.4.1/utils.html @@ -0,0 +1,1093 @@ + + + + + + + Utility Functions — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Utility Functions

+

Utility functions for typhos

+
+
+class typhos.utils.DeviceConnectionMonitorThread(device, include_lazy=False, **kwargs)[source]
+

Monitor connection status in a background thread

+
+
Parameters:
+
    +
  • device (ophyd.Device) – The device to grab signals from

  • +
  • include_lazy (bool, optional) – Include lazy signals as well

  • +
+
+
+
+
+connection_update
+

Connection update signal with signature:

+
(signal, connected, metadata_dict)
+
+
+
+
Type:
+

QtCore.Signal

+
+
+
+ +
+
+run(self)[source]
+
+ +
+
+stop(*, wait_ms: int = 1000)[source]
+

Stop the background thread and clean up.

+
+
Parameters:
+

wait_ms (int, optional) – Time to wait for the background thread to exit. Set to 0 to +disable.

+
+
+
+ +
+ +
+
+class typhos.utils.GrabKindItem(attr, component, signal)
+
+
+attr
+

Alias for field number 0

+
+ +
+
+component
+

Alias for field number 1

+
+ +
+
+signal
+

Alias for field number 2

+
+ +
+ +
+
+class typhos.utils.ObjectConnectionMonitorThread(objects=None, **kwargs)[source]
+

Monitor connection status in a background thread

+
+
+connection_update
+

Connection update signal with signature:

+
(signal, connected, metadata_dict)
+
+
+
+
Type:
+

QtCore.Signal

+
+
+
+ +
+
+run(self)[source]
+
+ +
+
+stop(*, wait_ms: int = 1000)[source]
+

Stop the background thread and clean up.

+
+
Parameters:
+

wait_ms (int, optional) – Time to wait for the background thread to exit. Set to 0 to +disable.

+
+
+
+ +
+ +
+
+class typhos.utils.ThreadPoolWorker(func, *args, **kwargs)[source]
+

Worker thread helper

+
+
Parameters:
+
    +
  • func (callable) – The function to call during run()

  • +
  • *args – Arguments for the function call

  • +
  • **kwargs – Keyword rarguments for the function call

  • +
+
+
+
+
+run(self)[source]
+
+ +
+ +
+
+class typhos.utils.TyphosBase(*args, **kwargs)[source]
+

Base widget for all Typhos widgets that interface with devices

+
+ +
+
+class typhos.utils.TyphosLoading(timeout_message, *, parent=None, **kwargs)[source]
+

A QLabel with an animation for loading status.

+
+
+LOADING_TIMEOUT_MS
+

The timeout value in milliseconds for when to stop the animation +and replace it with a default timeout message.

+
+
Type:
+

int

+
+
+
+ +
+
+contextMenuEvent(self, ev: QContextMenuEvent)[source]
+
+ +
+ +
+
+class typhos.utils.WeakPartialMethodSlot(signal_owner: QObject, signal: pyqtSignal, method: method, *args, **kwargs)[source]
+

A PyQt-compatible slot for a partial method.

+

This utility handles deleting the connection when the method class instance +gets garbage collected. This avoids cycles in the garbage collector +that would prevent the instance from being garbage collected prior to the +program exiting.

+
+
Parameters:
+
    +
  • signal_owner (QtCore.QObject) – The owner of the signal.

  • +
  • signal (QtCore.Signal) – The signal instance itself.

  • +
  • method (instance method) – The method slot to call when the signal fires.

  • +
  • *args – Arguments to pass to the method.

  • +
  • **kwargs – Keyword arguments to pass to the method.

  • +
+
+
+
+ +
+
+typhos.utils.apply_standard_stylesheets(dark: bool = False, paths: Iterable[str] | None = None, include_pydm: bool = True, widget: QtWidgets.QWidget | None = None) None[source]
+

Apply all the stylesheets at once, along with the Fusion style.

+

Applies stylesheets with the following priority order: +- Any existing stylesheet data on the widget +- User stylesheets in the paths argument +- User stylesheets in PYDM_STYLESHEET (which behaves as a path) +- Typhos’s stylesheet (either the dark or the light variant) +- PyDM’s built-in stylesheet, if PYDM_STYLESHEET_INCLUDE_DEFAULT is set.

+

The Fusion style can only be applied to a QApplication.

+
+
Parameters:
+
    +
  • dark (bool, optional) – Whether or not to use the QDarkStyleSheet theme. By default the light +theme is chosen.

  • +
  • paths (iterable of str, optional) – User-provided paths to stylesheets to apply.

  • +
  • include_pydm (bool, optional) – Whether or not to use the stylesheets defined in the pydm environment +variables. Defaults to True.

  • +
  • widget (QWidget, optional) – The widget to apply the stylesheet to. +If omitted, apply to the whole QApplication.

  • +
+
+
+
+ +
+
+typhos.utils.channel_from_signal(signal, read=True)[source]
+

Create a PyDM address from arbitrary signal type

+
+ +
+
+typhos.utils.channel_name(pv, protocol='ca')[source]
+

Create a valid PyDM channel from a PV name

+
+ +
+
+typhos.utils.clean_attr(attr)[source]
+

Create a nicer, human readable alias from a Python attribute name

+
+ +
+
+typhos.utils.clean_name(device, strip_parent=True)[source]
+

Create a human readable name for a device

+
+
Parameters:
+
    +
  • device (ophyd.Device) –

  • +
  • strip_parent (bool or Device) – Remove the parent name of the device from name. If strip_parent is +True, the name of the direct parent of the device is stripped. If a +device is provided the name of that device is used. This allows +specification for removal at any point of the device schema

  • +
+
+
+
+ +
+
+typhos.utils.clear_layout(layout)[source]
+

Clear a QLayout

+
+ +
+
+typhos.utils.code_from_device(device)[source]
+

Generate code required to load device in another process

+
+ +
+
+typhos.utils.code_from_device_repr(device)[source]
+

Return code to create a device from its repr information.

+
+
Parameters:
+

device (ophyd.Device) –

+
+
+
+ +
+
+typhos.utils.compose_stylesheets(stylesheets: Iterable[str | pathlib.Path]) str[source]
+

Combine multiple qss stylesheets into one qss stylesheet.

+

If two stylesheets make conflicting specifications, the one passed into +this function first will take priority.

+

This is accomplished by placing the text from the highest-priority +stylesheets at the bottom of the combined stylesheet. Stylesheets are +evaluated in order from top to bottom, and newer elements on the bottom +will override elements at the top.

+
+
Parameters:
+

stylesheets (iterable of str or pathlib.Path) – An itetable, such as a list, of the stylesheets to combine. +Each element can either be a fully-loaded stylesheet or a full path to +a stylesheet. Stylesheet paths must end in the .qss suffix. +In the unlikely event that a string is both a valid path +and a valid stylesheet, it will be interpretted as a path, +even if no file exists at that path.

+
+
Returns:
+

composed_style – A string suitable for passing into QWidget.setStylesheet that +incorporates all of the input stylesheets.

+
+
Return type:
+

str

+
+
Raises:
+
    +
  • OSError – If any error is encountered while reading a file

  • +
  • TypeError – If the input is not a valid type

  • +
+
+
+
+ +
+
+typhos.utils.connection_status_monitor(*signals, callback)[source]
+

[Context manager] Monitor connection status from a number of signals

+

Filters out any other metadata updates, only calling once +connected/disconnected

+
+
Parameters:
+
    +
  • *signals (ophyd.OphydObj) – Signals to monitor

  • +
  • callback (callable) – Callback to run, with same signature as that of +ophyd.OphydObj.subscribe(). obj and connected are +guaranteed kwargs.

  • +
+
+
+
+ +
+
+typhos.utils.dump_grid_layout(layout, rows=None, cols=None, *, cell_width=60)[source]
+

Dump the layout of a QtWidgets.QGridLayout to file.

+
+
Parameters:
+
    +
  • layout (QtWidgets.QGridLayout) – The layout

  • +
  • rows (int) – Number of rows to iterate over

  • +
  • cols (int) – Number of columns to iterate over

  • +
+
+
Returns:
+

table – The text for the summary table

+
+
Return type:
+

str

+
+
+
+ +
+
+typhos.utils.find_file_in_paths(filename, *, paths=None)[source]
+

Search for filename filename in the list of paths paths

+
+
Parameters:
+
    +
  • filename (str or pathlib.Path) – The filename

  • +
  • paths (list or iterable, optional) – List of paths to search. Defaults to DISPLAY_PATHS.

  • +
+
+
Yields:
+

All filenames that match in the given paths

+
+
+
+ +
+
+typhos.utils.find_parent_with_class(widget, cls=<class 'PyQt5.QtWidgets.QWidget'>)[source]
+

Finds the first parent of a widget that is an instance of klass

+
+
Parameters:
+
    +
  • widget (QWidget) – The widget from which to start the search

  • +
  • cls (type, optional) – The class which the parent must be an instance of

  • +
+
+
+
+ +
+
+typhos.utils.find_templates_for_class(cls, view_type, paths, *, extensions=None, include_mro=True)[source]
+

Given a class cls and a view type (such as ‘detailed’), search paths +for potential templates to show.

+
+
Parameters:
+
    +
  • cls (class) – Search for templates with this class name

  • +
  • view_type ({'detailed', 'engineering', 'embedded'}) – The view type

  • +
  • paths (iterable) – Iterable of paths to be expanded, de-duplicated, and searched

  • +
  • extensions (str or list, optional) – The template filename extension (default is '.ui' or '.py')

  • +
  • include_mro (bool, optional) – Include superclasses - those in the MRO - of cls as well

  • +
+
+
Yields:
+

path (pathlib.Path) – A matching path, ordered from most-to-least specific.

+
+
+
+ +
+
+typhos.utils.flatten_tree(param)[source]
+

Flatten a tree of parameters

+
+ +
+
+typhos.utils.get_all_signals_from_device(device, include_lazy=False, filter_by=None)[source]
+

Get all signals in a given device

+
+
Parameters:
+
    +
  • device (ophyd.Device) – ophyd Device to monitor

  • +
  • include_lazy (bool, optional) – Include lazy signals as well

  • +
  • filter_by (callable, optional) – Filter signals, with signature callable(ophyd.Device.ComponentWalk)

  • +
+
+
+
+ +
+
+typhos.utils.get_component(obj)[source]
+

Get the component that made the given object.

+
+
Parameters:
+

obj (ophyd.OphydItem) – The ophyd item for which to get the component.

+
+
Returns:
+

component – The component, if available.

+
+
Return type:
+

ophyd.Component

+
+
+
+ +
+
+typhos.utils.get_device_from_fake_class(cls)[source]
+

Return the non-fake class, given a fake class

+

That is:

+
fake_cls = ophyd.sim.make_fake_device(cls)
+get_device_from_fake_class(fake_cls)  # -> cls
+
+
+
+
Parameters:
+

cls (type) – The fake class

+
+
+
+ +
+
+typhos.utils.get_variety_metadata(cpt)[source]
+

Get “variety” metadata from a component or signal.

+
+
Parameters:
+

cpt (ophyd.Component or ophyd.OphydItem) – The component / ophyd item to get the metadata for.

+
+
Returns:
+

metadata – The metadata, if set. Otherwise an empty dictionary. This metadata is +guaranteed to be valid according to the known schemas.

+
+
Return type:
+

dict

+
+
+
+ +
+
+typhos.utils.is_fake_device_class(cls)[source]
+

Is cls a fake device from ophyd.sim.make_fake_device()?

+
+ +
+
+typhos.utils.is_signal_ro(signal)[source]
+

Return whether the signal is read-only, based on its class.

+

In the future this may be easier to do through improvements to +introspection in the ophyd library. Until that day we need to check classes

+
+ +
+
+typhos.utils.is_standard_template(template)[source]
+

Is the template a core one provided with typhos?

+
+
Parameters:
+

template (str or pathlib.Path) –

+
+
+
+ +
+ +

Registers the signal with PyDM, and sets the widget channel.

+
+
Parameters:
+
    +
  • signal (ophyd.OphydObj) – The signal to use.

  • +
  • widget (QtWidgets.QWidget) – The widget with which to connect the signal.

  • +
+
+
+
+ +
+
+typhos.utils.linked_attribute(property_attr, widget_attr, hide_unavailable=False)[source]
+

Decorator which connects a device signal with a widget.

+

Retrieves the signal from the device, registers it with PyDM, and sets the +widget channel.

+
+
Parameters:
+
    +
  • property_attr (str) – This is one level of indirection, allowing for the component attribute +to be configurable by way of designable properties. +In short, this looks like: +getattr(self.device, getattr(self, property_attr)) +The component attribute name may include multiple levels (e.g., +'cpt1.cpt2.low_limit').

  • +
  • widget_attr (str) – The attribute name of the widget, referenced from self. +The component attribute name may include multiple levels (e.g., +'ui.low_limit').

  • +
  • hide_unavailable (bool) – Whether or not to hide widgets for which the device signal is not +available

  • +
+
+
+
+ +
+
+typhos.utils.load_suite(path, cfg=None)[source]
+

” +Load a file saved via Typhos

+
+
Parameters:
+
    +
  • path (str) – Path to file describing the TyphosSuite. This needs to be of the +format created by the save_suite() function.

  • +
  • cfg (str, optional) – Location of happi configuration file to use to load devices. If not +entered the $HAPPI_CFG environment variable will be used.

  • +
+
+
Returns:
+

suite

+
+
Return type:
+

TyphosSuite

+
+
+
+ +
+
+typhos.utils.make_identifier(name)[source]
+

Make a Python string into a valid Python identifier

+
+ +
+
+typhos.utils.no_device_lazy_load()[source]
+

Context manager which disables the ophyd.device.Device +lazy_wait_for_connection behavior and later restore its value.

+
+ +
+
+typhos.utils.nullcontext()[source]
+

Stand-in for py3.7’s contextlib.nullcontext

+
+ +
+
+typhos.utils.patch_connect_slots()[source]
+

Patches QtCore.QMetaObject.connectSlotsByName to catch SystemErrors.

+
+ +
+
+typhos.utils.pyqt_class_from_enum(enum)[source]
+

Create an inheritable base class from a Python Enum, which can also be used +for Q_ENUMS.

+
+ +
+
+typhos.utils.raise_window(widget)[source]
+

Bring a widget’s window into focus and on top of the window stack.

+

If the window is minimized, unminimize it.

+

Different window managers respond differently to the various +methods called here, the chosen sequence was intended for +good behavior on as many systems as possible.

+
+ +
+
+typhos.utils.random_color()[source]
+

Return a random hex color description

+
+ +
+
+typhos.utils.reload_widget_stylesheet(widget, cascade=False)[source]
+

Reload the stylesheet of the provided widget

+
+ +
+
+typhos.utils.remove_duplicate_items(list_)[source]
+

Return a de-duplicated list/tuple of items in list_, retaining order

+
+ +
+
+typhos.utils.save_suite(suite, file_or_buffer)[source]
+

Create a file capable of relaunching the TyphosSuite

+
+
Parameters:
+
    +
  • suite (TyphosSuite) –

  • +
  • file_or_buffer (str or file-like) – Either a path to the file or a handle that supports write

  • +
+
+
+
+ +
+
+typhos.utils.subscription_context(*objects, callback, event_type=None, run=True)[source]
+

[Context manager] Subscribe to a specific event from all objects

+

Unsubscribes all signals before exiting

+
+
Parameters:
+
    +
  • *objects (ophyd.OphydObj) – Ophyd objects (signals) to monitor

  • +
  • callback (callable) – Callback to run, with same signature as that of +ophyd.OphydObj.subscribe().

  • +
  • event_type (str, optional) – The event type to subscribe to

  • +
  • run (bool, optional) – Run the previously cached subscription immediately

  • +
+
+
+
+ +
+
+typhos.utils.subscription_context_device(device, callback, event_type=None, run=True, *, include_lazy=False, filter_by=None)[source]
+

[Context manager] Subscribe to event_type from signals in device

+

Unsubscribes all signals before exiting

+
+
Parameters:
+
    +
  • device (ophyd.Device) – ophyd Device to monitor

  • +
  • callback (callable) – Callback to run, with same signature as that of +ophyd.OphydObj.subscribe()

  • +
  • event_type (str, optional) – The event type to subscribe to

  • +
  • run (bool, optional) – Run the previously cached subscription immediately

  • +
  • include_lazy (bool, optional) – Include lazy signals as well

  • +
  • filter_by (callable, optional) – Filter signals, with signature callable(ophyd.Device.ComponentWalk)

  • +
+
+
+
+ +
+
+typhos.utils.use_stylesheet(dark: bool = False, widget: QtWidgets.QWidget | None = None) None[source]
+

Use the Typhos stylesheet

+

This is no longer used directly in typhos in favor of +apply_standard_stylesheets.

+

This can still be used if you want the legacy behavior of ignoring PyDM +environment variables. The function is unchanged.

+
+
Parameters:
+

dark (bool, optional) – Whether or not to use the QDarkStyleSheet theme. By default the light +theme is chosen.

+
+
+
+ +
+
+typhos.utils.widget_to_image(widget, fill_color=19)[source]
+

Paint the given widget in a new QtGui.QImage.

+
+
Returns:
+

The display, as an image.

+
+
Return type:
+

QtGui.QImage

+
+
+
+ +
+
+

Cache Utilities

+
+

Path caching

+
+
+typhos.cache.get_global_display_path_cache()[source]
+

Get the _GlobalDisplayPathCache singleton.

+
+ +
+
+class typhos.cache._GlobalDisplayPathCache[source]
+

A cache for all configured display paths.

+
+
All paths from utils.DISPLAY_PATHS will be included:
    +
  1. Environment variable PYDM_DISPLAYS_PATH.

  2. +
  3. Typhos package built-in paths.

  4. +
+
+
+
+
+add_path(path)[source]
+

Add a path to be searched during glob.

+
+
Parameters:
+

path (pathlib.Path or str) – The path to add.

+
+
+
+ +
+
+update()[source]
+

Force a reload of all paths in the cache.

+
+ +
+ +
+
+

Ophyd Object Description Caching

+
+
+typhos.cache.get_global_describe_cache()[source]
+

Get the _GlobalDescribeCache singleton.

+
+ +
+
+class typhos.cache._GlobalDescribeCache[source]
+

Cache of ophyd object descriptions.

+

obj.describe() is called in a thread from the global QThreadPool, and +new results are marked by the Signal new_description.

+

To access a description, call get(). If available, it will be +returned immediately. Otherwise, wait for the new_description Signal.

+
+
+connect_thread
+

The thread which monitors connection status.

+
+
Type:
+

ObjectConnectionMonitorThread

+
+
+
+ +
+
+cache
+

The cache holding descriptions, keyed on obj.

+
+
Type:
+

dict

+
+
+
+ +
+
+clear()[source]
+

Clear the cache.

+
+ +
+
+get(obj)[source]
+

To access a description, call this method. If available, it will be +returned immediately. Otherwise, upon connection and successful +describe() call, the new_description Signal will be emitted.

+
+
Parameters:
+

obj (ophyd.OphydObj) – The object to get the description of.

+
+
Returns:
+

desc – If available in the cache, the description will be returned.

+
+
Return type:
+

dict or None

+
+
+
+ +
+ +
+
+

Ophyd Object to Widget Type Cache

+
+
+typhos.cache.get_global_widget_type_cache()[source]
+

Get the _GlobalWidgetTypeCache singleton.

+
+ +
+
+class typhos.cache._GlobalWidgetTypeCache[source]
+

Cache of ophyd object Typhos widget types.

+

obj.describe() is called using _GlobalDescribeCache and are +therefore threaded and run in the background. New results are marked by +the Signal widgets_determined.

+

To access a set of widget types, call get(). If available, it will +be returned immediately. Otherwise, wait for the widgets_determined +Signal.

+
+
+describe_cache
+

The describe cache, used for determining widget types.

+
+
Type:
+

_GlobalDescribeCache

+
+
+
+ +
+
+cache
+

The cache holding widget type information. +Keyed on obj, the values are SignalWidgetInfo tuples.

+
+
Type:
+

dict

+
+
+
+ +
+
+clear()[source]
+

Clear the cache.

+
+ +
+
+get(obj)[source]
+

To access widget types, call this method. If available, it will be +returned immediately. Otherwise, upon connection and successful +describe() call, the widgets_determined Signal will be emitted.

+
+
Parameters:
+

obj (ophyd.OphydObj) – The object to get the widget types.

+
+
Returns:
+

desc – If available in the cache, the information will be returned.

+
+
Return type:
+

SignalWidgetInfo or None

+
+
+
+ +
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/v2.4.1/widgets.html b/v2.4.1/widgets.html new file mode 100644 index 000000000..57cf24e95 --- /dev/null +++ b/v2.4.1/widgets.html @@ -0,0 +1,1746 @@ + + + + + + + Widgets — Typhos 2.4.1 documentation + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+
+
+ +
+
+
+
+ +
+

Widgets

+

Typhos uses a few custom widgets to create a clean and concise user interface. +While most users should not be interacting with these directly, there may be a +need if a user opts to create their display by hand instead of automatically +generating one.

+
+

Determining widget types

+

If you would just like a widget for an ophyd.Signal, there +is a function available:

+
+
+typhos.widgets.create_signal_widget(signal, read_only=False, tooltip=None)[source]
+

Factory for creating a PyDMWidget from a signal

+
+
Parameters:
+
    +
  • signal (ophyd.Signal) – Signal object to create widget

  • +
  • read_only (bool, optional) – Whether this widget should be able to write back to the signal you +provided

  • +
  • tooltip (str, optional) – Tooltip to use for the widget

  • +
+
+
Returns:
+

widget – PyDMLabel, PyDMLineEdit, or PyDMEnumComboBox based on whether we should +be able to write back to the widget and if the signal has enum_strs

+
+
Return type:
+

PyDMWidget

+
+
+
+ +
+
+class typhos.widgets.SignalWidgetInfo(read_cls, read_kwargs, write_cls, write_kwargs)[source]
+

Provides information on how to create signal widgets: class and kwargs.

+
+
Parameters:
+
    +
  • read_cls (type) – The readback widget class.

  • +
  • read_kwargs (dict) – The readback widget initialization keyword arguments.

  • +
  • write_cls (type) – The setpoint widget class.

  • +
  • write_kwargs (dict) – The setpoint widget initialization keyword arguments.

  • +
+
+
+
+
+classmethod from_signal(obj, desc=None)[source]
+

Create a SignalWidgetInfo given an object and its description.

+
+
Parameters:
+
    +
  • obj (ophyd.OphydObj) – The object

  • +
  • desc (dict, optional) – The object description, if available.

  • +
+
+
+
+ +
+ +
+
+typhos.widgets.widget_type_from_description(signal, desc, read_only=False)[source]
+

Determine which widget class should be used for the given signal

+
+
Parameters:
+
    +
  • signal (ophyd.Signal) – Signal object to determine widget class

  • +
  • desc (dict) – Previously recorded description from the signal

  • +
  • read_only (bool, optional) – Should the chosen widget class be read-only?

  • +
+
+
Returns:
+

    +
  • widget_class (class) – The class to use for the widget

  • +
  • kwargs (dict) – Keyword arguments for the class

  • +
+

+
+
+
+ +
+
+typhos.widgets.determine_widget_type(signal, read_only=False)[source]
+

Determine which widget class should be used for the given signal.

+
+
Parameters:
+
    +
  • signal (ophyd.Signal) – Signal object to determine widget class

  • +
  • read_only (bool, optional) – Should the chosen widget class be read-only?

  • +
+
+
Returns:
+

    +
  • widget_class (class) – The class to use for the widget

  • +
  • kwargs (dict) – Keyword arguments for the class

  • +
+

+
+
+
+ +
+
+

Panels

+

One of the major design principles of Typhos is that users should be able to +see what they need and hide one they don’t. Thefore, many of the widget +implementations are placed in “Panels” these consist of QPushButton header that +hides and shows the contents. Each variation in Typhos is documented below.

+
+

Basic Signal Panels

+
+
+class typhos.panel.SignalPanel(signals=None)[source]
+

Basic panel layout for ophyd.Signal and other ophyd objects.

+

This panel does not support hierarchical display of signals; rather, it +flattens a device hierarchy showing all signals in the same area.

+
+
Parameters:
+

signals (OrderedDict, optional) – Signals to include in the panel. +Parent of panel.

+
+
+
+
+loading_complete
+

A signal indicating that loading of the panel has completed.

+
+
Type:
+

QtCore.Signal

+
+
+
+ +
+
+NUM_COLS
+

The number of columns in the layout.

+
+
Type:
+

int

+
+
+
+ +
+
+COL_LABEL
+

The column number for the row label.

+
+
Type:
+

int

+
+
+
+ +
+
+COL_READBACK
+

The column number for the readback widget.

+
+
Type:
+

int

+
+
+
+ +
+
+COL_SETPOINT
+

The column number for the setpoint widget.

+
+
Type:
+

int

+
+
+
+ +
+

See also

+

CompositeSignalPanel

+
+
+
+add_device(device)[source]
+

Typhos hook for adding a new device.

+
+ +
+
+add_pv(read_pv, name, write_pv=None)[source]
+

Add a row, given PV names.

+
+
Parameters:
+
    +
  • read_pv (str) – The readback PV name.

  • +
  • name (str) – Name of signal to display.

  • +
  • write_pv (str, optional) – The setpoint PV name.

  • +
+
+
Returns:
+

row – Row number that the signal information was added to in the +SignalPanel.layout()`.

+
+
Return type:
+

int

+
+
+
+ +
+
+add_row(*widgets, **kwargs)[source]
+

Add widgets to the next row.

+

If fewer than NUM_COLS widgets are given, the last widget will be +adjusted automatically to span the remaining columns.

+
+
Parameters:
+

*widgets – List of QtWidgets.QWidget.

+
+
Returns:
+

row – The row number.

+
+
Return type:
+

int

+
+
+
+ +
+
+add_signal(signal, name=None, *, tooltip=None)[source]
+

Add a signal to the panel.

+

The type of widget control that is drawn is dependent on +_read_pv, and _write_pv. attributes.

+

If widget information for the given signal is available in the global +cache, the widgets will be created immediately. Otherwise, a row will +be reserved and widgets created upon signal connection and background +description callback.

+
+
Parameters:
+
    +
  • signal (EpicsSignal, EpicsSignalRO) – Signal to create a widget.

  • +
  • name (str, optional) – The name to be used for the row label. This defaults to +signal.name.

  • +
+
+
Returns:
+

row – Row number that the signal information was added to in the +SignalPanel.layout()`.

+
+
Return type:
+

int

+
+
+
+ +
+
+clear()[source]
+

Clear the SignalPanel.

+
+ +
+
+filter_signals(kinds, name_filter=None)[source]
+

Filter signals based on the given kinds.

+
+
Parameters:
+
    +
  • kinds (list of ophyd.Kind) – List of kinds to show.

  • +
  • name_filter (str, optional) – Additionally filter signals by name.

  • +
+
+
+
+ +
+
+label_text_from_attribute(attr, dotted_name)[source]
+

Get label text for a given attribute.

+

For a basic signal panel, use the full dotted name. This is because +this panel flattens the device hierarchy, and using only the last +attribute name may lead to ambiguity or name clashes.

+
+ +
+
+property row_count
+

Get the number of filled-in rows.

+
+ +
+
+property signals
+

Get all instantiated signals, omitting components.

+
+
Returns:
+

signals – With the form: {signal_name: signal}.

+
+
Return type:
+

dict

+
+
+
+ +
+
+property visible_elements
+

Get all signals visible according to filters, omitting components.

+
+
Returns:
+

signals – With the form: {signal_name: signal}.

+
+
Return type:
+

dict

+
+
+
+ +
+
+property visible_signals
+

Get all signals visible according to filters, omitting components.

+
+
Returns:
+

signals – With the form: {signal_name: signal}.

+
+
Return type:
+

dict

+
+
+
+ +
+ +
+
+class typhos.TyphosSignalPanel(parent=None, init_channel=None)[source]
+

Panel of Signals for a given device, using SignalPanel.

+
+
Parameters:
+
    +
  • parent (QtWidgets.QWidget, optional) – The parent widget.

  • +
  • init_channel (str, optional) – The PyDM channel with which to initialize the widget.

  • +
+
+
+
+
+class SignalOrder
+

Options for sorting signals.

+

This can be used as a base class for subclasses of +QtWidgets.QWidget, allowing this to be used in +QtCore.Property and therefore in the Qt designer.

+
+ +
+
+add_device(device)[source]
+

Typhos hook for adding a new device.

+
+ +
+
+property filter_settings
+

Get the filter settings dictionary.

+
+ +
+
+generate_context_menu()[source]
+

Generate a context menu for this TyphosSignalPanel.

+
+ +
+
+nameFilter
+

Get or set the current name filter.

+
+ +
+
+open_context_menu(ev)[source]
+

Open a context menu when the Default Context Menu is requested.

+
+
Parameters:
+

ev (QEvent) –

+
+
+
+ +
+
+set_device_display(display)[source]
+

Typhos hook for when the TyphosDeviceDisplay is associated.

+
+ +
+
+showConfig
+

Show ophyd.Kind.config signals

+
+ +
+
+showHints
+

Show ophyd.Kind.hinted signals

+
+ +
+
+showNormal
+

Show ophyd.Kind.normal signals

+
+ +
+
+showOmitted
+

Show ophyd.Kind.omitted signals

+
+ +
+
+property show_kinds
+

Get a list of the ophyd.Kind that should be shown.

+
+ +
+
+sortBy
+

Get or set the order that the signals will be placed in layout.

+
+ +
+ +
+
+

Composite Signal Panels

+
+
+class typhos.panel.CompositeSignalPanel[source]
+

Composite panel layout for ophyd.Signal and other ophyd objects.

+

Contrasted to SignalPanel, this class retains the hierarchy built +into an ophyd.Device hierarchy. Individual signals mix in with +sub-device displays, which may or may not have custom screens.

+
+
+loading_complete
+

A signal indicating that loading of the panel has completed.

+
+
Type:
+

QtCore.Signal

+
+
+
+ +
+
+NUM_COLS
+

The number of columns in the layout.

+
+
Type:
+

int

+
+
+
+ +
+
+COL_LABEL
+

The column number for the row label.

+
+
Type:
+

int

+
+
+
+ +
+
+COL_READBACK
+

The column number for the readback widget.

+
+
Type:
+

int

+
+
+
+ +
+
+COL_SETPOINT
+

The column number for the setpoint widget.

+
+
Type:
+

int

+
+
+
+ +
+
+add_device(device)[source]
+

Typhos hook for adding a new device.

+
+ +
+
+add_sub_device(device, name)[source]
+

Add a sub-device to the next row.

+
+
Parameters:
+
    +
  • device (ophyd.Device) – The device to add.

  • +
  • name (str) – The name/label to go with the device.

  • +
+
+
+
+ +
+
+label_text_from_attribute(attr, dotted_name)[source]
+

Get label text for a given attribute.

+
+ +
+
+property visible_elements
+

Return all visible signals and components.

+
+ +
+ +
+
+class typhos.TyphosCompositeSignalPanel(parent=None, init_channel=None)[source]
+

Hierarchical panel for a device, using CompositeSignalPanel.

+
+
Parameters:
+
    +
  • parent (QtWidgets.QWidget, optional) – The parent widget.

  • +
  • init_channel (str, optional) – The PyDM channel with which to initialize the widget.

  • +
+
+
+
+ +
+
+
+

TyphosPositionerWidget

+
+
+class typhos.TyphosPositionerWidget(parent=None)[source]
+

Widget to interact with a ophyd.Positioner.

+

Standard positioner motion requires a large amount of context for +operators. For most motors, it may not be enough to simply have a text +field where setpoints can be punched in. Instead, information like soft +limits and hardware limit switches are crucial for a full understanding of +the position and behavior of a motor. The widget will work with any object +that implements the method set, however to get other relevant +information, we see if we can find other useful signals. Below is a table +of attributes that the widget looks for to inform screen design.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

Widget

Attribute Selection

User Readback

The readback_attribute property is used, which defaults +to user_readback. Linked to UI element +user_readback.

User Setpoint

The setpoint_attribute property is used, which defaults +to user_setpoint. Linked to UI element +user_setpoint.

Limit Switches

The low_limit_switch_attribute and +high_limit_switch_attribute properties are used, which +default to low_limit_switch and high_limit_switch, +respectively.

Soft Limits

The low_limit_travel_attribute and +high_limit_travel_attribute properties are used, which +default to low_limit_travel and high_limit_travel, +respectively. As a fallback, the limit property on the +device may be queried directly.

Set and Tweak

Both of these methods simply use Device.set which is +expected to take a float and return a status object +that indicates the motion completeness. Must be implemented.

Stop

Device.stop(), if available, otherwise hide the button. +If you have a non-functional stop method inherited from +a parent device, you can hide it from typhos by +overriding it with a property that raises +AttributeError on access.

Move Indicator

The moving_attribute property is used, which defaults +to motor_is_moving. Linked to UI element +moving_indicator.

Error Message

The error_message_attribute property is used, which +defaults to error_message. Linked to UI element +error_label.

Clear Error

Device.clear_error(), if applicable. This also clears +any visible error messages from the status returned by +Device.set.

Alarm Circle

Uses the TyphosAlarmCircle widget to summarize the +alarm state of all of the device’s normal and +hinted signals.

+
+
+class KindLevel(value)
+

Options for TyphosAlarm.kindLevel.

+
+ +
+
+acceleration_attribute
+

The attribute name for the acceleration time signal.

+
+ +
+
+add_device(device)[source]
+

Add a device to the widget

+
+ +
+
+clear_error()[source]
+

Clear the error messages from the device and screen.

+

The device may have errors in the IOC. These will be cleared by calling +the clear_error method.

+

The screen may have errors from the status of the last move. These will +be cleared from view.

+
+ +
+
+property device
+

The associated device.

+
+ +
+
+error_message_attribute
+

The attribute name for the IOC error message label.

+
+ +
+
+failed_move
+

The last requested move failed

+
+ +
+
+high_limit_switch_attribute
+

The attribute name for the high limit switch signal.

+
+ +
+
+high_limit_travel_attribute
+

The attribute name for the high (soft) limit travel signal.

+
+ +
+
+low_limit_switch_attribute
+

The attribute name for the low limit switch signal.

+
+ +
+
+low_limit_travel_attribute
+

The attribute name for the low limit signal.

+
+ +
+
+move_changed()[source]
+

Called when a move is begun

+
+ +
+
+moving
+

Current state of widget

+

This will lag behind the actual state of the positioner in order to +prevent unnecessary rapid movements

+
+ +
+
+moving_attribute
+

The attribute name for the motor moving indicator.

+
+ +
+
+negative_tweak()[source]
+

Tweak negative by the amount listed in ui.tweak_value

+
+ +
+
+positive_tweak()[source]
+

Tweak positive by the amount listed in ui.tweak_value

+
+ +
+
+readback_attribute
+

The attribute name for the readback signal.

+
+ +
+
+set()[source]
+

Set the device to the value configured by ui.set_value

+
+ +
+
+setpoint_attribute
+

The attribute name for the setpoint signal.

+
+ +
+
+show_expert_button
+

If True, show the expert button.

+

The expert button opens a full suite for the device. +You typically want this False when you’re already inside the +suite that the button would open. +You typically want this True when you’re using the positioner widget +inside of an unrelated screen. +This will default to False.

+
+ +
+
+stop()[source]
+

Stop device

+
+ +
+
+successful_move
+

The last requested move was successful

+
+ +
+
+tweak(offset)[source]
+

Tweak by the given offset.

+
+ +
+
+update_alarm_text(alarm_level)[source]
+

Label the alarm circle with a short text bit.

+
+ +
+
+velocity_attribute
+

The attribute name for the velocity signal.

+
+ +
+ +
+
+

Functions and Methods

+
+
+class typhos.func.FunctionPanel(methods=None, parent=None)[source]
+

Function Panel.

+

Similar to SignalPanel but instead displays a set of function +widgets arranged in a row. Each provided method has a +FunctionDisplay generated for it an added to the layout.

+
+
Parameters:
+
    +
  • methods (list of callables, optional) – List of callables to add to the FunctionPanel.

  • +
  • parent (QWidget) –

  • +
+
+
+
+
+add_method(func, *args, **kwargs)[source]
+

Add a FunctionDisplay.

+
+
Parameters:
+
    +
  • func (callable) – Annotated callable function.

  • +
  • args – All additional parameters are passed directly to the +FunctionDisplay constructor.

  • +
  • kwargs – All additional parameters are passed directly to the +FunctionDisplay constructor.

  • +
+
+
+
+ +
+ +
+
+class typhos.TyphosMethodButton(parent=None)[source]
+

QPushButton to access a method of a Device.

+

The function provided by the loaded device and the method_name +will be run when the button is clicked. If use_status is set to True, +the button will be disabled while the Status object is active.

+
+
+add_device(device)[source]
+

Add a new device to the widget.

+
+
Parameters:
+

device (ophyd.Device) –

+
+
+
+ +
+
+execute()[source]
+

Execute the method given by method_name.

+
+ +
+
+classmethod from_device(device, parent=None)[source]
+

Create a TyphosMethodButton from a device.

+
+ +
+
+method_name
+

Name of method on provided Device to execute.

+
+ +
+
+use_status
+

Use the status to enable and disable the button.

+
+ +
+ +
+
+

Miscellaneous

+
+
+class typhos.widgets.ClickableBitIndicator(parent=None, circle=False)[source]
+

A bit indicator that emits clicked when clicked.

+
+
+mousePressEvent(self, a0: QMouseEvent)[source]
+
+ +
+ +
+
+class typhos.widgets.ImageDialogButton(init_channel, text=None, icon=None, parent=None)[source]
+

QPushButton to show a 2-d array.

+

Notes

+
    +
  • Used for variety array-image (readback)

  • +
+
+
+parent_widget_class
+

alias of QMainWindow

+
+ +
+
+widget()[source]
+

Create PyDMImageView

+
+ +
+ +
+
+class typhos.widgets.SignalDialogButton(init_channel, text=None, icon=None, parent=None)[source]
+

QPushButton to launch a QDialog with a PyDMWidget

+
+
+icon = NotImplemented
+
+ +
+
+parent_widget_class
+

alias of QWidget

+
+ +
+
+show_dialog()[source]
+

Show the channel in a QDialog

+
+ +
+
+text = NotImplemented
+
+ +
+
+widget(channel)[source]
+

Return a widget created with channel

+
+ +
+ +
+
+class typhos.widgets.SubDisplay[source]
+

QDockWidget modified to emit a signal when closed

+
+
+closeEvent(self, event: QCloseEvent)[source]
+
+ +
+ +
+
+class typhos.widgets.TyphosArrayTable(*args, variety_metadata=None, ophyd_signal=None, **kwargs)[source]
+

A table widget which reshapes and displays a given waveform value.

+

Notes

+
    +
  • Used for variety array-tabular (setpoint)

  • +
+
+
+value_changed(value)[source]
+

Callback invoked when the Channel value is changed.

+
+
Parameters:
+

new_waveform (np.ndarray) – The new waveform value from the channel.

+
+
+
+ +
+
+property variety_metadata
+

Additional component variety metadata.

+
+ +
+ +
+
+class typhos.widgets.TyphosByteIndicator(*args, variety_metadata=None, ophyd_signal=None, **kwargs)[source]
+

Displays an integer value as individual, read-only bit indicators.

+

Notes

+
    +
  • Used for variety bitmask (readback)

  • +
+
+
+property variety_metadata
+

Additional component variety metadata.

+
+ +
+ +
+
+class typhos.widgets.TyphosByteSetpoint(*args, variety_metadata=None, ophyd_signal=None, **kwargs)[source]
+

Displays an integer value as individual, toggleable bit indicators.

+

Notes

+
    +
  • Used for variety bitmask (setpoint)

  • +
+
+
+numBits
+

Number of bits to interpret.

+

Re-implemented from PyDM to support changing of bit indicators.

+
+ +
+
+value_changed(value)[source]
+

Receive and update the TyphosTextEdit for a new channel value.

+
+ +
+ +
+
+class typhos.widgets.TyphosComboBox(*args, variety_metadata=None, ophyd_signal=None, **kwargs)[source]
+

Notes

+
    +
  • Used for variety text-enum (setpoint)

  • +
  • Used for variety enum (setpoint)

  • +
+
+
+enum_strings_changed(new_enum_strings)[source]
+

Callback invoked when the Channel has new enum values. +This callback also triggers a value_changed call so the +new enum values to be broadcasted

+
+
Parameters:
+

new_enum_strings (tuple) – The new list of values

+
+
+
+ +
+
+wheelEvent(self, e: QWheelEvent)[source]
+
+ +
+ +
+
+class typhos.widgets.TyphosCommandButton(*args, variety_metadata=None, ophyd_signal=None, **kwargs)[source]
+

A pushbutton widget which executes a command by sending a specific value.

+ +

Notes

+
    +
  • Used for variety command-setpoint-tracks-readback (setpoint)

  • +
  • Used for variety command-proc (setpoint)

  • +
  • Used for variety command (setpoint)

  • +
+
+
+enum_strings_changed(new_enum_strings)[source]
+

Callback invoked when the Channel has new enum values. +This callback also triggers a value_changed call so the +new enum values to be broadcasted

+
+
Parameters:
+

new_enum_strings (tuple) – The new list of values

+
+
+
+ +
+
+property variety_metadata
+

Additional component variety metadata.

+
+ +
+ +
+
+class typhos.widgets.TyphosCommandEnumButton(*args, variety_metadata=None, ophyd_signal=None, **kwargs)[source]
+

A group of buttons which represent several command options.

+

These options can come from directly from the control layer or can be +overridden with variety metadata.

+
+

See also

+

TyphosCommandButton

+
+

Notes

+
    +
  • Used for variety command-enum (setpoint)

  • +
+
+
+enum_strings_changed(new_enum_strings)[source]
+

Callback invoked when the Channel has new enum values. +This callback also triggers a value_changed call so the +new enum values to be broadcasted.

+
+
Parameters:
+

new_enum_strings (tuple) – The new list of values

+
+
+
+ +
+
+property variety_metadata
+

Additional component variety metadata.

+
+ +
+ +
+
+class typhos.widgets.TyphosLabel(*args, display_format=None, ophyd_signal=None, **kwargs)[source]
+

Reimplementation of PyDMLabel to set some custom defaults

+

Notes

+
    +
  • Used for variety array-nd (setpoint)

  • +
  • Used for variety text-multiline (readback)

  • +
  • Used for variety text-enum (readback)

  • +
  • Used for variety text (readback)

  • +
  • Used for variety scalar-tweakable (readback)

  • +
  • Used for variety scalar-range (readback)

  • +
  • Used for variety scalar (readback)

  • +
  • Used for variety enum (readback)

  • +
  • Used for variety command-setpoint-tracks-readback (readback)

  • +
  • Used for variety command-enum (readback)

  • +
  • Used for variety array-nd (readback)

  • +
+
+
+enum_strings_changed(new_enum_strings)[source]
+

Callback invoked when the Channel has new enum values. +This callback also triggers a value_changed call so the +new enum values to be broadcasted

+
+
Parameters:
+

new_enum_strings (tuple) – The new list of values

+
+
+
+ +
+
+unit_changed(new_unit)[source]
+

Callback invoked when the Channel has new unit value. +This callback also triggers an update_format_string call so the +new unit value is considered if `showUnits` is set.

+
+
Parameters:
+

new_unit (str) – The new unit

+
+
+
+ +
+ +
+
+class typhos.widgets.TyphosLineEdit(*args, display_format=None, **kwargs)[source]
+

Reimplementation of PyDMLineEdit to set some custom defaults

+

Notes

+
    +
  • Used for variety text (setpoint)

  • +
  • Used for variety scalar (setpoint)

  • +
+
+
+send_value()[source]
+

Update channel value while recording setpoint history

+
+ +
+
+setpointHistoryCount
+

Number of items to show in the context menu “setpoint history”

+
+ +
+
+property setpoint_history
+

timestamp}

+
+
Type:
+

History of setpoints, as a dictionary of {setpoint

+
+
+
+ +
+
+unit_changed(new_unit)[source]
+

Callback invoked when the Channel has new unit value. +This callback also triggers an update_format_string call so the +new unit value is considered if `showUnits` is set.

+
+
Parameters:
+

new_unit (str) – The new unit

+
+
+
+ +
+
+widget_ctx_menu()[source]
+

Fetch the Widget specific context menu which will be populated with additional tools by assemble_tools_menu.

+
+
Returns:
+

If the return of this method is None a new QMenu will be created by assemble_tools_menu.

+
+
Return type:
+

QMenu or None

+
+
+
+ +
+ +
+
+class typhos.widgets.TyphosScalarRange(*args, variety_metadata=None, ophyd_signal=None, **kwargs)[source]
+

A slider widget which displays a scalar value with an explicit range.

+

Notes

+
    +
  • Used for variety scalar-range (setpoint)

  • +
+
+
+connection_changed(connected)[source]
+

Callback invoked when the connection state of the Channel is changed. +This callback acts on the connection state to enable/disable the widget +and also trigger the change on alarm severity to ALARM_DISCONNECTED.

+
+
Parameters:
+

connected (int) – When this value is 0 the channel is disconnected, 1 otherwise.

+
+
+
+ +
+
+property delta_signal
+

Delta signal, used as the source for “delta_value”.

+
+ +
+
+delta_value
+

Delta value, an alternative to “num_points” provided by PyDMSlider.

+

num_points is calculated using the current min/max and the delta value, +if set.

+
+ +
+
+property variety_metadata
+

Additional component variety metadata.

+
+ +
+ +
+
+class typhos.widgets.TyphosSidebarItem(param, depth)[source]
+

Class to display a Device or Tool in the sidebar

+

Notes

+
+
+embed_requested(triggered)[source]
+

Request to open embedded display for sidebar item

+
+ +
+
+hide_requested(triggered)[source]
+

Request to hide display for sidebar item

+
+ +
+
+open_requested(triggered)[source]
+

Request to open display for sidebar item

+
+ +
+
+treeWidgetChanged()[source]
+

Update the widget when add to a QTreeWidget

+
+ +
+ +
+
+class typhos.tweakable.TyphosTweakable(parent=None, init_channel=None, variety_metadata=None, ophyd_signal=None)[source]
+

Widget for a tweakable scalar.

+
+
Parameters:
+
    +
  • parent (QWidget) – The parent widget.

  • +
  • init_channel (str, optional) – The channel to be used by the widget.

  • +
+
+
+

Notes

+
    +
  • Used for variety scalar-tweakable (setpoint)

  • +
+
+
+negative_tweak()[source]
+

Tweak negative by the amount listed in ui.tweak_value

+
+ +
+
+positive_tweak()[source]
+

Tweak positive by the amount listed in ui.tweak_value

+
+ +
+
+tweak(offset)[source]
+

Tweak by the given offset.

+
+ +
+
+property variety_metadata
+

Additional component variety metadata.

+
+ +
+ +
+
+class typhos.textedit.TyphosTextEdit(parent=None, init_channel=None, variety_metadata=None, ophyd_signal=None)[source]
+

A writable, multiline text editor with support for PyDM Channels.

+
+
Parameters:
+
    +
  • parent (QWidget) – The parent widget.

  • +
  • init_channel (str, optional) – The channel to be used by the widget.

  • +
  • (setpoint) (* Used for variety text-multiline) –

  • +
+
+
+
+
+send_value()[source]
+

Emit a send_value_signal to update channel value.

+
+ +
+
+set_display()[source]
+

Set the text display of the TyphosTextEdit.

+
+ +
+
+value_changed(value)[source]
+

Receive and update the TyphosTextEdit for a new channel value.

+
+ +
+
+property variety_metadata
+

Additional component variety metadata.

+
+ +
+
+write_access_changed(new_write_access)[source]
+

Change the TyphosTextEdit to read only if write access is denied

+
+ +
+ +
+
+class typhos.widgets.WaveformDialogButton(init_channel, text=None, icon=None, parent=None)[source]
+

QPushButton to show a 1-d array.

+

Notes

+
    +
  • Used for variety array-histogram (readback)

  • +
  • Used for variety array-timeseries (readback)

  • +
+
+
+parent_widget_class
+

alias of QMainWindow

+
+ +
+
+widget()[source]
+

Create PyDMWaveformPlot

+
+ +
+ +
+
+

Designer

+
+
+class typhos.widgets.TyphosDesignerMixin(init_channel=None)[source]
+

A mixin class used to display Typhos widgets in the Qt designer.

+
+
+alarmSensitiveBorder
+
+ +
+
+alarmSensitiveContent
+
+ +
+
+channel
+

The channel address to use for this widget

+
+ +
+ +
+
+ + +
+
+ +
+
+
+
+ + + + \ No newline at end of file diff --git a/versions.json b/versions.json index cb46714e1..b7ae0ae7f 100644 --- a/versions.json +++ b/versions.json @@ -1 +1 @@ -{"folders": ["generated", "master", "v1.0.2", "v1.1.0", "v1.1.1", "v1.1.3", "v1.1.4", "v1.1.6", "v1.2.0", "v2.0.0", "v2.1.0", "v2.2.0", "v2.2.1", "v2.3.0", "v2.3.1", "v2.3.2", "v2.3.3", "v2.4.0"], "default-branch": "master", "labels": {"generated": "generated", "master": "master", "v1.0.2": "v1.0.2", "v1.1.0": "v1.1.0", "v1.1.1": "v1.1.1", "v1.1.3": "v1.1.3", "v1.1.4": "v1.1.4", "v1.1.6": "v1.1.6", "v1.2.0": "v1.2.0", "v2.0.0": "v2.0.0", "v2.1.0": "v2.1.0", "v2.2.0": "v2.2.0", "v2.2.1": "v2.2.1", "v2.3.0": "v2.3.0", "v2.3.1": "v2.3.1", "v2.3.2": "v2.3.2", "v2.3.3": "v2.3.3", "v2.4.0": "v2.4.0 (latest)"}, "versions": ["master", "v2.4.0", "v2.3.3", "v2.3.2", "v2.3.1", "v2.3.0", "v2.2.1", "v2.2.0", "v2.1.0", "v2.0.0", "v1.2.0", "v1.1.6", "v1.1.4", "v1.1.3", "v1.1.1", "v1.1.0", "v1.0.2", "generated"], "warnings": {"generated": ["unreleased"], "master": ["unreleased"], "v1.0.2": ["outdated"], "v1.1.0": ["outdated"], "v1.1.1": ["outdated"], "v1.1.3": ["outdated"], "v1.1.4": ["outdated"], "v1.1.6": ["outdated"], "v1.2.0": ["outdated"], "v2.0.0": ["outdated"], "v2.1.0": ["outdated"], "v2.2.0": ["outdated"], "v2.2.1": ["outdated"], "v2.3.0": ["outdated"], "v2.3.1": ["outdated"], "v2.3.2": ["outdated"], "v2.3.3": ["outdated"], "v2.4.0": []}, "latest": "v2.4.0", "downloads": {"generated": [], "master": [], "v1.0.2": [], "v1.1.0": [], "v1.1.1": [], "v1.1.3": [], "v1.1.4": [], "v1.1.6": [], "v1.2.0": [], "v2.0.0": [], "v2.1.0": [], "v2.2.0": [], "v2.2.1": [], "v2.3.0": [], "v2.3.1": [], "v2.3.2": [], "v2.3.3": [], "v2.4.0": []}} \ No newline at end of file +{"folders": ["generated", "master", "v1.0.2", "v1.1.0", "v1.1.1", "v1.1.3", "v1.1.4", "v1.1.6", "v1.2.0", "v2.0.0", "v2.1.0", "v2.2.0", "v2.2.1", "v2.3.0", "v2.3.1", "v2.3.2", "v2.3.3", "v2.4.0", "v2.4.1"], "default-branch": "master", "labels": {"generated": "generated", "master": "master", "v1.0.2": "v1.0.2", "v1.1.0": "v1.1.0", "v1.1.1": "v1.1.1", "v1.1.3": "v1.1.3", "v1.1.4": "v1.1.4", "v1.1.6": "v1.1.6", "v1.2.0": "v1.2.0", "v2.0.0": "v2.0.0", "v2.1.0": "v2.1.0", "v2.2.0": "v2.2.0", "v2.2.1": "v2.2.1", "v2.3.0": "v2.3.0", "v2.3.1": "v2.3.1", "v2.3.2": "v2.3.2", "v2.3.3": "v2.3.3", "v2.4.0": "v2.4.0", "v2.4.1": "v2.4.1 (latest)"}, "versions": ["master", "v2.4.1", "v2.4.0", "v2.3.3", "v2.3.2", "v2.3.1", "v2.3.0", "v2.2.1", "v2.2.0", "v2.1.0", "v2.0.0", "v1.2.0", "v1.1.6", "v1.1.4", "v1.1.3", "v1.1.1", "v1.1.0", "v1.0.2", "generated"], "warnings": {"generated": ["unreleased"], "master": ["unreleased"], "v1.0.2": ["outdated"], "v1.1.0": ["outdated"], "v1.1.1": ["outdated"], "v1.1.3": ["outdated"], "v1.1.4": ["outdated"], "v1.1.6": ["outdated"], "v1.2.0": ["outdated"], "v2.0.0": ["outdated"], "v2.1.0": ["outdated"], "v2.2.0": ["outdated"], "v2.2.1": ["outdated"], "v2.3.0": ["outdated"], "v2.3.1": ["outdated"], "v2.3.2": ["outdated"], "v2.3.3": ["outdated"], "v2.4.0": ["outdated"], "v2.4.1": []}, "latest": "v2.4.1", "downloads": {"generated": [], "master": [], "v1.0.2": [], "v1.1.0": [], "v1.1.1": [], "v1.1.3": [], "v1.1.4": [], "v1.1.6": [], "v1.2.0": [], "v2.0.0": [], "v2.1.0": [], "v2.2.0": [], "v2.2.1": [], "v2.3.0": [], "v2.3.1": [], "v2.3.2": [], "v2.3.3": [], "v2.4.0": [], "v2.4.1": []}} \ No newline at end of file