コード例 #1
0
class WelcomeDialog(QDialog):
    """
    A welcome widget shown at startup presenting a series
    of buttons (actions) for a beginner to choose from.
    """
    triggered = Signal(QAction)

    def __init__(self, *args, **kwargs):
        showAtStartup = kwargs.pop("showAtStartup", True)
        feedbackUrl = kwargs.pop("feedbackUrl", "")
        super().__init__(*args, **kwargs)

        self.__triggeredAction = None  # type: Optional[QAction]
        self.__showAtStartupCheck = None
        self.__mainLayout = None
        self.__feedbackUrl = None
        self.__feedbackLabel = None

        self.setupUi()

        self.setFeedbackUrl(feedbackUrl)
        self.setShowAtStartup(showAtStartup)

    def setupUi(self):
        self.setLayout(QVBoxLayout())
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().setSpacing(0)

        self.__mainLayout = QVBoxLayout()
        self.__mainLayout.setContentsMargins(0, 40, 0, 40)
        self.__mainLayout.setSpacing(65)

        self.layout().addLayout(self.__mainLayout)

        self.setStyleSheet(WELCOME_WIDGET_BUTTON_STYLE)

        dir_path = Path(__file__).resolve()
        parent_path = dir_path.parent.parent
        icon_path = f'{str(parent_path)}/icons'

        guanzhu_box = QVBoxLayout()
        guanzhu_label = QLabel()
        guanzhu_pic = QPixmap(icon_path + '/weixin.png')
        guanzhu_label.setPixmap(guanzhu_pic)

        guanzhu_text = QLabel('关注我们')
        guanzhu_box.addWidget(guanzhu_label,
                              alignment=Qt.AlignVCenter | Qt.AlignHCenter)
        guanzhu_box.addWidget(guanzhu_text,
                              alignment=Qt.AlignVCenter | Qt.AlignHCenter)

        zan_box = QVBoxLayout()
        zan_label = QLabel()
        pixmap = QPixmap(icon_path + '/zan.png')
        zan_label.setPixmap(pixmap)

        zan_text = QLabel('赞赏开发者')
        zan_box.addWidget(zan_label,
                          alignment=Qt.AlignVCenter | Qt.AlignHCenter)
        zan_box.addWidget(zan_text,
                          alignment=Qt.AlignVCenter | Qt.AlignHCenter)

        weixinBox = QHBoxLayout()
        weixinBox.addLayout(guanzhu_box)
        weixinBox.addLayout(zan_box)

        self.layout().addLayout(weixinBox)

        bottom_bar = QWidget(objectName="bottom-bar")
        bottom_bar_layout = QHBoxLayout()
        bottom_bar_layout.setContentsMargins(20, 10, 20, 10)
        bottom_bar.setLayout(bottom_bar_layout)
        bottom_bar.setSizePolicy(QSizePolicy.MinimumExpanding,
                                 QSizePolicy.Maximum)

        # self.__showAtStartupCheck = QCheckBox(
        #     self.tr("启动时显示"), bottom_bar, checked=True
        # )
        # self.__showAtStartupCheck = QCheckBox(
        #     self.tr("启动时显示"), bottom_bar, checked=True
        # )
        self.__feedbackLabel = QLabel(
            textInteractionFlags=Qt.TextBrowserInteraction,
            openExternalLinks=True,
            visible=False,
        )

        # bottom_bar_layout.addWidget(
        #     self.__showAtStartupCheck, alignment=Qt.AlignVCenter | Qt.AlignLeft
        # )
        bottom_bar_layout.addWidget(self.__feedbackLabel,
                                    alignment=Qt.AlignVCenter | Qt.AlignRight)
        self.layout().addWidget(bottom_bar,
                                alignment=Qt.AlignBottom,
                                stretch=1)

        self.setSizeGripEnabled(False)
        self.setFixedSize(620, 590)

    def setShowAtStartup(self, show):
        # type: (bool) -> None
        """
        Set the 'Show at startup' check box state.
        """
        pass
        # if self.__showAtStartupCheck.isChecked() != show:
        #     # self.__showAtStartupCheck.setChecked(show)

    def showAtStartup(self):
        # type: () -> bool
        """
        Return the 'Show at startup' check box state.
        """
        # return self.__showAtStartupCheck.isChecked()
        return True

    def setFeedbackUrl(self, url):
        # type: (str) -> None
        """
        Set an 'feedback' url. When set a link is displayed in the bottom row.
        """
        self.__feedbackUrl = url
        if url:
            text = self.tr("帮助我们改进!")
            self.__feedbackLabel.setText('<a href="{url}">{text}</a>'.format(
                url=url, text=escape(text)))
        else:
            self.__feedbackLabel.setText("")
        self.__feedbackLabel.setVisible(bool(url))

    def addRow(self, actions, background="light-orange"):
        """Add a row with `actions`.
        """
        count = self.__mainLayout.count()
        self.insertRow(count, actions, background)

    def insertRow(self, index, actions, background="light-orange"):
        # type: (int, Iterable[QAction], Union[QColor, str]) -> None
        """Insert a row with `actions` at `index`.
        """
        widget = QWidget(objectName="icon-row")
        layout = QHBoxLayout()
        layout.setContentsMargins(40, 0, 40, 0)
        layout.setSpacing(65)
        widget.setLayout(layout)

        self.__mainLayout.insertWidget(index,
                                       widget,
                                       stretch=10,
                                       alignment=Qt.AlignCenter)

        for i, action in enumerate(actions):
            self.insertAction(index, i, action, background)

    def insertAction(self, row, index, action, background="light-orange"):
        """Insert `action` in `row` in position `index`.
        """
        button = self.createButton(action, background)
        self.insertButton(row, index, button)

    def insertButton(self, row, index, button):
        # type: (int, int, QToolButton) -> None
        """Insert `button` in `row` in position `index`.
        """
        item = self.__mainLayout.itemAt(row)
        layout = item.widget().layout()
        layout.insertWidget(index, button)
        button.triggered.connect(self.__on_actionTriggered)

    def createButton(self, action, background="light-orange"):
        # type: (QAction, Union[QColor, str]) -> QToolButton
        """Create a tool button for action.
        """
        button = WelcomeActionButton(self)
        button.setDefaultAction(action)
        button.setText(action.iconText())
        button.setIcon(decorate_welcome_icon(action.icon(), background))
        button.setToolTip(action.toolTip())
        button.setFixedSize(100, 100)
        button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
        button.setVisible(action.isVisible())
        font = QFont(button.font())
        font.setPointSize(13)
        button.setFont(font)

        return button

    def buttonAt(self, i, j):
        # type: (int, int) -> QToolButton
        """Return the button at i-t row and j-th column.
        """
        item = self.__mainLayout.itemAt(i)
        row = item.widget()
        item = row.layout().itemAt(j)
        return item.widget()

    def triggeredAction(self):
        # type: () -> Optional[QAction]
        """Return the action that was triggered by the user.
        """
        return self.__triggeredAction

    def showEvent(self, event):
        # Clear the triggered action before show.
        self.__triggeredAction = None
        super().showEvent(event)

    def __on_actionTriggered(self, action):
        # type: (QAction) -> None
        """Called when the button action is triggered.
        """
        self.triggered.emit(action)
        self.__triggeredAction = action
