/* * Copyright 2006-2011, Haiku. * Distributed under the terms of the MIT License. * * Authors: * Stephan Aßmus * Axel Dörfler, axeld@pinc-software.de. */ #include "IconButton.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace BPrivate { enum { STATE_NONE = 0x0000, STATE_PRESSED = 0x0002, STATE_INSIDE = 0x0008, STATE_FORCE_PRESSED = 0x0010, }; BIconButton::BIconButton(const char* name, const char* label, BMessage* message, BHandler* target) : BControl(name, label, message, B_WILL_DRAW), fButtonState(0), fNormalBitmap(NULL), fDisabledBitmap(NULL), fClickedBitmap(NULL), fDisabledClickedBitmap(NULL), fTargetCache(target) { SetTarget(target); SetLowColor(ui_color(B_PANEL_BACKGROUND_COLOR)); SetViewColor(B_TRANSPARENT_32_BIT); } BIconButton::~BIconButton() { _DeleteBitmaps(); } void BIconButton::MessageReceived(BMessage* message) { switch (message->what) { default: BView::MessageReceived(message); break; } } void BIconButton::AttachedToWindow() { AdoptParentColors(); if (ViewUIColor() != B_NO_COLOR) SetLowUIColor(ViewUIColor()); SetTarget(fTargetCache); if (!Target()) SetTarget(Window()); } void BIconButton::Draw(BRect updateRect) { rgb_color background = LowColor(); BRect r(Bounds()); uint32 flags = 0; BBitmap* bitmap = fNormalBitmap; if (!IsEnabled()) { flags |= BControlLook::B_DISABLED; bitmap = fDisabledBitmap; } if (_HasFlags(STATE_PRESSED) || _HasFlags(STATE_FORCE_PRESSED)) flags |= BControlLook::B_ACTIVATED; if (ShouldDrawBorder()) { DrawBorder(r, updateRect, background, flags); DrawBackground(r, updateRect, background, flags); } else { SetHighColor(background); FillRect(r); } if (bitmap && bitmap->IsValid()) { if (bitmap->ColorSpace() == B_RGBA32 || bitmap->ColorSpace() == B_RGBA32_BIG) { SetDrawingMode(B_OP_ALPHA); SetBlendingMode(B_PIXEL_ALPHA, B_ALPHA_OVERLAY); } float x = r.left + floorf((r.Width() - bitmap->Bounds().Width()) / 2.0 + 0.5); float y = r.top + floorf((r.Height() - bitmap->Bounds().Height()) / 2.0 + 0.5); DrawBitmap(bitmap, BPoint(x, y)); } } bool BIconButton::ShouldDrawBorder() const { return (IsEnabled() && (IsInside() || IsTracking())) || _HasFlags(STATE_FORCE_PRESSED); } void BIconButton::DrawBorder(BRect& frame, const BRect& updateRect, const rgb_color& backgroundColor, uint32 flags) { be_control_look->DrawButtonFrame(this, frame, updateRect, backgroundColor, backgroundColor, flags); } void BIconButton::DrawBackground(BRect& frame, const BRect& updateRect, const rgb_color& backgroundColor, uint32 flags) { be_control_look->DrawButtonBackground(this, frame, updateRect, backgroundColor, flags); } void BIconButton::MouseDown(BPoint where) { if (!IsValid()) return; if (IsEnabled()) { if (Bounds().Contains(where)) { SetMouseEventMask(B_POINTER_EVENTS, B_LOCK_WINDOW_FOCUS); _SetFlags(STATE_PRESSED, true); _SetTracking(true); } else { _SetFlags(STATE_PRESSED, false); _SetTracking(false); } } } void BIconButton::MouseUp(BPoint where) { if (!IsValid()) return; if (IsEnabled() && _HasFlags(STATE_PRESSED) && Bounds().Contains(where)) { Invoke(); } else if (Bounds().Contains(where)) SetInside(true); _SetFlags(STATE_PRESSED, false); _SetTracking(false); } void BIconButton::MouseMoved(BPoint where, uint32 transit, const BMessage* message) { if (!IsValid()) return; uint32 buttons = 0; Window()->CurrentMessage()->FindInt32("buttons", (int32*)&buttons); // catch a mouse up event that we might have missed if (!buttons && _HasFlags(STATE_PRESSED)) { MouseUp(where); return; } if (buttons != 0 && !IsTracking()) return; SetInside((transit == B_INSIDE_VIEW || transit == B_ENTERED_VIEW) && IsEnabled()); if (IsTracking()) _SetFlags(STATE_PRESSED, Bounds().Contains(where)); } void BIconButton::GetPreferredSize(float* width, float* height) { float minWidth = 0.0f; float minHeight = 0.0f; if (IsValid()) { minWidth += fNormalBitmap->Bounds().IntegerWidth() + 1.0f; minHeight += fNormalBitmap->Bounds().IntegerHeight() + 1.0f; } const float kMinSpace = 15.0f; if (minWidth < kMinSpace) minWidth = kMinSpace; if (minHeight < kMinSpace) minHeight = kMinSpace; float hPadding = max_c(6.0f, ceilf(minHeight / 4.0f)); float vPadding = max_c(6.0f, ceilf(minWidth / 4.0f)); if (Label() != NULL && Label()[0] != '\0') { font_height fh; GetFontHeight(&fh); minHeight += ceilf(fh.ascent + fh.descent) + vPadding; minWidth += StringWidth(Label()) + vPadding; } if (width) *width = minWidth + hPadding; if (height) *height = minHeight + vPadding; } BSize BIconButton::MinSize() { BSize size; GetPreferredSize(&size.width, &size.height); return size; } BSize BIconButton::MaxSize() { return MinSize(); } status_t BIconButton::Invoke(BMessage* message) { if (message == NULL) message = Message(); if (message != NULL) { BMessage clone(*message); clone.AddInt64("be:when", system_time()); clone.AddPointer("be:source", (BView*)this); clone.AddInt32("be:value", Value()); return BInvoker::Invoke(&clone); } return BInvoker::Invoke(message); } void BIconButton::SetPressed(bool pressed) { _SetFlags(STATE_FORCE_PRESSED, pressed); } bool BIconButton::IsPressed() const { return _HasFlags(STATE_FORCE_PRESSED); } status_t BIconButton::SetIcon(int32 resourceID) { app_info info; status_t status = be_app->GetAppInfo(&info); if (status != B_OK) return status; BResources resources(&info.ref); status = resources.InitCheck(); if (status != B_OK) return status; size_t size; const void* data = resources.LoadResource(B_VECTOR_ICON_TYPE, resourceID, &size); if (data != NULL) { const BRect bitmapRect(BPoint(0, 0), be_control_look->ComposeIconSize(32)); BBitmap bitmap(bitmapRect, B_BITMAP_NO_SERVER_LINK, B_RGBA32); status = bitmap.InitCheck(); if (status != B_OK) return status; status = BIconUtils::GetVectorIcon(reinterpret_cast(data), size, &bitmap); if (status != B_OK) return status; return SetIcon(&bitmap); } // const void* data = resources.LoadResource(B_BITMAP_TYPE, resourceID, &size); return B_ERROR; } status_t BIconButton::SetIcon(const char* pathToBitmap) { if (pathToBitmap == NULL) return B_BAD_VALUE; status_t status = B_BAD_VALUE; BBitmap* fileBitmap = NULL; // try to load bitmap from either relative or absolute path BEntry entry(pathToBitmap, true); if (!entry.Exists()) { app_info info; status = be_app->GetAppInfo(&info); if (status == B_OK) { BEntry app_entry(&info.ref, true); BPath path; app_entry.GetPath(&path); status = path.InitCheck(); if (status == B_OK) { status = path.GetParent(&path); if (status == B_OK) { status = path.Append(pathToBitmap, true); if (status == B_OK) fileBitmap = BTranslationUtils::GetBitmap(path.Path()); else { printf("BIconButton::SetIcon() - path.Append() failed: " "%s\n", strerror(status)); } } else { printf("BIconButton::SetIcon() - path.GetParent() failed: " "%s\n", strerror(status)); } } else { printf("BIconButton::SetIcon() - path.InitCheck() failed: " "%s\n", strerror(status)); } } else { printf("BIconButton::SetIcon() - be_app->GetAppInfo() failed: " "%s\n", strerror(status)); } } else fileBitmap = BTranslationUtils::GetBitmap(pathToBitmap); if (fileBitmap) { status = _MakeBitmaps(fileBitmap); delete fileBitmap; } else status = B_ERROR; return status; } status_t BIconButton::SetIcon(const BBitmap* bitmap, uint32 flags) { if (bitmap && bitmap->ColorSpace() == B_CMAP8) { status_t status = bitmap->InitCheck(); if (status >= B_OK) { if (BBitmap* rgb32Bitmap = _ConvertToRGB32(bitmap)) { status = _MakeBitmaps(rgb32Bitmap); delete rgb32Bitmap; } else status = B_NO_MEMORY; } return status; } else return _MakeBitmaps(bitmap); } status_t BIconButton::SetIcon(const BMimeType* fileType, bool small) { status_t status = fileType ? fileType->InitCheck() : B_BAD_VALUE; if (status >= B_OK) { BBitmap* mimeBitmap = new(std::nothrow) BBitmap(BRect(0.0, 0.0, 15.0, 15.0), B_CMAP8); if (mimeBitmap && mimeBitmap->IsValid()) { status = fileType->GetIcon(mimeBitmap, small ? B_MINI_ICON : B_LARGE_ICON); if (status >= B_OK) { if (BBitmap* bitmap = _ConvertToRGB32(mimeBitmap)) { status = _MakeBitmaps(bitmap); delete bitmap; } else { printf("BIconButton::SetIcon() - B_RGB32 bitmap is not " "valid\n"); } } else { printf("BIconButton::SetIcon() - fileType->GetIcon() failed: " "%s\n", strerror(status)); } } else printf("BIconButton::SetIcon() - B_CMAP8 bitmap is not valid\n"); delete mimeBitmap; } else { printf("BIconButton::SetIcon() - fileType is not valid: %s\n", strerror(status)); } return status; } status_t BIconButton::SetIcon(const unsigned char* bitsFromQuickRes, uint32 width, uint32 height, color_space format, bool convertToBW) { status_t status = B_BAD_VALUE; if (bitsFromQuickRes && width > 0 && height > 0) { BBitmap* quickResBitmap = new(std::nothrow) BBitmap(BRect(0.0, 0.0, width - 1.0, height - 1.0), format); status = quickResBitmap ? quickResBitmap->InitCheck() : B_ERROR; if (status >= B_OK) { // It doesn't look right to copy BitsLength() bytes, but bitmaps // exported from QuickRes still contain their padding, so it is // all right. memcpy(quickResBitmap->Bits(), bitsFromQuickRes, quickResBitmap->BitsLength()); if (format != B_RGB32 && format != B_RGBA32 && format != B_RGB32_BIG && format != B_RGBA32_BIG) { // colorspace needs conversion BBitmap* bitmap = new(std::nothrow) BBitmap( quickResBitmap->Bounds(), B_RGB32, true); if (bitmap && bitmap->IsValid()) { if (bitmap->Lock()) { BView* helper = new BView(bitmap->Bounds(), "helper", B_FOLLOW_NONE, B_WILL_DRAW); bitmap->AddChild(helper); helper->SetHighColor(ui_color(B_PANEL_BACKGROUND_COLOR)); helper->FillRect(helper->Bounds()); helper->SetDrawingMode(B_OP_OVER); helper->DrawBitmap(quickResBitmap, BPoint(0.0, 0.0)); helper->Sync(); bitmap->Unlock(); } status = _MakeBitmaps(bitmap); } else { printf("BIconButton::SetIcon() - B_RGB32 bitmap is not " "valid\n"); } delete bitmap; } else { // native colorspace (32 bits) if (convertToBW) { // convert to gray scale icon uint8* bits = (uint8*)quickResBitmap->Bits(); uint32 bpr = quickResBitmap->BytesPerRow(); for (uint32 y = 0; y < height; y++) { uint8* handle = bits; uint8 gray; for (uint32 x = 0; x < width; x++) { gray = uint8((116 * handle[0] + 600 * handle[1] + 308 * handle[2]) / 1024); handle[0] = gray; handle[1] = gray; handle[2] = gray; handle += 4; } bits += bpr; } } status = _MakeBitmaps(quickResBitmap); } } else { printf("BIconButton::SetIcon() - error allocating bitmap: " "%s\n", strerror(status)); } delete quickResBitmap; } return status; } void BIconButton::ClearIcon() { _DeleteBitmaps(); _Update(); } void BIconButton::TrimIcon(bool keepAspect) { if (fNormalBitmap == NULL) return; uint8* bits = (uint8*)fNormalBitmap->Bits(); uint32 bpr = fNormalBitmap->BytesPerRow(); uint32 width = fNormalBitmap->Bounds().IntegerWidth() + 1; uint32 height = fNormalBitmap->Bounds().IntegerHeight() + 1; BRect trimmed(INT32_MAX, INT32_MAX, INT32_MIN, INT32_MIN); for (uint32 y = 0; y < height; y++) { uint8* b = bits + 3; bool rowHasAlpha = false; for (uint32 x = 0; x < width; x++) { if (*b) { rowHasAlpha = true; if (x < trimmed.left) trimmed.left = x; if (x > trimmed.right) trimmed.right = x; } b += 4; } if (rowHasAlpha) { if (y < trimmed.top) trimmed.top = y; if (y > trimmed.bottom) trimmed.bottom = y; } bits += bpr; } if (!trimmed.IsValid()) return; if (keepAspect) { float minInset = trimmed.left; minInset = min_c(minInset, trimmed.top); minInset = min_c(minInset, fNormalBitmap->Bounds().right - trimmed.right); minInset = min_c(minInset, fNormalBitmap->Bounds().bottom - trimmed.bottom); trimmed = fNormalBitmap->Bounds().InsetByCopy(minInset, minInset); } trimmed = trimmed & fNormalBitmap->Bounds(); BBitmap trimmedBitmap(trimmed.OffsetToCopy(B_ORIGIN), B_BITMAP_NO_SERVER_LINK, B_RGBA32); bits = (uint8*)fNormalBitmap->Bits(); bits += 4 * (int32)trimmed.left + bpr * (int32)trimmed.top; uint8* dst = (uint8*)trimmedBitmap.Bits(); uint32 trimmedWidth = trimmedBitmap.Bounds().IntegerWidth() + 1; uint32 trimmedHeight = trimmedBitmap.Bounds().IntegerHeight() + 1; uint32 trimmedBPR = trimmedBitmap.BytesPerRow(); for (uint32 y = 0; y < trimmedHeight; y++) { memcpy(dst, bits, trimmedWidth * 4); dst += trimmedBPR; bits += bpr; } SetIcon(&trimmedBitmap); } bool BIconButton::IsValid() const { return (fNormalBitmap && fDisabledBitmap && fClickedBitmap && fDisabledClickedBitmap && fNormalBitmap->IsValid() && fDisabledBitmap->IsValid() && fClickedBitmap->IsValid() && fDisabledClickedBitmap->IsValid()); } BBitmap* BIconButton::Bitmap() const { BBitmap* bitmap = NULL; if (fNormalBitmap && fNormalBitmap->IsValid()) { bitmap = new(std::nothrow) BBitmap(fNormalBitmap); if (bitmap != NULL && bitmap->IsValid()) { // TODO: remove this functionality when we use real transparent // bitmaps uint8* bits = (uint8*)bitmap->Bits(); uint32 bpr = bitmap->BytesPerRow(); uint32 width = bitmap->Bounds().IntegerWidth() + 1; uint32 height = bitmap->Bounds().IntegerHeight() + 1; color_space format = bitmap->ColorSpace(); if (format == B_CMAP8) { // replace gray with magic transparent index } else if (format == B_RGB32) { for (uint32 y = 0; y < height; y++) { uint8* bitsHandle = bits; for (uint32 x = 0; x < width; x++) { if (bitsHandle[0] == 216 && bitsHandle[1] == 216 && bitsHandle[2] == 216) { // make this pixel completely transparent bitsHandle[3] = 0; } bitsHandle += 4; } bits += bpr; } } } else { delete bitmap; bitmap = NULL; } } return bitmap; } void BIconButton::SetValue(int32 value) { BControl::SetValue(value); _SetFlags(STATE_PRESSED, value != 0); } void BIconButton::SetEnabled(bool enabled) { BControl::SetEnabled(enabled); if (!enabled) { SetInside(false); _SetTracking(false); } } // #pragma mark - protected bool BIconButton::IsInside() const { return _HasFlags(STATE_INSIDE); } void BIconButton::SetInside(bool inside) { _SetFlags(STATE_INSIDE, inside); } // #pragma mark - private BBitmap* BIconButton::_ConvertToRGB32(const BBitmap* bitmap) const { BBitmap* convertedBitmap = new(std::nothrow) BBitmap(bitmap->Bounds(), B_BITMAP_ACCEPTS_VIEWS, B_RGBA32); if (convertedBitmap && convertedBitmap->IsValid()) { memset(convertedBitmap->Bits(), 0, convertedBitmap->BitsLength()); if (convertedBitmap->Lock()) { BView* helper = new BView(bitmap->Bounds(), "helper", B_FOLLOW_NONE, B_WILL_DRAW); convertedBitmap->AddChild(helper); helper->SetDrawingMode(B_OP_OVER); helper->DrawBitmap(bitmap, BPoint(0.0, 0.0)); helper->Sync(); convertedBitmap->Unlock(); } } else { delete convertedBitmap; convertedBitmap = NULL; } return convertedBitmap; } status_t BIconButton::_MakeBitmaps(const BBitmap* bitmap) { status_t status = bitmap ? bitmap->InitCheck() : B_BAD_VALUE; if (status == B_OK) { // make our own versions of the bitmap BRect b(bitmap->Bounds()); _DeleteBitmaps(); color_space format = bitmap->ColorSpace(); fNormalBitmap = new(std::nothrow) BBitmap(b, format); fDisabledBitmap = new(std::nothrow) BBitmap(b, format); fClickedBitmap = new(std::nothrow) BBitmap(b, format); fDisabledClickedBitmap = new(std::nothrow) BBitmap(b, format); if (IsValid()) { // copy bitmaps from file bitmap uint8* nBits = (uint8*)fNormalBitmap->Bits(); uint8* dBits = (uint8*)fDisabledBitmap->Bits(); uint8* cBits = (uint8*)fClickedBitmap->Bits(); uint8* dcBits = (uint8*)fDisabledClickedBitmap->Bits(); uint8* fBits = (uint8*)bitmap->Bits(); int32 nbpr = fNormalBitmap->BytesPerRow(); int32 fbpr = bitmap->BytesPerRow(); int32 pixels = b.IntegerWidth() + 1; int32 lines = b.IntegerHeight() + 1; // nontransparent version: if (format == B_RGB32 || format == B_RGB32_BIG) { // iterate over color components for (int32 y = 0; y < lines; y++) { for (int32 x = 0; x < pixels; x++) { int32 nOffset = 4 * x; int32 fOffset = 4 * x; nBits[nOffset + 0] = fBits[fOffset + 0]; nBits[nOffset + 1] = fBits[fOffset + 1]; nBits[nOffset + 2] = fBits[fOffset + 2]; nBits[nOffset + 3] = 255; // clicked bits are darker (lame method...) cBits[nOffset + 0] = (uint8)((float)nBits[nOffset + 0] * 0.8); cBits[nOffset + 1] = (uint8)((float)nBits[nOffset + 1] * 0.8); cBits[nOffset + 2] = (uint8)((float)nBits[nOffset + 2] * 0.8); cBits[nOffset + 3] = 255; // disabled bits have less contrast (lame method...) uint8 grey = 216; float dist = (nBits[nOffset + 0] - grey) * 0.4; dBits[nOffset + 0] = (uint8)(grey + dist); dist = (nBits[nOffset + 1] - grey) * 0.4; dBits[nOffset + 1] = (uint8)(grey + dist); dist = (nBits[nOffset + 2] - grey) * 0.4; dBits[nOffset + 2] = (uint8)(grey + dist); dBits[nOffset + 3] = 255; // disabled bits have less contrast (lame method...) grey = 188; dist = (nBits[nOffset + 0] - grey) * 0.4; dcBits[nOffset + 0] = (uint8)(grey + dist); dist = (nBits[nOffset + 1] - grey) * 0.4; dcBits[nOffset + 1] = (uint8)(grey + dist); dist = (nBits[nOffset + 2] - grey) * 0.4; dcBits[nOffset + 2] = (uint8)(grey + dist); dcBits[nOffset + 3] = 255; } nBits += nbpr; dBits += nbpr; cBits += nbpr; dcBits += nbpr; fBits += fbpr; } // transparent version: } else if (format == B_RGBA32 || format == B_RGBA32_BIG) { // iterate over color components for (int32 y = 0; y < lines; y++) { for (int32 x = 0; x < pixels; x++) { int32 nOffset = 4 * x; int32 fOffset = 4 * x; nBits[nOffset + 0] = fBits[fOffset + 0]; nBits[nOffset + 1] = fBits[fOffset + 1]; nBits[nOffset + 2] = fBits[fOffset + 2]; nBits[nOffset + 3] = fBits[fOffset + 3]; // clicked bits are darker (lame method...) cBits[nOffset + 0] = (uint8)(nBits[nOffset + 0] * 0.8); cBits[nOffset + 1] = (uint8)(nBits[nOffset + 1] * 0.8); cBits[nOffset + 2] = (uint8)(nBits[nOffset + 2] * 0.8); cBits[nOffset + 3] = fBits[fOffset + 3]; // disabled bits have less opacity uint8 grey = ((uint16)nBits[nOffset + 0] * 10 + nBits[nOffset + 1] * 60 + nBits[nOffset + 2] * 30) / 100; float dist = (nBits[nOffset + 0] - grey) * 0.3; dBits[nOffset + 0] = (uint8)(grey + dist); dist = (nBits[nOffset + 1] - grey) * 0.3; dBits[nOffset + 1] = (uint8)(grey + dist); dist = (nBits[nOffset + 2] - grey) * 0.3; dBits[nOffset + 2] = (uint8)(grey + dist); dBits[nOffset + 3] = (uint8)(fBits[fOffset + 3] * 0.3); // disabled bits have less contrast (lame method...) dcBits[nOffset + 0] = (uint8)(dBits[nOffset + 0] * 0.8); dcBits[nOffset + 1] = (uint8)(dBits[nOffset + 1] * 0.8); dcBits[nOffset + 2] = (uint8)(dBits[nOffset + 2] * 0.8); dcBits[nOffset + 3] = (uint8)(fBits[fOffset + 3] * 0.3); } nBits += nbpr; dBits += nbpr; cBits += nbpr; dcBits += nbpr; fBits += fbpr; } // unsupported format } else { printf("BIconButton::_MakeBitmaps() - bitmap has unsupported " "colorspace\n"); status = B_MISMATCHED_VALUES; _DeleteBitmaps(); } } else { printf("BIconButton::_MakeBitmaps() - error allocating local " "bitmaps\n"); status = B_NO_MEMORY; _DeleteBitmaps(); } } else printf("BIconButton::_MakeBitmaps() - bitmap is not valid\n"); return status; } void BIconButton::_DeleteBitmaps() { delete fNormalBitmap; fNormalBitmap = NULL; delete fDisabledBitmap; fDisabledBitmap = NULL; delete fClickedBitmap; fClickedBitmap = NULL; delete fDisabledClickedBitmap; fDisabledClickedBitmap = NULL; } void BIconButton::_Update() { if (LockLooper()) { Invalidate(); UnlockLooper(); } } void BIconButton::_SetFlags(uint32 flags, bool set) { if (_HasFlags(flags) != set) { if (set) fButtonState |= flags; else fButtonState &= ~flags; if ((flags & STATE_PRESSED) != 0) SetValueNoUpdate(set ? B_CONTROL_ON : B_CONTROL_OFF); _Update(); } } bool BIconButton::_HasFlags(uint32 flags) const { return (fButtonState & flags) != 0; } //! This one calls _Update() if needed; BControl::SetTracking() isn't virtual. void BIconButton::_SetTracking(bool tracking) { if (IsTracking() == tracking) return; SetTracking(tracking); _Update(); } } // namespace BPrivate