/******************************************************************************
 JXInputField.cc

	Maintains a line of editable text.

	BASE CLASS = JXTEBase

	Copyright © 1996-98 by John Lindal. All rights reserved.

 ******************************************************************************/

#include <JXInputField.h>
#include <JXEditTable.h>
#include <JXWindowPainter.h>
#include <JXColormap.h>
#include <jXConstants.h>
#include <jXGlobals.h>
#include <jXKeysym.h>
#include <JString.h>
#include <jAssert.h>

/******************************************************************************
 Constructor

 ******************************************************************************/

JXInputField::JXInputField
	(
	JXContainer*		enclosure,
	const HSizingOption	hSizing,
	const VSizingOption	vSizing,
	const JCoordinate	x,
	const JCoordinate	y,
	const JCoordinate	w,
	const JCoordinate	h
	)
	:
	JXTEBase(kFullEditor, kTrue, kFalse, NULL,
			 enclosure, hSizing, vSizing, x,y, w,h)
{
	JXInputFieldX(kFalse);
}

JXInputField::JXInputField
	(
	const JBoolean		wordWrap,
	const JBoolean		acceptNewline,
	JXContainer*		enclosure,
	const HSizingOption	hSizing,
	const VSizingOption	vSizing,
	const JCoordinate	x,
	const JCoordinate	y,
	const JCoordinate	w,
	const JCoordinate	h
	)
	:
	JXTEBase(kFullEditor, JNegate(wordWrap), kFalse, NULL,
			 enclosure, hSizing, vSizing, x,y, w,h)
{
	JXInputFieldX(acceptNewline);
}

// private

void
JXInputField::JXInputFieldX
	(
	const JBoolean acceptNewline
	)
{
	itsAcceptNewlineFlag = acceptNewline;
	itsMinLength         = 0;
	itsMaxLength         = 0;

	itsTable = NULL;

	SetDefaultFontSize(kJXDefaultFontSize);
	TESetLeftMarginWidth(kMinLeftMarginWidth);
}

/******************************************************************************
 Destructor

 ******************************************************************************/

JXInputField::~JXInputField()
{
}

/******************************************************************************
 SetTable

	This can only be called once.

 ******************************************************************************/

void
JXInputField::SetTable
	(
	JXEditTable* table
	)
{
	assert( itsTable == NULL && table != NULL );

	itsTable = table;
	WantInput(kTrue, kTrue);
	SetBorderWidth(1);
}

/******************************************************************************
 Change font

 ******************************************************************************/

void
JXInputField::SetFontName
	(
	const JCharacter* name
	)
{
	if (!IsEmpty())
		{
		JXTEBase::SetFontName(1, GetTextLength(), name, kTrue);
		}
	else
		{
		SetCurrentFontName(name);
		ClearUndo();
		}

	SetDefaultFontName(name);
}

void
JXInputField::SetFontSize
	(
	const JSize size
	)
{
	if (!IsEmpty())
		{
		JXTEBase::SetFontSize(1, GetTextLength(), size, kTrue);
		}
	else
		{
		SetCurrentFontSize(size);
		ClearUndo();
		}

	SetDefaultFontSize(size);
}

void
JXInputField::SetFontStyle
	(
	const JFontStyle& style
	)
{
	if (!IsEmpty())
		{
		JXTEBase::SetFontStyle(1, GetTextLength(), style, kTrue);
		}
	else
		{
		SetCurrentFontStyle(style);
		ClearUndo();
		}

	SetDefaultFontStyle(style);
}

void
JXInputField::SetFont
	(
	const JCharacter*	name,
	const JSize			size,
	const JFontStyle&	style
	)
{
	if (!IsEmpty())
		{
		JXTEBase::SetFont(1, GetTextLength(), name, size, style, kTrue);
		}
	else
		{
		SetCurrentFont(name, size, style);
		ClearUndo();
		}

	SetDefaultFont(name, size, style);
}

/******************************************************************************
 SetMinLength

 ******************************************************************************/

void
JXInputField::SetMinLength
	(
	const JSize minLength
	)
{
	assert( itsMaxLength == 0 || minLength <= itsMaxLength );
	itsMinLength = minLength;
}

/******************************************************************************
 SetMaxLength

 ******************************************************************************/

void
JXInputField::SetMaxLength
	(
	const JSize maxLength
	)
{
	assert( maxLength > 0 && itsMinLength <= maxLength );
	itsMaxLength = maxLength;
}

/******************************************************************************
 SetLengthLimits

 ******************************************************************************/

void
JXInputField::SetLengthLimits
	(
	const JSize minLength,
	const JSize maxLength
	)
{
	assert( maxLength > 0 && minLength <= maxLength );
	itsMinLength = minLength;
	itsMaxLength = maxLength;
}

/******************************************************************************
 HandleFocusEvent (virtual protected)

 ******************************************************************************/

void
JXInputField::HandleFocusEvent()
{
	JXTEBase::HandleFocusEvent();
	ClearUndo();
	SelectAll();
}

/******************************************************************************
 HandleUnfocusEvent (virtual protected)

 ******************************************************************************/

void
JXInputField::HandleUnfocusEvent()
{
	JXTEBase::HandleUnfocusEvent();
	ClearUndo();
}

/******************************************************************************
 OKToUnfocus (virtual protected)

 ******************************************************************************/

JBoolean
JXInputField::OKToUnfocus()
{
	if (!JXTEBase::OKToUnfocus())
		{
		return kFalse;
		}
	else if (itsTable != NULL)
		{
		return itsTable->EndEditing();
		}
	else
		{
		return InputValid();
		}
}

