/*	XGEditText.cpp
 *
 *		This handles the standard editing control.
 */

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

  YAAF is Copyright 1997-1999 by William Woody.

  Redistribution and use in source and binary forms, with or without 
  modification, are permitted provided that the following conditions 
  are met:

  Redistributions of source code must retain the above copyright notice, 
  this list of conditions and the following disclaimer.

  Neither the name of the copyright holder or other contributors may
  be used to promote products derived from this software without
  specific written prior permission.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
  SOFTWARE.
  
  Contact:   woody@alumni.caltech.edu
  Web page:  http://www.pandawave.com/yaaf/

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

#include <XConfig.h>			// must be first to optimize compile on MSVC++
#include <string.h>
#include <ctype.h>
#include <XError.h>
#include <XTextControls.h>
#include <XEditCore.h>
#include <XDataUtil.h>
#include <XDraw.h>
#include <XCursorUtil.h>
#include <XMessages.h>
#include <XAppMenu.h>
#include <XHandle.h>
#include <XScroll.h>
#include <XMisc.h>
#include <XWindow.h>
#include <XCursorUtil.h>

/************************************************************************/
/*																		*/
/*	Construction/Destruction											*/
/*																		*/
/************************************************************************/

/*$	XGEditText::XGEditText
 *
 *		Create this edit text box with the specified location
 */

XGEditText::XGEditText(XGView *p, XGArgStream &s) : XGView(p,s)
{
	short font;
	XGSEditInitRecord init;
	
	char buffer[256];
	s.ReadString(buffer);
	font = s.ReadInteger();

	init.maxchars  = s.ReadInteger();
	init.chartype = (XGECharType)(s.ReadInteger());
	init.password = s.ReadBoolean();
	init.wordwrap = s.ReadBoolean();
	init.autoindent = s.ReadBoolean();
	init.noframe = s.ReadBoolean();
	init.tabstop = s.ReadInteger();
	init.editor = s.ReadBoolean();
	
	Init(buffer,font,init);
}

XGEditText::XGEditText(XGView *p, XGSEditInitRecord &s) : XGView(p,s.v.v)
{
	Init(s.v.text,s.v.font,s);
}

/*$	XGEditText::~XGEditText
 *
 *		No longer edit the text in here
 */

XGEditText::~XGEditText()
{
	delete fEditCore;
	fEditCore = 0;
}

/************************************************************************/
/*																		*/
/*	Standard Text Events												*/
/*																		*/
/************************************************************************/

/*$	XGEditText:DoDrawView
 *
 *		Draw this thing
 */

void XGEditText::DoDrawView(XRect)
{
	XGDraw draw(this);
	XRect r;
	
	r = GetContentRect();
	DrawTextFrame(draw,r);
	if (!fNoFrame) ::InsetRect(&r,3,3);
	
	fEditCore->DoDraw(draw,r);
}

/*$	XGEditText::DoActivate
 *
 *		Handle activation/deactivation (if this thing has the focus)
 */

void XGEditText::DoActivate(bool f)
{
	fEditCore->DoActivate(f);
	if (!fNoFrame) InvalView();			// make sure my frame redraws
}

/*$	XGEditText::DoMouseDown
 *
 *		Handle clicks in this thing
 */

bool XGEditText::DoMouseDown(XPoint where, short flags)
{
	::SetCursorID(KIBeamCursor);
	return fEditCore->DoMouseDown(where,flags);
}

/*$	XGEditText::DoMouseMove
 *
 *		Handle the mouse move event
 */

void XGEditText::DoMouseMove(XPoint where, short flags)
{
	::SetCursorID(KIBeamCursor);
	fEditCore->DoMouseMove(where,flags);
}

/*$	XGEditText::DoMouseUp
 *
 *		Handle mouse up
 */

void XGEditText::DoMouseUp(XPoint where, short flags)
{
	fEditCore->DoMouseUp(where,flags);
}

/*$	XGEditText::DoSizeView
 *
 *		Resize view
 */

