Exemplo n.º 1
0
class StatusArea(TaurusWidget):

    """A (scrolling) text area that displays device status, or any other
    string attribute."""

    statusTrigger = QtCore.pyqtSignal(str)

    def __init__(self, parent=None, down_command=None, up_command=None, state=None):
        TaurusWidget.__init__(self, parent)
        self._setup_ui()

    def _setup_ui(self):

        hbox = QtGui.QVBoxLayout(self)
        self.setLayout(hbox)

        self.status_label = QtGui.QLabel("(No status has been read.)")
        self.status_label.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
        self.status_label.setWordWrap(True)
        self.status_label.setAlignment(QtCore.Qt.AlignTop)
        status_scroll_area = QtGui.QScrollArea()
        status_scroll_area.setMaximumSize(QtCore.QSize(100000, 100))
        status_scroll_area.setWidgetResizable(True)
        status_scroll_area.setWidget(self.status_label)
        hbox.addWidget(status_scroll_area)
        self.status = None

        self.statusTrigger.connect(self.updateStatus)

    def setModel(self, model):
        if model:
            split_model = model.split("/")
            if len(split_model) < 4:
                self.status = Attribute("%s/Status" % model)
            else:
                self.status = Attribute(model)
            self.status.addListener(self.onStatusChange)
        else:
            self.status and self.status.removeListener(self.onStatusChange)

    def onStatusChange(self, evt_src, evt_type, evt_value):
        if evt_type in [PyTango.EventType.CHANGE_EVENT,
                        PyTango.EventType.PERIODIC_EVENT]:
            self.statusTrigger.emit(evt_value.value)

    def updateStatus(self, status):
        self.status_label.setText(status)
Exemplo n.º 2
0
class ForcedReadTool(QtGui.QAction, BaseConfigurableClass):
    """
    This tool provides a menu option to control the "Forced Read" period of
    Plot data items that implement a `setForcedReadPeriod` method
    (see, e.g. :meth:`TaurusTrendSet.setForcedReadPeriod`).

    The force-read feature consists on forcing periodic attribute reads for
    those attributes being plotted with a :class:`TaurusTrendSet` object.
    This allows to force plotting periodical updates even for attributes
    for which the taurus polling is not enabled.
    Note that this is done at the widget level and therefore does not affect
    the rate of arrival of events for other widgets connected to the same
    attributes

    This tool inserts an action with a spinbox and emits a `valueChanged`
    signal whenever the value is changed.

    The connection between the data items and this tool can be done manually
    (by connecting to the `valueChanged` signal or automatically, if
    :meth:`autoconnect()` is `True` (default). The autoconnection feature works
    by discovering the compliant data items that share associated to the
    plot_item.

    This tool is implemented as an Action, and provides a method to attach it
    to a :class:`pyqtgraph.PlotItem`
    """

    valueChanged = QtCore.pyqtSignal(int)

    def __init__(
        self,
        parent=None,
        period=0,
        text="Change forced read period...",
        autoconnect=True,
    ):
        BaseConfigurableClass.__init__(self)
        QtGui.QAction.__init__(self, text, parent)
        tt = "Period between forced readings.\nSet to 0 to disable"
        self.setToolTip(tt)
        self._period = period
        self._autoconnect = autoconnect

        # register config properties
        self.registerConfigProperty(self.period, self.setPeriod, "period")
        self.registerConfigProperty(
            self.autoconnect, self.setAutoconnect, "autoconnect"
        )

        # internal conections
        self.triggered.connect(self._onTriggered)

    def _onTriggered(self):
        period = self.period()
        period, ok = QtGui.QInputDialog.getInt(
            self.parentWidget(),
            "New read period",
            "Period (in ms) between forced readings.\nSet to 0 to disable",
            period,
            0,
            604800000,
            500,
        )
        if ok:
            self.setPeriod(period)

    def attachToPlotItem(self, plot_item):
        """Use this method to add this tool to a plot

        :param plot_item: (PlotItem)
        """
        menu = plot_item.getViewBox().menu
        menu.addAction(self)
        self.plot_item = plot_item
        # force an update of period for connected trendsets
        self.setPeriod(self.period())
        if self.autoconnect():
            # enable the forced reading also for trendsets added in the future
            try:  # requires https://github.com/pyqtgraph/pyqtgraph/pull/1388
                plot_item.scene().sigItemAdded.connect(self._onAddedItem)
            except AttributeError:
                pass

    def _onAddedItem(self, item):
        if hasattr(item, "setForcedReadPeriod"):
            item.setForcedReadPeriod(self.period())

    def autoconnect(self):
        """Returns autoconnect state

        :return: (bool)
        """
        return self._autoconnect

    def setAutoconnect(self, autoconnect):
        """Set autoconnect state. If True, the tool will autodetect trendsets
        associated to the plot item and will call setForcedReadPeriod
        on each of them for each change. If False, it will only emit a
        valueChanged signal and only those connected to it will be notified
        of changes

        :param autoconnect: (bool)
        """
        self._autoconnect = autoconnect

    def period(self):
        """Returns the current period value (in ms)

        :return: (int)
        """
        return self._period

    def setPeriod(self, period):
        """Change the period value. Use 0 for disabling

        :param period: (int) period in ms
        """
        self._period = period
        # update existing items
        if self.autoconnect() and self.plot_item is not None:
            for item in self.plot_item.listDataItems():
                if hasattr(item, "setForcedReadPeriod"):
                    item.setForcedReadPeriod(period)
        # emit valueChanged
        self.valueChanged.emit(period)
