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