1860 lines
40 KiB
C++
1860 lines
40 KiB
C++
/*
|
|
* Copyright 2003-2011, Haiku, Inc. All Rights Reserved.
|
|
* Copyright 2004-2005 yellowTAB GmbH. All Rights Reserverd.
|
|
* Copyright 2006 Bernd Korz. All Rights Reserved
|
|
* Distributed under the terms of the MIT License.
|
|
*
|
|
* Authors:
|
|
* Fernando Francisco de Oliveira
|
|
* Michael Wilber
|
|
* Michael Pfeiffer
|
|
* Ryan Leavengood
|
|
* yellowTAB GmbH
|
|
* Bernd Korz
|
|
* Stephan Aßmus <superstippi@gmx.de>
|
|
* Axel Dörfler, axeld@pinc-software.de
|
|
*/
|
|
|
|
|
|
#include "ShowImageView.h"
|
|
|
|
#include <math.h>
|
|
#include <new>
|
|
#include <stdio.h>
|
|
|
|
#include <Alert.h>
|
|
#include <Application.h>
|
|
#include <Bitmap.h>
|
|
#include <BitmapStream.h>
|
|
#include <Catalog.h>
|
|
#include <Clipboard.h>
|
|
#include <Cursor.h>
|
|
#include <Debug.h>
|
|
#include <Directory.h>
|
|
#include <Entry.h>
|
|
#include <File.h>
|
|
#include <Locale.h>
|
|
#include <MenuBar.h>
|
|
#include <MenuItem.h>
|
|
#include <Message.h>
|
|
#include <NodeInfo.h>
|
|
#include <Path.h>
|
|
#include <PopUpMenu.h>
|
|
#include <Rect.h>
|
|
#include <Region.h>
|
|
#include <Roster.h>
|
|
#include <Screen.h>
|
|
#include <ScrollBar.h>
|
|
#include <StopWatch.h>
|
|
#include <SupportDefs.h>
|
|
#include <TranslatorRoster.h>
|
|
|
|
#include <tracker_private.h>
|
|
|
|
#include "ImageCache.h"
|
|
#include "ShowImageApp.h"
|
|
#include "ShowImageWindow.h"
|
|
|
|
|
|
using std::nothrow;
|
|
|
|
|
|
class PopUpMenu : public BPopUpMenu {
|
|
public:
|
|
PopUpMenu(const char* name, BMessenger target);
|
|
virtual ~PopUpMenu();
|
|
|
|
private:
|
|
BMessenger fTarget;
|
|
};
|
|
|
|
|
|
// the delay time for hiding the cursor in 1/10 seconds (the pulse rate)
|
|
#define HIDE_CURSOR_DELAY_TIME 20
|
|
#define STICKY_ZOOM_DELAY_TIME 5
|
|
#define SHOW_IMAGE_ORIENTATION_ATTRIBUTE "ShowImage:orientation"
|
|
|
|
|
|
const rgb_color kBorderColor = { 0, 0, 0, 255 };
|
|
|
|
enum ShowImageView::image_orientation
|
|
ShowImageView::fTransformation[ImageProcessor::kNumberOfAffineTransformations]
|
|
[kNumberOfOrientations] = {
|
|
// rotate 90°
|
|
{k90, k180, k270, k0, k270V, k0V, k90V, k0H},
|
|
// rotate -90°
|
|
{k270, k0, k90, k180, k90V, k0H, k270V, k0V},
|
|
// mirror vertical
|
|
{k0H, k270V, k0V, k90V, k180, k270, k0, k90},
|
|
// mirror horizontal
|
|
{k0V, k90V, k0H, k270V, k0, k90, k180, k270}
|
|
};
|
|
|
|
const rgb_color kAlphaLow = (rgb_color) { 0xbb, 0xbb, 0xbb, 0xff };
|
|
const rgb_color kAlphaHigh = (rgb_color) { 0xe0, 0xe0, 0xe0, 0xff };
|
|
|
|
const uint32 kMsgPopUpMenuClosed = 'pmcl';
|
|
|
|
|
|
inline void
|
|
blend_colors(uint8* d, uint8 r, uint8 g, uint8 b, uint8 a)
|
|
{
|
|
d[0] = ((b - d[0]) * a + (d[0] << 8)) >> 8;
|
|
d[1] = ((g - d[1]) * a + (d[1] << 8)) >> 8;
|
|
d[2] = ((r - d[2]) * a + (d[2] << 8)) >> 8;
|
|
}
|
|
|
|
|
|
BBitmap*
|
|
compose_checker_background(const BBitmap* bitmap)
|
|
{
|
|
BBitmap* result = new (nothrow) BBitmap(bitmap);
|
|
if (result && !result->IsValid()) {
|
|
delete result;
|
|
result = NULL;
|
|
}
|
|
if (!result)
|
|
return NULL;
|
|
|
|
uint8* bits = (uint8*)result->Bits();
|
|
uint32 bpr = result->BytesPerRow();
|
|
uint32 width = result->Bounds().IntegerWidth() + 1;
|
|
uint32 height = result->Bounds().IntegerHeight() + 1;
|
|
|
|
for (uint32 i = 0; i < height; i++) {
|
|
uint8* p = bits;
|
|
for (uint32 x = 0; x < width; x++) {
|
|
uint8 alpha = p[3];
|
|
if (alpha < 255) {
|
|
p[3] = 255;
|
|
alpha = 255 - alpha;
|
|
if (x % 10 >= 5) {
|
|
if (i % 10 >= 5)
|
|
blend_colors(p, kAlphaLow.red, kAlphaLow.green, kAlphaLow.blue, alpha);
|
|
else
|
|
blend_colors(p, kAlphaHigh.red, kAlphaHigh.green, kAlphaHigh.blue, alpha);
|
|
|
|
} else {
|
|
if (i % 10 >= 5)
|
|
blend_colors(p, kAlphaHigh.red, kAlphaHigh.green, kAlphaHigh.blue, alpha);
|
|
else
|
|
blend_colors(p, kAlphaLow.red, kAlphaLow.green, kAlphaLow.blue, alpha);
|
|
}
|
|
}
|
|
p += 4;
|
|
}
|
|
bits += bpr;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
PopUpMenu::PopUpMenu(const char* name, BMessenger target)
|
|
:
|
|
BPopUpMenu(name, false, false),
|
|
fTarget(target)
|
|
{
|
|
SetAsyncAutoDestruct(true);
|
|
}
|
|
|
|
|
|
PopUpMenu::~PopUpMenu()
|
|
{
|
|
fTarget.SendMessage(kMsgPopUpMenuClosed);
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
ShowImageView::ShowImageView(BRect rect, const char* name, uint32 resizingMode,
|
|
uint32 flags)
|
|
:
|
|
BView(rect, name, resizingMode, flags),
|
|
fBitmapOwner(NULL),
|
|
fBitmap(NULL),
|
|
fDisplayBitmap(NULL),
|
|
fSelectionBitmap(NULL),
|
|
|
|
fZoom(1.0),
|
|
|
|
fScaleBilinear(true),
|
|
|
|
fBitmapLocationInView(0.0, 0.0),
|
|
|
|
fStretchToBounds(false),
|
|
fForceOriginalSize(false),
|
|
fHideCursor(false),
|
|
fScrollingBitmap(false),
|
|
fCreatingSelection(false),
|
|
fFirstPoint(0.0, 0.0),
|
|
fSelectionMode(false),
|
|
fAnimateSelection(true),
|
|
fHasSelection(false),
|
|
fShowCaption(false),
|
|
fShowingPopUpMenu(false),
|
|
fHideCursorCountDown(HIDE_CURSOR_DELAY_TIME),
|
|
fStickyZoomCountDown(0),
|
|
fIsActiveWin(true),
|
|
fDefaultCursor(NULL),
|
|
fGrabCursor(NULL)
|
|
{
|
|
ShowImageSettings* settings = my_app->Settings();
|
|
if (settings->Lock()) {
|
|
fStretchToBounds = settings->GetBool("StretchToBounds",
|
|
fStretchToBounds);
|
|
fScaleBilinear = settings->GetBool("ScaleBilinear", fScaleBilinear);
|
|
settings->Unlock();
|
|
}
|
|
|
|
fDefaultCursor = new BCursor(B_CURSOR_ID_SYSTEM_DEFAULT);
|
|
fGrabCursor = new BCursor(B_CURSOR_ID_GRABBING);
|
|
|
|
SetViewColor(B_TRANSPARENT_COLOR);
|
|
SetHighColor(kBorderColor);
|
|
SetLowColor(0, 0, 0);
|
|
}
|
|
|
|
|
|
ShowImageView::~ShowImageView()
|
|
{
|
|
_DeleteBitmap();
|
|
|
|
delete fDefaultCursor;
|
|
delete fGrabCursor;
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_AnimateSelection(bool enabled)
|
|
{
|
|
fAnimateSelection = enabled;
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::Pulse()
|
|
{
|
|
// animate marching ants
|
|
if (fHasSelection && fAnimateSelection && fIsActiveWin) {
|
|
fSelectionBox.Animate();
|
|
fSelectionBox.Draw(this, Bounds());
|
|
}
|
|
|
|
if (fHideCursor && !fHasSelection && !fShowingPopUpMenu && fIsActiveWin) {
|
|
if (fHideCursorCountDown == 0) {
|
|
// Go negative so this isn't triggered again
|
|
fHideCursorCountDown--;
|
|
|
|
BPoint mousePos;
|
|
uint32 buttons;
|
|
GetMouse(&mousePos, &buttons, false);
|
|
if (Bounds().Contains(mousePos))
|
|
be_app->ObscureCursor();
|
|
} else if (fHideCursorCountDown > 0)
|
|
fHideCursorCountDown--;
|
|
}
|
|
|
|
if (fStickyZoomCountDown > 0)
|
|
fStickyZoomCountDown--;
|
|
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_SendMessageToWindow(BMessage* message)
|
|
{
|
|
BMessenger target(Window());
|
|
target.SendMessage(message);
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_SendMessageToWindow(uint32 code)
|
|
{
|
|
BMessage message(code);
|
|
_SendMessageToWindow(&message);
|
|
}
|
|
|
|
|
|
//! send message to parent about new image
|
|
void
|
|
ShowImageView::_Notify()
|
|
{
|
|
BMessage msg(MSG_UPDATE_STATUS);
|
|
|
|
msg.AddInt32("width", fBitmap->Bounds().IntegerWidth() + 1);
|
|
msg.AddInt32("height", fBitmap->Bounds().IntegerHeight() + 1);
|
|
|
|
msg.AddInt32("colors", fBitmap->ColorSpace());
|
|
_SendMessageToWindow(&msg);
|
|
|
|
FixupScrollBars();
|
|
Invalidate();
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_UpdateStatusText()
|
|
{
|
|
BMessage msg(MSG_UPDATE_STATUS_TEXT);
|
|
|
|
if (fHasSelection) {
|
|
char size[50];
|
|
snprintf(size, sizeof(size), "(%.0fx%.0f)",
|
|
fSelectionBox.Bounds().Width() + 1.0,
|
|
fSelectionBox.Bounds().Height() + 1.0);
|
|
|
|
msg.AddString("status", size);
|
|
}
|
|
|
|
_SendMessageToWindow(&msg);
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_DeleteBitmap()
|
|
{
|
|
_DeleteSelectionBitmap();
|
|
|
|
if (fDisplayBitmap != fBitmap)
|
|
delete fDisplayBitmap;
|
|
fDisplayBitmap = NULL;
|
|
|
|
if (fBitmapOwner != NULL)
|
|
fBitmapOwner->ReleaseReference();
|
|
else
|
|
delete fBitmap;
|
|
|
|
fBitmapOwner = NULL;
|
|
fBitmap = NULL;
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_DeleteSelectionBitmap()
|
|
{
|
|
delete fSelectionBitmap;
|
|
fSelectionBitmap = NULL;
|
|
}
|
|
|
|
|
|
status_t
|
|
ShowImageView::SetImage(const BMessage* message)
|
|
{
|
|
BBitmap* bitmap;
|
|
entry_ref ref;
|
|
if (message->FindPointer("bitmap", (void**)&bitmap) != B_OK
|
|
|| message->FindRef("ref", &ref) != B_OK || bitmap == NULL)
|
|
return B_ERROR;
|
|
|
|
BitmapOwner* bitmapOwner;
|
|
message->FindPointer("bitmapOwner", (void**)&bitmapOwner);
|
|
|
|
status_t status = SetImage(&ref, bitmap, bitmapOwner);
|
|
if (status == B_OK) {
|
|
fFormatDescription = message->FindString("type");
|
|
fMimeType = message->FindString("mime");
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
status_t
|
|
ShowImageView::SetImage(const entry_ref* ref, BBitmap* bitmap,
|
|
BitmapOwner* bitmapOwner)
|
|
{
|
|
// Delete the old one, and clear everything
|
|
_SetHasSelection(false);
|
|
fCreatingSelection = false;
|
|
_DeleteBitmap();
|
|
|
|
fBitmap = bitmap;
|
|
fBitmapOwner = bitmapOwner;
|
|
if (ref == NULL)
|
|
fCurrentRef.device = -1;
|
|
else
|
|
fCurrentRef = *ref;
|
|
|
|
if (fBitmap != NULL) {
|
|
// prepare the display bitmap
|
|
if (fBitmap->ColorSpace() == B_RGBA32)
|
|
fDisplayBitmap = compose_checker_background(fBitmap);
|
|
if (fDisplayBitmap == NULL)
|
|
fDisplayBitmap = fBitmap;
|
|
|
|
BNode node(ref);
|
|
|
|
// restore orientation
|
|
int32 orientation;
|
|
fImageOrientation = k0;
|
|
if (node.ReadAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0,
|
|
&orientation, sizeof(orientation)) == sizeof(orientation)) {
|
|
orientation &= 255;
|
|
switch (orientation) {
|
|
case k0:
|
|
break;
|
|
case k90:
|
|
_DoImageOperation(ImageProcessor::kRotateClockwise, true);
|
|
break;
|
|
case k180:
|
|
_DoImageOperation(ImageProcessor::kRotateClockwise, true);
|
|
_DoImageOperation(ImageProcessor::kRotateClockwise, true);
|
|
break;
|
|
case k270:
|
|
_DoImageOperation(ImageProcessor::kRotateCounterClockwise, true);
|
|
break;
|
|
case k0V:
|
|
_DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom, true);
|
|
break;
|
|
case k90V:
|
|
_DoImageOperation(ImageProcessor::kRotateClockwise, true);
|
|
_DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom, true);
|
|
break;
|
|
case k0H:
|
|
_DoImageOperation(ImageProcessor::ImageProcessor::kFlipLeftToRight, true);
|
|
break;
|
|
case k270V:
|
|
_DoImageOperation(ImageProcessor::kRotateCounterClockwise, true);
|
|
_DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom, true);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
BPath path(ref);
|
|
fCaption = path.Path();
|
|
fFormatDescription = "Bitmap";
|
|
fMimeType = "image/x-be-bitmap";
|
|
|
|
be_roster->AddToRecentDocuments(ref, kApplicationSignature);
|
|
|
|
FitToBounds();
|
|
_Notify();
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
BPoint
|
|
ShowImageView::ImageToView(BPoint p) const
|
|
{
|
|
p.x = floorf(fZoom * p.x + fBitmapLocationInView.x);
|
|
p.y = floorf(fZoom * p.y + fBitmapLocationInView.y);
|
|
return p;
|
|
}
|
|
|
|
|
|
BPoint
|
|
ShowImageView::ViewToImage(BPoint p) const
|
|
{
|
|
p.x = floorf((p.x - fBitmapLocationInView.x) / fZoom);
|
|
p.y = floorf((p.y - fBitmapLocationInView.y) / fZoom);
|
|
return p;
|
|
}
|
|
|
|
|
|
BRect
|
|
ShowImageView::ImageToView(BRect r) const
|
|
{
|
|
BPoint leftTop(ImageToView(BPoint(r.left, r.top)));
|
|
BPoint rightBottom(r.right, r.bottom);
|
|
rightBottom += BPoint(1, 1);
|
|
rightBottom = ImageToView(rightBottom);
|
|
rightBottom -= BPoint(1, 1);
|
|
return BRect(leftTop.x, leftTop.y, rightBottom.x, rightBottom.y);
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::ConstrainToImage(BPoint& point) const
|
|
{
|
|
point.ConstrainTo(fBitmap->Bounds());
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::ConstrainToImage(BRect& rect) const
|
|
{
|
|
rect = rect & fBitmap->Bounds();
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::SetShowCaption(bool show)
|
|
{
|
|
if (fShowCaption != show) {
|
|
fShowCaption = show;
|
|
_UpdateCaption();
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::SetStretchToBounds(bool enable)
|
|
{
|
|
if (fStretchToBounds != enable) {
|
|
_SettingsSetBool("StretchToBounds", enable);
|
|
fStretchToBounds = enable;
|
|
if (enable || fZoom > 1.0)
|
|
FitToBounds();
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::SetHideIdlingCursor(bool hide)
|
|
{
|
|
fHideCursor = hide;
|
|
}
|
|
|
|
|
|
BBitmap*
|
|
ShowImageView::Bitmap()
|
|
{
|
|
return fBitmap;
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::SetScaleBilinear(bool enabled)
|
|
{
|
|
if (fScaleBilinear != enabled) {
|
|
_SettingsSetBool("ScaleBilinear", enabled);
|
|
fScaleBilinear = enabled;
|
|
Invalidate();
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::AttachedToWindow()
|
|
{
|
|
FitToBounds();
|
|
FixupScrollBars();
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::FrameResized(float width, float height)
|
|
{
|
|
FixupScrollBars();
|
|
}
|
|
|
|
|
|
float
|
|
ShowImageView::_FitToBoundsZoom() const
|
|
{
|
|
if (fBitmap == NULL)
|
|
return 1.0f;
|
|
|
|
// the width/height of the bitmap (in pixels)
|
|
float bitmapWidth = fBitmap->Bounds().Width() + 1;
|
|
float bitmapHeight = fBitmap->Bounds().Height() + 1;
|
|
|
|
// the available width/height for layouting the bitmap (in pixels)
|
|
float width = Bounds().Width() + 1;
|
|
float height = Bounds().Height() + 1;
|
|
|
|
float zoom = width / bitmapWidth;
|
|
|
|
if (zoom * bitmapHeight <= height)
|
|
return zoom;
|
|
|
|
return height / bitmapHeight;
|
|
}
|
|
|
|
|
|
BRect
|
|
ShowImageView::_AlignBitmap()
|
|
{
|
|
BRect rect(fBitmap->Bounds());
|
|
|
|
// the width/height of the bitmap (in pixels)
|
|
float bitmapWidth = rect.Width() + 1;
|
|
float bitmapHeight = rect.Height() + 1;
|
|
|
|
// the available width/height for layouting the bitmap (in pixels)
|
|
float width = Bounds().Width() + 1;
|
|
float height = Bounds().Height() + 1;
|
|
|
|
if (width == 0 || height == 0)
|
|
return rect;
|
|
|
|
// zoom image
|
|
rect.right = floorf(bitmapWidth * fZoom) - 1;
|
|
rect.bottom = floorf(bitmapHeight * fZoom) - 1;
|
|
|
|
// update the bitmap size after the zoom
|
|
bitmapWidth = rect.Width() + 1.0;
|
|
bitmapHeight = rect.Height() + 1.0;
|
|
|
|
// always align in the center if the bitmap is smaller than the window
|
|
if (width > bitmapWidth)
|
|
rect.OffsetBy(floorf((width - bitmapWidth) / 2.0), 0);
|
|
|
|
if (height > bitmapHeight)
|
|
rect.OffsetBy(0, floorf((height - bitmapHeight) / 2.0));
|
|
|
|
return rect;
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_DrawBackground(BRect border)
|
|
{
|
|
BRect bounds(Bounds());
|
|
// top
|
|
FillRect(BRect(0, 0, bounds.right, border.top - 1), B_SOLID_LOW);
|
|
// left
|
|
FillRect(BRect(0, border.top, border.left - 1, border.bottom), B_SOLID_LOW);
|
|
// right
|
|
FillRect(BRect(border.right + 1, border.top, bounds.right, border.bottom), B_SOLID_LOW);
|
|
// bottom
|
|
FillRect(BRect(0, border.bottom + 1, bounds.right, bounds.bottom), B_SOLID_LOW);
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_LayoutCaption(BFont& font, BPoint& pos, BRect& rect)
|
|
{
|
|
font_height fontHeight;
|
|
float width, height;
|
|
BRect bounds(Bounds());
|
|
font = be_plain_font;
|
|
width = font.StringWidth(fCaption.String());
|
|
font.GetHeight(&fontHeight);
|
|
height = fontHeight.ascent + fontHeight.descent;
|
|
// center text horizontally
|
|
pos.x = (bounds.left + bounds.right - width) / 2;
|
|
// flush bottom
|
|
pos.y = bounds.bottom - fontHeight.descent - 7;
|
|
|
|
// background rectangle
|
|
rect.Set(0, 0, width + 4, height + 4);
|
|
rect.OffsetTo(pos);
|
|
rect.OffsetBy(-2, -2 - fontHeight.ascent); // -2 for border
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_DrawCaption()
|
|
{
|
|
BFont font;
|
|
BPoint position;
|
|
BRect rect;
|
|
_LayoutCaption(font, position, rect);
|
|
|
|
PushState();
|
|
|
|
// draw background
|
|
SetDrawingMode(B_OP_ALPHA);
|
|
SetHighColor(255, 255, 255, 160);
|
|
FillRect(rect);
|
|
|
|
// draw text
|
|
SetDrawingMode(B_OP_OVER);
|
|
SetFont(&font);
|
|
SetLowColor(B_TRANSPARENT_COLOR);
|
|
SetHighColor(0, 0, 0);
|
|
DrawString(fCaption.String(), position);
|
|
|
|
PopState();
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_UpdateCaption()
|
|
{
|
|
BFont font;
|
|
BPoint pos;
|
|
BRect rect;
|
|
_LayoutCaption(font, pos, rect);
|
|
|
|
// draw over portion of image where caption is located
|
|
BRegion clip(rect);
|
|
PushState();
|
|
ConstrainClippingRegion(&clip);
|
|
Draw(rect);
|
|
PopState();
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_DrawImage(BRect rect)
|
|
{
|
|
// TODO: fix composing of fBitmap with other bitmaps
|
|
// with regard to alpha channel
|
|
if (!fDisplayBitmap)
|
|
fDisplayBitmap = fBitmap;
|
|
|
|
uint32 options = fScaleBilinear ? B_FILTER_BITMAP_BILINEAR : 0;
|
|
DrawBitmap(fDisplayBitmap, fDisplayBitmap->Bounds(), rect, options);
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::Draw(BRect updateRect)
|
|
{
|
|
if (fBitmap == NULL)
|
|
return;
|
|
|
|
if (IsPrinting()) {
|
|
DrawBitmap(fBitmap);
|
|
return;
|
|
}
|
|
|
|
BRect rect = _AlignBitmap();
|
|
fBitmapLocationInView.x = floorf(rect.left);
|
|
fBitmapLocationInView.y = floorf(rect.top);
|
|
|
|
_DrawBackground(rect);
|
|
_DrawImage(rect);
|
|
|
|
if (fShowCaption)
|
|
_DrawCaption();
|
|
|
|
if (fHasSelection) {
|
|
if (fSelectionBitmap != NULL) {
|
|
BRect srcRect;
|
|
BRect dstRect;
|
|
_GetSelectionMergeRects(srcRect, dstRect);
|
|
dstRect = ImageToView(dstRect);
|
|
DrawBitmap(fSelectionBitmap, srcRect, dstRect);
|
|
}
|
|
fSelectionBox.Draw(this, updateRect);
|
|
}
|
|
}
|
|
|
|
|
|
BBitmap*
|
|
ShowImageView::_CopySelection(uchar alpha, bool imageSize)
|
|
{
|
|
bool hasAlpha = alpha != 255;
|
|
|
|
if (!fHasSelection)
|
|
return NULL;
|
|
|
|
BRect rect = fSelectionBox.Bounds().OffsetToCopy(B_ORIGIN);
|
|
if (!imageSize) {
|
|
// scale image to view size
|
|
rect.right = floorf((rect.right + 1.0) * fZoom - 1.0);
|
|
rect.bottom = floorf((rect.bottom + 1.0) * fZoom - 1.0);
|
|
}
|
|
BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW);
|
|
BBitmap* bitmap = new(nothrow) BBitmap(rect, hasAlpha ? B_RGBA32
|
|
: fBitmap->ColorSpace(), true);
|
|
if (bitmap == NULL || !bitmap->IsValid()) {
|
|
delete bitmap;
|
|
return NULL;
|
|
}
|
|
|
|
if (bitmap->Lock()) {
|
|
bitmap->AddChild(&view);
|
|
#ifdef __HAIKU__
|
|
// On Haiku, B_OP_SUBSTRACT does not affect alpha like it did on BeOS.
|
|
// Don't know if it's better to fix it or not (stippi).
|
|
if (hasAlpha) {
|
|
view.SetHighColor(0, 0, 0, 0);
|
|
view.FillRect(view.Bounds());
|
|
view.SetDrawingMode(B_OP_ALPHA);
|
|
view.SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE);
|
|
view.SetHighColor(0, 0, 0, alpha);
|
|
}
|
|
if (fSelectionBitmap) {
|
|
view.DrawBitmap(fSelectionBitmap,
|
|
fSelectionBitmap->Bounds().OffsetToCopy(B_ORIGIN), rect);
|
|
} else
|
|
view.DrawBitmap(fBitmap, fCopyFromRect, rect);
|
|
#else
|
|
if (fSelectionBitmap) {
|
|
view.DrawBitmap(fSelectionBitmap,
|
|
fSelectionBitmap->Bounds().OffsetToCopy(B_ORIGIN), rect);
|
|
} else
|
|
view.DrawBitmap(fBitmap, fCopyFromRect, rect);
|
|
if (hasAlpha) {
|
|
view.SetDrawingMode(B_OP_SUBTRACT);
|
|
view.SetHighColor(0, 0, 0, 255 - alpha);
|
|
view.FillRect(rect, B_SOLID_HIGH);
|
|
}
|
|
#endif
|
|
view.Sync();
|
|
bitmap->RemoveChild(&view);
|
|
bitmap->Unlock();
|
|
}
|
|
|
|
return bitmap;
|
|
}
|
|
|
|
|
|
bool
|
|
ShowImageView::_AddSupportedTypes(BMessage* msg, BBitmap* bitmap)
|
|
{
|
|
BTranslatorRoster* roster = BTranslatorRoster::Default();
|
|
if (roster == NULL)
|
|
return false;
|
|
|
|
// add the current image mime first, will make it the preferred format on
|
|
// left mouse drag
|
|
msg->AddString("be:types", fMimeType);
|
|
msg->AddString("be:filetypes", fMimeType);
|
|
msg->AddString("be:type_descriptions", fFormatDescription);
|
|
|
|
bool foundOther = false;
|
|
bool foundCurrent = false;
|
|
|
|
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 (fMimeType == formats[j].MIME)
|
|
foundCurrent = true;
|
|
else if (strcmp(formats[j].MIME, "image/x-be-bitmap") != 0) {
|
|
foundOther = true;
|
|
// needed to send data in message
|
|
msg->AddString("be:types", formats[j].MIME);
|
|
// needed to pass data via file
|
|
msg->AddString("be:filetypes", formats[j].MIME);
|
|
msg->AddString("be:type_descriptions", formats[j].name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
stream.DetachBitmap(&bitmap);
|
|
|
|
if (!foundCurrent) {
|
|
msg->RemoveData("be:types", 0);
|
|
msg->RemoveData("be:filetypes", 0);
|
|
msg->RemoveData("be:type_descriptions", 0);
|
|
}
|
|
|
|
return foundOther || foundCurrent;
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_BeginDrag(BPoint sourcePoint)
|
|
{
|
|
BBitmap* bitmap = _CopySelection(128, false);
|
|
if (bitmap == NULL)
|
|
return;
|
|
|
|
SetMouseEventMask(B_POINTER_EVENTS);
|
|
|
|
// fill the drag message
|
|
BMessage drag(B_SIMPLE_DATA);
|
|
drag.AddInt32("be:actions", B_COPY_TARGET);
|
|
drag.AddString("be:clip_name", "Bitmap Clip");
|
|
// ShowImage specific fields
|
|
drag.AddPoint("be:_source_point", sourcePoint);
|
|
drag.AddRect("be:_frame", fSelectionBox.Bounds());
|
|
if (_AddSupportedTypes(&drag, bitmap)) {
|
|
// we also support "Passing Data via File" protocol
|
|
drag.AddString("be:types", B_FILE_MIME_TYPE);
|
|
// avoid flickering of dragged bitmap caused by drawing into the window
|
|
_AnimateSelection(false);
|
|
// only use a transparent bitmap on selections less than 400x400
|
|
// (taking into account zooming)
|
|
BRect selectionRect = fSelectionBox.Bounds();
|
|
if (selectionRect.Width() * fZoom < 400.0
|
|
&& selectionRect.Height() * fZoom < 400.0) {
|
|
sourcePoint -= selectionRect.LeftTop();
|
|
sourcePoint.x *= fZoom;
|
|
sourcePoint.y *= fZoom;
|
|
// DragMessage takes ownership of bitmap
|
|
DragMessage(&drag, bitmap, B_OP_ALPHA, sourcePoint);
|
|
bitmap = NULL;
|
|
} else {
|
|
delete bitmap;
|
|
// Offset and scale the rect
|
|
BRect rect(selectionRect);
|
|
rect = ImageToView(rect);
|
|
rect.InsetBy(-1, -1);
|
|
DragMessage(&drag, rect);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool
|
|
ShowImageView::_OutputFormatForType(BBitmap* bitmap, const char* type,
|
|
translation_format* format)
|
|
{
|
|
bool found = false;
|
|
|
|
BTranslatorRoster* roster = BTranslatorRoster::Default();
|
|
if (roster == NULL)
|
|
return false;
|
|
|
|
BBitmapStream stream(bitmap);
|
|
|
|
translator_info* outInfo;
|
|
int32 outNumInfo;
|
|
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) == 0) {
|
|
*format = formats[j];
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
stream.DetachBitmap(&bitmap);
|
|
return found;
|
|
}
|
|
|
|
|
|
#undef B_TRANSLATION_CONTEXT
|
|
#define B_TRANSLATION_CONTEXT "SaveToFile"
|
|
|
|
|
|
void
|
|
ShowImageView::SaveToFile(BDirectory* dir, const char* name, BBitmap* bitmap,
|
|
const translation_format* format)
|
|
{
|
|
if (bitmap == NULL) {
|
|
// If no bitmap is supplied, write out the whole image
|
|
bitmap = fBitmap;
|
|
}
|
|
|
|
BBitmapStream stream(bitmap);
|
|
|
|
bool loop = true;
|
|
while (loop) {
|
|
BTranslatorRoster* roster = BTranslatorRoster::Default();
|
|
if (!roster)
|
|
break;
|
|
// write data
|
|
BFile file(dir, name, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
|
|
if (file.InitCheck() != B_OK)
|
|
break;
|
|
if (roster->Translate(&stream, NULL, NULL, &file, format->type) < B_OK)
|
|
break;
|
|
// set mime type
|
|
BNodeInfo info(&file);
|
|
if (info.InitCheck() == B_OK)
|
|
info.SetType(format->MIME);
|
|
|
|
loop = false;
|
|
// break out of loop gracefully (indicates no errors)
|
|
}
|
|
if (loop) {
|
|
// If loop terminated because of a break, there was an error
|
|
char buffer[512];
|
|
snprintf(buffer, sizeof(buffer), B_TRANSLATE("The file '%s' could not "
|
|
"be written."), name);
|
|
BAlert* palert = new BAlert("", buffer, B_TRANSLATE("OK"));
|
|
palert->SetFlags(palert->Flags() | B_CLOSE_ON_ESCAPE);
|
|
palert->Go();
|
|
}
|
|
|
|
stream.DetachBitmap(&bitmap);
|
|
// Don't allow the bitmap to be deleted, this is
|
|
// especially important when using fBitmap as the bitmap
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_SendInMessage(BMessage* msg, BBitmap* bitmap,
|
|
translation_format* format)
|
|
{
|
|
BMessage reply(B_MIME_DATA);
|
|
BBitmapStream stream(bitmap); // destructor deletes bitmap
|
|
BTranslatorRoster* roster = BTranslatorRoster::Default();
|
|
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);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_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;
|
|
|
|
BBitmap* bitmap = _CopySelection();
|
|
if (bitmap == NULL)
|
|
return;
|
|
|
|
translation_format format;
|
|
if (!_OutputFormatForType(bitmap, type.String(), &format)) {
|
|
delete bitmap;
|
|
return;
|
|
}
|
|
|
|
if (saveToFile) {
|
|
BDirectory dir(&dirRef);
|
|
SaveToFile(&dir, name.String(), bitmap, &format);
|
|
delete bitmap;
|
|
} else if (sendInMessage) {
|
|
_SendInMessage(msg, bitmap, &format);
|
|
} else {
|
|
delete bitmap;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_ScrollBitmap(BPoint point)
|
|
{
|
|
point = ConvertToScreen(point);
|
|
BPoint delta = fFirstPoint - point;
|
|
fFirstPoint = point;
|
|
_ScrollRestrictedBy(delta.x, delta.y);
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_GetMergeRects(BBitmap* merge, BRect selection, BRect& srcRect,
|
|
BRect& dstRect)
|
|
{
|
|
// Constrain dstRect to target image size and apply the same edge offsets
|
|
// to the srcRect.
|
|
|
|
dstRect = selection;
|
|
|
|
BRect clippedDstRect(dstRect);
|
|
ConstrainToImage(clippedDstRect);
|
|
|
|
srcRect = merge->Bounds().OffsetToCopy(B_ORIGIN);
|
|
|
|
srcRect.left += clippedDstRect.left - dstRect.left;
|
|
srcRect.top += clippedDstRect.top - dstRect.top;
|
|
srcRect.right += clippedDstRect.right - dstRect.right;
|
|
srcRect.bottom += clippedDstRect.bottom - dstRect.bottom;
|
|
|
|
dstRect = clippedDstRect;
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_GetSelectionMergeRects(BRect& srcRect, BRect& dstRect)
|
|
{
|
|
_GetMergeRects(fSelectionBitmap, fSelectionBox.Bounds(), srcRect, dstRect);
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_MergeWithBitmap(BBitmap* merge, BRect selection)
|
|
{
|
|
BView view(fBitmap->Bounds(), NULL, B_FOLLOW_NONE, B_WILL_DRAW);
|
|
BBitmap* bitmap = new(nothrow) BBitmap(fBitmap->Bounds(),
|
|
fBitmap->ColorSpace(), true);
|
|
if (bitmap == NULL || !bitmap->IsValid()) {
|
|
delete bitmap;
|
|
return;
|
|
}
|
|
|
|
if (bitmap->Lock()) {
|
|
bitmap->AddChild(&view);
|
|
view.DrawBitmap(fBitmap, fBitmap->Bounds());
|
|
BRect srcRect;
|
|
BRect dstRect;
|
|
_GetMergeRects(merge, selection, srcRect, dstRect);
|
|
view.DrawBitmap(merge, srcRect, dstRect);
|
|
|
|
view.Sync();
|
|
bitmap->RemoveChild(&view);
|
|
bitmap->Unlock();
|
|
|
|
_DeleteBitmap();
|
|
fBitmap = bitmap;
|
|
|
|
_SendMessageToWindow(MSG_MODIFIED);
|
|
} else
|
|
delete bitmap;
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::MouseDown(BPoint position)
|
|
{
|
|
MakeFocus(true);
|
|
|
|
BPoint point = ViewToImage(position);
|
|
int32 clickCount = 0;
|
|
uint32 buttons = 0;
|
|
if (Window() != NULL && Window()->CurrentMessage() != NULL) {
|
|
clickCount = Window()->CurrentMessage()->FindInt32("clicks");
|
|
buttons = Window()->CurrentMessage()->FindInt32("buttons");
|
|
}
|
|
|
|
// Using clickCount >= 2 and the modulo 2 accounts for quickly repeated
|
|
// double-clicks
|
|
if (buttons == B_PRIMARY_MOUSE_BUTTON && clickCount >= 2 &&
|
|
clickCount % 2 == 0) {
|
|
Window()->PostMessage(MSG_FULL_SCREEN);
|
|
return;
|
|
}
|
|
|
|
if (fHasSelection && fSelectionBox.Bounds().Contains(point)
|
|
&& (buttons
|
|
& (B_PRIMARY_MOUSE_BUTTON | B_SECONDARY_MOUSE_BUTTON)) != 0) {
|
|
if (!fSelectionBitmap)
|
|
fSelectionBitmap = _CopySelection();
|
|
|
|
_BeginDrag(point);
|
|
} else if (buttons == B_PRIMARY_MOUSE_BUTTON
|
|
&& (fSelectionMode
|
|
|| (modifiers() & (B_COMMAND_KEY | B_CONTROL_KEY)) != 0)) {
|
|
// begin new selection
|
|
_SetHasSelection(true);
|
|
fCreatingSelection = true;
|
|
SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
|
|
ConstrainToImage(point);
|
|
fFirstPoint = point;
|
|
fCopyFromRect.Set(point.x, point.y, point.x, point.y);
|
|
fSelectionBox.SetBounds(this, fCopyFromRect);
|
|
Invalidate();
|
|
} else if (buttons == B_SECONDARY_MOUSE_BUTTON) {
|
|
_ShowPopUpMenu(ConvertToScreen(position));
|
|
} else if (buttons == B_PRIMARY_MOUSE_BUTTON
|
|
|| buttons == B_TERTIARY_MOUSE_BUTTON) {
|
|
// move image in window
|
|
SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
|
|
fScrollingBitmap = true;
|
|
fFirstPoint = ConvertToScreen(position);
|
|
be_app->SetCursor(fGrabCursor);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_UpdateSelectionRect(BPoint point, bool final)
|
|
{
|
|
BRect oldSelection = fCopyFromRect;
|
|
point = ViewToImage(point);
|
|
ConstrainToImage(point);
|
|
fCopyFromRect.left = min_c(fFirstPoint.x, point.x);
|
|
fCopyFromRect.right = max_c(fFirstPoint.x, point.x);
|
|
fCopyFromRect.top = min_c(fFirstPoint.y, point.y);
|
|
fCopyFromRect.bottom = max_c(fFirstPoint.y, point.y);
|
|
fSelectionBox.SetBounds(this, fCopyFromRect);
|
|
|
|
if (final) {
|
|
// selection must be at least 2 pixels wide or 2 pixels tall
|
|
if (fCopyFromRect.Width() < 1.0 && fCopyFromRect.Height() < 1.0)
|
|
_SetHasSelection(false);
|
|
} else
|
|
_UpdateStatusText();
|
|
|
|
if (oldSelection != fCopyFromRect || !fHasSelection) {
|
|
BRect updateRect;
|
|
updateRect = oldSelection | fCopyFromRect;
|
|
updateRect = ImageToView(updateRect);
|
|
updateRect.InsetBy(-1, -1);
|
|
Invalidate(updateRect);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::MouseMoved(BPoint point, uint32 state, const BMessage* message)
|
|
{
|
|
fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME;
|
|
if (fHideCursor) {
|
|
// Show toolbar when mouse hits top 15 pixels, hide otherwise
|
|
_ShowToolBarIfEnabled(ConvertToScreen(point).y <= 15);
|
|
}
|
|
if (fCreatingSelection)
|
|
_UpdateSelectionRect(point, false);
|
|
else if (fScrollingBitmap)
|
|
_ScrollBitmap(point);
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::MouseUp(BPoint point)
|
|
{
|
|
if (fCreatingSelection) {
|
|
_UpdateSelectionRect(point, true);
|
|
fCreatingSelection = false;
|
|
} else if (fScrollingBitmap) {
|
|
_ScrollBitmap(point);
|
|
fScrollingBitmap = false;
|
|
be_app->SetCursor(fDefaultCursor);
|
|
}
|
|
_AnimateSelection(true);
|
|
}
|
|
|
|
|
|
float
|
|
ShowImageView::_LimitToRange(float v, orientation o, bool absolute)
|
|
{
|
|
BScrollBar* psb = ScrollBar(o);
|
|
if (psb) {
|
|
float min, max, pos;
|
|
pos = v;
|
|
if (!absolute)
|
|
pos += psb->Value();
|
|
|
|
psb->GetRange(&min, &max);
|
|
if (pos < min)
|
|
pos = min;
|
|
else if (pos > max)
|
|
pos = max;
|
|
|
|
v = pos;
|
|
if (!absolute)
|
|
v -= psb->Value();
|
|
}
|
|
return v;
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_ScrollRestricted(float x, float y, bool absolute)
|
|
{
|
|
if (x != 0)
|
|
x = _LimitToRange(x, B_HORIZONTAL, absolute);
|
|
|
|
if (y != 0)
|
|
y = _LimitToRange(y, B_VERTICAL, absolute);
|
|
|
|
// We invalidate before we scroll to avoid the caption messing up the
|
|
// image, and to prevent it from flickering
|
|
if (fShowCaption)
|
|
Invalidate();
|
|
|
|
ScrollBy(x, y);
|
|
}
|
|
|
|
|
|
// XXX method is not unused
|
|
void
|
|
ShowImageView::_ScrollRestrictedTo(float x, float y)
|
|
{
|
|
_ScrollRestricted(x, y, true);
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_ScrollRestrictedBy(float x, float y)
|
|
{
|
|
_ScrollRestricted(x, y, false);
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::KeyDown(const char* bytes, int32 numBytes)
|
|
{
|
|
if (numBytes != 1) {
|
|
BView::KeyDown(bytes, numBytes);
|
|
return;
|
|
}
|
|
|
|
bool shiftKeyDown = (modifiers() & B_SHIFT_KEY) != 0;
|
|
|
|
switch (*bytes) {
|
|
case B_DOWN_ARROW:
|
|
if (shiftKeyDown)
|
|
_ScrollRestrictedBy(0, 10);
|
|
else
|
|
_SendMessageToWindow(MSG_FILE_NEXT);
|
|
break;
|
|
case B_RIGHT_ARROW:
|
|
if (shiftKeyDown)
|
|
_ScrollRestrictedBy(10, 0);
|
|
else
|
|
_SendMessageToWindow(MSG_FILE_NEXT);
|
|
break;
|
|
case B_UP_ARROW:
|
|
if (shiftKeyDown)
|
|
_ScrollRestrictedBy(0, -10);
|
|
else
|
|
_SendMessageToWindow(MSG_FILE_PREV);
|
|
break;
|
|
case B_LEFT_ARROW:
|
|
if (shiftKeyDown)
|
|
_ScrollRestrictedBy(-10, 0);
|
|
else
|
|
_SendMessageToWindow(MSG_FILE_PREV);
|
|
break;
|
|
case B_BACKSPACE:
|
|
_SendMessageToWindow(MSG_FILE_PREV);
|
|
break;
|
|
case B_HOME:
|
|
break;
|
|
case B_END:
|
|
break;
|
|
case B_SPACE:
|
|
_ToggleSlideShow();
|
|
break;
|
|
case B_ESCAPE:
|
|
// stop slide show
|
|
_StopSlideShow();
|
|
_ExitFullScreen();
|
|
|
|
ClearSelection();
|
|
break;
|
|
case B_DELETE:
|
|
if (fHasSelection)
|
|
ClearSelection();
|
|
else
|
|
_SendMessageToWindow(kMsgDeleteCurrentFile);
|
|
break;
|
|
case '0':
|
|
FitToBounds();
|
|
break;
|
|
case '1':
|
|
SetZoom(1.0f);
|
|
break;
|
|
case '+':
|
|
case '=':
|
|
ZoomIn();
|
|
break;
|
|
case '-':
|
|
ZoomOut();
|
|
break;
|
|
case '[':
|
|
Rotate(270);
|
|
break;
|
|
case ']':
|
|
Rotate(90);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_MouseWheelChanged(BMessage* message)
|
|
{
|
|
// The BeOS driver does not currently support
|
|
// X wheel scrolling, therefore, deltaX is zero.
|
|
// |deltaY| is the number of notches scrolled up or down.
|
|
// When the wheel is scrolled down (towards the user) deltaY > 0
|
|
// When the wheel is scrolled up (away from the user) deltaY < 0
|
|
const float kscrollBy = 40;
|
|
float deltaY;
|
|
float deltaX;
|
|
float x = 0;
|
|
float y = 0;
|
|
|
|
if (message->FindFloat("be:wheel_delta_x", &deltaX) == B_OK)
|
|
x = deltaX * kscrollBy;
|
|
|
|
if (message->FindFloat("be:wheel_delta_y", &deltaY) == B_OK)
|
|
y = deltaY * kscrollBy;
|
|
|
|
if ((modifiers() & B_SHIFT_KEY) != 0) {
|
|
// scroll up and down
|
|
_ScrollRestrictedBy(x, y);
|
|
} else if ((modifiers() & B_CONTROL_KEY) != 0) {
|
|
// scroll left and right
|
|
_ScrollRestrictedBy(y, x);
|
|
} else {
|
|
// zoom at location
|
|
BPoint where;
|
|
uint32 buttons;
|
|
GetMouse(&where, &buttons);
|
|
|
|
if (fStickyZoomCountDown <= 0) {
|
|
if (deltaY < 0)
|
|
ZoomIn(where);
|
|
else if (deltaY > 0)
|
|
ZoomOut(where);
|
|
|
|
if (fZoom == 1.0)
|
|
fStickyZoomCountDown = STICKY_ZOOM_DELAY_TIME;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_ShowPopUpMenu(BPoint screen)
|
|
{
|
|
if (!fShowingPopUpMenu) {
|
|
PopUpMenu* menu = new PopUpMenu("PopUpMenu", this);
|
|
|
|
ShowImageWindow* window = dynamic_cast<ShowImageWindow*>(Window());
|
|
if (window != NULL)
|
|
window->BuildContextMenu(menu);
|
|
|
|
menu->Go(screen, true, true, true);
|
|
fShowingPopUpMenu = true;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_SettingsSetBool(const char* name, bool value)
|
|
{
|
|
ShowImageSettings* settings;
|
|
settings = my_app->Settings();
|
|
if (settings->Lock()) {
|
|
settings->SetBool(name, value);
|
|
settings->Unlock();
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::MessageReceived(BMessage* message)
|
|
{
|
|
switch (message->what) {
|
|
case B_COPY_TARGET:
|
|
_HandleDrop(message);
|
|
break;
|
|
|
|
case B_MOUSE_WHEEL_CHANGED:
|
|
_MouseWheelChanged(message);
|
|
break;
|
|
|
|
case kMsgPopUpMenuClosed:
|
|
fShowingPopUpMenu = false;
|
|
break;
|
|
|
|
default:
|
|
BView::MessageReceived(message);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::FixupScrollBar(orientation o, float bitmapLength,
|
|
float viewLength)
|
|
{
|
|
float prop, range;
|
|
BScrollBar* psb;
|
|
|
|
psb = ScrollBar(o);
|
|
if (psb) {
|
|
range = bitmapLength - viewLength;
|
|
if (range < 0.0)
|
|
range = 0.0;
|
|
|
|
prop = viewLength / bitmapLength;
|
|
if (prop > 1.0)
|
|
prop = 1.0;
|
|
|
|
psb->SetRange(0, range);
|
|
psb->SetProportion(prop);
|
|
psb->SetSteps(10, 100);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::FixupScrollBars()
|
|
{
|
|
BRect viewRect = Bounds();
|
|
BRect bitmapRect;
|
|
if (fBitmap != NULL) {
|
|
bitmapRect = _AlignBitmap();
|
|
bitmapRect.OffsetTo(0, 0);
|
|
}
|
|
|
|
FixupScrollBar(B_HORIZONTAL, bitmapRect.Width(), viewRect.Width());
|
|
FixupScrollBar(B_VERTICAL, bitmapRect.Height(), viewRect.Height());
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::SetSelectionMode(bool selectionMode)
|
|
{
|
|
// The mode only has an effect in MouseDown()
|
|
fSelectionMode = selectionMode;
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::SelectAll()
|
|
{
|
|
fCopyFromRect.Set(0, 0, fBitmap->Bounds().Width(),
|
|
fBitmap->Bounds().Height());
|
|
fSelectionBox.SetBounds(this, fCopyFromRect);
|
|
_SetHasSelection(true);
|
|
Invalidate();
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::ClearSelection()
|
|
{
|
|
if (!fHasSelection)
|
|
return;
|
|
|
|
_SetHasSelection(false);
|
|
Invalidate();
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_SetHasSelection(bool hasSelection)
|
|
{
|
|
_DeleteSelectionBitmap();
|
|
fHasSelection = hasSelection;
|
|
|
|
_UpdateStatusText();
|
|
|
|
BMessage msg(MSG_SELECTION);
|
|
msg.AddBool("has_selection", fHasSelection);
|
|
_SendMessageToWindow(&msg);
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::CopySelectionToClipboard()
|
|
{
|
|
if (!fHasSelection || !be_clipboard->Lock())
|
|
return;
|
|
|
|
be_clipboard->Clear();
|
|
|
|
BMessage* data = be_clipboard->Data();
|
|
if (data != NULL) {
|
|
BBitmap* bitmap = _CopySelection();
|
|
if (bitmap != NULL) {
|
|
BMessage bitmapArchive;
|
|
bitmap->Archive(&bitmapArchive);
|
|
// NOTE: Possibly "image/x-be-bitmap" is more correct.
|
|
// This works with WonderBrush, though, which in turn had been
|
|
// tested with other apps.
|
|
data->AddMessage("image/bitmap", &bitmapArchive);
|
|
data->AddPoint("be:location", fSelectionBox.Bounds().LeftTop());
|
|
|
|
delete bitmap;
|
|
|
|
be_clipboard->Commit();
|
|
}
|
|
}
|
|
be_clipboard->Unlock();
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::SetZoom(float zoom, BPoint where)
|
|
{
|
|
float fitToBoundsZoom = _FitToBoundsZoom();
|
|
if (zoom > 32)
|
|
zoom = 32;
|
|
if (zoom < fitToBoundsZoom / 2 && zoom < 0.25)
|
|
zoom = min_c(fitToBoundsZoom / 2, 0.25);
|
|
|
|
if (zoom == fZoom) {
|
|
// window size might have changed
|
|
FixupScrollBars();
|
|
return;
|
|
}
|
|
|
|
// Invalidate before scrolling, as that prevents the app_server
|
|
// to do the scrolling server side
|
|
Invalidate();
|
|
|
|
// zoom to center if not otherwise specified
|
|
BPoint offset;
|
|
if (where.x == -1) {
|
|
where.Set(Bounds().Width() / 2, Bounds().Height() / 2);
|
|
offset = where;
|
|
where += Bounds().LeftTop();
|
|
} else
|
|
offset = where - Bounds().LeftTop();
|
|
|
|
float oldZoom = fZoom;
|
|
fZoom = zoom;
|
|
|
|
FixupScrollBars();
|
|
|
|
if (fBitmap != NULL) {
|
|
offset.x = (int)(where.x * fZoom / oldZoom + 0.5) - offset.x;
|
|
offset.y = (int)(where.y * fZoom / oldZoom + 0.5) - offset.y;
|
|
ScrollTo(offset);
|
|
}
|
|
|
|
BMessage message(MSG_UPDATE_STATUS_ZOOM);
|
|
message.AddFloat("zoom", fZoom);
|
|
_SendMessageToWindow(&message);
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::ZoomIn(BPoint where)
|
|
{
|
|
// snap zoom to "fit to bounds", and "original size"
|
|
float zoom = fZoom * 1.2;
|
|
float zoomSnap = fZoom * 1.25;
|
|
float fitToBoundsZoom = _FitToBoundsZoom();
|
|
if (fZoom < fitToBoundsZoom - 0.001 && zoomSnap > fitToBoundsZoom)
|
|
zoom = fitToBoundsZoom;
|
|
if (fZoom < 1.0 && zoomSnap > 1.0)
|
|
zoom = 1.0;
|
|
|
|
SetZoom(zoom, where);
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::ZoomOut(BPoint where)
|
|
{
|
|
// snap zoom to "fit to bounds", and "original size"
|
|
float zoom = fZoom / 1.2;
|
|
float zoomSnap = fZoom / 1.25;
|
|
float fitToBoundsZoom = _FitToBoundsZoom();
|
|
if (fZoom > fitToBoundsZoom + 0.001 && zoomSnap < fitToBoundsZoom)
|
|
zoom = fitToBoundsZoom;
|
|
if (fZoom > 1.0 && zoomSnap < 1.0)
|
|
zoom = 1.0;
|
|
|
|
SetZoom(zoom, where);
|
|
}
|
|
|
|
|
|
/*! Fits the image to the view bounds.
|
|
*/
|
|
void
|
|
ShowImageView::FitToBounds()
|
|
{
|
|
if (fBitmap == NULL)
|
|
return;
|
|
|
|
float fitToBoundsZoom = _FitToBoundsZoom();
|
|
if ((!fStretchToBounds && fitToBoundsZoom > 1.0f) || fForceOriginalSize)
|
|
SetZoom(1.0f);
|
|
else
|
|
SetZoom(fitToBoundsZoom);
|
|
|
|
FixupScrollBars();
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_DoImageOperation(ImageProcessor::operation op, bool quiet)
|
|
{
|
|
BMessenger msgr;
|
|
ImageProcessor imageProcessor(op, fBitmap, msgr, 0);
|
|
imageProcessor.Start(false);
|
|
BBitmap* bm = imageProcessor.DetachBitmap();
|
|
if (bm == NULL) {
|
|
// operation failed
|
|
return;
|
|
}
|
|
|
|
// update orientation state
|
|
if (op != ImageProcessor::kInvert) {
|
|
// Note: If one of these fails, check its definition in class ImageProcessor.
|
|
// ASSERT(ImageProcessor::kRotateClockwise <
|
|
// ImageProcessor::kNumberOfAffineTransformations);
|
|
// ASSERT(ImageProcessor::kRotateCounterClockwise <
|
|
// ImageProcessor::kNumberOfAffineTransformations);
|
|
// ASSERT(ImageProcessor::kFlipLeftToRight <
|
|
// ImageProcessor::kNumberOfAffineTransformations);
|
|
// ASSERT(ImageProcessor::kFlipTopToBottom <
|
|
// ImageProcessor::kNumberOfAffineTransformations);
|
|
fImageOrientation = fTransformation[op][fImageOrientation];
|
|
}
|
|
|
|
if (!quiet) {
|
|
// write orientation state
|
|
BNode node(&fCurrentRef);
|
|
int32 orientation = fImageOrientation;
|
|
if (orientation != k0) {
|
|
node.WriteAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0,
|
|
&orientation, sizeof(orientation));
|
|
} else
|
|
node.RemoveAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE);
|
|
}
|
|
|
|
// set new bitmap
|
|
_DeleteBitmap();
|
|
fBitmap = bm;
|
|
|
|
if (fBitmap->ColorSpace() == B_RGBA32)
|
|
fDisplayBitmap = compose_checker_background(fBitmap);
|
|
|
|
if (!quiet) {
|
|
// remove selection
|
|
_SetHasSelection(false);
|
|
_Notify();
|
|
}
|
|
}
|
|
|
|
|
|
//! Image operation initiated by user
|
|
void
|
|
ShowImageView::_UserDoImageOperation(ImageProcessor::operation op, bool quiet)
|
|
{
|
|
_DoImageOperation(op, quiet);
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::Rotate(int degree)
|
|
{
|
|
_UserDoImageOperation(degree == 90 ? ImageProcessor::kRotateClockwise
|
|
: ImageProcessor::kRotateCounterClockwise);
|
|
|
|
FitToBounds();
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::Flip(bool vertical)
|
|
{
|
|
if (vertical)
|
|
_UserDoImageOperation(ImageProcessor::kFlipLeftToRight);
|
|
else
|
|
_UserDoImageOperation(ImageProcessor::kFlipTopToBottom);
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::ResizeImage(int w, int h)
|
|
{
|
|
if (fBitmap == NULL || w < 1 || h < 1)
|
|
return;
|
|
|
|
Scaler scaler(fBitmap, BRect(0, 0, w - 1, h - 1), BMessenger(), 0, false);
|
|
scaler.Start(false);
|
|
BBitmap* scaled = scaler.DetachBitmap();
|
|
if (scaled == NULL) {
|
|
// operation failed
|
|
return;
|
|
}
|
|
|
|
// remove selection
|
|
_SetHasSelection(false);
|
|
_DeleteBitmap();
|
|
fBitmap = scaled;
|
|
|
|
_SendMessageToWindow(MSG_MODIFIED);
|
|
|
|
_Notify();
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_SetIcon(bool clear, icon_size which)
|
|
{
|
|
int32 size;
|
|
switch (which) {
|
|
case B_MINI_ICON: size = 16;
|
|
break;
|
|
case B_LARGE_ICON: size = 32;
|
|
break;
|
|
default:
|
|
return;
|
|
}
|
|
|
|
BRect rect(fBitmap->Bounds());
|
|
float s;
|
|
s = size / (rect.Width() + 1.0);
|
|
|
|
if (s * (rect.Height() + 1.0) <= size) {
|
|
rect.right = size - 1;
|
|
rect.bottom = static_cast<int>(s * (rect.Height() + 1.0)) - 1;
|
|
// center vertically
|
|
rect.OffsetBy(0, (size - rect.IntegerHeight()) / 2);
|
|
} else {
|
|
s = size / (rect.Height() + 1.0);
|
|
rect.right = static_cast<int>(s * (rect.Width() + 1.0)) - 1;
|
|
rect.bottom = size - 1;
|
|
// center horizontally
|
|
rect.OffsetBy((size - rect.IntegerWidth()) / 2, 0);
|
|
}
|
|
|
|
// scale bitmap to thumbnail size
|
|
BMessenger msgr;
|
|
Scaler scaler(fBitmap, rect, msgr, 0, true);
|
|
BBitmap* thumbnail = scaler.GetBitmap();
|
|
scaler.Start(false);
|
|
ASSERT(thumbnail->ColorSpace() == B_CMAP8);
|
|
// create icon from thumbnail
|
|
BBitmap icon(BRect(0, 0, size - 1, size - 1), B_CMAP8);
|
|
memset(icon.Bits(), B_TRANSPARENT_MAGIC_CMAP8, icon.BitsLength());
|
|
BScreen screen;
|
|
const uchar* src = (uchar*)thumbnail->Bits();
|
|
uchar* dest = (uchar*)icon.Bits();
|
|
const int32 srcBPR = thumbnail->BytesPerRow();
|
|
const int32 destBPR = icon.BytesPerRow();
|
|
const int32 deltaX = (int32)rect.left;
|
|
const int32 deltaY = (int32)rect.top;
|
|
|
|
for (int32 y = 0; y <= rect.IntegerHeight(); y++) {
|
|
for (int32 x = 0; x <= rect.IntegerWidth(); x++) {
|
|
const uchar* s = src + y * srcBPR + x;
|
|
uchar* d = dest + (y + deltaY) * destBPR + (x + deltaX);
|
|
*d = *s;
|
|
}
|
|
}
|
|
|
|
// set icon
|
|
BNode node(&fCurrentRef);
|
|
BNodeInfo info(&node);
|
|
info.SetIcon(clear ? NULL : &icon, which);
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::SetIcon(bool clear)
|
|
{
|
|
_SetIcon(clear, B_MINI_ICON);
|
|
_SetIcon(clear, B_LARGE_ICON);
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_ToggleSlideShow()
|
|
{
|
|
_SendMessageToWindow(MSG_SLIDE_SHOW);
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_StopSlideShow()
|
|
{
|
|
_SendMessageToWindow(kMsgStopSlideShow);
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_ExitFullScreen()
|
|
{
|
|
be_app->ShowCursor();
|
|
_SendMessageToWindow(MSG_EXIT_FULL_SCREEN);
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::_ShowToolBarIfEnabled(bool show)
|
|
{
|
|
BMessage message(kShowToolBarIfEnabled);
|
|
message.AddBool("show", show);
|
|
Window()->PostMessage(&message);
|
|
}
|
|
|
|
|
|
void
|
|
ShowImageView::WindowActivated(bool active)
|
|
{
|
|
fIsActiveWin = active;
|
|
fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME;
|
|
}
|
|
|