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:
PeopleChoiceModel()119 PeopleChoiceModel()
120 :
121 fChoices(5, true)
122 {
123 }
124
~PeopleChoiceModel()125 ~PeopleChoiceModel()
126 {
127 }
128
FetchChoicesFor(const BString & pattern)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
CountChoices() const160 virtual int32 CountChoices() const
161 {
162 return fChoices.CountItems();
163 }
164
ChoiceAt(int32 index) const165 virtual const BAutoCompleter::Choice* ChoiceAt(int32 index) const
166 {
167 return fChoices.ItemAt(index);
168 }
169
_CompareChoices(const BAutoCompleter::Choice * a,const BAutoCompleter::Choice * b)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
TextView(AddressTextControl * parent)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
~TextView()199 AddressTextControl::TextView::~TextView()
200 {
201 delete fAutoCompleter;
202 }
203
204
205 void
MessageReceived(BMessage * message)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
KeyDown(const char * bytes,int32 numBytes)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
MakeFocus(bool focus)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
MinSize()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
MaxSize()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*
ModificationMessage() const283 AddressTextControl::TextView::ModificationMessage() const
284 {
285 return fModificationMessage;
286 }
287
288
289 void
SetModificationMessage(BMessage * message)290 AddressTextControl::TextView::SetModificationMessage(BMessage* message)
291 {
292 fModificationMessage = message;
293 }
294
295
296 void
SetUpdateAutoCompleterChoices(bool update)297 AddressTextControl::TextView::SetUpdateAutoCompleterChoices(bool update)
298 {
299 fUpdateAutoCompleterChoices = update;
300 }
301
302
303 void
InsertText(const char * text,int32 length,int32 offset,const text_run_array * runs)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
DeleteText(int32 fromOffset,int32 toOffset)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
PopUpButton()377 AddressTextControl::PopUpButton::PopUpButton()
378 :
379 BControl(NULL, NULL, NULL, B_WILL_DRAW)
380 {
381 fPopUpMenu = new AddressPopUpMenu();
382 }
383
384
~PopUpButton()385 AddressTextControl::PopUpButton::~PopUpButton()
386 {
387 delete fPopUpMenu;
388 }
389
390
391 BSize
MinSize()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
PreferredSize()400 AddressTextControl::PopUpButton::PreferredSize()
401 {
402 return BSize(10, B_SIZE_UNSET);
403 }
404
405
406 BSize
MaxSize()407 AddressTextControl::PopUpButton::MaxSize()
408 {
409 return BSize(10, B_SIZE_UNLIMITED);
410 }
411
412
413 void
MouseDown(BPoint where)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
Draw(BRect updateRect)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
AddressPopUpMenu()449 AddressPopUpMenu::AddressPopUpMenu()
450 :
451 BPopUpMenu("", true)
452 {
453 static_cast<TMailApp*>(be_app)->PeopleQueryList().AddListener(this);
454 }
455
456
~AddressPopUpMenu()457 AddressPopUpMenu::~AddressPopUpMenu()
458 {
459 static_cast<TMailApp*>(be_app)->PeopleQueryList().RemoveListener(this);
460 }
461
462
463 void
EntryCreated(QueryList & source,const entry_ref & ref,ino_t node)464 AddressPopUpMenu::EntryCreated(QueryList& source,
465 const entry_ref& ref, ino_t node)
466 {
467 _RebuildMenu();
468 }
469
470
471 void
EntryRemoved(QueryList & source,const node_ref & nodeRef)472 AddressPopUpMenu::EntryRemoved(QueryList& source,
473 const node_ref& nodeRef)
474 {
475 _RebuildMenu();
476 }
477
478
479 void
_RebuildMenu()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
_AddGroup(const char * label,const char * group,PersonList & peopleList)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
_AddPeople(BMenu * menu,PersonList & peopleList,const char * group,bool addSeparator)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
_MatchesGroup(const Person & person,const char * group)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
AddressTextControl(const char * name,BMessage * message)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
~AddressTextControl()639 AddressTextControl::~AddressTextControl()
640 {
641 }
642
643
644 void
AttachedToWindow()645 AddressTextControl::AttachedToWindow()
646 {
647 BControl::AttachedToWindow();
648 fWindowActive = Window()->IsActive();
649 }
650
651
652 void
WindowActivated(bool active)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
Draw(BRect updateRect)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
MakeFocus(bool focus)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
SetEnabled(bool enabled)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
MessageReceived(BMessage * message)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*
ModificationMessage() const836 AddressTextControl::ModificationMessage() const
837 {
838 return fTextView->ModificationMessage();
839 }
840
841
842 void
SetModificationMessage(BMessage * message)843 AddressTextControl::SetModificationMessage(BMessage* message)
844 {
845 fTextView->SetModificationMessage(message);
846 }
847
848
849 bool
IsEditable() const850 AddressTextControl::IsEditable() const
851 {
852 return fEditable;
853 }
854
855
856 void
SetEditable(bool editable)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
SetText(const char * text)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*
Text() const882 AddressTextControl::Text() const
883 {
884 return fTextView->Text();
885 }
886
887
888 int32
TextLength() const889 AddressTextControl::TextLength() const
890 {
891 return fTextView->TextLength();
892 }
893
894
895 void
GetSelection(int32 * start,int32 * end) const896 AddressTextControl::GetSelection(int32* start, int32* end) const
897 {
898 fTextView->GetSelection(start, end);
899 }
900
901
902 void
Select(int32 start,int32 end)903 AddressTextControl::Select(int32 start, int32 end)
904 {
905 fTextView->Select(start, end);
906 }
907
908
909 void
SelectAll()910 AddressTextControl::SelectAll()
911 {
912 fTextView->Select(0, TextLength());
913 }
914
915
916 bool
HasFocus()917 AddressTextControl::HasFocus()
918 {
919 return fTextView->IsFocus();
920 }
921
922
923 void
_AddAddress(const char * text)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
_UpdateTextViewColors()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