Blob Blame History Raw
/*
 * Motif
 *
 * Copyright (c) 1987-2012, The Open Group. All rights reserved.
 *
 * These libraries and programs are free software; you can
 * redistribute them and/or modify them under the terms of the GNU
 * Lesser General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * These libraries and programs are distributed in the hope that
 * they will be useful, but WITHOUT ANY WARRANTY; without even the
 * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with these librararies and programs; if not, write
 * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
 * Floor, Boston, MA 02110-1301 USA
 * 
 */
/*
 * HISTORY
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif


#ifdef REV_INFO
#ifndef lint
static char rcsid[] = "$TOG: XmStringFunc.c /main/9 1999/10/14 11:20:50 mgreess $"
#endif
#endif

#include <Xm/XmosP.h>

#include "XmI.h"
#include "XmStringI.h"
#include "XmRenderTI.h"
#include "XmTabListI.h"

/********    Static Function Declarations    ********/

static void new_line(_XmString string) ;

/********    End Static Function Declarations    ********/


XmStringComponentType
XmStringPeekNextTriple(XmStringContext context)
{
  unsigned int len;
  XtPointer    val;

  return XmeStringGetComponent((_XmStringContext) context, False, False, &len, &val);
}

Boolean 
XmStringHasSubstring(
        XmString string,
        XmString substring )
{
  _XmStringContextRec stack_context;
  char               *text;
  char               *subtext;
  short               char_count;
  short               subchar_count;
  Boolean             found;
  int                 i, j, max;
  _XmStringEntry      line, *entry, seg;
  XmStringComponentType type;
  unsigned int        len;
  XtPointer	      val;
    
  _XmProcessLock();
  if ((string == NULL) || (substring == NULL) || (XmStringEmpty(substring))) {
    _XmProcessUnlock();
    return (FALSE);
  }

  /*
   * The substring must be a one line/one segment string.
   */
  
  if (_XmStrEntryCountGet(substring) != 1) {
    _XmProcessUnlock();
    return (FALSE);
  }

  if (((entry = _XmStrEntryGet(substring)) != NULL) && 
      _XmEntrySegmentCountGet(entry[0]) > 1) {
    _XmProcessUnlock();
    return (FALSE);
  }

  /*
   * Get the text out of the substring.
   */

  if (_XmStrOptimized(substring))
    {
      subchar_count = (short)_XmStrByteCount(substring);
      subtext = (char *)_XmStrText(substring);
    }
  else if (_XmStrMultiple(substring))
    {
      line = entry[0];
      
      if (_XmEntryMultiple(line))
	{
	  seg = (_XmStringEntry)_XmEntrySegmentGet(line)[0];
	  
	  subchar_count = (short)_XmEntryByteCountGet(seg);
	  subtext = (char*) _XmEntryTextGet(seg);
	}
      else
	{
	  subchar_count = (short)_XmEntryByteCountGet(line);
	  subtext = (char*) _XmEntryTextGet(line);
	}
    }
  else { 		  /* Oops, some weird string! */
    _XmProcessUnlock();
    return (FALSE);
  }
  
    
  if ((subchar_count == 0) || (subtext == NULL)) {
    _XmProcessUnlock();
    return (FALSE);
  }

  /** Find a text component that matches. **/
  if (string) {
    _XmStringContextReInit(&stack_context, string);
    while ((type = XmeStringGetComponent(&stack_context, TRUE, FALSE, 
					 &len, &val)) != 
	   XmSTRING_COMPONENT_END)
      {
	switch(type) 
	  {
	  case XmSTRING_COMPONENT_TEXT:
	  case XmSTRING_COMPONENT_LOCALE_TEXT:
	  case XmSTRING_COMPONENT_WIDECHAR_TEXT:

	    char_count = len;
	    text = (char *)val;
	      
	    if (char_count >= subchar_count) {
	      max = char_count - subchar_count;
	      for (i = 0; i <= max; i++) {
		found = TRUE;
	      
		for (j = 0; j < subchar_count; j++) {
		  if (text[i+j] != subtext[j])  {
		    found = FALSE;
		    break;
		  }
		}
		if (found) {
		  _XmStringContextFree(&stack_context);
    		  _XmProcessUnlock();
		  return(TRUE);
		}
	      }
	    }
	    break;
	  default:
	    break;
	  }
      }
    _XmStringContextFree(&stack_context);
  }
  _XmProcessUnlock();
  return (FALSE);
}

