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.
-
Create a new (empty) project in Paladin
-
From the “Project” menu in the Paladin window, select “Add New File”.
-
Create a file named
App.cpp
, and let Paladin create a header file for you too. -
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; }
-
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
-
From either editor window, use
Alt+R
to compile and run the program. -
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.
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:
-
Initialize our counter,
fCount
.fCount = 0;
-
Create a window
This creates a rectangle that we will use when creating our window.BRect frame(100,100,500,400);
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. Ourframe
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 andB_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).
-
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. ABMessage
whosewhat
isM_BUTTON_CLICKED
will be sent each time this button is clicked. We don’t mess with the optional argumentsresizingMode
andflags
.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 definingmyWindow
's message handler, so we wouldn’t be able to see the messages. We want to receive the clicks in our handler instead. CallingSetTarget(this)
makes the button send the messages to thisApp
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.This last line attaches the button to our window. Without this line, the button wouldn’t show up when our window gets rendered.myWindow->AddChild(button);
-
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.
BMessage
s 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:
-
Increment
fCount
fCount++;
-
Figure out what we want the new window title to be
BString labelString("Clicks: "); labelString << fCount;
-
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;
}