Godzilla vs. BArchivable: Armageddon in Menlo Park
By Adam Valjean Haberlach -- <adam@be.com>

I stuck my head into a private IRC network long enough to
say "I need a title for a newsletter article" and used the
first one I could sneak past the censors. Luckily, it has
something to do with the topic of discussion.

Long ago, before I came to work at Be more than two years
back (quite a while to be working at one company here in the
valley) I decided to write a CAD program to use for personal
hardware projects. My degree is in Computer Engineering, and
I pledged to use my knowledge for good, not evil. Or at least
to make neat toys. Alas, I haven't managed to create anything
more exciting than a few Non-Maskable Interrupt cards for Be.
But the long and short of it is that I set out to write a simple
program that would allow me to draw a simple electrical diagram,
organize it, label it, and print it so that I would have it as
a reference while building hardware.

To get the basics working, I built a custom view class that
handles drawing "CadItem" objects and their children.  I used
a model similar to the BListView, in which a List contains
objects and draws them by calling their Draw() function and
passing a pointer to the view in charge of the drawing. The
neat part is that I got a file format and the tools to read
and write it for free. When it's time to save the file, my
program can simply take the entire view, archive it, and
flatten it to disk. Luckily, this can be done in reverse to
load a file.

The BArchivable protocol is fairly simple. An archivable
object must have a constructor that takes a BMessage and an
Archive member function that stores the object's data into
a BMessage. First, I had to override the default ones from
BView and make sure they worked with my CadPictureView.

We'll cover the Archive half of the process first. The function
takes a pointer to a BMessage and a flag you use only to archive
an object without storing its associated information. As you can
see, we just jam the object's member variables into the BMessage.
When it's time to store the list of CadItems, we  iterate through
the list, archive each one individually, and store their messages
within the archive.

/*********/
status_t
CadPictureView::Archive(BMessage *archive, bool deep = true)
{
    int result;
    archive->what = BECAD_DATA_MESSAGE;

    /** this is important **/
    result = BView::Archive(archive, deep);

    archive->AddBool("fGraphState", fGraphState);
    archive->AddFloat("fGridSize", fGridSize);
    archive->AddFloat("fZoomFactor", fZoomFactor);

    if(deep) {
        CadItem    *thisItem;
        BMessage *thisMessage;
        int index = fItemList.CountItems();
        while (index > 0) {
            thisItem = (CadItem *) fItemList.ItemAt(index-1);
            thisMessage = new BMessage();
            thisItem->Archive(thisMessage, true);
            archive->AddMessage("CadItems",thisMessage);
            index--;
        }
    }
    return (result);
}
/*********/

This results in a BMessage which should contain everything
we need to create a working copy of the object elsewhere.
When it's time to unpack the object, we call its constructor
with the associated BMessage, like this:

/*********/
CadPictureView::CadPictureView(BMessage *archive):
    BControl(archive)
{
    long int     index=0;
    long int     numPoints;
    BMessage     thisMessage;
    BArchivable  *thisItem;
    CadItem      *thisCadItem;

    type_code typeFound;

    archive->FindBool("fGraphState", &fGraphState);
    archive->FindFloat("fGridSize", &fGridSize);
    archive->FindFloat("fZoomFactor", &fZoomFactor);

    archive->GetInfo("CadItems", &typeFound, &index);
    while (index > 0) {
        archive->FindMessage("CadItems", index -1, &thisMessage);
        thisItem = instantiate_object(&thisMessage);
        if (thisItem) {
            thisCadItem = dynamic_cast<CadItem *>(thisItem);
            if( thisCadItem) {
                fItemList.AddItem( (void *) thisCadItem);
            } else {
                printf("thisCadItem == NULL (could not cast)\n");
            }
        } else {
            printf("thisItem == NULL (could not instantiate)\n");
            thisMessage.PrintToStream();
        }
        index--;
    }

}
/*********/

The interesting thing here is the call to "instantiate_object"
which, given a pointer to a BMessage, attempts to turn it into
an instance of a BArchivable object. This works by checking
for the name of the class that was stored in the BMessage by
the call to BArchivable::Archive() -- you do remember to call
a parent class's member functions when you override them,
right? After that, we check to see that instantiation worked,
giving some debug information if it failed. Otherwise, we
try to cast it as a CadItem and complain on failure. If
everything works correctly, we just add it to the PictureView's
list of objects. In addition to the list of objects, we pull
out the rest of the state information for the View: grid
sizing/visibility and zoom factors. Voila! We now have a
copy of the CADPicureView just as it was when we stored it.

This saved me a lot of time -- I didn't have to come up with
a flattened file format to store lists of points and other
objects. I've intentionally created it so that it can be
extended by creating other subclasses of CadItem.  I'll leave
it as an exercise for the reader to find the "Archive/Flatten"
and "Unflatten/Instantiate" calls elsewhere in the sample code.
I even threw in the beginnings of a ToolBar class for good
measure.