Exemplo n.º 3
0
class MAXValueBar(TaurusWidget):

    value_trigger = QtCore.pyqtSignal(float, float)
    conf_trigger = QtCore.pyqtSignal()

    _delta = 1

    def __init__(self, parent=None, designMode=False):
        TaurusWidget.__init__(self, parent, designMode=designMode)
        self._setup_ui()

        self._throttle_timer = QtCore.QTimer()
        self._throttle_timer.setInterval(200)
        self._throttle_timer.setSingleShot(True)
        self.connect(self._throttle_timer, QtCore.SIGNAL("timeout()"),
                     self._writeValue)

        self._value = None

        self._acc_value = 0  # accumulate fast wheel events
        self._last_wheel = 0  # time of last wheel event

    @classmethod
    def getQtDesignerPluginInfo(cls):
        ret = TaurusWidget.getQtDesignerPluginInfo()
        ret['module'] = 'maxvaluebar'
        ret['group'] = 'MAX-lab Taurus Widgets'
        ret['container'] = ':/designer/frame.png'
        ret['container'] = False
        return ret

    def _setup_ui(self):
        vbox = QtGui.QHBoxLayout(self)
        self.valuebar = ValueBarWidget()
        vbox.addWidget(self.valuebar)
        self.setLayout(vbox)
        self.value_trigger.connect(self.valuebar.setValue)
        self.conf_trigger.connect(self.updateConfig)
        self.setFocusPolicy(QtCore.Qt.WheelFocus)

    def setModel(self, model):
        TaurusWidget.setModel(self, model)
        self.updateConfig()
        conf = Configuration("%s?configuration" % self.model)
        conf.addListener(lambda *args: self.conf_trigger.emit())

    def _decimalDigits(self, fmt):
        '''returns the number of decimal digits from a format string
        (or None if they are not defined)'''
        try:
            if fmt[-1].lower() in ['f', 'g'] and '.' in fmt:
                return int(fmt[:-1].split('.')[-1])
            else:
                return None
        except:
            return None

    def updateConfig(self):
        conf = Configuration("%s?configuration" % self.model)
        # Note: could be inefficient with lots of redraws?
        self.valuebar.setMaximum(float_or_none(conf.max_value))
        self.valuebar.setMinimum(float_or_none(conf.min_value))
        self.valuebar.setWarningHigh(float_or_none(conf.max_warning))
        self.valuebar.setWarningLow(float_or_none(conf.min_warning))
        self.valuebar.setAlarmHigh(float_or_none(conf.max_alarm))
        self.valuebar.setAlarmLow(float_or_none(conf.min_alarm))

        digits = self._decimalDigits(conf.format)
        if digits:
            self._delta = pow(10, -digits)

    @QtCore.pyqtSlot()
    def _writeValue(self):
        if self._value:
            self.getModelObj().write(self._value)

    def throttledWrite(self, value):
        """Intead of writing to Tango every time the value changes, we start a
        timer. Writes during the timer will be accumulated and when the timer
        runs out, the last value is written.
        """
        self._value = value
        if not self._throttle_timer.isActive():
            self._throttle_timer.start()

    def handleEvent(self, evt_src, evt_type, evt_value):
        if evt_type in (PyTango.EventType.PERIODIC_EVENT,
                        PyTango.EventType.CHANGE_EVENT):
            if evt_value.quality == PyTango.AttrQuality.ATTR_VALID:
                self.value_trigger.emit(evt_value.value, evt_value.w_value)
        elif evt_type in (PyTango.EventType.ATTR_CONF_EVENT,
                          PyTango.EventType.QUALITY_EVENT):
            # Note: why don't I get "ATTR_CONF" events when the attribute
            # config changes? Seems like I get QUALITY events instead..?
            self.conf_trigger.emit()

    def wheelEvent(self, evt):

        model = self.getModelObj()

        evt.accept()
        numDegrees = evt.delta() / 8
        numSteps = numDegrees / 15
        modifiers = evt.modifiers()
        if modifiers & Qt.Qt.ControlModifier:
            numSteps *= 10
        elif (modifiers & Qt.Qt.AltModifier) and model.isFloat():
            numSteps *= .1

        # We change the value by 1 in the least significant digit according
        # to the configured format.
        value = self.valuebar.write_value + numSteps * self._delta
        self.valuebar.setWriteValue(value)
        self.throttledWrite(value)
