

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
- 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]
- 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]
- 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]
- 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]
- 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]
- 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]
- 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]
- 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.
|