/**
 *
 * $Header: /cvsroot/hungry/lesstif/lib/Xm/TextStrSo.c,v 1.17 1998/05/02 21:01:08 rwscott Exp $
 *
 * Copyright (C) 1995 Free Software Foundation, Inc.
 *
 * This file is part of the GNU LessTif Library.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library 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
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 **/

static const char rcsid[] = "$Id: TextStrSo.c,v 1.17 1998/05/02 21:01:08 rwscott Exp $";

/*
 * Empirical evidence (fooling around with the structures found in Motif
 * apps at run time) shows that the following variables reflect the
 * selection (i.e. the PRIMARY selection) :
 *      - hasselection (Boolean) is set when we have the selection
 *      - left, right (XmTextPosition) indicate the edges of the selection
 *      - prim_time (Time) is the time of the primary selection.
 *
 * The reader has by now understood that the selection is not an XmText but
 * an XmTextSource issue.
 */

#define	DO_SANITY

/* Defining this is a bit too verbose for my liking ... */
#undef VERBOSE_XdbDebug

#include <LTconfig.h>
#include <Xm/XmP.h>
#include <Xm/TextP.h>
#include <X11/Xfuncs.h>
#ifdef HAVE_STRING_H
#include <string.h>
#define ANSI_STRING
#else
#include <strings.h>
#endif
#include <stdlib.h>

#include <XmI/DebugUtil.h>

#define SOURCE_ALLOC_SIZE 256

void _XmTextInvalidate(XmTextWidget w, XmTextPosition position,
		       XmTextPosition topos, long delta);

static char *
StrPtr(XmSourceData d, XmTextPosition pos)
{
    char *ptr;

    ptr = d->ptr + pos;
    return ptr;
}

static char *
StrNext(XmSourceData d, XmTextPosition pos)
{
    return d->ptr + pos + 1;
}

static char *
StrPrev(XmSourceData d, XmTextPosition pos)
{
    if (pos > 0)
    {
	return d->ptr + pos + 1;
    }
    else
    {
	return NULL;
    }
}

/* AddWidget ----------------------------------------------------------------
 *
 * Method that adds a widget to the source structure so that this new widget
 * will be updated as the text in the source changes.
 */
static void
AddWidget(XmTextSource source, XmTextWidget w)
{
    XmSourceData d;

    d = source->data;
    d->numwidgets++;
    d->widgets = (XmTextWidget *)XtRealloc((char *)d->widgets,
				       sizeof(XmTextWidget *) * d->numwidgets);

    d->widgets[d->numwidgets - 1] = w;

    if (d->maxallowed == 0)
    {				/* 1st time adding a widget! */
	d->maxallowed = Text_MaxLength(w);

	if (d->value == (char *)XmUNSPECIFIED || d->value == NULL)
	{
	    d->value = XtNewString("");
	    d->old_length = 0;
	}
	else
	{
	    d->value = XtNewString(d->value);
	    d->old_length = (d->value) ? strlen(d->value) : 0;
	}

	d->maxlength = d->old_length + SOURCE_ALLOC_SIZE;
	d->ptr = XtMalloc(d->maxlength + 1);
	d->length = d->old_length;
	strcpy(d->ptr, d->value);

	d->gap_start = d->ptr;
	d->gap_end = d->ptr;
    }

    Text_LastPos(w) = d->length;
}

/* RemoveWidget -------------------------------------------------------------

 * Removes a widget from a source, but DOES NOT delete the source if removing
 * the last widget.
 *
 * Danny 4/5/97 - why not ??
 *      Added it for now.
 */
static void
RemoveWidget(XmTextSource source, XmTextWidget w)
{
    XmSourceData d;
    XmTextWidget *get, *store;
    int i, found = 0;

    d = source->data;
    get = store = d->widgets;
    for (i = 0; i < d->numwidgets; i++)
    {
	if (*get != w)
	{
	    *store = *get;
	    store++;
	}
	else
	{
	    found++;
	}

	get++;
    }
    d->numwidgets -= found;

    if (d->numwidgets == 0)
    {
	_XmStringSourceDestroy(source);
    }
}

