root/src/apps/showimage/ShowImageView.cpp
/*
 * Copyright 2003-2011, Haiku, Inc. All Rights Reserved.
 * Copyright 2004-2005 yellowTAB GmbH. All Rights Reserverd.
 * Copyright 2006 Bernd Korz. All Rights Reserved
 * Distributed under the terms of the MIT License.
 *
 * Authors:
 *              Fernando Francisco de Oliveira
 *              Michael Wilber
 *              Michael Pfeiffer
 *              Ryan Leavengood
 *              yellowTAB GmbH
 *              Bernd Korz
 *              Stephan Aßmus <superstippi@gmx.de>
 *              Axel Dörfler, axeld@pinc-software.de
 */


#include "ShowImageView.h"

#include <math.h>
#include <new>
#include <stdio.h>

#include <Alert.h>
#include <Application.h>
#include <Bitmap.h>
#include <BitmapStream.h>
#include <Catalog.h>
#include <Clipboard.h>
#include <ControlLook.h>
#include <Cursor.h>
#include <Debug.h>
#include <Directory.h>
#include <Entry.h>
#include <File.h>
#include <Locale.h>
#include <MenuBar.h>
#include <MenuItem.h>
#include <Message.h>
#include <NodeInfo.h>
#include <Path.h>
#include <PopUpMenu.h>
#include <Rect.h>
#include <Region.h>
#include <Roster.h>
#include <Screen.h>
#include <ScrollBar.h>
#include <StopWatch.h>
#include <SupportDefs.h>
#include <TranslatorRoster.h>
#include <WindowScreen.h>

#include <tracker_private.h>

#include "ImageCache.h"
#include "ShowImageApp.h"
#include "ShowImageWindow.h"


using std::nothrow;


class PopUpMenu : public BPopUpMenu {
        public:
                PopUpMenu(const char* name, BMessenger target);
                virtual ~PopUpMenu();

        private:
                BMessenger fTarget;
};


// the delay time for hiding the cursor in 1/10 seconds (the pulse rate)
#define HIDE_CURSOR_DELAY_TIME 20
#define STICKY_ZOOM_DELAY_TIME 5
#define SHOW_IMAGE_ORIENTATION_ATTRIBUTE "ShowImage:orientation"


const rgb_color kBorderColor = { 0, 0, 0, 255 };

enum ShowImageView::image_orientation
ShowImageView::fTransformation[ImageProcessor::kNumberOfAffineTransformations]
                [kNumberOfOrientations] = {
        // rotate 90°
        {k90, k180, k270, k0, k270V, k0V, k90V, k0H},
        // rotate -90°
        {k270, k0, k90, k180, k90V, k0H, k270V, k0V},
        // mirror vertical
        {k0H, k270V, k0V, k90V, k180, k270, k0, k90},
        // mirror horizontal
        {k0V, k90V, k0H, k270V, k0, k90, k180, k270}
};

const rgb_color kAlphaLow = (rgb_color) { 0xbb, 0xbb, 0xbb, 0xff };
const rgb_color kAlphaHigh = (rgb_color) { 0xe0, 0xe0, 0xe0, 0xff };

const uint32 kMsgPopUpMenuClosed = 'pmcl';


inline void
blend_colors(uint8* d, uint8 r, uint8 g, uint8 b, uint8 a)
{
        d[0] = ((b - d[0]) * a + (d[0] << 8)) >> 8;
        d[1] = ((g - d[1]) * a + (d[1] << 8)) >> 8;
        d[2] = ((r - d[2]) * a + (d[2] << 8)) >> 8;
}


BBitmap*
compose_checker_background(const BBitmap* bitmap)
{
        BBitmap* result = new (nothrow) BBitmap(bitmap);
        if (result && !result->IsValid()) {
                delete result;
                result = NULL;
        }
        if (!result)
                return NULL;

        uint8* bits = (uint8*)result->Bits();
        uint32 bpr = result->BytesPerRow();
        uint32 width = result->Bounds().IntegerWidth() + 1;
        uint32 height = result->Bounds().IntegerHeight() + 1;

        for (uint32 i = 0; i < height; i++) {
                uint8* p = bits;
                for (uint32 x = 0; x < width; x++) {
                        uint8 alpha = p[3];
                        if (alpha < 255) {
                                p[3] = 255;
                                alpha = 255 - alpha;
                                if (x % 10 >= 5) {
                                        if (i % 10 >= 5)
                                                blend_colors(p, kAlphaLow.red, kAlphaLow.green, kAlphaLow.blue, alpha);
                                        else
                                                blend_colors(p, kAlphaHigh.red, kAlphaHigh.green, kAlphaHigh.blue, alpha);

                                } else {
                                        if (i % 10 >= 5)
                                                blend_colors(p, kAlphaHigh.red, kAlphaHigh.green, kAlphaHigh.blue, alpha);
                                        else
                                                blend_colors(p, kAlphaLow.red, kAlphaLow.green, kAlphaLow.blue, alpha);
                                }
                        }
                        p += 4;
                }
                bits += bpr;
        }
        return result;
}


//      #pragma mark -


PopUpMenu::PopUpMenu(const char* name, BMessenger target)
        :
        BPopUpMenu(name, false, false),
        fTarget(target)
{
        SetAsyncAutoDestruct(true);
}


PopUpMenu::~PopUpMenu()
{
        fTarget.SendMessage(kMsgPopUpMenuClosed);
}


//      #pragma mark -


ShowImageView::ShowImageView(const char* name, uint32 flags)
        :
        BView(name, flags),
        fBitmapOwner(NULL),
        fBitmap(NULL),
        fDisplayBitmap(NULL),
        fSelectionBitmap(NULL),

        fZoom(1.0),

        fScaleBilinear(true),

        fBitmapLocationInView(0.0, 0.0),

        fStretchToBounds(false),
        fForceOriginalSize(false),
        fHideCursor(false),
        fScrollingBitmap(false),
        fCreatingSelection(false),
        fFirstPoint(0.0, 0.0),
        fSelectionMode(false),
        fAnimateSelection(true),
        fHasSelection(false),
        fShowCaption(false),
        fShowingPopUpMenu(false),
        fHideCursorCountDown(HIDE_CURSOR_DELAY_TIME),
        fStickyZoomCountDown(0),
        fIsActiveWin(true),
        fDefaultCursor(NULL),
        fGrabCursor(NULL)
{
        ShowImageSettings* settings = my_app->Settings();
        if (settings->Lock()) {
                fStretchToBounds = settings->GetBool("StretchToBounds",
                        fStretchToBounds);
                fScaleBilinear = settings->GetBool("ScaleBilinear", fScaleBilinear);
                settings->Unlock();
        }

        fDefaultCursor = new BCursor(B_CURSOR_ID_SYSTEM_DEFAULT);
        fGrabCursor = new BCursor(B_CURSOR_ID_GRABBING);

        SetViewColor(B_TRANSPARENT_COLOR);
        SetHighColor(kBorderColor);
        SetLowColor(0, 0, 0);
}


