Source code for taurus.qt.qtgui.extra_guiqwt.curvesmodel

#!/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/>.
#
# ###########################################################################

"""
curvesmodel Model and view for new CurveItem configuration
"""

__all__ = ["TaurusCurveItemTableModel", "CurveItemConf", "CurveItemConfDlg"]

# raise UnimplementedError('Under Construction!')

import copy

from taurus.external.qt import Qt
from guiqwt.styles import CurveParam, AxesParam, update_style_attr
from guiqwt.builder import make

import taurus
from taurus.core.taurusexception import TaurusException
from taurus.qt.qtcore.mimetypes import (
    TAURUS_MODEL_LIST_MIME_TYPE,
    TAURUS_ATTR_MIME_TYPE,
)
from taurus.qt.qtgui.util.ui import UILoadable
from taurus.qt.qtgui.extra_guiqwt.styles import TaurusCurveParam

import qwt
import guiqwt


__guiqwt_version = list(map(int, guiqwt.__version__.split(".")[:3]))

AXIS_ID2NAME = {
    qwt.QwtPlot.yLeft: "left",
    qwt.QwtPlot.yRight: "right",
    qwt.QwtPlot.xBottom: "bottom",
    qwt.QwtPlot.xTop: "top",
}

# set some named constants
# columns:
NUMCOLS = 3
X, Y, TITLE = list(range(NUMCOLS))
SRC_ROLE = Qt.Qt.UserRole + 1


class Component(object):
    def __init__(self, src):
        self.display = ""
        self.icon = Qt.QIcon()
        self.ok = True
        self.processSrc(src)

    def processSrc(self, src):
        """processes the src and sets the values of display, icon and ok
        attributes"""

        if src is None:
            self.display, self.icon, self.ok = (
                "(Use indices)",
                Qt.QIcon(),
                True,
            )
            return
        src = str(src).strip()
        # empty
        if src == "":
            self.display, self.icon, self.ok = (
                "(Use indices)",
                Qt.QIcon(),
                True,
            )
            return
        # for formulas
        if src.startswith("="):
            # @todo: evaluate/validate the expression
            self.display, self.icon, self.ok = (
                src[1:].strip(),
                Qt.QIcon.fromTheme("accessories-calculator"),
                True,
            )
            return
        # for tango attributes
        try:
            attr = taurus.Attribute(src)
            dev = attr.getParentObj()
        except TaurusException:
            self.display, self.icon, self.ok = (
                src,
                Qt.QIcon.fromTheme("dialog-warning"),
                False,
            )
            return
        if not dev.isValidDev():
            self.display, self.icon, self.ok = (
                src,
                Qt.QIcon.fromTheme("network-error"),
                False,
            )
            return
        self.display, self.icon, self.ok = (
            attr.getSimpleName(),
            Qt.QIcon("logos:taurus.png"),
            True,
        )


