/**
 *
 * $Header: /usr/local/hungryCVS/hungry/lesstif/lib/Xm/TextStrSo.c,v 1.13 1997/08/20 00:41:02 miers 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 char rcsid[] = "$Id: TextStrSo.c,v 1.13 1997/08/20 00:41:02 miers 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)
    {
	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)
{
    if (source->data->hasselection)
    {
	*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;
	*value = &d->ptr[d->left];
	*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);
    }

    /* 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 */
}