Exemplo n.º 4
0
class DataModel(QtCore.QObject):
    '''
    An object containing one piece of data which is intended to be shared. The
    data will be identified by its UID (a unique identifier known to objects
    that intend to access the data)

    In general, you are not supposed to instantiate objects of this class
    directly. Instead, you should interact via the :class:`SharedDataManager`,
    which uses :meth:`SharedDataManager.__getDataModel` to ensure that the
    DataModels are singletons.
    '''

    dataChanged = QtCore.pyqtSignal(object)

    def __init__(self, parent, dataUID, defaultData=None):
        '''
        creator
        :param parent: (QObject) the object's parent
        :param dataUID: (str) a unique identifier for the Data Model
        '''
        QtCore.QObject.__init__(self, parent)
        self.__dataUID = dataUID
        self.__data = defaultData
        self.__isDataSet = False

        self.__readerSlots = []
        self.__writerSignals = []

    def __repr__(self):
        return '<DataModel object with dataUID="%s">' % self.dataUID()

    def dataUID(self):
        '''
        returns the data unique identifier

        :return: (str)
        '''
        return self.__dataUID

    def getData(self):
        '''
        Returns the data object.

        :return: (object) the data object
        '''
        return self.__data

    def setData(self, data):
        '''
        sets the data object and emits a "dataChanged" signal with the data as the parameter

        :param data: (object) the new value for the Model's data
        '''
        self.__data = data
        self.__isDataSet = True
        self.dataChanged.emit(self.__data)

    def connectReader(self, slot, readOnConnect=True):
        '''
        Registers the given slot method to receive notifications whenever the
        data is changed.

        :param slot: (callable) a method that will be called when the data changes.
                     This slot will be the receiver of a signal which has the
                     data as its first argument.
        :param readOnConnect: (bool) if True (default) the slot will be called
                              immediately with the current value of the data
                              if the data has been already initialized

        .. seealso:: :meth:`connectWriter`, :meth:`getData`
        '''
        self.dataChanged.connect(slot)

        if readOnConnect and self.__isDataSet:
            slot(self.__data)
        obj = getattr(slot, '__self__', slot)
        self.__readerSlots.append((weakref.ref(obj), slot.__name__))

    def connectWriter(self, writer, signalname):
        '''
        Registers the given writer object as a writer of the data. The writer is
        then expected to emit a `QtCore.SIGNAL(signalname)` with the new data as the
        first parameter.

        :param writer: (QObject) object that will change the data
        :param signalname: (str) the signal name that will notify changes
                           of the data

        .. seealso:: :meth:`connectReader`, :meth:`setData`
        '''
        try:
            get_signal(writer, signalname).connect(self.setData)
        except AttributeError:
            # support old-style signal
            self.connect(writer, QtCore.SIGNAL(signalname), self.setData)
        self.__writerSignals.append((weakref.ref(writer), signalname))

    def disconnectWriter(self, writer, signalname):
        '''unregister a writer from this data model

        :param writer: (QObject) object to unregister
        :param signalname: (str) the signal that was registered

        .. seealso:: :meth:`SharedDataManager.disconnectWriter`
        '''
        ok = get_signal(writer, signalname).disconnect(self.setData)
        self.__writerSignals.remove((weakref.ref(writer), signalname))

    def disconnectReader(self, slot):
        '''
        unregister a reader

        :param slot: (callable) the slot to which this was connected

        .. seealso:: :meth:`SharedDataManager.disconnectReader`, :meth:`getData`
        '''
        ok = self.dataChanged.disconnect(slot)
        self.__readerSlots.remove((weakref.ref(slot.__self__), slot.__name__))

    def isDataSet(self):
        '''Whether the data has been set at least once or if it is uninitialized

        :return: (bool) True if the data has been set. False it is uninitialized'''
        return self.__isDataSet

    def info(self):
        readers = ["%s::%s" % (repr(r()), s) for r, s in self.__readerSlots]
        writers = ["%s::%s" % (repr(r()), s) for r, s in self.__writerSignals]
        return "UID: %s\n\t Readers (%i):%s\n\t Writers (%i):%s\n" % (self.__dataUID, len(readers),
                                                                      readers, len(writers), writers)

    def readerCount(self):
        '''returns the number of currently registered readers of this model

        :return: (int)
        '''
        return len(self.__readerSlots)

    def writerCount(self):
        '''returns the number of currently registered writers of this model

        :return: (int)
        '''
        return len(self.__writerSignals)
Exemplo n.º 5
0
class MAXLineEdit(TaurusValueLineEdit):
    """A TaurusValueLineEdit tweaked to fit MAXIV purposes. Changes:

    - The current digit (left of the cursor) can be in- or decremented
    by pressing the up/down arrow keys. If autoApply is activated, the
    value will be written on each such keypress.

    - The mouse wheel can be used to freely change the value. The
    change will occur in the least significant digit, configured by
    the Tango attribute format. autoApply works like above.

    - The widget will update the write value even if it is changed
    from somewhere else. The exception is if the widget is currently
    focused (the assumption being that the user is editing it.)

    """

    _focus = False
    _wheel_delta = 1
    w_value_trigger = QtCore.pyqtSignal()

    def __init__(self, parent=None, designMode=False):
        TaurusValueLineEdit.__init__(self, parent, designMode)
        self.setFocusPolicy(QtCore.Qt.WheelFocus)
        self._throttle_timer = QtCore.QTimer()
        self._throttle_timer.setInterval(200)
        self._throttle_timer.setSingleShot(True)
        self.connect(self._throttle_timer, QtCore.SIGNAL("timeout()"),
                     self._writeValue)
        self.w_value_trigger.connect(self._updateWriteValue)

    def _stepBy(self, steps):
        """try to figure out which digit the cursor is at, and step by one in
        that digit."""

        text = str(self.text())
        cursor = len(text) - self.cursorPosition()

        if '.' not in self.text():
            decimal = 0
        else:
            decimal = len(text) - text.find('.') - 1

        if cursor == decimal:
            return
        if cursor == len(text):
            return

        exp = cursor - decimal
        if cursor > decimal:
            exp -= 1

        delta = 10**exp

        TaurusValueLineEdit._stepBy(self, steps * delta)
        self.setCursorPosition(len(self.text()) - cursor)
        if self._autoApply:
            self.writeValue()  # this seems a but risky...

    def focusInEvent(self, event):
        self._focus = True
        TaurusValueLineEdit.focusInEvent(self, event)

    def focusOutEvent(self, event):
        self._focus = False
        self.resetPendingOperations()  # discard unwritten changes (safer?)
        TaurusValueLineEdit.focusOutEvent(self, event)

    def _updateWriteValue(self):
        cursor_position = self.cursorPosition()
        self.setValue(self._w_value)
        self.setCursorPosition(cursor_position)

    def handleEvent(self, evt_src, evt_type, evt_value):
        TaurusValueLineEdit.handleEvent(self, evt_src, evt_type, evt_value)
        if evt_type in (PyTango.EventType.PERIODIC_EVENT,
                        PyTango.EventType.CHANGE_EVENT):
            # taurus.core.taurusbasetypes.TaurusEventType.Periodic,
            # taurus.core.taurusbasetypes.TaurusEventType.Change):
            if not self._focus:
                self._w_value = evt_value.w_value
                self.w_value_trigger.emit()
        elif evt_type in (PyTango.EventType.ATTR_CONF_EVENT,
                          PyTango.EventType.QUALITY_EVENT):
            # update the wheel delta to correspond to the LSD
            digits = self._decimalDigits(evt_value.format)
            if digits is not None:
                self._wheel_delta = pow(10, -digits)

    def _decimalDigits(self, fmt):
        '''returns the number of decimal digits from a format string
        (or None if they are not defined)'''
        try:
            if fmt[-1].lower() in ['f', 'g'] and '.' in fmt:
                return int(fmt[:-1].split('.')[-1])
            else:
                return None
        except:
            return None

    @QtCore.pyqtSlot()
    def _writeValue(self):
        self.writeValue()

    def throttledWrite(self, delta):
        """Intead of writing to Tango every time the value changes, we start a
        timer. Writes during the timer will be accumulated and when the timer
        runs out, the last value is written.
        """
        TaurusValueLineEdit._stepBy(self, delta)
        if not self._throttle_timer.isActive():
            self._throttle_timer.start()

    def wheelEvent(self, evt):
        if not self.getEnableWheelEvent() or Qt.QLineEdit.isReadOnly(self):
            return Qt.QLineEdit.wheelEvent(self, evt)
        model = self.getModelObj()
        if model is None or not model.isNumeric():
            return Qt.QLineEdit.wheelEvent(self, evt)

        evt.accept()
        numDegrees = evt.delta() / 8
        numSteps = numDegrees / 15
        modifiers = evt.modifiers()
        if modifiers & Qt.Qt.ControlModifier:
            numSteps *= 10
        elif (modifiers & Qt.Qt.AltModifier) and model.isFloat():
            numSteps *= .1

        # change the value by 1 in the least significant digit according
        # to the configured format.
        self.throttledWrite(numSteps * self._wheel_delta)

    @classmethod
    def getQtDesignerPluginInfo(cls):
        ret = TaurusValueLineEdit.getQtDesignerPluginInfo()
        ret['group'] = 'MAX-lab Taurus Widgets'
        ret['module'] = 'maxwidgets.input'
        return ret

    def resetInitialValue(self):
        pass
