vibefpmgui.wrapping_tabs

The editor's tabs on several rows (View → Editor Tabs → Several Rows), instead of one row that scrolls.

Qt's tab bar has one row only. WrappingTabBar lays the editor's tabs out in as many rows as they need, drawn as Qt draws tabs (the style's own tab shapes), and stands in for the tab bar, which is hidden but stays the tabs' model: the names, icons and tooltips are read from it, and choosing, moving and closing tabs is done through it (so everything else about the tabs works as before).

  1"""The editor's tabs on several rows (View → Editor Tabs → Several Rows),
  2instead of one row that scrolls.
  3
  4Qt's tab bar has one row only. `WrappingTabBar` lays the editor's tabs out
  5in as many rows as they need, drawn as Qt draws tabs (the style's own tab
  6shapes), and stands in for the tab bar, which is hidden but stays the
  7tabs' model: the names, icons and tooltips are read from it, and choosing,
  8moving and closing tabs is done through it (so everything else about the
  9tabs works as before).
 10"""
 11
 12from __future__ import annotations
 13
 14from qtpy.QtCore import QEvent, QPoint, QRect, QSize, Qt, QTimer, Signal
 15from qtpy.QtGui import QColor, QFont, QPainter
 16from qtpy.QtWidgets import (
 17    QApplication,
 18    QSizePolicy,
 19    QStyle,
 20    QStyleOptionTab,
 21    QStylePainter,
 22    QTabBar,
 23    QTabWidget,
 24    QToolTip,
 25    QWidget,
 26)
 27
 28from .icons import make_icon
 29
 30CLOSE_SIZE = 12  # the close glyph (as the tabs' own close buttons)
 31
 32
 33def close_pixmap(ratio: float):
 34    """The close glyph for a screen with this many pixels to a point (2 on a
 35    Retina screen, 1.25 or 1.5 on a scaled Windows one): sharp there, drawn
 36    CLOSE_SIZE points across."""
 37    return make_icon("close", QColor(140, 140, 140)).pixmap(QSize(CLOSE_SIZE, CLOSE_SIZE), ratio)
 38
 39
 40class WrappingTabBar(QWidget):
 41    """A ``QTabWidget``'s tabs, wrapped onto rows."""
 42
 43    close_requested = Signal(int)  # a tab's close glyph clicked
 44    empty_menu_requested = Signal(QPoint)  # a right-click on no tab: where, on the screen
 45
 46    def __init__(self, tabs: QTabWidget, parent=None):
 47        super().__init__(parent)
 48        self.tabs = tabs
 49        self.bar: QTabBar = tabs.tabBar()
 50        self._rects: list[QRect] = []
 51        self._pressed: int | None = None   # the tab pressed (being dragged to another place)
 52        self._press_pos = QPoint()
 53        self._closing: int | None = None   # a close glyph pressed
 54        self._hover: int | None = None     # (the close glyph under the mouse: highlighted)
 55        self.setMouseTracking(True)
 56        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
 57        tabs.currentChanged.connect(self.refresh)
 58        self.bar.tabMoved.connect(self.refresh)
 59        self.bar.installEventFilter(self)  # (its font or style changed: the tabs' sizes too)
 60
 61    # ------------------------------------------------------------ layout
 62    def refresh(self, *_):
 63        """The tabs changed (added, removed, renamed, moved, chosen)."""
 64        self._layout(self.width())
 65        self.updateGeometry()
 66        self.update()
 67
 68    def eventFilter(self, obj, event):
 69        if obj is self.bar and event.type() in (QEvent.FontChange, QEvent.StyleChange):
 70            QTimer.singleShot(0, self, self.refresh)  # (once the bar has dropped its old text sizes)
 71        return super().eventFilter(obj, event)
 72
 73    def _layout(self, width: int) -> int:
 74        """Place the tabs in rows ``width`` wide; the height they take."""
 75        rects, x, y, row_height = [], 0, 0, 0
 76        for index in range(self.bar.count()):
 77            size = self.bar.tabSizeHint(index)
 78            if x and x + size.width() > width:  # (no room left on this row: the next)
 79                x, y = 0, y + row_height
 80                row_height = 0
 81            rects.append(QRect(x, y, size.width(), size.height()))
 82            x += size.width()
 83            row_height = max(row_height, size.height())
 84        self._rects = rects
 85        return y + row_height
 86
 87    def hasHeightForWidth(self) -> bool:
 88        return True
 89
 90    def heightForWidth(self, width: int) -> int:
 91        rects = self._rects
 92        height = self._layout(width)
 93        self._rects = rects  # (only asked: the tabs stay where they are)
 94        return height
 95
 96    def sizeHint(self) -> QSize:
 97        return QSize(self.width() or 400, self.heightForWidth(self.width() or 400))
 98
 99    def minimumSizeHint(self) -> QSize:
100        return QSize(0, self.sizeHint().height())
101
102    def resizeEvent(self, event):
103        super().resizeEvent(event)
104        height = self._layout(event.size().width())
105        if height != self.height():
106            self.setFixedHeight(height)  # (more rows, or fewer: the editor below gets the rest)
107
108    def index_at(self, pos: QPoint) -> int:
109        return next((i for i, rect in enumerate(self._rects) if rect.contains(pos)), -1)
110
111    def tab_rect(self, index: int) -> QRect:
112        return QRect(self._rects[index]) if 0 <= index < len(self._rects) else QRect()
113
114    # ------------------------------------------------------------ drawing
115    def _option(self, index: int) -> QStyleOptionTab:
116        option = QStyleOptionTab()
117        self.bar.initStyleOption(option, index)
118        option.rect = self._rects[index]
119        row = [i for i, r in enumerate(self._rects) if r.top() == option.rect.top()]
120        option.position = (QStyleOptionTab.OnlyOneTab if len(row) == 1 else QStyleOptionTab.Beginning
121                           if index == row[0] else QStyleOptionTab.End if index == row[-1]
122                           else QStyleOptionTab.Middle)
123        option.state |= QStyle.State_Enabled
124        if index == self.tabs.currentIndex():
125            option.state |= QStyle.State_Selected
126        else:
127            option.state &= ~QStyle.State_Selected
128        option.text = self.bar.tabText(index)
129        return option
130
131    def _button_rect(self, element, option: QStyleOptionTab) -> QRect:
132        """Where the style puts a tab's button (``element``: its left or right
133        one), for a tab on any row. The macOS style places them from the top
134        of the bar whatever the tab's own top (one row's always 0): asked for
135        the tab on the first row, then moved down to its own."""
136        top = QStyleOptionTab(option)
137        top.rect = QRect(option.rect)
138        top.rect.moveTop(0)
139        return self.style().subElementRect(element, top, self).translated(0, option.rect.top())
140
141    def _close_rect(self, option: QStyleOptionTab) -> QRect:
142        area = self._button_rect(QStyle.SE_TabBarTabRightButton, option)
143        rect = QRect(0, 0, CLOSE_SIZE + 6, CLOSE_SIZE + 6)
144        rect.moveCenter(area.center())
145        return rect
146
147    def paintEvent(self, event):
148        if len(self._rects) != self.bar.count():
149            self._layout(self.width())
150        painter = QStylePainter(self)
151        italic = QFont(self.font())
152        italic.setItalic(True)
153        close = close_pixmap(self.devicePixelRatioF())
154        current = self.tabs.currentIndex()
155        for index in [i for i in range(len(self._rects)) if i != current] + ([current] if current >= 0 else []):
156            option = self._option(index)  # (the current tab last: drawn over its neighbours)
157            painter.setClipRect(option.rect)  # (a style may draw the current tab down into the page: not the next row)
158            painter.drawControl(QStyle.CE_TabBarTabShape, option)
159            name = self.bar.tab_name(index) if hasattr(self.bar, "tab_name") else option.text
160            is_italic = hasattr(self.bar, "italic_name") and self.bar.italic_name(index) is not None
161            option.text = name
162            painter.setFont(italic if is_italic else self.font())
163            painter.drawControl(QStyle.CE_TabBarTabLabel, option)
164            icon = self.bar.tabButton(index, QTabBar.LeftSide)
165            if icon is not None and hasattr(icon, "pixmap") and icon.pixmap() is not None:
166                area = self._button_rect(QStyle.SE_TabBarTabLeftButton, option)
167                pixmap = icon.pixmap()
168                # (Its size as drawn, not in device pixels: on a Retina screen a 16-point icon is 32 pixels.)
169                target = QRect(QPoint(0, 0), pixmap.deviceIndependentSize().toSize())
170                target.moveCenter(area.center())
171                painter.drawPixmap(target, pixmap)
172            rect = self._close_rect(option)
173            if index == self._hover:
174                painter.setRenderHint(QPainter.Antialiasing)
175                painter.setPen(Qt.NoPen)
176                painter.setBrush(QColor(128, 128, 128, 80))
177                painter.drawRoundedRect(rect, 4, 4)
178            glyph = QRect(0, 0, CLOSE_SIZE, CLOSE_SIZE)  # (its size as drawn, whatever the screen's pixels)
179            glyph.moveCenter(rect.center())
180            painter.drawPixmap(glyph, close)
181            painter.setClipping(False)
182
183    # ------------------------------------------------------------ the mouse
184    def _on_close(self, index: int, pos: QPoint) -> bool:
185        return index >= 0 and self._close_rect(self._option(index)).contains(pos)
186
187    def mousePressEvent(self, event):
188        index = self.index_at(event.position().toPoint())
189        if event.button() != Qt.LeftButton or index < 0:
190            super().mousePressEvent(event)
191            return
192        if self._on_close(index, event.position().toPoint()):
193            self._closing = index
194            return
195        self.tabs.setCurrentIndex(index)
196        self._pressed, self._press_pos = index, event.position().toPoint()
197
198    def mouseMoveEvent(self, event):
199        pos = event.position().toPoint()
200        index = self.index_at(pos)
201        hover = index if self._on_close(index, pos) else None
202        if hover != self._hover:
203            self._hover = hover
204            self.update()
205        if self._pressed is None or not event.buttons() & Qt.LeftButton:
206            return
207        if (pos - self._press_pos).manhattanLength() < QApplication.startDragDistance():
208            return
209        if index >= 0 and index != self._pressed:  # (dragged over another tab: it takes that one's place)
210            self.bar.moveTab(self._pressed, index)
211            self._pressed = index
212
213    def mouseReleaseEvent(self, event):
214        closing, self._closing, self._pressed = self._closing, None, None
215        index = self.index_at(event.position().toPoint())
216        if closing is not None and index == closing and self._on_close(index, event.position().toPoint()):
217            self.close_requested.emit(index)
218
219    def mouseDoubleClickEvent(self, event):
220        index = self.index_at(event.position().toPoint())
221        if index >= 0 and not self._on_close(index, event.position().toPoint()):
222            self.tabs.tabBarDoubleClicked.emit(index)
223
224    def contextMenuEvent(self, event):
225        if self.index_at(event.pos()) < 0:
226            self.empty_menu_requested.emit(event.globalPos())
227            event.accept()
228        else:
229            super().contextMenuEvent(event)
230
231    def leaveEvent(self, event):
232        if self._hover is not None:
233            self._hover = None
234            self.update()
235        super().leaveEvent(event)
236
237    def event(self, event):
238        if event.type() == event.Type.ToolTip:
239            pos = event.pos()
240            index = self.index_at(pos)
241            if index < 0:
242                QToolTip.hideText()
243            elif self._on_close(index, pos):
244                button = self.bar.tabButton(index, QTabBar.RightSide)
245                QToolTip.showText(event.globalPos(), button.toolTip() if button is not None else "Close", self)
246            else:
247                QToolTip.showText(event.globalPos(), self.bar.tabToolTip(index), self)
248            return True
249        return super().event(event)
CLOSE_SIZE = 12
def close_pixmap(ratio: float):
34def close_pixmap(ratio: float):
35    """The close glyph for a screen with this many pixels to a point (2 on a
36    Retina screen, 1.25 or 1.5 on a scaled Windows one): sharp there, drawn
37    CLOSE_SIZE points across."""
38    return make_icon("close", QColor(140, 140, 140)).pixmap(QSize(CLOSE_SIZE, CLOSE_SIZE), ratio)

The close glyph for a screen with this many pixels to a point (2 on a Retina screen, 1.25 or 1.5 on a scaled Windows one): sharp there, drawn CLOSE_SIZE points across.

class WrappingTabBar(PySide6.QtWidgets.QWidget):
 41class WrappingTabBar(QWidget):
 42    """A ``QTabWidget``'s tabs, wrapped onto rows."""
 43
 44    close_requested = Signal(int)  # a tab's close glyph clicked
 45    empty_menu_requested = Signal(QPoint)  # a right-click on no tab: where, on the screen
 46
 47    def __init__(self, tabs: QTabWidget, parent=None):
 48        super().__init__(parent)
 49        self.tabs = tabs
 50        self.bar: QTabBar = tabs.tabBar()
 51        self._rects: list[QRect] = []
 52        self._pressed: int | None = None   # the tab pressed (being dragged to another place)
 53        self._press_pos = QPoint()
 54        self._closing: int | None = None   # a close glyph pressed
 55        self._hover: int | None = None     # (the close glyph under the mouse: highlighted)
 56        self.setMouseTracking(True)
 57        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
 58        tabs.currentChanged.connect(self.refresh)
 59        self.bar.tabMoved.connect(self.refresh)
 60        self.bar.installEventFilter(self)  # (its font or style changed: the tabs' sizes too)
 61
 62    # ------------------------------------------------------------ layout
 63    def refresh(self, *_):
 64        """The tabs changed (added, removed, renamed, moved, chosen)."""
 65        self._layout(self.width())
 66        self.updateGeometry()
 67        self.update()
 68
 69    def eventFilter(self, obj, event):
 70        if obj is self.bar and event.type() in (QEvent.FontChange, QEvent.StyleChange):
 71            QTimer.singleShot(0, self, self.refresh)  # (once the bar has dropped its old text sizes)
 72        return super().eventFilter(obj, event)
 73
 74    def _layout(self, width: int) -> int:
 75        """Place the tabs in rows ``width`` wide; the height they take."""
 76        rects, x, y, row_height = [], 0, 0, 0
 77        for index in range(self.bar.count()):
 78            size = self.bar.tabSizeHint(index)
 79            if x and x + size.width() > width:  # (no room left on this row: the next)
 80                x, y = 0, y + row_height
 81                row_height = 0
 82            rects.append(QRect(x, y, size.width(), size.height()))
 83            x += size.width()
 84            row_height = max(row_height, size.height())
 85        self._rects = rects
 86        return y + row_height
 87
 88    def hasHeightForWidth(self) -> bool:
 89        return True
 90
 91    def heightForWidth(self, width: int) -> int:
 92        rects = self._rects
 93        height = self._layout(width)
 94        self._rects = rects  # (only asked: the tabs stay where they are)
 95        return height
 96
 97    def sizeHint(self) -> QSize:
 98        return QSize(self.width() or 400, self.heightForWidth(self.width() or 400))
 99
100    def minimumSizeHint(self) -> QSize:
101        return QSize(0, self.sizeHint().height())
102
103    def resizeEvent(self, event):
104        super().resizeEvent(event)
105        height = self._layout(event.size().width())
106        if height != self.height():
107            self.setFixedHeight(height)  # (more rows, or fewer: the editor below gets the rest)
108
109    def index_at(self, pos: QPoint) -> int:
110        return next((i for i, rect in enumerate(self._rects) if rect.contains(pos)), -1)
111
112    def tab_rect(self, index: int) -> QRect:
113        return QRect(self._rects[index]) if 0 <= index < len(self._rects) else QRect()
114
115    # ------------------------------------------------------------ drawing
116    def _option(self, index: int) -> QStyleOptionTab:
117        option = QStyleOptionTab()
118        self.bar.initStyleOption(option, index)
119        option.rect = self._rects[index]
120        row = [i for i, r in enumerate(self._rects) if r.top() == option.rect.top()]
121        option.position = (QStyleOptionTab.OnlyOneTab if len(row) == 1 else QStyleOptionTab.Beginning
122                           if index == row[0] else QStyleOptionTab.End if index == row[-1]
123                           else QStyleOptionTab.Middle)
124        option.state |= QStyle.State_Enabled
125        if index == self.tabs.currentIndex():
126            option.state |= QStyle.State_Selected
127        else:
128            option.state &= ~QStyle.State_Selected
129        option.text = self.bar.tabText(index)
130        return option
131
132    def _button_rect(self, element, option: QStyleOptionTab) -> QRect:
133        """Where the style puts a tab's button (``element``: its left or right
134        one), for a tab on any row. The macOS style places them from the top
135        of the bar whatever the tab's own top (one row's always 0): asked for
136        the tab on the first row, then moved down to its own."""
137        top = QStyleOptionTab(option)
138        top.rect = QRect(option.rect)
139        top.rect.moveTop(0)
140        return self.style().subElementRect(element, top, self).translated(0, option.rect.top())
141
142    def _close_rect(self, option: QStyleOptionTab) -> QRect:
143        area = self._button_rect(QStyle.SE_TabBarTabRightButton, option)
144        rect = QRect(0, 0, CLOSE_SIZE + 6, CLOSE_SIZE + 6)
145        rect.moveCenter(area.center())
146        return rect
147
148    def paintEvent(self, event):
149        if len(self._rects) != self.bar.count():
150            self._layout(self.width())
151        painter = QStylePainter(self)
152        italic = QFont(self.font())
153        italic.setItalic(True)
154        close = close_pixmap(self.devicePixelRatioF())
155        current = self.tabs.currentIndex()
156        for index in [i for i in range(len(self._rects)) if i != current] + ([current] if current >= 0 else []):
157            option = self._option(index)  # (the current tab last: drawn over its neighbours)
158            painter.setClipRect(option.rect)  # (a style may draw the current tab down into the page: not the next row)
159            painter.drawControl(QStyle.CE_TabBarTabShape, option)
160            name = self.bar.tab_name(index) if hasattr(self.bar, "tab_name") else option.text
161            is_italic = hasattr(self.bar, "italic_name") and self.bar.italic_name(index) is not None
162            option.text = name
163            painter.setFont(italic if is_italic else self.font())
164            painter.drawControl(QStyle.CE_TabBarTabLabel, option)
165            icon = self.bar.tabButton(index, QTabBar.LeftSide)
166            if icon is not None and hasattr(icon, "pixmap") and icon.pixmap() is not None:
167                area = self._button_rect(QStyle.SE_TabBarTabLeftButton, option)
168                pixmap = icon.pixmap()
169                # (Its size as drawn, not in device pixels: on a Retina screen a 16-point icon is 32 pixels.)
170                target = QRect(QPoint(0, 0), pixmap.deviceIndependentSize().toSize())
171                target.moveCenter(area.center())
172                painter.drawPixmap(target, pixmap)
173            rect = self._close_rect(option)
174            if index == self._hover:
175                painter.setRenderHint(QPainter.Antialiasing)
176                painter.setPen(Qt.NoPen)
177                painter.setBrush(QColor(128, 128, 128, 80))
178                painter.drawRoundedRect(rect, 4, 4)
179            glyph = QRect(0, 0, CLOSE_SIZE, CLOSE_SIZE)  # (its size as drawn, whatever the screen's pixels)
180            glyph.moveCenter(rect.center())
181            painter.drawPixmap(glyph, close)
182            painter.setClipping(False)
183
184    # ------------------------------------------------------------ the mouse
185    def _on_close(self, index: int, pos: QPoint) -> bool:
186        return index >= 0 and self._close_rect(self._option(index)).contains(pos)
187
188    def mousePressEvent(self, event):
189        index = self.index_at(event.position().toPoint())
190        if event.button() != Qt.LeftButton or index < 0:
191            super().mousePressEvent(event)
192            return
193        if self._on_close(index, event.position().toPoint()):
194            self._closing = index
195            return
196        self.tabs.setCurrentIndex(index)
197        self._pressed, self._press_pos = index, event.position().toPoint()
198
199    def mouseMoveEvent(self, event):
200        pos = event.position().toPoint()
201        index = self.index_at(pos)
202        hover = index if self._on_close(index, pos) else None
203        if hover != self._hover:
204            self._hover = hover
205            self.update()
206        if self._pressed is None or not event.buttons() & Qt.LeftButton:
207            return
208        if (pos - self._press_pos).manhattanLength() < QApplication.startDragDistance():
209            return
210        if index >= 0 and index != self._pressed:  # (dragged over another tab: it takes that one's place)
211            self.bar.moveTab(self._pressed, index)
212            self._pressed = index
213
214    def mouseReleaseEvent(self, event):
215        closing, self._closing, self._pressed = self._closing, None, None
216        index = self.index_at(event.position().toPoint())
217        if closing is not None and index == closing and self._on_close(index, event.position().toPoint()):
218            self.close_requested.emit(index)
219
220    def mouseDoubleClickEvent(self, event):
221        index = self.index_at(event.position().toPoint())
222        if index >= 0 and not self._on_close(index, event.position().toPoint()):
223            self.tabs.tabBarDoubleClicked.emit(index)
224
225    def contextMenuEvent(self, event):
226        if self.index_at(event.pos()) < 0:
227            self.empty_menu_requested.emit(event.globalPos())
228            event.accept()
229        else:
230            super().contextMenuEvent(event)
231
232    def leaveEvent(self, event):
233        if self._hover is not None:
234            self._hover = None
235            self.update()
236        super().leaveEvent(event)
237
238    def event(self, event):
239        if event.type() == event.Type.ToolTip:
240            pos = event.pos()
241            index = self.index_at(pos)
242            if index < 0:
243                QToolTip.hideText()
244            elif self._on_close(index, pos):
245                button = self.bar.tabButton(index, QTabBar.RightSide)
246                QToolTip.showText(event.globalPos(), button.toolTip() if button is not None else "Close", self)
247            else:
248                QToolTip.showText(event.globalPos(), self.bar.tabToolTip(index), self)
249            return True
250        return super().event(event)

A QTabWidget's tabs, wrapped onto rows.

WrappingTabBar(tabs: PySide6.QtWidgets.QTabWidget, parent=None)
47    def __init__(self, tabs: QTabWidget, parent=None):
48        super().__init__(parent)
49        self.tabs = tabs
50        self.bar: QTabBar = tabs.tabBar()
51        self._rects: list[QRect] = []
52        self._pressed: int | None = None   # the tab pressed (being dragged to another place)
53        self._press_pos = QPoint()
54        self._closing: int | None = None   # a close glyph pressed
55        self._hover: int | None = None     # (the close glyph under the mouse: highlighted)
56        self.setMouseTracking(True)
57        self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Fixed)
58        tabs.currentChanged.connect(self.refresh)
59        self.bar.tabMoved.connect(self.refresh)
60        self.bar.installEventFilter(self)  # (its font or style changed: the tabs' sizes too)

