/*
 * Copyright (c) Tony Bybell and Concept Engineering GmbH 2008-2016.
 *
 * 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 2
 * of the License, or (at your option) any later version.
 */

#include <config.h>
#include "globals.h"
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
#include <glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include "gtk23compat.h"
#include "analyzer.h"
#include "tree.h"
#include "symbol.h"
#include "vcd.h"
#include "lx2.h"
#include "busy.h"
#include "debug.h"
#include "hierpack.h"
#include "menu.h"
#include "tcl_helper.h"
#include "tcl_np.h"

#if !defined __MINGW32__
#include <sys/types.h>
#include <unistd.h>
#endif


#if (defined(__MACH__) && defined(__APPLE__))
#include <mach-o/dyld.h>
#endif

/*----------------------------------------------------------------------
 * tclBackslash -- Figure out how to handle a backslash sequence in tcl list.
 *
 * Results:
 *      The return value is the character that should be substituted
 *      in place of the backslash sequence that starts at src.  If
 *      readPtr isn't NULL then it is filled in with a count of the
 *      number of characters in the backslash sequence.
 *----------------------------------------------------------------------
 */
static char tclBackslash(const char* src, int* readPtr) {
    const char* p = src+1;
    char result;
    int count = 2;

    switch (*p) {
	/*
	 * Note: in the conversions below, use absolute values (e.g.,
	 * 0xa) rather than symbolic values (e.g. \n) that get converted
	 * by the compiler.  It's possible that compilers on some
	 * platforms will do the symbolic conversions differently, which
	 * could result in non-portable Tcl scripts.
	 */
	case 'a': result = 0x7; break;
	case 'b': result = 0x8; break;
	case 'f': result = 0xc; break;
	case 'n': result = 0xa; break;
	case 'r': result = 0xd; break;
	case 't': result = 0x9; break;
	case 'v': result = 0xb; break;
	case 'x':
	    if (isxdigit((int)(unsigned char)p[1])) {
		char* end;

		result = (char)strtoul(p+1, &end, 16);
		count = end - src;
	    } else {
		count = 2;
		result = 'x';
	    }
	    break;
	case '\n':
	    do {
		p++;
	    } while ((*p == ' ') || (*p == '\t'));
	    result = ' ';
	    count = p - src;
	    break;
	case 0:
	    result = '\\';
	    count = 1;
	    break;
	default:
	    /* Check for an octal number \oo?o? */
	    if (isdigit((int)(unsigned char)*p)) {
		result = *p - '0';
		p++;
		if (!isdigit((int)(unsigned char)*p)) break;

		count = 3;
		result = (result << 3) + (*p - '0');
		p++;
		if (!isdigit((int)(unsigned char)*p)) break;

		count = 4;
		result = (result << 3) + (*p - '0');
		break;
	    }
	    result = *p;
	    count = 2;
	    break;
    }
    if (readPtr) *readPtr = count;
    return result;
}


/*----------------------------------------------------------------------
 * tclFindElement -- locate the first (or next) element in the list.
 *
 * Results:
 *  The return value is normally 1 (OK), which means that the
 *  element was successfully located.  If 0 (ERROR) is returned
 *  it means that list didn't have proper tcl list structure
 *  (no detailed error message).
 *
 *  If 1 (OK) is returned, then *elementPtr will be set to point
 *  to the first element of list, and *nextPtr will be set to point
 *  to the character just after any white space following the last
 *  character that's part of the element.  If this is the last argument
 *  in the list, then *nextPtr will point to the NULL character at the
 *  end of list.  If sizePtr is non-NULL, *sizePtr is filled in with
 *  the number of characters in the element.  If the element is in
 *  braces, then *elementPtr will point to the character after the
 *  opening brace and *sizePtr will not include either of the braces.
 *  If there isn't an element in the list, *sizePtr will be zero, and
 *  both *elementPtr and *termPtr will refer to the null character at
 *  the end of list.  Note:  this procedure does NOT collapse backslash
 *  sequences.
 *----------------------------------------------------------------------
 */
static int tclFindElement(const char* list, const char** elementPtr,
			  const char** nextPtr, int* sizePtr, int *bracePtr) {
    const char *p;
    int openBraces = 0;
    int inQuotes = 0;
    int size;

    /*
     * Skim off leading white space and check for an opening brace or
     * quote.
     */
    while (isspace((int)(unsigned char)*list)) list++;

    if (*list == '{') {			/* } */
	openBraces = 1;
	list++;
    } else if (*list == '"') {
	inQuotes = 1;
	list++;
    }
    if (bracePtr) *bracePtr = openBraces;

    /*
     * Find the end of the element (either a space or a close brace or
     * the end of the string).
     */
    for (p=list; 1; p++) {
	switch (*p) {

	    /*
	     * Open brace: don't treat specially unless the element is
	     * in braces.  In this case, keep a nesting count.
	     */
	    case '{':
		if (openBraces) openBraces++;
		break;

	    /*
	     * Close brace: if element is in braces, keep nesting
	     * count and quit when the last close brace is seen.
	     */
	    case '}':
		if (openBraces == 1) {
		    size = p - list;
		    p++;
		    if (isspace((int)(unsigned char)*p) || (*p == 0)) goto done;

		    /* list element in braces followed by garbage instead of
		     * space
		     */
		    return 0/*ERROR*/;
		} else if (openBraces) {
		    openBraces--;
		}
		break;

	    /*
	     * Backslash:  skip over everything up to the end of the
	     * backslash sequence.
	     */
	    case '\\': {
		int siz;

		tclBackslash(p, &siz);
		p += siz - 1;
		break;
	    }

	    /*
	     * Space: ignore if element is in braces or quotes;  otherwise
	     * terminate element.
	     */
	    case ' ':
	    case '\f':
	    case '\n':
	    case '\r':
	    case '\t':
	    case '\v':
		if ((openBraces == 0) && !inQuotes) {
		    size = p - list;
		    goto done;
		}
		break;

	    /*
	     * Double-quote:  if element is in quotes then terminate it.
	     */
	    case '"':
		if (inQuotes) {
		    size = p-list;
		    p++;
		    if (isspace((int)(unsigned char)*p) || (*p == 0)) goto done;

		    /* list element in quotes followed by garbage instead of
		     * space
		     */
		    return 0/*ERROR*/;
		}
		break;

	    /*
	     * End of list:  terminate element.
	     */
	    case 0:
		if (openBraces || inQuotes) {
		    /* unmatched open brace or quote in list */
		    return 0/*ERROR*/;
		}
		size = p - list;
		goto done;
	}
    }

  done:
    while (isspace((int)(unsigned char)*p)) p++;

    *elementPtr = list;
    *nextPtr = p;
    if (sizePtr) *sizePtr = size;
    return 1/*OK*/;
}


/*----------------------------------------------------------------------
 * tclCopyAndCollapse -- Copy a string and eliminate any backslashes that
 *			 aren't in braces.
 *
 * Results:
 *  Count characters get copied from src to dst. Along the way, if
 *  backslash sequences are found outside braces, the backslashes are
 *  eliminated in the copy. After scanning count chars from source, a
 *  null character is placed at the end of dst.
 *----------------------------------------------------------------------
 */
static void tclCopyAndCollapse(int count, const char *src, char *dst)
{
    char c;
    int numRead;

    for (c = *src; count > 0; src++, c = *src, count--) {
	if (c == '\\') {
	    *dst = tclBackslash(src, &numRead);
	    dst++;
	    src += numRead-1;
	    count -= numRead-1;
	} else {
	    *dst = c;
	    dst++;
	}
    }
    *dst = 0;
}


/* ----------------------------------------------------------------------------
 * zSplitTclList - Splits a list up into its constituent fields.
 *
 * Results:
 *	The return value is a pointer to a list of element points,
 *	which means that the list was successfully split up.
 *	If NULL is returned, it means that "list" didn't have proper tcl list
 *	structure (we don't return an error message about the details).
 *
 *	This procedure does allocate a single memory block
 *	by calling malloc to store both, the the argv pointer array and
 *	the extracted list elements.  The returned element
 *	pointer array must be freed by calling free_2().
 *
 *	*argcPtr will get filled in with the number of valid elements
 *	in the array.  Note: *argcPtr is only modified if the procedure
 *	returns not NULL.
 * ----------------------------------------------------------------------------
 */
char** zSplitTclList(const char* list, int* argcPtr) {
    char** argv;
    const char* l;
    char* p;
    int size, i, ok, elSize, brace;
    const char *element;

    /*
     * Figure out how much space to allocate.  There must be enough
     * space for both the array of pointers and also for a copy of
     * the list.  To estimate the number of pointers needed, count
     * the number of space characters in the list.
     */
    for (size = 1, l = list; *l != 0; l++) {
	if (isspace((int)(unsigned char)*l)) size++;
    }
    size++;				/* Leave space for final NULL */

    i = (size * sizeof(char*)) + (l - list) + 1;
    argv = malloc_2(i);

    for (i = 0, p = (char*) (argv+size); *list != 0;  i++) {
	ok = tclFindElement(list, &element, &list, &elSize, &brace);

	if (!ok) {
	    free_2(argv);
	    return NULL;
	}
	if (*element == 0) break;

	if (i >= size) {
	    free_2(argv);
	    /* internal error in zSplitTclList */
	    return NULL;
	}
	argv[i] = p;
	if (brace) {
	    strncpy(p, element, elSize);
	    p += elSize;
	    *p = 0;
	    p++;
	} else {
	    tclCopyAndCollapse(elSize, element, p);
	    p += elSize+1;
	}
    }
    argv[i] = NULL;
    *argcPtr = i;
    return argv;
}


/*----------------------------------------------------------------------
 * tclScanElement -- scan a tcl list string to see what needs to be done.
 *
 *  This procedure is a companion procedure to tclConvertElement.
 *
 * Results:
 *  The return value is an overestimate of the number of characters
 *  that will be needed by tclConvertElement to produce a valid
 *  list element from string.  The word at *flagPtr is filled in
 *  with a value needed by tclConvertElement when doing the actual
 *  conversion.
 *
 *
 * This procedure and tclConvertElement together do two things:
 *
 * 1. They produce a proper list, one that will yield back the
 * argument strings when evaluated or when disassembled with
 * zSplitTclList.  This is the most important thing.
 *
 * 2. They try to produce legible output, which means minimizing the
 * use of backslashes (using braces instead).  However, there are
 * some situations where backslashes must be used (e.g. an element
 * like "{abc": the leading brace will have to be backslashed.  For
 * each element, one of three things must be done:
 *
 * (a) Use the element as-is (it doesn't contain anything special
 * characters).  This is the most desirable option.
 *
 * (b) Enclose the element in braces, but leave the contents alone.
 * This happens if the element contains embedded space, or if it
 * contains characters with special interpretation ($, [, ;, or \),
 * or if it starts with a brace or double-quote, or if there are
 * no characters in the element.
 *
 * (c) Don't enclose the element in braces, but add backslashes to
 * prevent special interpretation of special characters.  This is a
 * last resort used when the argument would normally fall under case
 * (b) but contains unmatched braces.  It also occurs if the last
 * character of the argument is a backslash or if the element contains
 * a backslash followed by newline.
 *
 * The procedure figures out how many bytes will be needed to store
 * the result (actually, it overestimates).  It also collects information
 * about the element in the form of a flags word.
 *----------------------------------------------------------------------
 */
#define DONT_USE_BRACES  1
#define USE_BRACES       2
#define BRACES_UNMATCHED 4