ShowImageView::~ShowImageView()
{
        _DeleteBitmap();

        delete fDefaultCursor;
        delete fGrabCursor;
}


void
ShowImageView::_AnimateSelection(bool enabled)
{
        fAnimateSelection = enabled;
}


void
ShowImageView::Pulse()
{
        // animate marching ants
        if (fHasSelection && fAnimateSelection && fIsActiveWin) {
                fSelectionBox.Animate();
                fSelectionBox.Draw(this, Bounds());
        }

        if (fHideCursor && !fHasSelection && !fShowingPopUpMenu && fIsActiveWin) {
                if (fHideCursorCountDown == 0) {
                        // Go negative so this isn't triggered again
                        fHideCursorCountDown--;

                        BPoint mousePos;
                        uint32 buttons;
                        GetMouse(&mousePos, &buttons, false);
                        if (Bounds().Contains(mousePos)) {
                                be_app->ObscureCursor();

                                // Set current mouse coordinates to avoid the screen saver kicking in
                                ConvertToScreen(&mousePos);
                                set_mouse_position((int32)mousePos.x, (int32)mousePos.y);
                        }
                } else if (fHideCursorCountDown > 0)
                        fHideCursorCountDown--;
        }

        if (fStickyZoomCountDown > 0)
                fStickyZoomCountDown--;

}


void
ShowImageView::_SendMessageToWindow(BMessage* message)
{
        BMessenger target(Window());
        target.SendMessage(message);
}


void
ShowImageView::_SendMessageToWindow(uint32 code)
{
        BMessage message(code);
        _SendMessageToWindow(&message);
}


//! send message to parent about new image
void
ShowImageView::_Notify()
{
        BMessage msg(MSG_UPDATE_STATUS);

        msg.AddInt32("width", fBitmap->Bounds().IntegerWidth() + 1);
        msg.AddInt32("height", fBitmap->Bounds().IntegerHeight() + 1);

        msg.AddInt32("colors", fBitmap->ColorSpace());
        _SendMessageToWindow(&msg);

        FixupScrollBars();
        Invalidate();
}


void
ShowImageView::_UpdateStatusText()
{
        BMessage msg(MSG_UPDATE_STATUS_TEXT);

        if (fHasSelection) {
                char size[50];
                snprintf(size, sizeof(size), "(%.0fx%.0f)",
                        fSelectionBox.Bounds().Width() + 1.0,
                        fSelectionBox.Bounds().Height() + 1.0);

                msg.AddString("status", size);
        }

        _SendMessageToWindow(&msg);
}


void
ShowImageView::_DeleteBitmap()
{
        _DeleteSelectionBitmap();

        if (fDisplayBitmap != fBitmap)
                delete fDisplayBitmap;
        fDisplayBitmap = NULL;

        if (fBitmapOwner != NULL)
                fBitmapOwner->ReleaseReference();
        else
                delete fBitmap;

        fBitmapOwner = NULL;
        fBitmap = NULL;
}


void
ShowImageView::_DeleteSelectionBitmap()
{
        delete fSelectionBitmap;
        fSelectionBitmap = NULL;
}


status_t
ShowImageView::SetImage(const BMessage* message)
{
        status_t status;
        if (message->FindInt32("error", &status) == B_OK && status != B_OK)
                return status;

        BBitmap* bitmap;
        entry_ref ref;
        if (message->FindPointer("bitmap", (void**)&bitmap) != B_OK
                || message->FindRef("ref", &ref) != B_OK || bitmap == NULL)
                return B_ERROR;

        BitmapOwner* bitmapOwner;
        message->FindPointer("bitmapOwner", (void**)&bitmapOwner);

        status = SetImage(&ref, bitmap, bitmapOwner);
        if (status == B_OK) {
                fFormatDescription = message->FindString("type");
                fMimeType = message->FindString("mime");
        }

        return status;
}


status_t
ShowImageView::SetImage(const entry_ref* ref, BBitmap* bitmap,
                BitmapOwner* bitmapOwner)
{
        // Delete the old one, and clear everything
        _SetHasSelection(false);
        fCreatingSelection = false;
        _DeleteBitmap();

        fBitmap = bitmap;
        fBitmapOwner = bitmapOwner;
        if (ref == NULL)
                fCurrentRef.device = -1;
        else
                fCurrentRef = *ref;

        if (fBitmap != NULL) {
                // prepare the display bitmap
                if (fBitmap->ColorSpace() == B_RGBA32)
                        fDisplayBitmap = compose_checker_background(fBitmap);
                if (fDisplayBitmap == NULL)
                        fDisplayBitmap = fBitmap;

                BNode node(ref);

                // restore orientation
                int32 orientation;
                fImageOrientation = k0;
                if (node.ReadAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0,
                                &orientation, sizeof(orientation)) == sizeof(orientation)) {
                        orientation &= 255;
                        switch (orientation) {
                                case k0:
                                        break;
                                case k90:
                                        _DoImageOperation(ImageProcessor::kRotateClockwise, true);
                                        break;
                                case k180:
                                        _DoImageOperation(ImageProcessor::kRotateClockwise, true);
                                        _DoImageOperation(ImageProcessor::kRotateClockwise, true);
                                        break;
                                case k270:
                                        _DoImageOperation(ImageProcessor::kRotateCounterClockwise, true);
                                        break;
                                case k0V:
                                        _DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom, true);
                                        break;
                                case k90V:
                                        _DoImageOperation(ImageProcessor::kRotateClockwise, true);
                                        _DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom, true);
                                        break;
                                case k0H:
                                        _DoImageOperation(ImageProcessor::ImageProcessor::kFlipLeftToRight, true);
                                        break;
                                case k270V:
                                        _DoImageOperation(ImageProcessor::kRotateCounterClockwise, true);
                                        _DoImageOperation(ImageProcessor::ImageProcessor::kFlipTopToBottom, true);
                                        break;
                        }
                }
        }

        BPath path(ref);
        fCaption = path.Path();
        fFormatDescription = "Bitmap";
        fMimeType = "image/x-be-bitmap";

        be_roster->AddToRecentDocuments(ref, kApplicationSignature);

        FitToBounds();
        _Notify();
        return B_OK;
}


BPoint
ShowImageView::ImageToView(BPoint p) const
{
        p.x = floorf(fZoom * p.x + fBitmapLocationInView.x);
        p.y = floorf(fZoom * p.y + fBitmapLocationInView.y);
        return p;
}


BPoint
ShowImageView::ViewToImage(BPoint p) const
{
        p.x = floorf((p.x - fBitmapLocationInView.x) / fZoom);
        p.y = floorf((p.y - fBitmapLocationInView.y) / fZoom);
        return p;
}


