Exemple #1
0
class PagedWidget(QFrame):
    class Page(SimpleNamespace):
        icon = ...  # type: QIcon
        text = ...  # type: str
        toolTip = ...  # type: str
        widget = ...  # type: QWidget

    class TabView(LinearIconView):
        def __init__(self, *args, focusPolicy=Qt.TabFocus, **kwargs):
            super().__init__(*args, focusPolicy=focusPolicy, **kwargs)

        def viewOptions(self):
            # type: () -> QStyleOptionViewItem
            option = super().viewOptions()
            # by default items in views are active only if the view is focused
            if self.isActiveWindow():
                option.state |= QStyle.State_Active
            return option

        def selectionCommand(self, index, event=None):
            # type: (QModelIndex, QEvent) -> QItemSelectionModel.SelectionFlags
            command = super().selectionCommand(index, event)
            if not index.isValid():
                # Prevent deselection on click/drag in an empty view part
                return QItemSelectionModel.NoUpdate
            else:
                # Prevent deselect on click + ctrl modifier
                return command & ~QItemSelectionModel.Deselect

    class TabViewDelegate(QStyledItemDelegate):
        def sizeHint(self, option, index):
            # type: (QStyleOptionViewItem, QModelIndex) -> QSize
            sh = super().sizeHint(option, index)
            widget = option.widget
            if isinstance(widget, PagedWidget.TabView):
                if widget.flow() == QListView.TopToBottom:
                    return sh.expandedTo(QSize(82, 100))
                else:
                    return sh.expandedTo(QSize(100, 82))
            else:
                return sh

        def initStyleOption(self, option, index):
            # type: (QStyleOptionViewItem, QModelIndex) -> None
            super().initStyleOption(option, index)
            widget = option.widget
            if isinstance(widget, PagedWidget.TabView):
                # extend the item rect to cover the whole viewport
                # (probably not a good idea).
                if widget.flow() == QListView.TopToBottom:
                    option.rect.setLeft(0)
                    option.rect.setRight(widget.viewport().width())
                else:
                    option.rect.setTop(0)
                    option.rect.setBottom(widget.viewport().height())

            if option.state & QStyle.State_Selected:
                # make sure the selection highlights cover the whole area
                option.showDecorationSelected = True

    #: Signal emitted when the current displayed widget changes
    currentIndexChanged = Signal(int)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.__pages = []  # type: List[PagedWidget.Page]
        self.__currentIndex = -1

        self.setContentsMargins(0, 0, 0, 0)
        self.setLayout(QHBoxLayout())
        self.layout().setContentsMargins(0, 0, 0, 0)
        self.layout().setSpacing(0)

        self.__tabview = PagedWidget.TabView(
            viewMode=QListView.IconMode,
            flow=QListView.TopToBottom,
            editTriggers=QListView.NoEditTriggers,
            uniformItemSizes=True,
            horizontalScrollBarPolicy=Qt.ScrollBarAlwaysOff)
        self.__tabview.setAttribute(Qt.WA_LayoutUsesWidgetRect)
        self.__tabview.setContentsMargins(0, 0, 0, 0)
        self.__tabview.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)

        self.__tabview.setItemDelegate(PagedWidget.TabViewDelegate())
        self.__tabview.setModel(QStandardItemModel(self))
        self.__tabview.selectionModel().selectionChanged.connect(
            self.__on_activated, Qt.UniqueConnection)
        iconsize = self.style().pixelMetric(QStyle.PM_LargeIconSize) * 3 // 2
        self.__tabview.setIconSize(QSize(iconsize, iconsize))
        self.__tabview.setAttribute(Qt.WA_MacShowFocusRect, False)

        self.__stack = QStackedWidget(objectName="contents")
        self.__stack.setContentsMargins(0, 0, 0, 0)
        self.__stack.setSizePolicy(QSizePolicy.Expanding,
                                   QSizePolicy.Expanding)

        self.layout().addWidget(self.__tabview)
        self.layout().addWidget(self.__stack)

    def currentIndex(self):
        # type: () -> int
        return self.__currentIndex

    def setCurrentIndex(self, index):
        # type: (int) -> None
        assert index < self.count()
        if self.__currentIndex != index:
            self.__currentIndex = index
            if index < 0:
                self.__tabview.selectionModel().clearSelection()
            else:
                self.__tabview.selectionModel().select(
                    self.__tabview.model().index(index, 0),
                    QItemSelectionModel.ClearAndSelect)
            self.__stack.setCurrentIndex(index)
            self.currentIndexChanged.emit(index)

    def count(self):
        # type: () -> int
        return len(self.__pages)

    def addPage(self, icon, text, widget):
        # type: (QIcon, str, QWidget) -> int
        return self.insertPage(len(self.__pages), icon, text, widget)

    def insertPage(self, index, icon, text, widget):
        # type: (int, QIcon, str, QWidget) -> int
        if not 0 <= index < self.count():
            index = self.count()

        page = PagedWidget.Page(icon=QIcon(icon),
                                text=text,
                                toolTip="",
                                widget=widget)
        item = QStandardItem()
        item.setIcon(icon)
        item.setText(text)

        self.__pages.insert(index, page)
        self.__tabview.model().insertRow(index, item)
        self.__stack.insertWidget(index, page.widget)

        if len(self.__pages) == 1:
            self.setCurrentIndex(0)
        elif index <= self.__currentIndex:
            self.__currentIndex += 1
        return index

    def removePage(self, index):
        # type: (int) -> None
        if 0 <= index < len(self.__pages):
            page = self.__pages[index]
            model = self.__tabview.model()  # type: QStandardItemModel
            currentIndex = self.__currentIndex
            if index < currentIndex:
                newCurrent = currentIndex - 1
            else:
                newCurrent = currentIndex
            selmodel = self.__tabview.selectionModel()
            selmodel.selectionChanged.disconnect(self.__on_activated)
            model.removeRow(index)
            del self.__pages[index]
            self.__stack.removeWidget(page.widget)
            selmodel.selectionChanged.connect(self.__on_activated,
                                              Qt.UniqueConnection)
            self.setCurrentIndex(newCurrent)

    def widget(self, index):
        # type: (int) -> QWidget
        return self.__pages[index].widget

    def setPageEnabled(self, index, enabled):
        # type: (int, bool) -> None
        item = self.__tabview.model().item(index)  # type: QStandardItem
        if item is not None:
            flags = item.flags()
            if enabled:
                flags = flags | Qt.ItemIsEnabled | Qt.ItemIsSelectable
            else:
                flags = flags & ~(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
            item.setFlags(flags)

    def isPageEnabled(self, index):
        # type: (int) -> bool
        item = self.__tabview.model().item(index)
        return bool(item.flags() & Qt.ItemIsEnabled)

    def setPageToolTip(self, index, toolTip):
        # type: (int, str) -> None
        if 0 <= index < self.count():
            model = self.__tabview.model()  # type: QStandardItemModel
            item = model.item(index, 0)
            item.setToolTip(toolTip)

    def pageToolTip(self, index):
        model = self.__tabview.model()  # type: QStandardItemModel
        return model.item(index, 0).toolTip()

    def __on_activated(self, selected, deselected):
        indexes = selected.indexes()
        if len(indexes) == 1:
            self.setCurrentIndex(indexes[0].row())
        elif len(indexes) == 0:
            self.setCurrentIndex(-1)
        else:
            assert False, "Invalid selection mode"