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)
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.
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.
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.
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).
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
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
97 def sizeHint(self) -> QSize: 98 return QSize(self.width() or 400, self.heightForWidth(self.width() or 400))
sizeHint(self, /) -> PySide6.QtCore.QSize
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
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
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
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
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
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
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
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
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