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