
/***************************************************************************
 *   Copyright (C) 1997 to 2004 by Jonathan Duddington                     *
 *   email: jonsd@users.sourceforge.net                                    *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 3 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write see:                           *
 *               <http://www.gnu.org/licenses/>.                           *
 ***************************************************************************/

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>


#include "wimp.h"
#include "wimpt.h"
#include "win.h"
#include "event.h"
#include "baricon.h"
#include "res.h"
#include "resspr.h"
#include "menu.h"
#include "template.h"
#include "dbox.h"
#include "saveas.h"
#include "xferrecv.h"
#include "werr.h"
#include "font.h"
#include "bbc.h"
#include "help.h"
#include "msgs.h"
#include "flex.h"
#include "time.h"

#include "narc.h"
#include "hdrs.h"

#define WIDTH       48    /* max width of word - 1 */

extern int lookup_dict(char *word,int dummy,int points);
extern void apply_rules(char *word, int ix, int n, int condition);
extern char *extract_word(char *ptr);
extern int get_hash9(char *word);
extern void crossword_lookup(char *word,int length);

extern void redraw_text_oneline(TEXTR *t,int line);

extern char *get_temp_fname(void);

/* these were externals */
extern int standard_font;
extern TEXTR *text_data_record[N_TEXT_DATA];


void spellcheck_word(char *word,int phonetic_depth);
void spell_load(void);
void spell_replace_word2(TEXTR *t);


int spell_open = 0;   /* export */
int spell_position = 0;
int spell_position_start = 0;
int prev_cursor_line = -1;
static int spell_operation = 0;   /* 0=check as you type, 1=spellcheck text */
static spell_error_found = 0;

int add_exceptions_flag = 0;   /* set to add words from exceptions table to display */
static int spellcheck_present = 0;
int spell_highlighted = 0;
TEXTR *textr_spellcheck=NULL;


FILE *f_trace;
extern OPTIONS options;



#define N_ENDINGS  150
char endtab_len[N_ENDINGS];
char *endtab[N_ENDINGS];
short *ending_index=NULL;

char *dict_addr=NULL;
int *hash_table=NULL;

static char *except_addr=NULL;
static int *except_htab=NULL;




int length;
int max_changes;
int rule_options;
static char wordbuf[32];
/* old phoneme codes:
  &A2 O,  &AB Z, &AC @, &AD 3:,
  &B1 @r, &B2 E, &B3 I, &B5 N, &B6 A, &B9 I#, &BB L, &BC 0, &BD U, &BE V,
  &DE T, &DF S,
  &E6 &, &F0 D,
*/

char *phonetics[] = {
	"", "XX", "_@", "k",
	"_s",       /* also allow S */
	"_z",
	"_S",       /* but not tS */
	"d",       /* allow dZ or dj */
	/* codes up to (& incl) 7 are fixed */

	"_h", "_g", "_Z",
	"a", "E", "_I", "0", "V",
	"i:", "3:", "A:", "O:", "u",
	"aI", "eI", "OI", "aU", "oU", "e@", "aI@", NULL
};


/* bit 0  consonant,   bit 1 hard consonant,   bit 3  vowel */
char ctype2 [] = {
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,   /* 0 */
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,   /* 10 */
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,   /* 20 */
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,   /* 30 */
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,   /* 40 */
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,   /* 50 */
	0,8,3,3,3,8,3,3,3,8,3,3,3,3,3,8,
	3,3,1,3,3,8,3,1,3,9,3,0,0,0,0,0,

	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
};


/* list adjacent keys on a qwerty keyboard */
char *nearkeys[] = {
	"qwesdzx",      /* A */
	"cvnmfghjd",     /* B */
	"zxvbsdfgk",     /* C */
	"asfgzxcvwertb", /* D */
	"qwrtasdfgi",    /* E */
	"sdghertyxcvb", /* F */
	"dfhjcvbnrtyu", /* G */
	"fgjkvbnmtyui", /* H */
	"yuophjkle",     /* I */
	"ghklbnmyuio",  /* J */
	"hjlnmuiopc",   /* K */
	"jkiopm",       /* L */
	"bnhjkl",       /* M */
	"vbmghjk",      /* N */
	"uipjkl",       /* O */
	"iokl",         /* P */
	"weasd",        /* Q */
	"wetysdfg",     /* R */
	"adfqwerzxc",   /* S */
	"eryudfgh",     /* T */
	"tyioghjk",     /* U */
	"xcbndfgh",     /* V */
	"qerasdf",      /* W */
	"zcvasdf",      /* X */
	"rtuifghj",     /* Y */
	"xcasdf",       /* Z */
};




wimp_w  w_spell;
wimp_w  w_spelllist;
dbox    d_spell;
static wimp_redrawstr spelllist_workarea;  /* extent of work area */



int  spelllist_item=0;
int  spelllist_height=0;

int  os_units_per_line = 40;


#define N_LIST 60
#define N_LISTCHARS 512


static int  n_list=0;     /* num. of entries in 'list' */
int  list_full=0;
char *list_ptr=NULL;    /* next free in listchars */
char *list[N_LIST];
char listchars[N_LISTCHARS];

typedef struct {
	char *link;
	char name[1];
} SPELL_TMP;

SPELL_TMP *spell_tmp=NULL;
char spell_tmp_hash[512];

int  apostrophe_s=0;      /* add 's to words in the list */
int  spell_word_edited = 0;
int  spell_addtemplist_flag = 0;



void show_spellcheck_state()
/**************************/
{
	int  i;
	TEXTR *t;
	int  button;

	for(i=0; i<N_TEXT_DATA; i++)
	{
		if((t = text_data_record[i]) != NULL)
		{
			if(t->text_type <= X_VIEW2)
				button = 19;
			else
				button = 15;

			set_icon_state(t->w_card,button,options.check_as_you_type);
		}
	}
}   /* end of show_spellcheck_state */




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



static short *userdict_hashtab=NULL;
static char *userdict=NULL;
static char *userdict_next;
static char *userdict_max;
#define USERDICT_EXTRA 256
#define USERDICT_NHASH 52


void userdict_wipe()
/******************/
{
	int  length;

	length = USERDICT_NHASH*sizeof(short);

	memset(userdict_hashtab,0,length);
	userdict_next = &userdict[length];;
}   /* end of userdict_wipe */



int userdict_hash(char *word)
/***************************/
{
	int i;

	i = (word[0] - 'a')*2;
	if(word[1] >= 'n')
		i++;
	return(i % USERDICT_NHASH);
}   /* end of userdict_hash */


static int userdict_len(char *p)
/******************************/
/* Find length of user dictionary entry */
{
	int  i;
	int  flags;

	flags = p[2];
	p += 3;
	i = strlen(p)+1;
	if(flags & 0x80)
	{
		/* followed by another string */
		p += i;
		i += (strlen(p) + 1);
	}
	return((i+4) & ~1);  /* round up to 2 byte boundary */
}   /* end of userdict_len */



void userdict_save()
/******************/
{
	int  hash;
	int  length;
	FILE *f;
	char *p;
	short *p_short;
	int  ix;
	int  next_ix;
	long int  next_displ;
	short hashtab[USERDICT_NHASH];
	char fname[100];

	sprintf(fname,"%sResources.UserDict",pluto_path);
	f = fopen(fname,"w");
	if(f == NULL)
	{
		return;
	}

	fwrite(userdict_hashtab,1,USERDICT_NHASH*sizeof(short),f);

	for(hash=0; hash<USERDICT_NHASH; hash++)
	{
		ix = userdict_hashtab[hash];
		if(ix==0)
		{
			/* no entries for this hash value */
			hashtab[hash]=0;
			continue;
		}

		hashtab[hash] = (short)ftell(f);

		while(ix != 0)
		{
			p = &userdict[ix];
			length = userdict_len(p);

			next_displ = ftell(f) + length;

			p_short = (short *)p;
			next_ix = *p_short;
			if(next_ix == 0)
			{
				fwrite(&next_ix,1,sizeof(short),f);
			}
			else
			{
				fwrite(&next_displ,1,sizeof(short),f);
			}
			fwrite(p+2,1,length-2,f);
			ix = next_ix;
		}
	}

	rewind(f);
	fwrite(hashtab,1,USERDICT_NHASH*sizeof(short),f);
	fclose(f);
}   /* end of userdict_save */



