예제 #1
0
def get_icon_html(icon: QIcon, size: QSize) -> str:
    """
    Transform an icon to html <img> tag.
    """
    if not size.isValid():
        return ""
    if size.width() < 0 or size.height() < 0:
        size = QSize(16, 16)  # just in case
    byte_array = QByteArray()
    buffer = QBuffer(byte_array)
    buffer.open(QIODevice.WriteOnly)
    pixmap = icon.pixmap(size)
    if pixmap.isNull():
        return ""
    pixmap.save(buffer, "PNG")
    buffer.close()

    dpr = pixmap.devicePixelRatioF()
    if dpr != 1.0:
        size_ = pixmap.size() / dpr
        size_part = ' width="{}" height="{}"'.format(
            int(math.floor(size_.width())), int(math.floor(size_.height()))
        )
    else:
        size_part = ''
    img_encoded = byte_array.toBase64().data().decode("utf-8")
    return '<img src="data:image/png;base64,{}"{}/>'.format(img_encoded, size_part)
예제 #2
0
class IconWidget(QWidget):
    """
    A widget displaying an `QIcon`
    """
    def __init__(self, parent=None, icon=QIcon(), iconSize=QSize(), **kwargs):
        sizePolicy = kwargs.pop(
            "sizePolicy", QSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed))
        super().__init__(parent, **kwargs)
        self.__icon = QIcon(icon)
        self.__iconSize = QSize(iconSize)
        self.setSizePolicy(sizePolicy)

    def setIcon(self, icon):
        # type: (QIcon) -> None
        if self.__icon != icon:
            self.__icon = QIcon(icon)
            self.updateGeometry()
            self.update()

    def icon(self):
        # type: () -> QIcon
        return QIcon(self.__icon)

    def iconSize(self):
        # type: () -> QSize
        if not self.__iconSize.isValid():
            size = self.style().pixelMetric(QStyle.PM_ButtonIconSize)
            return QSize(size, size)
        else:
            return QSize(self.__iconSize)

    def setIconSize(self, iconSize):
        # type: (QSize) -> None
        if self.__iconSize != iconSize:
            self.__iconSize = QSize(iconSize)
            self.updateGeometry()
            self.update()

    def sizeHint(self):
        sh = self.iconSize()
        m = self.contentsMargins()
        return QSize(sh.width() + m.left() + m.right(),
                     sh.height() + m.top() + m.bottom())

    def paintEvent(self, event):
        painter = QStylePainter(self)
        opt = QStyleOption()
        opt.initFrom(self)
        painter.drawPrimitive(QStyle.PE_Widget, opt)
        if not self.__icon.isNull():
            rect = self.contentsRect()
            if opt.state & QStyle.State_Active:
                mode = QIcon.Active
            else:
                mode = QIcon.Disabled
            self.__icon.paint(painter, rect, Qt.AlignCenter, mode, QIcon.Off)
        painter.end()
예제 #3
0
class IconWidget(QWidget):
    """
    A widget displaying an `QIcon`
    """
    def __init__(self, parent=None, icon=QIcon(), iconSize=QSize(), **kwargs):
        sizePolicy = kwargs.pop("sizePolicy", QSizePolicy(QSizePolicy.Fixed,
                                                          QSizePolicy.Fixed))
        super().__init__(parent, **kwargs)
        self.__icon = QIcon(icon)
        self.__iconSize = QSize(iconSize)
        self.setSizePolicy(sizePolicy)

    def setIcon(self, icon):
        # type: (QIcon) -> None
        if self.__icon != icon:
            self.__icon = QIcon(icon)
            self.updateGeometry()
            self.update()

    def icon(self):
        # type: () -> QIcon
        return QIcon(self.__icon)

    def iconSize(self):
        # type: () -> QSize
        if not self.__iconSize.isValid():
            size = self.style().pixelMetric(QStyle.PM_ButtonIconSize)
            return QSize(size, size)
        else:
            return QSize(self.__iconSize)

    def setIconSize(self, iconSize):
        # type: (QSize) -> None
        if self.__iconSize != iconSize:
            self.__iconSize = QSize(iconSize)
            self.updateGeometry()
            self.update()

    def sizeHint(self):
        sh = self.iconSize()
        m = self.contentsMargins()
        return QSize(sh.width() + m.left() + m.right(),
                     sh.height() + m.top() + m.bottom())

    def paintEvent(self, event):
        painter = QStylePainter(self)
        opt = QStyleOption()
        opt.initFrom(self)
        painter.drawPrimitive(QStyle.PE_Widget, opt)
        if not self.__icon.isNull():
            rect = self.contentsRect()
            if opt.state & QStyle.State_Active:
                mode = QIcon.Active
            else:
                mode = QIcon.Disabled
            self.__icon.paint(painter, rect, Qt.AlignCenter, mode, QIcon.Off)
        painter.end()
