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