Welcome back! Last time we made a start on our installer! Guess what: it won't be an installer anymore! In search of a good project, I have decided to make Beatrice the graphical frontend for CVS in BeOS. This is actually good for everyone, because working in a nice graphical environment when doing programming is lovely! But before we can even think of doing anything, we will have to get our knowledge of the BeOS API sharpened, so get ready for the real work!
This time we will dive a bit deeper under the hood of the interface kit. You know that the BeOS API is actually divided into separate kits that all have their own 'goal'. We will use the Interface kit this time. You can read more about it in your local copy of the Be Book in the documentation directory of your BeOS partition, or at the mirror of the old BeOS site, here:
http://www.beatjapan.org/mirror/www.be.com/developers/developer_library/interface_kit.html. [Editor's note: Sorry, still no hyperlinks]
The source code referred to in this article is also available for download here: http://www.openbeos.org/samples/beatrice-t2-1.zip.
Coordinates
In order to start GUI programming, you must master a very important concept, called coordinates. Pixels, those little dots on your screen, need to be addressed somehow in order to give them contents, and to track pixels we use coordinates. Coordinates in BeOS are in a two dimensional system where the y-coordinate value is higher when you go down, and the x-coordinate is higher when you go to the right. The distances are measured by floating point types, the type float
. What you should remember is that one whole unit represents one pixel, so the y-value 2 represents two pixels down, and the x-value 300 represents 300 pixels to the right.
Each point on the screen is represented by two values, the first being the x-value and the second being the y-value. So, point ( 25.0 , 15.0 ) is higher than the point ( 30.0 , 2.0 ), but the latter is more to the right (look at the picture in the BeBook for more on this). The lowest coordinates in the top left position of the screen are (0.0 , 0.0). Remember this.
To work with the coordinate system and coordinates, you should remember three different types of specific coordinate systems. The first one is the global coordinate system, with its origin (0.0 , 0.0) at the left top corner of the screen. The second is the window coordinate system, with its origin at the left top corner of the window, and last, the view coordinate system, with the origin at the left top corner of a view (what views are, will be told later).
Coordinates are abstract things, and you will understand them better, when we start working with views in the next section.
Views
A BView is a containter of a particular rectangular area in a window. The BView has the means to work with the contents of that area. For example, a BView that implements a button, paints that button in the window. But it also handles any user clicks that may be on that button. Why is that good? Let's look at that first.
Imagine there wasn't something like a BView. Imagine that if you wanted to draw things in a window, like a button, and you would have to draw it yourself. And every other place you want to use a button, you will also have to draw that button and implement the functionality. A BView is an encapsulation of some functionality: it is a compact reusable class that you can use over and over again in different windows. In our new CVS frontend, we need to display a list of files, but we also need to display a list of revision numbers. So what we have is actually the 'list view' encapsulated in a BView, so we can use it over and over again.
So if we want to make a button class, we need to make it ourselves, right? Wrong! The Be API comes along with quite a few derivations from BView that implement particular functionality. For example, we have ready-made BButtons, BCheckBoxes, BMenus, etcetera. This is easier for us, but it will also make our programs look more alike. Imagine if we all had to implement these visual controls ourselves. It would make the whole user interface a mess, especially across different applications made by different developers, now wouldn't it?
As I said before, all visual controls in the interface kit inherit BView, either directly, like BStringView, or indirectly like BButton. So if we would write our own application we would only have to get a piece of paper and sketch the elements we want to use in it. So I did. Now we can move on from the theory to practise. The main elements of the main window of Beatrice should be a menu bar, a listview that shows the files, and perhaps something like the statusbar. From the previous article, we already have the BeatriceMainWindow class, and we will implement the building of the window in the constructor.
But before we can do that, we first need to see what classes we will need to implement for all of this. For the menu bar we need the BMenuBar class. For the list view we have two choices: a BListView and a BOutlineListView. The first one is an unindented list view, and it is not what we want, because CVS deals with directories as well, so to represent a directory structure we will use BOutlineListView.
On To The Code
The place to prepare and set up the code is in the window that needs to display it. As we have already overridden BWindow in BeatriceMainWindow, we can change the constructor to create the interface elements. In the header file BeatriceMainWindow.h
we put the following code:
class BMenuBar;
class BMenu;
class BMenuItem;
class BOutlineListView;
class BeatriceMainWindow : public BWindow
{
public:
BeatriceMainWindow();
~BeatriceMainWindow() {};
virtual bool QuitRequested();
private:
BMenuBar *m_menubar;
BMenu *m_filemenu;
BMenuItem *m_quitaction;
BOutlineListView *m_filelist;
};
Have a look at the members declared in the private
section. Our menu bar is called m_menubar
. We will also add a file menu of the type BMenu with name m_filemenu
. In that menu we will add a Quit action. (Please note that action is the name I got from programming in Qt, it merely implies that it is a menu item). Also the list view is present. Look at the m_filelist variable and its type.
BeatriceMainWindow::BeatriceMainWindow()
: BWindow( BRect( 100 , 100 , 800 , 300 ),
"BeatriceWindow",
B_TITLED_WINDOW,
B_NOT_RESIZABLE | B_NOT_ZOOMABLE )
{
//Construct UI: menubar
m_menubar = new BMenuBar( BRect(0,0,0,0), "menubar" );
AddChild( m_menubar );
//File Menu
m_filemenu = new BMenu("File");
m_menubar->AddItem( m_filemenu );
m_quitaction = new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED), 'Q');
m_filemenu->AddItem( m_quitaction );
m_quitaction->SetTarget(this);
// OutlineListView
BRect r;
r = Bounds(); //Assign the size of the window to the rect
r.top = m_menubar->Bounds().Height() + 1; //Substract the height of the menu
m_filelist = new BOutlineListView( r , "listview" );
AddChild( m_filelist );
}
The code above is the actual implementation of the interface part. It is located in the BeatriceMainWindow.cpp
file. So how do we build an interface? It's easy. A BWindow can have multiple BView's as children. So you basically create the views, add these to the window, and have fun!
In the code example above, we create two BViews: one for the menu bar and also one for the file list. The standard constructor of a BView is:
BView(BRect frame,
const char *name,
uint32 resizingMode,
uint32 flags);
A BView always demands its screensize, and you need to set a name. It also requires a resizingMode and flags. However, many of the BView subclasses have their own standard settings. We've used BMenuBar, which has the constructor:
BMenuBar(BRect frame,
const char *name,
uint32 resizingMode = B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP,
menu_layout layout = B_ITEMS_IN_ROW,
bool resizeToFit = true)
.
You see that both the frame and name items are mandatory, and that the resizingmode is already set. Also, the flags aren't even present (the BMenuBar class itself determines which flags it needs). As we'll probably be using standard BView deratives, I won't go deeper into the BView class itself (we'll save that for later).
Looking at the code above you can see how BViews work. However, if you look more closely to the allocation of BMenuBar, you'll see that I've given it a size of 0,0,0,0. This may look weird, and I can only explain it by saying BMenuBar is an exception. In the constructor described above, you can see that the constructor argument resizeToFit is automatically set to true, thus this means that BMenuBar determines its own size. This is actually easy, because it means that we won't have to do this ourselves. I have named the BMenuBar "m_menubar"
.
The constructor of the BOutlineListView is quite long and you can find it in the BeBook. Also BOutlineListView needs only two arguments to construct, and thus naturally I keep it simple by only doing these two. However, this BView derative doesn't set its own size and we should do it for it. In the last article, I had already used the BRect class, and I'll do it again. The BWindow::Bounds()
method returns a rect that describes the window size. So we assign this one to our rect. However, because our menu bar has the file menu added in it, it has resized its height and we should set the beginning of the file list just below that menu bar. What I have done is I set the top of the rect, which is the starting y value to the height of the menubar plus one. I added one pixel because it looks a bit nicer, though it isn't mandatory.
After I created the BView deratives, I added these to the window using the BWindow::AddChild( BView *child )
method. This attaches the views to the window and makes sure that they are shown when the window is shown.
Creating menus
Menus are a very powerful concept, and they are very conveniently implemented in the Be API. It is quite easy for us to create and handle menu actions. The base class for menus is the BMenu class. The constructor of the BMenu class is as follows:
BMenu(const char *name, menu_layout layout = B_ITEMS_IN_COLUMN)
BMenu(const char *name, float width, float height)
BMenu(BMessage *archive)
BMenu(BRect frame, const char *name, uint32 resizingMode,
uint32 flags, menu_layout layout, bool resizeToFit)
BMenu is actually the base class of BMenuBar, which you could've guessed by looking at the fourth constructor. Anyway, to create the file menu, I use the first one, which makes sure I only have to specify the name of the menu. I named the file menu "File", which is rather traditional. To add this menu to the menubar we use BMenu::AddItem(BMenu *submenu)
, which naturally is inherited by BMenuBar.
The next thing we do is adding a Quit item to the menubar. The class for menu items is the BMenuItem class (how surprising) and the constructor reads as follows.
BMenuItem(const char *label, BMessage *message,
char shortcut = 0, uint32 modifiers = 0)
In the constructor, we specify the label (thus, the name that is displayed in the menu) for the item ("Quit"
in our example). The second argument is the message argument, which implies the message that is sent when the menu is clicked (the Quit action emits the B_QUIT_REQUESTED
message). The short cut is the underlined letter we can use to quickly call the action. Here I have chosen the Q. I haven't implemented the modifiers, but you can add these too if you want to asign a short cut that needs shift or control to be pressed.
We add the the menu item by using bool BMenu::AddItem(BMenuItem *item)
. We also need to set a point where to direct the message to when the action is selected, and we have selected this window, which calls our overridden BeatriceMainWindow::QuitRequested method automatically. This way, we get the quit functionality for free!
These are the basic things you need to know when using menus. Dive a bit into the BeBook to find out more!
Conclusion
What have we learned:
- Coordinates are representations of a point in a two-dimensional system
- The coordinates are represented by floats
- The x-value is higher if you go further to the right
- The y-value increases if you go down
- The global coordinate system represents the whole screen with (0,0) top left
- The window coordinate system represents the coordinates relative to the window
- The view coordinate system represents the coordinates relative to the view
- BViews encapsulate the functionality and looks of a screen element
- There are a lot of standard BView deratives that have a certain functionality
- The constructor of a BView derative requires a size and a name
- BMenuBar is a BView that takes represents the menu bar
- BMenuBar can resize itself
- BOutlineListview is an indented list view
- You can add views to a window using
BWindow::AddChild( BView *child)
- The
BWindow::Bounds()
returns the size of the window
- You can create menus with BMenu and their items with BMenuItem
Exercises:
- Play with adding items to our BOutlineListView. Look at the BStringItem class
- Add a BStringView to the bottom of the window. Add the text "Statusbar" to it. Find out the right height
Next time I will really get to BMessages and their meaning in the greater plan. And as always, feel free to email me about anything that is unclear or missing!