/*	TEditView.cpp
 *
 *		This contains the code which actually manages the editing window
 *	where the comments go.
 */

/*  XT - An example text editor application built with the YAAF library
 *
 *	PUBLIC DOMAIN: The source kit of 'xt' has been placed into the
 *	public domain by the author, William Edward Woody, on the hope
 *	that the source kit will provide a useful example of using the
 *	YAAF Library.
 *  
 *  To contact the author, either e-mail me at woody@pandawave.com, 
 *	or write to
 *  
 *          William Edward Woody
 *          In Phase Consulting
 *          1545 Ard Eevin Avenue
 *          Glendale, CA 91202
 *
 *	Or visit us on the web at http://www.pandawave.com
 */

#include "TEditView.h"
#include "TTextEditApp.h"

#include <string.h>
#include <XError.h>
#include <XDraw.h>
#include <XScroll.h>
#include <XString.h>
#include <XCursorUtil.h>
#include <XMessages.h>
#include <XDataUtil.h>
#include <XAppMenu.h>
#include <XPreferences.h>

#if OPT_XWIN
#include <time.h>
#include <sys/times.h>
#endif

/************************************************************************/
/*																		*/
/*	Class Declaration													*/
/*																		*/
/************************************************************************/

/*	TEditView::TEditView
 *
 *		Create one of me. No arguments
 */

TEditView::TEditView(XGOwner *owner, XGArgStream &stream) : XGView(owner,stream)
{
#if OPT_MACOS
	XGPreferences prefs("XTextEdit Prefs",'BTRC');
#endif

#if OPT_WINOS
	XGPreferences prefs("PandaWave","XTextEdit","1.0");
#endif

#if OPT_XWIN
	XGPreferences prefs("XTextEditRC");
#endif

	fLineHeight = 12;				// guess--refined later.
	fCaretState = false;
	fPosPixel = -1;
	fTabStop = prefs.GetInteger("TabStop");
	fAutoIndent = prefs.GetBoolean("AutoIndent");

	if (fTabStop < 2) fTabStop = 4;	
	fSearch[0] = 0;
	fReplace[0] = 0;
}

/*	TEditView::~TEditView
 *
 *		Delete one of me.
 */

TEditView::~TEditView()
{
}

/************************************************************************/
/*																		*/
/*	Class Declaration													*/
/*																		*/
/************************************************************************/

/*	TEditView::InitEdit
 *
 *		Initialize the editor with these two text handles
 */

void TEditView::InitEdit(XGHandle *e)
{
	fEditText.SetHandle(e);
	
	RecalcScroll();
	InvalView();
}

/************************************************************************/
/*																		*/
/*	Events																*/
/*																		*/
/************************************************************************/

/*	TEditView::DoDrawView
 *
 *		Handle drawing this view. Draws both the top text and the
 *	edited text, one on top of the other
 */

void TEditView::DoDrawView(Rect r)
{
	long x,y;
	unsigned long h;
	XGScrollView *v;
	unsigned long ypos;
	unsigned long sline,eline;
	unsigned long tmp;
	unsigned long n;
	XGHandle *handle;
	unsigned long linepos,linelen;
	unsigned long bselline,eselline;
	unsigned long bselchar,eselchar;
	unsigned long selstart,selend;
	Rect s;
	
	/*
	 *	Initialize drawing
	 */
	
	XGDraw draw(this);
	draw.SetFont(XGFont::LoadFont(2));
	fLineHeight = draw.GetFontHeight();
	draw.ClipRect(&r);
	
	/*
	 *	Quick abort in case of error
	 */
	
	if (fEditText.GetHandle() == NULL) {
		draw.EraseRect(r);
		return;
	}
	h = fLineHeight;
	ypos = h - draw.GetFontDescent();
	
	/*
	 *	Get the scrollbar locations
	 */
	
	v = dynamic_cast<XGScrollView *>(GetParent());
	v->GetScrollValue(&x,&y);
	
	sline = (unsigned long)(y + r.top / h);
	eline = (unsigned long)(y + (r.bottom + h - 1) / h);
		
	tmp = fEditText.GetNumLines();
	if (tmp > sline) {
		/*
		 *	Find the selection range
		 */
		
		fEditText.GetSelect(&selstart,&selend);
		bselline = fEditText.CharToLine(selstart);
		eselline = fEditText.CharToLine(selend);
		bselchar = selstart - fEditText.GetLineOffset(bselline);
		eselchar = selend   - fEditText.GetLineOffset(eselline);
		
		/*
		 *	Top lines are still visible
		 */
		
		handle = fEditText.GetHandle();
		XGHandleLock lock(*handle);

		for (n = sline; (n < eline) && (n < tmp); n++) {
			linepos = fEditText.GetLineOffset(n);
			linelen = fEditText.GetLineLength(n);
			
			s = r;									// erase line
			s.top = (short)(h * (n - y));
			s.bottom = (short)(s.top + h);
			draw.EraseRect(s);
			
			/*
			 *	Draw the text (if there is any to draw)
			 */
			
			if (linelen) {
				draw.MoveTo((short)(5-x*h),(short)(ypos + h * (n - y)));
				DrawText(draw,(char *)**handle + linepos,(short)linelen);
			}
			
			/*
			 *	Draw the hilite
			 */
			
			if ((n >= bselline) && (n <= eselline) &&
					(selstart != selend) && IsActive()) {
				
				/*
				 *	Draw selection into the background
				 */
				
				if (n == bselline) {
					/*
					 *	Set left
					 */
					
					if (bselchar > 0) {
						s.left = (short)(5 - x * h);
						s.left += (short)TextWidth(draw,(char *)**handle+linepos,bselchar);
					}
					if (s.left < r.left) s.left = r.left;
				}
				if (n == eselline) {
					/*
					 *	Set right
					 */
					
					s.right = s.left;
					if (eselchar > 0) {
						s.right = (short)(5 - x * h);
						s.right += (short)TextWidth(draw,(char *)**handle+linepos,eselchar);
					}
					if (s.right > r.right) s.right = r.right;
				}
				if (s.left < s.right) {
					/* Draw the hilite background */
					draw.SetForeColor(KXGColorHilite);
					draw.PaintRect(s);
					draw.SetForeColor(KXGColorHiliteText);
					
					/* Clip and draw the text in the hilite area */
					draw.ClipRect(&s);
					draw.MoveTo((short)(5-x*h),(short)(ypos + h * (n - y)));
					DrawText(draw,(char *)**handle + linepos,(short)linelen);
					draw.ClipRect();
					draw.SetForeColor(KXGColorBlack);
				}
			}
		}
		
		if ((selstart == selend) && fCaretState) InvertCaret();
	}

	/*
	 *	Is there still stuff left over?
	 */
	
	if (tmp < eline) {
		r.top = (short)(h * (tmp - y));
		draw.EraseRect(r);
	}
}

