1 /* 2 * Copyright 2001-2020 Haiku Inc. All rights reserved. 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 * John Scipione <jscipione@gmail.com> 10 */ 11 12 13 /*! BTextControl displays text that can act like a control. */ 14 15 16 #include <TextControl.h> 17 18 #include <string.h> 19 20 #include <AbstractLayoutItem.h> 21 #include <ControlLook.h> 22 #include <LayoutUtils.h> 23 #include <Message.h> 24 #include <PropertyInfo.h> 25 #include <Region.h> 26 #include <Window.h> 27 28 #include <binary_compatibility/Interface.h> 29 #include <binary_compatibility/Support.h> 30 31 #include "TextInput.h" 32 33 34 //#define TRACE_TEXT_CONTROL 35 #ifdef TRACE_TEXT_CONTROL 36 # include <stdio.h> 37 # include <FunctionTracer.h> 38 static int32 sFunctionDepth = -1; 39 # define CALLED(x...) FunctionTracer _ft("BTextControl", __FUNCTION__, \ 40 sFunctionDepth) 41 # define TRACE(x...) { BString _to; \ 42 _to.Append(' ', (sFunctionDepth + 1) * 2); \ 43 printf("%s", _to.String()); printf(x); } 44 #else 45 # define CALLED(x...) 46 # define TRACE(x...) 47 #endif 48 49 50 namespace { 51 const char* const kFrameField = "BTextControl:layoutitem:frame"; 52 const char* const kTextViewItemField = "BTextControl:textViewItem"; 53 const char* const kLabelItemField = "BMenuField:labelItem"; 54 } 55 56 57 static property_info sPropertyList[] = { 58 { 59 "Value", 60 { B_GET_PROPERTY, B_SET_PROPERTY }, 61 { B_DIRECT_SPECIFIER }, 62 NULL, 0, 63 { B_STRING_TYPE } 64 }, 65 66 { 0 } 67 }; 68 69 70 class BTextControl::LabelLayoutItem : public BAbstractLayoutItem { 71 public: 72 LabelLayoutItem(BTextControl* parent); 73 LabelLayoutItem(BMessage* from); 74 75 virtual bool IsVisible(); 76 virtual void SetVisible(bool visible); 77 78 virtual BRect Frame(); 79 virtual void SetFrame(BRect frame); 80 81 void SetParent(BTextControl* parent); 82 virtual BView* View(); 83 84 virtual BSize BaseMinSize(); 85 virtual BSize BaseMaxSize(); 86 virtual BSize BasePreferredSize(); 87 virtual BAlignment BaseAlignment(); 88 89 BRect FrameInParent() const; 90 91 virtual status_t Archive(BMessage* into, bool deep = true) const; 92 static BArchivable* Instantiate(BMessage* from); 93 94 private: 95 BTextControl* fParent; 96 BRect fFrame; 97 }; 98 99 100 class BTextControl::TextViewLayoutItem : public BAbstractLayoutItem { 101 public: 102 TextViewLayoutItem(BTextControl* parent); 103 TextViewLayoutItem(BMessage* from); 104 105 virtual bool IsVisible(); 106 virtual void SetVisible(bool visible); 107 108 virtual BRect Frame(); 109 virtual void SetFrame(BRect frame); 110 111 void SetParent(BTextControl* parent); 112 virtual BView* View(); 113 114 virtual BSize BaseMinSize(); 115 virtual BSize BaseMaxSize(); 116 virtual BSize BasePreferredSize(); 117 virtual BAlignment BaseAlignment(); 118 119 BRect FrameInParent() const; 120 121 virtual status_t Archive(BMessage* into, bool deep = true) const; 122 static BArchivable* Instantiate(BMessage* from); 123 private: 124 BTextControl* fParent; 125 BRect fFrame; 126 }; 127 128 129 struct BTextControl::LayoutData { 130 LayoutData(float width, float height) 131 : 132 label_layout_item(NULL), 133 text_view_layout_item(NULL), 134 previous_width(width), 135 previous_height(height), 136 valid(false) 137 { 138 } 139 140 LabelLayoutItem* label_layout_item; 141 TextViewLayoutItem* text_view_layout_item; 142 float previous_width; // used in FrameResized() for 143 float previous_height; // invalidation 144 font_height font_info; 145 float label_width; 146 float label_height; 147 BSize min; 148 BSize text_view_min; 149 bool valid; 150 }; 151 152 153 static const int32 kFrameMargin = 2; 154 static const int32 kLabelInputSpacing = 3; 155 156 157 // #pragma mark - BTextControl 158 159 160 BTextControl::BTextControl(BRect frame, const char* name, const char* label, 161 const char* text, BMessage* message, uint32 resizeMask, uint32 flags) 162 : 163 BControl(frame, name, label, message, resizeMask, flags | B_FRAME_EVENTS) 164 { 165 _InitData(label); 166 _InitText(text); 167 _ValidateLayout(); 168 } 169 170 171 BTextControl::BTextControl(const char* name, const char* label, 172 const char* text, BMessage* message, uint32 flags) 173 : 174 BControl(name, label, message, flags | B_FRAME_EVENTS) 175 { 176 _InitData(label); 177 _InitText(text); 178 _ValidateLayout(); 179 } 180 181 182 BTextControl::BTextControl(const char* label, const char* text, 183 BMessage* message) 184 : 185 BControl(NULL, label, message, 186 B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS) 187 { 188 _InitData(label); 189 _InitText(text); 190 _ValidateLayout(); 191 } 192 193 194 BTextControl::~BTextControl() 195 { 196 SetModificationMessage(NULL); 197 delete fLayoutData; 198 } 199 200 201 // #pragma mark - Archiving 202 203 204 BTextControl::BTextControl(BMessage* archive) 205 : 206 BControl(BUnarchiver::PrepareArchive(archive)) 207 { 208 BUnarchiver unarchiver(archive); 209 210 _InitData(Label(), archive); 211 212 if (!BUnarchiver::IsArchiveManaged(archive)) 213 _InitText(NULL, archive); 214 215 status_t err = B_OK; 216 if (archive->HasFloat("_divide")) 217 err = archive->FindFloat("_divide", &fDivider); 218 219 if (err == B_OK && archive->HasMessage("_mod_msg")) { 220 BMessage* message = new BMessage; 221 err = archive->FindMessage("_mod_msg", message); 222 SetModificationMessage(message); 223 } 224 225 unarchiver.Finish(err); 226 } 227 228 229 BArchivable* 230 BTextControl::Instantiate(BMessage* archive) 231 { 232 if (validate_instantiation(archive, "BTextControl")) 233 return new BTextControl(archive); 234 235 return NULL; 236 } 237 238 239 status_t 240 BTextControl::Archive(BMessage* data, bool deep) const 241 { 242 BArchiver archiver(data); 243 status_t result = BControl::Archive(data, deep); 244 245 alignment labelAlignment; 246 alignment textAlignment; 247 if (result == B_OK) 248 GetAlignment(&labelAlignment, &textAlignment); 249 250 if (result == B_OK) 251 result = data->AddInt32("_a_label", labelAlignment); 252 253 if (result == B_OK) 254 result = data->AddInt32("_a_text", textAlignment); 255 256 if (result == B_OK) 257 result = data->AddFloat("_divide", Divider()); 258 259 if (result == B_OK && ModificationMessage() != NULL) 260 result = data->AddMessage("_mod_msg", ModificationMessage()); 261 262 return archiver.Finish(result); 263 } 264 265 266 status_t 267 BTextControl::AllArchived(BMessage* into) const 268 { 269 BArchiver archiver(into); 270 status_t err = B_OK; 271 272 if (archiver.IsArchived(fLayoutData->text_view_layout_item)) { 273 err = archiver.AddArchivable(kTextViewItemField, 274 fLayoutData->text_view_layout_item); 275 } 276 277 if (err == B_OK && archiver.IsArchived(fLayoutData->label_layout_item)) { 278 err = archiver.AddArchivable(kLabelItemField, 279 fLayoutData->label_layout_item); 280 } 281 282 return err; 283 } 284 285 286 status_t 287 BTextControl::AllUnarchived(const BMessage* from) 288 { 289 status_t err; 290 if ((err = BControl::AllUnarchived(from)) != B_OK) 291 return err; 292 293 _InitText(NULL, from); 294 295 BUnarchiver unarchiver(from); 296 if (unarchiver.IsInstantiated(kTextViewItemField)) { 297 err = unarchiver.FindObject(kTextViewItemField, 298 BUnarchiver::B_DONT_ASSUME_OWNERSHIP, 299 fLayoutData->text_view_layout_item); 300 301 if (err == B_OK) 302 fLayoutData->text_view_layout_item->SetParent(this); 303 else 304 return err; 305 } 306 307 if (unarchiver.IsInstantiated(kLabelItemField)) { 308 err = unarchiver.FindObject(kLabelItemField, 309 BUnarchiver::B_DONT_ASSUME_OWNERSHIP, 310 fLayoutData->label_layout_item); 311 312 if (err == B_OK) 313 fLayoutData->label_layout_item->SetParent(this); 314 } 315 return err; 316 } 317 318 319 // #pragma mark - Hook methods 320 321 322 void 323 BTextControl::AllAttached() 324 { 325 BControl::AllAttached(); 326 } 327 328 329 void 330 BTextControl::AllDetached() 331 { 332 BControl::AllDetached(); 333 } 334 335 336 void 337 BTextControl::AttachedToWindow() 338 { 339 BControl::AttachedToWindow(); 340 341 _UpdateTextViewColors(IsEnabled()); 342 fText->MakeEditable(IsEnabled()); 343 } 344 345 346 void 347 BTextControl::DetachedFromWindow() 348 { 349 BControl::DetachedFromWindow(); 350 } 351 352 353 void 354 BTextControl::Draw(BRect updateRect) 355 { 356 bool enabled = IsEnabled(); 357 bool active = fText->IsFocus() && Window()->IsActive(); 358 359 BRect rect = fText->Frame(); 360 rect.InsetBy(-2, -2); 361 362 rgb_color base = ViewColor(); 363 uint32 flags = fLook; 364 if (!enabled) 365 flags |= BControlLook::B_DISABLED; 366 367 if (active) 368 flags |= BControlLook::B_FOCUSED; 369 370 be_control_look->DrawTextControlBorder(this, rect, updateRect, base, 371 flags); 372 373 if (Label() != NULL) { 374 if (fLayoutData->label_layout_item != NULL) { 375 rect = fLayoutData->label_layout_item->FrameInParent(); 376 } else { 377 rect = Bounds(); 378 rect.right = fDivider - kLabelInputSpacing; 379 } 380 381 // erase the is control flag before drawing the label so that the label 382 // will get drawn using B_PANEL_TEXT_COLOR 383 flags &= ~BControlLook::B_IS_CONTROL; 384 385 be_control_look->DrawLabel(this, Label(), rect, updateRect, 386 base, flags, BAlignment(fLabelAlign, B_ALIGN_MIDDLE)); 387 } 388 } 389 390 391 void 392 BTextControl::FrameMoved(BPoint newPosition) 393 { 394 BControl::FrameMoved(newPosition); 395 } 396 397 398 void 399 BTextControl::FrameResized(float width, float height) 400 { 401 CALLED(); 402 403 BControl::FrameResized(width, height); 404 405 // TODO: this causes flickering still... 406 407 // changes in width 408 409 BRect bounds = Bounds(); 410 411 if (bounds.Width() > fLayoutData->previous_width) { 412 // invalidate the region between the old and the new right border 413 BRect rect = bounds; 414 rect.left += fLayoutData->previous_width - kFrameMargin; 415 rect.right--; 416 Invalidate(rect); 417 } else if (bounds.Width() < fLayoutData->previous_width) { 418 // invalidate the region of the new right border 419 BRect rect = bounds; 420 rect.left = rect.right - kFrameMargin; 421 Invalidate(rect); 422 } 423 424 // changes in height 425 426 if (bounds.Height() > fLayoutData->previous_height) { 427 // invalidate the region between the old and the new bottom border 428 BRect rect = bounds; 429 rect.top += fLayoutData->previous_height - kFrameMargin; 430 rect.bottom--; 431 Invalidate(rect); 432 // invalidate label area 433 rect = bounds; 434 rect.right = fDivider; 435 Invalidate(rect); 436 } else if (bounds.Height() < fLayoutData->previous_height) { 437 // invalidate the region of the new bottom border 438 BRect rect = bounds; 439 rect.top = rect.bottom - kFrameMargin; 440 Invalidate(rect); 441 // invalidate label area 442 rect = bounds; 443 rect.right = fDivider; 444 Invalidate(rect); 445 } 446 447 fLayoutData->previous_width = bounds.Width(); 448 fLayoutData->previous_height = bounds.Height(); 449 450 TRACE("width: %.2f, height: %.2f\n", bounds.Width(), bounds.Height()); 451 } 452 453 454 status_t 455 BTextControl::Invoke(BMessage* message) 456 { 457 return BControl::Invoke(message); 458 } 459 460 461 void 462 BTextControl::LayoutInvalidated(bool descendants) 463 { 464 CALLED(); 465 466 fLayoutData->valid = false; 467 } 468 469 470 void 471 BTextControl::MessageReceived(BMessage* message) 472 { 473 if (message->what == B_COLORS_UPDATED) { 474 475 if (message->HasColor(ui_color_name(B_PANEL_BACKGROUND_COLOR)) 476 || message->HasColor(ui_color_name(B_PANEL_TEXT_COLOR)) 477 || message->HasColor(ui_color_name(B_DOCUMENT_BACKGROUND_COLOR)) 478 || message->HasColor(ui_color_name(B_DOCUMENT_TEXT_COLOR))) { 479 _UpdateTextViewColors(IsEnabled()); 480 } 481 } 482 483 if (message->what == B_GET_PROPERTY || message->what == B_SET_PROPERTY) { 484 BMessage reply(B_REPLY); 485 bool handled = false; 486 487 BMessage specifier; 488 int32 index; 489 int32 form; 490 const char* property; 491 if (message->GetCurrentSpecifier(&index, &specifier, &form, &property) == B_OK) { 492 if (strcmp(property, "Value") == 0) { 493 if (message->what == B_GET_PROPERTY) { 494 reply.AddString("result", fText->Text()); 495 handled = true; 496 } else { 497 const char* value = NULL; 498 // B_SET_PROPERTY 499 if (message->FindString("data", &value) == B_OK) { 500 fText->SetText(value); 501 reply.AddInt32("error", B_OK); 502 handled = true; 503 } 504 } 505 } 506 } 507 508 if (handled) { 509 message->SendReply(&reply); 510 return; 511 } 512 } 513 514 BControl::MessageReceived(message); 515 } 516 517 518 void 519 BTextControl::MouseDown(BPoint where) 520 { 521 if (!fText->IsFocus()) 522 fText->MakeFocus(true); 523 } 524 525 526 void 527 BTextControl::MouseMoved(BPoint where, uint32 transit, 528 const BMessage* dragMessage) 529 { 530 BControl::MouseMoved(where, transit, dragMessage); 531 } 532 533 534 void 535 BTextControl::MouseUp(BPoint where) 536 { 537 BControl::MouseUp(where); 538 } 539 540 541 void 542 BTextControl::WindowActivated(bool active) 543 { 544 if (fText->IsFocus()) { 545 // invalidate to remove/show focus indication 546 BRect rect = fText->Frame(); 547 rect.InsetBy(-1, -1); 548 Invalidate(rect); 549 550 // help out embedded text view which doesn't 551 // get notified of this 552 fText->Invalidate(); 553 } 554 } 555 556 557 // #pragma mark - Getters and Setters 558 559 560 void 561 BTextControl::SetText(const char* text) 562 { 563 if (InvokeKind() != B_CONTROL_INVOKED) 564 return; 565 566 CALLED(); 567 568 fText->SetText(text); 569 570 if (fText->IsFocus()) { 571 fText->SetInitialText(); 572 fText->SelectAll(); 573 } 574 575 fText->Invalidate(); 576 } 577 578 579 const char* 580 BTextControl::Text() const 581 { 582 return fText->Text(); 583 } 584 585 586 int32 587 BTextControl::TextLength() const 588 { 589 return fText->TextLength(); 590 } 591 592 593 void 594 BTextControl::MarkAsInvalid(bool invalid) 595 { 596 uint32 look = fLook; 597 598 if (invalid) 599 fLook |= BControlLook::B_INVALID; 600 else 601 fLook &= ~BControlLook::B_INVALID; 602 603 if (look != fLook) 604 Invalidate(); 605 } 606 607 608 void 609 BTextControl::SetValue(int32 value) 610 { 611 BControl::SetValue(value); 612 } 613 614 615 BTextView* 616 BTextControl::TextView() const 617 { 618 return fText; 619 } 620 621 622 void 623 BTextControl::SetModificationMessage(BMessage* message) 624 { 625 delete fModificationMessage; 626 fModificationMessage = message; 627 } 628 629 630 BMessage* 631 BTextControl::ModificationMessage() const 632 { 633 return fModificationMessage; 634 } 635 636 637 void 638 BTextControl::SetAlignment(alignment labelAlignment, alignment textAlignment) 639 { 640 fText->SetAlignment(textAlignment); 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 fText->SetTextRect(textFrame.OffsetToCopy(B_ORIGIN)); 915 916 fDivider = divider; 917 918 // invalidate dirty region 919 dirty = dirty | fText->Frame(); 920 dirty.InsetBy(-kFrameMargin, -kFrameMargin); 921 922 Invalidate(dirty); 923 } 924 925 926 // #pragma mark - protected methods 927 928 929 status_t 930 BTextControl::SetIcon(const BBitmap* icon, uint32 flags) 931 { 932 return BControl::SetIcon(icon, flags); 933 } 934 935 936 // #pragma mark - private methods 937 938 939 status_t 940 BTextControl::Perform(perform_code code, void* _data) 941 { 942 switch (code) { 943 case PERFORM_CODE_MIN_SIZE: 944 ((perform_data_min_size*)_data)->return_value 945 = BTextControl::MinSize(); 946 return B_OK; 947 948 case PERFORM_CODE_MAX_SIZE: 949 ((perform_data_max_size*)_data)->return_value 950 = BTextControl::MaxSize(); 951 return B_OK; 952 953 case PERFORM_CODE_PREFERRED_SIZE: 954 ((perform_data_preferred_size*)_data)->return_value 955 = BTextControl::PreferredSize(); 956 return B_OK; 957 958 case PERFORM_CODE_LAYOUT_ALIGNMENT: 959 ((perform_data_layout_alignment*)_data)->return_value 960 = BTextControl::LayoutAlignment(); 961 return B_OK; 962 963 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 964 ((perform_data_has_height_for_width*)_data)->return_value 965 = BTextControl::HasHeightForWidth(); 966 return B_OK; 967 968 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 969 { 970 perform_data_get_height_for_width* data 971 = (perform_data_get_height_for_width*)_data; 972 BTextControl::GetHeightForWidth(data->width, &data->min, &data->max, 973 &data->preferred); 974 return B_OK; 975 } 976 977 case PERFORM_CODE_SET_LAYOUT: 978 { 979 perform_data_set_layout* data = (perform_data_set_layout*)_data; 980 BTextControl::SetLayout(data->layout); 981 return B_OK; 982 } 983 984 case PERFORM_CODE_LAYOUT_INVALIDATED: 985 { 986 perform_data_layout_invalidated* data 987 = (perform_data_layout_invalidated*)_data; 988 BTextControl::LayoutInvalidated(data->descendants); 989 return B_OK; 990 } 991 992 case PERFORM_CODE_DO_LAYOUT: 993 { 994 BTextControl::DoLayout(); 995 return B_OK; 996 } 997 998 case PERFORM_CODE_SET_ICON: 999 { 1000 perform_data_set_icon* data = (perform_data_set_icon*)_data; 1001 return BTextControl::SetIcon(data->icon, data->flags); 1002 } 1003 1004 case PERFORM_CODE_ALL_UNARCHIVED: 1005 { 1006 perform_data_all_unarchived* data 1007 = (perform_data_all_unarchived*)_data; 1008 data->return_value = BTextControl::AllUnarchived(data->archive); 1009 return B_OK; 1010 } 1011 1012 case PERFORM_CODE_ALL_ARCHIVED: 1013 { 1014 perform_data_all_archived* data 1015 = (perform_data_all_archived*)_data; 1016 data->return_value = BTextControl::AllArchived(data->archive); 1017 return B_OK; 1018 } 1019 } 1020 1021 return BControl::Perform(code, _data); 1022 } 1023 1024 1025 // #pragma mark - FBC padding 1026 1027 1028 void BTextControl::_ReservedTextControl1() {} 1029 void BTextControl::_ReservedTextControl2() {} 1030 void BTextControl::_ReservedTextControl3() {} 1031 void BTextControl::_ReservedTextControl4() {} 1032 1033 1034 BTextControl& 1035 BTextControl::operator=(const BTextControl&) 1036 { 1037 return *this; 1038 } 1039 1040 1041 void 1042 BTextControl::_UpdateTextViewColors(bool enable) 1043 { 1044 rgb_color textColor = ui_color(B_DOCUMENT_TEXT_COLOR); 1045 rgb_color viewColor = ui_color(B_DOCUMENT_BACKGROUND_COLOR); 1046 BFont font; 1047 1048 fText->GetFontAndColor(0, &font); 1049 1050 if (!enable) { 1051 textColor = disable_color(textColor, ViewColor()); 1052 viewColor = disable_color(ViewColor(), viewColor); 1053 } 1054 1055 fText->SetFontAndColor(&font, B_FONT_ALL, &textColor); 1056 fText->SetViewColor(viewColor); 1057 fText->SetLowColor(viewColor); 1058 } 1059 1060 1061 void 1062 BTextControl::_CommitValue() 1063 { 1064 } 1065 1066 1067 void 1068 BTextControl::_InitData(const char* label, const BMessage* archive) 1069 { 1070 BRect bounds(Bounds()); 1071 1072 fText = NULL; 1073 fModificationMessage = NULL; 1074 fLabelAlign = B_ALIGN_LEFT; 1075 fDivider = 0.0f; 1076 fLayoutData = new LayoutData(bounds.Width(), bounds.Height()); 1077 1078 int32 flags = 0; 1079 1080 BFont font(be_plain_font); 1081 1082 if (!archive || !archive->HasString("_fname")) 1083 flags |= B_FONT_FAMILY_AND_STYLE; 1084 1085 if (!archive || !archive->HasFloat("_fflt")) 1086 flags |= B_FONT_SIZE; 1087 1088 if (flags != 0) 1089 SetFont(&font, flags); 1090 1091 if (label != NULL) 1092 fDivider = floorf(bounds.Width() / 2.0f); 1093 1094 fLook = 0; 1095 } 1096 1097 1098 void 1099 BTextControl::_InitText(const char* initialText, const BMessage* archive) 1100 { 1101 if (archive) 1102 fText = static_cast<BPrivate::_BTextInput_*>(FindView("_input_")); 1103 1104 if (fText == NULL) { 1105 BRect bounds(Bounds()); 1106 BRect frame(fDivider, bounds.top, bounds.right, bounds.bottom); 1107 // we are stroking the frame around the text view, which 1108 // is 2 pixels wide 1109 frame.InsetBy(kFrameMargin, kFrameMargin); 1110 BRect textRect(frame.OffsetToCopy(B_ORIGIN)); 1111 1112 fText = new BPrivate::_BTextInput_(frame, textRect, 1113 B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS 1114 | (Flags() & B_NAVIGABLE)); 1115 AddChild(fText); 1116 1117 SetText(initialText); 1118 fText->SetAlignment(B_ALIGN_LEFT); 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 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