Skip to content

ScrollAreaTocWidget

Qt Base Class: QTreeView

Signature: QTreeView(self, parent: Optional[PySide6.QtWidgets.QWidget] = None) -> None

Base classes

Name Children Inherits
TreeView
prettyqt.widgets.treeview

⋔ Inheritance diagram

graph TD
  1473367137008["custom_widgets.ScrollAreaTocWidget"]
  1473296360320["widgets.TreeView"]
  1473296343728["widgets.TreeViewMixin"]
  1473293692144["widgets.AbstractItemViewMixin"]
  1473293679456["widgets.AbstractScrollAreaMixin"]
  1473293662864["widgets.FrameMixin"]
  1473293688240["widgets.WidgetMixin"]
  1473299815024["core.ObjectMixin"]
  140713234304496["builtins.object"]
  1473245548480["gui.PaintDeviceMixin"]
  1473241448160["QtWidgets.QTreeView"]
  1473241438400["QtWidgets.QAbstractItemView"]
  1473290616416["QtWidgets.QAbstractScrollArea"]
  1473290626176["QtWidgets.QFrame"]
  1473290849680["QtWidgets.QWidget"]
  1473288842240["QtCore.QObject"]
  1473291690208["Shiboken.Object"]
  1473300082368["QtGui.QPaintDevice"]
  1473296360320 --> 1473367137008
  1473296343728 --> 1473296360320
  1473293692144 --> 1473296343728
  1473293679456 --> 1473293692144
  1473293662864 --> 1473293679456
  1473293688240 --> 1473293662864
  1473299815024 --> 1473293688240
  140713234304496 --> 1473299815024
  1473245548480 --> 1473293688240
  140713234304496 --> 1473245548480
  1473241448160 --> 1473296360320
  1473241438400 --> 1473241448160
  1473290616416 --> 1473241438400
  1473290626176 --> 1473290616416
  1473290849680 --> 1473290626176
  1473288842240 --> 1473290849680
  1473291690208 --> 1473288842240
  140713234304496 --> 1473291690208
  1473300082368 --> 1473290849680
  1473291690208 --> 1473300082368

🛈 DocStrings

Bases: TreeView

