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

Haiku Menus in the Interface Kit: Targets, Shortcuts, and Dynamic Items

Build maintainable Haiku menu trees with BMenu and BMenuItem, explicit message targets, layout rules, keyboard shortcuts, state, and dynamic population.

Haiku menus are a small object tree with a clear division of work: BMenu arranges items and submenus, BMenuItem holds a label, state, shortcut, and optional BMessage, and a target handler receives the selected command. BMenuBar, BPopUpMenu, and BMenuField specialize that base model for different interface roles. Treating menus as a bag of labels misses the parts that tend to break in production: message routing, object ownership, keyboard behavior, layout-specific methods, and changes made after the menu is attached.

This guide focuses on the Interface Kit API rather than the broader application message loop or layout system. BMenu is a BView-derived interface object, while its action messages follow the Application Kit’s handler/looper routing. Knowing which layer owns each responsibility makes menu code easier to test and safer to update.

Compose branches and item nodes

A BMenu is a branch in the hierarchy. Its name becomes the label when it is inserted as a submenu. A BMenuItem is usually a leaf that sends a message when selected, but a menu item can also contain a submenu. A new menu is empty: add items with AddItem() or a list operation, and add child menus to establish the tree.

enum : uint32 { kOpenDocument = 'opnd' };

void MainWindow::BuildMenus()
{
    BMenuBar* bar = new BMenuBar("main menu");
    BMenu* fileMenu = new BMenu("File");
    fileMenu->AddItem(new BMenuItem("Open...",
        new BMessage(kOpenDocument), 'O'));
    fileMenu->AddSeparatorItem();
    fileMenu->AddItem(new BMenuItem("Close",
        new BMessage(B_QUIT_REQUESTED), 'W'));

    // SetTargetForItems affects items already in this menu.
    fileMenu->SetTargetForItems(this);
    bar->AddItem(fileMenu);
    AddChild(bar);
}

This is a method excerpt for a BWindow subclass and assumes the normal Haiku headers and message handling are present. Production code should check the Boolean results from AddItem() and AddSeparatorItem() when allocation or a malformed hierarchy must be reported. Once an item is successfully attached, the menu hierarchy owns it; BMenu destruction also frees its attached items and submenus. Do not independently delete an item while its menu still contains it.

If a menu should invoke the application-wide handler, assign the target explicitly after adding its items. SetTargetForItems() is convenient but has two important limits: it acts only on items already present and does not recursively descend into submenus. Set the target on every submenu that needs one, and configure new items inserted later as part of dynamic population.

Choose the layout before choosing insertion methods

The ordinary constructor accepts B_ITEMS_IN_COLUMN or B_ITEMS_IN_ROW. A column is the expected layout for a pop-up or submenu; a row is typical for a menu bar. B_ITEMS_IN_MATRIX is a free-form arrangement and uses the constructor that takes explicit width and height. It is not just a cosmetic flag: many insertion helpers are valid only for certain layouts.

For example, AddItem(item) and AddSeparatorItem() are documented for column or row menus, while AddItem(item, frame) is the matrix-layout form. A separator is documented for a column menu, not a menu bar row. Calling the right method with the wrong layout is an API misuse, not a request for the toolkit to guess the desired geometry.

Let the normal menu layout calculate its item geometry for standard menus. Matrix menus are a specialized case where the caller supplies frames and must keep hit regions, visible bounds, and item state coherent. Prefer a row or column whenever it expresses the interaction plainly; custom geometry adds work when labels change, fonts scale, or translations grow.

Route commands without swallowing framework messages

BMenuItem inherits invoker behavior and holds a message that is sent when the item is chosen. The menu’s target may be a handler in the current window or a BMessenger for a different looper. A selected item is not proof that the underlying operation succeeded: the handler should validate its message, perform the operation, and report errors through the interface’s normal path.

Use stable command codes within the receiving context and typed fields for values that vary. Do not use a localized label as the command identifier. In MessageReceived(), handle commands you own and pass all unknown messages to the base class. This preserves inherited behavior and avoids breaking framework or application extensions as the menu evolves.

