/*
 * Copyright 2015, Axel Dörfler, axeld@pinc-software.de.
 * Copyright 2010 Stephan Aßmus <superstippi@gmx.de>
 * Distributed under the terms of the MIT License.
 */
 
 
#include "AddressTextControl.h"
 
#include <Autolock.h>
#include <Button.h>
#include <Catalog.h>
#include <ControlLook.h>
#include <Clipboard.h>
#include <File.h>
#include <LayoutBuilder.h>
#include <Locale.h>
#include <LayoutUtils.h>
#include <NodeInfo.h>
#include <PopUpMenu.h>
#include <SeparatorView.h>
#include <TextView.h>
#include <Window.h>
 
#include <stdio.h>
#include <stdlib.h>
 
#include "MailApp.h"
#include "Messages.h"
#include "QueryList.h"
#include "TextViewCompleter.h"
 
 
#undef B_TRANSLATION_CONTEXT
#define B_TRANSLATION_CONTEXT "AddressTextControl"
 
 
static const uint32 kMsgAddAddress = 'adad';
static const float kVerticalTextRectInset = 2.0;
 
 
class AddressTextControl::TextView : public BTextView {
private:
	static const uint32 MSG_CLEAR = 'cler';
 
public:
								TextView(AddressTextControl* parent);
	virtual						~TextView();
 
	virtual	void				MessageReceived(BMessage* message);
	virtual	void				FrameResized(float width, float height);
	virtual	void				KeyDown(const char* bytes, int32 numBytes);
	virtual	void				MakeFocus(bool focused = true);
 
	virtual	BSize				MinSize();
	virtual	BSize				MaxSize();
 
			const BMessage*		ModificationMessage() const;
			void				SetModificationMessage(BMessage* message);
 
			void				SetUpdateAutoCompleterChoices(bool update);
 
protected:
	virtual	void				InsertText(const char* text, int32 length,
									int32 offset,
									const text_run_array* runs);
	virtual	void				DeleteText(int32 fromOffset, int32 toOffset);
 
private:
			void				_AlignTextRect();
 
private:
			AddressTextControl*	fAddressTextControl;
			TextViewCompleter*	fAutoCompleter;
			BString				fPreviousText;
			bool				fUpdateAutoCompleterChoices;
			BMessage*			fModificationMessage;
};
 
 
class AddressPopUpMenu : public BPopUpMenu, public QueryListener {
public:
								AddressPopUpMenu();
	virtual						~AddressPopUpMenu();
 
protected:
	virtual	void				EntryCreated(QueryList& source,
									const entry_ref& ref, ino_t node);
	virtual	void				EntryRemoved(QueryList& source,
									const node_ref& nodeRef);
 
private:
			void				_RebuildMenu();
			void				_AddGroup(const char* label, const char* group,
									PersonList& peopleList);
			void				_AddPeople(BMenu* menu, PersonList& peopleList,
									const char* group,
									bool addSeparator = false);
			bool				_MatchesGroup(const Person& person,
									const char* group);
};
 
 
class AddressTextControl::PopUpButton : public BControl {
public:
								PopUpButton();
	virtual						~PopUpButton();
 
	virtual BSize				MinSize();
	virtual BSize				PreferredSize();
	virtual BSize				MaxSize();
 
	virtual	void				MouseDown(BPoint where);
	virtual	void				Draw(BRect updateRect);
 
private:
			AddressPopUpMenu*	fPopUpMenu;
};
 
 
class PeopleChoiceModel : public BAutoCompleter::ChoiceModel {
public:
	PeopleChoiceModel()
		:
		fChoices(5, true)
	{
	}
 
	~PeopleChoiceModel()
	{
	}
 
	virtual void FetchChoicesFor(const BString& pattern)
	{
		// Remove all existing choices
		fChoices.MakeEmpty();
 
		// Search through the people list for any matches
		PersonList& peopleList = static_cast<TMailApp*>(be_app)->People();
		BAutolock locker(peopleList);
 
		for (int32 index = 0; index < peopleList.CountPersons(); index++) {
			const Person* person = peopleList.PersonAt(index);
 
			const BString& baseText = person->Name();
			for (int32 addressIndex = 0;
					addressIndex < person->CountAddresses(); addressIndex++) {
				BString choiceText = baseText;
				choiceText << " <" << person->AddressAt(addressIndex) << ">";
 
				int32 match = choiceText.IFindFirst(pattern);
				if (match < 0)
					continue;
 
				fChoices.AddItem(new BAutoCompleter::Choice(choiceText,
					choiceText, match, pattern.Length()));
			}
		}
 
		locker.Unlock();
		fChoices.SortItems(_CompareChoices);
	}
 
