Haiku BTabView: Selection, Focus, and Pane Ownership
Manage Haiku BTabView panels with explicit selection and focus state, correct AddTab and RemoveTab ownership, stable settings, and resize tests.
BTabView is a container that presents one content view at a time behind a tab strip. Each BTab holds a label and a target BView. The control has two independent concepts that applications should not conflate: which tab is selected, and which tab currently has keyboard focus. A tab can be disabled; a tab label can differ from its content view’s name; and removing a tab returns an object whose destructor affects its content view.
The most important ownership fact comes from the current Haiku implementation: BTabView deletes its BTab objects in its destructor, and each BTab destructor removes and deletes its associated view. RemoveTab() removes a tab and returns the BTab* without deleting it. If the caller later deletes that returned tab, its content view is also deleted. Treat this as one ownership tree, not as unrelated objects that can be freed independently.
Add a tab with a stable label
AddTab(BView* target, BTab* tab = NULL) appends a tab and associates it with the target view. If no BTab is supplied, the control constructs one. If a tab is supplied, it calls SetView(target). That operation resets the tab label to the view’s name, so if the user-facing label should differ, call SetLabel() after setting the view.
Use labels that remain understandable when several tabs are adjacent. Avoid relying on a view’s internal name to be the right localized title. In Haiku, BTab::SetLabel() can change the label without renaming the target view; this differs from older BeOS behavior. The view name remains an internal lookup/debugging name, while the tab label is presentation text.
Build the content view and its layout completely before adding it when possible. If a tab is constructed with a null target and filled later, make the transition explicit and ensure the tab view has an appropriate minimum size. Do not use a tab label as a persistent model key because labels are localizable and users may rename related data.
Selection and focus are not the same state
Selection() returns the selected tab index or -1 when none is selected. Select(index) changes selection. FocusTab() and SetFocusTab() track focus state separately. Keyboard navigation can focus a different tab without the same meaning as a model-level choice; the focused tab should receive keyboard interaction, while the selected tab determines which content pane is shown.
Persist a stable application key for the chosen page, not only the index. Inserting a new tab before index two shifts every later index. If a saved index is used, validate it against CountTabs() after reconstructing the current UI and fall back to a safe default. For a dynamic tab set, map keys such as “network” or “appearance” to their current index.
When a tab becomes selected, the target view is activated through the tab view’s container. A selected pane may still need to restore its own focus to the last appropriate child, refresh model state, or cancel a pending operation. Keep these actions in the content view/application’s normal lifecycle rather than performing expensive work in drawing code.
Remove tabs without losing track of content
RemoveTab(index) returns the BTab* or NULL for an invalid index. The current documentation states that the BTab is not deleted by RemoveTab(); the caller should delete it if no longer needed. The current implementation’s destructor also deletes its content view. Therefore, deleting the returned tab destroys that pane. If the application intends to move the content view elsewhere, detach or replace it deliberately before deleting the tab, and verify exact behavior in the target Haiku revision.
Removing the selected tab changes both selection and focus state. The application should not assume the same index remains selected, especially when the last tab is removed or an earlier tab was deleted. Reconcile dependent controls and persist a fallback selection. If the tab represents a document, ask the document model whether it has unsaved state before closing; a tab widget does not provide document-close policy automatically.
Avoid dangling callbacks from a removed pane. Stop timers, detach message filters, cancel async work, and ensure pending worker replies do not target freed views. The BTab ownership tree does not automatically cancel application-owned workers or external observers.
Layout and resizing behavior
The tab strip and pane have separate geometry. Let each pane declare a useful minimum/preferred size and put resizable content in a layout. Do not force the entire tab view to the preferred size of the largest hidden pane unless that is a deliberate product choice; consider whether hidden tabs should influence the window’s minimum dimensions.
Test translated labels, many tabs, long names, and narrow windows. A tab label that no longer fits can be truncated or change the available pane geometry. Decide whether a menu or secondary navigation is needed for a large tab count rather than compressing every label until none is readable.
If a tab’s content changes its preferred size after selection, ensure the parent layout is invalidated through supported layout APIs. Do not manually resize the window in response to every selection unless that is the intended interaction. Avoid re-creating all panes on each Selection() change; hidden panes may retain valuable state, but keeping many heavy panes has memory cost. Choose lazy creation or retained views based on actual resource use.
Archiving and restore
BTabView and BTab support archiving. The view hierarchy can be reconstructed from a BMessage, but that does not restore arbitrary application model state automatically. Give each pane a versioned archive contract, validate optional fields, and restore the selected tab only after tabs have been rebuilt.
If the saved state contains an index, verify it is in range. If it refers to a removed feature, select a known default instead of failing the entire window. Persist user data outside the view archive where appropriate; interface serialization is not a substitute for a document file format.
Testing matrix
Test first-tab creation while attached to a window, selection changes, keyboard navigation, disabled tabs, zero tabs, one tab, and removal of first/middle/last tabs. Count target-view destructors when deleting removed BTab objects to verify ownership. Test a custom label distinct from the view name and confirm it survives the intended archive/restore path.
Also test close while async work is running in a pane, window resize with each pane selected, localized labels, and restoration when the previously selected tab no longer exists. Verify that removing a pane updates the application’s document registry and leaves no timer, message filter, or callback pointing at the deleted view.
For a data-driven tab set, keep a small application record beside each tab containing a stable key, the view/tab ownership state, and any document identity. Update that record in the same operation that adds, removes, or replaces a tab. This prevents a selected index from accidentally becoming the persistent identity when the collection changes. If a tab is closed asynchronously, mark it as closing and stop accepting new commands before detaching its view; otherwise a queued message can race with destruction.
Also test keyboard use independently from pointer selection. Tab order, focus visibility, and the selected pane should remain comprehensible at high text scale and when labels are localized. A disabled tab, if used, needs a visible reason or an alternate route to the unavailable content; silently skipping it can make the interface appear inconsistent. These are application-level policies, so record expected behavior in UI tests rather than relying on the tab widget to infer product intent.
BTabView is reliable when tab model, UI selection, keyboard focus, and view lifetime are represented separately. The control manages the BTab collection and its content-view ownership; the application manages document semantics, worker cancellation, saved identity, and fallback behavior. Being explicit about those boundaries prevents tabs from becoming a hidden source of leaks and use-after-free defects.
Related:
- Haiku Menus in the Interface Kit: Targets, Shortcuts, and Dynamic Items
- How to Build Resizable Haiku Interfaces with BLayout Instead of Fixed Coordinates
Sources: