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