/******************************************************************************
 DrawBorder (virtual protected)

 ******************************************************************************/

void
JXInputField::DrawBorder
	(
	JXWindowPainter&	p,
	const JRect&		frame
	)
{
	if (itsTable != NULL)
		{
		if (IsDNDTarget())
			{
			p.SetPenColor((p.GetColormap())->GetDefaultDNDBorderColor());
			}
		else
			{
			p.SetPenColor((GetColormap())->GetBlackColor());
			}
		p.JPainter::Rect(frame);
		}
	else
		{
		JXTEBase::DrawBorder(p, frame);
		}
}

/******************************************************************************
 HandleMouseDown (virtual protected)

 ******************************************************************************/

void
JXInputField::HandleMouseDown
	(
	const JPoint&			pt,
	const JXMouseButton		button,
	const JSize				clickCount,
	const JXButtonStates&	buttonStates,
	const JXKeyModifiers&	modifiers
	)
{
	ShouldAllowDragAndDrop(modifiers.meta());
	JXTEBase::HandleMouseDown(pt, button, clickCount, buttonStates, modifiers);
}

/******************************************************************************
 HandleDNDDrop (virtual protected)

	This is called when the data is dropped.  We do not accept the drop unless
	we can first obtain focus, because we cannot otherwise guarantee that
	our constraints will be checked.

 ******************************************************************************/

void
JXInputField::HandleDNDDrop
	(
	const JPoint&		pt,
	const JArray<Atom>&	typeList,
	const Atom			action,
	const Time			time,
	const JXWidget*		source
	)
{
	if (Focus())
		{
		JXTEBase::HandleDNDDrop(pt, typeList, action, time, source);
		}
}

/******************************************************************************
 InputValid (virtual)

 ******************************************************************************/

JBoolean
JXInputField::InputValid()
{
	const JString& text = GetText();
	const JSize length  = text.GetLength();
	if (itsMinLength == 1 && length == 0)
		{
		(JGetUserNotification())->ReportError(
			"This is a required field and cannot be left empty.");
		return kFalse;
		}
	else if (itsMinLength > 0 && itsMinLength == itsMaxLength &&
			 length != itsMinLength)
		{
		JString errorStr = "You must enter exactly ";
		if (itsMaxLength == 1)
			{
			errorStr += "1 character in this field.";
			}
		else
			{
			errorStr += JString(itsMinLength, 0);
			errorStr += " characters in this field.";
			}
		(JGetUserNotification())->ReportError(errorStr);
		return kFalse;
		}
	else if (itsMinLength > 0 && itsMaxLength > 0 &&
			 (length < itsMinLength || itsMaxLength < length))
		{
		JString errorStr = "You must enter between ";
		errorStr += JString(itsMinLength, 0);
		errorStr += " and ";
		errorStr += JString(itsMaxLength, 0);
		errorStr += " characters in this field.";
		(JGetUserNotification())->ReportError(errorStr);
		return kFalse;
		}
	else if (itsMinLength > 0 && length < itsMinLength)
		{
		JString errorStr = "You must enter at least ";
		errorStr += JString(itsMinLength, 0);			// itsMinLength > 1, see above
		errorStr += " characters in this field.";
		(JGetUserNotification())->ReportError(errorStr);
		return kFalse;
		}
	else if (itsMaxLength > 0 && length > itsMaxLength)
		{
		JString errorStr = "You cannot enter more than ";
		if (itsMaxLength == 1)
			{
			errorStr += "1 character in this field.";
			}
		else
			{
			errorStr += JString(itsMaxLength, 0);
			errorStr += " characters in this field.";
			}
		(JGetUserNotification())->ReportError(errorStr);
		return kFalse;
		}
	return kTrue;
}

/******************************************************************************
 HandleKeyPress (virtual)

 ******************************************************************************/

void
JXInputField::HandleKeyPress
	(
	const int				key,
	const JXKeyModifiers&	modifiers
	)
{
	if (itsTable != NULL && itsTable->WantsInputFieldKey(key, modifiers))
		{
		itsTable->HandleKeyPress(key, modifiers);
		return;
		}
	else if (itsTable != NULL)
		{
		JPoint cell;
		const JBoolean ok = itsTable->GetEditedCell(&cell);
		assert( ok );
		itsTable->TableScrollToCell(cell);
		}

	if (key == JXCtrl('K') && modifiers.control() && HasSelection())
		{
		Cut();
		}
	else if (key == JXCtrl('K') && modifiers.control())
		{
		JIndex i;
		const JBoolean ok = GetCaretLocation(&i);
		assert( ok );
		SetSelection(i, GetTextLength());
		Cut();
		}

	else if (!itsAcceptNewlineFlag && (key == '\n' || key == '\r'))
		{
		JXTEBase::HandleKeyPress(' ', modifiers);
		}

	else
		{
		JXTEBase::HandleKeyPress(key, modifiers);
		}
}

/******************************************************************************
 FilterSelectionData (virtual protected)

	Converts newlines to spaces.

	*** Note that style will be empty if the data was plain text.

 ******************************************************************************/

JError
JXInputField::FilterSelectionData
	(
	JString*			text,
	JRunArray<Font>*	style
	)
	const
{
	const JError err = JXTEBase::FilterSelectionData(text, style);
	if (!err.OK())
		{
		return err;
		}

	// convert newline to space

	if (!itsAcceptNewlineFlag)
		{
		const JSize length = text->GetLength();
		for (JIndex i=1; i<=length; i++)
			{
			if (text->GetCharacter(i) == '\n')
				{
				text->SetCharacter(i, ' ');
				}
			}
		}

	return JNoError();
}
