xref: /haiku/src/apps/mail/AddressTextControl.cpp (revision e81a954787e50e56a7f06f72705b7859b6ab06d1)
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