コード例 #2
0
ファイル: encodings.py プロジェクト: vishalbelsare/orange3
class SelectEncodingsWidget(QWidget):
    """
    Popup widget for selecting a set of text encodings for use in other parts
    of the GUI.
    """
    def __init__(self, *args, headingText="", **kwargs):
        super().__init__(*args, **kwargs)
        self.setLayout(QVBoxLayout())
        self.__top_label = QLabel(headingText,
                                  visible=bool(headingText),
                                  objectName="-top-heading-text")
        self.__top_label.setSizePolicy(QSizePolicy.Preferred,
                                       QSizePolicy.Fixed)
        self.__model = model = encodings_model()
        self.__model.setParent(self)

        self.__view = view = EncodingsView(
            self, uniformItemSizes=True, editTriggers=QListView.NoEditTriggers)
        self.__view.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Preferred)
        view.setModel(model)

        self.layout().addWidget(self.__top_label)
        self.layout().addWidget(view)

        buttons = QDialogButtonBox(
            standardButtons=QDialogButtonBox.RestoreDefaults)
        b = buttons.addButton("Select all", QDialogButtonBox.ActionRole)
        b.clicked.connect(self.selectAll)
        b = buttons.button(QDialogButtonBox.RestoreDefaults)
        b.clicked.connect(self.reset)
        self.layout().addWidget(buttons)
        self.setAttribute(Qt.WA_MacSmallSize)

    def headingText(self):
        # type: () -> str
        return self.__top_label.text()

    def setHeadingText(self, text):
        # type: (str) -> None
        self.__top_label.setText(text)
        self.__top_label.setVisible(bool(text))

    def setModel(self, model):
        if self.__model is not None:
            if self.__model.parent() is self:
                self.__model.deleteLater()
            self.__model = None

        self.__view.setModel(model)
        self.__model = model

    def model(self):
        return self.__model

    def selectedEncodings(self):
        # type: () -> List[str]
        """
        Return a list of currently selected (checked) encodings.
        """
        model = self.__model
        res = []
        for i in range(model.rowCount()):
            data = model.itemData(model.index(i, 0))
            if data.get(Qt.CheckStateRole) == Qt.Checked and \
                    EncodingNameRole in data:
                res.append(data[EncodingNameRole])
        return res

    @Slot()
    def selectAll(self):
        """
        Select all encodings.
        """
        model = self.__model
        for i in range(model.rowCount()):
            item = model.item(i)
            item.setCheckState(Qt.Checked)

    @Slot()
    def clearAll(self):
        """
        Clear (uncheck) all encodings.
        """
        model = self.__model
        for i in range(model.rowCount()):
            item = model.item(i)
            item.setCheckState(Qt.Checked)

    @Slot()
    def reset(self):
        """
        Reset the encodings model to the default selected set.
        """
        model = self.__model
        for i in range(model.rowCount()):
            item = model.item(i)
            co = item.data(CodecInfoRole)
            if isinstance(co, codecs.CodecInfo):
                state = co.name in DEFAULT_ENCODINGS
                item.setCheckState(Qt.Checked if state else Qt.Unchecked)
コード例 #3
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, **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)

    def sizeHint(self):
        sh = super().sizeHint()
        h = self.style().pixelMetric(QStyle.PM_SmallIconSize)
        return sh.expandedTo(QSize(0, h + 2))

    def openExternalLinks(self):
        # type: () -> bool
        """
        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`
        """
        return self.__openExternalLinks

    def setOpenExternalLinks(self, state):
        # type: (bool) -> None
        """
        """
        # TODO: update popup if open
        self.__openExternalLinks = state
        self.__textlabel.setOpenExternalLinks(state)

    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 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 __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.__textlabel.setTextFormat(summary.textFormat)
        self.__textlabel.setText(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(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,
                    text=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))
コード例 #4
0
ファイル: messagewidget.py プロジェクト: PrimozGodec/orange3
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

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

    @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.__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))
