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