Files
haiku-beta6/src/apps/showimage/ShowImageView.cpp
T
Stephan Aßmus 5ab8434cae * Fixed storage of image data in the system clipboard. According to the
comments in the code, different programs do it differently. However, I
   believe we should follow the BeBook documentation. This is the method which
   WonderBrush uses as well, and that one had been tested with other apps like
   Refraction. Potentially, copy&paste could work across GoBe Productive now
   also, but I didn't test.
 * Handle the case where the selection bitmap that gets restored from the
   clipboard has a bounds rectangle with an offset. Use this offset as an
   alternative to the "be:location" hack.
 * Fix the drag bitmap to be semi-transparent on Haiku. B_OP_SELECT does not
   affect the alpha channel on Haiku like it apparently did on BeOS, though
   I am not inclined to change that behavior.
 * Fixed some coding style violations, but tried to mix as little cleanup as
   possible.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@37118 a95241bf-73f2-0310-859d-f6bbb57e9c96
2010-06-12 17:03:53 +00:00

2657 lines
57 KiB
C++

/*
* Copyright 2003-2010, 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 <[email protected]>
*/
#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 <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 "ProgressWindow.h"
#include "ShowImageApp.h"
#include "ShowImageConstants.h"
#include "ShowImageWindow.h"
// TODO: Remove this and use Tracker's Command.h once it is moved into the private headers
namespace BPrivate {
const uint32 kMoveToTrash = 'Ttrs';
}
using std::nothrow;
class PopUpMenu : public BPopUpMenu {
public:
PopUpMenu(const char* name, BMessenger target);
virtual ~PopUpMenu();
private:
BMessenger fTarget;
};
#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';
static bool
entry_ref_is_file(const entry_ref *ref)
{
BEntry entry(ref, true);
if (entry.InitCheck() != B_OK)
return false;
return entry.IsFile();
}
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),
fDither(BScreen().ColorSpace() == B_CMAP8),
fDocumentIndex(1),
fDocumentCount(1),
fBitmap(NULL),
fDisplayBitmap(NULL),
fSelectionBitmap(NULL),
fZoom(1.0),
fScaleBilinear(true),
fScaler(NULL),
fShrinkToBounds(false),
fZoomToBounds(false),
fShrinkOrZoomToBounds(false),
fFullScreen(false),
fLeft(0.0),
fTop(0.0),
fMovesImage(false),
fMakesSelection(false),
fFirstPoint(0.0, 0.0),
fAnimateSelection(true),
fHasSelection(false),
fSlideShow(false),
fSlideShowDelay(3 * 10), // 3 seconds
fSlideShowCountDown(0),
#if DELAYED_SCALING
fScalingCountDown(SCALING_DELAY_TIME),
#endif
fShowCaption(false),
fInverted(false),
fShowingPopUpMenu(false),
fHideCursorCountDown(HIDE_CURSOR_DELAY_TIME),
fIsActiveWin(true),
fProgressWindow(NULL)
{
_InitPatterns();
ShowImageSettings* settings;
settings = my_app->Settings();
if (settings->Lock()) {
fDither = settings->GetBool("Dither", fDither);
fShrinkToBounds = settings->GetBool("ShrinkToBounds", fShrinkToBounds);
fZoomToBounds = settings->GetBool("ZoomToBounds", fZoomToBounds);
fSlideShowDelay = settings->GetInt32("SlideShowDelay", fSlideShowDelay);
fScaleBilinear = settings->GetBool("ScaleBilinear", fScaleBilinear);
settings->Unlock();
}
SetViewColor(B_TRANSPARENT_COLOR);
SetHighColor(kBorderColor);
SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
SetPenSize(PEN_SIZE);
}
ShowImageView::~ShowImageView()
{
_DeleteBitmap();
}
//! Use patterns to simulate marching ants for selection
void
ShowImageView::_InitPatterns()
{
uchar p;
uchar p1 = 0x33;
uchar p2 = 0xCC;
for (int i = 0; i <= 7; i ++) {
fPatternLeft.data[i] = p1;
fPatternRight.data[i] = p2;
if ((i / 2) % 2 == 0) {
p = 255;
} else {
p = 0;
}
fPatternUp.data[i] = p;
fPatternDown.data[i] = ~p;
}
}
void
ShowImageView::_RotatePatterns()
{
int i;
uchar p;
bool set;
// rotate up
p = fPatternUp.data[0];
for (i = 0; i <= 6; i ++) {
fPatternUp.data[i] = fPatternUp.data[i+1];
}
fPatternUp.data[7] = p;
// rotate down
p = fPatternDown.data[7];
for (i = 7; i >= 1; i --) {
fPatternDown.data[i] = fPatternDown.data[i-1];
}
fPatternDown.data[0] = p;
// rotate to left
p = fPatternLeft.data[0];
set = (p & 0x80) != 0;
p <<= 1;
p &= 0xfe;
if (set) p |= 1;
memset(fPatternLeft.data, p, 8);
// rotate to right
p = fPatternRight.data[0];
set = (p & 1) != 0;
p >>= 1;
if (set) p |= 0x80;
memset(fPatternRight.data, p, 8);
}
void
ShowImageView::_AnimateSelection(bool enabled)
{
fAnimateSelection = enabled;
}
void
ShowImageView::Pulse()
{
// animate marching ants
if (_HasSelection() && fAnimateSelection && fIsActiveWin) {
_RotatePatterns();
_DrawSelectionBox();
}
if (fSlideShow) {
fSlideShowCountDown --;
if (fSlideShowCountDown <= 0) {
fSlideShowCountDown = fSlideShowDelay;
if (!NextFile()) {
_FirstFile();
}
}
}
// Hide cursor in full screen mode
if (fFullScreen && !_HasSelection() && !fShowingPopUpMenu && fIsActiveWin) {
if (fHideCursorCountDown <= 0)
be_app->ObscureCursor();
else
fHideCursorCountDown--;
}
#if DELAYED_SCALING
if (fBitmap && (fScaleBilinear || fDither) && fScalingCountDown > 0) {
if (fScalingCountDown == 1) {
fScalingCountDown = 0;
_GetScaler(_AlignBitmap());
} else {
fScalingCountDown --;
}
}
#endif
}
bool
ShowImageView::_IsImage(const entry_ref *ref)
{
if (ref == NULL || !entry_ref_is_file(ref))
return false;
BFile file(ref, B_READ_ONLY);
if (file.InitCheck() != B_OK)
return false;
BTranslatorRoster *roster = BTranslatorRoster::Default();
if (!roster)
return false;
BMessage ioExtension;
if (ioExtension.AddInt32("/documentIndex", fDocumentIndex) != B_OK)
return false;
translator_info info;
memset(&info, 0, sizeof(translator_info));
if (roster->Identify(&file, &ioExtension, &info, 0, NULL,
B_TRANSLATOR_BITMAP) != B_OK)
return false;
return true;
}
void
ShowImageView::SetTrackerMessenger(const BMessenger& trackerMessenger)
{
fTrackerMessenger = trackerMessenger;
}
void
ShowImageView::_SendMessageToWindow(BMessage *message)
{
BMessenger msgr(Window());
msgr.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.AddString("status", fImageType.String());
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);
BString status_to_send = fImageType;
if (fHasSelection) {
char size[50];
sprintf(size, " (%.0fx%.0f)",
fSelectionRect.Width()+1.0,
fSelectionRect.Height()+1.0);
status_to_send << size;
}
msg.AddString("status", status_to_send.String());
_SendMessageToWindow(&msg);
}
void
ShowImageView::_AddToRecentDocuments()
{
be_roster->AddToRecentDocuments(&fCurrentRef, kApplicationSignature);
}
void
ShowImageView::_DeleteScaler()
{
if (fScaler) {
fScaler->Stop();
delete fScaler;
fScaler = NULL;
}
#if DELAYED_SCALING
fScalingCountDown = SCALING_DELAY_TIME;
#endif
}
void
ShowImageView::_DeleteBitmap()
{
_DeleteScaler();
_DeleteSelectionBitmap();
if (fDisplayBitmap != fBitmap)
delete fDisplayBitmap;
fDisplayBitmap = NULL;
delete fBitmap;
fBitmap = NULL;
}
void
ShowImageView::_DeleteSelectionBitmap()
{
delete fSelectionBitmap;
fSelectionBitmap = NULL;
}
status_t
ShowImageView::SetImage(const entry_ref *ref)
{
// If no file was specified, load the specified page of
// the current file.
if (ref == NULL)
ref = &fCurrentRef;
BTranslatorRoster *roster = BTranslatorRoster::Default();
if (!roster)
return B_ERROR;
if (!entry_ref_is_file(ref))
return B_ERROR;
BFile file(ref, B_READ_ONLY);
translator_info info;
memset(&info, 0, sizeof(translator_info));
BMessage ioExtension;
if (ref != &fCurrentRef) {
// if new image, reset to first document
fDocumentIndex = 1;
}
if (ioExtension.AddInt32("/documentIndex", fDocumentIndex) != B_OK)
return B_ERROR;
BMessage progress(kMsgProgressStatusUpdate);
if (ioExtension.AddMessenger("/progressMonitor", fProgressWindow) == B_OK
&& ioExtension.AddMessage("/progressMessage", &progress) == B_OK)
fProgressWindow->Start();
// Translate image data and create a new ShowImage window
BBitmapStream outstream;
status_t status = roster->Identify(&file, &ioExtension, &info, 0, NULL,
B_TRANSLATOR_BITMAP);
if (status == B_OK) {
status = roster->Translate(&file, &info, &ioExtension, &outstream,
B_TRANSLATOR_BITMAP);
}
fProgressWindow->Stop();
if (status != B_OK)
return status;
BBitmap *newBitmap = NULL;
if (outstream.DetachBitmap(&newBitmap) != B_OK)
return B_ERROR;
// Now that I've successfully loaded the new bitmap,
// I can be sure it is safe to delete the old one,
// and clear everything
fUndo.Clear();
_SetHasSelection(false);
fMakesSelection = false;
_DeleteBitmap();
fBitmap = newBitmap;
fCurrentRef = *ref;
// prepare the display bitmap
if (fBitmap->ColorSpace() == B_RGBA32)
fDisplayBitmap = compose_checker_background(fBitmap);
if (!fDisplayBitmap)
fDisplayBitmap = fBitmap;
// restore orientation
int32 orientation;
fImageOrientation = k0;
fInverted = false;
if (file.ReadAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0,
&orientation, sizeof(orientation)) == sizeof(orientation)) {
if (orientation & 256)
_DoImageOperation(ImageProcessor::ImageProcessor::kInvert, true);
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;
}
}
// get the number of documents (pages) if it has been supplied
int32 documentCount = 0;
if (ioExtension.FindInt32("/documentCount", &documentCount) == B_OK
&& documentCount > 0)
fDocumentCount = documentCount;
else
fDocumentCount = 1;
fImageType = info.name;
fImageMime = info.MIME;
GetPath(&fCaption);
if (fDocumentCount > 1)
fCaption << ", " << fDocumentIndex << "/" << fDocumentCount;
fCaption << ", " << fImageType;
fZoom = 1.0;
_AddToRecentDocuments();
_Notify();
return B_OK;
}
status_t
ShowImageView::_SetSelection(const entry_ref *ref, BPoint point)
{
BTranslatorRoster *roster = BTranslatorRoster::Default();
if (!roster)
return B_ERROR;
BFile file(ref, B_READ_ONLY);
translator_info info;
memset(&info, 0, sizeof(translator_info));
if (roster->Identify(&file, NULL, &info, 0, NULL,
B_TRANSLATOR_BITMAP) != B_OK)
return B_ERROR;
// Translate image data and create a new ShowImage window
BBitmapStream outstream;
if (roster->Translate(&file, &info, NULL, &outstream,
B_TRANSLATOR_BITMAP) != B_OK)
return B_ERROR;
BBitmap *newBitmap = NULL;
if (outstream.DetachBitmap(&newBitmap) != B_OK)
return B_ERROR;
return _PasteBitmap(newBitmap, point);
}
void
ShowImageView::SetDither(bool dither)
{
if (fDither != dither) {
_SettingsSetBool("Dither", dither);
fDither = dither;
Invalidate();
}
}
void
ShowImageView::SetShowCaption(bool show)
{
if (fShowCaption != show) {
fShowCaption = show;
_UpdateCaption();
}
}
void
ShowImageView::SetShrinkToBounds(bool enable)
{
if (fShrinkToBounds != enable) {
_SettingsSetBool("ShrinkToBounds", enable);
fShrinkToBounds = enable;
FixupScrollBars();
Invalidate();
}
}
void
ShowImageView::SetZoomToBounds(bool enable)
{
if (fZoomToBounds != enable) {
_SettingsSetBool("ZoomToBounds", enable);
fZoomToBounds = enable;
FixupScrollBars();
Invalidate();
}
}
void
ShowImageView::SetFullScreen(bool fullScreen)
{
if (fFullScreen != fullScreen) {
fFullScreen = fullScreen;
if (fFullScreen) {
SetLowColor(0, 0, 0, 255);
} else
SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR));
}
}
BBitmap *
ShowImageView::GetBitmap()
{
return fBitmap;
}
void
ShowImageView::GetName(BString* outName)
{
BEntry entry(&fCurrentRef);
char name[B_FILE_NAME_LENGTH];
if (entry.InitCheck() < B_OK || entry.GetName(name) < B_OK)
outName->SetTo("");
else
outName->SetTo(name);
}
void
ShowImageView::GetPath(BString *outPath)
{
BEntry entry(&fCurrentRef);
BPath path;
if (entry.InitCheck() < B_OK || entry.GetPath(&path) < B_OK)
outPath->SetTo("");
else
outPath->SetTo(path.Path());
}
void
ShowImageView::SetScaleBilinear(bool enabled)
{
if (fScaleBilinear != enabled) {
_SettingsSetBool("ScaleBilinear", enabled);
fScaleBilinear = enabled;
Invalidate();
}
}
void
ShowImageView::AttachedToWindow()
{
fUndo.SetWindow(Window());
FixupScrollBars();
fProgressWindow = new ProgressWindow(Window());
}
void
ShowImageView::DetachedFromWindow()
{
fProgressWindow->Lock();
fProgressWindow->Quit();
}
BRect
ShowImageView::_AlignBitmap()
{
BRect rect(fBitmap->Bounds());
// the width/height of the bitmap (in pixels)
float bitmapWidth = rect.Width() + 1.0;
float bitmapHeight = rect.Height() + 1.0;
// the available width/height for layouting the bitmap (in pixels)
float width = Bounds().Width() - 2 * PEN_SIZE + 1.0;
float height = Bounds().Height() - 2 * PEN_SIZE + 1.0;
if (width == 0 || height == 0)
return rect;
fShrinkOrZoomToBounds = (fShrinkToBounds &&
(bitmapWidth >= width || bitmapHeight >= height)) ||
(fZoomToBounds && (bitmapWidth < width && bitmapHeight < height));
if (fShrinkOrZoomToBounds) {
float s = width / bitmapWidth;
if (s * bitmapHeight <= height) {
rect.right = width - 1;
rect.bottom = static_cast<int>(s * bitmapHeight) - 1;
// center vertically
rect.OffsetBy(0, static_cast<int>((height - rect.Height()) / 2));
} else {
s = height / bitmapHeight;
rect.right = static_cast<int>(s * bitmapWidth) - 1;
rect.bottom = height - 1;
// center horizontally
rect.OffsetBy(static_cast<int>((width - rect.Width()) / 2), 0);
}
} else {
// 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 (width > bitmapWidth)
rect.OffsetBy(floorf((width - bitmapWidth) / 2.0), 0);
if (height > bitmapHeight)
rect.OffsetBy(0, floorf((height - bitmapHeight) / 2.0));
}
rect.OffsetBy(PEN_SIZE, PEN_SIZE);
return rect;
}
void
ShowImageView::_Setup(BRect rect)
{
fLeft = floorf(rect.left);
fTop = floorf(rect.top);
fZoom = (rect.Width()+1.0) / (fBitmap->Bounds().Width()+1.0);
}
BPoint
ShowImageView::_ImageToView(BPoint p) const
{
p.x = floorf(fZoom * p.x + fLeft);
p.y = floorf(fZoom * p.y + fTop);
return p;
}
BPoint
ShowImageView::_ViewToImage(BPoint p) const
{
p.x = floorf((p.x - fLeft) / fZoom);
p.y = floorf((p.y - fTop) / 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::_DrawBorder(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();
}
Scaler*
ShowImageView::_GetScaler(BRect rect)
{
if (fScaler == NULL || !fScaler->Matches(rect, fDither)) {
_DeleteScaler();
BMessenger msgr(this, Window());
fScaler = new Scaler(fDisplayBitmap, rect, msgr, MSG_INVALIDATE, fDither);
fScaler->Start();
}
return fScaler;
}
void
ShowImageView::_DrawImage(BRect rect)
{
if (fDither) {
#if DELAYED_SCALING
Scaler* scaler = fScaler;
if (scaler != NULL && !scaler->Matches(rect, fDither)) {
_DeleteScaler(); scaler = NULL;
}
#else
Scaler* scaler = _GetScaler(rect);
#endif
if (scaler != NULL && !scaler->IsRunning()) {
BBitmap* bitmap = scaler->GetBitmap();
if (bitmap) {
DrawBitmap(bitmap, BPoint(rect.left, rect.top));
return;
}
}
}
// 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();
_Setup(rect);
BRect border(rect);
border.InsetBy(-PEN_SIZE, -PEN_SIZE);
_DrawBorder(border);
// Draw black rectangle around image
StrokeRect(border);
// Draw image
_DrawImage(rect);
if (fShowCaption)
_DrawCaption();
if (_HasSelection()) {
if (fSelectionBitmap != NULL) {
BRect srcRect;
BRect dstRect;
_GetSelectionMergeRects(srcRect, dstRect);
dstRect = _ImageToView(dstRect);
DrawBitmap(fSelectionBitmap, srcRect, dstRect);
}
_DrawSelectionBox();
}
}
void
ShowImageView::_DrawSelectionBox()
{
if (fSelectionRect.Height() > 0.0 && fSelectionRect.Width() > 0.0) {
BRect r(fSelectionRect);
_ConstrainToImage(r);
r = _ImageToView(r);
// draw selection box *around* selection
r.InsetBy(-1, -1);
PushState();
rgb_color white = {255, 255, 255};
SetLowColor(white);
StrokeLine(BPoint(r.left, r.top), BPoint(r.right, r.top), fPatternLeft);
StrokeLine(BPoint(r.right, r.top+1), BPoint(r.right, r.bottom-1), fPatternUp);
StrokeLine(BPoint(r.left, r.bottom), BPoint(r.right, r.bottom), fPatternRight);
StrokeLine(BPoint(r.left, r.top+1), BPoint(r.left, r.bottom-1), fPatternDown);
PopState();
}
}
void
ShowImageView::FrameResized(float /* width */, float /* height */)
{
FixupScrollBars();
}
void
ShowImageView::_ConstrainToImage(BPoint &point)
{
point.ConstrainTo(fBitmap->Bounds());
}
void
ShowImageView::_ConstrainToImage(BRect &rect)
{
rect = rect & fBitmap->Bounds();
}
BBitmap*
ShowImageView::_CopyFromRect(BRect srcRect)
{
BRect rect(0, 0, srcRect.Width(), srcRect.Height());
BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW);
BBitmap *bitmap = new(nothrow) BBitmap(rect, fBitmap->ColorSpace(), true);
if (bitmap == NULL || !bitmap->IsValid()) {
delete bitmap;
return NULL;
}
if (bitmap->Lock()) {
bitmap->AddChild(&view);
view.DrawBitmap(fBitmap, srcRect, rect);
view.Sync();
bitmap->RemoveChild(&view);
bitmap->Unlock();
}
return bitmap;
}
BBitmap*
ShowImageView::_CopySelection(uchar alpha, bool imageSize)
{
bool hasAlpha = alpha != 255;
if (!_HasSelection())
return NULL;
BRect rect(0, 0, fSelectionRect.Width(), fSelectionRect.Height());
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", fImageMime);
msg->AddString("be:filetypes", fImageMime);
msg->AddString("be:type_descriptions", fImageType);
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 (fImageMime == 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", fSelectionRect);
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)
if ((fSelectionRect.Width() * fZoom) < 400.0
&& (fSelectionRect.Height() * fZoom) < 400.0) {
sourcePoint -= fSelectionRect.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(fSelectionRect);
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 *fmts;
int32 num_fmts;
roster->GetOutputFormats(outInfo[i].translator, &fmts, &num_fmts);
for (int32 j = 0; j < num_fmts; j++) {
if (strcmp(fmts[j].MIME, type) == 0) {
*format = fmts[j];
found = true;
break;
}
}
}
}
stream.DetachBitmap(&bitmap);
return found;
}
#undef B_TRANSLATE_CONTEXT
#define B_TRANSLATE_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->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::_MoveImage()
{
BPoint point, delta;
uint32 buttons;
// get CURRENT position
GetMouse(&point, &buttons);
point = ConvertToScreen(point);
delta = fFirstPoint - point;
fFirstPoint = point;
_ScrollRestrictedBy(delta.x, delta.y);
// in case we miss MouseUp
if ((_GetMouseButtons() & B_TERTIARY_MOUSE_BUTTON) == 0)
fMovesImage = false;
}
uint32
ShowImageView::_GetMouseButtons()
{
uint32 buttons;
BPoint point;
GetMouse(&point, &buttons);
if (buttons == B_PRIMARY_MOUSE_BUTTON) {
if ((modifiers() & B_CONTROL_KEY) != 0) {
buttons = B_SECONDARY_MOUSE_BUTTON; // simulate second button
} else if ((modifiers() & B_SHIFT_KEY) != 0) {
buttons = B_TERTIARY_MOUSE_BUTTON; // simulate third button
}
}
return buttons;
}
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, fSelectionRect, 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::_MergeSelection()
{
if (!_HasSelection())
return;
if (fSelectionBitmap == NULL) {
// Even though the merge will not change the background image,
// undo information still needs to be saved here.
fUndo.SetTo(fSelectionRect, NULL, _CopySelection());
return;
}
// Merge selection with background
fUndo.SetTo(fSelectionRect, _CopyFromRect(fSelectionRect),
_CopySelection());
_MergeWithBitmap(fSelectionBitmap, fSelectionRect);
}
void
ShowImageView::MouseDown(BPoint position)
{
BPoint point;
uint32 buttons;
MakeFocus(true);
point = _ViewToImage(position);
buttons = _GetMouseButtons();
if (_HasSelection() && fSelectionRect.Contains(point)
&& (buttons & (B_PRIMARY_MOUSE_BUTTON | B_SECONDARY_MOUSE_BUTTON))) {
if (!fSelectionBitmap)
fSelectionBitmap = _CopySelection();
BPoint sourcePoint = point;
_BeginDrag(sourcePoint);
while (buttons) {
// Keep reading mouse movement until
// the user lets up on all mouse buttons
GetMouse(&point, &buttons);
snooze(25 * 1000);
// sleep for 25 milliseconds to minimize CPU usage during loop
}
if (Bounds().Contains(point)) {
// If selection stayed inside this view
// (Some of the selection may be in the border area, which can be OK)
BPoint last, diff;
last = _ViewToImage(point);
diff = last - sourcePoint;
BRect newSelection = fSelectionRect;
newSelection.OffsetBy(diff);
if (fBitmap->Bounds().Intersects(newSelection)) {
// Do not accept the new selection box location
// if it does not intersect with the bitmap rectangle
fSelectionRect = newSelection;
Invalidate();
}
}
_AnimateSelection(true);
} else if (buttons == B_PRIMARY_MOUSE_BUTTON) {
_MergeSelection();
// If there is an existing selection,
// Make it part of the background image
// begin new selection
_SetHasSelection(true);
fMakesSelection = true;
SetMouseEventMask(B_POINTER_EVENTS);
_ConstrainToImage(point);
fFirstPoint = point;
fCopyFromRect.Set(point.x, point.y, point.x, point.y);
fSelectionRect = fCopyFromRect;
Invalidate();
} else if (buttons == B_SECONDARY_MOUSE_BUTTON) {
_ShowPopUpMenu(ConvertToScreen(position));
} else if (buttons == B_TERTIARY_MOUSE_BUTTON) {
// move image in window
SetMouseEventMask(B_POINTER_EVENTS);
fMovesImage = true;
fFirstPoint = ConvertToScreen(position);
}
}
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);
fSelectionRect = 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 || !_HasSelection()) {
BRect updateRect;
updateRect = oldSelection | fCopyFromRect;
updateRect = _ImageToView(updateRect);
updateRect.InsetBy(-PEN_SIZE, -PEN_SIZE);
Invalidate(updateRect);
}
}
void
ShowImageView::MouseMoved(BPoint point, uint32 state, const BMessage *message)
{
fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME;
if (fMakesSelection) {
_UpdateSelectionRect(point, false);
} else if (fMovesImage) {
_MoveImage();
}
}
void
ShowImageView::MouseUp(BPoint point)
{
if (fMakesSelection) {
_UpdateSelectionRect(point, true);
fMakesSelection = false;
} else if (fMovesImage) {
_MoveImage();
fMovesImage = false;
}
_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);
// hide the caption when using mouse wheel
// in full screen mode
// to prevent the caption from dirtying up the image
// during scrolling.
bool caption = fShowCaption;
if (caption) {
fShowCaption = false;
_UpdateCaption();
}
ScrollBy(x, y);
if (caption) {
// show the caption again
fShowCaption = true;
_UpdateCaption();
}
}
// 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
if (fSlideShow)
_ToggleSlideShow();
_ExitFullScreen();
ClearSelection();
break;
case B_DELETE:
{
// Move image to Trash
BMessage trash(BPrivate::kMoveToTrash);
trash.AddRef("refs", &fCurrentRef);
// We create our own messenger because the member fTrackerMessenger
// could be invalid
BMessenger tracker(kTrackerSignature);
if (tracker.SendMessage(&trash) == B_OK)
if (!NextFile()) {
// This is the last (or only file) in this directory,
// close the window
_SendMessageToWindow(B_QUIT_REQUESTED);
}
break;
}
case '+':
case '=':
ZoomIn();
break;
case '-':
ZoomOut();
break;
case '[':
Rotate(270);
break;
case ']':
Rotate(90);
break;
}
}
void
ShowImageView::_MouseWheelChanged(BMessage *msg)
{
// The BeOS driver does not currently support
// X wheel scrolling, therefore, dx is zero.
// |dy| is the number of notches scrolled up or down.
// When the wheel is scrolled down (towards the user) dy > 0
// When the wheel is scrolled up (away from the user) dy < 0
const float kscrollBy = 40;
float dy, dx;
float x, y;
x = 0; y = 0;
if (msg->FindFloat("be:wheel_delta_x", &dx) == B_OK)
x = dx * kscrollBy;
if (msg->FindFloat("be:wheel_delta_y", &dy) == B_OK)
y = dy * kscrollBy;
if (modifiers() & B_SHIFT_KEY)
_ScrollRestrictedBy(x, y);
else if (modifiers() & B_COMMAND_KEY)
_ScrollRestrictedBy(y, x);
else {
if (dy < 0)
ZoomIn();
else if (dy > 0)
ZoomOut();
}
}
void
ShowImageView::_ShowPopUpMenu(BPoint screen)
{
if (!fShowingPopUpMenu) {
PopUpMenu* menu = new PopUpMenu("PopUpMenu", this);
ShowImageWindow* showImage = dynamic_cast<ShowImageWindow*>(Window());
if (showImage)
showImage->BuildContextMenu(menu);
screen += BPoint(2, 2);
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 MSG_SELECTION_BITMAP:
{
// In response to a B_SIMPLE_DATA message, a view will
// send this message and expect a reply with a pointer to
// the currently selected bitmap clip. Although this view
// allocates the BBitmap * sent in the reply, it is only
// to be used and deleted by the view that is being replied to.
BMessage msg;
msg.AddPointer("be:_bitmap_ptr", _CopySelection());
message->SendReply(&msg);
break;
}
case B_SIMPLE_DATA:
if (message->WasDropped()) {
uint32 type;
int32 count;
status_t ret = message->GetInfo("refs", &type, &count);
if (ret == B_OK && type == B_REF_TYPE) {
// If file was dropped, open it as the selection
entry_ref ref;
if (message->FindRef("refs", 0, &ref) == B_OK) {
BPoint point = message->DropPoint();
point = ConvertFromScreen(point);
point = _ViewToImage(point);
_SetSelection(&ref, point);
}
} else {
// If a user drags a clip from another ShowImage window,
// request a BBitmap pointer to that clip, allocated by the
// other view, for use solely by this view, so that it can
// be dropped/pasted onto this view.
BMessenger retMsgr, localMsgr(this);
retMsgr = message->ReturnAddress();
if (retMsgr != localMsgr) {
BMessage msgReply;
retMsgr.SendMessage(MSG_SELECTION_BITMAP, &msgReply);
BBitmap *bitmap = NULL;
if (msgReply.FindPointer("be:_bitmap_ptr",
reinterpret_cast<void **>(&bitmap)) == B_OK) {
BRect sourceRect;
BPoint point, sourcePoint;
message->FindPoint("be:_source_point", &sourcePoint);
message->FindRect("be:_frame", &sourceRect);
point = message->DropPoint();
point.Set(point.x - (sourcePoint.x - sourceRect.left),
point.y - (sourcePoint.y - sourceRect.top));
// adjust drop point before scaling is factored in
point = ConvertFromScreen(point);
point = _ViewToImage(point);
_PasteBitmap(bitmap, point);
}
}
}
}
break;
case B_COPY_TARGET:
_HandleDrop(message);
break;
case B_MOUSE_WHEEL_CHANGED:
_MouseWheelChanged(message);
break;
case MSG_INVALIDATE:
Invalidate();
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 rctview = Bounds(), rctbitmap(0, 0, 0, 0);
if (fBitmap) {
rctbitmap = _AlignBitmap();
rctbitmap.OffsetTo(0, 0);
}
FixupScrollBar(B_HORIZONTAL, rctbitmap.Width() + 2 * PEN_SIZE, rctview.Width());
FixupScrollBar(B_VERTICAL, rctbitmap.Height() + 2 * PEN_SIZE, rctview.Height());
}
int32
ShowImageView::CurrentPage()
{
return fDocumentIndex;
}
int32
ShowImageView::PageCount()
{
return fDocumentCount;
}
void
ShowImageView::Undo()
{
int32 undoType = fUndo.GetType();
if (undoType != UNDO_UNDO && undoType != UNDO_REDO)
return;
// backup current selection
BRect undoneSelRect;
BBitmap *undoneSelection;
undoneSelRect = fSelectionRect;
undoneSelection = _CopySelection();
if (undoType == UNDO_UNDO) {
BBitmap *undoRestore;
undoRestore = fUndo.GetRestoreBitmap();
if (undoRestore)
_MergeWithBitmap(undoRestore, fUndo.GetRect());
}
// restore previous image/selection
BBitmap *undoSelection;
undoSelection = fUndo.GetSelectionBitmap();
// NOTE: ShowImageView is responsible for deleting this bitmap
// (Which it will, as it would with a fSelectionBitmap that it allocated itself)
if (!undoSelection)
_SetHasSelection(false);
else {
fCopyFromRect = BRect();
fSelectionRect = fUndo.GetRect();
_SetHasSelection(true);
fSelectionBitmap = undoSelection;
}
fUndo.Undo(undoneSelRect, NULL, undoneSelection);
Invalidate();
}
void
ShowImageView::_AddWhiteRect(BRect &rect)
{
// Paint white rectangle, using rect, into the background image
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());
view.FillRect(rect, B_SOLID_LOW);
// draw white rect
view.Sync();
bitmap->RemoveChild(&view);
bitmap->Unlock();
_DeleteBitmap();
fBitmap = bitmap;
_SendMessageToWindow(MSG_MODIFIED);
} else
delete bitmap;
}
void
ShowImageView::_RemoveSelection(bool toClipboard, bool neverCutBackground)
{
if (!_HasSelection())
return;
BRect rect = fSelectionRect;
bool cutBackground = (fSelectionBitmap) ? false : true;
BBitmap* selection = _CopySelection();
BBitmap* restore = NULL;
if (toClipboard)
CopySelectionToClipboard();
_SetHasSelection(false);
if (!neverCutBackground && cutBackground) {
// If the user hasn't dragged the selection,
// paint a white rectangle where the selection was
restore = _CopyFromRect(rect);
_AddWhiteRect(rect);
}
fUndo.SetTo(rect, restore, selection);
Invalidate();
}
void
ShowImageView::Cut()
{
// Copy the selection to the clipboard,
// then remove it
_RemoveSelection(true);
}
status_t
ShowImageView::_PasteBitmap(BBitmap* bitmap, BPoint point)
{
if (bitmap == NULL || !bitmap->IsValid())
return B_BAD_VALUE;
_MergeSelection();
fCopyFromRect = BRect();
fSelectionRect = bitmap->Bounds().OffsetToCopy(B_ORIGIN);
_SetHasSelection(true);
delete fSelectionBitmap;
fSelectionBitmap = bitmap;
BRect offsetRect(fSelectionRect.OffsetToCopy(point));
if (fBitmap->Bounds().Intersects(offsetRect)) {
// Move the selection rectangle to desired origin,
// but only if the resulting selection rectangle
// intersects with the background bitmap rectangle
fSelectionRect = offsetRect;
}
printf("bitmap: "); bitmap->Bounds().PrintToStream();
printf("selection: "); fSelectionRect.PrintToStream();
printf("point: "); point.PrintToStream();
Invalidate();
return B_OK;
}
void
ShowImageView::Paste()
{
if (!be_clipboard->Lock())
return;
BMessage* data = be_clipboard->Data();
if (data != NULL) {
BMessage bitmapArchive;
if (data->FindMessage("image/bitmap", &bitmapArchive) == B_OK
|| data->FindMessage("image/x-be-bitmap", &bitmapArchive)
== B_OK) {
BBitmap* bitmap = dynamic_cast<BBitmap*>(
BBitmap::Instantiate(&bitmapArchive));
if (bitmap != NULL) {
BPoint point;
if (data->FindPoint("be:location", &point) != B_OK)
point = bitmap->Bounds().LeftTop();
_PasteBitmap(bitmap, point);
}
}
}
be_clipboard->Unlock();
}
void
ShowImageView::SelectAll()
{
_SetHasSelection(true);
fCopyFromRect.Set(0, 0, fBitmap->Bounds().Width(),
fBitmap->Bounds().Height());
fSelectionRect = fCopyFromRect;
Invalidate();
}
void
ShowImageView::ClearSelection()
{
// Remove the selection,
// DON'T copy it to the clipboard
// or white out the selection
_RemoveSelection(false, true);
}
void
ShowImageView::_SetHasSelection(bool hasSelection)
{
_DeleteSelectionBitmap();
fHasSelection = hasSelection;
_UpdateStatusText();
BMessage msg(MSG_SELECTION);
msg.AddBool("has_selection", fHasSelection);
_SendMessageToWindow(&msg);
}
void
ShowImageView::CopySelectionToClipboard()
{
if (!_HasSelection() || !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", fSelectionRect.LeftTop());
delete bitmap;
be_clipboard->Commit();
}
}
be_clipboard->Unlock();
}
void
ShowImageView::FirstPage()
{
if (fDocumentIndex != 1) {
fDocumentIndex = 1;
SetImage(NULL);
}
}
void
ShowImageView::LastPage()
{
if (fDocumentIndex != fDocumentCount) {
fDocumentIndex = fDocumentCount;
SetImage(NULL);
}
}
void
ShowImageView::NextPage()
{
if (fDocumentIndex < fDocumentCount) {
fDocumentIndex++;
SetImage(NULL);
}
}
void
ShowImageView::PrevPage()
{
if (fDocumentIndex > 1) {
fDocumentIndex--;
SetImage(NULL);
}
}
int
ShowImageView::_CompareEntries(const void* a, const void* b)
{
entry_ref *r1, *r2;
r1 = *(entry_ref**)a;
r2 = *(entry_ref**)b;
return strcasecmp(r1->name, r2->name);
}
void
ShowImageView::GoToPage(int32 page)
{
if (page > 0 && page <= fDocumentCount && page != fDocumentIndex) {
fDocumentIndex = page;
SetImage(NULL);
}
}
void
ShowImageView::_FreeEntries(BList* entries)
{
const int32 n = entries->CountItems();
for (int32 i = 0; i < n; i ++) {
entry_ref* ref = (entry_ref*)entries->ItemAt(i);
delete ref;
}
entries->MakeEmpty();
}
void
ShowImageView::_SetTrackerSelectionToCurrent()
{
BMessage setsel(B_SET_PROPERTY);
setsel.AddSpecifier("Selection");
setsel.AddRef("data", &fCurrentRef);
fTrackerMessenger.SendMessage(&setsel);
}
bool
ShowImageView::_FindNextImage(entry_ref *in_current, entry_ref *ref, bool next,
bool rewind)
{
// Based on GetTrackerWindowFile function from BeMail
if (!fTrackerMessenger.IsValid())
return false;
//
// Ask the Tracker what the next/prev file in the window is.
// Continue asking for the next reference until a valid
// image is found.
//
entry_ref nextRef = *in_current;
bool foundRef = false;
while (!foundRef)
{
BMessage request(B_GET_PROPERTY);
BMessage spc;
if (rewind)
spc.what = B_DIRECT_SPECIFIER;
else if (next)
spc.what = 'snxt';
else
spc.what = 'sprv';
spc.AddString("property", "Entry");
if (rewind)
// if rewinding, ask for the ref to the
// first item in the directory
spc.AddInt32("data", 0);
else
spc.AddRef("data", &nextRef);
request.AddSpecifier(&spc);
BMessage reply;
if (fTrackerMessenger.SendMessage(&request, &reply) != B_OK)
return false;
if (reply.FindRef("result", &nextRef) != B_OK)
return false;
if (_IsImage(&nextRef))
foundRef = true;
rewind = false;
// stop asking for the first ref in the directory
}
*ref = nextRef;
return foundRef;
}
bool
ShowImageView::_ShowNextImage(bool next, bool rewind)
{
entry_ref curRef = fCurrentRef;
entry_ref imgRef;
bool found = _FindNextImage(&curRef, &imgRef, next, rewind);
if (found) {
// Keep trying to load images until:
// 1. The image loads successfully
// 2. The last file in the directory is found (for find next or find first)
// 3. The first file in the directory is found (for find prev)
// 4. The call to _FindNextImage fails for any other reason
while (SetImage(&imgRef) != B_OK) {
curRef = imgRef;
found = _FindNextImage(&curRef, &imgRef, next, false);
if (!found)
return false;
}
_SetTrackerSelectionToCurrent();
return true;
}
return false;
}
bool
ShowImageView::NextFile()
{
return _ShowNextImage(true, false);
}
bool
ShowImageView::PrevFile()
{
return _ShowNextImage(false, false);
}
bool
ShowImageView::HasNextFile()
{
entry_ref ref;
return _FindNextImage(&fCurrentRef, &ref, true, false);
}
bool
ShowImageView::HasPrevFile()
{
entry_ref ref;
return _FindNextImage(&fCurrentRef, &ref, false, false);
}
bool
ShowImageView::_FirstFile()
{
return _ShowNextImage(true, true);
}
void
ShowImageView::SetZoom(float zoom)
{
if ((fScaleBilinear || fDither) && fZoom != zoom) {
_DeleteScaler();
}
fZoom = zoom;
FixupScrollBars();
Invalidate();
}
void
ShowImageView::ZoomIn()
{
if (fZoom < 16)
SetZoom(fZoom + 0.25);
}
void
ShowImageView::ZoomOut()
{
if (fZoom > 0.25)
SetZoom(fZoom - 0.25);
}
void
ShowImageView::SetSlideShowDelay(float seconds)
{
ShowImageSettings* settings;
int32 delay = (int)(seconds * 10.0);
if (fSlideShowDelay != delay) {
// update counter
fSlideShowCountDown = delay - (fSlideShowDelay - fSlideShowCountDown);
if (fSlideShowCountDown <= 0) {
// show next image on next Pulse()
fSlideShowCountDown = 1;
}
fSlideShowDelay = delay;
settings = my_app->Settings();
if (settings->Lock()) {
settings->SetInt32("SlideShowDelay", fSlideShowDelay);
settings->Unlock();
}
}
}
void
ShowImageView::StartSlideShow()
{
fSlideShow = true; fSlideShowCountDown = fSlideShowDelay;
}
void
ShowImageView::StopSlideShow()
{
fSlideShow = false;
}
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];
} else {
fInverted = !fInverted;
}
if (!quiet) {
// write orientation state
BNode node(&fCurrentRef);
int32 orientation = fImageOrientation;
if (fInverted) orientation += 256;
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 (!quiet) {
// remove selection
_SetHasSelection(false);
_Notify();
}
}
//! image operation initiated by user
void
ShowImageView::_UserDoImageOperation(ImageProcessor::operation op, bool quiet)
{
fUndo.Clear();
_DoImageOperation(op, quiet);
}
void
ShowImageView::Rotate(int degree)
{
if (degree == 90) {
_UserDoImageOperation(ImageProcessor::kRotateClockwise);
} else if (degree == 270) {
_UserDoImageOperation(ImageProcessor::kRotateCounterClockwise);
}
}
void
ShowImageView::Flip(bool vertical)
{
if (vertical) {
_UserDoImageOperation(ImageProcessor::kFlipLeftToRight);
} else {
_UserDoImageOperation(ImageProcessor::kFlipTopToBottom);
}
}
void
ShowImageView::Invert()
{
if (fBitmap->ColorSpace() != B_CMAP8) {
// Only allow an invert operation if the
// bitmap color space is supported by the
// invert algorithm
_UserDoImageOperation(ImageProcessor::kInvert);
}
}
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);
fUndo.Clear();
_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 dx = (int32)rect.left;
const int32 dy = (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+dy) * destBPR + (x+dx);
*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::_ExitFullScreen()
{
be_app->ShowCursor();
_SendMessageToWindow(MSG_EXIT_FULL_SCREEN);
}
void
ShowImageView::WindowActivated(bool active)
{
fIsActiveWin = active;
fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME;
}