void XGEditText::DoSizeView()
{
	XRect r;
	
	XGView::DoSizeView();
	
	r = GetContentRect();
	if (!fNoFrame) ::InsetRect(&r,3,3);
	fEditCore->DoSize(r);
	RecalcScroll();
	InvalView();
}

/*$	XGEditText::Scroll
 *
 *		Scroll
 */

void XGEditText::Scroll(long x, long y)
{
	XRect r;
	long ax,ay;
	XGScrollView *v;
	
	v = dynamic_cast<XGScrollView *>(GetParent());
	if (v && fEditCore) {
		v->GetScrollValue(&ax,&ay);
		fEditCore->SetScrollValue(ax,ay);
	}
	
	r = GetContentRect();
	if (!fNoFrame) ::InsetRect(&r,3,3);
	ScrollView(x,y,&r);
}

/*$	XGEditText:DoGainFocus
 *
 *		Gain the focus. This enables this thing
 */

void XGEditText::DoGainFocus()
{
	XRect r;
	
#if OPT_MACOS
	extern long _GAppearance;
	
	r = GetContentRect();
	ViewToGlobal(&r);
	XGView *v = GetWindow();
	v->GlobalToView(&r);

	if (!fNoFrame) {
		if (_GAppearance) {
			InsetRect(&r,-3,-3);
		} else {
			InsetRect(&r,3,3);
		}
	}
	v->InvalView(&r);
#else
	r = GetContentRect();
	InvalView(&r);
#endif

	fEditCore->DoGainFocus(false);
}

/*$	XGEditText::DoLoseFocus
 *
 *		Lose the focus. This disables this thing
 */

void XGEditText::DoLoseFocus()
{
	XRect r;
	
#if OPT_MACOS
	extern long _GAppearance;
	
	r = GetContentRect();
	ViewToGlobal(&r);
	XGView *v = GetWindow();
	v->GlobalToView(&r);

	if (!fNoFrame) {
		if (_GAppearance) {
			InsetRect(&r,-3,-3);
		} else {
			InsetRect(&r,3,3);
		}
	}
	v->InvalView(&r);
#else
	r = GetContentRect();
	InvalView(&r);
#endif

	fEditCore->DoLoseFocus(false);
}

/************************************************************************/
/*																		*/
/*	Dispatched events													*/
/*																		*/
/************************************************************************/

BEGINDISPATCH(XGEditText,XGView)
	DISPATCHTABLE(KXGAppPeriodic,XGEditText::DoIdle)
	DISPATCHTABLE(KXGKeyEvent,XGEditText::DoKey)
	DISPATCHTABLE(KXGGetMenuStatus,XGEditText::DoGetMenuStatus)
	DISPATCHTABLE(KXGDoMenuCommand,XGEditText::DoMenuCommand)
ENDDISPATCH

/*	XGEditText::DoIdle
 *
 *		Handle idle event by blinking the cursor
 */

long XGEditText::DoIdle(long, void *)
{
	fEditCore->DoIdle();
	return 0;
}

/*$	XGEditText::DoGetMenuStatus
 *
 *		Handle getting the menu status
 */

long XGEditText::DoGetMenuStatus(long arg, void *parg)
{
	XGSMenuStatus *m = (XGSMenuStatus *)parg;
	
	switch (arg) {
		case KXGEditCut:
			m->enable = fEditCore->CanCut();
			return 0;
		case KXGEditCopy:
			m->enable = fEditCore->CanCopy();
			return 0;
		case KXGEditPaste:
			m->enable = fEditCore->CanPaste();
			return 0;
		case KXGEditClear:
			m->enable = fEditCore->CanDelete();
			return 0;
		case KXGEditSelectAll:
			m->enable = true;
			return 0;
	}
	return -1;
}

/*$	XGEditText::DoMenuCommand
 *
 *		Do the menu command
 */

