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 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: 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:
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 * 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, * Override * Override Using Interface Kit Controls
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 * If you call the inherited * If you call the inherited 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 * 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 * You notice that 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 * You want to receive keyboard events, or want to keep the
focus views from receiving keyboard events. Add
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:
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 That wraps it up for this week. Back to my post-release
hibernation...
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.
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. |