static int tclScanElement(const char* string, int* flagPtr) {
    const char *p;
    int nestingLevel = 0;
    int flags = 0;

    if (string == NULL) string = "";

    p = string;
    if ((*p == '{') || (*p == '"') || (*p == 0)) {	/* } */
	flags |= USE_BRACES;
    }
    for ( ; *p != 0; p++) {
	switch (*p) {
	    case '{':
		nestingLevel++;
		break;
	    case '}':
		nestingLevel--;
		if (nestingLevel < 0) {
		    flags |= DONT_USE_BRACES | BRACES_UNMATCHED;
		}
		break;
	    case '[':
	    case '$':
	    case ';':
	    case ' ':
	    case '\f':
	    case '\r':
	    case '\t':
	    case '\v':
		flags |= USE_BRACES;
		break;
	    case '\n':		/* lld: dont want line breaks inside a list */
		flags |= DONT_USE_BRACES;
		break;
	    case '\\':
		if ((p[1] == 0) || (p[1] == '\n')) {
		    flags = DONT_USE_BRACES | BRACES_UNMATCHED;
		} else {
		    int size;

		    tclBackslash(p, &size);
		    p += size-1;
		    flags |= USE_BRACES;
		}
		break;
	}
    }
    if (nestingLevel != 0) {
	flags = DONT_USE_BRACES | BRACES_UNMATCHED;
    }
    *flagPtr = flags;

    /* Allow enough space to backslash every character plus leave
     * two spaces for braces.
     */
    return 2*(p-string) + 2;
}


/*----------------------------------------------------------------------
 *
 * tclConvertElement - convert a string into a list element
 *
 *  This is a companion procedure to tclScanElement.  Given the
 *  information produced by tclScanElement, this procedure converts
 *  a string to a list element equal to that string.
 *
 *  See the comment block at tclScanElement above for details of how this
 *  works.
 *
 * Results:
 *  Information is copied to *dst in the form of a list element
 *  identical to src (i.e. if zSplitTclList is applied to dst it
 *  will produce a string identical to src).  The return value is
 *  a count of the number of characters copied (not including the
 *  terminating NULL character).
 *----------------------------------------------------------------------
 */
static int tclConvertElement(const char* src, char* dst, int flags)
{
    char *p = dst;

    if ((src == NULL) || (*src == 0)) {
	p[0] = '{';
	p[1] = '}';
	p[2] = 0;
	return 2;
    }
    if ((flags & USE_BRACES) && !(flags & DONT_USE_BRACES)) {
	*p = '{';
	p++;
	for ( ; *src != 0; src++, p++) {
	    *p = *src;
	}
	*p = '}';
	p++;
    } else {
	if (*src == '{') {		/* } */
	    /* Can't have a leading brace unless the whole element is
	     * enclosed in braces.  Add a backslash before the brace.
	     * Furthermore, this may destroy the balance between open
	     * and close braces, so set BRACES_UNMATCHED.
	     */
	    p[0] = '\\';
	    p[1] = '{';			/* } */
	    p += 2;
	    src++;
	    flags |= BRACES_UNMATCHED;
	}
	for (; *src != 0 ; src++) {
	    switch (*src) {
		case ']':
		case '[':
		case '$':
		case ';':
		case ' ':
		case '\\':
		case '"':
		    *p = '\\';
		    p++;
		    break;
		case '{':
		case '}':
		    /* It may not seem necessary to backslash braces, but
		     * it is.  The reason for this is that the resulting
		     * list element may actually be an element of a sub-list
		     * enclosed in braces, so there may be a brace mismatch
		     * if the braces aren't backslashed.
		     */
		    if (flags & BRACES_UNMATCHED) {
			*p = '\\';
			p++;
		    }
		    break;
		case '\f':
		    *p = '\\';
		    p++;
		    *p = 'f';
		    p++;
		    continue;
		case '\n':
		    *p = '\\';
		    p++;
		    *p = 'n';
		    p++;
		    continue;
		case '\r':
		    *p = '\\';
		    p++;
		    *p = 'r';
		    p++;
		    continue;
		case '\t':
		    *p = '\\';
		    p++;
		    *p = 't';
		    p++;
		    continue;
		case '\v':
		    *p = '\\';
		    p++;
		    *p = 'v';
		    p++;
		    continue;
	    }
	    *p = *src;
	    p++;
	}
    }
    *p = '\0';
    return p-dst;
}


/* ============================================================================
 * zMergeTclList - Creates a tcl list from a set of element strings.
 *
 *	Given a collection of strings, merge them together into a
 *	single string that has proper Tcl list structured (i.e.
 *	zSplitTclList may be used to retrieve strings equal to the
 *	original elements).
 *	The merged list is stored in dynamically-allocated memory.
 *
 * Results:
 *      The return value is the address of a dynamically-allocated string.
 * ============================================================================
 */
char* zMergeTclList(int argc, const char** argv) {
    enum  {LOCAL_SIZE = 20};
    int   localFlags[LOCAL_SIZE];
    int*  flagPtr;
    int   numChars;
    int   i;
    char* result;
    char* dst;

    /* Pass 1: estimate space, gather flags */
    if (argc <= LOCAL_SIZE) flagPtr = localFlags;
    else                    flagPtr = malloc_2(argc*sizeof(int));
    numChars = 1;

    for (i=0; i<argc; i++) numChars += tclScanElement(argv[i], &flagPtr[i]) + 1;

    result = malloc_2(numChars);

    /* Pass two: copy into the result area */
    dst = result;
    for (i = 0; i < argc; i++) {
	numChars = tclConvertElement(argv[i], dst, flagPtr[i]);
	dst += numChars;
	*dst = ' ';
	dst++;
    }
    if (dst == result) *dst = 0;
    else                dst[-1] = 0;

    if (flagPtr != localFlags) free_2(flagPtr);
    return result;
}


/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */
/* XXX functions for data coming into gtkwave XXX */
/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */


/* ----------------------------------------------------------------------------
 * determine_trace_from_y - finds trace under the marker
 *
 * Results:
 *      Returns Trptr which corresponds to the mouse pointer y position.
 * ----------------------------------------------------------------------------
 */

static Trptr determine_trace_from_y(void)
{
Trptr t;
int trwhich, trtarget;
GdkModifierType state;
gdouble x, y;
gint xi, yi;


if(GLOBALS->dnd_tgt_on_signalarea_treesearch_gtk2_c_1)
	{
	WAVE_GDK_GET_POINTER(gtk_widget_get_window(GLOBALS->signalarea), &x, &y, &xi, &yi, &state);
	WAVE_GDK_GET_POINTER_COPY;

        GtkAllocation allocation;
        gtk_widget_get_allocation(GLOBALS->signalarea, &allocation);

	if((x<0)||(y<0)||(x>=allocation.width)||(y>=allocation.height)) return(NULL);
	}
else
if(GLOBALS->dnd_tgt_on_wavearea_treesearch_gtk2_c_1)
	{
	WAVE_GDK_GET_POINTER(gtk_widget_get_window(GLOBALS->wavearea), &x, &y, &xi, &yi, &state);
	WAVE_GDK_GET_POINTER_COPY;

        GtkAllocation allocation;
        gtk_widget_get_allocation(GLOBALS->wavearea, &allocation);


	if((x<0)||(y<0)||(x>=allocation.width)||(y>=allocation.height)) return(NULL);
	}
else
	{
	return(NULL);
	}

if((t=GLOBALS->traces.first))
        {
        while(t)
                {
                t->flags&=~TR_HIGHLIGHT;
                t=t->t_next;
                }
        signalarea_configure_event(GLOBALS->signalarea, NULL);
        wavearea_configure_event(GLOBALS->wavearea, NULL);
	}

trtarget = ((int)y / (int)GLOBALS->fontheight) - 2;
if(trtarget < 0)
	{
	return(NULL);
	}
	else
	{
	t=GLOBALS->topmost_trace;
	}

trwhich=0;
while(t)
	{
        if((trwhich<trtarget)&&(GiveNextTrace(t)))
        	{
                trwhich++;
                t=GiveNextTrace(t);
                }
                else
                {
                break;
                }
	}

return(t);
}


/* ----------------------------------------------------------------------------
 * check_gtkwave_directive_from_tcl_list - parses tcl list for any gtkwave
 * directives
 *
 * Results:
 *      Returns decomposed list or NULL if not applicable.  Number of items
 *      is passed back through pointer in l.
 * ----------------------------------------------------------------------------
 */

static char **check_gtkwave_directive_from_tcl_list(char *s, int *l)
{
char** elem = NULL;

elem = zSplitTclList(s, l);

if(elem)
        {
        if((!elem[0]) || strcmp("gtkwave", elem[0])) /* scan-build: elem[0] is NULL */
		{
		free_2(elem);
		elem = NULL;
		}
	}
return(elem);
}


/* ----------------------------------------------------------------------------
 * make_net_name_from_tcl_list - creates gtkwave-style name from tcl list entry
 *
 * Results:
 *      Returns suitable name or NULL if not applicable.
 * ----------------------------------------------------------------------------
 */

static char *make_net_name_from_tcl_list(char *s, char **unescaped_str)
{
char *s_new = NULL;
char *s_new2 = NULL;
int len;
int i,l;
char** elem = NULL;
char *pnt, *pnt2;
int esc = 0;

if(!s)
	{
	return(NULL);
	}

elem = zSplitTclList(s, &l);

if(elem)
	{
	if ((elem[0]) &&  ((!strcmp("net", elem[0])) || (!strcmp("netBus", elem[0])))) /* scan-build: elem[0] can be NULL */
		{
		char delim_str[2];

		delim_str[0] = GLOBALS->hier_delimeter;
		delim_str[1] = 0;

		len = 0;
		for(i=1;i<l;i++)
			{
			len += (strlen(elem[i])) + 1;
			}

		s_new = calloc_2(1, len);
		for(i=1;i<l;i++)
			{
			strcat(s_new, elem[i]);
			if(i!=(l-1)) strcat(s_new, delim_str);
			}

		free_2(elem);
		}
		else
		{
		free_2(elem);
		return(NULL);
		}

	pnt = s_new;
	while(*pnt)
		{
		if( (!isalnum((int)(unsigned char)*pnt)) && (!isspace((int)(unsigned char)*pnt)) && (*pnt != GLOBALS->hier_delimeter) )
			{
			esc++;
			}
		pnt++;
		}

	if(esc)
		{
		s_new2 = calloc_2(1, len + esc);
		pnt = s_new;
		pnt2 = s_new2;
		while(*pnt)
			{
			if( (!isalnum((int)(unsigned char)*pnt)) && (!isspace((int)(unsigned char)*pnt)) && (*pnt != GLOBALS->hier_delimeter) )
				{
				*(pnt2++) = '\\';
				}

			*(pnt2++) = *(pnt++);
			}
		*unescaped_str = s_new;
		/* free_2(s_new); */
		s_new = s_new2;
		}
		else
		{
		*unescaped_str = s_new;
		}
	}

return(s_new);
}


/* ----------------------------------------------------------------------------
 * process_tcl_list - given a tcl list, inserts traces into viewer
 *
 * Results:
 *      Inserts traces if found in dumpfile, returns number of traces inserted
 * ----------------------------------------------------------------------------
 */

