Haiku API Bindings
Fonts
Not logged in

Documentation | Manual: Fonts

Chapter 6: Fonts

In this chapter, we are going to look at Fonts.

This program for this chapter is called FontViewer.ext.

Imports

Starting with this chapter, we will no longer include the importing of necessary modules in the code blocks. (However, the sample programs will, of course, include the imports.)

FontViewerWindow

We will subclass Window to do the heavy lifting.

Perl

package FontViewerWindow;

our $SET_FONT  = code_to_int('setf');
our $NEXT_FONT = code_to_int('nxtf');
our $TOGGLE    = code_to_int('tggl');

my @window_frame = (50,50,350,350);

my $button_width = 75;
my $button_height = 20;
my $spacer = 5;

my @Fonts;

Python

SET_FONT  = code_to_int('setf')
NEXT_FONT = code_to_int('nxtf')
TOGGLE    = code_to_int('tggl')

class FontViewerWindow(Window):
    window_frame = [50,50,350,350]

    button_width = 75
    button_height = 20
    spacer = 5

    fonts = []

Our subclass does not take any of the normal Window arguments; we pass our own values for those arguments to the base class.

It does, however, take one argument: the Application object, so we can use it later when responding to one of our messages.

Perl

sub new {
    my ($class, $app) = @_;
    my $self = $class->SUPER::new(
        frame => \@window_frame,
        title => "Font Viewer",
        type  => B_TITLED_WINDOW,
        flags => B_QUIT_ON_WINDOW_CLOSE,
    );

    $self->{app} = $app;

Python

    def __new__(cls, app):
        self = super(FontViewerWindow, cls).__new__(cls,
            frame = cls.window_frame,
            title = "Font Viewer",
            type  = B_TITLED_WINDOW,
            flags = B_QUIT_ON_WINDOW_CLOSE,
        )

        self.app = app

Our application is going to cycle through all the available fonts. So we go through all the installed fonts, creating a Menu and storing enough information to instantiate each font later.

Perl

    my $menu = Haiku::Menu->new("Font");
    for my $font (@{ Haiku::Font::get_font_list() }) {
        my $fname = $font->{name};
        my $fflags = $font->{flags};
        my $submenu = Haiku::Menu->new($fname);
        $menu->AddItem($submenu);
        for my $style (@{ $font->{styles} }) {
            my $sname = $style->{name};
            my $sflags = $style->{flags};
            my $fentry = {
                family => $fname,
                style  => $sname,
                flags  => $fflags | $sflags,
                index  => scalar(@Fonts),
            };
            push @Fonts, $fentry;
            my $message = Haiku::Message->new($SET_FONT);
            $message->AddInt32("index" => $fentry->{index});
            my $item = Haiku::MenuItem->new(
                label   => $sname,
                message => $message,
            );
            $submenu->AddItem($item);
            $fentry->{menuitem} = $item;
        }
    }

Python

        menu = Menu("Font")
        for font in Font.get_font_list():
            fname = font['name']
            fflags = font['flags']
            submenu = Menu(fname)
            menu.AddItem(submenu)
            for style in font['styles']:
                sname = style['name']
                sflags = style['flags']
                fentry = {
                    'family' : fname,
                    'style'  : sname,
                    'flags'  : fflags | sflags,
                    'index'  : len(self.fonts),
                }
                self.fonts.append(fentry)
                message = Message(SET_FONT)
                message.AddInt32("index", fentry['index'])
                item = MenuItem(
                    label   = sname,
                    message = message,
                )
                submenu.AddItem(item)
                fentry['menuitem'] = item

Next we create a Button to allow the pausing and resuming of font cycling.

Perl

    my @button_frame = (
        $spacer,
        $spacer,
    );
    $button_frame[2] = $button_frame[0]+$button_width;
    $button_frame[3] = $button_frame[1]+$button_height;
    $self->{button} = Haiku::Button->new(
        frame   => \@button_frame,
        name    => 'Pause',
        label   => 'Pause',
        message => Haiku::Message->new($TOGGLE),
    );
    $self->AddChild($self->{button});

    # button may resize its height
    $button_height = $self->{button}->Bounds()->Height();

Python

    button_frame = [
            self.spacer,
            self.spacer,
            0,0
        ]
        button_frame[2] = button_frame[0]+self.button_width
        button_frame[3] = button_frame[1]+self.button_height
        self.button = Button(
            frame   = button_frame,
            name    = 'Pause',
            label   = 'Pause',
            message = Message(TOGGLE),
        )
        self.AddChild(self.button)

        # button may resize its height
        self.button_height = self.button.Bounds().Height()

We also want to the user to be able to diretly select a font, instead of waiting for the desired font to cycle around, so we add a MenuField to present the fonts, using the Menu we created earlier.

Perl

    my @chooser_frame = (
        $button_frame[2]+$spacer,
        $spacer,
    );
    $chooser_frame[2] = $chooser_frame[0]+50;
    $chooser_frame[3] = $chooser_frame[1]+$button_height;
    $self->{chooser} = Haiku::MenuField->new(
        frame =>\@chooser_frame,
        name  => 'FontChooser',
        label => 'Font',
        menu  => $menu,
    );
    $self->{chooser}->SetDivider(0);
    $self->AddChild($self->{chooser});

Python

        chooser_frame = [
            button_frame[2]+self.spacer,
            self.spacer,
            0,0
        ]
        chooser_frame[2] = chooser_frame[0]+50
        chooser_frame[3] = chooser_frame[1]+self.button_height
        self.chooser = MenuField(
            frame =chooser_frame,
            name  = 'FontChooser',
            label = 'Font',
            menu  = menu,
        )
        self.chooser.SetDivider(0)
        self.AddChild(self.chooser)

Finally, we create a TextView to display a sample string in the selected font.

Perl

    my @fontview_frame = (
        $spacer,
        $button_frame[3] + 2*$spacer,
        $window_frame[2] - $window_frame[0] - $spacer,
        $window_frame[3] - $window_frame[1] - $spacer,
    );
    $self->{fontview} = Haiku::TextView->new(
        frame        => \@fontview_frame,
        name         => "FontView",
        textRect     => [2, 2 ,$fontview_frame[2] - $fontview_frame[0] - 4, 0],
        resizingMode => B_FOLLOW_ALL,
    );
    $self->{fontview}->SetText(text => "The quick brown fox jumped over the lazy dog");
    $self->{fontview}->SetStylable(0);

    my $scrollview = Haiku::ScrollView->new(
        name         => "FontView",
        target       => $self->{fontview},
        resizingMode => B_FOLLOW_ALL,
    );
    $self->AddChild($scrollview);

    return $self;
}