/*	TEditView::DoActivate
 *
 *		Handle text activation/deactivation
 */

void TEditView::DoActivate(bool)
{
	if (fCaretState) {
		InvertCaret();
		fCaretState = false;
	}
	InvalView();
}

/*	TEditView::DoSizeView
 *
 *		Handle resize of the view
 */

void TEditView::DoSizeView()
{
	RecalcScroll();
	XGView::DoSizeView();
	InvalView();
}

/*	TEditView::DoMouseDown
 *
 *		Handle the mouse down event
 */

bool TEditView::DoMouseDown(Point where, short flags)
{
	long x,y;
	unsigned long s,e;
	unsigned long h;
	Rect r;
	XGScrollView *v;
	
	/*
	 *	We're inside. Make sure the cursor is correct
	 */
	
	SetCursorID(KIBeamCursor);
	if (!SetFocus()) return false;				// try for the focus
	fPosPixel = -1;
	
	/*
	 *	Do the hit detection
	 */
	
	fEndSelect = HitDetect(where);
	if (fEndSelect == -1) return false;			// above the hit detect area
	
	/*
	 *	Determine the start, end select areas
	 */
	
	fEditText.GetSelect(&s,&e);
	
	if (flags & KKeyShift) {
		if (fEndSelect < s) {
			fStartSelect = e;
		} else {
			fStartSelect = s;
		}
	} else {
		fStartSelect = fEndSelect;
	}
	
	/*
	 *	Now figure out the start, end lines to draw
	 */
	
	if (s > fEndSelect) s = fEndSelect;			// grow select by the endpoint
	if (e < fEndSelect) e = fEndSelect;
	s = fEditText.CharToLine(s);
	e = fEditText.CharToLine(e);
	
	v = dynamic_cast<XGScrollView *>(GetParent());
	v->GetScrollValue(&x,&y);
	r = GetContentRect();
	h = (r.bottom - r.top + fLineHeight - 1)/fLineHeight;
	
	fEndSelect = fEndSelect;
	fEditText.SetSelect(fStartSelect,fEndSelect);
	s -= (unsigned long)y;
	e -= (unsigned long)y;
	if (s < 0) s = 0;
	if (e > h) e = h;
	
	/*
	 *	And redraw them. This updates the display to show the select
	 *	correctly
	 */
	
	if (s <= e) {
		r.top = (short)(fLineHeight * s);
		r.bottom = (short)(fLineHeight * (e + 1));
		DoDrawView(r);
	}
	
	/*
	 *	Done.
	 */
	
	return true;
}

/*	TEditView::DoMouseMove
 *
 *		Handle mouse move; this handles the selection changes
 */

void TEditView::DoMouseMove(Point where, short flags)
{
	unsigned long s,e;
	long x,y;
	unsigned long ptr;
	unsigned long h;
	XGScrollView *v;
	Rect r;
	
	if (!(flags & KCapturedMouse)) {
		SetCursorID(KIBeamCursor);
		return;
	}
	
	/*
	 *	Auto scroll?
	 */
	
	v = dynamic_cast<XGScrollView *>(GetParent());
	v->GetScrollValue(&x,&y);
	r = GetContentRect();
	if (where.h > r.right) x++;
	else if (where.h < r.left) x--;
	else if (where.v > r.bottom) y++;
	else if (where.v < r.top) y--;
	v->SetScrollValue(x,y);
	v->GetScrollValue(&x,&y);
	
	/*
	 *	Figure out where the new selection is
	 */
	
	ptr = HitDetect(where);
	if (ptr == -1) ptr = 0;				// drag above top
	if (ptr == fEndSelect) return;		// didn't change...
	
	/*
	 *	Initialize code to find the selection range that changed.
	 */
	
	s = fEditText.CharToLine(ptr) - y;
	e = fEditText.CharToLine(fEndSelect) - y;
	if (s > e) {
		unsigned long tmp = s;
		s = e;
		e = tmp;
	}

	fEditText.SetSelect(fStartSelect,ptr);
	fEndSelect = ptr;
	
	h = (r.bottom - r.top + fLineHeight - 1)/fLineHeight;
	if (s < 0) s = 0;
	if (e > h) e = h;

	if (s <= e) {
		r.top = (short)(fLineHeight * s);
		r.bottom = (short)(fLineHeight * (e + 1));
		DoDrawView(r);
	}
}

/*	TEditView::DoMouseUp
 *
 *		Handle mouse up. Does very bloody little, as the display should
 *	be correctly updated
 */

void TEditView::DoMouseUp(Point where, short flags)
{
	if (!(flags & KCapturedMouse)) {
		XGView::DoMouseUp(where,flags);
	}
}

/*	TEditView::LoseFocus
 *
 *		If I lose the focus, make sure the caret is erased
 */

void TEditView::LoseFocus()
{
	if (fCaretState) {
		InvertCaret();
		fCaretState = false;
	}
}


/************************************************************************/
/*																		*/
/*	Internal stuff														*/
/*																		*/
/************************************************************************/

/*	TEditView::RecalcScroll
 *
 *		Calculate all the scrollbar parameters
 */