BRect
ShowImageView::ImageToView(BRect r) const
{
        BPoint leftTop(ImageToView(BPoint(r.left, r.top)));
        BPoint rightBottom(r.right, r.bottom);
        rightBottom += BPoint(1, 1);
        rightBottom = ImageToView(rightBottom);
        rightBottom -= BPoint(1, 1);
        return BRect(leftTop.x, leftTop.y, rightBottom.x, rightBottom.y);
}


void
ShowImageView::ConstrainToImage(BPoint& point) const
{
        point.ConstrainTo(fBitmap->Bounds());
}


void
ShowImageView::ConstrainToImage(BRect& rect) const
{
        rect = rect & fBitmap->Bounds();
}


void
ShowImageView::SetShowCaption(bool show)
{
        if (fShowCaption != show) {
                fShowCaption = show;
                _UpdateCaption();
        }
}


void
ShowImageView::SetStretchToBounds(bool enable)
{
        if (fStretchToBounds != enable) {
                _SettingsSetBool("StretchToBounds", enable);
                fStretchToBounds = enable;
                if (enable || fZoom > 1.0)
                        FitToBounds();
        }
}


void
ShowImageView::SetHideIdlingCursor(bool hide)
{
        fHideCursor = hide;
}


BBitmap*
ShowImageView::Bitmap()
{
        return fBitmap;
}


void
ShowImageView::SetScaleBilinear(bool enabled)
{
        if (fScaleBilinear != enabled) {
                _SettingsSetBool("ScaleBilinear", enabled);
                fScaleBilinear = enabled;
                Invalidate();
        }
}


void
ShowImageView::AttachedToWindow()
{
        FitToBounds();
        FixupScrollBars();
}


void
ShowImageView::FrameResized(float width, float height)
{
        FixupScrollBars();
}


float
ShowImageView::_FitToBoundsZoom() const
{
        if (fBitmap == NULL)
                return 1.0f;

        // the width/height of the bitmap (in pixels)
        float bitmapWidth = fBitmap->Bounds().Width() + 1;
        float bitmapHeight = fBitmap->Bounds().Height() + 1;

        // the available width/height for layouting the bitmap (in pixels)
        float width = Bounds().Width() + 1;
        float height = Bounds().Height() + 1;

        float zoom = width / bitmapWidth;

        if (zoom * bitmapHeight <= height)
                return zoom;

        return height / bitmapHeight;
}


BRect
ShowImageView::_AlignBitmap()
{
        BRect rect(fBitmap->Bounds());

        // the width/height of the bitmap (in pixels)
        float bitmapWidth = rect.Width() + 1;
        float bitmapHeight = rect.Height() + 1;

        // the available width/height for layouting the bitmap (in pixels)
        float width = Bounds().Width() + 1;
        float height = Bounds().Height() + 1;

        if (width == 0 || height == 0)
                return rect;

        // zoom image
        rect.right = floorf(bitmapWidth * fZoom) - 1;
        rect.bottom = floorf(bitmapHeight * fZoom) - 1;

        // update the bitmap size after the zoom
        bitmapWidth = rect.Width() + 1.0;
        bitmapHeight = rect.Height() + 1.0;

        // always align in the center if the bitmap is smaller than the window
        if (width > bitmapWidth)
                rect.OffsetBy(floorf((width - bitmapWidth) / 2.0), 0);

        if (height > bitmapHeight)
                rect.OffsetBy(0, floorf((height - bitmapHeight) / 2.0));

        return rect;
}


void
ShowImageView::_DrawBackground(BRect border)
{
        BRect bounds(Bounds());
        // top
        FillRect(BRect(0, 0, bounds.right, border.top - 1), B_SOLID_LOW);
        // left
        FillRect(BRect(0, border.top, border.left - 1, border.bottom), B_SOLID_LOW);
        // right
        FillRect(BRect(border.right + 1, border.top, bounds.right, border.bottom), B_SOLID_LOW);
        // bottom
        FillRect(BRect(0, border.bottom + 1, bounds.right, bounds.bottom), B_SOLID_LOW);
}


void
ShowImageView::_LayoutCaption(BFont& font, BPoint& pos, BRect& rect)
{
        font_height fontHeight;
        float width, height;
        BRect bounds(Bounds());
        font = be_plain_font;
        width = font.StringWidth(fCaption.String());
        font.GetHeight(&fontHeight);
        height = fontHeight.ascent + fontHeight.descent;
        // center text horizontally
        pos.x = (bounds.left + bounds.right - width) / 2;
        // flush bottom
        pos.y = bounds.bottom - fontHeight.descent - 7;

        // background rectangle
        rect.Set(0, 0, width + 4, height + 4);
        rect.OffsetTo(pos);
        rect.OffsetBy(-2, -2 - fontHeight.ascent); // -2 for border
}


void
ShowImageView::_DrawCaption()
{
        BFont font;
        BPoint position;
        BRect rect;
        _LayoutCaption(font, position, rect);

        PushState();

        // draw background
        SetDrawingMode(B_OP_ALPHA);
        SetHighColor(255, 255, 255, 160);
        FillRect(rect);

        // draw text
        SetDrawingMode(B_OP_OVER);
        SetFont(&font);
        SetLowColor(B_TRANSPARENT_COLOR);
        SetHighColor(0, 0, 0);
        DrawString(fCaption.String(), position);

        PopState();
}


void
ShowImageView::_UpdateCaption()
{
        BFont font;
        BPoint pos;
        BRect rect;
        _LayoutCaption(font, pos, rect);

        // draw over portion of image where caption is located
        BRegion clip(rect);
        PushState();
        ConstrainClippingRegion(&clip);
        Draw(rect);
        PopState();
}


void
ShowImageView::_DrawImage(BRect rect)
{
        // TODO: fix composing of fBitmap with other bitmaps
        // with regard to alpha channel
        if (!fDisplayBitmap)
                fDisplayBitmap = fBitmap;

        uint32 options = fScaleBilinear ? B_FILTER_BITMAP_BILINEAR : 0;
        DrawBitmap(fDisplayBitmap, fDisplayBitmap->Bounds(), rect, options);
}


void
ShowImageView::Draw(BRect updateRect)
{
        if (fBitmap == NULL)
                return;

        if (IsPrinting()) {
                DrawBitmap(fBitmap);
                return;
        }

        BRect rect = _AlignBitmap();
        fBitmapLocationInView.x = floorf(rect.left);
        fBitmapLocationInView.y = floorf(rect.top);

        _DrawBackground(rect);
        _DrawImage(rect);

        if (fShowCaption)
                _DrawCaption();

        if (fHasSelection) {
                if (fSelectionBitmap != NULL) {
                        BRect srcRect;
                        BRect dstRect;
                        _GetSelectionMergeRects(srcRect, dstRect);
                        dstRect = ImageToView(dstRect);
                        DrawBitmap(fSelectionBitmap, srcRect, dstRect);
                }
                fSelectionBox.Draw(this, updateRect);
        }
}


