class MessagesWidget(QWidget):
    """
    An iconified multiple message display area.

    `MessagesWidget` displays a short message along with an icon. If there
    are multiple messages they are summarized. The user can click on the
    widget to display the full message text in a popup view.
    """
    #: Signal emitted when an embedded html link is clicked
    #: (if `openExternalLinks` is `False`).
    linkActivated = Signal(str)

    #: Signal emitted when an embedded html link is hovered.
    linkHovered = Signal(str)

    Severity = Severity
    #: General informative message.
    Information = Severity.Information
    #: A warning message severity.
    Warning = Severity.Warning
    #: An error message severity.
    Error = Severity.Error

    Message = Message

    def __init__(self,
                 parent=None,
                 openExternalLinks=False,
                 defaultStyleSheet="",
                 **kwargs):
        kwargs.setdefault(
            "sizePolicy", QSizePolicy(QSizePolicy.Minimum,
                                      QSizePolicy.Minimum))
        super().__init__(parent, **kwargs)
        self.__openExternalLinks = openExternalLinks  # type: bool
        self.__messages = OrderedDict()  # type: Dict[Hashable, Message]
        #: The full (joined all messages text - rendered as html), displayed
        #: in a tooltip.
        self.__fulltext = ""
        #: The full text displayed in a popup. Is empty if the message is
        #: short
        self.__popuptext = ""
        #: Leading icon
        self.__iconwidget = IconWidget(
            sizePolicy=QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
        #: Inline  message text
        self.__textlabel = QLabel(
            wordWrap=False,
            textInteractionFlags=Qt.LinksAccessibleByMouse,
            openExternalLinks=self.__openExternalLinks,
            sizePolicy=QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum))
        #: Indicator that extended contents are accessible with a click on the
        #: widget.
        self.__popupicon = QLabel(
            sizePolicy=QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum),
            text="\N{VERTICAL ELLIPSIS}",
            visible=False,
        )
        self.__textlabel.linkActivated.connect(self.linkActivated)
        self.__textlabel.linkHovered.connect(self.linkHovered)
        self.setLayout(QHBoxLayout())
        self.layout().setContentsMargins(2, 1, 2, 1)
        self.layout().setSpacing(0)
        self.layout().addWidget(self.__iconwidget)
        self.layout().addSpacing(4)
        self.layout().addWidget(self.__textlabel)
        self.layout().addWidget(self.__popupicon)
        self.__textlabel.setAttribute(Qt.WA_MacSmallSize)
        self.__defaultStyleSheet = defaultStyleSheet

        self.anim = QPropertyAnimation(self.__iconwidget, b"opacity")
        self.anim.setDuration(350)
        self.anim.setStartValue(1)
        self.anim.setKeyValueAt(0.5, 0)
        self.anim.setEndValue(1)
        self.anim.setEasingCurve(QEasingCurve.OutQuad)
        self.anim.setLoopCount(5)

    def sizeHint(self):
        sh = super().sizeHint()
        h = self.style().pixelMetric(QStyle.PM_SmallIconSize)
        if all(m.isEmpty() for m in self.messages()):
            sh.setWidth(0)
        return sh.expandedTo(QSize(0, h + 2))

    def minimumSizeHint(self):
        msh = super().minimumSizeHint()
        h = self.style().pixelMetric(QStyle.PM_SmallIconSize)
        if all(m.isEmpty() for m in self.messages()):
            msh.setWidth(0)
        else:
            msh.setWidth(h + 2)
        return msh.expandedTo(QSize(0, h + 2))

    def setOpenExternalLinks(self, state):
        # type: (bool) -> None
        """
        If `True` then `linkActivated` signal will be emitted when the user
        clicks on an html link in a message, otherwise links are opened
        using `QDesktopServices.openUrl`
        """
        # TODO: update popup if open
        self.__openExternalLinks = state
        self.__textlabel.setOpenExternalLinks(state)

    def openExternalLinks(self):
        # type: () -> bool
        """
        """
        return self.__openExternalLinks

    def setDefaultStyleSheet(self, css):
        # type: (str) -> None
        """
        Set a default css to apply to the rendered text.

        Parameters
        ----------
        css : str
            A css style sheet as supported by Qt's Rich Text support.

        Note
        ----
        Not to be confused with `QWidget.styleSheet`

        See Also
        --------
        `Supported HTML Subset`_

        .. _`Supported HTML Subset`:
            http://doc.qt.io/qt-5/richtext-html-subset.html
        """
        if self.__defaultStyleSheet != css:
            self.__defaultStyleSheet = css
            self.__update()

    def defaultStyleSheet(self):
        """
        Returns
        -------
        css : str
            The current style sheet
        """
        return self.__defaultStyleSheet

    def setMessage(self, message_id, message):
        # type: (Hashable, Message) -> None
        """
        Add a `message` for `message_id` to the current display.

        Note
        ----
        Set an empty `Message` instance to clear the message display but
        retain the relative ordering in the display should a message for
        `message_id` reactivate.
        """
        self.__messages[message_id] = message
        self.__update()

    def removeMessage(self, message_id):
        # type: (Hashable) -> None
        """
        Remove message for `message_id` from the display.

        Note
        ----
        Setting an empty `Message` instance will also clear the display,
        however the relative ordering of the messages will be retained,
        should the `message_id` 'reactivate'.
        """
        del self.__messages[message_id]
        self.__update()

    def setMessages(self, messages):
        # type: (Union[Iterable[Tuple[Hashable, Message]], Dict[Hashable, Message]]) -> None
        """
        Set multiple messages in a single call.
        """
        messages = OrderedDict(messages)
        self.__messages.update(messages)
        self.__update()

    def clear(self):
        # type: () -> None
        """
        Clear all messages.
        """
        self.__messages.clear()
        self.__update()

    def messages(self):
        # type: () -> List[Message]
        """
        Return all set messages.

        Returns
        -------
        messages: `List[Message]`
        """
        return list(self.__messages.values())

    def summarize(self):
        # type: () -> Message
        """
        Summarize all the messages into a single message.
        """
        messages = [m for m in self.__messages.values() if not m.isEmpty()]
        if messages:
            return summarize(messages)
        else:
            return Message()

    def flashIcon(self):
        for message in self.messages():
            if message.severity != Severity.Information:
                self.anim.start(QPropertyAnimation.KeepWhenStopped)
                break

    @staticmethod
    def __styled(css, html):
        # Prepend css style sheet before a html fragment.
        if css.strip():
            return "<style>\n" + escape(css) + "\n</style>\n" + html
        else:
            return html

    def __update(self):
        """
        Update the current display state.
        """
        self.ensurePolished()
        summary = self.summarize()
        icon = message_icon(summary)
        self.__iconwidget.setIcon(icon)
        self.__iconwidget.setVisible(not (summary.isEmpty() or icon.isNull()))
        self.anim.start(QPropertyAnimation.KeepWhenStopped)
        self.__textlabel.setTextFormat(summary.textFormat)
        self.__textlabel.setText(summary.text)
        self.__textlabel.setVisible(bool(summary.text))
        messages = [m for m in self.__messages.values() if not m.isEmpty()]
        if messages:
            messages = sorted(messages,
                              key=attrgetter("severity"),
                              reverse=True)
            fulltext = "<hr/>".join(m.asHtml() for m in messages)
        else:
            fulltext = ""
        self.__fulltext = fulltext
        self.setToolTip(self.__styled(self.__defaultStyleSheet, fulltext))

        def is_short(m):
            return not (m.informativeText or m.detailedText)

        if not messages or len(messages) == 1 and is_short(messages[0]):
            self.__popuptext = ""
        else:
            self.__popuptext = fulltext
        self.__popupicon.setVisible(bool(self.__popuptext))
        self.layout().activate()

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            if self.__popuptext:
                popup = QMenu(self)
                label = QLabel(
                    self,
                    textInteractionFlags=Qt.TextBrowserInteraction,
                    openExternalLinks=self.__openExternalLinks,
                )
                label.setText(
                    self.__styled(self.__defaultStyleSheet, self.__popuptext))

                label.linkActivated.connect(self.linkActivated)
                label.linkHovered.connect(self.linkHovered)
                action = QWidgetAction(popup)
                action.setDefaultWidget(label)
                popup.addAction(action)
                popup.popup(event.globalPos(), action)
                event.accept()
            return
        else:
            super().mousePressEvent(event)

    def enterEvent(self, event):
        super().enterEvent(event)
        self.update()

    def leaveEvent(self, event):
        super().leaveEvent(event)
        self.update()

    def changeEvent(self, event):
        super().changeEvent(event)
        self.update()

    def paintEvent(self, event):
        opt = QStyleOption()
        opt.initFrom(self)
        if not self.__popupicon.isVisible():
            return

        if not (opt.state & QStyle.State_MouseOver
                or opt.state & QStyle.State_HasFocus):
            return

        palette = opt.palette  # type: QPalette
        if opt.state & QStyle.State_HasFocus:
            pen = QPen(palette.color(QPalette.Highlight))
        else:
            pen = QPen(palette.color(QPalette.Dark))

        if self.__fulltext and \
                opt.state & QStyle.State_MouseOver and \
                opt.state & QStyle.State_Active:
            g = QLinearGradient()
            g.setCoordinateMode(QLinearGradient.ObjectBoundingMode)
            base = palette.color(QPalette.Window)
            base.setAlpha(90)
            g.setColorAt(0, base.lighter(200))
            g.setColorAt(0.6, base)
            g.setColorAt(1.0, base.lighter(200))
            brush = QBrush(g)
        else:
            brush = QBrush(Qt.NoBrush)
        p = QPainter(self)
        p.setBrush(brush)
        p.setPen(pen)
        p.drawRect(opt.rect.adjusted(0, 0, -1, -1))