void process_tcl_list_2(struct symbol *s, int which_msb, int which_lsb)
{
Trptr t;
nptr nexp;
int i;
TraceFlagsType default_flags = GLOBALS->default_flags;
bvptr v;

Trptr    buffer;            /* cut/copy buffer of traces */
Trptr    bufferlast;        /* last element of bufferchain */
int      buffercount;       /* number of traces in buffer */

GLOBALS->default_flags = TR_HIGHLIGHT;

buffer = GLOBALS->traces.buffer; /* copy cut buffer to make re-entrant */
bufferlast = GLOBALS->traces.bufferlast;
buffercount = GLOBALS->traces.buffercount;

GLOBALS->traces.buffer = NULL;
GLOBALS->traces.bufferlast = NULL;
GLOBALS->traces.buffercount = 0;

t=GLOBALS->traces.first;
while(t)
        {
        if(t->flags&TR_HIGHLIGHT)	/* copy TR_HIGHLIGHT->TR_RSVD and clear TR_HIGHLIGHT */
		{
	        t->flags&=(~TR_HIGHLIGHT);
		t->flags|=TR_RSVD;
		}
		else
		{
	        t->flags&=(~TR_RSVD);
		}

        t=t->t_next;
        }


if(which_msb <= which_lsb)
	{
	for(i=which_msb;i<=which_lsb;i++)
		{
	        nexp = ExtractNodeSingleBit(s->n, i);
	        if(nexp)  
	 		{
	                AddNode(nexp, NULL);
	                }
	                else
	                {
	                AddNodeUnroll(s->n, NULL);
	                }
		}
	}
	else
	{
	for(i=which_msb;i>=which_lsb;i--)
		{
	        nexp = ExtractNodeSingleBit(s->n, i);
	        if(nexp)  
	 		{
	                AddNode(nexp, NULL);
	                }
	                else
	                {
	                AddNodeUnroll(s->n, NULL);
	                }
		}
	}

v = combine_traces(1, NULL); /* down */
  if (v)
    	{
      	AddVector(v, NULL);
      	free_2(v->bits->name);
      	v->bits->name=NULL;
                
      	t = GLOBALS->traces.last;
                
      	RemoveTrace(t, 0);

      	/* t is now the composite signal trace */
                  
	create_group("unused_0", t);
	CloseTrace(t);
    	}

t=GLOBALS->traces.first;
while(t)
        {
	t->flags &= ~TR_HIGHLIGHT;

        if(t->flags&TR_RSVD) /* copy TR_RSVD->TR_HIGHLIGHT and clear TR_RSVD */
		{
	        t->flags|=TR_HIGHLIGHT;
		t->flags&=(~TR_RSVD);
		}

        t=t->t_next;
        }

GLOBALS->traces.buffer = buffer; /* restore cut buffer */
GLOBALS->traces.bufferlast = bufferlast;
GLOBALS->traces.buffercount = buffercount;

GLOBALS->default_flags = default_flags;
}


int process_tcl_list(char *sl, gboolean track_mouse_y)
{
char *s_new = NULL;
char *this_regex = "\\(\\[.*\\]\\)*$";
char *entry_suffixed;
int c, i, ii;
char **list;
char **s_new_list;
char **most_recent_lbrack_list;
char **most_recent_colon_list;
int *match_idx_list;
int *match_type_list;
Trptr t = NULL;
int found = 0;
int lbrack_adj;
int net_processing_is_off = 0;
int unesc_len;
int curr_srch_idx = 0;
char *unescaped_str = NULL;

if(!sl)
	{
	return(0);
	}

list = zSplitTclList(sl, &c);
if(!list)
	{
	return(0);
	}

read_save_helper_relative_init(NULL); /* should be passing canonicalized filter names here...so no need for relative processing */

s_new_list = calloc_2(c, sizeof(char *));
match_idx_list = calloc_2(c, sizeof(int *));
match_type_list = calloc_2(c, sizeof(int *));
most_recent_lbrack_list = calloc_2(c, sizeof(char *));
most_recent_colon_list = calloc_2(c, sizeof(char *));

GLOBALS->default_flags=TR_RJUSTIFY;
GLOBALS->default_fpshift=0;

GLOBALS->strace_current_window = 0; /* in case there are shadow traces; in reality this should never happen */

for(ii=0;ii<c;ii++)
	{
	s_new = make_net_name_from_tcl_list(list[ii], &unescaped_str);
	if(s_new)
		{
		if(net_processing_is_off) continue;
		}
	else
		{
		int ngl;
		char **gdirect = check_gtkwave_directive_from_tcl_list(list[ii], &ngl);
		if(gdirect)
			{
			switch(ngl)
				{
				case 3:
				 	if(!strcmp(gdirect[1], "PID"))
						{
						pid_t pid = atoi(gdirect[2]);
						if(pid == getpid())	/* block self-drags in XEmbed */
							{
							free_2(gdirect);
							goto cleanup;
							}
						}
					 else if(!strcmp(gdirect[1], "NET"))
						{
						net_processing_is_off = !strcmp(gdirect[2], "OFF");
						}
					 else if(!strcmp(gdirect[1], "SAVELIST"))
						{
						int is;
						for(is = 0; is < 4; is++)
							{
							char *pnt = gdirect[2];
							char *nxt_hd = pnt;

							if(is == 1)
								{
								if(found)
									{
							                if(GLOBALS->is_lx2)
										{
										lx2_import_masked();
										}

									if(track_mouse_y)
										{
										t = determine_trace_from_y();
										if(t)
											{
											t->flags |=  TR_HIGHLIGHT;
											}
										}

									memcpy(&GLOBALS->tcache_treesearch_gtk2_c_2,&GLOBALS->traces,sizeof(Traces));
									GLOBALS->traces.total=0;
									GLOBALS->traces.first=GLOBALS->traces.last=NULL;

									continue;
									}
									else
									{
									goto cleanup;
									}
								}
							else
							if(is==3)
								{
								goto paste_routine;
								}
							else /* (is == 0) or (is == 2) */
							for(;;)
								{
								if(*pnt == 0)
									{
									if(!(*nxt_hd))
										{
										break;
										}

									if((!is)&&(GLOBALS->is_lx2)) { parsewavline_lx2(nxt_hd, NULL, 0); found++; } else { parsewavline(nxt_hd, NULL, 0); }
									break;
									}
								else
								if(*pnt == '\n')
									{
									*pnt = 0;
									if((!is)&&(GLOBALS->is_lx2)) { parsewavline_lx2(nxt_hd, NULL, 0); found++; } else { parsewavline(nxt_hd, NULL, 0); }
									*pnt = '\n';
									nxt_hd = pnt+1;
									pnt++;
									}
								else
									{
									pnt++;
									}
								}
							}
						}
					 break;

				default: break;
				}

			free_2(gdirect);
			}

		continue;
		}
	s_new_list[ii] = s_new;

	lbrack_adj = 0;
	most_recent_lbrack_list[ii] = strrchr(s_new, '[');
	if((most_recent_lbrack_list[ii])&&(most_recent_lbrack_list[ii] != s_new))
		{
		char *chp = most_recent_lbrack_list[ii]-1;
		if(*chp == '\\')
			{
			most_recent_lbrack_list[ii] = chp;
			lbrack_adj = 1;
			}

		most_recent_colon_list[ii]=strchr(most_recent_lbrack_list[ii], ':');
		}

	unesc_len = strlen(unescaped_str);
	for(i=0;i<GLOBALS->numfacs;i++)
	        {
	        int was_packed = HIER_DEPACK_ALLOC;
	        char *hfacname = NULL;

       		hfacname = hier_decompress_flagged(GLOBALS->facs[curr_srch_idx]->name, &was_packed);

	        if(!strncmp(unescaped_str, hfacname, unesc_len))
	                {
			int hfacname_len = strlen(hfacname);
			if((unesc_len == hfacname_len) || ((hfacname_len > unesc_len) && (hfacname[unesc_len] == '[')))
				{
				found++;
				match_idx_list[ii] = curr_srch_idx;
				match_type_list[ii] = 1; /* match was on normal search */
			        if(was_packed) { free_2(hfacname); }
				if(s_new != unescaped_str) { free_2(unescaped_str); }
				goto import;
		                }
			}

		curr_srch_idx++;
		if(curr_srch_idx == GLOBALS->numfacs) curr_srch_idx = 0; /* optimization for rtlbrowse as names should be in order */

	        if(was_packed) { free_2(hfacname); }
	        }

	if(s_new != unescaped_str) { free_2(unescaped_str); }

	entry_suffixed=wave_alloca(2+strlen(s_new)+strlen(this_regex)+1);
	*entry_suffixed=0x00;
	strcpy(entry_suffixed, "\\<");
	strcat(entry_suffixed,s_new);
	strcat(entry_suffixed,this_regex);

	wave_regex_compile(entry_suffixed, WAVE_REGEX_DND);
	for(i=0;i<GLOBALS->numfacs;i++)
	        {
	        int was_packed = HIER_DEPACK_ALLOC;
	        char *hfacname = NULL;

       		hfacname = hier_decompress_flagged(GLOBALS->facs[i]->name, &was_packed);

	        if(wave_regex_match(hfacname, WAVE_REGEX_DND))
	                {
			found++;
			match_idx_list[ii] = i;
			match_type_list[ii] = 1; /* match was on normal search */
		        if(was_packed) { free_2(hfacname); }
			goto import;
	                }

	        if(was_packed) { free_2(hfacname); }
	        }

	if(most_recent_lbrack_list[ii])
		{
		*most_recent_lbrack_list[ii] = 0;

		entry_suffixed=wave_alloca(2+strlen(s_new)+strlen(this_regex)+1);
		*entry_suffixed=0x00;
		strcpy(entry_suffixed, "\\<");
		strcat(entry_suffixed,s_new);
		strcat(entry_suffixed,this_regex);

		wave_regex_compile(entry_suffixed, WAVE_REGEX_DND);
		for(i=0;i<GLOBALS->numfacs;i++)
		        {
		        int was_packed = HIER_DEPACK_ALLOC;
		        char *hfacname = NULL;

        		hfacname = hier_decompress_flagged(GLOBALS->facs[i]->name, &was_packed);

		        if(wave_regex_match(hfacname, WAVE_REGEX_DND))
		                {
				found++;
				match_idx_list[ii] = i;
				match_type_list[ii] = 2+lbrack_adj; /* match was on lbrack removal */
			        if(was_packed) { free_2(hfacname); }
				goto import;
		                }

		        if(was_packed) { free_2(hfacname); }
		        }
		}

	import:
	if(match_type_list[ii])
		{
		struct symbol *s = GLOBALS->facs[match_idx_list[ii]];
		struct symbol *schain = s->vec_root;

		if(GLOBALS->is_lx2)
			{
			if(schain)
				{
				while(schain)
					{
					lx2_set_fac_process_mask(schain->n);
					schain = schain-> vec_chain;
					}
				}
				else
				{
	                        lx2_set_fac_process_mask(s->n);
				}
			}
		}
	}

if(!found) goto cleanup;

if(GLOBALS->is_lx2)
	{
	lx2_import_masked();
	}

if(track_mouse_y)
	{
	t = determine_trace_from_y();
	if(t)
		{
		t->flags |=  TR_HIGHLIGHT;
		}
	}

memcpy(&GLOBALS->tcache_treesearch_gtk2_c_2,&GLOBALS->traces,sizeof(Traces));
GLOBALS->traces.total=0;
GLOBALS->traces.first=GLOBALS->traces.last=NULL;

for(ii=0;ii<c;ii++)
	{
	if(match_type_list[ii])
		{
		struct symbol *s = GLOBALS->facs[match_idx_list[ii]];

		if((match_type_list[ii] >= 2)&&(s->n->extvals))
			{
			nptr nexp;

			int bit_msb = atoi(most_recent_lbrack_list[ii]+1 + (match_type_list[ii] == 3)); /* == 3 for adjustment when lbrack is escaped */
			int bit_lsb; /* = bit_msb; scan-build: never read */
			int which_msb, which_lsb, cnt;

			if(s->n->lsi > s->n->msi)
				{
				for(which_msb=0,cnt=s->n->msi ; cnt<=s->n->lsi ; cnt++,which_msb++)
					{
					if(cnt==bit_msb) break;
					}
				}
				else
				{
				for(which_msb=0,cnt=s->n->msi ; cnt>=s->n->lsi ; cnt--,which_msb++)
					{
					if(cnt==bit_msb) break;
					}
				}
			which_lsb = which_msb;

			/* Need to fix this to extract more than a single bit as in the case of a subrange of an existing vector! */
			if(most_recent_colon_list[ii])
				{
				bit_lsb = atoi(most_recent_colon_list[ii]+1);

				if(s->n->lsi > s->n->msi)
					{
					for(which_lsb=0,cnt=s->n->msi ; cnt<=s->n->lsi ; cnt++,which_lsb++)
						{
						if(cnt==bit_lsb) break;
						}
					}
					else
					{
					for(which_lsb=0,cnt=s->n->msi ; cnt>=s->n->lsi ; cnt--,which_lsb++)
						{
						if(cnt==bit_lsb) break;
						}
					}
				}

			if(which_msb == which_lsb)
				{
				nexp = ExtractNodeSingleBit(s->n, which_msb);
				*most_recent_lbrack_list[ii] = '[';
		                if(nexp)
		                        {
		                        AddNode(nexp, NULL);
		                        }
					else
					{
					AddNodeUnroll(s->n, NULL);
					}
				}
				else
				{
				process_tcl_list_2(s, which_msb, which_lsb); /* is complicated, so split out to its own function */
				}
			}
			else
			{
			struct symbol *schain = s->vec_root;

			if(!schain)
				{
				AddNodeUnroll(s->n, NULL);
				}
				else
				{
				int len = 0;
				while(schain) { len++; schain = schain->vec_chain; }
				add_vector_chain(s->vec_root, len);
				}
			}
		}
	}

paste_routine:

GLOBALS->default_flags=TR_RJUSTIFY;
GLOBALS->default_fpshift=0;

GLOBALS->traces.buffercount=GLOBALS->traces.total;
GLOBALS->traces.buffer=GLOBALS->traces.first;
GLOBALS->traces.bufferlast=GLOBALS->traces.last;
GLOBALS->traces.first=GLOBALS->tcache_treesearch_gtk2_c_2.first;
GLOBALS->traces.last=GLOBALS->tcache_treesearch_gtk2_c_2.last;
GLOBALS->traces.total=GLOBALS->tcache_treesearch_gtk2_c_2.total;

if((t) || (!track_mouse_y))
	{
	PasteBuffer();
	}
	else
	{
	PrependBuffer();
	}

GLOBALS->traces.buffercount=GLOBALS->tcache_treesearch_gtk2_c_2.buffercount;
GLOBALS->traces.buffer=GLOBALS->tcache_treesearch_gtk2_c_2.buffer;
GLOBALS->traces.bufferlast=GLOBALS->tcache_treesearch_gtk2_c_2.bufferlast;

if(track_mouse_y)
	{
	if(t)
		{
		t->flags &= ~TR_HIGHLIGHT;
		}
	}

cleanup:
for(ii=0;ii<c;ii++)
	{
	if(s_new_list[ii]) free_2(s_new_list[ii]);
	}
free_2(s_new_list);
free_2(match_idx_list);
free_2(match_type_list);
free_2(most_recent_colon_list);
free_2(most_recent_lbrack_list);
free_2(list);

EnsureGroupsMatch();

return(found);
}


/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */
/* XXX functions for data exiting from gtkwave XXX */
/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */


/* ----------------------------------------------------------------------------
 * make_gtkwave_pid - generates gtkwave pid (necessary when using twinwave as
 * the XEmbed protocol seems to be dropping the source widget for drags which
 * causes drops to occur twice), this should only need to be called by
 * add_dnd_from_signal_window().
 *
 * Results:
 *      generated tcl list string containing gtkwave PID for drop filtering
 *      also contains current marker time
 * ----------------------------------------------------------------------------
 */

static char *make_gtkwave_pid(void)
{
#if !defined __MINGW32__
char pidstr[257];

sprintf(pidstr, "{gtkwave PID %d} ", getpid());

if(GLOBALS->tims.marker != -1)
	{
	char mrkbuf[128];
	reformat_time(mrkbuf, GLOBALS->tims.marker, GLOBALS->time_dimension);
	sprintf(pidstr+strlen(pidstr), "{marker %s} ", mrkbuf);
	}

return(strdup_2(pidstr));
#else
return(NULL);
#endif
}


/* ----------------------------------------------------------------------------
 * make_single_tcl_list_name - generates tcl name from a gtkwave one
 *
 * Results:
 *      generated tcl list string
 * ----------------------------------------------------------------------------
 */

char *make_single_tcl_list_name(char *s, char *opt_value, int promote_to_bus, int preserve_range)
{
char *rpnt = NULL;
char *pnt, *pnt2;
int delim_cnt = 0;
char *lbrack=NULL, *colon=NULL, *rbrack=NULL;
const char **names = NULL;
char *tcllist = NULL;
int tcllist_len;
int names_idx = 0;
char is_bus = 0;

if(s)
	{
	int len;
	int was_packed = HIER_DEPACK_ALLOC;
	char *s2;

	s = hier_decompress_flagged(s, &was_packed);
	len = strlen(s);
	s2 = wave_alloca(len+1);

	strcpy(s2, s);
	if(was_packed)
		{
		free_2(s);
		s = NULL;
		}

	pnt = s2;

	while(*pnt)
		{
		if(*pnt == GLOBALS->hier_delimeter)
			{
			delim_cnt++;
			}
		else if(*pnt == '[') { lbrack = pnt; }
		else if(*pnt == ':') { colon  = pnt; }
		else if(*pnt == ']') { rbrack = pnt; }

		pnt++;
		}

	if(!preserve_range) /* added for gtkwave::addSignalsFromList */
		{
		if((lbrack && colon && rbrack && ((colon-lbrack)>0) && ((rbrack - colon)>0) && ((rbrack-lbrack)>0)) || (lbrack && promote_to_bus))
			{
			is_bus = 1;
			*lbrack = 0;
			/* len = lbrack - s2; */ /* scan-build */
			}
		}

	names = calloc_2(delim_cnt+1, sizeof(char *));
	pnt = s2;
	names[0] = pnt;
	while(*pnt)
		{
		if(*pnt == GLOBALS->hier_delimeter)
			{
			*pnt = 0;
			names_idx++;
			pnt++;
			if(*pnt) { names[names_idx] = pnt; }
			}
			else
			{
			pnt++;
			}
		}


	tcllist = zMergeTclList(delim_cnt+1, names);
	tcllist_len = strlen(tcllist);
	free_2(names);

	if(!opt_value)
		{
		if(is_bus)
			{
			len = 8 + strlen(tcllist) + 1 + 1 + 1; /* "{netBus ...} " + trailing null char */

			/* pnt = s2; */ /* scan-build */
			rpnt = malloc_2(len+1);
			strcpy(rpnt, "{netBus ");
			pnt2 = rpnt + 8;
			}
			else
			{
			len = 5 + strlen(tcllist) + 1 + 1 + 1; /* "{net ...} " + trailing null char */

			/* pnt = s2; */ /* scan-build */
			rpnt = malloc_2(len+1);
			strcpy(rpnt, "{net ");
			pnt2 = rpnt + 5;
			}
		}
		else
		{
		int len_value = strlen(opt_value);

		if(is_bus)
			{
			len = 15 + (len_value + 1) + strlen(tcllist) + 1 + 1 + 1; /* "{netBusValue 0x...} " + trailing null char */

			/* pnt = s2; */ /* scan-build */
			rpnt = malloc_2(len+1);
			sprintf(rpnt, "{netBusValue 0x%s ", opt_value);
			pnt2 = rpnt + 15 + (len_value + 1);
			}
			else
			{
			len = 10 + (len_value + 1) + strlen(tcllist) + 1 + 1 + 1; /* "{netValue ...} " + trailing null char */

			/* pnt = s2; */ /* scan-build */
			rpnt = malloc_2(len+1);
			sprintf(rpnt, "{netValue %s ", opt_value);
			pnt2 = rpnt + 10 + (len_value + 1);
			}
		}

	strcpy(pnt2, tcllist);
	strcpy(pnt2 + tcllist_len, "} ");

	free_2(tcllist);
	}

return(rpnt);
}


/* ----------------------------------------------------------------------------
 * give_value_string - generates value string from trace @ markertime
 *
 * Results:
 *      generated value which is similar to that generated in the signalwindow
 *      pane when the marker button is pressed.  note that this modifies the
 *      flags so it is always TR_RJUSTIFY | TR_HEX
 * ----------------------------------------------------------------------------
 */
static char *give_value_string(Trptr t)
{
char *rc = NULL;
TraceFlagsType flags;
int f_filter, p_filter;

if(t)
	{
	flags = t->flags;
	f_filter = t->f_filter;
	p_filter = t->p_filter;

	t->flags = TR_RJUSTIFY | TR_HEX;
	t->f_filter = 0;
	t->p_filter = 0;

	if(GLOBALS->tims.marker != -1)
		{
		if(t->vector)
			{
			/* this is currently unused as vectors are exploded into single bits */
			vptr v = bsearch_vector(t->n.vec, GLOBALS->tims.marker - t->shift);
                        rc = convert_ascii(t, v);
			}
			else
			{
			hptr h_ptr = bsearch_node(t->n.nd, GLOBALS->tims.marker - t->shift);
			if(h_ptr)
				{
				if(!t->n.nd->extvals)
					{
					rc = (char *)calloc_2(2, 2*sizeof(char));
					rc[0] = AN_STR[h_ptr->v.h_val];
					}
					else
					{
					if(h_ptr->flags&HIST_REAL)
						{
						if(!(h_ptr->flags&HIST_STRING))
							{
#ifdef WAVE_HAS_H_DOUBLE
							rc = convert_ascii_real(t, &h_ptr->v.h_double);
#else
							rc = convert_ascii_real(t, (double *)h_ptr->v.h_vector);
#endif
							}
							else
							{
							rc = convert_ascii_string((char *)h_ptr->v.h_vector);
							}
						}
						else
						{
		                        	rc = convert_ascii_vec(t,h_ptr->v.h_vector);
						}
					}
				}
			}
		}

	t->flags = flags;
	t->f_filter = f_filter;
	t->p_filter = p_filter;
	}

return(rc);
}


/* ----------------------------------------------------------------------------
 * add_dnd_from_searchbox - generates tcl names from selected searchbox ones
 *
 * Results:
 *      tcl list containing all generated names
 * ----------------------------------------------------------------------------
 */

char *add_dnd_from_searchbox(void)
{
int i;
char *one_entry = NULL, *mult_entry = NULL;
unsigned int mult_len = 0;
GtkTreeIter iter;
enum { NAME_COLUMN, PTR_COLUMN, N_COLUMNS };

gtk_tree_model_get_iter_first (GTK_TREE_MODEL (GLOBALS->sig_store_search), &iter);
for(i=0;i<GLOBALS->num_rows_search_c_2;i++)
        {
        struct symbol *s, *t;

        gtk_tree_model_get(GTK_TREE_MODEL (GLOBALS->sig_store_search), &iter, PTR_COLUMN, &s, -1);
        gtk_tree_model_iter_next (GTK_TREE_MODEL (GLOBALS->sig_store_search), &iter);
        if(get_s_selected(s))
                {
                if((!s->vec_root)||(!GLOBALS->autocoalesce))
                        {
                        }
                        else
                        {
                        t=s->vec_root;
			set_s_selected(t, 1);
			t=t->vec_chain;
                        while(t)
                                {
                                if(get_s_selected(t))
                                        {
                                        set_s_selected(t, 0);
                                        }
                                t=t->vec_chain;
                                }
                        }
                }
        }

gtk_tree_model_get_iter_first (GTK_TREE_MODEL (GLOBALS->sig_store_search), &iter);
for(i=0;i<GLOBALS->num_rows_search_c_2;i++)
        {
        int len;
        struct symbol *s, *t;

        gtk_tree_model_get(GTK_TREE_MODEL (GLOBALS->sig_store_search), &iter, PTR_COLUMN, &s, -1);
        gtk_tree_model_iter_next (GTK_TREE_MODEL (GLOBALS->sig_store_search), &iter);
        if(get_s_selected(s))
                {
                if((!s->vec_root)||(!GLOBALS->autocoalesce))
                        {
			one_entry = make_single_tcl_list_name(s->n->nname, NULL, 0, 0);
			WAVE_OE_ME
                        }
                        else
                        {
                        len=0;
                        t=s->vec_root;
                        while(t)
                                {
				one_entry = make_single_tcl_list_name(t->n->nname, NULL, 1, 0);
				WAVE_OE_ME

                                if(get_s_selected(t))
                                        {
                                        if(len) set_s_selected(t, 0);
                                        }
                                /* len++; */ /* scan-build : artifact of below being removed */
				break; /* t=t->vec_chain; ...no longer needed because of for() loop above and handler in process_tcl_list() */
                                }
                        }
                }
        }
return(mult_entry);
}


