⛏️ index : haiku.git

#include "BitmapView.h"
#include <Alert.h>
#include <BitmapStream.h>
#include <Clipboard.h>
#include <Font.h>
#include <MenuItem.h>
#include <Entry.h>
#include <TranslationUtils.h>
#include <TranslatorRoster.h>
#include <TranslatorFormats.h>

// 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();
}