Source code for orangecanvas.application.welcomedialog

"""
Orange Canvas Welcome Dialog
"""
from typing import Optional, Union, Iterable

from xml.sax.saxutils import escape

from AnyQt.QtWidgets import (
    QDialog, QWidget, QToolButton, QCheckBox, QAction,
    QHBoxLayout, QVBoxLayout, QSizePolicy, QLabel, QApplication
)
from AnyQt.QtGui import (
    QFont, QIcon, QPixmap, QPainter, QColor, QBrush, QActionEvent, QIconEngine,
)

from AnyQt.QtCore import Qt, QRect, QSize, QPoint
from AnyQt.QtCore import pyqtSignal as Signal

from ..canvas.items.utils import radial_gradient
from ..registry import NAMED_COLORS
from ..gui.svgiconengine import StyledSvgIconEngine
from .. import styles


class DecoratedIconEngine(QIconEngine):
    def __init__(self, base: QIcon, background: QColor):
        super().__init__()
        self.__base = base
        self.__background = background
        self.__gradient = radial_gradient(background)

    def paint(
            self, painter: 'QPainter', rect: QRect, mode: QIcon.Mode,
            state: QIcon.State
    ) -> None:
        size = rect.size()
        dpr = painter.device().devicePixelRatioF()
        size = size * dpr
        pm = self.pixmap(size, mode, state)
        painter.drawPixmap(rect, pm)
        return

    def pixmap(
            self, size: QSize, mode: QIcon.Mode, state: QIcon.State
    ) -> QPixmap:
        pixmap = QPixmap(size)
        pixmap.fill(Qt.transparent)
        p = QPainter(pixmap)
        p.setRenderHint(QPainter.Antialiasing, True)
        p.setBrush(QBrush(self.__gradient))
        p.setPen(Qt.NoPen)
        icon_size = QSize(5 * size.width() // 8, 5 * size.height() // 8)
        icon_rect = QRect(QPoint(0, 0), icon_size)
        ellipse_rect = QRect(QPoint(0, 0), size)
        p.drawEllipse(ellipse_rect)
        icon_rect.moveCenter(ellipse_rect.center())
        palette = styles.breeze_light()
        # Special case for StyledSvgIconEngine. This is drawn on a
        # light-ish color background and should not render with a dark palette
        # (this is bad, and I feel bad).
        with StyledSvgIconEngine.setOverridePalette(palette):
            self.__base.paint(p, icon_rect, Qt.AlignCenter)
        p.end()
        return pixmap

    def clone(self) -> 'QIconEngine':
        return DecoratedIconEngine(
            self.__base, self.__background
        )


def decorate_welcome_icon(icon, background_color):
    # type: (QIcon, Union[QColor, str]) -> QIcon
    """Return a `QIcon` with a circle shaped background.
    """
    background_color = NAMED_COLORS.get(background_color, background_color)
    return QIcon(DecoratedIconEngine(icon, QColor(background_color)))


WELCOME_WIDGET_BUTTON_STYLE = """
WelcomeActionButton {
    border: 1px solid transparent;
    border-radius: 10px;
    font-size: 13px;
    icon-size: 75px;
}
WelcomeActionButton:pressed {
    background-color: palette(highlight);
    color: palette(highlighted-text);
}
WelcomeActionButton:focus {
    border: 1px solid palette(highlight);
}
"""


class WelcomeActionButton(QToolButton):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setFont(QApplication.font("QAbstractButton"))

    def actionEvent(self, event):
        # type: (QActionEvent) -> None
        super().actionEvent(event)
        if event.type() == QActionEvent.ActionChanged \
                and event.action() is self.defaultAction():
            # The base does not update self visibility for defaultAction.
            self.setVisible(event.action().isVisible())


[docs]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) 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)
[docs] def setShowAtStartup(self, show): # type: (bool) -> None """ Set the 'Show at startup' check box state. """ if self.__showAtStartupCheck.isChecked() != show: self.__showAtStartupCheck.setChecked(show)
[docs] def showAtStartup(self): # type: () -> bool """ Return the 'Show at startup' check box state. """ return self.__showAtStartupCheck.isChecked()
[docs] 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))
[docs] 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
[docs] 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