BBitmap*
ShowImageView::_CopySelection(uchar alpha, bool imageSize)
{
        bool hasAlpha = alpha != 255;

        if (!fHasSelection)
                return NULL;

        BRect rect = fSelectionBox.Bounds().OffsetToCopy(B_ORIGIN);
        if (!imageSize) {
                // scale image to view size
                rect.right = floorf((rect.right + 1.0) * fZoom - 1.0);
                rect.bottom = floorf((rect.bottom + 1.0) * fZoom - 1.0);
        }
        BView view(rect, NULL, B_FOLLOW_NONE, B_WILL_DRAW);
        BBitmap* bitmap = new(nothrow) BBitmap(rect, hasAlpha ? B_RGBA32
                : fBitmap->ColorSpace(), true);
        if (bitmap == NULL || !bitmap->IsValid()) {
                delete bitmap;
                return NULL;
        }

        if (bitmap->Lock()) {
                bitmap->AddChild(&view);
#ifdef __HAIKU__
                // On Haiku, B_OP_SUBSTRACT does not affect alpha like it did on BeOS.
                // Don't know if it's better to fix it or not (stippi).
                if (hasAlpha) {
                        view.SetHighColor(0, 0, 0, 0);
                        view.FillRect(view.Bounds());
                        view.SetDrawingMode(B_OP_ALPHA);
                        view.SetBlendingMode(B_CONSTANT_ALPHA, B_ALPHA_COMPOSITE);
                        view.SetHighColor(0, 0, 0, alpha);
                }
                if (fSelectionBitmap) {
                        view.DrawBitmap(fSelectionBitmap,
                                fSelectionBitmap->Bounds().OffsetToCopy(B_ORIGIN), rect);
                } else
                        view.DrawBitmap(fBitmap, fCopyFromRect, rect);
#else
                if (fSelectionBitmap) {
                        view.DrawBitmap(fSelectionBitmap,
                                fSelectionBitmap->Bounds().OffsetToCopy(B_ORIGIN), rect);
                } else
                        view.DrawBitmap(fBitmap, fCopyFromRect, rect);
                if (hasAlpha) {
                        view.SetDrawingMode(B_OP_SUBTRACT);
                        view.SetHighColor(0, 0, 0, 255 - alpha);
                        view.FillRect(rect, B_SOLID_HIGH);
                }
#endif
                view.Sync();
                bitmap->RemoveChild(&view);
                bitmap->Unlock();
        }

        return bitmap;
}


bool
ShowImageView::_AddSupportedTypes(BMessage* msg, BBitmap* bitmap)
{
        BTranslatorRoster* roster = BTranslatorRoster::Default();
        if (roster == NULL)
                return false;

        // add the current image mime first, will make it the preferred format on
        // left mouse drag
        msg->AddString("be:types", fMimeType);
        msg->AddString("be:filetypes", fMimeType);
        msg->AddString("be:type_descriptions", fFormatDescription);

        bool foundOther = false;
        bool foundCurrent = false;

        int32 infoCount;
        translator_info* info;
        BBitmapStream stream(bitmap);
        if (roster->GetTranslators(&stream, NULL, &info, &infoCount) == B_OK) {
                for (int32 i = 0; i < infoCount; i++) {
                        const translation_format* formats;
                        int32 count;
                        roster->GetOutputFormats(info[i].translator, &formats, &count);
                        for (int32 j = 0; j < count; j++) {
                                if (fMimeType == formats[j].MIME)
                                        foundCurrent = true;
                                else if (strcmp(formats[j].MIME, "image/x-be-bitmap") != 0) {
                                        foundOther = true;
                                        // needed to send data in message
                                        msg->AddString("be:types", formats[j].MIME);
                                        // needed to pass data via file
                                        msg->AddString("be:filetypes", formats[j].MIME);
                                        msg->AddString("be:type_descriptions", formats[j].name);
                                }
                        }
                }
        }
        stream.DetachBitmap(&bitmap);

        if (!foundCurrent) {
                msg->RemoveData("be:types", 0);
                msg->RemoveData("be:filetypes", 0);
                msg->RemoveData("be:type_descriptions", 0);
        }

        return foundOther || foundCurrent;
}


void
ShowImageView::_BeginDrag(BPoint sourcePoint)
{
        BBitmap* bitmap = _CopySelection(128, false);
        if (bitmap == NULL)
                return;

        SetMouseEventMask(B_POINTER_EVENTS);

        // fill the drag message
        BMessage drag(B_SIMPLE_DATA);
        drag.AddInt32("be:actions", B_COPY_TARGET);
        drag.AddString("be:clip_name", "Bitmap Clip");
        // ShowImage specific fields
        drag.AddPoint("be:_source_point", sourcePoint);
        drag.AddRect("be:_frame", fSelectionBox.Bounds());
        if (_AddSupportedTypes(&drag, bitmap)) {
                // we also support "Passing Data via File" protocol
                drag.AddString("be:types", B_FILE_MIME_TYPE);
                // avoid flickering of dragged bitmap caused by drawing into the window
                _AnimateSelection(false);
                // only use a transparent bitmap on selections less than 400x400
                // (taking into account zooming)
                BRect selectionRect = fSelectionBox.Bounds();
                if (selectionRect.Width() * fZoom < 400.0
                        && selectionRect.Height() * fZoom < 400.0) {
                        sourcePoint -= selectionRect.LeftTop();
                        sourcePoint.x *= fZoom;
                        sourcePoint.y *= fZoom;
                        // DragMessage takes ownership of bitmap
                        DragMessage(&drag, bitmap, B_OP_ALPHA, sourcePoint);
                        bitmap = NULL;
                } else {
                        delete bitmap;
                        // Offset and scale the rect
                        BRect rect(selectionRect);
                        rect = ImageToView(rect);
                        rect.InsetBy(-1, -1);
                        DragMessage(&drag, rect);
                }
        }
}


bool
ShowImageView::_OutputFormatForType(BBitmap* bitmap, const char* type,
        translation_format* format)
{
        bool found = false;

        BTranslatorRoster* roster = BTranslatorRoster::Default();
        if (roster == NULL)
                return false;

        BBitmapStream stream(bitmap);

        translator_info* outInfo;
        int32 outNumInfo;
        if (roster->GetTranslators(&stream, NULL, &outInfo, &outNumInfo) == B_OK) {
                for (int32 i = 0; i < outNumInfo; i++) {
                        const translation_format* formats;
                        int32 formatCount;
                        roster->GetOutputFormats(outInfo[i].translator, &formats,
                                &formatCount);
                        for (int32 j = 0; j < formatCount; j++) {
                                if (strcmp(formats[j].MIME, type) == 0) {
                                        *format = formats[j];
                                        found = true;
                                        break;
                                }
                        }
                }
        }
        stream.DetachBitmap(&bitmap);
        return found;
}


