Skip to content

Design: Minimum Viable Table and Tree Columns #4041

@corranwebster

Description

@corranwebster

What is the problem or limitation you are having?

The logic for how Table and Tree widgets extract text, icons, etc. from a row is somewhat complex, and is scattered around and replicated in a number of places. The idea of this proposal is to gather as much of that logic as possible into one place, and to do so in a way which is backward compatible, but also gives a framework for extending to allow other approaches and UI elements in the future.

In previous issues and PRs, there has been discussion of the idea of a Column object, that would satisfy this, but they appear to be outdated.

Rel #1066, #1478, #3760 .

Describe the solution you'd like

The proposal is to replace the current lists of headings and accessors in the Table and Tree widgets with a list of Column objects.

The first step would be to simply have the Columns store the values of each heading and accessor and effectively be an implementation detail: the list of columns would be the source of truth for the headers and accessors, and the current headings and accessors properties would extract the values from the Column list dynamically. The columns would be responsible for converting headings to accessors, rather than have that logic in the Table and Tree, and the insert/remove column methods would be adapted to insert/remove Column objects, which would also allow simplification of those methods. This also unifies a lot of the heading/accessor handling into a common location, rather than duplicating in the Table and Tree

This would look something like this (maybe with heading and accessor as properties):

class AccessorColumn:
    heading: str
    accessor: str

    def __init__(
        self,
        heading: str | None = None,
        accessor: str | None = None,
    ):
        if accessor is None:
            if heading is not None:
                accessor = to_accessor(heading)
            else:
                raise ValueError(
                    "Cannot create a column without either headings or accessors"
                )
        self.heading = heading
        self.accessor = accessor

The second step would be to add value, text, icon (and possibly widget) methods to this class which take a row, and use the rules for getting text and icons out of a row, something like:

def value(self, row):
    return getattr(row, self.accessor, None)

def text(self, row):
    value = self.value(row)
    if isinstance(value, tuple):
        value = value[1]
    if value is not None:
        value = str(value)
    return value

def icon(self, row):
    value = self.value(row)
    if isinstance(value, tuple):
        value = value[0]
    elif hasattr(value, 'icon'):
        value = value.icon
    return value

With this in place the widget implementations can be adapted so that instead of implementing this logic internally, they instead ask the appropriate column object for the text or icon to display for a given row. Similarly, they can ask a column for its heading. The goal is to remove direct use of accessors in the implementations. The ListSource and TreeSource will still need them, but the widget implementations should consider accessors to be a data source implementation detail.

This refactoring then opens up the API for piecemeal improvements, such as:

  • extending the Column API to expose other information which might be needed to display the column (tool-tips, checkmarks, colors, fonts, etc.) or the ways the information might be extracted from values (eg. if the value is a mapping, look for text and icon keys).
  • deprecating passing headings and accessors and instead passing Column objects
  • making the Column's methods a protocol which can be implemented in other ways (eg. columns which compute values from the rows in various ways) or which are tied closely to a particular Source implementation.
  • adding the ability to flag the column cells editable and set the value of the accessor on the row.
  • proper embedding of widgets or their analogues on non-macOS platforms
  • and probably lots of other things besides

All of these improvements are hypothetical, and not part of this proposal. Rather this is intended to be a minimal, backward-compatible refactoring that creates the Column objects and unifies the accessor logic so future changes can be made in one place and alternatives can be explored.

Describe alternatives you've considered

The status-quo works, so this is not urgent or required. It's possible that it might be better to revive a PR like #1478 or to instead design a better end-state and build it directly and not incrementally.

A more minimal API without the intent to head in the direction of #1478 would be a common utility function which contains the logic for extracting values: give it a row and an accessor and it returns the widget, text and icon, possibly also handling missing values and warning for backends which don't support Widgets.

This is making Tables and Trees more "column-oriented" which can impede transposed views (eg. comparisons of objects where each column is an object, and each row is a feature or statistic of that object) or grid/spreadhseet views (eg. a display of a 2D array of numbers).

The Qt approach where instead of a bunch of distinct column objects, you have the source object grow methods for supplying text, icons, etc. may possibly be better.

Additional context

There is an implementation of these ideas in #4042.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew features, or improvements to existing features.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions