haiku/src/apps/people/PictureView.cpp

648 lines
13 KiB
C++

/*
* Copyright 2011, Haiku.
* Distributed under the terms of the MIT License.
*
* Authors:
* Philippe Houdoin
*/
#include "PictureView.h"
#include <math.h>
#include <new>
#include <stdio.h>
#include <Alert.h>
#include <Bitmap.h>
#include <BitmapStream.h>
#include <Catalog.h>
#include <Clipboard.h>
#include <Directory.h>
#include <File.h>
#include <FilePanel.h>
#include <IconUtils.h>
#include <LayoutUtils.h>
#include <PopUpMenu.h>
#include <DataIO.h>
#include <MenuItem.h>
#include <Messenger.h>
#include <MimeType.h>
#include <NodeInfo.h>
#include <String.h>
#include <TranslatorRoster.h>
#include <TranslationUtils.h>
#include <Window.h>
#include "PeopleApp.h" // for B_PERSON_MIMETYPE
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "People"
const uint32 kMsgPopUpMenuClosed = 'pmcl';
class PopUpMenu : public BPopUpMenu {
public:
PopUpMenu(const char* name, BMessenger target);
virtual ~PopUpMenu();
private:
BMessenger fTarget;
};
PopUpMenu::PopUpMenu(const char* name, BMessenger target)
:
BPopUpMenu(name, false, false), fTarget(target)
{
SetAsyncAutoDestruct(true);
}
PopUpMenu::~PopUpMenu()
{
fTarget.SendMessage(kMsgPopUpMenuClosed);
}
// #pragma mark -
using std::nothrow;
const float kPictureMargin = 6.0;
PictureView::PictureView(float width, float height, const entry_ref* ref)
:
BView("pictureview", B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE | B_NAVIGABLE),
fPicture(NULL),
fOriginalPicture(NULL),
fDefaultPicture(NULL),
fShowingPopUpMenu(false),
fPictureType(0),
fFocusChanging(false),
fOpenPanel(new BFilePanel(B_OPEN_PANEL))
{
SetViewColor(255, 255, 255);
SetToolTip(B_TRANSLATE(
"Drop an image here,\n"
"or use the contextual menu."));
BSize size(width + 2 * kPictureMargin, height + 2 * kPictureMargin);
SetExplicitMinSize(size);
SetExplicitMaxSize(size);
BMimeType mime(B_PERSON_MIMETYPE);
uint8* iconData;
size_t iconDataSize;
if (mime.GetIcon(&iconData, &iconDataSize) == B_OK) {
float size = width < height ? width : height;
fDefaultPicture = new BBitmap(BRect(0, 0, size, size),
B_RGB32);
if (fDefaultPicture->InitCheck() != B_OK
|| BIconUtils::GetVectorIcon(iconData, iconDataSize,
fDefaultPicture) != B_OK) {
delete fDefaultPicture;
fDefaultPicture = NULL;
}
}
Update(ref);
}
PictureView::~PictureView()
{
delete fDefaultPicture;
delete fPicture;
if (fOriginalPicture != fPicture)
delete fOriginalPicture;
delete fOpenPanel;
}
bool
PictureView::HasChanged()
{
return fPicture != fOriginalPicture;
}
void
PictureView::Revert()
{
if (!HasChanged())
return;
_SetPicture(fOriginalPicture);
}
void
PictureView::Update()
{
if (fOriginalPicture != fPicture) {
delete fOriginalPicture;
fOriginalPicture = fPicture;
}
}
void
PictureView::Update(const entry_ref* ref)
{
// Don't update when user has modified the picture
if (HasChanged())
return;
if (_LoadPicture(ref) == B_OK) {
delete fOriginalPicture;
fOriginalPicture = fPicture;
}
}
BBitmap*
PictureView::Bitmap()
{
return fPicture;
}
uint32
PictureView::SuggestedType()
{
return fPictureType;
}
const char*
PictureView::SuggestedMIMEType()
{
if (fPictureMIMEType == "")
return NULL;
return fPictureMIMEType.String();
}
void
PictureView::MessageReceived(BMessage* message)
{
switch (message->what) {
case B_REFS_RECEIVED:
case B_SIMPLE_DATA:
{
entry_ref ref;
if (message->FindRef("refs", &ref) == B_OK
&& _LoadPicture(&ref) == B_OK)
MakeFocus(true);
else
_HandleDrop(message);
break;
}
case B_MIME_DATA:
// TODO
break;
case B_COPY_TARGET:
_HandleDrop(message);
break;
case B_PASTE:
{
if (be_clipboard->Lock() != B_OK)
break;
BMessage* data = be_clipboard->Data();
BMessage archivedBitmap;
if (data->FindMessage("image/bitmap", &archivedBitmap) == B_OK) {
BBitmap* picture = new(std::nothrow) BBitmap(&archivedBitmap);
_SetPicture(picture);
}
be_clipboard->Unlock();
break;
}
case B_DELETE:
case B_TRASH_TARGET:
_SetPicture(NULL);
break;
case kMsgLoadImage:
fOpenPanel->SetTarget(BMessenger(this));
fOpenPanel->Show();
break;
case kMsgPopUpMenuClosed:
fShowingPopUpMenu = false;
break;
default:
BView::MessageReceived(message);
break;
}
}
void
PictureView::Draw(BRect updateRect)
{
BRect rect = Bounds();
// Draw the outer frame
rgb_color base = ui_color(B_PANEL_BACKGROUND_COLOR);
if (IsFocus() && Window() && Window()->IsActive())
SetHighColor(ui_color(B_KEYBOARD_NAVIGATION_COLOR));
else
SetHighColor(tint_color(base, B_DARKEN_3_TINT));
StrokeRect(rect);
if (fFocusChanging) {
// focus frame is already redraw, stop here
return;
}
BBitmap* picture = fPicture ? fPicture : fDefaultPicture;
if (picture != NULL) {
// scale to fit and center picture in frame
BRect frame = rect.InsetByCopy(kPictureMargin, kPictureMargin);
BRect srcRect = picture->Bounds();
BSize size = frame.Size();
float pictureAspect = srcRect.Height() / srcRect.Width();
float frameAspect = size.height / size.width;
if (pictureAspect > frameAspect)
size.width = srcRect.Width() * size.height / srcRect.Height();
else if (pictureAspect < frameAspect)
size.height = srcRect.Height() * size.width / srcRect.Width();
fPictureRect = BLayoutUtils::AlignInFrame(frame, size,
BAlignment(B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER));
SetDrawingMode(B_OP_ALPHA);
if (picture == fDefaultPicture) {
SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_OVERLAY);
SetHighColor(0, 0, 0, 24);
}
DrawBitmapAsync(picture, srcRect, fPictureRect,
B_FILTER_BITMAP_BILINEAR);
SetDrawingMode(B_OP_OVER);
}
}
void
PictureView::WindowActivated(bool active)
{
BView::WindowActivated(active);
if (IsFocus())
Invalidate();
}
void
PictureView::MakeFocus(bool focused)
{
if (focused == IsFocus())
return;
BView::MakeFocus(focused);
if (Window()) {
fFocusChanging = true;
Invalidate();
Flush();
fFocusChanging = false;
}
}
void
PictureView::MouseDown(BPoint position)
{
MakeFocus(true);
uint32 buttons = 0;
if (Window() != NULL && Window()->CurrentMessage() != NULL)
buttons = Window()->CurrentMessage()->FindInt32("buttons");
if (fPicture != NULL && fPictureRect.Contains(position)
&& (buttons
& (B_PRIMARY_MOUSE_BUTTON | B_SECONDARY_MOUSE_BUTTON)) != 0) {
_BeginDrag(position);
} else if (buttons == B_SECONDARY_MOUSE_BUTTON)
_ShowPopUpMenu(ConvertToScreen(position));
}
void
PictureView::KeyDown(const char* bytes, int32 numBytes)
{
if (numBytes != 1) {
BView::KeyDown(bytes, numBytes);
return;
}
switch (*bytes) {
case B_DELETE:
_SetPicture(NULL);
break;
default:
BView::KeyDown(bytes, numBytes);
break;
}
}
// #pragma mark -
void
PictureView::_ShowPopUpMenu(BPoint screen)
{
if (fShowingPopUpMenu)
return;
PopUpMenu* menu = new PopUpMenu("PopUpMenu", this);
BMenuItem* item = new BMenuItem(B_TRANSLATE("Load image" B_UTF8_ELLIPSIS),
new BMessage(kMsgLoadImage));
menu->AddItem(item);
item = new BMenuItem(B_TRANSLATE("Remove image"), new BMessage(B_DELETE));
item->SetEnabled(fPicture != NULL);
menu->AddItem(item);
menu->SetTargetForItems(this);
menu->Go(screen, true, true, true);
fShowingPopUpMenu = true;
}
BBitmap*
PictureView::_CopyPicture(uint8 alpha)
{
bool hasAlpha = alpha != 255;
if (!fPicture)
return NULL;
BRect rect = fPictureRect.OffsetToCopy(B_ORIGIN);
BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW);
BBitmap* bitmap = new(nothrow) BBitmap(rect, hasAlpha ? B_RGBA32
: fPicture->ColorSpace(), true);
if (bitmap == NULL || !bitmap->IsValid()) {
delete bitmap;
return NULL;
}
if (bitmap->Lock()) {
bitmap->AddChild(&view);
if (hasAlpha) {
view.SetHighColor(0, 0, 0, 0);
view.FillRect(rect);
view.SetDrawingMode(B_OP_ALPHA);
view.SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE);
view.SetHighColor(0, 0, 0, alpha);
}
view.DrawBitmap(fPicture, fPicture->Bounds().OffsetToCopy(B_ORIGIN),
rect, B_FILTER_BITMAP_BILINEAR);
view.Sync();
bitmap->RemoveChild(&view);
bitmap->Unlock();
}
return bitmap;
}
void
PictureView::_BeginDrag(BPoint sourcePoint)
{
BBitmap* bitmap = _CopyPicture(128);
if (bitmap == NULL)
return;
// fill the drag message
BMessage drag(B_SIMPLE_DATA);
drag.AddInt32("be:actions", B_COPY_TARGET);
drag.AddInt32("be:actions", B_TRASH_TARGET);
// name the clip after person name, if any
BString name = B_TRANSLATE("%name% picture");
name.ReplaceFirst("%name%", Window() ? Window()->Title() :
B_TRANSLATE("Unnamed person"));
drag.AddString("be:clip_name", name.String());
BTranslatorRoster* roster = BTranslatorRoster::Default();
if (roster == NULL) {
delete bitmap;
return;
}
int32 infoCount;
translator_info* info;
BBitmapStream stream(bitmap);
if (roster->GetTranslators(&stream, NULL, &info, &infoCount) == B_OK) {
for (int32 i = 0; i < infoCount; i++) {
const translation_format* formats;
int32 count;
roster->GetOutputFormats(info[i].translator, &formats, &count);
for (int32 j = 0; j < count; j++) {
if (strcmp(formats[j].MIME, "image/x-be-bitmap") != 0) {
// needed to send data in message
drag.AddString("be:types", formats[j].MIME);
// needed to pass data via file
drag.AddString("be:filetypes", formats[j].MIME);
drag.AddString("be:type_descriptions", formats[j].name);
}
}
}
}
stream.DetachBitmap(&bitmap);
// we also support "Passing Data via File" protocol
drag.AddString("be:types", B_FILE_MIME_TYPE);
sourcePoint -= fPictureRect.LeftTop();
SetMouseEventMask(B_POINTER_EVENTS);
DragMessage(&drag, bitmap, B_OP_ALPHA, sourcePoint);
bitmap = NULL;
}
void
PictureView::_HandleDrop(BMessage* msg)
{
entry_ref dirRef;
BString name, type;
bool saveToFile = msg->FindString("be:filetypes", &type) == B_OK
&& msg->FindRef("directory", &dirRef) == B_OK
&& msg->FindString("name", &name) == B_OK;
bool sendInMessage = !saveToFile
&& msg->FindString("be:types", &type) == B_OK;
if (!sendInMessage && !saveToFile)
return;
BBitmap* bitmap = fPicture;
if (bitmap == NULL)
return;
BTranslatorRoster* roster = BTranslatorRoster::Default();
if (roster == NULL)
return;
BBitmapStream stream(bitmap);
// find translation format we're asked for
translator_info* outInfo;
int32 outNumInfo;
bool found = false;
translation_format format;
if (roster->GetTranslators(&stream, NULL, &outInfo, &outNumInfo) == B_OK) {
for (int32 i = 0; i < outNumInfo; i++) {
const translation_format* formats;
int32 formatCount;
roster->GetOutputFormats(outInfo[i].translator, &formats,
&formatCount);
for (int32 j = 0; j < formatCount; j++) {
if (strcmp(formats[j].MIME, type.String()) == 0) {
format = formats[j];
found = true;
break;
}
}
}
}
if (!found) {
stream.DetachBitmap(&bitmap);
return;
}
if (sendInMessage) {
BMessage reply(B_MIME_DATA);
BMallocIO memStream;
if (roster->Translate(&stream, NULL, NULL, &memStream,
format.type) == B_OK) {
reply.AddData(format.MIME, B_MIME_TYPE, memStream.Buffer(),
memStream.BufferLength());
msg->SendReply(&reply);
}
} else {
BDirectory dir(&dirRef);
BFile file(&dir, name.String(), B_WRITE_ONLY | B_CREATE_FILE
| B_ERASE_FILE);
if (file.InitCheck() == B_OK
&& roster->Translate(&stream, NULL, NULL, &file,
format.type) == B_OK) {
BNodeInfo nodeInfo(&file);
if (nodeInfo.InitCheck() == B_OK)
nodeInfo.SetType(type.String());
} else {
BString text = B_TRANSLATE("The file '%name%' could not "
"be written.");
text.ReplaceFirst("%name%", name);
BAlert* alert = new BAlert(B_TRANSLATE("Error"), text.String(),
B_TRANSLATE("OK"), NULL, NULL, B_WIDTH_AS_USUAL, B_STOP_ALERT);
alert->SetFlags(alert->Flags() | B_CLOSE_ON_ESCAPE);
alert->Go();
}
}
// Detach, as we don't want our fPicture to be deleted
stream.DetachBitmap(&bitmap);
}
status_t
PictureView::_LoadPicture(const entry_ref* ref)
{
BFile file;
status_t status = file.SetTo(ref, B_READ_ONLY);
if (status != B_OK)
return status;
off_t fileSize;
status = file.GetSize(&fileSize);
if (status != B_OK)
return status;
// Check that we've at least some data to translate...
if (fileSize < 1)
return B_OK;
translator_info info;
memset(&info, 0, sizeof(translator_info));
BMessage ioExtension;
BTranslatorRoster* roster = BTranslatorRoster::Default();
if (roster == NULL)
return B_ERROR;
status = roster->Identify(&file, &ioExtension, &info, 0, NULL,
B_TRANSLATOR_BITMAP);
BBitmapStream stream;
if (status == B_OK) {
status = roster->Translate(&file, &info, &ioExtension, &stream,
B_TRANSLATOR_BITMAP);
}
if (status != B_OK)
return status;
BBitmap* picture = NULL;
if (stream.DetachBitmap(&picture) != B_OK
|| picture == NULL)
return B_ERROR;
// Remember image format so we could store using the same
fPictureMIMEType = info.MIME;
fPictureType = info.type;
_SetPicture(picture);
return B_OK;
}
void
PictureView::_SetPicture(BBitmap* picture)
{
if (fPicture != fOriginalPicture)
delete fPicture;
fPicture = picture;
if (picture == NULL) {
fPictureType = 0;
fPictureMIMEType = "";
}
Invalidate();
}