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