/* ----------------------------------------------------------------------------
 * add_dnd_from_signal_window - generates tcl names from selected sigwin ones
 *
 * Results:
 *      tcl list containing all generated names
 * ----------------------------------------------------------------------------
 */
char *add_dnd_from_signal_window(void)
{
return(add_traces_from_signal_window(FALSE));
}


/* ----------------------------------------------------------------------------
 * add_traces_from_signal_window - generates tcl names from all sigwin ones
 *
 * Results:
 *      tcl list containing all generated names, does not contain
 *      {gtkwave NET OFF} directive as this is intended for tcl program usage.
 * ----------------------------------------------------------------------------
 */

char *add_traces_from_signal_window(gboolean is_from_tcl_command)
{
Trptr t;
char *one_entry = NULL, *mult_entry = NULL;
unsigned int mult_len = 0;
char *netoff = "{gtkwave NET OFF} ";
char *trace_val = NULL;
static const char xfwd[AN_COUNT]= AN_NORMAL;
char trace_val_vec_single[2] = { 0, 0 };

if(is_from_tcl_command)
	{
	mult_entry = strdup_2("");
	}

t=GLOBALS->traces.first;
while(t)
	{
        if( (!(t->flags&(TR_BLANK|TR_ANALOG_BLANK_STRETCH))) && ((t->flags & TR_HIGHLIGHT)||is_from_tcl_command) )
		{
                if(t->vector)
                        {
                        int i;
                        nptr *nodes;
                        vptr v = (GLOBALS->tims.marker != -1) ? bsearch_vector(t->n.vec, GLOBALS->tims.marker - t->shift) : NULL;
			unsigned char *bits = v ? (v->v) : NULL;
			char *first_str = NULL;
			int coalesce_pass = 1;

                        nodes=t->n.vec->bits->nodes;

			for(i=0;i<t->n.vec->bits->nnbits;i++)
				{
				if(!nodes[i]->expansion)
					{
					nptr n = nodes[i];
                                        char *str = append_array_row(n);
                                        char *p = strrchr(str, '[');
                                        if(p) { *p = 0; } else { coalesce_pass = 0; break; }

					if(!i)
						{
						first_str = strdup_2(str);
						}
						else
						{
						if((!first_str /*scan-build */) || strcmp(str, first_str)) { coalesce_pass = 0; break; }
						}
					}
					else
					{
					coalesce_pass = 0;
					}
				}

			if(coalesce_pass)
				{
				if(t->n.vec->bits->nnbits < 2)
					{
					coalesce_pass = 0;
					}
					else
					{
					nptr nl = nodes[0];
					char *strl = append_array_row(nl);
					char *pl = strrchr(strl, '[');
					int lidx = atoi(pl+1);

					nptr nr = nodes[t->n.vec->bits->nnbits - 1];
					char *strr = append_array_row(nr);
					char *pr = strrchr(strr, '[');
					int ridx = atoi(pr+1);

					int first_str_len = strlen(first_str ? first_str : (first_str = strdup_2("INTERNAL_ERROR"))); /* : case added for scan-build */
					char *newname = malloc_2(first_str_len + 40);

					sprintf(newname, "%s[%d:%d]", first_str, lidx, ridx); /* this disappears in make_single_tcl_list_name() but might be used in future code */

					if(!mult_entry) { one_entry = make_gtkwave_pid(); WAVE_OE_ME one_entry = strdup_2(netoff); WAVE_OE_ME}
					one_entry = is_from_tcl_command ? strdup_2s(newname) : make_single_tcl_list_name(newname, NULL, 0, 0);
					WAVE_OE_ME
					if(!is_from_tcl_command)
						{
						trace_val = give_value_string(t);
						if(trace_val)
							{
							one_entry = make_single_tcl_list_name(newname, trace_val, 0, 0);
							WAVE_OE_ME
							free_2(trace_val);
							}
						}

					free_2(newname);
					}

				free_2(first_str);
				first_str = NULL;
				}

			if(!coalesce_pass)
                        for(i=0;i<t->n.vec->bits->nnbits;i++)
                                {
                                if(nodes[i]->expansion)
                                        {
		                        int which, cnt;
					int bit = nodes[i]->expansion->parentbit;
					nptr n = nodes[i]->expansion->parent;
					char *str = append_array_row(n);
					char *p = strrchr(str, '[');
					if(p) { *p = 0; }

                        		if(n->lsi > n->msi)
                                		{
                                		for(which=0,cnt=n->lsi ; cnt>=n->msi ; cnt--,which++)
                                        		{
                                        		if(cnt==bit) break;
                                        		}
                                		}
                                		else
                                		{
                                		for(which=0,cnt=n->msi ; cnt>=n->lsi ; cnt--,which++)
                                        		{
                                        		if(cnt==bit) break;
                                        		}
                                		}

					sprintf(str+strlen(str), "[%d]", which);
					if(!mult_entry) { one_entry = make_gtkwave_pid(); WAVE_OE_ME one_entry = strdup_2(netoff); WAVE_OE_ME }
					one_entry = is_from_tcl_command ? strdup_2s(str) : make_single_tcl_list_name(str, NULL, 0, 0);
					WAVE_OE_ME

					if((bits)&&(!is_from_tcl_command))
						{
						int bitnum = bits[i];

						if(bitnum < 0) bitnum = AN_DASH;
						else if(bitnum >= AN_COUNT) bitnum = AN_DASH;

						trace_val_vec_single[0] = AN_STR[(int)xfwd[bitnum]];
						one_entry = make_single_tcl_list_name(str, trace_val_vec_single, 0, 0);
						WAVE_OE_ME
						}
                                        }
                                        else
                                        {
					if(!mult_entry) { one_entry = make_gtkwave_pid(); WAVE_OE_ME one_entry = strdup_2(netoff); WAVE_OE_ME}
					one_entry = is_from_tcl_command ? strdup_2s(append_array_row(nodes[i])) : make_single_tcl_list_name(append_array_row(nodes[i]), NULL, 0, 0);
					WAVE_OE_ME
					if(!is_from_tcl_command)
						{
						trace_val = give_value_string(t);
						if(trace_val)
							{
							one_entry = make_single_tcl_list_name(append_array_row(nodes[i]), trace_val, 0, 0);
							WAVE_OE_ME
							free_2(trace_val);
							}
						}
                                        }
                                }
                        }
			else
			{
			if(t->n.nd->expansion)
				{
	                        int which, cnt;
				int bit = t->n.nd->expansion->parentbit;
				nptr n = t->n.nd->expansion->parent;
				char *str = append_array_row(n);
				char *p = strrchr(str, '[');
				if(p) { *p = 0; }

                       		if(n->lsi > n->msi)
                               		{
                               		for(which=0,cnt=n->lsi ; cnt>=n->msi ; cnt--,which++)
                                       		{
                                       		if(cnt==bit) break;
                                       		}
                               		}
                               		else
                               		{
                               		for(which=0,cnt=n->msi ; cnt>=n->lsi ; cnt--,which++)
                                       		{
                                       		if(cnt==bit) break;
                                       		}
                               		}

				sprintf(str+strlen(str), "[%d]", which);
				if(!mult_entry) { one_entry = make_gtkwave_pid(); WAVE_OE_ME one_entry = strdup_2(netoff); WAVE_OE_ME}
				one_entry = is_from_tcl_command ? strdup_2s(str) : make_single_tcl_list_name(str, NULL, 0, 0);
				WAVE_OE_ME
				if(!is_from_tcl_command)
					{
					trace_val = give_value_string(t);
					if(trace_val)
						{
						one_entry = make_single_tcl_list_name(str, trace_val, 0, 0);
						WAVE_OE_ME
						free_2(trace_val);
						}
					}
				}
				else
				{
				if(!mult_entry) { one_entry = make_gtkwave_pid(); WAVE_OE_ME one_entry = strdup_2(netoff); WAVE_OE_ME}
				one_entry = is_from_tcl_command ? strdup_2s(append_array_row(t->n.nd)) : make_single_tcl_list_name(append_array_row(t->n.nd), NULL, 0, 0);
				WAVE_OE_ME
				if(!is_from_tcl_command)
					{
					trace_val = give_value_string(t);
					if(trace_val)
						{
						one_entry = make_single_tcl_list_name(append_array_row(t->n.nd), trace_val, 0, 0);
						WAVE_OE_ME
						free_2(trace_val);
						}
					}
				}
			}
		}
		else
		{
		if(!mult_entry)
			{
			one_entry = strdup_2(netoff); WAVE_OE_ME
			}
		}
	t = t->t_next;
	}

return(mult_entry);
}


/* ----------------------------------------------------------------------------
 * sig_selection_foreach_dnd - generates tcl names from iterated clist ones
 *
 * Results:
 *      tcl list containing all generated names coalesced back into *data
 * ----------------------------------------------------------------------------
 */

static void
sig_selection_foreach_dnd
                      (GtkTreeModel *model,
                       GtkTreePath *path,
                       GtkTreeIter *iter,
                       gpointer data)
{
(void)path;

  struct tree *sel;
  int i;
  int low, high;
  struct iter_dnd_strings *it;
  char *one_entry, *mult_entry;
  unsigned int mult_len;
  enum { NAME_COLUMN, TREE_COLUMN, N_COLUMNS };

  it = (struct iter_dnd_strings *)data;
  one_entry = it->one_entry;
  mult_entry = it->mult_entry;
  mult_len = it->mult_len;

  /* Get the tree.  */
  gtk_tree_model_get (model, iter, TREE_COLUMN, &sel, -1);

  if(!sel) return;

  low = fetchlow(sel)->t_which;
  high = fetchhigh(sel)->t_which;

  /* If signals are vectors, iterate through them if so.  */
  for(i=low;i<=high;i++)
        {
        struct symbol *s;
        s=GLOBALS->facs[i];
        if((s->vec_root)&&(GLOBALS->autocoalesce))
                {
		struct symbol *t = s->vec_root;
                while(t)
			{
                        one_entry = make_single_tcl_list_name(t->n->nname, NULL, 1, 0);
                        WAVE_OE_ME
                        break; /* t=t->vec_chain; ...no longer needed as this is resolved in process_tcl_list() */
                        }
                }
		else
		{
                one_entry = make_single_tcl_list_name(s->n->nname, NULL, 0, 0);
                WAVE_OE_ME
		}
        }

  it->one_entry = one_entry;
  it->mult_entry = mult_entry;
  it->mult_len = mult_len;
}

/* ----------------------------------------------------------------------------
 * add_dnd_from_tree_window - generates tcl names from selected tree clist ones
 *
 * Results:
 *      tcl list containing all generated names
 * ----------------------------------------------------------------------------
 */

