1 /* 2 * Copyright 2001-2008, Haiku Inc. 3 * Distributed under the terms of the MIT License. 4 * 5 * Authors: 6 * Frans van Nispen (xlr8@tref.nl) 7 * Stephan Aßmus <superstippi@gmx.de> 8 * Ingo Weinhold <bonefish@cs.tu-berlin.de> 9 */ 10 11 /*! BTextControl displays text that can act like a control. */ 12 13 14 #include <AbstractLayoutItem.h> 15 #include <LayoutUtils.h> 16 #include <Message.h> 17 #include <Region.h> 18 #include <TextControl.h> 19 #include <Window.h> 20 21 #include <binary_compatibility/Interface.h> 22 23 #include "TextInput.h" 24 25 26 //#define TRACE_TEXT_CONTROL 27 #ifdef TRACE_TEXT_CONTROL 28 # include <stdio.h> 29 # include <FunctionTracer.h> 30 static int32 sFunctionDepth = -1; 31 # define CALLED(x...) FunctionTracer _ft("BTextControl", __FUNCTION__, \ 32 sFunctionDepth) 33 # define TRACE(x...) { BString _to; \ 34 _to.Append(' ', (sFunctionDepth + 1) * 2); \ 35 printf("%s", _to.String()); printf(x); } 36 #else 37 # define CALLED(x...) 38 # define TRACE(x...) 39 #endif 40 41 42 class BTextControl::LabelLayoutItem : public BAbstractLayoutItem { 43 public: 44 LabelLayoutItem(BTextControl* parent); 45 46 virtual bool IsVisible(); 47 virtual void SetVisible(bool visible); 48 49 virtual BRect Frame(); 50 virtual void SetFrame(BRect frame); 51 52 virtual BView* View(); 53 54 virtual BSize BaseMinSize(); 55 virtual BSize BaseMaxSize(); 56 virtual BSize BasePreferredSize(); 57 virtual BAlignment BaseAlignment(); 58 59 private: 60 BTextControl* fParent; 61 BRect fFrame; 62 }; 63 64 65 class BTextControl::TextViewLayoutItem : public BAbstractLayoutItem { 66 public: 67 TextViewLayoutItem(BTextControl* parent); 68 69 virtual bool IsVisible(); 70 virtual void SetVisible(bool visible); 71 72 virtual BRect Frame(); 73 virtual void SetFrame(BRect frame); 74 75 virtual BView* View(); 76 77 virtual BSize BaseMinSize(); 78 virtual BSize BaseMaxSize(); 79 virtual BSize BasePreferredSize(); 80 virtual BAlignment BaseAlignment(); 81 82 private: 83 BTextControl* fParent; 84 BRect fFrame; 85 }; 86 87 88 struct BTextControl::LayoutData { 89 LayoutData(float width, float height) 90 : label_layout_item(NULL), 91 text_view_layout_item(NULL), 92 previous_width(width), 93 previous_height(height), 94 valid(false) 95 { 96 } 97 98 LabelLayoutItem* label_layout_item; 99 TextViewLayoutItem* text_view_layout_item; 100 float previous_width; // used in FrameResized() for 101 float previous_height; // invalidation 102 font_height font_info; 103 float label_width; 104 float label_height; 105 BSize min; 106 BSize text_view_min; 107 bool valid; 108 }; 109 110 111 // #pragma mark - 112 113 114 static const int32 kFrameMargin = 2; 115 static const int32 kLabelInputSpacing = 3; 116 117 118 BTextControl::BTextControl(BRect frame, const char* name, const char* label, 119 const char* text, BMessage* message, uint32 mask, uint32 flags) 120 : BControl(frame, name, label, message, mask, flags | B_FRAME_EVENTS) 121 { 122 _InitData(label, text); 123 _ValidateLayout(); 124 } 125 126 127 BTextControl::BTextControl(const char* name, const char* label, 128 const char* text, BMessage* message, uint32 flags) 129 : BControl(name, label, message, flags | B_FRAME_EVENTS) 130 { 131 _InitData(label, text); 132 _ValidateLayout(); 133 } 134 135 136 BTextControl::BTextControl(const char* label, const char* text, 137 BMessage* message) 138 : BControl(NULL, label, message, 139 B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS) 140 { 141 _InitData(label, text); 142 _ValidateLayout(); 143 } 144 145 146 BTextControl::~BTextControl() 147 { 148 SetModificationMessage(NULL); 149 delete fLayoutData; 150 } 151 152 153 BTextControl::BTextControl(BMessage* archive) 154 : BControl(archive) 155 { 156 _InitData(Label(), NULL, archive); 157 158 int32 labelAlignment = B_ALIGN_LEFT; 159 int32 textAlignment = B_ALIGN_LEFT; 160 161 if (archive->HasInt32("_a_label")) 162 archive->FindInt32("_a_label", &labelAlignment); 163 164 if (archive->HasInt32("_a_text")) 165 archive->FindInt32("_a_text", &textAlignment); 166 167 SetAlignment((alignment)labelAlignment, (alignment)textAlignment); 168 169 if (archive->HasFloat("_divide")) 170 archive->FindFloat("_divide", &fDivider); 171 172 if (archive->HasMessage("_mod_msg")) { 173 BMessage* message = new BMessage; 174 archive->FindMessage("_mod_msg", message); 175 SetModificationMessage(message); 176 } 177 } 178 179 180 BArchivable* 181 BTextControl::Instantiate(BMessage* archive) 182 { 183 if (validate_instantiation(archive, "BTextControl")) 184 return new BTextControl(archive); 185 186 return NULL; 187 } 188 189 190 status_t 191 BTextControl::Archive(BMessage *data, bool deep) const 192 { 193 status_t ret = BControl::Archive(data, deep); 194 alignment labelAlignment, textAlignment; 195 196 GetAlignment(&labelAlignment, &textAlignment); 197 198 if (ret == B_OK) 199 ret = data->AddInt32("_a_label", labelAlignment); 200 if (ret == B_OK) 201 ret = data->AddInt32("_a_text", textAlignment); 202 if (ret == B_OK) 203 ret = data->AddFloat("_divide", Divider()); 204 205 if (ModificationMessage() && (ret == B_OK)) 206 ret = data->AddMessage("_mod_msg", ModificationMessage()); 207 208 return ret; 209 } 210 211 212 void 213 BTextControl::SetText(const char *text) 214 { 215 if (InvokeKind() != B_CONTROL_INVOKED) 216 return; 217 218 CALLED(); 219 220 fText->SetText(text); 221 222 if (IsFocus()) 223 fText->SetInitialText(); 224 225 fText->Invalidate(); 226 } 227 228 229 const char * 230 BTextControl::Text() const 231 { 232 return fText->Text(); 233 } 234 235 236 void 237 BTextControl::SetValue(int32 value) 238 { 239 BControl::SetValue(value); 240 } 241 242 243 status_t 244 BTextControl::Invoke(BMessage *message) 245 { 246 return BControl::Invoke(message); 247 } 248 249 250 BTextView * 251 BTextControl::TextView() const 252 { 253 return fText; 254 } 255 256 257 void 258 BTextControl::SetModificationMessage(BMessage *message) 259 { 260 delete fModificationMessage; 261 fModificationMessage = message; 262 } 263 264 265 BMessage * 266 BTextControl::ModificationMessage() const 267 { 268 return fModificationMessage; 269 } 270 271 272 void 273 BTextControl::SetAlignment(alignment labelAlignment, alignment textAlignment) 274 { 275 fText->SetAlignment(textAlignment); 276 fText->AlignTextRect(); 277 278 if (fLabelAlign != labelAlignment) { 279 fLabelAlign = labelAlignment; 280 Invalidate(); 281 } 282 } 283 284 285 void 286 BTextControl::GetAlignment(alignment* _label, alignment* _text) const 287 { 288 if (_label) 289 *_label = fLabelAlign; 290 if (_text) 291 *_text = fText->Alignment(); 292 } 293 294 295 void 296 BTextControl::SetDivider(float dividingLine) 297 { 298 fDivider = floorf(dividingLine + 0.5); 299 300 _LayoutTextView(); 301 302 if (Window()) { 303 fText->Invalidate(); 304 Invalidate(); 305 } 306 } 307 308 309 float 310 BTextControl::Divider() const 311 { 312 return fDivider; 313 } 314 315 316 void 317 BTextControl::Draw(BRect updateRect) 318 { 319 rgb_color noTint = ui_color(B_PANEL_BACKGROUND_COLOR); 320 rgb_color lighten1 = tint_color(noTint, B_LIGHTEN_1_TINT); 321 rgb_color lighten2 = tint_color(noTint, B_LIGHTEN_2_TINT); 322 rgb_color lightenMax = tint_color(noTint, B_LIGHTEN_MAX_TINT); 323 rgb_color darken1 = tint_color(noTint, B_DARKEN_1_TINT); 324 rgb_color darken2 = tint_color(noTint, B_DARKEN_2_TINT); 325 rgb_color darken4 = tint_color(noTint, B_DARKEN_4_TINT); 326 rgb_color navigationColor = ui_color(B_KEYBOARD_NAVIGATION_COLOR); 327 328 bool enabled = IsEnabled(); 329 bool active = false; 330 331 if (fText->IsFocus() && Window()->IsActive()) 332 active = true; 333 334 // outer bevel 335 336 BRect rect = fText->Frame(); 337 rect.InsetBy(-2, -2); 338 339 if (enabled) 340 SetHighColor(darken1); 341 else 342 SetHighColor(noTint); 343 344 StrokeLine(rect.LeftBottom(), rect.LeftTop()); 345 StrokeLine(rect.RightTop()); 346 347 if (enabled) 348 SetHighColor(lighten2); 349 else 350 SetHighColor(lighten1); 351 352 StrokeLine(BPoint(rect.left + 1.0f, rect.bottom), rect.RightBottom()); 353 StrokeLine(BPoint(rect.right, rect.top + 1.0f), rect.RightBottom()); 354 355 // inner bevel 356 357 rect.InsetBy(1.0f, 1.0f); 358 359 if (active) { 360 SetHighColor(navigationColor); 361 StrokeRect(rect); 362 } else { 363 if (enabled) 364 SetHighColor(darken4); 365 else 366 SetHighColor(darken2); 367 368 StrokeLine(rect.LeftTop(), rect.LeftBottom()); 369 StrokeLine(rect.LeftTop(), rect.RightTop()); 370 371 SetHighColor(noTint); 372 StrokeLine(BPoint(rect.left + 1.0f, rect.bottom), rect.RightBottom()); 373 StrokeLine(BPoint(rect.right, rect.top + 1.0f)); 374 } 375 376 // label 377 378 if (Label()) { 379 _ValidateLayoutData(); 380 font_height& fontHeight = fLayoutData->font_info; 381 382 float y = Bounds().top + (Bounds().Height() + 1 - fontHeight.ascent 383 - fontHeight.descent) / 2 + fontHeight.ascent; 384 float x; 385 386 float labelWidth = StringWidth(Label()); 387 switch (fLabelAlign) { 388 case B_ALIGN_RIGHT: 389 x = fDivider - labelWidth - kLabelInputSpacing; 390 break; 391 392 case B_ALIGN_CENTER: 393 x = fDivider - labelWidth / 2.0; 394 break; 395 396 default: 397 x = 0.0; 398 break; 399 } 400 401 BRect labelArea(x, Bounds().top, x + labelWidth, Bounds().bottom); 402 if (x < fDivider && updateRect.Intersects(labelArea)) { 403 labelArea.right = fText->Frame().left - kLabelInputSpacing; 404 405 BRegion clipRegion(labelArea); 406 ConstrainClippingRegion(&clipRegion); 407 SetHighColor(IsEnabled() ? ui_color(B_CONTROL_TEXT_COLOR) 408 : tint_color(noTint, B_DISABLED_LABEL_TINT)); 409 DrawString(Label(), BPoint(x, y)); 410 } 411 } 412 } 413 414 415 void 416 BTextControl::MouseDown(BPoint where) 417 { 418 if (!fText->IsFocus()) { 419 fText->MakeFocus(true); 420 } 421 } 422 423 424 void 425 BTextControl::AttachedToWindow() 426 { 427 BControl::AttachedToWindow(); 428 429 _UpdateTextViewColors(IsEnabled()); 430 fText->MakeEditable(IsEnabled()); 431 } 432 433 434 void 435 BTextControl::MakeFocus(bool state) 436 { 437 if (state != fText->IsFocus()) { 438 fText->MakeFocus(state); 439 440 if (state) 441 fText->SelectAll(); 442 } 443 } 444 445 446 void 447 BTextControl::SetEnabled(bool enabled) 448 { 449 if (IsEnabled() == enabled) 450 return; 451 452 if (Window()) { 453 fText->MakeEditable(enabled); 454 455 _UpdateTextViewColors(enabled); 456 457 fText->Invalidate(); 458 Window()->UpdateIfNeeded(); 459 } 460 461 BControl::SetEnabled(enabled); 462 } 463 464 465 void 466 BTextControl::GetPreferredSize(float *_width, float *_height) 467 { 468 CALLED(); 469 470 _ValidateLayoutData(); 471 472 if (_width) { 473 float minWidth = fLayoutData->min.width; 474 if (Label() == NULL && !(Flags() & B_SUPPORTS_LAYOUT)) { 475 // Indeed, only if there is no label! BeOS backwards compatible 476 // behavior: 477 minWidth = max_c(minWidth, Bounds().Width()); 478 } 479 *_width = minWidth; 480 } 481 482 if (_height) 483 *_height = fLayoutData->min.height; 484 } 485 486 487 void 488 BTextControl::ResizeToPreferred() 489 { 490 BView::ResizeToPreferred(); 491 492 fDivider = 0.0; 493 const char* label = Label(); 494 if (label) 495 fDivider = ceil(StringWidth(label)) + 2.0; 496 497 _LayoutTextView(); 498 } 499 500 501 void 502 BTextControl::SetFlags(uint32 flags) 503 { 504 // If the textview is navigable, set it to not navigable if needed 505 // Else if it is not navigable, set it to navigable if needed 506 if (fText->Flags() & B_NAVIGABLE) { 507 if (!(flags & B_NAVIGABLE)) 508 fText->SetFlags(fText->Flags() & ~B_NAVIGABLE); 509 510 } else { 511 if (flags & B_NAVIGABLE) 512 fText->SetFlags(fText->Flags() | B_NAVIGABLE); 513 } 514 515 BView::SetFlags(flags); 516 } 517 518 519 void 520 BTextControl::MessageReceived(BMessage *msg) 521 { 522 switch(msg->what) { 523 case B_SET_PROPERTY: 524 case B_GET_PROPERTY: 525 // TODO 526 break; 527 default: 528 BControl::MessageReceived(msg); 529 break; 530 } 531 } 532 533 534 BHandler * 535 BTextControl::ResolveSpecifier(BMessage *msg, int32 index, 536 BMessage *specifier, int32 form, 537 const char *property) 538 { 539 /* 540 BPropertyInfo propInfo(prop_list); 541 BHandler *target = NULL; 542 543 if (propInfo.FindMatch(message, 0, specifier, what, property) < B_OK) 544 return BControl::ResolveSpecifier(message, index, specifier, what, 545 property); 546 else 547 return this; 548 */ 549 return BControl::ResolveSpecifier(msg, index, specifier, form, property); 550 } 551 552 553 status_t 554 BTextControl::GetSupportedSuites(BMessage *data) 555 { 556 return BControl::GetSupportedSuites(data); 557 } 558 559 560 void 561 BTextControl::MouseUp(BPoint pt) 562 { 563 BControl::MouseUp(pt); 564 } 565 566 567 void 568 BTextControl::MouseMoved(BPoint pt, uint32 code, const BMessage *msg) 569 { 570 BControl::MouseMoved(pt, code, msg); 571 } 572 573 574 void 575 BTextControl::DetachedFromWindow() 576 { 577 BControl::DetachedFromWindow(); 578 } 579 580 581 void 582 BTextControl::AllAttached() 583 { 584 BControl::AllAttached(); 585 } 586 587 588 void 589 BTextControl::AllDetached() 590 { 591 BControl::AllDetached(); 592 } 593 594 595 void 596 BTextControl::FrameMoved(BPoint newPosition) 597 { 598 BControl::FrameMoved(newPosition); 599 } 600 601 602 void 603 BTextControl::FrameResized(float width, float height) 604 { 605 CALLED(); 606 607 BControl::FrameResized(width, height); 608 609 // TODO: this causes flickering still... 610 611 // changes in width 612 613 BRect bounds = Bounds(); 614 615 if (bounds.Width() > fLayoutData->previous_width) { 616 // invalidate the region between the old and the new right border 617 BRect rect = bounds; 618 rect.left += fLayoutData->previous_width - kFrameMargin; 619 rect.right--; 620 Invalidate(rect); 621 } else if (bounds.Width() < fLayoutData->previous_width) { 622 // invalidate the region of the new right border 623 BRect rect = bounds; 624 rect.left = rect.right - kFrameMargin; 625 Invalidate(rect); 626 } 627 628 // changes in height 629 630 if (bounds.Height() > fLayoutData->previous_height) { 631 // invalidate the region between the old and the new bottom border 632 BRect rect = bounds; 633 rect.top += fLayoutData->previous_height - kFrameMargin; 634 rect.bottom--; 635 Invalidate(rect); 636 // invalidate label area 637 rect = bounds; 638 rect.right = fDivider; 639 Invalidate(rect); 640 } else if (bounds.Height() < fLayoutData->previous_height) { 641 // invalidate the region of the new bottom border 642 BRect rect = bounds; 643 rect.top = rect.bottom - kFrameMargin; 644 Invalidate(rect); 645 // invalidate label area 646 rect = bounds; 647 rect.right = fDivider; 648 Invalidate(rect); 649 } 650 651 fLayoutData->previous_width = bounds.Width(); 652 fLayoutData->previous_height = bounds.Height(); 653 654 TRACE("width: %.2f, height: %.2f\n", bounds.Width(), bounds.Height()); 655 } 656 657 658 void 659 BTextControl::WindowActivated(bool active) 660 { 661 if (fText->IsFocus()) { 662 // invalidate to remove/show focus indication 663 BRect rect = fText->Frame(); 664 rect.InsetBy(-1, -1); 665 Invalidate(rect); 666 667 // help out embedded text view which doesn't 668 // get notified of this 669 fText->Invalidate(); 670 } 671 } 672 673 674 BSize 675 BTextControl::MinSize() 676 { 677 CALLED(); 678 679 _ValidateLayoutData(); 680 return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min); 681 } 682 683 684 BSize 685 BTextControl::MaxSize() 686 { 687 CALLED(); 688 689 _ValidateLayoutData(); 690 691 BSize max = fLayoutData->min; 692 max.width = B_SIZE_UNLIMITED; 693 694 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), max); 695 } 696 697 698 BSize 699 BTextControl::PreferredSize() 700 { 701 CALLED(); 702 703 _ValidateLayoutData(); 704 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), fLayoutData->min); 705 } 706 707 708 void 709 BTextControl::InvalidateLayout(bool descendants) 710 { 711 CALLED(); 712 713 fLayoutData->valid = false; 714 715 BView::InvalidateLayout(descendants); 716 } 717 718 719 BLayoutItem* 720 BTextControl::CreateLabelLayoutItem() 721 { 722 if (!fLayoutData->label_layout_item) 723 fLayoutData->label_layout_item = new LabelLayoutItem(this); 724 return fLayoutData->label_layout_item; 725 } 726 727 728 BLayoutItem* 729 BTextControl::CreateTextViewLayoutItem() 730 { 731 if (!fLayoutData->text_view_layout_item) 732 fLayoutData->text_view_layout_item = new TextViewLayoutItem(this); 733 return fLayoutData->text_view_layout_item; 734 } 735 736 737 void 738 BTextControl::DoLayout() 739 { 740 // Bail out, if we shan't do layout. 741 if (!(Flags() & B_SUPPORTS_LAYOUT)) 742 return; 743 744 CALLED(); 745 746 // If the user set a layout, we let the base class version call its 747 // hook. 748 if (GetLayout()) { 749 BView::DoLayout(); 750 return; 751 } 752 753 _ValidateLayoutData(); 754 755 // validate current size 756 BSize size(Bounds().Size()); 757 if (size.width < fLayoutData->min.width) 758 size.width = fLayoutData->min.width; 759 if (size.height < fLayoutData->min.height) 760 size.height = fLayoutData->min.height; 761 762 // divider 763 float divider = 0; 764 if (fLayoutData->label_layout_item && fLayoutData->text_view_layout_item) { 765 // We have layout items. They define the divider location. 766 divider = fLayoutData->text_view_layout_item->Frame().left 767 - fLayoutData->label_layout_item->Frame().left; 768 } else { 769 if (fLayoutData->label_width > 0) 770 divider = fLayoutData->label_width + 5; 771 } 772 773 // text view 774 BRect dirty(fText->Frame()); 775 BRect textFrame(divider + 1, kFrameMargin, size.width - 2, 776 size.height - kFrameMargin); 777 778 // place the text view and set the divider 779 BLayoutUtils::AlignInFrame(fText, textFrame); 780 781 fDivider = divider; 782 783 // invalidate dirty region 784 dirty = dirty | fText->Frame(); 785 dirty.InsetBy(-kFrameMargin, -kFrameMargin); 786 787 Invalidate(dirty); 788 } 789 790 791 // #pragma mark - 792 793 794 status_t 795 BTextControl::Perform(perform_code code, void* _data) 796 { 797 switch (code) { 798 case PERFORM_CODE_MIN_SIZE: 799 ((perform_data_min_size*)_data)->return_value 800 = BTextControl::MinSize(); 801 return B_OK; 802 case PERFORM_CODE_MAX_SIZE: 803 ((perform_data_max_size*)_data)->return_value 804 = BTextControl::MaxSize(); 805 return B_OK; 806 case PERFORM_CODE_PREFERRED_SIZE: 807 ((perform_data_preferred_size*)_data)->return_value 808 = BTextControl::PreferredSize(); 809 return B_OK; 810 case PERFORM_CODE_LAYOUT_ALIGNMENT: 811 ((perform_data_layout_alignment*)_data)->return_value 812 = BTextControl::LayoutAlignment(); 813 return B_OK; 814 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 815 ((perform_data_has_height_for_width*)_data)->return_value 816 = BTextControl::HasHeightForWidth(); 817 return B_OK; 818 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 819 { 820 perform_data_get_height_for_width* data 821 = (perform_data_get_height_for_width*)_data; 822 BTextControl::GetHeightForWidth(data->width, &data->min, &data->max, 823 &data->preferred); 824 return B_OK; 825 } 826 case PERFORM_CODE_SET_LAYOUT: 827 { 828 perform_data_set_layout* data = (perform_data_set_layout*)_data; 829 BTextControl::SetLayout(data->layout); 830 return B_OK; 831 } 832 case PERFORM_CODE_INVALIDATE_LAYOUT: 833 { 834 perform_data_invalidate_layout* data 835 = (perform_data_invalidate_layout*)_data; 836 BTextControl::InvalidateLayout(data->descendants); 837 return B_OK; 838 } 839 case PERFORM_CODE_DO_LAYOUT: 840 { 841 BTextControl::DoLayout(); 842 return B_OK; 843 } 844 } 845 846 return BControl::Perform(code, _data); 847 } 848 849 850 void BTextControl::_ReservedTextControl1() {} 851 void BTextControl::_ReservedTextControl2() {} 852 void BTextControl::_ReservedTextControl3() {} 853 void BTextControl::_ReservedTextControl4() {} 854 855 856 BTextControl & 857 BTextControl::operator=(const BTextControl&) 858 { 859 return *this; 860 } 861 862 863 void 864 BTextControl::_UpdateTextViewColors(bool enabled) 865 { 866 rgb_color textColor; 867 rgb_color color; 868 BFont font; 869 870 fText->GetFontAndColor(0, &font); 871 872 if (enabled) 873 textColor = ui_color(B_DOCUMENT_TEXT_COLOR); 874 else { 875 textColor = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 876 B_DISABLED_LABEL_TINT); 877 } 878 879 fText->SetFontAndColor(&font, B_FONT_ALL, &textColor); 880 881 if (enabled) { 882 color = ui_color(B_DOCUMENT_BACKGROUND_COLOR); 883 } else { 884 color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 885 B_LIGHTEN_2_TINT); 886 } 887 888 fText->SetViewColor(color); 889 fText->SetLowColor(color); 890 } 891 892 893 void 894 BTextControl::_CommitValue() 895 { 896 } 897 898 899 void 900 BTextControl::_InitData(const char* label, const char* initialText, 901 BMessage* archive) 902 { 903 BRect bounds(Bounds()); 904 905 fText = NULL; 906 fModificationMessage = NULL; 907 fLabelAlign = B_ALIGN_LEFT; 908 fDivider = 0.0f; 909 fLayoutData = new LayoutData(bounds.Width(), bounds.Height()); 910 911 int32 flags = 0; 912 913 BFont font(be_plain_font); 914 915 if (!archive || !archive->HasString("_fname")) 916 flags |= B_FONT_FAMILY_AND_STYLE; 917 918 if (!archive || !archive->HasFloat("_fflt")) 919 flags |= B_FONT_SIZE; 920 921 if (flags != 0) 922 SetFont(&font, flags); 923 924 if (label) 925 fDivider = floorf(bounds.Width() / 2.0f); 926 927 uint32 navigableFlags = Flags() & B_NAVIGABLE; 928 929 if (archive) 930 fText = static_cast<BPrivate::_BTextInput_*>(FindView("_input_")); 931 932 if (fText == NULL) { 933 BRect frame(fDivider, bounds.top, bounds.right, bounds.bottom); 934 // we are stroking the frame around the text view, which 935 // is 2 pixels wide 936 frame.InsetBy(kFrameMargin, kFrameMargin); 937 BRect textRect(frame.OffsetToCopy(B_ORIGIN)); 938 939 fText = new BPrivate::_BTextInput_(frame, textRect, 940 B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS | navigableFlags); 941 AddChild(fText); 942 943 SetText(initialText); 944 fText->SetAlignment(B_ALIGN_LEFT); 945 fText->AlignTextRect(); 946 } 947 } 948 949 950 void 951 BTextControl::_ValidateLayout() 952 { 953 CALLED(); 954 955 _ValidateLayoutData(); 956 957 ResizeTo(Bounds().Width(), fLayoutData->min.height); 958 959 _LayoutTextView(); 960 } 961 962 963 void 964 BTextControl::_LayoutTextView() 965 { 966 CALLED(); 967 968 BRect frame = Bounds(); 969 frame.left = fDivider; 970 // we are stroking the frame around the text view, which 971 // is 2 pixels wide 972 frame.InsetBy(2.0, 2.0); 973 fText->MoveTo(frame.left, frame.top); 974 fText->ResizeTo(frame.Width(), frame.Height()); 975 fText->AlignTextRect(); 976 977 TRACE("width: %.2f, height: %.2f\n", Frame().Width(), Frame().Height()); 978 TRACE("fDivider: %.2f\n", fDivider); 979 TRACE("fText frame: (%.2f, %.2f, %.2f, %.2f)\n", 980 frame.left, frame.top, frame.right, frame.bottom); 981 } 982 983 984 void 985 BTextControl::_UpdateFrame() 986 { 987 CALLED(); 988 989 if (fLayoutData->label_layout_item && fLayoutData->text_view_layout_item) { 990 BRect labelFrame = fLayoutData->label_layout_item->Frame(); 991 BRect textFrame = fLayoutData->text_view_layout_item->Frame(); 992 993 // update divider 994 fDivider = textFrame.left - labelFrame.left; 995 996 MoveTo(labelFrame.left, labelFrame.top); 997 BSize oldSize = Bounds().Size(); 998 ResizeTo(textFrame.left + textFrame.Width() - labelFrame.left, 999 textFrame.top + textFrame.Height() - labelFrame.top); 1000 BSize newSize = Bounds().Size(); 1001 1002 // If the size changes, ResizeTo() will trigger a relayout, otherwise 1003 // we need to do that explicitly. 1004 if (newSize != oldSize) 1005 Relayout(); 1006 } 1007 } 1008 1009 1010 void 1011 BTextControl::_ValidateLayoutData() 1012 { 1013 CALLED(); 1014 1015 if (fLayoutData->valid) 1016 return; 1017 1018 // cache font height 1019 font_height& fh = fLayoutData->font_info; 1020 GetFontHeight(&fh); 1021 1022 if (Label() != NULL) { 1023 fLayoutData->label_width = ceilf(StringWidth(Label())); 1024 fLayoutData->label_height = ceilf(fh.ascent) + ceilf(fh.descent); 1025 } else { 1026 fLayoutData->label_width = 0; 1027 fLayoutData->label_height = 0; 1028 } 1029 1030 // compute the minimal divider 1031 float divider = 0; 1032 if (fLayoutData->label_width > 0) 1033 divider = fLayoutData->label_width + 5; 1034 1035 // If we shan't do real layout, we let the current divider take influence. 1036 if (!(Flags() & B_SUPPORTS_LAYOUT)) 1037 divider = max_c(divider, fDivider); 1038 1039 // get the minimal (== preferred) text view size 1040 fLayoutData->text_view_min = fText->MinSize(); 1041 1042 TRACE("text view min width: %.2f\n", fLayoutData->text_view_min.width); 1043 1044 // compute our minimal (== preferred) size 1045 BSize min(fLayoutData->text_view_min); 1046 min.width += 2 * kFrameMargin; 1047 min.height += 2 * kFrameMargin; 1048 1049 if (divider > 0) 1050 min.width += divider; 1051 if (fLayoutData->label_height > min.height) 1052 min.height = fLayoutData->label_height; 1053 1054 fLayoutData->min = min; 1055 1056 fLayoutData->valid = true; 1057 1058 TRACE("width: %.2f, height: %.2f\n", min.width, min.height); 1059 } 1060 1061 1062 // #pragma mark - 1063 1064 1065 BTextControl::LabelLayoutItem::LabelLayoutItem(BTextControl* parent) 1066 : fParent(parent), 1067 fFrame() 1068 { 1069 } 1070 1071 1072 bool 1073 BTextControl::LabelLayoutItem::IsVisible() 1074 { 1075 return !fParent->IsHidden(fParent); 1076 } 1077 1078 1079 void 1080 BTextControl::LabelLayoutItem::SetVisible(bool visible) 1081 { 1082 // not allowed 1083 } 1084 1085 1086 BRect 1087 BTextControl::LabelLayoutItem::Frame() 1088 { 1089 return fFrame; 1090 } 1091 1092 1093 void 1094 BTextControl::LabelLayoutItem::SetFrame(BRect frame) 1095 { 1096 fFrame = frame; 1097 fParent->_UpdateFrame(); 1098 } 1099 1100 1101 BView* 1102 BTextControl::LabelLayoutItem::View() 1103 { 1104 return fParent; 1105 } 1106 1107 1108 BSize 1109 BTextControl::LabelLayoutItem::BaseMinSize() 1110 { 1111 fParent->_ValidateLayoutData(); 1112 1113 if (!fParent->Label()) 1114 return BSize(-1, -1); 1115 1116 return BSize(fParent->fLayoutData->label_width + 5, 1117 fParent->fLayoutData->label_height); 1118 } 1119 1120 1121 BSize 1122 BTextControl::LabelLayoutItem::BaseMaxSize() 1123 { 1124 return BaseMinSize(); 1125 } 1126 1127 1128 BSize 1129 BTextControl::LabelLayoutItem::BasePreferredSize() 1130 { 1131 return BaseMinSize(); 1132 } 1133 1134 1135 BAlignment 1136 BTextControl::LabelLayoutItem::BaseAlignment() 1137 { 1138 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); 1139 } 1140 1141 1142 // #pragma mark - 1143 1144 1145 BTextControl::TextViewLayoutItem::TextViewLayoutItem(BTextControl* parent) 1146 : fParent(parent), 1147 fFrame() 1148 { 1149 // by default the part right of the divider shall have an unlimited maximum 1150 // width 1151 SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 1152 } 1153 1154 1155 bool 1156 BTextControl::TextViewLayoutItem::IsVisible() 1157 { 1158 return !fParent->IsHidden(fParent); 1159 } 1160 1161 1162 void 1163 BTextControl::TextViewLayoutItem::SetVisible(bool visible) 1164 { 1165 // not allowed 1166 } 1167 1168 1169 BRect 1170 BTextControl::TextViewLayoutItem::Frame() 1171 { 1172 return fFrame; 1173 } 1174 1175 1176 void 1177 BTextControl::TextViewLayoutItem::SetFrame(BRect frame) 1178 { 1179 fFrame = frame; 1180 fParent->_UpdateFrame(); 1181 } 1182 1183 1184 BView* 1185 BTextControl::TextViewLayoutItem::View() 1186 { 1187 return fParent; 1188 } 1189 1190 1191 BSize 1192 BTextControl::TextViewLayoutItem::BaseMinSize() 1193 { 1194 fParent->_ValidateLayoutData(); 1195 1196 BSize size = fParent->fLayoutData->text_view_min; 1197 size.width += 2 * kFrameMargin; 1198 size.height += 2 * kFrameMargin; 1199 1200 return size; 1201 } 1202 1203 1204 BSize 1205 BTextControl::TextViewLayoutItem::BaseMaxSize() 1206 { 1207 BSize size(BaseMinSize()); 1208 size.width = B_SIZE_UNLIMITED; 1209 return size; 1210 } 1211 1212 1213 BSize 1214 BTextControl::TextViewLayoutItem::BasePreferredSize() 1215 { 1216 BSize size(BaseMinSize()); 1217 // puh, no idea... 1218 size.width = 100; 1219 return size; 1220 } 1221 1222 1223 BAlignment 1224 BTextControl::TextViewLayoutItem::BaseAlignment() 1225 { 1226 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); 1227 } 1228 1229