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"