コード例 #5
0
ファイル: overlay.py プロジェクト: jacksujingcheng/orange3
class MessageWidget(QWidget):
    """
    A widget displaying a simple message to the user.

    This is an alternative to a full QMessageBox intended for inline
    modeless messages.

    [[icon] {Message text} (Ok) (Cancel)]
    """
    #: Emitted when a button with the AcceptRole is clicked
    accepted = Signal()
    #: Emitted when a button with the RejectRole is clicked
    rejected = Signal()
    #: Emitted when a button with the HelpRole is clicked
    helpRequested = Signal()
    #: Emitted when a button is clicked
    clicked = Signal(QAbstractButton)

    class StandardButton(enum.IntEnum):
        NoButton, Ok, Close, Help = 0x0, 0x1, 0x2, 0x4

    NoButton, Ok, Close, Help = list(StandardButton)

    class ButtonRole(enum.IntEnum):
        InvalidRole, AcceptRole, RejectRole, HelpRole = 0, 1, 2, 3

    InvalidRole, AcceptRole, RejectRole, HelpRole = list(ButtonRole)

    _Button = namedtuple("_Button", ["button", "role", "stdbutton"])

    def __init__(self,
                 parent=None,
                 icon=QIcon(),
                 text="",
                 wordWrap=False,
                 textFormat=Qt.AutoText,
                 standardButtons=NoButton,
                 **kwargs):
        super().__init__(parent, **kwargs)
        self.__text = text
        self.__icon = QIcon()
        self.__wordWrap = wordWrap
        self.__standardButtons = MessageWidget.NoButton
        self._buttons = []

        layout = QHBoxLayout()
        layout.setContentsMargins(8, 0, 8, 0)

        self.__iconlabel = QLabel(objectName="icon-label")
        self.__iconlabel.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        self.__textlabel = QLabel(objectName="text-label",
                                  text=text,
                                  wordWrap=wordWrap,
                                  textFormat=textFormat)

        if sys.platform == "darwin":
            self.__textlabel.setAttribute(Qt.WA_MacSmallSize)

        layout.addWidget(self.__iconlabel)
        layout.addWidget(self.__textlabel)

        self.setLayout(layout)
        self.setIcon(icon)
        self.setStandardButtons(standardButtons)

    def setText(self, text):
        """
        Set the current message text.

        :type message: str
        """
        if self.__text != text:
            self.__text = text
            self.__textlabel.setText(text)

    def text(self):
        """
        Return the current message text.

        :rtype: str
        """
        return self.__text

    def setIcon(self, icon):
        """
        Set the message icon.

        :type icon: QIcon | QPixmap | QString | QStyle.StandardPixmap
        """
        if isinstance(icon, QStyle.StandardPixmap):
            icon = self.style().standardIcon(icon)
        else:
            icon = QIcon(icon)

        if self.__icon != icon:
            self.__icon = QIcon(icon)
            if not self.__icon.isNull():
                size = self.style().pixelMetric(QStyle.PM_SmallIconSize, None,
                                                self)
                pm = self.__icon.pixmap(QSize(size, size))
            else:
                pm = QPixmap()

            self.__iconlabel.setPixmap(pm)
            self.__iconlabel.setVisible(not pm.isNull())

    def icon(self):
        """
        Return the current icon.

        :rtype: QIcon
        """
        return QIcon(self.__icon)

    def setWordWrap(self, wordWrap):
        """
        Set the message text wrap property

        :type wordWrap: bool
        """
        if self.__wordWrap != wordWrap:
            self.__wordWrap = wordWrap
            self.__textlabel.setWordWrap(wordWrap)

    def wordWrap(self):
        """
        Return the message text wrap property.

        :rtype: bool
        """
        return self.__wordWrap

    def setTextFormat(self, textFormat):
        """
        Set message text format

        :type textFormat: Qt.TextFormat
        """
        self.__textlabel.setTextFormat(textFormat)

    def textFormat(self):
        """
        Return the message text format.

        :rtype: Qt.TextFormat
        """
        return self.__textlabel.textFormat()

    def changeEvent(self, event):
        # reimplemented
        if event.type() == 177:  # QEvent.MacSizeChange:
            ...
        super().changeEvent(event)

    def setStandardButtons(self, buttons):
        for button in MessageWidget.StandardButton:
            existing = self.button(button)
            if button & buttons and existing is None:
                self.addButton(button)
            elif existing is not None:
                self.removeButton(existing)

    def standardButtons(self):
        return functools.reduce(
            operator.ior,
            (slot.stdbutton
             for slot in self._buttons if slot.stdbutton is not None),
            MessageWidget.NoButton)

    def addButton(self, button, *rolearg):
        """
        addButton(QAbstractButton, ButtonRole)
        addButton(str, ButtonRole)
        addButton(StandardButton)

        Add and return a button
        """
        stdbutton = None
        if isinstance(button, QAbstractButton):
            if len(rolearg) != 1:
                raise TypeError("Wrong number of arguments for "
                                "addButton(QAbstractButton, role)")
            role = rolearg[0]
        elif isinstance(button, MessageWidget.StandardButton):
            if len(rolearg) != 0:
                raise TypeError("Wrong number of arguments for "
                                "addButton(StandardButton)")
            stdbutton = button
            if button == MessageWidget.Ok:
                role = MessageWidget.AcceptRole
                button = QPushButton("Ok", default=False, autoDefault=False)
            elif button == MessageWidget.Close:
                role = MessageWidget.RejectRole
                #                 button = QPushButton(
                #                     default=False, autoDefault=False, flat=True,
                #                     icon=QIcon(self.style().standardIcon(
                #                                QStyle.SP_TitleBarCloseButton)))
                button = SimpleButton(icon=QIcon(self.style().standardIcon(
                    QStyle.SP_TitleBarCloseButton)))
            elif button == MessageWidget.Help:
                role = MessageWidget.HelpRole
                button = QPushButton("Help", default=False, autoDefault=False)
        elif isinstance(button, str):
            if len(rolearg) != 1:
                raise TypeError("Wrong number of arguments for "
                                "addButton(str, ButtonRole)")
            role = rolearg[0]
            button = QPushButton(button, default=False, autoDefault=False)

        if sys.platform == "darwin":
            button.setAttribute(Qt.WA_MacSmallSize)
        self._buttons.append(MessageWidget._Button(button, role, stdbutton))
        button.clicked.connect(self._button_clicked)
        self._relayout()

        return button

    def removeButton(self, button):
        """
        Remove a `button`.

        :type button: QAbstractButton
        """
        slot = [s for s in self._buttons if s.button is button]
        if slot:
            slot = slot[0]
            self._buttons.remove(slot)
            self.layout().removeWidget(slot.button)
            slot.button.setParent(None)

    def buttonRole(self, button):
        """
        Return the ButtonRole for button

        :type button: QAbstractButton
        """
        for slot in self._buttons:
            if slot.button is button:
                return slot.role
        else:
            return MessageWidget.InvalidRole

    def button(self, standardButton):
        """
        Return the button for the StandardButton.

        :type standardButton: StandardButton
        """
        for slot in self._buttons:
            if slot.stdbutton == standardButton:
                return slot.button
        else:
            return None

    def _button_clicked(self):
        button = self.sender()
        role = self.buttonRole(button)
        self.clicked.emit(button)

        if role == MessageWidget.AcceptRole:
            self.accepted.emit()
            self.close()
        elif role == MessageWidget.RejectRole:
            self.rejected.emit()
            self.close()
        elif role == MessageWidget.HelpRole:
            self.helpRequested.emit()

    def _relayout(self):
        for slot in self._buttons:
            self.layout().removeWidget(slot.button)
        order = {
            MessageOverlayWidget.HelpRole: 0,
            MessageOverlayWidget.AcceptRole: 2,
            MessageOverlayWidget.RejectRole: 3,
        }
        orderd = sorted(self._buttons,
                        key=lambda slot: order.get(slot.role, -1))

        prev = self.__textlabel
        for slot in orderd:
            self.layout().addWidget(slot.button)
            QWidget.setTabOrder(prev, slot.button)