__init__(self, /, parent: PySide6.QtWidgets.QWidget | None = None, f: PySide6.QtCore.Qt.WindowType = Default(Qt.WindowFlags), *, modal: bool | None = None, windowModality: PySide6.QtCore.Qt.WindowModality | None = None, enabled: bool | None = None, geometry: PySide6.QtCore.QRect | None = None, frameGeometry: PySide6.QtCore.QRect | None = None, normalGeometry: PySide6.QtCore.QRect | None = None, x: int | None = None, y: int | None = None, pos: PySide6.QtCore.QPoint | None = None, frameSize: PySide6.QtCore.QSize | None = None, size: PySide6.QtCore.QSize | None = None, width: int | None = None, height: int | None = None, rect: PySide6.QtCore.QRect | None = None, childrenRect: PySide6.QtCore.QRect | None = None, childrenRegion: PySide6.QtGui.QRegion | None = None, sizePolicy: PySide6.QtWidgets.QSizePolicy | None = None, minimumSize: PySide6.QtCore.QSize | None = None, maximumSize: PySide6.QtCore.QSize | None = None, minimumWidth: int | None = None, minimumHeight: int | None = None, maximumWidth: int | None = None, maximumHeight: int | None = None, sizeIncrement: PySide6.QtCore.QSize | None = None, baseSize: PySide6.QtCore.QSize | None = None, palette: PySide6.QtGui.QPalette | None = None, font: PySide6.QtGui.QFont | None = None, cursor: PySide6.QtGui.QCursor | None = None, mouseTracking: bool | None = None, tabletTracking: bool | None = None, isActiveWindow: bool | None = None, focusPolicy: PySide6.QtCore.Qt.FocusPolicy | None = None, focus: bool | None = None, contextMenuPolicy: PySide6.QtCore.Qt.ContextMenuPolicy | None = None, updatesEnabled: bool | None = None, visible: bool | None = None, minimized: bool | None = None, maximized: bool | None = None, fullScreen: bool | None = None, sizeHint: PySide6.QtCore.QSize | None = None, minimumSizeHint: PySide6.QtCore.QSize | None = None, acceptDrops: bool | None = None, windowTitle: str | None = None, windowIcon: PySide6.QtGui.QIcon | None = None, windowIconText: str | None = None, windowOpacity: float | None = None, windowModified: bool | None = None, toolTip: str | None = None, toolTipDuration: int | None = None, statusTip: str | None = None, whatsThis: str | None = None, accessibleName: str | None = None, accessibleDescription: str | None = None, accessibleIdentifier: str | None = None, layoutDirection: PySide6.QtCore.Qt.LayoutDirection | None = None, autoFillBackground: bool | None = None, styleSheet: str | None = None, locale: PySide6.QtCore.QLocale | None = None, windowFilePath: str | None = None, inputMethodHints: PySide6.QtCore.Qt.InputMethodHint | None = None) -> None