XmStringTable
XmStringTableParseStringArray(XtPointer   *strings,
			      Cardinal     count,
			      XmStringTag  tag, 
			      XmTextType   type, 
			      XmParseTable parse, 
			      Cardinal     parse_count,
			      XtPointer    call_data)
{
  int	i;
  XmStringTable strs;

  _XmProcessLock();
  if ((strings == NULL) || (count == 0)) {
    _XmProcessUnlock();
    return(NULL);
  }
  
  strs = (XmStringTable)XtMalloc(count * sizeof(XmString));
  
  for (i = 0; i < count; i++)
    {
      strs[i] = XmStringParseText(strings[i], NULL, tag, type, 
				  parse, parse_count, call_data);
    }

  _XmProcessUnlock();
  return(strs);
}

XtPointer *
XmStringTableUnparse(XmStringTable     table,
		     Cardinal	       count,
		     XmStringTag       tag, 
		     XmTextType        tag_type, 
		     XmTextType        output_type, 
		     XmParseTable      parse, 
		     Cardinal          parse_count, 
		     XmParseModel      parse_model)
{
  XtPointer	*strs;
  int		i;
  
  _XmProcessLock();
  if ((table == NULL) || (count == 0)) {
    _XmProcessUnlock();
    return(NULL);
  }
  
  strs = (XtPointer *)XtMalloc(count * sizeof(XtPointer));
  
  for (i = 0; i < count; i++)
    strs[i] = XmStringUnparse(table[i], tag, tag_type, output_type,
			      parse, parse_count, parse_model);
  _XmProcessUnlock();
  return(strs);
}

XmString
XmStringTableToXmString(XmStringTable  table,
			Cardinal       count,
			XmString       break_comp)
{
  /* Note: this is a very expensive way to do this.  Fix for Beta */
  int		i;
  XmString	str = NULL, tmp1, tmp2;

  _XmProcessLock();
  tmp1 = NULL;

  for (i = 0; i < count; i++)
    {
      tmp2 = XmStringConcatAndFree(tmp1, XmStringCopy(table[i]));
      str = XmStringConcatAndFree(tmp2, XmStringCopy(break_comp));

      tmp1 = str;
    }
  
  _XmProcessUnlock();
  return(str);
}

XmString
XmStringPutRendition(XmString string,
		     XmStringTag rendition)
{
  /* Quick and dirty.  Fix for beta! */
  XmString	str, tmp1, tmp2;
  
  tmp1 = XmStringComponentCreate(XmSTRING_COMPONENT_RENDITION_BEGIN,
				 strlen(rendition), (XtPointer)rendition);
  tmp2 = XmStringConcatAndFree(tmp1, XmStringCopy(string));

  tmp1 = XmStringComponentCreate(XmSTRING_COMPONENT_RENDITION_END,
				 strlen(rendition), (XtPointer)rendition);
  str = XmStringConcatAndFree(tmp2, tmp1);
  
  return(str);
}