static void userdict_load()
/*************************/
{
	int  length;
	int  length2;
	FILE *f;
	char fname[100];

	userdict = NULL;

	sprintf(fname,"%sResources.UserDict",pluto_path);
	length = get_filelength(fname);

	f = fopen(fname,"r");
	if(f == NULL)
	{
		length = USERDICT_NHASH * sizeof(short);
	}

	length2 = length + USERDICT_EXTRA;

	if(userdict != NULL)
		free(userdict);
	userdict = calloc(1,length2);
	if(userdict == NULL)
		return;

	userdict_hashtab = (short *)userdict;
	userdict_next = &userdict[length];
	userdict_max = &userdict[length2];

	if(f != NULL)
	{
		fread(userdict,1,length,f);
		fclose(f);
	}
}   /* end of userdict_load */




int userdict_delete(char *word)
/*****************************/
{
	int  ix;
	char *p;
	short *p_short;
	short *last_p_short;
	char buf[80];

	strcpy_lc(buf,word);
	last_p_short = &userdict_hashtab[userdict_hash(buf) & 0x3f];
	ix = *last_p_short;

	while(ix != 0)
	{
		p = &userdict[ix];
		p_short = (short *)p;
		ix = *p_short;           /* index of next item */

		if(strcmp(buf,&p[3])==0)
		{
			*last_p_short = ix;   /* unlink from chain */
		}

		last_p_short = p_short;
	}
	return(-1);
}   /* end of userdict_delete */




void userdict_add(char *word, char *expansion)
/********************************************/
{
	int  ix;
	char *p;
	short *p_short;
	short *last_p_short;
	int  hash;
	int  length;
	int  size;
	char buf[80];

	while(isspace(*word)) word++;
	if(word[0] == 0)
		return;

	strcpy_lc(buf,word);
	length = strlen(word) + 5;
	if(expansion)
		length += (strlen(expansion) + 1);
	length = length & ~1;

	userdict_delete(buf);

	if((userdict_next + length) >= userdict_max)
	{
		/*not enough room */
		ix = userdict_next - userdict;
		size = (userdict_next - userdict) + length + USERDICT_EXTRA;
		p = realloc(userdict,size);
		if(p != NULL)
		{
			userdict = p;
			userdict_hashtab = (short *)p;
			userdict_next = p + ix;
			userdict_max = p + size;
		}
		else
		{
			userdict_save();
			userdict_load();
		}
	}

	hash = userdict_hash(buf) & 0x3f;

	last_p_short = &userdict_hashtab[hash];
	ix = *last_p_short;

	while(ix != 0)
	{
		p = &userdict[ix];

		if(strcmp(buf,&p[3]) < 0)
		{
			break;
		}

		p_short = (short *)p;
		ix = *p_short;           /* index of next item */
		last_p_short = p_short;
	}

	*last_p_short = (short)(userdict_next - userdict);

	p_short = (short *)userdict_next;
	*p_short = ix;
	userdict_next[2] = 0;
	strcpy_lc(&userdict_next[3],word);

	if(expansion)
	{
		userdict_next[2] = 0x81;
		strcpy(&userdict_next[strlen(word)+4],expansion);
	}
	userdict_next += length;

}   /* end of userdict_add */




char *userdict_lookup(char *word)
/*******************************/
{
	int  ix;
	char *p;
	short *p_short;
	int  c;
	char buf[80];

	if(userdict==NULL)
		return(NULL);

	ix=0;
	while(isalnum(c = word[ix]) || (c == '\''))
	{
		buf[ix++] = tolower(c);
		if(ix >= sizeof(buf))
			return(NULL);
	}
	buf[ix] = 0;
	if(ix == 0)
		return(NULL);

	ix = userdict_hashtab[userdict_hash(buf) & 0x3f];
	while(ix != 0)
	{
		p = &userdict[ix];
		p_short = (short *)p;
		ix = *p_short;

		if(strcmp(buf,&p[3])==0)
		{
			if(p[2] & 0x80)
			{
				/* return second word */
				return(p + strlen(&p[3]) + 4);
			}
			return("");
		}
	}
	return(NULL);
}   /* end of userdict_lookup */



void userdict_list1(int type, FILE *f)
/************************************/
{
	int  hash;
	int  ix;
	char *p;
	short *p_short;
	int  flags;
	int  abbreviations=0;

	for(hash=0; hash<USERDICT_NHASH; hash++)
	{
		ix = userdict_hashtab[hash];

		while(ix != 0)
		{
			p = &userdict[ix];
			flags = p[2];
			p_short = (short *)p;
			ix = *p_short;

			if((flags & 0xf) != type)
				continue;

			p += 3;
			fprintf(f,"%s",p);

			if(flags & 0x80)
			{
				/* second word follows */
				p += (strlen(p) + 1);
				fprintf(f,"\t%s",p);
				abbreviations=1;
			}
			fputc('\n',f);
		}
	}
	if(abbreviations)
		fputc('\n',f);
}   /* end of userdict_list1 */



void userdict_list(char *fname)
/*****************************/
{
	FILE *f;

	f = fopen_werr(fname,"w",NULL);
	if(f == NULL)
		return;

	userdict_list1(1,f);
	userdict_list1(0,f);

	fclose(f);
}   /* end of userdict_list */



void userdict_import(int type, char *fname)
/*****************************************/
{
	FILE *f;
	char *p;
	int  count;
	char buf[120];
	char word1[80];
	char word2[120];

	f = fopen_werr(fname,"r",NULL);
	if(f == NULL)
		return;

	while(!feof(f))
	{
		if(fgets(buf,sizeof(buf),f) == NULL)
			break;

		count = sscanf(buf,"%s %s",word1,word2);
		if(count <= 0)
			continue;

		p = strpbrk(buf,"\t ");
		if((count > 1) && (p != NULL))
		{
			while(isspace(*p)) p++;
			buf[strlen(buf)-1] = 0;  /* delete terminating NL */
			userdict_add(word1,p);
		}
		else
		{
			userdict_add(word1,NULL);
		}
	}
	fclose(f);
	userdict_save();
}   /* end of userdict_import */


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





int is_vowel(int c)
/*****************/
{
	return(ctype2[c] & 8);
}   /* end of is_vowel */



int is_consonant(int c)
/*********************/
{
	return(ctype2[c] & 1);
}   /* end of is_consonant */


int is_hconsonant(int c)
/**********************/
{
	return(ctype2[c] & 2);
}   /* end of is_hconsonant */




int lookup_templist(char *word)
/*****************************/
{
	int  hash;
	SPELL_TMP *p;

	hash = get_hash9(word);

	if(spell_tmp_hash[hash] == 0)
		return(0);

	p = spell_tmp;
	while(p != NULL)
	{
		if(strcmp(word,p->name) == 0)
			return(1);
		p = (SPELL_TMP *)p->link;
	}
	return(0);

}  /* end of lookup_templist */



void add_templist(char *word)
/***************************/
{
	int hash;
	SPELL_TMP *p;
	char *name;

	p = malloc(strlen(word)+1+sizeof(SPELL_TMP));
	if(p == NULL)
		return;

	p->link = (char *)spell_tmp;
	spell_tmp = p;

	name = p->name;
	while((*name++ = tolower(*word++)) != 0);

	hash = get_hash9(p->name);
	spell_tmp_hash[hash] = 1;
}   /* end of add_templist */





void force_redraw_window(wimp_w w)
/********************************/
/* Force a redraw of a window */
{
	wimp_redrawstr r;

	r.w = w;
	r.box.x0=0;
	r.box.x1 = 2000;
	r.box.y0= -256*os_units_per_line;
	r.box.y1=0;
	wimp_force_redraw(&r);
}   /* end of force_redraw_window */





int search_list(char *word)
/*************************/
{
	int  i;

	for(i=0; i<n_list; i++)
	{
		if(list[i][1] == '*')
		{
			/* word in list starts with a '*', indicating the original word */
			if(strcmp(word,&list[i][3]) == 0)
				return(1);   /* found matching entry */
		}
		else
		{
			if(strcmp(word,&list[i][1]) == 0)
				return(1);   /* found matching entry */
		}
	}
	return(0);
}   /* end of search_list */



void start_list()
/***************/
{
	n_list = 0;
	list_full = 0;
	list_ptr = listchars;
}   /* end of start_list */




