/****************************************************************
 *								*
 *  xcol - program to edit color-name-occurrences in textfiles	*
 *								*
 *	     Written by Helmut Hoenig, Feb. 1990.		*
 *	    University of Kaiserslautern, Germany		*
 *								*
 *	     email: hoenig@informatik.uni-kl.de			*
 *								*
 ****************************************************************/

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>

#include <sys/param.h>
#include <stdio.h>
#include <strings.h>
#include <ctype.h>

#include "patchlevel.h"

/*----------------------------------------------------------------------------*/

#define	COLOR_FONT	"-adobe-times-bold-r-normal--14-*-*-*-p-*-iso8859-1"
#define	TEXT_FONT	"-adobe-times-bold-r-normal--12-*-*-*-p-*-iso8859-1"
#define DEFAULT_FONT	"-*-*-*-R-*-*-*-120-*-*-*-*-ISO8859-1"

#ifdef SOLARIS
#define RGB_FILE	"/usr/openwin/lib/rgb.txt"
#else
#define	RGB_FILE	"/usr/lib/X11/rgb.txt"
#endif

/* MAX_??? - definitions are used in the definition of some local arrays, 
   where I didn't want to make complicated memory allocation.		*/

#define	MAX_CELLS	256	/* number of cells, used by the programm*/

#define	MAX_COLS	1000	/* colors in rgb.txt-file		*/
#define	MAX_COLOR_LINES	400	/* color occurrences in a textfile	*/
#define	MAX_LINE_LENGTH	1000	/* linelength of textfile		*/

/*----------------------------------------------------------------------------*/

/* switch on/off some bugs ?? */
#define	CHG_TEXT_WINDOW_COLORMAP
#define _RECOLOR_CURSOR

/* variables for parameters (default is set) */
int		reverse	= 0;
int		BO_def	= 0;
int		darkness=50;
int		gran	=11;
int		original= 0;

/* X-Data */
Display		*display;
Screen		*screen;
XFontStruct	*finfo;
GC		gc;
int		cells;

/* the grey-pixmap is used to get an impression of mixing the foreground
 * and background color of a color-line.
 */
#define grey_width 16
#define grey_height 16
static char grey_bits[] = {
   0x55, 0x55, 0xaa, 0xaa, 0x55, 0x55, 0xaa, 0xaa, 
   0x55, 0x55, 0xaa, 0xaa, 0x55, 0x55, 0xaa, 0xaa, 
   0x55, 0x55, 0xaa, 0xaa, 0x55, 0x55, 0xaa, 0xaa, 
   0x55, 0x55, 0xaa, 0xaa, 0x55, 0x55, 0xaa, 0xaa};
Pixmap	grey_pixmap;

Cursor	mw_cursor;	/* Cursor in main-window			*/
Cursor	tw_cursor;	/* Cursor in text-window			*/
Cursor	subsel_cursor;	/* Cursor during selection of associated colors */
Cursor	text_cursor;	/* Cursor while looking in the source-file	*/
Cursor	quest_cursor;	/* Cursor while asking for write-confirmation	*/
Cursor	updown_cursor;	/* Cursor while making association		*/

/* Mainwindow-Data */
Window		mw;	/* Window-Id */
unsigned int	dx, dy;	/* current size of the window */
int		BO=14;	/* current size of the color-boxes in the window */

/* Textwindow-Data */
Window		tw = NULL;
XFontStruct	*tinfo;			/* Structure of the textfont */
int		char_width, char_height;/* char-size depending on the font */
char		*tw_message[2];		/* messages in header and footer-line */
int		twidth;			/* width in pixel */
int		tw_lines;		/* height in lines (header&footer not included) */
int		act_line=0;		/* active-line */
int		act_offset=0;		/* first color-line (when not all lines fit in the window) */

/* Subwindow-Data (little window for selected color) */
Window		subw;		/* Window-Id */
int		sdx, sdy;	/* current size of subwindow */
int		subcols;	/* number of associated colors in the subwindow */

/* Color-Management */
Colormap	my_map=NULL;
int		my_pixel[MAX_CELLS];	/* flag: 0 = pixel not used in my program */
int		only_mine;	/* flag: 1 = allocations only in own map */

#define	NotAllocated	(-1L)
#define	Undefined	(-2L)

typedef	struct _color_def {
	int			r, g, b;	/* rgb-value from the 'rbg.txt'-file */
	char			*name;	/* name from the 'rgb.txt'-file */
	int			x, y;	/* position in main window	*/
	unsigned long		pixel;	/* allocated pixel for the color */
	struct	_color_def	*associated;	/* colors with different intensity (e.g. 'blue' -> 'blue4' ... ) */
	struct	_color_def	*brother;	/* colors with different name (e.g. 'OldLace' -> 'old lace') */
} ColorDef;

typedef	struct _color_strings {
	char		*name;
	int		length;
	ColorDef	*def;
} ColorString;

ColorDef	*color[MAX_COLS];	/* field with the definitions of the colors */
ColorString	cname[MAX_COLS];	/* sorted field with the names of all colors */
ColorString	*first[256];		/* 'Hash-Table', points to the first name
					   that starts with the given letter. */

int	color_names;	/* number of color-names
			   = entries in the 'rgb.txt'-file */
int	color_number;	/* number of 'different' colors
			   = number of colors shown in the cube */

#define	BLACK_STRING	"black"
#define	WHITE_STRING	"white"
#define	GREY_STRING	"gray"

ColorDef std_black = {   0,   0,   0, BLACK_STRING };
ColorDef std_white = { 255, 255, 255, WHITE_STRING };
ColorDef std_grey  = { 192, 192, 192, GREY_STRING  };

unsigned long	white, black, grey;	/* pixel-values (in my_map !!) */
ColorDef *back_def;			/* default background in textwindow */

/*----------------------------------------------------------------------------*/

typedef	struct	_text_line {	/* structure to store the text-file */
	char			*contents;
	struct	_text_line	*next;
	struct	_text_line	*previous;
} TextLine;

typedef	struct	_color_line {	/* structure for a line with color-entry */
	TextLine	*line;		/* pointer to the textline */	
	ColorDef	*color;		/* the color defined in the line */
	ColorDef	*help;		/* a help-color for the background */
	int		reverse;	/* flag to display line in reverse */
	int		pos_in_line;	/* position of the color-name in the line */
	int		assoc;		/* corresponding color-line, when a
					   'foreground-background'-association
					   was found (or entered) */
} ColorLine;

char		*file_name;		/* name of the text-file */
TextLine	*loaded_text;		/* Pointer to the first line */
ColorLine	line[MAX_COLOR_LINES];	/* array of the color-lines */
int		color_lines;		/* number of the color-lines */
int		longest_colored;	/* length of the longest color-line */

char	space_char[256];		/* flag, which chars should be recognized
					   as spaces */


/********************************************************************************
 *	    parsing the text-file (not very efficient, but who cares)		*
 ********************************************************************************/


void set_default_spaces()
{
	memset( space_char, 0, (int)sizeof(space_char) );
	space_char[ ' ' ] = space_char[ '\t' ] = space_char[ '\n' ] = 
	space_char[ ':' ] = space_char[ '"' ] = 1;
}

#ifdef notdef
int strncase_equal( s1, s2, n )
	register char	*s1;
	register char	*s2;
	register int	n;
/* the function is NOT correct, since just proves, if strings are equal
   when the 5th bit of their characters is ignored.
 */
{
	while(n--)
	{	if ( ((*s1++)^(*s2++))&((unsigned char)~32) )	return(1);
	}
	return(0);
}

int strcase_equal( s1, s2 )
	char	*s1,*s2;
{
int	n1,n2;

	n1 = strlen(s1);
	n2 = strlen(s2);
	if (n1!=n2)	return(-1);
	else		return(strncase_equal( s1, s2, n1 ));
}
#else
#define strncase_equal(s1,s2,n) strncasecmp(s1,s2,n)
#define strcase_equal(s1,s2) strcasecmp(s1,s2)
#endif
     
string_exchange( cline, new_color )
	ColorLine	*cline;
	ColorDef	*new_color;
/* replaces the color-string in a color-line */
{
char	new_line[MAX_LINE_LENGTH];
int	pos;

	pos = cline->pos_in_line;
	strncpy( &new_line[0], cline->line->contents, pos );
	strcpy( &new_line[pos], new_color->name );
	strcat( new_line, &cline->line->contents[pos+strlen(cline->color->name)] );
	free( cline->line->contents );
	cline->line->contents = (char *)malloc((unsigned) strlen( new_line )+1 );
	strcpy( cline->line->contents, new_line );
}

void check_colors( in_line )
	TextLine	*in_line;
/* checks, if a color exists in the given line and possibly adds
 * the line to the list of color-lines.
 */
{
int		i, c;
char		lett;
char		check;
ColorString	*col;
char		*act_line = in_line->contents;

	c=strlen(act_line);
	while(c>0)
	{   do
	    {	c--;
		if (space_char[act_line[c-1]])	break;
	    }
	    while(c>1);

	    lett = act_line[c];
	    if (isalpha(lett))
	    {
	    	if (isupper(lett))	lett = tolower( lett );
	/* compares both cases */
		for (i=0;i<2;i++)
		{    for (col=first[lett];col<first[lett+1];col++)
		    {
		    	check = act_line[c+col->length];
			if (space_char[check])
			{	if ((strncase_equal( &act_line[c], col->name, col->length )==0 ))
				{	line[color_lines].line = in_line;
					line[color_lines].color= col->def;
					line[color_lines].help = back_def;
					line[color_lines].pos_in_line = c;
					line[color_lines].reverse = 0;
					line[color_lines].assoc = -1;

					if (original)
					{	string_exchange( &line[color_lines], col->def );
					}
					if (XTextWidth( tinfo, act_line, strlen(act_line) )>longest_colored)
						longest_colored = XTextWidth( tinfo, act_line, strlen(act_line) );
					color_lines++;
					return;
				}
			}
		    }
		    lett = toupper( lett );
		}
	    }
	}
}

int spc_line( search_string, line, name, length )
	char		*search_string;
	TextLine	*line;
	char		*name;
	int		*length;
/* searches for the search-string in the given line. If it exists, the name
 * in front of the search-string is copied to name and its length is returned.
 */
{
int	c, sl, cs;

	c = strlen( line->contents );
	sl = strlen(search_string);
	while( c>0 )
	{	if (strncase_equal( search_string, &line->contents[c], sl )==0)
		{	cs=c;
			do
			{	cs--;
				if (isspace(line->contents[cs]))	break;
			}
			while(cs>0);
			*length = c-cs;
			if (cs)
			{	(*length)--;	cs++;
			}

			strncpy( name, &line->contents[cs], *length );
			name[*length]='\0';
			return(1);
		}
		c--;
	}
	return(0);
}

void find_assocs()
/* tries to find 'Foreground-Background'-Associations in the Color-Lines */
{
int	i, j;
char	name1[100], name2[100];
int	length1, length2;

	for (i=0;i<color_lines;i++)
	{	if (spc_line("foreground", line[i].line, name1, &length1))
		{	for (j=i+1;j>=i-1;j-=2)
			{   if ((j>=0)&&(j<color_lines))
			    {	if (spc_line("background", line[j].line, name2, &length2))
				{
					if (strcmp(name1, name2)==0)
					{	line[i].reverse=0;
						line[i].help = line[j].color;
						line[i].assoc= j;

						line[j].reverse=1;
						line[j].help = line[i].color;
						line[j].assoc= i;
					}
				}
			    }
			}
		}
		else if ((spc_line("background", line[i].line, name1, &length1))&&(line[i].assoc<0))
		{	for (j=i+1;j>=i-1;j-=2)
			{   if ((j>=0)&&(j<color_lines))
			    {	if (spc_line("border", line[j].line, name2, &length2))
				{
					if (strcmp(name1, name2)==0)
					{	line[i].reverse=1;
						line[i].help = line[j].color;
						line[i].assoc= j;

						line[j].reverse=0;
						line[j].help = line[i].color;
						line[j].assoc= i;
					}
				}
			    }
			}
		}
	}
}

int load_file( fname )
	char	*fname;
/* loads the text-file and tries to find color-entries in each line */
{
FILE		*fp;
TextLine	*act, *next;
char		linebuffer[MAX_LINE_LENGTH];

/* load the file with colors */
	longest_colored = 0;
	color_lines = 0;

	fp = fopen( fname, "r" );
	if (fp==NULL)
	{	fprintf( stderr, "can't load file '%s'.\n", fname );
		return(0);
	}

	loaded_text = act = NULL;
	while (fgets( linebuffer, sizeof linebuffer, fp )!=NULL)
	{	next = (TextLine *)malloc((unsigned)sizeof(TextLine));
		next->contents = (char *)malloc((unsigned)strlen(linebuffer)+1);
		strcpy( next->contents, linebuffer );

		check_colors( next );
		if (act==NULL)
		{	loaded_text=act	= next;
			next->previous	= NULL;
		}
		else
		{	act->next	= next;
			next->previous	= act;
			act = next;
		}
	}
	fclose( fp );
	act->next=NULL;
	return(1);
}

int write_file( fname )
	char	*fname;
{
FILE		*fp;
TextLine	*act;

	fp = fopen( fname, "w" );
	if (fp==NULL)	return(-1);
	act = loaded_text;
	while( act )
	{	if (fputs( act->contents, fp )==EOF)
		{	fprintf(stderr, "write error on file '%s'\n", fname);
			fclose(fp);
			return(-2);
		}
		act = act->next;
	}
	fclose( fp );
	return(0);
}


/********************************************************************************
 *		allocating, freeing, sorting, searching COLORS.			*
 ********************************************************************************/


ColorDef *find_color( name )
	char	*name;
/* searches in the tree of colordefinitions for the named color */
{
ColorDef	*erg, *assoc;
int		i;

	for (i=0;i<color_number;i++)
	{	erg = color[i];
		do
		{
			if (strcase_equal(erg->name, name)==0)	return( erg );
			assoc = erg->associated;
			while( assoc )
			{	if (strcase_equal(assoc->name, name)==0)	return( assoc );
				assoc = assoc->associated;
			}
			erg = erg->brother;
		}
		while (erg);
	}
	return(NULL);
}

void load_color( act )
	ColorDef *act;
/* allocates a pixel for the color, if it is not already allocated. When it's
 * still possible to allocate pixels in the DefaultColormap, this is done and
 * the same color is stored in both maps. If no more cells are available in
 * the DefaultColormap, the program continues working only in its own map.
 */
{
XColor	scr;
long	pixel;

	if (act->pixel==NotAllocated)
	{	if (!only_mine)
		{	if ( (!XAllocColorCells( display, DefaultColormapOfScreen(screen), True, 
				NULL, 0, (unsigned long*)&pixel, 1 )) || (pixel>=cells) )
			{
				for (pixel=0;pixel<cells;pixel++)
				{
				    if (my_pixel[pixel])
				    {	XFreeColors( display, DefaultColormapOfScreen(screen), 
				    		(unsigned long*)&pixel, 1, 0 );
				    }
				}

				only_mine=1;
#ifdef CHG_TEXT_WINDOW_COLORMAP
/* when switched on, all windows (not only the textwindow), are displayed
 * in my Colormap, when the DefaultColormap hasn't got any more cells available.
 * Try the option -gran100 and search for the 'gray'-color in the main-window
 * to reach the extremes of the program.
 */
				if (tw)
				{	XSetWindowColormap( display, tw, my_map );
				}
#endif
			}
		}
		if (only_mine)
		{	for (pixel=cells-1;pixel>=0;pixel--)
			{	if (my_pixel[pixel]==0)	break;
			}
	/* the reaction to an overflow in the color-map is hard, but
	 * I can't think of an easy solution for that problem.
	 */		if (pixel<0)
			{	fprintf( stderr, "You did it. The Colormap if full.\n");
				if (CellsOfScreen(screen)>MAX_CELLS)
				{	fprintf( stderr, "Compile the program again with the\n");
					fprintf( stderr, "MAX_CELLS-macro set to an higher value.\n");
				}
				exit(0);
			}
		}
		act -> pixel = pixel;
		scr.pixel = pixel;
		scr.flags = DoRed | DoGreen | DoBlue;
		scr.red   = act->r << 8;
		scr.green = act->g << 8;
		scr.blue  = act->b << 8;
		XStoreColor( display, my_map, &scr );
		if (!only_mine)
			XStoreColor( display, DefaultColormapOfScreen(screen), &scr );
	}
	my_pixel[act->pixel]++;
}

void unload_color( act )
	ColorDef *act;
/* The color is not used anymore. It is possible that the color was loaded
 * more than once and therefor, the cell can't be freed.
 */
{
	if (act->pixel==NotAllocated)	return;

	if ((--my_pixel[act->pixel])==0)
	{	if (!only_mine)
		{	XFreeColors( display, DefaultColormapOfScreen(screen), 
					&act->pixel, 1, 0 );
		}
		act->pixel=NotAllocated;
	}
}

void load_text_colors()
/* to load all colors used in the color-lines of the text-file */
{
int i;

	for (i=0;i<color_lines;i++)
	{	load_color( line[i].color );
		load_color( line[i].help  );
	}
}

void exchange( old, new )
	ColorDef	**old;
	ColorDef	*new;
/* correct exchanging of a loaded color */
{
	if (*old)
	{	unload_color( *old );
	}
	load_color( new );
	*old = new;
}

/*----------------------------------------------------------------------------*/

/*
 * functions to sort the colors
 */

int color_name_sort( p1, p2 )
	ColorString	*p1, *p2;
{
	return(strcmp(p1->name, p2->name));
}

int color_sort( p1, p2 )
	ColorDef	**p1, **p2;
{
char	*s1, *s2;

	if ((*p1)->b!=(*p2)->b)	return(((*p1)->b>(*p2)->b)?1:-1);
	if ((*p1)->g!=(*p2)->g)	return(((*p1)->g>(*p2)->g)?1:-1);
	if ((*p1)->r!=(*p2)->r)	return(((*p1)->r>(*p2)->r)?1:-1);
	s1 = index((*p1)->name, ' ');
	s2 = index((*p2)->name, ' ');
	s1 = s2 = NULL;

	if (s1)
	{	if (s2)	return(strcmp((*p1)->name, (*p2)->name));
		else	return(1);
	}
	else
	{	if (s2)	return(-1);
		else	return(strcmp((*p1)->name, (*p2)->name));
	}
}

