Skip to content

tree_view

AYTreeView component module.

AYTreeView

Bases: StyleMixin, QTreeView

AYON-styled tree view.

Fully self-contained: uses AYONStyle for all painting, a custom item delegate that draws directly bypassing any parent QSS, and AYScrollBar instances for scrollbars.

Parameters:

Name Type Description Default
parent QWidget | None

Optional parent widget.

None
variant QTreeViewVariants

Visual style variant controlling background colour and item-state colours.

Default
Source code in client/ayon_ui_qt/components/tree_view.py
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
class AYTreeView(StyleMixin, QTreeView):
    """AYON-styled tree view.

    Fully self-contained: uses AYONStyle for all painting, a custom
    item delegate that draws directly bypassing any parent QSS, and
    AYScrollBar instances for scrollbars.

    Args:
        parent: Optional parent widget.
        variant: Visual style variant controlling background colour and
            item-state colours.
    """

    Variants = QTreeViewVariants
    selection_changed = Signal(QItemSelection, QItemSelection)
    double_clicked = Signal(QMouseEvent)

    def __init__(
        self,
        parent: QWidget | None = None,
        variant: QTreeViewVariants = QTreeViewVariants.Default,
    ) -> None:
        self._variant_str: str = variant.value

        super().__init__(parent)

        style = get_ayon_style()
        self.setStyle(style)

        # Self-contained: do not inherit parent background or stylesheet.
        self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground, True)
        self.setAttribute(Qt.WidgetAttribute.WA_Hover, True)
        self.setMouseTracking(True)

        # Viewport must also be opaque with our colour.
        self.viewport().setAttribute(
            Qt.WidgetAttribute.WA_TranslucentBackground, False
        )
        self.viewport().setAttribute(Qt.WidgetAttribute.WA_Hover, True)
        self.viewport().setMouseTracking(True)
        self.viewport().installEventFilter(self)
        self._hovered_row_key: tuple | None = None
        self._sync_viewport_palette()

        # Custom item delegate — paints items directly, avoids QSS.
        delegate = TreeViewItemDelegate(
            parent=self,
            style_model=style.model,
            variant=self._variant_str,
        )
        self.setItemDelegate(delegate)

        # Styled scrollbars.
        vsb = AYScrollBar(Qt.Orientation.Vertical, self)
        self.setVerticalScrollBar(vsb)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)
        hsb = AYScrollBar(Qt.Orientation.Horizontal, self)
        self.setHorizontalScrollBar(hsb)
        self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAsNeeded)

        # No header — single-column hierarchical browser.
        self.setHeaderHidden(True)

        # Indentation from style data.
        tv_style = style.model.get_style("QTreeView", self._variant_str)
        self.setIndentation(int(tv_style.get("indent", 20)))

        # Selection behaviour.
        self.setSelectionMode(QTreeView.SelectionMode.ExtendedSelection)
        self.setSelectionBehavior(QTreeView.SelectionBehavior.SelectRows)

        # No default frame — drawn manually in paintEvent.
        self.setFrameShape(QTreeView.Shape.NoFrame)

    def _sync_viewport_palette(self) -> None:
        """Apply the variant background colour to the viewport palette."""
        style = get_ayon_style()
        tv_style = style.model.get_style("QTreeView", self._variant_str)
        bg = QColor(tv_style.get("background-color", "#252a31"))
        p = self.viewport().palette()
        p.setColor(QPalette.ColorRole.Base, bg)
        p.setColor(QPalette.ColorRole.Window, bg)
        self.viewport().setPalette(p)

    def paintEvent(self, event: QPaintEvent) -> None:
        """Draw the outer container background before the items.

        Args:
            event: The paint event.
        """
        painter = QPainter(self.viewport())
        painter.setRenderHint(QPainter.RenderHint.Antialiasing, False)
        style = get_ayon_style()
        tv_style = style.model.get_style("QTreeView", self._variant_str)
        bg = QColor(tv_style.get("background-color", "#252a31"))
        painter.fillRect(self.viewport().rect(), bg)
        painter.end()

        # Let QTreeView draw its items on top.
        super().paintEvent(event)

    def eventFilter(self, obj, event):
        if obj is self.viewport():
            if event.type() == QEvent.Type.MouseMove:
                idx = self.indexAt(event.pos())
                key = (idx.row(), idx.parent()) if idx.isValid() else None
                if key != self._hovered_row_key:
                    self._hovered_row_key = key
                    self.viewport().update()
            elif event.type() == QEvent.Type.Leave:
                if self._hovered_row_key is not None:
                    self._hovered_row_key = None
                    self.viewport().update()
        return super().eventFilter(obj, event)

    def drawBranches(self, painter, rect, index):
        """Draw branch indicators with AYONStyle directly.

        Bypasses ``self.style()`` because, when an application-level QSS is
        active, Qt wraps the widget's style in a ``QStyleSheetStyle`` proxy
        which would otherwise intercept ``PE_IndicatorBranch`` and apply
        QSS ``QTreeView::branch`` rules on top of (or instead of) ours.
        """
        style = get_ayon_style()  # the raw AYONStyle, never wrapped

        opt = QStyleOption()
        opt.rect = rect
        opt.palette = self.palette()
        state = QStyle.StateFlag.State_Item
        if self.model() is not None and self.model().hasChildren(index):
            state |= QStyle.StateFlag.State_Children
        if self.isExpanded(index):
            state |= QStyle.StateFlag.State_Open
        if self.selectionModel().isSelected(index):
            state |= QStyle.StateFlag.State_Selected
        if self.isEnabled():
            state |= QStyle.StateFlag.State_Enabled

        # Row-level hover: is the cursor on the same row as `index`?
        hovered_index = self.indexAt(
            self.viewport().mapFromGlobal(QCursor.pos())
        )
        if (
            hovered_index.isValid()
            and hovered_index.row() == index.row()
            and hovered_index.parent() == index.parent()
        ):
            state |= QStyle.StateFlag.State_MouseOver

        opt.state = state

        # Call our drawer directly, not through self.style().
        style.drawers[
            enum_to_str(
                QStyle.PrimitiveElement,
                QStyle.PrimitiveElement.PE_IndicatorBranch,
                "QTreeView",
            )
        ](opt, painter, self)

    def mouseDoubleClickEvent(self, event: QMouseEvent) -> None:
        """Emit double_clicked signal on double-click."""
        self.double_clicked.emit(event)
        super().mouseDoubleClickEvent(event)

    def mousePressEvent(self, event) -> None:
        """Deselect all items when clicking in an empty area."""
        index = self.indexAt(event.pos())
        if not index.isValid():
            self.clearSelection()
            self.setCurrentIndex(self.rootIndex())
            return
        super().mousePressEvent(event)

    def selectionChanged(
        self,
        selected: QItemSelection,
        deselected: QItemSelection,
    ) -> None:
        """Override to emit a public signal on selection change.

        Args:
            selected: Newly selected items.
            deselected: Newly deselected items.
        """
        super().selectionChanged(selected, deselected)
        self.selection_changed.emit(selected, deselected)

