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

Haiku BFilePanel: Asynchronous Open and Save Dialogs

Use Haiku's BFilePanel message contract to handle open, save, and cancel actions without blocking the application looper or losing ownership.

BFilePanel is Haiku’s standard file-selection interface for open and save workflows. It belongs to the Storage Kit and communicates through BMessage delivery to a BMessenger target. Showing a panel is asynchronous from the application’s perspective: the UI remains responsive, and the target handles a message when the user confirms or cancels.

That message contract is more important than the dialog’s appearance. Open panels deliver entry references; save panels deliver a destination directory reference and the name entered by the user. The application still owns the job of opening, validating, or writing the selected file.

Choose mode and keep the panel alive

The panel mode is selected when the BFilePanel is constructed: B_OPEN_PANEL or B_SAVE_PANEL. Choose a target that outlives the panel, commonly the application’s window or another BHandler on a running BLooper. Store the panel in an owning object if it will be reused; hiding or closing the panel does not automatically delete the BFilePanel object.

#include <storage/FilePanel.h>

void MyWindow::ShowOpenPanel()
{
    if (fOpenPanel == nullptr) {
        BMessenger target(this);
        fOpenPanel = new BFilePanel(B_OPEN_PANEL, &target);
    }
    fOpenPanel->Show();
}

This example uses the constructor’s default open behavior and assumes MyWindow is a live handler owned by an application looper. A production class should destroy fOpenPanel during its own teardown, avoid constructing duplicate panels for repeated clicks, and ensure its message handler remains valid until outstanding messages are handled.

Handle open, save, and cancel as separate messages

For a default open notification, the message code is B_REFS_RECEIVED and the refs field contains one or more entry_ref values. A user may select a symbolic link; the reference identifies the selected entry, and the application must decide whether to follow it. If the target is the application messenger, the message can be delivered through RefsReceived; a custom handler target receives MessageReceived.

For a save notification, the message code is B_SAVE_REQUESTED. The directory field identifies the destination directory and the name field contains the text entered in the panel. The system does not write the file on the application’s behalf. Validate the name, handle an existing destination according to application policy, and report write failures to the user.

Cancellation uses B_CANCEL. When the panel sends a cancel notification, the old_what field identifies the message code that was replaced, which matters if the application supplied a custom message. Hiding the panel after a successful selection is a separate presentation behavior; do not treat it as proof that the user cancelled. Treat message codes explicitly and do not interpret arbitrary payload fields as valid paths without checking their types and presence.

void MyWindow::MessageReceived(BMessage* message)
{
    switch (message->what) {
        case B_REFS_RECEIVED:
            HandleOpenReferences(message); // validate each refs entry
            return;
        case B_SAVE_REQUESTED:
            HandleSaveRequest(message);     // read directory and name
            return;
        case B_CANCEL:
            HandlePanelCancelled(message);
            return;
        default:
            BWindow::MessageReceived(message);
            return;
    }
}

The handler should copy or resolve the small amount of message data it needs, then schedule lengthy parsing, network access, or file conversion away from the window’s message loop. Keep UI updates on the appropriate looper and report errors asynchronously rather than blocking the dialog response path.

Filters, multi-selection, and safe saves

Use BRefFilter when the panel should restrict which entries are presented, but validate the chosen file again when opening it. A filter improves the selection experience; it is not a security boundary and does not prevent a file from changing after selection. If multiple selection is enabled, process every ref rather than silently taking the first.

The constructor’s node-flavor and multiple-selection arguments shape the chooser before it is shown. Choose whether directories, files, or both are selectable based on the operation, and disable multiple selection when the command can accept only one result. These choices reduce invalid user paths but do not replace receiver-side checks: a message can be constructed by code other than the visible panel, and the referenced entry can be renamed or removed before use.

A BRefFilter should make a quick, deterministic decision from the entry and metadata supplied by the panel. Avoid network calls, long file reads, or modal UI in the filter callback; the panel may ask about many entries while populating or refreshing a directory. If the user can change which formats the app accepts, update the filter deliberately and refresh the panel rather than keeping a stale callback over mutable state.

Open and save messages have different ownership and validation needs. For open, enumerate all references in the message and copy the ones the operation needs before the message goes away. Open each reference with checked status, and decide explicitly whether symbolic links are followed. For save, validate the name as a leaf name rather than concatenating it into an unchecked absolute path; resolve the directory reference through the Storage Kit and apply the application’s overwrite policy there. The save panel chooses a destination, but it does not establish that the destination remains writable or that a prior file may be replaced.

For saves, the panel provides a directory and name, not an atomic-write guarantee. To protect existing data, write to a temporary file in the destination filesystem, flush and close it, then rename it according to the application’s overwrite policy. Handle permission errors, full volumes, and name collisions explicitly. Reusing a hidden panel can preserve its prior directory and position, which is convenient but should be considered when the application changes documents or security context.

If a write can take noticeable time, keep the panel’s target handler responsive: validate and copy the request data, then dispatch the file operation to a worker. Return a completion message to the owning looper with a job identifier so a late result cannot update a different document. On success, reopen or revalidate the created entry before announcing it; on failure, keep the user’s chosen name and directory available for correction when possible. A robust save path also distinguishes an existing target, a race in which another process creates it after the check, and a partial write caused by storage exhaustion.

The hide-when-done setting is a presentation choice, not an ownership rule. The API can keep a panel visible after a selection, while the close button still hides it. Decide whether repeated operations should reuse the same instance, reset its directory, or create a fresh panel. When changing a document or account context, set the directory intentionally so the panel does not reopen a location that leaks private workflow context or surprises the user.

Verify the complete panel lifecycle

Test the panel with one and several selected entries, a symbolic link, a directory selection, an empty directory, a vanished target, a read-only volume, and a filter that rejects all entries. For save, test a new name, an existing file, an unwritable destination, a full-volume failure, and a collision introduced after the panel closes. Also test a custom target/message, default application RefsReceived routing, cancellation and the old_what field, hide-on-done on and off, and window teardown while the panel is open.

Record the panel mode, node flavors, multiple-selection flag, target messenger, message code, and relevant result status when diagnosing a report. This separates a chooser configuration error from a Storage Kit race or the application’s own write logic. BFilePanel provides selection UI and a typed message boundary; the application remains responsible for file identity, authorization, atomicity, and recovery.

Test open, save, cancel, multiple selection, symbolic links, disappearing files, and destination overwrite behavior. Verify both message delivery and ownership cleanup; a dialog that looks correct can still leak panels or write to an unintended location.

Related:

Sources:

Comments