void TEditView::RecalcScroll()
{
	long x,y;
	long h;
	XGScrollView *v;
	Rect r;
	
	/*
	 *	Quick abort in case of error
	 */
	
	if (fEditText.GetHandle() == NULL) {
		return;
	}
	
	/*
	 *	Initialize pixel step
	 */
	
	XGDraw draw(this);
	draw.SetFont(XGFont::LoadFont(2));
	h = draw.GetFontHeight();
	v = dynamic_cast<XGScrollView *>(GetParent());
	
	v->SetPixelStep(h);
	
	/*
	 *	Initialize the page size
	 */
	
	r = GetContentRect();
	x = (r.right - r.left) / h;
	y = (r.bottom - r.top) / h;
	if (x < 1) x = 1;
	if (y < 1) y = 1;
	
	v->SetPageSize(x,y);
	
	/*
	 *	Initialize the max scroll ranges
	 */
	
	y = (long)fEditText.GetNumLines()-1;
	if (y < 0) y = 0;
	v->SetMaxValue(1000,y);
}

/*	TEditView::HitDetect
 *
 *		Hit detect. Convert from on-screen location to the char pos
 *	where the point was placed
 */

unsigned long TEditView::HitDetect(Point where)
{
	XGDraw draw(this);
	draw.SetFont(XGFont::LoadFont(2));
	long x,y;
	XGScrollView *v;
	unsigned long pos;
	unsigned long line;
	unsigned long schar,echar;
	long ws,we,wm;
	unsigned long h;
	char *ltext;
	XGHandle *handle;
	
	v = dynamic_cast<XGScrollView *>(GetParent());
	v->GetScrollValue(&x,&y);
	
	/*
	 *	What line am I on?
	 */
	
	h = draw.GetFontHeight();
	line = (unsigned long)(y + where.v / h);
	if (line >= fEditText.GetNumLines()) {
		// past top; return the last position.
		return fEditText.GetHandle()->GetSize();
	}
	
	/*
	 *	Now determine the character position
	 */
	
	schar = fEditText.GetLineOffset(line);
	echar = schar + fEditText.GetLineLength(line);
	
	/*
	 *	And figure out at what character the mouse is down
	 */
	
	handle = fEditText.GetHandle();
	XGHandleLock lock(*handle);
	ltext = (char *)**handle;
	we = 5 - x * h;
	for (pos = schar; pos < echar; pos++) {
		ws = we;
		we = 5 - x * h + TextWidth(draw,ltext+schar,(short)(1+pos-schar));
		
		wm = (ws + we) / 2;
		if (where.h < wm) break;
	}

	return pos;
}

/*	TEditView::InvertCaret
 *
 *		Calculate where the caret is on the screen, and if it's visible,
 *	invert it.
 */

void TEditView::InvertCaret()
{
	unsigned long s,e;
	long x,y;
	XGScrollView *v;
	Rect r;
	unsigned long t,b;
	unsigned long cpos;
	unsigned long line;
	char *txt;
	
	/*
	 *	And calculate where this thing actually is
	 */
	
	v = dynamic_cast<XGScrollView *>(GetParent());
	v->GetScrollValue(&x,&y);
	fEditText.GetSelect(&s,&e);
	if (s != e) return;
	
	r = GetContentRect();
	t = y + r.top / fLineHeight;
	b = y + (r.bottom + fLineHeight - 1)/fLineHeight;
	
	line = fEditText.CharToLine(s);
	if ((line < t) || (line > b)) return;		// not visible
	
	XGDraw draw(this);
	draw.SetFont(XGFont::LoadFont(2));
	
	cpos = (unsigned long)(5 - x * fLineHeight);
	e = s;
	s = fEditText.GetLineOffset(line);
	
	if (e > s) {
		XGHandle *handle = fEditText.GetHandle();
		XGHandleLock lock(*handle);

		txt = (char *)**handle;
		cpos += (short)TextWidth(draw,txt+s,e-s);
	}
	
	if ((cpos < r.left) || (cpos > r.right)) return;

	/*
	 *	We have the location of this caret, and it's visible.
	 *	Invert it
	 */
	
	r.top = (short)((line - y) * fLineHeight);
	r.bottom = r.top + (short)fLineHeight;
	r.left = (short)(cpos-1);
	r.right = (short)cpos;
	
	draw.InvertRect(r);
}

/*	TEditView::ScrollInRange
 *
 *		Scroll this thing into range
 */

void TEditView::ScrollInRange(void)
{
	long x,y;
	XGScrollView *v;
	Rect r;
	unsigned long s,e;
	unsigned long top,bottom;
	long delta;
	long deltax;
	long lchar,rchar;
	unsigned long start;
	unsigned long lcpos,rcpos;
	
	/*
	 *	Calculate the selection top, bottom lines
	 */
	
	fEditText.GetSelect(&s,&e);
	s = fEditText.CharToLine(s);
	e = fEditText.CharToLine(e);
	
	if (s == e) {
		/*
		 *	Calculate left, right scrolls
		 */
		
		XGDraw draw(this);
		XGHandle *handle;
		draw.SetFont(XGFont::LoadFont(2));
		
		char *text;
		fEditText.GetSelect(&lcpos,&rcpos);
		start = fEditText.GetLineOffset(s);
		lcpos -= start;
		rcpos -= start;
		
		handle = fEditText.GetHandle();
		XGHandleLock lock(*handle);
		text = (char *)**handle;
		lchar = TextWidth(draw,text + start, lcpos);
		rchar = TextWidth(draw,text + start, rcpos);
	}
	
	v = dynamic_cast<XGScrollView *>(GetParent());
	v->GetScrollValue(&x,&y);
	r = GetContentRect();
	
	top = (unsigned long)y;
	bottom = y + (r.bottom - r.top) / fLineHeight - 1;
	if (bottom < top) bottom = top;
	
	if (s == e) {
		Rect r;
		
		deltax = 0;
		r = GetContentRect();
		lchar += 5 - x * fLineHeight;
		rchar += 5 - x * fLineHeight;
		if (lchar < r.left) {
			deltax = - (long)((r.left - lchar + fLineHeight - 1) / fLineHeight);
			deltax -= 10;			// and some...
		} else {
			if (rchar > r.right) {
				deltax = (long)((rchar - r.right + fLineHeight - 1) / fLineHeight);
				lchar -= deltax * fLineHeight;
				deltax += 10;		// and then some.
				if (lchar < r.left) {
					deltax -= (long)((r.left - lchar + fLineHeight - 1) / fLineHeight);
					deltax -= 10;	// and then some
				}
			}
		}
	}
	
	/*
	 *	Now see if I have to change the scroll
	 */
	
	delta = 0;
	if (s < top) {
		delta = (long)s - (long)top;			// decrement y
	} else {
		if (e > bottom) {
			delta = (long)e - (long)bottom;
			if (s < top + delta) {
				delta = delta + (long)s - (long)top;
			}
		}
	}
	if (delta || ((s == e) && deltax)) {
		if (s == e) {
			v->SetScrollValue(x+deltax,y+delta);
		} else {
			v->SetScrollValue(0,y+delta);	// multiline==scroll hard left
		}
	}
}