void 
XmParseMappingGetValues(XmParseMapping mapping,
			ArgList        arg_list,
			Cardinal       arg_count)
{
  register Cardinal i;
  register String arg_name;

  _XmProcessLock();
  /* Do a little error checking. */
  if (mapping == NULL) {
    _XmProcessUnlock();
    return;
  }

  /* Modify the specified values. */
  for (i = 0; i < arg_count; i++)
    {
      arg_name = arg_list[i].name;

      if ((arg_name == XmNpattern) ||
	  (strcmp(arg_name, XmNpattern) == 0))
	*((XtPointer*)arg_list[i].value) = mapping->pattern;
      else if ((arg_name == XmNpatternType) ||
	       (strcmp(arg_name, XmNpatternType) == 0))
	*((XmTextType*)arg_list[i].value) = mapping->pattern_type;
      else if ((arg_name == XmNsubstitute) ||
	       (strcmp(arg_name, XmNsubstitute) == 0))
	*((XmString*)arg_list[i].value) = XmStringCopy(mapping->substitute);
      else if ((arg_name == XmNinvokeParseProc) ||
	       (strcmp(arg_name, XmNinvokeParseProc) == 0))
	*((XmParseProc*)arg_list[i].value) = mapping->parse_proc;
      else if ((arg_name == XmNclientData) ||
	       (strcmp(arg_name, XmNclientData) == 0))
	*((XtPointer*)arg_list[i].value) = mapping->client_data;
      else if ((arg_name == XmNincludeStatus) ||
	       (strcmp(arg_name, XmNincludeStatus) == 0))
	*((XmIncludeStatus*)arg_list[i].value) = mapping->include_status;
    }
  _XmProcessUnlock();
}

void 
XmParseMappingFree(XmParseMapping mapping)
{
  _XmProcessLock();
  if (mapping != NULL)
    {
      /* Free copied data. */
      XmStringFree(mapping->substitute);

      /* Free the record. */
      XtFree((char*) mapping);
    }
  _XmProcessUnlock();
}

void 
XmParseTableFree(XmParseTable parse_table,
		 Cardinal     parse_count)
{
  /* Free each entry in the table. */
  Cardinal i;

  _XmProcessLock();
  for (i = 0; i < parse_count; i++)
    XmParseMappingFree(parse_table[i]);

  /* Free the table itself. */
  XtFree((char*) parse_table);
  _XmProcessUnlock();
}

/*
 * XmeGetNextCharacter: An XmParseProc to consume the triggering
 *	character and insert the following character.
 */
/*ARGSUSED*/
XmIncludeStatus
XmeGetNextCharacter(XtPointer     *in_out, 
		    XtPointer      text_end,
		    XmTextType     type, 
		    XmStringTag    tag, 
		    XmParseMapping entry, /* unused */
		    int		   pattern_length,
		    XmString      *str_include,
		    XtPointer      call_data) /* unused */
{
  char* ptr = (char*) *in_out;
  int len = 0;
  XmStringComponentType comp_type;
  assert(in_out != NULL);

  _XmProcessLock();
  /* Initialize the out parameters */
  *str_include = NULL;

  /* Consume the triggering characters. */
  ptr += pattern_length;

  /* Select the component type. */
  switch (type)
    {
    case XmCHARSET_TEXT:
      if ((tag != NULL) && (strcmp(XmFONTLIST_DEFAULT_TAG, tag) == 0))
	comp_type = XmSTRING_COMPONENT_LOCALE_TEXT;
      else
	comp_type = XmSTRING_COMPONENT_TEXT;
      if ((text_end == NULL) || (ptr < (char*) text_end))
#ifndef NO_MULTIBYTE
	len = mblen(ptr, MB_CUR_MAX);
#else
        len = *ptr ? 1 : 0;
#endif
      break;

    case XmMULTIBYTE_TEXT:
      /* In Motif 2.0 dynamic switching of locales isn't supported. */
      comp_type = XmSTRING_COMPONENT_LOCALE_TEXT;
      if ((text_end == NULL) || (ptr < (char*) text_end))
#ifndef NO_MULTIBYTE
	len = mblen(ptr, MB_CUR_MAX);
#else
        len = *ptr ? 1 : 0;
#endif
      break;

    case XmWIDECHAR_TEXT:
      comp_type = XmSTRING_COMPONENT_WIDECHAR_TEXT;
      if ((text_end == NULL) || (ptr < (char*) text_end))
	len = sizeof(wchar_t);
      break;

    default:
      comp_type = XmSTRING_COMPONENT_UNKNOWN;
      break;
    }

  /* Quit if mblen() failed or if type was unrecognized. */
  if ((len <= 0) || (comp_type == XmSTRING_COMPONENT_UNKNOWN))
    {
      *in_out = (XtPointer) ptr;
      _XmProcessUnlock();
      return XmINSERT;
    }

  /* Create a component containing the next character. */
  *str_include = XmStringComponentCreate(comp_type, len, ptr);
  ptr += len;
  *in_out = (XtPointer) ptr;
  
  _XmProcessUnlock();
  return XmINSERT;
}