Exemplo n.º 6
0
class CyclePanel(TaurusWidget):
    "Panel for controlling the cycling functionality"

    trend_trigger = QtCore.pyqtSignal(bool)

    attrs = [
        "CyclingTimePlateau", "CyclingIterations", "CyclingSteps",
        "CyclingRampTime", "NominalSetPoint"
    ]
    scale_factor = 1.1

    def __init__(self, parent=None):
        TaurusWidget.__init__(self, parent)
        self._setup_ui()
        print('CyclePanel juste avant setmodel')

    #    self.setModel('sys/tg_test/1')

    def scaleSize(self):
        size = self.form.scrollArea.widget().frameSize()
        return QtCore.QSize(size.width(), size.height() * self.scale_factor)

    def _setup_ui(self):
        vbox = QtGui.QVBoxLayout(self)
        self.setLayout(vbox)

        grid = QtGui.QGridLayout()
        self.form = MAXForm(withButtons=False)

        grid.addWidget(self.form, 0, 0, 2, 1)
        # rescale taurus form methode
        self.form.scrollArea.sizeHint = self.scaleSize
        self.status_label = StatusArea()
        grid.addWidget(self.status_label, 0, 1, 1, 1)

        commandbox = QtGui.QVBoxLayout(self)
        self.start_button = TaurusCommandButton(command="StartCycle")
        self.start_button.setUseParentModel(True)
        self.stop_button = TaurusCommandButton(command="StopCycle")
        self.stop_button.setUseParentModel(True)
        commandbox.addWidget(self.start_button)
        commandbox.addWidget(self.stop_button)
        grid.addLayout(commandbox, 1, 1, 1, 1)

        vbox.addLayout(grid)

        self.trend = TaurusTrend()
        vbox.addWidget(self.trend, stretch=1)
        self.trend_trigger.connect(self.set_trend_paused)

        self.cyclingState = None

    def setModel(self, device):
        print self.__class__.__name__, "setModel", device
        TaurusWidget.setModel(self, device)
        # self.state_button.setModel(device)
        if device:
            self.form.setModel(
                ["%s/%s" % (device, attribute) for attribute in self.attrs])

            self.status_label.setModel("%s/cyclingStatus" % device)

            ps = str(PyTango.Database().get_device_property(
                device, "PowerSupplyProxy")["PowerSupplyProxy"][0])

            self.trend.setPaused()
            self.trend.setModel(["%s/Current" % ps])
            self.trend.setForcedReadingPeriod(0.2)
            self.trend.showLegend(True)

            # let's pause the trend when not cycling
            self.cyclingState = self.getModelObj().getAttribute("cyclingState")
            self.cyclingState.addListener(self.handle_cycling_state)
        else:
            if self.cyclingState:
                self.cyclingState.removeListener(self.handle_cycling_state)
            self.trend.setModel(None)
            self.status_label.setModel(None)

    def handle_cycling_state(self, evt_src, evt_type, evt_value):
        if evt_type in [
                PyTango.EventType.CHANGE_EVENT,
                PyTango.EventType.PERIODIC_EVENT
        ]:
            self.trend_trigger.emit(evt_value.value)

    def set_trend_paused(self, value):
        self.trend.setForcedReadingPeriod(0.2 if value else -1)
        self.trend.setPaused(not value)
