Source code for taurus.qt.qtgui.panel.taurusdevicepanel

#!/usr/bin/env python

# ###########################################################################
#
# This file is part of Taurus
#
# http://taurus-scada.org
#
# Copyright 2011 CELLS / ALBA Synchrotron, Bellaterra, Spain
#
# Taurus is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Taurus is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Taurus.  If not, see <http://www.gnu.org/licenses/>.
#
# ###########################################################################

"""
TaurusDevicePanel.py:
"""

import re
import traceback
import click

import taurus
from taurus.external.qt import Qt

from taurus import tauruscustomsettings
from taurus.core.taurusbasetypes import TaurusElementType
from taurus.core.taurusattribute import TaurusAttribute
from taurus.core.taurusdevice import TaurusDevice
from taurus.qt.qtgui.container import TaurusWidget
from taurus.qt.qtgui.taurusgui import TaurusGui
from taurus.qt.qtgui.display import TaurusLabel
from taurus.qt.qtgui.panel.taurusform import TaurusForm
from taurus.qt.qtgui.panel.taurusform import TaurusCommandsForm
from taurus.qt.qtgui.util.ui import UILoadable
from taurus.qt.qtgui.icon import getCachedPixmap
import taurus.cli.common


__docformat__ = "restructuredtext"


# ###########################################################################
# TaurusDevicePanel (from Vacca)

# Variables that control TaurusDevicePanel shape

STATUS_HEIGHT = 170
SPLIT_SIZES = [15, 65, 20]
IMAGE_SIZE = (200, 100)  # (width,height)

# Helper methods


def matchCl(m, k):  # TODO: Tango-centric
    return re.match(m.lower(), k.lower())


def searchCl(m, k):  # TODO: Tango-centric
    if m.startswith("^") or m.startswith("(^") or "(?!^" in m:
        return matchCl(m, k)
    return re.search(m.lower(), k.lower())


def get_regexp_dict(dct, key, default=None):  # TODO: Tango-centric
    for k, v in dct.items():  # Trying regular expression match
        if matchCl(k, key):
            return v
    for k, v in dct.items():  # If failed, trying if key is contained
        if k.lower() in key.lower():
            return v
    if default is not None:
        return default
    else:
        raise Exception("KeyNotFound:%s" % key)


def get_eqtype(dev):  # TODO: Tango-centric
    """It extracts the eqtype from a device name like
    domain/family/eqtype-serial
    """
    try:
        eq = str(dev).split("/")[-1].split("-", 1)[0].upper()
    except Exception:
        eq = ""
    return eq


def str_to_filter(seq):  # TODO: Tango-centric
    try:
        f = eval(seq)
    except Exception:
        f = seq
    if isinstance(f, str):
        return {".*": [f]}
    elif isinstance(f, list):
        return {".*": f}
    else:
        return f


# Stacked palette


def get_White_palette():
    palette = Qt.QPalette()

    brush = Qt.QBrush(Qt.QColor(255, 255, 255))
    brush.setStyle(Qt.Qt.SolidPattern)
    palette.setBrush(Qt.QPalette.Active, Qt.QPalette.Base, brush)

    brush = Qt.QBrush(Qt.QColor(255, 255, 255))
    brush.setStyle(Qt.Qt.SolidPattern)
    palette.setBrush(Qt.QPalette.Active, Qt.QPalette.Window, brush)

    brush = Qt.QBrush(Qt.QColor(255, 255, 255))
    brush.setStyle(Qt.Qt.SolidPattern)
    palette.setBrush(Qt.QPalette.Inactive, Qt.QPalette.Base, brush)

    brush = Qt.QBrush(Qt.QColor(255, 255, 255))
    brush.setStyle(Qt.Qt.SolidPattern)
    palette.setBrush(Qt.QPalette.Inactive, Qt.QPalette.Window, brush)

    brush = Qt.QBrush(Qt.QColor(255, 255, 255))
    brush.setStyle(Qt.Qt.SolidPattern)
    palette.setBrush(Qt.QPalette.Disabled, Qt.QPalette.Base, brush)

    brush = Qt.QBrush(Qt.QColor(255, 255, 255))
    brush.setStyle(Qt.Qt.SolidPattern)
    palette.setBrush(Qt.QPalette.Disabled, Qt.QPalette.Window, brush)
    return palette