static void 
new_line(
        _XmString string )
{
    int lc = _XmStrEntryCount(string);
    _XmStringEntry line;
    
    _XmStrImplicitLine(string) = TRUE;

    _XmStrEntry(string) = (_XmStringEntry *) 
      XtRealloc((char *) _XmStrEntry(string), 
		sizeof(_XmStringEntry) * (lc + 1));
    
    _XmEntryCreate(line, XmSTRING_ENTRY_ARRAY);
    _XmStrEntry(string)[lc] = line;

    _XmEntrySegmentCount(line) = 0;
    _XmEntrySegment(line) = NULL;

    _XmStrEntryCount(string)++;
}

static XmString
MakeStrFromSeg(XmStringContext start)
{
  _XmStringEntry	*line;
  _XmStringEntry	*segs, seg;
  _XmString		str;
  
  if (_XmStrContOpt(start)) {
    _XmStrContError(start) = TRUE;
    return(XmStringCopy(_XmStrContString(start)));
  } else {
    /* get segment */
    line = _XmStrEntry(_XmStrContString(start));
    
    /* Create XmString structure */
    _XmStrCreate(str, XmSTRING_MULTIPLE_ENTRY, 0);
    
    if (_XmEntryMultiple(line[_XmStrContCurrLine(start)])) {
      segs = (_XmStringEntry*)_XmEntrySegment(line[_XmStrContCurrLine(start)]);
      
      new_line(str);
    
      if (_XmStrContCurrSeg(start) < _XmEntrySegmentCount(line)) {
	seg = segs[_XmStrContCurrSeg(start)];
	
	_XmStringSegmentNew(str, 0, seg, True);
	
	_XmStrContCurrSeg(start)++;
	
	_XmStrContDir(start)     = _XmEntryDirectionGet(seg);
	_XmStrContTag(start)     = _XmEntryTag(seg);
	_XmStrContTagType(start) = (XmTextType) _XmEntryTextTypeGet(seg);
      } else {
	new_line(str);
	_XmStrContCurrSeg(start) = 0;
	_XmStrContCurrLine(start)++;
      }
    } else {
      seg = line[_XmStrContCurrLine(start)];
      _XmStringSegmentNew(str, 0, seg, True);
      
      _XmStrContCurrSeg(start) = 0;
      _XmStrContCurrLine(start)++;
      
      _XmStrContDir(start)     = _XmEntryDirectionGet(seg);
      _XmStrContTag(start)     = _XmEntryTag(seg);
      _XmStrContTagType(start) = (XmTextType) _XmEntryTextTypeGet(seg);
    }
    _XmStrContState(start)   = PUSH_STATE;
  }
  return(str);
}

static Boolean
LastSeg(XmStringContext start)
{
  _XmStringEntry	*line;
  
  if (_XmStrContOpt(start)) 
    {
      return(TRUE);
    } else {
      line = _XmStrEntry(_XmStrContString(start));
    
      if (_XmEntryMultiple(line[_XmStrContCurrLine(start)]))
	return(_XmStrContCurrSeg(start) == _XmEntrySegmentCount(line));
      else return(TRUE);
    }
}