drawBranches(painter, rect, index)

Draw branch indicators with AYONStyle directly.

Bypasses self.style() because, when an application-level QSS is active, Qt wraps the widget's style in a QStyleSheetStyle proxy which would otherwise intercept PE_IndicatorBranch and apply QSS QTreeView::branch rules on top of (or instead of) ours.

Source code in client/ayon_ui_qt/components/tree_view.py
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
def drawBranches(self, painter, rect, index):
    """Draw branch indicators with AYONStyle directly.

    Bypasses ``self.style()`` because, when an application-level QSS is
    active, Qt wraps the widget's style in a ``QStyleSheetStyle`` proxy
    which would otherwise intercept ``PE_IndicatorBranch`` and apply
    QSS ``QTreeView::branch`` rules on top of (or instead of) ours.
    """
    style = get_ayon_style()  # the raw AYONStyle, never wrapped

    opt = QStyleOption()
    opt.rect = rect
    opt.palette = self.palette()
    state = QStyle.StateFlag.State_Item
    if self.model() is not None and self.model().hasChildren(index):
        state |= QStyle.StateFlag.State_Children
    if self.isExpanded(index):
        state |= QStyle.StateFlag.State_Open
    if self.selectionModel().isSelected(index):
        state |= QStyle.StateFlag.State_Selected
    if self.isEnabled():
        state |= QStyle.StateFlag.State_Enabled

    # Row-level hover: is the cursor on the same row as `index`?
    hovered_index = self.indexAt(
        self.viewport().mapFromGlobal(QCursor.pos())
    )
    if (
        hovered_index.isValid()
        and hovered_index.row() == index.row()
        and hovered_index.parent() == index.parent()
    ):
        state |= QStyle.StateFlag.State_MouseOver

    opt.state = state

    # Call our drawer directly, not through self.style().
    style.drawers[
        enum_to_str(
            QStyle.PrimitiveElement,
            QStyle.PrimitiveElement.PE_IndicatorBranch,
            "QTreeView",
        )
    ](opt, painter, self)

