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