/* CountLines ---------------------------------------------------------------

 * Counts the number of lines...  Slightly more tricky than at first glance,
 * but only slightly.  Stuff having to do with " \n" having 2 lines.  Think
 * about it.
 */
static int
CountLines(XmTextSource source, XmTextPosition start, unsigned long len)
{
    XmSourceData d;
    char *ptr;
    int count;

    d = source->data;
    ptr = StrPtr(d, start);
    count = 1;
    while (len-- > 0)
    {
	if (*ptr == '\n')
	{
	    count++;
	}
    }

    return count;
}

/* ReadSource ---------------------------------------------------------------

 * Copies the text in the source to a text block.
 */
static XmTextPosition
ReadSource(XmTextSource source,
	   XmTextPosition pos,
	   XmTextPosition last,
	   XmTextBlock block)
{
    XmSourceData d;
    int len;

    d = source->data;

    if (((unsigned)last) > ((unsigned)d->length))
    {
	last = d->length;
    }

    if (pos > last)
    {
	int swap;

	swap = pos;
	pos = last;
	last = swap;
    }

    len = last - pos;
    block->length = len;
    block->ptr = XtMalloc(len + 1);
    block->format = XmFMT_8_BIT;

    if (len > 0)
    {
	strncpy(block->ptr, &d->ptr[pos], len);
    }

    block->ptr[len] = '\0';

    return pos;
}

/* Replace ------------------------------------------------------------------

 * (And sundry functions)
 *
 * Replace text in the source with something else.
 */
static void
CheckSize(XmSourceData d, int len)
{
    if (d->length + len > d->maxlength)
    {
	int i;

	i = SOURCE_ALLOC_SIZE;
	if (i < len)
	{
	    i = len;
	}

	d->maxlength += i + 1;
	d->ptr = XtRealloc(d->ptr, d->maxlength);

	DEBUGOUT(XdbDebug(__FILE__, NULL,
			  "*source->CheckSize: Alloced new space\n"));
    }
}

static void
Insert(XmSourceData d, XmTextPosition start, char *ptr, int len)
{
    int i;

#ifdef	DO_SANITY
/* Sanity check */
    if (start < 0)
    {
	start = 0;
    }
#endif

    for (i = d->length - 1; i >= start; i--)
    {
	d->ptr[i + len] = d->ptr[i];
    }

    strncpy(&d->ptr[start], ptr, len);
    d->length += len;
}

/*
 * This function should also take care of highlighting and selections.
 *
 * NOTE: test/Xm/text/test3 shows that if a widget is modified, callbacks
 *      are called only for the widget involved, NOT for all widgets displaying
 *      that particular source.
 *      Therefore, we only work on one widget in part of the implementation.
 *
 * NOTE : Do NOT check for d->editable (in order not to do the editing).
 *      At this low level this function gets called for ALL editing, not just
 *      the user-inflicted ones. Therefore edits must happen.
 */
static XmTextStatus
Replace(XmTextWidget w,
	XEvent *ev,
	XmTextPosition *startret,
	XmTextPosition *endret,
	XmTextBlock block,
	Boolean call_callback)
{
    XmSourceData d;
    int i;
    XmTextPosition start, end;
    XmTextVerifyCallbackStruct cbs;

    d = Text_Source(w)->data;

    start = *startret;
    end = *endret;

    if (start > end)
    {
	int swap;

	swap = start;
	start = end;
	end = swap;
    }

    if (end > d->length)
    {
	end = d->length;
    }

    if (start > d->length)
    {
	return EditError;
    }

    if (d->length + (end - start) + block->length > d->maxallowed)
    {
	return EditError;
    }


    for (i = 0; i < d->numwidgets; i++)
    {
	_XmTextDisableRedisplay(d->widgets[i], True);
    }

    if (end > start)
    {				/* we need to delete some stuff */
	char *dest, *source, *last;

	dest = d->ptr + start;
	source = d->ptr + end;
	last = d->ptr + d->length;
	while (source < last)
	{
	    *dest++ = *source++;
	}

	d->length -= end - start;
    }

    if (block && block->length > 0)
    {
	CheckSize(d, block->length);
	Insert(d, start, block->ptr, block->length);
    }

    for (i = 0; i < d->numwidgets; i++)
    {
	Text_LastPos(d->widgets[i]) = d->length;
    }

    /* Callback : only do this for the widget activating this ! */
    if (call_callback)
    {
	if (Text_ValueChangedCallback(w))
	{
	    cbs.reason = XmCR_VALUE_CHANGED;
	    cbs.currInsert = cbs.newInsert = start;
	    cbs.startPos = cbs.endPos = start;
	    cbs.text = block;

	    XtCallCallbacks((Widget)w, XmNvalueChangedCallback, &cbs);
	}
    }

    /* Simplistic treatment of selection (FIX ME)
     * What this does is eliminate the selection once the text is edited. */
    d->hasselection = False;
    for (i = 0; i < d->numwidgets; i++)
    {
	Text_Highlight(d->widgets[i]).number = 0;
    }
    /* End selection */

    for (i = 0; i < d->numwidgets; i++)
    {
	_XmTextUpdateLineTable((Widget)d->widgets[i], start, end, block, True);
    }

    for (i = 0; i < d->numwidgets; i++)
    {
	_XmTextInvalidate(d->widgets[i], start, end,
			  block->length - end + start);
    }

    for (i = 0; i < d->numwidgets; i++)
    {
	_XmTextEnableRedisplay(d->widgets[i]);
    }

    return EditDone;
}

/* Scan ---------------------------------------------------------------------

 * Search the source for something: (either forwards or backwards)
 *
 * XmSELECT_POSITION
 * XmSELECT_WHITESPACE
 * XmSELECT_WORD
 * XmSELECT_LINE
 * XmSELECT_ALL
 * XmSELECT_PARAGRAPH
 */
#define InWhiteSpace(a) (a==' ' || a=='\t' || a=='\n')
#define InWord(a) (!InWhiteSpace(a))

static XmTextPosition
Scan(XmTextSource source, XmTextPosition pos, XmTextScanType type,
     XmTextScanDirection dir, int count, Boolean inc)
{
    XmSourceData d = source->data;
    Boolean found = False;
    char *ptr;
    XmTextPosition ipos = pos;

    if (pos > d->length)
    {
	return(pos);
	pos = d->length;
    }

    if (count < 0)
    {
	count = d->length;
    }

    /*
     * This may not be completely right.
     *      If you start in whitespace, move towards non-whitespace before
     *      doing anything else.
     *      Currently, do this for XmSELECT_WORD.
     *      It may be appropriate to do this for other selections as well.
     * Danny 4/6/97
     */
    if (dir == XmsdLeft && type == XmSELECT_WORD)
    {
	ptr = StrPtr(d, pos - 1);
	while (count > 0 && pos > 0 && InWhiteSpace(*ptr))
	{
	    count--;
	    pos--;
	    ptr = StrPtr(d, pos - 1);
	}
    }
    else if (dir == XmsdRight && type == XmSELECT_WORD)
    {
	ptr = StrPtr(d, pos);
	while (count > 0 && pos < d->length && InWhiteSpace(*ptr))
	{
	    count--;
	    pos++;
	    ptr = StrPtr(d, pos);
	}
    }

    /* End initial whitespace treatment */

    if (dir == XmsdLeft)
    {
	while (count > 0 && pos > 0)
	{
	    ptr = StrPtr(d, pos - 1);
	    switch (type)
	    {
	    case XmSELECT_WHITESPACE:
		if (!InWhiteSpace(*ptr))
		{
		    found = True;
		}
		break;

	    case XmSELECT_WORD:
		if (!InWord(*ptr))
		{
		    found = True;
		}
		break;

	    case XmSELECT_LINE:
		if (*ptr == '\n')
		{
		    found = True;
		}
		break;

	    case XmSELECT_PARAGRAPH:
		if (*ptr == '\n')
		{
		    found = True;
		}
		break;

	    default:
		found = True;
		break;
	    }

	    if (found)
	    {
#ifdef VERBOSE_XdbDebug
		DEBUGOUT(XdbDebug(__FILE__, (Widget)d->widgets[0],
				  "Scan(%d)=>%d\n", ipos, pos));
#endif
		return pos;
	    }

	    pos--;
	    count--;
	}
    }
    else
    {				/* dir == XmsdRight */
	while (count > 0 && pos < d->length)
	{
	    ptr = StrPtr(d, pos);
	    switch (type)
	    {
	    case XmSELECT_WHITESPACE:
		if (!InWhiteSpace(*ptr))
		{
		    found = True;
		}
		break;

	    case XmSELECT_WORD:
		if (!InWord(*ptr))
		{
		    found = True;
		}
		break;

	    case XmSELECT_LINE:
		if (*ptr == '\n')
		{
		    found = True;
		}
		break;

	    case XmSELECT_PARAGRAPH:
		if (*ptr == '\n')
		{
		    found = True;
		}
		break;

	    default:
		found = True;
		break;
	    }

	    if (found)
	    {
#ifdef VERBOSE_XdbDebug
		DEBUGOUT(XdbDebug(__FILE__, (Widget)d->widgets[0],
				  "Scan(%d)=>%d\n", ipos, pos));
#endif
		return pos;
	    }

	    pos++;
	    count--;
	}
    }

#ifdef VERBOSE_XdbDebug
    DEBUGOUT(XdbDebug(__FILE__, (Widget)d->widgets[0],
		      "Scan(%d)=>%d\n", ipos, pos));
#endif
    return pos;
}

