|
 |


Table of Contents
BE ENGINEERING INSIGHTS: The Kitchen Sink
By Robert Polic robert@be.com
What started as simple tutorial on how to do
context-sensitive menus and drag-n-drop in a list view has
turned into a light- weight application launcher. The full
source (all 520 lines including headers and comments) for
"EZ Launcher" can be found on our ftp site...
ftp://ftp.be.com/pub/samples/application_kit/EZLauncher.zip
EZ Launcher is a simple BApplication that constructs a
single window with a scrolling list view containing icons
and labels for all applications in the /boot/apps folder.
Users can launch an application either by double-clicking
the item, right-clicking the mouse to access a
context-sensitive menu, or dragging and dropping back onto
the window. All operations are done asynchronously to limit
the amount of time the window is locked (and therefor
unresponsive). Overkill for this app? You bet, but with the
BeOS, it's almost as simple to spawn a thread to handle
user actions as not to.
So I'll assume everyone here is familiar with constructing
an application and window and will skip over that part and
get to the meat, which in this case is the list. In this
app I'll use a scrolling BListView to both maintain my list
items and allow the user to select them...
TEZLauncherWindow::TEZLauncherWindow(BRect frame)
:BWindow(frame, "EZ Launcher", B TITLED WINDOW,
B NOT ZOOMABLE | B WILL ACCEPT FIRST CLICK)
{
// set up a rectangle and instantiate a new view
BRect aRect(Bounds());
BScrollView *aScroller;
// reduce by size of vertical scroll bar
aRect.right -= B V SCROLL BAR WIDTH;
// construct a BListView
fList = new TEZLauncherView(aRect);
// construct a scroll view containing the list view
//and add it to the window
AddChild(aScroller = new BScrollView("", fList,
B FOLLOW ALL, B WILL DRAW, true,
true, B PLAIN BORDER));
BuildList();
}
BuildList is the method that actually adds all the items
from the /boot/apps directory to the list...
void TEZLauncherWindow::BuildList()
{
BDirectory dir;
BEntry entry;
BPath path;
// walk through the apps directory adding
//all apps to the list
find directory(B APPS DIRECTORY, &path, true);
dir.SetTo(path.Path());
// loop until we get them all
while (dir.GetNextEntry(&entry, true) ==
B NO ERROR)
{
if (entry.IsFile())
// construct a new BListItem
fList->AddItem(new TListItem(&entry));
}
}
TListItem is derived from from BListItem and takes a single
parameter, an entry ref. Through this entry ref, TListItem
will extract and cache the application name and icon...
TListItem::TListItem(BEntry *entry)
:BListItem()
{
BNode node;
BNodeInfo node info;
// try to get node info for this entry
if ((node.SetTo(entry) == B NO ERROR) &&
(node info.SetTo(&node) == B NO ERROR)) {
// cache name
entry->GetName(fName);
// create bitmap large enough for icon
fIcon = new BBitmap(
BRect(0, 0, B LARGE ICON - 1,
B LARGE ICON - 1), B COLOR 8 BIT);
// cache the icon
node info.GetIcon(fIcon);
// adjust size of item to fit icon
SetHeight(fIcon->Bounds().Height() +
kITEM MARGIN);
// cache ref
entry->GetRef(&fref);
}
else {
fIcon = NULL;
strcpy(fName, "<Lost File>");
SetHeight(kDEFAULT ITEM HEIGHT);
}
}
TListItem is also responsible for drawing the item in the
BListView's view...
void TListItem::DrawItem(BView *view, BRect rect,
bool /* complete */)
{
float offset = 10;
BFont font = be plain font;
font height finfo;
// set background color
if (IsSelected()) {
// fill color
view->SetHighColor(kSELECTED ITEM COLOR);
// anti-alias color
view->SetLowColor(kSELECTED ITEM COLOR);
}
else {
view->SetHighColor(kLIST COLOR);
view->SetLowColor(kLIST COLOR);
}
// fill item's rect
view->FillRect(rect);
// if we have an icon, draw it
if (fIcon) {
view->SetDrawingMode(B OP OVER);
view->DrawBitmap(fIcon,
BPoint(rect.left + 2, rect.top + 3));
view->SetDrawingMode(B OP COPY);
offset = fIcon->Bounds().Width() + 10;
}
// set text color
(IsEnabled()) ? view->SetHighColor(kTEXT COLOR) :
view->SetHighColor(kDISABLED TEXT COLOR);
// set up font
font.SetSize(12);
font.GetHeight(&finfo);
view->SetFont(&font);
// position pen
view->MovePenTo(offset,
rect.top + ((rect.Height() - (finfo.ascent +
finfo.descent + finfo.leading)) / 2) +
(finfo.ascent + finfo.descent) - 2);
// and draw label
view->DrawString(fName);
}
All mouse actions are directed to our ListView and from here
we decide whether to display a context-sensitive menu, spawn
a task to see if we need to initiate a drag, or do nothing
and let the base class handle it...
void TEZLauncherView::MouseDown(BPoint where)
{
uint32 buttons;
// retrieve the button state from the
// MouseDown message
if (Window()->CurrentMessage()->FindInt32(
"buttons", (int32 *)&buttons) == B NO ERROR)
{
// find item at the mouse location
int32 item = IndexOf(where);
// make sure item is valid
if ((item >= 0) && (item < CountItems()))
{
// if clicked with second mouse button,
// let's do a context-sensitive menu
if (buttons & B SECONDARY MOUSE BUTTON) {
BPoint point = where;
ConvertToScreen(&point);
// select this item
Select(item);
// do an async-popupmenu
fMenu->Go(point, true, false, true);
return;
}
// clicked with primary button
else
{
int32 clicks;
// see how many times we've
//been clicked
Window()->CurrentMessage()->
FindInt32("clicks", &clicks);
// if we've only been clicked once
// on this item, see if user
// intends to drag
if ((clicks == 1) ||
(item !CurrentSelection()))
{
// select this item
Select(item);
// create a structure of
// useful data
list tracking data *data =
new list tracking data();
data->start = where;
data->view = this;
// spawn a thread that watches
// the mouse to see if a drag
// should occur. this will free
// up the window for more
// important tasks
resume thread(spawn thread(
(status t (*)(void *)) TrackItem,
"list tracking",
B DISPLAY PRIORITY, data));
return;
}
}
}
}
// either the user dbl-clicked an item or
//clicked in an area with no
// items. either way, let BListView take care of it
BListView::MouseDown(where);
}
If we've determined that mouse down was a single-click on
the item, we'll spawn a thread that monitors the mouse
position and if the mouse moves more than kDRAG SLOP in any
direction, we'll initiate a DragMessage...
status t TEZLauncherView::TrackItem(
list tracking data *data)
{
uint32 buttons;
BPoint point;
// we're going to loop as long as the mouse
//is down and hasn't moved
// more than kDRAG SLOP pixels
while (1) {
// make sure window is still valid
if (data->view->Window()->Lock()) {
data->view->GetMouse(&point, &buttons);
data->view->Window()->Unlock();
}
// not? then why bother tracking
else
break;
// button up? then don't do anything
if (!buttons)
break;
// check to see if mouse has moved more
// than kDRAG SLOP pixels in any direction
if ((abs((int)(data->start.x - point.x))
> kDRAG SLOP) ||
(abs((int)(data->start.y - point.y))
> kDRAG SLOP))
{
// make sure window is still valid
if (data->view->Window()->Lock()) {
BBitmap *drag bits;
BBitmap *src bits;
BMessage drag msg(eItemDragged);
BView *offscreen view;
int32 index =
data->view->CurrentSelection();
TListItem *item;
// get the selected item
item = dynamic cast<TListItem *>
(data->view->ItemAt(index));
if (item) {
// init drag message with
//some useful information
drag msg.AddInt32("index",index);
// we can even include the item
drag msg.AddRef("entry ref",
item->Ref());
// get bitmap from current item
src bits = item->Bitmap();
// make sure bitmap is valid
if (src bits)
{
// create a new bitmap based on
// the one in the list (we
// can't just use the bitmap we
// get passed because the
// app server owns it after we
// call DragMessage, besides
// we wan't to create that cool
// semi-transparent look)
drag bits = new BBitmap(
src bits->Bounds(),
B RGBA32, true);
// we need a view
// so we can draw
offscreen view =
new BView(
drag bits->Bounds(), "",
B FOLLOW NONE, 0);
drag bits->
AddChild(offscreen view);
// lock it so we can draw
drag bits->Lock();
// fill bitmap with black
offscreen view->
SetHighColor(0, 0, 0, 0);
offscreen view->FillRect(
offscreen view->Bounds());
// set the alpha level
offscreen view->
SetDrawingMode(B OP ALPHA);
offscreen view->
SetHighColor(0, 0, 0, 128);
offscreen view->
SetBlendingMode(
B CONSTANT ALPHA,
B ALPHA COMPOSITE);
// blend in bitmap
offscreen view->
DrawBitmap(src bits);
drag bits->Unlock();
// initiate drag from center
// of bitmap
data->view->DragMessage(
&drag msg, drag bits,
B OP ALPHA,
BPoint(
drag bits->Bounds().Height()/2,
drag bits->Bounds().Width()/2 ));
} // endif src bits
else
{
// no src bitmap?
// then just drag a rect
data->view->DragMessage(&drag msg,
BRect(0, 0, B LARGE ICON - 1,
B LARGE ICON - 1));
}
} // endif item
data->view->Window()->Unlock();
} // endif window lock
break;
} // endif drag start
// take a breather
snooze(10000);
} // while button
// free resource
free(data);
return B NO ERROR;
}
The only thing left is to wait for a launch message to
arrive at the window...
void TEZLauncherWindow::MessageReceived(BMessage *msg)
{
char string[512];
int32 index;
entry ref entry;
entry ref *ref = NULL;
status t result;
TListItem *item;
switch (msg->what) {
case eItemDblClicked:
// item was dbl-clicked.
//from the message we can find the item
msg->FindInt32("index", &index);
item = dynamic cast<TListItem *>
(fList->ItemAt(index));
if (item)
ref = item->Ref();
break;
case eItemMenuSelected:
// item was selected with menu.
//find item using CurrentSelection
index = fList->CurrentSelection();
item = dynamic cast<TListItem *>
(fList->ItemAt(index));
if (item)
ref = item->Ref();
break;
case eItemDragged:
// item was dropped on us.
//get ref from message
if (msg->HasRef("entry ref")) {
msg->FindRef("entry ref", &entry);
ref = &entry;
}
break;
default:
BWindow::MessageReceived(msg);
}
if (ref) {
// if we got a ref, try launching it
result = be roster->Launch(ref);
if (result != B NO ERROR) {
sprintf(string,
"Error launching: %s", strerror(result));
(new BAlert("", string, "OK"))->Go();
}
}
}
DEVELOPERS' WORKSHOP: The Bitchin' Async
By Owen Smith orpheus@be.com
"Developers' Workshop" is a weekly feature that provides
answers to our developers' questions, or topic requests.
To submit a question, visit
http://www.be.com/developers/suggestion_box.html.
Recent events, such as, say, the intense effort to get R4
out the door, have inspired me to keep this article short
and sweet.
I'll be addressing R4's asynchronous control capabilities
in this article. These have already been covered in the R4
Beta release notes (with a few not-quite-correct points that
will be cleared up here), and The Animal's most nifty
summary article:
http://www.be.com/aboutbe/benewsletter/volume II/Issue45.html
My simple contribution here is to add some sample code so
that you can see these controls in action.
Enter Pot, the kitchen utensil for this week:
ftp://ftp.be.com/pub/samples/interface_kit/pot.zip
Actually, Pot doesn't refer to a kitchen utensil, nor to a
beefy roast, nor even to that medicinal restorative which
entertains countless carefree souls, but rather to a simple
BControl-derived class that implements a rotating dial.
I've also thrown in, absolutely free of charge, a test
application which shows this control in action (and
demonstrates the new B OP ALPHA mode on the side).
Whither Async?
New programmers and/or programmers coming from MFC or other
async-friendly APIs probably won't need much motivation to
start taking advantage of asynchronous controls. For those
coming from the BeOS R3 world of controls, though, some
justification may be in order.
Here's how your control might have handled mouse movements
in the past:
void ArthriticCtrl::MouseDown(BPoint where)
{
// handle mouse down
BPoint prev = where;
uint32 buttons;
do {
snooze(40000);
GetMouse(&where, &buttons);
if (buttons && (where != prev)) {
// handle mouse moved
}
} while (buttons);
// handle mouse up
}
There are two big wins you can get by moving to asynchronous
controls:
* Simplicity. In the previous case you have to write a mouse
processing loop and call a lower-level mouse handling
function to figure out when the mouse is moved and released.
With asynchronous controls, almost all of this work is done
for you. All you have to do is write the code to handle the
mouse moved and mouse button release.
* Performance. The code listed above is inefficient because
it forces the looper to sleep while it's not handling mouse
functions. (Note that simply removing the snooze doesn't
alleviate this situation at all, and degrades system
performance!) With asynchronous controls, your looper is
free to handle other messages while waiting for mouse input,
which allows your control to remain responsive to other
events.
Implementing Asynchronous Controls
Here are four simple steps to asynchronous nirvana if you're
deriving from BControl:
* Inside MouseDown, when you want to track the mouse
movement, you need to tell the app server that you want to
receive all the generated mouse events while the mouse is
moving -- including movements outside of your view!
Usually you'll want to use SetMouseEventMask, because the
tracking is automatically ended for you when the mouse
button is released.
* Mouse movements are usually sent to you whenever the mouse
is over the view -- not necessarily when you're tracking the
mouse. So, you also need to keep track inside your class of
when you're actively tracking mouse movement. BControl
provides two functions, SetTracking and IsTracking, that do
this for you. Call SetTracking(true) from within MouseDown,
and you'll be on your way.
* Override MouseMoved, and if you're currently tracking the
mouse (IsTracking() == true), do whatever you need to do
when the mouse moves.
* Override MouseUp, and if you're currently tracking the
mouse (IsTracking() == true), call SetTracking(false) after
you've finished handling the mouse up event, to mark that
you've finished tracking the mouse.
BPot, my twiddle-happy control example, shows how this is
done.
Using Interface Kit Controls
BButton, BCheckBox, and all the other Interface Kit
classes that derive from BControl, now lead a two-faced
existence. For compatibility's sake, they track mouse
movement the old way (using the mouse processing loop in
MouseDown) by default. However, they can be told to use
the asynchronous method instead. You tell these controls
to use the new implementation by passing the flag
B ASYNCHRONOUS CONTROLS to their parent window. Because of
the performance gain that the asynchronous method offers,
you'll probably want to enable asynchronous controls in
your windows, unless you're doing something special with
the controls that depends on their previous mouse handling
behavior.
If you're deriving from any of these classes, you can of
course completely replace their mouse handling behavior, or
leave their mouse handling code alone. However, if you want
to augment their existing behavior, you need to be careful
that you're working with them correctly:
* Make sure B ASYNCHRONOUS CONTROLS is set in the parent
window if you want them to handle things asynchronously --
otherwise, you'll be in for a rude shock when the
implementation's MouseDown is called!
* If you call the inherited MouseDown, ALWAYS call the
inherited MouseMoved and MouseUp as well if you override
these functions. Many of the Interface Kit controls set up
a special state in MouseDown that needs to be modified or
cleaned up when the mouse is moved or released.
* If you call the inherited MouseDown, you needn't take the
liberty of calling SetMouseEventMask and SetTracking in the
code -- these calls will be taken care of in the inherited
function, so long as you've set B ASYNCHRONOUS CONTROLS
correctly in the window.
Tweaking Asynchronous Behavior
Last week's article covered these, but to recap, there are
several ways you can tweak the asynchronous behavior to Do
The Right Thing (tm), depending on what your needs are:
* You have a burning desire to capture not only MouseMoved
and MouseUp events which occur outside of your view, but
also MouseDown events. Or, let's say you want to receive
mouse events which occur outside of your view all the time.
SetMouseEventMask just won't cut it here, because it gets
turned off when the mouse is released, at which point you'd
need to concoct some way to turn it back on. Rather, use
the more powerful SetEventMask, which is called in exactly
the same way as SetMouseEventMask, and does essentially the
same thing, but stays in effect until you explicitly turn it
off (using the call SetEventMask(0)).
* You'd really rather that Focus Follows Mouse doesn't steal
the glory from your window when one of the child views is
trying to track the mouse. Pass the flag B LOCK WINDOW FOCUS
as an option to SetMouseEventMask, and you'll ensure that
the focus doesn't change while you're tracking the mouse.
(This option doesn't have any effect when it's passed to
SetEventMask; only SetMouseEventMask supports it.)
* You notice that MouseUp events don't get handled until
several seconds after you release the mouse. The problem
here may be that your MouseMoved implementation is taking
too long -- I can receive upwards of 90 mouse events per
second on my machine, and if each MouseMoved call takes
.1 s to complete, the message queue deficit builds up
awfully quickly. One way to keep the queue clean, and keep
your application responsive, is to discard intermediate
MouseMoved events while you're busy tracking the mouse. The
B NO POINTER HISTORY option in SetMouseEventMask takes care
of this for you, so that your queue only has one pending
MouseMoved event at a time.
This problem is a symptom of a larger problem that afflicts
many Be applications: the more time you spend in message
handling functions, the less responsive your looper becomes.
In this case, discarding MouseMoved events may still have
the undesirable effect of skipping user input, and
suspending other looper activities, while an event is being
processed. An even better solution, when it's feasible, is
to reduce the time the looper spends processing the
messages, by accumulating results before performing
expensive operations on them, or passing off expensive
calculations to helper threads. Doing this frees up your
looper to respond to user events, and improves the visible
performance of your application.
* You want to receive keyboard events, or want to keep the
focus views from receiving keyboard events. Add
B KEYBOARD EVENTS to the mask, or add B SUSPEND VIEW FOCUS
to the options you pass to SetMouseEventMask.
(B SUSPEND VIEW FOCUS also doesn't have an effect on
SetEventMask; only SetMouseEventMask supports it.)
Using Asynchronous Mouse Handling in Doodle
Of course, you can use this shiny new mouse handling
behavior in other places than controls. In fact, any
BView-derived class can take advantage of the event mask.
You'll probably need to conjure up some equivalent to
BControl's SetTracking and IsTracking, though; a simple
bool in your class ought to take care of this.
In trying to keep with the times, I've altered Doodle yet
again so that the document view can take advantage of
asynchronous mouse handling (which really is a lot closer to
the way that the MFC library in Windows does things). The
new version of Doodle's source code can be found among the
optional items on the up-and-coming R4 CD-ROM, in:
/boot/optional/sample-code/doodle/
One big difference between the BeOS approach and the Windows
approach here is the number of simultaneous objects that can
"capture the mouse" (i.e., receive all mouse events). In
Windows, only one view at a time captures the mouse, and
instead of something like IsTracking(), you craft your code
in OnMouseMove to check to see whether the current view with
the capture is your view. In the BeOS, any number of views
can capture the mouse simultaneously, so each view needs to
keep track separately of whether they are currently
capturing the mouse. Also notice that the event mask in BeOS
is more flexible, allowing both mouse and keyboard events to
be captured, and giving you ways to tweak the behavior that
I've described above.
That wraps it up for this week. Back to my post-release
hibernation...
More notes from the road: Comdex
By Jean-Louis Gassée
Last week, I wrote from Tokyo, where we had a great time
with our partners, Hitachi and Plat'Home. This week,
although the neon resembles Tokyo by night -- down to the
creative syntax -- the streets are broader and the cabbies
more aggressive, so this must be Vegas.
I'll skip the fashionable complaints about Las Vegas and
Comdex. For me, Las Vegas looks like (I'll tone down the
metaphor) an experienced diner waitress, fast on her feet,
professional, and wise to the ways of human behavior. It's
the perfect setting for the excesses of our own profession.
I like those excesses; they're the mark of a prosperous and
still young industry. Would we prefer a convention of steam
engine makers? Put another way, there's no good culture
without a dash of bad taste; a monopoly of good taste
suggests restraint -- you're not pushing the envelope. In
this regard, Vegas and Comdex are very reassuring.
For the BeOS Release 4 coming out party, we have our own
booth. Last year, we were in the main hall, guests of our
Umax friends. This year, as true Comdex beginners, we're in
the basement of the Sands, with a large number of small,
aspiring companies. This beginner's status turns out to be a
pleasant one. Between people who wanted to see us, and
people who didn't expect to, we enjoy pretty good traffic.
Also, we can converse and give demonstrations without the
deafening noise in the main hall. There, like diners in a
noisy restaurant who must talk loudly because of the din
from other tables, exhibitors turn up the volume to be heard
over their neighbors' loud song and dance acts. While we
hope to have that problem in the future, for now we enjoy
not having to shout at the top of our lungs.
We approached the first public demonstrations of R4 with the
usual level of trepidation. "Undocumented features" have a
way of eluding testing, only to manifest themselves at
embarrassing moments, in the midst of a trade show demo,
preferably with media or OEM dignitaries in the audience for
added impact. The first day of this particular phase of
public testing went well, no claims expressed or implied,
and the demonstrations from our friends at Ro Design,
Beatware, MGI, Mediapede, Gobe, Maxon, and Hitachi went
smoothly.
Tomorrow, we'll hold a press conference, an opportunity to
re-state our basic messages: the Media OS, Release 4
breaking the laws of system software physics (more features
and faster), new applications, the specialized Media OS
coexisting peacefully with the general-purpose Windows. On
the last point, cheeky visitors needled me a little about
the latest bit of BeOS PR, from Microsoft this time, at
their shareholders meeting last week. It appears Bill Gates
mentioned Linux and the BeOS as competitive concerns. To
borrow a famous line, I'm shocked. But seriously, I hope
this isn't a bedtime story for the DOJ, especially when
Apple is nowhere mentioned.
Looking at our respective financial weights, according to
the terms of our last round of financing, Be is worth
between 1/2000th and 1/3000th of Microsoft market
capitalization. So, on the sunny side of Bill's statement,
he agrees with our investors, he sees potential. In any
event, we appreciate the publicity, we need it. Or, as we
say in California, we feel validated.
Recent Be Newsletters |
1998 Be Newsletters
1997 Be Newsletters |
1995 & 1996 Be Newsletters
Copyright ©1998 Be, Inc.
Be is a registered trademark, and BeOS, BeBox, BeWare, GeekPort, the Be logo and the BeOS logo
are trademarks of Be, Inc. All other trademarks mentioned are the property of their respective owners.
Comments about this site? Please write us at webmaster@be.com.
|