	virtual int32 CountChoices() const
	{
		return fChoices.CountItems();
	}
 
	virtual const BAutoCompleter::Choice* ChoiceAt(int32 index) const
	{
		return fChoices.ItemAt(index);
	}
 
	static int _CompareChoices(const BAutoCompleter::Choice* a,
		const BAutoCompleter::Choice* b)
	{
		return a->DisplayText().Compare(b->DisplayText());
	}
 
private:
	BObjectList<BAutoCompleter::Choice> fChoices;
};
 
 
// #pragma mark - TextView
 
 
AddressTextControl::TextView::TextView(AddressTextControl* parent)
	:
	BTextView("mail"),
	fAddressTextControl(parent),
	fAutoCompleter(new TextViewCompleter(this,
		new PeopleChoiceModel())),
	fPreviousText(""),
	fUpdateAutoCompleterChoices(true)
{
	MakeResizable(true);
	SetStylable(true);
	fAutoCompleter->SetModificationsReported(true);
}
 
 
AddressTextControl::TextView::~TextView()
{
	delete fAutoCompleter;
}
 
 
void
AddressTextControl::TextView::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case MSG_CLEAR:
			SetText("");
			break;
 
		default:
			BTextView::MessageReceived(message);
			break;
	}
}
 
 
void
AddressTextControl::TextView::FrameResized(float width, float height)
{
	BTextView::FrameResized(width, height);
	_AlignTextRect();
}
 
 
void
AddressTextControl::TextView::KeyDown(const char* bytes, int32 numBytes)
{
	switch (bytes[0]) {
		case B_TAB:
			BView::KeyDown(bytes, numBytes);
			break;
 
		case B_ESCAPE:
			// Revert to text as it was when we received keyboard focus.
			SetText(fPreviousText.String());
			SelectAll();
			break;
 
		case B_RETURN:
			// Don't let this through to the text view.
			break;
 
		default:
			BTextView::KeyDown(bytes, numBytes);
			break;
	}
}
 
