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