Issue 4-33, August 18, 1999

Be Engineering Insights: Hacking the Stack

By Scott Barta

The lack of debugging tools on the BeOS often means that you have to roll your own. Writing good debugging tools isn't an easy proposition, but some simple stuff is within reach.

Call stack tracing (like MALLOC_DEBUG does for blocks) is one of the easiest and most useful tricks to have in your toolbox. It's pretty nice to be able to attach a call stack to an object so that when something goes wrong with it later on, you can tell where it came from. This technique is good not only for memory trashers, but synchronization bugs, too.

Stack crawling inside your own program is made possible by a quick hack that Ficus worked up one afternoon, being put to use in the leak checker and in MALLOC_DEBUG. It consists of a function that looks a given depth into the call stack and returns the address it finds there. It does this by walking each stack frame and grabbing return addresses from the specific point where they live in that architecture.

A limitation of MALLOC_DEBUG is the fact that it doesn't resolve the return addresses into symbols. This is pretty easy to do, however, so we'll explain the process here.

The Kernel Kit provides a set of image functions intended to help you extract symbols not only from shared libraries, but executable files as well.

Given an image id obtained from get next image info, you can run through all the symbols contained in a loaded image and extract them using get_nth_image_symbol, which gives you addresses, symbol names, and symbol types. Once stored in a table, it's a fairly simple task for your program to resolve each return address in a stack crawl and dump the results to stdout or to your favorite place for debug output and other trivia.

Responsible Be engineers always include some usable snippets of code in their articles; you can download a copy of the code for this article from the address below. I'll try to keep the explanations brief, as the code is fairly simple.

<ftp://ftp.be.com/pub/samples/kernel kit/StackHack.zip>

A call to init_sym_table() from main() is the all that is necessary to read symbols from loaded images and store the data in a global table (which you need to free yourself).

Return addresses located at a given depth in the stack are obtained through get_caller_address(). It walks the stack, looking for valid return addresses in the stack frames. The level parameter specifies which return address to find.

Be aware that GCC's -fomit-frame-pointer option will cause a nasty crash if try to do this, because there will be no stack frames to walk. Use this only when building your debugging builds with -fomit-frame-pointer turned off.

Once you have an address, you can use lookup_symbol() to find the symbol name and offset from the function beginning.

A quick look at the function shows that, given an address, lookup_symbol() does a modified binary search of its sorted list of symbols to find a match and calculate the offset.

That's about all there is to it. Feel free to use the code in your own programs to try to track down various and sundry memory problems. They can be really tough to find and fix, especially given the sparse set of BeOS debugging tools.


Be Engineering Insights: Font Sensitivity Training

By Robert Chinn

This article offers some tips and techniques for creating a clean, font-sensitive, consistent user interface. Putting together a good UI isn't difficult, but it requires a little extra thought about sizing and placement of UI elements and a basic understanding of how our view system works.

To have a consistent interface you must first understand what the views will use for layout and placement. The BBox is the only Interface Kit class that, by default, sets its view font to be_bold_font; all other views use be_plain_font unless they're set otherwise. So—what is be plain font or be_bold_font? In the first panel of the Fonts preference application you'll see the current system settings for the plain, bold, and fixed fonts the system is using. Next, open the Fonts.h interface file. Near the bottom of the file you'll find this:

extern  IMPEXP_BE const BFont* be_plain_font;
extern  IMPEXP_BE const BFont* be_bold_font;
extern  IMPEXP_BE const BFont* be_fixed_font;

You can use these three globals to reference the current fonts for each of the three system settings. Two items to note are calculating the height of a control and the width of text based on the desired font. You can do this easily by using

float stringwidth
    = be_plain_font->StringWidth("some piece of text");

which will return the width necessary to draw "some piece of text" in whatever the current plain font might be. To calculate the font height, try using

static float
FontHeight(const BFont* font, bool full)
{
    font_height finfo;
    font->GetHeight(&finfo);
    float h = finfo.ascent + finfo.descent;

    if (full)
        h += finfo.leading;

    return h;
}

This will return the correct height necessary for all the text to be visible for any font.

Please note that the font settings for the following examples aren't necessarily recommended, but are used here as an example of how settings affect view layout.