void
AddressTextControl::TextView::MakeFocus(bool focus)
{
	if (focus == IsFocus())
		return;
 
	BTextView::MakeFocus(focus);
 
	if (focus) {
		fPreviousText = Text();
		SelectAll();
	}
 
	fAddressTextControl->Invalidate();
}
 
 
BSize
AddressTextControl::TextView::MinSize()
{
	BSize min;
	min.height = ceilf(LineHeight(0) + kVerticalTextRectInset);
		// we always add at least one pixel vertical inset top/bottom for
		// the text rect.
	min.width = min.height * 3;
	return BLayoutUtils::ComposeSize(ExplicitMinSize(), min);
}
 
 
BSize
AddressTextControl::TextView::MaxSize()
{
	BSize max(MinSize());
	max.width = B_SIZE_UNLIMITED;
	return BLayoutUtils::ComposeSize(ExplicitMaxSize(), max);
}
 
 
const BMessage*
AddressTextControl::TextView::ModificationMessage() const
{
	return fModificationMessage;
}
 
 
void
AddressTextControl::TextView::SetModificationMessage(BMessage* message)
{
	fModificationMessage = message;
}
 
 
void
AddressTextControl::TextView::SetUpdateAutoCompleterChoices(bool update)
{
	fUpdateAutoCompleterChoices = update;
}
 
 
void
AddressTextControl::TextView::InsertText(const char* text,
	int32 length, int32 offset, const text_run_array* runs)
{
	if (!strncmp(text, "mailto:", 7)) {
		text += 7;
		length -= 7;
		if (runs != NULL)
			runs = NULL;
	}
 
	// Filter all line breaks, note that text is not terminated.
	if (length == 1) {
		if (*text == '\n' || *text == '\r')
			BTextView::InsertText(" ", 1, offset, runs);
		else
			BTextView::InsertText(text, 1, offset, runs);
	} else {
		BString filteredText(text, length);
		filteredText.ReplaceAll('\n', ' ');
		filteredText.ReplaceAll('\r', ' ');
		BTextView::InsertText(filteredText.String(), length, offset,
			runs);
	}
 
	// TODO: change E-mail representation
/*
	// Make the base URL part bold.
	BString text(Text(), TextLength());
	int32 baseUrlStart = text.FindFirst("://");
	if (baseUrlStart >= 0)
		baseUrlStart += 3;
	else
		baseUrlStart = 0;
	int32 baseUrlEnd = text.FindFirst("/", baseUrlStart);
	if (baseUrlEnd < 0)
		baseUrlEnd = TextLength();
 
	BFont font;
	GetFont(&font);
	const rgb_color black = (rgb_color) { 0, 0, 0, 255 };
	const rgb_color gray = (rgb_color) { 60, 60, 60, 255 };
	if (baseUrlStart > 0)
		SetFontAndColor(0, baseUrlStart, &font, B_FONT_ALL, &gray);
	if (baseUrlEnd > baseUrlStart) {
		font.SetFace(B_BOLD_FACE);
		SetFontAndColor(baseUrlStart, baseUrlEnd, &font, B_FONT_ALL, &black);
	}
	if (baseUrlEnd < TextLength()) {
		font.SetFace(B_REGULAR_FACE);
		SetFontAndColor(baseUrlEnd, TextLength(), &font, B_FONT_ALL, &gray);
	}
*/
	fAutoCompleter->TextModified(fUpdateAutoCompleterChoices);
	fAddressTextControl->InvokeNotify(fModificationMessage,
		B_CONTROL_MODIFIED);
}
 
 
void
AddressTextControl::TextView::DeleteText(int32 fromOffset,
	int32 toOffset)
{
	BTextView::DeleteText(fromOffset, toOffset);
 
	fAutoCompleter->TextModified(fUpdateAutoCompleterChoices);
	fAddressTextControl->InvokeNotify(fModificationMessage,
		B_CONTROL_MODIFIED);
}
 
 
void
AddressTextControl::TextView::_AlignTextRect()
{
	// Layout the text rect to be in the middle, normally this means there
	// is one pixel spacing on each side.
	BRect textRect(Bounds());
	textRect.left = 0.0;
	float vInset = max_c(1,
		floorf((textRect.Height() - LineHeight(0)) / 2.0 + 0.5));
 
	float hInset = 0;
	if (be_control_look != NULL)
		hInset = be_control_look->DefaultLabelSpacing();
 
	textRect.InsetBy(hInset, vInset);
	SetTextRect(textRect);
}
 
 
//	#pragma mark - PopUpButton
 
 
AddressTextControl::PopUpButton::PopUpButton()
	:
	BControl(NULL, NULL, NULL, B_WILL_DRAW)
{
	fPopUpMenu = new AddressPopUpMenu();
}
 
 
AddressTextControl::PopUpButton::~PopUpButton()
{
	delete fPopUpMenu;
}
 
 
BSize
AddressTextControl::PopUpButton::MinSize()
{
	// TODO: BControlLook does not give us any size information!
	return BSize(10, 10);
}
 
 
BSize
AddressTextControl::PopUpButton::PreferredSize()
{
	return BSize(10, B_SIZE_UNSET);
}
 
 
BSize
AddressTextControl::PopUpButton::MaxSize()
{
	return BSize(10, B_SIZE_UNLIMITED);
}
 
 
void
AddressTextControl::PopUpButton::MouseDown(BPoint where)
{
	if (fPopUpMenu->Parent() != NULL)
		return;
 
	float width;
	fPopUpMenu->GetPreferredSize(&width, NULL);
	fPopUpMenu->SetTargetForItems(Parent());
 
	BPoint point(Bounds().Width() - width, Bounds().Height() + 2);
	ConvertToScreen(&point);
	fPopUpMenu->Go(point, true, true, true);
}
 
 
void
AddressTextControl::PopUpButton::Draw(BRect updateRect)
{
	uint32 flags = 0;
	if (!IsEnabled())
		flags |= BControlLook::B_DISABLED;
 
	if (IsFocus() && Window()->IsActive())
		flags |= BControlLook::B_FOCUSED;
 
	rgb_color base = ui_color(B_MENU_BACKGROUND_COLOR);
	BRect rect = Bounds();
	be_control_look->DrawMenuFieldBackground(this, rect,
		updateRect, base, true, flags);
}
 
 
//	#pragma mark - PopUpMenu
 
 
AddressPopUpMenu::AddressPopUpMenu()
	:
	BPopUpMenu("", true)
{
	static_cast<TMailApp*>(be_app)->PeopleQueryList().AddListener(this);
}
 
 
AddressPopUpMenu::~AddressPopUpMenu()
{
	static_cast<TMailApp*>(be_app)->PeopleQueryList().RemoveListener(this);
}
 
 
void
AddressPopUpMenu::EntryCreated(QueryList& source,
	const entry_ref& ref, ino_t node)
{
	_RebuildMenu();
}
 
 
void
AddressPopUpMenu::EntryRemoved(QueryList& source,
	const node_ref& nodeRef)
{
	_RebuildMenu();
}
 
 
void
AddressPopUpMenu::_RebuildMenu()
{
	// Remove all items
	int32 index = CountItems();
	while (index-- > 0) {
		 delete RemoveItem(index);
	}
 
	// Rebuild contents
	PersonList& peopleList = static_cast<TMailApp*>(be_app)->People();
	BAutolock locker(peopleList);
 
	if (peopleList.CountPersons() > 0)
		_AddGroup(B_TRANSLATE("All people"), NULL, peopleList);
 
	GroupList& groupList = static_cast<TMailApp*>(be_app)->PeopleGroups();
	BAutolock groupLocker(groupList);
 
	for (int32 index = 0; index < groupList.CountGroups(); index++) {
		BString group = groupList.GroupAt(index);
		_AddGroup(group, group, peopleList);
	}
 
	groupLocker.Unlock();
 
	_AddPeople(this, peopleList, "", true);
}
 
 
void
AddressPopUpMenu::_AddGroup(const char* label, const char* group,
	PersonList& peopleList)
{
	BMenu* menu = new BMenu(label);
	AddItem(menu);
	menu->Superitem()->SetMessage(new BMessage(kMsgAddAddress));
 
	_AddPeople(menu, peopleList, group);
}
 
 
void
AddressPopUpMenu::_AddPeople(BMenu* menu, PersonList& peopleList,
	const char* group, bool addSeparator)
{
	for (int32 index = 0; index < peopleList.CountPersons(); index++) {
		const Person* person = peopleList.PersonAt(index);
		if (!_MatchesGroup(*person, group))
			continue;
 
		if (person->CountAddresses() != 0 && addSeparator) {
			menu->AddSeparatorItem();
			addSeparator = false;
		}
 
		for (int32 addressIndex = 0; addressIndex < person->CountAddresses();
				addressIndex++) {
			BString email = person->Name();
			email << " <" << person->AddressAt(addressIndex) << ">";
 
			BMessage* message = new BMessage(kMsgAddAddress);
			message->AddString("email", email);
			menu->AddItem(new BMenuItem(email, message));
 
			if (menu->Superitem() != NULL)
				menu->Superitem()->Message()->AddString("email", email);
		}
	}
}
 
 
bool
AddressPopUpMenu::_MatchesGroup(const Person& person, const char* group)
{
	if (group == NULL)
		return true;
 
	if (group[0] == '\0')
		return person.CountGroups() == 0;
 
	return person.IsInGroup(group);
}
 
 
// TODO: sort lists!
/*
void
AddressTextControl::PopUpMenu::_AddPersonItem(const entry_ref *ref, ino_t node, BString &name,
	BString &email, const char *attr, BMenu *groupMenu, BMenuItem *superItem)
{
	BString	label;
	BString	sortKey;
		// For alphabetical order sorting, usually last name.
 
	// if we have no Name, just use the email address
	if (name.Length() == 0) {
		label = email;
		sortKey = email;
	} else {
		// otherwise, pretty-format it
		label << name << " (" << email << ")";
 
		// Extract the last name (last word in the name),
		// removing trailing and leading spaces.
		const char *nameStart = name.String();
		const char *string = nameStart + strlen(nameStart) - 1;
		const char *wordEnd;
 
		while (string >= nameStart && isspace(*string))
			string--;
		wordEnd = string + 1; // Points to just after last word.
		while (string >= nameStart && !isspace(*string))
			string--;
		string++; // Point to first letter in the word.
		if (wordEnd > string)
			sortKey.SetTo(string, wordEnd - string);
		else // Blank name, pretend that the last name is after it.
			string = nameStart + strlen(nameStart);
 
		// Append the first names to the end, so that people with the same last
		// name get sorted by first name.  Note no space between the end of the
		// last name and the start of the first names, but that shouldn't
		// matter for sorting.
		sortKey.Append(nameStart, string - nameStart);
	}
}
*/
 