/************************************************************************/
/*																		*/
/*	Events																*/
/*																		*/
/************************************************************************/

BEGINDISPATCH(TEditView,XGView)
	DISPATCHTABLE(KXGAppPeriodic,TEditView::DoPeriodic)
	DISPATCHTABLE(KXGKeyEvent,TEditView::DoKey)
	DISPATCHTABLE(KXGGetMenuStatus,TEditView::DoGetMenuStatus)
	DISPATCHTABLE(KXGDoMenuCommand,TEditView::DoMenuCommand)
ENDDISPATCH

/*	TEditView::DoPeriodic
 *
 *		Handle periodic events
 */

long TEditView::DoPeriodic(long, void *)
{
	unsigned long s,e;
	static unsigned long gTick;
	
#if OPT_WINOS
	if (gTick > ::GetTickCount()) return -1;
	gTick = ::GetTickCount() + 500;
#endif

#if OPT_MACOS
	if (gTick > ::TickCount()) return -1;
	gTick = ::TickCount() + ::GetCaretTime();;
#endif

#if OPT_XWIN
	struct tms buf;
	clock_t t;
	
	/*
	 *	'cause clock() returns CPU time, not user time
	 */
	
	t = times(&buf);
	if (gTick > t) return -1;
	gTick = t + CLK_TCK / 2;
#endif
	
	fEditText.GetSelect(&s,&e);
	if (s == e) {
		fCaretState = !fCaretState;
		InvertCaret();
	}
	
	return -1;
}

/*	TEditView::DoKeyEvent
 *
 *		Handle the keyboard event
 */

long TEditView::DoKey(long msg, void *)
{
	short f;
	short c;
	
	c = (short)GETLOWORD(msg);
	f = (short)GETHIWORD(msg);
	
	if (f & KSpecialKey) {
		if (c == KKeyDelete) {
			DeleteChar();
			ScrollInRange();
			return 0;
		}
		
		if (c == KKeyLeftCursor) {
			DoCursorLR(true,(f & KKeyShift) ? true : false);
			return 0;
		} else if (c == KKeyRightCursor) {
			DoCursorLR(false,(f & KKeyShift) ? true : false);
			return 0;
		} else if (c == KKeyUpCursor) {
			DoCursorUD(true,(f & KKeyShift) ? true : false);
			return 0;
		} else if (c == KKeyDownCursor) {
			DoCursorUD(false,(f & KKeyShift) ? true : false);
			return 0;
		}
	} else {
#if OPT_MACOS
		// screen out control keys
		if (f & KKeyControl) return -1;
#endif
		if (c == 0x08) {
			DeleteChar();
			ScrollInRange();
		} else {
			InsertChar((char)c);
			ScrollInRange();
		}
		return 0;
	}
	return -1;
}

/*	TEditView::DoGetMenuStatus
 *
 *		Get the current menu status
 */

long TEditView::DoGetMenuStatus(long arg, void *parg)
{
	unsigned long s,e;
	XGSMenuStatus *stat = (XGSMenuStatus *)parg;
	
	switch (arg) {
		case KXGEditCut:
		case KXGEditCopy:
		case KXGEditClear:
			fEditText.GetSelect(&s,&e);
			stat->enable = (s != e);
			return 0;
		case KXGEditPaste:
		case KMenuEditPrefs:
			stat->enable = true;
			return 0;
			
		case KMenuSearchFind:
		case KMenuSearchFindAgain:
		case KMenuSearchReplace:
		case KMenuSearchReplaceAndFind:
		case KMenuSearchGoto:
			stat->enable = true;
			return 0;
		default:
			return -1;
	}
}

/*	TEditView::DoMenuCommand
 *
 *		Get the current menu status
 */

long TEditView::DoMenuCommand(long arg, void *)
{
	switch (arg) {
		case KMenuEditPrefs:
			PrefsDialog();
			return 0;
		case KXGEditCut:
			DoCut();
			return 0;
		case KXGEditCopy:
			DoCopy();
			return 0;
		case KXGEditPaste:
			DoPaste();
			return 0;
		case KXGEditClear:
			DoClear();
			return 0;
		case KMenuSearchFind:
			FindDialog();
			return 0;
		case KMenuSearchFindAgain:
			FindNext();
			return 0;
		case KMenuSearchReplace:
			Replace();
			return 0;
		case KMenuSearchReplaceAndFind:
			ReplaceNext();
			return 0;
		case KMenuSearchGoto:
			GotoDialog();
			return 0;
		default:
			return -1;
	}
}

/************************************************************************/
/*																		*/
/*	Low level support													*/
/*																		*/
/************************************************************************/

/*	TEditView::InsertChar
 *
 *		Insert a character
 */

void TEditView::InsertChar(char c)
{
	unsigned long olen;
	Rect r;
	
	fPosPixel = -1;
	ScrollInRange();
	if ((c == 0x0A) || (c == 0x0D)) {
		c = '\n';			// make sure it's correct
	}
	
	olen = fEditText.GetNumLines();
	fEditText.InsertChar(c);
	if ((c == '\n') && fAutoIndent) AutoIndent();
	MarkDirty();
	if ((olen != fEditText.GetNumLines()) || (c == '\n')) {
		/* newline change... */
		r = GetContentRect();
		DoDrawView(r);
		RecalcScroll();
	} else {
		/* Only update the current line */
		DrawCurLine();
	}
}

/*	TEditView::DeleteChar
 *
 *		Insert a character
 */