void add_word(char *word,int points)
/**********************************/
/* Add a word to the list */
{
	int  i;
	char word2[WIDTH+2];

	if(n_list >= (N_LIST-1))
	{
		list_full++;
		return;   /* list full */
	}


	if(lookup_dict(word,0,points) == 0)
	{
		if(userdict_lookup(word)==NULL)
			return;   /* not a valid word */
	}




	if(list_ptr >= &listchars[N_LISTCHARS - strlen(word) - 1])
	{
		list_full++;
		return;   /* list full */
	}

	if(apostrophe_s)
	{
		sprintf(word2,"%s's",word);
		word = word2;
	}

	/*  search list for a match */
	if(search_list(word))
		return;  /* duplicate entry */


	/* higher points scores indicate unfavorable words */
	i = (points & 0x1f) - 0x10;
	points = (points & 0xe0) >> 4;   /* each level is 2 points */

	points = points + i;




	list[n_list++] = list_ptr;
	*list_ptr++ = points;
	while((*list_ptr++ = *word++) != 0);

}   /* end of add_word */




void add_word0(char *word,int points)
/***********************************/
/* Add a word to the list, dont do a lookup. */
{
	char word2[WIDTH+2];

	if(n_list >= (N_LIST-1))
	{
		list_full++;
		return;   /* list full */
	}

	if(list_ptr >= &listchars[N_LISTCHARS - strlen(word) - 1])
	{
		list_full++;
		return;   /* list full */
	}


	/* already a word with this hash value, search list for a match */

	if(apostrophe_s)
	{
		sprintf(word2,"%s's",word);
		word = word2;
	}

	if(search_list(word))
		return;  /* duplicate entry */

	list[n_list++] = list_ptr;
	*list_ptr++ = points;
	while((*list_ptr++ = *word++) != 0);

}   /* end of add_word0 */




int spelllist_sorter(const void *a1, const void *a2)
/**************************************************/
{
	int i;
	char *p1, *p2;

	i = **((char **)a1) - **((char **)a2);

	if(i == 0)
	{
		p1 = *((char **)a1);
		p2 = *((char **)a2);

		if(p1[0] == 0)
		{
			/* points = 0 means sort on character string */
			i = strcmp_lc(&p1[1],&p2[1]);
		}
		else
		{
			i = p1 - p2;   /* order in which words were placed in the list */
		}
	}
	return(i);
}   /* end of spelllist_sorter */





void list_complete()
/******************/
{
	int height;

	height = (n_list+1)*os_units_per_line;

	if(height < spelllist_height)
		height = spelllist_height;

	spelllist_workarea.box.y0 = spelllist_workarea.box.y1 - height;
	spelllist_workarea.w = w_spelllist;
	wimp_set_extent(&spelllist_workarea);

	if(n_list > 0)
	{
		spelllist_item = 0;
		qsortG(&list,n_list,sizeof(char *),spelllist_sorter);
	}
	else
		spelllist_item = -1;   /* display "No words found" */

	force_redraw_window(w_spelllist);
}   /* end of list_complete */




void display_spelllist(int x, int y, int item, int n_items)
/*********************************************************/
{
	int  count;
	int  line;
	char *p;
	char full_msg[12];

	os_swi4(0x4074f,standard_font,0xffffff00,0x00000000,14);  /* ColourTrans_SetFonTColours */

	if(((spelllist_item == -1) || (spelllist_item == -3)) && (item==0))
	{
		if(spelllist_item == -1)
			p = "No words found";   /* -1 */
		else
			p = "COMPLETED";        /* -3,  -2=blank */

		if(standard_font >= 0)
		{
			font_paint(p,0x10,x,y-24);
		}
		else
		{
			os_swi3(0x45, 4, x, y);    /* OS_Plot  MOVE */
			os_swi1(0x02, (int)p);
		}
//      os_swi4(0x4074f,c_font,0xffffff00,0x00000000,14);  /* ColourTrans_SetFonTColours */
		return;
	}


	for(count=0; count<n_items; count++)
	{
		line = item+count;

		if(line > n_list)
			break;

		if(line == spelllist_item)
		{
			set_colour(CYAN,0,0);
			bbc_rectanglefill(x,y-34,550,4);
			set_colour(BLACK,0,0);
		}

		if(line == n_list)
		{
			if(list_full)
			{
				sprintf(full_msg,"%d MORE",list_full);
				p = full_msg;
			}
			else
			{
				p = "";
			}
		}
		else
		{
			p = &list[line][1];
		}

		if(standard_font >= 0)
		{
			font_paint(p,0x10,x,y-24);
		}
		else
		{
			os_swi3(0x45, 4, x, y);    /* OS_Plot   MOVE  */
			os_swi1(0x02, (int)p);      /* OS_Write0    write string */
		}
		y -= os_units_per_line;
	}
//   os_swi4(0x4074f,c_font,0xffffff00,0x00000000,14);  /* ColourTrans_SetFonTColours */
}   /* display_spelllist */




void set_spell_font(int s_font)
/*****************************/
{
	wimp_set_icon_state(w_spell,1,s_font<<24,0xff000000);
	force_redraw_window(w_spelllist);
}   /* set_spell_font */





void close_spell_window(TEXTR *t)
/****************************/
{
	if(spell_highlighted)
	{
		t->region_start=0;
		t->region_end=0;
		spell_highlighted = 0;
		redraw_text_oneline(t,t->cursor_line);
	}
	wimp_hide_window(dbox_syshandle(d_spell));
	wimp_hide_window(w_spelllist);
	spell_open = 0;

	if(t == textr_spellcheck)
		textr_spellcheck = NULL;
}   /* close_spell_window */






void redraw_spelllist_window(wimp_w handle)
/*****************************************/
{
	int  more;
	int  n_items;
	int  wa_y1;
	int  wa_y0;
	int  dy;
	int  screen_y;
	int  line;

	wimp_redrawstr r;

	/* Start the redraw */
	r.w = handle;

	wimpt_noerr(wimp_redraw_wind(&r, &more));

	while(more)
	{
		wa_y0 = r.g.y0 - r.box.y1 + r.scy;   /* bottom */
		wa_y1 = r.g.y1 - r.box.y1 + r.scy;   /* top */
		line = -wa_y1 / os_units_per_line;

		n_items = (r.box.y1 - r.box.y0)/os_units_per_line;
		dy = wa_y1 + (line * os_units_per_line) + 4;
		screen_y = r.g.y1 - dy;

		display_spelllist(r.box.x0+4, screen_y, line, n_items+1);
		wimp_get_rectangle(&r, &more);
	}

}   /* end of redraw_spelllist_window */




void click_spelllist_window(wimp_eventdata *d)
/*****************************************/
{
	int  screen_y;
	int  window_y;
	int  line;                  /* line within the list */
	int  i;
	char *p;
	wimp_wstate wstate;

	if(n_list <= 0)
		return;   /* list is empty */

	screen_y = d->but.m.y;      /* screen coord in click */

	wimp_get_wind_state(w_spelllist,&wstate);
	window_y = wstate.o.box.y1;
	i = window_y - screen_y;    /* displacement down window */

	i = i + (-wstate.o.y);      /* account for scroll offset */
	line = i / os_units_per_line;

	spelllist_item = line;
	if(spelllist_item >= n_list)
		spelllist_item = n_list-1;

	p = &list[spelllist_item][1];
	if(p[0] == '*')  p+=2;       /* indicator for original word */

	switch(d->but.m.bbits)
	{
	case wimp_BCLICKLEFT:
		/* Select, copy into word box */
		dbox_setfield(d_spell,1,p);
		set_input_focus(w_spell,1,strlen(p));
		break;

	case wimp_BCLICKRIGHT:
		/* Adjust, speak the word */
		speak_string(p);
		break;

	case wimp_BLEFT:
		/* Double-click, replace into text */
		spell_addtemplist_flag = 0;
		spell_replace_word2(textr_spellcheck);
		break;
	}

	force_redraw_window(w_spelllist);

}   /* end of click_spelllist_window */



void spell_reset_cursor(TEXTR *t,int cursor)
/******************************************/
{
	set_focus(t->w_text);
	t->cursor_index = cursor;
	t->region_start = 0;
	t->region_end = 0;
	spell_highlighted = 0;
	set_caret_pos(t);
}   /* end of spell_reset_cursor */




void scroll_spelllist_check()
/***************************/
/* Ensure highlighted word in within the window */
{
	scroll_to_y(w_spelllist,(-spelllist_item-1) * os_units_per_line);
}   /* end fo scroll_spelllist_check */



int spell_replace(TEXTR *t, char *word, int start, int end)
/*********************************************************/
{
	int  diff;
	int  upper_case = 0;
	char *p;
	int  c;
	int  first;

	p = &t->text_base[start];
	if(isupper(*p))
	{
		upper_case = 1;
		if(isupper(p[1]))
			upper_case = 2;
	}

	diff = strlen(word) - (end - start);
	if(diff > 0)
		move_down(t,end,diff);
	else
		move_up(t,end,-diff);


	/* copy new word */
	first=1;
	p = &t->text_base[start];
	while((c = *word++) != 0)
	{
		if((c == '|') && (*word == 'M'))
		{
			*p++ = ' ';
			c = '\n';
			word++;
		}

		if((upper_case == 2) || (first && (upper_case > 0)))
			c = toupper(c);
		*p++ = c;
		first=0;
	}

	return(diff);
}   /* end of spell_replace */