Python

        fontview_frame = [
            self.spacer,
            button_frame[3] + 2*self.spacer,
            self.window_frame[2] - self.window_frame[0] - self.spacer,
            self.window_frame[3] - self.window_frame[1] - self.spacer,
        ]
        self.fontview = TextView(
            frame        = fontview_frame,
            name         = "FontView",
            textRect     = [2, 2 ,fontview_frame[2] - fontview_frame[0] - 4, 0],
            resizingMode = B_FOLLOW_ALL,
        )
        self.fontview.SetText(text = "The quick brown fox jumped over the lazy dog")
        self.fontview.SetStylable(0)

        scrollview = ScrollView(
            name         = "FontView",
            target       = self.fontview,
            resizingMode = B_FOLLOW_ALL,
        )
        self.AddChild(scrollview)

    self.current_font = -1;

    return self

Our Button and our MenuItems have been constructed to send messages to the Window. We need to override MessageReceived to handle those messages.

Perl

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

    my $what = $message->what;
    if ($what == $NEXT_FONT) {
        my $new_index = 0;
        if ($self->{current_font} != $#Fonts) {
            $new_index = $self->{current_font}+1;
        }
        $self->set_font($new_index);
        return;
    }
    if ($what == $SET_FONT) {
        my $new_index = $message->FindInt32("index");

        $self->set_font($new_index);
        return;
    }
    if ($what == $TOGGLE) {
        $self->{app}->toggle_cycling();
        if ($self->{app}->is_cycling()) {
            $self->{button}->SetLabel('Pause');
        }
        else {
            $self->{button}->SetLabel('Resume');
        }
        return;
    }

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