Initialize self. See help(type(self)) for accurate signature.

def close_requested(unknown):

The type of the None singleton.

def empty_menu_requested(unknown):

The type of the None singleton.

tabs
bar: PySide6.QtWidgets.QTabBar
def refresh(self, *_):
63    def refresh(self, *_):
64        """The tabs changed (added, removed, renamed, moved, chosen)."""
65        self._layout(self.width())
66        self.updateGeometry()
67        self.update()

The tabs changed (added, removed, renamed, moved, chosen).

def eventFilter(self, obj, event):
69    def eventFilter(self, obj, event):
70        if obj is self.bar and event.type() in (QEvent.FontChange, QEvent.StyleChange):
71            QTimer.singleShot(0, self, self.refresh)  # (once the bar has dropped its old text sizes)
72        return super().eventFilter(obj, event)

eventFilter(self, watched: PySide6.QtCore.QObject, event: PySide6.QtCore.QEvent, /) -> bool

def hasHeightForWidth(self) -> bool:
88    def hasHeightForWidth(self) -> bool:
89        return True

hasHeightForWidth(self, /) -> bool

def heightForWidth(self, width: int) -> int:
91    def heightForWidth(self, width: int) -> int:
92        rects = self._rects
93        height = self._layout(width)
94        self._rects = rects  # (only asked: the tabs stay where they are)
95        return height

