# coding: utf-8
# /*##########################################################################
#
# Copyright (c) 2004-2017 European Synchrotron Radiation Facility
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
#
# ###########################################################################*/
"""Widget displaying curves legends and allowing to operate on curves.
This widget is meant to work with :class:`PlotWindow`.
"""
__authors__ = ["V.A. Sole", "T. Rueter", "T. Vincent"]
__license__ = "MIT"
__data__ = "28/04/2016"
import logging
import weakref
from .. import qt
_logger = logging.getLogger(__name__)
# Build all symbols
# Courtesy of the pyqtgraph project
Symbols = dict([(name, qt.QPainterPath())
for name in ['o', 's', 't', 'd', '+', 'x', '.', ',']])
Symbols['o'].addEllipse(qt.QRectF(.1, .1, .8, .8))
Symbols['.'].addEllipse(qt.QRectF(.3, .3, .4, .4))
Symbols[','].addEllipse(qt.QRectF(.4, .4, .2, .2))
Symbols['s'].addRect(qt.QRectF(.1, .1, .8, .8))
coords = {
't': [(0.5, 0.), (.1, .8), (.9, .8)],
'd': [(0.1, 0.5), (0.5, 0.), (0.9, 0.5), (0.5, 1.)],
'+': [(0.0, 0.40), (0.40, 0.40), (0.40, 0.), (0.60, 0.),
(0.60, 0.40), (1., 0.40), (1., 0.60), (0.60, 0.60),
(0.60, 1.), (0.40, 1.), (0.40, 0.60), (0., 0.60)],
'x': [(0.0, 0.40), (0.40, 0.40), (0.40, 0.), (0.60, 0.),
(0.60, 0.40), (1., 0.40), (1., 0.60), (0.60, 0.60),
(0.60, 1.), (0.40, 1.), (0.40, 0.60), (0., 0.60)]
}
for s, c in coords.items():
Symbols[s].moveTo(*c[0])
for x, y in c[1:]:
Symbols[s].lineTo(x, y)
Symbols[s].closeSubpath()
tr = qt.QTransform()
tr.rotate(45)
Symbols['x'].translate(qt.QPointF(-0.5, -0.5))
Symbols['x'] = tr.map(Symbols['x'])
Symbols['x'].translate(qt.QPointF(0.5, 0.5))
NoSymbols = (None, 'None', 'none', '', ' ')
"""List of values resulting in no symbol being displayed for a curve"""
LineStyles = {
None: qt.Qt.NoPen,
'None': qt.Qt.NoPen,
'none': qt.Qt.NoPen,
'': qt.Qt.NoPen,
' ': qt.Qt.NoPen,
'-': qt.Qt.SolidLine,
'--': qt.Qt.DashLine,
':': qt.Qt.DotLine,
'-.': qt.Qt.DashDotLine
}
"""Conversion from matplotlib-like linestyle to Qt"""
NoLineStyle = (None, 'None', 'none', '', ' ')
"""List of style values resulting in no line being displayed for a curve"""
[docs]class LegendIcon(qt.QWidget):
"""Object displaying a curve linestyle and symbol."""
def __init__(self, parent=None):
super(LegendIcon, self).__init__(parent)
# Visibilities
self.showLine = True
self.showSymbol = True
# Line attributes
self.lineStyle = qt.Qt.NoPen
self.lineWidth = 1.
self.lineColor = qt.Qt.green
self.symbol = ''
# Symbol attributes
self.symbolStyle = qt.Qt.SolidPattern
self.symbolColor = qt.Qt.green
self.symbolOutlineBrush = qt.QBrush(qt.Qt.white)
# Control widget size: sizeHint "is the only acceptable
# alternative, so the widget can never grow or shrink"
# (c.f. Qt Doc, enum QSizePolicy::Policy)
self.setSizePolicy(qt.QSizePolicy.Fixed,
qt.QSizePolicy.Fixed)
def sizeHint(self):
return qt.QSize(50, 15)
# Modify Symbol
def setSymbol(self, symbol):
symbol = str(symbol)
if symbol not in NoSymbols:
if symbol not in Symbols:
raise ValueError("Unknown symbol: <%s>" % symbol)
self.symbol = symbol
# self.update() after set...?
# Does not seem necessary
[docs] def setSymbolColor(self, color):
"""
:param color: determines the symbol color
:type style: qt.QColor
"""
self.symbolColor = qt.QColor(color)
# Modify Line
def setLineColor(self, color):
self.lineColor = qt.QColor(color)
def setLineWidth(self, width):
self.lineWidth = float(width)
[docs] def setLineStyle(self, style):
"""Set the linestyle.
Possible line styles:
- '', ' ', 'None': No line
- '-': solid
- '--': dashed
- ':': dotted
- '-.': dash and dot
:param str style: The linestyle to use
"""
if style not in LineStyles:
raise ValueError('Unknown style: %s', style)
self.lineStyle = LineStyles[style]
# Paint
[docs] def paintEvent(self, event):
"""
:param event: event
:type event: QPaintEvent
"""
painter = qt.QPainter(self)
self.paint(painter, event.rect(), self.palette())
def paint(self, painter, rect, palette):
painter.save()
painter.setRenderHint(qt.QPainter.Antialiasing)
# Scale painter to the icon height
# current -> width = 2.5, height = 1.0
scale = float(self.height())
ratio = float(self.width()) / scale
painter.scale(scale,
scale)
symbolOffset = qt.QPointF(.5 * (ratio - 1.), 0.)
# Determine and scale offset
offset = qt.QPointF(float(rect.left()) / scale, float(rect.top()) / scale)
# Draw BG rectangle (for debugging)
# bottomRight = qt.QPointF(
# float(rect.right())/scale,
# float(rect.bottom())/scale)
# painter.fillRect(qt.QRectF(offset, bottomRight),
# qt.QBrush(qt.Qt.green))
llist = []
if self.showLine:
linePath = qt.QPainterPath()
linePath.moveTo(0., 0.5)
linePath.lineTo(ratio, 0.5)
# linePath.lineTo(2.5, 0.5)
linePen = qt.QPen(
qt.QBrush(self.lineColor),
(self.lineWidth / self.height()),
self.lineStyle,
qt.Qt.FlatCap
)
llist.append((linePath,
linePen,
qt.QBrush(self.lineColor)))
if (self.showSymbol and len(self.symbol) and
self.symbol not in NoSymbols):
# PITFALL ahead: Let this be a warning to others
# symbolPath = Symbols[self.symbol]
# Copy before translate! Dict is a mutable type
symbolPath = qt.QPainterPath(Symbols[self.symbol])
symbolPath.translate(symbolOffset)
symbolBrush = qt.QBrush(
self.symbolColor,
self.symbolStyle
)
symbolPen = qt.QPen(
self.symbolOutlineBrush, # Brush
1. / self.height(), # Width
qt.Qt.SolidLine # Style
)
llist.append((symbolPath,
symbolPen,
symbolBrush))
# Draw
for path, pen, brush in llist:
path.translate(offset)
painter.setPen(pen)
painter.setBrush(brush)
painter.drawPath(path)
painter.restore()
[docs]class LegendModel(qt.QAbstractListModel):
"""Data model of curve legends.
It holds the information of the curve:
- color
- line width
- line style
- visibility of the lines
- symbol
- visibility of the symbols
"""
iconColorRole = qt.Qt.UserRole + 0
iconLineWidthRole = qt.Qt.UserRole + 1
iconLineStyleRole = qt.Qt.UserRole + 2
showLineRole = qt.Qt.UserRole + 3
iconSymbolRole = qt.Qt.UserRole + 4
showSymbolRole = qt.Qt.UserRole + 5
def __init__(self, legendList=None, parent=None):
super(LegendModel, self).__init__(parent)
if legendList is None:
legendList = []
self.legendList = []
self.insertLegendList(0, legendList)
def __getitem__(self, idx):
if idx >= len(self.legendList):
raise IndexError('list index out of range')
return self.legendList[idx]
def rowCount(self, modelIndex=None):
return len(self.legendList)
def flags(self, index):
return (qt.Qt.ItemIsEditable |
qt.Qt.ItemIsEnabled |
qt.Qt.ItemIsSelectable)
def data(self, modelIndex, role):
if modelIndex.isValid:
idx = modelIndex.row()
else:
return None
if idx >= len(self.legendList):
raise IndexError('list index out of range')
item = self.legendList[idx]
if role == qt.Qt.DisplayRole:
# Data to be rendered in the form of text
legend = str(item[0])
return legend
elif role == qt.Qt.SizeHintRole:
# size = qt.QSize(200,50)
_logger.warning('LegendModel -- size hint role not implemented')
return qt.QSize()
elif role == qt.Qt.TextAlignmentRole:
alignment = qt.Qt.AlignVCenter | qt.Qt.AlignLeft
return alignment
elif role == qt.Qt.BackgroundRole:
# Background color, must be QBrush
if idx % 2:
brush = qt.QBrush(qt.QColor(240, 240, 240))
else:
brush = qt.QBrush(qt.Qt.white)
return brush
elif role == qt.Qt.ForegroundRole:
# ForegroundRole color, must be QBrush
brush = qt.QBrush(qt.Qt.blue)
return brush
elif role == qt.Qt.CheckStateRole:
return bool(item[2]) # item[2] == True
elif role == qt.Qt.ToolTipRole or role == qt.Qt.StatusTipRole:
return ''
elif role == self.iconColorRole:
return item[1]['color']
elif role == self.iconLineWidthRole:
return item[1]['linewidth']
elif role == self.iconLineStyleRole:
return item[1]['linestyle']
elif role == self.iconSymbolRole:
return item[1]['symbol']
elif role == self.showLineRole:
return item[3]
elif role == self.showSymbolRole:
return item[4]
else:
_logger.info('Unkown role requested: %s', str(role))
return None
def setData(self, modelIndex, value, role):
if modelIndex.isValid:
idx = modelIndex.row()
else:
return None
if idx >= len(self.legendList):
# raise IndexError('list index out of range')
_logger.warning(
'setData -- List index out of range, idx: %d', idx)
return None
item = self.legendList[idx]
try:
if role == qt.Qt.DisplayRole:
# Set legend
item[0] = str(value)
elif role == self.iconColorRole:
item[1]['color'] = qt.QColor(value)
elif role == self.iconLineWidthRole:
item[1]['linewidth'] = int(value)
elif role == self.iconLineStyleRole:
item[1]['linestyle'] = str(value)
elif role == self.iconSymbolRole:
item[1]['symbol'] = str(value)
elif role == qt.Qt.CheckStateRole:
item[2] = value
elif role == self.showLineRole:
item[3] = value
elif role == self.showSymbolRole:
item[4] = value
except ValueError:
_logger.warning('Conversion failed:\n\tvalue: %s\n\trole: %s',
str(value), str(role))
# Can that be right? Read docs again..
self.dataChanged.emit(modelIndex, modelIndex)
return True
[docs] def insertLegendList(self, row, llist):
"""
:param int row: Determines after which row the items are inserted
:param llist: Carries the new legend information
:type llist: List
"""
modelIndex = self.createIndex(row, 0)
count = len(llist)
super(LegendModel, self).beginInsertRows(modelIndex,
row,
row + count)
head = self.legendList[0:row]
tail = self.legendList[row:]
new = []
for (legend, icon) in llist:
linestyle = icon.get('linestyle', None)
if linestyle in NoLineStyle:
# Curve had no line, give it one and hide it
# So when toggle line, it will display a solid line
showLine = False
icon['linestyle'] = '-'
else:
showLine = True
symbol = icon.get('symbol', None)
if symbol in NoSymbols:
# Curve had no symbol, give it one and hide it
# So when toggle symbol, it will display 'o'
showSymbol = False
icon['symbol'] = 'o'
else:
showSymbol = True
selected = icon.get('selected', True)
item = [legend,
icon,
selected,
showLine,
showSymbol]
new.append(item)
self.legendList = head + new + tail
super(LegendModel, self).endInsertRows()
return True
def insertRows(self, row, count, modelIndex=qt.QModelIndex()):
raise NotImplementedError('Use LegendModel.insertLegendList instead')
def removeRow(self, row):
return self.removeRows(row, 1)
def removeRows(self, row, count, modelIndex=qt.QModelIndex()):
length = len(self.legendList)
if length == 0:
# Nothing to do..
return True
if row < 0 or row >= length:
raise IndexError('Index out of range -- ' +
'idx: %d, len: %d' % (row, length))
if count == 0:
return False
super(LegendModel, self).beginRemoveRows(modelIndex,
row,
row + count)
del(self.legendList[row:row + count])
super(LegendModel, self).endRemoveRows()
return True
[docs] def setEditor(self, event, editor):
"""
:param str event: String that identifies the editor
:param editor: Widget used to change data in the underlying model
:type editor: QWidget
"""
if event not in self.eventList:
raise ValueError('setEditor -- Event must be in %s' %
str(self.eventList))
self.editorDict[event] = editor
[docs]class LegendListItemWidget(qt.QItemDelegate):
"""Object displaying a single item (i.e., a row) in the list."""
# Notice: LegendListItem does NOT inherit
# from QObject, it cannot emit signals!
def __init__(self, parent=None, itemType=0):
super(LegendListItemWidget, self).__init__(parent)
# Dictionary to render checkboxes
self.cbDict = {}
self.labelDict = {}
self.iconDict = {}
# Keep checkbox and legend to get sizeHint
self.checkbox = qt.QCheckBox()
self.legend = qt.QLabel()
self.icon = LegendIcon()
# Context Menu and Editors
self.contextMenu = None
[docs] def paint(self, painter, option, modelIndex):
"""
Here be docs..
:param QPainter painter:
:param QStyleOptionViewItem option:
:param QModelIndex modelIndex:
"""
painter.save()
rect = option.rect
# Calculate the icon rectangle
iconSize = self.icon.sizeHint()
# Calculate icon position
x = rect.left() + 2
y = rect.top() + int(.5 * (rect.height() - iconSize.height()))
iconRect = qt.QRect(qt.QPoint(x, y), iconSize)
# Calculate label rectangle
legendSize = qt.QSize(rect.width() - iconSize.width() - 30,
rect.height())
# Calculate label position
x = rect.left() + iconRect.width()
y = rect.top()
labelRect = qt.QRect(qt.QPoint(x, y), legendSize)
labelRect.translate(qt.QPoint(10, 0))
# Calculate the checkbox rectangle
x = rect.right() - 30
y = rect.top()
chBoxRect = qt.QRect(qt.QPoint(x, y), rect.bottomRight())
# Remember the rectangles
idx = modelIndex.row()
self.cbDict[idx] = chBoxRect
self.iconDict[idx] = iconRect
self.labelDict[idx] = labelRect
# Draw background first!
if option.state & qt.QStyle.State_MouseOver:
backgroundBrush = option.palette.highlight()
else:
backgroundBrush = modelIndex.data(qt.Qt.BackgroundRole)
painter.fillRect(rect, backgroundBrush)
# Draw label
legendText = modelIndex.data(qt.Qt.DisplayRole)
textBrush = modelIndex.data(qt.Qt.ForegroundRole)
textAlign = modelIndex.data(qt.Qt.TextAlignmentRole)
painter.setBrush(textBrush)
painter.setFont(self.legend.font())
painter.drawText(labelRect, textAlign, legendText)
# Draw icon
iconColor = modelIndex.data(LegendModel.iconColorRole)
iconLineWidth = modelIndex.data(LegendModel.iconLineWidthRole)
iconLineStyle = modelIndex.data(LegendModel.iconLineStyleRole)
iconSymbol = modelIndex.data(LegendModel.iconSymbolRole)
icon = LegendIcon()
icon.resize(iconRect.size())
icon.move(iconRect.topRight())
icon.showSymbol = modelIndex.data(LegendModel.showSymbolRole)
icon.showLine = modelIndex.data(LegendModel.showLineRole)
icon.setSymbolColor(iconColor)
icon.setLineColor(iconColor)
icon.setLineWidth(iconLineWidth)
icon.setLineStyle(iconLineStyle)
icon.setSymbol(iconSymbol)
icon.symbolOutlineBrush = backgroundBrush
icon.paint(painter, iconRect, option.palette)
# Draw the checkbox
if modelIndex.data(qt.Qt.CheckStateRole):
checkState = qt.Qt.Checked
else:
checkState = qt.Qt.Unchecked
self.drawCheck(
painter, qt.QStyleOptionViewItem(), chBoxRect, checkState)
painter.restore()
def editorEvent(self, event, model, option, modelIndex):
# From the docs:
# Mouse events are sent to editorEvent()
# even if they don't start editing of the item.
if event.button() == qt.Qt.RightButton and self.contextMenu:
self.contextMenu.exec_(event.globalPos(), modelIndex)
return True
elif event.button() == qt.Qt.LeftButton:
# Check if checkbox was clicked
idx = modelIndex.row()
cbRect = self.cbDict[idx]
if cbRect.contains(event.pos()):
# Toggle checkbox
model.setData(modelIndex,
not modelIndex.data(qt.Qt.CheckStateRole),
qt.Qt.CheckStateRole)
event.ignore()
return True
else:
return super(LegendListItemWidget, self).editorEvent(
event, model, option, modelIndex)
def createEditor(self, parent, option, idx):
_logger.info('### Editor request ###')
def sizeHint(self, option, idx):
# return qt.QSize(68,24)
iconSize = self.icon.sizeHint()
legendSize = self.legend.sizeHint()
checkboxSize = self.checkbox.sizeHint()
height = max([iconSize.height(),
legendSize.height(),
checkboxSize.height()]) + 4
width = iconSize.width() + legendSize.width() + checkboxSize.width()
return qt.QSize(width, height)
[docs]class LegendListView(qt.QListView):
"""Widget displaying a list of curve legends, line style and symbol."""
sigLegendSignal = qt.Signal(object)
"""Signal emitting a dict when an action is triggered by the user."""
__mouseClickedEvent = 'mouseClicked'
__checkBoxClickedEvent = 'checkBoxClicked'
__legendClickedEvent = 'legendClicked'
def __init__(self, parent=None, model=None, contextMenu=None):
super(LegendListView, self).__init__(parent)
self.__lastButton = None
self.__lastClickPos = None
self.__lastModelIdx = None
# Set default delegate
self.setItemDelegate(LegendListItemWidget())
# Set default editors
# self.setSizePolicy(qt.QSizePolicy.MinimumExpanding,
# qt.QSizePolicy.MinimumExpanding)
# Set edit triggers by hand using self.edit(QModelIndex)
# in mousePressEvent (better to control than signals)
self.setEditTriggers(qt.QAbstractItemView.NoEditTriggers)
# Control layout
# self.setBatchSize(2)
# self.setLayoutMode(qt.QListView.Batched)
# self.setFlow(qt.QListView.LeftToRight)
# Control selection
self.setSelectionMode(qt.QAbstractItemView.NoSelection)
if model is None:
model = LegendModel()
self.setModel(model)
self.setContextMenu(contextMenu)
def setLegendList(self, legendList, row=None):
self.clear()
if row is None:
row = 0
model = self.model()
model.insertLegendList(row, legendList)
_logger.debug('LegendListView.setLegendList(legendList) finished')
def clear(self):
model = self.model()
model.removeRows(0, model.rowCount())
_logger.debug('LegendListView.clear() finished')
def setContextMenu(self, contextMenu=None):
delegate = self.itemDelegate()
if isinstance(delegate, LegendListItemWidget) and self.model():
if contextMenu is None:
delegate.contextMenu = LegendListContextMenu(self.model())
delegate.contextMenu.sigContextMenu.connect(
self._contextMenuSlot)
else:
delegate.contextMenu = contextMenu
def __getitem__(self, idx):
model = self.model()
try:
item = model[idx]
except ValueError:
item = None
return item
def _contextMenuSlot(self, ddict):
self.sigLegendSignal.emit(ddict)
def mousePressEvent(self, event):
self.__lastButton = event.button()
self.__lastPosition = event.pos()
super(LegendListView, self).mousePressEvent(event)
# call _handleMouseClick after editing was handled
# If right click (context menu) is aborted, no
# signal is emitted..
self._handleMouseClick(self.indexAt(self.__lastPosition))
def mouseDoubleClickEvent(self, event):
self.__lastButton = event.button()
self.__lastPosition = event.pos()
super(LegendListView, self).mouseDoubleClickEvent(event)
# call _handleMouseClick after editing was handled
# If right click (context menu) is aborted, no
# signal is emitted..
self._handleMouseClick(self.indexAt(self.__lastPosition))
def mouseMoveEvent(self, event):
# LegendListView.mouseMoveEvent is overwritten
# to suppress unwanted behavior in the delegate.
pass
def mouseReleaseEvent(self, event):
# LegendListView.mouseReleaseEvent is overwritten
# to subpress unwanted behavior in the delegate.
pass
def _handleMouseClick(self, modelIndex):
"""
Distinguish between mouse click on Legend
and mouse click on CheckBox by setting the
currentCheckState attribute in LegendListItem.
Emits signal sigLegendSignal(ddict)
:param QModelIndex modelIndex: index of the clicked item
"""
_logger.debug('self._handleMouseClick called')
if self.__lastButton not in [qt.Qt.LeftButton,
qt.Qt.RightButton]:
return
if not modelIndex.isValid():
_logger.debug('_handleMouseClick -- Invalid QModelIndex')
return
# model = self.model()
idx = modelIndex.row()
delegate = self.itemDelegate()
cbClicked = False
if isinstance(delegate, LegendListItemWidget):
for cbRect in delegate.cbDict.values():
if cbRect.contains(self.__lastPosition):
cbClicked = True
break
# TODO: Check for doubleclicks on legend/icon and spawn editors
ddict = {
'legend': str(modelIndex.data(qt.Qt.DisplayRole)),
'icon': {
'linewidth': str(modelIndex.data(
LegendModel.iconLineWidthRole)),
'linestyle': str(modelIndex.data(
LegendModel.iconLineStyleRole)),
'symbol': str(modelIndex.data(LegendModel.iconSymbolRole))
},
'selected': modelIndex.data(qt.Qt.CheckStateRole),
'type': str(modelIndex.data())
}
if self.__lastButton == qt.Qt.RightButton:
_logger.debug('Right clicked')
ddict['button'] = "right"
ddict['event'] = self.__mouseClickedEvent
elif cbClicked:
_logger.debug('CheckBox clicked')
ddict['button'] = "left"
ddict['event'] = self.__checkBoxClickedEvent
else:
_logger.debug('Legend clicked')
ddict['button'] = "left"
ddict['event'] = self.__legendClickedEvent
_logger.debug(' idx: %d\n ddict: %s', idx, str(ddict))
self.sigLegendSignal.emit(ddict)
[docs]class LegendListContextMenu(qt.QMenu):
"""Contextual menu associated to items in a :class:`LegendListView`."""
sigContextMenu = qt.Signal(object)
"""Signal emitting a dict upon contextual menu actions."""
def __init__(self, model):
super(LegendListContextMenu, self).__init__(parent=None)
self.model = model
self.addAction('Set Active', self.setActiveAction)
self.addAction('Map to left', self.mapToLeftAction)
self.addAction('Map to right', self.mapToRightAction)
self._pointsAction = self.addAction(
'Points', self.togglePointsAction)
self._pointsAction.setCheckable(True)
self._linesAction = self.addAction('Lines', self.toggleLinesAction)
self._linesAction.setCheckable(True)
self.addAction('Remove curve', self.removeItemAction)
self.addAction('Rename curve', self.renameItemAction)
def exec_(self, pos, idx):
self.__currentIdx = idx
# Set checkable action state
modelIndex = self.currentIdx()
self._pointsAction.setChecked(
modelIndex.data(LegendModel.showSymbolRole))
self._linesAction.setChecked(
modelIndex.data(LegendModel.showLineRole))
super(LegendListContextMenu, self).popup(pos)
def currentIdx(self):
return self.__currentIdx
def mapToLeftAction(self):
_logger.debug('LegendListContextMenu.mapToLeftAction called')
modelIndex = self.currentIdx()
legend = str(modelIndex.data(qt.Qt.DisplayRole))
ddict = {
'legend': legend,
'label': legend,
'selected': modelIndex.data(qt.Qt.CheckStateRole),
'type': str(modelIndex.data()),
'event': "mapToLeft"
}
self.sigContextMenu.emit(ddict)
def mapToRightAction(self):
_logger.debug('LegendListContextMenu.mapToRightAction called')
modelIndex = self.currentIdx()
legend = str(modelIndex.data(qt.Qt.DisplayRole))
ddict = {
'legend': legend,
'label': legend,
'selected': modelIndex.data(qt.Qt.CheckStateRole),
'type': str(modelIndex.data()),
'event': "mapToRight"
}
self.sigContextMenu.emit(ddict)
def removeItemAction(self):
_logger.debug('LegendListContextMenu.removeCurveAction called')
modelIndex = self.currentIdx()
legend = str(modelIndex.data(qt.Qt.DisplayRole))
ddict = {
'legend': legend,
'label': legend,
'selected': modelIndex.data(qt.Qt.CheckStateRole),
'type': str(modelIndex.data()),
'event': "removeCurve"
}
self.model.removeRow(modelIndex.row())
self.sigContextMenu.emit(ddict)
def renameItemAction(self):
_logger.debug('LegendListContextMenu.renameCurveAction called')
modelIndex = self.currentIdx()
legend = str(modelIndex.data(qt.Qt.DisplayRole))
ddict = {
'legend': legend,
'label': legend,
'selected': modelIndex.data(qt.Qt.CheckStateRole),
'type': str(modelIndex.data()),
'event': "renameCurve"
}
self.sigContextMenu.emit(ddict)
def toggleLinesAction(self):
modelIndex = self.currentIdx()
legend = str(modelIndex.data(qt.Qt.DisplayRole))
ddict = {
'legend': legend,
'label': legend,
'selected': modelIndex.data(qt.Qt.CheckStateRole),
'type': str(modelIndex.data()),
}
linestyle = modelIndex.data(LegendModel.iconLineStyleRole)
visible = not modelIndex.data(LegendModel.showLineRole)
_logger.debug('toggleLinesAction -- lines visible: %s', str(visible))
ddict['event'] = "toggleLine"
ddict['line'] = visible
ddict['linestyle'] = linestyle if visible else ''
self.model.setData(modelIndex, visible, LegendModel.showLineRole)
self.sigContextMenu.emit(ddict)
def togglePointsAction(self):
modelIndex = self.currentIdx()
legend = str(modelIndex.data(qt.Qt.DisplayRole))
ddict = {
'legend': legend,
'label': legend,
'selected': modelIndex.data(qt.Qt.CheckStateRole),
'type': str(modelIndex.data()),
}
flag = modelIndex.data(LegendModel.showSymbolRole)
symbol = modelIndex.data(LegendModel.iconSymbolRole)
visible = not flag or symbol in NoSymbols
_logger.debug(
'togglePointsAction -- Symbols visible: %s', str(visible))
ddict['event'] = "togglePoints"
ddict['points'] = visible
ddict['symbol'] = symbol if visible else ''
self.model.setData(modelIndex, visible, LegendModel.showSymbolRole)
self.sigContextMenu.emit(ddict)
def setActiveAction(self):
modelIndex = self.currentIdx()
legend = str(modelIndex.data(qt.Qt.DisplayRole))
_logger.debug('setActiveAction -- active curve: %s', legend)
ddict = {
'legend': legend,
'label': legend,
'selected': modelIndex.data(qt.Qt.CheckStateRole),
'type': str(modelIndex.data()),
'event': "setActiveCurve",
}
self.sigContextMenu.emit(ddict)
[docs]class RenameCurveDialog(qt.QDialog):
"""Dialog box to input the name of a curve."""
def __init__(self, parent=None, current="", curves=()):
super(RenameCurveDialog, self).__init__(parent)
self.setWindowTitle("Rename Curve %s" % current)
self.curves = curves
layout = qt.QVBoxLayout(self)
self.lineEdit = qt.QLineEdit(self)
self.lineEdit.setText(current)
self.hbox = qt.QWidget(self)
self.hboxLayout = qt.QHBoxLayout(self.hbox)
self.hboxLayout.addStretch(1)
self.okButton = qt.QPushButton(self.hbox)
self.okButton.setText('OK')
self.hboxLayout.addWidget(self.okButton)
self.cancelButton = qt.QPushButton(self.hbox)
self.cancelButton.setText('Cancel')
self.hboxLayout.addWidget(self.cancelButton)
self.hboxLayout.addStretch(1)
layout.addWidget(self.lineEdit)
layout.addWidget(self.hbox)
self.okButton.clicked.connect(self.preAccept)
self.cancelButton.clicked.connect(self.reject)
def preAccept(self):
text = str(self.lineEdit.text())
addedText = ""
if len(text):
if text not in self.curves:
self.accept()
return
else:
addedText = "Curve already exists."
text = "Invalid Curve Name"
msg = qt.QMessageBox(self)
msg.setIcon(qt.QMessageBox.Critical)
msg.setWindowTitle(text)
text += "\n%s" % addedText
msg.setText(text)
msg.exec_()
def getText(self):
return str(self.lineEdit.text())
[docs]class LegendsDockWidget(qt.QDockWidget):
"""QDockWidget with a :class:`LegendSelector` connected to a PlotWindow.
It makes the link between the LegendListView widget and the PlotWindow.
:param parent: See :class:`QDockWidget`
:param plot: :class:`.PlotWindow` instance on which to operate
"""
def __init__(self, parent=None, plot=None):
assert plot is not None
self._plotRef = weakref.ref(plot)
self._isConnected = False # True if widget connected to plot signals
super(LegendsDockWidget, self).__init__("Legends", parent)
self._legendWidget = LegendListView()
self.layout().setContentsMargins(0, 0, 0, 0)
self.setWidget(self._legendWidget)
self.visibilityChanged.connect(
self._visibilityChangedHandler)
self._legendWidget.sigLegendSignal.connect(self._legendSignalHandler)
@property
[docs] def plot(self):
"""The :class:`.PlotWindow` this widget is attached to."""
return self._plotRef()
[docs] def renameCurve(self, oldLegend, newLegend):
"""Change the name of a curve using remove and addCurve
:param str oldLegend: The legend of the curve to be change
:param str newLegend: The new legend of the curve
"""
curve = self.plot.getCurve(oldLegend)
self.plot.remove(oldLegend, kind='curve')
self.plot.addCurve(curve.getXData(copy=False),
curve.getYData(copy=False),
legend=newLegend,
info=curve.getInfo(),
color=curve.getColor(),
symbol=curve.getSymbol(),
linewidth=curve.getLineWidth(),
linestyle=curve.getLineStyle(),
xlabel=curve.getXLabel(),
ylabel=curve.getYLabel(),
xerror=curve.getXErrorData(copy=False),
yerror=curve.getYErrorData(copy=False),
z=curve.getZValue(),
selectable=curve.isSelectable(),
fill=curve.isFill(),
resetzoom=False)
def _legendSignalHandler(self, ddict):
"""Handles events from the LegendListView signal"""
_logger.debug("Legend signal ddict = %s", str(ddict))
if ddict['event'] == "legendClicked":
if ddict['button'] == "left":
self.plot.setActiveCurve(ddict['legend'])
elif ddict['event'] == "removeCurve":
self.plot.removeCurve(ddict['legend'])
elif ddict['event'] == "renameCurve":
curveList = self.plot.getAllCurves(just_legend=True)
oldLegend = ddict['legend']
dialog = RenameCurveDialog(self.plot, oldLegend, curveList)
ret = dialog.exec_()
if ret:
newLegend = dialog.getText()
self.renameCurve(oldLegend, newLegend)
elif ddict['event'] == "setActiveCurve":
self.plot.setActiveCurve(ddict['legend'])
elif ddict['event'] == "checkBoxClicked":
self.plot.hideCurve(ddict['legend'], not ddict['selected'])
elif ddict['event'] in ["mapToRight", "mapToLeft"]:
legend = ddict['legend']
curve = self.plot.getCurve(legend)
yaxis = 'right' if ddict['event'] == 'mapToRight' else 'left'
self.plot.addCurve(x=curve.getXData(copy=False),
y=curve.getYData(copy=False),
legend=curve.getLegend(),
info=curve.getInfo(),
yaxis=yaxis)
elif ddict['event'] == "togglePoints":
legend = ddict['legend']
curve = self.plot.getCurve(legend)
symbol = ddict['symbol'] if ddict['points'] else ''
self.plot.addCurve(x=curve.getXData(copy=False),
y=curve.getYData(copy=False),
legend=curve.getLegend(),
info=curve.getInfo(),
symbol=symbol)
elif ddict['event'] == "toggleLine":
legend = ddict['legend']
curve = self.plot.getCurve(legend)
linestyle = ddict['linestyle'] if ddict['line'] else ''
self.plot.addCurve(x=curve.getXData(copy=False),
y=curve.getYData(copy=False),
legend=curve.getLegend(),
info=curve.getInfo(),
linestyle=linestyle)
else:
_logger.debug("unhandled event %s", str(ddict['event']))
[docs] def updateLegends(self, *args):
"""Sync the LegendSelector widget displayed info with the plot.
"""
legendList = []
for curve in self.plot.getAllCurves(withhidden=True):
legend = curve.getLegend()
# Use active color if curve is active
if legend == self.plot.getActiveCurve(just_legend=True):
color = qt.QColor(self.plot.getActiveCurveColor())
else:
color = qt.QColor.fromRgbF(*curve.getColor())
curveInfo = {
'color': color,
'linewidth': curve.getLineWidth(),
'linestyle': curve.getLineStyle(),
'symbol': curve.getSymbol(),
'selected': not self.plot.isCurveHidden(legend)}
legendList.append((legend, curveInfo))
self._legendWidget.setLegendList(legendList)
def _visibilityChangedHandler(self, visible):
if visible:
self.updateLegends()
if not self._isConnected:
self.plot.sigContentChanged.connect(self.updateLegends)
self.plot.sigActiveCurveChanged.connect(self.updateLegends)
self._isConnected = True
else:
if self._isConnected:
self.plot.sigContentChanged.disconnect(self.updateLegends)
self.plot.sigActiveCurveChanged.disconnect(self.updateLegends)
self._isConnected = False
[docs] def showEvent(self, event):
"""Make sure this widget is raised when it is shown
(when it is first created as a tab in PlotWindow or when it is shown
again after hiding).
"""
self.raise_()