/********************************************************************
 *																	*
 *	WinLIB PRO Revision II: Custom text editor						*
 *																	*
 *	Copyright (c) 1994, Bitgate Software							*
 *																	*
 ********************************************************************/

#include <string.h>
#include <stdio.h>
#include <ctype.h>

#include "winlib.h"		/* WinLIB header file */
#include "nkcc.h"		/* Normalized Keycodes header file */

#ifdef __TURBOC__
#pragma warn -pia
#endif

#ifndef __FORMKEYBOARD__
#define __FORMKEYBOARD__
#endif

/*
 *  Turn the editing cursor off
 */
GLOBAL void edit_off(WINDOW *win)
{
	objc_edit(win->tree, win->edobject, 0, &win->edpos, ED_END);
}


/*
 *  Turn on the editing cursor
 */
GLOBAL void edit_on(WINDOW *win)
{
	objc_edit(win->tree, win->edobject, 0, &win->edpos, ED_INIT);
}


/*
 *  Reposition the cursor to an index
 *  (It took a bit of thinking it out, but it worked in the end!)
 *
 *  where = offset into string to put cursor on
 */
GLOBAL void edit_pos(WINDOW *win, int where)
{
	register char c, *t = win->tree[win->edobject].ob_spec.tedinfo->te_ptext;

	if (where!=win->edpos) {
		t += where;

		edit_off(win);
		c = *t;
		*t = 0;
		edit_on(win);
		*t = c;
	}
}

/*
 *	Function to reposition cursor inside an object
 *
 *	This code segment by Jan Starzynski
 *	Modifications by Ken Hollis
 *
 *	obj = Address of the root object (dialog box)
 *	No  = editable object which was clicked on
 *	mx  = X coordinate of mouse at time of click (from event info)
 *
 *	Returns
 */
GLOBAL int find_position(OBJECT *obj, int No, int mx)
{
	int te_x= 0;	/* Absolute coordinate of text */
	int	P_text = 0;	/* Position in text */
	int	P_mask = 0;	/* Position in mask */
	int	size;		/* Text font size */
	int	i, Child;	/* Children and the parent pointer */

	size = (obj[No].ob_spec.tedinfo->te_font == 3) ? 8 : 6;
	te_x = (obj[No].ob_spec.tedinfo->te_just)
			? obj[No].ob_width - (obj[No].ob_spec.tedinfo->te_tmplen - 1) * size : 0;
	if(obj[No].ob_spec.tedinfo->te_just == 2)
			te_x /= 2;
	te_x += obj[No].ob_x + size / 2;
	Child = No;
	for (i = No - 1; 0 <= i; i--)
		if ((obj[i].ob_head <= Child) && (Child <= obj[i].ob_tail))	{
			Child = i;
			te_x += obj[i].ob_x;
		}

	while ((te_x < mx) && (P_mask < obj[No].ob_spec.tedinfo->te_tmplen - 1)) {
		if (obj[No].ob_spec.tedinfo->te_ptmplt[P_mask++] == '_')
			P_text++;
		te_x += size;
	}
	return P_text;
}


/*
 *  Check to see if an object is hidden
 *
 *  tree = Address of dialog box to check
 *  obj  = Object to check
 *  flag = Flag to truly check for the object (usually TRUE)
 *
 *  Returns: TRUE = it is hidden
 *			 FALSE = it is not
 */
LOCAL int IsHidden(OBJECT *tree, int obj, int flag)
{
	int	index = obj, next;

	if (!(tree[obj].ob_flags & HIDETREE))
		if (flag == TRUE || !(tree[obj].ob_state & DISABLED)) {
			while ((next = tree[index].ob_next) != -1) {
				if ((tree[index].ob_flags & HIDETREE))
					return TRUE;
				index = next;
			}
			return FALSE;
		}

	return TRUE;
}


/*
 *  Check for a specified flag against an object
 *
 *  tree = Address of the dialog box
 *  obj  = Object to check
 *  flag = Boolean value to truly check or not (usually TRUE)
 *  mask = Mask to check for; make sure it's an integer value!
 *
 *  Returns: TRUE = object is flag
 *			 FALSE = it is not
 */
LOCAL int IsFlag(OBJECT *tree, int obj, int flag, int mask)
{
	if ((flag & mask) && (!IsHidden(tree, obj, FALSE)))
		return TRUE;

	return FALSE;
}


/*
 *  Jump to the first editable object
 *
 *  tree = Address of the dialog box to go to the first object in
 *
 *  Returns: index of the first object
 *			 0 on failure
 */
