TransRepliShow: Dragging Replicants Transparently

Body

This article follows up on Dr. Reh's previous newsletter article, RepliShow: A Replicable Image Viewer. The code discussed is available here for your convenience.

Just remember Seth Flaxman's RepliShow: While dragging the replicant you only see the border lines of an empty rectangle - not very Be-like. However, only a few lines of code are necessary to obtain a rectangle containing the dragged image and looking transparently. Because the dragging action is managed by the BDragger class we need to do some subclassing. We create a new RepliDragger class inherited from BDragger. When this is done we override the hook function virtual void MouseDown( BPoint where ):

void RepliDragger ::MouseDown( BPoint where )                            // mouse down
{
BPoint cursor;
uint32 buttons;

SetMouseEventMask( B_POINTER_EVENTS, 0 ); // view can receive mouse-events
GetMouse(&cursor,&buttons);

if ( (fBitmap != NULL) && (fArchive != NULL) && (buttons & B_PRIMARY_MOUSE_BUTTON) )
{
BPoint origBitmap;
BRect bitmapRect = fBitmap -> Bounds(); // get boundaries of image
BBitmap *bitmap = new BBitmap(bitmapRect, B_RGB32,true); // create bitmap for drag-and-drop
memcpy(bitmap->Bits(), fBitmap->Bits(), fBitmap->BitsLength()); // copy bitmap
origBitmap = BPoint(bitmapRect.Width(),bitmapRect.Height()); // position relative to bitmap
origBitmap = origBitmap - BPoint(7-where.x, 7-where.y);
DragMessage( fArchive, bitmap, B_OP_BLEND, origBitmap, NULL);
}

BDragger :: MouseDown(where); // BDragger mouse down events
}

void DragMessage(BMessage *message, BBitmap *image, drawing_mode dragMode, BPoint offset, replyTarget = NULL) is responsible for the drag-and-drop handling. message is a BMessage object containing all information that will be dragged and dropped to the BShelf container view. In our special case the message is fArchive, our archived view. Using the dragMode B_OP_BLEND lets you drag the BBitmap image transparently around. Because the bitmap is automatically freed when the message is dropped, we need to duplicate the original bitmap. BPoint offset locates the hot spot within the image (in the bitmap's coordinate system). This is the point that's aligned with the location passed to MouseDown(). And last but not least we call BDragger :: MouseDown(where) to handle all remaining mouse-down events, e.g. BDragger's pop-up menu.

A small problem is arising: first we call DragMessage() and then BDragger::MouseDown(). However, MouseDown() is responsible for initiating the Archive(BMessage *archive, bool deep) procedure, therefore DragMessage() is missing the archived view. We must guarantee that the view is archived before we call DragMessage() - we need a valid fArchive and a valid fBitmap.

void RepliDragger :: GetBitmap(BBitmap *bitmap)					
{
fBitmap = bitmap;
}

void RepliDragger :: GetArchive(BMessage *archive)
{
fArchive = archive;
}

Every time we are going to create a replicant out of our view, this view has to call GetArchive(), or GetBitmap() when changing our image. Then RepliDragger will be equipped with a valid pointer to perform DragMessage().

This was the main work to do, excepting some small modification left to TransRepliView.

The TransRepliView class holds two constructors. Both must create the RepliDragger class using CreateDragger() - we have done subclassing and are not able to archive the new RepliDragger class. Then we simulate a dragged replicant by archiving our view, setting the archive message's command to B_ARCHIVED_OBJECT, and sending the message to a remote application using the above mentioned DragMessage() function. If the remote application has a BShelf object, the BShelf will pick up the message (through a BMessageFilter) and pass it to the hook function. Of course we must inform RepliDragger about the newly archived view: fRepliDragger -> GetArchive(&fArchive).

TransRepliView :: TransRepliView(BRect frame) 
: BView(frame, "TransRepliShowView", B_FOLLOW_NONE, B_WILL_DRAW)
{
fReplicated = false;
fBitmap = NULL;
CreateDragger();
}

TransRepliView :: TransRepliView(BMessage *archive)
: BView(archive)
{
fReplicated = true;
fBitmap = new BBitmap(archive);
CreateDragger();

fArchive = BMessage( B_ARCHIVED_OBJECT);
Archive(&fArchive,true);
fRepliDragger -> GetArchive(&fArchive);
}

CreateDragger() is responsible for the new RepliDragger() class and telling the class about a new bitmap.

void TransRepliView :: CreateDragger()
{
BRect frame = Bounds();
frame.left = frame.right - 7;
frame.top = frame.bottom - 7;

fRepliDragger = new RepliDragger(frame, this);
AddChild(fRepliDragger);
fRepliDragger -> GetBitmap(fBitmap);
}

When a new image was dropped onto the view we must tell the RepliDragger about this, so the MessageReceived() hook function needs four additional lines of code when handling B_SIMPLE_DATA:

    ...
fArchive = BMessage( B_ARCHIVED_OBJECT);
Archive(&fArchive,true);
fRepliDragger -> GetArchive(&fArchive);
fRepliDragger -> GetBitmap(fBitmap);

Finally a snapshot showing the fearless leader floating transparently over the desktop:

Fearless Leader