heightForWidth(self, arg__1: int, /) -> int

def sizeHint(self) -> PySide6.QtCore.QSize:
97    def sizeHint(self) -> QSize:
98        return QSize(self.width() or 400, self.heightForWidth(self.width() or 400))

sizeHint(self, /) -> PySide6.QtCore.QSize

def minimumSizeHint(self) -> PySide6.QtCore.QSize:
100    def minimumSizeHint(self) -> QSize:
101        return QSize(0, self.sizeHint().height())

minimumSizeHint(self, /) -> PySide6.QtCore.QSize

def resizeEvent(self, event):
103    def resizeEvent(self, event):
104        super().resizeEvent(event)
105        height = self._layout(event.size().width())
106        if height != self.height():
107            self.setFixedHeight(height)  # (more rows, or fewer: the editor below gets the rest)

resizeEvent(self, event: PySide6.QtGui.QResizeEvent, /) -> None

def index_at(self, pos: PySide6.QtCore.QPoint) -> int:
109    def index_at(self, pos: QPoint) -> int:
110        return next((i for i, rect in enumerate(self._rects) if rect.contains(pos)), -1)
def tab_rect(self, index: int) -> PySide6.QtCore.QRect:
112    def tab_rect(self, index: int) -> QRect:
113        return QRect(self._rects[index]) if 0 <= index < len(self._rects) else QRect()
def paintEvent(self, event):
148    def paintEvent(self, event):
149        if len(self._rects) != self.bar.count():
150            self._layout(self.width())
151        painter = QStylePainter(self)
152        italic = QFont(self.font())
153        italic.setItalic(True)
154        close = close_pixmap(self.devicePixelRatioF())
155        current = self.tabs.currentIndex()
156        for index in [i for i in range(len(self._rects)) if i != current] + ([current] if current >= 0 else []):
157            option = self._option(index)  # (the current tab last: drawn over its neighbours)
158            painter.setClipRect(option.rect)  # (a style may draw the current tab down into the page: not the next row)
159            painter.drawControl(QStyle.CE_TabBarTabShape, option)
160            name = self.bar.tab_name(index) if hasattr(self.bar, "tab_name") else option.text
161            is_italic = hasattr(self.bar, "italic_name") and self.bar.italic_name(index) is not None
162            option.text = name
163            painter.setFont(italic if is_italic else self.font())
164            painter.drawControl(QStyle.CE_TabBarTabLabel, option)
165            icon = self.bar.tabButton(index, QTabBar.LeftSide)
166            if icon is not None and hasattr(icon, "pixmap") and icon.pixmap() is not None:
167                area = self._button_rect(QStyle.SE_TabBarTabLeftButton, option)
168                pixmap = icon.pixmap()
169                # (Its size as drawn, not in device pixels: on a Retina screen a 16-point icon is 32 pixels.)
170                target = QRect(QPoint(0, 0), pixmap.deviceIndependentSize().toSize())
171                target.moveCenter(area.center())
172                painter.drawPixmap(target, pixmap)
173            rect = self._close_rect(option)
174            if index == self._hover:
175                painter.setRenderHint(QPainter.Antialiasing)
176                painter.setPen(Qt.NoPen)
177                painter.setBrush(QColor(128, 128, 128, 80))
178                painter.drawRoundedRect(rect, 4, 4)
179            glyph = QRect(0, 0, CLOSE_SIZE, CLOSE_SIZE)  # (its size as drawn, whatever the screen's pixels)
180            glyph.moveCenter(rect.center())
181            painter.drawPixmap(glyph, close)
182            painter.setClipping(False)