void TEditView::DeleteChar()
{
	unsigned long olen;
	Rect r;
	
	fPosPixel = -1;
	ScrollInRange();
	olen = fEditText.GetNumLines();
	fEditText.DeleteChar();
	MarkDirty();
	if (olen != fEditText.GetNumLines()) {
		/* newline change... */
		r = GetContentRect();
		DoDrawView(r);
		RecalcScroll();
	} else {
		/* Just draw the line */
		DrawCurLine();
	}
}
	
/*	TEditView::DrawCurLine
 *
 *		Draw the current line.
 */

void TEditView::DrawCurLine(unsigned long sel)
{
	unsigned long s,e;
	unsigned long line;
	XGScrollView *v;
	Rect r;
	Rect sr;
	char *data;
	long x,y;
	unsigned long tmp;
	unsigned long leftsel,rightsel;

	/*
	 *	Initialize stuff
	 */
	
	r = GetContentRect();
	v = dynamic_cast<XGScrollView *>(GetParent());
	v->GetScrollValue(&x,&y);
	
	if (sel == 0xFFFFFFFF) {
		fEditText.GetSelect(&s,&e);
		sel = s;
	}
	
	line = fEditText.CharToLine(sel);
	sel = (unsigned long)(line - y);
	if ((sel >= 0) && (sel * fLineHeight < r.bottom)) {
		r.top = (short)(sel * fLineHeight);
		r.bottom = r.top + (short)fLineHeight;
		
		/*
		 *	We're visible. Erase the line and redraw it
		 */
		
		XGDraw draw(this);
		draw.SetFont(XGFont::LoadFont(2));			// draw my line
		draw.EraseRect(r);
		XGHandle *handle;
		
		/*
		 *	Draw the line
		 */
		
		fEditText.GetSelect(&leftsel,&rightsel);
		s = fEditText.GetLineOffset(line);
		e = fEditText.GetLineLength(line);
		
		handle = fEditText.GetHandle();
		XGHandleLock lock(*handle);
		data = (char *)**handle;
		draw.MoveTo((short)(5 - x * fLineHeight), 
					(short)(r.bottom - draw.GetFontDescent()));
		DrawText(draw,data+s,(short)e);

		/*
		 *	Figure out the selection range
		 */
		
		tmp = s + e + 1;
		if (((tmp > leftsel) || (rightsel > s)) && (leftsel != rightsel)) {
			/* Selection overlap */
			if (leftsel <= s) leftsel = (unsigned long)r.left;
			else {
				leftsel  = TextWidth(draw,data+s,(short)(leftsel - s));
				leftsel += r.left + 5 - x * fLineHeight;
			}
			if (rightsel >= tmp) rightsel = (unsigned long)r.right;
			else {
				rightsel = TextWidth(draw,data+s,(short)(rightsel - s));
				rightsel += r.left + 5 - x * fLineHeight;
			}
			
			sr = r;
			sr.left = (short)leftsel;
			sr.right = (short)rightsel;
			if (sr.left < sr.right) {
				/* Draw the hilite */
				draw.SetForeColor(KXGColorHilite);
				draw.PaintRect(sr);
				draw.SetForeColor(KXGColorHiliteText);
					
				/* Clip and draw the text in the hilite area */
				draw.ClipRect(&sr);
				draw.MoveTo((short)(5 - x * fLineHeight), 
							(short)(r.bottom - draw.GetFontDescent()));
				DrawText(draw,data+s,(short)e);
				draw.ClipRect();
				draw.SetForeColor(KXGColorBlack);
			}
		}
	}
	
	/*
	 *	Draw the caret
	 */
	
	if (fCaretState) InvertCaret();
}

/*	TEditView::AutoIndent
 *
 *		Handle autoindent
 */

void TEditView::AutoIndent(void)
{
	unsigned long s,e;
	unsigned long line;
	unsigned long x;
	XGHandle *h;
	char c;
	
	fEditText.GetSelect(&s,&e);
	line = fEditText.CharToLine(s);
	if (line == 0) return;
	
	s = fEditText.GetLineOffset(line-1);
	e = fEditText.GetLineLength(line-1);
	h = fEditText.GetHandle();
	for (x = 0; x < e; x++) {
		c = ((char *)(**h))[x+s];
		if ((c != '\t') && (c != ' ')) break;
		fEditText.InsertChar(c);
	}
}

/************************************************************************/
/*																		*/
/*	Dirty bit management												*/
/*																		*/
/************************************************************************/

/*	TEditView::MarkDirty
 *
 *		Mark the dirty bit
 */

void TEditView::MarkDirty()
{
	DoDispatch(KMarkDirty,0,NULL);
}

/************************************************************************/
/*																		*/
/*	Cursor arrow support												*/
/*																		*/
/************************************************************************/

/*	TEditView::DoCursorLR
 *
 *		Move the cursor left
 */

void TEditView::DoCursorLR(bool left, bool shift)
{
	unsigned long s,e;
	unsigned long sline,eline;
	unsigned long tsize;
	Rect r;
	
	if (!left) tsize = fEditText.GetHandle()->GetSize();
		
	fEditText.GetSelect(&s,&e);
	if (shift) {
		/*
		 *	Move the left/right select off by one
		 */
		
		if (left) {
			/* Left: move left range */
			if (s <= 0) return;
			sline = fEditText.CharToLine(s);
			s--;
			eline = fEditText.CharToLine(s);
		} else {
			/* Right: move right range */
			if (e >= tsize) return;
			sline = fEditText.CharToLine(e);
			e++;
			eline = fEditText.CharToLine(e);
		}
		
		fEditText.SetSelect(s,e);
		if (sline != eline) {
			/*
			 *	Cursor moved up a line. Scroll in range and
			 *	redraw the entire thing
			 */
			
			r = GetContentRect();
			DoDrawView(r);
		} else {
			/*
			 *	Cursor doesn't; just redraw line
			 */
			 
			if (left) DrawCurLine(s);
			else DrawCurLine(e);
		}
	} else {
		/*
		 *	Move the cursor around
		 */
		
		if (s == e) {
			if (left) {
				if (s > 0) s--;
				else return;
			} else {
				if (e < tsize) e++;
				else return;
			}
		}
		sline = fEditText.CharToLine(s);
		eline = fEditText.CharToLine(e);
		
		if (left) fEditText.SetSelect(s,s);
		else fEditText.SetSelect(e,e);
		
		if (sline != eline) {
			/*
			 *	Edit selection changed...
			 */
			
			r = GetContentRect();
			DoDrawView(r);
		} else {
			/*
			 *	Edit selection didn't move up a line
			 */
			
			if (left) DrawCurLine(s);
			else DrawCurLine(e);
		}
	}

	/*
	 *	Make sure whole thing is visible
	 */
	
	fPosPixel = -1;
	ScrollInRange();
}