int color_sort_r( p1, p2 )
	ColorDef	**p1, **p2;
{
	if ((*p1)->b!=(*p2)->b)	return(((*p1)->b>(*p2)->b)?-1:1);
	if ((*p1)->g!=(*p2)->g)	return(((*p1)->g>(*p2)->g)?-1:1);
	if ((*p1)->r!=(*p2)->r)	return(((*p1)->r>(*p2)->r)?-1:1);
	return(0);
}

void brother_sort( first )
	ColorDef	**first;
{
ColorDef *list[1000];
int	 length;

	length = 0;

	list[length] = *first;
	while(list[length]->brother)
	{	list[length+1] = list[length]->brother;
		length++;
	}

	qsort( (char *)list, length+1, sizeof(ColorDef *), color_sort );

	list[length]->brother = NULL;

	while(length>0)
	{	list[length-1]->brother = list[length];
		length--;
	}
	*first = list[0];
}

void associated_sort( first )
	ColorDef	*first;
{
ColorDef *list[1000];
int	 length;

	length = 0;

	list[length] = first;
	while(list[length]->associated)
	{	list[length+1] = list[length]->associated;
		length++;
	}

	qsort( (char *)&list[1], length, sizeof(ColorDef *), color_sort );

	list[length]->associated = NULL;

	while(length>0)
	{	list[length-1]->associated = list[length];
		length--;
	}
}

/*----------------------------------------------------------------------------*/

/*
 * functions to load the color-file and build the tree with colordefinitions
 */

char *cut_digits( name )
	char	*name;
/* cuts digits from a color-string. returns a pointer to a static string
 * which of course must not be freed. if there where no digits, the original
 * pointer is returned.
 */
{
static char short_name[40];
int i;

	i = strlen(name);
	if ((i>39)||(!isdigit(name[i-1])))	return(name);

	strcpy( short_name, name );
	i--;
	while ((i>0)&&(isdigit(short_name[i])))
	{	short_name[i] = '\0';
		i--;
	}
	return(short_name);
}

void next_name( act )
	ColorDef	*act;
/* collects color-names for the hash-table */
{
	if (act->pixel==Undefined)
	{	fprintf(stderr, "%s not defined.\n", act->name);
		return;
	}
	cname[color_names].name   = act->name;
	cname[color_names].length = strlen(act->name);
	cname[color_names].def    = act;
	color_names++;
}

ColorDef *make_entry( new )
	ColorDef *new;
/* allocates space for the data of a new color */
{
ColorDef	*erg;

	erg = (ColorDef *)malloc((unsigned) sizeof(ColorDef) );
	memcpy( (char *)erg, (char *)new, sizeof(ColorDef) );
	erg -> associated = NULL;
	erg -> brother    = NULL;
	erg -> pixel      = NotAllocated;

	return( erg );
}

void new_color( new )
	ColorDef	*new;
/* searches for overlaps in the color-field and stores the new color */
{
char		*search_name;
ColorDef	*act;
ColorDef	*help;
int		i;
int		subname = 0;

	search_name = cut_digits( new->name );
	subname = (search_name!=new->name);

	act = find_color( search_name );

	if (act)
	{	if (subname)		/* found and associated entry */
		{	help = act->associated;
			act->associated = make_entry( new );
			act->associated->associated = help;
		}
		else			/* strange: color declared twice ? */
		{	if (act->pixel==Undefined)
			{	memcpy( (char *)act, (char *)new, sizeof(ColorDef)-2*sizeof(ColorDef *) );
				act->pixel = NotAllocated;
			}
			else
			{
				help = act->brother;
				act->brother = make_entry( new );
				act->brother->brother = help;
			}
		}
	}
	else
	{	for (i=0;i<color_number;i++)
		{	if ( (new->r==color[i]->r) && (new->g==color[i]->g) && (new->b==color[i]->b) )	break;
		}
		if (i<color_number)
		{	help = color[i]->brother;
			color[i]->brother = make_entry( new );
			color[i]->brother->brother = help;
		}
		else
		{	if (subname)	/* indexed name found before original */
			{	act = (ColorDef *)malloc((unsigned) sizeof(ColorDef) );
				memset( (char *)act, (char)-1, sizeof(ColorDef) );
				act->pixel = Undefined;
				act->name  = (char *)malloc((unsigned) strlen(search_name)+1 );
				strcpy( act->name, search_name );
				act->associated = make_entry( new );
				act->brother = NULL;
				color[color_number++] = act;
			}
			else		/* found new color */
			{	color[color_number++] = make_entry( new );
			}
		}
	}
}

load_standard( act )
	ColorDef	*act;
{
XColor		exact_def,screen_def;

	if (XLookupColor( display, DefaultColormapOfScreen(screen), act->name,
		&exact_def, &screen_def ))
	{	act->r = exact_def.red   >> 8;
		act->g = exact_def.green >> 8;
		act->b = exact_def.blue  >> 8;
	}
	new_color( act );
}
		
/*----------------------------------------------------------------------------*/

update_map()
/* sets unused pixels in my_map to the colors (with different intensity)
 * of the DefaultColormap.
 */
{
XColor	qcols[MAX_CELLS];
int	i, j;

	j=0;
	for (i=0;i<cells;i++)
	{	if (my_pixel[i]==0)	qcols[j++].pixel=i;
	}
	XQueryColors( display, DefaultColormapOfScreen(screen), qcols, j );
	for (i=0;i<j;i++)
	{	qcols[i].red   = ((long)qcols[i].red  *darkness)/100;
		qcols[i].green = ((long)qcols[i].green*darkness)/100;
		qcols[i].blue  = ((long)qcols[i].blue *darkness)/100;
		qcols[i].flags = DoRed | DoGreen | DoBlue;
	}
	XStoreColors( display, my_map, qcols, j );
}

create_new_map()
/* get an own colormap */
{
int		i;
XColor		qcols[MAX_CELLS];
unsigned long	pixel[MAX_CELLS];

/* allocate new colormap and copy the darkened colors of the default-map */
	my_map = XCreateColormap( display, mw, 
			XDefaultVisualOfScreen(screen), AllocAll);
	if (my_map==(Colormap)0)
	{	fprintf(stderr, "can't create colormap.\n" );
		exit(0);
	}
	memset( (char *)my_pixel, (char)0, sizeof(my_pixel) );

/* copy the default-map */
	for (i=0;i<cells;i++)	qcols[i].pixel = i;
	XQueryColors( display, DefaultColormapOfScreen(screen), qcols, cells );
	for (i=0;i<cells;i++)
	{	qcols[i].red   = ((long)qcols[i].red  *darkness)/100;
		qcols[i].green = ((long)qcols[i].green*darkness)/100;
		qcols[i].blue  = ((long)qcols[i].blue *darkness)/100;
		qcols[i].flags = DoRed | DoGreen | DoBlue;
	}
	XStoreColors( display, my_map, qcols, cells );

/* try to allocate the color_cells in the default-map */
	if (XAllocColorCells( display, DefaultColormapOfScreen(screen), True, 
		NULL, 0, pixel, color_number ))
	{	int	high_value=0;

    /* store the colors into my map and into and into the default-colormap
     * as there are enough cells for both.
     */
		for (i=0;i<color_number;i++)
		{	qcols[i].pixel = pixel[i];
			qcols[i].flags = DoRed | DoGreen | DoBlue;
			qcols[i].red   = color[i]->r << 8;
			qcols[i].green = color[i]->g << 8;
			qcols[i].blue  = color[i]->b << 8;
			color[i]->pixel = pixel[i];
			if (pixel[i]>=cells)
			{	high_value=1;
				break;
			}
			my_pixel[pixel[i]]=1;
		}

		if (high_value)
		{	XFreeColors( display, DefaultColormapOfScreen(screen), 
				pixel, color_number, 0 );
			only_mine = 1;
		}
		else
		{
			XStoreColors( display, DefaultColormapOfScreen(screen), qcols, color_number );
			XStoreColors( display, my_map, qcols, color_number );
			only_mine = 0;
		}
	}
	else	only_mine = 1;

	if (only_mine)
	{
    /* pixels are just used from high to low numbers */
		for (i=0;i<color_number;i++)
		{
			qcols[i].pixel = cells-i-1;
			qcols[i].flags = DoRed | DoGreen | DoBlue;
			qcols[i].red   = color[i]->r << 8;
			qcols[i].green = color[i]->g << 8;
			qcols[i].blue  = color[i]->b << 8;
			color[i]->pixel = cells-i-1;
			my_pixel[cells-i-1]=1;
		}
		XStoreColors( display, my_map, qcols, color_number );

    /* afterwards, all free pixels of the default-map are allocated
     * and set to the values of the new colormap.
     */
		i=0;
		while( XAllocColorCells( display, DefaultColormapOfScreen(screen), True, 
			NULL, 0, &pixel[i], 1 ) )
		{	qcols[i].pixel = pixel[i];
			i++;
		}
		XQueryColors( display, my_map, qcols, i );
		XStoreColors( display, DefaultColormapOfScreen(screen), qcols, i );
		XFreeColors( display, DefaultColormapOfScreen(screen), pixel, i, 0 );
		only_mine = 1;
	}

/* the pixels for black, white have to be used from my_map, since
 * the entries of the DefaultColormap are possibly darkened.
 */
	black = find_color( BLACK_STRING )->pixel;
	my_pixel[black]++;
	white = find_color( WHITE_STRING )->pixel;
	my_pixel[white]++;
	grey  = find_color( GREY_STRING )->pixel;
	my_pixel[grey]++;
}

