Haiku API Bindings
Controls
Not logged in

Documentation | Manual: Controls

Chapter 5: Controls

In addition to Buttons and Menus, Haiku has a lot of other kinds of views. In this chapter, we are going to look at ListView and ScrollView.

This program for this chapter is called ListTitle.ext.

Imports

Like in the previous chapter, we begin by importing the modules we need.

Perl

# InterfaceKit will also pull in ApplicationKit; we don't need to pull
# in individual class names, just constants
use Haiku::InterfaceKit qw(ui_color :ui_colors);
use Haiku::Window qw(:window_type :window_flags);
use Haiku::View qw(:resizing :flags);
use Haiku::ListView qw(:list_view_type);
use Haiku::ScrollBar qw(:size);

Python

from HaikuR1.SupportKit import code_to_int
from HaikuR1.ApplicationKit import Message, Application # classes
from HaikuR1.InterfaceKit import Window, View, ListView, ScrollBar, Button, ScrollView, StringItem # classes
from HaikuR1.InterfaceKit import ui_color # classes
from HaikuR1.InterfaceKit import B_TITLED_WINDOW, B_ASYNCHRONOUS_CONTROLS, B_QUIT_ON_WINDOW_CLOSE, B_FOLLOW_ALL, B_WILL_DRAW, B_PANEL_BACKGROUND_COLOR, B_FOLLOW_RIGHT, B_FOLLOW_BOTTOM, B_V_SCROLL_BAR_WIDTH, B_H_SCROLL_BAR_HEIGHT, B_SINGLE_SELECTION_LIST # constants

ListTitleWindow

We don't need to subclass any Views this time around; we'll just subclass Window.

Perl

package ListTitleWindow;
use parent 'Haiku::Window';
use Haiku::SupportKit qw(code_to_int);
use strict;

# the Perl imports shown above will actually go here,
# since we need them in the class, not the main namespace

my $reset_code = code_to_int('rswn');
my $title_code = code_to_int('sttl');
my $control_padding = 10;
my $default_title = "The Weird World of Sports";
my @titles = (
    "Toe Wrestling",
    "Electric Toilet Racing",
    "Bog Snorkeling",
    "Chess Boxing",
    "Cheese Rolling",
    "Unicycle Polo",
);

Python

class ListTitleWindow(Window):

reset_code = code_to_int('rswn')
title_code = code_to_int('sttl')
control_padding = 10
default_title = "The Weird World of Sports"
titles = [
    "Toe Wrestling",
    "Electric Toilet Racing",
    "Bog Snorkeling",
    "Chess Boxing",
    "Cheese Rolling",
    "Unicycle Polo",
]

In this class, we accept no parameters and pass our own values for the parameters required by the base class.

Ordinarily, we would allow the code calling the constructor more freedom in the placement, size, and behavior of the Window, but since this is a one-off subclass that will only be used in this program, we are putting everything in the constructor. (This is a common pattern in C++ programs that use one-off Window subclasses.)

Perl

sub new {
    my $class = shift;
    my $self = $class->SUPER::new(
        title => $default_title,
        frame => [100,100,500,400],
        type  => B_TITLED_WINDOW,
        flags => B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE,
    );

Python

# by the time we get to __init__, the C++ object is already created,
# so if we want to provide our own arguments for the base class object
# we need to do it here
def __new__(cls):
    return super(ListTitleWindow, cls).__new__(cls,
        title = cls.default_title,
        frame = [100,100,500,400],
        type  = B_TITLED_WINDOW,
        flags = B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE,
    )

The first View we create is a background panel. This allows us to use another background color instead of Haiku's default white.

We will use a temporary local variable for this object, since once we add it to the panel, the panel will take ownership.

Perl

    my $panel = Haiku::View->new(
        frame        => $self->Bounds(),
        name         => "panel", 
        resizingMode => B_FOLLOW_ALL,
        flags        => B_WILL_DRAW,
    );
    $self->AddChild($panel);
    $panel->SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR));

Python

# because the previous code was in __new__, we need to start __init__
def __init__(self, *args, **kwargs):
    panel = View(
        frame        = self.Bounds(),
        name         = "panel", 
        resizingMode = B_FOLLOW_ALL,
        flags        = B_WILL_DRAW,
    )
    self.AddChild(panel)
    panel.SetViewColor(ui_color(B_PANEL_BACKGROUND_COLOR))

Next a button. We want it to stay in the bottom right corner, so we will create it with an empty frame, let it pick its own preferred size, then adjust the frame.

The preferred size is based on the font used; since we are not setting a font, this will be the default font, which will not necessarily be the same on every system. This approach allows the button to size itself so that it doesn't appear squeezed, or have too much white space.

We don't want the various controls to bump against the Window edges or against each other, so we leave some padding. We want to be consistent throughout the Window, so we set it to 10 above. It can be whatever you want, but 5 or 10 are common choices.

Again, we use a temporary local variable for the View.

Perl

    my $resetbutton = Haiku::Button->new(
        frame        => [], # empty frame because we're going to resize it anyway
        name         => "resetbutton",
        resizingMode => B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM,
        label        => "Reset",
        message      => Haiku::Message->new($reset_code),
    );
    $panel->AddChild($resetbutton);

    $resetbutton->ResizeToPreferred();
    my $wb = $self->Bounds();
    my $bb = $resetbutton->Bounds();
    $resetbutton->MoveTo(
        $wb->right - $bb->Width() - $control_padding,
        $wb->bottom - $bb->Height() - $control_padding,
    );