LOCAL int FirstEdit(OBJECT *tree)
{
	int	index = 0;

	do {
		index++;
		if (IsFlag(tree, index, tree[index].ob_flags, EDITABLE))
			return index;
	}
	while (!(tree[index].ob_flags & LASTOB));

	return 0;
}


/*
 *  Jump to the last editable object
 *
 *  tree = Address of the dialog box to jump to the last object in
 *
 *  Returns: index of the last object
 *			 0 on failure
 */
LOCAL int LastEdit(OBJECT *tree)
{
	int	index = 0, last = 0;

	do {
		index++;
		if (IsFlag(tree, index, tree[index].ob_flags, EDITABLE))
			last = index;
	}
	while (!(tree[index].ob_flags & LASTOB));

	if (last)
		return last;

	return 0;
}


/*
 *	Find next editable object
 *
 *	tree = Address of the dialog box to search
 *	cobj = Current object the cursor is at
 *
 *	Returns: index of the next object
 *			 0 on failure
 */
LOCAL int NextEdit(OBJECT *tree, int cobj)
{
	int	index = cobj;
	int	flag = FALSE;

	if (index) {
		while (!(tree[index].ob_flags & LASTOB)) {
			index++;
			if (IsFlag(tree, index, tree[index].ob_flags, EDITABLE))
				return index;
		}

		if (!flag)
			return FirstEdit(tree);
	}

	return 0;
}


/*
 *	Clear all editable fields
 *
 *	tree = Object tree to clear out editable fields
 */
LOCAL int ClearAllEdit(OBJECT *tree)
{
	int	index = 0;

	do {
		if (tree[++index].ob_flags & EDITABLE)
			tree[index].ob_spec.tedinfo->te_ptext = "\0";
	} while (!(tree[index].ob_flags & LASTOB));

	return FirstEdit(tree);
}


/*
 *  Find the object before the current one
 *
 *  tree = Address of the dialog box to search
 *  cobj = Current object that the cursor is at
 *
 *  Returns: the index of the previous editable object
 *			 0 on failure
 */
LOCAL int PrevEdit(OBJECT *tree, int cobj)
{
	int	index = cobj;
	int	flag = FALSE;

	if (index) {
		do {
			index--;
			if (IsFlag(tree, index, tree[index].ob_flags, EDITABLE))
				return index;
		}
		while (index);

		if (!flag)
			return LastEdit(tree);
	}

	return 0;
}


/*
 *  Customized form_keybd call
 *
 *  win    = Window structure
 *  key    = Keyboard keycode from EvntMulti
 *	kstate = Keyboard status
 *  nchr   = Processed keycode (0 if handled, otherwise, it
 *           returns the keycode that was typed.)
 *
 *	Returns: 0 if an exitobject was selected
 */
GLOBAL int WForm_keybd(WINDOW *win, int key, int kstate, int *nobj, int *nchr)
{
	int n_key;

	n_key = nkc_tconv((key & 0xff) | (((long) key & 0xff00) << 8) | ((long) kstate << 24)) & ~(NKF_FUNC | NKF_RESVD | NKF_NUM);
	*nchr = 0;

	if ((!win->edobject && n_key == NK_RET) || (n_key == (NK_RET | NKF_CTRL))) {
		int	i = 0;
		OBJECT *obj = win->tree;

		do {
			if (obj->ob_flags & DEFAULT) {
				*nobj = i;
				return FALSE;
			}
			i++;
		} while (!((obj++)->ob_flags & LASTOB));

		*nobj = 0;		
		return TRUE;
	}

	if (!win->edobject || n_key & NKF_ALT) {
		int	i = 0, mask = (toupper(n_key & 0xff) & 0x7f) << 8;
		OBJECT *obj = win->tree;

		do {
			if (((obj->ob_type & 0x7f00) == mask) && !(obj->ob_state & DISABLED) && ((obj->ob_flags & SELECTABLE) || (obj->ob_flags & EXIT))) {
				*nobj = i;
				return FALSE;
			}
			i++;
		}
		while (!((obj++)->ob_flags & LASTOB));
	}

	if (win->edobject) {
		register char *text = win->tree[win->edobject].ob_spec.tedinfo->te_ptext;
	
		switch (n_key) {
			case NK_ENTER:
			case NK_DOWN:
			case NK_TAB:
			case NK_RET:
				edit_off(win);
				win->edobject = NextEdit(win->tree, win->edobject);
				win->edpos = (int) strlen(text);
				edit_on(win);
				break;

			case NK_UP:
			case NK_TAB | NKF_LSH:
			case NK_TAB | NKF_RSH:
				edit_off(win);
				win->edobject = PrevEdit(win->tree, win->edobject);
				win->edpos = (int) strlen(text);
				edit_on(win);
				break;

			case NK_UNDO:
				WCallUndoDispatcher(win);
				break;

			case NK_HELP:
				WCallHelpDispatcher(win);
 				break;

			case NK_F1:
				WCallFKeyDispatcher(win, 1);
				break;

			case NK_F2:
				WCallFKeyDispatcher(win, 2);
				break;

			case NK_F3:
				WCallFKeyDispatcher(win, 3);
				break;

			case NK_F4:
				WCallFKeyDispatcher(win, 4);
				break;

			case NK_F5:
				WCallFKeyDispatcher(win, 5);
				break;

			case NK_F6:
				WCallFKeyDispatcher(win, 6);
				break;

			case NK_F7:
				WCallFKeyDispatcher(win, 7);
				break;

			case NK_F8:
				WCallFKeyDispatcher(win, 8);
				break;

			case NK_F9:
				WCallFKeyDispatcher(win, 9);
				break;

			case NK_F10:
				WCallFKeyDispatcher(win, 10);
				break;

			case NK_CLRHOME | NKF_CTRL:
				{
					int x, y, w, h;

					win->edobject = ClearAllEdit(win->tree);
					WWindGet(win, WF_WORKXYWH, &x, &y, &w, &h);
					WRedrawWindow(win, x, y, w, h);
				}
				break;

			case NK_LEFT | NKF_LSH:
			case NK_LEFT | NKF_RSH:
				edit_pos(win, 0);
				break;

			case NK_RIGHT | NKF_LSH:
			case NK_RIGHT | NKF_RSH:
				edit_pos(win, (int) strlen(text));
				break;

			case NK_RIGHT | NKF_CTRL:
				{
					char *ptr = text + win->edpos;

					if (*ptr) {
						while (*(++ptr))
							if (isalnum(*ptr) && !isalnum(*(ptr - 1)))
								break;

						edit_pos(win, (int) (ptr - text));
					}
				}
				break;

			case NK_LEFT | NKF_CTRL:
				if (win->edpos) {
					char *ptr = text + win->edpos;

					while (text != --ptr)
						if (!isalnum(*ptr) && text != ptr - 1)
							if (isalnum(*(ptr - 1)))
								break;

					edit_pos(win, (int) (ptr - text));
				}
				break;

			case NK_LEFT:
				{
					int pos = win->edpos;

					pos--;
					if (pos<0) pos = 0;
					edit_pos(win, pos);
				}
				break;

			case NK_RIGHT:
				{
					int pos = win->edpos;

					pos++;
					if (pos>(int) strlen(text)) pos = (int) strlen(text);
					edit_pos(win, pos);
				}
				break;

			case NK_UP | NKF_LSH:
			case NK_UP | NKF_RSH:
			case NK_CLRHOME:
				{
					int prev = win->edobject;
					int storage;

					storage = FirstEdit(win->tree);
					if (storage == prev) {
						win->edobject = prev;
						edit_pos(win, 0);
					} else {
						edit_off(win);
						win->edobject = storage;
						edit_on(win);
					}
				}
				break;

			case NK_CLRHOME | NKF_LSH:
			case NK_CLRHOME | NKF_RSH:
			case NK_DOWN | NKF_LSH:
			case NK_DOWN | NKF_RSH:
				{
					int prev = win->edobject;
					int	storage;

					storage = LastEdit(win->tree);
					if (storage == prev) {
						win->edobject = prev;
						edit_pos(win, 0);
					} else {
						edit_off(win);
						win->edobject = storage;
						edit_on(win);
					}
				}
				break;

			case NK_INS:
				WGrafMouse(ARROW);
				{
					int edittype = WFind_EditType(win);
					*nchr = (char) WAscii_Table(edittype);
				}
				break;

			default:
				*nchr = key;
				break;
		}
	}

	*nobj = win->edobject;

	return TRUE;
}

GLOBAL int WFind_EditType(WINDOW *win)
{
	int type, pos = 0, len = strlen(win->tree[win->edobject].ob_spec.tedinfo->te_pvalid);

	do {
		pos++;

		switch(win->tree[win->edobject].ob_spec.tedinfo->te_pvalid[pos]) {
			case '9': return E_NUMERIC;
			case 'a': return E_LOW_ASCII;
			case 'n': return E_LOW_NUMERIC;
			case 'p': return E_LOW_PATH;
			case 'A': return E_UP_ASCII;
			case 'N': return E_UP_NUMERIC;
			case 'P': return E_UP_PATH;
			case 'X': return E_ANY;
			case 'S': return E_SPECIAL;
		}
	} while (pos < len);

	return 0;
}
