#include "BitmapView.h" #include #include #include #include #include #include #include #include #include // TODO: Add support for labels #define M_REMOVE_IMAGE 'mrmi' #define M_PASTE_IMAGE 'mpsi' enum { CLIP_NONE = 0, CLIP_BEOS = 1, CLIP_SHOWIMAGE = 2, CLIP_PRODUCTIVE = 3 }; inline void SetRGBColor(rgb_color *col, uint8 r, uint8 g, uint8 b, uint8 a = 255); void SetRGBColor(rgb_color *col, uint8 r, uint8 g, uint8 b, uint8 a) { if (col) { col->red = r; col->green = g; col->blue = b; col->alpha = a; } } BitmapView::BitmapView(BRect frame, const char *name, BMessage *mod, BBitmap *bitmap, const char *label, border_style borderstyle, int32 resize, int32 flags) : BView(frame, name, resize, flags) { SetViewUIColor(B_PANEL_BACKGROUND_COLOR); if (bitmap && bitmap->IsValid()) fBitmap = bitmap; else fBitmap = NULL; if (mod) SetMessage(mod); fLabel = label; fBorderStyle = borderstyle; fFixedSize = false; fEnabled = true; fRemovableBitmap = false; fAcceptDrops = true; fAcceptPaste = true; fConstrainDrops = true; fMaxWidth = 100; fMaxHeight = 100; fPopUpMenu = new BPopUpMenu("deletepopup", false, false); fPopUpMenu->AddItem(new BMenuItem("Close This Menu", new BMessage(B_CANCEL))); fPopUpMenu->AddSeparatorItem(); fPasteItem = new BMenuItem("Paste Photo from Clipboard", new BMessage(M_PASTE_IMAGE)); fPopUpMenu->AddItem(fPasteItem); fPopUpMenu->AddSeparatorItem(); fRemoveItem = new BMenuItem("Remove Photo", new BMessage(M_REMOVE_IMAGE)); fPopUpMenu->AddItem(fRemoveItem); CalculateBitmapRect(); // Calculate the offsets for each of the words -- the phrase will be center justified fNoPhotoWidths[0] = StringWidth("Drop"); fNoPhotoWidths[1] = StringWidth("a"); fNoPhotoWidths[2] = StringWidth("Photo"); fNoPhotoWidths[3] = StringWidth("Here"); font_height fh; GetFontHeight(&fh); float totalheight = fh.ascent + fh.descent + fh.leading; float yoffset = (Bounds().Height() - 10 - (totalheight * 4)) / 2; fNoPhotoOffsets[0].Set((Bounds().Width() - fNoPhotoWidths[0]) / 2, totalheight + yoffset); fNoPhotoOffsets[1].Set((Bounds().Width() - fNoPhotoWidths[1]) / 2, fNoPhotoOffsets[0].y + totalheight); fNoPhotoOffsets[2].Set((Bounds().Width() - fNoPhotoWidths[2]) / 2, fNoPhotoOffsets[1].y + totalheight); fNoPhotoOffsets[3].Set((Bounds().Width() - fNoPhotoWidths[3]) / 2, fNoPhotoOffsets[2].y + totalheight); } BitmapView::~BitmapView(void) { delete fPopUpMenu; } void BitmapView::AttachedToWindow(void) { SetTarget((BHandler*)Window()); fPopUpMenu->SetTargetForItems(this); } void BitmapView::SetBitmap(BBitmap *bitmap) { if (bitmap && bitmap->IsValid()) { if (fBitmap == bitmap) return; fBitmap = bitmap; } else { if (!fBitmap) return; fBitmap = NULL; } CalculateBitmapRect(); if (!IsHidden()) Invalidate(); } void BitmapView::SetEnabled(bool value) { if (fEnabled != value) { fEnabled = value; Invalidate(); } } /* void BitmapView::SetLabel(const char *label) { if (fLabel.Compare(label) != 0) { fLabel = label; CalculateBitmapRect(); if (!IsHidden()) Invalidate(); } } */ void BitmapView::SetStyle(border_style style) { if (fBorderStyle != style) { fBorderStyle = style; CalculateBitmapRect(); if (!IsHidden()) Invalidate(); } } void BitmapView::SetFixedSize(bool isfixed) { if (fFixedSize != isfixed) { fFixedSize = isfixed; CalculateBitmapRect(); if (!IsHidden()) Invalidate(); } } void BitmapView::MessageReceived(BMessage *msg) { if (msg->WasDropped() && AcceptsDrops()) { // We'll handle two types of drops: those from Tracker and those from ShowImage if (msg->what == B_SIMPLE_DATA) { int32 actions; if (msg->FindInt32("be:actions", &actions) == B_OK) { // ShowImage drop. This is a negotiated drag&drop, so send a reply BMessage reply(B_COPY_TARGET), response; reply.AddString("be:types", "image/jpeg"); reply.AddString("be:types", "image/png"); msg->SendReply(&reply, &response); // now, we've gotten the response if (response.what == B_MIME_DATA) { // Obtain and translate the received data uint8 *imagedata; ssize_t datasize; // Try JPEG first if (response.FindData("image/jpeg", B_MIME_DATA, (const void **)&imagedata, &datasize) != B_OK) { // Try PNG next and piddle out if unsuccessful if (response.FindData("image/png", B_PNG_FORMAT, (const void **)&imagedata, &datasize) != B_OK) return; } // Set up to decode into memory BMemoryIO memio(imagedata, datasize); BTranslatorRoster *roster = BTranslatorRoster::Default(); BBitmapStream bstream; if (roster->Translate(&memio, NULL, NULL, &bstream, B_TRANSLATOR_BITMAP) == B_OK) { BBitmap *bmp; if (bstream.DetachBitmap(&bmp) != B_OK) return; SetBitmap(bmp); if (fConstrainDrops) ConstrainBitmap(); Invoke(); } } return; } entry_ref ref; if (msg->FindRef("refs", &ref) == B_OK) { // Tracker drop BBitmap *bmp = BTranslationUtils::GetBitmap(&ref); if (bmp) { SetBitmap(bmp); if (fConstrainDrops) ConstrainBitmap(); Invoke(); } } } return; } switch (msg->what) { case M_REMOVE_IMAGE: { BAlert *alert = new BAlert("ResEdit", "This cannot be undone. " "Remove the image?", "Remove", "Cancel"); alert->SetShortcut(1, B_ESCAPE); int32 value = alert->Go(); if (value == 0) { SetBitmap(NULL); if (Target()) { BMessenger msgr(Target()); msgr.SendMessage(new BMessage(M_BITMAP_REMOVED)); return; } } } case M_PASTE_IMAGE: { PasteBitmap(); Invoke(); } } BView::MessageReceived(msg); } void BitmapView::Draw(BRect rect) { if (fBitmap) DrawBitmap(fBitmap, fBitmap->Bounds(), fBitmapRect); else { SetHighColor(0, 0, 0, 80); SetDrawingMode(B_OP_ALPHA); DrawString("Drop", fNoPhotoOffsets[0]); DrawString("a", fNoPhotoOffsets[1]); DrawString("Photo", fNoPhotoOffsets[2]); DrawString("Here", fNoPhotoOffsets[3]); SetDrawingMode(B_OP_COPY); } if (fBorderStyle == B_FANCY_BORDER) { rgb_color base= { 216, 216, 216, 255 }; rgb_color work; SetHighColor(base); StrokeRect(Bounds().InsetByCopy(2, 2)); BeginLineArray(12); BRect r(Bounds()); work = tint_color(base, B_DARKEN_2_TINT); AddLine(r.LeftTop(), r.RightTop(), work); AddLine(r.LeftTop(), r.LeftBottom(), work); r.left++; work = tint_color(base, B_DARKEN_4_TINT); AddLine(r.RightTop(), r.RightBottom(), work); AddLine(r.LeftBottom(), r.RightBottom(), work); r.right--; r.top++; r.bottom--; work = tint_color(base, B_LIGHTEN_MAX_TINT); AddLine(r.LeftTop(), r.RightTop(), work); AddLine(r.LeftTop(), r.LeftBottom(), work); r.left++; work = tint_color(base, B_DARKEN_3_TINT); AddLine(r.RightTop(), r.RightBottom(), work); AddLine(r.LeftBottom(), r.RightBottom(), work); // this rect handled by the above StrokeRect, so inset a total of 2 pixels r.left++; r.right -= 2; r.top += 2; r.bottom -= 2; work = tint_color(base, B_DARKEN_3_TINT); AddLine(r.LeftTop(), r.RightTop(), work); AddLine(r.LeftTop(), r.LeftBottom(), work); r.left++; work = tint_color(base, B_LIGHTEN_MAX_TINT); AddLine(r.RightTop(), r.RightBottom(), work); AddLine(r.LeftBottom(), r.RightBottom(), work); r.right--; r.top++; r.bottom--; EndLineArray(); SetHighColor(tint_color(base, B_DARKEN_2_TINT)); StrokeRect(r); } else { // Plain border SetHighColor(0, 0, 0); StrokeRect(fBitmapRect); } } void BitmapView::MouseDown(BPoint pt) { BPoint mousept; uint32 buttons; GetMouse(&mousept, &buttons); if (buttons & B_SECONDARY_MOUSE_BUTTON) { ConvertToScreen(&mousept); mousept.x= (mousept.x>5) ? mousept.x-5 : 0; mousept.y= (mousept.y>5) ? mousept.y-5 : 0; if (AcceptsPaste() && ClipboardHasBitmap()) fPasteItem->SetEnabled(true); else fPasteItem->SetEnabled(false); if (fRemovableBitmap && fBitmap) fRemoveItem->SetEnabled(true); else fRemoveItem->SetEnabled(false); fPopUpMenu->Go(mousept, true, true, true); } } void BitmapView::FrameResized(float w, float h) { CalculateBitmapRect(); } void BitmapView::CalculateBitmapRect(void) { if (!fBitmap || fFixedSize) { fBitmapRect = Bounds().InsetByCopy(1, 1); return; } uint8 borderwidth = (fBorderStyle == B_FANCY_BORDER) ? 5 : 1; BRect r(Bounds()); fBitmapRect= ScaleRectToFit(fBitmap->Bounds(), r.InsetByCopy(borderwidth, borderwidth)); } void BitmapView::SetAcceptDrops(bool accept) { fAcceptDrops = accept; } void BitmapView::SetAcceptPaste(bool accept) { fAcceptPaste = accept; } void BitmapView::SetConstrainDrops(bool value) { fConstrainDrops = value; } void BitmapView::MaxBitmapSize(float *width, float *height) const { *width = fMaxWidth; *height = fMaxHeight; } void BitmapView::SetMaxBitmapSize(const float &width, const float &height) { fMaxWidth = width; fMaxHeight = height; ConstrainBitmap(); } void BitmapView::SetBitmapRemovable(bool isremovable) { fRemovableBitmap = isremovable; } void BitmapView::ConstrainBitmap(void) { if (!fBitmap || fMaxWidth < 1 || fMaxHeight < 1) return; BRect r = ScaleRectToFit(fBitmap->Bounds(), BRect(0, 0, fMaxWidth - 1, fMaxHeight - 1)); r.OffsetTo(0, 0); BBitmap *scaled = new BBitmap(r, fBitmap->ColorSpace(), true); BView *view = new BView(r, "drawview", 0, 0); scaled->Lock(); scaled->AddChild(view); view->DrawBitmap(fBitmap, fBitmap->Bounds(), scaled->Bounds()); scaled->Unlock(); delete fBitmap; fBitmap = new BBitmap(scaled, false); } bool BitmapView::ClipboardHasBitmap(void) { BMessage *clip = NULL, flattened; uint8 clipval = CLIP_NONE; bool returnval; if (be_clipboard->Lock()) { clip = be_clipboard->Data(); if (!clip->IsEmpty()) { returnval = (clip->FindMessage("image/bitmap", &flattened) == B_OK); if (returnval) clipval = CLIP_BEOS; else { BString string; returnval = (clip->FindString("class", &string) == B_OK && string == "BBitmap"); // Try method Gobe Productive uses if that, too, didn't work if (returnval) clipval = CLIP_SHOWIMAGE; else { returnval = (clip->FindMessage("image/x-vnd.Be-bitmap", &flattened) == B_OK); if (returnval) clipval = CLIP_SHOWIMAGE; else clipval = CLIP_NONE; } } } be_clipboard->Unlock(); } return (clipval != CLIP_NONE)?true:false; } BBitmap * BitmapView::BitmapFromClipboard(void) { BMessage *clip = NULL, flattened; BBitmap *bitmap; if (!be_clipboard->Lock()) return NULL; clip = be_clipboard->Data(); if (!clip) return NULL; uint8 clipval = CLIP_NONE; // Try ArtPaint-style storage status_t status = clip->FindMessage("image/bitmap", &flattened); // If that didn't work, try ShowImage-style if (status != B_OK) { BString string; status = clip->FindString("class", &string); // Try method Gobe Productive uses if that, too, didn't work if (status == B_OK && string == "BBitmap") clipval = CLIP_SHOWIMAGE; else { status = clip->FindMessage("image/x-vnd.Be-bitmap", &flattened); if (status == B_OK) clipval = CLIP_PRODUCTIVE; else clipval = CLIP_NONE; } } else clipval = CLIP_BEOS; be_clipboard->Unlock(); switch (clipval) { case CLIP_SHOWIMAGE: { // Showimage does it a slightly different way -- it dumps the BBitmap // data directly to the clipboard message instead of packaging it in // a bitmap like everyone else. if (!be_clipboard->Lock()) return NULL; BMessage datamsg(*be_clipboard->Data()); be_clipboard->Unlock(); const void *buffer; ssize_t bufferLength; BRect frame; color_space cspace = B_NO_COLOR_SPACE; status = datamsg.FindRect("_frame", &frame); if (status != B_OK) return NULL; status = datamsg.FindInt32("_cspace", (int32)cspace); if (status != B_OK) return NULL; cspace = B_RGBA32; bitmap = new BBitmap(frame, cspace, true); status = datamsg.FindData("_data", B_RAW_TYPE, (const void **)&buffer, &bufferLength); if (status != B_OK) { delete bitmap; return NULL; } memcpy(bitmap->Bits(), buffer, bufferLength); return bitmap; } case CLIP_PRODUCTIVE: // Productive doesn't name the packaged BBitmap data message the same, but // uses exactly the same data format. case CLIP_BEOS: { const void *buffer; ssize_t bufferLength; BRect frame; color_space cspace = B_NO_COLOR_SPACE; status = flattened.FindRect("_frame", &frame); if (status != B_OK) return NULL; status = flattened.FindInt32("_cspace", (int32)cspace); if (status != B_OK) return NULL; cspace = B_RGBA32; bitmap = new BBitmap(frame, cspace, true); status = flattened.FindData("_data", B_RAW_TYPE, (const void **)&buffer, &bufferLength); if (status != B_OK) { delete bitmap; return NULL; } memcpy(bitmap->Bits(), buffer, bufferLength); return bitmap; } default: return NULL; } // shut the compiler up return NULL; } BRect ScaleRectToFit(const BRect &from, const BRect &to) { // Dynamic sizing algorithm // 1) Check to see if either dimension is bigger than the view's display area // 2) If smaller along both axes, make bitmap rect centered and return // 3) Check to see if scaling is to be horizontal or vertical on basis of longer axis // 4) Calculate scaling factor // 5) Scale both axes down by scaling factor, accounting for border width // 6) Center the rectangle in the direction of the smaller axis if (!to.IsValid()) return from; if (!from.IsValid()) return to; BRect r(to); if ((from.Width() <= r.Width()) && (from.Height() <= r.Height())) { // Smaller than view, so just center and return r = from; r.OffsetBy((to.Width() - r.Width()) / 2, (to.Height() - r.Height()) / 2); return r; } float multiplier = from.Width()/from.Height(); if (multiplier > 1) { // Landscape orientation // Scale rectangle to bounds width and center height r.bottom = r.top + (r.Width() / multiplier); r.OffsetBy(0, (to.Height() - r.Height()) / 2); } else { // Portrait orientation // Scale rectangle to bounds height and center width r.right = r.left + (r.Height() * multiplier); r.OffsetBy((to.Width() - r.Width()) / 2, 0); } return r; } void BitmapView::RemoveBitmap(void) { SetBitmap(NULL); } void BitmapView::PasteBitmap(void) { BBitmap *bmp = BitmapFromClipboard(); if (bmp) SetBitmap(bmp); if (fConstrainDrops) ConstrainBitmap(); }