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