Exemplo n.º 7
0
class TaurusDevCombo(TaurusWidget):
    try:
        modelChosen = QtCore.pyqtSignal()
    except:
        modelChosen = MyQtSignal('modelChosen')

    def __init__(self, parent=None, designMode=False):
        TaurusWidget.__init__(self, parent, designMode=designMode)
        self.loadUi()
        self._selectedDevice = ""
        if hasattr(self.modelChosen, '_parent'):
            self.modelChosen._parent = self
        try:
            self._ui.selectorCombo.currentIndexChanged.connect(self.selection)
        except Exception as e:
            self.warning("Using deprecated connect signal")
            Qt.QObject.connect(self._ui.selectorCombo,
                               Qt.SIGNAL('currentIndexChanged(int)'),
                               self.selection)

    @classmethod
    def getQtDesignerPluginInfo(cls):
        ret = TaurusWidget.getQtDesignerPluginInfo()
        ret['module'] = 'widgets.TaurusDevCombo'
        ret['group'] = 'Taurus Views'
        ret['container'] = ':/designer/frame.png'
        ret['container'] = False
        return ret

    def setModel(self, model):
        self.getDeviceListByDeviceServerName(model)
        devNames = list(self._deviceNames.keys())
        devNames.sort()
        self._ui.selectorCombo.addItems(devNames)
        self.selection(devNames[0])

    def getDeviceListByDeviceServerName(self, deviceServerName):
        authority = Authority()
        foundInstances = authority.getServerNameInstances(deviceServerName)
        self.info("by %s found %d instances: %s."
                  % (deviceServerName, len(foundInstances),
                     ','.join("%s" % instance.name()
                              for instance in foundInstances)))
        self._deviceNames = {}
        for instance in foundInstances:
            for i, devName in enumerate(instance.getDeviceNames()):
                if not devName.startswith('dserver'):
                    self._deviceNames[devName] = instance.getClassNames()[i]
        return list(self._deviceNames.keys())

    def selection(self, devName):
        if isinstance(devName, int):
            devName = self._ui.selectorCombo.currentText()
        if devName not in self._deviceNames.keys():
            self.warning("Selected device is not in the list "
                         "of devices found!")
        self.info("selected %s" % (devName))
        self._selectedDevice = devName
        self.modelChosen.emit()

    def getSelectedDeviceName(self):
        # self.debug("Requested which device was selected")
        return self._selectedDevice

    def getSelectedDeviceClass(self):
        try:
            return self._deviceNames[self._selectedDevice]
        except:
            self.error("Uou! As the selected device is not in the device "
                       "instances found, its class is unknown")
            return "unknown"
Exemplo n.º 8
0
class ForcedReadTool(QtGui.QWidgetAction, BaseConfigurableClass):
    """
    This tool provides a menu option to control the "Forced Read" period of
    Plot data items that implement a `setForcedReadPeriod` method
    (see, e.g. :meth:`TaurusTrendSet.setForcedReadPeriod`).

    The force-read feature consists on forcing periodic attribute reads for
    those attributes being plotted with a :class:`TaurusTrendSet` object.
    This allows to force plotting periodical updates even for attributes
    for which the taurus polling is not enabled.
    Note that this is done at the widget level and therefore does not affect
    the rate of arrival of events for other widgets connected to the same
    attributes

    This tool inserts an action with a spinbox and emits a `valueChanged`
    signal whenever the value is changed.

    The connection between the data items and this tool can be done manually
    (by connecting to the `valueChanged` signal or automatically, if
    :meth:`autoconnect()` is `True` (default). The autoconnection feature works
    by discovering the compliant data items that share associated to the
    plot_item.

    This tool is implemented as an Action, and provides a method to attach it
    to a :class:`pyqtgraph.PlotItem`
    """

    valueChanged = QtCore.pyqtSignal(int)

    def __init__(self,
                 parent=None,
                 period=0,
                 text="Forced read",
                 autoconnect=True):
        BaseConfigurableClass.__init__(self)
        QtGui.QWidgetAction.__init__(self, parent)

        # defining the widget
        self._w = QtGui.QWidget()
        self._w.setLayout(QtGui.QHBoxLayout())
        tt = "Period between forced readings.\nSet to 0 to disable"
        self._w.setToolTip(tt)
        self._label = QtGui.QLabel(text)
        self._w.layout().addWidget(self._label)
        self._sb = QtGui.QSpinBox()
        self._w.layout().addWidget(self._sb)
        self._sb.setRange(0, 604800000)
        self._sb.setValue(period)
        self._sb.setSingleStep(500)
        self._sb.setSuffix(" ms")
        self._sb.setSpecialValueText("disabled")
        self._autoconnect = autoconnect

        self.setDefaultWidget(self._w)

        # register config properties
        self.registerConfigProperty(self.period, self.setPeriod, "period")
        self.registerConfigProperty(self.autoconnect, self.setAutoconnect,
                                    "autoconnect")

        # internal conections
        self._sb.valueChanged[int].connect(self._onValueChanged)

    def _onValueChanged(self, period):
        """emit valueChanged and update all associated trendsets (if
        self.autoconnect=True
        """
        self.valueChanged.emit(period)
        if self.autoconnect() and self.plot_item is not None:
            for item in self.plot_item.listDataItems():
                if hasattr(item, "setForcedReadPeriod"):
                    item.setForcedReadPeriod(period)

    def attachToPlotItem(self, plot_item):
        """Use this method to add this tool to a plot

        :param plot_item: (PlotItem)
        """
        menu = plot_item.getViewBox().menu
        menu.addAction(self)
        self.plot_item = plot_item
        # force an update of period for connected trendsets
        self._onValueChanged(self.period())

    def autoconnect(self):
        """Returns autoconnect state

        :return: (bool)
        """
        return self._autoconnect

    def setAutoconnect(self, autoconnect):
        """Set autoconnect state. If True, the tool will autodetect trendsets
        associated to the plot item and will call setForcedReadPeriod
        on each of them for each change. If False, it will only emit a
        valueChanged signal and only those connected to it will be notified
        of changes

        :param autoconnect: (bool)
        """
        self._autoconnect = autoconnect

    def period(self):
        """Returns the current period value (in ms)

        :return: (int)
        """
        return self._sb.value()

    def setPeriod(self, value):
        """Change the period value. Use 0 for disabling

        :param period: (int) period in ms
        """
        self._sb.setValue(value)