destroy_colormap()
/* not used */
{
unsigned long	pixel[MAX_CELLS];
int	i, j;
ColorDef *assoc, *erg;

	XFreeColormap( display, my_map );
	j=0;
	if (!only_mine)
	{	for (i=0;i<cells;i++)
		{	if (my_pixel[i])	pixel[j++] = i;
		}
		XFreeColors( display, DefaultColormapOfScreen(screen), pixel, j, 0 );
	}
	my_map = NULL;
	memset( (char *)my_pixel, (char)0, sizeof(my_pixel) );

	for (i=0;i<color_number;i++)
	{	erg = color[i];
		do
		{	erg->pixel = NotAllocated;
			assoc = erg->associated;
			while( assoc )
			{	assoc->pixel = NotAllocated;
				assoc = assoc->associated;
			}
			erg = erg->brother;
		}
		while (erg);
	}
}


/********************************************************************************
 *			    Textwindow-Functions				*
 ********************************************************************************/


void draw_message( num )
	int	num;
/* draws upper (0) or lower (1) message-entry */
{
int	y;

	y = (num)?(tw_lines+1)*char_height:0;

	XClearArea( display, tw, 0, y, twidth, char_height, False );
	XSetForeground( display, gc, white );
	if (tw_message[num])
	{	XSetFont( display, gc, tinfo->fid );
		XDrawString( display, tw, gc, 
			0, y+tinfo->ascent, 
			tw_message[num], strlen( tw_message[num] ) );
	}
	if (num)	XDrawLine( display, tw, gc, 0, y, twidth, y );
	else		XDrawLine( display, tw, gc, 0, char_height-1, twidth, char_height-1 );
}

void set_message( msg, num )
	char	*msg;
	int	num;
/* sets upper or lower message entry */
{
	if (tw_message[num])		free(tw_message[num]);
	if (msg)
	{	tw_message[num] = (char *)malloc((unsigned) strlen(msg)+1 );
		strcpy( tw_message[num], msg );
	}
	else	tw_message[num]=NULL;

	draw_message( num );
}

void reset_messages()
{
char	mess[20];

	if (act_offset)
	{	sprintf( mess, "*** UP (%d) ***", act_offset);
		set_message( mess, 0 );
	}
	else	set_message( "  click right button here to write file.", 0 );

	if (act_offset+tw_lines<color_lines)
	{	sprintf( mess, "*** DOWN (%d) ***", color_lines-act_offset-tw_lines);
		set_message( mess, 1 );
	}
	else	set_message( (char *)NULL, 1 );
}

void draw_string( i )
	int	i;
/* draws a color-line */
{
int	j;
int	y;
int	p1,p2;
unsigned long	fg_pixel, bg_pixel, help;

	j=i-act_offset;
	if ((tw==(Window)0)||(j<0)||(j>=tw_lines))	return;

	y=(j+1)*char_height;

	fg_pixel = line[i].color->pixel;
	if (fg_pixel==NotAllocated)
	{	fprintf(stderr, "fg_pixel of %s not allocated.\n", line[i].line->contents);
	}
	bg_pixel = line[i].help->pixel;
	if (bg_pixel==NotAllocated)
	{	fprintf(stderr, "bg_pixel of %s not allocated.\n", line[i].line->contents);
	}

	if (line[i].reverse)
	{	help     = fg_pixel;
		fg_pixel = bg_pixel;
		bg_pixel = help;
	}

/* clearing the background */
	XSetForeground(display, gc, bg_pixel);
	XFillRectangle( display, tw, gc, 0, y, twidth, char_height);

	XSetFont( display, gc, tinfo->fid );
	XSetForeground( display, gc, fg_pixel );

	p1 = line[i].pos_in_line;
	p2 = p1 + strlen( line[i].color->name );
	p1 = XTextWidth( tinfo, line[i].line->contents, p1 )+2*char_width;
	p2 = XTextWidth( tinfo, line[i].line->contents, p2 )+2*char_width;

	XDrawString( display, tw, gc, 
		2*char_width, y+tinfo->ascent, 
		line[i].line->contents, strlen(line[i].line->contents)-1 );

	XSetForeground( display, gc, fg_pixel );
	XDrawLine( display, tw, gc, p1, y+char_height-2, p2, y+char_height-2 );	

/* draws the pixmap (at least some planes of it) */
	XSetState( display, gc, (unsigned long)AllPlanes, (unsigned long)0,
				GXxor, fg_pixel^bg_pixel );
	XCopyArea( display, grey_pixmap, tw, gc, 0, 0, char_width, char_height, twidth-3*char_width, y );
	XSetState( display, gc, fg_pixel, bg_pixel,
				GXcopy, (unsigned long)AllPlanes );
	
	if (i==act_line)
	{	XDrawRectangle( display, tw, gc, 0, y, twidth-1, char_height-1);
		XFillRectangle( display, tw, gc, 0, y, 2*char_width, char_height);
		XFillRectangle( display, tw, gc, twidth-2*char_width, y, 2*char_width, char_height);
		XSetForeground( display, gc, bg_pixel );
	}
	else	XSetForeground( display, gc, fg_pixel );

/* drawing indicator 'bg', 'fg', or 'R' */
	if (line[i].reverse)
	{	if (line[i].assoc>=0)
		{	XDrawString( display, tw, gc, 0, y+tinfo->ascent, "bg", 2 );
		}
		else
		{	XSetForeground( display, gc, black );
			XSetBackground( display, gc, white );
			XDrawImageString( display, tw, gc, 0, y+tinfo->ascent, "R", 1 );
		}
	}
	else
	{	if (line[i].assoc>=0)
			XDrawString( display, tw, gc, 0, y+tinfo->ascent, "fg", 2 );
	}
}

void change_color( act_line, new_color )
	int		act_line;
	ColorDef	*new_color;
/* complete change of the color in the given line */
{
	if (act_line<0)	return;

/* change the string */
	string_exchange( &line[act_line], new_color );

/* allocate the color */
	exchange( &line[act_line].color, new_color );

/* update the window (exchange corresponding color too) */
	draw_string(act_line);
	if (line[act_line].assoc>=0)
	{	exchange( &line[line[act_line].assoc].help, new_color );
		draw_string(line[act_line].assoc);
	}
}

redraw_tw( l1, l2 )
	int	l1, l2;
/* redraws parts of the text-window */
{
int	i;

	if (l1==0)
	{	draw_message( 0 );
		l1++;
	}
	if (l2>tw_lines)
	{	draw_message( 1 );
		l2=tw_lines;
	}
	for (i=l1;i<=l2;i++)	draw_string( act_offset+i-1 );
}

/*----------------------------------------------------------------------------*/

void show_string( row, text )
	int		row;
	TextLine	*text;
{
	if (text)
	{	XSetForeground( display, gc, white );
		XFillRectangle( display, tw, gc, 0, row*char_height, twidth, char_height );
		XSetForeground( display, gc, black );
		XDrawString( display, tw, gc, 
			2*char_width, row*char_height+tinfo->ascent, 
			text->contents, strlen(text->contents) );
	}
	else
	{	XSetForeground( display, gc, black );
		XFillRectangle( display, tw, gc, 0, row*char_height, twidth, char_height );
	}
}

void show_text( i )
	int	i;
/* shows the part of the text, where line i occurred. The old contents of the
 * text-window is stored in a pixmap if possible.
 */
{
TextLine	*upper,*lower;
int		upper_i,lower_i;
XEvent		event;
Pixmap		back;

	back = XCreatePixmap( display, tw, twidth, (tw_lines+2)*char_height, DefaultDepthOfScreen(screen) );
	if (back)
	{	XCopyArea( display, tw, back, gc, 
			0, 0, twidth, (tw_lines+2)*char_height, 
			0, 0 );
	}
	
	XGrabPointer(display, tw, False, 
		ButtonPressMask|ButtonReleaseMask, GrabModeAsync, GrabModeAsync, 
		tw, text_cursor, CurrentTime );
	XSetFont( display, gc, tinfo->fid );

	upper   = line[i].line;
	upper_i = i-act_offset+1;
	XSetForeground( display, gc, grey );
	XFillRectangle( display, tw, gc, 0, upper_i*char_height, twidth, char_height );
	XSetForeground( display, gc, black );
	XDrawString( display, tw, gc, 
		2*char_width, upper_i*char_height+tinfo->ascent, 
		upper->contents, strlen(upper->contents) );

	lower = upper;
	upper = upper;
	lower_i = upper_i + 1;
	upper_i--;

	while( ( upper_i>=0 )||(lower_i<=tw_lines+1) )
	{	if ( upper_i>=0 )
		{	if (upper)	upper=upper->previous;
			show_string( upper_i--, upper );
		}
		if (lower_i<=tw_lines+1)
		{	if (lower)	lower=lower->next;
			show_string( lower_i++, lower );
		}
	}

	XWindowEvent( display, tw, ButtonPressMask|ButtonReleaseMask, &event );

	XUngrabPointer( display, CurrentTime );
	if (back)
	{	XCopyArea( display, back, tw, gc, 
			0, 0, twidth, (tw_lines+2)*char_height, 
			0, 0 );
		XFreePixmap( display, back );
	}
	else	redraw_tw( 0, tw_lines+1 );
}