long XGEditText::DoMenuCommand(long arg, void *)
{
	char obuf[256];
	char nbuf[256];
	
	switch (arg) {
		case KXGEditCut:
		case KXGEditCopy:
		case KXGEditPaste:
		case KXGEditClear:
			GetTitle(obuf);
			switch (arg) {
				case KXGEditCut:
					fEditCore->DoCut();
					break;
				case KXGEditCopy:
					fEditCore->DoCopy();
					break;
				case KXGEditPaste:
					fEditCore->DoPaste();
					break;
				case KXGEditClear:
					fEditCore->DoDelete();
					break;
			}
			GetTitle(nbuf);
			if (strcmp(nbuf,obuf)) {
				if (!ValidateText(nbuf)) {
					SetTitle(obuf);
				}
			}
			return 0;
		case KXGEditSelectAll:
			fEditCore->DoSelectAll();
			return 0;
	}
	return -1;
}

/*$	XGEditText::DoKey
 *
 *		Handle keyboard command
 */

long XGEditText::DoKey(long arg, void *)
{
	short c;
	short mods;
	
	mods = GETHIWORD(arg);
	c = GETLOWORD(arg);
	if (mods & KSpecialKey) {
		if (c == KKeyDelete) {
			fEditCore->DoRightArrow(false);
			DeleteChar();
		} else if (c == KKeyHome) {
			SetSelect(0,0);
		} else if (c == KKeyEnd) {
			long length = fTextHandle.GetSize();
			SetSelect(length,length);
		} else if (c == KKeyLeftCursor) {
			fEditCore->DoLeftArrow((mods & KKeyShift) ? true : false);
		} else if (c == KKeyRightCursor) {
			fEditCore->DoRightArrow((mods & KKeyShift) ? true : false);
		} else if (c == KKeyUpCursor) {
			fEditCore->DoUpArrow((mods & KKeyShift) ? true : false);
		} else if (c == KKeyDownCursor) {
			fEditCore->DoDownArrow((mods & KKeyShift) ? true : false);
		} else {
			return -1;
		}
	} else {
#if OPT_MACOS
		/* Screen out control keys */
		if (mods & KKeyControl) return -1;
#endif

		if (IsSpecialKey(c)) return -1;				// pass keys upwards

		if (c == 0x08) {
			DeleteChar();
		} else {
			InsertChar(c);
		}
	}
	
	RecalcScroll();
	return 0;
}

/************************************************************************/
/*																		*/
/*	Miscellaneous Support												*/
/*																		*/
/************************************************************************/

/*$	XGView::SetSelect
 *
 *		Set the current selection
 */

void XGEditText::SetSelect(long s, long e)
{
	fEditCore->SetSelect(s,e);
}

/*$	XGView::GetSelect
 *
 *		Get the current selection range
 */

void XGEditText::GetSelect(long *s, long *e)
{
	fEditCore->GetSelect(s,e);
}

/************************************************************************/
/*																		*/
/*	Text manipulation													*/
/*																		*/
/************************************************************************/

/*$	XGEditText::SetTitle
 *
 *		Set the text in this thing
 */

void XGEditText::SetTitle(const char *text)
{
	XGHandle h;
	int len;
	
	len = strlen(text);
	h.SetSize(len);
	memcpy((char *)*h,text,len);
	SetTextHandle(h);
}

/*$	XGEditText::GetTitle
 *
 *		Get the text from this thing
 */

void XGEditText::GetTitle(char *text) const
{
	long len;
	
	len = fTextHandle.GetSize();
	if (len > 255) len = 255;
	memcpy(text,(char *)*fTextHandle,len);
	text[len] = 0;
}

// phook: hack stuff so we can do stli editing easy
long XGEditText::GetTitleEx(char *text, long max_len ) const
{
	long len;
	
	len = fTextHandle.GetSize();
	if (len > max_len) len = max_len;
	memcpy(text,(char *)*fTextHandle,max_len);
	text[len] = 0;
	
	return len;
}

