예제 #1
0
 def setModel(self, image, section=None):
     self.set_url(image)
     self.registry = Registry(self.attribute_listener,
                              self.unsubscribe_listener)
     self.registry.start()
     self.js.plugin_command.connect(self.run_plugin_command)
     self.js.hovered.connect(self._hovered)
 def setModel(self, image, section=None):
     self.set_url(image)
     self.registry = Registry(self.attribute_listener,
                              self.unsubscribe_listener)
     self.registry.start()
     self.js.plugin_command.connect(self.run_plugin_command)
     self.js.hovered.connect(self._hovered)
예제 #3
0
class TaurusSynopticWidget(SynopticWidget, TaurusWidget):
    """A SynopticWidget that connects to TANGO in order to
    get updates for models (attributes)."""

    tooltip_trigger = QtCore.pyqtSignal(str)

    def __init__(self, parent=None, **kwargs):
        super(TaurusSynopticWidget, self).__init__(parent=parent)
        Manager().setSerializationMode(TaurusSerializationMode.Concurrent)
        self.tooltip_trigger.connect(self._update_device_tooltip)
        self._panels = {}

    def setModel(self, image, section=None):
        self.set_url(image)
        self.registry = Registry(self.attribute_listener,
                                 self.unsubscribe_listener)
        self.registry.start()
        self.js.plugin_command.connect(self.run_plugin_command)
        self.js.hovered.connect(self._hovered)
        # self._tooltip_data = {}
        # self.tooltip_registry = Registry(self._tooltip_updater)
        # self.tooltip_registry.start()

    def _hovered(self, sec, mods):
        attr = self.registry.get_listener(str(mods))

    def getModel(self):
        return self._url

    def closeEvent(self, event):
        "Clean things up when the widget is closed"
        self.registry.clear()
        self.registry.stop()
        self.registry.wait()
        self.registry = None

    def run_plugin_command(self, plugin, cmd, args):
        try:
            plugins = __import__("plugins.%s" % plugin, globals(), locals(),
                                 [cmd], -1)
        except ImportError as e:
            print "Could not initialize plugin '%s'!" % plugin
            print e
            return ""
        return getattr(plugins, cmd)(self, args)

    def handle_subscriptions(self, models=[]):
        if self.registry:
            self.registry.subscribe(models)

    def unsubscribe_listener(self, unsubscribed):
        """Tell the synoptic about unsubscribed models. This is
        needed because it's all asunchronous so it cannot be assumed
        that a model is really unsubscribed until it is."""
        classes = STATE_CLASSES[None]
        for model in unsubscribed:
            self.js.evaluate("synoptic.setClasses('model', %r, %s)" %
                             (model, classes))

    def filter_fragment(self, model, value):
        # the "fragment" is an optional part at the end of an eval attribute,
        # prefixed by a "#" and intended to make it possible to "filter" only
        # part of a spectrum or image through "slicing". It is up to the
        # client to do this, so we implement it here.
        frag = self.registry.eval_validator.getNames(model, fragment=True)[3]
        if frag:
            indices = frag[1:-1]
            try:
                # this is the special case where the slice is just an index
                # I'm not 100% that this should be allowed, but it's so useful!
                index = int(indices)
                return value[index]
            except ValueError:
                pass
            try:
                slice_ = [int(i) for i in indices.split(":")]
                return value[slice(*slice_)]
            except ValueError:
                pass
        return value

    def attribute_listener(self, model, evt_src, evt_type, evt_value):
        "Handle events"

        if evt_type == TaurusEventType.Error:
            return  # handle errors somehow
        if evt_type == TaurusEventType.Config:
            return  # need to do something here too
        value = evt_value.value

        # check for the presence of a "fragment" ending (e.g. ...#[3])
        if self.registry and self.registry.eval_validator.isValid(model):
            try:
                value = self.filter_fragment(model, value)
            except Exception:
                pass  # fragment is a taurus 4 feature

        # handle the value differently depending on what it is
        # TODO: clean this up!

        if isinstance(value,
                      (DevState, PyTango.DevState, PyTango._PyTango.DevState)):
            classes = STATE_CLASSES[value]
            device, attr = model.rsplit("/", 1)
            if attr.lower() == "state":
                # this is the normal "State" attribute of the
                # device. Let's set the color of device models
                # too, for convenience.
                self.js.evaluate("synoptic.setClasses('model', %r, %s)" %
                                 (device, classes))
                self.js.evaluate(
                    "synoptic.setClasses('model', '%s/State', %s)" %
                    (device, classes))
            else:
                # Apparently it's an attribute of type DevState
                # but it is not the normal "State" attribute.
                self.js.evaluate("synoptic.setClasses('model', %r, %s)" %
                                 (model, classes))
            self.js.evaluate("synoptic.setText('model', %r, '%s')" %
                             (model, value))
        elif isinstance(value, (bool, np.bool_)):
            classes = {"boolean-true": bool(value), "boolean-false": not value}
            self.js.evaluate("synoptic.setClasses('model', %r, %s)" %
                             (model, json.dumps(classes)))
            self.js.evaluate("synoptic.setText('model', %r, '%s')" %
                             (model, value))
        else:
            # everything else needs to be displayed as text
            quality = evt_value.quality
            if quality == PyTango.AttrQuality.ATTR_INVALID:
                text = "?"  # do something more sophisticated here
            else:
                try:
                    text = evt_src.displayValue(value)
                except AttributeError:
                    text = str(value)
            try:
                unit = evt_src.getConfig().unit
            except AttributeError:
                unit = None

            if unit in (None, "No unit"):
                unit = ""
            self.js.evaluate("synoptic.setText('model', %r, '%s %s')" %
                             (model, text, unit))

    def on_click(self, kind, name):
        """The default behavior is to mark a clicked device and to zoom to a
        clicked section.  Override this function if you need something
        else.
        """
        print "on_click", kind, name
        if kind == "model" and self.registry.device_validator.isValid(name):
            self.select(kind, [name])
            self.emit(Qt.SIGNAL("graphicItemSelected(QString)"), name)
        elif kind == "section":
            self.zoom_to(kind, name)
        else:
            self.unselect_all()

    def get_device_panel(self, device):
        """Override to change which panel is opened for a given device
        name. Return a widget class, a widget, or None if you're
        handling the panel yourself. TaurusDevicePanel is a reasonable
        fallback.
        """
        return TaurusDevicePanel

    def on_rightclick(self, kind, name):
        "The default behavior for right clicking a device is to open a panel."
        if kind == "model" and self.registry.device_validator.isValid(name):
            if name.lower() in self._panels:

                widget = self._panels[name.lower()]
                print "Found existing panel for %s:" % name, widget
                if not widget.isVisible():
                    widget.show()
                widget.activateWindow()
                widget.raise_()
                return

            # check if we recognise the class of the device
            widget = self.get_device_panel(name)

            if not widget:
                # assume that the widget is handled somewhere else
                return
            if isclass(widget):
                # assume it's a widget class
                widget = widget()
                try:
                    # try to set the model
                    widget.setModel(name)
                except AttributeError:
                    pass

            widget.setWindowTitle(name)
            # monkey patch to cleanup on close...
            widget.closeEvent = lambda _: self._cleanup_panel(widget)
            # keep a reference to the widget
            self._panels[name.lower()] = widget
            widget.show()

    def _cleanup_panel(self, w):
        """In the long run it seems like a good idea to try and clean up
        closed panels. In particular, the Taurus polling thread can
        become pretty bogged down."""
        if self.registry:
            with self.registry.lock:
                print "cleaning up panel for", w.getModel(), "..."
                self._panels.pop(str(w.getModel()).lower(), None)
                w.setModel(None)
                print "done!"

    # Note: the tooltip stuff is broken and not currently in use.
    # Currently there is only the default tooltip which displays the
    # model name.

    def on_tooltip(self, models):
        # FIXME: looks like tooltip listeners aren't cleaned up until a new
        # tooltip is displayed. This seems wasteful.
        if models:
            all_models = []
            for model in models:
                if self.registry.device_validator.isValid(model):
                    all_models.append(model + "/State")
                    all_models.append(model + "/Status")
                else:
                    all_models.append(model)
            try:
                self.tooltip_registry.subscribe(all_models)
            except ValueError:
                pass
            self._tooltip_data = dict((str(model), {}) for model in models)
        else:
            self.tooltip_registry.subscribe()
            self._tooltip_data.clear()

    def _tooltip_updater(self, model, attr_value):
        value = attr_value.value
        device, attr = model.rsplit("/", 1)
        if attr in ("State", "Status"):
            if attr == "Status":
                # hack to keep newlines
                value = value.replace("\n", "<br>")
            self._tooltip_data.setdefault(device, {})[attr] = value
            #dev = evt_src.getParentObj()
            #info = dev.getHWObj().info()
            # self._tooltip_data[device]["Class"] = info.dev_class
            self.tooltip_trigger.emit(device)
        else:
            self._tooltip_data[model] = value

    def _update_device_tooltip(self, device):
        # TODO: redo this in a neat way.
        data = {"Class": "...", "State": "...", "Status": "..."}
        data.update(self._tooltip_data[device])
        html = json.dumps(
            ('<div>Class:&nbsp;<span>{Class}</span></div>' +
             '<div>State:&nbsp;<span class="{State}">{State}</span></div>' +
             '<div>Status:&nbsp;<span>{Status}</span></div>').format(**data))
        self.js.evaluate('synoptic.setTooltipHTML("%s", %s)' % (device, html))

    def closeEvent(self, event):
        # Get rid of all opened panels, otherwise the application will
        # not exit cleanly.
        super(TaurusSynopticWidget, self).closeEvent(event)
        for model, panel in self._panels.items():
            print "closing panel for", model
            panel.close()