mouseDoubleClickEvent(event)

Emit double_clicked signal on double-click.

Source code in client/ayon_ui_qt/components/tree_view.py
202
203
204
205
def mouseDoubleClickEvent(self, event: QMouseEvent) -> None:
    """Emit double_clicked signal on double-click."""
    self.double_clicked.emit(event)
    super().mouseDoubleClickEvent(event)

mousePressEvent(event)

Deselect all items when clicking in an empty area.

Source code in client/ayon_ui_qt/components/tree_view.py
207
208
209
210
211
212
213
214
def mousePressEvent(self, event) -> None:
    """Deselect all items when clicking in an empty area."""
    index = self.indexAt(event.pos())
    if not index.isValid():
        self.clearSelection()
        self.setCurrentIndex(self.rootIndex())
        return
    super().mousePressEvent(event)

paintEvent(event)

Draw the outer container background before the items.

Parameters:

Name Type Description Default
event QPaintEvent

The paint event.

required
Source code in client/ayon_ui_qt/components/tree_view.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
def paintEvent(self, event: QPaintEvent) -> None:
    """Draw the outer container background before the items.

    Args:
        event: The paint event.
    """
    painter = QPainter(self.viewport())
    painter.setRenderHint(QPainter.RenderHint.Antialiasing, False)
    style = get_ayon_style()
    tv_style = style.model.get_style("QTreeView", self._variant_str)
    bg = QColor(tv_style.get("background-color", "#252a31"))
    painter.fillRect(self.viewport().rect(), bg)
    painter.end()

    # Let QTreeView draw its items on top.
    super().paintEvent(event)

selectionChanged(selected, deselected)

Override to emit a public signal on selection change.

Parameters:

Name Type Description Default
selected QItemSelection

Newly selected items.

required
deselected QItemSelection

Newly deselected items.

required
Source code in client/ayon_ui_qt/components/tree_view.py
216
217
218
219
220
221
222
223
224
225
226
227
228
def selectionChanged(
    self,
    selected: QItemSelection,
    deselected: QItemSelection,
) -> None:
    """Override to emit a public signal on selection change.

    Args:
        selected: Newly selected items.
        deselected: Newly deselected items.
    """
    super().selectionChanged(selected, deselected)
    self.selection_changed.emit(selected, deselected)

TreeViewItemDelegate

Bases: StyleMixin, QStyledItemDelegate

Item delegate for AYTreeView that paints directly, bypassing QSS.

Reads style data from the QTreeView style entry to draw item backgrounds (hover, selected) and text/icons. The paint() method uses raw QPainter calls so that a parent-level QStyleSheet cannot intercept and override the colours.

Parameters:

Name Type Description Default
parent QWidget | None

The parent widget (expected to be an AYTreeView instance).

None
style_model StyleData | None

StyleData instance providing colour/dimension data.

None
variant str

The variant string used to look up the correct style.