Exemplo n.º 9
0
class AttributeColumnsTable(TaurusWidget):

    """Display several 1D spectrum attributes belonging to the same
    device as columns in a table."""

    trigger = QtCore.pyqtSignal((int, int))

    def __init__(self, parent=None):
        TaurusWidget.__init__(self, parent)
        self._setup_ui()

    def _setup_ui(self):
        hbox = QtGui.QHBoxLayout(self)
        self.setLayout(hbox)

        self.table = QtGui.QTableWidget()
        self.table.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)

        hbox.addWidget(self.table)

        self.trigger.connect(self.updateColumn)
        self.attributes = []
        self._columns = []
        self._values = {}
        self._config = {}

    def setModel(self, attrs):
        if not attrs:
            for att, col in zip(self.attributes, self._columns):
                att and att.removeListener(col.event_received)
        else:
            try:
                TaurusWidget.setModel(self, attrs[0].rsplit("/", 1)[0])
                self.attributes = [Attribute(a) for a in attrs]

                self.table.setColumnCount(len(attrs))
                fmt = "%s [%s]"
                labels = []
                for a in self.attributes:
                    config = a.getConfig()
                    label = fmt % (config.getLabel(), config.getUnit())
                    labels.append(label)

                self.table.setHorizontalHeaderLabels(labels)
                header = self.table.horizontalHeader()
                header.setResizeMode(QtGui.QHeaderView.Stretch)

                # Check if there are any columns at all
                row_lengths = [len(a.read().value) for a in self.attributes
                               if a.read().quality == PyTango.AttrQuality.ATTR_VALID]
                if not any(row_lengths):
                    return None
                self.table.setRowCount(max(row_lengths))

                self._values = {}
                self._config = {}
                self._columns = []

                for i, att in enumerate(self.attributes):
                    # JFF: this is a workaround for a behavior in Taurus. Just
                    # adding a new listener to each attribute does not work, the
                    # previous ones get removed for some reason.
                    col = AttributeColumn(self, i)
                    self._columns.append(col)  # keep reference to prevent GC
                    att.addListener(col.event_received)
            except PyTango.DevFailed:
                pass

    def keyPressEvent(self, e):
        "Copy selected cells to the clipboard on Ctrl+C"
        if (e.modifiers() & QtCore.Qt.ControlModifier):
            selected = self.table.selectedRanges()
            if e.key() == QtCore.Qt.Key_C:
                s = ""
                for r in xrange(selected[0].topRow(), selected[0].bottomRow() + 1):
                    for c in xrange(selected[0].leftColumn(), selected[0].rightColumn() + 1):
                        try:
                            s += str(self.table.item(r, c).text()) + "\t"
                        except AttributeError:
                            s += "\t"
                    s = s[:-1] + "\n"  # eliminate last '\t'
                clipboard = QtGui.QApplication.clipboard()
                clipboard.setText(s)

    def onEvent(self, column, evt_src, evt_type, evt_value):
        if evt_type in [PyTango.EventType.CHANGE_EVENT,
                    PyTango.EventType.PERIODIC_EVENT]:
            self._values[column] = evt_value
            self.trigger.emit(column)
        if isinstance(evt_value, PyTango.DeviceAttributeConfig):
            self._config[column] = evt_value

    def updateColumn(self, column):
        if not self._values:
            return  # when does this happen?
        data = self._values[column]
        for row, value in enumerate(data.value):
            if not isnan(value):
                cfg = self._config.get(column)
                if cfg and cfg.format != "Not specified":
                    item = QtGui.QTableWidgetItem(cfg.format % value)
                else:
                    item = QtGui.QTableWidgetItem(str(value))
                item.setFlags(QtCore.Qt.ItemIsSelectable |
                              QtCore.Qt.ItemIsEnabled)
            else:
                item = QtGui.QTableWidgetItem("NaN")
                item.setFlags(QtCore.Qt.ItemIsSelectable |
                              QtCore.Qt.ItemIsEnabled)
                item.setBackgroundColor(QtGui.QColor(220, 220, 220))
            item.setTextAlignment(QtCore.Qt.AlignRight |
                                  QtCore.Qt.AlignVCenter)
            self.table.setItem(row, column, item)
Exemplo n.º 10
0
class ToggleButton(TaurusWidget):

    """A button that has two states, pressed and unpressed. When pressing
    it, the 'down' command is run, and when unpressing it the 'up' command
    is run. The 'pressedness' of the button is connected to a given
    Tango state, e.g. ON."""

    state_trigger = QtCore.pyqtSignal(bool)

    def __init__(self, parent=None, down_command=None, up_command=None, state=None):
        TaurusWidget.__init__(self, parent)
        self._down_command = down_command
        self._up_command = up_command
        self._state = state
        self._setup_ui()

    def _setup_ui(self):
        hbox = QtGui.QHBoxLayout(self)
        self.setLayout(hbox)

        self.button = QtGui.QPushButton()
        #self.button.setText()
        self.button.setCheckable(True)
        hbox.addWidget(self.button)

        self.button.clicked.connect(self.onClick)
        self.state_trigger.connect(self.change_state)

    def setModel(self, model):
        TaurusWidget.setModel(self, model)
        if model:
            m = self.getModelObj()
            self.down_command = getattr(m, self._down_command)
            self.up_command = getattr(m, self._up_command)

            self.state = m.getAttribute("State")
            self.state.addListener(self.handle_state_event)
        else:
            if self.state:
                self.state.removeListener(self.handle_state_event)

    def onClick(self):
        print "state is", self.state.read()
        pressed = self.state.read().value == self._state
        print "pressed", pressed
        if pressed:
            print "running up_commnad", self._up_command
            self.up_command()
        else:
            print "running down_command", self._down_command
            self.down_command()

    def change_state(self, new_state):
        print "change_state", new_state
        self.button.setChecked(new_state)
        self.button.setText((self._down_command, self._up_command)[new_state])

    def handle_state_event(self, evt_src, evt_type, evt_value):
        if evt_type in [PyTango.EventType.CHANGE_EVENT,
                        PyTango.EventType.PERIODIC_EVENT]:
            print "state", self._state
            self.state_trigger.emit(evt_value.value == self._state)