#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "SaveToFile"


void
ShowImageView::SaveToFile(BDirectory* dir, const char* name, BBitmap* bitmap,
        const translation_format* format)
{
        if (bitmap == NULL) {
                // If no bitmap is supplied, write out the whole image
                bitmap = fBitmap;
        }

        BBitmapStream stream(bitmap);

        bool loop = true;
        while (loop) {
                BTranslatorRoster* roster = BTranslatorRoster::Default();
                if (!roster)
                        break;
                // write data
                BFile file(dir, name, B_WRITE_ONLY | B_CREATE_FILE | B_ERASE_FILE);
                if (file.InitCheck() != B_OK)
                        break;
                if (roster->Translate(&stream, NULL, NULL, &file, format->type) < B_OK)
                        break;
                // set mime type
                BNodeInfo info(&file);
                if (info.InitCheck() == B_OK)
                        info.SetType(format->MIME);

                loop = false;
                        // break out of loop gracefully (indicates no errors)
        }
        if (loop) {
                // If loop terminated because of a break, there was an error
                char buffer[512];
                snprintf(buffer, sizeof(buffer), B_TRANSLATE("The file '%s' could not "
                        "be written."), name);
                BAlert* palert = new BAlert("", buffer, B_TRANSLATE("OK"));
                palert->SetFlags(palert->Flags() | B_CLOSE_ON_ESCAPE);
                palert->Go();
        }

        stream.DetachBitmap(&bitmap);
                // Don't allow the bitmap to be deleted, this is
                // especially important when using fBitmap as the bitmap
}


void
ShowImageView::_SendInMessage(BMessage* msg, BBitmap* bitmap,
        translation_format* format)
{
        BMessage reply(B_MIME_DATA);
        BBitmapStream stream(bitmap); // destructor deletes bitmap
        BTranslatorRoster* roster = BTranslatorRoster::Default();
        BMallocIO memStream;
        if (roster->Translate(&stream, NULL, NULL, &memStream, format->type) == B_OK) {
                reply.AddData(format->MIME, B_MIME_TYPE, memStream.Buffer(),
                        memStream.BufferLength());
                msg->SendReply(&reply);
        }
}


void
ShowImageView::_HandleDrop(BMessage* msg)
{
        entry_ref dirRef;
        BString name, type;
        bool saveToFile = msg->FindString("be:filetypes", &type) == B_OK
                && msg->FindRef("directory", &dirRef) == B_OK
                && msg->FindString("name", &name) == B_OK;

        bool sendInMessage = !saveToFile
                && msg->FindString("be:types", &type) == B_OK;

        BBitmap* bitmap = _CopySelection();
        if (bitmap == NULL)
                return;

        translation_format format;
        if (!_OutputFormatForType(bitmap, type.String(), &format)) {
                delete bitmap;
                return;
        }

        if (saveToFile) {
                BDirectory dir(&dirRef);
                SaveToFile(&dir, name.String(), bitmap, &format);
                delete bitmap;
        } else if (sendInMessage) {
                _SendInMessage(msg, bitmap, &format);
        } else {
                delete bitmap;
        }
}


void
ShowImageView::_ScrollBitmap(BPoint point)
{
        point = ConvertToScreen(point);
        BPoint delta = fFirstPoint - point;
        fFirstPoint = point;
        _ScrollRestrictedBy(delta.x, delta.y);
}


void
ShowImageView::_GetMergeRects(BBitmap* merge, BRect selection, BRect& srcRect,
        BRect& dstRect)
{
        // Constrain dstRect to target image size and apply the same edge offsets
        // to the srcRect.

        dstRect = selection;

        BRect clippedDstRect(dstRect);
        ConstrainToImage(clippedDstRect);

        srcRect = merge->Bounds().OffsetToCopy(B_ORIGIN);

        srcRect.left += clippedDstRect.left - dstRect.left;
        srcRect.top += clippedDstRect.top - dstRect.top;
        srcRect.right += clippedDstRect.right - dstRect.right;
        srcRect.bottom += clippedDstRect.bottom - dstRect.bottom;

        dstRect = clippedDstRect;
}


void
ShowImageView::_GetSelectionMergeRects(BRect& srcRect, BRect& dstRect)
{
        _GetMergeRects(fSelectionBitmap, fSelectionBox.Bounds(), srcRect, dstRect);
}


void
ShowImageView::_MergeWithBitmap(BBitmap* merge, BRect selection)
{
        BView view(fBitmap->Bounds(), NULL, B_FOLLOW_NONE, B_WILL_DRAW);
        BBitmap* bitmap = new(nothrow) BBitmap(fBitmap->Bounds(),
                fBitmap->ColorSpace(), true);
        if (bitmap == NULL || !bitmap->IsValid()) {
                delete bitmap;
                return;
        }

        if (bitmap->Lock()) {
                bitmap->AddChild(&view);
                view.DrawBitmap(fBitmap, fBitmap->Bounds());
                BRect srcRect;
                BRect dstRect;
                _GetMergeRects(merge, selection, srcRect, dstRect);
                view.DrawBitmap(merge, srcRect, dstRect);

                view.Sync();
                bitmap->RemoveChild(&view);
                bitmap->Unlock();

                _DeleteBitmap();
                fBitmap = bitmap;

                _SendMessageToWindow(MSG_MODIFIED);
        } else
                delete bitmap;
}


void
ShowImageView::MouseDown(BPoint position)
{
        MakeFocus(true);

        BPoint point = ViewToImage(position);
        int32 clickCount = 0;
        uint32 buttons = 0;
        if (Window() != NULL && Window()->CurrentMessage() != NULL) {
                clickCount = Window()->CurrentMessage()->FindInt32("clicks");
                buttons = Window()->CurrentMessage()->FindInt32("buttons");
        }

        // Using clickCount >= 2 and the modulo 2 accounts for quickly repeated
        // double-clicks
        if (buttons == B_PRIMARY_MOUSE_BUTTON && clickCount >= 2 &&
                        clickCount % 2 == 0) {
                Window()->PostMessage(MSG_FULL_SCREEN);
                return;
        }

        if (fHasSelection && fSelectionBox.Bounds().Contains(point)
                && (buttons
                                & (B_PRIMARY_MOUSE_BUTTON | B_SECONDARY_MOUSE_BUTTON)) != 0) {
                if (!fSelectionBitmap)
                        fSelectionBitmap = _CopySelection();

                _BeginDrag(point);
        } else if (buttons == B_PRIMARY_MOUSE_BUTTON
                        && (fSelectionMode
                                || (modifiers() & (B_COMMAND_KEY | B_CONTROL_KEY)) != 0)) {
                // begin new selection
                _SetHasSelection(true);
                fCreatingSelection = true;
                SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
                ConstrainToImage(point);
                fFirstPoint = point;
                fCopyFromRect.Set(point.x, point.y, point.x, point.y);
                fSelectionBox.SetBounds(this, fCopyFromRect);
                Invalidate();
        } else if (buttons == B_SECONDARY_MOUSE_BUTTON) {
                _ShowPopUpMenu(ConvertToScreen(position));
        } else if (buttons == B_PRIMARY_MOUSE_BUTTON
                || buttons == B_TERTIARY_MOUSE_BUTTON) {
                // move image in window
                SetMouseEventMask(B_POINTER_EVENTS, B_NO_POINTER_HISTORY);
                fScrollingBitmap = true;
                fFirstPoint = ConvertToScreen(position);
                be_app->SetCursor(fGrabCursor);
        }
}