/*	TEditView::FindPrevNext
 *
 *		Find the previous/next line
 */

unsigned long TEditView::FindPrevNext(unsigned long pos, bool up)
{
	XGDraw draw(this);
	draw.SetFont(XGFont::LoadFont(2));
	unsigned long line;
	unsigned long start;
	unsigned long tmp;
	unsigned long len;
	long wl,wr,wm;
	char *text;
	XGHandle *handle;
	
	handle = fEditText.GetHandle();
	XGHandleLock lock(*handle);
	text = (char *)**handle;
	line = fEditText.CharToLine(pos);

	if (fPosPixel == -1) {
		start = fEditText.GetLineOffset(line);

		/*
		 *	Find the number of pixels over the cursor is
		 */
		
		if (start < pos) {
			fPosPixel = TextWidth(draw,text+start,(short)(pos-start));
		} else {
			fPosPixel = 0;
		}
	}
	
	/*
	 *	Now see if I can move up or down a line
	 */
	 
	if (up) {
		if (line <= 0) {
			return pos;
		}
		line--;
	} else {
		line++;
		if (line >= fEditText.GetNumLines()) {
			return pos;
		}
	}
	start = fEditText.GetLineOffset(line);
	
	/*
	 *	And figure out the character position based on the pixel
	 *	position
	 */
	
	wr = 0;
	len = fEditText.GetLineLength(line);
	if (len == 0) {
		return start;
	}
	
	for (tmp = 1; tmp <= len; tmp++) {
		wl = wr;
		wr = TextWidth(draw,text+start,(short)tmp);
		wm = (wl + wr) / 2;
		if (fPosPixel < wm) break;
	}
	return start + tmp - 1;
}

/*	TEditView::DoCursorUD
 *
 *		Move the cursor up/down
 */

void TEditView::DoCursorUD(bool up, bool shift)
{
	unsigned long s,e;
	unsigned long sline,eline;
	Rect r;
	unsigned long tmp;
	
	fEditText.GetSelect(&s,&e);
	if (shift) {
		/*
		 *	Move the up/down select off by one
		 */
		
		if (up) {
			/* up: move left range */
			sline = fEditText.CharToLine(s);
			tmp = FindPrevNext(s,up);
			if (tmp == s) return;
			s = tmp;
			eline = fEditText.CharToLine(s);
		} else {
			/* Right: move right range */
			sline = fEditText.CharToLine(e);
			tmp = FindPrevNext(e,up);
			if (tmp == e) return;
			e = tmp;
			eline = fEditText.CharToLine(e);
		}
		
		fEditText.SetSelect(s,e);
		if (sline != eline) {
			/*
			 *	Cursor moved up a line. Scroll in range and
			 *	redraw the entire thing
			 */
			
			r = GetContentRect();
			DoDrawView(r);
		} else {
			/*
			 *	Cursor doesn't; just redraw line
			 */
			 
			if (up) DrawCurLine(s);
			else DrawCurLine(e);
		}
	} else {
		/*
		 *	Move the cursor around
		 */
		
		if (s == e) {
			if (up) {	
				tmp = FindPrevNext(s,up);
				if (tmp == s) return;
				s = tmp;
			} else {
				tmp = FindPrevNext(e,up);
				if (tmp == e) return;
				e = tmp;
			}
		}
		sline = fEditText.CharToLine(s);
		eline = fEditText.CharToLine(e);
		
		if (up) fEditText.SetSelect(s,s);
		else fEditText.SetSelect(e,e);
		
		if (sline != eline) {
			/*
			 *	Edit selection changed...
			 */
			
			r = GetContentRect();
			DoDrawView(r);
		} else {
			/*
			 *	Edit selection didn't move up a line
			 */
			
			if (up) DrawCurLine(s);
			else DrawCurLine(e);
		}
	}

	/*
	 *	Make sure whole thing is visible
	 */
	
	ScrollInRange();
}


/************************************************************************/
/*																		*/
/*	Edit menu support													*/
/*																		*/
/************************************************************************/

#if OPT_XWIN
extern XGHandle	GClipboard;				// defined in YAAF libraries
#endif

#if OPT_WINOS

/*	CvtToCRLF
 *
 *		CRLF conversion of text handle
 */

static HANDLE CvtToCRLF(XGHandle &in)
{
	unsigned long i,len;
	char *x;
	unsigned long nlen;
	HANDLE h;
	char *ptr;
	char c;

	/*
	 *	Figure out the size needed for the handle
	 */
		
	len = in.GetSize();
	XGHandleLock lock(in);
	x = (char *)*in;
	i = 0;
	nlen = 1;
	while (i < len) {
		if ((x[i] == 0x0D) || (x[i] == 0x0A)) {
			c = x[i];
			i++;
			nlen += 2;
			if (i >= len) break;
			if ((c != x[i]) && ((x[i] == 0x0D) || (x[i] == 0x0A))) {
				i++;
			}
		} else {
			nlen++;
			i++;
		}
	}
	
	/*
	 *	Create a movable handle of the correct size
	 */
	
	h = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE,nlen);
	if (!h) throw XGPostError(KXGOutMemory);
	ptr = (char *)::GlobalLock(h);
	
	/*
	 *	Copy the data into the CF_TEXT format expected by Windows
	 */
	
	nlen = 0;
	i = 0;
	while (i < len) {
		if ((x[i] == 0x0D) || (x[i] == 0x0A)) {
			c = x[i];
			ptr[nlen++] = 0x0D;
			ptr[nlen++] = 0x0A;
			i++;
			if (i >= len) break;
			if ((c != x[i]) && ((x[i] == 0x0D) || (x[i] == 0x0A))) {
				i++;
			}
		} else ptr[nlen++] = x[i++];
	}
	ptr[nlen++] = 0;
	
	/*
	 *	Unlock and return
	 */
	
	::GlobalUnlock(h);
	return h;
}