class TaurusSynopticWidget(SynopticWidget, TaurusWidget):

    """A SynopticWidget that connects to TANGO in order to
    get updates for models (attributes)."""

    tooltip_trigger = QtCore.pyqtSignal(str)

    def __init__(self, parent=None, **kwargs):
        super(TaurusSynopticWidget, self).__init__(parent=parent)
        Manager().setSerializationMode(TaurusSerializationMode.Concurrent)
        self.tooltip_trigger.connect(self._update_device_tooltip)
        self._panels = {}

    def setModel(self, image, section=None):
        self.set_url(image)
        self.registry = Registry(self.attribute_listener,
                                 self.unsubscribe_listener)
        self.registry.start()
        self.js.plugin_command.connect(self.run_plugin_command)
        self.js.hovered.connect(self._hovered)
        # self._tooltip_data = {}
        # self.tooltip_registry = Registry(self._tooltip_updater)
        # self.tooltip_registry.start()

    def _hovered(self, sec, mods):
        attr = self.registry.get_listener(str(mods))

    def getModel(self):
        return self._url

    def closeEvent(self, event):
        "Clean things up when the widget is closed"
        self.registry.clear()
        self.registry.stop()
        self.registry.wait()
        self.registry = None

    def run_plugin_command(self, plugin, cmd, args):
        try:
            plugins = __import__("plugins.%s" % plugin, globals(),
                                 locals(), [cmd], -1)
        except ImportError as e:
            print "Could not initialize plugin '%s'!" % plugin
            print e
            return ""
        return getattr(plugins, cmd)(self, args)

    def handle_subscriptions(self, models=[]):
        if self.registry:
            self.registry.subscribe(models)

    def unsubscribe_listener(self, unsubscribed):
        """Tell the synoptic about unsubscribed models. This is
        needed because it's all asunchronous so it cannot be assumed
        that a model is really unsubscribed until it is."""
        classes = STATE_CLASSES[None]
        for model in unsubscribed:
            self.js.evaluate("synoptic.setClasses('model', %r, %s)" %
                             (model, classes))

    def filter_fragment(self, model, value):
        # the "fragment" is an optional part at the end of an eval attribute,
        # prefixed by a "#" and intended to make it possible to "filter" only
        # part of a spectrum or image through "slicing". It is up to the
        # client to do this, so we implement it here.
        frag = self.registry.eval_validator.getNames(model, fragment=True)[3]
        if frag:
            indices = frag[1:-1]
            try:
                # this is the special case where the slice is just an index
                # I'm not 100% that this should be allowed, but it's so useful!
                index = int(indices)
                return value[index]
            except ValueError:
                pass
            try:
                slice_ = [int(i) for i in indices.split(":")]
                return value[slice(*slice_)]
            except ValueError:
                pass
        return value

    def attribute_listener(self, model, evt_src, evt_type, evt_value):
        "Handle events"

        if evt_type == TaurusEventType.Error:
            return  # handle errors somehow
        if evt_type == TaurusEventType.Config:
            return  # need to do something here too
        value = evt_value.value

        # check for the presence of a "fragment" ending (e.g. ...#[3])
        if self.registry and self.registry.eval_validator.isValid(model):
            try:
                value = self.filter_fragment(model, value)
            except Exception:
                pass  # fragment is a taurus 4 feature

        # handle the value differently depending on what it is
        # TODO: clean this up!

        if isinstance(value, (DevState, PyTango.DevState, PyTango._PyTango.DevState)):
            classes = STATE_CLASSES[value]
            device, attr = model.rsplit("/", 1)
            if attr.lower() == "state":
                # this is the normal "State" attribute of the
                # device. Let's set the color of device models
                # too, for convenience.
                self.js.evaluate("synoptic.setClasses('model', %r, %s)" %
                                 (device, classes))
                self.js.evaluate("synoptic.setClasses('model', '%s/State', %s)" %
                                 (device, classes))
            else:
                # Apparently it's an attribute of type DevState
                # but it is not the normal "State" attribute.
                self.js.evaluate("synoptic.setClasses('model', %r, %s)" %
                                 (model, classes))
            self.js.evaluate("synoptic.setText('model', %r, '%s')" % (model, value))
        elif isinstance(value, (bool, np.bool_)):
            classes = {"boolean-true": bool(value),
                       "boolean-false": not value}
            self.js.evaluate("synoptic.setClasses('model', %r, %s)" %
                             (model, json.dumps(classes)))
            self.js.evaluate("synoptic.setText('model', %r, '%s')" %
                             (model, value))
        else:
            # everything else needs to be displayed as text
            quality = evt_value.quality
            if quality == PyTango.AttrQuality.ATTR_INVALID:
                text = "?"  # do something more sophisticated here
            else:
                try:
                    text = evt_src.displayValue(value)
                except AttributeError:
                    text = str(value)
            try:
                unit = evt_src.getConfig().unit
            except AttributeError:
                unit = None

            if unit in (None, "No unit"):
                unit = ""
            self.js.evaluate("synoptic.setText('model', %r, '%s %s')" %
                             (model, text, unit))

    def on_click(self, kind, name):
        """The default behavior is to mark a clicked device and to zoom to a
        clicked section.  Override this function if you need something
        else.
        """
        print "on_click", kind, name
        if kind == "model" and self.registry.device_validator.isValid(name):
            self.select(kind, [name])
            self.emit(Qt.SIGNAL("graphicItemSelected(QString)"), name)
        elif kind == "section":
            self.zoom_to(kind, name)
        else:
            self.unselect_all()

    def get_device_panel(self, device):
        """Override to change which panel is opened for a given device
        name. Return a widget class, a widget, or None if you're
        handling the panel yourself. TaurusDevicePanel is a reasonable
        fallback.
        """
        return TaurusDevicePanel

    def on_rightclick(self, kind, name):
        "The default behavior for right clicking a device is to open a panel."
        if kind == "model" and self.registry.device_validator.isValid(name):
            if name.lower() in self._panels:

                widget = self._panels[name.lower()]
                print "Found existing panel for %s:" % name, widget
                if not widget.isVisible():
                    widget.show()
                widget.activateWindow()
                widget.raise_()
                return


            # check if we recognise the class of the device
            widget = self.get_device_panel(name)

            if not widget:
                # assume that the widget is handled somewhere else
                return
            if isclass(widget):
                # assume it's a widget class
                widget = widget()
                try:
                    # try to set the model
                    widget.setModel(name)
                except AttributeError:
                    pass

            widget.setWindowTitle(name)
            # monkey patch to cleanup on close...
            widget.closeEvent = lambda _: self._cleanup_panel(widget)
            # keep a reference to the widget
            self._panels[name.lower()] = widget
            widget.show()

    def _cleanup_panel(self, w):
        """In the long run it seems like a good idea to try and clean up
        closed panels. In particular, the Taurus polling thread can
        become pretty bogged down."""
        if self.registry:
            with self.registry.lock:
                print "cleaning up panel for", w.getModel(), "..."
                self._panels.pop(str(w.getModel()).lower(), None)
                w.setModel(None)
                print "done!"

    # Note: the tooltip stuff is broken and not currently in use.
    # Currently there is only the default tooltip which displays the
    # model name.

    def on_tooltip(self, models):
        # FIXME: looks like tooltip listeners aren't cleaned up until a new
        # tooltip is displayed. This seems wasteful.
        if models:
            all_models = []
            for model in models:
                if self.registry.device_validator.isValid(model):
                    all_models.append(model + "/State")
                    all_models.append(model + "/Status")
                else:
                    all_models.append(model)
            try:
                self.tooltip_registry.subscribe(all_models)
            except ValueError:
                pass
            self._tooltip_data = dict((str(model), {}) for model in models)
        else:
            self.tooltip_registry.subscribe()
            self._tooltip_data.clear()

    def _tooltip_updater(self, model, attr_value):
        value = attr_value.value
        device, attr = model.rsplit("/", 1)
        if attr in ("State", "Status"):
            if attr == "Status":
                # hack to keep newlines
                value = value.replace("\n", "<br>")
            self._tooltip_data.setdefault(device, {})[attr] = value
            #dev = evt_src.getParentObj()
            #info = dev.getHWObj().info()
            # self._tooltip_data[device]["Class"] = info.dev_class
            self.tooltip_trigger.emit(device)
        else:
            self._tooltip_data[model] = value

    def _update_device_tooltip(self, device):
        # TODO: redo this in a neat way.
        data = {"Class": "...", "State": "...", "Status": "..."}
        data.update(self._tooltip_data[device])
        html = json.dumps(
            ('<div>Class:&nbsp;<span>{Class}</span></div>' +
             '<div>State:&nbsp;<span class="{State}">{State}</span></div>' +
             '<div>Status:&nbsp;<span>{Status}</span></div>').format(**data))
        self.js.evaluate('synoptic.setTooltipHTML("%s", %s)' %
                         (device, html))

    def closeEvent(self, event):
        # Get rid of all opened panels, otherwise the application will
        # not exit cleanly.
        super(TaurusSynopticWidget, self).closeEvent(event)
        for model, panel in self._panels.items():
            print "closing panel for", model
            panel.close()