We'll assume that these views have the following code as their parent, and that the code is added to a window:

BBox* box = new BBox(Bounds(), "background",
    B_FOLLOW_ALL,
    B_WILL_DRAW | B_FRAME_EVENTS,
    B_PLAIN_BORDER);
AddChild(box);

Many classes in the Interface Kit size themselves based on the plain font setting, although some do not. Most initially use some basic default values for layout, and many of these values should be overridden to achieve a clean interface. Now then, let's take a look at a number of the Interface Kit classes.

BStringView

A BStringView is a simple view that displays static, noneditable text. It's often used to display information such as labels or the status of some process. Remember when you're using a BStringView that it requires adequate space, both width and height, to display completely any text that it might hold.

To properly size a BStringView, try this:

BRect frame(10, 10,
    10 + be_plain_font->StringWidth("Just a string"),
    10 + FontHeight(be_plain_font, true));

BStringView* stringview = new BStringView(frame,
    "string",
    "Just a string");
box->AddChild(stringview);

If you do this the string will always be completely displayed to the user, regardless of the current plain font settings.

BTextView

A BTextView, unlike a BStringView, can be an editable text field for displaying multi-line text. As with sizing the BStringView, correct initial sizing of a BTextView can also be done quite easily:

frame.Set(10, stringview->Frame().bottom+10,
        Bounds().Width()-10,
        stringview->Frame().bottom+10 + 3 +
            (3 * FontHeight(be_plain_font, true)) + 3);

//
//    The text rect is inset a bit so that there
//    is a reasonable gutter surrounding the text
//
BRect textrect(frame);
textrect.OffsetTo(0,0);
textrect.InsetBy(2,2);

BTextView* textview = new BTextView(frame, "text",
    textrect,
    B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP,
    B_WILL_DRAW);
box->AddChild(textview);

This creates a simple editable text area that displays three full lines.

BTextControl, BMenuField

A BTextControl is a mixed control; the left portion is a static label, the right portion is a field that allows text entry and can send a message notifying the host that the text has been modified. A BMenuField is similar, but has a pop-up menu on the right.

As with the previous classes, sizing is important for display, but for this class, the height is set by the class once it is added to a parent. Note that both of these classes include a label that displays static text. Rather than just placing multiple BTextControls or BMenuFields with their left edges aligned, you can get a more pleasing effect by aligning the controls at their Divider, the line that divides the label from the actual control. Once again, you can accomplish this by doing some initial calculations and by understanding the controls themselves.

const char* const kAnythingStr = "Anything";
const char* const kNumbersOnlyStr = "10 Numbers Only";

float maxlabelwidth =
    be_bold_font->StringWidth(kNumbersOnlyStr) + 5;
float labelwidth =
    be_plain_font->StringWidth(kAnythingStr) + 5;
//
//    based on the longest label "Anything"
//    simply calculate from that controls divider
//    to where a smaller label should start
//
frame.left = (10 + maxlabelwidth) - labelwidth;
//
//    make this view relative to the last view added
//    it will resize itself vertically
//    so, just create a valid BRect
//
frame.top = textview->Frame().bottom + 10;
frame.bottom = frame.top + 1;
BTextControl* tc1 = new BTextControl(frame, "any text",
    kAnythingStr, kAnythingStr, NULL,
    B_FOLLOW_LEFT_RIGHT | B_FOLLOW_TOP);
box->AddChild(tc1);
//
//    set the divider and alignment
//    so that it looks good
//
tc1->SetDivider(
    be_plain_font->StringWidth(kAnythingStr) + 5);
tc1->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);

//
//    once again, place the next view below the last
//
frame.top = tc1->Frame().bottom + 5;
frame.bottom = frame.top + 1;
frame.left = 10;
//
//    calculate the actual width based on the
//    text and font
//
frame.right =
    be_bold_font->StringWidth(kNumbersOnlyStr) + 5 +
    10 + (be_bold_font->StringWidth("0")*10) + 10;
BTextControl* tc2 = new BTextControl(frame,
    "numbers only",
    kNumbersOnlyStr,
    "0987654321",NULL);
box->AddChild(tc2);
tc2->SetFont(be_bold_font);
tc2->SetDivider(
    be_bold_font->StringWidth(kNumbersOnlyStr) + 5);