char *add_dnd_from_tree_window(void)
{
struct iter_dnd_strings it;

memset(&it, 0, sizeof(struct iter_dnd_strings));
gtk_tree_selection_selected_foreach(GLOBALS->sig_selection_treesearch_gtk2_c_1, &sig_selection_foreach_dnd, (gpointer)&it);

return(it.mult_entry);
}


/* ----------------------------------------------------------------------------
 * make_message - printf() which mallocs into a string
 *
 * Results:
 *      dynamically allocated string
 * ----------------------------------------------------------------------------
 */

static char *make_message (const char *fmt, ...)
{
  /* Guess we need no more than 100 bytes. */
  int n, size = 100;
  char *p, *np;
  va_list ap;

  if ((p = malloc_2(size)) == NULL)
    return NULL;

  while (1)
    {
      /* Try to print in the allocated space. */
      va_start (ap, fmt);
      n = vsnprintf (p, size, fmt, ap);
      va_end (ap);
      /* If that worked, return the string. */
      if (n > -1 && n < size)
	return p;
      /* Else try again with more space. */
      if (n > -1)		/* glibc 2.1 */
	size = n + 1;		/* precisely what is needed */
      else			/* glibc 2.0 */
	size *= 2;		/* twice the old size */
      if ((np = realloc_2(p, size)) == NULL)
	{
	  free (p);
	  return NULL;
	}
      else
	{
	  p = np;
	}
    }
}


/* ----------------------------------------------------------------------------
 * emit_gtkwave_savefile_formatted_entries_in_tcl_list - performs as named
 *
 * Results:
 *      tcl list which mimics a gtkwave save file for cut and paste entries
 *      which is later iteratively run through the normal gtkwave save file
 *      loader parsewavline() on the distant end.  the reason this is
 *      necessary is in order to pass attribute and concatenation information
 *      along to the distant end.
 * ----------------------------------------------------------------------------
 */

char *emit_gtkwave_savefile_formatted_entries_in_tcl_list(Trptr t, gboolean use_tcl_mode) {
	char *one_entry, *mult_entry = NULL;
	unsigned int mult_len = 0;
	TraceFlagsType prev_flags = 0;

	TraceFlagsType def=0;
	TimeType prevshift=LLDescriptor(0);
	char is_first = 1;
	char flag_skip;

	while(t)
		{
		flag_skip = 0;

		if(use_tcl_mode)
		  {
		    if(IsSelected(t) || (t->t_grp && IsSelected(t->t_grp)))
		      {
			/* members of closed groups may not be highlighted */
			/* so propogate highlighting here */
			t->flags |= TR_HIGHLIGHT;
		      }
		    else
		      {
			if((prev_flags & TR_ANALOGMASK) && (t->flags &TR_ANALOG_BLANK_STRETCH))
			  {
			    flag_skip = 1;
			  }
			else
			  {
			    t = t->t_next;
			    continue;
			  }
		      }
		  }

		if((t->flags!=def)||(is_first))
			{
			is_first = 0;
			if((t->flags & TR_PTRANSLATED) && (!t->p_filter)) t->flags &= (~TR_PTRANSLATED);
			if((t->flags & TR_FTRANSLATED) && (!t->f_filter)) t->flags &= (~TR_FTRANSLATED);
			one_entry = make_message("@%"TRACEFLAGSPRIFMT"\n",(def=t->flags) & ~TR_HIGHLIGHT);
			WAVE_OE_ME
			if(!flag_skip) prev_flags = def;
			}

		if(t->t_color)
			{
			one_entry = make_message("[color] %d\n", t->t_color);
			WAVE_OE_ME
			}

		if(t->t_fpdecshift)
			{
			one_entry = make_message("[fpshift_count] %d\n", t->t_fpdecshift);
			WAVE_OE_ME
			}

		if((t->shift)||((prevshift)&&(!t->shift)))
			{
			one_entry = make_message(">"TTFormat"\n", t->shift);
			WAVE_OE_ME
			}
		prevshift=t->shift;

		if(!(t->flags&(TR_BLANK|TR_ANALOG_BLANK_STRETCH)))
			{
			if(t->flags & TR_FTRANSLATED)
				{
				if(t->f_filter && GLOBALS->filesel_filter[t->f_filter])
					{
					one_entry = make_message("^%d %s\n", t->f_filter, GLOBALS->filesel_filter[t->f_filter]);
					WAVE_OE_ME
					}
					else
					{
					one_entry = make_message("^%d %s\n", 0, "disabled");
					WAVE_OE_ME
					}
				}
			else
			if(t->flags & TR_PTRANSLATED)
				{
				if(t->p_filter && GLOBALS->procsel_filter[t->p_filter])
					{
					one_entry = make_message("^>%d %s\n", t->p_filter, GLOBALS->procsel_filter[t->p_filter]);
					WAVE_OE_ME
					}
					else
					{
					one_entry = make_message("^>%d %s\n", 0, "disabled");
					WAVE_OE_ME
					}
				}

			/* NOT an else! */
			if(t->flags & TR_TTRANSLATED)
				{
                                if(t->transaction_args)
                                        {
                                        one_entry = make_message("[transaction_args] \"%s\"\n", t->transaction_args);
					WAVE_OE_ME
                                        }
                                        else
                                        {
                                        one_entry = make_message("[transaction_args] \"%s\"\n", "");
					WAVE_OE_ME
                                        }

				if(t->t_filter && GLOBALS->ttranssel_filter[t->t_filter])
					{
					one_entry = make_message("^<%d %s\n", t->t_filter, GLOBALS->ttranssel_filter[t->t_filter]);
					WAVE_OE_ME
					}
					else
					{
					one_entry = make_message("^<%d %s\n", 0, "disabled");
					WAVE_OE_ME
					}
				}

			if(t->vector && !(t->n.vec->transaction_cache && t->n.vec->transaction_cache->transaction_nd))
				{
				int i;
				nptr *nodes;
				bptr bits;
				baptr ba;

				if(HasAlias(t)) { one_entry = make_message("+{%s} ",t->name_full);
				                  WAVE_OE_ME }
				bits = t->n.vec->bits;
				ba = bits ? bits->attribs : NULL;

				one_entry = make_message("%c{%s}", ba ? ':' : '#',
					t->n.vec->transaction_cache ? t->n.vec->transaction_cache->bvname : t->n.vec->bvname);
				WAVE_OE_ME

				nodes=t->n.vec->bits->nodes;
				for(i=0;i<t->n.vec->bits->nnbits;i++)
					{
					if(nodes[i]->expansion)
						{
						one_entry = make_message(" (%d)%s",nodes[i]->expansion->parentbit, append_array_row(nodes[i]->expansion->parent));
						WAVE_OE_ME
						}
						else
						{
						one_entry = make_message(" %s",append_array_row(nodes[i]));
						WAVE_OE_ME
						}
					if(ba)
						{
						one_entry = make_message(" "TTFormat" %"TRACEFLAGSPRIFMT, ba[i].shift, ba[i].flags);
						WAVE_OE_ME
						}
					}

				one_entry = make_message("\n");
				WAVE_OE_ME
				}
				else
				{
				  nptr nd = (t->vector && t->n.vec->transaction_cache && t->n.vec->transaction_cache->transaction_nd) ?
						t->n.vec->transaction_cache->transaction_nd : t->n.nd;

				  if(HasAlias(t))
					{
					if(nd->expansion)
						{
						one_entry = make_message("+{%s} (%d)%s\n",t->name_full,nd->expansion->parentbit, append_array_row(nd->expansion->parent));
						WAVE_OE_ME
						}
						else
						{
						one_entry = make_message("+{%s} %s\n",t->name_full,append_array_row(nd));
						WAVE_OE_ME
						}
					}
					else
					{
					if(nd->expansion)
						{
						one_entry = make_message("(%d)%s\n",nd->expansion->parentbit, append_array_row(nd->expansion->parent));
						WAVE_OE_ME
						}
						else
						{
						one_entry = make_message("%s\n",append_array_row(nd));
						WAVE_OE_ME
						}
					}
				}
			}
			else
			{
			if(!t->name) { one_entry = make_message("-\n"); WAVE_OE_ME }
			else { one_entry = make_message("-%s\n",t->name); WAVE_OE_ME }
			}

		t=t->t_next;
		}

if(mult_entry)
	{
	const char *hdr = "{gtkwave SAVELIST ";
	int hdr_len = strlen(hdr);
	const char *av[1];
	char *zm;
	int zm_len;

	av[0] = mult_entry;
	zm = zMergeTclList(1, av);
	zm_len = strlen(zm);

	free_2(mult_entry);

	mult_entry = malloc_2(hdr_len + zm_len + 2 + 1);
	memcpy(mult_entry, hdr, hdr_len);
	memcpy(mult_entry + hdr_len, zm, zm_len);
	strcpy(mult_entry + hdr_len + zm_len, "} ");

	free_2(zm);
	}

return(mult_entry);
}


/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */
/* XXX functions for URL (not TCL list) handling XXX */
/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */

enum GtkwaveFtype { WAVE_FTYPE_UNKNOWN, WAVE_FTYPE_DUMPFILE, WAVE_FTYPE_STEMSFILE, WAVE_FTYPE_SAVEFILE };

/* ----------------------------------------------------------------------------
 * determine_ftype - examines filename (perhaps initial contents) and
 *      determines file type
 *
 * Results:
 *      enum of ftype determination
 * ----------------------------------------------------------------------------
 */
static int determine_ftype(char *s, char **dotpnt)
{
char *pnt = s;
char *dot = NULL, *dot2 = NULL;
int ftype = WAVE_FTYPE_UNKNOWN;

while(*pnt)
	{
	if(*pnt == '.') { dot2 = dot; dot = pnt; }
	pnt++;
	}

*dotpnt = dot;

if(dot)
	{
	if(!strcasecmp("sav", dot+1))
		{
		ftype = WAVE_FTYPE_SAVEFILE;
		}
	else
	if(!strcasecmp("gtkw", dot+1))
		{
		ftype = WAVE_FTYPE_SAVEFILE;
		}
	else
	if(!strcasecmp("stems", dot+1))
		{
		ftype = WAVE_FTYPE_STEMSFILE;
		}
	else /* detect dumpfile type */
	if	(
#ifdef EXTLOAD_SUFFIX
                (!strcasecmp(EXTLOAD_SUFFIX, dot+1)) ||
#endif
		(!strcasecmp("vcd", dot+1)) ||
		(!strcasecmp("dmp", dot+1)) ||
		(!strcasecmp("lxt", dot+1)) ||
		(!strcasecmp("lx2", dot+1)) ||
		(!strcasecmp("lxt2", dot+1)) ||
		(!strcasecmp("vzt", dot+1)) ||
		(!strcasecmp("fst", dot+1)) ||
		(!strcasecmp("ghw", dot+1)) ||
		(!strcasecmp("aet", dot+1)) ||	/* ignore .aet? filename types */
		(!strcasecmp("ae2", dot+1))
		)
		{
		ftype = WAVE_FTYPE_DUMPFILE;
		}
	else
	if(dot2)
		{
		if	(
			(!strcasecmp("ghw.gz", dot2+1))  ||
			(!strcasecmp("ghw.bz2", dot2+1)) ||
			(!strcasecmp("ghw.bz2", dot2+1)) ||
			(!strcasecmp("vcd.gz", dot2+1))  ||
			(!strcasecmp("vcd.zip", dot2+1))
			)
			{
			ftype = WAVE_FTYPE_DUMPFILE;
			}
		}
	}
	else
	{
	FILE *f = fopen(s, "rb");
	if(f)
		{
		int ch0 = getc(f);
		int ch1 = getc(f);

		if(ch0 == EOF) { ch0 = ch1 = 0; }
		else
		if(ch1 == EOF) { ch1 = 0; }

		if((ch0 == '+') && (ch1 == '+'))
			{
			ftype = WAVE_FTYPE_STEMSFILE; /* stems file */
			}
		else
		if(ch0 == '[')
			{
			ftype = WAVE_FTYPE_SAVEFILE; /* save file */
			}

		fclose(f);
		}
	}

return(ftype);
}