예제 #4
0
class ToolBox(QFrame):
    """
    A tool box widget.
    """
    # Emitted when a tab is toggled.
    tabToogled = Signal(int, bool)

    def setExclusive(self, exclusive):
        """
        Set exclusive tabs (only one tab can be open at a time).
        """
        if self.__exclusive != exclusive:
            self.__exclusive = exclusive
            self.__tabActionGroup.setExclusive(exclusive)
            checked = self.__tabActionGroup.checkedAction()
            if checked is None:
                # The action group can be out of sync with the actions state
                # when switching between exclusive states.
                actions_checked = [page.action for page in self.__pages
                                   if page.action.isChecked()]
                if actions_checked:
                    checked = actions_checked[0]

            # Trigger/toggle remaining open pages
            if exclusive and checked is not None:
                for page in self.__pages:
                    if checked != page.action and page.action.isChecked():
                        page.action.trigger()

    def exclusive(self):
        """
        Are the tabs in the toolbox exclusive.
        """
        return self.__exclusive

    exclusive_ = Property(bool,
                          fget=exclusive,
                          fset=setExclusive,
                          designable=True,
                          doc="Exclusive tabs")

    def __init__(self, parent=None, **kwargs):
        QFrame.__init__(self, parent, **kwargs)

        self.__pages = []
        self.__tabButtonHeight = -1
        self.__tabIconSize = QSize()
        self.__exclusive = False
        self.__setupUi()

    def __setupUi(self):
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)

        # Scroll area for the contents.
        self.__scrollArea = \
                _ToolBoxScrollArea(self, objectName="toolbox-scroll-area")

        self.__scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.__scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.__scrollArea.setSizePolicy(QSizePolicy.MinimumExpanding,
                                        QSizePolicy.MinimumExpanding)
        self.__scrollArea.setFrameStyle(QScrollArea.NoFrame)
        self.__scrollArea.setWidgetResizable(True)

        # A widget with all of the contents.
        # The tabs/contents are placed in the layout inside this widget
        self.__contents = QWidget(self.__scrollArea,
                                  objectName="toolbox-contents")
        self.__contentsLayout = _ToolBoxLayout(
            sizeConstraint=_ToolBoxLayout.SetMinAndMaxSize,
            spacing=0
        )
        self.__contentsLayout.setContentsMargins(0, 0, 0, 0)
        self.__contents.setLayout(self.__contentsLayout)

        self.__scrollArea.setWidget(self.__contents)

        layout.addWidget(self.__scrollArea)

        self.setLayout(layout)
        self.setSizePolicy(QSizePolicy.Fixed,
                           QSizePolicy.MinimumExpanding)

        self.__tabActionGroup = \
                QActionGroup(self, objectName="toolbox-tab-action-group")

        self.__tabActionGroup.setExclusive(self.__exclusive)

        self.__actionMapper = QSignalMapper(self)
        self.__actionMapper.mapped[QObject].connect(self.__onTabActionToogled)

    def setTabButtonHeight(self, height):
        """
        Set the tab button height.
        """
        if self.__tabButtonHeight != height:
            self.__tabButtonHeight = height
            for page in self.__pages:
                page.button.setFixedHeight(height)

    def tabButtonHeight(self):
        """
        Return the tab button height.
        """
        return self.__tabButtonHeight

    def setTabIconSize(self, size):
        """
        Set the tab button icon size.
        """
        if self.__tabIconSize != size:
            self.__tabIconSize = size
            for page in self.__pages:
                page.button.setIconSize(size)

    def tabIconSize(self):
        """
        Return the tab icon size.
        """
        return self.__tabIconSize

    def tabButton(self, index):
        """
        Return the tab button at `index`
        """
        return self.__pages[index].button

    def tabAction(self, index):
        """
        Return open/close action for the tab at `index`.
        """
        return self.__pages[index].action

    def addItem(self, widget, text, icon=None, toolTip=None):
        """
        Append the `widget` in a new tab and return its index.

        Parameters
        ----------
        widget : :class:`QWidget`
            A widget to be inserted. The toolbox takes ownership
            of the widget.

        text : str
            Name/title of the new tab.

        icon : :class:`QIcon`, optional
            An icon for the tab button.

        toolTip : str, optional
            Tool tip for the tab button.

        """
        return self.insertItem(self.count(), widget, text, icon, toolTip)

    def insertItem(self, index, widget, text, icon=None, toolTip=None):
        """
        Insert the `widget` in a new tab at position `index`.

        See also
        --------
        ToolBox.addItem

        """
        button = self.createTabButton(widget, text, icon, toolTip)

        self.__contentsLayout.insertWidget(index * 2, button)
        self.__contentsLayout.insertWidget(index * 2 + 1, widget)

        widget.hide()

        page = _ToolBoxPage(index, widget, button.defaultAction(), button)
        self.__pages.insert(index, page)

        for i in range(index + 1, self.count()):
            self.__pages[i] = self.__pages[i]._replace(index=i)

        self.__updatePositions()

        # Show (open) the first tab.
        if self.count() == 1 and index == 0:
            page.action.trigger()

        self.__updateSelected()

        self.updateGeometry()
        return index

    def removeItem(self, index):
        """
        Remove the widget at `index`.

        .. note:: The widget hidden but is is not deleted.

        """
        self.__contentsLayout.takeAt(2 * index + 1)
        self.__contentsLayout.takeAt(2 * index)
        page = self.__pages.pop(index)

        # Update the page indexes
        for i in range(index, self.count()):
            self.__pages[i] = self.__pages[i]._replace(index=i)

        page.button.deleteLater()

        # Hide the widget and reparent to self
        # This follows QToolBox.removeItem
        page.widget.hide()
        page.widget.setParent(self)

        self.__updatePositions()
        self.__updateSelected()

        self.updateGeometry()

    def count(self):
        """
        Return the number of widgets inserted in the toolbox.
        """
        return len(self.__pages)

    def widget(self, index):
        """
        Return the widget at `index`.
        """
        return self.__pages[index].widget

    def createTabButton(self, widget, text, icon=None, toolTip=None):
        """
        Create the tab button for `widget`.
        """
        action = QAction(text, self)
        action.setCheckable(True)

        if icon:
            action.setIcon(icon)

        if toolTip:
            action.setToolTip(toolTip)
        self.__tabActionGroup.addAction(action)
        self.__actionMapper.setMapping(action, action)
        action.toggled.connect(self.__actionMapper.map)

        button = ToolBoxTabButton(self, objectName="toolbox-tab-button")
        button.setDefaultAction(action)
        button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        button.setSizePolicy(QSizePolicy.Ignored,
                             QSizePolicy.Fixed)

        if self.__tabIconSize.isValid():
            button.setIconSize(self.__tabIconSize)

        if self.__tabButtonHeight > 0:
            button.setFixedHeight(self.__tabButtonHeight)

        return button

    def ensureWidgetVisible(self, child, xmargin=50, ymargin=50):
        """
        Scroll the contents so child widget instance is visible inside
        the viewport.

        """
        self.__scrollArea.ensureWidgetVisible(child, xmargin, ymargin)

    def sizeHint(self):
        hint = self.__contentsLayout.sizeHint()

        if self.count():
            # Compute max width of hidden widgets also.
            scroll = self.__scrollArea
            scroll_w = scroll.verticalScrollBar().sizeHint().width()
            frame_w = self.frameWidth() * 2 + scroll.frameWidth() * 2
            max_w = max([p.widget.sizeHint().width() for p in self.__pages])
            hint = QSize(max(max_w, hint.width()) + scroll_w + frame_w,
                         hint.height())

        return QSize(200, 200).expandedTo(hint)

    def __onTabActionToogled(self, action):
        page = find(self.__pages, action, key=attrgetter("action"))
        on = action.isChecked()
        page.widget.setVisible(on)
        index = page.index

        if index > 0:
            # Update the `previous` tab buttons style hints
            previous = self.__pages[index - 1].button
            flag = QStyleOptionToolBox.NextIsSelected
            if on:
                previous.selected |= flag
            else:
                previous.selected &= ~flag

            previous.update()

        if index < self.count() - 1:
            next = self.__pages[index + 1].button
            flag = QStyleOptionToolBox.PreviousIsSelected
            if on:
                next.selected |= flag
            else:
                next.selected &= ~flag

            next.update()

        self.tabToogled.emit(index, on)

        self.__contentsLayout.invalidate()

    def __updateSelected(self):
        """Update the tab buttons selected style flags.
        """
        if self.count() == 0:
            return

        opt = QStyleOptionToolBox

        def update(button, next_sel, prev_sel):
            if next_sel:
                button.selected |= opt.NextIsSelected
            else:
                button.selected &= ~opt.NextIsSelected

            if prev_sel:
                button.selected |= opt.PreviousIsSelected
            else:
                button.selected &= ~ opt.PreviousIsSelected

            button.update()

        if self.count() == 1:
            update(self.__pages[0].button, False, False)
        elif self.count() >= 2:
            pages = self.__pages
            for i in range(1, self.count() - 1):
                update(pages[i].button,
                       pages[i + 1].action.isChecked(),
                       pages[i - 1].action.isChecked())

    def __updatePositions(self):
        """Update the tab buttons position style flags.
        """
        if self.count() == 0:
            return
        elif self.count() == 1:
            self.__pages[0].button.position = QStyleOptionToolBox.OnlyOneTab
        else:
            self.__pages[0].button.position = QStyleOptionToolBox.Beginning
            self.__pages[-1].button.position = QStyleOptionToolBox.End
            for p in self.__pages[1:-1]:
                p.button.position = QStyleOptionToolBox.Middle

        for p in self.__pages:
            p.button.update()