tc2->SetAlignment(B_ALIGN_RIGHT, B_ALIGN_LEFT);
//
//    here is a bit of extra code that
//    shows how to limit a BTextView to
//    a specific set of characters
//
BTextView* tv = tc2->TextView();
tv->SetMaxBytes(10);
for (long i = 0; i < 256; i++)
    tv->DisallowChar(i);
for (long i = '0'; i <= '9'; i++)
    tv->AllowChar(i);
tv->AllowChar(B_BACKSPACE);

//    and, now for a couple BMenuFields
//
//    build up a sample menu
//
BPopUpMenu* menu = new BPopUpMenu("a simple menu");
menu->AddItem(new BMenuItem("First Item", NULL));
menu->AddItem(new BMenuItem("Second Item", NULL));
menu->ItemAt(0)->SetMarked(true);
//
//    place this control relative to the BTextControls
//
frame.top = tc2->Frame().bottom + 10;
frame.bottom = frame.top + 1;
frame.left = (tc2->Frame().left + tc2->Divider()) -
    (be_plain_font->StringWidth("A Longer Label")+5);
//
//    and, again, calculate the correct width based
//    on the text and font
//
frame.right = frame.left
    + be_plain_font->StringWidth("A Longer Label")
    + be_plain_font->StringWidth("Second Item") + 30;
BMenuField* menubtn1 = new BMenuField(frame,
    "menu/menufield",
    "A Longer Label", menu);
box->AddChild(menubtn1);
menubtn1->SetDivider(
    be_plain_font->StringWidth("A Longer Label") + 5);

//
//    and, create a second one
//
menu = new BPopUpMenu("a simple menu");
menu->AddItem(new BMenuItem("Some Item", NULL));
menu->AddItem(new BMenuItem("Another Item", NULL));
menu->ItemAt(1)->SetMarked(true);
menu->SetFont(be_bold_font);

frame.top = menubtn1->Frame().bottom;
frame.bottom = frame.top + 1;
frame.left = (menubtn1->Frame().left
    + menubtn1->Divider()) -
      (be_bold_font->StringWidth("Menu")+5);
frame.right = frame.left
    + be_bold_font->StringWidth("Menu")
    + be_plain_font->StringWidth("Another Item") + 30;
BMenuField* menubtn2 = new BMenuField(frame,
    "menu/menufield",
    "Menu", menu);
box->AddChild(menubtn2);
menubtn2->SetFont(be_bold_font);
menubtn2->SetDivider(
    be_bold_font->StringWidth("Menu")+5);

Not only will the dividers be aligned, but there will be no dead space to the left of the label. The effect is that only the label and the control will be clickable, resulting in a moderately cleaner UI.

BButton

A BButton is a simple push button. It's easy to create and use, but tricky to have it show its display label correctly. As with a BTextControl, a BButton's height is based on the current plain font. Its width, though, should be calculated and padded with respect to the plain font.

//    the default minimum width of a button used by Be
//
const float kMinimumButtonWidth = 75.0;
const char* const kOkayStr = "Okay";
const char* const kLongStr
    = "Some extra long button name";

float width = be_plain_font->StringWidth(kOkayStr) + 20;
frame.top = menubtn2->Frame().bottom + 10;
frame.bottom = frame.top + 1;
frame.left = 10;
//
//    accommodate a longer width, depending on the text
//    and the font, else use the default minimum size
//
frame.right = frame.left
    + ((width > kMinimumButtonWidth)
        ? width : kMinimumButtonWidth);
BButton* btn1 = new BButton(frame, "button",
    kOkayStr, NULL);
box->AddChild(btn1);

//
//    and, add another wider button
//
width = be_bold_font->StringWidth(kLongStr) + 20;
frame.left = frame.right + 10;
frame.right
    = frame.left + ((width > kMinimumButtonWidth)
        ? width : kMinimumButtonWidth);
BButton* btn2 = new BButton(frame, "button",
    kLongStr, NULL);
box->AddChild(btn2);
btn2->SetFont(be_bold_font);

BCheckBox, BRadioButton

The last two controls I'll address are BCheckBox and BRadioButton. Both are standard and, like BButton, their height is sized appropriately, based on the current plain font. Note that to create a more intuitive UI, sizing the width appropriately will make your controls a bit cleaner. Once again, use StringWidth() and some padding for the frame when creating these controls.

frame.left = 10;
frame.right = frame.left + 20 +
    be_bold_font->StringWidth("Bold RadioButton");
frame.top = btn2->Frame().bottom + 10;
frame.bottom = frame.top+1;
BRadioButton* rb1 = new BRadioButton(frame,
    "radio button", "Bold RadioButton", NULL);
box->AddChild(rb1);
rb1->SetFont(be_bold_font);

frame.top = rb1->Frame().bottom;
frame.bottom = frame.top + 1;
frame.right = frame.left + 20 +
    be_bold_font->StringWidth("RadioButton");
BRadioButton* rb2 = new BRadioButton(frame,
    "radio button", "RadioButton", NULL);
box->AddChild(rb2);
rb2->SetFont(be_bold_font);

frame.top = rb1->Frame().top;
frame.bottom = frame.top + 1;
frame.left = rb1->Frame().right + 20;
frame.right = frame.left + 20 +
    be_plain_font->StringWidth("CheckBox");
BCheckBox* cb1 = new BCheckBox(frame, "checkbox",
    "CheckBox", NULL);
box->AddChild(cb1);

frame.top = rb2->Frame().top;
frame.bottom = frame.top + 1;
frame.right = frame.left + 20 +
    be_plain_font->StringWidth("Longer CheckBox Title");
BCheckBox* cb2 = new BCheckBox(frame, "checkbox",
    "Longer CheckBox Title", NULL);
box->AddChild(cb2);

As with the labels for a BTextControl or BMenuField, the only portion of the BCheckBox or BRadioButton that is clickable is now the label and the control itself.

Notice that many of these examples use a previous control to initially place the next control. Doing this makes your layout sensitive to the font height; also, controls set up this way will not collide with each other upon display.

Lastly, the window should flow with the font sensitivity of the views that it contains. Here, a basic heuristic is used to determine what the height and width should be:

//
//    the height of the window will be relative
//    to the last control added, in this case the
//    last BCheckBox
//    the width of the window will be based on the
//    control that has its right edge furthest to
//    the right, in this case either the last
//    BCheckBox or the second BButton
//
float right = (cb2->Frame().right > btn2->Frame().right)
    ? cb2->Frame().right : btn2->Frame().right;
ResizeTo(right + 10, cb2->Frame().bottom + 10);

Add the above code to a window of an application and run it. All the controls will be visible and sized appropriately. The views themselves will not overrun their siblings and only the visible portions of the controls will be active. Now open the Fonts preference application and select any set of fonts, close the panel, and open the test application again. While the panel may be larger, or possibly smaller, the same criteria will hold true.

So, why bother with these extra calculations and concerns? Simple—to make your interface consistent and always usable. Since the user can configure his system in many ways, particularly the choice of fonts, applications should defer to the user's wishes.


Developers' Workshop: Pulse—The Next Generation

By Daniel Switkin

The BeOS Pulse utility has—until now—actually been two and a half programs: the standard app you open in the Tracker, and a MiniPulse version that works as either an application or a replicant. In this article (and the accompanying modifications) I'll explain the consolidation of these two apps into one that's tied together through the GUI. Combining the two boosts responsiveness, adds a preferences panel, and uses some newer APIs. The techniques I'll discuss could spice up any old application, without requiring drastic changes. You can grab the new source code here:

<ftp://ftp.be.com/pub/samples/application kit/Pulse.zip>

Normal Mode, With a Vengeance

The first thing you notice when you build and launch Pulse is... nothing different. That's because this version intentionally retains the visual elements that made it a classic. When you right-click in the window, though, you'll start to see the changes. The organizational theme of the new Pulse is that it can be run in three different modes, one of them a replicant in the Deskbar. The pop-up context menu presents you with the other two available choices.

Taking a step back, let's run through how Pulse got its name. BViews that need to do regular (but not precisely timed) work can be constructed with the B_PULSE_NEEDED flag. The hook function BView::Pulse() is then called at intervals determined by its window, which can be set with BWindow::SetPulseRate(). This setting affects all views attached to that window.

All three modes contain a view that descends from a class called PulseView. Its job is to build the parts of the pop-up menu that they all share, and to launch the menu when the view is clicked. Consider this:

void PulseView::MouseDown(BPoint point) {
    BPoint cursor;
    uint32 buttons;
    MakeFocus(true);
    GetMouse(&cursor, &buttons, true);

    if (buttons & B_SECONDARY_MOUSE_BUTTON) {
        ConvertToScreen(&point);
        popupmenu->Go(point, true, false, true);
    }
}

What's important here is that the menu is launched asynchronously, and the Go() call returns immediately. The BMenuItem that is chosen (if one is chosen) sends a copy of its message when it is invoked. If the Go() method were synchronous, no Pulse() events would be received as long as the menu was held down, and hence there would be no redraws. As we say around here, B_DONT_DO_THAT. Similarly, the BAlert-based About boxes in Pulse are launched with alert->Go(NULL), the asynchronous version. In general, if you need to know which button the user clicked, give a valid BInvoker and check the "which" field when the message arrives.

The real work of PulseView is to calculate system activity. This is done by examining the system_info struct as follows:

void PulseView::Update() {
    system_info sys_info;
    get_system_info(&sys_info);
    bigtime_t now = system_time();

    for (int x = 0; x < sys_info.cpu_count; x++) {
        double cpu_time = (double)
            (sys_info.cpu_infos[x].active_time -
            prev_active[x]) / (now - prev_time);
        prev_active[x] = sys_info.cpu_infos[x].active_time;
        if (cpu_time < 0) cpu_time = 0;
        if (cpu_time > 1) cpu_time = 1;
        cpu_times[x] = cpu_time;
    }
    prev_time = now;
}

The active time variable is the number of microseconds spent doing work on this CPU since the machine was booted. The activity for this processor is determined by the change in this figure divided by the amount of time that has passed since the last call to Update().

For lucky users with multiple CPUs, this class also controls processor enabling and disabling. This is accomplished by telling the scheduler not to assign any work to that CPU. In the BeBox days, you could turn off both processors. This had two purposes: the geek thrill that you could do it, and to show how fast the BeOS can reboot. As much fun as that used to be, Pulse now does a sanity check to prevent disabling the last enabled CPU.

Going back to NormalPulseView, there are now two ways to show off disabling CPUs: with the CPUButtons (which are also replicants), or through the pop-up menu. To make sure we stay consistent, the Update() function keeps the BMenuItems current, and the CPUButtons take care of themselves, as is necessary when they're not attached to our window.

If you've dived into the Pulse source before, you might notice that CPUButton used to be a BPictureButton. It's now been rewritten to inherit from BControl directly and do its own drawing, as you'll see later on. For now, check out how CPUButton tracks the mouse asynchronously (does a pattern begin to emerge?):

void CPUButton::MouseDown(BPoint point) {
    SetValue(!Value());
    SetTracking(true);
    SetMouseEventMask(B_POINTER_EVENTS,
        B_LOCK_WINDOW_FOCUS);
}

void CPUButton::MouseMoved(BPoint point, uint32 transit,
    const BMessage *message) {

    if (IsTracking()) {
        if (transit == B_ENTERED_VIEW ||
            transit == B_EXITED_VIEW)
                SetValue(!Value());
    }
}

void CPUButton::MouseUp(BPoint point) {
    if (Bounds().Contains(point)) Invoke();
    SetTracking(false);
}

That's all you need for the guts of a two-state button that doesn't hog the window's thread while the mouse button is down.

Preferences—A New Hope

Rather than set your options from the command line, Pulse now uses a GUI to set (and remember) your choices. Pulse will launch in the mode you last left it in, unless you launch from the command line and force either -normal , -mini , or -deskbar modes . This is for the sake of compatibility and for users who like to launch Pulse as a replicant from ~/config/boot/UserBootscript.

Preferences are stored through the Prefs class, which writes them as attributes to a file in the user's settings directory. This is important -- while BeOS is single-user at the moment, it may not always be so, and from now on it will be considered bad form (and soon it will be impossible) to attach your settings as attributes to the application itself. To get started, you need to find the user's settings directory:

BPath path;
find_directory(B_USER_SETTINGS_DIRECTORY, &path);
path.Append("Pulse settings");
file = new BFile(path.Path(),
    B_READ_WRITE | B_CREATE_FILE);

Always use find_directory() for this purpose; do not hard code a path to ~/config/settings. Because BFile subclasses BNode, you can then read and write your attributes using the standard ReadAttr() and WriteAttr() functions. The Prefs class in Pulse has a number of wrapper methods that will handle common errors and insert a default value if a given attribute was not found. I'd be happy to round out this class with the other popular data types and put it in the Developer Library. Drop me a line if this would be helpful.

So—what's so great about GUI preferences? The ability to change Pulse's colors in real time! At the moment, BColorControl ignores the B_ASYNCHRONOUS_CONTROLS flag, so for real time updates you have to subclass it and override one important method:

void RTColorControl::SetValue(int32 color) {
    BColorControl::SetValue(color);
    Invoke();
}

This forces the color control to send its model message every time there's a change, rather than only when the mouse button is released. Its parent, a ConfigView, then packages all the PulseView settings that we're updating. But how does this message find its way to the views to do their drawing? Pulse uses a simple message model to make sure everything is notified correctly: the window or view that spawns the PrefsWindow passes a BMessenger to itself in the constructor, so messages can find their way home. The ConfigViews just borrow this messenger.

On the receiving end, the appropriate MessageReceived() passes the message to UpdateColors() There the message is decoded and Draw() is called to make the new settings take effect. Update() is not called because we don't want to recalculate system activity as we drag—this would make the ProgressBars flicker and change rapidly.

These real time color changes explain why CPUButton now does its own drawing—you wouldn't want to create a new BPicture and redraw it each time a new message arrived. We want to encourage programs whose settings take effect immediately. Only changes that involve a significant amount of work should require an Apply button.

Mini Mode—Exactly Like You, But 1/8th Your Size

MiniPulseView is based on Arve's MiniPulse application, as is DeskbarPulseView. It implements its own UpdateColors() method to set all three variables when messages arrive. Note that you must specify the target for your BMenuItems in AttachedToWindow(). Because BPopUpMenus never get attached to a window, their targets can't default to the window's handler. And since we launch the pop-up menu asynchronously, we don't wait around to find out which item was selected.

To reduce flicker, we call SetViewColor(B_TRANSPARENT_COLOR) to prevent the view from being redrawn in its background color. This is only useful if your Draw() method will touch every pixel of the view, which it does. To facilitate smooth resizing, PulseView passes the B_FRAME_EVENTS flag to the BView constructor. Taking advantage of this is as simple as

void MiniPulseView::FrameResized(float width, float height)
{
    Draw(Bounds());
}

Deskbar Mode—The Quickening

DeskbarPulseView essentially drops a MiniPulseView into the Deskbar, so you can see what your system is up to at a glance. For a refresher course in replicants, check out Eric Shepherd's tutorial:

Developers' Workshop: Stepping Up To the Deskbar

