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 49namespace { 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 56static 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 68class BTextControl::LabelLayoutItem : public BAbstractLayoutItem { 69public: 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 92private: 93 BTextControl* fParent; 94 BRect fFrame; 95}; 96 97 98class BTextControl::TextViewLayoutItem : public BAbstractLayoutItem { 99public: 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); 121private: 122 BTextControl* fParent; 123 BRect fFrame; 124}; 125 126 127struct 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 154static const int32 kFrameMargin = 2; 155static const int32 kLabelInputSpacing = 3; 156 157 158BTextControl::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 169BTextControl::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 180BTextControl::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 192BTextControl::~BTextControl() 193{ 194 SetModificationMessage(NULL); 195 delete fLayoutData; 196} 197 198 199BTextControl::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 224BArchivable* 225BTextControl::Instantiate(BMessage* archive) 226{ 227 if (validate_instantiation(archive, "BTextControl")) 228 return new BTextControl(archive); 229 230 return NULL; 231} 232 233 234status_t 235BTextControl::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 257status_t 258BTextControl::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 277status_t 278BTextControl::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 310void 311BTextControl::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 329const char * 330BTextControl::Text() const 331{ 332 return fText->Text(); 333} 334 335 336void 337BTextControl::SetValue(int32 value) 338{ 339 BControl::SetValue(value); 340} 341 342 343status_t 344BTextControl::Invoke(BMessage *message) 345{ 346 return BControl::Invoke(message); 347} 348 349 350BTextView * 351BTextControl::TextView() const 352{ 353 return fText; 354} 355 356 357void 358BTextControl::SetModificationMessage(BMessage *message) 359{ 360 delete fModificationMessage; 361 fModificationMessage = message; 362} 363 364 365BMessage * 366BTextControl::ModificationMessage() const 367{ 368 return fModificationMessage; 369} 370 371 372void 373BTextControl::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 385void 386BTextControl::GetAlignment(alignment* _label, alignment* _text) const 387{ 388 if (_label) 389 *_label = fLabelAlign; 390 if (_text) 391 *_text = fText->Alignment(); 392} 393 394 395void 396BTextControl::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 409float 410BTextControl::Divider() const 411{ 412 return fDivider; 413} 414 415 416void 417BTextControl::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 535void 536BTextControl::MouseDown(BPoint where) 537{ 538 if (!fText->IsFocus()) 539 fText->MakeFocus(true); 540} 541 542 543void 544BTextControl::AttachedToWindow() 545{ 546 BControl::AttachedToWindow(); 547 548 _UpdateTextViewColors(IsEnabled()); 549 fText->MakeEditable(IsEnabled()); 550} 551 552 553void 554BTextControl::MakeFocus(bool state) 555{ 556 if (state != fText->IsFocus()) { 557 fText->MakeFocus(state); 558 559 if (state) 560 fText->SelectAll(); 561 } 562} 563 564 565void 566BTextControl::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 588void 589BTextControl::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 610void 611BTextControl::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 624void 625BTextControl::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 645void 646BTextControl::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 683BHandler * 684BTextControl::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 698status_t 699BTextControl::GetSupportedSuites(BMessage *data) 700{ 701 return BControl::GetSupportedSuites(data); 702} 703 704 705void 706BTextControl::MouseUp(BPoint pt) 707{ 708 BControl::MouseUp(pt); 709} 710 711 712void 713BTextControl::MouseMoved(BPoint pt, uint32 code, const BMessage *msg) 714{ 715 BControl::MouseMoved(pt, code, msg); 716} 717 718 719void 720BTextControl::DetachedFromWindow() 721{ 722 BControl::DetachedFromWindow(); 723} 724 725 726void 727BTextControl::AllAttached() 728{ 729 BControl::AllAttached(); 730} 731 732 733void 734BTextControl::AllDetached() 735{ 736 BControl::AllDetached(); 737} 738 739 740void 741BTextControl::FrameMoved(BPoint newPosition) 742{ 743 BControl::FrameMoved(newPosition); 744} 745 746 747void 748BTextControl::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 803void 804BTextControl::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 819BSize 820BTextControl::MinSize() 821{ 822 CALLED(); 823 824 _ValidateLayoutData(); 825 return BLayoutUtils::ComposeSize(ExplicitMinSize(), fLayoutData->min); 826} 827 828 829BSize 830BTextControl::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 843BSize 844BTextControl::PreferredSize() 845{ 846 CALLED(); 847 848 _ValidateLayoutData(); 849 return BLayoutUtils::ComposeSize(ExplicitPreferredSize(), fLayoutData->min); 850} 851 852 853BLayoutItem* 854BTextControl::CreateLabelLayoutItem() 855{ 856 if (!fLayoutData->label_layout_item) 857 fLayoutData->label_layout_item = new LabelLayoutItem(this); 858 return fLayoutData->label_layout_item; 859} 860 861 862BLayoutItem* 863BTextControl::CreateTextViewLayoutItem() 864{ 865 if (!fLayoutData->text_view_layout_item) 866 fLayoutData->text_view_layout_item = new TextViewLayoutItem(this); 867 return fLayoutData->text_view_layout_item; 868} 869 870 871void 872BTextControl::LayoutInvalidated(bool descendants) 873{ 874 CALLED(); 875 876 fLayoutData->valid = false; 877} 878 879 880void 881BTextControl::DoLayout() 882{ 883 // Bail out, if we shan't do layout. 884 if (!(Flags() & B_SUPPORTS_LAYOUT)) 885 return; 886 887 CALLED(); 888 889 // If the user set a layout, we let the base class version call its 890 // hook. 891 if (GetLayout()) { 892 BView::DoLayout(); 893 return; 894 } 895 896 _ValidateLayoutData(); 897 898 // validate current size 899 BSize size(Bounds().Size()); 900 if (size.width < fLayoutData->min.width) 901 size.width = fLayoutData->min.width; 902 if (size.height < fLayoutData->min.height) 903 size.height = fLayoutData->min.height; 904 905 BRect dirty(fText->Frame()); 906 BRect textFrame; 907 908 // divider 909 float divider = 0; 910 if (fLayoutData->text_view_layout_item != NULL) { 911 if (fLayoutData->label_layout_item != NULL) { 912 // We have layout items. They define the divider location. 913 divider = fabs(fLayoutData->text_view_layout_item->Frame().left 914 - fLayoutData->label_layout_item->Frame().left); 915 } 916 textFrame = fLayoutData->text_view_layout_item->FrameInParent(); 917 } else { 918 if (fLayoutData->label_width > 0) { 919 divider = fLayoutData->label_width 920 + be_control_look->DefaultLabelSpacing(); 921 } 922 textFrame.Set(divider, 0, size.width, size.height); 923 } 924 925 // place the text view and set the divider 926 textFrame.InsetBy(kFrameMargin, kFrameMargin); 927 BLayoutUtils::AlignInFrame(fText, textFrame); 928 929 fDivider = divider; 930 931 // invalidate dirty region 932 dirty = dirty | fText->Frame(); 933 dirty.InsetBy(-kFrameMargin, -kFrameMargin); 934 935 Invalidate(dirty); 936} 937 938 939// #pragma mark - 940 941 942status_t 943BTextControl::Perform(perform_code code, void* _data) 944{ 945 switch (code) { 946 case PERFORM_CODE_MIN_SIZE: 947 ((perform_data_min_size*)_data)->return_value 948 = BTextControl::MinSize(); 949 return B_OK; 950 case PERFORM_CODE_MAX_SIZE: 951 ((perform_data_max_size*)_data)->return_value 952 = BTextControl::MaxSize(); 953 return B_OK; 954 case PERFORM_CODE_PREFERRED_SIZE: 955 ((perform_data_preferred_size*)_data)->return_value 956 = BTextControl::PreferredSize(); 957 return B_OK; 958 case PERFORM_CODE_LAYOUT_ALIGNMENT: 959 ((perform_data_layout_alignment*)_data)->return_value 960 = BTextControl::LayoutAlignment(); 961 return B_OK; 962 case PERFORM_CODE_HAS_HEIGHT_FOR_WIDTH: 963 ((perform_data_has_height_for_width*)_data)->return_value 964 = BTextControl::HasHeightForWidth(); 965 return B_OK; 966 case PERFORM_CODE_GET_HEIGHT_FOR_WIDTH: 967 { 968 perform_data_get_height_for_width* data 969 = (perform_data_get_height_for_width*)_data; 970 BTextControl::GetHeightForWidth(data->width, &data->min, &data->max, 971 &data->preferred); 972 return B_OK; 973} 974 case PERFORM_CODE_SET_LAYOUT: 975 { 976 perform_data_set_layout* data = (perform_data_set_layout*)_data; 977 BTextControl::SetLayout(data->layout); 978 return B_OK; 979 } 980 case PERFORM_CODE_LAYOUT_INVALIDATED: 981 { 982 perform_data_layout_invalidated* data 983 = (perform_data_layout_invalidated*)_data; 984 BTextControl::LayoutInvalidated(data->descendants); 985 return B_OK; 986 } 987 case PERFORM_CODE_DO_LAYOUT: 988 { 989 BTextControl::DoLayout(); 990 return B_OK; 991 } 992 case PERFORM_CODE_ALL_UNARCHIVED: 993 { 994 perform_data_all_unarchived* data 995 = (perform_data_all_unarchived*)_data; 996 997 data->return_value = BTextControl::AllUnarchived(data->archive); 998 return B_OK; 999 } 1000 case PERFORM_CODE_ALL_ARCHIVED: 1001 { 1002 perform_data_all_archived* data 1003 = (perform_data_all_archived*)_data; 1004 1005 data->return_value = BTextControl::AllArchived(data->archive); 1006 return B_OK; 1007 } 1008 } 1009 1010 return BControl::Perform(code, _data); 1011} 1012 1013 1014void BTextControl::_ReservedTextControl1() {} 1015void BTextControl::_ReservedTextControl2() {} 1016void BTextControl::_ReservedTextControl3() {} 1017void BTextControl::_ReservedTextControl4() {} 1018 1019 1020BTextControl & 1021BTextControl::operator=(const BTextControl&) 1022{ 1023 return *this; 1024} 1025 1026 1027void 1028BTextControl::_UpdateTextViewColors(bool enabled) 1029{ 1030 rgb_color textColor; 1031 rgb_color color; 1032 BFont font; 1033 1034 fText->GetFontAndColor(0, &font); 1035 1036 if (enabled) 1037 textColor = ui_color(B_DOCUMENT_TEXT_COLOR); 1038 else { 1039 textColor = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 1040 B_DISABLED_LABEL_TINT); 1041 } 1042 1043 fText->SetFontAndColor(&font, B_FONT_ALL, &textColor); 1044 1045 if (enabled) { 1046 color = ui_color(B_DOCUMENT_BACKGROUND_COLOR); 1047 } else { 1048 color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 1049 B_LIGHTEN_2_TINT); 1050 } 1051 1052 fText->SetViewColor(color); 1053 fText->SetLowColor(color); 1054} 1055 1056 1057void 1058BTextControl::_CommitValue() 1059{ 1060} 1061 1062 1063void 1064BTextControl::_InitData(const char* label, const BMessage* archive) 1065{ 1066 BRect bounds(Bounds()); 1067 1068 fText = NULL; 1069 fModificationMessage = NULL; 1070 fLabelAlign = B_ALIGN_LEFT; 1071 fDivider = 0.0f; 1072 fLayoutData = new LayoutData(bounds.Width(), bounds.Height()); 1073 1074 int32 flags = 0; 1075 1076 BFont font(be_plain_font); 1077 1078 if (!archive || !archive->HasString("_fname")) 1079 flags |= B_FONT_FAMILY_AND_STYLE; 1080 1081 if (!archive || !archive->HasFloat("_fflt")) 1082 flags |= B_FONT_SIZE; 1083 1084 if (flags != 0) 1085 SetFont(&font, flags); 1086 1087 if (label) 1088 fDivider = floorf(bounds.Width() / 2.0f); 1089} 1090 1091 1092void 1093BTextControl::_InitText(const char* initialText, const BMessage* archive) 1094{ 1095 if (archive) 1096 fText = static_cast<BPrivate::_BTextInput_*>(FindView("_input_")); 1097 1098 if (fText == NULL) { 1099 BRect bounds(Bounds()); 1100 BRect frame(fDivider, bounds.top, bounds.right, bounds.bottom); 1101 // we are stroking the frame around the text view, which 1102 // is 2 pixels wide 1103 frame.InsetBy(kFrameMargin, kFrameMargin); 1104 BRect textRect(frame.OffsetToCopy(B_ORIGIN)); 1105 1106 fText = new BPrivate::_BTextInput_(frame, textRect, 1107 B_FOLLOW_ALL, B_WILL_DRAW | B_FRAME_EVENTS 1108 | (Flags() & B_NAVIGABLE)); 1109 AddChild(fText); 1110 1111 SetText(initialText); 1112 fText->SetAlignment(B_ALIGN_LEFT); 1113 fText->AlignTextRect(); 1114 } 1115 1116 // Although this is not strictly initializing the text view, 1117 // it cannot be done while fText is NULL, so it resides here. 1118 if (archive) { 1119 int32 labelAlignment = B_ALIGN_LEFT; 1120 int32 textAlignment = B_ALIGN_LEFT; 1121 1122 status_t err = B_OK; 1123 if (archive->HasInt32("_a_label")) 1124 err = archive->FindInt32("_a_label", &labelAlignment); 1125 1126 if (err == B_OK && archive->HasInt32("_a_text")) 1127 err = archive->FindInt32("_a_text", &textAlignment); 1128 1129 SetAlignment((alignment)labelAlignment, (alignment)textAlignment); 1130 } 1131 1132 uint32 navigableFlags = Flags() & B_NAVIGABLE; 1133 if (navigableFlags != 0) 1134 BView::SetFlags(Flags() & ~B_NAVIGABLE); 1135} 1136 1137 1138void 1139BTextControl::_ValidateLayout() 1140{ 1141 CALLED(); 1142 1143 _ValidateLayoutData(); 1144 1145 ResizeTo(Bounds().Width(), fLayoutData->min.height); 1146 1147 _LayoutTextView(); 1148} 1149 1150 1151void 1152BTextControl::_LayoutTextView() 1153{ 1154 CALLED(); 1155 1156 BRect frame; 1157 if (fLayoutData->text_view_layout_item != NULL) { 1158 frame = fLayoutData->text_view_layout_item->FrameInParent(); 1159 } else { 1160 frame = Bounds(); 1161 frame.left = fDivider; 1162 } 1163 1164 // we are stroking the frame around the text view, which 1165 // is 2 pixels wide 1166 frame.InsetBy(kFrameMargin, kFrameMargin); 1167 fText->MoveTo(frame.left, frame.top); 1168 fText->ResizeTo(frame.Width(), frame.Height()); 1169 fText->AlignTextRect(); 1170 1171 TRACE("width: %.2f, height: %.2f\n", Frame().Width(), Frame().Height()); 1172 TRACE("fDivider: %.2f\n", fDivider); 1173 TRACE("fText frame: (%.2f, %.2f, %.2f, %.2f)\n", 1174 frame.left, frame.top, frame.right, frame.bottom); 1175} 1176 1177 1178void 1179BTextControl::_UpdateFrame() 1180{ 1181 CALLED(); 1182 1183 if (fLayoutData->text_view_layout_item != NULL) { 1184 BRect textFrame = fLayoutData->text_view_layout_item->Frame(); 1185 BRect labelFrame; 1186 if (fLayoutData->label_layout_item != NULL) 1187 labelFrame = fLayoutData->label_layout_item->Frame(); 1188 1189 BRect frame; 1190 if (labelFrame.IsValid()) { 1191 frame = textFrame | labelFrame; 1192 1193 // update divider 1194 fDivider = fabs(textFrame.left - labelFrame.left); 1195 } else { 1196 frame = textFrame; 1197 fDivider = 0; 1198 } 1199 1200 MoveTo(frame.left, frame.top); 1201 BSize oldSize = Bounds().Size(); 1202 ResizeTo(frame.Width(), frame.Height()); 1203 BSize newSize = Bounds().Size(); 1204 1205 // If the size changes, ResizeTo() will trigger a relayout, otherwise 1206 // we need to do that explicitly. 1207 if (newSize != oldSize) 1208 Relayout(); 1209 } 1210} 1211 1212 1213void 1214BTextControl::_ValidateLayoutData() 1215{ 1216 CALLED(); 1217 1218 if (fLayoutData->valid) 1219 return; 1220 1221 // cache font height 1222 font_height& fh = fLayoutData->font_info; 1223 GetFontHeight(&fh); 1224 1225 if (Label() != NULL) { 1226 fLayoutData->label_width = ceilf(StringWidth(Label())); 1227 fLayoutData->label_height = ceilf(fh.ascent) + ceilf(fh.descent); 1228 } else { 1229 fLayoutData->label_width = 0; 1230 fLayoutData->label_height = 0; 1231 } 1232 1233 // compute the minimal divider 1234 float divider = 0; 1235 if (fLayoutData->label_width > 0) { 1236 divider = fLayoutData->label_width 1237 + be_control_look->DefaultLabelSpacing(); 1238 } 1239 1240 // If we shan't do real layout, we let the current divider take influence. 1241 if (!(Flags() & B_SUPPORTS_LAYOUT)) 1242 divider = max_c(divider, fDivider); 1243 1244 // get the minimal (== preferred) text view size 1245 fLayoutData->text_view_min = fText->MinSize(); 1246 1247 TRACE("text view min width: %.2f\n", fLayoutData->text_view_min.width); 1248 1249 // compute our minimal (== preferred) size 1250 BSize min(fLayoutData->text_view_min); 1251 min.width += 2 * kFrameMargin; 1252 min.height += 2 * kFrameMargin; 1253 1254 if (divider > 0) 1255 min.width += divider; 1256 if (fLayoutData->label_height > min.height) 1257 min.height = fLayoutData->label_height; 1258 1259 fLayoutData->min = min; 1260 1261 fLayoutData->valid = true; 1262 ResetLayoutInvalidation(); 1263 1264 TRACE("width: %.2f, height: %.2f\n", min.width, min.height); 1265} 1266 1267 1268// #pragma mark - 1269 1270 1271BTextControl::LabelLayoutItem::LabelLayoutItem(BTextControl* parent) 1272 : 1273 fParent(parent), 1274 fFrame() 1275{ 1276} 1277 1278 1279BTextControl::LabelLayoutItem::LabelLayoutItem(BMessage* from) 1280 : 1281 BAbstractLayoutItem(from), 1282 fParent(NULL), 1283 fFrame() 1284{ 1285 from->FindRect(kFrameField, &fFrame); 1286} 1287 1288 1289bool 1290BTextControl::LabelLayoutItem::IsVisible() 1291{ 1292 return !fParent->IsHidden(fParent); 1293} 1294 1295 1296void 1297BTextControl::LabelLayoutItem::SetVisible(bool visible) 1298{ 1299 // not allowed 1300} 1301 1302 1303BRect 1304BTextControl::LabelLayoutItem::Frame() 1305{ 1306 return fFrame; 1307} 1308 1309 1310void 1311BTextControl::LabelLayoutItem::SetFrame(BRect frame) 1312{ 1313 fFrame = frame; 1314 fParent->_UpdateFrame(); 1315} 1316 1317 1318void 1319BTextControl::LabelLayoutItem::SetParent(BTextControl* parent) 1320{ 1321 fParent = parent; 1322} 1323 1324 1325BView* 1326BTextControl::LabelLayoutItem::View() 1327{ 1328 return fParent; 1329} 1330 1331 1332BSize 1333BTextControl::LabelLayoutItem::BaseMinSize() 1334{ 1335 fParent->_ValidateLayoutData(); 1336 1337 if (!fParent->Label()) 1338 return BSize(-1, -1); 1339 1340 return BSize(fParent->fLayoutData->label_width 1341 + be_control_look->DefaultLabelSpacing(), 1342 fParent->fLayoutData->label_height); 1343} 1344 1345 1346BSize 1347BTextControl::LabelLayoutItem::BaseMaxSize() 1348{ 1349 return BaseMinSize(); 1350} 1351 1352 1353BSize 1354BTextControl::LabelLayoutItem::BasePreferredSize() 1355{ 1356 return BaseMinSize(); 1357} 1358 1359 1360BAlignment 1361BTextControl::LabelLayoutItem::BaseAlignment() 1362{ 1363 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); 1364} 1365 1366 1367BRect 1368BTextControl::LabelLayoutItem::FrameInParent() const 1369{ 1370 return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top); 1371} 1372 1373 1374status_t 1375BTextControl::LabelLayoutItem::Archive(BMessage* into, bool deep) const 1376{ 1377 BArchiver archiver(into); 1378 status_t err = BAbstractLayoutItem::Archive(into, deep); 1379 if (err == B_OK) 1380 err = into->AddRect(kFrameField, fFrame); 1381 1382 return archiver.Finish(err); 1383} 1384 1385 1386BArchivable* 1387BTextControl::LabelLayoutItem::Instantiate(BMessage* from) 1388{ 1389 if (validate_instantiation(from, "BTextControl::LabelLayoutItem")) 1390 return new LabelLayoutItem(from); 1391 return NULL; 1392} 1393 1394 1395// #pragma mark - 1396 1397 1398BTextControl::TextViewLayoutItem::TextViewLayoutItem(BTextControl* parent) 1399 : 1400 fParent(parent), 1401 fFrame() 1402{ 1403 // by default the part right of the divider shall have an unlimited maximum 1404 // width 1405 SetExplicitMaxSize(BSize(B_SIZE_UNLIMITED, B_SIZE_UNSET)); 1406} 1407 1408 1409BTextControl::TextViewLayoutItem::TextViewLayoutItem(BMessage* from) 1410 : 1411 BAbstractLayoutItem(from), 1412 fParent(NULL), 1413 fFrame() 1414{ 1415 from->FindRect(kFrameField, &fFrame); 1416} 1417 1418 1419bool 1420BTextControl::TextViewLayoutItem::IsVisible() 1421{ 1422 return !fParent->IsHidden(fParent); 1423} 1424 1425 1426void 1427BTextControl::TextViewLayoutItem::SetVisible(bool visible) 1428{ 1429 // not allowed 1430} 1431 1432 1433BRect 1434BTextControl::TextViewLayoutItem::Frame() 1435{ 1436 return fFrame; 1437} 1438 1439 1440void 1441BTextControl::TextViewLayoutItem::SetFrame(BRect frame) 1442{ 1443 fFrame = frame; 1444 fParent->_UpdateFrame(); 1445} 1446 1447 1448void 1449BTextControl::TextViewLayoutItem::SetParent(BTextControl* parent) 1450{ 1451 fParent = parent; 1452} 1453 1454 1455BView* 1456BTextControl::TextViewLayoutItem::View() 1457{ 1458 return fParent; 1459} 1460 1461 1462BSize 1463BTextControl::TextViewLayoutItem::BaseMinSize() 1464{ 1465 fParent->_ValidateLayoutData(); 1466 1467 BSize size = fParent->fLayoutData->text_view_min; 1468 size.width += 2 * kFrameMargin; 1469 size.height += 2 * kFrameMargin; 1470 1471 return size; 1472} 1473 1474 1475BSize 1476BTextControl::TextViewLayoutItem::BaseMaxSize() 1477{ 1478 BSize size(BaseMinSize()); 1479 size.width = B_SIZE_UNLIMITED; 1480 return size; 1481} 1482 1483 1484BSize 1485BTextControl::TextViewLayoutItem::BasePreferredSize() 1486{ 1487 BSize size(BaseMinSize()); 1488 // puh, no idea... 1489 size.width = 100; 1490 return size; 1491} 1492 1493 1494BAlignment 1495BTextControl::TextViewLayoutItem::BaseAlignment() 1496{ 1497 return BAlignment(B_ALIGN_USE_FULL_WIDTH, B_ALIGN_USE_FULL_HEIGHT); 1498} 1499 1500 1501BRect 1502BTextControl::TextViewLayoutItem::FrameInParent() const 1503{ 1504 return fFrame.OffsetByCopy(-fParent->Frame().left, -fParent->Frame().top); 1505} 1506 1507 1508status_t 1509BTextControl::TextViewLayoutItem::Archive(BMessage* into, bool deep) const 1510{ 1511 BArchiver archiver(into); 1512 status_t err = BAbstractLayoutItem::Archive(into, deep); 1513 if (err == B_OK) 1514 err = into->AddRect(kFrameField, fFrame); 1515 1516 return archiver.Finish(err); 1517} 1518 1519 1520BArchivable* 1521BTextControl::TextViewLayoutItem::Instantiate(BMessage* from) 1522{ 1523 if (validate_instantiation(from, "BTextControl::TextViewLayoutItem")) 1524 return new TextViewLayoutItem(from); 1525 return NULL; 1526} 1527 1528 1529extern "C" void 1530B_IF_GCC_2(InvalidateLayout__12BTextControlb, 1531 _ZN12BTextControl16InvalidateLayoutEb)(BView* view, bool descendants) 1532{ 1533 perform_data_layout_invalidated data; 1534 data.descendants = descendants; 1535 1536 view->Perform(PERFORM_CODE_LAYOUT_INVALIDATED, &data); 1537} 1538 1539