Hello, Haiku -- Writing Your First Program for Haiku

Jan 14, 2013   #haiku  #project  #tutorial  #code 

This is a walk through of creating your first GUI program for Haiku. We’re writing a super-simple application using just the built-in API.

Assumptions About You

  • Haiku and Paladin

    I’m assuming that you’ve installed Haiku, and have used installoptionalpackages to get the Paladin IDE.

  • OOP and pointers

    We’re writing the program in C++ because that’s what Haiku and its APIs are written in. If you’re familiar with class-based OOP and with C pointers, you should be very well equipped to follow this.

Haiku API Documentation

The main API documentation seems to be the BeBook. You’ll find a PDF of it on your desktop in Haiku. I find it a bit hard to follow, but it is very useful for looking up the specific API available.

To figure out how to actually use the API described in the BeBook, I’m using Lessons 14 and 15 of DarkWyrm’s Learning to Program With Haiku. I skipped most of the book, but did the Unit Reviews to double check that I wasn’t missing much. As far as I can tell, the Haiku-specific stuff doesn’t start until Lesson 14.

First, I’ll give some brief instructions on setting up the project in Paladin, and provide the actual code. You can compile and run the code to prove that it works. Then, I’ll explain my understanding of what happens when the code is executed.

The Actual Doing Stuff Part

To get started, open the Paladin IDE.

  1. Create a new (empty) project in Paladin

  2. From the “Project” menu in the Paladin window, select “Add New File”.

  3. Create a file named App.cpp, and let Paladin create a header file for you too.

  4. Open App.cpp in the Paladin editor (double-click the file name). Type the following into the file:

            #include "App.h"
            #include <Window.h>
            #include <Button.h>
            #include <View.h>
            #include <String.h>
    
            enum
            {
              M_BUTTON_CLICKED = 'btcl'
            };
     
            App::App(void)
                :    BApplication("application/x-vnd.lh-MyFirstApp")
            {
              //initialize counter
              fCount = 0;
    
              //create window
              BRect frame(100,100,500,400);
              myWindow = new BWindow(frame,"My First App"
                                             , B_TITLED_WINDOW
                                             , B_ASYNCHRONOUS_CONTROLS
                                               | B_QUIT_ON_WINDOW_CLOSE);
    
              //set up button and add to window
              BButton *button = new BButton(BRect(10,10,11,11),"button","Click Me!"
                                           , new BMessage(M_BUTTON_CLICKED));
              button->SetTarget(this);
              button->ResizeToPreferred();
              myWindow->AddChild(button);
    
              //actually display the window
              myWindow->Show();
            }
    
            void
            App::MessageReceived(BMessage *msg)
            {
              switch(msg->what)
              {
                case M_BUTTON_CLICKED:
                {
                  fCount++;
                  BString labelString("Clicks: ");
                  labelString << fCount;
                  myWindow->SetTitle(labelString.String());
                  break;
                }
                default:
                {
                  BApplication::MessageReceived(msg);
                  break;
                }
              }
            }
    
            int
            main (void)
            {
              App *app = new App();
              app->Run();
              delete app;
              return 0;
            }
        
    
  5. While in the App.cpp editor window, press Alt+Tab to open the App.h header file. Edit App.h to contain the following:

            #ifndef APP_H
            #define APP_H
    
            #include <Application.h>
    
            class App : public BApplication
            {
            public:
              App(void);
              void MessageReceived(BMessage *msg);
            private:
                int32 fCount;
                BWindow *myWindow;
            };
    
            #endif
        
    
  6. From either editor window, use Alt+R to compile and run the program.

  7. Assuming no typos, you should see a new window appear. The window title will be “My First App” and it will have a “Click me!” button. If you click the button, the title bar will change to “Clicks: 1”. Repeated clicks will increase the number in the title bar.

    Screenshot of launched application

How I Think It Works

This example is based on Lesson 14’s example. I then edited it to have the same functionality at Lesson 15’s example. It doesn’t look quite like Lesson 15 because I got tired of precisely following directions and thought making a whole new file just for the main window seemed silly.

The Header File

In the header file, we do a few important things.

The line

 #include <Application.h>

is how we import the stuff in Haiku’s Application kit. This includes the message handling stuff and the BApplication class.

class App : public BApplication

This class definition is saying that we’re making a class, called App, which is a subclass of BApplication. Being a subclass of BApplication provides us with default implementations of various useful methods, such as MessageReceived and Run.

public:
  App(void);
  void MessageReceived(BMessage *msg);