/*----------------------------------------------------------------------------*/

void write_quest( num )
	int	num;
/* waits for a confirmation before the file is written with backup. */
{
XEvent	event;

	set_message( "  confirm writing with right button !", num );
	XBell( display, 10 );

	XGrabPointer(display, tw, False, 
		ButtonPressMask|LeaveWindowMask, GrabModeAsync, GrabModeAsync, None, quest_cursor, CurrentTime );
	XWindowEvent( display, tw, ButtonPressMask|LeaveWindowMask, &event );

	if ((event.xany.type==ButtonPress)&&(event.xbutton.button==Button3))
	{	char	back_name[MAXPATHLEN];

		strcpy( back_name, file_name );
		strcat( back_name, "~" );

		if (rename( file_name, back_name ))
		{	set_message("  can't create backup. writing aborted.", num);
		}
		else
		{	if (write_file( file_name ))
				set_message("  writing failed.", num);
			else	set_message("  file written with backup.", num);
		}
	}
	else set_message("  writing aborted.", num);

	XUngrabPointer( display, CurrentTime );
}

/*----------------------------------------------------------------------------*/

void new_assoc( act )
	int	act;
/* the function waits, until the button is released or the mouse is moved
 * into an associated line. If both lines were already connected, they get
 * disconnected. If no line was connected, the first line will get the
 * foreground and the second line the background of the association.
 */
{
XEvent	event;
int	new;

	if (line[act].assoc>=0)
	{	set_message( "  move to associated line to disconnect.", 0 );
	}
	else	set_message( "  move to background to connect.", 0 );

	XGrabPointer(display, tw, False, 
		ButtonReleaseMask|PointerMotionMask, GrabModeAsync, GrabModeAsync, tw, updown_cursor, CurrentTime );

	new = act;
	do
	{	XWindowEvent( display, tw, ButtonReleaseMask|PointerMotionMask, &event );
		if (event.xany.type==MotionNotify)
		{	new = (event.xmotion.y/char_height)+act_offset-1;
			if (new!=act)	break;
		}
	}
	while( event.xany.type!=ButtonRelease );

	if (new==act)
	{	if (line[act].assoc<0)
		{	line[act].reverse^=1;
		}
		else
		{	ColorDef	*help;

			help = line[act].color;
			change_color( act, line[line[act].assoc].color );
			change_color( line[act].assoc, help );
		}
		reset_messages();
	}
	else
	{	if (line[act].assoc<0)
		{	if (line[new].assoc<0)
			{	set_message( "  new association set.", 0 );
				XBell( display, 5 );

				line[act].assoc   = new;
				exchange( &line[act].help, line[new].color );
				line[act].reverse = 0;

				line[new].assoc   = act;
				exchange( &line[new].help, line[act].color );
				line[new].reverse = 1;

				draw_string( new );
				draw_string( act );
			}
			else
			{	set_message( "  line already connected.", 0 );
				XBell( display, 10 );
			}
		}
		else
		{	if (line[act].assoc==new)
			{	set_message( "  disconnected.", 0 );
				XBell( display, 5 );
				line[act].assoc = -1;
				line[new].assoc = -1;
				draw_string( new );
				draw_string( act );
			}
			else
			{	set_message( "  lines not associated.", 0 );
				XBell( display, 10 );
			}
		}
	}

	while (event.xany.type!=ButtonRelease)
	{	XWindowEvent( display, tw, ButtonReleaseMask, &event );
	}
	XUngrabPointer( display, CurrentTime );
}

/*----------------------------------------------------------------------------*/

void tw_event( event )
	XEvent	*event;
/* event-execution for the text-window */
{
    switch( event->xany.type )
    {
	case ConfigureNotify:
	{	XEvent	help;
		int	tw_new;
	
		tw_new = (event->xconfigure.height/char_height)-2;
		if ((twidth==event->xconfigure.width)&&(tw_lines==tw_new))	break;

		twidth   = event->xconfigure.width;
		tw_lines = tw_new;

		if (tw_lines>color_lines)	tw_lines=color_lines;
		if (act_offset+tw_lines>color_lines)
			act_offset = color_lines-tw_lines;

		redraw_tw( 0, tw_lines+1 );
		XSync(display, 0);
		while( XCheckWindowEvent( display, tw, ExposureMask, &help ) );

		break;
	}

	case KeyPress:
	{	char	sign;
		KeySym	keysym;

	   	XLookupString(&event->xkey, &sign, 1, &keysym, NULL);
		if (sign=='q')	exit(0);
		break;
	}

	case Expose:
	{	XEvent	help;
		int	first, first2;
		int	last, last2;

		first = event->xexpose.y/char_height;
		last  = (event->xexpose.y + event->xexpose.height)/char_height+1;

		while( XCheckWindowEvent( display, tw, ExposureMask, &help ) )
		{	first2 = help.xexpose.y/char_height;
			last2  = (help.xexpose.y + help.xexpose.height)/char_height+1;

			if (first2 < first)	first = first2;
			if (last2  > last)	last  = last2;
		}
		redraw_tw( first, last );
		break;
	}

	case ButtonPress:
	{	int	i;
		int	old_act;
		int	offset_delta;

		i= (event->xbutton.y / char_height)-1;

	/* has the header or footer of the textwindow been selected ? */
		if (i<0)
		{	if (event->xbutton.button==Button3)
			{	write_quest(0);
				return;
			}
			else if (act_offset)
			{	offset_delta=(event->xbutton.button==Button1)?1:10;
				if (act_offset-offset_delta<0)
					offset_delta = act_offset;
				act_offset -= offset_delta;
				XCopyArea( display, tw, tw, gc,
					0, char_height, twidth, (tw_lines-offset_delta)*char_height,
					0, (offset_delta+1)*char_height );
				redraw_tw( 1, offset_delta+1 );
			}
			else	XBell(display, 10);
			break;
		}
		if (i>=tw_lines)
		{	if (event->xbutton.button==Button3)
			{	write_quest(1);
				return;
			}
			else if (act_offset+tw_lines<color_lines)
			{	offset_delta=(event->xbutton.button==Button1)?1:10;
				if (act_offset+offset_delta>color_lines-tw_lines)
					offset_delta = color_lines-act_offset-tw_lines;
				act_offset += offset_delta;
				XCopyArea( display, tw, tw, gc,
					0, (offset_delta+1)*char_height,
					twidth, (tw_lines-offset_delta)*char_height,
					0, char_height );
				redraw_tw( tw_lines-offset_delta+1, tw_lines );
			}
			else	XBell(display, 10);
			break;
		}
		i+=act_offset;

	/* action on a textline depending on the button */
		switch(event->xbutton.button)
		{
		case Button2:
			old_act  = act_line;
			act_line = -1;
			draw_string( old_act );
			new_assoc( i );
			act_line=i;
			draw_string( act_line );
			return;

		case Button1:
			old_act = act_line;
			act_line = i;
			draw_string( old_act );
			draw_string( act_line );
			break;

		case Button3:
			old_act = act_line;
			act_line = i;
			draw_string( old_act );
			show_text( act_line );
			draw_string( act_line );
			break;
		}
	} /* end case ButtonPress */
    } /* end switch(type) */
    reset_messages();
}

/*----------------------------------------------------------------------------*/

Window create_textwindow( fname )
	char	*fname;
{
Window			tw;
XSizeHints		hints;
XWMHints		normal_hints;
XClassHint		class_hints;

static char *iname = "TextView";
char	wname[MAXPATHLEN+10];
char	*name_ptr;

/* parsing the geometry */ 
	hints.flags = PSize | PMaxSize | PMinSize | PResizeInc;

	if (color_lines<4)	hints.min_height = (color_lines+2) * (char_height);
	else			hints.min_height = 7 * (char_height);
	hints.min_width = 20 * char_width;

	if (color_lines>50)	tw_lines = 25;
	else			tw_lines = color_lines;
	hints.width = longest_colored+ 6 * char_width;
	hints.height= (tw_lines+2) * (char_height);
	act_offset = 0;

	hints.max_height = (color_lines+2) * (char_height);
	hints.max_width = 2*(longest_colored);

	hints.width_inc  = char_width;
	hints.height_inc = char_height;

	twidth = hints.width;

/* names */
	name_ptr = rindex( fname, '/' );
	if (name_ptr)	fname = name_ptr+1;

	sprintf( wname, "TextView '%s'", fname );
	name_ptr = wname;

	tw = XCreateSimpleWindow( display, RootWindowOfScreen(screen), 
		0, 0, hints.width, hints.height, 3, grey, black );

/* class-hints */
	class_hints.res_name = "xcol";
	class_hints.res_class= "XCol";

/* normal-hint */
	normal_hints.flags = StateHint | WindowGroupHint;
	
	normal_hints.initial_state = NormalState;
	normal_hints.window_group  = mw;

#ifdef XTextProperty
{	
XTextProperty		window_name, icon_name;

	XStringListToTextProperty( &name_ptr, 1, &window_name );
	XStringListToTextProperty( &iname, 1, &icon_name );
	
	XSetWMProperties( display, tw, &window_name, &icon_name, (char **)0, 0, 
				&hints, &normal_hints, &class_hints );

	XFree( (char *)window_name.value );
	XFree( (char *)icon_name.value );
}
#else
	XSetStandardProperties( display, tw, name_ptr, iname, None, (char **)0, 0, &hints );
	XSetClassHint( display, tw, &class_hints );
#endif

	XSelectInput( display, tw, 
		ExposureMask | ButtonPressMask | StructureNotifyMask |
		KeyPressMask | EnterWindowMask | LeaveWindowMask );
	XDefineCursor( display, tw, tw_cursor );
	XMapRaised( display, tw );
	return( tw );
}