/************************************************************************/
/*																		*/
/*	Miscellaneous Hooks													*/
/*																		*/
/************************************************************************/

/*$	XGEditText::IsSpecialKey
 *
 *		This determines the 'special keys'--those keys which are passed
 *	up to the containing view. By default, these are the enter key and
 *	the tab key if this is a field.
 */

bool XGEditText::IsSpecialKey(char c)
{
	if (c == 0x09) return true;
	if (fEditCore->GetFieldEditor()) {
		if (c == 0x0D) return true;
	}
	return false;
}

/*$	XGEditText::DrawTextFrame
 *
 *		This draws the frame around the edited text
 */

void XGEditText::DrawTextFrame(XGDraw &draw, XRect r)
{
	if (!fNoFrame) {
		if (IsEnabled() && IsActive()) {
			draw.Draw3DRect(r,KXGEInset);
			::InsetRect(&r,1,1);
			draw.Draw3DRect(r,KXGEFrame);
			::InsetRect(&r,1,1);
#if OPT_MACOS
			extern long _GAppearance;

			if (HasFocus() && _GAppearance) {
				XGView *par = GetParent();
				
				if (par) {
					XGDraw draw(par);
					XRect s;
					
					s = r;
					ViewToGlobal(&s);
					par->GlobalToView(&s);
					Rect mr = s.MacRect();
					::DrawThemeFocusRect(&mr,true);
				} else {
					RgnHandle rgn = NewRgn();
					GetClip(rgn);
					::InsetRect(&r, -3, -3);
					Rect mr = r.MacRect();
					::ClipRect(&mr);
					::InsetRect(&r, 3, 3);
					mr = r.MacRect();
					DrawThemeFocusRect(&mr, true);
					SetClip(rgn);
					DisposeRgn(rgn);
				}
			}
#endif
		} else {
			draw.Draw3DRect(r,KXGEGrayFrame);
			::InsetRect(&r,1,1);
		}
	}
	draw.EraseRect(r);
}

/*$	XGEditText::ValidateText
 *
 *		Validate the input. Is this an appropriate input value? By default
 *	this transmits a message upwards. A return value of '1' means the text
 *	input is *not* valid.
 */

bool XGEditText::ValidateText(char *text)
{
	char *c;
	short x;
	
	/*
	 *	Check to see if this is fine with the limits defined by the
	 *	resource specification
	 */
	
	switch (fCharType) {
		case KXGEAnyChar:
		case KXGENumeric:
		case KXGEHexDigit:
		case KXGEAlphaNumeric:
			break;
		case KXGENumSign:
		case KXGEFloating:
			/*
			 *	Make sure the only '+' and '-' is at the start
			 */
			
			if (*text) {
				for (c = text+1; *c; c++) {
					if ((*c == '+') || (*c == '-')) return false;
				}
			}
			if (fCharType == KXGENumSign) break;
			
			/*
			 *	For floating, make sure there is only one '.'
			 */
			
			x = 0;
			for (c = text; *c; c++) {
				if (*c == '.') x++;
				if (x > 1) return false;
			}
			break;
	}
	
	/*
	 *	Now see if the parent thinks this is fine.
	 */
	
	if (1 == DoDispatch(KXGKeyClick,GetViewID(),(void *)text)) {
		return false;
	}
	return true;
}

/*$	XGEditText::CanInsertKey
 *
 *		Can this key be inserted? By default use the attributes
 *	that were passed to me
 */

bool XGEditText::CanInsertKey(char c)
{
	long len;
	long min,max;
	
	switch (fCharType) {
		case KXGEAnyChar:
			break;
		case KXGENumeric:
			if (!isdigit(c)) return false;
			break;
		case KXGENumSign:
			if (!isdigit(c) && (c != '+') && (c != '-')) return false;
			break;
		case KXGEFloating:
			if (!isdigit(c) && (c != '.') && (c != '+') && (c != '-')) return false;
			break;
		case KXGEHexDigit:
			if (!isxdigit(c)) return false;
			break;
		case KXGEAlphaNumeric:
			if (!isalnum(c)) return false;
			break;
	}
	
	if (fMaxChars == 0) return true;
	len = fTextHandle.GetSize();
	GetSelect(&min,&max);
	
	len -= (max - min);
	if (len >= fMaxChars) return false;
	return true;
}