static Boolean
GetSelection(XmTextSource source, XmTextPosition *left,
	     XmTextPosition *right)
{
    *left = source->data->left;
    *right = source->data->right;
    if (source->data->hasselection)
    {
	/* rws 2 May 1998
	   In ml the Insert Text button in the Reply window calls
	   XmTextGetSelectionPosition and ignores the return value.  Instead
	   if compares the value of left and right to determine if anything
	   is selected.  Therefore always setting left and right makes it 
	   happy.  If we do not have the selection left == right anyway, so
	   everyone should be happy.
	*left = source->data->left;
	*right = source->data->right;
	*/
	return True;
    }

    return False;
}

/*
 * This gets called from Xt to get a copy of whatever's selected in our text
 * widget. We're delivering it all in one go; according to Asente & Swick
 * Xt will break it up for us if the data delivered is too big.
 *
 * FIX ME is it wise to do this for e.g. a 1MB selection ?
 */
static Boolean
_XmTextConvertSelection(Widget w, Atom *selection, Atom *target, Atom *type,
			XtPointer *value, unsigned long *length, int *format)
{
    XmSourceData d = Text_Source(w)->data;

    DEBUGOUT(XdbDebug(__FILE__, w, "_XmTextConvertSelection\n"));

    if (*target == XA_STRING && d->hasselection)
    {
	*length = (long)(d->right - d->left);
#if 0
	/* rws 12 Dec 1997
	   I think we need to make a copy of the value here. In ml if you
	   select part of a message and try to paste it into another window
	   you get a segfault in free(). It would appear that Xt is free'ing
	   the contents of value.
	 */
	*value = &d->ptr[d->left];
#else
	*value = XtMalloc(d->right - d->left + 1);
	strncpy(*value, &d->ptr[d->left], d->right - d->left);
#endif
	*type = XA_STRING;
	*format = 8;
	return True;
    }

    return False;
}

/*
 * Another widget has taken the selection that we used to own.
 */
static void
_XmTextLoseSelection(Widget w, Atom *selection)
{
    if (*selection == XA_PRIMARY)
    {
	DEBUGOUT(XdbDebug(__FILE__, w, "_XmTextLoseSelection(PRIMARY)\n"));

	XmTextSetHighlight(w, 0, XmTextGetLastPosition(w),
			   XmHIGHLIGHT_NORMAL);
    }
    else
    {
	DEBUGOUT(XdbDebug(__FILE__, w, "_XmTextLoseSelection(???)\n"));
	/* ??? */
    }
}

/*
 * SetSelection : indicate that we have the selection.
 *
 * Actions :
 *      - indicate it in the source
 *      - deal with Xt Selections (XtOwnSelection etc.)
 *      - call callback(s) (LoseSelection,GainSelection)
 *      - tell the widgets that display us about this (highlight)
 */
