1 /* 2 * Copyright 2015, Axel Dörfler, axeld@pinc-software.de. 3 * Copyright 2010 Stephan Aßmus <superstippi@gmx.de> 4 * Distributed under the terms of the MIT License. 5 */ 6 7 8 #include "AddressTextControl.h" 9 10 #include <Autolock.h> 11 #include <Button.h> 12 #include <Catalog.h> 13 #include <ControlLook.h> 14 #include <Clipboard.h> 15 #include <File.h> 16 #include <LayoutBuilder.h> 17 #include <Locale.h> 18 #include <LayoutUtils.h> 19 #include <NodeInfo.h> 20 #include <PopUpMenu.h> 21 #include <SeparatorView.h> 22 #include <TextView.h> 23 #include <Window.h> 24 25 #include <stdio.h> 26 #include <stdlib.h> 27 28 #include "MailApp.h" 29 #include "Messages.h" 30 #include "QueryList.h" 31 #include "TextViewCompleter.h" 32 33 34 #undef B_TRANSLATION_CONTEXT 35 #define B_TRANSLATION_CONTEXT "AddressTextControl" 36 37 38 static const uint32 kMsgAddAddress = 'adad'; 39 static const float kVerticalTextRectInset = 2.0; 40 41 42 class AddressTextControl::TextView : public BTextView { 43 private: 44 static const uint32 MSG_CLEAR = 'cler'; 45 46 public: 47 TextView(AddressTextControl* parent); 48 virtual ~TextView(); 49 50 virtual void MessageReceived(BMessage* message); 51 virtual void KeyDown(const char* bytes, int32 numBytes); 52 virtual void MakeFocus(bool focused = true); 53 54 virtual BSize MinSize(); 55 virtual BSize MaxSize(); 56 57 const BMessage* ModificationMessage() const; 58 void SetModificationMessage(BMessage* message); 59 60 void SetUpdateAutoCompleterChoices(bool update); 61 62 protected: 63 virtual void InsertText(const char* text, int32 length, 64 int32 offset, 65 const text_run_array* runs); 66 virtual void DeleteText(int32 fromOffset, int32 toOffset); 67 68 private: 69 AddressTextControl* fAddressTextControl; 70 TextViewCompleter* fAutoCompleter; 71 BString fPreviousText; 72 bool fUpdateAutoCompleterChoices; 73 BMessage* fModificationMessage; 74 }; 75 76 77 class AddressPopUpMenu : public BPopUpMenu, public QueryListener { 78 public: 79 AddressPopUpMenu(); 80 virtual ~AddressPopUpMenu(); 81 82 protected: 83 virtual void EntryCreated(QueryList& source, 84 const entry_ref& ref, ino_t node); 85 virtual void EntryRemoved(QueryList& source, 86 const node_ref& nodeRef); 87 88 private: 89 void _RebuildMenu(); 90 void _AddGroup(const char* label, const char* group, 91 PersonList& peopleList); 92 void _AddPeople(BMenu* menu, PersonList& peopleList, 93 const char* group, 94 bool addSeparator = false); 95 bool _MatchesGroup(const Person& person, 96 const char* group); 97 }; 98 99 100 class AddressTextControl::PopUpButton : public BControl { 101 public: 102 PopUpButton(); 103 virtual ~PopUpButton(); 104 105 virtual BSize MinSize(); 106 virtual BSize PreferredSize(); 107 virtual BSize MaxSize(); 108 109 virtual void MouseDown(BPoint where); 110 virtual void Draw(BRect updateRect); 111 112 private: 113 AddressPopUpMenu* fPopUpMenu; 114 }; 115 116 117 class PeopleChoiceModel : public BAutoCompleter::ChoiceModel { 118 public: 119 PeopleChoiceModel() 120 : 121 fChoices(5, true) 122 { 123 } 124 125 ~PeopleChoiceModel() 126 { 127 } 128 129 virtual void FetchChoicesFor(const BString& pattern) 130 { 131 // Remove all existing choices 132 fChoices.MakeEmpty(); 133 134 // Search through the people list for any matches 135 PersonList& peopleList = static_cast<TMailApp*>(be_app)->People(); 136 BAutolock locker(peopleList); 137 138 for (int32 index = 0; index < peopleList.CountPersons(); index++) { 139 const Person* person = peopleList.PersonAt(index); 140 141 const BString& baseText = person->Name(); 142 for (int32 addressIndex = 0; 143 addressIndex < person->CountAddresses(); addressIndex++) { 144 BString choiceText = baseText; 145 choiceText << " <" << person->AddressAt(addressIndex) << ">"; 146 147 int32 match = choiceText.IFindFirst(pattern); 148 if (match < 0) 149 continue; 150 151 fChoices.AddItem(new BAutoCompleter::Choice(choiceText, 152 choiceText, match, pattern.Length())); 153 } 154 } 155 156 locker.Unlock(); 157 fChoices.SortItems(_CompareChoices); 158 } 159 160 virtual int32 CountChoices() const 161 { 162 return fChoices.CountItems(); 163 } 164 165 virtual const BAutoCompleter::Choice* ChoiceAt(int32 index) const 166 { 167 return fChoices.ItemAt(index); 168 } 169 170 static int _CompareChoices(const BAutoCompleter::Choice* a, 171 const BAutoCompleter::Choice* b) 172 { 173 return a->DisplayText().Compare(b->DisplayText()); 174 } 175 176 private: 177 BObjectList<BAutoCompleter::Choice> fChoices; 178 }; 179 180 181 // #pragma mark - TextView 182 183 184 AddressTextControl::TextView::TextView(AddressTextControl* parent) 185 : 186 BTextView("mail"), 187 fAddressTextControl(parent), 188 fAutoCompleter(new TextViewCompleter(this, 189 new PeopleChoiceModel())), 190 fPreviousText(""), 191 fUpdateAutoCompleterChoices(true) 192 { 193 MakeResizable(true); 194 SetStylable(true); 195 fAutoCompleter->SetModificationsReported(true); 196 } 197 198 199 AddressTextControl::TextView::~TextView() 200 { 201 delete fAutoCompleter; 202 } 203 204 205 void 206 AddressTextControl::TextView::MessageReceived(BMessage* message) 207 { 208 switch (message->what) { 209 case MSG_CLEAR: 210 SetText(""); 211 break; 212 213 default: 214 BTextView::MessageReceived(message); 215 break; 216 } 217 } 218 219 220 void 221 AddressTextControl::TextView::KeyDown(const char* bytes, int32 numBytes) 222 { 223 switch (bytes[0]) { 224 case B_TAB: 225 BView::KeyDown(bytes, numBytes); 226 break; 227 228 case B_ESCAPE: 229 // Revert to text as it was when we received keyboard focus. 230 SetText(fPreviousText.String()); 231 SelectAll(); 232 break; 233 234 case B_RETURN: 235 // Don't let this through to the text view. 236 break; 237 238 default: 239 BTextView::KeyDown(bytes, numBytes); 240 break; 241 } 242 } 243 244 void 245 AddressTextControl::TextView::MakeFocus(bool focus) 246 { 247 if (focus == IsFocus()) 248 return; 249 250 BTextView::MakeFocus(focus); 251 252 if (focus) { 253 fPreviousText = Text(); 254 SelectAll(); 255 } 256 257 fAddressTextControl->Invalidate(); 258 } 259 260 261 BSize 262 AddressTextControl::TextView::MinSize() 263 { 264 BSize min; 265 min.height = ceilf(LineHeight(0) + kVerticalTextRectInset); 266 // we always add at least one pixel vertical inset top/bottom for 267 // the text rect. 268 min.width = min.height * 3; 269 return BLayoutUtils::ComposeSize(ExplicitMinSize(), min); 270 } 271 272 273 BSize 274 AddressTextControl::TextView::MaxSize() 275 { 276 BSize max(MinSize()); 277 max.width = B_SIZE_UNLIMITED; 278 return BLayoutUtils::ComposeSize(ExplicitMaxSize(), max); 279 } 280 281 282 const BMessage* 283 AddressTextControl::TextView::ModificationMessage() const 284 { 285 return fModificationMessage; 286 } 287 288 289 void 290 AddressTextControl::TextView::SetModificationMessage(BMessage* message) 291 { 292 fModificationMessage = message; 293 } 294 295 296 void 297 AddressTextControl::TextView::SetUpdateAutoCompleterChoices(bool update) 298 { 299 fUpdateAutoCompleterChoices = update; 300 } 301 302 303 void 304 AddressTextControl::TextView::InsertText(const char* text, 305 int32 length, int32 offset, const text_run_array* runs) 306 { 307 if (!strncmp(text, "mailto:", 7)) { 308 text += 7; 309 length -= 7; 310 if (runs != NULL) 311 runs = NULL; 312 } 313 314 // Filter all line breaks, note that text is not terminated. 315 if (length == 1) { 316 if (*text == '\n' || *text == '\r') 317 BTextView::InsertText(" ", 1, offset, runs); 318 else 319 BTextView::InsertText(text, 1, offset, runs); 320 } else { 321 BString filteredText(text, length); 322 filteredText.ReplaceAll('\n', ' '); 323 filteredText.ReplaceAll('\r', ' '); 324 BTextView::InsertText(filteredText.String(), length, offset, 325 runs); 326 } 327 328 // TODO: change E-mail representation 329 /* 330 // Make the base URL part bold. 331 BString text(Text(), TextLength()); 332 int32 baseUrlStart = text.FindFirst("://"); 333 if (baseUrlStart >= 0) 334 baseUrlStart += 3; 335 else 336 baseUrlStart = 0; 337 int32 baseUrlEnd = text.FindFirst("/", baseUrlStart); 338 if (baseUrlEnd < 0) 339 baseUrlEnd = TextLength(); 340 341 BFont font; 342 GetFont(&font); 343 const rgb_color black = (rgb_color) { 0, 0, 0, 255 }; 344 const rgb_color gray = (rgb_color) { 60, 60, 60, 255 }; 345 if (baseUrlStart > 0) 346 SetFontAndColor(0, baseUrlStart, &font, B_FONT_ALL, &gray); 347 if (baseUrlEnd > baseUrlStart) { 348 font.SetFace(B_BOLD_FACE); 349 SetFontAndColor(baseUrlStart, baseUrlEnd, &font, B_FONT_ALL, &black); 350 } 351 if (baseUrlEnd < TextLength()) { 352 font.SetFace(B_REGULAR_FACE); 353 SetFontAndColor(baseUrlEnd, TextLength(), &font, B_FONT_ALL, &gray); 354 } 355 */ 356 fAutoCompleter->TextModified(fUpdateAutoCompleterChoices); 357 fAddressTextControl->InvokeNotify(fModificationMessage, 358 B_CONTROL_MODIFIED); 359 } 360 361 362 void 363 AddressTextControl::TextView::DeleteText(int32 fromOffset, 364 int32 toOffset) 365 { 366 BTextView::DeleteText(fromOffset, toOffset); 367 368 fAutoCompleter->TextModified(fUpdateAutoCompleterChoices); 369 fAddressTextControl->InvokeNotify(fModificationMessage, 370 B_CONTROL_MODIFIED); 371 } 372 373 374 // #pragma mark - PopUpButton 375 376 377 AddressTextControl::PopUpButton::PopUpButton() 378 : 379 BControl(NULL, NULL, NULL, B_WILL_DRAW) 380 { 381 fPopUpMenu = new AddressPopUpMenu(); 382 } 383 384 385 AddressTextControl::PopUpButton::~PopUpButton() 386 { 387 delete fPopUpMenu; 388 } 389 390 391 BSize 392 AddressTextControl::PopUpButton::MinSize() 393 { 394 // TODO: BControlLook does not give us any size information! 395 return BSize(10, 10); 396 } 397 398 399 BSize 400 AddressTextControl::PopUpButton::PreferredSize() 401 { 402 return BSize(10, B_SIZE_UNSET); 403 } 404 405 406 BSize 407 AddressTextControl::PopUpButton::MaxSize() 408 { 409 return BSize(10, B_SIZE_UNLIMITED); 410 } 411 412 413 void 414 AddressTextControl::PopUpButton::MouseDown(BPoint where) 415 { 416 if (fPopUpMenu->Parent() != NULL) 417 return; 418 419 float width; 420 fPopUpMenu->GetPreferredSize(&width, NULL); 421 fPopUpMenu->SetTargetForItems(Parent()); 422 423 BPoint point(Bounds().Width() - width, Bounds().Height() + 2); 424 ConvertToScreen(&point); 425 fPopUpMenu->Go(point, true, true, true); 426 } 427 428 429 void 430 AddressTextControl::PopUpButton::Draw(BRect updateRect) 431 { 432 uint32 flags = 0; 433 if (!IsEnabled()) 434 flags |= BControlLook::B_DISABLED; 435 436 if (IsFocus() && Window()->IsActive()) 437 flags |= BControlLook::B_FOCUSED; 438 439 rgb_color base = ui_color(B_MENU_BACKGROUND_COLOR); 440 BRect rect = Bounds(); 441 be_control_look->DrawMenuFieldBackground(this, rect, 442 updateRect, base, true, flags); 443 } 444 445 446 // #pragma mark - PopUpMenu 447 448 449 AddressPopUpMenu::AddressPopUpMenu() 450 : 451 BPopUpMenu("", true) 452 { 453 static_cast<TMailApp*>(be_app)->PeopleQueryList().AddListener(this); 454 } 455 456 457 AddressPopUpMenu::~AddressPopUpMenu() 458 { 459 static_cast<TMailApp*>(be_app)->PeopleQueryList().RemoveListener(this); 460 } 461 462 463 void 464 AddressPopUpMenu::EntryCreated(QueryList& source, 465 const entry_ref& ref, ino_t node) 466 { 467 _RebuildMenu(); 468 } 469 470 471 void 472 AddressPopUpMenu::EntryRemoved(QueryList& source, 473 const node_ref& nodeRef) 474 { 475 _RebuildMenu(); 476 } 477 478 479 void 480 AddressPopUpMenu::_RebuildMenu() 481 { 482 // Remove all items 483 int32 index = CountItems(); 484 while (index-- > 0) { 485 delete RemoveItem(index); 486 } 487 488 // Rebuild contents 489 PersonList& peopleList = static_cast<TMailApp*>(be_app)->People(); 490 BAutolock locker(peopleList); 491 492 if (peopleList.CountPersons() > 0) 493 _AddGroup(B_TRANSLATE("All people"), NULL, peopleList); 494 495 GroupList& groupList = static_cast<TMailApp*>(be_app)->PeopleGroups(); 496 BAutolock groupLocker(groupList); 497 498 for (int32 index = 0; index < groupList.CountGroups(); index++) { 499 BString group = groupList.GroupAt(index); 500 _AddGroup(group, group, peopleList); 501 } 502 503 groupLocker.Unlock(); 504 505 _AddPeople(this, peopleList, "", true); 506 } 507 508 509 void 510 AddressPopUpMenu::_AddGroup(const char* label, const char* group, 511 PersonList& peopleList) 512 { 513 BMenu* menu = new BMenu(label); 514 AddItem(menu); 515 menu->Superitem()->SetMessage(new BMessage(kMsgAddAddress)); 516 517 _AddPeople(menu, peopleList, group); 518 } 519 520 521 void 522 AddressPopUpMenu::_AddPeople(BMenu* menu, PersonList& peopleList, 523 const char* group, bool addSeparator) 524 { 525 for (int32 index = 0; index < peopleList.CountPersons(); index++) { 526 const Person* person = peopleList.PersonAt(index); 527 if (!_MatchesGroup(*person, group)) 528 continue; 529 530 if (person->CountAddresses() != 0 && addSeparator) { 531 menu->AddSeparatorItem(); 532 addSeparator = false; 533 } 534 535 for (int32 addressIndex = 0; addressIndex < person->CountAddresses(); 536 addressIndex++) { 537 BString email = person->Name(); 538 email << " <" << person->AddressAt(addressIndex) << ">"; 539 540 BMessage* message = new BMessage(kMsgAddAddress); 541 message->AddString("email", email); 542 menu->AddItem(new BMenuItem(email, message)); 543 544 if (menu->Superitem() != NULL) 545 menu->Superitem()->Message()->AddString("email", email); 546 } 547 } 548 } 549 550 551 bool 552 AddressPopUpMenu::_MatchesGroup(const Person& person, const char* group) 553 { 554 if (group == NULL) 555 return true; 556 557 if (group[0] == '\0') 558 return person.CountGroups() == 0; 559 560 return person.IsInGroup(group); 561 } 562 563 564 // TODO: sort lists! 565 /* 566 void 567 AddressTextControl::PopUpMenu::_AddPersonItem(const entry_ref *ref, ino_t node, BString &name, 568 BString &email, const char *attr, BMenu *groupMenu, BMenuItem *superItem) 569 { 570 BString label; 571 BString sortKey; 572 // For alphabetical order sorting, usually last name. 573 574 // if we have no Name, just use the email address 575 if (name.Length() == 0) { 576 label = email; 577 sortKey = email; 578 } else { 579 // otherwise, pretty-format it 580 label << name << " (" << email << ")"; 581 582 // Extract the last name (last word in the name), 583 // removing trailing and leading spaces. 584 const char *nameStart = name.String(); 585 const char *string = nameStart + strlen(nameStart) - 1; 586 const char *wordEnd; 587 588 while (string >= nameStart && isspace(*string)) 589 string--; 590 wordEnd = string + 1; // Points to just after last word. 591 while (string >= nameStart && !isspace(*string)) 592 string--; 593 string++; // Point to first letter in the word. 594 if (wordEnd > string) 595 sortKey.SetTo(string, wordEnd - string); 596 else // Blank name, pretend that the last name is after it. 597 string = nameStart + strlen(nameStart); 598 599 // Append the first names to the end, so that people with the same last 600 // name get sorted by first name. Note no space between the end of the 601 // last name and the start of the first names, but that shouldn't 602 // matter for sorting. 603 sortKey.Append(nameStart, string - nameStart); 604 } 605 } 606 */ 607 608 // #pragma mark - AddressTextControl 609 610 611 AddressTextControl::AddressTextControl(const char* name, BMessage* message) 612 : 613 BControl(name, NULL, message, B_WILL_DRAW), 614 fRefDropMenu(NULL), 615 fWindowActive(false), 616 fEditable(true) 617 { 618 fTextView = new TextView(this); 619 fTextView->SetExplicitMinSize(BSize(100, B_SIZE_UNSET)); 620 fPopUpButton = new PopUpButton(); 621 622 BLayoutBuilder::Group<>(this, B_HORIZONTAL, 0) 623 .SetInsets(2) 624 .Add(fTextView) 625 .Add(fPopUpButton); 626 627 SetFlags(Flags() | B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE); 628 SetLowUIColor(ViewUIColor()); 629 SetViewUIColor(fTextView->ViewUIColor()); 630 631 SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH, 632 B_ALIGN_VERTICAL_CENTER)); 633 634 SetEnabled(fEditable); 635 // Sets the B_NAVIGABLE flag on the TextView 636 } 637 638 639 AddressTextControl::~AddressTextControl() 640 { 641 } 642 643 644 void 645 AddressTextControl::AttachedToWindow() 646 { 647 BControl::AttachedToWindow(); 648 fWindowActive = Window()->IsActive(); 649 } 650 651 652 void 653 AddressTextControl::WindowActivated(bool active) 654 { 655 BControl::WindowActivated(active); 656 if (fWindowActive != active) { 657 fWindowActive = active; 658 Invalidate(); 659 } 660 } 661 662 663 void 664 AddressTextControl::Draw(BRect updateRect) 665 { 666 if (!IsEditable()) 667 return; 668 669 BRect bounds(Bounds()); 670 rgb_color base(LowColor()); 671 uint32 flags = 0; 672 if (!IsEnabled()) 673 flags |= BControlLook::B_DISABLED; 674 if (fWindowActive && fTextView->IsFocus()) 675 flags |= BControlLook::B_FOCUSED; 676 be_control_look->DrawTextControlBorder(this, bounds, updateRect, base, 677 flags); 678 } 679 680 681 void 682 AddressTextControl::MakeFocus(bool focus) 683 { 684 // Forward this to the text view, we never accept focus ourselves. 685 fTextView->MakeFocus(focus); 686 } 687 688 689 void 690 AddressTextControl::SetEnabled(bool enabled) 691 { 692 BControl::SetEnabled(enabled); 693 fTextView->MakeEditable(enabled && fEditable); 694 if (enabled) 695 fTextView->SetFlags(fTextView->Flags() | B_NAVIGABLE); 696 else 697 fTextView->SetFlags(fTextView->Flags() & ~B_NAVIGABLE); 698 699 fPopUpButton->SetEnabled(enabled); 700 701 _UpdateTextViewColors(); 702 } 703 704 705 void 706 AddressTextControl::MessageReceived(BMessage* message) 707 { 708 switch (message->what) { 709 case B_SIMPLE_DATA: 710 { 711 int32 buttons = -1; 712 BPoint point; 713 if (message->FindInt32("buttons", &buttons) != B_OK) 714 buttons = B_PRIMARY_MOUSE_BUTTON; 715 716 if (buttons != B_PRIMARY_MOUSE_BUTTON 717 && message->FindPoint("_drop_point_", &point) != B_OK) 718 return; 719 720 BMessage forwardRefs(B_REFS_RECEIVED); 721 bool forward = false; 722 723 entry_ref ref; 724 for (int32 index = 0;message->FindRef("refs", index, &ref) == B_OK; index++) { 725 BFile file(&ref, B_READ_ONLY); 726 if (file.InitCheck() == B_NO_ERROR) { 727 BNodeInfo info(&file); 728 char type[B_FILE_NAME_LENGTH]; 729 info.GetType(type); 730 731 if (!strcmp(type,"application/x-person")) { 732 // add person's E-mail address to the To: field 733 734 BString attr = ""; 735 if (buttons == B_PRIMARY_MOUSE_BUTTON) { 736 if (message->FindString("attr", &attr) < B_OK) 737 attr = "META:email"; 738 } else { 739 BNode node(&ref); 740 node.RewindAttrs(); 741 742 char buffer[B_ATTR_NAME_LENGTH]; 743 744 delete fRefDropMenu; 745 fRefDropMenu = new BPopUpMenu("RecipientMenu"); 746 747 while (node.GetNextAttrName(buffer) == B_OK) { 748 if (strstr(buffer, "email") == NULL) 749 continue; 750 751 attr = buffer; 752 753 BString address; 754 node.ReadAttrString(buffer, &address); 755 if (address.Length() <= 0) 756 continue; 757 758 BMessage* itemMsg 759 = new BMessage(kMsgAddAddress); 760 itemMsg->AddString("email", address.String()); 761 762 BMenuItem* item = new BMenuItem( 763 address.String(), itemMsg); 764 fRefDropMenu->AddItem(item); 765 } 766 767 if (fRefDropMenu->CountItems() > 1) { 768 fRefDropMenu->SetTargetForItems(this); 769 fRefDropMenu->Go(point, true, true, true); 770 return; 771 } else { 772 delete fRefDropMenu; 773 fRefDropMenu = NULL; 774 } 775 } 776 777 BString email; 778 file.ReadAttrString(attr.String(), &email); 779 780 // we got something... 781 if (email.Length() > 0) { 782 // see if we can get a username as well 783 BString name; 784 file.ReadAttrString("META:name", &name); 785 786 BString address; 787 if (name.Length() == 0) { 788 // if we have no Name, just use the email address 789 address = email; 790 } else { 791 // otherwise, pretty-format it 792 address << "\"" << name << "\" <" << email << ">"; 793 } 794 795 _AddAddress(address); 796 } 797 } else { 798 forward = true; 799 forwardRefs.AddRef("refs", &ref); 800 } 801 } 802 } 803 804 if (forward) { 805 // Pass on to parent 806 Window()->PostMessage(&forwardRefs, Parent()); 807 } 808 break; 809 } 810 811 case M_SELECT: 812 { 813 BTextView *textView = (BTextView *)ChildAt(0); 814 if (textView != NULL) 815 textView->Select(0, textView->TextLength()); 816 break; 817 } 818 819 case kMsgAddAddress: 820 { 821 const char* email; 822 for (int32 index = 0; 823 message->FindString("email", index++, &email) == B_OK;) 824 _AddAddress(email); 825 break; 826 } 827 828 default: 829 BControl::MessageReceived(message); 830 break; 831 } 832 } 833 834 835 const BMessage* 836 AddressTextControl::ModificationMessage() const 837 { 838 return fTextView->ModificationMessage(); 839 } 840 841 842 void 843 AddressTextControl::SetModificationMessage(BMessage* message) 844 { 845 fTextView->SetModificationMessage(message); 846 } 847 848 849 bool 850 AddressTextControl::IsEditable() const 851 { 852 return fEditable; 853 } 854 855 856 void 857 AddressTextControl::SetEditable(bool editable) 858 { 859 fTextView->MakeEditable(IsEnabled() && editable); 860 fTextView->MakeSelectable(IsEnabled() && editable); 861 fEditable = editable; 862 863 if (editable && fPopUpButton->IsHidden(this)) 864 fPopUpButton->Show(); 865 else if (!editable && !fPopUpButton->IsHidden(this)) 866 fPopUpButton->Hide(); 867 } 868 869 870 void 871 AddressTextControl::SetText(const char* text) 872 { 873 if (text == NULL || Text() == NULL || strcmp(Text(), text) != 0) { 874 fTextView->SetUpdateAutoCompleterChoices(false); 875 fTextView->SetText(text); 876 fTextView->SetUpdateAutoCompleterChoices(true); 877 } 878 } 879 880 881 const char* 882 AddressTextControl::Text() const 883 { 884 return fTextView->Text(); 885 } 886 887 888 int32 889 AddressTextControl::TextLength() const 890 { 891 return fTextView->TextLength(); 892 } 893 894 895 void 896 AddressTextControl::GetSelection(int32* start, int32* end) const 897 { 898 fTextView->GetSelection(start, end); 899 } 900 901 902 void 903 AddressTextControl::Select(int32 start, int32 end) 904 { 905 fTextView->Select(start, end); 906 } 907 908 909 void 910 AddressTextControl::SelectAll() 911 { 912 fTextView->Select(0, TextLength()); 913 } 914 915 916 bool 917 AddressTextControl::HasFocus() 918 { 919 return fTextView->IsFocus(); 920 } 921 922 923 void 924 AddressTextControl::_AddAddress(const char* text) 925 { 926 int last = fTextView->TextLength(); 927 if (last != 0) { 928 fTextView->Select(last, last); 929 // TODO: test if there is already a ',' 930 fTextView->Insert(", "); 931 } 932 fTextView->Insert(text); 933 } 934 935 936 void 937 AddressTextControl::_UpdateTextViewColors() 938 { 939 BFont font; 940 fTextView->GetFontAndColor(0, &font); 941 942 rgb_color textColor; 943 if (!IsEditable() || IsEnabled()) 944 textColor = ui_color(B_DOCUMENT_TEXT_COLOR); 945 else { 946 textColor = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 947 B_DISABLED_LABEL_TINT); 948 } 949 950 fTextView->SetFontAndColor(&font, B_FONT_ALL, &textColor); 951 952 rgb_color color; 953 if (!IsEditable()) 954 color = ui_color(B_PANEL_BACKGROUND_COLOR); 955 else if (IsEnabled()) 956 color = ui_color(B_DOCUMENT_BACKGROUND_COLOR); 957 else { 958 color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR), 959 B_LIGHTEN_2_TINT); 960 } 961 962 fTextView->SetViewColor(color); 963 fTextView->SetLowColor(color); 964 } 965