void spell_replace_word(TEXTR *t, int cursor, char *word)
/*******************************************************/
{
	int increase;
	int  length;
	int  c;
	char *p;
	int diff;

	if((cursor < t->text_start) || (cursor >= t->text_body_end))
		return;

	p = t->text_base + cursor;

	/* find start of word */
	for(;;)
	{
		c = *(--p);
		if(!isalnum(c) && (c != '\''))
		{
			p++;
			break;
		}

	}
	if(*p == '\'') p++;   /* ignore ' at start of word */

	/* find length of word */
	length=0;
	for(;;)
	{
		c = p[length++];
		if(!isalnum(c) && (c != '\''))
		{
			length--;
			break;
		}
	}

	if(p[length-1] == '\'')  length--;   /* ignore ' at end of word */
	cursor = p - t->text_base;

	if(d_spell != NULL)
	{
		/* replace word in spell dialogue box */
		dbox_setfield(d_spell,1,word);
	}

	diff = spell_replace(t, word, cursor, cursor+length);

	/* set spell_position to end of the new word */
	spell_position = cursor+length+1 + diff;

	increase = diff;
	if(increase >= 0)
	{
		recalc_line_tab(t,t->cursor_line,increase);
	}
	else
	{
		text_recalc_delete(t,cursor,increase);
	}

	if(increase == 0)
		redraw_text_oneline(t,t->cursor_line);
	else
		redraw_text_lines(t,t->cursor_line,-1);

	if(spell_operation == 1)
		spell_reset_cursor(t,spell_position_start); /* back start of word */
	else
		spell_reset_cursor(t,spell_position);   /* check as you type, ready for typing next word */

	mark_changed(t);
}   /* end of spell_replace_word */




void spell_replace_word2(TEXTR *t)
/********************************/
{
	char *p;

	if(spell_word_edited)
	{
		dbox_getfield(d_spell,1,wordbuf,31);
		spell_replace_word(t,t->cursor_index,wordbuf);
	}
	else if(spelllist_item >= 0)
	{
		p = &list[spelllist_item][1];
		if(p[0] == '*') p+=2;   /* marker indicating the original word is valid */

		spell_replace_word(t,t->cursor_index,p);
	}
	else
	{
		redraw_text_oneline(t,t->cursor_line);
		spell_reset_cursor(t,spell_position+1);
	}
}   /* end of spell_replace_word2 */



BOOL key_spelllist_window(wimp_eventdata *d)
/******************************************/
{
	int  c;
	int  step=1;
	wimp_caretstr caretstr;

	switch(c = d->key.chcode)
	{
	case KEY_SH_UP:
		step = 7;
	case KEY_UP:
		if(spelllist_item > 0)
		{
			spelllist_item -= step;
			if(spelllist_item < 0)
				spelllist_item = 0;
			force_redraw_window(w_spelllist);
			scroll_spelllist_check();
		}
		break;


	case KEY_SH_DOWN:
		step = 7;
	case KEY_DOWN:
		if(spelllist_item < (n_list-1))
		{
			spelllist_item += step;
			if(spelllist_item > (n_list-1))
				spelllist_item = n_list-1;

			force_redraw_window(w_spelllist);
			scroll_spelllist_check();
		}
		break;

	case '\r':
		if(spell_operation == 0)
		{
			spell_replace_word2(textr_spellcheck);
			close_spell_window(textr_spellcheck);  /* ??? */
		}
		break;

	case 0x1b:   /* escape, return to text window */
	case F_KEY_CTRL+2:
		if(textr_spellcheck != NULL)
		{
			redraw_text_oneline(textr_spellcheck,textr_spellcheck->cursor_line);
			spell_reset_cursor(textr_spellcheck,textr_spellcheck->cursor_index+2);
			close_spell_window(textr_spellcheck);
		}
		break;

	case 0x08:   /* delete, close window then delete character in text */
	case 0x7f:
		if(textr_spellcheck != NULL)
		{
			wimp_get_caret_pos(&caretstr);
			if((caretstr.w != dbox_syshandle(d_spell)) || (caretstr.i != 1))
			{
				/* cursor is not the the spell dbox writable icon */
				redraw_text_oneline(textr_spellcheck,textr_spellcheck->cursor_line);
				spell_reset_cursor(textr_spellcheck,textr_spellcheck->cursor_index+1);
				text_character_delete(textr_spellcheck);
				close_spell_window(textr_spellcheck);
			}
		}
		break;

	}

	if(d->key.c.i == 1)
	{
		/* editing of the 'word' icon */
		if((c < 0x100) && (c != '\r') && (c != '\n'))
		{
			spell_word_edited = 1;
			spell_addtemplist_flag = 0;
			return(TRUE);
		}
	}
	/*   wimp_processkey(c); */
	return(FALSE);
}   /* end of key_spelllist_window */





void spelllist_window_handler(wimp_eventstr *e, void *handle)
/***********************************************************/
{
	switch (e->e)
	{
	case wimp_EREDRAW:
		redraw_spelllist_window(e->data.o.w);
		break;

	case wimp_EOPEN:
		/* force scrolling to multiple of text lines */
		wimpt_noerr(wimp_open_wind(&e->data.o));
		break;

	case wimp_ECLOSE:  /* Pass on close request */
		wimpt_noerr(wimp_close_wind(e->data.o.w));
		break;

	case wimp_EBUT:
		click_spelllist_window(&e->data);
		break;

	case wimp_ESCROLL:
		scroll_window(&e->data);
		break;

	case wimp_EKEY:
		key_spelllist_window(&e->data);
		break;

	default:   /* Ignore any other event */
		break;
	}
}   /* end of spelllist_window_handler */








void open_spell_window(wimp_openstr *o, int initial)
/**************************************************/
/* Called when main dbox 'open' event is received,
   so we can move its pane windows */
{
	static int  behind;
	static int  x;
	static int  y;
	int  top_window;
	wimp_wstate wstate;
	wimp_wstate wstate_list;

	spell_open = 1;

	/*
	   if((x==o->box.x0) && (y==o->box.y0) && (behind==o->behind))
	      return;
	*/
	x = o->box.x0;
	y = o->box.y0;


	wimp_get_wind_state(w_spelllist,&wstate_list);

	if(initial)
	{
		o->behind = -1;       /* bring to front */
		wstate_list.o.y = 0;   /* scroll to top */
	}

	top_window = behind = o->behind;
	if(behind <= -2)
	{
		if(behind == -3)
			wimp_open_wind(o);

		wstate_list.o.behind = behind;
		wimp_open_wind(&wstate_list.o);

		if(behind == -2)
			wimp_open_wind(o);

		return;
	}

	if((o->behind != -1) && (wstate_list.o.behind == -1))
		o->behind = w_spelllist;
	wimp_open_wind(o);   /* open customer dbox */

	wimp_get_wind_state(w_spell,&wstate);

	wstate_list.o.box.x0 = wstate.o.box.x0+28;
	/*   wstate_list.o.box.x1 = wstate.o.box.x1-60; */
	wstate_list.o.box.y1 = wstate.o.box.y1 - 104;
	wstate_list.o.box.y0 = wstate_list.o.box.y1 - spelllist_height;

	if(o->behind == -1)
		wstate_list.o.behind = -1;
	else
		wstate_list.o.behind = top_window;
	wimp_open_wind(&wstate_list.o);    /* open pane  window */

	wstate.o.behind = w_spelllist;
	wimp_open_wind(&wstate.o);     /* ensure it's behind the list window */

}   /* end of open_spell_window */






void spell_show(TEXTR *t)
/*********************/
{
	char buf[36];

	wimp_wstate wstate;

	spell_load();
	if(spellcheck_present != 1)
		return;

	if(t != NULL)
	{
		textr_spellcheck = t;
		start_list();    /* empty the suggestions list */
		spell_position_start = 0;

		if(!options.open_on_error && spell_error_found)
		{
			dbox_getfield(d_spell,1,buf,sizeof(buf)-1);
			spellcheck_word(buf,options.spell_depth);
		}
		else
		{
			spell_position = 0;
			prev_cursor_line = -1;
			dbox_setfield(d_spell,1,"");
		}
	}

	spell_word_edited = 0;

	dbox_setfield(d_spell,6,"Next");
	dbox_setnumeric(d_spell,2,options.check_as_you_type);
	dbox_setnumeric(d_spell,3,options.spell_opts & 1);    /* phonetic */
	dbox_setnumeric(d_spell,4,(options.spell_opts >> 1) & 1);  /* typo */
	dbox_setnumeric(d_spell,10,options.open_on_error);

	dbox_showstatic(d_spell);

	wimp_get_wind_state(w_spell,&wstate);
	open_spell_window(&wstate.o,1);

	dbox_getfield(d_spell,1,buf,sizeof(buf)-1);
	set_input_focus(dbox_syshandle(d_spell),1,strlen(buf));
	set_focus(w_spelllist);
}   /* end of show_spell */