Python

def MessageReceived(self, message):
        what = message.what
        if what == NEXT_FONT:
            new_index = 0
            if self.current_font != len(self.fonts)-1:
                new_index = self.current_font+1
            self.set_font(new_index)
            return
        if what == SET_FONT:
            new_index = message.FindInt32("index")
            self.set_font(new_index)
            return
        if what == TOGGLE:
            self.app.toggle_cycling()
            if self.app.is_cycling():
                self.button.SetLabel('Pause')
            else:
                self.button.SetLabel('Resume')
            return

    super(FontViewerWindow, self).MessageReceived(message)

We encapsulate the code that actually updates the font into it own function.

Perl

sub set_font {
    my ($self, $index) = @_;

    # uncheck old font's menu item
    $Fonts[ $self->{current_font} ]{menuitem}->SetMarked(0);

    $self->{current_font} = $index;

    my $family = $Fonts[$index]{family};
    my $style = $Fonts[$index]{style};
    my $item = $Fonts[$index]{menuitem};

    my $font = Haiku::Font->new();
    $font->SetFamilyAndStyle($family, $style);

    $self->{fontview}->SetFontAndColor(0, 100, $font);
    $self->SetTitle("FontViewer - $family $style");
    $item->SetMarked(1);
}

Python

def set_font(self, index):
        # uncheck old font's menu item
        self.fonts[ self.current_font ]['menuitem'].SetMarked(0)

    self.current_font = index

    family = self.fonts[index]['family']
        style = self.fonts[index]['style']
        item = self.fonts[index]['menuitem']

    font = Font()
        font.SetFamilyAndStyle(family, style)

    self.fontview.SetFontAndColor(0, 100, font)
        self.SetTitle("FontViewer - " + family + " " + style)
        item.SetMarked(1)

FontViewerApp

There is one more piece left. In MessageReceived, we responded to a message that we had not sent, and we called custom functions on the Application object. Now we will create our Application subclass to implement the missing message and functions.

Perl

package FontViewerApp;
use parent 'Haiku::Application';

my $pulse_rate = 1000000;

sub new {
    my ($class) = @_;
    my $self = $class->SUPER::new("application/x.vnd-hab.perl.FontViewer");

    $self->{window} = FontViewerWindow->new($self);
    $self->{window}->Show();

    $self->toggle_cycling();

    return $self;
}

Python

class FontViewerApp(Application):
    pulse_rate = 1000000

    def __new__(cls):
        self = super(FontViewerApp, cls).__new__(cls, "application/x.vnd-hab.python.FontViewer")

        self.window = FontViewerWindow(self)
        self.window.Show()

        self.cycling = 0
        self.toggle_cycling()

        return self

The meat of our Application subclass is the font cycling. The actual updating is done via the Pulse method, and pausing/resuming is handled in the custom functions.

Since NEXT_FONT doesn't require any data, we can use the shortcut version of PostMessage, which takes the message code instead a Message object.

(If Window had its own Pulse method, we could have handled this all in our Window subclass, but since it does not, in order to get a regular repeated message like this, we either have to create a MessageRunner or subclass Application or one of the View classes.)

Perl

sub Pulse {
    my ($self) = @_;

    $self->{window}->PostMessage($FontViewerWindow::NEXT_FONT);
}

sub toggle_cycling {
    my ($self) = @_;

    $self->{is_cycling} = $self->{is_cycling} ? 0 : 1;
    if ($self->{is_cycling}) {
        $self->SetPulseRate($pulse_rate);
    }
    else {
        $self->SetPulseRate(0);
    }
}

sub is_cycling {
    return $_[0]->{is_cycling};
}

Python

    def Pulse(self):
        self.window.PostMessage(NEXT_FONT)

    def toggle_cycling(self):
        if self.cycling:
            self.cycling = 0
            self.SetPulseRate(0)
        else:
            self.cycling = 1
            self.SetPulseRate(self.pulse_rate)

    def is_cycling(self):
        return self.cycling

Running the app

Since we handled creating and showing the window in our Application subclass, there is not much left to do.