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