/************************************************************************/
/*																		*/
/*	Edit Core															*/
/*																		*/
/************************************************************************/

/*$	XGEditText::InsertChar
 *
 *		Insert a character
 */

void XGEditText::InsertChar(char c)
{
	XGHandle backup;
	char obuf[256];
	char nbuf[256];
	
	if (c != 0x08) {
		if (!CanInsertKey(c)) return;
	}

	GetTitle(obuf);
	fEditCore->DoKey(c);
	GetTitle(nbuf);
	if (!ValidateText(nbuf)) {
		SetTitle(obuf);
		return;
	}
	
	RecalcScroll();
}

/*$	XGEditText::DeleteChar
 *
 *		Delete a character
 */

void XGEditText::DeleteChar()
{
	InsertChar(0x08);
}
	

/************************************************************************/
/*																		*/
/*	Text Scroll															*/
/*																		*/
/************************************************************************/

/*$	XGEditText::RecalcScroll
 *
 *		Recalculate the scroll boundaries; this calculates the number of
 *	pixels wide the text is
 */

void XGEditText::RecalcScroll()
{
	long x,y,maxy;
	long h;
	XGScrollView *v;
	XRect r;
	
	/*
	 *	Quick abort in case of error
	 */
	
	if (fEditCore == NULL) return;
	v = dynamic_cast<XGScrollView *>(GetParent());
	
	/*
	 *	Initialize pixel step
	 */
	
	h = fEditCore->GetPixelStep();
	if (v) v->SetPixelStep(h);
	
	/*
	 *	Initialize the page size
	 */
	
	r = GetContentRect();
	if (!fNoFrame) ::InsetRect(&r,3,3);
	x = (r.right - r.left) / h;
	y = (r.bottom - r.top) / h;
	if (x < 1) x = 1;
	if (y < 1) y = 1;
	if (v) v->SetPageSize(x,y);
	
	/*
	 *	Initialize the max scroll ranges
	 */
	
	y = fEditCore->GetMaxHeight();
	x = fEditCore->GetMaxWidth();
	
	x = (x - r.right + r.left + h - 1) / h;
	maxy = (y - r.bottom + r.top + h - 1) / h;
	if (x < 0) x = 0;
	if (maxy < 0) maxy = 0;
	if (v) v->SetMaxValue(x,maxy);
	
	/*
	 *	And get the current value
	 */
	
	fEditCore->GetScrollValue(&x,&y);
	if (v) v->SetScrollValue(x,y);
}

/************************************************************************/
/*																		*/
/*	Initialization														*/
/*																		*/
/************************************************************************/

/*$	XGEditText::Init
 *
 *		Initialize this thing
 */

void XGEditText::Init(char *buffer, short font, XGSEditInitRecord &init)
{
	int i;
	XRect r;
	
	fFontPtr = XGFont::LoadFont(font);
	
	i = strlen(buffer);
	fTextHandle.SetSize(i);
	memcpy((char *)*fTextHandle,buffer,i);
	
	fNoFrame = init.noframe;
	fMaxChars = init.maxchars;
	fCharType = init.chartype;
	
	r = GetContentRect();
	if (!fNoFrame) ::InsetRect(&r,3,3);
	fEditCore = new XGEditCore(this,fFontPtr,r);
	fEditCore->SetText(&fTextHandle);
	if (init.password) fEditCore->SetPassword(true);
	if (!init.editor) fEditCore->SetFieldEditor(true);
	if (init.wordwrap) fEditCore->SetWordWrap(true);
	
	if (dynamic_cast<XGScrollView *>(GetParent())) {
		fEditCore->SetScrollPixels(false);
	}
}
