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
38static const uint32 kMsgAddAddress = 'adad';
39static const float kVerticalTextRectInset = 2.0;
40
41
42class AddressTextControl::TextView : public BTextView {
43private:
44	static const uint32 MSG_CLEAR = 'cler';
45
46public:
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
62protected:
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
68private:
69			AddressTextControl*	fAddressTextControl;
70			TextViewCompleter*	fAutoCompleter;
71			BString				fPreviousText;
72			bool				fUpdateAutoCompleterChoices;
73			BMessage*			fModificationMessage;
74};
75
76
77class AddressPopUpMenu : public BPopUpMenu, public QueryListener {
78public:
79								AddressPopUpMenu();
80	virtual						~AddressPopUpMenu();
81
82protected:
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
88private:
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
100class AddressTextControl::PopUpButton : public BControl {
101public:
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
112private:
113			AddressPopUpMenu*	fPopUpMenu;
114};
115
116
117class PeopleChoiceModel : public BAutoCompleter::ChoiceModel {
118public:
119	PeopleChoiceModel()
120		:
121		fChoices(5, true)
122	{
123	}
124
125	~PeopleChoiceModel()
126	{
127	}
128
129	virtual void FetchChoicesFor(const BString& pattern)
130	{
131		// Remove all existing choices
132		fChoices.MakeEmpty();
133
134		// Search through the people list for any matches
135		PersonList& peopleList = static_cast<TMailApp*>(be_app)->People();
136		BAutolock locker(peopleList);
137
138		for (int32 index = 0; index < peopleList.CountPersons(); index++) {
139			const Person* person = peopleList.PersonAt(index);
140
141			const BString& baseText = person->Name();
142			for (int32 addressIndex = 0;
143					addressIndex < person->CountAddresses(); addressIndex++) {
144				BString choiceText = baseText;
145				choiceText << " <" << person->AddressAt(addressIndex) << ">";
146
147				int32 match = choiceText.IFindFirst(pattern);
148				if (match < 0)
149					continue;
150
151				fChoices.AddItem(new BAutoCompleter::Choice(choiceText,
152					choiceText, match, pattern.Length()));
153			}
154		}
155
156		locker.Unlock();
157		fChoices.SortItems(_CompareChoices);
158	}
159
160	virtual int32 CountChoices() const
161	{
162		return fChoices.CountItems();
163	}
164
165	virtual const BAutoCompleter::Choice* ChoiceAt(int32 index) const
166	{
167		return fChoices.ItemAt(index);
168	}
169
170	static int _CompareChoices(const BAutoCompleter::Choice* a,
171		const BAutoCompleter::Choice* b)
172	{
173		return a->DisplayText().Compare(b->DisplayText());
174	}
175
176private:
177	BObjectList<BAutoCompleter::Choice> fChoices;
178};
179
180
181// #pragma mark - TextView
182
183
184AddressTextControl::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
199AddressTextControl::TextView::~TextView()
200{
201	delete fAutoCompleter;
202}
203
204
205void
206AddressTextControl::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
220void
221AddressTextControl::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
244void
245AddressTextControl::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
261BSize
262AddressTextControl::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
273BSize
274AddressTextControl::TextView::MaxSize()
275{
276	BSize max(MinSize());
277	max.width = B_SIZE_UNLIMITED;
278	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), max);
279}
280
281
282const BMessage*
283AddressTextControl::TextView::ModificationMessage() const
284{
285	return fModificationMessage;
286}
287
288
289void
290AddressTextControl::TextView::SetModificationMessage(BMessage* message)
291{
292	fModificationMessage = message;
293}
294
295
296void
297AddressTextControl::TextView::SetUpdateAutoCompleterChoices(bool update)
298{
299	fUpdateAutoCompleterChoices = update;
300}
301
302
303void
304AddressTextControl::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
362void
363AddressTextControl::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
377AddressTextControl::PopUpButton::PopUpButton()
378	:
379	BControl(NULL, NULL, NULL, B_WILL_DRAW)
380{
381	fPopUpMenu = new AddressPopUpMenu();
382}
383
384
385AddressTextControl::PopUpButton::~PopUpButton()
386{
387	delete fPopUpMenu;
388}
389
390
391BSize
392AddressTextControl::PopUpButton::MinSize()
393{
394	// TODO: BControlLook does not give us any size information!
395	return BSize(10, 10);
396}
397
398
399BSize
400AddressTextControl::PopUpButton::PreferredSize()
401{
402	return BSize(10, B_SIZE_UNSET);
403}
404
405
406BSize
407AddressTextControl::PopUpButton::MaxSize()
408{
409	return BSize(10, B_SIZE_UNLIMITED);
410}
411
412
413void
414AddressTextControl::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
429void
430AddressTextControl::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
449AddressPopUpMenu::AddressPopUpMenu()
450	:
451	BPopUpMenu("", true)
452{
453	static_cast<TMailApp*>(be_app)->PeopleQueryList().AddListener(this);
454}
455
456
457AddressPopUpMenu::~AddressPopUpMenu()
458{
459	static_cast<TMailApp*>(be_app)->PeopleQueryList().RemoveListener(this);
460}
461
462
463void
464AddressPopUpMenu::EntryCreated(QueryList& source,
465	const entry_ref& ref, ino_t node)
466{
467	_RebuildMenu();
468}
469
470
471void
472AddressPopUpMenu::EntryRemoved(QueryList& source,
473	const node_ref& nodeRef)
474{
475	_RebuildMenu();
476}
477
478
479void
480AddressPopUpMenu::_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
509void
510AddressPopUpMenu::_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
521void
522AddressPopUpMenu::_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
551bool
552AddressPopUpMenu::_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/*
566void
567AddressTextControl::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
611AddressTextControl::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
639AddressTextControl::~AddressTextControl()
640{
641}
642
643
644void
645AddressTextControl::AttachedToWindow()
646{
647	BControl::AttachedToWindow();
648	fWindowActive = Window()->IsActive();
649}
650
651
652void
653AddressTextControl::WindowActivated(bool active)
654{
655	BControl::WindowActivated(active);
656	if (fWindowActive != active) {
657		fWindowActive = active;
658		Invalidate();
659	}
660}
661
662
663void
664AddressTextControl::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
681void
682AddressTextControl::MakeFocus(bool focus)
683{
684	// Forward this to the text view, we never accept focus ourselves.
685	fTextView->MakeFocus(focus);
686}
687
688
689void
690AddressTextControl::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
705void
706AddressTextControl::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
835const BMessage*
836AddressTextControl::ModificationMessage() const
837{
838	return fTextView->ModificationMessage();
839}
840
841
842void
843AddressTextControl::SetModificationMessage(BMessage* message)
844{
845	fTextView->SetModificationMessage(message);
846}
847
848
849bool
850AddressTextControl::IsEditable() const
851{
852	return fEditable;
853}
854
855
856void
857AddressTextControl::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
870void
871AddressTextControl::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
881const char*
882AddressTextControl::Text() const
883{
884	return fTextView->Text();
885}
886
887
888int32
889AddressTextControl::TextLength() const
890{
891	return fTextView->TextLength();
892}
893
894
895void
896AddressTextControl::GetSelection(int32* start, int32* end) const
897{
898	fTextView->GetSelection(start, end);
899}
900
901
902void
903AddressTextControl::Select(int32 start, int32 end)
904{
905	fTextView->Select(start, end);
906}
907
908
909void
910AddressTextControl::SelectAll()
911{
912	fTextView->Select(0, TextLength());
913}
914
915
916bool
917AddressTextControl::HasFocus()
918{
919	return fTextView->IsFocus();
920}
921
922
923void
924AddressTextControl::_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
936void
937AddressTextControl::_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