static Boolean
ContextsMatch(XmStringContext a,
	      XmStringContext b)
{
  if ((_XmStrContCurrLine(a) == _XmStrContCurrLine(b)) &&
      (_XmStrContCurrSeg(a) == _XmStrContCurrSeg(b)) &&
      (_XmStrContState(a) == _XmStrContState(b)))

    if (((_XmStrContState(a) == BEGIN_REND_STATE) ||
	(_XmStrContState(a) == END_REND_STATE)))

      if (_XmStrContRendIndex(a) == _XmStrContRendIndex(b))
	return(TRUE);
      else return(FALSE);

    else return(TRUE);

  else return(FALSE);
}

static XmString
MakeStr(XmStringContext start,
	XmStringContext end)
{
  /* This is quick and dirty, need to be smarter about it before Beta. */
  XmStringComponentType	type;
  unsigned int		len;
  XtPointer		val;
  XmString		str;
  

  /* Next component over start until at segment break */
  str = NULL;
  
  while (_XmStrContState(start) != PUSH_STATE)
    {
      type = XmeStringGetComponent(start, TRUE, FALSE, &len, &val);

      if (ContextsMatch(start, end)) return(str);

      str = XmStringConcatAndFree(str,
				  XmStringComponentCreate(type, len, val));
    }
       
  /* Next segment over start incrementing until one segment before context */
  while ((_XmStrContCurrLine(start) < (_XmStrContCurrLine(end) - 1)) ||
	 ((_XmStrContCurrLine(start) == _XmStrContCurrLine(end)) &&
	  (_XmStrContCurrSeg(start) < _XmStrContCurrSeg(end))) ||
	 !LastSeg(start))
    {
      str = XmStringConcatAndFree(str, MakeStrFromSeg(start));
    }

  /* Next component over start until it matches context */
  type = XmeStringGetComponent(start, TRUE, FALSE, &len, &val);
  while (!ContextsMatch(start, end))
    {
      str = XmStringConcatAndFree(str, XmStringComponentCreate(type, len, val));
      type = XmeStringGetComponent(start, TRUE, FALSE, &len, &val);
  }
    
  return(str);
}

Cardinal
XmStringToXmStringTable(XmString string, 
			XmString break_component,
			XmStringTable *table)
{
  /* Note: this is a very expensive way to do this.  Fix for Beta */
  _XmStringContextRec	stack_context, stack_start;
  XmStringComponentType	type, b_type;
  unsigned int		len, b_len;
  XtPointer		val, b_val;
  int			i, count;
  
  _XmProcessLock();
  /* Get triple for first component of break_component */
  if (break_component)
    {
      _XmStringContextReInit(&stack_context, break_component);
      b_type = XmeStringGetComponent(&stack_context, TRUE, FALSE, 
				     &b_len, &b_val);
      _XmStringContextFree(&stack_context);
    }
  else
    /* Nothing to match against.  Return complete string. */
    {
      if (table != NULL)
	{
	  *table = (XmStringTable)XtMalloc(sizeof(XmString));
	  *table[0] = XmStringCopy(string);
	}
      _XmProcessUnlock();
      return(1);
    }

  /* Get context */
  if (!string)
    {
      if (table != NULL) *table = NULL;
      _XmProcessUnlock();
      return(0);
    }
  _XmStringContextReInit(&stack_context, string);
  
  /* Count number of entries for table */
  count = 0;
  while ((type = XmeStringGetComponent(&stack_context, TRUE, FALSE, 
				       &len, &val)) != 
	 XmSTRING_COMPONENT_END)
    {
      if ((type == b_type) && (len == b_len) &&
	  (memcmp(val, b_val, len) == 0))
	count++;
    }

  /* Allocate table and insert new strings */
  if (table != NULL)
    {
      *table = (XmStringTable)XtMalloc(count * sizeof(XmString));
      
      _XmStringContextReInit(&stack_context, string);
      _XmStringContextReInit(&stack_start, string);
      
      i = 0;
      
      while ((type = XmeStringGetComponent(&stack_context, TRUE, FALSE, 
					   &len, &val)) != 
	     XmSTRING_COMPONENT_END)
	{
	  if ((type == b_type) && (len == b_len) &&
	      (memcmp(val, b_val, len) == 0))
	    {
	      /* make XmString from start to end */
	      (*table)[i] = MakeStr(&stack_start, &stack_context);
	      i++;
	    }
	} 

      _XmStringContextFree(&stack_start);
    }
  _XmStringContextFree(&stack_context);

  _XmProcessUnlock();
  return(count);
}



