When I came aboard at Be in January to begin my headlong dive into the guts of NetPositive, I quickly learned that one of the features most often requested was a hierarchical bookmark menu, along with better bookmark management than what PR2 offered (which was essentially nothing). Based on experience with other browsers and platforms, my usual response was to stare at my shoes and mumble something about how I should someday implement a bookmark manager à la Netscape or Internet Explorer. Most people's response was, "No, use the Tracker instead. It's the Be way of doing things."
For the Release 3 release, at Pavel's prodding, I changed the bookmark data
format to use the attribute defined by the
x.vnd-Be.bookmark
file type for storing the bookmark
URL, and made sure
that bookmarks were given this type when saved. This opened up
NetPositive bookmarks for editing in
Tracker windows by allowing users to display and
edit the URL attribute of bookmarks in the
Tracker list view of
NetPositive's
Bookmarks
directory. In retrospect, I should
probably have modified the code to use the Title
bookmark attribute as well, instead of relying on the filename for the
bookmark's title, as this would have prevented a few weird quirks in
bookmark naming.
In my post-Release 3 work (which you'll see in the R4 release of
NetPositive, or very likely sooner in an interim release after Release
3.1), I've extended the relationship between NetPositive
and the Tracker
for handling bookmarks—I use NodeMonitors to keep informed of changes
to the Bookmarks
directory so
I can update my Bookmarks menu on the fly.
I also allow the user to create hierarchies in the Bookmarks menu by
creating subdirectories in the Bookmarks
directory.
Having done this work, I'm now a complete convert to using the Tracker to manage your data. I've found that combining file directories with custom attributes, Tracker management of those files, and NodeMonitors to keep you up to date on changes, is a powerful way to manage lists of information with minimal work on your part, while providing users with a lot of power over that data through an already familiar interface. I plan to extend NetPositive to manage site passwords, cookies, and other information in this way, without the need to come up with a new user interface and storage format for each type of information.
So where should you use this sort of mechanism in your applications? Anywhere you need user-maintained lists of information; examples include bookmarks, recent or favorite documents (you could maintain a directory of symlinks to do this), shortcuts, macros, plug-ins, etc. It's probably best suited to applications where the list of information is relatively short (i.e., dozens of items); it's not be the best way to maintain a user spelling dictionary.
How is it done? Follow these steps:
Define a file type with the custom attributes you need. For those attributes, set up the information that will make the attributes visible to the Tracker and editable by it. For more information on how to do this, see Doug Fulton's article in Be Newsletter Volume 3 Issue 11, "How To Get Tracker to See Your Attributes".
Save those files in a directory someplace meaningful to your application.
Set up a Node Monitor for that directory that will catch additions, deletions, and name changes inside the directory.
Since the directory Node Monitor will not receive notifications of attribute changes to files inside the directory, you need to set up a separate Node Monitor for each file in the directory to do this. Since Node Monitors are a limited system resource, this is a good reason to use this mechanism only for relatively small lists. Whenever the directory Node Monitor receives notification for added, deleted, and moved files, add and remove Node Monitors for those files as appropriate.
At program launch, read through the files in the directory, slurp out their attributes, and store them in a meaningful internal representation. When you receive notification from one of the Node Monitors of a change, update your data and take appropriate action.
When you receive a Node Monitor message, munge the low-level information it gives you into a meaningful format that you can deal with. This is actually a pretty hard thing to do because the Node Monitor interface is so low-level as to be an extreme pain in the neck.
"So where's some code I can copy and paste?" you ask. I've written a handy FolderWatcher class that does most of these tasks for you. Due to its length (close to 1,000 lines of code), it's not included here, but you can get it from our sample code archives at ftp://ftp.be.com/pub/samples/storage_kit/FolderWatcher.zip
The sample code includes the FolderWatcher
class and a simple application
that creates some folders in your home directory, adds a few bookmark
files to them, and then spits out any messages it gets back from the
FolderWatcher. It's a command line program and has no
GUI, so the
application itself isn't very useful, but it should show you how to use
the FolderWatcher
.
To use this class, create a separate FolderWatcher
instantiation for each
folder that you wish to watch. In the constructor, pass in a BEntry
to
the directory to be observed, the file type of the file of interest, the
signature of the preferred application for the files, and the BLooper
to
be notified when something interesting happens. If you want, the
FolderWatcher
can create the folder if it doesn't already exist.
Once the FolderWatcher
is created, call
Init()
on it to kick it off. It
will set up the Node Monitor on the folder, iterate through each matching
file in the folder, set a Node Monitor on it, and send your application a
FolderWatcher::FileAdded message for it so you can add it to your
internal list. Init()
creates a
BMessageFilter
and inserts it into the
BLooper
's message loop so that it can spy on the Node Monitor messages
that come back, and convert them to a friendlier form. It should behave
itself and not eat Node Monitor messages that don't pertain to the
folders it's watching.
I've found that for simple cases like I'm assuming here, a BMessage
is a
good way to represent attribute data, since BMessage
s can contain the
type/name/value triplets that attributes use. Therefore, I use BMessage
s
for passing attributes back and forth in the FolderWatcher
interface.
Note, however, that FolderWatcher
will become extremely inefficient if
you store large amounts of attribute data in these files.
To create new files in the folder, you can do it yourself or call
FolderWatcher
::AddFile()
, passing in the
filename a BMessage
containing
the attribute data to be put into the file. Similarly, to remove a file,
you can call RemoveFile()
, passing the filename; to change a file's
attribute, call ChangeFile()
, passing the
filename and a BMessage
. These
functions will cause notification messages to be sent back to inform you
of the change, so be ready for it.
If you don't need to watch the folder anymore, delete the FolderWatcher
instance; it will clean up silently (without sending further messages
back to you).
All other communication with the FolderWatcher
occurs as messages sent
back to your BLooper
when something happens:
FolderWatcher
::FileAdded
—A new file has been added to the
directory, either due to user action in the Tracker, or because your
application has added a file.
Message data: "Filename" (String): The name of the file that was added. "FolderWatcher" (Pointer): TheFolderWatcher
that is watching this file. "AttrData" (BMessage): The file's attributes, stored in aBMessage
.
FolderWatcher
::FileRemoved
—A new file was removed from the
directory, either due to user action or because of your application.
Message data:
"Filename" (String): The filename of the file that was
removed.
"FolderWatcher" (Pointer): The FolderWatcher
that is
watching this file.
FolderWatcher
::FileChanged
—An attribute of a file was changed,
either due to user action or because of your application.
Message data:
"Filename" (String): The filename of the file that was
changed.
"FolderWatcher" (Pointer): The FolderWatcher
that is
watching this file.
"AttrData": The attributes of the file that changed.
Since the Node Monitor doesn't actually tell you which
attribute changed, neither do we; you get all of the
attributes in the file.
As it is written, the class doesn't recurse into subdirectories, so that
might be a good way to modify it if you want to enhance its capabilities.
(In case you're wondering, this class was written after I implemented
hierarchical bookmarks in NetPositive; I'm going to go back and shoehorn
in a modified version of this class later.) Another suggestion would be
to modify AddFile()
to let it automatically generate a filename for a
file if you don't want to do it yourself.
As a final warning, the code hasn't been stringently tested, so caveat emptor. If you find problems, though, let me know and I'll fix them and update the sample code archive. Happy coding!
Be's success depends on the creation of a solid BeOS applications market and online commerce model. We strive to give our developers the best engineering resources and support, so they can develop the best applications for the BeOS. Now we will also provide them with the tools they need to successfully market their product. To this end, Be has recently acquired StarCode, Inc. of Redwood City, CA.
StarCode's products, BeDepot, Software Valet 1.5 for Intel and PPC, and PackageBuilder will be integrated into the line of Be products and services. This integration will allow Be to provide developers and end-users with software management solutions and with an online commerce system for selling and buying BeOS applications over the web.
Developers will have the tools to market their BeOS applications efficiently and conveniently, and users will have the means to easily and quickly access a wide range of BeOS products. StarCode and Be are working together to ensure a smooth transition and integration. Over time, Be will incorporate these products and services into our developer program.
Software Valet is an installer/deinstaller technology for the Beos, and will provide developers and users with a consistent reliable means of installing applications, updates, drivers, and other good stuff.
Package Builder provides tools for creating downloadable and installable/deinstallable applications and updates.
BeDepot is an online commerce system for selling applications over the web.
Tune in to our web site, as more information will progressively be available, or write to info@be.com.
Last time, we created a simple application with a menu bar that the user could play with. We also poked a toe into the waters of the BMessage class, but I skimmed over the details, leaving you high and dry until this week's exciting episode. If you haven't read parts 1 and 2 of this series, you really ought to do so: Issue 3-#7 Issue 3-#13
As promised, this week we're going to delve a little more deeply into messaging on the BeOS. In particular, we're going to add support for multiple windows to the MenuWorld application from last time, using messages to keep track of how many windows are open, and to ensure that no two untitled windows get the same name.
To accomplish this magnificent feat of software engineering, we're going
to extend the HelloApp
class to serve as a registry for our windows.
Whenever a new window is opened within the MessageWorld application, that
window tells the HelloApp
object that a new window has been opened.
HelloApp
, in return, lets the new window know what its name should be.
As always, you might want to have your Web browser aimed at the Be Book so you can read the more detailed descriptions of the functions discussed here; we won't be covering anything in complete detail, but just taking a look at how things might be done, to give you a place to start.
We begin with the MenuWorld application from last time. Before getting
into messaging, let's take a look at a minor change to the HelloWindow
constructor. It was pointed out on the BeDevTalk mailing list that
MenuWorld was assuming that the menu bar was always going to be 20 pixels
tall, which is not a safe assumption to make.
Additionally, there are a couple of minor changes needed so the window can register itself with the application object.
So the HelloWindow
constructor now does the following:
HelloWindow
::HelloWindow
(BRect
frame
) :BWindow
(frame
, "Untitled ",B_TITLED_WINDOW
,B_NOT_RESIZABLE
|B_NOT_ZOOMABLE
) {BRect
r
;BMenu
*menu
;BMenuItem
*item
; // Add the menu barr
=Bounds
();menubar
= newBMenuBar
(r
, "menu_bar");AddChild
(menubar
); // Add File menu to menu bar /* same code as last time */ // Add Options menu to menu bar /* same code as last time */ // Add the drawing viewr
.top
=menubar
->Bounds
().bottom
+1;AddChild
(helloview
= newHelloView
(r
));Register
(true
);Show
(); }
The menu bar is now added using the full bounds rectangle of the window.
This is safe to do because, as described in the Be Book, the
BMenuBar
constructor resizes the height of a menu
bar to the correct value. Then we add all the menus to the menu bar. Once
that's done, the drawing view is created. The frame rectangle of the
drawing view is determined by taking the bounds rectangle of the window,
and setting the top edge of that rectangle to one greater than the bottom
edge of the menu bar's bounds rectangle—this results in a view that
fills the remainder of the window.
We've also added a call to the new Register()
function (which we'll look
at later). This registers the new window's existence with the
application, and causes the application to assign the new window a number.
Note also that we've changed the title of the HelloWindow
to "Untitled ".
This isn't important, but it seemed like a good idea.
There are three basic classes in the BeOS messaging system. These are:
BMessage
.
The BMessage
is the class that represents a bundle of
information that is sent from one place to another. This class includes
functions for adding, changing, and removing named fields of data.
BMessenger
.
The BMessenger
class handles transmitting
BMessage
objects from one place to another. Its
central function, SendMessage()
, does the actual
job of transmitting messages. Upon construction, the
BMessenger
's target (the
BHandler
that will receive messages it transmits) is
specified. The target can't be changed.
BHandler
.
The BHandler
class receives
BMessage
objects that are sent
to it, and can optionally reply to those messages. It can't initiate a
transaction.
When a message is sent, the application server delivers it to a
BHandler
by calling that
BHandler
's
MessageReceived()
function. The message is passed
into MessageReceived()
, which interprets the
message and acts upon it as necessary. The BHandler
may choose to reply to the message by calling the message's
SendReply()
function, but this isn't necessary.
The BView
class is derived from
BHandler
; a view can receive and respond
to messages. That's how user commands are received—messages indicating
mouse and keyboard activity are delivered to the view, which interprets
and acts upon them.
If you flip through the Be Book for a while, you'll notice that
BLooper
is derived from
BHandler
. BLooper
is a handy
class that establishes a thread that runs a message loop repeatedly until
the loop's Quit() function is called. But the really good stuff (at least,
as far as we're concerned today) happens when you look at some of the
classes derived from BLooper
, such as
BApplication
and BWindow
.
A BApplication
object is instantiated, then you call
its Run()
function. Once you've done that, it
doesn't return until its Quit()
function is
called. Its MessageReceived()
function, which is
inherited from BHandler
, processes incoming
messages.
Likewise, a BWindow
loops, handling incoming
messages in its MessageReceived()
function, until
its Quit()
function is called. The only difference
is that you don't call a Run()
function; this is
done implicitly for you. It's still a BLooper
, and
it can still receive messages, just like anything derived from
BHandler
.
So now we know that BApplication
and
BWindow
are both capable of handling
messages (in fact, we saw this last time, when we added code to our
HelloWindow
class to process incoming
messages indicating that the menu
bar was being used).
Let's add the registry code to the HelloApp
class.
We'll need to add the MessageReceived()
function,
as well as two new private variables:
classHelloApp
: publicBApplication
{ public:HelloApp
(); virtual voidMessageReceived
(BMessage
*message
); private: int32window_count
; int32next_untitled_number
; };
The window_count variable keep tracks of how many windows our application
has open (there's actually a BApplication
function that does this, but
we're going to keep track of it ourselves for educational purposes).
next_untitled_number tracks the number to use when naming a new untitled
window (such as "Untitled 42"). This number always increases, so we'll
never see the same number twice.
The HelloApp
constructor needs to be updated to initialize these
variables:
HelloApp
::HelloApp
() :BApplication
(APP_SIGNATURE
) {BRect
windowRect
;windowRect
.Set
(50,50,349,399);window_count
= 0; // No windows yetnext_untitled_number
= 1; // Next window is "Untitled 1" newHelloWindow
(windowRect
); }
When the application is first run, there aren't any open windows, and the first window is called "Untitled 1".
The window registry—the mechanism by which the application counts and names windows—is handled entirely by accepting and responding to two application-defined messages:
constuint32WINDOW_REGISTRY_ADD
= 'WRad'; constuint32WINDOW_REGISTRY_SUB
= 'WRsb';
WINDOW_REGISTRY_ADD
messages will be sent
to the HelloApp
object when a
new window is opened and needs to be added to the registry.
WINDOW_REGISTRY_SUB
messages will be sent to remove
a window from the registry as it's being closed.
The HelloApp
needs to be able to reply
to WINDOW_REGISTRY_ADD
messages so
that it can tell the window what number to use when giving itself a new
"Untitled" name. So we need to define a command code for this reply
message:
constuint32 WINDOW_REGISTRY_ADDED
= 'WRdd';
Now let's look at HelloApp
's
MessageReceived()
function. This will be
called by the application server whenever a message is delivered to the
HelloApp object:
voidHelloApp
::MessageReceived
(BMessage
*message
) {switch
(message
->what
) {
The function begins by looking at the BMessage
's command code. This
32-bit value is located in the public what
variable in the BMessage
class, and specifies what type of message has been received. If it's a
WINDOW_REGISTRY_ADD
message, we do the following:
caseWINDOW_REGISTRY_ADD
: { boolneed_id
=false
; if (message
->FindBool
("need_id", &need_id
) ==B_OK
) { if (need_id
) {BMessage
reply
(WINDOW_REGISTRY_ADDED
);reply
.AddInt32
("new_window_number",next_untitled_number
);message
->SendReply
(&reply
);next_untitled_number
++; }window_count
++; } break; }
Before handing the WINDOW_REGISTRY_ADD
function, we
look at the need_id
field of the message. If it's
true
, the window needs to be assigned an ID number
(since the window is untitled). If need_id
is
false
, the window doesn't need an ID. This will be
used when we add the ability to open existing documents, which will already
have names, and won't need to have an "Untitled" name.
If need_id
is false
, a new
BMessage
, called reply
, is
created, with the command code WINDOW_REGISTRY_ADDED
.
This is the message we'll send back to the
HelloWindow
object as a reply once we've processed
the WINDOW_REGISTRY_ADD
message.
We then add a new field to the reply message by calling
AddInt32()
. The field is given the name
new_window_number
and its value is set to the value of
next_untitled_number
. We could add as many named fields
as we want to this message, but for now, this is all we need.
Then the reply is sent by calling the received
BMessage
's SendReply()
function. This directs the reply message to the appropriate
BHandler
object (in this case, the
HelloWindow
that sent the
WINDOW_REGISTRY_ADD
message).
Finally, the next_untitled_number
variable is incremented, so that the
next new window's number will be one greater than the previous window's.
The window count is then incremented (whether a reply was sent or not), since there's a new window registered. We always want to count up every new window, even if it's not untitled.
caseWINDOW_REGISTRY_SUB
:window_count
--; if (!window_count
) {Quit
(); } break;
If the message is a WINDOW_REGISTRY_SUB
message, the window count is
decremented, thereby indicating that one less window is open. If the
count becomes zero, the application's Quit()
function is called. This
causes the application to terminate when there aren't any open windows
left.
default:BApplication
::MessageReceived
(message
); break; } }
All other messages are routed back to the inherited BApplication
MessageReceived()
function.
Now let's look at the changes needed to the HelloWindow
class:
classHelloWindow
: publicBWindow
{ public:HelloWindow
(BRect
frame
);~HelloWindow
(); virtual boolQuitRequested
(); virtual voidMessageReceived
(BMessage
*message
); private: voidRegister
(void); voidUnregister
(void);BMenuBar
*menubar
;HelloView
*helloview
; };
The updated HelloWindow
class needs a customized destructor, so that the
window can be automatically unregistered whenever it's closed. Two
private functions are added as well: Register()
, which registers the
window with the application, and Unregister()
, which unregisters the
window.
The destructor is really simple—it just calls the
Unregister()
function:
HelloWindow
::~HelloWindow
() {Unregister
(); }
The Register()
function sends a
WINDOW_REGISTRY_ADD
message to the
HelloApp
object. To accomplish this, we need to
create a BMessenger
object to deliver messages to
the application. This is done by passing the application's signature to the
BMessenger
constructor; this creates a
BMessenger
that delivers messages to the
HelloApp
.
The WINDOW_REGISTRY_ADD
message is created on the
stack, and the need_id
argument is added to it by
calling AddBool()
. The message is then sent to the
application by calling BMessenger::SendMessage()
.
SendMessage()
lets you specify the handler to
which replies should be directed. Since we want replies sent to the
HelloWindow
object, we specify the object
(this
) as the reply handler; by default, replies are
sent to the BApplication
object, which isn't what we
want.
voidHelloWindow
::Register
(boolneed_id
) {BMessenger
messenger
(APP_SIGNATURE
);BMessage
message
(WINDOW_REGISTRY_ADD
);message
.AddBool
("need_id",need_id
);messenger
.SendMessage
(&message
,this
); }
The Unregister()
function is nearly identical,
except that it sends a WINDOW_REGISTRY_SUB
message,
and, since no replies are expected, it doesn't specify a reply handler.
voidHelloWindow
::Unregister
(void) {BMessenger
messenger
(APP_SIGNATURE
);messenger
.SendMessage
(newBMessage
(WINDOW_REGISTRY_SUB
)); }
HelloWindow
's
MessageReceived()
function needs to be augmented a
bit. We're going to implement the ,
, and
options in the menu, and we need to handle the
WINDOW_REGISTRY_ADDED
message (as you recall, this is
the reply sent by the HelloApp
object when a window
is successfully registered).
Here are the constants for the message codes we're adding support for:
constuint32MENU_FILE_NEW
= 'MFnw'; constuint32MENU_FILE_CLOSE
= 'MFcl'; constuint32MENU_FILE_QUIT
= 'MFqu';
The code is as follows:
voidHelloWindow
::MessageReceived
(BMessage
*message
) { switch(message
->what
) { caseWINDOW_REGISTRY_ADDED
: { chars
[22]; int32id
= 0; if (message
->FindInt32
("new_window_number", &id
) ==B_OK
) {sprintf
(s
, "Untitled %ld",id
);SetTitle
(s
); } } break;
When a WINDOW_REGISTRY_ADDED
message is received, we
extract the window's number from the message's
new_window_number
field by calling
FindInt32()
. This value is passed to the ANSI C
s
function to create a string to be used as
the window's title, such as "Untitled 1" or "Untitled
42". That string is then passed to the window's
printf
()SetTitle()
function to change the window's title.
This ensures that every window has a unique name.
caseMENU_FILE_NEW
: {BRect
r
;r
=Frame
();r
.OffsetBy
(20,20); newHelloWindow
(r
); } break;
The HelloWindow
, whose frame
rectangle is the same as the current window's, but offset by 20 pixels
down and to the left. This staggering effect helps keep the windows
orderly on the screen (although in real life you want to be sure the
windows don't eventually creep off the bottom or right edge of the
screen!).
caseMENU_FILE_CLOSE
:Quit
(); break;
The HelloWindow
::Quit()
function to close the window.
Because the HelloWindow
destructor calls
Unregister()
, the
window will be unregistered from the application (thereby reducing the
window count).
caseMENU_FILE_QUIT
:be_app
->PostMessage
(B_QUIT_REQUESTED
); break;
The B_QUIT_REQUESTED
message to the application
object. This causes the application to close all the windows and
terminate itself.
caseMENU_OPT_HELLO
: /* this is the same as last time */ break; default:BWindow
::MessageReceived
(message
); break; } }
The rest of the code in HelloWindow
::MessageReceived()
is unchanged since last time.
Finally, because we don't want closing the window to automatically quit
the application, we rewrite the HelloWindow
::QuitRequested()
function to
simply return true
—it's always okay to close the window. We no longer
want to request that the application quit as well:
boolHelloWindow
::QuitRequested
() { returntrue
; }
This has been a very brief overview of messaging on the BeOS. Because
messaging is so pervasive throughout the entire operating system, we'll
learn more about it as we go on. But this will give you the background
needed to follow along as we continue to explore. Try adding code to
insert more data into the registration messages, or make the
WINDOW_REGISTRY_SUB
message send a reply.
Although this sample project's messages are mostly transmitted within the
application, there's no reason you can't write two applications and send
messages back and forth between them (try this—just remember to use
the appropriate signature when creating a BMessenger
object for sending messages to the other application).
You can download the source code for this week's project >from the Be FTP site: ftp://ftp.be.com/pub/samples/intro/messageworld.zip
In about six weeks, we'll try making the HelloWindow
class do something more useful than simply display a string in a window.
We'll look at the BTextView
class and start down the
road toward turning our sample application into a text editor.
Last week, we executed the closing documents to finalize Be's acquisition of StarCode. It is a great pleasure to welcome Carlin Wiegner and his team into Be. Their pioneering spirit and their achievements have been an inspiration and, in more ways than one, that's precisely why the deal got done.
It shouldn't come as news for those (few) who read the original Be business plan that we believe in electronic distribution of software. A section of that plan explains how we'd create a dedicated Be BBS, knitting together the community of Be developers, Be customers, and the company itself. This was written in 1991, when you could buy a few phone lines, an inexpensive PC, and software from Mustang or Galacticom. With a Mac you needed a really nice product aptly called FirstClass, and you were in business. You had your own little Compuserve, your petit MoiOnLine.
There were over 10,000 BBS in the country. The reference magazine for the BBS trade was Boardwatch—still one of the best reads, nicely converted to the Internet under its erudite and opinionated editor-in-chief Jack Rickard http://www.boardwatch.com/.
These BBS systems were, ahem, scalable. The largest one linked over 100 PCs via a Novell network—very messy, but very inexpensive.
McAfee, then reigning king of anti-virus software, used such a contraption for electronic delivery of its software; as a result, its IPO prospectus listed as a major risk the fact only one individual knew how to run the thing.
So, we were going to build such an electronic community, alert our customers every week to new and improved application, utility, game, or even system software available. Developers would advertise their wares on our BBS. The weekly Newsletter would ask politely if we could deposit demo software and updates on their disk.
The word "push" hadn't been invented yet. If customers liked the sampleware, they could call the developer, order with a credit card, and receive the software electronically or via snail mail. Not very sophisticated but, at the time, it was judged a little futuristic and risky. Customers might balk at the idea of having software loaded on their machines without having full control over the process. Cookies hadn't been invented either.
Jump to 1996. Carlin works at Be as a summer intern and starts a company called StarCode, a site called BeDepot, and a product called Software Valet. BeDepot sells and delivers software over the Web. The Web is a still-evolving contraption, but a ubiquitous and inexpensive one. Software Valet sits in your BeOS system, where it downloads, installs, and registers software; it also notifies you of updates. StarCode practiced what we preached.
Two more factors made StarCode attractive to Be: the people and the e-commerce infrastructure. With this in mind, and wanting to make life easy for customers and profitable for developers, the make vs. buy analysis was fairly straightforward. Carlin, his team, and their technology provide us with a better and faster way to build a BeOS software community and, therefore, the Be platform.
Mere implementation details remain...which is a way to say we have much to do to realize the potential this reunion represents, but at least we are started on the right path.