Exemple #2
0
class GraphicsIconItem(QGraphicsWidget):
    """
    A graphics item displaying an :class:`QIcon`.
    """
    def __init__(self, parent=None, icon=None, iconSize=None, **kwargs):
        QGraphicsItem.__init__(self, parent, **kwargs)
        self.setFlag(QGraphicsItem.ItemUsesExtendedStyleOption, True)

        if icon is None:
            icon = QIcon()

        if iconSize is None:
            style = QApplication.instance().style()
            size = style.pixelMetric(style.PM_LargeIconSize)
            iconSize = QSize(size, size)

        self.__transformationMode = Qt.SmoothTransformation

        self.__iconSize = QSize(iconSize)
        self.__icon = QIcon(icon)

        self._opacity = 1
        self.anim = QPropertyAnimation(self, b"opacity")
        self.anim.setDuration(350)
        self.anim.setStartValue(1)
        self.anim.setKeyValueAt(0.5, 0)
        self.anim.setEndValue(1)
        self.anim.setEasingCurve(QEasingCurve.OutQuad)
        self.anim.setLoopCount(5)

    def setIcon(self, icon):
        """
        Set the icon (:class:`QIcon`).
        """
        if self.__icon != icon:
            self.__icon = QIcon(icon)
            self.update()

    def getOpacity(self):
        return self._opacity

    def setOpacity(self, o):
        self._opacity = o
        self.update()

    opacity = Property(float, fget=getOpacity, fset=setOpacity)

    def icon(self):
        """
        Return the icon (:class:`QIcon`).
        """
        return QIcon(self.__icon)

    def setIconSize(self, size):
        """
        Set the icon (and this item's) size (:class:`QSize`).
        """
        if self.__iconSize != size:
            self.prepareGeometryChange()
            self.__iconSize = QSize(size)
            self.update()

    def iconSize(self):
        """
        Return the icon size (:class:`QSize`).
        """
        return QSize(self.__iconSize)

    def setTransformationMode(self, mode):
        """
        Set pixmap transformation mode. (`Qt.SmoothTransformation` or
        `Qt.FastTransformation`).

        """
        if self.__transformationMode != mode:
            self.__transformationMode = mode
            self.update()

    def transformationMode(self):
        """
        Return the pixmap transformation mode.
        """
        return self.__transformationMode

    def boundingRect(self):
        return QRectF(0, 0, self.__iconSize.width(), self.__iconSize.height())

    def paint(self, painter, option, widget=None):
        if not self.__icon.isNull():
            if option.state & QStyle.State_Selected:
                mode = QIcon.Selected
            elif option.state & QStyle.State_Enabled:
                mode = QIcon.Normal
            elif option.state & QStyle.State_Active:
                mode = QIcon.Active
            else:
                mode = QIcon.Disabled

            w, h = self.__iconSize.width(), self.__iconSize.height()
            target = QRect(0, 0, w, h)
            painter.setRenderHint(
                QPainter.SmoothPixmapTransform,
                self.__transformationMode == Qt.SmoothTransformation
            )
            painter.setOpacity(self._opacity)
            self.__icon.paint(painter, target, Qt.AlignCenter, mode)
