Skip to main content
`Ctrl`+`K`

# Source code for silx.gui.plot.LegendSelector

    # /*##########################################################################
    #
    # Copyright (c) 2004-2023 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__ = "16/10/2017"

    import logging
    import weakref

    import numpy

    from .. import qt, colors
    from ..widgets.LegendIconWidget import LegendIconWidget
    from . import items
    from ...utils.deprecation import deprecated

    _logger = logging.getLogger(__name__)

    class LegendIcon(LegendIconWidget):
        """Object displaying a curve linestyle and symbol.

        :param QWidget parent: See :class:`QWidget`
        :param Union[~silx.gui.plot.items.Curve,None] curve:
            Curve with which to synchronize
        """

        def __init__(self, parent=None, curve=None):
            super(LegendIcon, self).__init__(parent)
            self._curveRef = None
            self.setCurve(curve)

        def getCurve(self):
            """Returns curve associated to this widget

            :rtype: Union[~silx.gui.plot.items.Curve,None]
            """
            return None if self._curveRef is None else self._curveRef()

        def setCurve(self, curve):
            """Set the curve with which to synchronize this widget.

            :param curve: Union[~silx.gui.plot.items.Curve,None]
            """
            assert curve is None or isinstance(curve, items.Curve)

            previousCurve = self.getCurve()
            if curve == previousCurve:
                return

            if previousCurve is not None:
                previousCurve.sigItemChanged.disconnect(self._curveChanged)

            self._curveRef = None if curve is None else weakref.ref(curve)

            if curve is not None:
                curve.sigItemChanged.connect(self._curveChanged)

            self._update()

        def _update(self):
            """Update widget according to current curve state."""
            curve = self.getCurve()
            if curve is None:
                _logger.error("Curve no more exists")
                self.setEnabled(False)
                return

            style = curve.getCurrentStyle()

            self.setEnabled(curve.isVisible())
            self.setSymbol(style.getSymbol())
            self.setLineWidth(style.getLineWidth())
            self.setLineStyle(style.getLineStyle())

            color = style.getColor()
            if numpy.asarray(color).ndim != 1:
                # array of colors, use transparent black
                color = 0.0, 0.0, 0.0, 0.0
            color = colors.rgba(color)  # Make sure it is float in [0, 1]
            alpha = curve.getAlpha()
            color = qt.QColor.fromRgbF(color[0], color[1], color[2], color[3] * alpha)
            self.setLineColor(color)
            self.setSymbolColor(color)
            self.update()  # TODO this should not be needed

        def _curveChanged(self, event):
            """Handle update of curve item

            :param event: Kind of change
            """
            if event in (
                items.ItemChangedType.VISIBLE,
                items.ItemChangedType.SYMBOL,
                items.ItemChangedType.SYMBOL_SIZE,
                items.ItemChangedType.LINE_WIDTH,
                items.ItemChangedType.LINE_STYLE,
                items.ItemChangedType.COLOR,
                items.ItemChangedType.ALPHA,
                items.ItemChangedType.HIGHLIGHTED,
                items.ItemChangedType.HIGHLIGHTED_STYLE,
            ):
                self._update()

    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
        itemRole = qt.Qt.UserRole + 6

        def __init__(self, legendList=None, parent=None):
            super(LegendModel, self).__init__(parent)
            if legendList is None:
                legendList = []
            self.legendList = []
            self.insertLegendList(0, legendList)
            self._palette = qt.QPalette()

        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]
            isActive = item[1].get("active", False)
            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 isActive:
                    brush = self._palette.brush(qt.QPalette.Active, qt.QPalette.Highlight)
                elif 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
                if isActive:
                    brush = self._palette.brush(
                        qt.QPalette.Active, qt.QPalette.HighlightedText
                    )
                else:
                    brush = self._palette.brush(qt.QPalette.Active, qt.QPalette.WindowText)
                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]
            elif role == self.itemRole:
                return item[5]
            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"] = 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
                elif role == self.itemRole:
                    item[5] = 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

        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 LegendIconWidget.isEmptyLineStyle(linestyle):
                    # 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 LegendIconWidget.isEmptySymbol(symbol):
                    # 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,
                    icon.get("item", None),
                ]
                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

        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

    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

        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(0.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.setPen(textBrush.color())
            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:
                if qt.BINDING == "PyQt5":
                    position = event.globalPos()
                else:  # Qt6
                    position = event.globalPosition().toPoint()
                self.contextMenu.exec(position, 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)

    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(parent=self)
            self.setModel(model)
            self.setContextMenu(contextMenu)

        def setLegendList(self, legendList, row=None):
            if row is not None:
                model = self.model()
                model.insertLegendList(row, legendList)
            elif len(legendList) != self.model().rowCount():
                self.clear()
                model = self.model()
                model.insertLegendList(0, legendList)
            else:
                model = self.model()
                for i, (new_legend, icon) in enumerate(legendList):
                    modelIndex = model.index(i)
                    legend = str(modelIndex.data(qt.Qt.DisplayRole))
                    if new_legend != legend:
                        model.setData(modelIndex, new_legend, qt.Qt.DisplayRole)

                    color = modelIndex.data(LegendModel.iconColorRole)
                    new_color = icon.get("color", None)
                    if new_color != color:
                        model.setData(modelIndex, new_color, LegendModel.iconColorRole)

                    linewidth = modelIndex.data(LegendModel.iconLineWidthRole)
                    new_linewidth = icon.get("linewidth", 1.0)
                    if new_linewidth != linewidth:
                        model.setData(
                            modelIndex, new_linewidth, LegendModel.iconLineWidthRole
                        )

                    linestyle = modelIndex.data(LegendModel.iconLineStyleRole)
                    new_linestyle = icon.get("linestyle", None)
                    visible = not LegendIconWidget.isEmptyLineStyle(new_linestyle)
                    model.setData(modelIndex, visible, LegendModel.showLineRole)
                    if new_linestyle != linestyle:
                        model.setData(
                            modelIndex, new_linestyle, LegendModel.iconLineStyleRole
                        )

                    symbol = modelIndex.data(LegendModel.iconSymbolRole)
                    new_symbol = icon.get("symbol", None)
                    visible = not LegendIconWidget.isEmptySymbol(new_symbol)
                    model.setData(modelIndex, visible, LegendModel.showSymbolRole)
                    if new_symbol != symbol:
                        model.setData(modelIndex, new_symbol, LegendModel.iconSymbolRole)

                    selected = modelIndex.data(qt.Qt.CheckStateRole)
                    new_selected = icon.get("selected", True)
                    if new_selected != selected:
                        model.setData(modelIndex, new_selected, qt.Qt.CheckStateRole)

                    item = modelIndex.data(LegendModel.itemRole)
                    newItem = icon.get("item", None)
                    if item is not newItem:
                        model.setData(modelIndex, newItem, LegendModel.itemRole)
            _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": modelIndex.data(LegendModel.iconLineStyleRole),
                    "symbol": str(modelIndex.data(LegendModel.iconSymbolRole)),
                },
                "selected": modelIndex.data(qt.Qt.CheckStateRole),
                "type": str(modelIndex.data()),
                "item": modelIndex.data(LegendModel.itemRole),
            }
            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)

    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())

    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
        def plot(self):
            """The :class:`.PlotWindow` this widget is attached to."""
            return self._plotRef()

        @deprecated(reason="No longer needed", since_version="2.0.0")
        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 changed
            :param str newLegend: The new legend of the curve
            """
            is_active = self.plot.getActiveCurve(just_legend=True) == oldLegend
            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,
            )
            if is_active:
                self.plot.setActiveCurve(newLegend)

        def _legendSignalHandler(self, ddict):
            """Handles events from the LegendListView signal"""
            _logger.debug("Legend signal ddict = %s", str(ddict))
            # If item is not provided, retrieve it from its legend
            curve = ddict.get("item", None)
            if curve is None:
                curve = self.plot.getCurve(ddict["legend"])

            if ddict["event"] == "legendClicked":
                if ddict["button"] == "left":
                    self.plot.setActiveCurve(curve)

            elif ddict["event"] == "removeCurve":
                self.plot.removeItem(curve)

            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()
                    wasActive = self.plot.getActiveCurve() is curve
                    self.plot.removeItem(curve)
                    curve.setName(newLegend)
                    self.plot.addItem(curve)
                    if wasActive:
                        self.plot.setActiveCurve(curve)

            elif ddict["event"] == "setActiveCurve":
                self.plot.setActiveCurve(curve)

            elif ddict["event"] == "checkBoxClicked":
                curve.setVisible(ddict["selected"])

            elif ddict["event"] in ["mapToRight", "mapToLeft"]:
                curve.setYAxis("right" if ddict["event"] == "mapToRight" else "left")

            elif ddict["event"] == "togglePoints":
                curve.setSymbol(ddict["symbol"] if ddict["points"] else "")

            elif ddict["event"] == "toggleLine":
                curve.setLineStyle(ddict["linestyle"] if ddict["line"] else "")

            else:
                _logger.debug("unhandled event %s", str(ddict["event"]))

        def updateLegends(self, *args):
            """Sync the LegendSelector widget displayed info with the plot."""
            legendList = []
            for curve in self.plot.getAllCurves(withhidden=True):
                legend = curve.getName()
                # Use active color if curve is active
                isActive = legend == self.plot.getActiveCurve(just_legend=True)
                style = curve.getCurrentStyle()
                color = style.getColor()
                if numpy.asarray(color).ndim != 1:
                    # array of colors, use transparent black
                    color = 0.0, 0.0, 0.0, 0.0

                curveInfo = {
                    "color": qt.QColor.fromRgbF(*color),
                    "linewidth": style.getLineWidth(),
                    "linestyle": style.getLineStyle(),
                    "symbol": style.getSymbol(),
                    "selected": not self.plot.isCurveHidden(legend),
                    "active": isActive,
                    "item": curve,
                }
                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