# TaurusDevicePanel class


[docs]class TaurusDevicePanel(TaurusWidget): """ TaurusDevPanel is a Taurus Application inspired in Jive and Atk Panel. It Provides a Device selector and a panel for displaying information from the selected device. """ # TODO: Tango-centric (This whole class should be called TangoDevicePanel) # TODO: a scheme-agnostic base class should be implemented READ_ONLY = False # A dictionary like {device_regexp:[attribute_regexps]} _attribute_filter = {} # A dictionary like {device_regexp:[(command_regexp,default_args)]} _command_filter = {} _icon_map = {} # A dictionary like {device_regexp:pixmap_url}
[docs] @classmethod def setIconMap(klass, filters): """A dictionary like {device_regexp:pixmap_url}""" klass._icon_map = filters
[docs] @classmethod def getIconMap(klass): return klass._icon_map
[docs] @classmethod def setAttributeFilters(klass, filters): """ It will set the attribute filters filters will be like: {device_regexp:[attribute_regexps]} example: {'.*/VGCT-.*': ['ChannelState','p[0-9]']} """ klass._attribute_filter.update(filters)
[docs] @classmethod def getAttributeFilters(klass): return klass._attribute_filter
[docs] @classmethod def setCommandFilters(klass, filters): """ It will set the command filters filters will be like: {device_regexp:[command_regexps]} example:: {'.*/IPCT-.*': ( ('setmode',('SERIAL','LOCAL','STEP','FIXED','START','PROTECT')), ('onhv1',()), ('offhv1',()), ('onhv2',()), ('offhv2',()), ('sendcommand',()) ), } """ klass._command_filter.update(filters)
[docs] @classmethod def getCommandFilters(klass): return klass._command_filter
########################################################################### def __init__(self, parent=None, model=None, palette=None, bound=True): TaurusWidget.__init__(self, parent) if palette: self.setPalette(palette) self.setLayout(Qt.QGridLayout()) self.bound = bound self._dups = [] self.setWindowTitle("TaurusDevicePanel") self._label = Qt.QLabel() self._label.font().setBold(True) self._stateframe = TaurusWidget(self) self._stateframe.setLayout(Qt.QGridLayout()) self._stateframe.layout().addWidget( Qt.QLabel("State"), 0, 0, Qt.Qt.AlignCenter ) self._statelabel = TaurusLabel(self._stateframe) self._statelabel.setMinimumWidth(100) self._statelabel.setBgRole("rvalue") self._stateframe.layout().addWidget( self._statelabel, 0, 1, Qt.Qt.AlignCenter ) self._statusframe = Qt.QScrollArea(self) self._status = TaurusLabel(self._statusframe) self._status.setBgRole("none") self._status.setAlignment(Qt.Qt.AlignLeft) self._status.setFixedHeight(2000) self._status.setFixedWidth(5000) # self._statusframe.setFixedHeight(STATUS_HEIGHT) self._statusframe.setHorizontalScrollBarPolicy(Qt.Qt.ScrollBarAlwaysOn) self._statusframe.setVerticalScrollBarPolicy(Qt.Qt.ScrollBarAlwaysOn) self._statusframe.setWidget(self._status) self._statusframe.setPalette(get_White_palette()) self._attrsframe = Qt.QTabWidget(self) # Horizontal will not allow to show labels of attributes! self._splitter = Qt.QSplitter(Qt.Qt.Vertical, self) self._attrs, self._comms = None, None self.layout().addWidget(self._splitter, 0, 0) self._header = Qt.QFrame() self._header.setFixedHeight(int(1.1 * IMAGE_SIZE[1])) self._header.setLayout(Qt.QGridLayout()) self._dup = Qt.QPushButton() qpixmap = Qt.QIcon("actions:window-new.svg") self._dup.setIcon(Qt.QIcon(qpixmap)) self._dup.setIconSize(Qt.QSize(15, 15)) self._dup.pressed.connect(self.duplicate) self._image = Qt.QLabel() self._header.layout().addWidget( self._image, 0, 0, 2, 1, Qt.Qt.AlignCenter ) self._header.layout().addWidget(self._label, 0, 1, Qt.Qt.AlignLeft) self._header.layout().addWidget( self._stateframe, 1, 1, 1, 2, Qt.Qt.AlignLeft ) self._header.layout().addWidget(self._dup, 0, 2, Qt.Qt.AlignRight) self._splitter.insertWidget(0, self._header) self._splitter.insertWidget(1, self._attrsframe) self._splitter.insertWidget(2, self._statusframe) self._splitter.setSizes(SPLIT_SIZES) [ self._splitter.setStretchFactor(i, v) for i, v in enumerate(SPLIT_SIZES) ] self._splitter.setCollapsible(0, False) self._splitter.setCollapsible(1, False) if model: self.setModel(model)
[docs] def loadConfigFile(self, ifile=None): self.debug("In TaurusDevicePanel.loadConfigFile(%s)" % ifile) if isinstance(ifile, str) and ifile.endswith(".py"): import importlib spec = importlib.util.spec_from_file_location("config_file", ifile) config_file = importlib.util.module_from_spec(spec) spec.loader.exec_module(config_file) af, cf, im = [ getattr(config_file, x, None) for x in ("AttributeFilters", "CommandFilters", "IconMap") ] if af is not None: self.setAttributeFilters(af) if cf is not None: self.setCommandFilters(cf) if im is not None: self.setIconMap(im) else: TaurusWidget.loadConfigFile(self, ifile) self.debug("AttributeFilters are:\n%s" % self.getAttributeFilters())
[docs] def duplicate(self): self._dups.append(TaurusDevicePanel(bound=False)) self._dups[-1].setModel(self.getModel()) self._dups[-1].show()
[docs] @Qt.pyqtSlot("QString") def setModel(self, model, pixmap=None, **kwargs): model, modelclass, raw = str(model).strip(), "", model if model: model = model and model.split()[0] or "" modelclass = taurus.Factory().findObjectClass(model) self.trace( "In TaurusDevicePanel.setModel(%s(%s),%s)" % (raw, modelclass, pixmap) ) if model == self.getModel(): return elif raw is None or not model or not modelclass: if self.getModel(): self.detach() return elif issubclass(modelclass, TaurusAttribute): # if model.lower().endswith('/state'): model = model.rsplit("/", 1)[0] # TODO: Tango-centric elif not issubclass(modelclass, TaurusDevice): self.warning("TaurusDevicePanel accepts only Device models") return try: taurus.Device(model).ping() # TODO: Tango-centric if self.getModel(): # Do not dettach previous model before pinging the new one # (fail message will be shown at except: clause) self.detach() TaurusWidget.setModel(self, model) model_obj = self.getModelObj() simple_name = model_obj.getSimpleName().upper() self.setWindowTitle(simple_name) model = self.getModel() self._label.setToolTip(model_obj.getFullName().upper()) self._label.setText(simple_name) font = self._label.font() font.setPointSize(15) self._label.setFont(font) if pixmap is None and self.getIconMap(): for k, v in self.getIconMap().items(): if searchCl(k, model): pixmap = v if pixmap is not None: qpixmap = Qt.QPixmap(pixmap) if qpixmap.height() > 0.9 * IMAGE_SIZE[1]: qpixmap = qpixmap.scaledToHeight(0.9 * IMAGE_SIZE[1]) if qpixmap.width() > 0.9 * IMAGE_SIZE[0]: qpixmap = qpixmap.scaledToWidth(0.9 * IMAGE_SIZE[0]) else: logo = getattr( tauruscustomsettings, "ORGANIZATION_LOGO", "logos:taurus.png", ) qpixmap = getCachedPixmap(logo) self._image.setPixmap(qpixmap) if hasattr(self, "_statelabel"): self._statelabel.setModel( model + "/state" ) # TODO: Tango-centric self._status.setModel(model + "/status") # TODO: Tango-centric try: self._attrsframe.clear() filters = get_regexp_dict( TaurusDevicePanel._attribute_filter, model, [".*"] ) if hasattr(filters, "keys"): filters = list(filters.items()) # Dictionary! if filters and isinstance( filters[0], (list, tuple) ): # Mapping self._attrs = [] for tab, attrs in filters: self._attrs.append( self.get_attrs_form( device=model, filters=attrs, parent=self ) ) self._attrsframe.addTab(self._attrs[-1], tab) else: if self._attrs and isinstance(self._attrs, list): self._attrs = self._attrs[0] self._attrs = self.get_attrs_form( device=model, form=self._attrs, filters=filters, parent=self, ) if self._attrs: self._attrsframe.addTab(self._attrs, "Attributes") if not TaurusDevicePanel.READ_ONLY: self._comms = self.get_comms_form(model, self._comms, self) if self._comms: self._attrsframe.addTab(self._comms, "Commands") if SPLIT_SIZES: self._splitter.setSizes(SPLIT_SIZES) except Exception: self.warning(traceback.format_exc()) qmsg = Qt.QMessageBox( Qt.QMessageBox.Critical, "%s Error" % model, "%s not available" % model, Qt.QMessageBox.Ok, self, ) qmsg.setDetailedText(traceback.format_exc()) qmsg.show() except Exception: self.warning(traceback.format_exc()) qmsg = Qt.QMessageBox( Qt.QMessageBox.Critical, "%s Error" % model, "%s not available" % model, Qt.QMessageBox.Ok, self, ) qmsg.show() return
[docs] def detach(self): self.trace("In TaurusDevicePanel(%s).detach()" % self.getModel()) _detached = [] # long imports to avoid comparison problems in the isinstance below import taurus.qt.qtgui.container import taurus.qt.qtgui.base def detach_recursive(obj): if obj in _detached: return if isinstance(obj, taurus.qt.qtgui.container.TaurusBaseContainer): for t in obj.taurusChildren(): detach_recursive(t) if obj is not self and isinstance( obj, taurus.qt.qtgui.base.TaurusBaseWidget ): try: if getattr(obj, "model", None): # self.debug('detaching %s from %s'%(obj,obj.model)) obj.setModel([] if isinstance(obj, TaurusForm) else "") except Exception: self.warning("detach of %s failed!" % obj) self.warning(traceback.format_exc()) _detached.append(obj) detach_recursive(self) try: self._label.setText("") self._label.setToolTip("") if hasattr(self, "_statelabel"): self._statelabel.setModel("") self._status.setModel("") self._image.setPixmap(Qt.QPixmap()) except Exception: self.warning(traceback.format_exc())
[docs] def get_attrs_form(self, device, form=None, filters=None, parent=None): filters = filters or get_regexp_dict( TaurusDevicePanel._attribute_filter, device, [".*"] ) self.trace( "In TaurusDevicePanel.get_attrs_form(%s,%s)" % (device, filters) ) allattrs = sorted( str(a) for a in taurus.Device(device).get_attribute_list() if str(a).lower() not in ("state", "status") ) attrs = [] for a in filters: for t in allattrs: if a and searchCl(a.strip(), t.strip()): aname = "%s/%s" % (device, t) if aname not in attrs: attrs.append(aname) if attrs: # self.trace( 'Matching attributes are: %s' % str(attrs)[:100]) if form is None: form = TaurusForm(parent) elif hasattr(form, "setModel"): form.setModel([]) # Configuring the TauForm: form.setWithButtons(False) form.setWindowTitle(device) try: form.setModel(attrs) except Exception: self.warning( "TaurusDevicePanel.ERROR: Unable to setModel " + "for TaurusDevicePanel.attrs_form!!: %s", traceback.format_exc(), ) return form else: return None
[docs] def get_comms_form(self, device, form=None, parent=None): self.trace("In TaurusDevicePanel.get_comms_form(%s)" % device) params = get_regexp_dict(TaurusDevicePanel._command_filter, device, []) # If filters are defined only listed devices will show commands if TaurusDevicePanel._command_filter and not params: self.debug( "TaurusDevicePanel.get_comms_form(%s): " + "By default an unknown device type will display no commands", device, ) return None if not form: form = TaurusCommandsForm(parent) elif hasattr(form, "setModel"): form.setModel("") try: form.setModel(device) if params: form.setSortKey( lambda x, vals=[s[0].lower() for s in params]: vals.index( x.cmd_name.lower() ) if str(x.cmd_name).lower() in vals else 100 ) form.setViewFilters( [ lambda c: str(c.cmd_name).lower() not in ("state", "status") and any( searchCl(s[0], str(c.cmd_name)) for s in params ) ] ) form.setDefaultParameters( dict( (k, v) for k, v in ( params if not hasattr(params, "items") else list(params.items()) ) if v ) ) for wid in form._cmdWidgets: if not hasattr(wid, "getCommand") or not hasattr( wid, "setDangerMessage" ): continue if re.match( ".*(on|off|init|open|close).*", str(wid.getCommand().lower()), ): wid.setDangerMessage( "This action may affect other systems!" ) # form._splitter.setStretchFactor(1,70) # form._splitter.setStretchFactor(0,30) form._splitter.setSizes([80, 20]) except Exception: self.warning( "Unable to setModel for TaurusDevicePanel.comms_form!!: %s" % traceback.format_exc() ) return form
def filterNonExported(obj): # TODO: Tango-centric from taurus.core.tango.tangodatabase import TangoDevInfo if not isinstance(obj, TangoDevInfo) or obj.exported(): return obj return None
[docs]@UILoadable(with_ui="_ui") class TaurusDevPanel(TaurusGui): """ TaurusDevPanel is a Taurus Application inspired in Jive and Atk Panel. It Provides a Device selector and several dockWidgets for interacting and displaying information from the selected device. """ HELP_MENU_ENABLED = False def __init__(self, parent=None, designMode=False): TaurusGui.__init__(self, parent) self.loadUi() # setting up the device Tree. # @todo: This should be done in the ui file when the # TaurusDatabaseTree Designer plugin is available import taurus.qt.qtgui.tree TaurusDbTreeWidget = taurus.qt.qtgui.tree.TaurusDbTreeWidget self.deviceTree = TaurusDbTreeWidget( perspective=TaurusElementType.Device ) self.deviceTree.getQModel().setSelectables( [TaurusElementType.Member] ) # TODO: Tango-centric # self.deviceTree.insertFilter(filterNonExported) self.setCentralWidget(self.deviceTree) # register subwidgets for configuration purposes # self.registerConfigDelegate(self.taurusAttrForm) # self.registerConfigDelegate(self.deviceTree) self.registerConfigDelegate(self._ui.taurusCommandsForm) self.loadSettings() self.createActions() # self.addToolBar(self.basicTaurusToolbar()) self.deviceTree.currentItemChanged.connect(self.onItemSelectionChanged) self.updatePerspectivesMenu() if not designMode: self.splashScreen().finish(self)
[docs] def createActions(self): """create actions""" # View Menu self.showAttrAction = self.viewMenu.addAction( self._ui.attrDW.toggleViewAction() ) self.showCommandsAction = self.viewMenu.addAction( self._ui.commandsDW.toggleViewAction() )
[docs] def setTangoHost(self, host): """extended from :class:setTangoHost""" TaurusGui.setTangoHost(self, host) self.deviceTree.setModel(host)
# self.deviceTree.insertFilter(filterNonExported)
[docs] def onItemSelectionChanged(self, current, previous): itemData = current.itemData() from taurus.core.tango.tangodatabase import TangoDevInfo if isinstance(itemData, TangoDevInfo): # TODO: Tango-centric self.onDeviceSelected(itemData)
[docs] def onDeviceSelected(self, devinfo): devname = devinfo.name() msg = 'Connecting to "%s"...' % devname self.statusBar().showMessage(msg) # abort if the device is not exported if not devinfo.exported(): # TODO: Tango-centric msg = 'Connection to "%s" failed (not exported)' % devname self.statusBar().showMessage(msg) self.info(msg) Qt.QMessageBox.warning(self, "Device unreachable", msg) self.setModel("") return self.setDevice(devname)
[docs] def setDevice(self, devname): """set the device to be shown by the commands and attr forms""" self.setModel(devname) self._ui.attrDW.setWindowTitle("Attributes - %s" % devname) self._ui.commandsDW.setWindowTitle("Commands - %s" % devname)
[docs] def setModel(self, name, **kwargs): """Reimplemented to delegate model to the commands and attrs forms""" TaurusGui.setModel(self, name) self._ui.taurusAttrForm.setModel(name) self._ui.taurusCommandsForm.setModel(name)
[docs] @classmethod def getQtDesignerPluginInfo(cls): ret = TaurusGui.getQtDesignerPluginInfo() ret["module"] = "taurus.qt.qtgui.panel" return ret
# ========================================================================= # Launchers # ========================================================================= @click.command("device") @click.argument("dev", nargs=1, default=None, required=False) @click.option( "-f", "--filter", help=( "regexp to filter for attributes to show " + "(it can be passed more than once)" ), multiple=True, ) @taurus.cli.common.config_file def device_cmd(dev, filter, config_file): """Show a Device Panel""" import sys from taurus.qt.qtgui.application import TaurusApplication app = TaurusApplication(cmd_line_parser=None, app_name="TaurusDevicePanel") w = TaurusDevicePanel() w.show() if dev is None: from taurus.qt.qtgui.panel import TaurusModelChooser models, ok = TaurusModelChooser.modelChooserDlg( w, selectables=[TaurusElementType.Member], singleModel=True ) dev = models[0] if ok and models else None if config_file is not None: w.loadConfigFile(config_file) elif dev and filter: w.setAttributeFilters({dev: list(filter)}) w.setModel(dev) sys.exit(app.exec_()) @click.command("panel") @click.option( "--tango-host", "tango_host", default=None, help="Tango Host name (the system's default if not given)", ) @click.option("-d", "--dev", default=None, help="pre-selected device") @click.option( "-t", "--trend", is_flag=True, help="Create a temporal trend widget" ) def panel_cmd(tango_host, dev, trend): """ Show a TaurusPanel (a Taurus application inspired in Jive and Atk Panel) """ from taurus.qt.qtgui.application import TaurusApplication import sys app = TaurusApplication(cmd_line_parser=None, app_name="tauruspanel") w = TaurusDevPanel() if tango_host is None: from taurus import Factory tango_host = Factory("tango").getAuthority().getFullName() w.setTangoHost(tango_host) if dev is not None: w.setDevice(dev) if trend is True: # TODO: Allow to select TaurusTrend back-end try: from taurus_pyqtgraph import TaurusTrend w.info("Using tpg back-end") except Exception: TaurusTrend = None w.warning("TaurusTrend widget is not available") if TaurusTrend is not None: plot = TaurusTrend() w.createPanel(plot, "TaurusTrend", permanent=False) w.show() sys.exit(app.exec_())