/* * Copyright 2001-2022 Haiku, Inc. All Rights Reserved. * Distributed under the terms of the MIT license. * * Authors: * Stephan Aßmus, superstippi@gmx.de * DarkWyrm, bpmagic@columbus.rr.com * Axel Dörfler, axeld@pinc-software.de * Marc Flerackers, mflerackers@androme.be * John Scipione, jscipione@gmail.com */ #include #include #include #include #include #include #include #include #include #include struct BBox::LayoutData { LayoutData() : valid(false) { } BRect label_box; // label box (label string or label view); in case // of a label string not including descent BRect insets; // insets induced by border and label BSize min; BSize max; BSize preferred; BAlignment alignment; bool valid; // validity the other fields }; BBox::BBox(BRect frame, const char* name, uint32 resizingMode, uint32 flags, border_style border) : BView(frame, name, resizingMode, flags | B_WILL_DRAW | B_FRAME_EVENTS), fStyle(border) { _InitObject(); } BBox::BBox(const char* name, uint32 flags, border_style border, BView* child) : BView(name, flags | B_WILL_DRAW | B_FRAME_EVENTS), fStyle(border) { _InitObject(); if (child) AddChild(child); } BBox::BBox(border_style border, BView* child) : BView(NULL, B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE_JUMP), fStyle(border) { _InitObject(); if (child) AddChild(child); } BBox::BBox(BMessage* archive) : BView(archive), fStyle(B_FANCY_BORDER) { _InitObject(archive); } BBox::~BBox() { _ClearLabel(); delete fLayoutData; } BArchivable* BBox::Instantiate(BMessage* archive) { if (validate_instantiation(archive, "BBox")) return new BBox(archive); return NULL; } status_t BBox::Archive(BMessage* archive, bool deep) const { status_t ret = BView::Archive(archive, deep); if (fLabel && ret == B_OK) ret = archive->AddString("_label", fLabel); if (fLabelView && ret == B_OK) ret = archive->AddBool("_lblview", true); if (fStyle != B_FANCY_BORDER && ret == B_OK) ret = archive->AddInt32("_style", fStyle); return ret; } void BBox::SetBorder(border_style border) { if (border == fStyle) return; fStyle = border; InvalidateLayout(); if (Window() != NULL && LockLooper()) { Invalidate(); UnlockLooper(); } } border_style BBox::Border() const { return fStyle; } //! This function is not part of the R5 API and is not yet finalized yet float BBox::TopBorderOffset() { _ValidateLayoutData(); if (fLabel != NULL || fLabelView != NULL) return fLayoutData->label_box.Height() / 2; return 0; } //! This function is not part of the R5 API and is not yet finalized yet BRect BBox::InnerFrame() { _ValidateLayoutData(); BRect frame(Bounds()); frame.left += fLayoutData->insets.left; frame.top += fLayoutData->insets.top; frame.right -= fLayoutData->insets.right; frame.bottom -= fLayoutData->insets.bottom; return frame; } void BBox::SetLabel(const char* string) { _ClearLabel(); if (string) fLabel = strdup(string); InvalidateLayout(); if (Window()) Invalidate(); } status_t BBox::SetLabel(BView* viewLabel) { _ClearLabel(); if (viewLabel) { fLabelView = viewLabel; fLabelView->MoveTo(10.0f, 0.0f); AddChild(fLabelView, ChildAt(0)); } InvalidateLayout(); if (Window()) Invalidate(); return B_OK; } const char* BBox::Label() const { return fLabel; } BView* BBox::LabelView() const { return fLabelView; } void BBox::Draw(BRect updateRect) { _ValidateLayoutData(); PushState(); BRect labelBox = BRect(0, 0, 0, 0); if (fLabel != NULL) { labelBox = fLayoutData->label_box; BRegion update(updateRect); update.Exclude(labelBox); ConstrainClippingRegion(&update); } else if (fLabelView != NULL) labelBox = fLabelView->Bounds(); switch (fStyle) { case B_FANCY_BORDER: _DrawFancy(labelBox); break; case B_PLAIN_BORDER: _DrawPlain(labelBox); break; default: break; } if (fLabel != NULL) { ConstrainClippingRegion(NULL); font_height fontHeight; GetFontHeight(&fontHeight); // offset label up by 1/6 the font height float lineHeight = fontHeight.ascent + fontHeight.descent; float yOffset = roundf(lineHeight / 6.0f); SetHighColor(ui_color(B_PANEL_TEXT_COLOR)); DrawString(fLabel, BPoint(10.0f, fontHeight.ascent - yOffset)); } PopState(); } void BBox::AttachedToWindow() { AdoptParentColors(); // Force low color to match view color for proper label drawing. float viewTint = B_NO_TINT; float lowTint = B_NO_TINT; if (LowUIColor(&lowTint) != ViewUIColor(&viewTint) || viewTint != lowTint) SetLowUIColor(ViewUIColor(), viewTint); else if (LowColor() != ViewColor()) SetLowColor(ViewColor()); if (ViewColor() == B_TRANSPARENT_COLOR) AdoptSystemColors(); // The box could have been resized in the mean time fBounds = Bounds().OffsetToCopy(0, 0); } void BBox::DetachedFromWindow() { BView::DetachedFromWindow(); } void BBox::AllAttached() { BView::AllAttached(); } void BBox::AllDetached() { BView::AllDetached(); } void BBox::FrameResized(float width, float height) { BRect bounds(Bounds()); // invalidate the regions that the app_server did not // (for removing the previous or drawing the new border) if (fStyle != B_NO_BORDER) { // TODO: this must be made part of the be_control_look stuff! int32 borderSize = fStyle == B_PLAIN_BORDER ? 0 : 2; // Horizontal BRect invalid(bounds); if (fBounds.Width() < bounds.Width()) { // enlarging invalid.left = bounds.left + fBounds.right - borderSize; invalid.right = bounds.left + fBounds.right; Invalidate(invalid); } else if (fBounds.Width() > bounds.Width()) { // shrinking invalid.left = bounds.left + bounds.right - borderSize; Invalidate(invalid); } // Vertical invalid = bounds; if (fBounds.Height() < bounds.Height()) { // enlarging invalid.top = bounds.top + fBounds.bottom - borderSize; invalid.bottom = bounds.top + fBounds.bottom; Invalidate(invalid); } else if (fBounds.Height() > bounds.Height()) { // shrinking invalid.top = bounds.top + bounds.bottom - borderSize; Invalidate(invalid); } } fBounds.right = width; fBounds.bottom = height; } void BBox::MessageReceived(BMessage* message) { BView::MessageReceived(message); } void BBox::MouseDown(BPoint point) { BView::MouseDown(point); } void BBox::MouseUp(BPoint point) { BView::MouseUp(point); } void BBox::WindowActivated(bool active) { BView::WindowActivated(active); } void BBox::MouseMoved(BPoint point, uint32 transit, const BMessage* message) { BView::MouseMoved(point, transit, message); } void BBox::FrameMoved(BPoint newLocation) { BView::FrameMoved(newLocation); } BHandler* BBox::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier, int32 what, const char* property) { return BView::ResolveSpecifier(message, index, specifier, what, property); } void BBox::ResizeToPreferred() { float width, height; GetPreferredSize(&width, &height); // make sure the box don't get smaller than it already is if (width < Bounds().Width()) width = Bounds().Width(); if (height < Bounds().Height()) height = Bounds().Height(); BView::ResizeTo(width, height); } void BBox::GetPreferredSize(float* _width, float* _height) { _ValidateLayoutData(); if (_width) *_width = fLayoutData->preferred.width; if (_height) *_height = fLayoutData->preferred.height; } void BBox::MakeFocus(bool focused) { BView::MakeFocus(focused); } status_t BBox::GetSupportedSuites(BMessage* message) { return BView::GetSupportedSuites(message); } status_t BBox::Perform(perform_code code, void* _data) { switch (code) { case PERFORM_CODE_MIN_SIZE: ((perform_data_min_size*)_data)->return_value = BBox::MinSize(); return B_OK; case PERFORM_CODE_MAX_SIZE: ((perform_data_max_size*)_data)->return_value = BBox::MaxSize(); return B_OK; case PERFORM_CODE_PREFERRED_SIZE: ((perform_data_preferred_size*)_data)->return_value = BBox::PreferredSize(); return B_OK; case PERFORM_CODE_LAYOUT_ALIGNMENT: ((perform_data_layout_alignment*)_data)->return_value = BBox::LayoutAlignment(); return B_OK; case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: ((perform_data_has_height_for_width*)_data)->return_value = BBox::HasHeightForWidth(); return B_OK; case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: { perform_data_get_height_for_width* data = (perform_data_get_height_for_width*)_data; BBox::GetHeightForWidth(data->width, &data->min, &data->max, &data->preferred); return B_OK; } case PERFORM_CODE_SET_LAYOUT: { perform_data_set_layout* data = (perform_data_set_layout*)_data; BBox::SetLayout(data->layout); return B_OK; } case PERFORM_CODE_LAYOUT_INVALIDATED: { perform_data_layout_invalidated* data = (perform_data_layout_invalidated*)_data; BBox::LayoutInvalidated(data->descendants); return B_OK; } case PERFORM_CODE_DO_LAYOUT: { BBox::DoLayout(); return B_OK; } } return BView::Perform(code, _data); } BSize BBox::MinSize() { _ValidateLayoutData(); BSize size = (GetLayout() ? GetLayout()->MinSize() : fLayoutData->min); if (size.width < fLayoutData->min.width) size.width = fLayoutData->min.width; return BLayoutUtils::ComposeSize(ExplicitMinSize(), size); } BSize BBox::MaxSize() { _ValidateLayoutData(); BSize size = (GetLayout() ? GetLayout()->MaxSize() : fLayoutData->max); return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size); } BSize BBox::PreferredSize() { _ValidateLayoutData(); BSize size = (GetLayout() ? GetLayout()->PreferredSize() : fLayoutData->preferred); return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size); } BAlignment BBox::LayoutAlignment() { _ValidateLayoutData(); BAlignment alignment = (GetLayout() ? GetLayout()->Alignment() : fLayoutData->alignment); return BLayoutUtils::ComposeAlignment(ExplicitAlignment(), alignment); } void BBox::LayoutInvalidated(bool descendants) { fLayoutData->valid = false; } void BBox::DoLayout() { // Bail out, if we shan't do layout. if (!(Flags() & B_SUPPORTS_LAYOUT)) return; BLayout* layout = GetLayout(); // If the user set a layout, let the base class version call its // hook. In case when we have BView as a label, remove it from child list // so it won't be layouted with the rest of views and add it again // after that. if (layout != NULL) { if (fLabelView) RemoveChild(fLabelView); BView::DoLayout(); if (fLabelView != NULL) { DisableLayoutInvalidation(); // don't trigger a relayout AddChild(fLabelView, ChildAt(0)); EnableLayoutInvalidation(); } } _ValidateLayoutData(); // Even if the user set a layout, restore label view to it's // desired position. // layout the label view if (fLabelView != NULL) { fLabelView->MoveTo(fLayoutData->label_box.LeftTop()); fLabelView->ResizeTo(fLayoutData->label_box.Size()); } // If we have layout return here and do not layout the child if (layout != NULL) return; // layout the child BView* child = _Child(); if (child != NULL) { BRect frame(Bounds()); frame.left += fLayoutData->insets.left; frame.top += fLayoutData->insets.top; frame.right -= fLayoutData->insets.right; frame.bottom -= fLayoutData->insets.bottom; if ((child->Flags() & B_SUPPORTS_LAYOUT) != 0) BLayoutUtils::AlignInFrame(child, frame); else child->MoveTo(frame.LeftTop()); } } void BBox::_ReservedBox1() {} void BBox::_ReservedBox2() {} BBox & BBox::operator=(const BBox &) { return *this; } void BBox::_InitObject(BMessage* archive) { fBounds = Bounds().OffsetToCopy(0, 0); fLabel = NULL; fLabelView = NULL; fLayoutData = new LayoutData; int32 flags = 0; BFont font(be_bold_font); if (!archive || !archive->HasString("_fname")) flags = B_FONT_FAMILY_AND_STYLE; if (!archive || !archive->HasFloat("_fflt")) flags |= B_FONT_SIZE; if (flags != 0) SetFont(&font, flags); if (archive != NULL) { const char* string; if (archive->FindString("_label", &string) == B_OK) SetLabel(string); bool fancy; int32 style; if (archive->FindBool("_style", &fancy) == B_OK) fStyle = fancy ? B_FANCY_BORDER : B_PLAIN_BORDER; else if (archive->FindInt32("_style", &style) == B_OK) fStyle = (border_style)style; bool hasLabelView; if (archive->FindBool("_lblview", &hasLabelView) == B_OK) fLabelView = ChildAt(0); } AdoptSystemColors(); } void BBox::_DrawPlain(BRect labelBox) { BRect rect = Bounds(); rect.top += TopBorderOffset(); float lightTint; float shadowTint; lightTint = B_LIGHTEN_1_TINT; shadowTint = B_DARKEN_1_TINT; if (rect.Height() == 0.0 || rect.Width() == 0.0) { // used as separator rgb_color shadow = tint_color(ViewColor(), B_DARKEN_2_TINT); SetHighColor(shadow); StrokeLine(rect.LeftTop(),rect.RightBottom()); } else { // used as box rgb_color light = tint_color(ViewColor(), lightTint); rgb_color shadow = tint_color(ViewColor(), shadowTint); BeginLineArray(4); AddLine(BPoint(rect.left, rect.bottom), BPoint(rect.left, rect.top), light); AddLine(BPoint(rect.left + 1.0f, rect.top), BPoint(rect.right, rect.top), light); AddLine(BPoint(rect.left + 1.0f, rect.bottom), BPoint(rect.right, rect.bottom), shadow); AddLine(BPoint(rect.right, rect.bottom - 1.0f), BPoint(rect.right, rect.top + 1.0f), shadow); EndLineArray(); } } void BBox::_DrawFancy(BRect labelBox) { BRect rect = Bounds(); rect.top += TopBorderOffset(); rgb_color base = ViewColor(); if (rect.Height() == 1.0) { // used as horizontal separator be_control_look->DrawGroupFrame(this, rect, rect, base, BControlLook::B_TOP_BORDER); } else if (rect.Width() == 1.0) { // used as vertical separator be_control_look->DrawGroupFrame(this, rect, rect, base, BControlLook::B_LEFT_BORDER); } else { // used as box be_control_look->DrawGroupFrame(this, rect, rect, base); } } void BBox::_ClearLabel() { if (fLabel) { free(fLabel); fLabel = NULL; } else if (fLabelView) { fLabelView->RemoveSelf(); delete fLabelView; fLabelView = NULL; } } BView* BBox::_Child() const { for (int32 i = 0; BView* view = ChildAt(i); i++) { if (view != fLabelView) return view; } return NULL; } void BBox::_ValidateLayoutData() { if (fLayoutData->valid) return; // compute the label box, width and height bool label = true; float labelHeight = 0; // height of the label (pixel count) if (fLabel) { // leave 6 pixels of the frame, and have a gap of 4 pixels between // the frame and the text on either side font_height fontHeight; GetFontHeight(&fontHeight); fLayoutData->label_box.Set(6.0f, 0, 14.0f + StringWidth(fLabel), ceilf(fontHeight.ascent)); labelHeight = ceilf(fontHeight.ascent + fontHeight.descent) + 1; } else if (fLabelView) { // the label view is placed at (0, 10) at its preferred size BSize size = fLabelView->PreferredSize(); fLayoutData->label_box.Set(10, 0, 10 + size.width, size.height); labelHeight = size.height + 1; } else label = false; // border switch (fStyle) { case B_PLAIN_BORDER: fLayoutData->insets.Set(1, 1, 1, 1); break; case B_FANCY_BORDER: fLayoutData->insets.Set(3, 3, 3, 3); break; case B_NO_BORDER: default: fLayoutData->insets.Set(0, 0, 0, 0); break; } // if there's a label, the top inset will be dictated by the label if (label && labelHeight > fLayoutData->insets.top) fLayoutData->insets.top = labelHeight; // total number of pixel the border adds float addWidth = fLayoutData->insets.left + fLayoutData->insets.right; float addHeight = fLayoutData->insets.top + fLayoutData->insets.bottom; // compute the minimal width induced by the label float minWidth; if (label) minWidth = fLayoutData->label_box.right + fLayoutData->insets.right; else minWidth = addWidth - 1; BAlignment alignment(B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER); // finally consider the child constraints, if we shall support layout BView* child = _Child(); if (child && (child->Flags() & B_SUPPORTS_LAYOUT)) { BSize min = child->MinSize(); BSize max = child->MaxSize(); BSize preferred = child->PreferredSize(); min.width += addWidth; min.height += addHeight; preferred.width += addWidth; preferred.height += addHeight; max.width = BLayoutUtils::AddDistances(max.width, addWidth - 1); max.height = BLayoutUtils::AddDistances(max.height, addHeight - 1); if (min.width < minWidth) min.width = minWidth; BLayoutUtils::FixSizeConstraints(min, max, preferred); fLayoutData->min = min; fLayoutData->max = max; fLayoutData->preferred = preferred; BAlignment childAlignment = child->LayoutAlignment(); if (childAlignment.horizontal == B_ALIGN_USE_FULL_WIDTH) alignment.horizontal = B_ALIGN_USE_FULL_WIDTH; if (childAlignment.vertical == B_ALIGN_USE_FULL_HEIGHT) alignment.vertical = B_ALIGN_USE_FULL_HEIGHT; fLayoutData->alignment = alignment; } else { fLayoutData->min.Set(minWidth, addHeight - 1); fLayoutData->max.Set(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED); fLayoutData->preferred = fLayoutData->min; fLayoutData->alignment = alignment; } fLayoutData->valid = true; ResetLayoutInvalidation(); } extern "C" void B_IF_GCC_2(InvalidateLayout__4BBoxb, _ZN4BBox16InvalidateLayoutEb)( BBox* box, bool descendants) { perform_data_layout_invalidated data; data.descendants = descendants; box->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data); }