haiku/src/apps/resedit/BitmapView.cpp

687 lines
14 KiB
C++

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