Skip to content
Haiku OSDeep Dive Published Updated 5 min readViews unavailable

Haiku BColumnListView: Multi-Column Trees and Private-API Risk

Inspect Haiku's private BColumnListView: field indices, hierarchical rows, column ordering, ownership, and compatibility limits before relying on it.

Haiku’s BColumnListView presents hierarchical rows across multiple columns, but the first engineering decision is not how to draw a cell: it is whether an application should depend on this class at all. In the current Haiku source tree, its header is under headers/private/interface/ColumnListView.h, not the public headers/os/interface API. The implementation is useful to study and may be available to in-tree or source-aware builds, but its location is a compatibility warning. Do not treat the class as a stable public SDK contract merely because its name begins with B and its methods resemble other Interface Kit classes.

If a product accepts that risk, isolate all use behind a small view/model adapter and verify each supported Haiku revision. For ordinary third-party applications that require a stable public API, prefer public controls or a maintained application-owned list/tree implementation unless the target SDK explicitly documents this class as supported.

Columns have display order and logical field identity

BColumnListView is composed from three model types:

  • A BColumn describes a visible column, including its width, heading, alignment, drawing behavior, and comparison behavior.
  • A BField is the value rendered at one row/column intersection; concrete field classes provide values such as text or numbers.
  • A BRow stores fields and may own a child-row hierarchy.

When adding a column, the caller supplies a logical field index. That index connects a column to the corresponding slot in every row. It is not the same thing as the column’s current left-to-right position. If the user reorders columns, row data should remain keyed to stable logical indices; do not rebuild field mappings from the visible column order.

In source-aware code, define stable indices once and use them for both column registration and row population. The following is a schematic example; the concrete BStringField and column subclasses must come from the exact Haiku source/SDK revision being targeted:

enum ColumnField {
    kNameField = 0,
    kVersionField = 1
};

// Registration and row population must use the same logical indices.
list->AddColumn(new NameColumn("Name"), kNameField);
list->AddColumn(new VersionColumn("Version"), kVersionField);

BRow* row = new BRow(20.0f);
row->SetField(new BStringField("Package A"), kNameField);
row->SetField(new BStringField("2.4"), kVersionField);
list->AddRow(row);

The example is not a public-API promise. It illustrates the current private source model: the header describes each BRow as holding fields associated by logical index, and the implementation stores the index on BColumn separately from visual placement.

Hierarchy, sorting, and selection are model operations

Rows can be inserted at the root or beneath a parent row. That makes the control a tree as well as a table: a package row may contain dependency rows, a file row may contain child entries, or a settings row may contain grouped details. Keep the application’s authoritative model separate from the visual BRow objects. The view’s expanded state and visible-row order are presentation state, not durable identity for the underlying record.

If sorting is enabled, a column’s comparison function determines order for the associated field. Use typed comparisons for numeric or version data instead of comparing formatted strings: lexicographic comparison puts values such as 10 before 2. For multi-column sorting, define and test the priority order explicitly. When a field changes, update the row through the control’s documented source-level invalidation/update path rather than assuming that modifying a string automatically reorders or redraws the item.

Selection callbacks also cross the view/model boundary. Map a selected BRow back to a stable application identifier, validate that the record still exists, and then perform the action. Do not persist a pointer, visible row number, or column position as the record’s identity. A sort, collapse, insertion, or asynchronous model refresh can change those values without changing the selected domain object.

Ownership details require version-specific review

The private header and implementation expose several ownership-sensitive behaviors. The current BRow::SetField() implementation deletes an existing field in that slot before installing the replacement, and the BRow destructor deletes its stored fields. Do not also own those field pointers in an independent smart pointer that will delete them a second time.

The current header explicitly says that RemoveRow() does not delete the row or its children. Removing a row from the visible structure is therefore not the same as destroying the application’s model. The current implementation’s Clear() path recursively deletes attached rows, while a removed row may still have child state to reclaim. Treat removal as a detach operation, keep explicit ownership records, and test recursive cleanup with the exact source version. Never assume that clearing a list and removing one subtree have the same destructor effects.

Columns have a separate lifetime contract: the current BColumnListView destructor deletes columns still registered with the view. RemoveColumn() detaches a column in the current implementation, so code that removes it must decide who then owns and deletes it. These details are particularly important because this is a private interface; implementation behavior can change without the compatibility guarantees expected of public kits.

Isolate the dependency and test upgrades

If using the class in a controlled build, keep the private header include and concrete column/field adapters in one module. Add a small compile-and-run compatibility test for every supported Haiku revision that checks:

  1. columns retain the intended logical index after visual reordering;
  2. replacing a field releases exactly one previous field;
  3. root and child rows render after expand/collapse and sorting;
  4. selection resolves to stable application identifiers after reorder;
  5. removing a row does not trigger accidental double deletion or leak its descendants;
  6. clearing or destroying the view releases only the objects the tested implementation owns.

Run those tests on Haiku rather than assuming that another operating system’s SDK has identical layout, event timing, or ownership. For cross-platform UI code, put BColumnListView behind a platform adapter and maintain a public-API fallback. Recheck the header path and implementation after each Haiku update; a moved header, changed constructor, or altered removal contract should fail a compile test instead of breaking users at runtime.

BColumnListView can be an effective model for understanding Haiku’s column/tree UI, and its implementation documents concrete behaviors that help when debugging an existing application. Its private-header location is equally important information: use the current source as evidence for a particular build, not as a promise that future Haiku releases will preserve the same API.

Related:

Sources:

Comments