Exemple #3
0
class GraphicsIconItem(QGraphicsWidget):
    """
    A graphics item displaying an :class:`QIcon`.
    """
    def __init__(self, parent=None, icon=QIcon(), iconSize=QSize(), **kwargs):
        # type: (Optional[QGraphicsItem], QIcon, QSize, Any) -> None
        super().__init__(parent, **kwargs)
        self.setFlag(QGraphicsItem.ItemUsesExtendedStyleOption, True)

        if icon is None:
            icon = QIcon()

        if iconSize is None or iconSize.isNull():
            style = QApplication.instance().style()
            size = style.pixelMetric(style.PM_LargeIconSize)
            iconSize = QSize(size, size)

        self.__transformationMode = Qt.SmoothTransformation

        self.__iconSize = QSize(iconSize)
        self.__icon = QIcon(icon)

        self.anim = QPropertyAnimation(self, b"opacity")
        self.anim.setDuration(350)
        self.anim.setStartValue(1)
        self.anim.setKeyValueAt(0.5, 0)
        self.anim.setEndValue(1)
        self.anim.setEasingCurve(QEasingCurve.OutQuad)
        self.anim.setLoopCount(5)

    def setIcon(self, icon):
        # type: (QIcon) -> None
        """
        Set the icon (:class:`QIcon`).
        """
        if self.__icon != icon:
            self.__icon = QIcon(icon)
            self.update()

    def icon(self):
        # type: () -> QIcon
        """
        Return the icon (:class:`QIcon`).
        """
        return QIcon(self.__icon)

    def setIconSize(self, size):
        # type: (QSize) -> None
        """
        Set the icon (and this item's) size (:class:`QSize`).
        """
        if self.__iconSize != size:
            self.prepareGeometryChange()
            self.__iconSize = QSize(size)
            self.update()

    def iconSize(self):
        # type: () -> QSize
        """
        Return the icon size (:class:`QSize`).
        """
        return QSize(self.__iconSize)

    def setTransformationMode(self, mode):
        # type: (Qt.TransformationMode) -> None
        """
        Set pixmap transformation mode. (`Qt.SmoothTransformation` or
        `Qt.FastTransformation`).

        """
        if self.__transformationMode != mode:
            self.__transformationMode = mode
            self.update()

    def transformationMode(self):
        # type: () -> Qt.TransformationMode
        """
        Return the pixmap transformation mode.
        """
        return self.__transformationMode

    def boundingRect(self):
        # type: () -> QRectF
        return QRectF(0, 0, self.__iconSize.width(), self.__iconSize.height())

    def paint(self, painter, option, widget=None):
        # type: (QPainter, QStyleOptionGraphicsItem, Optional[QWidget]) -> None
        if not self.__icon.isNull():
            if option.state & QStyle.State_Selected:
                mode = QIcon.Selected
            elif option.state & QStyle.State_Enabled:
                mode = QIcon.Normal
            elif option.state & QStyle.State_Active:
                mode = QIcon.Active
            else:
                mode = QIcon.Disabled

            w, h = self.__iconSize.width(), self.__iconSize.height()
            target = QRect(0, 0, w, h)
            painter.setRenderHint(
                QPainter.SmoothPixmapTransform,
                self.__transformationMode == Qt.SmoothTransformation)
            self.__icon.paint(painter, target, Qt.AlignCenter, mode)
Exemple #4
0
class MessagesWidget(QWidget):
    """
    An iconified multiple message display area.

    `MessagesWidget` displays a short message along with an icon. If there
    are multiple messages they are summarized. The user can click on the
    widget to display the full message text in a popup view.
    """
    #: Signal emitted when an embedded html link is clicked
    #: (if `openExternalLinks` is `False`).
    linkActivated = Signal(str)

    #: Signal emitted when an embedded html link is hovered.
    linkHovered = Signal(str)

    Severity = Severity
    #: General informative message.
    Information = Severity.Information
    #: A warning message severity.
    Warning = Severity.Warning
    #: An error message severity.
    Error = Severity.Error

    Message = Message

    def __init__(self, parent=None, openExternalLinks=False,
                 defaultStyleSheet="", **kwargs):
        kwargs.setdefault(
            "sizePolicy",
            QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
        )
        super().__init__(parent, **kwargs)
        self.__openExternalLinks = openExternalLinks  # type: bool
        self.__messages = OrderedDict()  # type: Dict[Hashable, Message]
        #: The full (joined all messages text - rendered as html), displayed
        #: in a tooltip.
        self.__fulltext = ""
        #: The full text displayed in a popup. Is empty if the message is
        #: short
        self.__popuptext = ""
        #: Leading icon
        self.__iconwidget = IconWidget(
            sizePolicy=QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        )
        #: Inline  message text
        self.__textlabel = QLabel(
            wordWrap=False,
            textInteractionFlags=Qt.LinksAccessibleByMouse,
            openExternalLinks=self.__openExternalLinks,
            sizePolicy=QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
        )
        #: Indicator that extended contents are accessible with a click on the
        #: widget.
        self.__popupicon = QLabel(
            sizePolicy=QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum),
            text="\N{VERTICAL ELLIPSIS}",
            visible=False,
        )
        self.__textlabel.linkActivated.connect(self.linkActivated)
        self.__textlabel.linkHovered.connect(self.linkHovered)
        self.setLayout(QHBoxLayout())
        self.layout().setContentsMargins(2, 1, 2, 1)
        self.layout().setSpacing(0)
        self.layout().addWidget(self.__iconwidget)
        self.layout().addSpacing(4)
        self.layout().addWidget(self.__textlabel)
        self.layout().addWidget(self.__popupicon)
        self.__textlabel.setAttribute(Qt.WA_MacSmallSize)
        self.__defaultStyleSheet = defaultStyleSheet

        self.anim = QPropertyAnimation(self.__iconwidget, b"opacity")
        self.anim.setDuration(350)
        self.anim.setStartValue(1)
        self.anim.setKeyValueAt(0.5, 0)
        self.anim.setEndValue(1)
        self.anim.setEasingCurve(QEasingCurve.OutQuad)
        self.anim.setLoopCount(5)

    def sizeHint(self):
        sh = super().sizeHint()
        h = self.style().pixelMetric(QStyle.PM_SmallIconSize)
        if all(m.isEmpty() for m in self.messages()):
            sh.setWidth(0)
        return sh.expandedTo(QSize(0, h + 2))

    def minimumSizeHint(self):
        msh = super().minimumSizeHint()
        h = self.style().pixelMetric(QStyle.PM_SmallIconSize)
        if all(m.isEmpty() for m in self.messages()):
            msh.setWidth(0)
        else:
            msh.setWidth(h + 2)
        return msh.expandedTo(QSize(0, h + 2))

    def setOpenExternalLinks(self, state):
        # type: (bool) -> None
        """
        If `True` then `linkActivated` signal will be emitted when the user
        clicks on an html link in a message, otherwise links are opened
        using `QDesktopServices.openUrl`
        """
        # TODO: update popup if open
        self.__openExternalLinks = state
        self.__textlabel.setOpenExternalLinks(state)

    def openExternalLinks(self):
        # type: () -> bool
        """
        """
        return self.__openExternalLinks

    def setDefaultStyleSheet(self, css):
        # type: (str) -> None
        """
        Set a default css to apply to the rendered text.

        Parameters
        ----------
        css : str
            A css style sheet as supported by Qt's Rich Text support.

        Note
        ----
        Not to be confused with `QWidget.styleSheet`

        See Also
        --------
        `Supported HTML Subset`_

        .. _`Supported HTML Subset`:
            http://doc.qt.io/qt-5/richtext-html-subset.html
        """
        if self.__defaultStyleSheet != css:
            self.__defaultStyleSheet = css
            self.__update()

    def defaultStyleSheet(self):
        """
        Returns
        -------
        css : str
            The current style sheet
        """
        return self.__defaultStyleSheet

    def setMessage(self, message_id, message):
        # type: (Hashable, Message) -> None
        """
        Add a `message` for `message_id` to the current display.

        Note
        ----
        Set an empty `Message` instance to clear the message display but
        retain the relative ordering in the display should a message for
        `message_id` reactivate.
        """
        self.__messages[message_id] = message
        self.__update()

    def removeMessage(self, message_id):
        # type: (Hashable) -> None
        """
        Remove message for `message_id` from the display.

        Note
        ----
        Setting an empty `Message` instance will also clear the display,
        however the relative ordering of the messages will be retained,
        should the `message_id` 'reactivate'.
        """
        del self.__messages[message_id]
        self.__update()

    def setMessages(self, messages):
        # type: (Union[Iterable[Tuple[Hashable, Message]], Dict[Hashable, Message]]) -> None
        """
        Set multiple messages in a single call.
        """
        messages = OrderedDict(messages)
        self.__messages.update(messages)
        self.__update()

    def clear(self):
        # type: () -> None
        """
        Clear all messages.
        """
        self.__messages.clear()
        self.__update()

    def messages(self):
        # type: () -> List[Message]
        """
        Return all set messages.

        Returns
        -------
        messages: `List[Message]`
        """
        return list(self.__messages.values())

    def summarize(self):
        # type: () -> Message
        """
        Summarize all the messages into a single message.
        """
        messages = [m for m in self.__messages.values() if not m.isEmpty()]
        if messages:
            return summarize(messages)
        else:
            return Message()

    def flashIcon(self):
        for message in self.messages():
            if message.severity != Severity.Information:
                self.anim.start(QPropertyAnimation.KeepWhenStopped)
                break

    @staticmethod
    def __styled(css, html):
        # Prepend css style sheet before a html fragment.
        if css.strip():
            return "<style>\n" + escape(css) + "\n</style>\n" + html
        else:
            return html

    def __update(self):
        """
        Update the current display state.
        """
        self.ensurePolished()
        summary = self.summarize()
        icon = message_icon(summary)
        self.__iconwidget.setIcon(icon)
        self.__iconwidget.setVisible(not (summary.isEmpty() or icon.isNull()))
        self.anim.start(QPropertyAnimation.KeepWhenStopped)
        self.__textlabel.setTextFormat(summary.textFormat)
        self.__textlabel.setText(summary.text)
        self.__textlabel.setVisible(bool(summary.text))
        messages = [m for m in self.__messages.values() if not m.isEmpty()]
        if messages:
            messages = sorted(messages, key=attrgetter("severity"),
                              reverse=True)
            fulltext = "<hr/>".join(m.asHtml() for m in messages)
        else:
            fulltext = ""
        self.__fulltext = fulltext
        self.setToolTip(self.__styled(self.__defaultStyleSheet, fulltext))

        def is_short(m):
            return not (m.informativeText or m.detailedText)

        if not messages or len(messages) == 1 and is_short(messages[0]):
            self.__popuptext = ""
        else:
            self.__popuptext = fulltext
        self.__popupicon.setVisible(bool(self.__popuptext))
        self.layout().activate()

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            if self.__popuptext:
                popup = QMenu(self)
                label = QLabel(
                    self, textInteractionFlags=Qt.TextBrowserInteraction,
                    openExternalLinks=self.__openExternalLinks,
                )
                label.setText(self.__styled(self.__defaultStyleSheet,
                                            self.__popuptext))

                label.linkActivated.connect(self.linkActivated)
                label.linkHovered.connect(self.linkHovered)
                action = QWidgetAction(popup)
                action.setDefaultWidget(label)
                popup.addAction(action)
                popup.popup(event.globalPos(), action)
                event.accept()
            return
        else:
            super().mousePressEvent(event)

    def enterEvent(self, event):
        super().enterEvent(event)
        self.update()

    def leaveEvent(self, event):
        super().leaveEvent(event)
        self.update()

    def changeEvent(self, event):
        super().changeEvent(event)
        self.update()

    def paintEvent(self, event):
        opt = QStyleOption()
        opt.initFrom(self)
        if not self.__popupicon.isVisible():
            return

        if not (opt.state & QStyle.State_MouseOver or
                opt.state & QStyle.State_HasFocus):
            return

        palette = opt.palette  # type: QPalette
        if opt.state & QStyle.State_HasFocus:
            pen = QPen(palette.color(QPalette.Highlight))
        else:
            pen = QPen(palette.color(QPalette.Dark))

        if self.__fulltext and \
                opt.state & QStyle.State_MouseOver and \
                opt.state & QStyle.State_Active:
            g = QLinearGradient()
            g.setCoordinateMode(QLinearGradient.ObjectBoundingMode)
            base = palette.color(QPalette.Window)
            base.setAlpha(90)
            g.setColorAt(0, base.lighter(200))
            g.setColorAt(0.6, base)
            g.setColorAt(1.0, base.lighter(200))
            brush = QBrush(g)
        else:
            brush = QBrush(Qt.NoBrush)
        p = QPainter(self)
        p.setBrush(brush)
        p.setPen(pen)
        p.drawRect(opt.rect.adjusted(0, 0, -1, -1))