コード例 #6
0
class PreviewBrowser(QWidget):
    """A Preview Browser for recent/premade scheme selection.
    """
    # Emitted when the current previewed item changes
    currentIndexChanged = Signal(int)

    # Emitted when an item is double clicked in the preview list.
    activated = Signal(int)

    def __init__(self, *args, heading="", previewMargins=12, **kwargs):
        super().__init__(*args)
        self.__model = None
        self.__currentIndex = -1
        self.__template = DESCRIPTION_TEMPLATE
        self.__margin = previewMargins
        self.__setupUi()
        self.setHeading(heading)

    def __setupUi(self):
        vlayout = QVBoxLayout()
        vlayout.setContentsMargins(0, 0, 0, 0)
        top_layout = QVBoxLayout(objectName="top-layout")
        margin = self.__margin
        top_layout.setContentsMargins(margin, margin, margin, margin)
        # Optional heading label

        self.__heading = QLabel(
            self, objectName="heading", visible=False
        )
        # Horizontal row with full text description and a large preview
        # image.
        hlayout = QHBoxLayout()
        hlayout.setContentsMargins(0, 0, 0, 0)
        self.__label = QLabel(
            self, objectName="description-label",
            wordWrap=True, alignment=Qt.AlignTop | Qt.AlignLeft
        )

        self.__label.setWordWrap(True)
        self.__label.setFixedSize(220, PREVIEW_SIZE[1])
        self.__label.setMinimumWidth(PREVIEW_SIZE[0] // 2)
        self.__label.setMaximumHeight(PREVIEW_SIZE[1])

        self.__image = QSvgWidget(self, objectName="preview-image")
        self.__image.setFixedSize(*PREVIEW_SIZE)

        self.__imageFrame = DropShadowFrame(self)
        self.__imageFrame.setWidget(self.__image)

        hlayout.addWidget(self.__label)
        hlayout.addWidget(self.__image)

        # Path text below the description and image
        path_layout = QHBoxLayout()
        path_layout.setContentsMargins(0, 0, 0, 0)
        path_label = QLabel("<b>{0!s}</b>".format(self.tr("Path:")), self,
                            objectName="path-label")
        self.__path = TextLabel(self, objectName="path-text")

        path_layout.addWidget(path_label)
        path_layout.addWidget(self.__path)

        top_layout.addWidget(self.__heading)
        top_layout.addLayout(hlayout)
        top_layout.addLayout(path_layout)

        vlayout.addLayout(top_layout)

        # An list view with small preview icons.
        self.__previewList = LinearIconView(
            objectName="preview-list-view",
            wordWrap=True
        )
        self.__previewList.doubleClicked.connect(self.__onDoubleClicked)

        vlayout.addWidget(self.__previewList)
        self.setLayout(vlayout)

    def setHeading(self, text):
        self.__heading.setVisible(bool(text))
        self.__heading.setText(text)

    def setPreviewMargins(self, margin):
        # type: (int) -> None
        """
        Set the left, top and right margins of the top widget part (heading
        and description)

        Parameters
        ----------
        margin : int
            Margin
        """
        if margin != self.__margin:
            layout = self.layout().itemAt(0).layout()
            assert isinstance(layout, QVBoxLayout)
            assert layout.objectName() == "top-layout"
            layout.setContentsMargins(margin, margin, margin, 0)

    def setModel(self, model):
        """Set the item model for preview.
        """
        if self.__model != model:
            if self.__model:
                s_model = self.__previewList.selectionModel()
                s_model.selectionChanged.disconnect(self.__onSelectionChanged)
                self.__model.dataChanged.disconnect(self.__onDataChanged)

            self.__model = model
            self.__previewList.setModel(model)

            if model:
                s_model = self.__previewList.selectionModel()
                s_model.selectionChanged.connect(self.__onSelectionChanged)
                self.__model.dataChanged.connect(self.__onDataChanged)

            if model and model.rowCount():
                self.setCurrentIndex(0)

    def model(self):
        """Return the item model.
        """
        return self.__model

    def setPreviewDelegate(self, delegate):
        """Set the delegate to render the preview images.
        """
        raise NotImplementedError

    def setDescriptionTemplate(self, template):
        self.__template = template
        self.__update()

    def setCurrentIndex(self, index):
        """Set the selected preview item index.
        """
        if self.__model is not None and self.__model.rowCount():
            index = min(index, self.__model.rowCount() - 1)
            index = self.__model.index(index, 0)
            sel_model = self.__previewList.selectionModel()
            # This emits selectionChanged signal and triggers
            # __onSelectionChanged, currentIndex is updated there.
            sel_model.select(index, sel_model.ClearAndSelect)

        elif self.__currentIndex != -1:
            self.__currentIndex = -1
            self.__update()
            self.currentIndexChanged.emit(-1)

    def currentIndex(self):
        """Return the current selected index.
        """
        return self.__currentIndex

    def __onSelectionChanged(self, *args):
        """Selected item in the preview list has changed.
        Set the new description and large preview image.

        """
        rows = self.__previewList.selectedIndexes()
        if rows:
            index = rows[0]
            self.__currentIndex = index.row()
        else:
            index = QModelIndex()
            self.__currentIndex = -1

        self.__update()
        self.currentIndexChanged.emit(self.__currentIndex)

    def __onDataChanged(self, topleft, bottomRight):
        """Data changed, update the preview if current index in the changed
        range.

        """
        if self.__currentIndex <= topleft.row() and \
                self.__currentIndex >= bottomRight.row():
            self.__update()

    def __onDoubleClicked(self, index):
        """Double click on an item in the preview item list.
        """
        self.activated.emit(index.row())

    def __update(self):
        """Update the current description.
        """
        if self.__currentIndex != -1:
            index = self.model().index(self.__currentIndex, 0)
        else:
            index = QModelIndex()

        if not index.isValid():
            description = ""
            name = ""
            path = ""
            svg = NO_PREVIEW_SVG
        else:
            description = index.data(Qt.WhatsThisRole)
            if description:
                description = description
            else:
                description = "No description."

            description = escape(description)
            description = description.replace("\n", "<br/>")

            name = index.data(Qt.DisplayRole)
            if name:
                name = name
            else:
                name = "Untitled"

            name = escape(name)
            path = str(index.data(Qt.StatusTipRole))
            svg = str(index.data(previewmodel.ThumbnailSVGRole))

        desc_text = self.__template.format(description=description, name=name)

        self.__label.setText(desc_text)

        self.__path.setText(contractuser(path))

        if not svg:
            svg = NO_PREVIEW_SVG

        if svg:
            self.__image.load(QByteArray(svg.encode("utf-8")))
コード例 #7
0
ファイル: overlay.py プロジェクト: RachitKansal/orange3
class MessageWidget(QWidget):
    """
    A widget displaying a simple message to the user.

    This is an alternative to a full QMessageBox intended for inline
    modeless messages.

    [[icon] {Message text} (Ok) (Cancel)]
    """
    #: Emitted when a button with the AcceptRole is clicked
    accepted = Signal()
    #: Emitted when a button with the RejectRole is clicked
    rejected = Signal()
    #: Emitted when a button with the HelpRole is clicked
    helpRequested = Signal()
    #: Emitted when a button is clicked
    clicked = Signal(QAbstractButton)

    class StandardButton(enum.IntEnum):
        NoButton, Ok, Close, Help = 0x0, 0x1, 0x2, 0x4
    NoButton, Ok, Close, Help = list(StandardButton)

    class ButtonRole(enum.IntEnum):
        InvalidRole, AcceptRole, RejectRole, HelpRole = 0, 1, 2, 3

    InvalidRole, AcceptRole, RejectRole, HelpRole = list(ButtonRole)

    _Button = namedtuple("_Button", ["button", "role", "stdbutton"])

    def __init__(self, parent=None, icon=QIcon(), text="", wordWrap=False,
                 textFormat=Qt.AutoText, standardButtons=NoButton, **kwargs):
        super().__init__(parent, **kwargs)
        self.__text = text
        self.__icon = QIcon()
        self.__wordWrap = wordWrap
        self.__standardButtons = MessageWidget.NoButton
        self.__buttons = []

        layout = QHBoxLayout()
        layout.setContentsMargins(8, 0, 8, 0)

        self.__iconlabel = QLabel(objectName="icon-label")
        self.__iconlabel.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        self.__textlabel = QLabel(objectName="text-label", text=text,
                                  wordWrap=wordWrap, textFormat=textFormat)

        if sys.platform == "darwin":
            self.__textlabel.setAttribute(Qt.WA_MacSmallSize)

        layout.addWidget(self.__iconlabel)
        layout.addWidget(self.__textlabel)

        self.setLayout(layout)
        self.setIcon(icon)
        self.setStandardButtons(standardButtons)

    def setText(self, text):
        """
        Set the current message text.

        :type message: str
        """
        if self.__text != text:
            self.__text = text
            self.__textlabel.setText(text)

    def text(self):
        """
        Return the current message text.

        :rtype: str
        """
        return self.__text

    def setIcon(self, icon):
        """
        Set the message icon.

        :type icon: QIcon | QPixmap | QString | QStyle.StandardPixmap
        """
        if isinstance(icon, QStyle.StandardPixmap):
            icon = self.style().standardIcon(icon)
        else:
            icon = QIcon(icon)

        if self.__icon != icon:
            self.__icon = QIcon(icon)
            if not self.__icon.isNull():
                size = self.style().pixelMetric(
                    QStyle.PM_SmallIconSize, None, self)
                pm = self.__icon.pixmap(QSize(size, size))
            else:
                pm = QPixmap()

            self.__iconlabel.setPixmap(pm)
            self.__iconlabel.setVisible(not pm.isNull())

    def icon(self):
        """
        Return the current icon.

        :rtype: QIcon
        """
        return QIcon(self.__icon)

    def setWordWrap(self, wordWrap):
        """
        Set the message text wrap property

        :type wordWrap: bool
        """
        if self.__wordWrap != wordWrap:
            self.__wordWrap = wordWrap
            self.__textlabel.setWordWrap(wordWrap)

    def wordWrap(self):
        """
        Return the message text wrap property.

        :rtype: bool
        """
        return self.__wordWrap

    def setTextFormat(self, textFormat):
        """
        Set message text format

        :type textFormat: Qt.TextFormat
        """
        self.__textlabel.setTextFormat(textFormat)

    def textFormat(self):
        """
        Return the message text format.

        :rtype: Qt.TextFormat
        """
        return self.__textlabel.textFormat()

    def changeEvent(self, event):
        # reimplemented
        if event.type() == 177:  # QEvent.MacSizeChange:
            ...
        super().changeEvent(event)

    def setStandardButtons(self, buttons):
        for button in MessageWidget.StandardButton:
            existing = self.button(button)
            if button & buttons and existing is None:
                self.addButton(button)
            elif existing is not None:
                self.removeButton(existing)

    def standardButtons(self):
        return functools.reduce(
            operator.ior,
            (slot.stdbutton for slot in self.__buttons
             if slot.stdbutton is not None),
            MessageWidget.NoButton)

    def addButton(self, button, *rolearg):
        """
        addButton(QAbstractButton, ButtonRole)
        addButton(str, ButtonRole)
        addButton(StandardButton)

        Add and return a button
        """
        stdbutton = None
        if isinstance(button, QAbstractButton):
            if len(rolearg) != 1:
                raise TypeError("Wrong number of arguments for "
                                "addButton(QAbstractButton, role)")
            role = rolearg[0]
        elif isinstance(button, MessageWidget.StandardButton):
            if len(rolearg) != 0:
                raise TypeError("Wrong number of arguments for "
                                "addButton(StandardButton)")
            stdbutton = button
            if button == MessageWidget.Ok:
                role = MessageWidget.AcceptRole
                button = QPushButton("Ok", default=False, autoDefault=False)
            elif button == MessageWidget.Close:
                role = MessageWidget.RejectRole
#                 button = QPushButton(
#                     default=False, autoDefault=False, flat=True,
#                     icon=QIcon(self.style().standardIcon(
#                                QStyle.SP_TitleBarCloseButton)))
                button = SimpleButton(
                    icon=QIcon(self.style().standardIcon(
                               QStyle.SP_TitleBarCloseButton)))
            elif button == MessageWidget.Help:
                role = MessageWidget.HelpRole
                button = QPushButton("Help", default=False, autoDefault=False)
        elif isinstance(button, str):
            if len(rolearg) != 1:
                raise TypeError("Wrong number of arguments for "
                                "addButton(str, ButtonRole)")
            role = rolearg[0]
            button = QPushButton(button, default=False, autoDefault=False)

        if sys.platform == "darwin":
            button.setAttribute(Qt.WA_MacSmallSize)
        self.__buttons.append(MessageWidget._Button(button, role, stdbutton))
        button.clicked.connect(self.__button_clicked)
        self.__relayout()

        return button

    def removeButton(self, button):
        """
        Remove a `button`.

        :type button: QAbstractButton
        """
        slot = [s for s in self.__buttons if s.button is button]
        if slot:
            slot = slot[0]
            self.__buttons.remove(slot)
            self.layout().removeWidget(slot.button)
            slot.button.setParent(None)

    def buttonRole(self, button):
        """
        Return the ButtonRole for button

        :type button: QAbsstractButton
        """
        for slot in self.__buttons:
            if slot.button is button:
                return slot.role
        else:
            return MessageWidget.InvalidRole

    def button(self, standardButton):
        """
        Return the button for the StandardButton.

        :type standardButton: StandardButton
        """
        for slot in self.__buttons:
            if slot.stdbutton == standardButton:
                return slot.button
        else:
            return None

    def __button_clicked(self):
        button = self.sender()
        role = self.buttonRole(button)
        self.clicked.emit(button)

        if role == MessageWidget.AcceptRole:
            self.accepted.emit()
            self.close()
        elif role == MessageWidget.RejectRole:
            self.rejected.emit()
            self.close()
        elif role == MessageWidget.HelpRole:
            self.helpRequested.emit()

    def __relayout(self):
        for slot in self.__buttons:
            self.layout().removeWidget(slot.button)
        order = {
            MessageOverlayWidget.HelpRole: 0,
            MessageOverlayWidget.AcceptRole: 2,
            MessageOverlayWidget.RejectRole: 3,
        }
        orderd = sorted(self.__buttons,
                        key=lambda slot: order.get(slot.role, -1))

        prev = self.__textlabel
        for slot in orderd:
            self.layout().addWidget(slot.button)
            QWidget.setTabOrder(prev, slot.button)
コード例 #8
0
class NotificationMessageWidget(QWidget):
    #: Emitted when a button with the AcceptRole is clicked
    accepted = Signal()
    #: Emitted when a button with the RejectRole is clicked
    rejected = Signal()
    #: Emitted when a button is clicked
    clicked = Signal(QAbstractButton)

    class StandardButton(enum.IntEnum):
        NoButton, Ok, Close = 0x0, 0x1, 0x2

    NoButton, Ok, Close = list(StandardButton)

    class ButtonRole(enum.IntEnum):
        InvalidRole, AcceptRole, RejectRole, DismissRole = 0, 1, 2, 3

    InvalidRole, AcceptRole, RejectRole, DismissRole = list(ButtonRole)

    _Button = namedtuple("_Button", ["button", "role", "stdbutton"])

    def __init__(self,
                 parent=None,
                 icon=QIcon(),
                 title="",
                 text="",
                 wordWrap=False,
                 textFormat=Qt.PlainText,
                 standardButtons=NoButton,
                 acceptLabel="Ok",
                 rejectLabel="No",
                 **kwargs):
        super().__init__(parent, **kwargs)
        self._title = title
        self._text = text
        self._icon = QIcon()
        self._wordWrap = wordWrap
        self._standardButtons = NotificationMessageWidget.NoButton
        self._buttons = []
        self._acceptLabel = acceptLabel
        self._rejectLabel = rejectLabel

        self._iconlabel = QLabel(objectName="icon-label")
        self._titlelabel = QLabel(objectName="title-label",
                                  text=title,
                                  wordWrap=wordWrap,
                                  textFormat=textFormat)
        self._textlabel = QLabel(objectName="text-label",
                                 text=text,
                                 wordWrap=wordWrap,
                                 textFormat=textFormat)
        self._textlabel.setTextInteractionFlags(Qt.TextBrowserInteraction)
        self._textlabel.setOpenExternalLinks(True)

        if sys.platform == "darwin":
            self._titlelabel.setAttribute(Qt.WA_MacSmallSize)
            self._textlabel.setAttribute(Qt.WA_MacSmallSize)

        layout = QHBoxLayout()
        self._iconlabel.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        layout.addWidget(self._iconlabel)
        layout.setAlignment(self._iconlabel, Qt.AlignTop)

        message_layout = QVBoxLayout()
        self._titlelabel.setSizePolicy(QSizePolicy.Expanding,
                                       QSizePolicy.Fixed)
        if sys.platform == "darwin":
            self._titlelabel.setContentsMargins(0, 1, 0, 0)
        else:
            self._titlelabel.setContentsMargins(0, 0, 0, 0)
        message_layout.addWidget(self._titlelabel)
        self._textlabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        message_layout.addWidget(self._textlabel)

        self.button_layout = QHBoxLayout()
        self.button_layout.setAlignment(Qt.AlignLeft)
        message_layout.addLayout(self.button_layout)

        layout.addLayout(message_layout)
        layout.setSpacing(7)
        self.setLayout(layout)
        self.setIcon(icon)
        self.setStandardButtons(standardButtons)

    def setText(self, text):
        """
        Set the current message text.

        :type message: str
        """
        if self._text != text:
            self._text = text
            self._textlabel.setText(text)

    def text(self):
        """
        Return the current message text.

        :rtype: str
        """
        return self._text

    def setTitle(self, title):
        """
        Set the current title text.

        :type title: str
        """
        if self._title != title:
            self._title = title
            self._titleLabel.setText(title)

    def title(self):
        """
        Return the current title text.

        :rtype: str
        """
        return self._title

    def setIcon(self, icon):
        """
        Set the message icon.

        :type icon: QIcon | QPixmap | QString | QStyle.StandardPixmap
        """
        if isinstance(icon, QStyle.StandardPixmap):
            icon = self.style().standardIcon(icon)
        else:
            icon = QIcon(icon)

        if self._icon != icon:
            self._icon = QIcon(icon)
            if not self._icon.isNull():
                size = self.style().pixelMetric(QStyle.PM_SmallIconSize, None,
                                                self)
                pm = self._icon.pixmap(QSize(size, size))
            else:
                pm = QPixmap()

            self._iconlabel.setPixmap(pm)
            self._iconlabel.setVisible(not pm.isNull())

    def icon(self):
        """
        Return the current icon.

        :rtype: QIcon
        """
        return QIcon(self._icon)

    def setWordWrap(self, wordWrap):
        """
        Set the message text wrap property

        :type wordWrap: bool
        """
        if self._wordWrap != wordWrap:
            self._wordWrap = wordWrap
            self._textlabel.setWordWrap(wordWrap)

    def wordWrap(self):
        """
        Return the message text wrap property.

        :rtype: bool
        """
        return self._wordWrap

    def setTextFormat(self, textFormat):
        """
        Set message text format

        :type textFormat: Qt.TextFormat
        """
        self._textlabel.setTextFormat(textFormat)

    def textFormat(self):
        """
        Return the message text format.

        :rtype: Qt.TextFormat
        """
        return self._textlabel.textFormat()

    def setAcceptLabel(self, label):
        """
        Set the accept button label.
        :type label: str
        """
        self._acceptLabel = label

    def acceptLabel(self):
        """
        Return the accept button label.
        :rtype str
        """
        return self._acceptLabel

    def setRejectLabel(self, label):
        """
        Set the reject button label.
        :type label: str
        """
        self._rejectLabel = label

    def rejectLabel(self):
        """
        Return the reject button label.
        :rtype str
        """
        return self._rejectLabel

    def setStandardButtons(self, buttons):
        for button in NotificationMessageWidget.StandardButton:
            existing = self.button(button)
            if button & buttons and existing is None:
                self.addButton(button)
            elif existing is not None:
                self.removeButton(existing)

    def standardButtons(self):
        return functools.reduce(
            operator.ior,
            (slot.stdbutton
             for slot in self._buttons if slot.stdbutton is not None),
            NotificationMessageWidget.NoButton)

    def addButton(self, button, *rolearg):
        """
        addButton(QAbstractButton, ButtonRole)
        addButton(str, ButtonRole)
        addButton(StandardButton)

        Add and return a button
        """
        stdbutton = None
        if isinstance(button, QAbstractButton):
            if len(rolearg) != 1:
                raise TypeError("Wrong number of arguments for "
                                "addButton(QAbstractButton, role)")
            role = rolearg[0]
        elif isinstance(button, NotificationMessageWidget.StandardButton):
            if rolearg:
                raise TypeError("Wrong number of arguments for "
                                "addButton(StandardButton)")
            stdbutton = button
            if button == NotificationMessageWidget.Ok:
                role = NotificationMessageWidget.AcceptRole
                button = QPushButton(self._acceptLabel,
                                     default=False,
                                     autoDefault=False)
            elif button == NotificationMessageWidget.Close:
                role = NotificationMessageWidget.RejectRole
                button = QPushButton(self._rejectLabel,
                                     default=False,
                                     autoDefault=False)
        elif isinstance(button, str):
            if len(rolearg) != 1:
                raise TypeError("Wrong number of arguments for "
                                "addButton(str, ButtonRole)")
            role = rolearg[0]
            button = QPushButton(button, default=False, autoDefault=False)

        if sys.platform == "darwin":
            button.setAttribute(Qt.WA_MacSmallSize)

        self._buttons.append(
            NotificationMessageWidget._Button(button, role, stdbutton))
        button.clicked.connect(self._button_clicked)
        self._relayout()

        return button

    def _relayout(self):
        for slot in self._buttons:
            self.button_layout.removeWidget(slot.button)
        order = {
            NotificationWidget.AcceptRole: 0,
            NotificationWidget.RejectRole: 1,
        }
        ordered = sorted([
            b for b in self._buttons if
            self.buttonRole(b.button) != NotificationMessageWidget.DismissRole
        ],
                         key=lambda slot: order.get(slot.role, -1))

        prev = self._textlabel
        for slot in ordered:
            self.button_layout.addWidget(slot.button)
            QWidget.setTabOrder(prev, slot.button)

    def removeButton(self, button):
        """
        Remove a `button`.

        :type button: QAbstractButton
        """
        slot = [s for s in self._buttons if s.button is button]
        if slot:
            slot = slot[0]
            self._buttons.remove(slot)
            self.layout().removeWidget(slot.button)
            slot.button.setParent(None)

    def buttonRole(self, button):
        """
        Return the ButtonRole for button

        :type button: QAbstractButton
        """
        for slot in self._buttons:
            if slot.button is button:
                return slot.role
        return NotificationMessageWidget.InvalidRole

    def button(self, standardButton):
        """
        Return the button for the StandardButton.

        :type standardButton: StandardButton
        """
        for slot in self._buttons:
            if slot.stdbutton == standardButton:
                return slot.button
        return None

    def _button_clicked(self):
        button = self.sender()
        role = self.buttonRole(button)
        self.clicked.emit(button)

        if role == NotificationMessageWidget.AcceptRole:
            self.accepted.emit()
            self.close()
        elif role == NotificationMessageWidget.RejectRole:
            self.rejected.emit()
            self.close()
コード例 #9
0
class PreviewBrowser(QWidget):
    """
    A Preview Browser for recent/example workflow selection.
    """
    # Emitted when the current previewed item changes
    currentIndexChanged = Signal(int)

    # Emitted when an item is double clicked in the preview list.
    activated = Signal(int)

    def __init__(self, *args, heading="", previewMargins=12, **kwargs):
        # type: (Any, str, int, Any) -> None
        super().__init__(*args, **kwargs)
        self.__model = None  # type: Optional[QAbstractItemModel]
        self.__currentIndex = -1
        self.__template = DESCRIPTION_TEMPLATE
        self.__margin = previewMargins

        vlayout = QVBoxLayout()
        vlayout.setContentsMargins(0, 0, 0, 0)
        top_layout = QVBoxLayout(objectName="top-layout")
        margin = self.__margin
        top_layout.setContentsMargins(margin, margin, margin, margin)
        # Optional heading label

        self.__heading = QLabel(self, objectName="heading", visible=False)
        # Horizontal row with full text description and a large preview
        # image.
        hlayout = QHBoxLayout()
        hlayout.setContentsMargins(0, 0, 0, 0)
        self.__label = QLabel(self,
                              objectName="description-label",
                              wordWrap=True,
                              alignment=Qt.AlignTop | Qt.AlignLeft)

        self.__label.setWordWrap(True)
        self.__label.setFixedSize(220, PREVIEW_SIZE[1])
        self.__label.setMinimumWidth(PREVIEW_SIZE[0] // 2)
        self.__label.setMaximumHeight(PREVIEW_SIZE[1])

        self.__image = QSvgWidget(self, objectName="preview-image")
        self.__image.setFixedSize(*PREVIEW_SIZE)

        self.__imageFrame = DropShadowFrame(self)
        self.__imageFrame.setWidget(self.__image)

        hlayout.addWidget(self.__label)
        hlayout.addWidget(self.__image)

        # Path text below the description and image
        path_layout = QHBoxLayout()
        path_layout.setContentsMargins(0, 0, 0, 0)
        path_label = QLabel("<b>{0!s}</b>".format(self.tr("Path:")),
                            self,
                            objectName="path-label")
        self.__path = TextLabel(self, objectName="path-text")

        path_layout.addWidget(path_label)
        path_layout.addWidget(self.__path)

        top_layout.addWidget(self.__heading)
        top_layout.addLayout(hlayout)
        top_layout.addLayout(path_layout)

        vlayout.addLayout(top_layout)

        # An list view with small preview icons.
        self.__previewList = LinearIconView(objectName="preview-list-view",
                                            wordWrap=True)
        self.__previewList.doubleClicked.connect(self.__onDoubleClicked)

        vlayout.addWidget(self.__previewList)
        self.setLayout(vlayout)

        self.setHeading(heading)

    def setHeading(self, text):
        # type: (str) -> None
        """
        Set the heading text.

        Parameters
        ----------
        text: str
            The new heading text. If empty the heading is hidden.
        """
        self.__heading.setVisible(bool(text))
        self.__heading.setText(text)

    def setPreviewMargins(self, margin):
        # type: (int) -> None
        """
        Set the left, top and right margins of the top widget part (heading
        and description)

        Parameters
        ----------
        margin : int
            Margin
        """
        if margin != self.__margin:
            layout = self.layout().itemAt(0).layout()
            assert isinstance(layout, QVBoxLayout)
            assert layout.objectName() == "top-layout"
            layout.setContentsMargins(margin, margin, margin, 0)

    def setModel(self, model):
        # type: (QAbstractItemModel) -> None
        """
        Set the item model for preview.

        Parameters
        ----------
        model : QAbstractItemModel
        """
        if self.__model != model:
            if self.__model:
                s_model = self.__previewList.selectionModel()
                s_model.selectionChanged.disconnect(self.__onSelectionChanged)
                self.__model.dataChanged.disconnect(self.__onDataChanged)

            self.__model = model
            self.__previewList.setModel(model)

            if model:
                s_model = self.__previewList.selectionModel()
                s_model.selectionChanged.connect(self.__onSelectionChanged)
                self.__model.dataChanged.connect(self.__onDataChanged)

            if model and model.rowCount():
                self.setCurrentIndex(0)

    def model(self):
        # type: () -> Optional[QAbstractItemModel]
        """
        Return the item model.
        """
        return self.__model

    def setDescriptionTemplate(self, template):
        self.__template = template
        self.__update()

    def setCurrentIndex(self, index):
        # type: (int) -> None
        """
        Set the selected preview item index.

        Parameters
        ----------
        index : int
            The current selected index.
        """
        if self.__model is not None and self.__model.rowCount():
            index = min(index, self.__model.rowCount() - 1)
            index = self.__model.index(index, 0)
            sel_model = self.__previewList.selectionModel()
            # This emits selectionChanged signal and triggers
            # __onSelectionChanged, currentIndex is updated there.
            sel_model.select(index, sel_model.ClearAndSelect)
        elif self.__currentIndex != -1:
            self.__currentIndex = -1
            self.__update()
            self.currentIndexChanged.emit(-1)

    def currentIndex(self):  # type: () -> int
        """
        Return the current selected index.
        """
        return self.__currentIndex

    def __onSelectionChanged(self):
        # type: () -> None
        """Selected item in the preview list has changed.
        Set the new description and large preview image.
        """
        rows = self.__previewList.selectedIndexes()
        if rows:
            index = rows[0]
            self.__currentIndex = index.row()
        else:
            self.__currentIndex = -1

        self.__update()
        self.currentIndexChanged.emit(self.__currentIndex)

    def __onDataChanged(self, topLeft, bottomRight):
        # type: (QModelIndex, QModelIndex) -> None
        """Data changed, update the preview if current index in the changed
        range.
        """
        if topLeft.row() <= self.__currentIndex <= bottomRight.row():
            self.__update()

    def __onDoubleClicked(self, index):
        # type: (QModelIndex) -> None
        """Double click on an item in the preview item list.
        """
        self.activated.emit(index.row())

    def __update(self):
        # type: () -> None
        """Update the current description.
        """
        if self.__currentIndex != -1 and self.__model is not None:
            index = self.__model.index(self.__currentIndex, 0)
        else:
            index = QModelIndex()

        if not index.isValid():
            description = ""
            name = ""
            path = ""
            svg = NO_PREVIEW_SVG
        else:
            description = index.data(Qt.WhatsThisRole)
            if description:
                description = description
            else:
                description = "没有说明。"

            description = escape(description)
            description = description.replace("\n", "<br/>")

            name = index.data(Qt.DisplayRole)
            if name:
                name = name
            else:
                name = "Untitled"

            name = escape(name)
            path = str(index.data(Qt.StatusTipRole))
            svg = str(index.data(previewmodel.ThumbnailSVGRole))

        desc_text = self.__template.format(description=description, name=name)

        self.__label.setText(desc_text)

        self.__path.setText(contractuser(path))

        if not svg:
            svg = NO_PREVIEW_SVG

        if svg:
            self.__image.load(QByteArray(svg.encode("utf-8")))
コード例 #10
0
class WelcomeDialog(QDialog):
    """
    A welcome widget shown at startup presenting a series
    of buttons (actions) for a beginner to choose from.
    """
    triggered = Signal(QAction)

    def __init__(self, *args, **kwargs):
        showAtStartup = kwargs.pop("showAtStartup", True)
        feedbackUrl = kwargs.pop("feedbackUrl", "")
        super().__init__(*args, **kwargs)

        self.__triggeredAction = None
        self.__showAtStartupCheck = None
        self.__mainLayout = None
        self.__feedbackUrl = None
        self.__feedbackLabel = None

        self.setupUi()

        self.setFeedbackUrl(feedbackUrl)
        self.setShowAtStartup(showAtStartup)

    def setupUi(self):
        self.setLayout(QVBoxLayout())
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().setSpacing(0)

        self.__mainLayout = QVBoxLayout()
        self.__mainLayout.setContentsMargins(0, 40, 0, 40)
        self.__mainLayout.setSpacing(65)

        self.layout().addLayout(self.__mainLayout)

        self.setStyleSheet(WELCOME_WIDGET_BUTTON_STYLE)

        bottom_bar = QWidget(objectName="bottom-bar")
        bottom_bar_layout = QHBoxLayout()
        bottom_bar_layout.setContentsMargins(20, 10, 20, 10)
        bottom_bar.setLayout(bottom_bar_layout)
        bottom_bar.setSizePolicy(QSizePolicy.MinimumExpanding,
                                 QSizePolicy.Maximum)

        self.__showAtStartupCheck = QCheckBox(
            self.tr("Show at startup"), bottom_bar, checked=False
        )
        self.__feedbackLabel = QLabel(
            textInteractionFlags=Qt.TextBrowserInteraction,
            openExternalLinks=True,
            visible=False,
        )

        bottom_bar_layout.addWidget(
            self.__showAtStartupCheck, alignment=Qt.AlignVCenter | Qt.AlignLeft
        )
        bottom_bar_layout.addWidget(
            self.__feedbackLabel, alignment=Qt.AlignVCenter | Qt.AlignRight
        )
        self.layout().addWidget(bottom_bar, alignment=Qt.AlignBottom,
                                stretch=1)

        self.setSizeGripEnabled(False)
        self.setFixedSize(620, 390)

    def setShowAtStartup(self, show):
        """
        Set the 'Show at startup' check box state.
        """
        if self.__showAtStartupCheck.isChecked() != show:
            self.__showAtStartupCheck.setChecked(show)

    def showAtStartup(self):
        """
        Return the 'Show at startup' check box state.
        """
        return self.__showAtStartupCheck.isChecked()

    def setFeedbackUrl(self, url):
        # type: (str) -> None
        """
        Set an 'feedback' url. When set a link is displayed in the bottom row.
        """
        self.__feedbackUrl = url
        if url:
            text = self.tr("Help us improve!")
            self.__feedbackLabel.setText(
                '<a href="{url}">{text}</a>'.format(url=url, text=escape(text))
            )
        else:
            self.__feedbackLabel.setText("")
        self.__feedbackLabel.setVisible(bool(url))

    def addRow(self, actions, background="light-orange"):
        """Add a row with `actions`.
        """
        count = self.__mainLayout.count()
        self.insertRow(count, actions, background)

    def insertRow(self, index, actions, background="light-orange"):
        """Insert a row with `actions` at `index`.
        """
        widget = QWidget(objectName="icon-row")
        layout = QHBoxLayout()
        layout.setContentsMargins(40, 0, 40, 0)
        layout.setSpacing(65)
        widget.setLayout(layout)

        self.__mainLayout.insertWidget(index, widget, stretch=10,
                                       alignment=Qt.AlignCenter)

        for i, action in enumerate(actions):
            self.insertAction(index, i, action, background)

    def insertAction(self, row, index, action,
                      background="light-orange"):
        """Insert `action` in `row` in position `index`.
        """
        button = self.createButton(action, background)
        self.insertButton(row, index, button)

    def insertButton(self, row, index, button):
        """Insert `button` in `row` in position `index`.
        """
        item = self.__mainLayout.itemAt(row)
        layout = item.widget().layout()
        layout.insertWidget(index, button)
        button.triggered.connect(self.__on_actionTriggered)

    def createButton(self, action, background="light-orange"):
        """Create a tool button for action.
        """
        button = WelcomeActionButton(self)
        button.setDefaultAction(action)
        button.setText(action.iconText())
        button.setIcon(decorate_welcome_icon(action.icon(), background))
        button.setToolTip(action.toolTip())
        button.setFixedSize(100, 100)
        button.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
        button.setVisible(action.isVisible())
        font = QFont(button.font())
        font.setPointSize(13)
        button.setFont(font)

        return button

    def buttonAt(self, i, j):
        """Return the button at i-t row and j-th column.
        """
        item = self.__mainLayout.itemAt(i)
        row = item.widget()
        item = row.layout().itemAt(j)
        return item.widget()

    def triggeredAction(self):
        """Return the action that was triggered by the user.
        """
        return self.__triggeredAction

    def showEvent(self, event):
        # Clear the triggered action before show.
        self.__triggeredAction = None
        super().showEvent(event)

    def __on_actionTriggered(self, action):
        """Called when the button action is triggered.
        """
        self.triggered.emit(action)
        self.__triggeredAction = action