RepliShow: A Replicable Image Viewer

Body

The original version of RepliShow was written by Seth Flaxman, and has been modified by Dr. Hartmut Reh. The source discussed in this article can be found here.

Today we'll be constructing a rather primitive application that displays images and is replicable. It will demonstrate two things: BeOS replicant technology and the Translation Kit.

Replicant technology is a standard part of BeOS, although it has not been widely used by many applications. You can see replicants in place in NetPositive and the Clock application that both ship with BeOS. You can tell something is replicable when it has a little dragger somewhere in its view, as shown below.

Figure 1: Replicant Dragger
Figure 1: Replicant Dragger


When you click and drag the dragger to the desktop, the NetPositive view become embedded in the Desktop. In addition, the Container application (found in R5's Application Kit sample code folder) also serves as a repository for replicants.

Figure 2: Container
Figure 2: Container


If you don't see the little dragger in the Clock or NetPositive view, replicants are probably turned off. Select "Show Replicants" from the Be Menu to turn them back on.

Figure 3: Show Replicants
Figure 3: Show Replicants


In BeOS, anything that is derived from BView is replicable. This means that you cannot replicate BWindows. When designing an app that is going to be replicable, remember to put all the important code in your view, because your window will not be present when your view is embedded in the Desktop.

To make a view replicable in BeOS you need to implement three functions:

  1. A constructor that takes a BMessage as its only argument
  2. An instantiate function
  3. An archive function

Strictly speaking, the constructor is not nescessary, but putting it in is good form. In addition to these functions, all replicable views need to be exported. To export a view, put a line that looks like this above it:

class _EXPORT ViewName

...and at least a resource file containing the unique signature is necessary.

As I mentioned before, if a view is going to be replicable all the work needs to be done in the view, not the window. Thus, our window will only have two functions: a Constructor() and a QuitRequested() function. Without the QuitRequested() function, the application would not quit when we close the window, because the QuitRequested() function is responsible for telling the application to quit when the window gets closed.

Now, let's get to the code. We need to write code containing RepliApp (derived from BApplication), RepliWindow (derived from BWindow) and RepliView (derived from BView).

Figure 4: RepliShow Project
Figure 4: RepliShow Project


Inside RepliApp.cpp you'll find the main() function. The main function is where our program starts, so in it we must construct an application object and tell it to run. BApplication :: Run() does not return until the application is ready to quit.

RepliApp :: RepliApp()
: BApplication("application/x-vnd.BeNews-RepliShow")
{
RepliWindow *repliWindow = new RepliWindow();
repliWindow -> Show();
}

int main()
{
RepliApp theApplication;
theApplication.Run();
return (0);
}

Everything here should be straightforward. We create the RepliApp with a signature that uniquely describes it. Next, we create the RepliWindow and show it.

RepliWindow :: RepliWindow()
: BWindow(BRect(100,100,350,350), "RepliShowWindow",
B_TITLED_WINDOW, B_NOT_RESIZABLE | B_NOT_ZOOMABLE)
{
RepliView *replView = new RepliView(Bounds());
AddChild(replView);
}

bool RepliWindow::QuitRequested()
{
be_app->PostMessage(B_QUIT_REQUESTED);
return true;
}

The RepliWindow constructor passes four arguments along to BWindow: the window frame, title, type, and flags. Type tells BeOS how the window should look: whether it should be floating, should be modal, should have a border, etc. Lastly, the flag tells how the window should be resizable. In our case, we have set the window to be neither resizable nor zoomable.

Now, let's look at the RepliView class:


RepliView :: RepliView(BRect frame)
: BView(frame, "RepliShowView", B_FOLLOW_NONE, B_WILL_DRAW)
{
fBitmap = NULL;
fReplicated = false;

frame.left = frame.right - 7;
frame.top = frame.bottom - 7;
BDragger *dragger = new BDragger(frame, this, B_FOLLOW_RIGHT | B_FOLLOW_BOTTOM);
AddChild(dragger);
}

RepliView :: RepliView(BMessage *archive)
: BView(archive)
{
fReplicated = true;
fBitmap = new BBitmap(archive);
}

RepliView :: ~RepliView()
{
delete fBitmap;
}

Our first three functions are two constructors and one destructor. The first of our two constructors is the one that is used when we create the view manually. The second constructor takes a BMessage and is used when the view is replicated. The BMessage returns data that we stuffed into it in our archive function. The first constructor sets fBitmap to NULL and creates a BDragger. The frame of the BDragger is currently hardcoded to 8 pixels by 8 pixels. Last, we set fReplicated to false. We use fReplicated in our MessageReceived() function when we decide whether to resize the view or to resize the window. The replicant constructor attempts to create a BBitmap from the data stored in the message. This is called unarchiving the BBitmap. BBitmap takes a BMessage as one of its constructors and looks in the BMessage for the data we would usually pass to the constructor: the colorspace, the frame, its contents, etc. The replicant constructor of RepliView is much like the replicant constructor of the BBitmap. It unarchives the view. Many replicants store other things like what text was being displayed in the message and then unarchive this information.

void RepliView::Draw(BRect)
{
if(fBitmap) DrawBitmap(fBitmap, B_ORIGIN);
}

The Draw() function draws the bitmap on the view.

void RepliView :: MessageReceived(BMessage *msg)
{
switch(msg->what)
{
case B_SIMPLE_DATA:
{
entry_ref ref;
msg->FindRef("refs", &ref);
BEntry entry(&ref);
BPath path(&entry);

delete fBitmap;
fBitmap = BTranslationUtils::GetBitmap(path.Path());

if(fBitmap != NULL)
{
BRect rect = fBitmap->Bounds();
if(!fReplicated) Window() -> ResizeTo(rect.right, rect.bottom);
ResizeTo(rect.right, rect.bottom);
Invalidate();
}
}
break;

case B_ABOUT_REQUESTED:
{
AboutRequested();
}
break;

default:
BView::MessageReceived(msg);
break;
}
}

In the MessageReceived() function we look for a message with what type B_SIMPLE_DATA. We get that message when a user drops an image on the view. If we find the message, we look for an entry_ref named "refs" inside the message. Next, we convert that ref to a BPath and pass that path along to a static function in the BTranslationUtils class. Before calling this function we delete the bitmap so that we don't leave memory lying around.

BTranslationUtils :: GetBitmap() is a convenience function that takes a path to a file and attempts to find a translator that knows to handle that file. If it finds a translator that can handle the file it returns the newly created bitmap, otherwise it returns NULL. If we get a bitmap from GetBitmap() we check to see if we are replicated or if we are in a regular BWindow. If replicated, we resize the view. Otherwise, we resize the window and then we resize the view. Last we call Invalidate() to make sure the new BBitmap gets drawn.

A further message the RepliView is able to handle is the what type B_ABOUT_REQUESTED. This message is sent when clicking the little dragger using the right mouse button. We get a pop-up menu offering to delete the replicant or to obtain informations about the RepliShow.

Figure 5: Replicant Pop-Up Menu
Figure 5: Replicant Pop-Up Menu


If B_SIMPLE_DATA and B_ABOUT_REQUESTED are not the what type of the BMessage we pass the message along to BView to handle.

The function AboutRequested() is responsible for displaying a BAlert() containing the requiered informations.

void RepliView  :: AboutRequested()
{
BAlert *alert = new BAlert("", "RepliShow from Seth Flaxman", "OK");
alert->Go();
}
Figure 6: B_ABOUT_REQUESTED
Figure 6: B_ABOUT_REQUESTED


status_t RepliView :: Archive(BMessage *archive, bool deep) const
{
BView :: Archive(archive, deep);
archive -> AddString("add_on", "application/x-vnd.BeNews-RepliShow");
archive -> AddString("class", "RepliShow");

if(fBitmap)
{
fBitmap->Lock();
fBitmap->Archive(archive);
fBitmap->Unlock();
}
// archive -> PrintToStream();
return B_OK;
}

BArchivable *RepliView :: Instantiate(BMessage *data)
{
return new RepliView(data);
}

The Instantiate() and Archive() functions handle instantiating the view after it has been archived and archiving the view (so that it can be instantiated later), respectively. In more understandable terms, the Instantiate() function returns a copy of the RepliView class, constructed with the constructor that takes a BMessage. The Archive() function is the function that adds to that BMessage. First, we pass the BMessage along to our parent view so it can add stuff to the message. Next, we add a string containing "add_on", which holds the application's signature, and then a string containing "class", which holds the name of the class. Last, we lock the bitmap and tell it to archive itself in the message. The archived BBitmap is added to the archived view now containing all information.

Let's have a look at the archive using the PrintToStream() function:

BMessage: what = ARCV (0x41524356, or 1095910230)
entry class, type='CSTR', c=2, size=10, data[0]: "RepliView"
size=10, data[1]: "RepliShow"
entry _name, type='CSTR', c=1, size=14, data[0]: "RepliShowView"
entry _frame, type='RECT', c=1, size=16, data[0]: BRect(l:0.0, t:0.0, r:250.0, b:250.0)
entry _flags, type='LONG', c=1, size= 4, data[0]: 0x20000000 (536870912, ' ')
entry _views, type='MSGG', c=1, size= 0,
entry add_on, type='CSTR', c=1, size=35, data[0]: "application/x-vnd.BeNews-RepliShow"

The archive contains three classes:

  • "RepliView" - the view class name of the RepliShow application,
  • "RepliShow" - defined in our Archive() function (AddString...).
  • "BBitmap" - the bitmap

The entry_name is "RepliShowView", the name of the class "RepliView".

At the end of the archive we find the raw bitmap data.

When creating your own replicant applications, remember to create a resource file for the application. If the application does not link against a resource file, it will not know its signature and when Tracker or Container tries to replicate it, neither one will be able to find your application.

Figure 7: Resource File
Figure 7: Resource File


Well, now the application should work. We can drop an image on it, when it is in regular mode, then replicate it and the image will still be there--even after quitting the RepliShow application. Also, we can drop an image on it in replicant mode, then reboot (or if the replicant is part of the Tracker, just restart the Tracker) and the image will still be there. Pretty neat, huh? My hope is that this tutorial will serve as a starting point for enterprising developers out there who want to use replicant technology to do cool stuff. With that in mind, below is a list of some ideas for other applications that could use replicants. In addition, I hope that all reading this will post their own ideas in the comments section. Replicants can be fun, but there are lots of practical uses for them, too. Without further ado, here's my quick list of ideas:

  • Expand RepliShow so that it connects to someone else's computer running RepliShow and sends it the image that was just dropped, then that computer displays the image.
  • Make RepliShow into a whiteboard type application where a user on one side sketches their ideas, then they are sent over the internet and displayed on someone else's computer. With replicants you could have five people giving you their ideas at once, each one in a different box that's stuck to your Desktop.
  • Make RepliShow fetch a cartoon (using FetchUrl) and display that cartoon on the Desktop.

Final Remarks

When developing your replicant it's a good idea to test its behaviour using the ShelfInspector application and setting the drop zone to the Container window instead of to the Desktop window. To remove all obsolete data simply delete the file Container_data located in /boot/home/config/settings/.

If you are working with the plain Desktop window as a shelf view you will not immediately see changes made to your replicant during the development phase. The Tracker rejects changes made to the replicant view because the add-on/library defining the replicant is still loaded; however, changes are updated after restarting the Tracker. If you are still running into problems remove the file tracker_shelf (from /boot/home/config/settings/)--all Tracker-replicants will be removed.

Figure 8: Helper Apps
Figure 8: Helper Apps


If you intend to develop replicants more comfortably, use the extended versions of the above mentioned programs.

Stay tuned for the following issues!

Teaser