/*	CvtFromCRLF
 *
 *		CRLF conversion of text handle
 */

static void CvtFromCRLF(XGHandle &out, HANDLE in)
{
	char *ptr;
	unsigned long hsize;
	unsigned long len;
	unsigned long i;
	char *optr;
	
	/*
	 *	Figure out the size
	 */
	
	hsize = ::GlobalSize(in);
	ptr = (char *)::GlobalLock(in);
	for (len = 0, i = 0; i < hsize; i++) {
		if (ptr[i] == 0) return;
		if (ptr[i] != 0x0A) len++;
	}
	
	/*
	 *	Now set the size and copy
	 */
	
	out.SetSize(len);
	
	XGHandleLock lock(out);
	optr = (char *)*out;
	for (len = 0, i = 0; i < hsize; i++) {
		if (ptr[i] == 0) return;
		if (ptr[i] == 0x0D) {
			optr[len++] = '\n';
		} else {
			optr[len++] = ptr[i];
		}
	}
	::GlobalUnlock(in);
}

#endif // OPT_WINOS

/*	TEditView::DoCut
 *
 *		Handle cut command
 */

void TEditView::DoCut()
{
	DoCopy();
	DoClear();
}

/*	TEditView::DoCopy
 *
 *		Handle copy command. This copies the data out of the text
 *	into a local handle, and slams that data into the scrap
 */

void TEditView::DoCopy()
{
#if OPT_MACOS
	XGHandle text;
	
	fEditText.CopyText(text);
	::ZeroScrap();
	if (text.GetSize()) {
		XGHandleLock lock(text);
		::PutScrap((long)text.GetSize(),'TEXT',(Ptr)*text);
	}
#endif

#if OPT_XWIN
	fEditText.CopyText(GClipboard);
#endif

#if OPT_WINOS
	XGHandle text;
	HANDLE h;
	
	/*
	 *	Copy text
	 */
	
	fEditText.CopyText(text);
	h = CvtToCRLF(text);
	text.SetSize(0);						// zero out old handle
	
	/*
	 *	Convert to a global handle
	 */
	 
	::OpenClipboard(NULL);
	::EmptyClipboard();
	::SetClipboardData(CF_TEXT,h);
	::CloseClipboard();
#endif
}

/*	TEditView::DoPaste
 *
 *		Do the paste command
 */

void TEditView::DoPaste()
{
	Rect r;
	
	ScrollInRange();
	fPosPixel = -1;

#if OPT_MACOS
	XGHandle text;
	Handle h;
	long res;
	long offset;
	
	h = ::NewHandle(0);
	res = ::GetScrap(h,'TEXT',&offset);
	if (res < 0) {
		text.SetSize(0);
	} else {
		text.SetSize((unsigned long)::GetHandleSize(h));
		memcpy(*text,*h,text.GetSize());
	}
	::DisposeHandle(h);
	
	fEditText.PasteText(text);
#endif

#if OPT_XWIN
	fEditText.PasteText(GClipboard);
#endif

#if OPT_WINOS
	HANDLE h;
	XGHandle text;
	
	/*
	 *	Get text
	 */
	
	::OpenClipboard(NULL);
	h = ::GetClipboardData(CF_TEXT);
	if (!h) {
		text.SetSize(0);
	} else {
		CvtFromCRLF(text,h);
	}
	::CloseClipboard();
	
	fEditText.PasteText(text);
#endif

	r = GetContentRect();
	DoDrawView(r);
	RecalcScroll();
	MarkDirty();
}

/*	TEditView::DoClear
 *
 *		Clear
 */

void TEditView::DoClear()
{
	Rect r;
	
	fPosPixel = -1;
	ScrollInRange();
	fEditText.ClearText();
	r = GetContentRect();
	DoDrawView(r);
	RecalcScroll();
	MarkDirty();
}

/*	TEditView::DrawText
 *
 *		Draw a line of text with tabs
 */

void TEditView::DrawText(XGDraw &draw, char *line, unsigned long len)
{
	unsigned long x;
	unsigned long pixel = 0;
	unsigned long pixtab = draw.StringWidth("m") * fTabStop;
	unsigned long i = 0;
	unsigned long newpix;
	Point pos;
	
	/*
	 *	Quick abort
	 */
	
	if (len == 0) return;
	
	/*
	 *	Scan the text and draw the non-tab stuff
	 */
	
	while (i < len) {
		/*
		 *	Find tab
		 */
		
		for (x = i; x < len; x++) {
			if (line[x] == '\t') break;
		}
		
		/*
		 *	Draw non-tab stuff
		 */
		
		if (x > i) {
			draw.DrawString(line+i,x-i);
			pixel += draw.StringWidth(line+i,x-i);
		}
		
		if (x >= len) break;
		i = x;
		
		/*
		 *	Now handle tab
		 */
		
		newpix = pixel;
		newpix += pixtab - newpix % pixtab;
		pos = draw.GetPenPos();
		pos.h += (short)(newpix - pixel);
		pixel = newpix;
		
		draw.MoveTo(pos.h,pos.v);
		i++;
	}
}

/*	TEditView::TextWidth
 *
 *		Draw a line of text with tabs
 */

long TEditView::TextWidth(XGDraw &draw, char *line, unsigned long len)
{
	unsigned long x;
	unsigned long pixel = 0;
	unsigned long pixtab = draw.StringWidth("m") * fTabStop;
	unsigned long i = 0;
	
	/*
	 *	Quick abort
	 */
	
	if (len == 0) return 0;
	
	/*
	 *	Scan the text and draw the non-tab stuff
	 */
	
	while (i < len) {
		/*
		 *	Find tab
		 */
		
		for (x = i; x < len; x++) {
			if (line[x] == '\t') break;
		}
		
		/*
		 *	Draw non-tab stuff
		 */
		
		if (x > i) {
			pixel += draw.StringWidth(line+i,x-i);
		}
		
		if (x >= len) break;
		i = x;
		
		/*
		 *	Now handle tab
		 */
		
		pixel += pixtab - pixel % pixtab;
		i++;
	}
	
	return (long)pixel;
}