예제 #5
0
class ToolGrid(QFrame):
    """
    A widget containing a grid of actions/buttons.

    Actions can be added using standard :func:`QWidget.addAction(QAction)`
    and :func:`QWidget.insertAction(int, QAction)` methods.

    Parameters
    ----------
    parent : :class:`QWidget`
        Parent widget.
    columns : int
        Number of columns in the grid layout.
    buttonSize : QSize
        Size of tool buttons in the grid.
    iconSize : QSize
        Size of icons in the buttons.
    toolButtonStyle : :class:`Qt.ToolButtonStyle`
        Tool button style.
    """
    #: Signal emitted when an action is triggered
    actionTriggered = Signal(QAction)
    #: Signal emitted when an action is hovered
    actionHovered = Signal(QAction)

    def __init__(self,
                 parent=None, columns=4, buttonSize=QSize(),
                 iconSize=QSize(), toolButtonStyle=Qt.ToolButtonTextUnderIcon,
                 **kwargs):
        # type: (Optional[QWidget], int, QSize, QSize, Qt.ToolButtonStyle, Any) -> None
        sizePolicy = kwargs.pop("sizePolicy", None)  # type: Optional[QSizePolicy]
        super().__init__(parent, **kwargs)

        if buttonSize is None:
            buttonSize = QSize()
        if iconSize is None:
            iconSize = QSize()

        self.__columns = columns
        self.__buttonSize = QSize(buttonSize)
        self.__iconSize = QSize(iconSize)
        self.__toolButtonStyle = toolButtonStyle

        self.__gridSlots = []  # type: List[_ToolGridSlot]
        self.__mapper = QSignalMapper()
        self.__mapper.mapped[QObject].connect(self.__onClicked)

        layout = QGridLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        self.setLayout(layout)
        if sizePolicy is None:
            self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding)
            self.setAttribute(Qt.WA_WState_OwnSizePolicy, True)
        else:
            self.setSizePolicy(sizePolicy)

    def setButtonSize(self, size):
        # type: (QSize) -> None
        """
        Set the button size.
        """
        if self.__buttonSize != size:
            self.__buttonSize = QSize(size)
            for slot in self.__gridSlots:
                slot.button.setFixedSize(size)

    def buttonSize(self):
        # type: () -> QSize
        """
        Return the button size.
        """
        return QSize(self.__buttonSize)

    def setIconSize(self, size):
        # type: (QSize) -> None
        """
        Set the button icon size.

        The default icon size is style defined.
        """
        if self.__iconSize != size:
            self.__iconSize = QSize(size)
            size = self.__effectiveIconSize()
            for slot in self.__gridSlots:
                slot.button.setIconSize(size)

    def iconSize(self):
        # type: () -> QSize
        """
        Return the icon size. If no size is set a default style defined size
        is returned.
        """
        return self.__effectiveIconSize()

    def __effectiveIconSize(self):
        # type: () -> QSize
        if not self.__iconSize.isValid():
            opt = QStyleOptionToolButton()
            opt.initFrom(self)
            s = self.style().pixelMetric(QStyle.PM_LargeIconSize, opt, None)
            return QSize(s, s)
        else:
            return QSize(self.__iconSize)

    def changeEvent(self, event):
        # type: (QEvent) -> None
        if event.type() == QEvent.StyleChange:
            size = self.__effectiveIconSize()
            for item in self.__gridSlots:
                item.button.setIconSize(size)
        super().changeEvent(event)

    def setToolButtonStyle(self, style):
        # type: (Qt.ToolButtonStyle) -> None
        """
        Set the tool button style.
        """
        if self.__toolButtonStyle != style:
            self.__toolButtonStyle = style
            for slot in self.__gridSlots:
                slot.button.setToolButtonStyle(style)

    def toolButtonStyle(self):
        # type: () -> Qt.ToolButtonStyle
        """
        Return the tool button style.
        """
        return self.__toolButtonStyle

    def setColumnCount(self, columns):
        # type: (int) -> None
        """
        Set the number of button/action columns.
        """
        if self.__columns != columns:
            self.__columns = columns
            self.__relayout()

    def columns(self):
        # type: () -> int
        """
        Return the number of columns in the grid.
        """
        return self.__columns

    def clear(self):
        # type: () -> None
        """
        Clear all actions/buttons.
        """
        for slot in reversed(list(self.__gridSlots)):
            self.removeAction(slot.action)
        self.__gridSlots = []

    def insertAction(self, before, action):
        # type: (Union[QAction, int], QAction) -> None
        """
        Insert a new action at the position currently occupied
        by `before` (can also be an index).

        Parameters
        ----------
        before : :class:`QAction` or int
            Position where the `action` should be inserted.
        action : :class:`QAction`
            Action to insert
        """
        if isinstance(before, int):
            actions = list(self.actions())
            if len(actions) == 0 or before >= len(actions):
                # Insert as the first action or the last action.
                return self.addAction(action)

            before = actions[before]

        return super().insertAction(before, action)

    def setActions(self, actions):
        # type: (Iterable[QAction]) -> None
        """
        Clear the grid and add `actions`.
        """
        self.clear()

        for action in actions:
            self.addAction(action)

    def buttonForAction(self, action):
        # type: (QAction) -> QToolButton
        """
        Return the :class:`QToolButton` instance button for `action`.
        """
        actions = [slot.action for slot in self.__gridSlots]
        index = actions.index(action)
        return self.__gridSlots[index].button

    def createButtonForAction(self, action):
        # type: (QAction) -> QToolButton
        """
        Create and return a :class:`QToolButton` for action.
        """
        button = ToolGridButton(self)
        button.setDefaultAction(action)

        if self.__buttonSize.isValid():
            button.setFixedSize(self.__buttonSize)
        button.setIconSize(self.__effectiveIconSize())
        button.setToolButtonStyle(self.__toolButtonStyle)
        button.setProperty("tool-grid-button", True)
        return button

    def count(self):
        # type: () -> int
        """
        Return the number of buttons/actions in the grid.
        """
        return len(self.__gridSlots)

    def actionEvent(self, event):
        # type: (QActionEvent) -> None
        super().actionEvent(event)

        if event.type() == QEvent.ActionAdded:
            # Note: the action is already in the self.actions() list.
            actions = list(self.actions())
            index = actions.index(event.action())
            self.__insertActionButton(index, event.action())

        elif event.type() == QEvent.ActionRemoved:
            self.__removeActionButton(event.action())

    def __insertActionButton(self, index, action):
        # type: (int, QAction) -> None
        """Create a button for the action and add it to the layout at index.
        """
        self.__shiftGrid(index, 1)
        button = self.createButtonForAction(action)

        row = index // self.__columns
        column = index % self.__columns

        layout = self.layout()
        assert isinstance(layout, QGridLayout)
        layout.addWidget(button, row, column)

        self.__gridSlots.insert(
            index, _ToolGridSlot(button, action, row, column)
        )

        self.__mapper.setMapping(button, action)
        button.clicked.connect(self.__mapper.map)
        button.installEventFilter(self)

    def __removeActionButton(self, action):
        # type: (QAction) -> None
        """Remove the button for the action from the layout and delete it.
        """
        actions = [slot.action for slot in self.__gridSlots]
        index = actions.index(action)
        slot = self.__gridSlots.pop(index)

        slot.button.removeEventFilter(self)
        self.__mapper.removeMappings(slot.button)

        self.layout().removeWidget(slot.button)
        self.__shiftGrid(index + 1, -1)

        slot.button.deleteLater()

    def __shiftGrid(self, start, count=1):
        # type: (int, int) -> None
        """Shift all buttons starting at index `start` by `count` cells.
        """
        layout = self.layout()
        assert isinstance(layout, QGridLayout)
        button_count = layout.count()
        columns = self.__columns

        direction = 1 if count >= 0 else -1
        if direction == 1:
            start, end = button_count - 1, start - 1
        else:
            start, end = start, button_count

        for index in range(start, end, -direction):
            item = layout.itemAtPosition(
                index // columns, index % columns
            )
            if item:
                button = item.widget()
                new_index = index + count
                layout.addWidget(
                    button, new_index // columns, new_index % columns,
                )

    def __relayout(self):
        # type: () -> None
        """Relayout the buttons.
        """
        layout = self.layout()
        assert isinstance(layout, QGridLayout)

        for i in reversed(range(layout.count())):
            layout.takeAt(i)

        self.__gridSlots = [
            _ToolGridSlot(slot.button, slot.action,
                          i // self.__columns, i % self.__columns)
            for i, slot in enumerate(self.__gridSlots)
        ]
        for slot in self.__gridSlots:
            layout.addWidget(slot.button, slot.row, slot.column)

    def __indexOf(self, button):
        # type: (QWidget) -> int
        """Return the index of button widget.
        """
        buttons = [slot.button for slot in self.__gridSlots]
        return buttons.index(button)

    def __onButtonEnter(self, button):
        # type: (QToolButton) -> None
        action = button.defaultAction()
        self.actionHovered.emit(action)

    @Slot(QObject)
    def __onClicked(self, action):
        # type: (QAction) -> None
        assert isinstance(action, QAction)
        self.actionTriggered.emit(action)

    def eventFilter(self, obj, event):
        # type: (QObject, QEvent) -> bool
        etype = event.type()
        if etype == QEvent.KeyPress and obj.hasFocus():
            key = event.key()
            if key in [Qt.Key_Up, Qt.Key_Down, Qt.Key_Left, Qt.Key_Right]:
                if self.__focusMove(obj, key):
                    event.accept()
                    return True
        elif etype == QEvent.HoverEnter and obj.parent() is self:
            self.__onButtonEnter(obj)
        return super().eventFilter(obj, event)

    def __focusMove(self, focus, key):
        # type: (QWidget, Qt.Key) -> bool
        assert focus is self.focusWidget()
        try:
            index = self.__indexOf(focus)
        except IndexError:
            return False

        if key == Qt.Key_Down:
            index += self.__columns
        elif key == Qt.Key_Up:
            index -= self.__columns
        elif key == Qt.Key_Left:
            index -= 1
        elif key == Qt.Key_Right:
            index += 1

        if 0 <= index < self.count():
            button = self.__gridSlots[index].button
            button.setFocus(Qt.TabFocusReason)
            return True
        else:
            return False
예제 #6
0
    class StartItem(QWidget):
        """
        An active item in the bottom row of the welcome screen.
        """
        def __init__(self,
                     *args,
                     text="",
                     icon=QIcon(),
                     iconSize=QSize(),
                     iconActive=QIcon(),
                     **kwargs):
            self.__iconSize = QSize()
            self.__icon = QIcon()
            self.__icon_active = QIcon()
            self.__text = ""
            self.__active = False
            super().__init__(*args, **kwargs)
            self.setAutoFillBackground(True)
            font = self.font()
            font.setPointSize(18)
            self.setFont(font)
            self.setAttribute(Qt.WA_SetFont, False)
            self.setText(text)
            self.setIcon(icon)
            self.setIconSize(iconSize)
            self.setIconActive(iconActive)
            self.installEventFilter(self)

        def iconSize(self):
            if not self.__iconSize.isValid():
                size = self.style().pixelMetric(QStyle.PM_LargeIconSize, None,
                                                self) * 2
                return QSize(size, size)
            else:
                return QSize(self.__iconSize)

        def setIconSize(self, size):
            if size != self.__iconSize:
                self.__iconSize = QSize(size)
                self.updateGeometry()

        iconSize_ = Property(QSize, iconSize, setIconSize, designable=True)

        def icon(self):
            if self.__active:
                return QIcon(self.__icon_active)
            else:
                return QIcon(self.__icon)

        def setIcon(self, icon):
            self.__icon = QIcon(icon)
            self.update()

        icon_ = Property(QIcon, icon, setIcon, designable=True)

        def iconActive(self):
            return QIcon(self.__icon_active)

        def setIconActive(self, icon):
            self.__icon_active = QIcon(icon)
            self.update()

        icon_active_ = Property(QIcon,
                                iconActive,
                                setIconActive,
                                designable=True)

        def sizeHint(self):
            return QSize(200, 150)

        def setText(self, text):
            if self.__text != text:
                self.__text = text
                self.updateGeometry()
                self.update()

        def text(self):
            return self.__text

        text_ = Property(str, text, setText, designable=True)

        def initStyleOption(self, option):
            # type: (QStyleOptionViewItem) -> None
            option.initFrom(self)
            option.backgroundBrush = option.palette.brush(
                self.backgroundRole())
            option.font = self.font()
            option.text = self.text()
            option.icon = self.icon()

            option.decorationPosition = QStyleOptionViewItem.Top
            option.decorationAlignment = Qt.AlignCenter
            option.decorationSize = self.iconSize()
            option.displayAlignment = Qt.AlignCenter
            option.features = (QStyleOptionViewItem.WrapText
                               | QStyleOptionViewItem.HasDecoration
                               | QStyleOptionViewItem.HasDisplay)
            option.showDecorationSelected = True
            option.widget = self

        def paintEvent(self, event):
            style = self.style()  # type: QStyle
            painter = QPainter(self)
            option = QStyleOption()
            option.initFrom(self)
            style.drawPrimitive(QStyle.PE_Widget, option, painter, self)

            option = QStyleOptionViewItem()
            self.initStyleOption(option)
            style.drawControl(QStyle.CE_ItemViewItem, option, painter, self)

        def eventFilter(self, obj, event):
            try:
                if event.type() == QEvent.Enter:
                    self.__active = True
                    self.setCursor(Qt.PointingHandCursor)
                    self.update()
                    return True
                elif event.type() == QEvent.Leave:
                    self.__active = False
                    self.unsetCursor()
                    self.update()
                    return True
            except Exception as ex:
                pass
            return False
예제 #7
0
class ToolBox(QFrame):
    """
    A tool box widget.
    """
    # Emitted when a tab is toggled.
    tabToogled = Signal(int, bool)

    def setExclusive(self, exclusive):
        """
        Set exclusive tabs (only one tab can be open at a time).
        """
        if self.__exclusive != exclusive:
            self.__exclusive = exclusive
            self.__tabActionGroup.setExclusive(exclusive)
            checked = self.__tabActionGroup.checkedAction()
            if checked is None:
                # The action group can be out of sync with the actions state
                # when switching between exclusive states.
                actions_checked = [page.action for page in self.__pages
                                   if page.action.isChecked()]
                if actions_checked:
                    checked = actions_checked[0]

            # Trigger/toggle remaining open pages
            if exclusive and checked is not None:
                for page in self.__pages:
                    if checked != page.action and page.action.isChecked():
                        page.action.trigger()

    def exclusive(self):
        """
        Are the tabs in the toolbox exclusive.
        """
        return self.__exclusive

    exclusive_ = Property(bool,
                          fget=exclusive,
                          fset=setExclusive,
                          designable=True,
                          doc="Exclusive tabs")

    def __init__(self, parent=None, **kwargs):
        super().__init__(parent, **kwargs)

        self.__pages = []
        self.__tabButtonHeight = -1
        self.__tabIconSize = QSize()
        self.__exclusive = False
        self.__setupUi()

    def __setupUi(self):
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)

        # Scroll area for the contents.
        self.__scrollArea = \
                _ToolBoxScrollArea(self, objectName="toolbox-scroll-area")

        self.__scrollArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
        self.__scrollArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        self.__scrollArea.setSizePolicy(QSizePolicy.MinimumExpanding,
                                        QSizePolicy.MinimumExpanding)
        self.__scrollArea.setFrameStyle(QScrollArea.NoFrame)
        self.__scrollArea.setWidgetResizable(True)

        # A widget with all of the contents.
        # The tabs/contents are placed in the layout inside this widget
        self.__contents = QWidget(self.__scrollArea,
                                  objectName="toolbox-contents")
        self.__contentsLayout = _ToolBoxLayout(
            sizeConstraint=_ToolBoxLayout.SetMinAndMaxSize,
            spacing=0
        )
        self.__contentsLayout.setContentsMargins(0, 0, 0, 0)
        self.__contents.setLayout(self.__contentsLayout)

        self.__scrollArea.setWidget(self.__contents)

        layout.addWidget(self.__scrollArea)

        self.setLayout(layout)
        self.setSizePolicy(QSizePolicy.Fixed,
                           QSizePolicy.MinimumExpanding)

        self.__tabActionGroup = \
                QActionGroup(self, objectName="toolbox-tab-action-group")

        self.__tabActionGroup.setExclusive(self.__exclusive)

        self.__actionMapper = QSignalMapper(self)
        self.__actionMapper.mapped[QObject].connect(self.__onTabActionToogled)

    def setTabButtonHeight(self, height):
        """
        Set the tab button height.
        """
        if self.__tabButtonHeight != height:
            self.__tabButtonHeight = height
            for page in self.__pages:
                page.button.setFixedHeight(height)

    def tabButtonHeight(self):
        """
        Return the tab button height.
        """
        return self.__tabButtonHeight

    def setTabIconSize(self, size):
        """
        Set the tab button icon size.
        """
        if self.__tabIconSize != size:
            self.__tabIconSize = size
            for page in self.__pages:
                page.button.setIconSize(size)

    def tabIconSize(self):
        """
        Return the tab icon size.
        """
        return self.__tabIconSize

    def tabButton(self, index):
        """
        Return the tab button at `index`
        """
        return self.__pages[index].button

    def tabAction(self, index):
        """
        Return open/close action for the tab at `index`.
        """
        return self.__pages[index].action

    def addItem(self, widget, text, icon=None, toolTip=None):
        """
        Append the `widget` in a new tab and return its index.

        Parameters
        ----------
        widget : :class:`QWidget`
            A widget to be inserted. The toolbox takes ownership
            of the widget.

        text : str
            Name/title of the new tab.

        icon : :class:`QIcon`, optional
            An icon for the tab button.

        toolTip : str, optional
            Tool tip for the tab button.

        """
        return self.insertItem(self.count(), widget, text, icon, toolTip)

    def insertItem(self, index, widget, text, icon=None, toolTip=None):
        """
        Insert the `widget` in a new tab at position `index`.

        See also
        --------
        ToolBox.addItem

        """
        button = self.createTabButton(widget, text, icon, toolTip)

        self.__contentsLayout.insertWidget(index * 2, button)
        self.__contentsLayout.insertWidget(index * 2 + 1, widget)

        widget.hide()

        page = _ToolBoxPage(index, widget, button.defaultAction(), button)
        self.__pages.insert(index, page)

        for i in range(index + 1, self.count()):
            self.__pages[i] = self.__pages[i]._replace(index=i)

        self.__updatePositions()

        # Show (open) the first tab.
        if self.count() == 1 and index == 0:
            page.action.trigger()

        self.__updateSelected()

        self.updateGeometry()
        return index

    def removeItem(self, index):
        """
        Remove the widget at `index`.

        .. note:: The widget hidden but is is not deleted.

        """
        self.__contentsLayout.takeAt(2 * index + 1)
        self.__contentsLayout.takeAt(2 * index)
        page = self.__pages.pop(index)

        # Update the page indexes
        for i in range(index, self.count()):
            self.__pages[i] = self.__pages[i]._replace(index=i)

        page.button.deleteLater()

        # Hide the widget and reparent to self
        # This follows QToolBox.removeItem
        page.widget.hide()
        page.widget.setParent(self)

        self.__updatePositions()
        self.__updateSelected()

        self.updateGeometry()

    def count(self):
        """
        Return the number of widgets inserted in the toolbox.
        """
        return len(self.__pages)

    def widget(self, index):
        """
        Return the widget at `index`.
        """
        return self.__pages[index].widget

    def createTabButton(self, widget, text, icon=None, toolTip=None):
        """
        Create the tab button for `widget`.
        """
        action = QAction(text, self)
        action.setCheckable(True)

        if icon:
            action.setIcon(icon)

        if toolTip:
            action.setToolTip(toolTip)
        self.__tabActionGroup.addAction(action)
        self.__actionMapper.setMapping(action, action)
        action.toggled.connect(self.__actionMapper.map)

        button = ToolBoxTabButton(self, objectName="toolbox-tab-button")
        button.setDefaultAction(action)
        button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        button.setSizePolicy(QSizePolicy.Ignored,
                             QSizePolicy.Fixed)

        if self.__tabIconSize.isValid():
            button.setIconSize(self.__tabIconSize)

        if self.__tabButtonHeight > 0:
            button.setFixedHeight(self.__tabButtonHeight)

        return button

    def ensureWidgetVisible(self, child, xmargin=50, ymargin=50):
        """
        Scroll the contents so child widget instance is visible inside
        the viewport.

        """
        self.__scrollArea.ensureWidgetVisible(child, xmargin, ymargin)

    def sizeHint(self):
        hint = self.__contentsLayout.sizeHint()

        if self.count():
            # Compute max width of hidden widgets also.
            scroll = self.__scrollArea
            scroll_w = scroll.verticalScrollBar().sizeHint().width()
            frame_w = self.frameWidth() * 2 + scroll.frameWidth() * 2
            max_w = max([p.widget.sizeHint().width() for p in self.__pages])
            hint = QSize(max(max_w, hint.width()) + scroll_w + frame_w,
                         hint.height())

        return QSize(200, 200).expandedTo(hint)

    def __onTabActionToogled(self, action):
        page = find(self.__pages, action, key=attrgetter("action"))
        on = action.isChecked()
        page.widget.setVisible(on)
        index = page.index

        if index > 0:
            # Update the `previous` tab buttons style hints
            previous = self.__pages[index - 1].button
            flag = QStyleOptionToolBox.NextIsSelected
            if on:
                previous.selected |= flag
            else:
                previous.selected &= ~flag

            previous.update()

        if index < self.count() - 1:
            next = self.__pages[index + 1].button
            flag = QStyleOptionToolBox.PreviousIsSelected
            if on:
                next.selected |= flag
            else:
                next.selected &= ~flag

            next.update()

        self.tabToogled.emit(index, on)

        self.__contentsLayout.invalidate()

    def __updateSelected(self):
        """Update the tab buttons selected style flags.
        """
        if self.count() == 0:
            return

        opt = QStyleOptionToolBox

        def update(button, next_sel, prev_sel):
            if next_sel:
                button.selected |= opt.NextIsSelected
            else:
                button.selected &= ~opt.NextIsSelected

            if prev_sel:
                button.selected |= opt.PreviousIsSelected
            else:
                button.selected &= ~ opt.PreviousIsSelected

            button.update()

        if self.count() == 1:
            update(self.__pages[0].button, False, False)
        elif self.count() >= 2:
            pages = self.__pages
            for i in range(1, self.count() - 1):
                update(pages[i].button,
                       pages[i + 1].action.isChecked(),
                       pages[i - 1].action.isChecked())

    def __updatePositions(self):
        """Update the tab buttons position style flags.
        """
        if self.count() == 0:
            return
        elif self.count() == 1:
            self.__pages[0].button.position = QStyleOptionToolBox.OnlyOneTab
        else:
            self.__pages[0].button.position = QStyleOptionToolBox.Beginning
            self.__pages[-1].button.position = QStyleOptionToolBox.End
            for p in self.__pages[1:-1]:
                p.button.position = QStyleOptionToolBox.Middle

        for p in self.__pages:
            p.button.update()