XmTabList
XmStringTableProposeTablist(XmStringTable strings,
			    Cardinal num_strings,
			    Widget widget,
			    float pad_value,
			    XmOffsetModel offset_model)
{
  int			i, j;
  _XmStringContextRec	stack_ctx;
  XmTabList		tl;
  XmTab			tab, prev, start;
  float			width, val;
  unsigned char		units;
  Arg			args[1];
  int			n;
  _XmRenditionRec	scratch;
  XmRendition		rend;
  _XmRendition		tmp;
  XmRenderTable		rt;
  NextTabResult		ret_val;
  
  _XmProcessLock();
  if ((strings == NULL) || (num_strings == 0)) {
    _XmProcessUnlock();
    return ((XmTabList)NULL);
  }
  
  bzero((char*) &scratch, sizeof(_XmRenditionRec));
  tmp = &scratch;
  rend = &tmp;
  
  _XmRendDisplay(rend) = XtDisplayOfObject(widget);

  n = 0;
  XtSetArg(args[n], XmNrenderTable, &rt); n++;
  XtGetValues(widget, args, n);

  /* Work around weird bug with XtGetValues. */
  n = 0;
  XtSetArg(args[n], XmNunitType, &units); n++;
  XtGetValues(widget, args, n);

  if (rt == NULL) rt = XmeGetDefaultRenderTable(widget, XmTEXT_FONTLIST);

  tab = XmTabCreate(0.0, units, offset_model, XmALIGNMENT_BEGINNING, ".");

  tl = XmTabListInsertTabs(NULL, &tab, 1, 0);
  
  XmTabFree(tab);
  
  for (i = 0; i < num_strings; i++)
    {
      if (!strings[i])
	{
	  /* Clean up */
	  XmTabListFree(tl);
	  _XmProcessUnlock();
	  return((XmTabList)NULL);
	}
      _XmStringContextReInit(&stack_ctx, strings[i]);      

      tab = _XmTabLStart(tl);
      val = 0.0;
      
      /* Scan str for tabs, update tl if necessary. */
      j = 0;
      
      while ((ret_val = _XmStringGetNextTabWidth(&stack_ctx, widget, units, 
						 rt, &width, &rend)) != 
	     XmTAB_EOS)
	{
	  if (ret_val == XmTAB_NEWLINE) 
	    {
	      tab = _XmTabLStart(tl);
	      j = 0;
	      continue;
	    }
	  
	  val = width + pad_value;
	  
	  if (j >= _XmTabLCount(tl))
	    /* Need to add a tab */
	    {
	      tab = XmTabCreate(0.0, units, offset_model,
				XmALIGNMENT_BEGINNING, ".");
	      start = _XmTabLStart(tl);
	      prev = _XmTabPrev(start);
	      
	      _XmTabNext(prev) = tab;
	      _XmTabPrev(tab) = prev;
	      _XmTabNext(tab) = start;
	      _XmTabPrev(start) = tab;
	      _XmTabLCount(tl)++;
	    }
	  else if (j > 0)
	    {
	      tab = _XmTabNext(tab);
	    }

	  if (val > _XmTabValue(tab)) XmTabSetValue(tab, val);
	  else val = _XmTabValue(tab);
	  j++;
	}
      
      _XmStringContextFree(&stack_ctx);
    }
  
  if (offset_model == XmABSOLUTE)
    {
      start = _XmTabLStart(tl);
      val = _XmTabValue(start);
      
      for (tab = _XmTabNext(start); tab != start; tab = _XmTabNext(tab))
	{
	  val += _XmTabValue(tab);
	  XmTabSetValue(tab, val);
	}
    }
  
  _XmProcessUnlock();
  return(tl);
}