/********************************************************************************
 *			    Subwindow-Functions					*
 ********************************************************************************/


void destroy_subwindow( i )
	int	i;
/* destroys the tiny subwindow and its associated colors */
{
ColorDef	*act;

	act  = color[i];
	while( act->associated )
	{	act = act->associated;
		unload_color( act );
	}
	XDestroyWindow( display, subw );
}

void create_subwindow( i )
	int	i;
/* creates, maps and draws a tiny information-window for the given color */
{
XSetWindowAttributes    attrib;
int	x, y;
int	subs;
int	j;
ColorDef	*shades[MAX_COLS];
ColorDef	*act;
int	bord=6;

/* what associated color should be shown ? */
	act = color[i];
	subs = 0;
	shades[subs++] = act;
	while( act->associated )
	{	act = act->associated;
		shades[subs++]=act;
	}
	if (subs>gran+1)
	{	for (j=0;j<(gran);j++)
		{	shades[j+1]= shades[1+(j*(subs-2))/(gran-1)];
		}
		subs=gran+1;
	}
	for (j=0;j<subs;j++)		load_color( shades[j] );
	
/* compute size */
	sdx = XTextWidth( finfo, color[i]->name, strlen(color[i]->name) )+4;
	sdy = finfo->ascent + finfo->descent+2;
	if (subs>1)
	{	int	palette_width=8;

		sdy += 14;
		if (sdx<(subs*palette_width))
		{	do
			{	sdx=subs*palette_width;
				palette_width--;
			}
			while( (sdx+2*bord>dx)&&(palette_width>3) );
		}
		subcols = subs;
	}
	else
	{	subcols = subs = 0;
	}

/* compute position */
	if (color[i]->x < dx/2)		x = color[i]->x + BO;
	else				x = color[i]->x -BO -(2*bord) -sdx;
	if (x+sdx+(2*bord)>dx)		x = dx - sdx - (2*bord);
	if (x<0)			x=0;

	if (color[i]->y < dy/2)		y = color[i]->y + BO;
	else				y = color[i]->y -BO -(2*bord) -sdy;

/* create, map and draw */
	subw = XCreateSimpleWindow( display, mw, x, y, sdx, sdy, bord, color[i]->pixel, black );
	attrib.save_under = True;
	XChangeWindowAttributes( display, subw, CWSaveUnder, &attrib );
	XMapRaised( display, subw );

	XSetFont( display, gc, finfo->fid );
	XSetForeground( display, gc, white );
	XSetBackground( display, gc, black );
	XDrawImageString( display, subw, gc, 
				(sdx-XTextWidth( finfo, color[i]->name, strlen(color[i]->name)))>>1, 
				finfo->ascent, color[i]->name, strlen( color[i]->name ) );
	if (subs)
	{	for (j=0;j<subs;j++)
		{
			XSetForeground( display, gc, shades[j]->pixel );
			XFillRectangle( display, subw, gc, 2+j*(sdx-2)/subs, sdy-12, (j+1)*(sdx-2)/subs-(2+j*(sdx-2)/subs), 10);
		}
	}
}

/*----------------------------------------------------------------------------*/

/* LittleBox draws 2 rectangles around a color-field of the subwindow */
#define	LittleBox(col)							\
	XDrawRectangle( display, subw, gc, 				\
		1+col*(sdx-2)/subcols, sdy-13, 				\
		(col+1)*(sdx-2)/subcols-(2+col*(sdx-2)/subcols)+1, 11);	\
	XDrawRectangle( display, subw, gc, 				\
		1+col*(sdx-2)/subcols-1, sdy-13-1, 			\
		(col+1)*(sdx-2)/subcols-(2+col*(sdx-2)/subcols)+1+2, 11+2)


void sub_select( line_index, i, help_color )
	int	line_index, i, help_color;
/* select a sub-color */
{
ColorDef *shades[MAX_COLS];
ColorDef *act;
int	subs, j;
XEvent	event;
int	col_index;	/* current index to the shades-field */
int	old_col_index;	/* last index to the shades-field */

/* collect shades (shades[0] is used for the old value of the color) */
	if (help_color)		shades[0] = line[line_index].help;
	else			shades[0] = line[line_index].color;

	act = color[i];
	subs = 1;
	shades[subs++] = act;
	while( act->associated )
	{	act = act->associated;
		shades[subs++]=act;
	}

	if (subs>gran+1)
	{	for (j=0;j<(gran);j++)
		{	shades[j+2]= shades[2+(j*(subs-3))/(gran-1)];
		}
	}

/* first exchange of the color */
	col_index = 0;
	if (help_color)
	{	exchange( &line[line_index].help, shades[col_index+1] );
		draw_string( line_index );
	}
	else	change_color( line_index, shades[col_index+1] );

/* if there are no associated colors, the selection is done */
	if (subs<=2)	return;

	col_index=0;
	old_col_index=0;
	XSetForeground( display, gc, white );
	LittleBox( col_index );

/* warp the pointer to the main color of the subwindow */
	XWarpPointer( display, None, subw, 0, 0,  0, 0, ((sdx/subcols)>>1)+2, sdy-7 );
	XGrabPointer(display, subw, False, 
		ButtonPressMask | ButtonReleaseMask | PointerMotionMask, 
		GrabModeAsync, GrabModeAsync, 
		subw, subsel_cursor, CurrentTime );

	do
	{	XWindowEvent( display, subw, ButtonPressMask | ButtonReleaseMask | PointerMotionMask, &event );
		if (event.xany.type!=ButtonRelease)
		{
			if (event.xany.type==MotionNotify)
			{	while(XCheckMaskEvent(display, PointerMotionMask, &event));

				col_index = (event.xmotion.x-1)*subcols/(sdx-2);
				if (col_index<0)	col_index=0;
				if (col_index>=subcols)	col_index=subcols-1;

				if ((event.xmotion.y<sdy-13)||(event.xmotion.y>sdy-2))
				{	col_index = -1;
				}
			}
			else		col_index = -1;	/* another ButtonPress == abort */

			if (old_col_index!=col_index)
			{	if (old_col_index>=0)
				{	XSetForeground( display, gc, black );
					LittleBox( old_col_index );
				}
				if (col_index>=0)
				{	XSetForeground( display, gc, white );
					LittleBox( col_index );
				}
				old_col_index = col_index;
				if (help_color)
				{	exchange( &line[line_index].help, shades[col_index+1] );
					draw_string( line_index );
				}
				else	change_color( line_index, shades[col_index+1] );
			}
		}
	}
	while( event.xany.type==MotionNotify );

	XUngrabPointer(display, CurrentTime);
	XSetForeground( display, gc, black );
	LittleBox( old_col_index );
/* warp pointer back to its former position */
	XWarpPointer( display, None, mw, 0, 0, 0, 0, shades[1]->x, shades[1]->y );
/*	XSync(display, 1);	*/
}


/********************************************************************************
 *			    main-window-functions				*
 ********************************************************************************/


int act_color( x, y )
	int	x, y;
/* queries the next color in range of the mouse-pointer */
{
int i;
int min_dist;
int min_i = 0;
int dx1, dy1;

	min_dist = dy*dy+dx*dx;

	for (i=0;i<color_number;i++)
	{	dx1 = abs(color[i]->x-x);
		dy1 = abs(color[i]->y-y);

		if (dx1*dx1+dy1*dy1<min_dist)
		{	min_dist = dx1*dx1+dy1*dy1;
			min_i = i;
		}
	}
	if (min_dist<(BO)*(BO))
		return(min_i);
	else
		return(-1);
}

/*----------------------------------------------------------------------------*/
	
#define	XPos(r, g, b)	( (BO>>1)+2+					\
			  ( (reverse)	? (127+(255-r)-((255-b)>>1))	\
			  		: (127+r-((b)>>1))		\
			  ) * (dx-BO-4)/384				\
			)
#define	YPos(r, g, b)	( (BO>>1)+2+					\
			  ( (reverse)	? (g+((255-b)>>1))		\
			  		: ((255-g)+(b>>1))		\
			  ) * (dy-BO-4)/384				\
			)