We’re defining two public methods App(void) and MessageReceived(BMessage *msg). App is the constructor, which takes no arguments. MessageReceived is how our application can receive messages (from itself, other applications, the operating system). It’s overloading a function of the same name in the BApplication class. A BMessage has a few fields, but the only part we’ll use today is the what field. This is an int that gets used like the subject of an email.

private:
    int32 fCount;
    BWindow *myWindow;

Our private members, fCount and myWindow, are the data that our constructor and message handling function will need to share, and that our messaging handling function will need to save between invocations. fCount is the counter for “how many times has the button been clicked”. myWindow is just a pointer to our main window.

The Class File

The class file is where we live up to the contract we made in the header file. We’ll define a constant, our constructor and message handler, and a main function to orchestrate everything.

The Constant M_BUTTON_CLICKED

In App.cpp, we start by defining an enum.

enum
{
  M_BUTTON_CLICKED = 'btcl'
};

DarkWyrm says that using an enum rather than a #define here is better, but he does not explain why. The enum we defined above is equivalent to this line:

#define M_BUTTON_CLICKED 'btcl'

'btcl' is a multi-character constant, as I learned from this StackOverflow Answer. Multi-character constants are an implementation-specific feature that gcc/g++ implements. In our code, M_BUTTON_CLICKED is a 32-bit value. Each character contributes an 8-bit digit to the value. To be more concrete:

M_BUTTON_CLICKED
= (256^3)*b + (256^2)*t + 256*c + l
= 16777216 * 98 + 65536 * 116 + 256 * 99 + 108
= 1651794796

Main

Let’s start our explanation where execution starts, in main.

int
main (void)
{
  App *app = new App();
  app->Run();
  delete app;
  return 0;
}

The first thing that happens is that we create an instance of App. This means that the constructor that we defined runs.

Then, we call this mysterious Run() method. Since we don’t define one, this is inherited from BApplication.

The BeBook’s BApplication entry summarizes what Run does:

As with all BLoopers, to use a BApplication you construct the object and then tell it to start its message loop by calling the Run() function. However, unlike other loopers, BApplication’s Run() doesn’t return until the application is told to quit. And after Run() returns, you delete the object it isn’t deleted for you.

The rest of main is just us cleaning up, by deleting out instance of App and providing a return value.

####Constructor

The constructor is defined with

App::App(void) : BApplication("application/x-vnd.lh-MyFirstApp")

The first thing we do is call one of the constructors from our superclass, BApplication. We pass it a string, which is our application’s signature. As far as conventions, DarkWyrm advises that the lh portion should be the author’s initials and the MyFirstApp portion should be the name of your application.

I’m guessing you’re wondering what an application signature is. I’m still a bit confused.

The BeBook’s System Overview BApplication entry says that this string is “the application’s signature”:

The string passed to the constructor sets the application’s signature. This is a precautionary measure it’s better to add the signature as a resource than to define it here (a resource signature overrides the constructor signature). Use the FileTypes app to set the signature as a resource.

So, there are other places were we could set the signature. This also points to the application signature being related to the filetype of the executable.

The Classes and Methods BApplication entry provides more information on this signature thing, but does not explain what it’s used for.

The signature constructors assign the argument as the app’s application signature. The argument is ignored if a signature is already specified in a resource or attribute of the application’s executable (serious apps should always set the signature as both an attribute and a resource). The signature is a MIME type string that must have the supertype “application”. For more information on application signatures and how to set them, see TODO.

Yes, it really does say TODO. Haiku’s file system (BFS) does not use filename extensions (.jpg,.gif, etc). Instead, it uses MIME types. MIME types are also used on the internet when transferring files. As far as the “application” supertype, that’s why our string starts with “application”.

The uses of application signatures are hinted at in this mailing-list thread:

Also this will make it easier to send messages between apps or use hey without having to look up the app signature.

There is also a mention of them in the Haiku User Guide:

On top, you’ll see, instead of a standardized MIME string, the unique application signature. With it, the system finds the program wherever it’s installed.

It seems like your application’s signature is used as it’s unique name. This allows for other programs to start our program or send it messages. I’m still not sure what the x-vnd part means, but it appears to be a standard part of the signature for applications.