Python

    resetbutton = Button(
        frame        = [], # empty frame because we're going to resize it anyway
        name         = "resetbutton",
        resizingMode = B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM,
        label        = "Reset",
        message      = Message(self.reset_code),
    )
    panel.AddChild(resetbutton)

    resetbutton.ResizeToPreferred()
    wb = self.Bounds()
    bb = resetbutton.Bounds()
    resetbutton.MoveTo(
        wb.right - bb.Width() - self.control_padding,
        wb.bottom - bb.Height() - self.control_padding,
    )

Now we create the ListView. Since we are going to want to access this ListView later in MessageReceived, we will make it part of the object instead of using a local variable.

We want to use up all the remaining space in the Window, so we adjust for the padding and for the size of the Button. Since we are planning to put this ListView inside a ScrollView, we also adjust for the scroll bars.

Unlike a Button, ListView does not take a Messageas one of its parameters, so we have to set it after creation.

Perl

    # 1) start with entire internal space of panel
    my $frame = $self->Bounds();
    # 2) account for padding
    $frame->InsetBy($control_padding, $control_padding);
    # 3) account for vertical scroll bar
    $frame->right($frame->right - B_V_SCROLL_BAR_WIDTH);
    # 4) account for button, padding, and horizontal scroll bar
    $frame->bottom($resetbutton->Frame()->top - $control_padding - B_H_SCROLL_BAR_HEIGHT);

    $self->{colorlist} = Haiku::ListView->new(
        frame        => $frame,
        name         => "colorlist", 
        type         => B_SINGLE_SELECTION_LIST,
        resizingMode => B_FOLLOW_ALL,
    );

    $self->{colorlist}->SetSelectionMessage(Haiku::Message->new($title_code));

Python

    # 1) start with entire internal space of panel
    frame = self.Bounds()
    # 2) account for padding
    frame.InsetBy(self.control_padding, self.control_padding)
    # 3) account for vertical scroll bar
    frame.right = frame.right - B_V_SCROLL_BAR_WIDTH
    # 4) account for button, padding, and horizontal scroll bar
    frame.bottom = resetbutton.Frame().top - self.control_padding - B_H_SCROLL_BAR_HEIGHT

    self.colorlist = ListView(
        frame        = frame,
        name         = "colorlist", 
        type         = B_SINGLE_SELECTION_LIST,
        resizingMode = B_FOLLOW_ALL,
    )

    self.colorlist.SetSelectionMessage(Message(self.title_code))

Now we wrap the ListView in a ScrollView. Note that we do not add the ListView to the panel first; this would cause a fatal error, because a View can only have one owner. Instead, we set the ListView as the ScrollView's target, which will cause the ScrollView to add the ListView to itself, and then we add the ScrollView to the panel.

Since we will not need to access the ScrollView later, we again use a local variable.

Perl

    my $scrollview = Haiku::ScrollView->new(
        name         => "scrollview", 
        target       => $self->{colorlist},
        resizingMode => B_FOLLOW_ALL,
        horizontal   => 1,
        vertical     => 1,
    );
    $panel->AddChild($scrollview);

Python

    scrollview = ScrollView(
        name         = "scrollview", 
        target       = self.colorlist,
        resizingMode = B_FOLLOW_ALL,
        horizontal   = 1,
        vertical     = 1,
    )
    panel.AddChild(scrollview)

Finally, we need to add some ListItems to our list. We already specified the text as a class variable above, so now we need to put those text strings into StringItems.

ListView does not take ownership of ListItems, so we need to make sure that these StringItems remain in scope for as ling as the ListView needs them. The simplest way to do that is to make them part of the object.

Perl

    my $scrollview = Haiku::ScrollView->new(
    $self->{items} = [];
    for my $title (@titles) {
        my $item = Haiku::StringItem->new($title);
        $self->{colorlist}->AddItem($item);
        push @{ $self->{items} }, $item;
    }

    return $self;
}

Python

    scrollview = ScrollView(
    self.items = []
    for title in self.titles:
        item = StringItem(title)
        self.colorlist.AddItem(item)
        self.items.append(item)

    # we already returned the object in __new__
    # __init__ doesn't return anything

Now that our constructor is complete, we need to implement MessageReceived to respond to user actions.

Perl

sub MessageReceived {
    my ($self, $message) = @_;

    if ($message->what == $reset_code) {
        $self->{colorlist}->DeselectAll();
        return;
    }

    if ($message->what == $title_code) {
        my $selected = $self->{colorlist}->CurrentSelection();

        # when the list is cleared (via the reset button)
        # the selection changes to -1, so we revert to default
        if ($selected < 0) {
            $self->SetTitle($default_title);
            return;
        }

        my $item = $self->{colorlist}->ItemAt($selected,  'Haiku::StringItem');
        if ($item) {
            $self->SetTitle($item->Text());
        }

        return;
    }

    $self->SUPER::MessageReceived($message);
}

Python

def MessageReceived(self, message):

    if message.what == self.reset_code:
        self.colorlist.DeselectAll()
        return

    if message.what == self.title_code:
        selected = self.colorlist.CurrentSelection()

        # when the list is cleared (via the reset button)
        # the selection changes to -1, so we revert to default
        if selected < 0:
            self.SetTitle(self.default_title)
            return

        item = self.colorlist.ItemAt(selected,  StringItem)
        if item:
            self.SetTitle(item.Text())

        return

    return super(ListTitleWindow, self).MessageReceived(message)

This general approach (custom Window subclass with standard View classes) will probably be sufficient for most of your needs.

Running the app

Since we did almost everything in our Window subclass, the app itself is pretty simple.