void spell_invoke(TEXTR *t)
/*************************/
{
	if(spellcheck_present == 1)
	{
		if((t->spell_error_index >= 0) && (t->cursor_index < t->spell_error_index) < 6)
		{
			t->cursor_index = t->spell_error_index;
			text_highlight_cursor_word(t);
			spell_highlighted = 1;
		}
		set_focus(w_spelllist);
	}
	if(spell_open == 0)
	{
		spell_show(t);
	}
}   /* end of spell_invoke */





void spell_set_word(TEXTR *t, int index, int open)
/************************************************/
/* Put the specified word into the spell window */
{
	int  i;
	int  c;
	char *p;
	char buf[36];

	textr_spellcheck = t;


	spell_position = index;
	p = &t->text_base[index];

	if(!isalnum(*p) && (*p != '\''))
	{
		if(isalnum(p[-1]))
			p--;
		else
			return;    /* not on a word */
	}

	/* find start of word */
	while((p > t->text_base) && (isalnum(*p) || (*p == '\'')))
		p--;

	if(!isalnum(*p))  p++;   /* first letter of word */
	while(*p == '\'') p++;   /* skip leading ' */


	for(i=0; (i<(sizeof(buf)-1)) && (isalnum(c = p[i]) || (c == '\'')); i++)
	{
		buf[i] = c;
	}
	if((i>0) && (buf[i-1] == '\''))
		i--;    /* ignore trailing ' */

	buf[i] = 0;
	spell_position = &p[i] - t->text_base;

	dbox_setfield(d_spell,1,buf);
	n_list = 0;        /* clear suggested word list */
	spelllist_item = -2;   /* initially invisible */

	if((open != 0) || spell_open)
	{
		spellcheck_word(buf,options.spell_depth);
		spell_show(NULL);
	}
	spell_operation = 0;
}   /* end of spell_set_word */



int spell_lookup(TEXTR *t, int index)
/***********************************/
{
	int  i;
	int  c;
	int  len;
	int  start;
	int  all_capitals;
	int  alphanumeric;   /* contains numeric characters */
	int  letters;        /* count of alphabetic characters */
	char word[64];

	spell_error_found = 0;
	spell_load();

	if(spellcheck_present != 1)
		return(1);

	textr_spellcheck = t;

	for(start=index; start>=t->text_start; start--)
	{
		c = t->text_base[start];
		if(!isalnum(c) && (c != '\''))
			break;
	}

	if((c = t->text_base[start+1]) == '\'')
		start++;   /* ignore ' at start of word */

	len = index - start;
	if(len > sizeof(word)-1)
		return(1);  /* too long */

	alphanumeric = 0;
	all_capitals = 1;
	letters = 0;
	for(i=0; i<len; i++)
	{
		c = t->text_base[start+i+1];
		word[i] = tolower(c);
		if(islower(c)) all_capitals = 0;
		if(isdigit(c)) alphanumeric = 1;
		if(isalpha(c)) letters++;
	}
	word[len]=0;

	if(letters <= 1)
		return(1);

	if(!isalpha(word[0]) && (letters <= 2))
		return(1);

	if((all_capitals) && (options.spell_capitals))
		return(1);  /* option is set to ignore words which are all capitals */

	if(word[len-1] == '\'')
		word[len-1] = 0;

	if((!alphanumeric) && (lookup_dict(word,0,0) != 0))
		return(1);   /* found */

	if(userdict_lookup(word) != NULL)
		return(1);

	/* ignore a 's suffix */
	len = strlen(word);
	if((word[len-2] == '\'') && (tolower(word[len-1]) == 's'))
	{
		word[len-2] = 0;

		if((!alphanumeric) && (lookup_dict(word,0,0)!=0))
			return(1);    /* OK */

		if(userdict_lookup(word) != NULL)
			return(1);
	}
	/* word not found in dictionary, is it in the temporary list ? */
	if(lookup_templist(word) != 0)
		return(1);   /* yes */

	t->spell_error_index = index;
	spell_error_found = 1;
	return(0);
}   /* end of spell_lookup */



int spell_find_next_error(TEXTR *t)
/*********************************/
{
	int  c;
	char *p;
	char *p_end;
	int  i;
	int  index;
	int  index_end;
	int  all_capitals;
	int  alphanumeric;
	int  letters;
	int  skip_tag=0;
	int count=0;
	char buf[36];
	char buf2[36];


	if(spell_position < t->text_start)
		spell_position = t->text_start;

	index = spell_position;
	index_end = t->text_body_end;   /* attachments ??? */

	add_exceptions_flag = 0;
	for(;;)
	{
		p = &t->text_base[index];
		p_end = &t->text_base[index_end];

		while(p < p_end)
		{
			if(!skip_tag && isalnum(*p))
				break;

			if(skip_tag)
			{
				if((*p == '>') || (skip_tag > 256))
					skip_tag = 0;
				else
					skip_tag++;
			}

			if(options.spell_skip_tags && (*p == '<'))
				skip_tag = 1;

			p++;
		}

		if(p >= p_end)
		{
			dbox_setfield(d_spell,6,"OK");
			spelllist_item = -3;   /* display "COMPLETED" */
			force_redraw_window(w_spelllist);

			user_message("Spellcheck Completed");

			if(t != NULL)
			{
				t->region_start = 0;
				t->region_end = 0;
			}
			return(0);
		}

		/* find end of the word */
		alphanumeric = 0;
		all_capitals = 1;
		letters = 0;
		for(i=0; (i<31) && (isalnum(c = p[i]) || (c == '\'')); i++)
		{
			buf2[i] = c;     /* keep case of letter */
			buf[i] = tolower(c);
			if(islower(c)) all_capitals = 0;
			if(isdigit(c)) alphanumeric = 1;
			if(isalpha(c)) letters++;
		}
		if((i>0) && (buf[i-1] == '\''))
			i--;    /* ignore trailing ' */

		buf[i] = 0;
		buf2[i] = 0;
		spell_position = p - t->text_base;
		p += i;
		index = p - t->text_base;

		if(letters <= 1) continue;     /* dont check single letters */
		if(!isalpha(buf[0]) && (letters <= 2)) continue;  /* don't fail 1st, 4th, etc */

		if((all_capitals) && (options.spell_capitals)) continue;

		count++;
		if(userdict_lookup(buf) != NULL)
			continue;

		if(alphanumeric || (lookup_dict(buf,0,0) == 0))
		{
			/* ignore a 's suffix */
			if((buf[i-2]=='\'') && (buf[i-1]=='s'))
			{
				buf[i-2] = 0;
				if(userdict_lookup(buf) != NULL)
					continue;

				if((!alphanumeric) && (lookup_dict(buf,0,0)!=0))
					continue;    /* OK */
			}
			/* word not found in dictionary, is it in the temporary list ? */
			if(lookup_templist(buf) != 0)
				continue;   /* yes */

			t->cursor_index = spell_position;
			spell_position_start = spell_position;
			spell_position += i;

			t->region_start = t->cursor_index;
			t->region_end = spell_position;
			t->cursor_index = spell_position;
			set_caret_pos(t);

			redraw_text_oneline(t,t->cursor_line);
			if((prev_cursor_line >= 0) && (prev_cursor_line != t->cursor_line))
				redraw_text_oneline(t,prev_cursor_line);
			prev_cursor_line = t->cursor_line;

			dbox_setfield(d_spell,1,buf2);
			spell_operation = 1;
			n_list = 0;
			spelllist_item = -2;   /* list cursor initially invisible */
			spell_highlighted=1;
			force_redraw_window(w_spelllist);
			call_event_process(4);
			break;
		}
	}

	return(1);
}   /* end of spell_find_next_error */




void spelldict_edit(char *fname)
/******************************/
/* Called from OLE when file has been edited */
{
	userdict_wipe();
	userdict_import(0,fname);
	remove(fname);
}   /* end of spelldict_edit */



BOOL spell_raw_handler(dbox d, void *event, void *handle)
/******************************************************/
{
	int  i;

	wimp_eventstr *e = event;
	wimp_mousestr *mouse;

	switch(e->e)
	{
	case wimp_EOPEN:
		open_spell_window(&e->data.o,0);
		return(TRUE);

	case wimp_ECLOSE:
		return(FALSE);

	case wimp_EKEY:
		i = key_spelllist_window(&e->data);
		return(i);
		break;

	case wimp_EBUT:
		/* click on icon.  get icon number. */
		mouse = &e->data.but.m;
		switch(mouse->i)
		{
		case 2:
		case 10:
			options.check_as_you_type = dbox_getnumeric(d,2);
			options.open_on_error = dbox_getnumeric(d,10);
			options_save();

			show_spellcheck_state();
			break;
		}
		break;

#ifdef deleted
	case wimp_ESEND:
	case wimp_ESENDWANTACK:
		switch(e->data.msg.hdr.action)
		{
			int  filetype;
			char *filename;
		case wimp_MDATALOAD:
			filetype = xferrecv_checkinsert(&filename);
			if(filetype == 0xfff)
			{
				userdict_import(0,filename);
				xferrecv_insertfileok();
				return(TRUE);
			}
		}
		break;
#endif
	}

	/* now call the help handler */
	return(help_handler(event,HELP_SPELL));
}  /* end of spell_raw_handler */





void learn_word(char *word)
/*************************/
{
	FILE *f;

	f = fopen("<SpellJSD$Dir>.Learn","a");
	if(f != NULL)
	{
		fprintf(f,"%s\n",word);
		fclose(f);
	}
}


void spell_dbox_handler(dbox d, void *handle)
/*******************************************/
{
	int  field;
	int  i;
	int  length;
	TEXTR *t;
	char *fname;
	int  close_flag;

	t = textr_spellcheck;
	if(t == NULL)
		return;   /* window should already be closed */

	field = dbox_get(d);

	options.spell_opts &= 0xfc;
	options.spell_opts |=  (dbox_getnumeric(d_spell,3) & 1);
	options.spell_opts |= (dbox_getnumeric(d_spell,4) & 1) << 1;

	dbox_getfield(d,1,wordbuf,sizeof(wordbuf));
	close_flag = 0;

	switch(field)
	{
	case 99:    /* spellcheck word */
		/* clear spelllist */
		spelllist_item = -2;
		n_list = 0;
		force_redraw_window(w_spelllist);
		event_process();

		length = strlen(wordbuf);
		if(length==0)
			break;

		spellcheck_word(wordbuf,options.spell_depth);
		spell_show(0);
		break;

	case 2:   /* speak */
		speak_string(&list[spelllist_item][1]);
		break;


	case 9:   /* Lookup word */
		spellcheck_word(wordbuf,options.spell_depth);
		break;

	case 7:  /* Replace word at cursor with word selected from list */
		spell_addtemplist_flag = 0;
		spell_replace_word2(t);
		close_flag = 1;
		break;

	case 5:   /* Add to user dictionary */
	case 8:   /* OK, find next spelling error in the text */
	case 0:
	case 6:   /* Skip, find next spelling error in the text */
		close_flag = 1;

		spell_addtemplist_flag = 0;
		spell_position = t->cursor_index;

		if(field == 8)   /* IGNORE */
		{
			add_templist(wordbuf);
		}
		if(field == 5)  /* ADD to user dictionary */
		{
			userdict_add(wordbuf,NULL);
			userdict_save();
		}

		if((field == 8) || (field == 5))
		{
			if(spell_operation == 0)   /* check as you type */
			{
				t->cursor_index += 2;
				if(t->cursor_index >= t->text_body_end)
					t->cursor_index = t->text_body_end - 1;

				set_caret_pos(t);
				break;   /* check as you type */
			}
		}

		if(spell_find_next_error(t) == 0)
		{
			spell_addtemplist_flag = 0;
			close_spell_window(t);
			break;
		}
		spell_addtemplist_flag = 1;

		dbox_getfield(d,1,wordbuf,sizeof(wordbuf));
		length = strlen(wordbuf);
		if(length <= 9)
			spellcheck_word(wordbuf,options.spell_depth);
		else
		{
			if((i = options.spell_depth) >= 3)
				i--;
			spellcheck_word(wordbuf,i);
		}
		spell_show(0);
		break;

	case 11:   /* user dictionary */
		fname = get_temp_fname();   /* temp file */
		userdict_list(fname);
		OLE_start(fname,0xfff,5,NULL,0,NULL);
		break;

	case -1:
		close_spell_window(t);
		break;

	}

	if(close_flag & (spell_operation == 0))
	{
		/* check a you type */
		close_spell_window(t);  /* ??? */
	}
}   /* end of spell_dbox_handler */






void options_spellcheck()
/***********************/
{
	dbox d_opt;
	int  i;

	d_opt = dbox_new("SpellOpt");
	dbox_raw_eventhandler(d_opt,dbox_help_handler,(void *)HELP_OPSPELL);

	dbox_setnumeric(d_opt,1,options.spell_depth);
	dbox_setnumeric(d_opt,10,options.spell_capitals);
	dbox_setnumeric(d_opt,11,options.spell_skip_tags);

	for(i=0; i<=4; i++)
	{
		dbox_setnumeric(d_opt,i+2,(options.spell_opts >> i) & 1);
	}

	dbox_show(d_opt);
	if(dbox_fillin(d_opt) == 0)
	{
		options.spell_depth = dbox_getnumeric(d_opt,1);
		options.spell_capitals = dbox_getnumeric(d_opt,10);
		options.spell_skip_tags = dbox_getnumeric(d_opt,11);

		options.spell_opts = 0;
		for(i=0; i<=4; i++)
		{
			if(dbox_getnumeric(d_opt,i+2))
				options.spell_opts |= (1 << i);
		}

		options_save();
	}
	dbox_dispose(&d_opt);
}   /* end of options_spellcheck */












void swap_pairs(char *word)
/*************************/
{
	int  i;
	int  x;
	char w[WIDTH];

	strcpy(w,word);

	for(i=length-2; i>=0; i--)
	{
		x = w[i];
		w[i] = w[i+1];
		w[i+1] = x;
		add_word(w,0xfc);
		x = w[i];
		w[i] = w[i+1];
		w[i+1] = x;
	}
}   /* end of swap_pairs */



void change_letter_all(char *word)
/********************************/
/* if opt.qwerty is set, then only change to adjacent letters on the kerboard */
{
	int  i;
	int  c;
	char *p;
	int  start;
	int  priority=0xff;  /* low */
	int  any_vowels=0;
	int  spell_qwerty=0;
	char w[WIDTH];

	strcpy(w,word);

	if((options.spell_opts & SPELL_QWERTY) && (length <= 6))
		spell_qwerty = 1;

	for(i=0; i<length; i++)
	{
		if(is_vowel(word[i]))
		{
			any_vowels = 1;
			break;
		}
	}
	if(any_vowels==0)
	{
		priority = 0xfc;
	}

	start=0;
	if(length <= 3)
	{
		spell_qwerty=1;

		if(any_vowels)
			start = 1;
	}

	/* in reverse order so that changes at end of word appear before those at the beginning */
	for(i=length-1; i>=start; i--)
	{
		c = tolower(word[i]) - 'a';

		if(spell_qwerty && (c >= 0) && (c < 26))
		{
			p = nearkeys[c];
			while((c = *p++) != 0)
			{
				w[i] = c;
				add_word(w,priority);    /* low priority */
			}
		}
		else
		{
			for(c='a'; c<='z'; c++)
			{
				w[i] = c;
				add_word(w,priority);     /* low priority */
			}
		}
		w[i] = word[i];
	}
}   /* end of change_letter_all */



void add_letter_all(char *word)
/*****************************/
{
	int  i;
	int  j;
	int  c;

	char w[WIDTH+1];

	i=0;
	if(length <= 3)  i = 1;

	for(i=length; i>=0; i--)
	{
		strcpy(w,word);
		for(j=length; j>i; j--)
		{
			w[j] = w[j-1];
		}
		w[length+1] = 0;

		for(c='a'; c<='z'; c++)
		{
			w[i] = c;
			add_word(w,0xfd);
		}
	}
}   /* end of add_letter_all */



void omit_letter_all(char *word)
/******************************/
{
	int i;
	int j;

	char w[WIDTH];

	for(i=length-1; i>=0; i--)
	{
		strcpy(w,word);
		for(j=i; j<length; j++)
		{
			w[j] = w[j+1];
		}
		add_word(w,0xfe);
	}
}   /* end of omit_letter_all */



void check_missing_space(char *word)
/**********************************/
/* Is this two words joined together ? */
{
	int  start=3;
	int  end;
	int  bk;

	char w1[WIDTH+1];
	char w2[WIDTH];

	if((word[0]=='a') && (!is_vowel(word[1])))
		start = 1;
	else if((word[0]=='a') && (word[1]=='n') && (is_vowel(word[2])))
		start = 2;

	if(word[length-1]=='a')
		end = length;
	else
		end = length-1;

	for(bk=start; bk<end; bk++)
	{
		memcpy(w1,word,bk);
		w1[bk]=0;

		strcpy(w2,&word[bk]);

		if(lookup_dict(w1,0,0) && (lookup_dict(w2,0,0)))
		{
			/* both parts are valid words */
			sprintf(w1,"%s %s",w1,w2);
			add_word0(w1,0xff);   /* low priority */
		}
	}
}   /* check_missing_space */




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

void add_word1(char *w, int condition, int points)
/************************************************/
{
	char *ph;
	char *ph_code;
	char *ptr;
	char *result;
	int  condition2;
	int  ix;
	int c;
	os_error *error;
	char phonemes[80];

	/* check for phonetic condition */

	if(condition)
	{
		error = os_swi3r(SWI_SPEAK+2,3,(int)w,5,(int *)(&result),NULL,NULL);
		if(error == 0)
		{
			ph = phonemes;
			for(ix=0; (c = result[ix]) != 0; ix++)
			{
				if((c!='\'') && (c!=','))
					*ph++ = c;
			}
			ph[0] = '_';
			ph[1] = 0;
			ph = phonemes;

			condition2 = 0;
			if(condition > 0x10000)
			{
				condition2 = condition & 0xffff;
				condition = condition >> 16;
			}

			while((condition & 0xff) > 1)
			{
				/* is the phonetic code in 'condition' present in the word ? */
				ph_code = phonetics[condition & 0xff];
				ptr = strstr(ph,ph_code);

				switch(condition & 0xff)
				{
				case 2:   /* @ */
					if(ptr == NULL)
					{
						if(((ptr = strstr(ph,"_a#")) != NULL) || ((ptr = strstr(ph,"_I")) != NULL) || ((ptr = strstr(ph,"3_")) != NULL))
							;   /* allow  or  for  */
					}
					break;

				case 4:   /* s, also allow S  */
					if(ptr == NULL)
						ptr = strstr(ph,"_S");

					/* next sound should not be 'k' */
					if((ptr != NULL) && (ptr[3] == 'k'))
						ptr = NULL;
					break;

				case 6:   /* S  dont allow tS */
					if((ptr != NULL) && (ptr > result) && (ptr[-1] == 't'))
						ptr = NULL;
					break;

				case 7:   /* d   allow dZ or dj */
					if(ptr != NULL)
					{
						if((ptr[1] != 'Z') && (ptr[1] != 'j'))
							ptr = NULL;
					}
					break;
				}

#ifdef deleted
				{
					FILE *fv;
					int x=0;
					if(ptr != NULL) x = 1;
					fv=fopen("log_espeak","a");
					fprintf(fv,"%d w(%s) c=%3x pt=%3d ph(%s)\n", x, w, condition, points, result);
					fclose(fv);
				}
#endif
				if(ptr == NULL)
					return;    /* condition failed, dont list this word */

				if((condition < 0x100) && ((ptr - result) > 1))
					return;   /* condition matches, but not at start of the word */

				condition = condition2;
				condition2 = 0;
			}
		}
	}

	/* higher points scores indicate unfavorable words */
	ix = (points & 0x1f) - 0x10;
	points = (points & 0xe0) >> 4;   /* each level is 2 points */

	points = points + ix;

	add_word0(w,points);
}   /* end of add_word1 */






void output(char *w, int condition,int n)
/*********************************/
{

}




/* Format of match data

	char *index[26];     table of entry points for each alphabetic letter

	char n_bytes;        length of record
	char consume;        num.of bytes to advance along word
	char match_str[];    characters to match (0 terminator)
	char pre_str[];      characters to match previously (0 terminator) reverse order
	char replacement[];  replacement characters
	char phonetic;       phonetic condition (0 terminator)

	list of records terminated with record with n_bytes = 0
*/



int *index_tab;
char *rules_data;





void spellcheck_word(char *word, int phonetic_depth)
/**************************************************/
{
	int  vowels;
	char *p;
	int  i;
	int  wildcard;
	int  timer;
	char wordbuf[WIDTH+8];
	char wordbuf2[WIDTH+8];
	char buf[WIDTH+2];


	if(word[0] == 0)
		return;

	start_list();

	visdelay2_begin();
	timer = clock();

	wordbuf[3] = 0;

	length = strlen(word);
	wildcard=0;

	for(i=0; i<=length; i++)
	{
		if((wordbuf[i+4] = tolower(word[i])) == '?')
			wildcard++;
	}
	word = &wordbuf[4];     /* align to word boundary */

	if(wildcard)
	{
		crossword_lookup(word,length);
		visdelay2_end();
		list_complete();
		return;
	}
	add_exceptions_flag = 1;

	if(lookup_dict(word,0,0))
	{
		sprintf(wordbuf2,"* %s",word);
		add_word1(wordbuf2,0,0x10);   /* original word */
	}

	sprintf(wordbuf2,"%se",word);   /* add an 'e' */
	if(lookup_dict(wordbuf2,0,0))
	{
		add_word1(wordbuf2,0,0x11);
	}

	max_changes = 1;     /* always run phonetic rules to at least depth 1 */
	if(options.spell_opts & SPELL_PHONETIC)
		max_changes = phonetic_depth;

	rule_options = options.spell_opts >> 3;

	apostrophe_s = 0;
	if((word[length-2] == '\'') && (word[length-1] == 's'))
	{
		/* drop 's ending and add it later */
		word[length-2] = 0;
		apostrophe_s = 1;
	}

	p = word;
	vowels = 0;
	while(*p != 0)
	{
		if(is_vowel(*p++))
			vowels++;
	}


	apply_rules(word,0,0x110 - (max_changes<<5),0);


	if(vowels == 0)
	{
		add_letter_all(word);
	}


	/* precede by 'h' if word starts with a vowel */
	wordbuf2[3] = 0;
	if((length <= 8) && is_vowel(word[0]))
	{
		strcpy(&wordbuf2[5],word);
		wordbuf2[4] = 'h';                /* start on word boundary */
		i = max_changes - 1;
		if(i <= 0) i = 1;

		i = 0x110 - (i << 5) + 2;
		if(lookup_dict(wordbuf2+4,0,0))
		{
			add_word1(wordbuf2+4,0,i);   /* original word, with 'h' */
		}


		apply_rules(wordbuf2+4,0,i,0);
	}

	if(options.spell_opts & SPELL_TYPO)
	{
		swap_pairs(word);
		omit_letter_all(word);
		add_letter_all(word);
		change_letter_all(word);
		check_missing_space(word);
	}

	add_exceptions_flag = 0;

	timer = clock() - timer;
	sprintf(buf,"%d",timer);
	/*   dbox_setfield(d_spell,1,buf);  */
	visdelay2_end();

	list_complete();

}   /* end of spellcheck_word */










void lookup_exceptions(char *word,int points)
/*******************************************/
/* Lookup the word in the exceptions list and add the translation to the 'found' list */
{
	int hash;
	char *p;
	char *p_end;
	int  n_bytes;
	char word2[40];

	if(!add_exceptions_flag)
		return;

	hash = get_hash9(word);
	p = except_addr + except_htab[hash];
	p_end = except_addr + except_htab[hash+1];

	while(p < p_end)
	{
		n_bytes = *p;
		if(strcmp(p+1,word) == 0)
		{
			p+=(strlen(p+1)+2);
			add_word1(p,0,points);
			return;
		}
		p+= n_bytes;
	}

	/* not found, try without the 's' suffix */
	strcpy(word2,word);
	length = strlen(word2);
	if(word2[length-1] == 's')
	{
		word2[length-1] = 0;

		hash = get_hash9(word2);
		p = except_addr + except_htab[hash];
		p_end = except_addr + except_htab[hash+1];

		while(p < p_end)
		{
			n_bytes = *p;
			if(strcmp(p+1,word2) == 0)
			{
				p+=(strlen(p+1)+2);
				sprintf(word2,"%ss",p);   /* add the 's' suffix to the translation */
				add_word1(word2,0,points);
				return;
			}
			p+= n_bytes;
		}
	}

}   /* end of lookup_exceptions */