static void
SetSelection(XmTextSource source, XmTextPosition left,
	     XmTextPosition right, Time time)
{
    XmSourceData d = source->data;
    int i;
    Boolean gain = False;

    DEBUGOUT(XdbDebug(__FILE__, NULL, "XmTextStrSource SetSelection %d %d\n",
		      left, right));

    if (left >= right)
    {				/* No decent selection */
	source->data->hasselection = False;
	source->data->left = left;
	source->data->right = right;
	source->data->prim_time = time;

	XtDisownSelection((Widget)d->widgets[0], XA_PRIMARY, time);

	for (i = 0; i < d->numwidgets; i++)
	{
	    XmTextSetHighlight((Widget)d->widgets[i],
			       0, XmTextGetLastPosition((Widget)d->widgets[0]),
			       XmHIGHLIGHT_NORMAL);
	}

	return;
    }

    /* Remember whether we used to have the selection */
    gain = (source->data->hasselection == False);

    /* We have a decent selection; indicate in memory */
    source->data->hasselection = True;
    source->data->left = left;
    source->data->right = right;
    source->data->prim_time = time;

    /* Xt, callbacks */
    DEBUGOUT(XdbDebug(__FILE__, (Widget)d->widgets[0],
		      "XtOwnSelection(_, XA_PRIMARY, ...)\n"));

    if (!XtOwnSelection((Widget)d->widgets[0], XA_PRIMARY, time,
			_XmTextConvertSelection, _XmTextLoseSelection, NULL))
    {
	gain = False;
    }

    if (gain)
    {
	XmAnyCallbackStruct cbs;

	cbs.reason = XmCR_GAIN_PRIMARY;
	cbs.event = NULL;	/* Have no information nested this deep */

	/* FIX ME : Only for one widget ? */
	XtCallCallbackList((Widget)source->data->widgets[0],
			   source->data->widgets[0]->text.gain_primary_callback,
			   (XtPointer)&cbs);

    }

    /* Widgets */
    for (i = 0; i < d->numwidgets; i++)
    {
	XmTextSetHighlight((Widget)d->widgets[i], left, right,
			   XmHIGHLIGHT_SELECTED);
    }
}

XmTextSourceRec sourceRec =
{
    /* _XmSourceDataRec      */ NULL,
    /* AddWidgetProc         */ AddWidget,
    /* CountLinesProc        */ CountLines,
    /* RemoveWidgetProc      */ RemoveWidget,
    /* ReadProc              */ ReadSource,
    /* ReplaceProc           */ Replace,
    /* ScanProc              */ Scan,
    /* GetSelectionProc      */ GetSelection,
    /* SetSelectionProc      */ SetSelection
};

XmTextSource
_XmStringSourceCreate(char *value, Boolean is_wchar)
{
    XmTextSource source;
    XmSourceData d;


    source = (XmTextSource)XtMalloc(sizeof(XmTextSourceRec));
    bcopy(&sourceRec, source, sizeof(XmTextSourceRec));

    d = (XmSourceData)XtMalloc(sizeof(XmSourceDataRec));
    d->source = source;
    d->widgets = NULL;
    d->numwidgets = 0;
    d->value = value;
    d->ptr = d->gap_start = d->gap_end = NULL;
    d->length = d->old_length = d->maxlength = d->maxallowed = 0;
    d->hasselection = False;

    source->data = d;

    return source;
}



/*
 * Quasi-Public functions -----------------------------------------------------
 */


void
_XmStringSourceDestroy(XmTextSource source)
{
    XmSourceData d = source->data;

    if (d->old_length > 0)
    {
	XtFree(d->value);
    }
    if (d->length > 0)
    {
	XtFree(d->ptr);
    }
    XtFree((char *)d);
    XtFree((char *)source);
}

char *
_XmStringSourceGetValue(XmTextSource source, Boolean want_wchar)
{
    XmSourceData d = source->data;
    char *txt;

    if (want_wchar)
    {
	DEBUGOUT(XdbDebug(__FILE__, NULL,
	   "_XmStringSourceGetValue: wide characters not implemented yet.\n"));

	return NULL;
    }

    txt = XtMalloc(d->length + 1);

    strncpy(txt, d->ptr, d->length);
    txt[d->length] = '\0';

    DEBUGOUT(XdbDebug(__FILE__, NULL,
		      "_XmStringSourceGetValue => %p '%s'\n", txt, txt));

    return txt;
}