Source code in prettyqt\custom_widgets\scrollareatocwidget.py
class ScrollAreaTocWidget(widgets.TreeView):
    section_changed = core.Signal()

    ScrollMode = ScrollMode
    core.Enum(ScrollMode)

    def __init__(
        self,
        scrollarea: widgets.QScrollArea,
        orientation: constants.Orientation
        | constants.OrientationStr = constants.VERTICAL,
        widget_class: type = widgets.QWidget,
        **kwargs,
    ) -> None:
        """A TreeView which can show a Table-of-contents list based on a given ScrollArea.

        With default settings, it will scan the widgets contained in the scrollArea for
         a windowTitle.
        These widgets will be shown in the TreeView, and the TreeView selection will be
        synced to what is currently visible in the ScrollArea.
        This basically emulates the behaviour of many websites
        (like the [Qt Website][https://doc.qt.io/qt-6/supported-platforms.html])
        or from VS code settings.

        You can set up the Toc Tree by passing a ScrollArea instance:

        ```py
        widget = widgets.Widget()
        layout = widget.set_layout("horizontal")
        scrollarea = widgets.ScrollArea()
        # add some widgets to the ScrollArea here
        # ...
        toc = ScrollAreaTocWidget(scrollarea)
        layout.add(toc)
        layout.add(scrollarea)
        ```

        The Toc tree can be configured to use other properties for populating
        than windowTitle.
        You can also set a widget class filter if you only want a specific widget
        class to be shown.

        The widget supports 3 different scroll modes:

        1) Single: Only one row in the Tree will be highlighted. (the one which equals
        the topmost one from the scrollArea)
        2) Multi: All rows which equal to visible widgets in the ScrollArea will be shown.
        3) HeadersOnly: like Single, but only highlights top-level widgets from the
                        ScrollArea.

        There are also two different expand modes to choose from:

        1) ExpandAll: All subsections are always expanded.
        2) on_focus: Only the section containing the focused item is expanded.

        This widget is used by [ConfigWidget](ConfigWidget.md).
        """
        # TODO: not sure if parent should always equal scrollarea..."""
        self._WidgetClass = widget_class
        self._scroll_mode = ScrollMode.Single
        self._always_expanded = False
        self._last_visible = None
        self.scrollarea = scrollarea
        super().__init__(scrollarea, **kwargs)
        self._orientation = constants.ORIENTATION.get_enum_value(orientation)
        self.setFixedWidth(200)
        self.h_header.hide()
        self.h_header.setStretchLastSection(True)
        self.setAlternatingRowColors(False)
        self.setRootIsDecorated(False)
        # self.setStyleSheet(
        #     """::item:hover {background: transparent; border-color:transparent}
        #     ::item:selected { border-color:transparent;
        #     border-style:outset; border-width:2px; color:black; }"""
        # )
        if self._orientation == constants.VERTICAL:
            scrollarea.v_scrollbar.valueChanged.connect(self._on_scroll)
        else:
            scrollarea.h_scrollbar.valueChanged.connect(self._on_scroll)
        self.set_widget(scrollarea)

    def _get_map(self):
        maps = super()._get_map()
        maps |= {"scrollMode": SCROLL_MODE}
        return maps

    def showEvent(self, event):
        super().showEvent(event)
        self._on_scroll()

    # @classmethod
    # def setup_example(cls):
    #     scrollarea = widgets.ScrollArea()
    #     w = widgets.Widget()
    #     scrollarea.set_widget(w)
    #     return cls(scrollarea)

    def set_widget(self, widget: widgets.QScrollArea):
        """Set the ScrollArea widget to follow."""
        if widget.widget() is None:
            raise RuntimeError("No widget set on ScrollArea.")
        self.scrollarea = widget
        model = ScrollAreaTocModel(
            widget.widget(),
            show_root=True,
            parent=self.scrollarea,
            widget_class=self._WidgetClass,
        )
        self.set_model(model)
        self.proxy = self.proxifier.set_sort_filter_proxy(
            recursive_filtering_enabled=True
        )
        self.proxy.set_filter_case_sensitive(False)
        self.show_root(False)
        widget.widget().installEventFilter(self)
        self.selectionModel().currentRowChanged.connect(self._on_current_change)
        self.selectionModel().selectionChanged.connect(self._on_selection_change)
        # if self._always_expanded:
        self.expandAll()

    def _on_current_change(self, new, old):
        if self.model() is None:
            return
        is_vertical = self._orientation == constants.VERTICAL
        area = self.scrollarea
        scrollbar = area.v_scrollbar if is_vertical else area.h_scrollbar
        with self.signal_blocked(scrollbar.valueChanged, self._on_scroll):
            widget = self.model().data(new, role=constants.USER_ROLE)
            area.scroll_to_bottom()
            area.ensureWidgetVisible(widget, 10, 10)

    def _on_selection_change(self, new, old):
        if self.model() is None:
            return
        widgets = [i.data(constants.USER_ROLE) for i in self.selected_indexes()]
        self.get_model(skip_proxies=True).set_highlighted_widgets(widgets)

    def _on_scroll(self):
        model: ScrollAreaTocModel | None = self.model()
        if model is None:
            return
        visible_widgets = self.scrollarea.get_visible_widgets(typ=self._WidgetClass)
        if not visible_widgets or visible_widgets == self._last_visible:
            return
        self._last_visible = visible_widgets
        sig = self.selectionModel().currentRowChanged
        with self.signal_blocked(sig, self._on_current_change):
            self.select_index(None)
            if not self._always_expanded:
                self.collapseAll()
            match self.get_scroll_mode():
                case "multi":
                    indexes = model.search_tree(visible_widgets, constants.USER_ROLE)
                    for index in indexes:
                        children = model.get_child_indexes(index)
                        # only select if all children selected.
                        # if all(c in indexes for c in children):

                        # highlight when no children or when first child is visible.
                        if not children or children[0] in indexes:
                            self.select_index(index, clear=False)
                        self.set_expanded(indexes)
                        self.scroll_to(indexes[0])
                        self.scroll_to(indexes[-1])
                case "headers_only":
                    if indexes := model.search_tree(
                        visible_widgets,
                        role=constants.USER_ROLE,
                        max_results=1,
                    ):
                        self.set_current_index(indexes[0])
                        self.scroll_to(indexes[0])
                case "single":
                    if indexes := model.search_tree(visible_widgets, constants.USER_ROLE):
                        viewport = self.scrollarea.viewport()
                        # sort indexes by closest distance to top
                        indexes.sort(
                            key=lambda x: abs(
                                x.data(constants.USER_ROLE)
                                .map_to(viewport, x.data(constants.USER_ROLE).rect())
                                .top()
                            ),
                        )
                        self.collapseAll()
                        self.model().fetchMore(indexes[0])
                        self.set_current_index(indexes[0])
                        self.scroll_to(indexes[0])
                case _:
                    raise ValueError(self._scroll_mode)
        # model.set_highlighted_indexes(indexes)

    def wheelEvent(self, e):
        self.scrollarea.wheelEvent(e)

    def eventFilter(self, source: core.QObject, event: core.QEvent) -> bool:
        match event.type():
            case core.Event.Type.ChildAdded:
                self._on_scroll()
        return False

    def _scrollMode(self) -> ScrollMode:
        return self._scroll_mode

    def get_scroll_mode(self) -> ScrollModeStr:
        return SCROLL_MODE.inverse[self._scrollMode()]

    def set_scroll_mode(self, mode: ScrollMode | ScrollModeStr):
        self._scroll_mode = SCROLL_MODE.get_enum_value(mode)

    def is_always_expanded(self) -> bool:
        return self._always_expanded

    def set_always_expanded(self, always_expanded: bool):
        self._always_expanded = always_expanded

    scrollMode = core.Property(
        enum.Enum,
        _scrollMode,
        set_scroll_mode,
        doc="Scrolling mode",
    )
    always_expanded = core.Property(
        bool,
        is_always_expanded,
        set_always_expanded,
        doc="Whether the tree is always expanded.",
    )