An item can also have a submenu. The submenu’s BMenu name supplies the visible parent label; the submenu has its own items and target routing. When assembling nested menus, set targets deliberately at each level because SetTargetForItems() is non-recursive. A design that happens to route correctly only because every object defaults to the same handler is harder to audit.

Make keyboard behavior explicit

A BMenuItem shortcut is a printable character with modifier keys. The API assumes the Command key by default; B_NO_COMMAND_KEY is how code specifies a shortcut without that default modifier. A shortcut appears beside the item’s label and can invoke the item without opening its menu. Check collisions with other window shortcuts because setting an item shortcut can override a shortcut already assigned to the window.

Trigger characters are a separate menu-navigation mechanism. Although the API exposes trigger-related methods, current Haiku documentation marks triggers as disabled. Do not promise trigger activation based solely on the presence of SetTrigger() in a header.

Test keyboard behavior in the actual attached window: open the menu bar, navigate into and out of submenus, invoke each shortcut, and confirm focus returns to the expected control. A visually correct menu can still have unreachable commands or a shortcut routed to the wrong handler.

Use check and radio state for different meanings

SetMarked() is useful for a checked item, such as a toggled display option. Calling SetRadioMode(true) on a menu changes the meaning: only one item in that menu is marked at a time, and marking a new one clears the previous item. Use radio mode for mutually exclusive choices, not a set of independent Boolean preferences. SetLabelFromMarked() also enables radio mode and changes the menu label to reflect its selected item, which is useful for a compact choice control.

Enabled state is separate from marked state. A disabled menu item cannot be selected or invoked, although the submenu attached to a disabled parent can still open; the submenu’s own items determine whether actions inside it remain usable. Update enabled state from the actual application model before presenting the menu so the user is not offered a command that fails only after selection.

When a menu’s state changes, use its public setters rather than drawing custom checkmarks or manually repainting menu pixels. Methods such as SetLabel(), SetEnabled(), SetMarked(), and SetShortcut() invalidate the attached menu as documented. Keep model state authoritative and derive menu appearance from it.

Populate dynamic menus with bounded work

For a menu whose items change over time, AddDynamicItem(add_state) is the subclass hook. Haiku calls it when the menu is shown and continues until it returns false. The first invocation uses B_INITIAL_ADD, later invocations use B_PROCESSING, and dismissal can call it with B_ABORT so the subclass can stop or clean up its work. The hook is documented for row and column menus, not matrix layout.

Treat the hook as part of menu opening, not as a background worker. Enumerating a modest recent-file list can be appropriate; scanning a large tree or waiting on the network can make the menu feel frozen. Prepare expensive data beforehand, bound the number of entries, and add a clear empty state. If newly created items need an explicit target, assign it as they are added because a previous SetTargetForItems() call will not affect future items.

Dynamic content also needs a stable identity. Do not encode a changing list index as if it were a durable document identifier; include the actual path or model key in the item’s message, then verify it still refers to an available object when the target receives the command. A file may disappear between opening the menu and selecting its entry.

Keep ownership and teardown understandable

The menu tree controls the lifetime of items and nested menus once they are attached. RemoveItem() detaches an item; check the relevant overload’s ownership semantics before deciding whether the caller should destroy the returned object. Never keep a raw pointer to an item after a code path can remove or replace it unless that path updates the reference too.

When replacing a submenu, detach it from the parent before destroying it. If a dynamic menu is rebuilt on every opening, remove old entries with the documented deletion behavior and do not leave stale pointers in the data model. A clean menu hierarchy makes shutdown straightforward: the window owns its menu bar view, and the menu tree releases its attached children.

Accept the menu as an interaction, not a screenshot

Exercise mouse selection, keyboard navigation, shortcuts, disabled entries, check and radio state, submenu open/close, dynamic insertion, target delivery, and destruction while the window closes. Repeat with long translated labels and larger system fonts. Verify that every command reaches its intended handler and unknown messages still reach the base class.

Record the menu layout, target for each branch, shortcut modifiers, ownership after every insertion/removal, and update timing. These are small contracts, but they are what keep menus responsive, predictable, and compatible with Haiku’s message-driven application model.

Related:

Sources:

Comments