Be Newsletter

Volume III, Issue 33; August 18, 1999

Table of Contents


BE EVENTS:
I. Joint BUGs IRC with Be Inc.
II. Be Developer Conference - Europe

I. JOINT BUGs IRC with BE INC.
Saturday, August 21, 10 am PST

This Internet Relay Chat is for members of the Joint BeOS User Groups and Be Inc. The IRC will be hosted on newnet at irc.busprod.com(6667) #BeOS joint BUG IRC Members of the joint BUGs may obtain details from the president of their particular BUG or from Shannon (s_ndmn@yahoo.com).

II. BE DEVELOPER CONFERENCE - EUROPE October 7-8, 1999
The Holiday Inn Crowne Plaza Hotel
Frankfurt, Germany

This fall Be Europe will be hosting a Be Developer Conference. Space is limitted, so sign up now and experience the splendor of a BeDC in Deutschland.

  • Attend the opening night welcome session, keynote address, and reception.
  • Learn the ins and outs of the Interface Kit, the Media Kit, incorporating graphics and user interfaces into applications, and device drivers.
  • Bond with the ever-growing European Be Developer Community no matter where you call home.
  • Book a discounted, elegant room at the conference site so you can enjoy a sauna between sessions.
  • Have a heart-to-heart talk with a DTS Engineer.
  • Check out demos of cutting-edge BeOS apps.
  • Put Be employees on the spot at the Be Team Q&A.
  • Attend the BeGeistert Event on October 9th.

Registration is now open. Get more information here: <http://www.be.com/world/events/bedc/october1999/>


BE DEVELOPER CLASSIFIEDS: BIAS Hiring Experienced Application Developer


Be Developer Classifieds are an opportunity for Be developers to announce a product, request assistance on a project, or make their BeOS talents known for other projects.

For more information, check out the Classified Ads section in the Registered Be Developer area.


BIAS Hiring Experienced Application Developer

Berkley Integrated Audio Software, Inc. has an opportunity for an experienced application developer; Mac OS, Windows, or BeOS experience a plus.

Hot on the heels of their recent announcement to bring Peak, their award-winning audio editor, to BeOS, BIAS is hiring an experienced developer to focus on BeOS development. Please send a letter of inquiry and a resume to:

Steve Berkley, President <steve@bias-inc.com>
Berkley Integrated Audio Software, Inc.
1370 Industrial Ave, Suite A
Petaluma, CA 94952
Headquarters: 707-782-1866   FAX: 707-782-1874

For more info on Peak, please visit http://www.bias-inc.com


BE ENGINEERING INSIGHTS: Hacking the Stack
By Scott Barta sbarta@be.com

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.

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 rudeboy@be.com

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 CheckBox 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 switkin@be.com


"Developers' Workshop" is a weekly feature that provides answers to developers' questions or topic requests. To submit a question or suggestion, visit: <http://www.be.com/developers/suggestion box.html>

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:

<http://www-classic.be.com/aboutbe/benewsletter/volume III/Issue27.html#Workshop >

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 hippo@be.com

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: <http://www-classic.be.com/documentation/be book/The%20Storage%20Kit/ index.html>

or: <file:///boot/beos/documentation/Be%20Book/The%20Storage%20Kit/ index.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

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.

Although these rules may sound constraining, they seem less so when we keep in mind who benefits: shareholders and, as a result, the companies they invest in.


Recent Be Newsletters | 1998 Be Newsletters
1997 Be Newsletters | 1995 & 1996 Be Newsletters

Copyright © 1999 by Be, Inc. All rights reserved. Legal information (includes icon usage info).
Comments, questions, or confessions about our site? Please write the Webmaster.