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 BLayoutItem* 845 BTextControl::CreateLabelLayoutItem() 846 { 847 if (!fLayoutData->label_layout_item) 848 fLayoutData->label_layout_item = new LabelLayoutItem(this); 849 return fLayoutData->label_layout_item; 850 } 851 852 853 BLayoutItem* 854 BTextControl::CreateTextViewLayoutItem() 855 { 856 if (!fLayoutData->text_view_layout_item) 857 fLayoutData->text_view_layout_item = new TextViewLayoutItem(this); 858 return fLayoutData->text_view_layout_item; 859 } 860 861 862 void 863 BTextControl::LayoutInvalidated(bool descendants) 864 { 865 CALLED(); 866 867 fLayoutData->valid = false; 868 } 869 870 871 void 872 BTextControl::DoLayout() 873 { 874 // Bail out, if we shan't do layout. 875 if (!(Flags() & B_SUPPORTS_LAYOUT)) 876 return; 877 878 CALLED(); 879 880 // If the user set a layout, we let the base class version call its 881 // hook. 882 if (GetLayout()) { 883 BView::DoLayout(); 884 return; 885 } 886 887 _ValidateLayoutData(); 888 889 // validate current size 890 BSize size(Bounds().Size()); 891 if (size.width < fLayoutData->min.width) 892 size.width = fLayoutData->min.width; 893 if (size.height < fLayoutData->min.height) 894 size.height = fLayoutData->min.height; 895 896 // divider 897 float divider = 0; 898 if (fLayoutData->label_layout_item && fLayoutData->text_view_layout_item) { 899 // We have layout items. They define the divider location. 900 divider = fLayoutData->text_view_layout_item->Frame().left 901 - fLayoutData->label_layout_item->Frame().left; 902 } else { 903 if (fLayoutData->label_width > 0) 904 divider = fLayoutData->label_width + 5; 905 } 906 907 // text view 908 BRect dirty(fText->Frame()); 909 BRect textFrame(divider + kFrameMargin, kFrameMargin, 910 size.width - kFrameMargin, size.height - kFrameMargin); 911 912 // place the text view and set the divider 913 BLayoutUtils::AlignInFrame(fText, textFrame); 914 915 fDivider = divider; 916 917 // invalidate dirty region 918 dirty = dirty | fText->Frame(); 919 dirty.InsetBy(-kFrameMargin, -kFrameMargin); 920 921 Invalidate(dirty); 922 } 923 924 925 // #pragma mark - 926 927 928 status_t 929 BTextControl::Perform(perform_code code, void* _data) 930 { 931 switch (code) { 932 case PERFORM_CODE_MIN_SIZE: 933 ((perform_data_min_size*)_data)->return_value 934 = BTextControl::MinSize(); 935 return B_OK; 936 case PERFORM_CODE_MAX_SIZE: 937 ((perform_data_max_size*)_data)->return_value 938 = BTextControl::MaxSize(); 939 return B_OK; 940 case PERFORM_CODE_PREFERRED_SIZE: 941 ((perform_data_preferred_size*)_data)->return_value 942 = BTextControl::PreferredSize(); 943 return B_OK; 944 case PERFORM_CODE_LAYOUT_ALIGNMENT: 945 ((perform_data_layout_alignment*)_data)->return_value 946 = BTextControl::LayoutAlignment(); 947 return B_OK; 948 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 949 ((perform_data_has_height_for_width*)_data)->return_value 950 = BTextControl::HasHeightForWidth(); 951 return B_OK; 952 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 953 { 954 perform_data_get_height_for_width* data 955 = (perform_data_get_height_for_width*)_data; 956 BTextControl::GetHeightForWidth(data->width, &data->min, &data->max, 957 &data->preferred); 958 return B_OK; 959 } 960 case PERFORM_CODE_SET_LAYOUT: 961 { 962 perform_data_set_layout* data = (perform_data_set_layout*)_data; 963 BTextControl::SetLayout(data->layout); 964 return B_OK; 965 } 966 case PERFORM_CODE_LAYOUT_INVALIDATED: 967 { 968 perform_data_layout_invalidated* data 969 = (perform_data_layout_invalidated*)_data; 970 BTextControl::LayoutInvalidated(data->descendants); 971 return B_OK; 972 } 973 case PERFORM_CODE_DO_LAYOUT: 974 { 975 BTextControl::DoLayout(); 976 return B_OK; 977 } 978 case PERFORM_CODE_ALL_UNARCHIVED: 979 { 980 perform_data_all_unarchived* data 981 = (perform_data_all_unarchived*)_data; 982 983 data->return_value = BTextControl::AllUnarchived(data->archive); 984 return B_OK; 985 } 986 case PERFORM_CODE_ALL_ARCHIVED: 987 { 988 perform_data_all_archived* data 989 = (perform_data_all_archived*)_data; 990 991 data->return_value = BTextControl::AllArchived(data->archive); 992 return B_OK; 993 } 994 } 995 996 return BControl::Perform(code, _data); 997 } 998 999 1000 void BTextControl::_ReservedTextControl1() {} 1001 void BTextControl::_ReservedTextControl2() {} 1002 void BTextControl::_ReservedTextControl3() {} 1003 void BTextControl::_ReservedTextControl4() {} 1004 1005 1006 BTextControl & 1007 BTextControl::operator=(const BTextControl&) 1008 { 1009 return *this; 1010 } 1011 1012 1013 void 1014 BTextControl::_UpdateTextViewColors(bool enabled) 1015 { 1016 rgb_color textColor; 1017 rgb_color color; 1018 BFont font; 1019 1020 fText->GetFontAndColor(0, &font); 1021 1022 if (enabled) 1023 textColor = ui_color(B_DOCUMENT_TEXT_COLOR); 1024 else { 1025 textColor = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 1026 B_DISABLED_LABEL_TINT); 1027 } 1028 1029 fText->SetFontAndColor(&font, B_FONT_ALL, &textColor); 1030 1031 if (enabled) { 1032 color = ui_color(B_DOCUMENT_BACKGROUND_COLOR); 1033 } else { 1034 color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 1035 B_LIGHTEN_2_TINT); 1036 } 1037 1038 fText->SetViewColor(color); 1039 fText->SetLowColor(color); 1040 } 1041 1042 1043 void 1044 BTextControl::_CommitValue() 1045 { 1046 } 1047 1048 1049 void 1050 BTextControl::_InitData(const char* label, const BMessage* archive) 1051 { 1052 BRect bounds(Bounds()); 1053 1054 fText = NULL; 1055 fModificationMessage = NULL; 1056 fLabelAlign = B_ALIGN_LEFT; 1057 fDivider = 0.0f; 1058 fLayoutData = new LayoutData(bounds.Width(), bounds.Height()); 1059 1060 int32 flags = 0; 1061 1062 BFont font(be_plain_font); 1063 1064 if (!archive || !archive->HasString("_fname")) 1065 flags |= B_FONT_FAMILY_AND_STYLE; 1066 1067 if (!archive || !archive->HasFloat("_fflt")) 1068 flags |= B_FONT_SIZE; 1069 1070 if (flags != 0) 1071 SetFont(&font, flags); 1072 1073 if (label) 1074 fDivider = floorf(bounds.Width() / 2.0f); 1075 1076 } 1077 1078 1079 void 1080 BTextControl::_InitText(const char* initialText, const BMessage* archive) 1081 { 1082 if (archive) 1083 fText = static_cast<BPrivate::_BTextInput_*>(FindView("_input_")); 1084 1085 if (fText == NULL) { 1086 BRect bounds(Bounds()); 1087 BRect frame(fDivider, bounds.top, bounds.right, bounds.bottom); 1088 // we are stroking the frame around the text view, which 1089 // is 2 pixels wide 1090 frame.InsetBy(kFrameMargin, kFrameMargin); 1091 BRect textRect(frame.OffsetToCopy(B_ORIGIN)); 1092 1093 fText = new BPrivate::_BTextInput_(frame, textRect, 1094 B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS 1095 | (Flags() & B_NAVIGABLE)); 1096 AddChild(fText); 1097 1098 SetText(initialText); 1099 fText->SetAlignment(B_ALIGN_LEFT); 1100 fText->AlignTextRect(); 1101 } 1102 1103 // Although this is not strictly initializing the text view, 1104 // it cannot be done while fText is NULL, so it resides here. 1105 if (archive) { 1106 int32 labelAlignment = B_ALIGN_LEFT; 1107 int32 textAlignment = B_ALIGN_LEFT; 1108 1109 status_t err = B_OK; 1110 if (archive->HasInt32("_a_label")) 1111 err = archive->FindInt32("_a_label", &labelAlignment); 1112 1113 if (err == B_OK && archive->HasInt32("_a_text")) 1114 err = archive->FindInt32("_a_text", &textAlignment); 1115 1116 SetAlignment((alignment)labelAlignment, (alignment)textAlignment); 1117 } 1118 1119 uint32 navigableFlags = Flags() & B_NAVIGABLE; 1120 if (navigableFlags != 0) 1121 BView::SetFlags(Flags() & ~B_NAVIGABLE); 1122 } 1123 1124 1125 void 1126 BTextControl::_ValidateLayout() 1127 { 1128 CALLED(); 1129 1130 _ValidateLayoutData(); 1131 1132 ResizeTo(Bounds().Width(), fLayoutData->min.height); 1133 1134 _LayoutTextView(); 1135 } 1136 1137 1138 void 1139 BTextControl::_LayoutTextView() 1140 { 1141 CALLED(); 1142 1143 BRect frame = Bounds(); 1144 frame.left = fDivider; 1145 // we are stroking the frame around the text view, which 1146 // is 2 pixels wide 1147 frame.InsetBy(kFrameMargin, kFrameMargin); 1148 fText->MoveTo(frame.left, frame.top); 1149 fText->ResizeTo(frame.Width(), frame.Height()); 1150 fText->AlignTextRect(); 1151 1152 TRACE("width: %.2f, height: %.2f\n", Frame().Width(), Frame().Height()); 1153 TRACE("fDivider: %.2f\n", fDivider); 1154 TRACE("fText frame: (%.2f, %.2f, %.2f, %.2f)\n", 1155 frame.left, frame.top, frame.right, frame.bottom); 1156 } 1157 1158 1159 void 1160 BTextControl::_UpdateFrame() 1161 { 1162 CALLED(); 1163 1164 if (fLayoutData->label_layout_item && fLayoutData->text_view_layout_item) { 1165 BRect labelFrame = fLayoutData->label_layout_item->Frame(); 1166 BRect textFrame = fLayoutData->text_view_layout_item->Frame(); 1167 1168 // update divider 1169 fDivider = textFrame.left - labelFrame.left; 1170 1171 MoveTo(labelFrame.left, labelFrame.top); 1172 BSize oldSize = Bounds().Size(); 1173 ResizeTo(textFrame.left + textFrame.Width() - labelFrame.left, 1174 textFrame.top + textFrame.Height() - labelFrame.top); 1175 BSize newSize = Bounds().Size(); 1176 1177 // If the size changes, ResizeTo() will trigger a relayout, otherwise 1178 // we need to do that explicitly. 1179 if (newSize != oldSize) 1180 Relayout(); 1181 } 1182 } 1183 1184 1185 void 1186 BTextControl::_ValidateLayoutData() 1187 { 1188 CALLED(); 1189 1190 if (fLayoutData->valid) 1191 return; 1192 1193 // cache font height 1194 font_height& fh = fLayoutData->font_info; 1195 GetFontHeight(&fh); 1196 1197 if (Label() != NULL) { 1198 fLayoutData->label_width = ceilf(StringWidth(Label())); 1199 fLayoutData->label_height = ceilf(fh.ascent) + ceilf(fh.descent); 1200 } else { 1201 fLayoutData->label_width = 0; 1202 fLayoutData->label_height = 0; 1203 } 1204 1205 // compute the minimal divider 1206 float divider = 0; 1207 if (fLayoutData->label_width > 0) 1208 divider = fLayoutData->label_width + 5; 1209 1210 // If we shan't do real layout, we let the current divider take influence. 1211 if (!(Flags() & B_SUPPORTS_LAYOUT)) 1212 divider = max_c(divider, fDivider); 1213 1214 // get the minimal (== preferred) text view size 1215 fLayoutData->text_view_min = fText->MinSize(); 1216 1217 TRACE("text view min width: %.2f\n", fLayoutData->text_view_min.width); 1218 1219 // compute our minimal (== preferred) size 1220 BSize min(fLayoutData->text_view_min); 1221 min.width += 2 * kFrameMargin; 1222 min.height += 2 * kFrameMargin; 1223 1224 if (divider > 0) 1225 min.width += divider; 1226 if (fLayoutData->label_height > min.height) 1227 min.height = fLayoutData->label_height; 1228 1229 fLayoutData->min = min; 1230 1231 fLayoutData->valid = true; 1232 ResetLayoutInvalidation(); 1233 1234 TRACE("width: %.2f, height: %.2f\n", min.width, min.height); 1235 } 1236 1237 1238 // #pragma mark - 1239 1240 1241 BTextControl::LabelLayoutItem::LabelLayoutItem(BTextControl* parent) 1242 : 1243 fParent(parent), 1244 fFrame() 1245 { 1246 } 1247 1248 1249 BTextControl::LabelLayoutItem::LabelLayoutItem(BMessage* from) 1250 : 1251 BAbstractLayoutItem(from), 1252 fParent(NULL), 1253 fFrame() 1254 { 1255 from->FindRect(kFrameField, &fFrame); 1256 } 1257 1258 1259 bool 1260 BTextControl::LabelLayoutItem::IsVisible() 1261 { 1262 return !fParent->IsHidden(fParent); 1263 } 1264 1265 1266 void 1267 BTextControl::LabelLayoutItem::SetVisible(bool visible) 1268 { 1269 // not allowed 1270 } 1271 1272 1273 BRect 1274 BTextControl::LabelLayoutItem::Frame() 1275 { 1276 return fFrame; 1277 } 1278 1279 1280 void 1281 BTextControl::LabelLayoutItem::SetFrame(BRect frame) 1282 { 1283 fFrame = frame; 1284 fParent->_UpdateFrame(); 1285 } 1286 1287 1288 void 1289 BTextControl::LabelLayoutItem::SetParent(BTextControl* parent) 1290 { 1291 fParent = parent; 1292 } 1293 1294 1295 BView* 1296 BTextControl::LabelLayoutItem::View() 1297 { 1298 return fParent; 1299 } 1300 1301 1302 BSize 1303 BTextControl::LabelLayoutItem::BaseMinSize() 1304 { 1305 fParent->_ValidateLayoutData(); 1306 1307 if (!fParent->Label()) 1308 return BSize(-1, -1); 1309 1310 return BSize(fParent->fLayoutData->label_width + 5, 1311 fParent->fLayoutData->label_height); 1312 } 1313 1314 1315 BSize 1316 BTextControl::LabelLayoutItem::BaseMaxSize() 1317 { 1318 return BaseMinSize(); 1319 } 1320 1321 1322 BSize 1323 BTextControl::LabelLayoutItem::BasePreferredSize() 1324 { 1325 return BaseMinSize(); 1326 } 1327 1328 1329 BAlignment 1330 BTextControl::LabelLayoutItem::BaseAlignment() 1331 { 1332 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); 1333 } 1334 1335 1336 status_t 1337 BTextControl::LabelLayoutItem::Archive(BMessage* into, bool deep) const 1338 { 1339 BArchiver archiver(into); 1340 status_t err = BAbstractLayoutItem::Archive(into, deep); 1341 if (err == B_OK) 1342 err = into->AddRect(kFrameField, fFrame); 1343 1344 return archiver.Finish(err); 1345 } 1346 1347 1348 BArchivable* 1349 BTextControl::LabelLayoutItem::Instantiate(BMessage* from) 1350 { 1351 if (validate_instantiation(from, "BTextControl::LabelLayoutItem")) 1352 return new LabelLayoutItem(from); 1353 return NULL; 1354 } 1355 1356 1357 // #pragma mark - 1358 1359 1360 BTextControl::TextViewLayoutItem::TextViewLayoutItem(BTextControl* parent) 1361 : 1362 fParent(parent), 1363 fFrame() 1364 { 1365 // by default the part right of the divider shall have an unlimited maximum 1366 // width 1367 SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 1368 } 1369 1370 1371 BTextControl::TextViewLayoutItem::TextViewLayoutItem(BMessage* from) 1372 : 1373 BAbstractLayoutItem(from), 1374 fParent(NULL), 1375 fFrame() 1376 { 1377 from->FindRect(kFrameField, &fFrame); 1378 } 1379 1380 1381 bool 1382 BTextControl::TextViewLayoutItem::IsVisible() 1383 { 1384 return !fParent->IsHidden(fParent); 1385 } 1386 1387 1388 void 1389 BTextControl::TextViewLayoutItem::SetVisible(bool visible) 1390 { 1391 // not allowed 1392 } 1393 1394 1395 BRect 1396 BTextControl::TextViewLayoutItem::Frame() 1397 { 1398 return fFrame; 1399 } 1400 1401 1402 void 1403 BTextControl::TextViewLayoutItem::SetFrame(BRect frame) 1404 { 1405 fFrame = frame; 1406 fParent->_UpdateFrame(); 1407 } 1408 1409 1410 void 1411 BTextControl::TextViewLayoutItem::SetParent(BTextControl* parent) 1412 { 1413 fParent = parent; 1414 } 1415 1416 1417 BView* 1418 BTextControl::TextViewLayoutItem::View() 1419 { 1420 return fParent; 1421 } 1422 1423 1424 BSize 1425 BTextControl::TextViewLayoutItem::BaseMinSize() 1426 { 1427 fParent->_ValidateLayoutData(); 1428 1429 BSize size = fParent->fLayoutData->text_view_min; 1430 size.width += 2 * kFrameMargin; 1431 size.height += 2 * kFrameMargin; 1432 1433 return size; 1434 } 1435 1436 1437 BSize 1438 BTextControl::TextViewLayoutItem::BaseMaxSize() 1439 { 1440 BSize size(BaseMinSize()); 1441 size.width = B_SIZE_UNLIMITED; 1442 return size; 1443 } 1444 1445 1446 BSize 1447 BTextControl::TextViewLayoutItem::BasePreferredSize() 1448 { 1449 BSize size(BaseMinSize()); 1450 // puh, no idea... 1451 size.width = 100; 1452 return size; 1453 } 1454 1455 1456 BAlignment 1457 BTextControl::TextViewLayoutItem::BaseAlignment() 1458 { 1459 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); 1460 } 1461 1462 1463 status_t 1464 BTextControl::TextViewLayoutItem::Archive(BMessage* into, bool deep) const 1465 { 1466 BArchiver archiver(into); 1467 status_t err = BAbstractLayoutItem::Archive(into, deep); 1468 if (err == B_OK) 1469 err = into->AddRect(kFrameField, fFrame); 1470 1471 return archiver.Finish(err); 1472 } 1473 1474 1475 BArchivable* 1476 BTextControl::TextViewLayoutItem::Instantiate(BMessage* from) 1477 { 1478 if (validate_instantiation(from, "BTextControl::TextViewLayoutItem")) 1479 return new TextViewLayoutItem(from); 1480 return NULL; 1481 } 1482 1483 1484 #if __GNUC__ == 2 1485 1486 1487 extern "C" void 1488 InvalidateLayout__12BTextControlb(BView* view, bool descendants) 1489 { 1490 perform_data_layout_invalidated data; 1491 data.descendants = descendants; 1492 1493 view->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data); 1494 } 1495 1496 1497 #endif 1498