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