/*
 * Helper function for XmTabList.c
 * This routine performs successive reads on an XmStringContext
 * and returns the width (in units of XmNunitType of widget) of
 * the text segments between the previous and next tab or end of line.  
 * It uses the XmNrenderTable from widget to calculate the width.  It 
 * returns XmTAB_EOS if the end of the string has been reached, XmTAB_NEWLINE
 * if the end of line is reached and XmTAB_NEXT if a tab is encountered.
 */
NextTabResult
_XmStringGetNextTabWidth(XmStringContext ctx,
			 Widget widget,
			 unsigned char units,
			 XmRenderTable rt,
			 float *width,
			 XmRendition *rend)
{
  float 	divisor;
  int		toType;			  /*  passed to XmConvertUnits */
  Dimension	w_sum, w_cur;
  
  if (_XmStrContError(ctx))
    {
      *width = 0.0;
      return(XmTAB_EOS);
    }
  
  w_sum = 0;
  *width = 0.0;

  /* Big units need to be converted to small ones. */
  toType = _XmConvertFactor(units, &divisor);
      
  /* Calculate the width to the next tab. */
  if (_XmStrContOpt(ctx))
    {
      _XmStrContError(ctx) = True;
      return(XmTAB_EOS);
    } 
  else 
    {
      _XmString 		str = _XmStrContString(ctx);
      _XmStringEntry 		line;
      int			line_count;
      _XmStringEntry 		seg;
      int			seg_count;
      _XmStringArraySegRec	array_seg;
      
      line_count = _XmStrLineCountGet(str);
      
      /* Keep checking lines and segments until we run out or hit a tab. */
      if (_XmStrContCurrLine(ctx) < line_count) 
	{
	  if (_XmStrImplicitLine(str))
	    {
	      line = _XmStrEntry(str)[_XmStrContCurrLine(ctx)];
	    }
	  else
	    {
	      _XmEntryType(&array_seg) = XmSTRING_ENTRY_ARRAY;
	      _XmEntrySegmentCount(&array_seg) = _XmStrEntryCount(str);
	      _XmEntrySegment(&array_seg) = (_XmStringNREntry *)_XmStrEntry(str);
	      line = (_XmStringEntry)&array_seg;
	    }
      
	  if (_XmEntryMultiple(line)) 
	    seg_count = _XmEntrySegmentCount(line);
	  else 
	    seg_count = 1;

	  if (seg_count == 0) {
	    /* Empty line. */
	    _XmStrContCurrLine(ctx)++;
	    *width = 0.0;
	    return(XmTAB_NEWLINE);
	  }

	  while (_XmStrContCurrSeg(ctx) < seg_count)
	    {
	      if (_XmEntryMultiple(line)) 
		seg = (_XmStringEntry)_XmEntrySegment(line)[_XmStrContCurrSeg(ctx)];
	      else
		seg = line;

	      w_cur = 0;
	
	      if (_XmStrContTabCount(ctx) < _XmEntryTabsGet(seg)) {
		_XmStrContTabCount(ctx)++;
		*width = (XmConvertUnits(widget, XmHORIZONTAL,
					 XmPIXELS, w_sum, toType) / divisor);
		return(XmTAB_NEXT);
	      }

	      (void)_XmStringSegmentExtents(seg, rt, rend, NULL,
					    &w_cur, NULL, NULL, NULL);
	      w_sum += w_cur;
	
	      _XmStrContCurrSeg(ctx)++;
	      _XmStrContTabCount(ctx) = 0;
	    }

	  _XmStrContCurrLine(ctx)++;
	  _XmStrContCurrSeg(ctx) = 0;
	  _XmStrContTabCount(ctx) = 0;
	  
	  return(XmTAB_NEWLINE);
	}

      _XmStrContError(ctx) = True;
      return(XmTAB_EOS);
    }
}