'default'
Source code in client/ayon_ui_qt/components/tree_view.py
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
class TreeViewItemDelegate(StyleMixin, QStyledItemDelegate):
    """Item delegate for AYTreeView that paints directly, bypassing QSS.

    Reads style data from the QTreeView style entry to draw item
    backgrounds (hover, selected) and text/icons.  The paint() method
    uses raw QPainter calls so that a parent-level QStyleSheet cannot
    intercept and override the colours.

    Args:
        parent: The parent widget (expected to be an AYTreeView instance).
        style_model: StyleData instance providing colour/dimension data.
        variant: The variant string used to look up the correct style.
    """

    def __init__(
        self,
        parent: QWidget | None = None,
        style_model: StyleData | None = None,
        variant: str = "default",
    ) -> None:
        super().__init__(parent)
        self._style_model = style_model
        self._variant_str = variant
        self._icon_cache: dict[str, QIcon] = {}

    def _tv_styles(self) -> dict[str, dict]:
        """Return *base*, *hover* and *selected* style dicts at once."""
        if self._style_model is None:
            return {"base": {}, "hover": {}, "selected": {}}
        return self._style_model.get_styles(
            "QTreeView",
            self._variant_str,
            ["base", "hover", "selected"],
        )

    def initStyleOption(
        self,
        option: QStyleOptionViewItem,
        index: QModelIndex | QPersistentModelIndex,
    ) -> None:
        """Initialize the style option with the default implementation, then
        override any properties needed for our custom painting.

        Args:
            option: The style option to initialize.
            index: The model index of the item.
        """
        super().initStyleOption(option, index)
        option.font = self.font()
        option.fontMetrics = self.fontMetrics()

    def sizeHint(
        self,
        option: QStyleOptionViewItem,
        index: QModelIndex | QPersistentModelIndex,
    ) -> QSize:
        """Return a fixed row height from the style data.

        Args:
            option: The style option for the item.
            index: The model index of the item.

        Returns:
            The size hint for the item.
        """
        if self._style_model:
            style = self._style_model.get_style("QTreeView", self._variant_str)
            h = int(style.get("item-height", 28))
        else:
            h = 28
        return QSize(option.rect.width(), h)

    def paint(
        self,
        painter: QPainter,
        option: QStyleOptionViewItem,
        index: QModelIndex | QPersistentModelIndex,
    ) -> None:
        """Paint a tree-view item directly, bypassing QStyle completely.

        Args:
            painter: The QPainter to use.
            option: The style option for the item.
            index: The model index of the item.
        """
        painter.save()
        painter.setRenderHint(QPainter.RenderHint.Antialiasing)

        opt = QStyleOptionViewItem(option)
        self.initStyleOption(opt, index)

        state = opt.state
        is_selected = bool(state & QStyle.StateFlag.State_Selected)
        is_hovered = bool(state & QStyle.StateFlag.State_MouseOver)

        styles = self._tv_styles()
        base_style = styles["base"]
        hover_style = styles["hover"]
        selected_style = styles["selected"]

        item_padding = base_style.get("item-padding", [4, 8])
        icon_text_spacing = int(base_style.get("icon-text-spacing", 6))

        # --- background ------------------------------------------------
        if is_selected:
            bg_color = QColor(
                selected_style.get(
                    "background-color",
                    base_style.get("background-color", "transparent"),
                )
            )
        elif is_hovered:
            bg_color = QColor(
                hover_style.get(
                    "background-color",
                    base_style.get("background-color", "transparent"),
                )
            )
        else:
            bg_color = QColor(
                base_style.get("background-color", "transparent")
            )

        painter.setBrush(QBrush(bg_color))
        painter.setPen(Qt.PenStyle.NoPen)
        painter.drawRect(opt.rect)

        # --- text colour -----------------------------------------------
        if is_selected:
            text_color = QColor(
                selected_style.get(
                    "color",
                    base_style.get("color", "#f4f5f5"),
                )
            )
        else:
            text_color = QColor(base_style.get("color", "#f4f5f5"))

        # disabled dimming
        if not (state & QStyle.StateFlag.State_Enabled):
            text_color.setAlpha(
                int(
                    text_color.alpha()
                    * base_style.get("disabled-opacity", 0.5)
                )
            )

        # --- icon + text layout ----------------------------------------
        content_rect = QRect(opt.rect).adjusted(
            item_padding[1],
            item_padding[0],
            -item_padding[1],
            -item_padding[0],
        )
        content_left = content_rect.left()

        if not opt.icon.isNull():
            icon_size = opt.decorationSize
            icon_rect = QRect(
                content_left,
                opt.rect.center().y() - icon_size.height() // 2,
                icon_size.width(),
                icon_size.height(),
            )
            mode = (
                QIcon.Mode.Normal
                if state & QStyle.StateFlag.State_Enabled
                else QIcon.Mode.Disabled
            )
            opt.icon.paint(
                painter,
                icon_rect,
                Qt.AlignmentFlag.AlignCenter,
                mode,
            )
            content_left = icon_rect.right() + icon_text_spacing

        if opt.text:
            text_rect = QRect(opt.rect)
            text_rect.setLeft(content_left)
            text_rect.setRight(content_rect.right())
            painter.setPen(text_color)
            painter.setFont(opt.font)
            painter.drawText(
                text_rect,
                Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignLeft,
                opt.text,
            )

        painter.restore()

initStyleOption(option, index)

Initialize the style option with the default implementation, then override any properties needed for our custom painting.

Parameters:

Name Type Description Default
option QStyleOptionViewItem

The style option to initialize.

required
index QModelIndex | QPersistentModelIndex

The model index of the item.

required
Source code in client/ayon_ui_qt/components/tree_view.py
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
def initStyleOption(
    self,
    option: QStyleOptionViewItem,
    index: QModelIndex | QPersistentModelIndex,
) -> None:
    """Initialize the style option with the default implementation, then
    override any properties needed for our custom painting.

    Args:
        option: The style option to initialize.
        index: The model index of the item.
    """
    super().initStyleOption(option, index)
    option.font = self.font()
    option.fontMetrics = self.fontMetrics()