Exemplo n.º 11
0
class DeviceRowsTable(TaurusWidget):

    """A widget that displays a table where each row displays a device,
    and the values of selected attributes."""

    STATE_COLORS = {
        PyTango.DevState.ON: (0, 255, 0),
        PyTango.DevState.OFF: (255, 255, 255),
        PyTango.DevState.CLOSE: (255, 255, 255),
        PyTango.DevState.OPEN: (0, 255, 0),
        PyTango.DevState.INSERT: (255, 255, 255),
        PyTango.DevState.EXTRACT: (0, 255, 0),
        PyTango.DevState.MOVING: (128, 160, 255),
        PyTango.DevState.STANDBY: (255, 255, 0),
        PyTango.DevState.FAULT: (255, 0, 0),
        PyTango.DevState.INIT: (204, 204, 122),
        PyTango.DevState.RUNNING: (128, 160, 255),
        PyTango.DevState.ALARM:  (255, 140, 0),
        PyTango.DevState.DISABLE: (255, 0, 255),
        PyTango.DevState.UNKNOWN: (128, 128, 128),
        None: (128, 128, 128)
    }

    trigger = QtCore.pyqtSignal(int, int)

    def __init__(self, parent=None):
        TaurusWidget.__init__(self, parent)
        self._setup_ui()

    def _setup_ui(self):
        hbox = QtGui.QHBoxLayout(self)
        self.setLayout(hbox)

        self.table = QtGui.QTableWidget(parent=self)

        hbox.addWidget(self.table)

        self.trigger.connect(self.update_item)
        self.table.itemClicked.connect(self.on_item_clicked)

        self._items = {}
        self.attributes = {}

    def keyPressEvent(self, e):
        "Copy selected cells to the clipboard on Ctrl+C"
        if (e.modifiers() & QtCore.Qt.ControlModifier):
            selected = self.table.selectedRanges()
            if e.key() == QtCore.Qt.Key_C:
                s = ""
                for r in xrange(selected[0].topRow(), selected[0].bottomRow() + 1):
                    for c in xrange(selected[0].leftColumn(), selected[0].rightColumn() + 1):
                        try:
                            s += str(self.table.item(r, c).text()) + "\t"
                        except AttributeError:
                            s += "\t"
                    s = s[:-1] + "\n"  # eliminate last '\t'
                clipboard = QtGui.QApplication.clipboard()
                clipboard.setText(s)

    def setModel(self, devices, attributes=[]):
        if not devices:
            for dev, attrs in self.attributes.items():
                for att in attrs:
                    att and att.removeListener(
                        self._items[dev][att.name].event_received)
        else:
            try:
                #TaurusWidget.setModel(self, attrs[0].rsplit("/", 1)[0])
                attrnames = [a[0] if isinstance(a, tuple) else a
                             for a in attributes]
                self.attributes = dict((dev, [Attribute("%s/%s" % (dev, a))
                                              for a in attrnames])
                                       for dev in devices)

                self.table.setColumnCount(len(attributes) + 1)
                colnames = [a[1] if isinstance(a, tuple) else a
                            for a in attributes]
                labels = ["Device"] + colnames
                self.table.setHorizontalHeaderLabels(labels)
                header = self.table.horizontalHeader()
                header.setResizeMode(QtGui.QHeaderView.Stretch)

                # Check if there are any columns at all
                self.table.setRowCount(len(devices))

                for r, (dev, attrs) in enumerate(self.attributes.items()):
                    item = QtGui.QTableWidgetItem(dev)
                    item.setFlags(QtCore.Qt.ItemIsSelectable |
                                  QtCore.Qt.ItemIsEnabled)
                    self.table.setItem(r, 0, item)
                    for c, att in enumerate(attrs, 1):
                        # JFF: this is a workaround for a behavior in Taurus. Just
                        # adding a new listener to each attribute does not work, the
                        # previous ones get removed for some reason.
                        item = TableItem(self.trigger)
                        self.table.setItem(r, c, item)
                        att.addListener(item.event_received)

            except PyTango.DevFailed:
                pass

    def update_item(self, row, column):
        item = self.table.item(row, column)
        value, config = item.value, item.config

        # Set text
        if config and config.format != "Not specified":
            item.setText(config.format % value.value)
        else:
            item.setText(str(value.value))

	# Set flags and state
        if item.writable_boolean:
            item.setFlags(QtCore.Qt.ItemIsSelectable
                          | QtCore.Qt.ItemIsEnabled
                          | QtCore.Qt.ItemIsUserCheckable)
            state = QtCore.Qt.Checked if value.w_value else QtCore.Qt.Unchecked
            item.setCheckState(state)
	else:
            item.setFlags(QtCore.Qt.ItemIsSelectable | QtCore.Qt.ItemIsEnabled)

        # Set background color
        if value.type == PyTango.CmdArgType.DevState:
            if value.value in self.STATE_COLORS:
                item.setBackgroundColor(QtGui.QColor(*self.STATE_COLORS[value.value]))


    def on_item_clicked(self, item):
        # Not a writable item
        if not isinstance(item, TableItem) or not item.writable_boolean:
	    return
        button_state = (item.checkState() == QtCore.Qt.Checked)
        value_state = item.value.w_value
        if button_state != value_state:
            item.source.write(button_state)
