1 /* 2 * Copyright 2001-2017 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 29 struct 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 46 BBox::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 56 BBox::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 68 BBox::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 80 BBox::BBox(BMessage* archive) 81 : 82 BView(archive), 83 fStyle(B_FANCY_BORDER) 84 { 85 _InitObject(archive); 86 } 87 88 89 BBox::~BBox() 90 { 91 _ClearLabel(); 92 93 delete fLayoutData; 94 } 95 96 97 BArchivable* 98 BBox::Instantiate(BMessage* archive) 99 { 100 if (validate_instantiation(archive, "BBox")) 101 return new BBox(archive); 102 103 return NULL; 104 } 105 106 107 status_t 108 BBox::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 125 void 126 BBox::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 142 border_style 143 BBox::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 150 float 151 BBox::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 163 BRect 164 BBox::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 178 void 179 BBox::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 193 status_t 194 BBox::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 213 const char* 214 BBox::Label() const 215 { 216 return fLabel; 217 } 218 219 220 BView* 221 BBox::LabelView() const 222 { 223 return fLabelView; 224 } 225 226 227 void 228 BBox::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) { 258 ConstrainClippingRegion(NULL); 259 260 font_height fontHeight; 261 GetFontHeight(&fontHeight); 262 263 SetHighColor(ui_color(B_PANEL_TEXT_COLOR)); 264 DrawString(fLabel, BPoint(10.0f, ceilf(fontHeight.ascent))); 265 } 266 267 PopState(); 268 } 269 270 271 void 272 BBox::AttachedToWindow() 273 { 274 AdoptParentColors(); 275 276 // Force low color to match view color for proper label drawing. 277 float viewTint = B_NO_TINT; 278 float lowTint = B_NO_TINT; 279 280 if (LowUIColor(&lowTint) != ViewUIColor(&viewTint) || viewTint != lowTint) 281 SetLowUIColor(ViewUIColor(), viewTint); 282 else if (LowColor() != ViewColor()) 283 SetLowColor(ViewColor()); 284 285 if (ViewColor() == B_TRANSPARENT_COLOR) 286 AdoptSystemColors(); 287 288 // The box could have been resized in the mean time 289 fBounds = Bounds().OffsetToCopy(0, 0); 290 } 291 292 293 void 294 BBox::DetachedFromWindow() 295 { 296 BView::DetachedFromWindow(); 297 } 298 299 300 void 301 BBox::AllAttached() 302 { 303 BView::AllAttached(); 304 } 305 306 307 void 308 BBox::AllDetached() 309 { 310 BView::AllDetached(); 311 } 312 313 314 void 315 BBox::FrameResized(float width, float height) 316 { 317 BRect bounds(Bounds()); 318 319 // invalidate the regions that the app_server did not 320 // (for removing the previous or drawing the new border) 321 if (fStyle != B_NO_BORDER) { 322 // TODO: this must be made part of the be_control_look stuff! 323 int32 borderSize = fStyle == B_PLAIN_BORDER ? 0 : 2; 324 325 // Horizontal 326 BRect invalid(bounds); 327 if (fBounds.Width() < bounds.Width()) { 328 // enlarging 329 invalid.left = bounds.left + fBounds.right - borderSize; 330 invalid.right = bounds.left + fBounds.right; 331 332 Invalidate(invalid); 333 } else if (fBounds.Width() > bounds.Width()) { 334 // shrinking 335 invalid.left = bounds.left + bounds.right - borderSize; 336 337 Invalidate(invalid); 338 } 339 340 // Vertical 341 invalid = bounds; 342 if (fBounds.Height() < bounds.Height()) { 343 // enlarging 344 invalid.top = bounds.top + fBounds.bottom - borderSize; 345 invalid.bottom = bounds.top + fBounds.bottom; 346 347 Invalidate(invalid); 348 } else if (fBounds.Height() > bounds.Height()) { 349 // shrinking 350 invalid.top = bounds.top + bounds.bottom - borderSize; 351 352 Invalidate(invalid); 353 } 354 } 355 356 fBounds.right = width; 357 fBounds.bottom = height; 358 } 359 360 361 void 362 BBox::MessageReceived(BMessage* message) 363 { 364 BView::MessageReceived(message); 365 } 366 367 368 void 369 BBox::MouseDown(BPoint point) 370 { 371 BView::MouseDown(point); 372 } 373 374 375 void 376 BBox::MouseUp(BPoint point) 377 { 378 BView::MouseUp(point); 379 } 380 381 382 void 383 BBox::WindowActivated(bool active) 384 { 385 BView::WindowActivated(active); 386 } 387 388 389 void 390 BBox::MouseMoved(BPoint point, uint32 transit, const BMessage* message) 391 { 392 BView::MouseMoved(point, transit, message); 393 } 394 395 396 void 397 BBox::FrameMoved(BPoint newLocation) 398 { 399 BView::FrameMoved(newLocation); 400 } 401 402 403 BHandler* 404 BBox::ResolveSpecifier(BMessage* message, int32 index, BMessage* specifier, 405 int32 what, const char* property) 406 { 407 return BView::ResolveSpecifier(message, index, specifier, what, property); 408 } 409 410 411 void 412 BBox::ResizeToPreferred() 413 { 414 float width, height; 415 GetPreferredSize(&width, &height); 416 417 // make sure the box don't get smaller than it already is 418 if (width < Bounds().Width()) 419 width = Bounds().Width(); 420 if (height < Bounds().Height()) 421 height = Bounds().Height(); 422 423 BView::ResizeTo(width, height); 424 } 425 426 427 void 428 BBox::GetPreferredSize(float* _width, float* _height) 429 { 430 _ValidateLayoutData(); 431 432 if (_width) 433 *_width = fLayoutData->preferred.width; 434 if (_height) 435 *_height = fLayoutData->preferred.height; 436 } 437 438 439 void 440 BBox::MakeFocus(bool focused) 441 { 442 BView::MakeFocus(focused); 443 } 444 445 446 status_t 447 BBox::GetSupportedSuites(BMessage* message) 448 { 449 return BView::GetSupportedSuites(message); 450 } 451 452 453 status_t 454 BBox::Perform(perform_code code, void* _data) 455 { 456 switch (code) { 457 case PERFORM_CODE_MIN_SIZE: 458 ((perform_data_min_size*)_data)->return_value 459 = BBox::MinSize(); 460 return B_OK; 461 case PERFORM_CODE_MAX_SIZE: 462 ((perform_data_max_size*)_data)->return_value 463 = BBox::MaxSize(); 464 return B_OK; 465 case PERFORM_CODE_PREFERRED_SIZE: 466 ((perform_data_preferred_size*)_data)->return_value 467 = BBox::PreferredSize(); 468 return B_OK; 469 case PERFORM_CODE_LAYOUT_ALIGNMENT: 470 ((perform_data_layout_alignment*)_data)->return_value 471 = BBox::LayoutAlignment(); 472 return B_OK; 473 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 474 ((perform_data_has_height_for_width*)_data)->return_value 475 = BBox::HasHeightForWidth(); 476 return B_OK; 477 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 478 { 479 perform_data_get_height_for_width* data 480 = (perform_data_get_height_for_width*)_data; 481 BBox::GetHeightForWidth(data->width, &data->min, &data->max, 482 &data->preferred); 483 return B_OK; 484 } 485 case PERFORM_CODE_SET_LAYOUT: 486 { 487 perform_data_set_layout* data = (perform_data_set_layout*)_data; 488 BBox::SetLayout(data->layout); 489 return B_OK; 490 } 491 case PERFORM_CODE_LAYOUT_INVALIDATED: 492 { 493 perform_data_layout_invalidated* data 494 = (perform_data_layout_invalidated*)_data; 495 BBox::LayoutInvalidated(data->descendants); 496 return B_OK; 497 } 498 case PERFORM_CODE_DO_LAYOUT: 499 { 500 BBox::DoLayout(); 501 return B_OK; 502 } 503 } 504 505 return BView::Perform(code, _data); 506 } 507 508 509 BSize 510 BBox::MinSize() 511 { 512 _ValidateLayoutData(); 513 514 BSize size = (GetLayout() ? GetLayout()->MinSize() : fLayoutData->min); 515 if (size.width < fLayoutData->min.width) 516 size.width = fLayoutData->min.width; 517 return BLayoutUtils::ComposeSize(ExplicitMinSize(), size); 518 } 519 520 521 BSize 522 BBox::MaxSize() 523 { 524 _ValidateLayoutData(); 525 526 BSize size = (GetLayout() ? GetLayout()->MaxSize() : fLayoutData->max); 527 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), size); 528 } 529 530 531 BSize 532 BBox::PreferredSize() 533 { 534 _ValidateLayoutData(); 535 536 BSize size = (GetLayout() ? GetLayout()->PreferredSize() 537 : fLayoutData->preferred); 538 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), size); 539 } 540 541 542 BAlignment 543 BBox::LayoutAlignment() 544 { 545 _ValidateLayoutData(); 546 547 BAlignment alignment = (GetLayout() ? GetLayout()->Alignment() 548 : fLayoutData->alignment); 549 return BLayoutUtils::ComposeAlignment(ExplicitAlignment(), alignment); 550 } 551 552 553 void 554 BBox::LayoutInvalidated(bool descendants) 555 { 556 fLayoutData->valid = false; 557 } 558 559 560 void 561 BBox::DoLayout() 562 { 563 // Bail out, if we shan't do layout. 564 if (!(Flags() & B_SUPPORTS_LAYOUT)) 565 return; 566 567 BLayout* layout = GetLayout(); 568 569 // If the user set a layout, let the base class version call its 570 // hook. In case when we have BView as a label, remove it from child list 571 // so it won't be layouted with the rest of views and add it again 572 // after that. 573 if (layout != NULL) { 574 if (fLabelView) 575 RemoveChild(fLabelView); 576 577 BView::DoLayout(); 578 579 if (fLabelView != NULL) { 580 DisableLayoutInvalidation(); 581 // don't trigger a relayout 582 AddChild(fLabelView, ChildAt(0)); 583 EnableLayoutInvalidation(); 584 } 585 } 586 587 _ValidateLayoutData(); 588 589 // Even if the user set a layout, restore label view to it's 590 // desired position. 591 592 // layout the label view 593 if (fLabelView != NULL) { 594 fLabelView->MoveTo(fLayoutData->label_box.LeftTop()); 595 fLabelView->ResizeTo(fLayoutData->label_box.Size()); 596 } 597 598 // If we have layout return here and do not layout the child 599 if (layout != NULL) 600 return; 601 602 // layout the child 603 BView* child = _Child(); 604 if (child != NULL) { 605 BRect frame(Bounds()); 606 frame.left += fLayoutData->insets.left; 607 frame.top += fLayoutData->insets.top; 608 frame.right -= fLayoutData->insets.right; 609 frame.bottom -= fLayoutData->insets.bottom; 610 611 if ((child->Flags() & B_SUPPORTS_LAYOUT) != 0) 612 BLayoutUtils::AlignInFrame(child, frame); 613 else 614 child->MoveTo(frame.LeftTop()); 615 } 616 } 617 618 619 void BBox::_ReservedBox1() {} 620 void BBox::_ReservedBox2() {} 621 622 623 BBox & 624 BBox::operator=(const BBox &) 625 { 626 return *this; 627 } 628 629 630 void 631 BBox::_InitObject(BMessage* archive) 632 { 633 fBounds = Bounds().OffsetToCopy(0, 0); 634 635 fLabel = NULL; 636 fLabelView = NULL; 637 fLayoutData = new LayoutData; 638 639 int32 flags = 0; 640 641 BFont font(be_bold_font); 642 643 if (!archive || !archive->HasString("_fname")) 644 flags = B_FONT_FAMILY_AND_STYLE; 645 646 if (!archive || !archive->HasFloat("_fflt")) 647 flags |= B_FONT_SIZE; 648 649 if (flags != 0) 650 SetFont(&font, flags); 651 652 if (archive != NULL) { 653 const char* string; 654 if (archive->FindString("_label", &string) == B_OK) 655 SetLabel(string); 656 657 bool fancy; 658 int32 style; 659 660 if (archive->FindBool("_style", &fancy) == B_OK) 661 fStyle = fancy ? B_FANCY_BORDER : B_PLAIN_BORDER; 662 else if (archive->FindInt32("_style", &style) == B_OK) 663 fStyle = (border_style)style; 664 665 bool hasLabelView; 666 if (archive->FindBool("_lblview", &hasLabelView) == B_OK) 667 fLabelView = ChildAt(0); 668 } 669 670 AdoptSystemColors(); 671 } 672 673 674 void 675 BBox::_DrawPlain(BRect labelBox) 676 { 677 BRect rect = Bounds(); 678 rect.top += TopBorderOffset(); 679 680 float lightTint; 681 float shadowTint; 682 lightTint = B_LIGHTEN_1_TINT; 683 shadowTint = B_DARKEN_1_TINT; 684 685 if (rect.Height() == 0.0 || rect.Width() == 0.0) { 686 // used as separator 687 rgb_color shadow = tint_color(ViewColor(), B_DARKEN_2_TINT); 688 689 SetHighColor(shadow); 690 StrokeLine(rect.LeftTop(),rect.RightBottom()); 691 } else { 692 // used as box 693 rgb_color light = tint_color(ViewColor(), lightTint); 694 rgb_color shadow = tint_color(ViewColor(), shadowTint); 695 696 BeginLineArray(4); 697 AddLine(BPoint(rect.left, rect.bottom), 698 BPoint(rect.left, rect.top), light); 699 AddLine(BPoint(rect.left + 1.0f, rect.top), 700 BPoint(rect.right, rect.top), light); 701 AddLine(BPoint(rect.left + 1.0f, rect.bottom), 702 BPoint(rect.right, rect.bottom), shadow); 703 AddLine(BPoint(rect.right, rect.bottom - 1.0f), 704 BPoint(rect.right, rect.top + 1.0f), shadow); 705 EndLineArray(); 706 } 707 } 708 709 710 void 711 BBox::_DrawFancy(BRect labelBox) 712 { 713 BRect rect = Bounds(); 714 rect.top += TopBorderOffset(); 715 716 rgb_color base = ViewColor(); 717 if (rect.Height() == 1.0) { 718 // used as horizontal separator 719 be_control_look->DrawGroupFrame(this, rect, rect, base, 720 BControlLook::B_TOP_BORDER); 721 } else if (rect.Width() == 1.0) { 722 // used as vertical separator 723 be_control_look->DrawGroupFrame(this, rect, rect, base, 724 BControlLook::B_LEFT_BORDER); 725 } else { 726 // used as box 727 be_control_look->DrawGroupFrame(this, rect, rect, base); 728 } 729 } 730 731 732 void 733 BBox::_ClearLabel() 734 { 735 if (fLabel) { 736 free(fLabel); 737 fLabel = NULL; 738 } else if (fLabelView) { 739 fLabelView->RemoveSelf(); 740 delete fLabelView; 741 fLabelView = NULL; 742 } 743 } 744 745 746 BView* 747 BBox::_Child() const 748 { 749 for (int32 i = 0; BView* view = ChildAt(i); i++) { 750 if (view != fLabelView) 751 return view; 752 } 753 754 return NULL; 755 } 756 757 758 void 759 BBox::_ValidateLayoutData() 760 { 761 if (fLayoutData->valid) 762 return; 763 764 // compute the label box, width and height 765 bool label = true; 766 float labelHeight = 0; // height of the label (pixel count) 767 if (fLabel) { 768 // leave 6 pixels of the frame, and have a gap of 4 pixels between 769 // the frame and the text on either side 770 font_height fontHeight; 771 GetFontHeight(&fontHeight); 772 fLayoutData->label_box.Set(6.0f, 0, 14.0f + StringWidth(fLabel), 773 ceilf(fontHeight.ascent)); 774 labelHeight = ceilf(fontHeight.ascent + fontHeight.descent) + 1; 775 } else if (fLabelView) { 776 // the label view is placed at (0, 10) at its preferred size 777 BSize size = fLabelView->PreferredSize(); 778 fLayoutData->label_box.Set(10, 0, 10 + size.width, size.height); 779 labelHeight = size.height + 1; 780 } else 781 label = false; 782 783 // border 784 switch (fStyle) { 785 case B_PLAIN_BORDER: 786 fLayoutData->insets.Set(1, 1, 1, 1); 787 break; 788 case B_FANCY_BORDER: 789 fLayoutData->insets.Set(3, 3, 3, 3); 790 break; 791 case B_NO_BORDER: 792 default: 793 fLayoutData->insets.Set(0, 0, 0, 0); 794 break; 795 } 796 797 // if there's a label, the top inset will be dictated by the label 798 if (label && labelHeight > fLayoutData->insets.top) 799 fLayoutData->insets.top = labelHeight; 800 801 // total number of pixel the border adds 802 float addWidth = fLayoutData->insets.left + fLayoutData->insets.right; 803 float addHeight = fLayoutData->insets.top + fLayoutData->insets.bottom; 804 805 // compute the minimal width induced by the label 806 float minWidth; 807 if (label) 808 minWidth = fLayoutData->label_box.right + fLayoutData->insets.right; 809 else 810 minWidth = addWidth - 1; 811 812 BAlignment alignment(B_ALIGN_HORIZONTAL_CENTER, B_ALIGN_VERTICAL_CENTER); 813 814 // finally consider the child constraints, if we shall support layout 815 BView* child = _Child(); 816 if (child && (child->Flags() & B_SUPPORTS_LAYOUT)) { 817 BSize min = child->MinSize(); 818 BSize max = child->MaxSize(); 819 BSize preferred = child->PreferredSize(); 820 821 min.width += addWidth; 822 min.height += addHeight; 823 preferred.width += addWidth; 824 preferred.height += addHeight; 825 max.width = BLayoutUtils::AddDistances(max.width, addWidth - 1); 826 max.height = BLayoutUtils::AddDistances(max.height, addHeight - 1); 827 828 if (min.width < minWidth) 829 min.width = minWidth; 830 BLayoutUtils::FixSizeConstraints(min, max, preferred); 831 832 fLayoutData->min = min; 833 fLayoutData->max = max; 834 fLayoutData->preferred = preferred; 835 836 BAlignment childAlignment = child->LayoutAlignment(); 837 if (childAlignment.horizontal == B_ALIGN_USE_FULL_WIDTH) 838 alignment.horizontal = B_ALIGN_USE_FULL_WIDTH; 839 if (childAlignment.vertical == B_ALIGN_USE_FULL_HEIGHT) 840 alignment.vertical = B_ALIGN_USE_FULL_HEIGHT; 841 842 fLayoutData->alignment = alignment; 843 } else { 844 fLayoutData->min.Set(minWidth, addHeight - 1); 845 fLayoutData->max.Set(B_SIZE_UNLIMITED, B_SIZE_UNLIMITED); 846 fLayoutData->preferred = fLayoutData->min; 847 fLayoutData->alignment = alignment; 848 } 849 850 fLayoutData->valid = true; 851 ResetLayoutInvalidation(); 852 } 853 854 855 extern "C" void 856 B_IF_GCC_2(InvalidateLayout__4BBoxb, _ZN4BBox16InvalidateLayoutEb)( 857 BBox* box, bool descendants) 858 { 859 perform_data_layout_invalidated data; 860 data.descendants = descendants; 861 862 box->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data); 863 } 864 865