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