Exemplo n.º 12
0
class SwitchBoardPanel(TaurusWidget):
    modeChanged = QtCore.pyqtSignal()
    typeChanged = QtCore.pyqtSignal()

    def __init__(self, parent=None):
        TaurusWidget.__init__(self, parent)

        self.loadUi()

        self.statusLabel.setUseParentModel(True)
        self.statusLabel.setModel('/Status')

        self.modeCB.setUseParentModel(True)
        self.modeCB.setModel('/Mode')

        self.type_attribute = None
        self.mode_attribute = None
        self.type_value = None
        self.mode_value = None

        self.modelChanged.connect(self.onModelChanged)
        self.modeChanged.connect(self.onModeChanged)
        self.typeChanged.connect(self.onTypeChanged)

    def eventReceived(self, evt_src, evt_type, evt_value):
        if not hasattr(evt_value, 'value'):
            return

        # We can't interact with QWidgets from here
        # (calling thread is not a QThread). Emit signals instead.
        if evt_src is self.type_attribute and \
           evt_value.value != self.type_value:
            self.type_value = evt_value.value
            self.typeChanged.emit()

        if evt_src is self.mode_attribute and \
           evt_value.value != self.mode_value:
            self.mode_value = evt_value.value
            self.modeChanged.emit()

    def onModelChanged(self, model):
        if self.mode_attribute:
            self.mode_attribute.removeListener(self)
            self.mode_attribute = None
        if self.type_attribute:
            self.type_attribute.removeListener(self)
            self.type_attribute = None

        device = self.getModelObj()
        if device:
            self.mode_attribute = device.getAttribute('Mode')
            self.mode_attribute.addListener(self)
            self.type_attribute = device.getAttribute('Type')
            self.type_attribute.addListener(self)

    def onTypeChanged(self):
        # fetch list of valid mode names
        device = self.getModelObj()
        if device:
            modes = device.command_inout('getAttrStringValueList', 'Mode')
        else:
            modes = []
        # update combo box
        choices = [(m, m) for m in modes]
        self.modeCB.setValueNames(choices)
        # update image
        self.onModeChanged()

    def onModeChanged(self):
        # update image
        image_map = BOARD_MODES.get(self.type_value)
        if image_map:
            svg = image_map[self.mode_value]
        else:
            svg = ':/unknown.svg'
        self.svgWidget.load(svg)
Exemplo n.º 13
0
class BufferSizeTool(QtGui.QAction, BaseConfigurableClass):
    """
    This tool provides a menu option to control the "Maximum buffer" of
    Plot data items that implement a `setBufferSize` method
    (see, e.g. :meth:`TaurusTrendSet.setBufferSize`).

    This tool inserts an action with a spinbox and emits a `valueChanged`
    signal whenever the value is changed.

    The connection between the data items and this tool can be done manually
    (by connecting to the `valueChanged` signal or automatically, if
    :meth:`autoconnect()` is `True` (default). The autoconnection feature works
    by discovering the compliant data items that are associated to the
    plot_item.

    This tool is implemented as an Action, and provides a method to attach it
    to a :class:`pyqtgraph.PlotItem`
    """

    valueChanged = QtCore.pyqtSignal(int)

    def __init__(
        self,
        parent=None,
        buffer_size=65536,
        text="Change buffers size...",
        autoconnect=True,
    ):
        BaseConfigurableClass.__init__(self)
        QtGui.QAction.__init__(self, text, parent)
        self.setToolTip("Maximum number of points for each trend")
        self._maxSize = buffer_size
        self._autoconnect = autoconnect

        # register config properties
        self.registerConfigProperty(self.bufferSize, self.setBufferSize,
                                    "buffer_size")
        self.registerConfigProperty(self.autoconnect, self.setAutoconnect,
                                    "autoconnect")

        # internal conections
        self.triggered.connect(self._onTriggered)

    def _onEdittingFinished(self):
        """
        emit valueChanged and update all associated trendsets (if
        self.autoconnect=True
        """
        buffer_size = self.bufferSize()
        self.valueChanged.emit(buffer_size)
        if self.autoconnect() and self.plot_item is not None:
            for item in self.plot_item.listDataItems():
                if hasattr(item, "setBufferSize"):
                    item.setBufferSize(buffer_size)

    def _onTriggered(self):
        maxSize = self.bufferSize()
        maxSize, ok = QtGui.QInputDialog.getInt(
            self.parentWidget(),
            "New buffer data size",
            "Enter the number of points to be kept in memory for each curve",
            maxSize,
            2,
            10000000,
            1000,
        )
        if ok:
            self.setBufferSize(maxSize)

    def attachToPlotItem(self, plot_item):
        """Use this method to add this tool to a plot

        :param plot_item: (PlotItem)
        """
        menu = plot_item.getViewBox().menu
        menu.addAction(self)
        self.plot_item = plot_item
        # ensure that current items are set to max size
        self.setBufferSize(self.bufferSize())
        if self.autoconnect():
            # enable the buffer limit also for trendsets added in the future
            try:  # requires https://github.com/pyqtgraph/pyqtgraph/pull/1388
                plot_item.scene().sigItemAdded.connect(self._onAddedItem)
            except AttributeError:
                pass

    def _onAddedItem(self, item):
        if hasattr(item, "setBufferSize"):
            item.setBufferSize(self.bufferSize())

    def autoconnect(self):
        """Returns autoconnect state

        :return: (bool)
        """
        return self._autoconnect

    def setAutoconnect(self, autoconnect):
        """Set autoconnect state. If True, the tool will autodetect trendsets
        associated to the plot item and will call setBufferSize
        on each of them for each change. If False, it will only emit a
        valueChanged signal and only those connected to it will be notified
        of changes

        :param autoconnect: (bool)
        """
        self._autoconnect = autoconnect

    def bufferSize(self):
        """Returns the current buffer_size value

        :return: (int)
        """
        return self._maxSize

    def setBufferSize(self, buffer_size):
        """Change the buffer_size value.

        :param buffer_size: (int) buffer_size
        """
        self._maxSize = buffer_size
        # update existing items
        if self.autoconnect() and self.plot_item is not None:
            for item in self.plot_item.listDataItems():
                if hasattr(item, "setBufferSize"):
                    item.setBufferSize(buffer_size)
        # emit a valueChanged signal
        self.valueChanged.emit(buffer_size)