// #pragma mark - AddressTextControl
 
 
AddressTextControl::AddressTextControl(const char* name, BMessage* message)
	:
	BControl(name, NULL, message, B_WILL_DRAW),
	fRefDropMenu(NULL),
	fWindowActive(false),
	fEditable(true)
{
	fTextView = new TextView(this);
	fTextView->SetExplicitMinSize(BSize(100, B_SIZE_UNSET));
	fPopUpButton = new PopUpButton();
 
	BLayoutBuilder::Group<>(this, B_HORIZONTAL, 0)
		.SetInsets(2)
		.Add(fTextView)
		.Add(fPopUpButton);
 
	SetFlags(Flags() | B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE);
	SetLowUIColor(ViewUIColor());
	SetViewUIColor(fTextView->ViewUIColor());
 
	SetExplicitAlignment(BAlignment(B_ALIGN_USE_FULL_WIDTH,
		B_ALIGN_VERTICAL_CENTER));
 
	SetEnabled(fEditable);
		// Sets the B_NAVIGABLE flag on the TextView
}
 
 
AddressTextControl::~AddressTextControl()
{
}
 
 
void
AddressTextControl::AttachedToWindow()
{
	BControl::AttachedToWindow();
	fWindowActive = Window()->IsActive();
}
 
 
void
AddressTextControl::WindowActivated(bool active)
{
	BControl::WindowActivated(active);
	if (fWindowActive != active) {
		fWindowActive = active;
		Invalidate();
	}
}
 
 
void
AddressTextControl::Draw(BRect updateRect)
{
	if (!IsEditable())
		return;
 
	BRect bounds(Bounds());
	rgb_color base(LowColor());
	uint32 flags = 0;
	if (!IsEnabled())
		flags |= BControlLook::B_DISABLED;
	if (fWindowActive && fTextView->IsFocus())
		flags |= BControlLook::B_FOCUSED;
	be_control_look->DrawTextControlBorder(this, bounds, updateRect, base,
		flags);
}
 
 
void
AddressTextControl::MakeFocus(bool focus)
{
	// Forward this to the text view, we never accept focus ourselves.
	fTextView->MakeFocus(focus);
}
 
 
void
AddressTextControl::SetEnabled(bool enabled)
{
	BControl::SetEnabled(enabled);
	fTextView->MakeEditable(enabled && fEditable);
	if (enabled)
		fTextView->SetFlags(fTextView->Flags() | B_NAVIGABLE);
	else
		fTextView->SetFlags(fTextView->Flags() & ~B_NAVIGABLE);
 
	fPopUpButton->SetEnabled(enabled);
 
	_UpdateTextViewColors();
}
 
 
void
AddressTextControl::MessageReceived(BMessage* message)
{
	switch (message->what) {
		case B_SIMPLE_DATA:
		{
			int32 buttons = -1;
			BPoint point;
			if (message->FindInt32("buttons", &buttons) != B_OK)
				buttons = B_PRIMARY_MOUSE_BUTTON;
 
			if (buttons != B_PRIMARY_MOUSE_BUTTON
				&& message->FindPoint("_drop_point_", &point) != B_OK)
				return;
 
			BMessage forwardRefs(B_REFS_RECEIVED);
			bool forward = false;
 
			entry_ref ref;
			for (int32 index = 0;message->FindRef("refs", index, &ref) == B_OK; index++) {
				BFile file(&ref, B_READ_ONLY);
				if (file.InitCheck() == B_NO_ERROR) {
					BNodeInfo info(&file);
					char type[B_FILE_NAME_LENGTH];
					info.GetType(type);
 
					if (!strcmp(type,"application/x-person")) {
						// add person's E-mail address to the To: field
 
						BString attr = "";
						if (buttons == B_PRIMARY_MOUSE_BUTTON) {
							if (message->FindString("attr", &attr) < B_OK)
								attr = "META:email";
						} else {
							BNode node(&ref);
							node.RewindAttrs();
 
							char buffer[B_ATTR_NAME_LENGTH];
 
							delete fRefDropMenu;
							fRefDropMenu = new BPopUpMenu("RecipientMenu");
 
							while (node.GetNextAttrName(buffer) == B_OK) {
								if (strstr(buffer, "email") == NULL)
									continue;
 
								attr = buffer;
 
								BString address;
								node.ReadAttrString(buffer, &address);
								if (address.Length() <= 0)
									continue;
 
								BMessage* itemMsg
									= new BMessage(kMsgAddAddress);
								itemMsg->AddString("email", address.String());
 
								BMenuItem* item = new BMenuItem(
									address.String(), itemMsg);
								fRefDropMenu->AddItem(item);
							}
 
							if (fRefDropMenu->CountItems() > 1) {
								fRefDropMenu->SetTargetForItems(this);
								fRefDropMenu->Go(point, true, true, true);
								return;
							} else {
								delete fRefDropMenu;
								fRefDropMenu = NULL;
							}
						}
 
						BString email;
						file.ReadAttrString(attr.String(), &email);
 
						// we got something...
						if (email.Length() > 0) {
							// see if we can get a username as well
							BString name;
							file.ReadAttrString("META:name", &name);
 
							BString	address;
							if (name.Length() == 0) {
								// if we have no Name, just use the email address
								address = email;
							} else {
								// otherwise, pretty-format it
								address << "\"" << name << "\" <" << email << ">";
							}
 
							_AddAddress(address);
						}
					} else {
						forward = true;
						forwardRefs.AddRef("refs", &ref);
					}
				}
			}
 
			if (forward) {
				// Pass on to parent
				Window()->PostMessage(&forwardRefs, Parent());
			}
			break;
		}
 
		case M_SELECT:
		{
			BTextView *textView = (BTextView *)ChildAt(0);
			if (textView != NULL)
				textView->Select(0, textView->TextLength());
			break;
		}
 
		case kMsgAddAddress:
		{
			const char* email;
			for (int32 index = 0;
					message->FindString("email", index++, &email) == B_OK;)
				_AddAddress(email);
			break;
		}
 
		default:
			BControl::MessageReceived(message);
			break;
	}
}
 
 
const BMessage*
AddressTextControl::ModificationMessage() const
{
	return fTextView->ModificationMessage();
}
 
 
void
AddressTextControl::SetModificationMessage(BMessage* message)
{
	fTextView->SetModificationMessage(message);
}
 
 
bool
AddressTextControl::IsEditable() const
{
	return fEditable;
}
 
 
void
AddressTextControl::SetEditable(bool editable)
{
	fTextView->MakeEditable(IsEnabled() && editable);
	fEditable = editable;
 
	if (editable && fPopUpButton->IsHidden(this))
		fPopUpButton->Show();
	else if (!editable && !fPopUpButton->IsHidden(this))
		fPopUpButton->Hide();
}
 
 
void
AddressTextControl::SetText(const char* text)
{
	if (text == NULL || Text() == NULL || strcmp(Text(), text) != 0) {
		fTextView->SetUpdateAutoCompleterChoices(false);
		fTextView->SetText(text);
		fTextView->SetUpdateAutoCompleterChoices(true);
	}
}
 
 
const char*
AddressTextControl::Text() const
{
	return fTextView->Text();
}
 
 
int32
AddressTextControl::TextLength() const
{
	return fTextView->TextLength();
}
 
 
void
AddressTextControl::GetSelection(int32* start, int32* end) const
{
	fTextView->GetSelection(start, end);
}
 
 
void
AddressTextControl::Select(int32 start, int32 end)
{
	fTextView->Select(start, end);
}
 
 
void
AddressTextControl::SelectAll()
{
	fTextView->Select(0, TextLength());
}
 
 
bool
AddressTextControl::HasFocus()
{
	return fTextView->IsFocus();
}
 
 
void
AddressTextControl::_AddAddress(const char* text)
{
	int last = fTextView->TextLength();
	if (last != 0) {
		fTextView->Select(last, last);
		// TODO: test if there is already a ','
		fTextView->Insert(", ");
	}
	fTextView->Insert(text);
}
 
 
void
AddressTextControl::_UpdateTextViewColors()
{
	BFont font;
	fTextView->GetFontAndColor(0, &font);
 
	rgb_color textColor;
	if (!IsEditable() || IsEnabled())
		textColor = ui_color(B_DOCUMENT_TEXT_COLOR);
	else {
		textColor = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
			B_DISABLED_LABEL_TINT);
	}
 
	fTextView->SetFontAndColor(&font, B_FONT_ALL, &textColor);
 
	rgb_color color;
	if (!IsEditable())
		color = ui_color(B_PANEL_BACKGROUND_COLOR);
	else if (IsEnabled())
		color = ui_color(B_DOCUMENT_BACKGROUND_COLOR);
	else {
		color = tint_color(ui_color(B_PANEL_BACKGROUND_COLOR),
			B_LIGHTEN_2_TINT);
	}
 
	fTextView->SetViewColor(color);
	fTextView->SetLowColor(color);
}

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fModificationMessage.