char coding[256] = {
	0xb0, 0xcb, 0x49, 0x68, 0x07, 0x45, 0xc8, 0x7f,
	0x13, 0xc4, 0x6c, 0x2b, 0xba, 0x61, 0x65, 0xa1,
	0x72, 0x64, 0x39, 0x3d, 0xee, 0xd2, 0xb5, 0x09,
	0x54, 0xe6, 0x07, 0x68, 0xe8, 0xdf, 0x56, 0xc4,
	0x72, 0x00, 0x99, 0xe8, 0x66, 0xe1, 0x18, 0xec,
	0x63, 0x90, 0x27, 0xd6, 0x80, 0x58, 0xd1, 0x88,
	0x4f, 0x6e, 0x63, 0xdd, 0xe8, 0xdb, 0x74, 0x3d,
	0x49, 0x01, 0xef, 0xab, 0xa9, 0x97, 0xff, 0x27,
	0xfc, 0x52, 0x90, 0xe1, 0xbd, 0x72, 0xfa, 0xb3,
	0xa7, 0xa0, 0xcb, 0xba, 0x90, 0x7d, 0x91, 0x2f,
	0x08, 0x57, 0x93, 0x88, 0xed, 0xc3, 0x99, 0xfc,
	0xeb, 0x78, 0x47, 0x9e, 0x8a, 0xe6, 0x16, 0x37,
	0xc7, 0xa1, 0x3a, 0xe8, 0x4e, 0x19, 0x7e, 0x76,
	0xba, 0x70, 0x70, 0xc8, 0x37, 0xd6, 0x45, 0xec,
	0x67, 0xf2, 0x47, 0x21, 0xe5, 0x3d, 0x33, 0x19,
	0x31, 0x1b, 0xa5, 0xc0, 0x13, 0x31, 0x1d, 0x5a,
	0x3b, 0x2a, 0x58, 0xb4, 0x67, 0x74, 0xf4, 0xbe,
	0x85, 0xcf, 0x0e, 0xa8, 0xd6, 0x2f, 0x04, 0xa9,
	0x62, 0x43, 0x4a, 0xf9, 0x47, 0x54, 0x21, 0xdc,
	0x64, 0x40, 0x02, 0x80, 0xf2, 0x6a, 0x78, 0xf5,
	0x3f, 0xd6, 0xdf, 0xac, 0x74, 0xf0, 0x10, 0x54,
	0x8a, 0x6b, 0xde, 0x84, 0x87, 0x3a, 0x0d, 0x7f,
	0xe5, 0xc2, 0x93, 0x23, 0x87, 0xd2, 0x30, 0x69,
	0x2e, 0x93, 0x13, 0xe8, 0xe4, 0xce, 0xd9, 0x59,
	0x79, 0x86, 0x5a, 0xb6, 0x3e, 0xb6, 0xe1, 0x28,
	0x0c, 0x8c, 0x6a, 0xdb, 0xf3, 0x0c, 0xcd, 0x6e,
	0x95, 0x93, 0xda, 0x4e, 0x46, 0xce, 0x98, 0x4f,
	0x65, 0x06, 0x2a, 0x2e, 0xc6, 0x35, 0xb2, 0x77,
	0x6a, 0xee, 0x62, 0x2d, 0x19, 0x4c, 0x53, 0x57,
	0x26, 0xfd, 0xd5, 0x18, 0xfe, 0x01, 0x7d, 0xf8,
	0x19, 0x12, 0x12, 0x94, 0x0e, 0x0b, 0xf0, 0xf0,
	0x48, 0x18, 0x6b, 0xcf, 0x2d, 0x7b, 0x63, 0xb2,
};




void decode_data(char *addr, int length)
/**************************************/
{
	int  i;

	for(i=0; i<length; i++)
	{
		addr[i] = addr[i] - coding[i & 0xff];
	}
}   /* end of decode_data */




int load_endings(char *fname)
/***************************/
{
	int length;
	FILE *f;
	char *p;
	int  i;

	f = fopen(fname,"r");
	if(f == NULL)
	{
		return(1);
	}

	length = get_filelength(fname);
	p = malloc(length);
	if(p == NULL)
	{
		werr(0,"Can't allocate memory for endings table");
		fclose(f);
		return(1);
	}

	fread(p,1,length,f);
	fclose(f);

	ending_index = (short *)p;
	p += 512;

	for(i=0; i<N_ENDINGS; i++)
	{
		endtab[i] = p;
		length = strlen(p);
		endtab_len[i] = length;
		p += (length + 1);
	}
	return(0);
}   /* end of load_endings */




int load_dict(char *fname)
/************************/
{
	FILE *f;
	int length;

	f = fopen(fname,"r");
	if(f == NULL)
	{
		return(1);
	}

	length = get_filelength(fname);
	dict_addr = malloc(length);
	if(dict_addr == NULL)
	{
		werr(0,"Not enough memory to load dictionary file '%s'",fname);
		fclose(f);
		return(1);
	}

	fread(dict_addr,1,length,f);
	fclose(f);

	hash_table = (int *)dict_addr;
	return(0);
}   /* end of load_dict2 */




int load_exceptions(char *fname)
/******************************/
{
	FILE *f;
	int length;

	f = fopen(fname,"r");
	if(f == NULL)
		return(1);

	length = get_filelength(fname);
	except_addr = malloc(length);
	if(except_addr == NULL)
	{
		werr(0,"Not enough memory to load exceptions file '%s'",fname);
		fclose(f);
		return(1);
	}

	fread(except_addr,1,length,f);
	fclose(f);
	decode_data(except_addr,length);

	except_htab = (int *)except_addr;
	return(0);
}   /* end of load_exceptions */





void spell_reset()
/****************/
{
	spell_position = 0;
}   /* end of spell_reset */





void init_spell_display()
/*****************/
{
	wimp_wind *window_template;
	wimp_icon icon_info;

	window_template = template_syshandle("Spell2");

	if(options.position_y[4] > 0)
	{
		window_template->box.x0 = options.position_x[4];
		window_template->box.y1 = options.position_y[4];
	}

	/* customer dbox and panes */
	d_spell = dbox_new("Spell2");
	w_spell = dbox_syshandle(d_spell);

	window_template = template_syshandle("SpellList");
	wimp_create_wind(window_template ,&w_spelllist);
	win_register_event_handler(w_spelllist,spelllist_window_handler,NULL);

	wimp_get_icon_info(w_spell,0,&icon_info);
	spelllist_height = icon_info.box.y1 - icon_info.box.y0 - 24;

	memcpy(&spelllist_workarea.box,&window_template->ex,sizeof(wimp_box));
	spelllist_workarea.box.y0 = spelllist_workarea.box.y1 - spelllist_height;
	spelllist_workarea.w = w_spelllist;
	wimp_set_extent(&spelllist_workarea);

	dbox_raw_eventhandler(d_spell,spell_raw_handler,NULL);
	dbox_eventhandler(d_spell, spell_dbox_handler, NULL);

}   /* end of init_spell_display */




int init_dict()
/**************/
{
	if(load_endings("<SpellJSD$Dir>.DictSp1"))
		return(0);

	if(load_dict("<SpellJSD$Dir>.Dict"))
		return(0);

	if(load_exceptions("<SpellJSD$Dir>.DictSp3"))
		return(0);

	return(1);
}



void spell_load_attempt()
/*********************/
/* Reset the flag that was set if the spell load failed.
   This will cause a re-try to load the spell data */
{
	if(spellcheck_present == -1)
		spellcheck_present = 0;
	spelllist_item = -2;
}   /* end of spell_load_attempt */



void spell_load()
/***************/
{
	FILE *f;
	int length;
	static char *fname = "<SpellJSD$Dir>.DictSp2";

	if(spellcheck_present != 0)
		return;

	spellcheck_present = -1;

	speak_load();

	f = fopen(fname,"r");
	if(f != NULL)
	{
		length = get_filelength(fname);
		rules_data = malloc(length);
		if(rules_data == NULL)
		{
			werr(0,"Failed to allocate memory for spell checker");
			return;
		}
		index_tab = (int *)rules_data;

		fread(rules_data,1,length,f);
		fclose(f);
		decode_data(rules_data,length);


		spellcheck_present = init_dict();
		init_spell_display();
	}

	if(spellcheck_present != 1)
	{
		spellcheck_present = -1;  /* failed */
		werr(0,"Spell Check data is not available");
		return;
	}

}   /* end of spell_load */



void init_spell()
/***************/
{
	userdict_load();
}   /* end of init_spell */
