1/* 2 * Copyright 2001-2022 Haiku, Inc. All Rights Reserved. 3 * Distributed under the terms of the MIT license. 4 * 5 * Authors: 6 * Stephan A��mus, superstippi@gmx.de 7 * DarkWyrm, bpmagic@columbus.rr.com 8 * Axel D��rfler, axeld@pinc-software.de 9 * Marc Flerackers, mflerackers@androme.be 10 * John Scipione, jscipione@gmail.com 11 */ 12 13 14#include <Box.h> 15 16#include <stdio.h> 17#include <stdlib.h> 18#include <string.h> 19 20#include <ControlLook.h> 21#include <Layout.h> 22#include <LayoutUtils.h> 23#include <Message.h> 24#include <Region.h> 25 26#include <binary_compatibility/Interface.h> 27 28 29struct BBox::LayoutData { 30 LayoutData() 31 : valid(false) 32 { 33 } 34 35 BRect label_box; // label box (label string or label view); in case 36 // of a label string not including descent 37 BRect insets; // insets induced by border and label 38 BSize min; 39 BSize max; 40 BSize preferred; 41 BAlignment alignment; 42 bool valid; // validity the other fields 43}; 44 45 46BBox::BBox(BRect frame, const char* name, uint32 resizingMode, uint32 flags, 47 border_style border) 48 : 49 BView(frame, name, resizingMode, flags | B_WILL_DRAW | B_FRAME_EVENTS), 50 fStyle(border) 51{ 52 _InitObject(); 53} 54 55 56BBox::BBox(const char* name, uint32 flags, border_style border, BView* child) 57 : 58 BView(name, flags | B_WILL_DRAW | B_FRAME_EVENTS), 59 fStyle(border) 60{ 61 _InitObject(); 62 63 if (child) 64 AddChild(child); 65} 66 67 68BBox::BBox(border_style border, BView* child) 69 : 70 BView(NULL, B_WILL_DRAW | B_FRAME_EVENTS | B_NAVIGABLE_JUMP), 71 fStyle(border) 72{ 73 _InitObject(); 74 75 if (child) 76 AddChild(child); 77} 78 79 80BBox::BBox(BMessage* archive) 81 : 82 BView(archive), 83 fStyle(B_FANCY_BORDER) 84{ 85 _InitObject(archive); 86} 87 88 89BBox::~BBox() 90{ 91 _ClearLabel(); 92 93 delete fLayoutData; 94} 95 96 97BArchivable* 98BBox::Instantiate(BMessage* archive) 99{ 100 if (validate_instantiation(archive, "BBox")) 101 return new BBox(archive); 102 103 return NULL; 104} 105 106 107status_t 108BBox::Archive(BMessage* archive, bool deep) const 109{ 110 status_t ret = BView::Archive(archive, deep); 111 112 if (fLabel && ret == B_OK) 113 ret = archive->AddString("_label", fLabel); 114 115 if (fLabelView && ret == B_OK) 116 ret = archive->AddBool("_lblview", true); 117 118 if (fStyle != B_FANCY_BORDER && ret == B_OK) 119 ret = archive->AddInt32("_style", fStyle); 120 121 return ret; 122} 123 124 125void 126BBox::SetBorder(border_style border) 127{ 128 if (border == fStyle) 129 return; 130 131 fStyle = border; 132 133 InvalidateLayout(); 134 135 if (Window() != NULL && LockLooper()) { 136 Invalidate(); 137 UnlockLooper(); 138 } 139} 140 141 142border_style 143BBox::Border() const 144{ 145 return fStyle; 146} 147 148 149//! This function is not part of the R5 API and is not yet finalized yet 150float 151BBox::TopBorderOffset() 152{ 153 _ValidateLayoutData(); 154 155 if (fLabel != NULL || fLabelView != NULL) 156 return fLayoutData->label_box.Height() / 2; 157 158 return 0; 159} 160 161 162//! This function is not part of the R5 API and is not yet finalized yet 163BRect 164BBox::InnerFrame() 165{ 166 _ValidateLayoutData(); 167 168 BRect frame(Bounds()); 169 frame.left += fLayoutData->insets.left; 170 frame.top += fLayoutData->insets.top; 171 frame.right -= fLayoutData->insets.right; 172 frame.bottom -= fLayoutData->insets.bottom; 173 174 return frame; 175} 176 177 178void 179BBox::SetLabel(const char* string) 180{ 181 _ClearLabel(); 182 183 if (string) 184 fLabel = strdup(string); 185 186 InvalidateLayout(); 187 188 if (Window()) 189 Invalidate(); 190} 191 192 193status_t 194BBox::SetLabel(BView* viewLabel) 195{ 196 _ClearLabel(); 197 198 if (viewLabel) { 199 fLabelView = viewLabel; 200 fLabelView->MoveTo(10.0f, 0.0f); 201 AddChild(fLabelView, ChildAt(0)); 202 } 203 204 InvalidateLayout(); 205 206 if (Window()) 207 Invalidate(); 208 209 return B_OK; 210} 211 212 213const char* 214BBox::Label() const 215{ 216 return fLabel; 217} 218 219 220BView* 221BBox::LabelView() const 222{ 223 return fLabelView; 224} 225 226 227void 228BBox::Draw(BRect updateRect) 229{ 230 _ValidateLayoutData(); 231 232 PushState(); 233 234 BRect labelBox = BRect(0, 0, 0, 0); 235 if (fLabel != NULL) { 236 labelBox = fLayoutData->label_box; 237 BRegion update(updateRect); 238 update.Exclude(labelBox); 239 240 ConstrainClippingRegion(&update); 241 } else if (fLabelView != NULL) 242 labelBox = fLabelView->Bounds(); 243 244 switch (fStyle) { 245 case B_FANCY_BORDER: 246 _DrawFancy(labelBox); 247 break; 248 249 case B_PLAIN_BORDER: 250 _DrawPlain(labelBox); 251 break; 252 253 default: 254 break; 255 } 256 257 if (fLabel != NULL) { 258 ConstrainClippingRegion(NULL); 259 260 font_height fontHeight; 261 GetFontHeight(&fontHeight); 262 263 // offset label up by 1/6 the font height 264 float lineHeight = fontHeight.ascent + fontHeight.descent; 265 float yOffset = roundf(lineHeight / 6.0f); 266 267 SetHighColor(ui_color(B_PANEL_TEXT_COLOR)); 268 DrawString(fLabel, BPoint(10.0f, fontHeight.ascent - yOffset)); 269 } 270 271 PopState(); 272} 273 274 275void 276BBox::AttachedToWindow() 277{ 278 AdoptParentColors(); 279 280 // Force low color to match view color for proper label drawing. 281 float viewTint = B_NO_TINT; 282 float lowTint = B_NO_TINT; 283 284 if (LowUIColor(&lowTint) != ViewUIColor(&viewTint) || viewTint != lowTint) 285 SetLowUIColor(ViewUIColor(), viewTint); 286 else if (LowColor() != ViewColor()) 287 SetLowColor(ViewColor()); 288 289 if (ViewColor() == B_TRANSPARENT_COLOR) 290 AdoptSystemColors(); 291 292 // The box could have been resized in the mean time 293 fBounds = Bounds().OffsetToCopy(0, 0); 294} 295 296 297void 298BBox::DetachedFromWindow() 299{ 300 BView::DetachedFromWindow(); 301} 302 303 304void 305BBox::AllAttached() 306{ 307 BView::AllAttached(); 308} 309 310 311void 312BBox::AllDetached() 313{ 314 BView::AllDetached(); 315} 316 317 318void 319BBox::FrameResized(float width, float height) 320{ 321 BRect bounds(Bounds()); 322 323 // invalidate the regions that the app_server did not 324 // (for removing the previous or drawing the new border) 325 if (fStyle != B_NO_BORDER) { 326 // TODO: this must be made part of the be_control_look stuff! 327 int32 borderSize = fStyle == B_PLAIN_BORDER ? 0 : 2; 328 329 // Horizontal 330 BRect invalid(bounds); 331 if (fBounds.Width() < bounds.Width()) { 332 // enlarging 333 invalid.left = bounds.left + fBounds.right - borderSize; 334 invalid.right = bounds.left + fBounds.right; 335 336 Invalidate(invalid); 337 } else if (fBounds.Width() > bounds.Width()) { 338 // shrinking 339 invalid.left = bounds.left + bounds.right - borderSize; 340 341 Invalidate(invalid); 342 } 343 344 // Vertical 345 invalid = bounds; 346 if (fBounds.Height() < bounds.Height()) { 347 // enlarging 348 invalid.top = bounds.top + fBounds.bottom - borderSize; 349 invalid.bottom = bounds.top + fBounds.bottom; 350 351 Invalidate(invalid); 352 } else if (fBounds.Height() > bounds.Height()) { 353 // shrinking 354 invalid.top = bounds.top + bounds.bottom - borderSize; 355 356 Invalidate(invalid); 357 } 358 } 359 360 fBounds.right = width; 361 fBounds.bottom = height; 362} 363 364 365void 366BBox::MessageReceived(BMessage* message) 367{ 368 BView::MessageReceived(message); 369} 370 371 372void 373BBox::MouseDown(BPoint point) 374{ 375 BView::MouseDown(point); 376} 377 378 379void 380BBox::MouseUp(BPoint point) 381{ 382 BView::MouseUp(point); 383} 384 385 386void 387BBox::WindowActivated(bool active) 388{ 389 BView::WindowActivated(active); 390} 391 392 393void 394BBox::MouseMoved(BPoint point, uint32 transit, const BMessage* message) 395{ 396 BView::MouseMoved(point, transit, message); 397} 398 399 400void 401BBox::FrameMoved(BPoint newLocation) 402{ 403 BView::FrameMoved(newLocation); 404} 405 406 407BHandler* 408BBox::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier, 409 int32 what, const char* property) 410{ 411 return BView::ResolveSpecifier(message, index, specifier, what, property); 412} 413 414 415void 416BBox::ResizeToPreferred() 417{ 418 float width, height; 419 GetPreferredSize(&width, &height); 420 421 // make sure the box don't get smaller than it already is 422 if (width < Bounds().Width()) 423 width = Bounds().Width(); 424 if (height < Bounds().Height()) 425 height = Bounds().Height(); 426 427 BView::ResizeTo(width, height); 428} 429 430 431void 432BBox::GetPreferredSize(float* _width, float* _height) 433{ 434 _ValidateLayoutData(); 435 436 if (_width) 437 *_width = fLayoutData->preferred.width; 438 if (_height) 439 *_height = fLayoutData->preferred.height; 440} 441 442 443void 444BBox::MakeFocus(bool focused) 445{ 446 BView::MakeFocus(focused); 447} 448 449 450status_t 451BBox::GetSupportedSuites(BMessage* message) 452{ 453 return BView::GetSupportedSuites(message); 454} 455 456 457status_t 458BBox::Perform(perform_code code, void* _data) 459{ 460 switch (code) { 461 case PERFORM_CODE_MIN_SIZE: 462 ((perform_data_min_size*)_data)->return_value 463 = BBox::MinSize(); 464 return B_OK; 465 case PERFORM_CODE_MAX_SIZE: 466 ((perform_data_max_size*)_data)->return_value 467 = BBox::MaxSize(); 468 return B_OK; 469 case PERFORM_CODE_PREFERRED_SIZE: 470 ((perform_data_preferred_size*)_data)->return_value 471 = BBox::PreferredSize(); 472 return B_OK; 473 case PERFORM_CODE_LAYOUT_ALIGNMENT: 474 ((perform_data_layout_alignment*)_data)->return_value 475 = BBox::LayoutAlignment(); 476 return B_OK; 477 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 478 ((perform_data_has_height_for_width*)_data)->return_value 479 = BBox::HasHeightForWidth(); 480 return B_OK; 481 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 482 { 483 perform_data_get_height_for_width* data 484 = (perform_data_get_height_for_width*)_data; 485 BBox::GetHeightForWidth(data->width, &data->min, &data->max, 486 &data->preferred); 487 return B_OK; 488 } 489 case PERFORM_CODE_SET_LAYOUT: 490 { 491 perform_data_set_layout* data = (perform_data_set_layout*)_data; 492 BBox::SetLayout(data->layout); 493 return B_OK; 494 } 495 case PERFORM_CODE_LAYOUT_INVALIDATED: 496 { 497 perform_data_layout_invalidated* data 498 = (perform_data_layout_invalidated*)_data; 499 BBox::LayoutInvalidated(data->descendants); 500 return B_OK; 501 } 502 case PERFORM_CODE_DO_LAYOUT: 503 { 504 BBox::DoLayout(); 505 return B_OK; 506 } 507 } 508 509 return BView::Perform(code, _data); 510} 511 512 513BSize 514BBox::MinSize() 515{ 516 _ValidateLayoutData(); 517 518 BSize size = (GetLayout() ? GetLayout()->MinSize() : fLayoutData->min); 519 if (size.width < fLayoutData->min.width) 520 size.width = fLayoutData->min.width; 521 return BLayoutUtils::ComposeSize(ExplicitMinSize(), size); 522} 523 524 525BSize 526BBox::MaxSize() 527{ 528 _ValidateLayoutData(); 529 530 BSize size = (GetLayout() ? GetLayout()->MaxSize() : fLayoutData->max); 531 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size); 532} 533 534 535BSize 536BBox::PreferredSize() 537{ 538 _ValidateLayoutData(); 539 540 BSize size = (GetLayout() ? GetLayout()->PreferredSize() 541 : fLayoutData->preferred); 542 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size); 543} 544 545 546BAlignment 547BBox::LayoutAlignment() 548{ 549 _ValidateLayoutData(); 550 551 BAlignment alignment = (GetLayout() ? GetLayout()->Alignment() 552 : fLayoutData->alignment); 553 return BLayoutUtils::ComposeAlignment(ExplicitAlignment(), alignment); 554} 555 556 557void 558BBox::LayoutInvalidated(bool descendants) 559{ 560 fLayoutData->valid = false; 561} 562 563 564void 565BBox::DoLayout() 566{ 567 // Bail out, if we shan't do layout. 568 if (!(Flags() & B_SUPPORTS_LAYOUT)) 569 return; 570 571 BLayout* layout = GetLayout(); 572 573 // If the user set a layout, let the base class version call its 574 // hook. In case when we have BView as a label, remove it from child list 575 // so it won't be layouted with the rest of views and add it again 576 // after that. 577 if (layout != NULL) { 578 if (fLabelView) 579 RemoveChild(fLabelView); 580 581 BView::DoLayout(); 582 583 if (fLabelView != NULL) { 584 DisableLayoutInvalidation(); 585 // don't trigger a relayout 586 AddChild(fLabelView, ChildAt(0)); 587 EnableLayoutInvalidation(); 588 } 589 } 590 591 _ValidateLayoutData(); 592 593 // Even if the user set a layout, restore label view to it's 594 // desired position. 595 596 // layout the label view 597 if (fLabelView != NULL) { 598 fLabelView->MoveTo(fLayoutData->label_box.LeftTop()); 599 fLabelView->ResizeTo(fLayoutData->label_box.Size()); 600 } 601 602 // If we have layout return here and do not layout the child 603 if (layout != NULL) 604 return; 605 606 // layout the child 607 BView* child = _Child(); 608 if (child != NULL) { 609 BRect frame(Bounds()); 610 frame.left += fLayoutData->insets.left; 611 frame.top += fLayoutData->insets.top; 612 frame.right -= fLayoutData->insets.right; 613 frame.bottom -= fLayoutData->insets.bottom; 614 615 if ((child->Flags() & B_SUPPORTS_LAYOUT) != 0) 616 BLayoutUtils::AlignInFrame(child, frame); 617 else 618 child->MoveTo(frame.LeftTop()); 619 } 620} 621 622 623void BBox::_ReservedBox1() {} 624void BBox::_ReservedBox2() {} 625 626 627BBox & 628BBox::operator=(const BBox &) 629{ 630 return *this; 631} 632 633 634void 635BBox::_InitObject(BMessage* archive) 636{ 637 fBounds = Bounds().OffsetToCopy(0, 0); 638 639 fLabel = NULL; 640 fLabelView = NULL; 641 fLayoutData = new LayoutData; 642 643 int32 flags = 0; 644 645 BFont font(be_bold_font); 646 647 if (!archive || !archive->HasString("_fname")) 648 flags = B_FONT_FAMILY_AND_STYLE; 649 650 if (!archive || !archive->HasFloat("_fflt")) 651 flags |= B_FONT_SIZE; 652 653 if (flags != 0) 654 SetFont(&font, flags); 655 656 if (archive != NULL) { 657 const char* string; 658 if (archive->FindString("_label", &string) == B_OK) 659 SetLabel(string); 660 661 bool fancy; 662 int32 style; 663 664 if (archive->FindBool("_style", &fancy) == B_OK) 665 fStyle = fancy ? B_FANCY_BORDER : B_PLAIN_BORDER; 666 else if (archive->FindInt32("_style", &style) == B_OK) 667 fStyle = (border_style)style; 668 669 bool hasLabelView; 670 if (archive->FindBool("_lblview", &hasLabelView) == B_OK) 671 fLabelView = ChildAt(0); 672 } 673 674 AdoptSystemColors(); 675} 676 677 678void 679BBox::_DrawPlain(BRect labelBox) 680{ 681 BRect rect = Bounds(); 682 rect.top += TopBorderOffset(); 683 684 float lightTint; 685 float shadowTint; 686 lightTint = B_LIGHTEN_1_TINT; 687 shadowTint = B_DARKEN_1_TINT; 688 689 if (rect.Height() == 0.0 || rect.Width() == 0.0) { 690 // used as separator 691 rgb_color shadow = tint_color(ViewColor(), B_DARKEN_2_TINT); 692 693 SetHighColor(shadow); 694 StrokeLine(rect.LeftTop(),rect.RightBottom()); 695 } else { 696 // used as box 697 rgb_color light = tint_color(ViewColor(), lightTint); 698 rgb_color shadow = tint_color(ViewColor(), shadowTint); 699 700 BeginLineArray(4); 701 AddLine(BPoint(rect.left, rect.bottom), 702 BPoint(rect.left, rect.top), light); 703 AddLine(BPoint(rect.left + 1.0f, rect.top), 704 BPoint(rect.right, rect.top), light); 705 AddLine(BPoint(rect.left + 1.0f, rect.bottom), 706 BPoint(rect.right, rect.bottom), shadow); 707 AddLine(BPoint(rect.right, rect.bottom - 1.0f), 708 BPoint(rect.right, rect.top + 1.0f), shadow); 709 EndLineArray(); 710 } 711} 712 713 714void 715BBox::_DrawFancy(BRect labelBox) 716{ 717 BRect rect = Bounds(); 718 rect.top += TopBorderOffset(); 719 720 rgb_color base = ViewColor(); 721 if (rect.Height() == 1.0) { 722 // used as horizontal separator 723 be_control_look->DrawGroupFrame(this, rect, rect, base, 724 BControlLook::B_TOP_BORDER); 725 } else if (rect.Width() == 1.0) { 726 // used as vertical separator 727 be_control_look->DrawGroupFrame(this, rect, rect, base, 728 BControlLook::B_LEFT_BORDER); 729 } else { 730 // used as box 731 be_control_look->DrawGroupFrame(this, rect, rect, base); 732 } 733} 734 735 736void 737BBox::_ClearLabel() 738{ 739 if (fLabel) { 740 free(fLabel); 741 fLabel = NULL; 742 } else if (fLabelView) { 743 fLabelView->RemoveSelf(); 744 delete fLabelView; 745 fLabelView = NULL; 746 } 747} 748 749 750BView* 751BBox::_Child() const 752{ 753 for (int32 i = 0; BView* view = ChildAt(i); i++) { 754 if (view != fLabelView) 755 return view; 756 } 757 758 return NULL; 759} 760 761 762void 763BBox::_ValidateLayoutData() 764{ 765 if (fLayoutData->valid) 766 return; 767 768 // compute the label box, width and height 769 bool label = true; 770 float labelHeight = 0; // height of the label (pixel count) 771 if (fLabel) { 772 // leave 6 pixels of the frame, and have a gap of 4 pixels between 773 // the frame and the text on either side 774 font_height fontHeight; 775 GetFontHeight(&fontHeight); 776 fLayoutData->label_box.Set(6.0f, 0, 14.0f + StringWidth(fLabel), 777 ceilf(fontHeight.ascent)); 778 labelHeight = ceilf(fontHeight.ascent + fontHeight.descent) + 1; 779 } else if (fLabelView) { 780 // the label view is placed at (0, 10) at its preferred size 781 BSize size = fLabelView->PreferredSize(); 782 fLayoutData->label_box.Set(10, 0, 10 + size.width, size.height); 783 labelHeight = size.height + 1; 784 } else 785 label = false; 786 787 // border 788 switch (fStyle) { 789 case B_PLAIN_BORDER: 790 fLayoutData->insets.Set(1, 1, 1, 1); 791 break; 792 case B_FANCY_BORDER: 793 fLayoutData->insets.Set(3, 3, 3, 3); 794 break; 795 case B_NO_BORDER: 796 default: 797 fLayoutData->insets.Set(0, 0, 0, 0); 798 break; 799 } 800 801 // if there's a label, the top inset will be dictated by the label 802 if (label && labelHeight > fLayoutData->insets.top) 803 fLayoutData->insets.top = labelHeight; 804 805 // total number of pixel the border adds 806 float addWidth = fLayoutData->insets.left + fLayoutData->insets.right; 807 float addHeight = fLayoutData->insets.top + fLayoutData->insets.bottom; 808 809 // compute the minimal width induced by the label 810 float minWidth; 811 if (label) 812 minWidth = fLayoutData->label_box.right + fLayoutData->insets.right; 813 else 814 minWidth = addWidth - 1; 815 816 BAlignment alignment(B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER); 817 818 // finally consider the child constraints, if we shall support layout 819 BView* child = _Child(); 820 if (child && (child->Flags() & B_SUPPORTS_LAYOUT)) { 821 BSize min = child->MinSize(); 822 BSize max = child->MaxSize(); 823 BSize preferred = child->PreferredSize(); 824 825 min.width += addWidth; 826 min.height += addHeight; 827 preferred.width += addWidth; 828 preferred.height += addHeight; 829 max.width = BLayoutUtils::AddDistances(max.width, addWidth - 1); 830 max.height = BLayoutUtils::AddDistances(max.height, addHeight - 1); 831 832 if (min.width < minWidth) 833 min.width = minWidth; 834 BLayoutUtils::FixSizeConstraints(min, max, preferred); 835 836 fLayoutData->min = min; 837 fLayoutData->max = max; 838 fLayoutData->preferred = preferred; 839 840 BAlignment childAlignment = child->LayoutAlignment(); 841 if (childAlignment.horizontal == B_ALIGN_USE_FULL_WIDTH) 842 alignment.horizontal = B_ALIGN_USE_FULL_WIDTH; 843 if (childAlignment.vertical == B_ALIGN_USE_FULL_HEIGHT) 844 alignment.vertical = B_ALIGN_USE_FULL_HEIGHT; 845 846 fLayoutData->alignment = alignment; 847 } else { 848 fLayoutData->min.Set(minWidth, addHeight - 1); 849 fLayoutData->max.Set(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED); 850 fLayoutData->preferred = fLayoutData->min; 851 fLayoutData->alignment = alignment; 852 } 853 854 fLayoutData->valid = true; 855 ResetLayoutInvalidation(); 856} 857 858 859extern "C" void 860B_IF_GCC_2(InvalidateLayout__4BBoxb, _ZN4BBox16InvalidateLayoutEb)( 861 BBox* box, bool descendants) 862{ 863 perform_data_layout_invalidated data; 864 data.descendants = descendants; 865 866 box->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data); 867} 868 869