void redraw_window()
/* redraws the cube and the color-boxes in the main window */
{
int i, j, op;
int ex[8], ey[8];
int s_dummy;
unsigned int u_dummy;
Window	root;

	XGetGeometry( display, mw, &root, &s_dummy, &s_dummy, &dx, &dy, &u_dummy, &u_dummy );
	if (BO_def)
		BO = BO_def;
	else
	{	BO = (dx/50);
		if ((dy/50)>BO)	BO=(dy/50);
	}
	XClearWindow( display, mw );

/* draw the cube */
	XSetForeground( display, gc, white );
	for (i=0;i<8;i++)
	{	ex[i] = XPos( ((i&1)?255:0), ((i&2)?255:0), ((i&4)?255:0) );
		ey[i] = YPos( ((i&1)?255:0), ((i&2)?255:0), ((i&4)?255:0) );
	}
	for (i=0;i<8;i++)
	{	for (j=0;j<3;j++)
		{	op = i^(1<<j);
			if (op<i);
				XDrawLine(display, mw, gc, ex[i], ey[i], ex[op], ey[op]);
		}
	}

/* draw the boxes */
	for (i=0;i<color_number;i++)
	{
		color[i]->x = XPos(color[i]->r, color[i]->g, color[i]->b);
		color[i]->y = YPos(color[i]->r, color[i]->g, color[i]->b);

		XSetForeground( display, gc, color[i]->pixel );
		XFillRectangle( display, mw, gc, 
			color[i]->x-(BO>>1), color[i]->y-(BO>>1), BO, BO);
	}
	XFlush(display);
}

/*----------------------------------------------------------------------------*/

mw_event( event )
	XEvent	*event;
{
static int new_i;
static int old_i= -1;

    switch( event->xany.type )
    {
    case Expose:
    		while(XCheckWindowEvent( display, mw, ExposureMask, event ));

    		if (event->xexpose.count==0)	redraw_window();
    		break;

    case MotionNotify:
    		while( XCheckWindowEvent( display, mw, PointerMotionMask, event ) );
		new_i = act_color( event->xmotion.x, event->xmotion.y );
		if ((old_i!=new_i)&&(old_i>=0))
		{	destroy_subwindow(old_i);
			old_i = -1;
		}
		if (old_i!=new_i)
		{	create_subwindow(new_i);
			old_i = new_i;
		}
    		break;

    case EnterNotify:
    	update_map();
    	break;

    case LeaveNotify:
    	if (old_i>=0)
    	{	destroy_subwindow(old_i);
    		old_i = -1;
    	}
    	break;

    case ButtonPress:
        switch (event->xbutton.button)
    	{
    	    case Button1:
    	    {	if ((tw)&&(old_i>=0))
		{	sub_select( act_line, old_i, 0 );
		}
		else
		{	if (old_i>=0)
			{	sub_select( 0, old_i, 0 );
				XStoreBytes( display, line[0].color->name, strlen(line[0].color->name) );
				XBell(display, 10);
			}
		}
		break;	/* end switch(button) */
	    }

	    case Button2:
	    {	if ((tw)&&(act_line>=0)&&(old_i>=0))
		{	if (line[act_line].assoc<0)
				sub_select( act_line, old_i, 1 );
			else	sub_select( line[act_line].assoc, old_i, 0 );
		}
		break;	/* end switch(button) */
	    }

	    case Button3:
	    {	ColorDef	*act;
		char		*short_name;
		char		*name;
		char		name_copy[30];
		int		name_length;

		name = short_name = NULL;

		if ((tw)&&(act_line>=0))
		{	short_name = cut_digits( line[act_line].color->name );
		}
		else
		{	if (tw==(Window)0)
			{	name = XFetchBytes( display, &name_length );
				if (name_length<28)
				{	strncpy( name_copy, name, name_length );
					name_copy[ name_length ] = '\0';
					short_name = cut_digits( name );
				}
				XFree( name );
			}
		}
		if (short_name)
		{	act = find_color( short_name );
			if (act)
			XWarpPointer( display, None, mw, 0, 0,  0, 0, 
				XPos( act->r, act->g, act->b ), 	YPos( act->r, act->g, act->b ) );
		}
		if (name)	XFree(name);

		break;	/* end switch(button) */
	    }
    	}  	/* end switch(type) */

    case KeyPress:
	{	char	sign;
		KeySym	keysym;

	   	XLookupString(&event->xkey, &sign, 1, &keysym, NULL);
		if (sign=='q')	exit(0);
		break;
	}
    }
}

/*----------------------------------------------------------------------------*/

void create_mainwindow( argc, argv, geometry )
	int	argc;
	char	**argv;
	char	*geometry;
{
int			x, y;
int			result;
XSizeHints		hints;
XWMHints		normal_hints;
XClassHint		class_hints;
static char *window_string = "ColorView";
static char *icon_string   = "ColorView";

/* parsing the geometry */ 
	if (geometry)
		result = XParseGeometry( geometry, &x, &y, &dx, &dy );
	else	result = 0;

	hints.flags = PAspect | PMinSize | PMaxSize;
	hints.min_width = hints.min_height = 100;
	hints.max_width = hints.max_height = HeightOfScreen(screen);

	hints.min_aspect.x = 1;	hints.min_aspect.y = 1;
	hints.max_aspect.x = 1;	hints.max_aspect.y = 1;

	if ((result&XValue)&&(result&YValue))
	{	hints.x = x;
		hints.y = y;
		hints.flags |= USPosition;
	}
	else
	{	x=0;
		y=0;
	}
	if ((result&WidthValue)&&(result&HeightValue))
	{	hints.width  = dx;
		hints.height = dy;
		hints.flags |= USSize;
	}
	else
	{	hints.width  = dx = HeightOfScreen(screen)/3;
		hints.height = dy = HeightOfScreen(screen)/3;
		hints.flags |= PSize;
	}

	mw = XCreateSimpleWindow( display, RootWindowOfScreen(screen), 
		x, y, dx, dy, 3, WhitePixelOfScreen(screen), BlackPixelOfScreen(screen) );

/* class-hints */
	class_hints.res_name = "xcol";
	class_hints.res_class= "XCol";

/* normal-hint */
	normal_hints.flags = StateHint | WindowGroupHint;
	normal_hints.initial_state = NormalState;
	normal_hints.window_group  = mw;

#ifdef XTextProperty
{	
XTextProperty		window_name, icon_name;

	XStringListToTextProperty( &window_string, 1, &window_name );
	XStringListToTextProperty( &icon_string, 1, &icon_name );
	
	XSetWMProperties( display, mw, &window_name, &icon_name, argv, argc, 
				&hints, &normal_hints, &class_hints );

	XFree( (char *)window_name.value );
	XFree( (char *)icon_name.value );
}
#else
	XSetStandardProperties( display, mw, window_string, icon_string, None, argv, argc, &hints );
	XSetClassHint( display, mw, &class_hints );
#endif
}


/********************************************************************************
 *			initialization and helps				*
 ********************************************************************************/


void help()
{
printf("ColorView:\n");
printf("\tThe colors of the rgb.txt file of the server can be selected\n");
printf("\tin this window.  The colors are sorted by their RGB values.  Colors\n");
printf("\twith different intensity, but equivalent names, are grouped together.\n");
printf("\n");
printf("\t\tButton1:\tselect color for active line.\n");
printf("\t\tButton2:\tselect help-color for active line.\n");
printf("\t\tButton3:\twarp to color of active line.\n");
printf("\t\t'q':\t\tquit the program.\n");
printf("\n");
printf("TextView:\n");
printf("\tAll appearances of color names in a text file are collected and\n");
printf("\tdisplayed in this window.  For each line, the color and an additional\n");
printf("\thelp color can be set.\n");
printf("\n");
printf("  on a text line:\n");
printf("\t\tButton1:\tselect line as active line.\n");
printf("\t\tButton2:\ttoggle reverse-mode / make association\n");
printf("\t\tButton3:\tshow line in text file.\n");
printf("\n");
printf("  on header or footer:\n");
printf("\t\tButton1:\tscroll by 1, if possible.\n");
printf("\t\tButton2:\tscroll by 10, if possible.\n");
printf("\t\tButton3:\twrite file with backup (after confirmation).\n");
}

void usage()
{
	printf("usage:\txcol [options] [file]\n\n");
	printf("    \"file\" is the file to be edited.\n\n");
	printf("  Options are:\n");
	printf("    -rv          Color positions are reversed in the cube.\n");
	printf("    -b<n>        Size of color blocks is set to constant <n>.\n");
	printf("    -gran<n>     Maximum number of associated colors is set to <n>.\n");
	printf("    -dark<n>     Intensity percentage of colors outside of xcol is set to <n>.\n");

	printf("    -strings     Names of colors in the file are only recognized\n");
	printf("                 when used in a string (useful for C source code).\n");

	printf("    +<char_list> Adds characters to list of \"space\" characters.\n" );

	printf("    -case        Converts color strings to correct case.\n");
	printf("    -help        Prints a help message.\n");
	printf("    -file <name> Names of a different \"rgb.txt\" file.\n");

	printf("\n");
	printf("    -display <display_name>\n");
	printf("    -geometry <geometry>\n");
	exit(0);
}

/*----------------------------------------------------------------------------*/