/* ----------------------------------------------------------------------------
 * process_url_file - examines filename and performs appropriate side-effect
 *
 * Results:
 *      Loads save file, new dump file, or stems file viewer
 * ----------------------------------------------------------------------------
 */
int process_url_file(char *s)
{
static int processing_missing_file = 0; /* in case of malformed save files causing recursion */

int rc = 0;
char *dotpnt = NULL;
int ftype = determine_ftype(s, &dotpnt);

switch(ftype)
	{
	case WAVE_FTYPE_SAVEFILE:
		if((GLOBALS->loaded_file_type == MISSING_FILE)&&(!processing_missing_file))
			{
			gboolean modified;
			int opt_vcd;
			char *dfn = extract_dumpname_from_save_file(s, &modified, &opt_vcd);
			if(dfn)
				{
				char *dfn_local = strdup(dfn);
				free_2(dfn);

				processing_missing_file = 1;
				if(process_url_file(dfn_local))
					{
					GLOBALS->dumpfile_is_modified = modified;
					}
				free(dfn_local);
				processing_missing_file = 0;
				}
			}

		GLOBALS->fileselbox_text = &GLOBALS->filesel_writesave;
	        GLOBALS->filesel_ok=1;
        	if(*GLOBALS->fileselbox_text) free_2(*GLOBALS->fileselbox_text);
        	*GLOBALS->fileselbox_text=(char *)strdup_2(s);

#ifndef MAC_INTEGRATION
		GLOBALS->block_xy_update = 1;
#else
		GLOBALS->block_xy_update = (GLOBALS->num_notebook_pages > 1); /* let window always resize if 1 tab */
#endif
		wave_gconf_client_set_string("/current/savefile", s);
		read_save_helper(s, NULL, NULL, NULL, NULL, NULL);
		GLOBALS->block_xy_update = 0;
		rc = 1;
		break;

	case WAVE_FTYPE_STEMSFILE:
#if !defined __MINGW32__
		GLOBALS->fileselbox_text = &GLOBALS->stems_name;
	        GLOBALS->filesel_ok=1;
        	if(*GLOBALS->fileselbox_text) free_2(*GLOBALS->fileselbox_text);
        	*GLOBALS->fileselbox_text=(char *)strdup_2(s);

		menu_read_stems_cleanup(NULL, NULL);
#endif
		rc = 1;
		break;

	case WAVE_FTYPE_DUMPFILE:
		GLOBALS->fileselbox_text = &GLOBALS->filesel_newviewer_menu_c_1;
	        GLOBALS->filesel_ok=1;
        	if(*GLOBALS->fileselbox_text) free_2(*GLOBALS->fileselbox_text);
        	*GLOBALS->fileselbox_text=(char *)strdup_2(s);

		menu_new_viewer_tab_cleanup(NULL, NULL);
		rc = 1;
		break;

	default:
		break;
	}

return(rc);
}

/* ----------------------------------------------------------------------------
 * uri_cmp - qsort compare function that ensures save files and stems are
 *      ordered after their respective dumpfiles
 *
 * Results:
 *      returns correct sort order for processing (based on name and
 *      GtkwaveFtype
 * ----------------------------------------------------------------------------
 */
static int uri_cmp(const void *v1, const void *v2)
{
char *s1 = *(char **)v1;
char *s2 = *(char **)v2;
char *d1, *d2;
int typ1 = determine_ftype(s1, &d1);
int typ2 = determine_ftype(s2, &d2);
int rc;

if(!d1 || !d2)
	{
	return(strcmp(s1, s2));
	}

*d1 = 0; *d2 = 0;

rc = strcmp(s1, s2);
if(!rc)
	{
	rc = (typ1 - typ2); /* use suffix ftype to manipulate sort */
	}

*d1 = '.'; *d2 = '.';

return(rc);
}

/* ----------------------------------------------------------------------------
 * process_url_list - examines list of URLs and processes if valid files
 *
 * Results:
 *      Indicates if any URLs were processed
 * ----------------------------------------------------------------------------
 */

int process_url_list(char *s)
{
int is_url = 0;
int i;
int url_cnt = 0;
char pch = 0;

char *nxt_hd = s;
char *pnt = s;
char *path;
char **url_list = g_malloc(sizeof(gchar *)); /* deliberate g_funcs() as context can swap out from under us */

if(*pnt == '{') { g_free(url_list); return(0); } /* exit early if tcl list */

for(;;)
	{
	if(*pnt == 0)
		{
		if(!(*nxt_hd))
			{
			break;
			}

		path = g_filename_from_uri(nxt_hd, NULL, NULL);
		if(path) { url_list[url_cnt++] = path; url_list = g_realloc(url_list, (url_cnt+1) * sizeof(gchar *)); }
		break;
		}
	else
	if((*pnt == '\n')||(*pnt == '\r'))
		{
		if((pch != '\n') && (pch != '\r'))
			{
			char sav = *pnt;
			*pnt = 0;

			path = g_filename_from_uri(nxt_hd, NULL, NULL);
			if(path) { url_list[url_cnt++] = path; url_list = g_realloc(url_list, (url_cnt+1) * sizeof(gchar *)); }
			*pnt = sav;
			}
		pch = *pnt;
		nxt_hd = pnt+1;
		pnt++;
		}
	else
		{
		pch = *pnt;
		pnt++;
		}
	}

if(url_list)
	{
	if(url_cnt > 2)
		{
		qsort(url_list, url_cnt, sizeof(struct gchar *), uri_cmp);
		}
	else
	if(url_cnt == 2) /* in case there are only 2 files, make the savefile last */
		{
		char *d1, *d2;
		int typ1 = determine_ftype(url_list[0], &d1);
		int typ2 = determine_ftype(url_list[1], &d2);

		if(typ1 > typ2)
			{
			char *tmp_swap = url_list[0];
			url_list[0] = url_list[1];
			url_list[1] = tmp_swap;
			}
		}

	for(i=0;i<url_cnt;i++)
		{
		is_url += process_url_file(url_list[i]);
		g_free(url_list[i]);
		}

	g_free(url_list);
	}

return(is_url);
}

/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */
/* XXX functions for embedding TCL interpreter XXX */
/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */

#if defined(HAVE_LIBTCL)

/* declare variables that will be shared with the repscript */
#undef WAVE_TCLCB_M
#define WAVE_TCLCB_M(a,b,c) a
static const char *tclcb_var_names[] = { WAVE_TCLCB_MACRO_EXPANSION };

#undef WAVE_TCLCB_M
#define WAVE_TCLCB_M(a,b,c) b
static const int tclcb_var_flags[] = { WAVE_TCLCB_MACRO_EXPANSION };

#undef WAVE_TCLCB_M
#define WAVE_TCLCB_M(a,b,c) c
static const char *tclcb_var_inits[] = { WAVE_TCLCB_MACRO_EXPANSION };

static void declare_tclcb_variables(Tcl_Interp *interp)
{
(void)interp;

int i = 0 ;
while(tclcb_var_flags[i] != -1)
	{
	gtkwavetcl_setvar(tclcb_var_names[i], tclcb_var_inits[i], tclcb_var_flags[i]) ;
	i++;
	}
}


static gboolean setvar_timer(gpointer arg)
{
static gboolean run_once = FALSE;

if(run_once == FALSE) /* avoid any race conditions with the toolkit for uninitialized data */
        {
        run_once = TRUE;
        return(TRUE);
        }
        else
        {
        static gboolean in_timer = FALSE;

        if(!GLOBALS->busy_busy_c_1 && !in_timer)
                {
                Tcl_Interp *interp = (Tcl_Interp *)arg;
                const char *tv = NULL;

                in_timer = TRUE;
                tv = Tcl_GetVar(interp, WAVE_TCLCB_TIMER_PERIOD, WAVE_TCLCB_TIMER_PERIOD_FLAGS);
		if(tv)
			{
	                gtkwavetcl_setvar_nonblocking(WAVE_TCLCB_TIMER_PERIOD,tv,WAVE_TCLCB_TIMER_PERIOD_FLAGS);
	                tv = Tcl_GetVar(interp, WAVE_TCLCB_TIMER_PERIOD, WAVE_TCLCB_TIMER_PERIOD_FLAGS);
			}
                in_timer = FALSE;

		if(tv)
			{
	                g_timeout_add(atoi(tv), setvar_timer, arg);
			}
                return(FALSE);
                }
                else
                {
                return(TRUE);
                }
        }
}


static void init_setvar_timer(Tcl_Interp *interp)
{
g_timeout_add(atoi(WAVE_TCLCB_TIMER_PERIOD_INIT), setvar_timer, (gpointer)interp);
}



static int menu_func(ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[])
{
gtkwave_mlist_t *ife = (gtkwave_mlist_t *)clientData;
int i;
struct wave_script_args *old_wave_script_args = GLOBALS->wave_script_args; /* stackable args */
char fexit = GLOBALS->enable_fast_exit;

if(GLOBALS->in_tcl_callback) /* don't allow callbacks to call menu functions (yet) */
	{
	char reportString[1024];
	char menuItem[512];
	Tcl_Obj *aobj;

	char *src = ife->path;
	char *dst = menuItem;

	while(*src)
		{
		*dst = (*src != ' ') ? *src : '_';
		src++;
		dst++;
		}
	*dst = 0;

	sprintf(reportString, "gtkwave::%s prohibited in callback", menuItem);
	gtkwavetcl_setvar_nonblocking(WAVE_TCLCB_ERROR,reportString,WAVE_TCLCB_ERROR_FLAGS);

	aobj = Tcl_NewStringObj(reportString, -1);
	Tcl_SetObjResult(interp, aobj);
	return(TCL_ERROR);
	}

GLOBALS->wave_script_args = NULL;
GLOBALS->enable_fast_exit = 1;

if(objc > 1)
	{
	struct wave_script_args *wc = NULL;

	for(i=1;i<objc;i++)
		{
		char *s = Tcl_GetString(objv[i]);
		int slen = strlen(s);
		struct wave_script_args *w = wave_alloca(sizeof(struct wave_script_args) + slen + 1);
			/*  alloca used in case we context switch and get our allocator ripped out from under us -- the call stack won't go away */
		if(slen)
			{
			strcpy(w->payload, s); /* scan-build complains but it thinks payload[1] is the actual memory allocated */
			}
		w->curr = NULL; /* yes, curr is only ever used for the 1st struct, but there is no sense creating head/follower structs for this */
		w->next = NULL;

		if(!GLOBALS->wave_script_args)
			{
			GLOBALS->wave_script_args = w;
			w->curr = w;
			}
			else
			{
			if(wc)	/* scan-build: suppress warning, this will never happen */
				{
				wc->next = w; /* we later really traverse through curr->next from the head pointer */
				}
			}

		wc = w;
		}

	if(!GLOBALS->wave_script_args) /* create a dummy list in order to keep requesters from popping up in file.c, etc. */
		{
		GLOBALS->wave_script_args = wave_alloca(sizeof(struct wave_script_args) + 1);
		GLOBALS->wave_script_args->curr = NULL;
		GLOBALS->wave_script_args->next = NULL;
		GLOBALS->wave_script_args->payload[0] = 0;
		}

	ife->callback();
	gtkwave_main_iteration();

	GLOBALS->wave_script_args = NULL;
	}
	else
	{
	ife->callback();
	gtkwave_main_iteration();
	}

GLOBALS->enable_fast_exit = fexit;
GLOBALS->wave_script_args = old_wave_script_args;
return(TCL_OK); /* signal error with rc=TCL_ERROR, Tcl_Obj *aobj = Tcl_NewStringObj(reportString, -1); Tcl_SetObjResult(interp, aobj); */
}

