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