void
ShowImageView::_UpdateSelectionRect(BPoint point, bool final)
{
        BRect oldSelection = fCopyFromRect;
        point = ViewToImage(point);
        ConstrainToImage(point);
        fCopyFromRect.left = min_c(fFirstPoint.x, point.x);
        fCopyFromRect.right = max_c(fFirstPoint.x, point.x);
        fCopyFromRect.top = min_c(fFirstPoint.y, point.y);
        fCopyFromRect.bottom = max_c(fFirstPoint.y, point.y);
        fSelectionBox.SetBounds(this, fCopyFromRect);

        if (final) {
                // selection must be at least 2 pixels wide or 2 pixels tall
                if (fCopyFromRect.Width() < 1.0 && fCopyFromRect.Height() < 1.0)
                        _SetHasSelection(false);
        } else
                _UpdateStatusText();

        if (oldSelection != fCopyFromRect || !fHasSelection) {
                BRect updateRect;
                updateRect = oldSelection | fCopyFromRect;
                updateRect = ImageToView(updateRect);
                updateRect.InsetBy(-1, -1);
                Invalidate(updateRect);
        }
}


void
ShowImageView::MouseMoved(BPoint point, uint32 state, const BMessage* message)
{
        fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME;
        if (fHideCursor) {
                // Show toolbar when mouse hits top 15 pixels, hide otherwise
                _ShowToolBarIfEnabled(ConvertToScreen(point).y <= 15);
        }
        if (fCreatingSelection)
                _UpdateSelectionRect(point, false);
        else if (fScrollingBitmap)
                _ScrollBitmap(point);
}


void
ShowImageView::MouseUp(BPoint point)
{
        if (fCreatingSelection) {
                _UpdateSelectionRect(point, true);
                fCreatingSelection = false;
        } else if (fScrollingBitmap) {
                _ScrollBitmap(point);
                fScrollingBitmap = false;
                be_app->SetCursor(fDefaultCursor);
        }
        _AnimateSelection(true);
}


float
ShowImageView::_LimitToRange(float v, orientation o, bool absolute)
{
        BScrollBar* psb = ScrollBar(o);
        if (psb) {
                float min, max, pos;
                pos = v;
                if (!absolute)
                        pos += psb->Value();

                psb->GetRange(&min, &max);
                if (pos < min)
                        pos = min;
                else if (pos > max)
                        pos = max;

                v = pos;
                if (!absolute)
                        v -= psb->Value();
        }
        return v;
}


void
ShowImageView::_ScrollRestricted(float x, float y, bool absolute)
{
        if (x != 0)
                x = _LimitToRange(x, B_HORIZONTAL, absolute);

        if (y != 0)
                y = _LimitToRange(y, B_VERTICAL, absolute);

        // We invalidate before we scroll to avoid the caption messing up the
        // image, and to prevent it from flickering
        if (fShowCaption)
                Invalidate();

        ScrollBy(x, y);
}


// XXX method is not unused
void
ShowImageView::_ScrollRestrictedTo(float x, float y)
{
        _ScrollRestricted(x, y, true);
}


void
ShowImageView::_ScrollRestrictedBy(float x, float y)
{
        _ScrollRestricted(x, y, false);
}


void
ShowImageView::KeyDown(const char* bytes, int32 numBytes)
{
        if (numBytes != 1) {
                BView::KeyDown(bytes, numBytes);
                return;
        }

        bool shiftKeyDown = (modifiers() & B_SHIFT_KEY) != 0;

        switch (*bytes) {
                case B_DOWN_ARROW:
                        if (shiftKeyDown)
                                _ScrollRestrictedBy(0, 10);
                        else
                                _SendMessageToWindow(MSG_FILE_NEXT);
                        break;
                case B_RIGHT_ARROW:
                        if (shiftKeyDown)
                                _ScrollRestrictedBy(10, 0);
                        else
                                _SendMessageToWindow(MSG_FILE_NEXT);
                        break;
                case B_UP_ARROW:
                        if (shiftKeyDown)
                                _ScrollRestrictedBy(0, -10);
                        else
                                _SendMessageToWindow(MSG_FILE_PREV);
                        break;
                case B_LEFT_ARROW:
                        if (shiftKeyDown)
                                _ScrollRestrictedBy(-10, 0);
                        else
                                _SendMessageToWindow(MSG_FILE_PREV);
                        break;
                case B_BACKSPACE:
                        _SendMessageToWindow(MSG_FILE_PREV);
                        break;
                case B_HOME:
                        break;
                case B_END:
                        break;
                case B_SPACE:
                        _ToggleSlideShow();
                        break;
                case B_ESCAPE:
                        // stop slide show
                        _StopSlideShow();
                        _ExitFullScreen();

                        ClearSelection();
                        break;
                case B_DELETE:
                        if (fHasSelection)
                                ClearSelection();
                        else
                                _SendMessageToWindow(kMsgDeleteCurrentFile);
                        break;
                case '0':
                        FitToBounds();
                        break;
                case '1':
                        SetZoom(1.0f);
                        break;
                case '+':
                case '=':
                        ZoomIn();
                        break;
                case '-':
                        ZoomOut();
                        break;
                case '[':
                        Rotate(270);
                        break;
                case ']':
                        Rotate(90);
                        break;
        }
}


void
ShowImageView::_MouseWheelChanged(BMessage* message)
{
        // The BeOS driver does not currently support
        // X wheel scrolling, therefore, deltaX is zero.
        // |deltaY| is the number of notches scrolled up or down.
        // When the wheel is scrolled down (towards the user) deltaY > 0
        // When the wheel is scrolled up (away from the user) deltaY < 0
        const float kscrollBy = 40;
        float deltaY;
        float deltaX;
        float x = 0;
        float y = 0;

        if (message->FindFloat("be:wheel_delta_x", &deltaX) == B_OK)
                x = deltaX * kscrollBy;

        if (message->FindFloat("be:wheel_delta_y", &deltaY) == B_OK)
                y = deltaY * kscrollBy;

        if ((modifiers() & B_SHIFT_KEY) != 0) {
                // scroll up and down
                _ScrollRestrictedBy(x, y);
        } else if ((modifiers() & B_CONTROL_KEY) != 0) {
                // scroll left and right
                _ScrollRestrictedBy(y, x);
        } else {
                // zoom at location
                BPoint where;
                uint32 buttons;
                GetMouse(&where, &buttons);

                if (fStickyZoomCountDown <= 0) {
                        if (deltaY < 0)
                                ZoomIn(where);
                        else if (deltaY > 0)
                                ZoomOut(where);

                        if (fZoom == 1.0)
                                fStickyZoomCountDown = STICKY_ZOOM_DELAY_TIME;
                }

        }
}