paintEvent(self, event: PySide6.QtGui.QPaintEvent, /) -> None

def mousePressEvent(self, event):
188    def mousePressEvent(self, event):
189        index = self.index_at(event.position().toPoint())
190        if event.button() != Qt.LeftButton or index < 0:
191            super().mousePressEvent(event)
192            return
193        if self._on_close(index, event.position().toPoint()):
194            self._closing = index
195            return
196        self.tabs.setCurrentIndex(index)
197        self._pressed, self._press_pos = index, event.position().toPoint()

mousePressEvent(self, event: PySide6.QtGui.QMouseEvent, /) -> None

def mouseMoveEvent(self, event):
199    def mouseMoveEvent(self, event):
200        pos = event.position().toPoint()
201        index = self.index_at(pos)
202        hover = index if self._on_close(index, pos) else None
203        if hover != self._hover:
204            self._hover = hover
205            self.update()
206        if self._pressed is None or not event.buttons() & Qt.LeftButton:
207            return
208        if (pos - self._press_pos).manhattanLength() < QApplication.startDragDistance():
209            return
210        if index >= 0 and index != self._pressed:  # (dragged over another tab: it takes that one's place)
211            self.bar.moveTab(self._pressed, index)
212            self._pressed = index

mouseMoveEvent(self, event: PySide6.QtGui.QMouseEvent, /) -> None