The replicant is constructed and added with the BDeskbar class, which is new in R4.5. This is done by building an instance of the view with the standard constructor, which gets archived. Two important notes here are that standard Deskbar replicants should be 16x16 pixels (remember, that's BRect r(0, 0, 15, 15)), and that you should delete the new instance after calling BDeskbar::AddItem(). The Pulse replicant permits variable width, but the height is always fixed. If you want your replicant to be wider than 16 pixels, try to keep it reasonable so that you don't overwrite the time. A good test is to move the Deskbar to one of the vertical orientations, where the tray width is fixed. Pulse makes 50 pixels the upper limit for width. The lower limit is based on the number of CPUs you have, such that each one will have at least a one-pixel-wide activity bar.

Since replicants have to handle their own messages, AttachedToWindow() sets all targets to itself:

BMessenger messenger(this);
mode1->SetTarget(messenger);
// etc.

And now for the nonintuitive part. The Pulse replicant doesn't use Pulse(). The reasoning goes like this: if you want to receive regular events, you probably want to control how often they occur, and that setting affects all views attached to your window. From now on, you should consider BWindow::SetPulseRate() off limits as a Deskbar replicant, because the Deskbar itself may need to do periodic work at its own pace. This may be enforced in the future. In general it's a bad idea to the set the pulse rate from any replicant that might be used in someone else's window—it's not courteous.

But fear not—we can still respond to periodic events by constructing a BMessageRunner, an often unnoticed addition from R4. Using the messenger from above in AttachedToWindow(), use

messagerunner = new BMessageRunner(messenger,
    new BMessage(PV_REPLICANT_PULSE), 200000, -1);

This delivers messages to DeskbarPulseView::MessageReceived() every 1/5th of a second until messagerunner is deleted. To prevent any work from being done in Pulse(), override MiniPulseView's version with an empty function.

Two final points about replicants: on PowerPC, you'll need to use specific linker settings. From BeIDE choose either "All Globals" or "Use .exp file". From a makefile add the following after the makefile engine #include:

ifeq ($(CPU),ppc)
    LDFLAGS += -export all
endif

The other option is to force your replicant class to be exported. In that case you want the "Use #pragma" option, so you don't expose your naming conventions to the world. The code looks like this:

#include <interface/BView.h>
#include <BeBuild.h>

class  EXPORT MyView : public BView {
    // the usual stuff goes here
}

The other thing to remember is that you'll have to kill and restart the Deskbar each time you want to try a new version of your replicant. This is because the code runs in the Deskbar's memory space. When you try to launch a new version, the old code is still cached and is used instead.

Conclusion—Beyond Thunderdome

I hope this article provides some good tips on how to write BeOS apps that are responsive and use the newest APIs. Feel free to contact me with questions, comments, and requests. And no, you can't disable all CPUs -- the kernel no longer allows it. The sanity check used to perform this function; now it just warns the user that the request can't be completed.


Bit By Bit: Opening Files

By Stephen Beaulieu

Rephrase inches towards usability. This week—opening files from the command line in Terminal. To make each file open up in its own phrase window, type:

Rephrase <space separated file names>

You can find this version of Rephrase at:

<ftp://ftp.be.com/pub/samples/tutorials/rephrase/rephrase0.1d2.zip>

Rephrase 0.1d2 New Features
Open Files from command line arguments at launch
Multiple Windows

Programming Concepts

You access files in BeOS through the classes of the Storage Kit. Conceptually, the Storage Kit consists of two types of objects: those representing a location in a filesystem, and those representing the content at a specific location.

The objects representing a location include:

  • entry_ref—a location expressed through a name, a directory token, and a volume token. entry_refs are usually the most efficient way to represent a location, but they must be converted to a more useful format to do much real work.

  • BPath—a convenience class representing a location as a string. It provides the means to manipulate and parse the path string. A path string is the appropriate method to persistently store a location, as an entry_ref's tokens can change across a reboot.

  • BEntry—a class representing a location. It provides information about that location, including whether a file exists there. It also provides facilities to rename, move, or remove a file at that location.

The content at a specific location is represented by subclasses of BNode. This version of Rephrase deals with one such class, the BFile, and only with it's creation. The BFile is manipulated through the BTextView.

For more detailed information on the Storage Kit or on BeOS installation see:
TheStorageKit.html

BApplication has a hook function ArgvReceived() that passes in any command line arguments passed to the application. Rephrase interprets these arguments as a list of files to open. In ArgvReceived() Rephrase translates each file name to a BEntry, and if there is a file at that location, puts the appropriate entry_ref in a B_REFS_RECEIVED message, which it passes to RefsReceived().

RefsReceived() is a BApplication hook function that presents a BMessage with one or more entry_refs for the app to process. In Rephrase, the appropriate action is to open a new window for each specified ref. In the future, the system will call RefsReceived() when files are dropped onto the Rephrase binary. It made sense to put the file opening code where it will be used.

Implementation Details

  1. If you accept command line arguments to open files, you should call RefsRecieved() directly from ArgvReceived(). Why? The ReadyToRun() hook provides a last chance to prepare for user interaction. It is called after all launch-time messages have been handled. Posting the B_REFS_RECEIVED message results in ReadyToRun() being called before RefsReceived(). This creates the default phrase editing window in addition to any windows opened by RefsReceived(). This is not the intended behavior. [Rephrase.cpp:84-85]

  2. Windows can call Show() in their constructor. This ensures that the window will be shown when created. Try to be consistent—pick a place to call Show() and stick with it. [pDisplay.cpp:75]

  3. An application that opens multiple windows should make the effort to ensure that they don't all open right on top of one another. The pDisplay class keeps track of the next position for a window. It tiles the windows across the screen in rows, until the screen is filled, then starts over. [pDisplay.cpp:140-167]

  4. The BWindow and BView system lets you find a view by its name. BWindow::FindView() and BView::FindView() return a BView* with the given name if one exists. Use a safe casting mechanism to the appropriate BView subclass, and always make sure you actually have a view before acting on it. FindView() returns NULL if no view is found and dynamic_cast() returns NULL if the view is not of the specified type. When you need to refer to a specific view often, it's wiser to cache a pointer to the view when it is created. The BTextView will be an obvious choice as Rephrase develops. [pDisplay.cpp:123]

  5. You can also search a BMenu hierarchy for BMenuItems with a given name. Accordingly, we don't hold onto a pointer to the "About Rephrase" item any longer, but simply look it up. Submenus added to a BMenu (which includes BMenuBars) have a BMenuItem created for them. To find a submenu, look for the BMenuItem with the correct name and call Submenu() to get the menu object. [pDisplay.cpp:60-66]

  6. Instead of reading contents out of the file directly, Rephrase currently uses the version of BTextView::SetText() that accepts a BFile pointer as an argument. This is a matter of convenience in these early stages. In an upcoming installment we'll show how to read and write to the BFile. [pDisplay.cpp:131]

  7. BMessage is a class that acts as a data container. It provides easy methods to add and find data and to query what types of data are available. [Rephrase.cpp:88-108]

  8. I've fixed an off-by-one bug from 01.d1. Views should not overlap: accordingly the BTextView should start one pixel below the end of the menu. [pDisplay.cpp:47]

Next Week: Resizing and Scroll bars


After the Quiet Period

By Jean-Louis Gassée

We're now out of the customary 25-day period after the effective date of an IPO—July 20th in our case—during which we were embargoed from making public statements. The idea behind the silence is to let the market "digest" the news of an IPO before a company can offer information not contained in its prospectus. The prospectus, edited under the watchful eye of the SEC, contains the data investors use to evaluate the pros and cons of buying stock in a company.

The rule is that you're not supposed to add anything to a prospectus during the IPO process for 25 days after the offering is made. The very simple, sensible idea behind this rule is to level the investing field and make sure all investors operate with the same set of data. This makes investors trust the workings of the market and is good for the investing business.

Moving from lofty principles to practical consequences, maintaining this atmosphere of good faith creates certain restrictions that every public company operates under. For instance, when discussing the future of our business, in this newsletter or elsewhere, we have to be careful not to impart information that would create an imbalance between individuals who received the data and the public at large. So, in the interest of balance we shouldn't make revenue or earnings projections or issue statements that would lead one to infer such a forecast.

More specifically, we can say this new release is terrific because it contains these new features and fixes these embarrassing bugs. We can even say it's better than the Sumo Wrestler OS for digital media over broadband, but we cannot say that it will increase our revenue and earnings by such and such percentage. If the spirit of the moment carries us away and moves us to make such an imprudent statement, we're obliged to immediately put out a press release disclosing the information to the general public, in order to restore a level playing field.

Today's story doesn't say how the SEC and others would view repeat or seriously disruptive incidents. In the same vein—giving investors equal access to information and enough time to process it—earnings reports are released right after the market closes, giving investors time to sleep on the news.

Lastly, a word about a paragraph you might have seen in many press releases, including our recent earnings statement. The paragraph starts like this: "The statements contained in this Press Release may contain 'forward-looking statements.' Actual events or results may differ materially as a result of risks...," etc. The intent is to remind readers of press releases, or audiences listening to speeches, not to mistake some forward-looking statements for actual forecasts. For instance, discussion of future releases may or may not contain forward-looking statements, and companies entertaining such discussions may or may not want to remind readers of the limits and context.

Creative Commons License
Legal Notice
This work is licensed under a Creative Commons Attribution-Non commercial-No Derivative Works 3.0 License.