#!/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/>.
#
# ###########################################################################
"""
itemsmodel Model and view for new CurveItem configuration
"""
# raise UnimplementedError('Under Construction!')
import copy
from taurus.external.qt import Qt
import taurus
from taurus.core.taurusbasetypes import TaurusElementType
from taurus.core.taurusexception import TaurusException
from taurus.qt.qtcore.mimetypes import TAURUS_MODEL_LIST_MIME_TYPE
from taurus.qt.qtcore.mimetypes import TAURUS_ATTR_MIME_TYPE
from taurus.qt.qtcore.mimetypes import TAURUS_MODEL_MIME_TYPE
from taurus.qt.qtgui.icon import getElementTypeIcon
# set some named constants
SRC_ROLE = Qt.Qt.UserRole + 1
[docs]
class TaurusModelItem(object):
"""An item object for :class:`TaurusModelModel`. Exposes `display` `icon`
and `ok` attributes which are calculated and kept in synch with the
property `src`
"""
def __init__(self, src=None, display=None):
self.icon = Qt.QIcon()
self.ok = True
self._src = None
self.setSrc(src)
if display is not None:
self.display = display
def __deepcopy__(self, memo):
cls = self.__class__
result = cls.__new__(cls)
memo[id(self)] = result
result.icon = Qt.QIcon(self.icon)
result.ok = copy.deepcopy(self.ok, memo)
result._src = copy.deepcopy(self._src, memo)
result.display = copy.deepcopy(self.display, memo)
return result
def __repr__(self):
ret = "TaurusModelItem('%s')" % (self.display)
return ret
[docs]
def getSrc(self):
"""getter for src."""
return self._src
[docs]
def setSrc(self, src):
"""processes the src and sets the values of _src, display, icon and ok
attributes"""
if src is None:
self._src, self.display, self.icon, self.ok = (
"",
"(Empty)",
Qt.QIcon(),
True,
)
return
src = str(src).strip()
if src == self._src:
return
self._src = src
# empty
if src == "":
self.display, self.icon, self.ok = "(Empty)", Qt.QIcon(), True
return
# TODO tango-centric. For tango devices
try:
dev = taurus.Device(src)
if dev.getDeviceProxy() is not None:
self.display, self.icon, self.ok = (
dev.getSimpleName(),
getElementTypeIcon(TaurusElementType.Device),
True,
)
return
else:
self.display, self.icon, self.ok = (
src,
Qt.QIcon.fromTheme("network-error"),
False,
)
return
except Exception:
# 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
except Exception:
# @todo: this catchall except is here as an emergency bugfix,
# but should probably be narrowed to PyTango DevFailed.
self.display, self.icon, self.ok = (
src,
Qt.QIcon.fromTheme("network-error"),
False,
)
return
if (
taurus.getSchemeFromName(attr.getFullName()) == "tango"
and dev.getDeviceProxy() is None
):
self.display, self.icon, self.ok = (
src,
Qt.QIcon.fromTheme("network-error"),
False,
)
return
self.display, self.icon, self.ok = (
attr.getSimpleName(),
getElementTypeIcon(TaurusElementType.Attribute),
True,
)
# properties
src = property(getSrc, setSrc)
[docs]
class TaurusModelModel(Qt.QAbstractListModel):
"""A Qt data model for describing taurus models"""
def __init__(self, items=None):
if items is None:
items = []
super(TaurusModelModel, self).__init__()
self.items = items
[docs]
def addItem(self, item):
"""appends an item to the internal list
:param item:
:type item: TaurusModelItem
"""
self.items.append(item)
[docs]
def dumpData(self):
"""returns a deep copy of the internal item list representation"""
return copy.deepcopy(self.items)
[docs]
def rowCount(self, index=Qt.QModelIndex()):
"""reimplemented from :class:`Qt.QAbstractListModel`"""
return len(self.items)
[docs]
def data(self, index, role=Qt.Qt.DisplayRole):
"""reimplemented from :class:`Qt.QAbstractListModel`"""
if not index.isValid() or not (0 <= index.row() < self.rowCount()):
return None
row = index.row()
# Display Role
if role == Qt.Qt.DisplayRole:
return str(self.items[row].display)
elif role == Qt.Qt.DecorationRole:
return self.items[row].icon
elif role == Qt.Qt.TextColorRole:
if not self.items[row].src:
return Qt.QColor("gray")
return Qt.QColor(self.items[row].ok and "green" or "red")
elif role == SRC_ROLE:
return str(self.items[row].src)
elif role == Qt.Qt.ToolTipRole:
return str(self.items[row].src)
if role == Qt.Qt.EditRole:
return str(self.items[row].src)
return None
[docs]
def flags(self, index):
"""reimplemented from :class:`Qt.QAbstractListModel`"""
if not index.isValid():
return Qt.Qt.ItemIsEnabled | Qt.Qt.ItemIsDropEnabled
return Qt.Qt.ItemFlags(
Qt.Qt.ItemIsEnabled
| Qt.Qt.ItemIsEditable
| Qt.Qt.ItemIsDragEnabled
| Qt.Qt.ItemIsSelectable
)
[docs]
def setData(self, index, value=None, role=Qt.Qt.EditRole):
"""reimplemented from :class:`Qt.QAbstractListModel`"""
if index.isValid() and (0 <= index.row() < self.rowCount()):
row = index.row()
item = self.items[row]
if role == Qt.Qt.EditRole:
item.src = value
elif role == Qt.Qt.DisplayRole:
item.display = value
self.dataChanged.emit(index, index)
return True
return False
[docs]
def insertRows(self, position=None, rows=1, parentindex=None, items=None):
"""reimplemented from :class:`Qt.QAbstractListModel`"""
if position is None or position == -1:
position = self.rowCount()
if parentindex is None:
parentindex = Qt.QModelIndex()
if items is None:
slice = [TaurusModelItem() for i in range(rows)]
else:
slice = list(items)
# note that the rows parameter is ignored if items is passed
rows = len(slice)
self.beginInsertRows(parentindex, position, position + rows - 1)
self.items = self.items[:position] + slice + self.items[position:]
self.endInsertRows()
return True
[docs]
def removeRows(self, position, rows=1, parentindex=None):
"""reimplemented from :class:`Qt.QAbstractListModel`"""
if parentindex is None:
parentindex = Qt.QModelIndex()
self.beginResetModel()
self.beginRemoveRows(parentindex, position, position + rows - 1)
self.items = self.items[:position] + self.items[position + rows :]
self.endRemoveRows()
self.endResetModel()
return True
[docs]
def clearAll(self):
"""clears all rows"""
self.removeRows(0, self.rowCount())
[docs]
def swapItems(self, index1, index2):
"""swap the items described by index1 and index2 in the list"""
r1, r2 = index1.row(), index2.row()
self.items[r1], self.items[r2] = self.items[r2], self.items[r1]
self.dataChanged.emit(index1, index2)
[docs]
def mimeTypes(self):
"""reimplemented from :class:`Qt.QAbstractListModel`"""
result = list(Qt.QAbstractItemModel.mimeTypes(self))
result += [
TAURUS_ATTR_MIME_TYPE,
TAURUS_MODEL_MIME_TYPE,
TAURUS_MODEL_LIST_MIME_TYPE,
"text/plain",
]
return result
[docs]
def dropMimeData(self, data, action, row, column, parent):
"""reimplemented from :class:`Qt.QAbstractListModel`"""
if row == -1 and parent.isValid():
row = parent.row()
if data.hasFormat(TAURUS_ATTR_MIME_TYPE):
items = [bytes(data.data(TAURUS_ATTR_MIME_TYPE)).decode("utf-8")]
elif data.hasFormat(TAURUS_MODEL_MIME_TYPE):
items = [bytes(data.data(TAURUS_MODEL_MIME_TYPE)).decode("utf-8")]
elif data.hasFormat(TAURUS_MODEL_LIST_MIME_TYPE):
items = (
bytes(data.data(TAURUS_MODEL_LIST_MIME_TYPE))
.decode("utf-8")
.split()
)
elif data.hasText():
items = [str(data.text())]
else:
return False
self.insertItems(row, items)
return True
[docs]
def insertItems(self, row, items):
"""convenience method to add new rows by passing a list of strings ()
:param row: the row of the list at which the item insertion starts, if
row==-1, items will be appended to the list
:type row: int
:param items: a sequence items to add to the list. The objects in the
sequence can be either strings, :class:`TaurusModelItem` objects or
tuples of valid arguments for initializing :class:`TaurusModelItem`
objects
:type items: seq
"""
itemobjs = []
for e in items:
if isinstance(e, TaurusModelItem):
itemobjs.append(e)
elif isinstance(e, str):
itemobjs.append(TaurusModelItem(src=e))
else:
# assuming it is a sequence of arguments that can be passed
# to the constructor of TaurusModelItem
itemobjs.append(TaurusModelItem(*e))
self.insertRows(position=row, items=itemobjs)
[docs]
def mimeData(self, indexes):
"""reimplemented from :class:`Qt.QAbstractListModel`"""
mimedata = Qt.QAbstractListModel.mimeData(self, indexes)
if len(indexes) == 1:
# mimedata.setData(TAURUS_ATTR_MIME_TYPE, self.data(indexes[0]))
txt = self.data(indexes[0], role=SRC_ROLE)
mimedata.setText(txt)
return mimedata
# mimedata.setData()
[docs]
class TaurusModelList(Qt.QListView):
"""A list view widget to display and manage a list of models
Tries to identify the type of model and show the state of the device/attr
associated with it. It also allows drag and drop of models and sorting.
"""
dataChangedSignal = Qt.pyqtSignal(list, name="dataChanged")
def __init__(self, parent=None, items=None, designMode=False):
super(TaurusModelList, self).__init__(parent)
if items is None:
items = []
self._model = TaurusModelModel(items)
self.setModel(self._model)
self.setDragDropMode(self.DragDrop)
# self.setAcceptDrops(True)
self.setSelectionMode(self.ExtendedSelection)
self._contextMenu = Qt.QMenu(self)
self.addRowAction = self._contextMenu.addAction(
Qt.QIcon.fromTheme("list-add"),
"Add new row",
self.newRow,
Qt.QKeySequence.New,
)
self.removeSelectedAction = self._contextMenu.addAction(
Qt.QIcon.fromTheme("list-remove"),
"Remove Selected",
self.removeSelected,
Qt.QKeySequence.Delete,
)
self.removeAllAction = self._contextMenu.addAction(
Qt.QIcon.fromTheme("edit-clear"),
"Clear all",
self.clear,
Qt.QKeySequence("Ctrl+Del"),
)
self.moveUpAction = self._contextMenu.addAction(
Qt.QIcon.fromTheme("go-up"),
"Move up in the list",
self._onMoveUpAction,
Qt.QKeySequence("Alt+Up"),
)
self.moveDownAction = self._contextMenu.addAction(
Qt.QIcon.fromTheme("go-down"),
"Move down in the list",
self._onMoveDownAction,
Qt.QKeySequence("Alt+Down"),
)
self.editDisplayAction = self._contextMenu.addAction(
Qt.QIcon("actions:format-text-italic.svg"),
"Edit the display (leave the source)",
self._onEditDisplay,
Qt.QKeySequence("Alt+D"),
)
self.addActions(
[
self.addRowAction,
self.removeSelectedAction,
self.removeAllAction,
self.moveUpAction,
self.moveDownAction,
self.editDisplayAction,
]
)
# signal connections
selectionmodel = self.selectionModel()
selectionmodel.selectionChanged.connect(self._onSelectionChanged)
self._model.dataChanged.connect(self._onDataChanged)
self._model.rowsInserted.connect(self._onDataChanged)
self._model.rowsRemoved.connect(self._onDataChanged)
self._onSelectionChanged(Qt.QItemSelection(), Qt.QItemSelection())
[docs]
def clear(self):
"""removes all items from the list"""
self._model.clearAll()
def _onEditDisplay(self):
selected = self.selectionModel().selectedIndexes()
if len(selected) == 1:
idx = selected[0]
else:
return
value = self._model.data(idx, role=Qt.Qt.DisplayRole)
src = self._model.data(idx, role=SRC_ROLE)
value, ok = Qt.QInputDialog.getText(
self,
"Display Value",
"Display value for %s?" % src,
Qt.QLineEdit.Normal,
value,
)
if not ok:
return
self._model.setData(idx, value, role=Qt.Qt.DisplayRole)
def _onSelectionChanged(self, selected, deselected):
"""updates the status of the actions that depend on the selection"""
selectedIndexes = self.selectionModel().selectedRows()
self.removeSelectedAction.setEnabled(len(selectedIndexes) > 0)
self.moveUpAction.setEnabled(
len(selectedIndexes) == 1 and selectedIndexes[0].row() > 0
)
self.moveDownAction.setEnabled(
len(selectedIndexes) == 1
and (0 <= selectedIndexes[0].row() < self._model.rowCount() - 1)
)
self.editDisplayAction.setEnabled(len(selectedIndexes) > 0)
def _onDataChanged(self, *args):
"""emits a signal containing the current data as a list of strings"""
self.dataChangedSignal.emit(self.getModelItems())
def _onMoveUpAction(self):
"""slot for move up action"""
selected = self.selectionModel().selectedIndexes()
if len(selected) != 1:
return
i1 = selected[0]
i2 = self._model.index(i1.row() - 1)
self._model.swapItems(i1, i2)
self.selectionModel().select(i2, Qt.QItemSelectionModel.ClearAndSelect)
def _onMoveDownAction(self):
"""slot for move down action"""
selected = self.selectionModel().selectedIndexes()
if len(selected) != 1:
return
i1 = selected[0]
i2 = self._model.index(i1.row() + 1)
self._model.swapItems(i1, i2)
self.selectionModel().select(i2, Qt.QItemSelectionModel.ClearAndSelect)
[docs]
def newRow(self, position=None):
"""adds an empty row *before* the given position
:param position: position at which the new row will be added. If None
passed, it will be added at the end.
:type position: int or None
"""
if position is None:
selected = self.selectionModel().selectedIndexes()
if len(selected) == 0:
position = -1
elif len(selected) == 1:
position = selected[0].row()
else:
return
self._model.insertItems(position, [""])
[docs]
def removeSelected(self):
"""removes selected items from the list"""
selected = sorted(
[idx.row() for idx in self.selectionModel().selectedRows()],
reverse=True,
)
for row in selected: # we remove rows starting from the last one
self._model.removeRows(row)
[docs]
def addModels(self, models):
"""adds models to the list
:param models: sequence of model names to be added
:type models: list<str>
"""
self._model.insertItems(-1, models)
[docs]
def getModelItems(self):
"""returns the model item objects
:return:
:rtype: list<TaurusModelItem>
.. seealso:: :meth:`getModelList`
"""
return self._model.dumpData()
[docs]
def getModelList(self):
"""returns a the model names corresponding to the items in the list
:return:
:rtype: list<str>
.. seealso:: :meth:`getModelItems`
"""
return [str(s.src) for s in self.getModelItems()]
[docs]
@classmethod
def getQtDesignerPluginInfo(cls):
return {
"group": "Taurus Input",
"icon": "logos:taurus.png",
"container": False,
"module": "taurus.qt.qtgui.panel",
}
if __name__ == "__main__":
from taurus.qt.qtgui.application import TaurusApplication
import sys
app = TaurusApplication(cmd_line_parser=None)
w = TaurusModelList()
w.addModels(
["item%i" % i for i in range(3)]
+ [TaurusModelItem(src="src1", display="d1")]
+ [("src2", "d2")]
)
w.show()
sys.exit(app.exec_())