main( argc, argv )
	int	argc;
	char	**argv;
{
int		disparg=0;
char		*geometry=NULL;
char		linebuffer[80];	/* buffer for lines of the color-file */

char		*name;
ColorDef	act;

FILE		*fp;
char		*rgb_file;
int		i, l;
XEvent		event;

int		last;

	file_name = NULL;
	rgb_file  = NULL;

	set_default_spaces();

/* parse some parameters */
	for (i=1;i<argc;i++)
	{	if (strcmp(argv[i], "-display")==0)      disparg = ++i;
		else if (strcmp(argv[i], "-geometry")==0) geometry= argv[++i];
		else if (strcmp(argv[i], "-file")==0)	rgb_file = argv[++i];
		else if (strcmp(argv[i], "-rv")==0)	reverse = 1;
		else if (strncmp(argv[i], "-b", 2)==0)	BO_def = atoi(&argv[i][2]);
		else if (argv[i][0]=='+')
		{	l = strlen(argv[i]);
			while(l-->1)	space_char[argv[i][l]]=1;
		}
		else if (strncmp(argv[i], "-gran", 5)==0)
		{	gran = atoi(&argv[i][5]);
			if (gran==0)	gran=11;
		}
		else if (strncmp(argv[i], "-dark", 5)==0)
		{	darkness = atoi(&argv[i][5]);
			if (darkness>100)	darkness=100;
		}
		else if (strcmp(argv[i], "-help")==0)		help();
		else if (strcmp(argv[i], "-case")==0)		original=1;
		else if (strcmp(argv[i], "-strings")==0)
		{	memset( space_char, 0, sizeof(space_char) );
			space_char['"'] = 1;
		}
		else if (argv[i][0]=='-')	usage();
		else file_name = argv[i];
	}

	if ((display = XOpenDisplay( (disparg>0)?argv[disparg]:NULL ))==NULL)
	{	fprintf(stderr, "*** can't open display. ***\n");
		exit(0);
	}
	screen = ScreenOfDisplay(display, DefaultScreen(display));
	cells  = CellsOfScreen(screen);

	if (cells<256)
	{	fprintf(stderr, "You don't have enough color-cells to use this program.\n" );
		exit(0);
	}
	if (cells>MAX_CELLS)
	{	fprintf(stderr, "There are many color-cells on your screen.\n" );
		cells=MAX_CELLS;
	}

/* load textfonts */

	if (! (finfo = XLoadQueryFont(display, COLOR_FONT ))) {
	     fprintf(stderr, "Couldn't open font:\n\t%s\nTrying server default.\n",
		     COLOR_FONT);
	     if (! (finfo = XLoadQueryFont(display, DEFAULT_FONT))) {
		  fprintf(stderr, "Couldn't get server default fault font.\n");
		  exit(-1);
	     }
	}

	if (! (tinfo = XLoadQueryFont(display, TEXT_FONT ))) {
	     fprintf(stderr, "Couldn't open font:\n\t%s\nTrying server default.\n",
		     TEXT_FONT);
	     if (! (tinfo = XLoadQueryFont(display, DEFAULT_FONT))) {
		  fprintf(stderr, "Couldn't get server default fault font.\n");
		  exit(-1);
	     }
	}

	char_height = tinfo->descent + tinfo->ascent;
	char_width = tinfo->max_bounds.rbearing + tinfo->min_bounds.lbearing;

/* load the file with colors */
	if (rgb_file)	fp = fopen( rgb_file, "r" );
	else		fp = fopen( RGB_FILE, "r" );

	if (fp==NULL)
	{	fprintf( stderr, "unable to load the 'rgb.txt'-file.\n" );
		exit(0);
	}
	color_number=0;
	load_standard( &std_black );
	load_standard( &std_white );
	load_standard( &std_grey  );

	while (fgets( linebuffer, sizeof linebuffer, fp )!=NULL)
	{	int n;

		linebuffer[strlen(linebuffer)-1]='\0';

		n = sscanf(linebuffer, "%d %d %d", 
			&act.r, &act.g, &act.b);

		if (n==3)
		{	name = linebuffer;
			while( !isalpha(*name) )	name++;

			act.name = (char *)malloc((unsigned)strlen(name)+1);
			strcpy(act.name, name);
			new_color( &act );
		}
	}
	fclose( fp );

/* sort the colors */
	if (reverse)
		qsort( (char *)color, color_number, sizeof(ColorDef *), color_sort_r );
	else	qsort( (char *)color, color_number, sizeof(ColorDef *), color_sort );

	for (i=0;i<color_number;i++)
	{	ColorDef	*act;

		brother_sort( &color[i] );
		act = color[i];
		while( act )
		{	associated_sort( act );
			act = act->brother;
		};
	}

/* collect the color-names */
	color_names = 0;
	for (i=0;i<color_number;i++)
	{	ColorDef	*act, *assoc;

		act = color[i];
		if (act->pixel==Undefined)
		{
	/* if the unindexed name is not defined, the first associated
	 * entry of its list is copied onto it.
	 */
			assoc = act->associated;
			memcpy( (char *)act, (char *)assoc, sizeof(ColorDef) );
			free( (char *)assoc );
		}

		do
		{	next_name( act );
			assoc = act->associated;
			while( assoc )
			{	next_name( assoc );
				assoc = assoc->associated;
			}
			act = act->brother;
		}
		while (act);
	}
	qsort( (char *)cname, color_names, sizeof(ColorString), color_name_sort );
	last = 0;
	first[0] = &cname[0];
	for (i=0;i<color_names;i++)
	{	if (cname[i].name[0]!=last)
		{	do
			{	last++;
				first[last] = &cname[i];
			}
			while( cname[i].name[0]!=last );
		}
	}
	while( last<255 )
	{	last++;
		first[last] = &cname[i];
	}

/* initializing XWindow stuff */
	grey_pixmap = XCreatePixmapFromBitmapData( display, RootWindowOfScreen(screen), 
			grey_bits, grey_width, grey_height, AllPlanes, 0, DefaultDepthOfScreen(screen) );
	gc = XCreateGC( display, RootWindowOfScreen(screen), (long)0, NULL);
	XCopyGC(display, DefaultGCOfScreen(screen), (long)-1, gc);
	XSetGraphicsExposures( display, gc, False );

	create_mainwindow( argc, argv, geometry );
	create_new_map();
	XSetWindowColormap( display, mw, my_map );
	XSetWindowBackground( display, mw, black );
	XSetWindowBorder( display, mw, grey );
	
	mw_cursor     = XCreateFontCursor( display, XC_top_left_arrow );
	tw_cursor     = mw_cursor;
	subsel_cursor = mw_cursor;
	text_cursor   = XCreateFontCursor( display, XC_middlebutton );
	quest_cursor  = mw_cursor;
	updown_cursor = XCreateFontCursor( display, XC_sb_v_double_arrow );

#ifdef RECOLOR_CURSOR
/* the cursor have to get the pixel-values of my colormap
 * not working the way i expected. i don't think it's my fault.
 * only in the main-, not in the text-window, the colors are correct.
 */
 {	ColorDef *fg, *bg;	/* Cursor Foreground/Background */
	XColor	 xfg, xbg;

	bg = find_color( "LightGoldenrod" );
	fg = find_color( "MidnightBlue" );
	load_color( bg );
	load_color( fg );
	xbg.pixel = bg->pixel;
	xfg.pixel = fg->pixel;
	XQueryColor( display, my_map, &xbg );
	XQueryColor( display, my_map, &xfg );

	XRecolorCursor( display, mw_cursor, 	&xfg, &xbg );
	XRecolorCursor( display, text_cursor, 	&xfg, &xbg );
	XRecolorCursor( display, updown_cursor, &xfg, &xbg );
 }
#endif

	XDefineCursor( display, mw, mw_cursor );
	XSelectInput( display, mw, 
		ExposureMask | PointerMotionMask |
		ButtonPressMask | LeaveWindowMask | EnterWindowMask |
		KeyPressMask );

	XMapRaised( display, mw );
	XSync( display, 0 );
	redraw_window();

/* loading the file */
	back_def = find_color( GREY_STRING );

	if (file_name)
	{
		if (load_file( file_name ))
		{	if (color_lines>0)
			{	find_assocs();
				tw = create_textwindow( file_name );
				load_text_colors();
				if (only_mine)
				{	XSetWindowColormap( display, tw, my_map );
				}
			}
			else
				printf("no colors found in your file.\n");
		};
	}
	else
	{	loaded_text = (TextLine *)malloc( (unsigned)sizeof(TextLine) );
		loaded_text->next = loaded_text->previous = NULL;
		loaded_text->contents = (char *)malloc( 6 );
		strcpy( loaded_text->contents, "white" );

		line[0].line		= loaded_text;
		line[0].color		= find_color( WHITE_STRING );
		line[0].help		= find_color( BLACK_STRING );
		line[0].reverse		= 0;
		line[0].pos_in_line	= 0;
		line[0].assoc		= -1;

		color_lines = 1;
	}

	do
	{	XNextEvent( display, &event );
		if (event.xany.window==mw)
		{	mw_event( &event );
		}
		else
		{	tw_event( &event );
		}
	}
	while(1);	
}


