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