/*****************************************************************************/ // 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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(); } bool ShowImageView::IsImage(const entry_ref *pref) { if (!pref) 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("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) { entry_ref ref; if (!pref) ref = fCurrentRef; else ref = *pref; BTranslatorRoster *proster = BTranslatorRoster::Default(); if (!proster) return B_ERROR; BFile file(&ref, B_READ_ONLY); translator_info info; memset(&info, 0, sizeof(translator_info)); BMessage ioExtension; if (ref != fCurrentRef) // if new image, reset to first document fDocumentIndex = 1; if (ioExtension.AddInt32("/documentIndex", fDocumentIndex) != B_OK) return B_ERROR; 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 = ref; // 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(s * (rect.Height()+1.0))-1; // center vertically rect.OffsetBy(0, static_cast((height - rect.Height()) / 2)); } else { s = height / (rect.Height()+1.0); rect.right = static_cast(s * (rect.Width()+1.0))-1; rect.bottom = height-1; // center horizontally rect.OffsetBy(static_cast((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((rect.right+1.0)*zoom)-1; rect.bottom = static_cast((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(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(&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::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::FindNextImage(entry_ref *in_current, entry_ref *ref, bool next, bool rewind) { // shamelessly stolen, I mean copied from BeMail! // XXX rewind is not implemented yet => slide show will stop with last image if (!fTrackerMessenger.IsValid()) return false; // // Ask the Tracker what the next/prev file in the window is. // Continue asking for the next reference until a valid // image is found. // entry_ref nextRef = *in_current; bool foundRef = false; while (!foundRef) { BMessage request(B_GET_PROPERTY); BMessage spc; if (next) spc.what = 'snxt'; else spc.what = 'sprv'; spc.AddString("property", "Entry"); 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; } } *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(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(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); }