687 lines
14 KiB
C++
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();
|
|
}
|