void
ShowImageView::_ShowPopUpMenu(BPoint screen)
{
        if (!fShowingPopUpMenu) {
                PopUpMenu* menu = new PopUpMenu("PopUpMenu", this);

                ShowImageWindow* window = dynamic_cast<ShowImageWindow*>(Window());
                if (window != NULL)
                        window->BuildContextMenu(menu);

                menu->Go(screen, true, true, true);
                fShowingPopUpMenu = true;
        }
}


void
ShowImageView::_SettingsSetBool(const char* name, bool value)
{
        ShowImageSettings* settings;
        settings = my_app->Settings();
        if (settings->Lock()) {
                settings->SetBool(name, value);
                settings->Unlock();
        }
}


void
ShowImageView::MessageReceived(BMessage* message)
{
        switch (message->what) {
                case B_COPY_TARGET:
                        _HandleDrop(message);
                        break;

                case B_MOUSE_WHEEL_CHANGED:
                        _MouseWheelChanged(message);
                        break;

                case kMsgPopUpMenuClosed:
                        fShowingPopUpMenu = false;
                        break;

                default:
                        BView::MessageReceived(message);
                        break;
        }
}


void
ShowImageView::FixupScrollBar(orientation o, float bitmapLength,
        float viewLength)
{
        float prop, range;
        BScrollBar* psb;

        psb = ScrollBar(o);
        if (psb) {
                range = bitmapLength - viewLength;
                if (range < 0.0)
                        range = 0.0;

                prop = viewLength / bitmapLength;
                if (prop > 1.0)
                        prop = 1.0;

                psb->SetRange(0, range);
                psb->SetProportion(prop);
                psb->SetSteps(10, 100);
        }
}


void
ShowImageView::FixupScrollBars()
{
        BRect viewRect = Bounds();
        BRect bitmapRect;
        if (fBitmap != NULL) {
                bitmapRect = _AlignBitmap();
                bitmapRect.OffsetTo(0, 0);
        }

        FixupScrollBar(B_HORIZONTAL, bitmapRect.Width(), viewRect.Width());
        FixupScrollBar(B_VERTICAL, bitmapRect.Height(), viewRect.Height());
}


void
ShowImageView::SetSelectionMode(bool selectionMode)
{
        // The mode only has an effect in MouseDown()
        fSelectionMode = selectionMode;
}


void
ShowImageView::SelectAll()
{
        fCopyFromRect.Set(0, 0, fBitmap->Bounds().Width(),
                fBitmap->Bounds().Height());
        fSelectionBox.SetBounds(this, fCopyFromRect);
        _SetHasSelection(true);
        Invalidate();
}


void
ShowImageView::ClearSelection()
{
        if (!fHasSelection)
                return;

        _SetHasSelection(false);
        Invalidate();
}


void
ShowImageView::_SetHasSelection(bool hasSelection)
{
        _DeleteSelectionBitmap();
        fHasSelection = hasSelection;

        _UpdateStatusText();

        BMessage msg(MSG_SELECTION);
        msg.AddBool("has_selection", fHasSelection);
        _SendMessageToWindow(&msg);
}


void
ShowImageView::CopySelectionToClipboard()
{
        if (!fHasSelection || !be_clipboard->Lock())
                return;

        be_clipboard->Clear();

        BMessage* data = be_clipboard->Data();
        if (data != NULL) {
                BBitmap* bitmap = _CopySelection();
                if (bitmap != NULL) {
                        BMessage bitmapArchive;
                        bitmap->Archive(&bitmapArchive);
                        // NOTE: Possibly "image/x-be-bitmap" is more correct.
                        // This works with WonderBrush, though, which in turn had been
                        // tested with other apps.
                        data->AddMessage("image/bitmap", &bitmapArchive);
                        data->AddPoint("be:location", fSelectionBox.Bounds().LeftTop());

                        delete bitmap;

                        be_clipboard->Commit();
                }
        }
        be_clipboard->Unlock();
}


void
ShowImageView::SetZoom(float zoom, BPoint where)
{
        float fitToBoundsZoom = _FitToBoundsZoom();
        if (zoom > 32)
                zoom = 32;
        if (zoom < fitToBoundsZoom / 2 && zoom < 0.25)
                zoom = min_c(fitToBoundsZoom / 2, 0.25);

        if (zoom == fZoom) {
                // window size might have changed
                FixupScrollBars();
                return;
        }

        // Invalidate before scrolling, as that prevents the app_server
        // to do the scrolling server side
        Invalidate();

        // zoom to center if not otherwise specified
        BPoint offset;
        if (where.x == -1) {
                where.Set(Bounds().Width() / 2, Bounds().Height() / 2);
                offset = where;
                where += Bounds().LeftTop();
        } else
                offset = where - Bounds().LeftTop();

        float oldZoom = fZoom;
        fZoom = zoom;

        FixupScrollBars();

        if (fBitmap != NULL) {
                offset.x = (int)(where.x * fZoom / oldZoom + 0.5) - offset.x;
                offset.y = (int)(where.y * fZoom / oldZoom + 0.5) - offset.y;
                ScrollTo(offset);
        }

        BMessage message(MSG_UPDATE_STATUS_ZOOM);
        message.AddFloat("zoom", fZoom);
        _SendMessageToWindow(&message);
}


void
ShowImageView::ZoomIn(BPoint where)
{
        // snap zoom to "fit to bounds", and "original size"
        float zoom = fZoom * 1.2;
        float zoomSnap = fZoom * 1.25;
        float fitToBoundsZoom = _FitToBoundsZoom();
        if (fZoom < fitToBoundsZoom - 0.001 && zoomSnap > fitToBoundsZoom)
                zoom = fitToBoundsZoom;
        if (fZoom < 1.0 && zoomSnap > 1.0)
                zoom = 1.0;

        SetZoom(zoom, where);
}


void
ShowImageView::ZoomOut(BPoint where)
{
        // snap zoom to "fit to bounds", and "original size"
        float zoom = fZoom / 1.2;
        float zoomSnap = fZoom / 1.25;
        float fitToBoundsZoom = _FitToBoundsZoom();
        if (fZoom > fitToBoundsZoom + 0.001 && zoomSnap < fitToBoundsZoom)
                zoom = fitToBoundsZoom;
        if (fZoom > 1.0 && zoomSnap < 1.0)
                zoom = 1.0;

        SetZoom(zoom, where);
}