paint(painter, option, index)

Paint a tree-view item directly, bypassing QStyle completely.

Parameters:

Name Type Description Default
painter QPainter

The QPainter to use.

required
option QStyleOptionViewItem

The style option for the item.

required
index QModelIndex | QPersistentModelIndex

The model index of the item.

required
Source code in client/ayon_ui_qt/components/tree_view.py
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
def paint(
    self,
    painter: QPainter,
    option: QStyleOptionViewItem,
    index: QModelIndex | QPersistentModelIndex,
) -> None:
    """Paint a tree-view item directly, bypassing QStyle completely.

    Args:
        painter: The QPainter to use.
        option: The style option for the item.
        index: The model index of the item.
    """
    painter.save()
    painter.setRenderHint(QPainter.RenderHint.Antialiasing)

    opt = QStyleOptionViewItem(option)
    self.initStyleOption(opt, index)

    state = opt.state
    is_selected = bool(state & QStyle.StateFlag.State_Selected)
    is_hovered = bool(state & QStyle.StateFlag.State_MouseOver)

    styles = self._tv_styles()
    base_style = styles["base"]
    hover_style = styles["hover"]
    selected_style = styles["selected"]

    item_padding = base_style.get("item-padding", [4, 8])
    icon_text_spacing = int(base_style.get("icon-text-spacing", 6))

    # --- background ------------------------------------------------
    if is_selected:
        bg_color = QColor(
            selected_style.get(
                "background-color",
                base_style.get("background-color", "transparent"),
            )
        )
    elif is_hovered:
        bg_color = QColor(
            hover_style.get(
                "background-color",
                base_style.get("background-color", "transparent"),
            )
        )
    else:
        bg_color = QColor(
            base_style.get("background-color", "transparent")
        )

    painter.setBrush(QBrush(bg_color))
    painter.setPen(Qt.PenStyle.NoPen)
    painter.drawRect(opt.rect)

    # --- text colour -----------------------------------------------
    if is_selected:
        text_color = QColor(
            selected_style.get(
                "color",
                base_style.get("color", "#f4f5f5"),
            )
        )
    else:
        text_color = QColor(base_style.get("color", "#f4f5f5"))

    # disabled dimming
    if not (state & QStyle.StateFlag.State_Enabled):
        text_color.setAlpha(
            int(
                text_color.alpha()
                * base_style.get("disabled-opacity", 0.5)
            )
        )

    # --- icon + text layout ----------------------------------------
    content_rect = QRect(opt.rect).adjusted(
        item_padding[1],
        item_padding[0],
        -item_padding[1],
        -item_padding[0],
    )
    content_left = content_rect.left()

    if not opt.icon.isNull():
        icon_size = opt.decorationSize
        icon_rect = QRect(
            content_left,
            opt.rect.center().y() - icon_size.height() // 2,
            icon_size.width(),
            icon_size.height(),
        )
        mode = (
            QIcon.Mode.Normal
            if state & QStyle.StateFlag.State_Enabled
            else QIcon.Mode.Disabled
        )
        opt.icon.paint(
            painter,
            icon_rect,
            Qt.AlignmentFlag.AlignCenter,
            mode,
        )
        content_left = icon_rect.right() + icon_text_spacing

    if opt.text:
        text_rect = QRect(opt.rect)
        text_rect.setLeft(content_left)
        text_rect.setRight(content_rect.right())
        painter.setPen(text_color)
        painter.setFont(opt.font)
        painter.drawText(
            text_rect,
            Qt.AlignmentFlag.AlignVCenter | Qt.AlignmentFlag.AlignLeft,
            opt.text,
        )

    painter.restore()

sizeHint(option, index)

Return a fixed row height from the style data.

Parameters:

Name Type Description Default
option QStyleOptionViewItem

The style option for the item.

required
index QModelIndex | QPersistentModelIndex

The model index of the item.

required

Returns:

Type Description
QSize

The size hint for the item.

Source code in client/ayon_ui_qt/components/tree_view.py
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
def sizeHint(
    self,
    option: QStyleOptionViewItem,
    index: QModelIndex | QPersistentModelIndex,
) -> QSize:
    """Return a fixed row height from the style data.

    Args:
        option: The style option for the item.
        index: The model index of the item.

    Returns:
        The size hint for the item.
    """
    if self._style_model:
        style = self._style_model.get_style("QTreeView", self._variant_str)
        h = int(style.get("item-height", 28))
    else:
        h = 28
    return QSize(option.rect.width(), h)