/* XXXXXXXXXXXXXXXXXXXXXXXXX */
/* XXX  RPC Tcl Variant  XXX */
/* XXXXXXXXXXXXXXXXXXXXXXXXX */

char *rpc_script_execute(const char *nam)
{
char *tpnt = NULL;
char *s;

if((nam) && (strlen(nam)) && (!GLOBALS->tcl_running))
	{
	int tclrc;
	int nlen = strlen(nam);
        char *tcl_cmd = wave_alloca(8 + nlen + 1 + 1);
        strcpy(tcl_cmd, "source {");
        strcpy(tcl_cmd+8, nam);
        strcpy(tcl_cmd+8+nlen, "}");

	GLOBALS->tcl_running = 1;
	tclrc = Tcl_Eval (GLOBALS->interp, tcl_cmd);
	GLOBALS->tcl_running = 0;

	if(tclrc != TCL_OK)
		{
		tpnt = strdup_2(Tcl_GetStringResult (GLOBALS->interp));
		}
		else
		{
		tpnt = strdup_2("TCL_OK");
		}
	}

if(!tpnt) tpnt = strdup_2("TCL_ERROR : no filename specified");

s = malloc_2(strlen("--script ") + strlen(tpnt) + 1 + 1);
sprintf(s, "%s%s\n", "--script ", tpnt);
free_2(tpnt);

return(s);
}


/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */
/* XXX  Bluespec Tcl Variant  XXX */
/* XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX */

void gtkUpdate(ClientData ignore)
{
(void)ignore;

  while (gtk_events_pending()) { gtk_main_iteration(); }
  Tcl_CreateTimerHandler(50,gtkUpdate, (ClientData) NULL); /* ajb: was 0 period ...caused 100% CPU spike */
}

int  gtkwaveInterpreterInit(Tcl_Interp *interp) {
  int i;
  char commandName[128];
  gtkwave_mlist_t *ife;
  int num_menu_items;

#ifdef WAVE_TCL_STUBIFY
/* not needed...this does a double init if enabled:  set_globals_interp(); */
#else
  if(Tcl_Init(interp) == TCL_ERROR) return TCL_ERROR;
  if(Tk_Init(interp) == TCL_ERROR) return TCL_ERROR;
  Tcl_SetVar(interp,"tcl_rcFileName","~/.wishrc",TCL_GLOBAL_ONLY);
#endif

  strcpy(commandName, "gtkwave::");

  ife = retrieve_menu_items_array(&num_menu_items);
  for(i=0;i<num_menu_items;i++)
    {
      if(ife[i].callback)
	{
	  char *pnt = commandName + 9;
	  strcpy(pnt, ife[i].path);
	  while(*pnt)
	    {
	      if(*pnt==' ') *pnt='_';
	      pnt++;
	    }

	  Tcl_CreateObjCommand(interp, commandName,
			       (Tcl_ObjCmdProc *)menu_func,
			       (ClientData)(ife+i), (Tcl_CmdDeleteProc *)NULL);
	}
    }


  for (i = 0; gtkwave_commands[i].func != NULL; i++)
    {
      strcpy(commandName + 9, gtkwave_commands[i].cmdstr);

      Tcl_CreateObjCommand(interp, commandName,
			   (Tcl_ObjCmdProc *)gtkwave_commands[i].func,
			   (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
    }

  declare_tclcb_variables(interp) ;

  /* hide the "wish" window */
  Tcl_Eval(interp, "wm withdraw .");

  Tcl_Eval(interp,
    "puts \"Interpreter id is [file tail $::argv0]\"");

  if (GLOBALS->tcl_init_cmd)
    {
      Tcl_Eval(interp, GLOBALS->tcl_init_cmd);
    }

  Tcl_CreateTimerHandler(50,gtkUpdate, (ClientData) NULL);
  init_setvar_timer(interp);

  return TCL_OK;
}

/* XXXXXXXXXXXXXXXXXXXXXXXXXXXX */
/* XXX  Simpod Tcl Variant  XXX */
/* XXXXXXXXXXXXXXXXXXXXXXXXXXXX */

static gboolean repscript_timer(gpointer dummy)
{
(void)dummy;

static gboolean run_once = FALSE;

if(run_once == FALSE) /* avoid any race conditions with the toolkit for uninitialized data */
        {
        run_once = TRUE;
        return(TRUE);
        }

if((GLOBALS->repscript_name) && (!GLOBALS->tcl_running))
	{
	int tclrc;
	int nlen = strlen(GLOBALS->repscript_name);
	char *tcl_cmd = wave_alloca(8 + nlen + 1 + 1);
        strcpy(tcl_cmd, "source {");
        strcpy(tcl_cmd+8, GLOBALS->repscript_name);
        strcpy(tcl_cmd+8+nlen, "}");

	GLOBALS->tcl_running = 1;
	tclrc = Tcl_Eval (GLOBALS->interp, tcl_cmd);
	GLOBALS->tcl_running = 0;
#if WAVE_TCL_CHECK_VERSION(8,5,0)
	if(tclrc != TCL_OK) {
	  Tcl_Obj *options;
	  Tcl_Obj *key;
	  Tcl_Obj *stackTrace;

	  fprintf(stderr, "GTKWAVE | %s\n",
		  Tcl_GetStringResult (GLOBALS->interp));
	  options = Tcl_GetReturnOptions(GLOBALS->interp, tclrc);
	  key = Tcl_NewStringObj("-errorinfo", -1);
	  Tcl_IncrRefCount(key);
	  Tcl_DictObjGet(NULL, options, key, &stackTrace);
	  Tcl_DecrRefCount(key);
	  fprintf(stderr, "TCL Stack Trace\n%s\n",
		  Tcl_GetStringFromObj(stackTrace, NULL)) ;
	  /* Do something with stackTrace */
	}
#else
	if(tclrc != TCL_OK) { fprintf (stderr, "GTKWAVE | %s\n", Tcl_GetStringResult (GLOBALS->interp)); }
#endif

	return(TRUE);
	}
	else
	{
	return(FALSE);
	}
}


void set_globals_interp(char *me, int install_tk)
{
#ifdef WAVE_TCL_STUBIFY
  if(NpCreateMainInterp(me, install_tk))
	{
	GLOBALS->interp = NpGetMainInterp();
	}
	else
	{
	fprintf(stderr, "GTKWAVE | Error, failed to find Tcl/Tk runtime libraries.\n");
	fprintf(stderr, "GTKWAVE | Set the environment variable TCL_PLUGIN_DLL to point to\n");
	fprintf(stderr, "GTKWAVE | the Tcl shared object file.\n");
	exit(255);
	}
#else
(void)me;
(void)install_tk;
GLOBALS->interp = Tcl_CreateInterp();
#endif
}


void make_tcl_interpreter(char *argv[])
{
  int i;
  char commandName[32768];
  gtkwave_mlist_t *ife;
  int num_menu_items;
#if !((defined(__MACH__) && defined(__APPLE__)))
  int n = 0;
#endif

#ifndef WAVE_TCL_STUBIFY
  Tcl_FindExecutable(argv[0]);
#endif
#if (defined(__MACH__) && defined(__APPLE__))
  {
  uint32_t size = sizeof(commandName);
  if(_NSGetExecutablePath(commandName, &size) == 0)
	{
	set_globals_interp(commandName, 0);
	}
	else
	{
	char *p = calloc_2(1, size+1);
	size++;
  	if(_NSGetExecutablePath(p, &size) == 0)
		{
		set_globals_interp(p, 0);
		}
		else
		{
		fprintf(stderr, "GTKWAVE | Problem with _NSGetExecutablePath, exiting.\n");
		exit(255);
		}
	free_2(p);
	}
  }
#else
#ifdef WIN32
  if(!GetModuleFileName(NULL, commandName, 256))
    n = -1 ;
#else
  n = readlink("/proc/self/exe", commandName, 256) ;
#endif
  if(n == -1) {
    fprintf(stderr, "GTKWAVE | Tcl_Init error: Failed to get my fullpath\n");
    exit(EXIT_FAILURE);
  } else {
    commandName[n] = '\0' ;
  }
  set_globals_interp(commandName, 0);
#endif

#ifndef WAVE_TCL_STUBIFY
  if (TCL_OK != Tcl_Init(GLOBALS->interp))
    {
      fprintf(stderr, "GTKWAVE | Tcl_Init error: %s\n", Tcl_GetStringResult (GLOBALS->interp));
      exit(EXIT_FAILURE);
    }
#endif

  strcpy(commandName, "gtkwave::");

  ife = retrieve_menu_items_array(&num_menu_items);
  for(i=0;i<num_menu_items;i++)
    {
      if(ife[i].callback)
	{
	  char *pnt = commandName + 9;
	  strcpy(pnt, ife[i].path);
	  while(*pnt)
	    {
	      if(*pnt==' ') *pnt='_';
	      pnt++;
	    }

	  Tcl_CreateObjCommand(GLOBALS->interp, commandName,
			       (Tcl_ObjCmdProc *)menu_func,
			       (ClientData)(ife+i), (Tcl_CmdDeleteProc *)NULL);
	}
    }


  for (i = 0; gtkwave_commands[i].func != NULL; i++)
    {
      strcpy(commandName + 9, gtkwave_commands[i].cmdstr);

      Tcl_CreateObjCommand(GLOBALS->interp, commandName,
                (Tcl_ObjCmdProc *)gtkwave_commands[i].func,
			   (ClientData)NULL, (Tcl_CmdDeleteProc *)NULL);
    }

  declare_tclcb_variables(GLOBALS->interp);

  if(GLOBALS->repscript_name)
    {
      FILE *f = fopen(GLOBALS->repscript_name, "rb");
      if(f)
	{
	  fclose(f);
	  g_timeout_add(GLOBALS->repscript_period, repscript_timer, NULL);
	}
      else
	{
	  fprintf(stderr, "GTKWAVE | Could not open repscript '%s', exiting.\n", GLOBALS->repscript_name);
	  perror("Why");
	  exit(255);
	}
    }

  init_setvar_timer(GLOBALS->interp);
}


/* blocking version which keeps recursive setvars from happening */
const char *gtkwavetcl_setvar(const char *name1, const char *val, int flags)
{
const char *rc = NULL;
if(GLOBALS->interp && !GLOBALS->in_tcl_callback)
        {
	GLOBALS->in_tcl_callback = 1;
        rc = Tcl_SetVar(GLOBALS->interp, name1, val, flags);
	GLOBALS->in_tcl_callback = 0;
        }

return(rc);
}

/* version which would be used, for example by timer interrupts */
const char *gtkwavetcl_setvar_nonblocking(const char *name1, const char *val, int flags)
{
const char *rc = NULL;
if(GLOBALS->interp)
        {
        rc = Tcl_SetVar(GLOBALS->interp, name1, val, flags);
        }

return(rc);
}


#else

void make_tcl_interpreter(char *argv[])
{
(void)argv;

/* nothing */
}


const char *gtkwavetcl_setvar(const char *name1, const char *val, int flags)
{
(void)name1;
(void)val;
(void) flags;

return(NULL);
}


const char *gtkwavetcl_setvar_nonblocking(const char *name1, const char *val, int flags)
{
(void) name1;
(void) val;
(void) flags;

return(NULL);
}


char *rpc_script_execute(const char *nam)
{
(void) nam;

return(strdup_2("--script TCL_ERROR : Tcl support not compiled into gtkwave\n"));
}


#endif