/*!     Fits the image to the view bounds.
*/
void
ShowImageView::FitToBounds()
{
        if (fBitmap == NULL)
                return;

        float fitToBoundsZoom = _FitToBoundsZoom();
        if ((!fStretchToBounds && fitToBoundsZoom > 1.0f) || fForceOriginalSize)
                SetZoom(1.0f);
        else
                SetZoom(fitToBoundsZoom);

        FixupScrollBars();
}


void
ShowImageView::_DoImageOperation(ImageProcessor::operation op, bool quiet)
{
        BMessenger msgr;
        ImageProcessor imageProcessor(op, fBitmap, msgr, 0);
        imageProcessor.Start(false);
        BBitmap* bm = imageProcessor.DetachBitmap();
        if (bm == NULL) {
                // operation failed
                return;
        }

        // update orientation state
        if (op != ImageProcessor::kInvert) {
                // Note: If one of these fails, check its definition in class ImageProcessor.
//              ASSERT(ImageProcessor::kRotateClockwise <
//                      ImageProcessor::kNumberOfAffineTransformations);
//              ASSERT(ImageProcessor::kRotateCounterClockwise <
//                      ImageProcessor::kNumberOfAffineTransformations);
//              ASSERT(ImageProcessor::kFlipLeftToRight <
//                      ImageProcessor::kNumberOfAffineTransformations);
//              ASSERT(ImageProcessor::kFlipTopToBottom <
//                      ImageProcessor::kNumberOfAffineTransformations);
                fImageOrientation = fTransformation[op][fImageOrientation];
        }

        if (!quiet) {
                // write orientation state
                BNode node(&fCurrentRef);
                int32 orientation = fImageOrientation;
                if (orientation != k0) {
                        node.WriteAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE, B_INT32_TYPE, 0,
                                &orientation, sizeof(orientation));
                } else
                        node.RemoveAttr(SHOW_IMAGE_ORIENTATION_ATTRIBUTE);
        }

        // set new bitmap
        _DeleteBitmap();
        fBitmap = bm;

        if (fBitmap->ColorSpace() == B_RGBA32)
                fDisplayBitmap = compose_checker_background(fBitmap);

        if (!quiet) {
                // remove selection
                _SetHasSelection(false);
                _Notify();
        }
}


//! Image operation initiated by user
void
ShowImageView::_UserDoImageOperation(ImageProcessor::operation op, bool quiet)
{
        _DoImageOperation(op, quiet);
}


void
ShowImageView::Rotate(int degree)
{
        _UserDoImageOperation(degree == 90 ? ImageProcessor::kRotateClockwise
                : ImageProcessor::kRotateCounterClockwise);

        FitToBounds();
}


void
ShowImageView::Flip(bool vertical)
{
        if (vertical)
                _UserDoImageOperation(ImageProcessor::kFlipLeftToRight);
        else
                _UserDoImageOperation(ImageProcessor::kFlipTopToBottom);
}


void
ShowImageView::ResizeImage(int w, int h)
{
        if (fBitmap == NULL || w < 1 || h < 1)
                return;

        Scaler scaler(fBitmap, BRect(0, 0, w - 1, h - 1), BMessenger(), 0, false);
        scaler.Start(false);
        BBitmap* scaled = scaler.DetachBitmap();
        if (scaled == NULL) {
                // operation failed
                return;
        }

        // remove selection
        _SetHasSelection(false);
        _DeleteBitmap();
        fBitmap = scaled;

        _SendMessageToWindow(MSG_MODIFIED);

        _Notify();
}


void
ShowImageView::_SetIcon(bool clear, icon_size which)
{
        const int32 size = be_control_look->ComposeIconSize(which).IntegerWidth() + 1;

        BRect rect(fBitmap->Bounds());
        float s;
        s = size / (rect.Width() + 1.0);

        if (s * (rect.Height() + 1.0) <= size) {
                rect.right = size - 1;
                rect.bottom = static_cast<int>(s * (rect.Height() + 1.0)) - 1;
                // center vertically
                rect.OffsetBy(0, (size - rect.IntegerHeight()) / 2);
        } else {
                s = size / (rect.Height() + 1.0);
                rect.right = static_cast<int>(s * (rect.Width() + 1.0)) - 1;
                rect.bottom = size - 1;
                // center horizontally
                rect.OffsetBy((size - rect.IntegerWidth()) / 2, 0);
        }

        // scale bitmap to thumbnail size
        BMessenger msgr;
        Scaler scaler(fBitmap, rect, msgr, 0, true);
        BBitmap* thumbnail = scaler.GetBitmap();
        scaler.Start(false);
        ASSERT(thumbnail->ColorSpace() == B_CMAP8);
        // create icon from thumbnail
        BBitmap icon(BRect(0, 0, size - 1, size - 1), B_CMAP8);
        memset(icon.Bits(), B_TRANSPARENT_MAGIC_CMAP8, icon.BitsLength());
        BScreen screen;
        const uchar* src = (uchar*)thumbnail->Bits();
        uchar* dest = (uchar*)icon.Bits();
        const int32 srcBPR = thumbnail->BytesPerRow();
        const int32 destBPR = icon.BytesPerRow();
        const int32 deltaX = (int32)rect.left;
        const int32 deltaY = (int32)rect.top;

        for (int32 y = 0; y <= rect.IntegerHeight(); y++) {
                for (int32 x = 0; x <= rect.IntegerWidth(); x++) {
                        const uchar* s = src + y * srcBPR + x;
                        uchar* d = dest + (y + deltaY) * destBPR + (x + deltaX);
                        *d = *s;
                }
        }

        // set icon
        BNode node(&fCurrentRef);
        BNodeInfo info(&node);
        info.SetIcon(clear ? NULL : &icon, which);
}


void
ShowImageView::SetIcon(bool clear)
{
        _SetIcon(clear, B_MINI_ICON);
        _SetIcon(clear, B_LARGE_ICON);
}


void
ShowImageView::_ToggleSlideShow()
{
        _SendMessageToWindow(MSG_SLIDE_SHOW);
}


void
ShowImageView::_StopSlideShow()
{
        _SendMessageToWindow(kMsgStopSlideShow);
}


void
ShowImageView::_ExitFullScreen()
{
        be_app->ShowCursor();
        _SendMessageToWindow(MSG_EXIT_FULL_SCREEN);
}


void
ShowImageView::_ShowToolBarIfEnabled(bool show)
{
        BMessage message(kShowToolBarIfEnabled);
        message.AddBool("show", show);
        Window()->PostMessage(&message);
}


void
ShowImageView::WindowActivated(bool active)
{
        fIsActiveWin = active;
        fHideCursorCountDown = HIDE_CURSOR_DELAY_TIME;
}