Files
haiku-beta6/src/apps/showimage/ShowImageView.cpp
T
Matthew Wilber 3a85b92372 Fixed page changing, simplified entry_ref usage in SetImage()
git-svn-id: file:///srv/svn/repos/haiku/trunk/current@11366 a95241bf-73f2-0310-859d-f6bbb57e9c96
2005-02-13 17:57:38 +00:00

2308 lines
53 KiB
C++

/*****************************************************************************/
// ShowImageView
// Written by Fernando Francisco de Oliveira, Michael Wilber, Michael Pfeiffer
//
// ShowImageView.cpp
//
//
// Copyright (c) 2003 OpenBeOS Project
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
/*****************************************************************************/
#include <stdio.h>
#include <math.h>
#include <Debug.h>
#include <Message.h>
#include <ScrollBar.h>
#include <StopWatch.h>
#include <Alert.h>
#include <MenuBar.h>
#include <MenuItem.h>
#include <File.h>
#include <Bitmap.h>
#include <TranslatorRoster.h>
#include <BitmapStream.h>
#include <Rect.h>
#include <SupportDefs.h>
#include <Directory.h>
#include <Entry.h>
#include <Application.h>
#include <Roster.h>
#include <NodeInfo.h>
#include <Clipboard.h>
#include <Path.h>
#include <PopUpMenu.h>
#include <Region.h>
#include <Screen.h>
#include "ShowImageApp.h"
#include "ShowImageConstants.h"
#include "ShowImageView.h"
#include "ShowImageWindow.h"
#ifndef min
#define min(a,b) ((a)>(b)?(b):(a))
#endif
#ifndef max
#define max(a,b) ((a)>(b)?(a):(b))
#endif
#define SHOW_IMAGE_ORIENTATION_ATTRIBUTE "ShowImage:orientation"
#define BORDER_WIDTH 16
#define BORDER_HEIGHT 16
#define PEN_SIZE 1.0f
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}
};
// 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 a)
{
fAnimateSelection = a;
}
void
ShowImageView::Pulse()
{
// animate marching ants
if (HasSelection() && fAnimateSelection && Window()->IsActive()) {
RotatePatterns();
DrawSelectionBox();
}
if (fSlideShow) {
fSlideShowCountDown --;
if (fSlideShowCountDown <= 0) {
fSlideShowCountDown = fSlideShowDelay;
if (!NextFile()) {
FirstFile();
}
}
}
#if DELAYED_SCALING
if (fBitmap && (fScaleBilinear || fDither) && fScalingCountDown > 0) {
fScalingCountDown --;
if (fScalingCountDown == 0) {
GetScaler(AlignBitmap());
}
}
#endif
}
ShowImageView::ShowImageView(BRect rect, const char *name, uint32 resizingMode,
uint32 flags)
: BView(rect, name, resizingMode, flags)
{
ShowImageSettings* settings;
settings = my_app->Settings();
InitPatterns();
fDither = false;
fBitmap = NULL;
fSelBitmap = NULL;
fDocumentIndex = 1;
fDocumentCount = 1;
fAnimateSelection = true;
fHasSelection = false;
fShrinkToBounds = false;
fZoomToBounds = false;
fHasBorder = true;
fHAlignment = B_ALIGN_LEFT;
fVAlignment = B_ALIGN_TOP;
fSlideShow = false;
fSlideShowDelay = 3 * 10; // 3 seconds
fShowCaption = false;
fZoom = 1.0;
fMovesImage = false;
fScaleBilinear = false;
fScaler = NULL;
#if DELAYED_SCALING
fScalingCountDown = 10;
#endif
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();
}
// returns B_ERROR if problems reading ref
// B_OK if ref is not a directory
// B_OK + 1 if ref is a directory
status_t
ent_is_dir(const entry_ref *ref)
{
BEntry ent(ref);
if (ent.InitCheck() != B_OK)
return B_ERROR;
struct stat st;
if (ent.GetStat(&st) != B_OK)
return B_ERROR;
return S_ISDIR(st.st_mode) ? (B_OK + 1) : B_OK;
}
bool
ShowImageView::IsImage(const entry_ref *pref)
{
if (!pref)
return false;
if (ent_is_dir(pref) != B_OK)
// if ref is erroneous or a directory, return false
return false;
BFile file(pref, B_READ_ONLY);
if (file.InitCheck() != B_OK)
return false;
BTranslatorRoster *proster = BTranslatorRoster::Default();
if (!proster)
return false;
BMessage ioExtension;
if (ioExtension.AddInt32("/documentIndex", fDocumentIndex) != B_OK)
return false;
translator_info info;
memset(&info, 0, sizeof(translator_info));
if (proster->Identify(&file, &ioExtension, &info, 0, NULL,
B_TRANSLATOR_BITMAP) != B_OK)
return false;
return true;
}
void
ShowImageView::SetTrackerMessenger(const BMessenger& trackerMessenger)
{
fTrackerMessenger = trackerMessenger;
}
// send message to parent about new image
void
ShowImageView::Notify(const char* status)
{
BMessage msg(MSG_UPDATE_STATUS);
if (status != NULL) {
msg.AddString("status", status);
}
msg.AddInt32("width", fBitmap->Bounds().IntegerWidth() + 1);
msg.AddInt32("height", fBitmap->Bounds().IntegerHeight() + 1);
msg.AddInt32("colors", fBitmap->ColorSpace());
BMessenger msgr(Window());
msgr.SendMessage(&msg);
FixupScrollBars();
Invalidate();
}
void
ShowImageView::AddToRecentDocuments()
{
be_roster->AddToRecentDocuments(&fCurrentRef, APP_SIG);
}
void
ShowImageView::DeleteScaler()
{
if (fScaler) {
fScaler->Stop();
delete fScaler;
fScaler = NULL;
}
#if DELAYED_SCALING
fScalingCountDown = 3; // delay for 3/10 seconds
#endif
}
void
ShowImageView::DeleteBitmap()
{
DeleteScaler();
DeleteSelBitmap();
delete fBitmap;
fBitmap = NULL;
}
void ShowImageView::DeleteSelBitmap()
{
delete fSelBitmap;
fSelBitmap = NULL;
}
status_t
ShowImageView::SetImage(const entry_ref *pref)
{
// If no file was specified, load the specified page of
// the current file.
if (pref == NULL)
pref = &fCurrentRef;
BTranslatorRoster *proster = BTranslatorRoster::Default();
if (!proster)
return B_ERROR;
if (ent_is_dir(pref) != B_OK)
// if ref is erroneous or a directory, return error
return B_ERROR;
BFile file(pref, B_READ_ONLY);
translator_info info;
memset(&info, 0, sizeof(translator_info));
BMessage ioExtension;
if (pref != &fCurrentRef)
// if new image, reset to first document
fDocumentIndex = 1;
if (ioExtension.AddInt32("/documentIndex", fDocumentIndex) != B_OK)
return B_ERROR;
if (proster->Identify(&file, &ioExtension, &info, 0, NULL,
B_TRANSLATOR_BITMAP) != B_OK)
return B_ERROR;
// Translate image data and create a new ShowImage window
BBitmapStream outstream;
if (proster->Translate(&file, &info, &ioExtension, &outstream,
B_TRANSLATOR_BITMAP) != B_OK)
return B_ERROR;
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;
newBitmap = NULL;
fCurrentRef = *pref;
// 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::kRotateAntiClockwise, true);
break;
case k0V:
DoImageOperation(ImageProcessor::ImageProcessor::kMirrorHorizontal, true);
break;
case k90V:
DoImageOperation(ImageProcessor::kRotateClockwise, true);
DoImageOperation(ImageProcessor::ImageProcessor::kMirrorHorizontal, true);
break;
case k0H:
DoImageOperation(ImageProcessor::ImageProcessor::kMirrorVertical, true);
break;
case k270V:
DoImageOperation(ImageProcessor::kRotateAntiClockwise, true);
DoImageOperation(ImageProcessor::ImageProcessor::kMirrorHorizontal, 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;
GetPath(&fCaption);
if (fDocumentCount > 1) {
fCaption << ", " << fDocumentIndex << "/" << fDocumentCount;
}
fCaption << ", " << info.name;
AddToRecentDocuments();
Notify(info.name);
return B_OK;
}
status_t
ShowImageView::SetSelection(const entry_ref *pref, BPoint point)
{
BTranslatorRoster *proster = BTranslatorRoster::Default();
if (!proster)
return B_ERROR;
BFile file(pref, B_READ_ONLY);
translator_info info;
memset(&info, 0, sizeof(translator_info));
if (proster->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 (proster->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::SetBorder(bool hasBorder)
{
if (fHasBorder != hasBorder) {
fHasBorder = hasBorder;
FixupScrollBars();
Invalidate();
}
}
void
ShowImageView::SetAlignment(alignment horizontal, vertical_alignment vertical)
{
bool hasChanged;
hasChanged = fHAlignment != horizontal || fVAlignment != vertical;
if (hasChanged) {
fHAlignment = horizontal;
fVAlignment = vertical;
FixupScrollBars();
Invalidate();
}
}
BBitmap *
ShowImageView::GetBitmap()
{
return fBitmap;
}
void
ShowImageView::GetName(BString *name)
{
*name = "";
BEntry entry(&fCurrentRef);
if (entry.InitCheck() == B_OK) {
char n[B_FILE_NAME_LENGTH];
if (entry.GetName(n) == B_OK) {
name->SetTo(n);
}
}
}
void
ShowImageView::GetPath(BString *name)
{
*name = "";
BEntry entry(&fCurrentRef);
if (entry.InitCheck() == B_OK) {
BPath path;
entry.GetPath(&path);
if (path.InitCheck() == B_OK) {
name->SetTo(path.Path());
}
}
}
void
ShowImageView::FlushToLeftTop()
{
BRect rect = AlignBitmap();
BPoint p(rect.left, rect.top);
ScrollTo(p);
}
void
ShowImageView::SetScaleBilinear(bool s)
{
if (fScaleBilinear != s) {
SettingsSetBool("ScaleBilinear", s);
fScaleBilinear = s; Invalidate();
}
}
void
ShowImageView::AttachedToWindow()
{
fUndo.SetWindow(Window());
FixupScrollBars();
}
BRect
ShowImageView::AlignBitmap()
{
BRect rect(fBitmap->Bounds());
float width, height;
width = Bounds().Width()-2*PEN_SIZE+1;
height = Bounds().Height()-2*PEN_SIZE+1;
if (width == 0 || height == 0) return rect;
fShrinkOrZoomToBounds = fShrinkToBounds &&
(rect.Width() >= Bounds().Width() || rect.Height() >= Bounds().Height()) ||
fZoomToBounds && rect.Width() < Bounds().Width() && rect.Height() < Bounds().Height();
if (fShrinkOrZoomToBounds) {
float s;
s = width / (rect.Width()+1.0);
if (s * (rect.Height()+1.0) <= height) {
rect.right = width-1;
rect.bottom = static_cast<int>(s * (rect.Height()+1.0))-1;
// center vertically
rect.OffsetBy(0, static_cast<int>((height - rect.Height()) / 2));
} else {
s = height / (rect.Height()+1.0);
rect.right = static_cast<int>(s * (rect.Width()+1.0))-1;
rect.bottom = height-1;
// center horizontally
rect.OffsetBy(static_cast<int>((width - rect.Width()) / 2), 0);
}
} else {
float zoom;
if (fShrinkToBounds || fZoomToBounds) {
// ignore user zoom setting in automatic zoom modes
zoom = 1.0;
} else {
zoom = fZoom;
}
// zoom image
rect.right = static_cast<int>((rect.right+1.0)*zoom)-1;
rect.bottom = static_cast<int>((rect.bottom+1.0)*zoom)-1;
// align
switch (fHAlignment) {
case B_ALIGN_CENTER:
if (width > rect.Width()) {
rect.OffsetBy((width - rect.Width()) / 2.0, 0);
break;
}
// fall through
default:
case B_ALIGN_LEFT:
if (fHasBorder) {
rect.OffsetBy(BORDER_WIDTH, 0);
}
break;
}
switch (fVAlignment) {
case B_ALIGN_MIDDLE:
if (height > rect.Height()) {
rect.OffsetBy(0, (height - rect.Height()) / 2.0);
break;
}
// fall through
default:
case B_ALIGN_TOP:
if (fHasBorder) {
rect.OffsetBy(0, BORDER_WIDTH);
}
break;
}
}
rect.OffsetBy(PEN_SIZE, PEN_SIZE);
return rect;
}
void
ShowImageView::Setup(BRect rect)
{
fLeft = floorf(rect.left);
fTop = floorf(rect.top);
fScaleX = (rect.Width()+1.0) / (fBitmap->Bounds().Width()+1.0);
fScaleY = (rect.Height()+1.0) / (fBitmap->Bounds().Height()+1.0);
}
BPoint
ShowImageView::ImageToView(BPoint p) const
{
p.x = floorf(fScaleX * p.x + fLeft);
p.y = floorf(fScaleY * p.y + fTop);
return p;
}
BPoint
ShowImageView::ViewToImage(BPoint p) const
{
p.x = floorf((p.x - fLeft) / fScaleX);
p.y = floorf((p.y - fTop) / fScaleY);
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()) + 1; // 1 for text shadow
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 - 5;
// background rectangle
rect.Set(0, 0, (width-1)+2, (height-1)+2+1); // 2 for border and 1 for text shadow
rect.OffsetTo(pos);
rect.OffsetBy(-1, -1-fontHeight.ascent); // -1 for border
}
void
ShowImageView::DrawCaption()
{
BFont font;
BPoint pos;
BRect rect;
LayoutCaption(font, pos, rect);
PushState();
// draw background
SetDrawingMode(B_OP_ALPHA);
SetHighColor(0, 0, 255, 128);
FillRect(rect);
// draw text
SetDrawingMode(B_OP_OVER);
SetFont(&font);
SetLowColor(B_TRANSPARENT_COLOR);
// text shadow
pos += BPoint(1, 1);
SetHighColor(0, 0, 0);
SetPenSize(1);
DrawString(fCaption.String(), pos);
// text
pos -= BPoint(1, 1);
SetHighColor(255, 255, 0);
DrawString(fCaption.String(), pos);
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(fBitmap, rect, msgr, MSG_INVALIDATE, fDither);
fScaler->Start();
}
return fScaler;
}
void
ShowImageView::DrawImage(BRect rect)
{
if (fScaleBilinear || 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;
}
}
}
DrawBitmap(fBitmap, fBitmap->Bounds(), rect);
}
void
ShowImageView::Draw(BRect updateRect)
{
if (fBitmap) {
if (!IsPrinting()) {
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) {
// fShowCaption is set to false by ScrollRestricted()
// to prevent the caption from dirtying up the image
// during scrolling.
DrawCaption();
}
if (HasSelection()) {
if (fSelBitmap) {
BRect srcBits, destRect;
GetSelMergeRects(srcBits, destRect);
destRect = ImageToView(destRect);
DrawBitmap(fSelBitmap, srcBits, destRect);
}
DrawSelectionBox();
}
} else {
DrawBitmap(fBitmap);
}
}
}
void
ShowImageView::DrawSelectionBox()
{
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)
{
BRect bounds = fBitmap->Bounds();
BPoint leftTop, rightBottom;
leftTop = rect.LeftTop();
leftTop.ConstrainTo(bounds);
rightBottom = rect.RightBottom();
rightBottom.ConstrainTo(bounds);
rect.SetLeftTop(leftTop);
rect.SetRightBottom(rightBottom);
}
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 BBitmap(rect, fBitmap->ColorSpace(), true);
if (bitmap == NULL) 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) * fScaleX - 1.0);
rect.bottom = floorf((rect.bottom + 1.0) * fScaleY - 1.0);
}
BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW);
BBitmap *bitmap = new BBitmap(rect, hasAlpha ? B_RGBA32 : fBitmap->ColorSpace(), true);
if (bitmap == NULL) return NULL;
if (bitmap->Lock()) {
bitmap->AddChild(&view);
if (fSelBitmap)
view.DrawBitmap(fSelBitmap, fSelBitmap->Bounds(), 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);
}
view.Sync();
bitmap->RemoveChild(&view);
bitmap->Unlock();
}
return bitmap;
}
bool
ShowImageView::AddSupportedTypes(BMessage* msg, BBitmap* bitmap)
{
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, "image/x-be-bitmap") != 0) {
// needed to send data in message
msg->AddString("be:types", fmts[j].MIME);
// needed to pass data via file
msg->AddString("be:filetypes", fmts[j].MIME);
msg->AddString("be:type_descriptions", fmts[j].name);
}
found = true;
}
}
}
stream.DetachBitmap(&bitmap);
return found;
}
void
ShowImageView::BeginDrag(BPoint sourcePoint)
{
BBitmap* bitmap = CopySelection(128, false);
if (bitmap == NULL) return;
SetMouseEventMask(B_POINTER_EVENTS);
BPoint leftTop(fSelectionRect.left, fSelectionRect.top);
// 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);
sourcePoint -= leftTop;
sourcePoint.x *= fScaleX;
sourcePoint.y *= fScaleY;
// DragMessage takes ownership of bitmap
DragMessage(&drag, bitmap, B_OP_ALPHA, sourcePoint);
bitmap = NULL;
}
}
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;
}
void
ShowImageView::SaveToFile(BDirectory* dir, const char* name, BBitmap* bitmap, const translation_format* format)
{
if (!bitmap)
// 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
BString errText;
errText << "Sorry, the file '" << name << "' could not be written.";
BAlert *palert = new BAlert(NULL, errText.String(), "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)
{
BMessage data(B_MIME_DATA);
entry_ref dirRef;
BString name, type;
bool saveToFile;
bool sendInMessage;
BBitmap *bitmap;
saveToFile = msg->FindString("be:filetypes", &type) == B_OK &&
msg->FindRef("directory", &dirRef) == B_OK &&
msg->FindString("name", &name) == B_OK;
sendInMessage = (!saveToFile) && msg->FindString("be:types", &type) == B_OK;
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 &srcBits, BRect &destRect)
{
destRect = selection;
ConstrainToImage(destRect);
srcBits = selection;
if (srcBits.left < 0)
srcBits.left = -(srcBits.left);
else
srcBits.left = 0;
if (srcBits.top < 0)
srcBits.top = -(srcBits.top);
else
srcBits.top = 0;
if (srcBits.right > fBitmap->Bounds().right)
srcBits.right = srcBits.left + destRect.Width();
else
srcBits.right = merge->Bounds().right;
if (srcBits.bottom > fBitmap->Bounds().bottom)
srcBits.bottom = srcBits.top + destRect.Height();
else
srcBits.bottom = merge->Bounds().bottom;
}
void
ShowImageView::GetSelMergeRects(BRect &srcBits, BRect &destRect)
{
GetMergeRects(fSelBitmap, fSelectionRect, srcBits, destRect);
}
void
ShowImageView::MergeWithBitmap(BBitmap *merge, BRect selection)
{
BView view(fBitmap->Bounds(), NULL, B_FOLLOW_NONE, B_WILL_DRAW);
BBitmap *bitmap = new BBitmap(fBitmap->Bounds(), fBitmap->ColorSpace(), true);
if (bitmap == NULL)
return;
if (bitmap->Lock()) {
bitmap->AddChild(&view);
view.DrawBitmap(fBitmap, fBitmap->Bounds());
BRect srcBits, destRect;
GetMergeRects(merge, selection, srcBits, destRect);
view.DrawBitmap(merge, srcBits, destRect);
view.Sync();
bitmap->RemoveChild(&view);
bitmap->Unlock();
DeleteBitmap();
fBitmap = bitmap;
BMessenger msgr(Window());
msgr.SendMessage(MSG_MODIFIED);
} else
delete bitmap;
}
void
ShowImageView::MergeSelection()
{
if (!HasSelection())
return;
if (!fSelBitmap) {
// Even though the merge will not change
// the background image, I still need to save
// some undo information here
fUndo.SetTo(fSelectionRect, NULL, CopySelection());
return;
}
// Merge selection with background
fUndo.SetTo(fSelectionRect, CopyFromRect(fSelectionRect), CopySelection());
MergeWithBitmap(fSelBitmap, 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 (!fSelBitmap) {
fSelBitmap = 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(fFirstPoint.x, point.x);
fCopyFromRect.right = max(fFirstPoint.x, point.x);
fCopyFromRect.top = min(fFirstPoint.y, point.y);
fCopyFromRect.bottom = max(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);
}
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 *pmsg)
{
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();
if (fMovesImage)
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
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) {
switch (*bytes) {
case B_DOWN_ARROW:
ScrollRestrictedBy(0, 10);
break;
case B_UP_ARROW:
ScrollRestrictedBy(0, -10);
break;
case B_LEFT_ARROW:
ScrollRestrictedBy(-10, 0);
break;
case B_RIGHT_ARROW:
ScrollRestrictedBy(10, 0);
break;
case B_SPACE:
case B_ENTER:
NextFile();
break;
case B_BACKSPACE:
PrevFile();
break;
case B_HOME:
break;
case B_END:
break;
case B_ESCAPE:
if (fSlideShow) {
BMessenger msgr(Window());
msgr.SendMessage(MSG_SLIDE_SHOW);
}
ClearSelection();
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;
}
ScrollRestrictedBy(x, y);
}
void
ShowImageView::ShowPopUpMenu(BPoint screen)
{
BPopUpMenu* menu = new BPopUpMenu("PopUpMenu");
menu->SetAsyncAutoDestruct(true);
menu->SetRadioMode(false);
ShowImageWindow* showImage = dynamic_cast<ShowImageWindow*>(Window());
if (showImage) {
showImage->BuildViewMenu(menu);
}
menu->AddSeparatorItem();
menu->AddItem(new BMenuItem("Cancel", 0, 0));
screen -= BPoint(10, 10);
menu->Go(screen, true, false, 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 *pmsg)
{
switch (pmsg->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());
pmsg->SendReply(&msg);
break;
}
case B_SIMPLE_DATA:
if (pmsg->WasDropped()) {
uint32 type;
int32 count;
status_t ret = pmsg->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 (pmsg->FindRef("refs", 0, &ref) == B_OK) {
BPoint point = pmsg->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 = pmsg->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;
pmsg->FindPoint("be:_source_point", &sourcePoint);
pmsg->FindRect("be:_frame", &sourceRect);
point = pmsg->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(pmsg);
break;
case B_MOUSE_WHEEL_CHANGED:
MouseWheelChanged(pmsg);
break;
case MSG_INVALIDATE:
Invalidate();
break;
default:
BView::MessageReceived(pmsg);
break;
}
}
void
ShowImageView::FixupScrollBar(orientation o, float bitmapLength, float viewLength)
{
float prop, range;
BScrollBar *psb;
psb = ScrollBar(o);
if (psb) {
if (fHasBorder && !fShrinkOrZoomToBounds) {
bitmapLength += BORDER_WIDTH*2;
}
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) {
BRect rect(AlignBitmap());
rctbitmap.Set(0, 0, rect.Width(), rect.Height());
}
FixupScrollBar(B_HORIZONTAL, rctbitmap.Width(), rctview.Width());
FixupScrollBar(B_VERTICAL, rctbitmap.Height(), 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 fSelBitmap that it allocated itself)
if (!undoSelection)
SetHasSelection(false);
else {
SetHasSelection(true);
fCopyFromRect = BRect();
fSelectionRect = fUndo.GetRect();
fSelBitmap = 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 BBitmap(fBitmap->Bounds(), fBitmap->ColorSpace(), true);
if (bitmap == NULL)
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;
BMessenger msgr(Window());
msgr.SendMessage(MSG_MODIFIED);
} else
delete bitmap;
}
void
ShowImageView::RemoveSelection(bool bToClipboard)
{
if (HasSelection()) {
BRect rect = fSelectionRect;
bool bCutBackground = (fSelBitmap) ? false : true;
BBitmap *selection, *restore = NULL;
selection = CopySelection();
if (bToClipboard)
CopySelectionToClipboard();
SetHasSelection(false);
if (bCutBackground) {
// 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 && bitmap->IsValid()) {
MergeSelection();
SetHasSelection(true);
fSelBitmap = bitmap;
fCopyFromRect = BRect();
fSelectionRect = bitmap->Bounds();
BRect offsetRect = fSelectionRect;
offsetRect.OffsetBy(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;
Invalidate();
return B_OK;
}
return B_ERROR;
}
void
ShowImageView::Paste()
{
if (be_clipboard->Lock()) {
BMessage *pclip;
if ((pclip = be_clipboard->Data()) != NULL) {
BPoint point(0, 0);
pclip->FindPoint("be:location", &point);
BBitmap *pbits;
pbits = dynamic_cast<BBitmap *>(BBitmap::Instantiate(pclip));
PasteBitmap(pbits, 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
RemoveSelection(false);
}
void
ShowImageView::SetHasSelection(bool bHasSelection)
{
DeleteSelBitmap();
fHasSelection = bHasSelection;
BMessage msg(MSG_SELECTION);
msg.AddBool("has_selection", fHasSelection);
BMessenger msgr(Window());
msgr.SendMessage(&msg);
}
void
ShowImageView::CopySelectionToClipboard()
{
if (HasSelection() && be_clipboard->Lock()) {
be_clipboard->Clear();
BMessage *clip = NULL;
if ((clip = be_clipboard->Data()) != NULL) {
BMessage data;
BBitmap* bitmap = CopySelection();
if (bitmap != NULL) {
#if 0
// According to BeBook and Becasso, Gobe Productive do the following.
// Paste works in Productive, but not in Becasso and original ShowImage.
BMessage msg(B_OK); // Becasso uses B_TRANSLATOR_BITMAP, BeBook says its unused
bitmap->Archive(&msg);
clip->AddMessage("image/x-be-bitmap", &msg);
#else
// original ShowImage performs this. Paste works with original ShowImage.
bitmap->Archive(clip);
// original ShowImage uses be:location for insertion point
clip->AddPoint("be:location", BPoint(fSelectionRect.left, fSelectionRect.top));
#endif
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::FindNextImageByDir(entry_ref *in_current, entry_ref *out_image, bool next, bool rewind)
{
ASSERT(next || !rewind);
BEntry curImage(in_current);
entry_ref entry, *ref;
BDirectory parent;
BList entries;
bool found = false;
int32 cur;
if (curImage.GetParent(&parent) != B_OK)
return false;
while (parent.GetNextRef(&entry) == B_OK) {
if (entry != *in_current) {
entries.AddItem(new entry_ref(entry));
} else {
// insert current ref, so we can find it easily after sorting
entries.AddItem(in_current);
}
}
entries.SortItems(CompareEntries);
cur = entries.IndexOf(in_current);
ASSERT(cur >= 0);
// remove it so FreeEntries() does not delete it
entries.RemoveItem(in_current);
if (next) {
// find the next image in the list
if (rewind) cur = 0; // start with first
for (; (ref = (entry_ref*)entries.ItemAt(cur)) != NULL; cur ++) {
if (IsImage(ref)) {
found = true;
*out_image = (const entry_ref)*ref;
break;
}
}
} else {
// find the previous image in the list
cur --;
for (; cur >= 0; cur --) {
ref = (entry_ref*)entries.ItemAt(cur);
if (IsImage(ref)) {
found = true;
*out_image = (const entry_ref)*ref;
break;
}
}
}
FreeEntries(&entries);
return found;
}
bool
ShowImageView::FindNextImage(entry_ref *in_current, entry_ref *ref, bool next, bool rewind)
{
// Based on similar function from BeMail!
if (!fTrackerMessenger.IsValid())
// If tracker scripting is not available,
// fall back on directory searching code
return FindNextImageByDir(in_current, ref, next, rewind);
//
// 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)
{
bool found;
entry_ref curRef, imgRef;
curRef = fCurrentRef;
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::kRotateAntiClockwise < ImageProcessor::kNumberOfAffineTransformations);
ASSERT(ImageProcessor::kMirrorVertical < ImageProcessor::kNumberOfAffineTransformations);
ASSERT(ImageProcessor::kMirrorHorizontal < 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(NULL);
}
}
// 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::kRotateAntiClockwise);
}
}
void
ShowImageView::Mirror(bool vertical)
{
if (vertical) {
UserDoImageOperation(ImageProcessor::kMirrorVertical);
} else {
UserDoImageOperation(ImageProcessor::kMirrorHorizontal);
}
}
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::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);
}