When main calls our App constructor, we:

  1. Initialize our counter, fCount.

        fCount = 0;
        
    
  2. Create a window

        BRect frame(100,100,500,400);
        
    
    This creates a rectangle that we will use when creating our window. BRect comes from the Interface Kit and is used to create various buttons and windows and widgets. The BeBook Overview of BRect has an excellent explanation with pictures. To be brief, we’re calling a function that takes (left,top,right,bottom) coordinates. The origin is at the top left corner of the screen. Integer coordinates are located in the centers of pixels. Our frame is a 400x300 pixel rectangle.

        myWindow = new BWindow(frame
                               ,"My First App"
                               , B_TITLED_WINDOW
                               , B_ASYNCHRONOUS_CONTROLS | B_QUIT_ON_WINDOW_CLOSE);
        
    

    We’re creating a BWindow using this constructor:

        BWindow(BRect frame,
                const char* title,
                window_type type,
                uint32 flags,
                uint32 workspaces = B_CURRENT_WORKSPACE);
        
    

    “My First App” is the window title. B_TITLED_WINDOW means this is just a normally-styled Haiku window. According to the BeBook, B_QUIT_ON_WINDOW_CLOSE does nothing and B_ASYNCHRONOUS_CONTROLS should be used on any window that contains controls:

    Tells the window to allow controls to run asynchronously. All windows that contain controls should include this flag (it’s off by default because of backwards compatibility).

  3. Create a button and put it in the window

        BButton *button = new BButton(BRect(10,10,11,11),"button","Click Me!"
                                    , new BMessage(M_BUTTON_CLICKED));
        
    

    This creates out new button. It has not yet been added to the window, so this is just the idea of a button. The constructor we’re using is:

        BButton(BRect frame,
                const char* name,
                const char* label,
                BMessage* message,
                uint32 resizingMode = B_FOLLOW_LEFT | B_FOLLOW_TOP,
                uint32 flags = B_WILL_DRAW | B_NAVIGABLE);
        
    

    The BRect frame tells the button how big and where it should be. "button" is the name. "Click Me!" is the text that will appear on the button. A BMessage whose what is M_BUTTON_CLICKED will be sent each time this button is clicked. We don’t mess with the optional arguments resizingMode and flags.

    The name does not seem to matter for the purposes of this program. We can provide a name because BButton is a BControl, which is a BView, which is a BHandler, which uses a name to register with the messaging system.

    According to BView, we don’t need to specify a name:

    assigns the BView an identifying name, which can be NULL.

        button->SetTarget(this);
        
    

    The first thing we’ll do is tell the button where to send it’s messages. By default, that would be it’s window. In this case, that would mean myWindow. However, we aren’t defining myWindow's message handler, so we wouldn’t be able to see the messages. We want to receive the clicks in our handler instead. Calling SetTarget(this) makes the button send the messages to this App instance instead of the window. The button will send a message every time it is clicked.

        button->ResizeToPreferred();
        
    

    This is a method from BView. It calculates the preferred size:

    For example, a BButton object reports the optimal size for displaying the button border and label given the current font.

    And then:

    resizes the BView’s frame rectangle to the preferred size, keeping its left and top sides constant.

    To summarize, this line tells the button to forget the original frame size and resize to just enough space for “Click Me!” and a border.

        myWindow->AddChild(button);
        
    
    This last line attaches the button to our window. Without this line, the button wouldn’t show up when our window gets rendered.

  4. Display the window

        myWindow->Show();
        
    

    This is what causes the window to show up on the screen and starts it receiving messages.

The part of this that matters to our message handler is the button. In the button constructor, we describe what kind of message it will send when clicked. This is were that M_BUTTON_CLICKED thing comes in too.

BMessages are a key concept in Haiku. They’re used to communicate between applications and between threads in an application. A message has a what and data. For the moment, all we care about is the what. This is a number and it is used like the subject of an email. By using M_BUTTON_CLICKED as the subject of the message, we can only deal with the messages from our button in our message handler, and then pass everything else on to the much wiser MessageReceived of BApplication.

Message Handler

When we receive a message, MessageReceived is called with the message.

  void App::MessageReceived(BMessage *msg)

The void in the type signature means that we’re not trying to produce anything. We’re just reacting to the message.

  switch(msg->what)

Most of this function is taken up by a switch statement. All we care about is filtering out the messages from our button.

If the button is sending us a message, then we:

  1. Increment fCount

        fCount++;
        
    
  2. Figure out what we want the new window title to be

        BString labelString("Clicks: ");
        labelString << fCount;
        
    
  3. Set the window title

        myWindow->SetTitle(labelString.String());
        
    

On the other hand, if we get some other message, then we just pass it along to our super-class BApplication since we’ve got no idea what to do.

default:
{
  BApplication::MessageReceived(msg);
  break;
}