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 <HSL.h> 23 #include <LayoutUtils.h> 24 #include <Message.h> 25 #include <PropertyInfo.h> 26 #include <Region.h> 27 #include <Window.h> 28 29 #include <binary_compatibility/Interface.h> 30 #include <binary_compatibility/Support.h> 31 32 #include "TextInput.h" 33 34 35 //#define TRACE_TEXT_CONTROL 36 #ifdef TRACE_TEXT_CONTROL 37 # include <stdio.h> 38 # include <FunctionTracer.h> 39 static int32 sFunctionDepth = -1; 40 # define CALLED(x...) FunctionTracer _ft("BTextControl", __FUNCTION__, \ 41 sFunctionDepth) 42 # define TRACE(x...) { BString _to; \ 43 _to.Append(' ', (sFunctionDepth + 1) * 2); \ 44 printf("%s", _to.String()); printf(x); } 45 #else 46 # define CALLED(x...) 47 # define TRACE(x...) 48 #endif 49 50 51 namespace { 52 const char* const kFrameField = "BTextControl:layoutitem:frame"; 53 const char* const kTextViewItemField = "BTextControl:textViewItem"; 54 const char* const kLabelItemField = "BMenuField:labelItem"; 55 } 56 57 58 static property_info sPropertyList[] = { 59 { 60 "Value", 61 { B_GET_PROPERTY, B_SET_PROPERTY }, 62 { B_DIRECT_SPECIFIER }, 63 NULL, 0, 64 { B_STRING_TYPE } 65 }, 66 67 { 0 } 68 }; 69 70 71 class BTextControl::LabelLayoutItem : public BAbstractLayoutItem { 72 public: 73 LabelLayoutItem(BTextControl* parent); 74 LabelLayoutItem(BMessage* from); 75 76 virtual bool IsVisible(); 77 virtual void SetVisible(bool visible); 78 79 virtual BRect Frame(); 80 virtual void SetFrame(BRect frame); 81 82 void SetParent(BTextControl* parent); 83 virtual BView* View(); 84 85 virtual BSize BaseMinSize(); 86 virtual BSize BaseMaxSize(); 87 virtual BSize BasePreferredSize(); 88 virtual BAlignment BaseAlignment(); 89 90 BRect FrameInParent() const; 91 92 virtual status_t Archive(BMessage* into, bool deep = true) const; 93 static BArchivable* Instantiate(BMessage* from); 94 95 private: 96 BTextControl* fParent; 97 BRect fFrame; 98 }; 99 100 101 class BTextControl::TextViewLayoutItem : public BAbstractLayoutItem { 102 public: 103 TextViewLayoutItem(BTextControl* parent); 104 TextViewLayoutItem(BMessage* from); 105 106 virtual bool IsVisible(); 107 virtual void SetVisible(bool visible); 108 109 virtual BRect Frame(); 110 virtual void SetFrame(BRect frame); 111 112 void SetParent(BTextControl* parent); 113 virtual BView* View(); 114 115 virtual BSize BaseMinSize(); 116 virtual BSize BaseMaxSize(); 117 virtual BSize BasePreferredSize(); 118 virtual BAlignment BaseAlignment(); 119 120 BRect FrameInParent() const; 121 122 virtual status_t Archive(BMessage* into, bool deep = true) const; 123 static BArchivable* Instantiate(BMessage* from); 124 private: 125 BTextControl* fParent; 126 BRect fFrame; 127 }; 128 129 130 struct BTextControl::LayoutData { 131 LayoutData(float width, float height) 132 : 133 label_layout_item(NULL), 134 text_view_layout_item(NULL), 135 previous_width(width), 136 previous_height(height), 137 valid(false) 138 { 139 } 140 141 LabelLayoutItem* label_layout_item; 142 TextViewLayoutItem* text_view_layout_item; 143 float previous_width; // used in FrameResized() for 144 float previous_height; // invalidation 145 font_height font_info; 146 float label_width; 147 float label_height; 148 BSize min; 149 BSize text_view_min; 150 bool valid; 151 }; 152 153 154 static const int32 kFrameMargin = 2; 155 static const int32 kLabelInputSpacing = 3; 156 157 158 // #pragma mark - BTextControl 159 160 161 BTextControl::BTextControl(BRect frame, const char* name, const char* label, 162 const char* text, BMessage* message, uint32 resizeMask, uint32 flags) 163 : 164 BControl(frame, name, label, message, resizeMask, flags | B_FRAME_EVENTS) 165 { 166 _InitData(label); 167 _InitText(text); 168 _ValidateLayout(); 169 } 170 171 172 BTextControl::BTextControl(const char* name, const char* label, 173 const char* text, BMessage* message, uint32 flags) 174 : 175 BControl(name, label, message, flags | B_FRAME_EVENTS) 176 { 177 _InitData(label); 178 _InitText(text); 179 _ValidateLayout(); 180 } 181 182 183 BTextControl::BTextControl(const char* label, const char* text, 184 BMessage* message) 185 : 186 BControl(NULL, label, message, 187 B_WILL_DRAW | B_NAVIGABLE | B_FRAME_EVENTS) 188 { 189 _InitData(label); 190 _InitText(text); 191 _ValidateLayout(); 192 } 193 194 195 BTextControl::~BTextControl() 196 { 197 SetModificationMessage(NULL); 198 delete fLayoutData; 199 } 200 201 202 // #pragma mark - Archiving 203 204 205 BTextControl::BTextControl(BMessage* archive) 206 : 207 BControl(BUnarchiver::PrepareArchive(archive)) 208 { 209 BUnarchiver unarchiver(archive); 210 211 _InitData(Label(), archive); 212 213 if (!BUnarchiver::IsArchiveManaged(archive)) 214 _InitText(NULL, archive); 215 216 status_t err = B_OK; 217 if (archive->HasFloat("_divide")) 218 err = archive->FindFloat("_divide", &fDivider); 219 220 if (err == B_OK && archive->HasMessage("_mod_msg")) { 221 BMessage* message = new BMessage; 222 err = archive->FindMessage("_mod_msg", message); 223 SetModificationMessage(message); 224 } 225 226 unarchiver.Finish(err); 227 } 228 229 230 BArchivable* 231 BTextControl::Instantiate(BMessage* archive) 232 { 233 if (validate_instantiation(archive, "BTextControl")) 234 return new BTextControl(archive); 235 236 return NULL; 237 } 238 239 240 status_t 241 BTextControl::Archive(BMessage* data, bool deep) const 242 { 243 BArchiver archiver(data); 244 status_t result = BControl::Archive(data, deep); 245 246 alignment labelAlignment; 247 alignment textAlignment; 248 if (result == B_OK) 249 GetAlignment(&labelAlignment, &textAlignment); 250 251 if (result == B_OK) 252 result = data->AddInt32("_a_label", labelAlignment); 253 254 if (result == B_OK) 255 result = data->AddInt32("_a_text", textAlignment); 256 257 if (result == B_OK) 258 result = data->AddFloat("_divide", Divider()); 259 260 if (result == B_OK && ModificationMessage() != NULL) 261 result = data->AddMessage("_mod_msg", ModificationMessage()); 262 263 return archiver.Finish(result); 264 } 265 266 267 status_t 268 BTextControl::AllArchived(BMessage* into) const 269 { 270 BArchiver archiver(into); 271 status_t err = B_OK; 272 273 if (archiver.IsArchived(fLayoutData->text_view_layout_item)) { 274 err = archiver.AddArchivable(kTextViewItemField, 275 fLayoutData->text_view_layout_item); 276 } 277 278 if (err == B_OK && archiver.IsArchived(fLayoutData->label_layout_item)) { 279 err = archiver.AddArchivable(kLabelItemField, 280 fLayoutData->label_layout_item); 281 } 282 283 return err; 284 } 285 286 287 status_t 288 BTextControl::AllUnarchived(const BMessage* from) 289 { 290 status_t err; 291 if ((err = BControl::AllUnarchived(from)) != B_OK) 292 return err; 293 294 _InitText(NULL, from); 295 296 BUnarchiver unarchiver(from); 297 if (unarchiver.IsInstantiated(kTextViewItemField)) { 298 err = unarchiver.FindObject(kTextViewItemField, 299 BUnarchiver::B_DONT_ASSUME_OWNERSHIP, 300 fLayoutData->text_view_layout_item); 301 302 if (err == B_OK) 303 fLayoutData->text_view_layout_item->SetParent(this); 304 else 305 return err; 306 } 307 308 if (unarchiver.IsInstantiated(kLabelItemField)) { 309 err = unarchiver.FindObject(kLabelItemField, 310 BUnarchiver::B_DONT_ASSUME_OWNERSHIP, 311 fLayoutData->label_layout_item); 312 313 if (err == B_OK) 314 fLayoutData->label_layout_item->SetParent(this); 315 } 316 return err; 317 } 318 319 320 // #pragma mark - Hook methods 321 322 323 void 324 BTextControl::AllAttached() 325 { 326 BControl::AllAttached(); 327 } 328 329 330 void 331 BTextControl::AllDetached() 332 { 333 BControl::AllDetached(); 334 } 335 336 337 void 338 BTextControl::AttachedToWindow() 339 { 340 BControl::AttachedToWindow(); 341 342 _UpdateTextViewColors(IsEnabled()); 343 fText->MakeEditable(IsEnabled()); 344 } 345 346 347 void 348 BTextControl::DetachedFromWindow() 349 { 350 BControl::DetachedFromWindow(); 351 } 352 353 354 void 355 BTextControl::Draw(BRect updateRect) 356 { 357 bool enabled = IsEnabled(); 358 bool active = fText->IsFocus() && Window()->IsActive(); 359 360 BRect rect = fText->Frame(); 361 rect.InsetBy(-2, -2); 362 363 rgb_color base = ViewColor(); 364 uint32 flags = fLook; 365 if (!enabled) 366 flags |= BControlLook::B_DISABLED; 367 368 if (active) 369 flags |= BControlLook::B_FOCUSED; 370 371 be_control_look->DrawTextControlBorder(this, rect, updateRect, base, 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 be_control_look->DrawLabel(this, Label(), rect, updateRect, 382 base, flags, BAlignment(fLabelAlign, B_ALIGN_MIDDLE)); 383 } 384 } 385 386 387 void 388 BTextControl::FrameMoved(BPoint newPosition) 389 { 390 BControl::FrameMoved(newPosition); 391 } 392 393 394 void 395 BTextControl::FrameResized(float width, float height) 396 { 397 CALLED(); 398 399 BControl::FrameResized(width, height); 400 401 // TODO: this causes flickering still... 402 403 // changes in width 404 405 BRect bounds = Bounds(); 406 407 if (bounds.Width() > fLayoutData->previous_width) { 408 // invalidate the region between the old and the new right border 409 BRect rect = bounds; 410 rect.left += fLayoutData->previous_width - kFrameMargin; 411 rect.right--; 412 Invalidate(rect); 413 } else if (bounds.Width() < fLayoutData->previous_width) { 414 // invalidate the region of the new right border 415 BRect rect = bounds; 416 rect.left = rect.right - kFrameMargin; 417 Invalidate(rect); 418 } 419 420 // changes in height 421 422 if (bounds.Height() > fLayoutData->previous_height) { 423 // invalidate the region between the old and the new bottom border 424 BRect rect = bounds; 425 rect.top += fLayoutData->previous_height - kFrameMargin; 426 rect.bottom--; 427 Invalidate(rect); 428 // invalidate label area 429 rect = bounds; 430 rect.right = fDivider; 431 Invalidate(rect); 432 } else if (bounds.Height() < fLayoutData->previous_height) { 433 // invalidate the region of the new bottom border 434 BRect rect = bounds; 435 rect.top = rect.bottom - kFrameMargin; 436 Invalidate(rect); 437 // invalidate label area 438 rect = bounds; 439 rect.right = fDivider; 440 Invalidate(rect); 441 } 442 443 fLayoutData->previous_width = bounds.Width(); 444 fLayoutData->previous_height = bounds.Height(); 445 446 TRACE("width: %.2f, height: %.2f\n", bounds.Width(), bounds.Height()); 447 } 448 449 450 status_t 451 BTextControl::Invoke(BMessage* message) 452 { 453 return BControl::Invoke(message); 454 } 455 456 457 void 458 BTextControl::LayoutInvalidated(bool descendants) 459 { 460 CALLED(); 461 462 fLayoutData->valid = false; 463 } 464 465 466 void 467 BTextControl::MessageReceived(BMessage* message) 468 { 469 if (message->what == B_COLORS_UPDATED) { 470 471 if (message->HasColor(ui_color_name(B_PANEL_BACKGROUND_COLOR)) 472 || message->HasColor(ui_color_name(B_PANEL_TEXT_COLOR)) 473 || message->HasColor(ui_color_name(B_DOCUMENT_BACKGROUND_COLOR)) 474 || message->HasColor(ui_color_name(B_DOCUMENT_TEXT_COLOR)) 475 || message->HasColor(ui_color_name(B_FAILURE_COLOR))) { 476 _UpdateTextViewColors(IsEnabled()); 477 } 478 } 479 480 if (message->what == B_GET_PROPERTY || message->what == B_SET_PROPERTY) { 481 BMessage reply(B_REPLY); 482 bool handled = false; 483 484 BMessage specifier; 485 int32 index; 486 int32 form; 487 const char* property; 488 if (message->GetCurrentSpecifier(&index, &specifier, &form, &property) == B_OK) { 489 if (strcmp(property, "Value") == 0) { 490 if (message->what == B_GET_PROPERTY) { 491 reply.AddString("result", fText->Text()); 492 handled = true; 493 } else { 494 const char* value = NULL; 495 // B_SET_PROPERTY 496 if (message->FindString("data", &value) == B_OK) { 497 fText->SetText(value); 498 reply.AddInt32("error", B_OK); 499 handled = true; 500 } 501 } 502 } 503 } 504 505 if (handled) { 506 message->SendReply(&reply); 507 return; 508 } 509 } 510 511 BControl::MessageReceived(message); 512 } 513 514 515 void 516 BTextControl::MouseDown(BPoint where) 517 { 518 if (!fText->IsFocus()) 519 fText->MakeFocus(true); 520 } 521 522 523 void 524 BTextControl::MouseMoved(BPoint where, uint32 transit, 525 const BMessage* dragMessage) 526 { 527 BControl::MouseMoved(where, transit, dragMessage); 528 } 529 530 531 void 532 BTextControl::MouseUp(BPoint where) 533 { 534 BControl::MouseUp(where); 535 } 536 537 538 void 539 BTextControl::WindowActivated(bool active) 540 { 541 if (fText->IsFocus()) { 542 // invalidate to remove/show focus indication 543 BRect rect = fText->Frame(); 544 rect.InsetBy(-1, -1); 545 Invalidate(rect); 546 547 // help out embedded text view which doesn't 548 // get notified of this 549 fText->Invalidate(); 550 } 551 } 552 553 554 // #pragma mark - Getters and Setters 555 556 557 void 558 BTextControl::SetText(const char* text) 559 { 560 if (InvokeKind() != B_CONTROL_INVOKED) 561 return; 562 563 CALLED(); 564 565 fText->SetText(text); 566 567 if (fText->IsFocus()) { 568 fText->SetInitialText(); 569 fText->SelectAll(); 570 } 571 572 fText->Invalidate(); 573 } 574 575 576 const char* 577 BTextControl::Text() const 578 { 579 return fText->Text(); 580 } 581 582 583 int32 584 BTextControl::TextLength() const 585 { 586 return fText->TextLength(); 587 } 588 589 590 void 591 BTextControl::MarkAsInvalid(bool invalid) 592 { 593 uint32 look = fLook; 594 595 if (invalid) 596 fLook |= BControlLook::B_INVALID; 597 else 598 fLook &= ~BControlLook::B_INVALID; 599 600 if (look != fLook) { 601 _UpdateTextViewColors(IsEnabled()); 602 Invalidate(); 603 } 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 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 fText->SetTextRect(textFrame.OffsetToCopy(B_ORIGIN)); 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 } else if (fLook & BControlLook::B_INVALID) { 1053 hsl_color normalViewColor = hsl_color::from_rgb(viewColor); 1054 rgb_color failureColor = ui_color(B_FAILURE_COLOR); 1055 hsl_color newViewColor = hsl_color::from_rgb(failureColor); 1056 if (normalViewColor.lightness < 0.15) 1057 newViewColor.lightness = 0.15; 1058 else if (normalViewColor.lightness > 0.95) 1059 newViewColor.lightness = 0.95; 1060 else 1061 newViewColor.lightness = normalViewColor.lightness; 1062 1063 viewColor = newViewColor.to_rgb(); 1064 } 1065 1066 fText->SetFontAndColor(&font, B_FONT_ALL, &textColor); 1067 fText->SetViewColor(viewColor); 1068 fText->SetLowColor(viewColor); 1069 } 1070 1071 1072 void 1073 BTextControl::_CommitValue() 1074 { 1075 } 1076 1077 1078 void 1079 BTextControl::_InitData(const char* label, const BMessage* archive) 1080 { 1081 BRect bounds(Bounds()); 1082 1083 fText = NULL; 1084 fModificationMessage = NULL; 1085 fLabelAlign = B_ALIGN_LEFT; 1086 fDivider = 0.0f; 1087 fLayoutData = new LayoutData(bounds.Width(), bounds.Height()); 1088 1089 int32 flags = 0; 1090 1091 BFont font(be_plain_font); 1092 1093 if (!archive || !archive->HasString("_fname")) 1094 flags |= B_FONT_FAMILY_AND_STYLE; 1095 1096 if (!archive || !archive->HasFloat("_fflt")) 1097 flags |= B_FONT_SIZE; 1098 1099 if (flags != 0) 1100 SetFont(&font, flags); 1101 1102 if (label != NULL) 1103 fDivider = floorf(bounds.Width() / 2.0f); 1104 1105 fLook = 0; 1106 } 1107 1108 1109 void 1110 BTextControl::_InitText(const char* initialText, const BMessage* archive) 1111 { 1112 if (archive) 1113 fText = static_cast<BPrivate::_BTextInput_*>(FindView("_input_")); 1114 1115 if (fText == NULL) { 1116 BRect bounds(Bounds()); 1117 BRect frame(fDivider, bounds.top, bounds.right, bounds.bottom); 1118 // we are stroking the frame around the text view, which 1119 // is 2 pixels wide 1120 frame.InsetBy(kFrameMargin, kFrameMargin); 1121 BRect textRect(frame.OffsetToCopy(B_ORIGIN)); 1122 1123 fText = new BPrivate::_BTextInput_(frame, textRect, 1124 B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS 1125 | (Flags() & B_NAVIGABLE)); 1126 AddChild(fText); 1127 1128 SetText(initialText); 1129 fText->SetAlignment(B_ALIGN_LEFT); 1130 } 1131 1132 // Although this is not strictly initializing the text view, 1133 // it cannot be done while fText is NULL, so it resides here. 1134 if (archive) { 1135 int32 labelAlignment = B_ALIGN_LEFT; 1136 int32 textAlignment = B_ALIGN_LEFT; 1137 1138 status_t err = B_OK; 1139 if (archive->HasInt32("_a_label")) 1140 err = archive->FindInt32("_a_label", &labelAlignment); 1141 1142 if (err == B_OK && archive->HasInt32("_a_text")) 1143 err = archive->FindInt32("_a_text", &textAlignment); 1144 1145 SetAlignment((alignment)labelAlignment, (alignment)textAlignment); 1146 } 1147 1148 uint32 navigableFlags = Flags() & B_NAVIGABLE; 1149 if (navigableFlags != 0) 1150 BView::SetFlags(Flags() & ~B_NAVIGABLE); 1151 } 1152 1153 1154 void 1155 BTextControl::_ValidateLayout() 1156 { 1157 CALLED(); 1158 1159 _ValidateLayoutData(); 1160 1161 ResizeTo(Bounds().Width(), fLayoutData->min.height); 1162 1163 _LayoutTextView(); 1164 } 1165 1166 1167 void 1168 BTextControl::_LayoutTextView() 1169 { 1170 CALLED(); 1171 1172 BRect frame; 1173 if (fLayoutData->text_view_layout_item != NULL) { 1174 frame = fLayoutData->text_view_layout_item->FrameInParent(); 1175 } else { 1176 frame = Bounds(); 1177 frame.left = fDivider; 1178 } 1179 1180 // we are stroking the frame around the text view, which 1181 // is 2 pixels wide 1182 frame.InsetBy(kFrameMargin, kFrameMargin); 1183 fText->MoveTo(frame.left, frame.top); 1184 fText->ResizeTo(frame.Width(), frame.Height()); 1185 1186 TRACE("width: %.2f, height: %.2f\n", Frame().Width(), Frame().Height()); 1187 TRACE("fDivider: %.2f\n", fDivider); 1188 TRACE("fText frame: (%.2f, %.2f, %.2f, %.2f)\n", 1189 frame.left, frame.top, frame.right, frame.bottom); 1190 } 1191 1192 1193 void 1194 BTextControl::_UpdateFrame() 1195 { 1196 CALLED(); 1197 1198 if (fLayoutData->text_view_layout_item != NULL) { 1199 BRect textFrame = fLayoutData->text_view_layout_item->Frame(); 1200 BRect labelFrame; 1201 if (fLayoutData->label_layout_item != NULL) 1202 labelFrame = fLayoutData->label_layout_item->Frame(); 1203 1204 BRect frame; 1205 if (labelFrame.IsValid()) { 1206 frame = textFrame | labelFrame; 1207 1208 // update divider 1209 fDivider = fabs(textFrame.left - labelFrame.left); 1210 } else { 1211 frame = textFrame; 1212 fDivider = 0; 1213 } 1214 1215 // update our frame 1216 MoveTo(frame.left, frame.top); 1217 BSize oldSize(Bounds().Size()); 1218 ResizeTo(frame.Width(), frame.Height()); 1219 BSize newSize(Bounds().Size()); 1220 1221 // If the size changes, ResizeTo() will trigger a relayout, otherwise 1222 // we need to do that explicitly. 1223 if (newSize != oldSize) 1224 Relayout(); 1225 } 1226 } 1227 1228 1229 void 1230 BTextControl::_ValidateLayoutData() 1231 { 1232 CALLED(); 1233 1234 if (fLayoutData->valid) 1235 return; 1236 1237 // cache font height 1238 font_height& fh = fLayoutData->font_info; 1239 GetFontHeight(&fh); 1240 1241 const char* label = Label(); 1242 if (label != NULL) { 1243 fLayoutData->label_width = ceilf(StringWidth(label)); 1244 fLayoutData->label_height = ceilf(fh.ascent) + ceilf(fh.descent); 1245 } else { 1246 fLayoutData->label_width = 0; 1247 fLayoutData->label_height = 0; 1248 } 1249 1250 // compute the minimal divider 1251 float divider = 0; 1252 if (fLayoutData->label_width > 0) { 1253 divider = fLayoutData->label_width 1254 + be_control_look->DefaultLabelSpacing(); 1255 } 1256 1257 // If we shan't do real layout, we let the current divider take influence. 1258 if (!(Flags() & B_SUPPORTS_LAYOUT)) 1259 divider = max_c(divider, fDivider); 1260 1261 // get the minimal (== preferred) text view size 1262 fLayoutData->text_view_min = fText->MinSize(); 1263 1264 TRACE("text view min width: %.2f\n", fLayoutData->text_view_min.width); 1265 1266 // compute our minimal (== preferred) size 1267 BSize min(fLayoutData->text_view_min); 1268 min.width += 2 * kFrameMargin; 1269 min.height += 2 * kFrameMargin; 1270 1271 if (divider > 0) 1272 min.width += divider; 1273 1274 if (fLayoutData->label_height > min.height) 1275 min.height = fLayoutData->label_height; 1276 1277 fLayoutData->min = min; 1278 1279 fLayoutData->valid = true; 1280 ResetLayoutInvalidation(); 1281 1282 TRACE("width: %.2f, height: %.2f\n", min.width, min.height); 1283 } 1284 1285 1286 // #pragma mark - BTextControl::LabelLayoutItem 1287 1288 1289 BTextControl::LabelLayoutItem::LabelLayoutItem(BTextControl* parent) 1290 : 1291 fParent(parent), 1292 fFrame() 1293 { 1294 } 1295 1296 1297 BTextControl::LabelLayoutItem::LabelLayoutItem(BMessage* from) 1298 : 1299 BAbstractLayoutItem(from), 1300 fParent(NULL), 1301 fFrame() 1302 { 1303 from->FindRect(kFrameField, &fFrame); 1304 } 1305 1306 1307 bool 1308 BTextControl::LabelLayoutItem::IsVisible() 1309 { 1310 return !fParent->IsHidden(fParent); 1311 } 1312 1313 1314 void 1315 BTextControl::LabelLayoutItem::SetVisible(bool visible) 1316 { 1317 // not allowed 1318 } 1319 1320 1321 BRect 1322 BTextControl::LabelLayoutItem::Frame() 1323 { 1324 return fFrame; 1325 } 1326 1327 1328 void 1329 BTextControl::LabelLayoutItem::SetFrame(BRect frame) 1330 { 1331 fFrame = frame; 1332 fParent->_UpdateFrame(); 1333 } 1334 1335 1336 void 1337 BTextControl::LabelLayoutItem::SetParent(BTextControl* parent) 1338 { 1339 fParent = parent; 1340 } 1341 1342 1343 BView* 1344 BTextControl::LabelLayoutItem::View() 1345 { 1346 return fParent; 1347 } 1348 1349 1350 BSize 1351 BTextControl::LabelLayoutItem::BaseMinSize() 1352 { 1353 fParent->_ValidateLayoutData(); 1354 1355 if (!fParent->Label()) 1356 return BSize(-1, -1); 1357 1358 return BSize(fParent->fLayoutData->label_width 1359 + be_control_look->DefaultLabelSpacing(), 1360 fParent->fLayoutData->label_height); 1361 } 1362 1363 1364 BSize 1365 BTextControl::LabelLayoutItem::BaseMaxSize() 1366 { 1367 return BaseMinSize(); 1368 } 1369 1370 1371 BSize 1372 BTextControl::LabelLayoutItem::BasePreferredSize() 1373 { 1374 return BaseMinSize(); 1375 } 1376 1377 1378 BAlignment 1379 BTextControl::LabelLayoutItem::BaseAlignment() 1380 { 1381 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); 1382 } 1383 1384 1385 BRect 1386 BTextControl::LabelLayoutItem::FrameInParent() const 1387 { 1388 return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top); 1389 } 1390 1391 1392 status_t 1393 BTextControl::LabelLayoutItem::Archive(BMessage* into, bool deep) const 1394 { 1395 BArchiver archiver(into); 1396 status_t err = BAbstractLayoutItem::Archive(into, deep); 1397 if (err == B_OK) 1398 err = into->AddRect(kFrameField, fFrame); 1399 1400 return archiver.Finish(err); 1401 } 1402 1403 1404 BArchivable* 1405 BTextControl::LabelLayoutItem::Instantiate(BMessage* from) 1406 { 1407 if (validate_instantiation(from, "BTextControl::LabelLayoutItem")) 1408 return new LabelLayoutItem(from); 1409 return NULL; 1410 } 1411 1412 1413 // #pragma mark - BTextControl::TextViewLayoutItem 1414 1415 1416 BTextControl::TextViewLayoutItem::TextViewLayoutItem(BTextControl* parent) 1417 : 1418 fParent(parent), 1419 fFrame() 1420 { 1421 // by default the part right of the divider shall have an unlimited maximum 1422 // width 1423 SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 1424 } 1425 1426 1427 BTextControl::TextViewLayoutItem::TextViewLayoutItem(BMessage* from) 1428 : 1429 BAbstractLayoutItem(from), 1430 fParent(NULL), 1431 fFrame() 1432 { 1433 from->FindRect(kFrameField, &fFrame); 1434 } 1435 1436 1437 bool 1438 BTextControl::TextViewLayoutItem::IsVisible() 1439 { 1440 return !fParent->IsHidden(fParent); 1441 } 1442 1443 1444 void 1445 BTextControl::TextViewLayoutItem::SetVisible(bool visible) 1446 { 1447 // not allowed 1448 } 1449 1450 1451 BRect 1452 BTextControl::TextViewLayoutItem::Frame() 1453 { 1454 return fFrame; 1455 } 1456 1457 1458 void 1459 BTextControl::TextViewLayoutItem::SetFrame(BRect frame) 1460 { 1461 fFrame = frame; 1462 fParent->_UpdateFrame(); 1463 } 1464 1465 1466 void 1467 BTextControl::TextViewLayoutItem::SetParent(BTextControl* parent) 1468 { 1469 fParent = parent; 1470 } 1471 1472 1473 BView* 1474 BTextControl::TextViewLayoutItem::View() 1475 { 1476 return fParent; 1477 } 1478 1479 1480 BSize 1481 BTextControl::TextViewLayoutItem::BaseMinSize() 1482 { 1483 fParent->_ValidateLayoutData(); 1484 1485 BSize size = fParent->fLayoutData->text_view_min; 1486 size.width += 2 * kFrameMargin; 1487 size.height += 2 * kFrameMargin; 1488 1489 return size; 1490 } 1491 1492 1493 BSize 1494 BTextControl::TextViewLayoutItem::BaseMaxSize() 1495 { 1496 BSize size(BaseMinSize()); 1497 size.width = B_SIZE_UNLIMITED; 1498 1499 return size; 1500 } 1501 1502 1503 BSize 1504 BTextControl::TextViewLayoutItem::BasePreferredSize() 1505 { 1506 BSize size(BaseMinSize()); 1507 // puh, no idea... 1508 size.width = 100; 1509 1510 return size; 1511 } 1512 1513 1514 BAlignment 1515 BTextControl::TextViewLayoutItem::BaseAlignment() 1516 { 1517 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); 1518 } 1519 1520 1521 BRect 1522 BTextControl::TextViewLayoutItem::FrameInParent() const 1523 { 1524 return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top); 1525 } 1526 1527 1528 status_t 1529 BTextControl::TextViewLayoutItem::Archive(BMessage* into, bool deep) const 1530 { 1531 BArchiver archiver(into); 1532 status_t err = BAbstractLayoutItem::Archive(into, deep); 1533 if (err == B_OK) 1534 err = into->AddRect(kFrameField, fFrame); 1535 1536 return archiver.Finish(err); 1537 } 1538 1539 1540 BArchivable* 1541 BTextControl::TextViewLayoutItem::Instantiate(BMessage* from) 1542 { 1543 if (validate_instantiation(from, "BTextControl::TextViewLayoutItem")) 1544 return new TextViewLayoutItem(from); 1545 1546 return NULL; 1547 } 1548 1549 1550 extern "C" void 1551 B_IF_GCC_2(InvalidateLayout__12BTextControlb, 1552 _ZN12BTextControl16InvalidateLayoutEb)(BView* view, bool descendants) 1553 { 1554 perform_data_layout_invalidated data; 1555 data.descendants = descendants; 1556 1557 view->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data); 1558 } 1559