/************************************************************************/
/*																		*/
/*	Find Routines														*/
/*																		*/
/************************************************************************/

/*	TEditView::FindDialogProc
 *
 *		Manage the dialog
 */

long TEditView::FindDialogProc(XGDialog *dlog, void *data, long msg, long arg, void *)
{
	TEditView *ptr = (TEditView *)data;
	
	switch (msg) {
		case KXGDialogMessage:
			if (arg == KXGDMInitDialog) {
				dlog->SetText(1,ptr->fSearch);
				dlog->SetText(2,ptr->fReplace);
			}
			return -1;
			
		case KXGControlClick:
			if (arg == 100) {
				dlog->GetText(1,sizeof(ptr->fSearch),ptr->fSearch);
				dlog->GetText(2,sizeof(ptr->fReplace),ptr->fReplace);
				dlog->EndDialog(1);
				return 0;
			}
			if (arg == 101) {
				dlog->EndDialog(0);
				return 0;
			}
			return -1;
	}
	return -1;
}

/*	TEditView::FindDialog
 *
 *		Run the find dialog.
 */

void TEditView::FindDialog()
{
	if (XGDialog::ModalDialog(130,FindDialogProc,(void *)this)) {
		FindNext();
	}
}

/*	TEditView::FindNext
 *
 *		Find the specified item
 */

void TEditView::FindNext()
{
	long start;
	long len;
	XGHandle *text;
	long pos;
	char *a,*b;
	unsigned long s,e;
	
	if (fSearch[0] == 0) return;
	
	fEditText.GetSelect(&s,&e);			// len is just a spare variable...
	start = e;
	text = fEditText.GetHandle();
	len = text->GetSize();
	
	for (pos = start; pos < len - strlen(fSearch); pos++) {
		a = (char *)(**text) + pos;
		b = fSearch;
		while (*b) {
			if (*a != *b) break;
			a++;
			b++;
		}
		if (*b == 0) {
			/*
			 *	Match
			 */
			
			fEditText.SetSelect(pos,pos+strlen(fSearch));
			ScrollInRange();
			fPosPixel = -1;
			InvalView();
			return;
		}
	}
}

/*	TEditView::Replace
 *
 *		Replace the specified item
 */

void TEditView::Replace()
{
	Rect r;
	XGHandle h;
	int len;
	
	ScrollInRange();
	fPosPixel = -1;

	len = strlen(fReplace);
	h.SetSize(len);
	memmove(*h,fReplace,len);
	fEditText.PasteText(h);

	r = GetContentRect();
	DoDrawView(r);
	RecalcScroll();
	MarkDirty();
}

/*	TEditView::GotoDialogProc
 *
 *		Manage the dialog
 */

long TEditView::GotoDialogProc(XGDialog *dlog, void *data, long msg, long arg, void *)
{
	long *ptr = (long *)data;
	
	switch (msg) {
		case KXGDialogMessage:
			if (arg == KXGDMInitDialog) {
				dlog->SetValue(1,*ptr);
			}
			return -1;
			
		case KXGControlClick:
			if (arg == 100) {
				*ptr = dlog->GetValue(1);
				dlog->EndDialog(1);
				return 0;
			}
			if (arg == 101) {
				dlog->EndDialog(0);
				return 0;
			}
			return -1;
	}
	return -1;
}

/*	TEditView::GotoDialog
 *
 *		Run the goto dialog
 */

void TEditView::GotoDialog()
{
	long s,e;
	long len;
	long line = 1;
	if (XGDialog::ModalDialog(129,GotoDialogProc,(void *)&line)) {
	
		/*
		 *	Select the specified line
		 */
		
		if ((line < 1) || (line > fEditText.GetNumLines())) return;
		
		s = fEditText.GetLineOffset(line-1);
		e = s+fEditText.GetLineLength(line-1) + 1;
		len = fEditText.GetHandle()->GetSize();
		if (e > len) e = len;
		
		fEditText.SetSelect(s,e);
		ScrollInRange();
		fPosPixel = -1;
		InvalView();
	}
}

/*	TEditView::PrefsDialogProc
 *
 *		Manage the dialog
 */

long TEditView::PrefsDialogProc(XGDialog *dlog, void *data, long msg, long arg, void *)
{
	PrefRecord *ptr = (PrefRecord *)data;
	
	switch (msg) {
		case KXGDialogMessage:
			if (arg == KXGDMInitDialog) {
				dlog->SetValue(1,ptr->tabstop);
				dlog->SetValue(2,(ptr->autoindent) ? true : false);
			}
			return -1;
			
		case KXGControlClick:
			if (arg == 100) {
				ptr->tabstop = dlog->GetValue(1);
				ptr->autoindent = dlog->GetValue(2) ? true : false;
				dlog->EndDialog(1);
				return 0;
			}
			if (arg == 101) {
				dlog->EndDialog(0);
				return 0;
			}
			if (arg == 2) {
				dlog->SetValue(2,dlog->GetValue(2) ? 0 : 1);
				return 0;
			}
			return -1;
	}
	return -1;
}

/*	TEditView::PrefsDialog
 *
 *		Run the preferences dialog
 */

void TEditView::PrefsDialog()
{
	PrefRecord pref;
	
	pref.tabstop = fTabStop;
	pref.autoindent = fAutoIndent;
	
	if (XGDialog::ModalDialog(131,PrefsDialogProc,(void *)&pref)) {
	
		/*
		 *	Select the specified line
		 */
		
		if ((pref.tabstop >= 2) && (pref.tabstop <= 60)) {
			fTabStop = pref.tabstop;
		}
		fAutoIndent = pref.autoindent;
		InvalView();

		/*
		 *	Write preferences
		 */
		 
#if OPT_MACOS
		XGPreferences prefs("XTextEdit Prefs",'BTRC');
#endif

#if OPT_WINOS
		XGPreferences prefs("PandaWave","XTextEdit","1.0");
#endif

#if OPT_XWIN
		XGPreferences prefs("XTextEditRC");
#endif

		prefs.SetInteger("TabStop",fTabStop);
		prefs.SetBoolean("AutoIndent",fAutoIndent);
	}
}
