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