def mouseReleaseEvent(self, event):
214    def mouseReleaseEvent(self, event):
215        closing, self._closing, self._pressed = self._closing, None, None
216        index = self.index_at(event.position().toPoint())
217        if closing is not None and index == closing and self._on_close(index, event.position().toPoint()):
218            self.close_requested.emit(index)

mouseReleaseEvent(self, event: PySide6.QtGui.QMouseEvent, /) -> None

def mouseDoubleClickEvent(self, event):
220    def mouseDoubleClickEvent(self, event):
221        index = self.index_at(event.position().toPoint())
222        if index >= 0 and not self._on_close(index, event.position().toPoint()):
223            self.tabs.tabBarDoubleClicked.emit(index)

mouseDoubleClickEvent(self, event: PySide6.QtGui.QMouseEvent, /) -> None

def contextMenuEvent(self, event):
225    def contextMenuEvent(self, event):
226        if self.index_at(event.pos()) < 0:
227            self.empty_menu_requested.emit(event.globalPos())
228            event.accept()
229        else:
230            super().contextMenuEvent(event)

contextMenuEvent(self, event: PySide6.QtGui.QContextMenuEvent, /) -> None

def leaveEvent(self, event):
232    def leaveEvent(self, event):
233        if self._hover is not None:
234            self._hover = None
235            self.update()
236        super().leaveEvent(event)

leaveEvent(self, event: PySide6.QtCore.QEvent, /) -> None

def event(self, event):
238    def event(self, event):
239        if event.type() == event.Type.ToolTip:
240            pos = event.pos()
241            index = self.index_at(pos)
242            if index < 0:
243                QToolTip.hideText()
244            elif self._on_close(index, pos):
245                button = self.bar.tabButton(index, QTabBar.RightSide)
246                QToolTip.showText(event.globalPos(), button.toolTip() if button is not None else "Close", self)
247            else:
248                QToolTip.showText(event.globalPos(), self.bar.tabToolTip(index), self)
249            return True
250        return super().event(event)

event(self, event: PySide6.QtCore.QEvent, /) -> bool

staticMetaObject = PySide6.QtCore.QMetaObject("WrappingTabBar" inherits "QWidget": Methods: #33 type=Signal, signature=close_requested(int), parameters=int #34 type=Signal, signature=empty_menu_requested(QPoint), parameters=QPoint )