[docs] class CurveItemConf(object): def __init__(self, taurusparam=None, curveparam=None, axesparam=None): if taurusparam is None: taurusparam = TaurusCurveParam() self.taurusparam = taurusparam if curveparam is None: curveparam = CurveParam() style = next(make.style) # cycle through colors and linestyles update_style_attr(style, curveparam) curveparam.line.width = 2 self.curveparam = curveparam self.axesparam = axesparam self.x = Component(taurusparam.xModel) self.y = Component(taurusparam.yModel) if not self.curveparam.label: self.curveparam.label = taurusparam.xModel def __repr__(self): ret = "CurveItemConf(xModel='%s', yModel='%s')" % ( self.taurusparam.xModel, self.taurusparam.yModel, ) return ret
[docs] @staticmethod def fromTaurusCurveItem(item): plot = item.plot() if plot is not None: axesparam = AxesParam() axesparam.update_param(item) return CurveItemConf( taurusparam=item.taurusparam, curveparam=item.curveparam, axesparam=axesparam, )
[docs] @staticmethod def fromAny(obj): """return a CurveItemConf from whatever given in input (if possible). Raises ValueError if not possible """ if isinstance(obj, CurveItemConf): return copy.deepcopy(obj) try: return CurveItemConf.fromTaurusCurveItem(obj) except Exception: raise try: return CurveItemConf(*obj) except Exception: pass raise ValueError("Cannot convert %s into a CurveItemConf" % repr(obj))
[docs] class TaurusCurveItemTableModel(Qt.QAbstractTableModel): """A Qt data model for describing curves""" dataChanged = Qt.pyqtSignal("QModelIndex", "QModelIndex") def __init__(self, curves=None): if curves is None: curves = [] curves = [CurveItemConf.fromAny(c) for c in curves] # convert curves super(TaurusCurveItemTableModel, self).__init__() self.ncolumns = NUMCOLS self.curves = curves
[docs] def dumpData(self): return copy.deepcopy(self.curves)
[docs] def rowCount(self, index=Qt.QModelIndex()): return len(self.curves)
[docs] def columnCount(self, index=Qt.QModelIndex()): return self.ncolumns
[docs] def data(self, index, role=Qt.Qt.DisplayRole): if not index.isValid() or not (0 <= index.row() < self.rowCount()): return None row = index.row() column = index.column() # Display Role if role == Qt.Qt.DisplayRole: if column == X: return str(self.curves[row].x.display) elif column == Y: return str(self.curves[row].y.display) elif column == TITLE: return str(self.curves[row].curveparam.label) else: return None elif role == Qt.Qt.DecorationRole: if column == X: return self.curves[row].x.icon elif column == Y: return self.curves[row].y.icon else: return None elif role == Qt.Qt.TextColorRole: if column == X: Qt.QColor(self.curves[row].x.ok and "green" or "red") elif column == Y: Qt.QColor(self.curves[row].y.ok and "green" or "red") else: return None elif role == SRC_ROLE: if column == X: return str(self.curves[row].taurusparam.xModel) elif column == Y: return str(self.curves[row].taurusparam.yModel) else: return None elif role == Qt.Qt.ToolTipRole: if column == X: return str(self.curves[row].taurusparam.xModel) elif column == Y: return str(self.curves[row].taurusparam.yModel) else: return None if role == Qt.Qt.EditRole: if column == X: return str(self.curves[row].taurusparam.xModel) elif column == Y: return str(self.curves[row].taurusparam.yModel) elif column == TITLE: return str(self.curves[row].curveparam.label) else: return None return None
[docs] def headerData(self, section, orientation, role=Qt.Qt.DisplayRole): if role == Qt.Qt.TextAlignmentRole: if orientation == Qt.Qt.Horizontal: return int(Qt.Qt.AlignLeft | Qt.Qt.AlignVCenter) return int(Qt.Qt.AlignRight | Qt.Qt.AlignVCenter) if role != Qt.Qt.DisplayRole: return None # So this is DisplayRole... if orientation == Qt.Qt.Horizontal: if section == X: return "X source" elif section == Y: return "Y Source" elif section == TITLE: return "Title" return None else: return str(section + 1)
[docs] def flags( self, index ): # use this to set the editable flag when fix is selected if not index.isValid(): return Qt.Qt.ItemIsEnabled column = index.column() if column in (X, Y): return Qt.Qt.ItemFlags( Qt.Qt.ItemIsEnabled | Qt.Qt.ItemIsEditable | Qt.Qt.ItemIsDragEnabled | Qt.Qt.ItemIsDropEnabled | Qt.Qt.ItemIsSelectable ) elif column == TITLE: return Qt.Qt.ItemFlags( Qt.Qt.ItemIsEnabled | Qt.Qt.ItemIsEditable | Qt.Qt.ItemIsDragEnabled ) return Qt.Qt.ItemFlags( Qt.Qt.ItemIsEnabled | Qt.Qt.ItemIsEditable | Qt.Qt.ItemIsDragEnabled )
[docs] def setData(self, index, value=None, role=Qt.Qt.EditRole): if index.isValid() and (0 <= index.row() < self.rowCount()): row = index.row() curve = self.curves[row] column = index.column() if column == X: curve.taurusparam.xModel = value curve.x.processSrc(value) elif column == Y: curve.taurusparam.yModel = value curve.y.processSrc(value) elif column == TITLE: curve.curveparam.label = value self.dataChanged.emit(index, index) return True return False
[docs] def insertRows(self, position=None, rows=1, parentindex=None): if position is None: position = self.rowCount() if parentindex is None: parentindex = Qt.QModelIndex() self.beginInsertRows(parentindex, position, position + rows - 1) slice = [CurveItemConf() for i in range(rows)] self.curves = self.curves[:position] + slice + self.curves[position:] self.endInsertRows() return True
[docs] def removeRows(self, position, rows=1, parentindex=None): if parentindex is None: parentindex = Qt.QModelIndex() self.beginResetModel() self.beginRemoveRows(parentindex, position, position + rows - 1) self.curves = self.curves[:position] + self.curves[position + rows :] self.endRemoveRows() self.endResetModel() return True
[docs] def clearAll(self): self.removeRows(0, self.rowCount())
[docs] def mimeTypes(self): result = list(Qt.QAbstractTableModel.mimeTypes(self)) result += [TAURUS_ATTR_MIME_TYPE, "text/plain"] return result
[docs] def dropMimeData(self, data, action, row, column, parent): if row == -1: if parent.isValid(): row = parent.row() else: row = parent.rowCount() if column == -1: if parent.isValid(): column = parent.column() else: column = parent.columnCount() if data.hasFormat(TAURUS_ATTR_MIME_TYPE): model = bytes(data.data(TAURUS_ATTR_MIME_TYPE)).decode("utf-8") self.setData(self.index(row, column), value=model) return True elif data.hasFormat(TAURUS_MODEL_LIST_MIME_TYPE): d = bytes(data.data(TAURUS_MODEL_LIST_MIME_TYPE)) models = d.decode("utf-8").split() if len(models) == 1: self.setData(self.index(row, column), value=models[0]) return True else: self.insertRows(row, len(models)) for i, m in enumerate(models): self.setData(self.index(row + i, column), value=m) return True elif data.hasText(): self.setData(self.index(row, column), data.text()) return True return False
[docs] def mimeData(self, indexes): mimedata = Qt.QAbstractTableModel.mimeData(self, indexes) if len(indexes) == 1: data = self.data(indexes[0], role=SRC_ROLE) # mimedata.setData(TAURUS_ATTR_MIME_TYPE, data) mimedata.setText(data) return mimedata
# mimedata.setData()
[docs] @UILoadable(with_ui="ui") class CurveItemConfDlg(Qt.QWidget): """A configuration dialog for creating new CurveItems. Provides a browser for Taurus models and an editable table for the sources and title of data """ dataChanged = Qt.pyqtSignal("QModelIndex", "QModelIndex") applied = Qt.pyqtSignal() def __init__(self, parent=None, curves=None, showXcol=True): super(CurveItemConfDlg, self).__init__(parent) self.loadUi() self._showXcol = showXcol if curves is None: curves = [CurveItemConf()] # add the NeXusWidget if extra_nexus is available try: from taurus.qt.qtgui.extra_nexus import TaurusNeXusBrowser nexusWidget = TaurusNeXusBrowser() self.ui.tabWidget.addTab(nexusWidget, "NeXus") except Exception: import taurus.core.util.log _logger = taurus.core.util.log.Logger("CurveItemConfDlg") _logger.warning("TaurusNeXusBrowser not available") self.traceback() self.ui.tangoTree.setButtonsPos(Qt.Qt.RightToolBarArea) # @todo: The action for this button is not yet implemented self.ui.reloadBT.setEnabled(False) self.model = TaurusCurveItemTableModel(curves) table = self.ui.curvesTable table.setModel(self.model) table.setColumnHidden(X, not self._showXcol) # host import taurus # @todo: I get "UnboundLocalError: # local variable 'taurus' referenced before # assignment" if I don't import taurus again here host = taurus.Authority().getNormalName() self.ui.tangoTree.setModel(host) # Connections self.ui.applyBT.clicked.connect(self.onApply) self.ui.reloadBT.clicked.connect(self.onReload) self.ui.cancelBT.clicked.connect(self.close) self.ui.tangoTree.addModels.connect(self.onModelsAdded) self.ui.curvesTable.customContextMenuRequested.connect( self.onTableContextMenu )
[docs] def onTableContextMenu(self, pos): index = self.ui.curvesTable.indexAt(pos) row = index.row() menu = Qt.QMenu(self.ui.curvesTable) if row >= 0: menu.addAction( Qt.QIcon.fromTheme("list-remove"), "Remove this curve", self._onRemoveThisAction, ) menu.addAction( Qt.QIcon.fromTheme("edit-clear"), "Clear all", self.model.clearAll ) menu.addAction( Qt.QIcon.fromTheme("list-add"), "Add new row", self.model.insertRows, ) if row >= 0: menu.addAction( Qt.QIcon.fromTheme("preferences-system"), "Edit parameters of this curve...", self._onEditParsAction, ) menu.exec_(Qt.QCursor.pos())
def _onRemoveThisAction(self): row = self.ui.curvesTable.currentIndex().row() self.model.removeRows(row) def _onEditParsAction(self): row = self.ui.curvesTable.currentIndex().row() if row >= 0: c = self.model.curves[row] params = [ p for p in (c.curveparam, c.axesparam, c.taurusparam) if p is not None ] if params: from guidata.dataset.datatypes import DataSetGroup group = DataSetGroup( params, "Parameters for curve %s" % c.curveparam.label ) group.edit() c.x.processSrc(c.taurusparam.xModel) c.y.processSrc(c.taurusparam.yModel) self.dataChanged.emit( self.model.index(row, 0), self.model.index(row, self.model.rowCount() - 1), )
[docs] def onModelsAdded(self, models): nmodels = len(models) rowcount = self.model.rowCount() self.model.insertRows(rowcount, nmodels) for i, m in enumerate(models): self.model.setData(self.model.index(rowcount + i, Y), value=m) title = self.model.data( self.model.index(rowcount + i, Y) ) # the display data self.model.setData( self.model.index(rowcount + i, TITLE), value=title )
[docs] def getCurveItemConfs(self): return self.model.dumpData()
[docs] @staticmethod def showDlg(parent=None, curves=None): """Static method that launches a modal dialog containing a CurveItemConfDlg :param parent: parent for the dialog :type parent: QObject :return: Returns a models,ok tuple. models can be either a list of models or a QMimeData object, depending on `asMimeData`. ok is True if the dialog was accepted (by clicking on the "update" button) and False otherwise :rtype: list,bool or QMimeData,bool """ dlg = Qt.QDialog(parent) dlg.setWindowTitle("Curves Selection") layout = Qt.QVBoxLayout() w = CurveItemConfDlg(parent=parent, curves=curves) layout.addWidget(w) dlg.setLayout(layout) w.applied.connect(dlg.accept) w.ui.cancelBT.clicked.connect(dlg.close) dlg.exec_() return w.getCurveItemConfs(), (dlg.result() == dlg.Accepted)
[docs] def onApply(self): self.applied.emit()
[docs] def onReload(self): print("RELOAD!!! (todo)")