__init__(scrollarea: widgets.QScrollArea, orientation: constants.Orientation | constants.OrientationStr = constants.VERTICAL, widget_class: type = widgets.QWidget, **kwargs: type) -> None

A TreeView which can show a Table-of-contents list based on a given ScrollArea.

With default settings, it will scan the widgets contained in the scrollArea for a windowTitle. These widgets will be shown in the TreeView, and the TreeView selection will be synced to what is currently visible in the ScrollArea. This basically emulates the behaviour of many websites (like the [Qt Website][https://doc.qt.io/qt-6/supported-platforms.html]) or from VS code settings.

You can set up the Toc Tree by passing a ScrollArea instance:

widget = widgets.Widget()
layout = widget.set_layout("horizontal")
scrollarea = widgets.ScrollArea()
# add some widgets to the ScrollArea here
# ...
toc = ScrollAreaTocWidget(scrollarea)
layout.add(toc)
layout.add(scrollarea)

The Toc tree can be configured to use other properties for populating than windowTitle. You can also set a widget class filter if you only want a specific widget class to be shown.

The widget supports 3 different scroll modes:

1) Single: Only one row in the Tree will be highlighted. (the one which equals the topmost one from the scrollArea) 2) Multi: All rows which equal to visible widgets in the ScrollArea will be shown. 3) HeadersOnly: like Single, but only highlights top-level widgets from the ScrollArea.

There are also two different expand modes to choose from:

1) ExpandAll: All subsections are always expanded. 2) on_focus: Only the section containing the focused item is expanded.

This widget is used by ConfigWidget.

Source code in prettyqt\custom_widgets\scrollareatocwidget.py
def __init__(
    self,
    scrollarea: widgets.QScrollArea,
    orientation: constants.Orientation
    | constants.OrientationStr = constants.VERTICAL,
    widget_class: type = widgets.QWidget,
    **kwargs,
) -> None:
    """A TreeView which can show a Table-of-contents list based on a given ScrollArea.

    With default settings, it will scan the widgets contained in the scrollArea for
     a windowTitle.
    These widgets will be shown in the TreeView, and the TreeView selection will be
    synced to what is currently visible in the ScrollArea.
    This basically emulates the behaviour of many websites
    (like the [Qt Website][https://doc.qt.io/qt-6/supported-platforms.html])
    or from VS code settings.

    You can set up the Toc Tree by passing a ScrollArea instance:

    ```py
    widget = widgets.Widget()
    layout = widget.set_layout("horizontal")
    scrollarea = widgets.ScrollArea()
    # add some widgets to the ScrollArea here
    # ...
    toc = ScrollAreaTocWidget(scrollarea)
    layout.add(toc)
    layout.add(scrollarea)
    ```

    The Toc tree can be configured to use other properties for populating
    than windowTitle.
    You can also set a widget class filter if you only want a specific widget
    class to be shown.

    The widget supports 3 different scroll modes:

    1) Single: Only one row in the Tree will be highlighted. (the one which equals
    the topmost one from the scrollArea)
    2) Multi: All rows which equal to visible widgets in the ScrollArea will be shown.
    3) HeadersOnly: like Single, but only highlights top-level widgets from the
                    ScrollArea.

    There are also two different expand modes to choose from:

    1) ExpandAll: All subsections are always expanded.
    2) on_focus: Only the section containing the focused item is expanded.

    This widget is used by [ConfigWidget](ConfigWidget.md).
    """
    # TODO: not sure if parent should always equal scrollarea..."""
    self._WidgetClass = widget_class
    self._scroll_mode = ScrollMode.Single
    self._always_expanded = False
    self._last_visible = None
    self.scrollarea = scrollarea
    super().__init__(scrollarea, **kwargs)
    self._orientation = constants.ORIENTATION.get_enum_value(orientation)
    self.setFixedWidth(200)
    self.h_header.hide()
    self.h_header.setStretchLastSection(True)
    self.setAlternatingRowColors(False)
    self.setRootIsDecorated(False)
    # self.setStyleSheet(
    #     """::item:hover {background: transparent; border-color:transparent}
    #     ::item:selected { border-color:transparent;
    #     border-style:outset; border-width:2px; color:black; }"""
    # )
    if self._orientation == constants.VERTICAL:
        scrollarea.v_scrollbar.valueChanged.connect(self._on_scroll)
    else:
        scrollarea.h_scrollbar.valueChanged.connect(self._on_scroll)
    self.set_widget(scrollarea)

