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