void
_XmStringSourceSetValue(XmTextWidget w, char *value)
{
    XmTextSource source = Text_Source(w);
    XmSourceData d = source->data;
    int len;
    XmAnyCallbackStruct cbs;

    len = value ? strlen(value) : 0;
    d->length = 0;
    d->gap_start = d->ptr;
    d->gap_end = d->ptr;
    CheckSize(d, len);

    if (len)
    {
	Insert(d, 0, value, len);
    }
    else
    {
	/* This may not be necessary.
	   d->ptr may still contain something that confuses us later.
	   Get rid of it.
	 */
	if (d->length && d->ptr)
	    d->ptr[0] = '\0';
    }

    /* Need to call XmNvalueChangedCallback here; not possible a level lower. */
    if (Text_ValueChangedCallback(w))
    {
	cbs.reason = XmCR_VALUE_CHANGED;
	cbs.event = NULL;
	XtCallCallbacks((Widget)w, XmNvalueChangedCallback, &cbs);
    }
}

Boolean
_XmStringSourceHasSelection(XmTextSource source)
{
    return source->data->hasselection;
}

Boolean
_XmStringSourceGetEditable(XmTextSource source)
{
    return source->data->editable;
}

void
_XmStringSourceSetEditable(XmTextSource source, Boolean editable)
{
    XmSourceData d;

    d = source->data;
    d->editable = editable;
}

int
_XmStringSourceGetMaxLength(XmTextSource source)
{
    return source->data->maxlength;
}

void
_XmStringSourceSetMaxLength(XmTextSource source, int max)
{
    XmSourceData d;

    d = source->data;
    if (max > d->length)
    {
	XmTextBlockRec block;

	block.ptr = "";
	block.length = 0;
	block.format = XmFMT_8_BIT;

	/* FIX ME Why is the following line commented out ? */
/*     (*source->Replace)(source, NULL, NULL, max, d->length, &block, True); */
    }

    d->maxallowed = max;
}

char *
_XmStringSourceGetString(XmTextWidget w,
			 XmTextPosition from,
			 XmTextPosition to,
			 Boolean want_wchar)
{
#if 1
    _XmWarning((Widget)w, "_XmStringSourceGetString is not implemented yet.");

    return NULL;
#else
    if (want_wchar)
    {
	DEBUGOUT(XdbDebug(__FILE__, NULL,
	  "_XmStringSourceGetString: wide characters not implemented yet.\n"));

	return NULL;
    }
    return NULL;		/* FIX ME */
#endif
}


Boolean
_XmTextFindStringBackwards(Widget w,
			   XmTextPosition start,
			   char *search_string,
			   XmTextPosition *position)
{
    _XmWarning(w, "_XmTextFindStringBackwards is not implemented yet.");

    return False;		/* FIX ME */
}

Boolean
_XmTextFindStringForwards(Widget w,
			  XmTextPosition start,
			  char *search_string,
			  XmTextPosition *position)
{
    return False;		/* FIX ME */
}

Boolean
_XmStringSourceFindString(Widget w,
			  XmTextPosition start,
			  char *string,
			  XmTextPosition *position)
{
    _XmWarning(w, "_XmStringSourceFindString is not implemented yet.");

    return False;		/* FIX ME */
}

void
_XmStringSourceSetGappedBuffer(XmSourceData data, XmTextPosition position)
{
    _XmWarning(NULL, "_XmStringSourceSetGappedBuffer is not implemented yet.");

    /* FIX ME */
}

Boolean
_XmTextModifyVerify(XmTextWidget initiator,
		    XEvent *event,
		    XmTextPosition *start,
		    XmTextPosition *end,
		    XmTextPosition *cursorPos,
		    XmTextBlock block,
		    XmTextBlock newblock,
		    Boolean *freeBlock)
{
    _XmWarning((Widget)initiator, "_XmTextModifyVerify is not implemented yet.");
    return False;		/* FIX ME */
}