set_widget(widget: widgets.QScrollArea)

Set the ScrollArea widget to follow.

Source code in prettyqt\custom_widgets\scrollareatocwidget.py
def set_widget(self, widget: widgets.QScrollArea):
    """Set the ScrollArea widget to follow."""
    if widget.widget() is None:
        raise RuntimeError("No widget set on ScrollArea.")
    self.scrollarea = widget
    model = ScrollAreaTocModel(
        widget.widget(),
        show_root=True,
        parent=self.scrollarea,
        widget_class=self._WidgetClass,
    )
    self.set_model(model)
    self.proxy = self.proxifier.set_sort_filter_proxy(
        recursive_filtering_enabled=True
    )
    self.proxy.set_filter_case_sensitive(False)
    self.show_root(False)
    widget.widget().installEventFilter(self)
    self.selectionModel().currentRowChanged.connect(self._on_current_change)
    self.selectionModel().selectionChanged.connect(self._on_selection_change)
    # if self._always_expanded:
    self.expandAll()

⌗ Property table

Qt Property Type Doc
objectName QString
modal bool
windowModality Qt::WindowModality
enabled bool
geometry QRect
frameGeometry QRect
normalGeometry QRect
x int
y int
pos QPoint
frameSize QSize
size QSize
width int
height int
rect QRect
childrenRect QRect
childrenRegion QRegion
sizePolicy QSizePolicy
minimumSize QSize
maximumSize QSize
minimumWidth int
minimumHeight int
maximumWidth int
maximumHeight int
sizeIncrement QSize
baseSize QSize
palette QPalette
font QFont
cursor QCursor
mouseTracking bool
tabletTracking bool
isActiveWindow bool
focusPolicy Qt::FocusPolicy
focus bool
contextMenuPolicy Qt::ContextMenuPolicy
updatesEnabled bool
visible bool
minimized bool
maximized bool
fullScreen bool
sizeHint QSize
minimumSizeHint QSize
acceptDrops bool
windowTitle QString
windowIcon QIcon
windowIconText QString
windowOpacity double
windowModified bool
toolTip QString
toolTipDuration int
statusTip QString
whatsThis QString
accessibleName QString
accessibleDescription QString
layoutDirection Qt::LayoutDirection
autoFillBackground bool
styleSheet QString
locale QLocale
windowFilePath QString
inputMethodHints QFlags
frameShape QFrame::Shape
frameShadow QFrame::Shadow
lineWidth int
midLineWidth int
frameWidth int
frameRect QRect
verticalScrollBarPolicy Qt::ScrollBarPolicy
horizontalScrollBarPolicy Qt::ScrollBarPolicy
sizeAdjustPolicy QAbstractScrollArea::SizeAdjustPolicy
autoScroll bool
autoScrollMargin int
editTriggers QFlags
tabKeyNavigation bool
showDropIndicator bool
dragEnabled bool
dragDropOverwriteMode bool
dragDropMode QAbstractItemView::DragDropMode
defaultDropAction Qt::DropAction
alternatingRowColors bool
selectionMode QAbstractItemView::SelectionMode
selectionBehavior QAbstractItemView::SelectionBehavior
iconSize QSize
textElideMode Qt::TextElideMode
verticalScrollMode QAbstractItemView::ScrollMode
horizontalScrollMode QAbstractItemView::ScrollMode
autoExpandDelay int
indentation int
rootIsDecorated bool
uniformRowHeights bool
itemsExpandable bool
sortingEnabled bool
animated bool
allColumnsShowFocus bool
wordWrap bool
headerHidden bool
expandsOnDoubleClick bool
scrollMode PySide::PyObjectWrapper Scrolling mode
always_expanded bool Whether the tree is always expanded.