/*ScianColors.c
  Color management routines for scian
  Eric Pepke
  March 15, 1990

*/

#include "Scian.h"
#include "ScianTypes.h"
#include "ScianIDs.h"
#include "ScianArrays.h"
#include "ScianWindows.h"
#include "ScianObjWindows.h"
#include "ScianButtons.h"
#include "ScianColors.h"
#include "ScianEvents.h"
#include "ScianErrors.h"
#include "ScianScripts.h"
#include "ScianPictures.h"
#include "ScianControls.h"
#include "ScianDialogs.h"
#include "ScianSliders.h"
#include "ScianDatasets.h"
#include "ScianStyle.h"
#include "ScianTitleBoxes.h"
#include "ScianTextBoxes.h"
#include "ScianLists.h"
#include "ScianMethods.h"
#include "ScianDraw.h"
#include "ScianIcons.h"
#include "ScianFiles.h"
#include "ScianVisWindows.h"
#include "ScianObjFunctions.h"
#include "ScianTemplates.h"
#include "ScianTemplateHelper.h"
#include "ScianSciences.h"
#include "gamtables.h"
#include "ScianSymbols.h"

#ifdef GRAPHICS
#ifdef NEEDSGAMMA
#endif

#define OVERCLEAR	0		/*Clear in overlay planes*/
#define OVERRED		1		/*Red in overlay planes*/

#ifdef NEEDSGAMMA
#define CVAL(k) LinToGamma[k]
#else
#define CVAL(k) (k)
#endif

#endif

int overDraw = 0;			/*Counter for overlay drawing*/

short curRed, curGreen, curBlue;	/*Current color components in other 
					  than screen draw mode*/
ObjPtr iconColorPalette;

char *spfNames[NPALETTEFUNCS] =
    {
	"ramp",
	"reverse",
	"ruffle",
	"smooth",
	"sharpen"
    };


char *toolNames[NPALETTETOOLS] = 
    {
	"free form",
	"sine wave",
	"triangle wave",
	"sawtooth wave",
	"reverse sawtooth wave",
	"square wave"
    };

char *componentNames[NCOLORMODELS][3] =
    {
	{"red", "green", "blue"},
	{"\"Y\"", "\"I\"", "\"Q\""},
	{"hue", "saturation", "value"},
	{"hue", "lightness", "saturation"}
    };

char *shortComponentNames[NCOLORMODELS][3] =
    {
	{"R", "G", "B"},
	{"Y", "I", "Q"},
	{"H", "S", "V"},
	{"H", "L", "S"}
    };

char *colorModelNames[NCOLORMODELS] = 
    {
	"RGB",
	"YIQ",
	"HSV",
	"HLS"
    };

real RGB2YIQMat[3][3] =
    {{ 0.30,  0.59,  0.11},
     { 0.60, -0.28, -0.32},
     { 0.21, -0.52,  0.31}};

real YIQ2RGBMat[3][3] = 
    {{ 1.0000,  0.9483,  0.6240},
     { 1.0000, -0.2761, -0.6398},
     { 1.0000, -1.1055,  1.7299}};

int paletteSerialNum = 0;		/*Serial num for palettes*/

static int ColorToPixel(ObjPtr, int);
static int PixelToColor(ObjPtr, int);
static int AllocColors(int);
 
typedef struct cr
    {
	struct cr *next;	/*Next color range*/
	int beg, end;		/*Beginning and end (+ 1)*/
    } ColorRange;

ColorRange *colorRanges;	/*The available color ranges*/

PPtr activePalettes = 0;	/*The active palettes*/

#define COLORWHEELBORDER 	6	/*Border around HS control*/
#define COLORWHEELSPOTSIZE	4	/*Size of spot in HS control*/

int curUIColorIndex;			/*Global current color index*/
short uiColors[NUICOLORS][3];		/*Components for the UI colors*/
int uiColorIndex[NUICOLORS];		/*Indices for ui colors*/
short3 *colorsToScavenge;		/*Colors to scavenge*/
int nScavengeColors = 0;		/*Number of colors for scavenging*/

PPtr curPalette;			/*Current palette*/
int curNColors;				/*Current number of colors*/
real curMin, curMax;			/*Current min and max values*/
int curBeg;
real diffMult;				/*Amount to multiply to get difference*/
short3 *curColors;

ObjPtr paletteClass;			/*Class for a color palette*/
ObjPtr paletteDisplayClass;		/*Class for a palette display*/
ObjPtr colorControlClass;
ObjPtr colorWheelClass;
ObjPtr colorBarClass;
#define COLORFAILTIMEOUT	60	/*Color failure timeout in seconds*/
long colorFailTime = 0;			/*Time of last color failure*/

#define COLORWHEELHEIGHT	4	/*Height of a color wheel*/

#ifdef PROTO
static void SetColorComponent(PPtr palette, int whichColor, int comp, int cc);
static int GetColorComponent(PPtr palette, int whichColor, int comp);
#endif

static short o0r, o0g, o0b;
static short o1r, o1g, o1b;
static short o2r, o2g, o2b;

#ifdef PROTO
void OverDraw(Bool whether)
#else
void OverDraw(whether)
Bool whether;
#endif
/*Sets system into overlay drawing mode, or not*/
{
    if (hasOverDraw == false)
    {
	printf("No overlay drawing!\n");
	return;
    }

#ifdef GRAPHICS
    if (whether)
    {
	if (!overDraw++)
	{
	    pushattributes();
	    drawmode(pupForOverDraw ? PUPDRAW : OVERDRAW);
	    getmcolor(OVERCLEAR, &o0r, &o0g, &o0b);
	    getmcolor(OVERRED, &o1r, &o1g, &o1b);
	    mapcolor(OVERCLEAR, 0, 0, 0);
	    mapcolor(OVERRED, 255, 0, 0);
	    color(1);
	}
    }
    else
    {
	if (0 == --overDraw)
	{
	    mapcolor(OVERCLEAR, o0r, o0g, o0b);
	    mapcolor(OVERRED, o1r, o1g, o1b);
	    drawmode(NORMALDRAW);
	    popattributes();
	}
    }
#endif
}


#ifdef PROTO
void MapPalette(ObjPtr p)
#else
void MapPalette(p)
ObjPtr p;
#endif
/*Maps palette p*/

{
#ifdef GRAPHICS
    int k;
    if (hasCmap == false) return;
    if (((PPtr) p) -> beg < 0) return;
    for (k = 0; k < ((PPtr) p) -> nColors; ++k)
    {
	mapcolor(((PPtr) p) -> beg + k,
		 ((PPtr) p) -> colors[k][0],
		 ((PPtr) p) -> colors[k][1],
		 ((PPtr) p) -> colors[k][2]);
	TinyDelay();
    }
#endif
}

#ifdef PROTO
void MapPaletteColors(PPtr p, int start, int end)
#else
void MapPaletteColors(p, start, end)
PPtr p; int start; int end;
#endif
/*Maps color from start to end in palette p*/
{
#ifdef GRAPHICS
    int k;
    if (hasCmap == false) return;
    if (((PPtr) p) -> beg < 0) return;
    for (k = start; k < MAX(((PPtr) p) -> nColors, end + 1); ++k)
    {
	mapcolor(((PPtr) p) -> beg + k,
		 ((PPtr) p) -> colors[k][0],
		 ((PPtr) p) -> colors[k][1],
		 ((PPtr) p) -> colors[k][2]);
	TinyDelay();
    }
#endif
}

#ifdef PROTO
void CopyColorsToComponents(PPtr p, int start, int finish)
#else
void CopyColorsToComponents(p, start, finish)
PPtr p; int start; int finish;
#endif
/*Copies the colors to components based on color model*/
{
    ObjPtr var;
    int cm, k;
    short3 *colors, *components;
    real r, g, b, h, s, v, l, y, i, q;

    var = GetIntVar("CopyColorsToComponents", (ObjPtr) p, COLORMODEL);
    if (var)
    {
	cm = GetInt(var);
    }
    else
    {
	cm = CM_RGB;
    }

    colors = p -> colors;
    components = p -> components;

    for (k = start; k <= finish; ++k)
    {
	switch (cm)
	{
	    case CM_RGB:
		components[k][0] = colors[k][0];
		components[k][1] = colors[k][1];
		components[k][2] = colors[k][2];
		break;
	    case CM_HSV:
		r = ((real) colors[k][0]) / 255.0;
		g = ((real) colors[k][1]) / 255.0;
		b = ((real) colors[k][2]) / 255.0;

		RGB2HSV(&h, &s, &v, r, g, b);

		components[k][0] = h * 255.0 + 0.5;
		components[k][1] = s * 255.0 + 0.5;
		components[k][2] = v * 255.0 + 0.5;
		break;
	    case CM_HLS:
		r = ((real) colors[k][0]) / 255.0;
		g = ((real) colors[k][1]) / 255.0;
		b = ((real) colors[k][2]) / 255.0;

		RGB2HLS(&h, &l, &s, r, g, b);

		components[k][0] = h * 255.0 + 0.5;
		components[k][1] = l * 255.0 + 0.5;
		components[k][2] = s * 255.0 + 0.5;
		break;
	    case CM_YIQ:
		r = ((real) colors[k][0]) / 255.0;
		g = ((real) colors[k][1]) / 255.0;
		b = ((real) colors[k][2]) / 255.0;

		RGB2YIQ(&y, &i, &q, r, g, b);

		components[k][0] = y * 255.0 + 0.5;
		components[k][1] = i * 255.0 + 0.5;
		components[k][2] = q * 255.0 + 0.5;
		break;
	    default:
		components[k][0] = colors[k][0];
		components[k][1] = colors[k][1];
		components[k][2] = colors[k][2];
		break;
	}
    }
}

#ifdef PROTO
void CopyComponentsToColors(PPtr p, int start, int finish)
#else
void CopyComponentsToColors(p, start, finish)
PPtr p; int start; int finish;
#endif
/*Copies the colors to components based on color model*/
{
    ObjPtr var;
    int cm, k;
    short3 *colors, *components;
    real r, g, b, h, s, v, l, y, i, q; 

    var = GetIntVar("CopyComponentsToColors", (ObjPtr) p, COLORMODEL);
    if (var)
    {
	cm = GetInt(var);
    }
    else
    {
	cm = CM_RGB;
    }

    colors = p -> colors;
    components = p -> components;

    for (k = start; k <= finish; ++k)
    {
	switch (cm)
	{
	    case CM_RGB:
		colors[k][0] = components[k][0];
		colors[k][1] = components[k][1];
		colors[k][2] = components[k][2];
		break;
	    case CM_HSV:
		h = ((real) components[k][0]) / 255.0;
		s = ((real) components[k][1]) / 255.0;
		v = ((real) components[k][2]) / 255.0;

		HSV2RGB(&r, &g, &b, h, s, v);

		colors[k][0] = r * 255.0 + 0.5;
		colors[k][1] = g * 255.0 + 0.5;
		colors[k][2] = b * 255.0 + 0.5;
		break;
	    case CM_HLS:
		h = ((real) components[k][0]) / 255.0;
		l = ((real) components[k][1]) / 255.0;
		s = ((real) components[k][2]) / 255.0;

		HLS2RGB(&r, &g, &b, h, l, s);

		colors[k][0] = r * 255.0 + 0.5;
		colors[k][1] = g * 255.0 + 0.5;
		colors[k][2] = b * 255.0 + 0.5;
		break;
	    case CM_YIQ:
		y = ((real) components[k][0]) / 255.0;
		i = ((real) components[k][1]) / 255.0;
		q = ((real) components[k][2]) / 255.0;

		YIQ2RGB(&r, &g, &b, y, i, q);

		colors[k][0] = r * 255.0 + 0.5;
		colors[k][1] = g * 255.0 + 0.5;
		colors[k][2] = b * 255.0 + 0.5;
		break;
	    default:
		components[k][0] = colors[k][0];
		components[k][1] = colors[k][1];
		components[k][2] = colors[k][2];
		break;
	}
    }
}

void CopyColorsToPalette(p, colors)
ObjPtr p;
short3 *colors;
/*Copies the colors into palette p*/
{
    int k;
    for (k = 0; k < ((PPtr) p) -> nColors; ++k)
    {
	((PPtr) p) -> colors[k][0] = colors[k][0];
	((PPtr) p) -> colors[k][1] = colors[k][1];
	((PPtr) p) -> colors[k][2] = colors[k][2];
    }
    CopyColorsToComponents((PPtr) p, 0, ((PPtr) p) -> nColors - 1);
}

void FieldPaletteName(palette, field)
ObjPtr palette, field;
/*Names a palette based on a field*/
{
    ObjPtr name;

    MakeVar(field, NAME);
    name = GetStringVar("FieldPaletteName", field, NAME);

    if (!name)
    {
	return;
    }

    strcpy(tempStr, GetString(name));
    strcat(tempStr, " Palette");
    SetVar(palette, NAME, NewString(tempStr));
}

ObjPtr MakePaletteName(palette)
ObjPtr palette;
/*Makes a name for a palette*/
{
    if (GetVar(palette, NAME))
    {
	return ObjFalse;
    }
    else
    {
	sprintf(tempStr, "Palette %ld", ++paletteSerialNum);
	SetVar(palette, NAME, NewString(tempStr));
	return ObjTrue;
    }
}

void AlertColorFailure()
/*Alerts the user that a color allocation has failed*/
{
    WinInfoPtr errWindow;

    errWindow = AlertUser(UICAUTIONALERT, (WinInfoPtr) 0,
	"SciAn has run out of color table entries.  It will continue to try to find enough \
colors to run normally.  Until it does, you may see some unusual colors in \
some windows.",
        0, 0, "Oh well");
    SetVar((ObjPtr) errWindow, HELPSTRING,
        NewString("All the windows of SciAn, as well as the windows of other \
processes, share the same color table.  When there are several visualizations \
on the screen, SciAn must share the color table between windows.  It tries to \
allocate and deallocate chunks of the color table according to need, but \
sometimes the number of visualizations grows so large that there are simply not \
enough colors to go around.\n\
\n\
SciAn will continue to try to allocate colors.  You can reduce the number of \
colors you are using by a number of ways.  If your computer can do 24-bit RGB, \
you can set some of the visualization windows to Full Color by clicking on the \
Full Color button at the bottom right of the visualization window.  You can close \
visualization windows that you are not using.  You can color some of the \
visualizations white or increase visualizations colored by datasets to full \
brightness."));
}

void SetPalette(p)
ObjPtr p;
/*Sets the current palette to p*/
{
    PPtr tp;
    tp = (PPtr) p;

    curPalette = tp;
    curNColors = tp -> nColors;
    curMin = tp -> min;
    curMax = tp -> max;

#ifdef GRAPHICS
    if ((tp -> beg < 0) && (false == rgbp))
    {
	/*Allocate some colors on demand*/
	tp -> beg = AllocColors(curNColors);
	if (tp -> beg >= 0)
	{
	    /*Allocation succeeded*/
	    MapPalette(p);
	    curBeg = tp -> beg + 2;
	}
	else
	{
	    /*Allocation failed*/
	    struct tms buffer;
	    long curTime;

	    curTime = times(&buffer) / HEARTBEAT;

	    if (curTime > colorFailTime + COLORFAILTIMEOUT)
	    {
		colorFailTime = curTime;
		DoUniqueTask(AlertColorFailure);
	    }
	    curNColors = uiColorIndex[UIWHITE] -
			 uiColorIndex[UIBLACK];
	    curBeg = uiColorIndex[UIBLACK];
	}
    }
    else
#endif
    {
	curBeg = tp -> beg + 2;
    }

    curColors = tp -> colors + 2;

    diffMult = ((real) curNColors - 4) / (curMax - curMin);
}

#ifdef PROTO
void SetPaletteMinMax(ObjPtr p, real min, real max)
#else
void SetPaletteMinMax(p, min, max)
ObjPtr p;
real min, max;
#endif
/*Sets the min and max of palette p to min and max*/
{
    ((PPtr) p) -> min = min;
    ((PPtr) p) -> max = max;
    SetVar(p, CHANGED, ObjTrue);
}

static void CompactColors()
/*Compacts all the colors*/
{
    ColorRange *runner;

    if (colorRanges == 0)
    {
	ReportError("CompactColors", "No color ranges");
	return;
    }

    runner = colorRanges;
    while (runner && runner -> next)
    {
	if (runner -> end >= runner -> next -> beg)
	{
	    /*Compact two adjacent colors*/
	    ColorRange *next;

	    runner -> end = runner -> next -> end;
	    next = runner -> next -> next;
	    free(runner -> next);
	    runner -> next = next;
	}
	else
	{
	    runner = runner -> next;
	}
    }
}

static int AllocColors(n)
int n;
/*Allocates n contiguous colors, returns their start or -1*/
{
    ColorRange **runner, **bestFit;
    int fit;			/*Fit of best fit so far*/

    if (colorRanges == 0)
    {
	ReportError("AllocColors", "No color ranges");
	return -1;
    }

    /*Compact the colors*/
    CompactColors();

    /*Search for best fit*/
    fit = 32767;
    bestFit = (ColorRange **) 0;
    runner = &colorRanges;

    while (*runner)
    {
	int nAvailable;
	nAvailable = (*runner) -> end - (*runner) -> beg;
	if (nAvailable >= n)
	{
	    if (nAvailable - n < fit)
	    {
		fit = nAvailable - n;
		bestFit = runner;
	    }
	}
	runner = &((*runner) -> next);
    }

    if (bestFit)
    {
	int retVal;
	retVal = (*bestFit) -> beg;

	if (fit == 0)
	{
	    /*Must delete this fit*/
	    ColorRange *thisFit;
	    thisFit = (*bestFit);
	    free(thisFit);
	    (*bestFit) = (*bestFit) -> next;
	}
	else
	{
	    /*Just need to adjust it*/
	    (*bestFit) -> beg += n;
	}
	return retVal;
    }
    else
    {
	/*Allocation failed*/
	return -1;
    }
}

static void FreeColors(start, n)
int start, n;
/*Frees n contiguous colors at start*/
{
    ColorRange **runner, *next;

    if (colorRanges == 0)
    {
	ReportError("FreeColors", "No color ranges");
	return;
    }

    /*Add a new color range at the appropriate point*/
    runner = &colorRanges;

    while (*runner && (*runner) -> beg < start + n)
    {
	runner = &((*runner) -> next);
    }
    next = *runner;
    *runner = newp(ColorRange);
    (*runner) -> beg = start;
    (*runner) -> end = start + n;
    (*runner) -> next = next;
}

static void MakeUIColor(whichColor, r, g, b)
int whichColor;
short r, g, b;
/*Makes UI color whichColor to r, g, b*/
{
#ifdef GRAPHICS
    r = CVAL(r);
    g = CVAL(g);
    b = CVAL(b);

    /*Search the existing colors for the best match.*/
    if (hasCmap && scavengeColors)
    {
	short bestR, bestG, bestB;
	int bestC;
	short testR, testG, testB;
	int testC;
	int bestDist = MAXCDIST + 1;
	int testDist;

	for (testC = 0; testC < nScavengeColors; ++testC)
	{
	    if (testC < 16 || testC >= 32)
	    {
		/*Colors between 16 and 32 unreliable*/
	    testR = colorsToScavenge[testC][0];
	    testG = colorsToScavenge[testC][1];
	    testB = colorsToScavenge[testC][2];

	    testDist = MAX(ABS(testR - r), MAX(ABS(testG - g), ABS(testB - b)));
	    if (testDist <= bestDist) /*Change to < for first not last*/
	    {
		if ((r == g) && (g == b))
		{
		    /*Test for gray dirtiness failure*/
		    if (MAX(testR, MAX(testG, testB)) -
			MIN(testR, MIN(testG, testB))
			> MAXGRAYDIRT)
		    {
			/*Disallow this one and continue search*/
			continue;
		    }
		}
		bestDist = testDist;
		bestR = testR;
		bestG = testG;
		bestB = testB;
		bestC = testC;
	    }
	    }
	}

	/*Now see if it's good enough*/
	if (bestDist <= MAXCDIST)
	{
	    /*It's OK, use this one*/
	    if (showConfig)
	    {
		printf("Best match to UI color %d (%d %d %d) found at %d (%d %d %d)\n",
			whichColor, r, g, b, (int) bestC, (int) bestR, (int) bestG, (int) bestB);
	    }
	    uiColorIndex[whichColor] = bestC;
	}
	else
	{
	    /*No good, allocate one*/
	    if (hasCmap)
	    {
		if (showConfig)
		{
		    printf("UI Color %d (%d %d %d) failed color scavenge search.  Allocated at %d.\n",
			whichColor, r, g, b, curUIColorIndex);
		}
		uiColorIndex[whichColor] = curUIColorIndex++;
		mapcolor(uiColorIndex[whichColor], r, g, b);
		TinyDelay();
	    }
	    else
	    {
		uiColorIndex[whichColor] = -1;
		if (showConfig)
		{
		    printf("UI Color %d (%d %d %d) failed color scavenge search.  Not allocated.\n",
			whichColor, r, g, b);
		}
	    }
	}
    }
    else
    {
    /*Just use the next value in the color map*/
    if (hasCmap)
    {
	if (showConfig)
	{
	    printf("UI Color %d (%d %d %d) allocated at %d.\n",
			whichColor, r, g, b, curUIColorIndex);
	}
	uiColorIndex[whichColor] = curUIColorIndex++;
	if (hasCmap)
	{
	    mapcolor(uiColorIndex[whichColor], r, g, b);
	    TinyDelay();
	}
    }
    else
    {
	if (showConfig)
	{
	    printf("UI Color %d (%d %d %d) not allocated because it's always RGB.\n",
			whichColor, r, g, b);
	}
	uiColorIndex[whichColor] = -1;
    }
    }

    uiColors[whichColor][0] = r;
    uiColors[whichColor][1] = g;
    uiColors[whichColor][2] = b;
#endif
}

#ifdef PROTO
int ClosestUIColor(float clr[3])
#else
int ClosestUIColor(clr)
float clr[3];
#endif
/*Returns the closest UI color to clr*/
{
    int k;
    short r, g, b;
    short bestError;
    short best;
    r = clr[0] * 256.0;
    g = clr[1] * 256.0;
    b = clr[2] * 256.0;

    bestError = 5000;	/*Big*/
    for (k = 0; k < NUICOLORS; ++k)
    {
	short error;
	error = ABS(uiColors[k][0] - r) +
	        ABS(uiColors[k][1] - g) +
	        ABS(uiColors[k][2] - b);
	if (error < bestError)
	{
	    bestError = error;
	    best = k;
	}
    }
    return best;
}

static ObjPtr DrawColorWheel(object)
ObjPtr object;
/*Draws an HS control*/
{
#ifdef GRAPHICS
    int left, right, bottom, top;
    int cx, cy;
    int width, height, radius;
    ObjPtr backColor;			/*Color of the background*/
    FuncTyp drawContents;		/*Routine to draw the contents*/
    ObjPtr valueArray;
    real value[2];

    Get2DIntBounds(object, &left, &right, &bottom, &top);

    /*Draw the control frame*/
    DrawRaisedRect(left, right, bottom, top, UIGRAY50);

    /*Draw the color background*/
    cx = (left + right) / 2;
    cy = (bottom + top) / 2;
    width = right - left;
    height = top - bottom;
    
    if (width > height)
    {
	radius = height / 2 - COLORWHEELBORDER;
    }
    else
    {
	radius = width / 2 - COLORWHEELBORDER;
    }

    /*Draw the color wedges*/
    FillUIWedge(cx, cy, radius, -30, 30, UIRED);
    FillUIWedge(cx, cy, radius, 30, 90, UIYELLOW);
    FillUIWedge(cx, cy, radius, 90, 150, UIGREEN);
    FillUIWedge(cx, cy, radius, 150, 210, UICYAN);
    FillUIWedge(cx, cy, radius, 210, 270, UIBLUE);
    FillUIWedge(cx, cy, radius, 270, 330, UIMAGENTA);
    FillUIGauzeDisc(cx, cy, (int) radius * 2 / 3, UIWHITE);
    FillUIDisc(cx, cy, (int) radius / 3, UIWHITE);

    /*Draw the spot*/
    valueArray = GetVar(object, VALUE);
    if (valueArray)
    { 
	Array2CArray(value, valueArray);

	SetUIColor(UIBLACK);
	cx += radius * value[1] * cos(2.0 * M_PI * value[0]);
	cy += radius * value[1] * sin(2.0 * M_PI * value[0]);
	DrawUILine(cx - COLORWHEELSPOTSIZE, cy, cx + COLORWHEELSPOTSIZE, cy, UIBLACK);
	DrawUILine(cx, cy - COLORWHEELSPOTSIZE, cx, cy + COLORWHEELSPOTSIZE, UIBLACK);
    }

    if (!GetPredicate(object, ACTIVATED))
    {
	FillUIGauzeRect(left, right, bottom, top, UIBACKGROUND);
    }
#endif
    return ObjTrue;
}

#ifdef PROTO
void RGB2YIQ(real *y, real *i, real*q, real r, real g, real b)
#else
void RGB2YIQ(y, i, q, r, g, b)
real *y, *i, *q, r, g, b;
#endif
/*Converts r, g, b, into y, i, q*/
{
    *y = RGB2YIQMat[0][0] * r +
	 RGB2YIQMat[0][1] * g +
	 RGB2YIQMat[0][2] * b; 
    *i = RGB2YIQMat[1][0] * r +
	 RGB2YIQMat[1][1] * g +
	 RGB2YIQMat[1][2] * b; 
    *q = RGB2YIQMat[2][0] * r +
	 RGB2YIQMat[2][1] * g +
	 RGB2YIQMat[2][2] * b;

    *i = (*i + 0.6) / 1.2;
    *q = (*q + 0.52) / 1.04;

    if (*y < 0.0) *y = 0.0;
    if (*y > 1.0) *y = 1.0;
    if (*i < 0.0) *i = 0.0;
    if (*i > 1.0) *i = 1.0;
    if (*q < 0.0) *q = 0.0;
    if (*q > 1.0) *q = 1.0;
}

#ifdef PROTO
void YIQ2RGB(real *r, real *g, real *b, real y, real i, real q)
#else
void YIQ2RGB(r, g, b, y, i, q)
real y, i, q, *r, *g, *b;
#endif
/*Converts y, i, q, into r, g, b*/
{
    i = i * 1.2 - 0.6;
    q = q * 1.04 - 0.52;

    *r = YIQ2RGBMat[0][0] * y +
	 YIQ2RGBMat[0][1] * i +
	 YIQ2RGBMat[0][2] * q; 
    *g = YIQ2RGBMat[1][0] * y +
	 YIQ2RGBMat[1][1] * i +
	 YIQ2RGBMat[1][2] * q; 
    *b = YIQ2RGBMat[2][0] * y +
	 YIQ2RGBMat[2][1] * i +
	 YIQ2RGBMat[2][2] * q;

    if (*r < 0.0) *r = 0.0;
    if (*r > 1.0) *r = 1.0;
    if (*g < 0.0) *g = 0.0;
    if (*g > 1.0) *g = 1.0;
    if (*b < 0.0) *b = 0.0;
    if (*b > 1.0) *b = 1.0;
}

#ifdef PROTO
void HSV2RGB(real *r, real *g, real *b, real h, real s, real v)
#else
void HSV2RGB(r, g, b, h, s, v)
real *r, *g, *b, h, s, v;
#endif
/*Converts h s v into r g b.  All within range 0..1
  Adapted from Foley & VanDam*/
{
    int i;
    real f;
    real p, q, t;

    while (h < 0.0) h += 1.0;
    while (h > 1.0) h -= 1.0;
    h *= 6;		/*h from 0 to 6*/
    i = h;
    f = h - i;		/*fractional part of i*/
    i %= 6;		/*i from 0 to 5*/
    p = v * (1.0 - s);
    q = v * (1.0 - (s * f));
    t = v * (1.0 - (s * (1.0 - f)));

    switch (i)
    {
	case 0:
	    *r = v;
	    *g = t;
	    *b = p;
	    break;
	case 1:
	    *r = q;
	    *g = v;
	    *b = p;
	    break;
	case 2:
	    *r = p;
	    *g = v;
	    *b = t;
	    break;
	case 3:
	    *r = p;
	    *g = q;
	    *b = v;
	    break;
	case 4:
	    *r = t;
	    *g = p;
	    *b = v;
	    break;
	case 5:
	    *r = v;
	    *g = p;
	    *b = q;
	    break;
    }
    if (*r > 1.0) *r = 0.0; else if (*r < 0.0) *r = 0.0;
    if (*g > 1.0) *g = 0.0; else if (*g < 0.0) *g = 0.0;
    if (*b > 1.0) *b = 0.0; else if (*b < 0.0) *b = 0.0;
}

#ifdef PROTO
real HLSValue(real n1, real n2, real h)
#else
real HLSValue(n1, n2, h)
real n1; real n2; real h;
#endif
{
    while (h < 0.0) h += 1.0;
    while (h > 1.0) h -= 1.0;

    if (h < 1.0 / 6.0)
    {
	return n1 + (n2 - n1) * h * 6.0;
    }
    else if (h < 0.5)
    {
	return n2;
    }
    else if (h < 4.0 / 6.0)
    {
	return n1 + (n2 - n1) * (4.0 / 6.0 - h) * 6.0;
    }
    else
    {
	return n1;
    }
}

#ifdef PROTO
void HLS2RGB(real *r, real *g, real *b, real h, real l, real s)
#else
void HLS2RGB(r, g, b, h, l, s)
real *r, *g, *b, h, s, l;
#endif
/*Converts h l s into r g b.  All within range 0..1
  Adapted from Foley & VanDam*/
{
    real m1, m2;
    if (l <= 0.5)
    {
	m2 = l * (1.0 + s);
    }
    else
    {
	m2 = l + s - l * s;
    }
    m1 = 2 * l - m2;

    if (s == 0)
    {
	*r = *g = *b = 1.0;
    }
    else
    {
	*r = HLSValue(m1, m2, h + 1.0 / 3.0);
	*g = HLSValue(m1, m2, h);
	*b = HLSValue(m1, m2, h - 1.0 / 3.0);
    }

    if (*r > 1.0) *r = 0.0; else if (*r < 0.0) *r = 0.0;
    if (*g > 1.0) *g = 0.0; else if (*g < 0.0) *g = 0.0;
    if (*b > 1.0) *b = 0.0; else if (*b < 0.0) *b = 0.0;
}

#ifdef PROTO
void RGB2HSV(real *h, real *s, real *v, real r, real g, real b)
#else
void RGB2HSV(h, s, v, r, g, b)
real r, g, b, *h, *s, *v;
#endif
/*Converts rgb to hsv.  All numbers within range 0 to 1.*/
{
    real max, min;

    max = MAX(r, MAX(g, b));
    min = MIN(r, MIN(g, b));
    *v = max;

    if (max > 0.0)
    {
	*s = (max - min) / max;
    }
    else
    {
	*s = 0;
    }

    if (*s > 0.0)
    {
	real rc, gc, bc;

	rc = (max - r) / (max - min);
	gc = (max - g) / (max - min);
	bc = (max - b) / (max - min);
	if (r == max)
	{
	    *h = (bc - gc) / 6.0;
	}
	else if (g == max)
	{
	    *h = (2.0 + rc - bc) / 6.0;
	}
	else
	{
	    *h = (4.0 + gc - rc) / 6.0;
	}
    }
    else
    {
	*h = 0.0;
    }
    if (*h < 0.0) *h += 1.0;
}

#ifdef PROTO
void RGB2HLS(real *h, real *l, real *s, real r, real g, real b)
#else
void RGB2HLS(h, l, s, r, g, b)
real r, g, b, *h, *s, *l;
#endif
/*Converts rgb to hls.  All numbers within range 0 to 1.*/
{
    real max, min;

    max = MAX(r, MAX(g, b));
    min = MIN(r, MIN(g, b));
    *l = (max + min) * 0.5;

    if (max == min)
    {
	*s = 0.0;
	*h = 0.0;
    }
    else
    {
	real rc, gc, bc;

	if (*l <= 0.5)
	{
	    *s = (max - min) / (max + min);
	}
	else
	{
	    *s = (max - min) / (2 - max - min);
	}

	rc = (max - r) / (max - min);
	gc = (max - g) / (max - min);
	bc = (max - b) / (max - min);
	if (r == max)
	{
	    *h = (bc - gc) / 6.0;
	}
	else if (g == max)
	{
	    *h = (2.0 + rc - bc) / 6.0;
	}
	else
	{
	    *h = (4.0 + gc - rc) / 6.0;
	}
    }
}

static ObjPtr PressColorWheel(object, mouseX, mouseY, flags)
ObjPtr object;
int mouseX, mouseY;
long flags;
/*Track a click in an HS control.  Double-click snaps to closest pure hue*/
{
    int left, right, bottom, top;
    int cx, cy;
    int width, height, radius;
    ObjPtr backColor;			/*Color of the background*/
    FuncTyp drawContents;		/*Routine to draw the contents*/
    ObjPtr valueArray;
    real value[2];
    int lastX, lastY;			/*Last X and Y mouse position*/
    int sX, sY;				/*Shifted x and y for calculation*/
    Bool dontTrack;			/*True iff don't track*/

    Get2DIntBounds(object, &left, &right, &bottom, &top);

    if (mouseX < left || mouseX > right || mouseY < bottom || mouseY > top)
    {
	return ObjFalse;
    }

    if (TOOL(flags) == T_HELP)
    {
	ContextHelp(object);
	return ObjTrue;
    }

    if (!GetPredicate(object, ACTIVATED)) return ObjTrue;

    MakeMeCurrent(object);

    SaveForUndo(object);

    cx = (left + right) / 2;
    cy = (bottom + top) / 2;
    width = right - left;
    height = top - bottom;
    
    if (width > height)
    {
	radius = height / 2 - COLORWHEELBORDER;
    }
    else
    {
	radius = width / 2 - COLORWHEELBORDER;
    }

    /*Get the current value of the control*/
    valueArray = GetFixedArrayVar("PressColorWheel", object, VALUE, 1, 2L);
    if (!valueArray)
    { 
	return ObjFalse;
    }
    Array2CArray(value, valueArray);

    /*Make laxtX and lastY correspond to value*/
    lastX = cx + value[1] * cos(2.0 * M_PI * value[0]);
    lastY = cy + value[1] * sin(2.0 * M_PI * value[0]);

    if (flags & F_DOUBLECLICK)
    {
	/*Snap current value to closest pure hue*/
	if (value[1] < 0.33)
	{
	    /*White*/
	    value[1] = 0.0;
	}
	else
	{
	    int testo;
	    /*50 % or 100% saturated something*/
	    if (value[1] > 0.66)
	    {
	        value[1] = 1.0;
	    }
	    else
	    {
		value[1] = 0.5;
	    }
	    testo = value[0] * 6.0 + 0.5;
	    value[0] = ((real) testo) / 6.0;
	}
	valueArray = NewRealArray(1, 2L);
	CArray2Array(valueArray, value);
	SetVar(object, VALUE, valueArray);
	DrawMe(object);
	ChangedValue(object);
	if (logging)
	{
	    LogControl(object);
	}
	return ObjTrue;
    }

    dontTrack = GetPredicate(object, TRACKNOT);

    InhibitLogging(true);
    while (Mouse(&mouseX, &mouseY))
    {
	if (mouseX != lastX || mouseY != lastY)
	{
	    real hue, saturation;
	    /*Mouse has moved.  Update.*/
	    sX = mouseX - cx;
	    sY = mouseY - cy;

	    if (sX == 0 && sY == 0)
	    {
		/*It's at the origin, so choose 0 for h and s*/
		hue = 0.0;
		saturation = 0.0;
	    }
	    else
	    {
		/*Hue is angle*/
		hue = atan2(((double) sY) / ((double) radius),
			    ((double) sX) / ((double) radius)) / (2.0 * M_PI);
		while (hue < 0.0) hue += 1.0;

		/*Saturation is radius, clipped*/
		saturation = sqrt(((double) sY) * ((double) sY) +
				  ((double) sX) * ((double) sX)) / ((double) radius);
		if (saturation > 1.0) saturation = 1.0;
		if (flags & F_SHIFTDOWN)
		{
		    if (saturation > 0.66)
		    {
		        saturation = 1.0;
		    }
		    else if (saturation > 0.33)
		    {
			saturation = 0.5;
		    }
		    else
		    {
			saturation = 0.0;
		    }
		}
	    }

	    /*See if value is different*/
	    valueArray = GetVar(object, VALUE);
	    if (valueArray)
	    {
		Array2CArray(value, valueArray);
		if (hue == value[0] && saturation == value[1])
		{
		    continue;
		}
	    }
	    value[0] = hue;
	    value[1] = saturation;
	    valueArray = NewRealArray(1, 2L);
	    CArray2Array(valueArray, value);
	    SetVar(object, VALUE, valueArray);
	    DrawMe(object);
	    if (!dontTrack)
	    {
		ChangedValue(object);
	    }
	}
    }
    if (dontTrack)
    {
	ChangedValue(object);
    }
    InhibitLogging(false);
    if (logging)
    {
	LogControl(object);
    }
    return ObjTrue;
}

ObjPtr SetColorWheelVal(object, value)
ObjPtr object;
ObjPtr value;
/*Sets the value of the object to value*/
{
    if (value == NULLOBJ ||
	(IsRealArray(value) && RANK(value) == 1 && DIMS(value)[0] == 2))
    {
	SetVar(object, VALUE, value);
	ImInvalid(object);
	ChangedValue(object);
	if (logging)
	{
	    LogControl(object);
	}
	return ObjTrue;
    }
    else 
    {
	return ObjFalse;
    }
}

static ObjPtr CleanupPalette(palette)
ObjPtr palette;
/*Cleans up the palette before deleting*/
{
    PPtr *runner;
    if (((PPtr) palette) -> beg > 0 && ((PPtr) palette) -> nColors > 0)
    {
	/*There are some colors*/
	FreeColors(((PPtr) palette) -> beg, ((PPtr) palette) -> nColors);
    }
    if (((PPtr) palette) -> colors)
    {
	free(((PPtr) palette) -> colors);
	((PPtr) palette) -> colors = 0;
    }
    if (((PPtr) palette) -> components)
    {
	free(((PPtr) palette) -> components);
	((PPtr) palette) -> components = 0;
    }
    runner = &activePalettes;
    while (*runner)
    {
	if (*runner == (PPtr) palette)
	{
	    *runner = (*runner) -> next;
	}
	else
	{
	    runner = &((*runner) -> next);
	}
    }
    return ObjTrue;
}

ObjPtr NewPalette(nColors)
int nColors;
/*Returns a new palette of nColors, initially set to a grey ramp.  Returns
  0 if it can't.*/
{
    int k, colorBeg;
    PPtr retVal;
    short3 *colors, *components;

    colors = (short3 *) malloc(sizeof(short3) * nColors);
    if (!colors)
    {
	OMErr();
	return NULLOBJ;
    }

    components = (short3 *) malloc(sizeof(short3) * nColors);
    if (!components)
    {
	free(colors);
	OMErr();
	return NULLOBJ;
    }

    retVal = (PPtr) 
	NewObject(paletteClass, 
		 sizeof(Palette) - sizeof(Thing));
    if (!retVal)
    {
	return (ObjPtr) retVal;
    }
    retVal -> next = activePalettes;
    activePalettes = retVal -> next;
    SETOBJTYPE(retVal -> thing . flags, PALETTE);
    retVal -> colors = colors;
    retVal -> components = components;
    retVal -> beg = -1;
    retVal -> nColors = nColors;
    retVal -> min = -1.0;
    retVal -> max = 1.0;

    for (k = 0; k < nColors; ++k)
    {
	retVal -> colors[k][0] = 
	retVal -> colors[k][1] = 
	retVal -> colors[k][2] = (k + 1) * 255 / nColors;
    }
    CopyColorsToComponents(retVal, k, nColors - 1);
    return (ObjPtr) retVal;
}

ObjPtr NewAtomicPalette()
/*Returns a new atomic palette, which maps atomic numbers onto colors*/
{
    int atom;
    short3 *colors;
    ObjPtr retVal;

    retVal = NewPalette(N_ATOMS + 3);
    SetPaletteMinMax(retVal, 1, (real) N_ATOMS);

    /*Give it some colors*/
    colors = ((PPtr) retVal) -> colors;
    colors += 2;

    for (atom = 0; atom < N_ATOMS; ++atom)
    {
	(*colors)[0] = atomInfo[atom] . color[0];
	(*colors)[1] = atomInfo[atom] . color[1];
	(*colors)[2] = atomInfo[atom] . color[2];
	++colors;
    }

    return retVal;
}

void CopyPalette(d, s)
ObjPtr d, s;
/*Copies palette s to d*/
{
    int k;

    /*Free the colors*/
    if (((PPtr) d) -> beg > 0)
    {
	FreeColors(((PPtr) d) -> beg, ((PPtr) d) -> nColors);
	((PPtr) d) -> beg = -1;
    }

    if (((PPtr) d) -> nColors != ((PPtr) s) -> nColors)
    {
	SetVar(d, CHANGED, ObjTrue);
	free(((PPtr) d) -> colors);
	free(((PPtr) d) -> components);
	((PPtr) d) -> colors = malloc(sizeof(short3) * ((PPtr) s) -> nColors);
	((PPtr) d) -> components = malloc(sizeof(short3) * ((PPtr) s) -> nColors);
	((PPtr) d) -> nColors = ((PPtr) s) -> nColors;
    }
    ((PPtr) d) -> min = ((PPtr) s) -> min;
    ((PPtr) d) -> max = ((PPtr) s) -> max;

    for (k = 0; k < ((PPtr) d) -> nColors; ++k)
    {
	((PPtr) d) -> colors[k][0] = ((PPtr) s) -> colors[k][0];
	((PPtr) d) -> colors[k][1] = ((PPtr) s) -> colors[k][1];
	((PPtr) d) -> colors[k][2] = ((PPtr) s) -> colors[k][2];
	((PPtr) d) -> components[k][0] = ((PPtr) s) -> components[k][0];
	((PPtr) d) -> components[k][1] = ((PPtr) s) -> components[k][1];
	((PPtr) d) -> components[k][2] = ((PPtr) s) -> components[k][2];
    }
    SetVar(d, COLORMODEL, GetVar(s, COLORMODEL));
    SetVar(d, JUSTCOLORCHANGE, ObjTrue);

    MapPalette(d);
}

void SetPaletteNColors(p, nColors)
ObjPtr p;
int nColors;
/*Sets palette to be a nColors palette, doing any necessary resampling.*/
{
    short3 *oldColors, *colors;
    short3 *oldComponents, *components;
    real beg, end, increment;
    int s1, s2, i, k, comp;
    int temp;

    /*Free the colors*/
    if (((PPtr) p) -> beg > 0)
    {
	FreeColors(((PPtr) p) -> beg, ((PPtr) p) -> nColors);
	((PPtr) p) -> beg = -1;
    }

    /*Make a new set of colors*/
    oldColors = ((PPtr) p) -> colors;
    colors = (short3 *) malloc(nColors * sizeof(short3));

    /*And a new set of components*/
    oldComponents = ((PPtr) p) -> components;
    components = (short3 *) malloc(nColors * sizeof(short3));

    /*Copy ov, und, missing*/
    colors[0][0] = oldColors[0][0];
    colors[0][1] = oldColors[0][1];
    colors[0][2] = oldColors[0][2];

    colors[1][0] = oldColors[1][0];
    colors[1][1] = oldColors[1][1];
    colors[1][2] = oldColors[1][2];

    colors[nColors - 1][0] = oldColors[((PPtr) p) -> nColors][0];
    colors[nColors - 1][1] = oldColors[((PPtr) p) -> nColors][1];
    colors[nColors - 1][2] = oldColors[((PPtr) p) -> nColors][2];

    increment = ((real) ((PPtr) p) -> nColors - 3) / ((real) nColors - 3);
    
    for (k = 0; k < nColors - 2; ++k)
    {
	beg = k * increment;
	end = beg + increment;
	for (comp = 0; comp < 3; ++comp)
	{
	    s1 = beg;
	    s2 = end;

	    if (s1 == s2)
	    {
		/*Chunk of just one color*/
		colors[k + 2][comp] = oldColors[s1 + 2][comp];
	    }
	    else
	    {
		/*Chunk of more than one color*/
		real cum;

		/*First one*/
		cum = (1.0 - (beg - s1)) * oldColors[s1 + 2][comp];

		/*Intermediate ones*/
		for (i = s1 + 3; i < s2 + 2; ++i)
		{
		    cum += oldColors[i][comp];
		}

		/*Last one*/
		cum += (end - s2) * oldColors[s2 + 2][comp];

		cum /= (end - beg);

		temp = cum + 0.5;
		if (temp < 0) temp = 0;
		else if (temp > 255) temp = 255;

		colors[k + 2][comp] = temp;
	    }
	}
    }

    /*Update nColors*/
    ((PPtr) p) -> nColors = nColors;
    ((PPtr) p) -> colors = colors;
    ((PPtr) p) -> components = components;

    /*Free the old colors*/
    free(oldColors);
    free(oldComponents);

    CopyColorsToComponents((PPtr) p, 0, nColors - 1);

    SetVar(p, CHANGED, ObjTrue);
}

#ifdef PROTO
void CopyAttenuatedPalette(ObjPtr d, ObjPtr s, real atten)
#else
void CopyAttenuatedPalette(d, s, atten)
ObjPtr d, s;
real atten;
#endif
/*Copies palette s to d, attenuated by d*/
{
    int k;

    /*Free the colors*/
    if (((PPtr) d) -> beg > 0)
    {
	FreeColors(((PPtr) d) -> beg, ((PPtr) d) -> nColors);
	((PPtr) d) -> beg = -1;
    }

    if (((PPtr) d) -> nColors != ((PPtr) s) -> nColors)
    {
	SetVar(d, CHANGED, ObjTrue);
	SAFEFREE(((PPtr) d) -> colors);
	((PPtr) d) -> colors = malloc(sizeof(short3) * ((PPtr) s) -> nColors);
	SAFEFREE(((PPtr) d) -> components);
	((PPtr) d) -> components = malloc(sizeof(short3) * ((PPtr) s) -> nColors);
	((PPtr) d) -> nColors = ((PPtr) s) -> nColors;
    }
    ((PPtr) d) -> min = ((PPtr) s) -> min;
    ((PPtr) d) -> max = ((PPtr) s) -> max;

    for (k = 0; k < ((PPtr) d) -> nColors; ++k)
    {
	((PPtr) d) -> colors[k][0] = ((PPtr) s) -> colors[k][0] * atten;
	((PPtr) d) -> colors[k][1] = ((PPtr) s) -> colors[k][1] * atten;
	((PPtr) d) -> colors[k][2] = ((PPtr) s) -> colors[k][2] * atten;
    }

    SetVar(d, JUSTCOLORCHANGE, ObjTrue);
    CopyColorsToComponents((PPtr) d, 0, ((PPtr) d) -> nColors - 1);
    MapPalette(d);
}

ObjPtr ClonePalette(oldPalette)
ObjPtr oldPalette;
/*Clones a new palette*/
{
    PPtr retVal;
    short3 *colors, *components;

    colors = (short3 *) malloc(sizeof(short3) * ((PPtr) oldPalette) -> nColors);
    if (!colors)
    {
	OMErr();
	return oldPalette;
    }

    components = (short3 *) malloc(sizeof(short3) * ((PPtr) oldPalette) -> nColors);
    if (!components)
    {
	OMErr();
	return oldPalette;
    }

    retVal = (PPtr) 
	NewObject(paletteClass,
		sizeof(Palette) - sizeof(Thing));
    if (!retVal)
    {
	return (ObjPtr) retVal;
    }
    retVal -> next = activePalettes;
    activePalettes = retVal -> next;
    SETOBJTYPE(retVal -> thing . flags, PALETTE);
    retVal -> colors = colors;
    retVal -> components = components;
    retVal -> beg = -1;
    retVal -> nColors = ((PPtr) oldPalette) -> nColors;

    CopyPalette((ObjPtr) retVal, oldPalette);
    return (ObjPtr) retVal;
}

void InterpPalette(palette, r1, g1, b1, r2, g2, b2)
ObjPtr palette;
int r1, g1, b1, r2, g2, b2;
/*Makes palette be an interpolation between r1, g1, b1, and r2, g2, b2*/
{
    int k;
    int n;

    n = ((PPtr) palette) -> nColors - 3;	/*Room for min, max, missing*/

    for (k = 0; k < n; ++k)
    {
	((PPtr) palette) -> colors[k + 2][0] =
	    (k + 1) * r2 / n + 
	    (n - k) * r1 / n;
	((PPtr) palette) -> colors[k + 2][1] = 
	    (k + 1) * g2 / n + 
	    (n - k) * g1 / n;
	((PPtr) palette) -> colors[k + 2][2] =
	    (k + 1) * b2 / n + 
	    (n - k) * b1 / n;
    }

    /*Fill in missing*/
    ((PPtr) palette) -> colors[0][0] = 64;
    ((PPtr) palette) -> colors[0][1] = 64;
    ((PPtr) palette) -> colors[0][2] = 64;

    /*Fill in min*/
    ((PPtr) palette) -> colors[1][0] = ((PPtr) palette) -> colors[2][0];
    ((PPtr) palette) -> colors[1][1] = ((PPtr) palette) -> colors[2][1];
    ((PPtr) palette) -> colors[1][2] = ((PPtr) palette) -> colors[2][2];

    /*Fill in max*/
    ((PPtr) palette) -> colors[n + 2][0] = ((PPtr) palette) -> colors[n + 1][0];
    ((PPtr) palette) -> colors[n + 2][1] = ((PPtr) palette) -> colors[n + 1][1];
    ((PPtr) palette) -> colors[n + 2][2] = ((PPtr) palette) -> colors[n + 1][2];

    CopyColorsToComponents((PPtr) palette, 0, ((PPtr) palette) -> nColors - 1);

    MapPalette(palette);
}

void BlueToRedPalette(p)
ObjPtr p;
/*Makes a blue to red palette in p*/
{
    int k;
    real hue, r, g, b;
    int n;

    n = ((PPtr) p) -> nColors - 3;	/*Room for min, max, missing*/

    for (k = 0; k < n; ++k)
    {
	real v = 1.0;
/*
	hue = (((real)(n - k))
		/ (real) n) * 1.15 - 0.2;
*/
/*
	hue = (((real)(n - k))
		/ (real) n) * 0.8;
*/
	hue = (((real)(n - k))
		/ (real) n) * 0.9 - 0.1;
	if (hue < 0.0) {v = 1.0 + (hue * 3.0); hue = 0.0;} 
	HSV2RGB(&r, &g, &b, hue, 1.0, v);
	((PPtr) p) -> colors[k + 2][0] = r * 255;
	((PPtr) p) -> colors[k + 2][1] = g * 255;
	((PPtr) p) -> colors[k + 2][2] = b * 255;
    }

    /*Fill in missing*/
    ((PPtr) p) -> colors[0][0] = 64;
    ((PPtr) p) -> colors[0][1] = 64;
    ((PPtr) p) -> colors[0][2] = 64;

    /*Fill in min*/
    ((PPtr) p) -> colors[1][0] = ((PPtr) p) -> colors[2][0];
    ((PPtr) p) -> colors[1][1] = ((PPtr) p) -> colors[2][1];
    ((PPtr) p) -> colors[1][2] = ((PPtr) p) -> colors[2][2];

    /*Fill in max*/
    ((PPtr) p) -> colors[n + 2][0] = ((PPtr) p) -> colors[n + 1][0];
    ((PPtr) p) -> colors[n + 2][1] = ((PPtr) p) -> colors[n + 1][1];
    ((PPtr) p) -> colors[n + 2][2] = ((PPtr) p) -> colors[n + 1][2];

    CopyColorsToComponents((PPtr) p, 0, ((PPtr) p) -> nColors - 1);

    MapPalette(p);
}

void GreenToRedPalette(p)
ObjPtr p;
/*Makes a green to red palette in p*/
{
    int k;
    real hue, r, g, b;
    int n;

    n = ((PPtr) p) -> nColors - 3;	/*Room for min, max, missing*/

    for (k = 0; k < n; ++k)
    {
	hue = ((real)(n - k))
		/ (real) n * 0.35;
	HSV2RGB(&r, &g, &b, hue, 1.0, 1.0);
	((PPtr) p) -> colors[k + 2][0] = r * 255;
	((PPtr) p) -> colors[k + 2][1] = g * 255;
	((PPtr) p) -> colors[k + 2][2] = b * 255;
    }

    /*Fill in missing*/
    ((PPtr) p) -> colors[0][0] = 64;
    ((PPtr) p) -> colors[0][1] = 64;
    ((PPtr) p) -> colors[0][2] = 64;

    /*Fill in min*/
    ((PPtr) p) -> colors[1][0] = ((PPtr) p) -> colors[2][0];
    ((PPtr) p) -> colors[1][1] = ((PPtr) p) -> colors[2][1];
    ((PPtr) p) -> colors[1][2] = ((PPtr) p) -> colors[2][2];

    /*Fill in max*/
    ((PPtr) p) -> colors[n + 2][0] = ((PPtr) p) -> colors[n + 1][0];
    ((PPtr) p) -> colors[n + 2][1] = ((PPtr) p) -> colors[n + 1][1];
    ((PPtr) p) -> colors[n + 2][2] = ((PPtr) p) -> colors[n + 1][2];

    CopyColorsToComponents((PPtr) p, 0, ((PPtr) p) -> nColors - 1);

    MapPalette(p);
}

void DisposePalette(palette)
ObjPtr palette;
/*Disposes of palette*/
{
    free(palette);
}

real PSColor()
/*Returns a grayscale equivalent for the current color in DRAW_POSTSCRIPT mode*/
{
    real y, dummy;
    real h, s, v, w;
    int ival;
    real retVal;

    RGB2HSV(&h, &s, &v, curRed/255.0, curGreen/255.0, curBlue/255.0);
    RGB2YIQ(&y, &dummy, &dummy, curRed/255.0, curGreen/255.0, curBlue/255.0);

    w = (s - 0.25) * 4.0 / 3.0;
    retVal = (w * y + (1.0 - w) * v);
    if (retVal > 1.0) retVal = 1.0;
    if (retVal < 0.0) retVal = 0.0;

    ival = retVal * 255.0;
    retVal = ((real) LinToGamma[ival]) / 255.0;

    if (retVal > 1.0) retVal = 1.0;
    if (retVal < 0.0) retVal = 0.0;
    return retVal;
}

void SetUIColor(c)
int c;
/*Sets the current drawing color to user interface color c, regardless
  of whether the current window is RGB or colormap*/
{
#ifdef GRAPHICS
    if (drawingMode == DRAW_SCREEN)
    {
	if (overDraw)
	{
	if (c == UIBLACK)
	{
	    color(OVERCLEAR);
	}
	else
	{
	    color(OVERRED);
	}
	}
	else
	if (rgbp)
	{
	    /*It's an RGB window*/
	    lmcolor(LMC_COLOR);
#ifdef INDIGOCOLORS
	    if (uiColorIndex[c] >= 0)
	    {
		color(uiColorIndex[c]);
	    }
	    else

#endif
	    c3s(uiColors[c]);
	}
	else
	{
	    /*It's a cmap window*/
	    color(uiColorIndex[c]);
	}
    }
    else
    {
	curRed = uiColors[c][0];
	curGreen = uiColors[c][1];
	curBlue = uiColors[c][2];
    }
#endif
}

ObjPtr NewColorWheel(left, right, bottom, top, name)
int left, right, bottom, top;
char *name;
/*Makes a new hue/saturation control in left, right, bottom, top*/
{
    real value[2];
    ObjPtr valueArray;
    ObjPtr retVal;

    
    value[0] = 0.0;
    value[1] = 0.0;
    valueArray = NewRealArray(1, (long) 2);
    if (valueArray)
    {
	CArray2Array(valueArray, value);

	retVal = NewObject(colorWheelClass, 0);
	Set2DIntBounds(retVal, left, right, bottom, top);
	SetVar(retVal, VALUE, valueArray);
	SetVar(retVal, NAME, NewString(name));
	return retVal;
    }
    else
    {
	return NULLOBJ;
    }
}

static int ColorToPixel(colorBar, clr)
ObjPtr colorBar;
int clr;
/*Returns the horizontal pixel for the color value in clr*/
{
    int left, right, bottom, top;
    PPtr palette;
    int nColors;

    /*Get the palette*/
    palette = (PPtr) GetPaletteVar("ColorToPixel", colorBar, REPOBJ);
    if (!palette)
    {
	return 0;
    }

    Get2DIntBounds(colorBar, &left, &right, &bottom, &top);

    /*Get the stuff from the palette*/
    nColors = palette -> nColors;

    if (clr == 0)
    {
	/*Missing*/
	return left + CBLBORDER + CBLTEXTSPACE + CBBOXWIDTH / 2;
    }
    else if (clr == 1)
    {
	/*Underflow*/
	return left + CBLBORDER + CBLTEXTSPACE + CBBOXWIDTH + CBHGAP + CBBOXWIDTH / 2;
    }
    else if (clr == nColors - 1)
    {
	/*Overflow*/
	return right - CBRBORDER - CBBOXWIDTH / 2;
    }
    else
    {
	/*In the center*/
	int r, l;

	l = left + CBLBORDER + CBLTEXTSPACE + 2 * CBBOXWIDTH + 2 * CBHGAP + 1;
	r = right  - CBRBORDER - CBBOXWIDTH - CBHGAP - 1;

	return ((clr - 2) * (r - l) + (r - l) / 2) / (nColors - 3) + l;
    }
}

static Bool ColorToPixels(int *, int *, ObjPtr, int);

static Bool ColorToPixels(lPix, rPix, colorBar, clr)
ObjPtr colorBar;
int clr;
int *lPix, *rPix;
/*Returns the horizontal pixelx around the color value in clr*/
{
    int left, right, bottom, top;
    PPtr palette;
    int nColors;

    /*Get the palette*/
    palette = (PPtr) GetPaletteVar("ColorToPixel", colorBar, REPOBJ);
    if (!palette)
    {
	return false;
    }

    Get2DIntBounds(colorBar, &left, &right, &bottom, &top);

    /*Get the stuff from the palette*/
    nColors = palette -> nColors;

    if (clr == 0)
    {
	/*Missing*/
	*lPix = left + CBLBORDER + CBLTEXTSPACE + 1;
	*rPix = left + CBLBORDER + CBLTEXTSPACE + CBBOXWIDTH - 1;
	return true;
    }
    else if (clr == 1)
    {
	/*Underflow*/
	*lPix = left + CBLBORDER + CBLTEXTSPACE + CBBOXWIDTH + CBHGAP + 1;
	*rPix = left + CBLBORDER + CBLTEXTSPACE + 2 * CBBOXWIDTH + CBHGAP - 1;
	return true;
    }
    else if (clr == nColors - 1)
    {
	/*Overflow*/
	*lPix = right - CBRBORDER - CBBOXWIDTH + 1;
	*rPix = right - CBRBORDER - 1;
	return true;
    }
    else
    {
	/*In the center*/
	int r, l;

	l = left + CBLBORDER + CBLTEXTSPACE + 2 * CBBOXWIDTH + 2 * CBHGAP + 1;
	r = right  - CBRBORDER - CBBOXWIDTH - CBHGAP - 1;

	if (clr == 2)
	{
	    *lPix = l;
	}
	else
	{
	    *lPix = l + (clr - 2) * (r - l) / (nColors - 3) + 1;
	}
	*rPix = l + (clr - 1) * (r - l) / (nColors - 3);

	return true;
    }
}

static int PixelToColor(colorBar, pix)
ObjPtr colorBar;
int pix;
/*Returns the color for the horizontal pixel pixel*/
{
    int left, right, bottom, top;
    PPtr palette;
    int nColors;

    /*Get the palette*/
    palette = (PPtr) GetPaletteVar("ColorToPixel", colorBar, REPOBJ);
    if (!palette)
    {
	return 0;
    }

    Get2DIntBounds(colorBar, &left, &right, &bottom, &top);

    /*Get the stuff from the palette*/
    nColors = palette -> nColors;

    if (pix < left + CBLBORDER + CBLTEXTSPACE + CBBOXWIDTH + CBHGAP / 2)
    {
	/*Missing*/
	return 0;
    }
    else if (pix < left + CBLBORDER + CBLTEXTSPACE + 2 * CBBOXWIDTH + CBHGAP)
    {
	/*Underflow*/
	return 1;
    }
    else if (pix > right - CBRBORDER - CBBOXWIDTH)
    {
	/*Overflow*/
	return nColors - 1;
    }
    else
    {
	/*In the center*/
	int r, l, retVal;

	l = left + CBLBORDER + CBLTEXTSPACE + 2 * CBBOXWIDTH + 2 * CBHGAP + 1;
	r = right  - CBRBORDER - CBBOXWIDTH - CBHGAP - 1;
	retVal = ((pix - l) * (nColors - 3) + (nColors - 3) / 2) / (r - l)
		+ 2;
	if (retVal >= nColors - 1)
	{
	    retVal = nColors - 2;
	}
	if (retVal < 2)
	{
	    retVal = 2;
	}
	return retVal;
    }
}

#ifdef PROTO
void CalcGoodSteps(double diff, int pixWidth, int minMajorPix, double *majorWidth, int *nMinorSteps)
#else
void CalcGoodSteps(diff, pixWidth, minMajorPix, majorWidth, nMinorSteps)
double diff;
int pixWidth;
int minMajorPix;
double *majorWidth;
int *nMinorSteps;
#endif
/*Calculates good steps for a 10-base scale
  diff is the difference between minimum and max
  pixWidth is the width in pixels between minimum and max
  minMajorPix is the minimum number of pixels between major tics
  *majorWidth will be the width of a major step
  *nMinorTics will be the number of minor steps
*/
{
    long deltaLog;	/*Log of the delta at minimum*/
    double minDelta;	/*Minimum delta between major tics*/

    if (diff <= 0.0)
    {
	*majorWidth = 0.0;
	*nMinorSteps = 1;
    }

    minDelta = diff * minMajorPix / pixWidth;

    deltaLog = ((long) (1000.0 + log10((double) minDelta))) - 1000;
    *majorWidth = pow((double) 10.0, (double) deltaLog);
    *nMinorSteps = 1;

    while (*majorWidth < minDelta)
    {
	if (*majorWidth < minDelta)
	{
	    /*Try 2*/
	    *majorWidth *= 2.0;
	    *nMinorSteps = 2;
	}
	if (*majorWidth < minDelta)
	{
	    /*Try 5*/
	    *majorWidth *= 2.5;
	    *nMinorSteps = 5;
	}
	if (*majorWidth < minDelta)
	{
	    /*Try 10*/
	    *majorWidth *= 2.0;
	    *nMinorSteps = 10;
	}
    }
}

void ResetColorBarTools(colorBar)
ObjPtr colorBar;
/*Resets the tools associated with a color bar*/
{
    ObjPtr radio, freeFormButton;
    
    InhibitLogging(true);
    freeFormButton = GetVar(colorBar, FREEFORMBUTTON);
    radio = GetVar(colorBar, TOOLGROUP);
    if (radio)
    {
	/***UPDATE For some reason, this doesn't work properly.*/
	SetValue(radio, NewInt(freeFormButton ? 
				(GetPredicate(freeFormButton, ACTIVATED) ? PT_FREEFORM : -1)
				: PT_FREEFORM));
    }
    InhibitLogging(false);
}

ObjPtr SetColorBarVal(object, value)
ObjPtr object;
ObjPtr value;
/*Sets the value of the object to value*/
{
    if (IsRealArray(value) && RANK(value) == 1 && DIMS(value)[0] == 3)
    {
	ObjPtr radio;
	PPtr repObj;
	real *elements;

	elements = ELEMENTS(value);
	if (elements[0] < 0.0 || elements[0] > 3.0 ||
	    elements[1] < 0.0 || elements[2] < 0.0)
	{
	    ReportError("SetColorBarVal", "Value error");
	    return ObjFalse;
	}

	repObj = (PPtr) GetPaletteVar("SetColorBarVal", object, REPOBJ);
	if (repObj)
	{
	    if (elements[1] >= ((real) repObj -> nColors) - 0.5 ||
		elements[2] >= ((real) repObj -> nColors) - 0.5)
	    {
		ReportError("SetColorBarVal", "Value error");
		return ObjFalse;
	    }
	}

	SetVar(object, VALUE, value);
	ImInvalid(object);

	ChangedValue(object);
	if (logging)
	{
	    LogControl(object);
	}

	/*Set the edit tool to 0*/
	ResetColorBarTools(object);

	return ObjTrue;
    }
    else
    {
	ReportError("SetColorBarVal", "Bad value for color bar");
	return ObjFalse;
    }
}

static ObjPtr ReinitColorBar(colorBar)
ObjPtr colorBar;
/*Reinitializes a color bar*/
{
    return ObjTrue;
}

real GetColorValue(palette, color)
ObjPtr palette;
int color;
/*Gets a field value for center of color color.  Ignores missing*/
{
    return
	((PPtr) palette) -> min +
	(color - 2) * (((PPtr) palette) -> max - ((PPtr) palette) -> min) /
	(((PPtr) palette) -> nColors - 4);
}

static int hueColors[7] =
    {
	UIRED, UIYELLOW, UIGREEN, UICYAN, UIBLUE, UIMAGENTA, UIRED
    };

#ifdef PROTO
static void ColorModelRect(int left, int right, int bottom, int top, int cm, int k)
#else
static void ColorModelRect(left, right, bottom, top, cm, k)
int left;
int right;
int bottom;
int top;
int cm;
int k;
#endif
/*Fills a rectangle within the bounds for color model cm in field k*/
{
#ifdef GRAPHICS
    switch (cm)
    {
	case CM_RGB:
	    FillUIRect(left, right, bottom, top, 
			k == 1 ? UIBLUE : k == 2 ? UIGREEN : UIRED);
	    break;
	case CM_HSV:
	case CM_HLS:
	    shademodel(GOURAUD);
	    if (k == 3)
	    {
		/*Hue*/
		int c;
		long v[2];

		bgnpolygon();
		SetUIColor(UIRED);
		v[1] = bottom;
		v[0] = left;
		v2i(v);
		v[0] = right;
		v2i(v);

		for (c = 1; c < 5; ++c)
		{
		    SetUIColor(hueColors[c]);
		    v[1] = bottom + (top - bottom) * c / 6;
		    v[0] = right;
		    v2i(v);
		    v[0] = left;
		    v2i(v);
		    endpolygon();

		    bgnpolygon();
		    v2i(v);
		    v[0] = right;
		    v2i(v);
		}
		SetUIColor(hueColors[c]);
		v[1] = top;
		v[0] = right;
		v2i(v);
		v[0] = left;
		v2i(v);
		endpolygon();
	    }
	    else if ((cm == CM_HSV && k == 2) ||
		     (cm == CM_HLS && k == 1))
	    {
		/*Saturation*/
		long v[2];
		bgnpolygon();
		SetUIColor(UIWHITE);
		v[0] = left;
		v[1] = bottom;
		v2i(v);
		v[0] = right;
		v2i(v);
		SetUIColor(UICYAN);
		v[1] = top;
		v2i(v);
		v[0] = left;
		v2i(v);
		endpolygon();
	    }
	    else if (cm == CM_HSV)
	    {
		/*Must be value*/
		long v[2];
		bgnpolygon();
		SetUIColor(UIBLACK);
		v[0] = left;
		v[1] = bottom;
		v2i(v);
		v[0] = right;
		v2i(v);
		SetUIColor(UICYAN);
		v[1] = top;
		v2i(v);
		v[0] = left;
		v2i(v);
		endpolygon();
	    }
	    else
	    {
		/*Must be lightness*/
		long v[2];
		bgnpolygon();
		SetUIColor(UIBLACK);
		v[0] = left;
		v[1] = bottom;
		v2i(v);
		v[0] = right;
		v2i(v);
		SetUIColor(UICYAN);
		v[1] = (top + bottom) / 2;
		v2i(v);
		v[0] = left;
		v2i(v);
		endpolygon();

		bgnpolygon();
		v[0] = left;
		v2i(v);
		v[0] = right;
		v2i(v);
		SetUIColor(UIWHITE);
		v[1] = top;
		v2i(v);
		v[0] = left;
		v2i(v);
		endpolygon();
	    }
	
	    shademodel(FLAT);
	    break;
	case CM_YIQ:
	    shademodel(GOURAUD);
	    if (k == 3)
	    {
		/*Must be y*/
		long v[2];
		bgnpolygon();
		SetUIColor(UIBLACK);
		v[0] = left;
		v[1] = bottom;
		v2i(v);
		v[0] = right;
		v2i(v);
		SetUIColor(UICYAN);
		v[1] = (top + bottom) / 2;
		v2i(v);
		v[0] = left;
		v2i(v);
		endpolygon();

		bgnpolygon();
		v[0] = left;
		v2i(v);
		v[0] = right;
		v2i(v);
		SetUIColor(UIWHITE);
		v[1] = top;
		v2i(v);
		v[0] = left;
		v2i(v);
		endpolygon();
	    }
	    else if (k == 2)
	    {
		/*i, cyan/orange index (use red instead of orange)*/
		long v[2];
		bgnpolygon();
		SetUIColor(UICYAN);
		v[0] = left;
		v[1] = bottom;
		v2i(v);
		v[0] = right;
		v2i(v);
		SetUIColor(UIRED);
		v[1] = top;
		v2i(v);
		v[0] = left;
		v2i(v);
		endpolygon();
	    }
	    else
	    {
		/*q, green/magenta axis*/
		long v[2];
		bgnpolygon();
		SetUIColor(UIGREEN);
		v[0] = left;
		v[1] = bottom;
		v2i(v);
		v[0] = right;
		v2i(v);
		SetUIColor(UIMAGENTA);
		v[1] = top;
		v2i(v);
		v[0] = left;
		v2i(v);
		endpolygon();
	    }
	
	    shademodel(FLAT);
	    break;
    }
#endif
}

#ifdef PROTO
static void DrawFunctionBox(real *elements, int l, int r, int b, int t, real start, real finish)
#else
static void DrawFunctionBox(elements, l, r, b, t, start, finish)
real *elements;
int l, r, b, t;
real start, finish;
#endif
/*Draws a function box*/
{
		int boxL, boxR, boxB, boxT, boxX, boxY;
    real ddiff;

    ddiff = finish - start;

		boxL = l + (elements[0] - start) * (r - l) / (ddiff);
		boxR = l + (elements[1] - start) * (r - l) / (ddiff);
		boxB = b + elements[2] * (t - b) / 255.0;
		boxT = b + elements[3] * (t - b) / 255.0;
		boxX = (boxL + boxR) / 2;
		boxY = (boxB + boxT) / 2;

		/*Now draw the box and its handles*/
		FrameUIRect(boxL + 1, boxR + 2, boxB - 2, boxT - 1, UIBLACK);
		FrameUIRect(boxL + 1 - CBHANDLESIZE, boxL + 1,
			    boxY - 1 - CBHANDLESIZE / 2, boxY - 1 + CBHANDLESIZE / 2, UIBLACK);
		FrameUIRect(boxR + 2, boxR + 2 + CBHANDLESIZE,
			    boxY - 1 - CBHANDLESIZE / 2, boxY - 1 + CBHANDLESIZE / 2, UIBLACK);
		FrameUIRect(boxX + 2 - CBHANDLESIZE / 2, boxX + 2 + CBHANDLESIZE / 2,
			    boxT - 1, boxT - 1 + CBHANDLESIZE, UIBLACK);
		FrameUIRect(boxX + 2 - CBHANDLESIZE / 2, boxX + 2 + CBHANDLESIZE / 2,
			    boxB - 2 - CBHANDLESIZE, boxB - 2, UIBLACK);

		FrameUIRect(boxL, boxR, boxB, boxT, UIYELLOW);
		FrameUIRect(boxL - 1, boxR + 1, boxB - 1, boxT + 1, UIYELLOW);

		FillUIRect(boxL - 1 - CBHANDLESIZE, boxL,
			    boxY - CBHANDLESIZE / 2, boxY + 1 + CBHANDLESIZE / 2, UIYELLOW);
		FillUIRect(boxR, boxR + 1 + CBHANDLESIZE,
			    boxY - CBHANDLESIZE / 2, boxY + 1 + CBHANDLESIZE / 2, UIYELLOW);
		FillUIRect(boxX - CBHANDLESIZE / 2, boxX + 1 + CBHANDLESIZE / 2,
			    boxT, boxT + 1 + CBHANDLESIZE, UIYELLOW);
		FillUIRect(boxX - CBHANDLESIZE / 2, boxX + 1 + CBHANDLESIZE / 2,
			    boxB - 1 - CBHANDLESIZE, boxB, UIYELLOW);
}

static ObjPtr DrawColorBar(colorBar)
ObjPtr colorBar;
/*Draws a color bar*/
{
#ifdef GRAPHICS
    int left, right, bottom, top;
    int l, r, b, t;
    int start, diff, comp;
    PPtr palette;
    int nColors;
    short3 *colors;
    ObjPtr value;			/*Value of the control*/
    real *elements;
    ObjPtr var;
    int highlighted;
    int k;
    double majorWidth, minorWidth;	/*Width of a major step*/
    double ddiff;				/*Data difference*/
    int nTics;				/*Number of minor tics*/
    long temp;
    double halfSpace;
    int pixel;				/*Temporary pixel*/
    double curValue;			/*Current value for making tics*/
    int cm;				/*Color model in use*/

    Get2DIntBounds(colorBar, &left, &right, &bottom, &top);
    if (IsDrawingRestricted(left, right, bottom, top)) return ObjFalse;

    /*Get the palette*/
    palette = (PPtr) GetPaletteVar("DrawColorBar", colorBar, REPOBJ);
    if (!palette)
    {
	return ObjFalse;
    }

    /*Get the stuff from the palette*/
    nColors = palette -> nColors;
    colors = palette -> colors;

    /*Draw the bump and plateau, but not if the changed bounds is smaller than
      the bounds*/
    var = GetVar(colorBar, CHANGEDBOUNDS);
    if (!var || !IsArray(var))
    {
	DrawRaisedRect(left, right, bottom, top, UIBACKGROUND);
    }
    else
    {
	elements = ELEMENTS(var);
	if (left >= CURSTATE . screenMask[0] - CURSTATE . translation[0] &&
	    right <= CURSTATE . screenMask[1] - CURSTATE . translation[0]  &&
	    bottom >= CURSTATE . screenMask[2] - CURSTATE . translation[1]  &&
	    top <= CURSTATE . screenMask[3] - CURSTATE . translation[1])
	{
	    DrawRaisedRect(left, right, bottom, top, UIBACKGROUND);
	}
    }

    /*Draw the legends and tic marks and stuff at the bottom*/
    SetUIColor(UIBLACK);
    SetupFont(CBTEXTFONT, CBTEXTSIZE);
    b = bottom + CBBORDER + CBTEXTUP + CBTEXTSEP;

    DrawAString(CENTERALIGN, 
		left + CBLBORDER + CBLTEXTSPACE + CBBOXWIDTH / 2,
		bottom + CBBORDER + CBTEXTUP, "Missing");
    DrawUILine( left + CBLBORDER + CBLTEXTSPACE + CBBOXWIDTH / 2, b,
		left + CBLBORDER + CBLTEXTSPACE + CBBOXWIDTH / 2, b - CBMAJORTICLEN, UIBLACK);
    DrawAString(CENTERALIGN,
		left + CBLBORDER + CBLTEXTSPACE + CBBOXWIDTH + CBHGAP + CBBOXWIDTH / 2,
		bottom + CBBORDER + CBTEXTUP, "Under");
    DrawUILine( left + CBLBORDER + CBLTEXTSPACE + CBBOXWIDTH + CBHGAP + CBBOXWIDTH / 2, b,
		left + CBLBORDER + CBLTEXTSPACE + CBBOXWIDTH + CBHGAP + CBBOXWIDTH / 2, b - CBMAJORTICLEN, UIBLACK);

    /*Draw all the tics in the middle*/
    l = left + CBLBORDER + CBLTEXTSPACE + 2 * CBBOXWIDTH + 2 * CBHGAP + 1;
    r = right  - CBRBORDER - CBBOXWIDTH - CBHGAP - 1;
    halfSpace = (palette -> max - palette -> min) / (palette -> nColors - 4) * 0.5;
    ddiff = palette -> max - palette -> min;

    CalcGoodSteps(ddiff,
		  r - l,
		  CBSTEPPIXELS,
		  &majorWidth, &nTics);
    minorWidth = majorWidth / nTics;

    /*Minor and major tics first*/
    temp = palette -> min / majorWidth;
    curValue = temp * majorWidth;

    while (curValue > palette -> min)
    {
	curValue -= majorWidth;
    }
    k = 0;
    while (curValue < palette -> min)
    {
	++k;
	if (k >= nTics) k = 0;

	curValue += minorWidth;
    }

    /*Now actually draw them*/
    if (ABS(curValue) < ddiff * 1.0E-6) curValue = 0.0;
    while (curValue <= palette -> max + ddiff * 1.0E-6)
    {
	pixel = l + (curValue - palette -> min) * (r - l) / (ddiff);
	if (k == 0)
	{
	    /*Major tic*/
	    DrawUILine(pixel, b,
		       pixel, b - CBMAJORTICLEN,
		       UIBLACK);
	    sprintf(tempStr, "%lg", curValue);
	    DrawAString(CENTERALIGN, pixel, b - CBTEXTSEP, tempStr);
	}
	else
	{
	    /*Minor tic*/
	    DrawUILine(pixel, b,
		       pixel, b - CBMAJORTICLEN / 2,
		       UIBLACK);
	}

	curValue += minorWidth;
	if (ABS(curValue) < ddiff * 1.0E-6) curValue = 0.0;
	++k;
	if (k >= nTics) k = 0;
    }

    DrawAString(CENTERALIGN,
		right - CBRBORDER - CBBOXWIDTH + CBBOXWIDTH / 2,
		bottom + CBBORDER + CBTEXTUP, "Over");
    DrawUILine( right - CBRBORDER - CBBOXWIDTH + CBBOXWIDTH / 2, b,
		right - CBRBORDER - CBBOXWIDTH + CBBOXWIDTH / 2, b - CBMAJORTICLEN, UIBLACK);

    /*Get the color model*/
    var = GetIntVar("DrawColorBar", (ObjPtr) palette, COLORMODEL);
    if (!var)
    {
	return ObjFalse;
    }
    cm = GetInt(var);

    /*Draw the boxes*/
    b = bottom + CBBORDER + CBTEXTUP + CBTEXTSEP;
    SetupFont(CBBIGTEXTFONT, CBBIGTEXTSIZE);

    for (k = 0; k < 4; ++k)
    {
	t = b + CBCOMPHEIGHT;

	/*Draw the box outlines*/

	/*Draw the missing data box*/
	FrameUIRect(left + CBLBORDER + CBLTEXTSPACE, left + CBLBORDER + CBLTEXTSPACE + CBBOXWIDTH, b, t, UIBLACK);

	/*Now the underflow box*/
	FrameUIRect(left + CBLBORDER + CBLTEXTSPACE + CBBOXWIDTH + CBHGAP,
		left + CBLBORDER + CBLTEXTSPACE + 2 * CBBOXWIDTH + CBHGAP,
		b, t, UIBLACK);

	/*Now the middle bit*/
	FrameUIRect(left + CBLBORDER + CBLTEXTSPACE + 2 * CBBOXWIDTH + 2 * CBHGAP,
		right  - CBRBORDER - CBBOXWIDTH - CBHGAP,
		b, t, UIBLACK);

	/*Now the overflow box*/
	FrameUIRect(right - CBRBORDER - CBBOXWIDTH,
		right - CBRBORDER,
		b, t, UIBLACK);

	/*Draw the background of the boxex*/

	/*Draw the missing data box*/
	ColorModelRect(left + CBLBORDER + CBLTEXTSPACE + 1, left + CBLBORDER + CBLTEXTSPACE + CBBOXWIDTH - 1, b + 1, t - 1, 
			cm, k);

	/*Now the underflow box*/
	ColorModelRect(left + CBLBORDER + CBLTEXTSPACE + CBBOXWIDTH + CBHGAP + 1,
			left + CBLBORDER + CBLTEXTSPACE + 2 * CBBOXWIDTH + CBHGAP - 1,
			b + 1, t - 1, cm, k);

	/*Now the middle bit*/
	ColorModelRect(left + CBLBORDER + CBLTEXTSPACE + 2 * CBBOXWIDTH + 2 * CBHGAP + 1,
			right  - CBRBORDER - CBBOXWIDTH - CBHGAP - 1,
			b + 1, t - 1, cm, k);

	/*Now the overflow box*/
	ColorModelRect(right - CBRBORDER - CBBOXWIDTH + 1,
			right - CBRBORDER - 1,
			b + 1, t - 1, cm, k);

	/*Now the component names*/
	if (k)
	{
	    SetUIColor(UIBLACK);
	    DrawAString(LEFTALIGN, 
		left + CBLBORDER, (b + t) / 2 - CBCOMPTEXTDOWN,
		shortComponentNames[cm][3 - k]);
	}

	b = t + CBVGAP;
    }

    /*Now the expanded readout*/
    FrameUIRect(left + CBLBORDER,
		right  - CBRBORDER,
		b, top - CBTBORDER - CBTEXTDOWN - CBTEXTDOWNSEP, UIBLACK);

    b = bottom + CBBORDER + CBTEXTUP + CBTEXTSEP + 1;
    t = b + CBCOMPHEIGHT - 2;

    /*Now draw the colors*/
    if (rgbp)
    {
	/*It's in RGB mode, so we don't have to set the palette*/

	l = left + CBLBORDER + CBLTEXTSPACE + 2 * CBBOXWIDTH + 2 * CBHGAP + 1;
	r = right  - CBRBORDER - CBBOXWIDTH - CBHGAP - 1;

	/*Do missing data box*/
	c3s(colors[0]);
	FillRect( left + CBLBORDER + CBLTEXTSPACE + 1, 
		left + CBLBORDER + CBLTEXTSPACE + CBBOXWIDTH - 1, b, t);

	/*Do underflow data box*/
	c3s(colors[1]);
	FillRect( left + CBLBORDER + CBLTEXTSPACE + CBBOXWIDTH + CBHGAP + 1,
		left + CBLBORDER + CBLTEXTSPACE + 2 * CBBOXWIDTH + CBHGAP - 1, b, t);

	/*Do the colors in the center*/
	diff = r - l;
	start = l;
	for (k = 2; k < nColors - 1; ++k)
	{
	    r = (k - 1) * diff / (nColors - 3) + start;
	    c3s(colors[k]);
	    FillRect(l, r, b, t);
	    l = r;
	}

	/*Do overflow data box*/
	c3s(colors[nColors - 1]);
	FillRect( right - CBRBORDER - CBBOXWIDTH + 1,
		right - CBRBORDER - 1, b, t);
    }
    else
    {
	/*It's a color map window, have to set the colors*/
	int beg;

	l = left + CBLBORDER + CBLTEXTSPACE + 2 * CBBOXWIDTH + 2 * CBHGAP + 1;
	r = right  - CBRBORDER - CBBOXWIDTH - CBHGAP - 1;

	SetPalette((ObjPtr) palette);

	beg = palette -> beg;

	/*Do missing data box*/
	color(beg);
	FillRect( left + CBLBORDER + CBLTEXTSPACE + 1,
		left + CBLBORDER + CBLTEXTSPACE + CBBOXWIDTH - 1, b, t);

	/*Do underflow data box*/
	color(beg + 1);
	FillRect( left + CBLBORDER + CBLTEXTSPACE + CBBOXWIDTH + CBHGAP + 1,
		left + CBLBORDER + CBLTEXTSPACE + 2 * CBBOXWIDTH + CBHGAP - 1, b, t);

	/*Do the colors in the center*/
	diff = r - l;
	start = l;
	for (k = 2; k < nColors - 1; ++k)
	{
	    r = (k - 1) * diff / (nColors - 3) + start;
	    color(beg + k);
	    FillRect(l, r, b, t);
	    l = r;
	}

	/*Do overflow data box*/
	color(beg + nColors - 1);
	FillRect( right - CBRBORDER - CBBOXWIDTH + 1, 
		right - CBRBORDER - 1, b, t);
    }


    b = t + CBVGAP + 2;
    t = b + CBCOMPHEIGHT - 1;
    /*Do 3 components*/
    for (comp = 2; comp >= 0; --comp)
    {
	int height;

	SetUIColor(UIGRAY62);

	/*Do missing data box*/
	height = GetColorComponent(palette, 0, comp) * (CBCOMPHEIGHT - 1) / 255;
	if (height < CBCOMPHEIGHT - 1)
	{
	    FillRect( left + CBLBORDER + CBLTEXTSPACE + 1, 
		    left + CBLBORDER + CBLTEXTSPACE + CBBOXWIDTH - 1, 
		    t - CBCOMPHEIGHT + height + 1, t - 1);
	}

	/*Do underflow data box*/
	height = GetColorComponent(palette, 1, comp) * (CBCOMPHEIGHT - 1) / 255;
	if (height < CBCOMPHEIGHT - 1)
	{
	    FillRect( left + CBLBORDER + CBLTEXTSPACE + CBBOXWIDTH + CBHGAP + 1,
		    left + CBLBORDER + CBLTEXTSPACE + 2 * CBBOXWIDTH + CBHGAP - 1,
		    t - CBCOMPHEIGHT + height + 1, t - 1);
	}

	/*Do the colors in the center*/
	l = left + CBLBORDER + CBLTEXTSPACE + 2 * CBBOXWIDTH + 2 * CBHGAP + 1;
	r = right  - CBRBORDER - CBBOXWIDTH - CBHGAP - 1;
	diff = r - l;
	start = l;
	for (k = 2; k < nColors - 1; ++k)
	{
	    r = (k - 1) * diff / (nColors - 3) + start;
	    height = GetColorComponent(palette, k, comp) * (CBCOMPHEIGHT - 1) / 255;
	    if (height < CBCOMPHEIGHT - 1)
	    {
		FillRect(l, r, t - CBCOMPHEIGHT + height + 1, t - 1);
	    }
	    l = r;
	}

	/*Overflow*/
	height = GetColorComponent(palette, nColors - 1, comp) * (CBCOMPHEIGHT - 1) / 255;
	if (height < CBCOMPHEIGHT - 1)
	{
	    FillRect(right - CBRBORDER - CBBOXWIDTH + 1, 
		right - CBRBORDER - 1, t - CBCOMPHEIGHT + height + 1, t - 1);
	}
	b = t + CBVGAP + 1;
	t = b + CBCOMPHEIGHT - 1;
    }

    /*Now deal with the value*/
    value = GetValue(colorBar);
    if (!value)
    {
	/*Have to give it a value*/
	value = NewRealArray(1, 3L);
	elements = ELEMENTS(value);
	elements[0] = 0.0;
	elements[1] = 0.0;
	elements[2] = 0.0;
    }
    else
    {
	elements = ELEMENTS(value);
    }

    if (elements[0] >= 0.5)
    {
	/*Draw the selection in the magnified area*/
	int dummy;

	b = bottom + CBBORDER + CBTEXTUP + CBTEXTSEP +
		((int) (elements[0] - 0.5)) * (CBVGAP + CBCOMPHEIGHT);
	t = b + CBCOMPHEIGHT;

	ColorToPixels(&l, &dummy, colorBar, (int) (elements[1] + 0.5));
	ColorToPixels(&dummy, &r, colorBar, (int) (elements[2] + 0.5));

	FrameUIRect(l - 1, r + 3, b - 2, t, UIBLACK);
	FrameUIRect(l - 2, r + 1, b, t + 1, UIYELLOW);
	FrameUIRect(l - 3, r + 2, b - 1, t + 2, UIYELLOW);

	/*Draw the expanded range*/
	if (elements[0] == 1.0)
	{
	    int c1, c2;
	    /*Draw the full color*/
	    l = left + CBLBORDER + 1;
	    r = right  - CBRBORDER - 1;
	    t = top - CBTBORDER - CBTEXTDOWN - CBTEXTDOWNSEP - 1;
	    b = bottom + CBBORDER + CBTEXTUP + CBTEXTSEP + 4 * CBCOMPHEIGHT
		+ 4 * CBVGAP + 1;
	    if (rgbp)
	    {
		/*It's in RGB mode, so we don't have to set the palette*/

		/*Do the colors in the center*/
		diff = r - l;
		start = l;
		c1 = elements[1];
		c2 = elements[2];
		
		for (k = c1; k <= c2; ++k)
		{
		    r = (k - c1 + 1) * diff / (c2 - c1 + 1) + start;
		    c3s(colors[k]);
		    FillRect(l, r, b, t);
		    l = r;
		}
	    }
	    else
	    {
		/*It's a color map window, have to set the colors*/
		int beg;
		SetPalette((ObjPtr) palette);

		beg = palette -> beg;

		/*Do the colors in the center*/
		diff = r - l;
		start = l;
		c1 = elements[1];
		c2 = elements[2];
		for (k = c1; k <=c2; ++k)
		{
		    r = (k - c1 + 1) * diff / (c2 - c1 + 1) + start;
		    color(beg + k);
		    FillRect(l, r, b, t);
		    l = r;
		}
	    }
	}
	else
	{
	    int c1, c2;
	    int height;
	    c1 = elements[1];
	    c2 = elements[2];

	    comp = 4 - ((int) elements[0]);

	    l = left + CBLBORDER + 1;
	    r = right  - CBRBORDER - 1;
	    t = top - CBTBORDER - CBTEXTDOWN - CBTEXTDOWNSEP - 1;
	    b = bottom + CBBORDER + CBTEXTUP + CBTEXTSEP + 4 * CBCOMPHEIGHT
		+ 4 * CBVGAP + 1;

            ColorModelRect(l, r, b, t, cm, (int) elements[0] - 1);

	    SetUIColor(UIGRAY62);
	    /*Do the colors in the center*/
	    diff = r - l;
	    start = l;
	    for (k = c1; k <= c2; ++k)
	    {
		r = (k - c1 + 1) * diff / (c2 - c1 + 1) + start;
		height = GetColorComponent(palette, k, comp) * (t - b) / 255.0 + 0.5;
		if (height < t - b)
		{
		    FillRect(l, r, b + height, t);
		}
		l = r;
	    }
	}
    }	

    /*Draw the tics at the top*/
    if (elements[0] > 0.0)
    {
	real start, finish, halfSpace;
	l = left + CBLBORDER + 1;
	r = right - CBRBORDER - 1;
	t = top - CBTBORDER - CBTEXTDOWN - CBTEXTDOWNSEP - 1;
	b = bottom + CBBORDER + CBTEXTUP + CBTEXTSEP +  4 * CBCOMPHEIGHT + 4 * CBVGAP + 1;

	if  (elements[2] >= elements[1])
	{
	    /*It's a range*/

	    halfSpace = (palette -> max - palette -> min) / (palette -> nColors - 4) * 0.5;
	    start = GetColorValue((ObjPtr) palette, (int) elements[1]) - halfSpace;
	    finish = GetColorValue((ObjPtr) palette, (int) elements[2]) + halfSpace;
	    ddiff = finish - start;

	    CalcGoodSteps(ddiff,
		      r - l,
		      CBSTEPPIXELS,
		      &majorWidth, &nTics);

	    minorWidth = majorWidth / nTics;

	    /*Minor and major tics first*/
	    temp = start / majorWidth;
	    curValue = temp * majorWidth;
	    while (curValue > start)
	    {
		 curValue -= majorWidth;
	    }
	    k = 0;

	    while (curValue < start)
	    {
		++k;
		if (k >= nTics) k = 0;

		curValue += minorWidth;
	    }

	    SetupFont(CBBIGTEXTFONT, CBBIGTEXTSIZE);

	    /*Now actually draw them*/
	    if (ABS(curValue) < ddiff * 1.0E-6) curValue = 0.0;
	    while (curValue <= finish + ddiff * 1.0E-6)
	    {
		pixel = l + (curValue - start) * (r - l) / (ddiff);
		if (k == 0)
		{
		    /*Major tic*/
		    DrawUILine(pixel,
			    top - CBTBORDER - CBTEXTDOWN - CBTEXTDOWNSEP,
			    pixel,
			    top - CBTBORDER - CBTEXTDOWN - CBTEXTDOWNSEP + CBMAJORTICLEN,
			    UIBLACK);

		    sprintf(tempStr, "%lg", curValue);
		    {
			DrawAString(CENTERALIGN, pixel, top - CBTBORDER - CBTEXTDOWN, tempStr);
		    }
		}
		else
		{
		    /*Minor tic*/
		    DrawUILine(pixel,
			    top - CBTBORDER - CBTEXTDOWN - CBTEXTDOWNSEP,
			    pixel,
			    top - CBTBORDER - CBTEXTDOWN - CBTEXTDOWNSEP + CBMAJORTICLEN / 2,
			    UIBLACK);
		}

		curValue += minorWidth;
		if (ABS(curValue) < ddiff * 1.0E-6) curValue = 0.0;
		++k;
		if (k >= nTics) k = 0;
	    }

	    /*Draw the function box, if any*/
	    var = GetVar(colorBar, FUNCTIONBOX);
	    if (var)
	    {
		real *elements;
		elements = ELEMENTS(var);

		SetClipRect(left + 3, right - 3, bottom + 3, top - 3);

		DrawFunctionBox(elements, l, r, b, t, start, finish);

		RestoreClipRect();
	    }
	}
    }

#endif
    return ObjTrue;
}

ObjPtr SetFunctionBox(colorBar, functionBox)
ObjPtr colorBar;
ObjPtr functionBox;
/*Sets the FUNCTIONBOX of colorBar to functionBox, also logs*/
{
    real *elements;
    PPtr repObj;
    if (functionBox && (!IsRealArray(functionBox) || RANK(functionBox) != 1 || 
	DIMS(functionBox)[0] != 4))
    {
	ReportError("SetFunctionBox", "Bad value given");
	return ObjFalse;
    }
    repObj = (PPtr) GetPaletteVar("SetFunctionBox", colorBar, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }
    if (functionBox)
    {
	elements = ELEMENTS(functionBox);
	if (elements[2] < -0.5 || elements[2] >= 255.5 ||
		elements[3] < -0.5 || elements[3] >= 255.5 ||
		elements[2] > elements[3] ||
		elements[0] > elements[1])
	{
	    ReportError("SetFunctionBox", "Value out of range");
	    return ObjFalse;
	}
    }

    SetVar(colorBar, FUNCTIONBOX, functionBox);

    if (logging)
    {
	char cmd[256];
	char *s;

	sprintf(cmd, "set functionbox ");
	s = &(cmd[0]);
	while (*s) ++s;
	MakeObjectName(s, colorBar);
	while (*s) ++s;
	*s++ = ' ';
	PrintScriptObject(s, functionBox);
	while (*s) ++s;
	*s++ = '\n';
	*s = 0;
	Log(cmd);
    }
    return ObjTrue;
}

void LogColorChange(colorBar, colors, newColor)
ObjPtr colorBar;
short3 *colors;
int newColor;
/*Logs a change to colors[newColor] within colorBar*/
{
    if (logging)
    {
	char cmd[256];
	char *s;

	sprintf(cmd, "set color ");
	s = &(cmd[0]);
	while (*s) ++s;
	MakeObjectName(s, colorBar);
	while (*s) ++s;
	*s++ = ' ';
	sprintf(s, "%d %d %d %d\n", newColor, colors[newColor][0], 
			colors[newColor][1], colors[newColor][2]);
	Log(cmd);
    }
}

ObjPtr SetColorBarColor(colorBar, whichColor, r, g, b)
ObjPtr colorBar;
int whichColor, r, g, b;
/*Sets color whichColor from inside a color bar*/
{
    PPtr palette;
    short3 *colors;

    palette = (PPtr) GetPaletteVar("SetColorBarColor", colorBar, REPOBJ);
    if (!palette)
    {
	return ObjFalse;
    }

    colors = palette -> colors;
    colors[whichColor][0] = r;
    colors[whichColor][1] = g;
    colors[whichColor][2] = b;

    ImInvalid(colorBar);
    LogColorChange(colorBar, colors, whichColor);
    ChangedValue(colorBar);

    CopyColorsToComponents(palette, whichColor, whichColor);

#ifdef GRAPHICS
    MapPaletteColors(palette, whichColor, whichColor);
#endif
    SetVar((ObjPtr) palette, JUSTCOLORCHANGE, ObjTrue);
    ForAllVisWindows(ImInvalid);
    return ObjTrue;
}

static ObjPtr PressColorBar(colorBar, x, y, flags)
ObjPtr colorBar;
int x, y;
long flags;
/*Does a press in a color bar beginning at x and y.  Returns
  true iff the press really was in the control.*/
{
#ifdef INTERACTIVE
    int left, right, bottom, top;
    int l, r, b, t;
    int k;
    int cm;
    Bool doubleP;

    Get2DIntBounds(colorBar, &left, &right, &bottom, &top);

    if (x >= left && x <= right && y >= bottom && y <= top)
    {
	real val[3];
	ObjPtr value, var;
	real *origVal;
	ObjPtr palette;
	/*Hey!  It really was a click in the color bar*/

	if (TOOL(flags) == T_HELP)
	{
	    ContextHelp(colorBar);
	    return ObjTrue;
	}

	b = bottom + CBBORDER + CBTEXTUP + CBTEXTSEP;
	if (y < b)
	{
	    /*Click below*/
	    val[0] = val[1] = val[2] = 0.0;
	    value = NewRealArray(1, 3L);
	    CArray2Array(value, val);
	    SetValue(colorBar, value);
	    return ObjTrue;
	}

	doubleP = IsDoubleBuf(selWinInfo) ? true : false;

	MakeMeCurrent(colorBar);

	SaveForUndo(colorBar);

	/*Get current value*/
	value = GetFixedArrayVar("PressColorBar", colorBar, VALUE, 1, 3L);
	if (!value)
	{
	    return ObjFalse;
	}

	origVal = (real *) ELEMENTS(value);

	palette = (ObjPtr) GetPaletteVar("PressColorBar", colorBar, REPOBJ);
	if (!palette)
	{
	    return ObjFalse;
	}

	/*Get the color model*/
	var = GetIntVar("PressColorBar", (ObjPtr) palette, COLORMODEL);
	if (!var)
	{
	    return ObjFalse;
	}
	cm = GetInt(var);

	/*See if it's in one of the components or full color*/
	for (k = 0; k < 4; ++k)
	{
	    t = b + CBCOMPHEIGHT;
	    if (y <= t && y >= b)
	    {
		ObjPtr radio;
		int newX, newY;
		int startColor;
		int lastColor;
		int newColor;
		Bool inp;			/*True if inside*/
		int dummy;			/*Dummy for colorToPixels*/
		int cb, ct, cl, cr;		/*b, t, l, r for cursor*/
		

		/*It's a press in track k*/
		InhibitLogging(true);

		if ((flags & F_SHIFTDOWN) || TOOL(flags) == T_ROTATE)
		{
		    /*Shift click, determine start from farthest*/
		    newColor = PixelToColor(colorBar, x);
		    if (ABS(newColor - origVal[2]) < ABS(newColor - origVal[1]))
		    {
			startColor = origVal[1];
		    }
		    else
		    {
			startColor = origVal[2];
		    }
		}
		else
		{
		    startColor = PixelToColor(colorBar, x);
		}
		lastColor = -1;

		val[0] = (real) (k + 1);

		inp = true;

		/*Set the edit tool to free form*/
		ResetColorBarTools(colorBar);

		if (!doubleP)
		{
		    DrawSkeleton(true);

		    cb = bottom + CBBORDER + CBTEXTUP + CBTEXTSEP +
			((int) (val[0] - 0.5)) * (CBVGAP + CBCOMPHEIGHT);
		    ct = b + CBCOMPHEIGHT;

		    ColorToPixels(&cl, &dummy, colorBar, (int) (val[1] + 0.5));
		    ColorToPixels(&dummy, &cr, colorBar, (int) (val[2] + 0.5));

		    FrameUIRect(cl - 2, cr + 1, cb, ct + 1, UIRED);
		    FrameUIRect(cl - 3, cr + 2, cb - 1, ct + 2, UIRED);
		}

		while (Mouse(&newX, &newY))
		{
		    if (newY > t + SLOP ||
			newY < b - SLOP ||
			newX < left - SLOP ||
			newX > right + SLOP)
		    {
			/*It's outside*/
			if (inp)
			{
			    /*Transition from in to out*/
			    SetVar(colorBar, VALUE, value);
			    inp = false;
			    lastColor = -1;
			    if (doubleP)
			    {
				DrawMe(colorBar);
			    }
			    else
			    {
				EraseAll();
			    }
			}
		    }
		    else
		    {
			/*It's inside*/
			if (!inp)
			{
			    /*Transition from out to in*/
			    inp = true;
			}
			newColor = PixelToColor(colorBar, newX);
			if (newColor != lastColor)
			{
			    ObjPtr var;

			    /*It's a new color, set it*/
			    if (newColor >= startColor)
			    {
				val[1] = (real) startColor;
				val[2] = (real) newColor;
			    }
			    else
			    {
				val[2] = (real) startColor;
				val[1] = (real) newColor;
			    }
			     var = NewRealArray(1, 3L);
			    CArray2Array(var, val);
			    SetVar(colorBar, VALUE, var);
			    if (doubleP)
			    {
				DrawMe(colorBar);
			    }
			    else
			    {
				ColorToPixels(&cl, &dummy, colorBar, (int) (val[1] + 0.5));
				ColorToPixels(&dummy, &cr, colorBar, (int) (val[2] + 0.5));
				EraseAll();
				FrameUIRect(cl - 2, cr + 1, cb, ct + 1, UIRED);
				FrameUIRect(cl - 3, cr + 2, cb - 1, ct + 2, UIRED);
			    }
			    lastColor = newColor;
			}
		    }
		}
		if (!doubleP)
		{
		    DrawSkeleton(false);
		}
		ChangedValue(colorBar);
		ImInvalid(colorBar);
		InhibitLogging(false);
		LogControl(colorBar);
		return ObjTrue;
	    }
	    b = t + CBVGAP;
	}

	/*It wasn't in a component, maybe it's in the readout*/
	t = top - CBTBORDER - CBTEXTDOWN - CBTEXTDOWNSEP - 1;
	l = left + CBLBORDER + 1;
	r = right - CBRBORDER - 1;
	if (origVal[0] > 1.0 &&
	    y >= b - SLOP && y <= t + SLOP && x >= l - SLOP && x <= r + SLOP)
	{
	    /*It is!  Do stuff based on edit mode*/
	    int editMode;
	    int lastColor, lastAmp;
	    PPtr palette;
	    int index1, index2;
	    int k;
	    int comp;
	    short3 *colors;
	    int newX, newY, newColor, newAmp;
	    ObjPtr panel, button;

	    palette = (PPtr) GetPaletteVar("PressColorBar", colorBar, REPOBJ);
	    if (!palette)
	    {
		return ObjFalse;
	    }

	    colors = palette -> colors;

	    comp = 4 - origVal[0];

	    var = GetIntVar("PressColorBar", colorBar, EDITMODE);
	    if (!var)
	    {
		return ObjFalse;
	    }
	    editMode = GetInt(var);

	    index1 = origVal[1] + 0.5;
	    index2 = origVal[2] + 0.5;

	    panel = GetVar(colorBar, PARENT);
	    if (panel)
	    {
		button = GetVar(panel, KEEPBUTTON);
		if (button)
		{
		    ActivateButton(button, true);
		}
		button = GetVar(panel, REVERTBUTTON);
		if (button)
		{
		    ActivateButton(button, true);
		}
	    }

	    if (editMode == PT_FREEFORM)
	    {
		int firstAltered, lastAltered;
		Bool constrainX, constrainY;
		/*Free form*/
		lastColor = lastAmp = -1;

		firstAltered = index2 + 1;
		lastAltered = index1 - 1;

		constrainX = constrainY = false;

		while (Mouse(&newX, &newY))
		{
		    if (newX >= l - SLOP && newX <= r + SLOP && newY >= b - SLOP && newY <= t + SLOP)
		    {
			
			if (flags & F_SHIFTDOWN)
			{
			    if (!constrainX && !constrainY)
			    {
				int xDiff, yDiff;

				/*Must make some constraints*/
				xDiff = ABS(newX - x);
				yDiff = ABS(newY - y);
				if (xDiff >= MINCONSTRAINT &&
				    yDiff >= MINCONSTRAINT)
				{
				    if (yDiff > xDiff)
				    {
					constrainX = true;
				    }
				    else
				    {
					constrainY = true;
				    }
				}
			    }
			    if (constrainX) newX = x;
			    if (constrainY) newY = y;
			}

			/*Calculate newAmp*/
			newAmp = ((newY - b) + 0.5) * 255.0 / (real) (t - b - 1);
			if (newAmp < 0) newAmp = 0;
			else if (newAmp > 255) newAmp = 255;

			/*Calculate newColor*/
			newColor = index1 + ((real) newX - l) * ((real) index2 - index1 + 1) / ((real) (r - l - 1));	    	
			if (newColor < index1) newColor = index1;
			else if (newColor > index2) newColor = index2;

			if (newColor < firstAltered)
			{
			    firstAltered = newColor;
			}
			if (newColor > lastAltered)
			{
			    lastAltered = newColor;
			}

			if (newAmp != lastAmp || newColor != lastColor)
			{
			    /*Set the new color to be correct*/
			    
			    if (lastColor <= -1 || lastColor == newColor)
			    {
				/*New in this range, just set this color*/
				SetColorComponent(palette, newColor, comp, newAmp);
#ifdef GRAPHICS
				MapPaletteColors(palette, newColor, newColor);
#endif
			    }
			    else if (newColor > lastColor)
			    {
				int nc;
				/*Linearly interpolate up to this color*/
				for (k = lastColor + 1; k <= newColor; ++k)
				{
				    nc = ((newColor - k) * lastAmp +
					  (k - lastColor) * newAmp) / 
						(newColor - lastColor);
				    if (nc < 0) nc = 0;
				    else if (nc > 255) nc = 255;

				    SetColorComponent(palette, k, comp, nc);

				}
#ifdef GRAPHICS
				MapPaletteColors(palette, lastColor, newColor);
#endif
			    }
			    else if (newColor < lastColor)
			    {
				int nc;
				/*Linearly interpolate down to this color*/
				for (k = lastColor - 1; k >= newColor; --k)
				{
				    nc = ((k - newColor) * lastAmp +
					  (lastColor - k) * newAmp) / 
						(lastColor - newColor);
				    if (nc < 0) nc = 0;
				    else if (nc > 255) nc = 255;

				    SetColorComponent(palette, k, comp, nc);
				}
#ifdef GRAPHICS
				MapPaletteColors(palette, lastColor, newColor);
#endif
			    }
			    DrawMeInBounds(colorBar, left + 3, right - 3, bottom + 3, top - 3);
 			    lastAmp = newAmp;
			    lastColor = newColor;
			}
		    }
		    else
		    {
			lastColor = lastAmp = -1;
		    }
		}
		for (k = firstAltered; k <= lastAltered; ++k)
		{
		    LogColorChange(colorBar, colors, k);
		}
		ChangedValue(colorBar);
		SetVar((ObjPtr) palette, JUSTCOLORCHANGE, ObjTrue);
		ForAllVisWindows(ImInvalid);
	    }
	    else
	    {
		/*It's a tool*/
		ObjPtr toolBounds;
		int boxL, boxR, boxB, boxT;
		int xOffset, yOffset;
		double start, finish, ddiff, halfSpace;
		real *elements;
		real newElements[4];
		int whichMove = 0;
		real lastSide, nextSide;
		double majorWidth, minorWidth;
		int nTics;
		long temp;

#define MOVEBOXTOP 	1
#define MOVEBOXBOTTOM	2
#define MOVEBOXLEFT	3
#define MOVEBOXRIGHT	4
#define MOVEBOXLR	5

		halfSpace = (palette -> max - palette -> min) / (palette -> nColors - 4) * 0.5;
		start = GetColorValue((ObjPtr) palette, index1) - halfSpace;
		finish = GetColorValue((ObjPtr) palette, index2) + halfSpace;
		ddiff = finish - start;

		toolBounds = GetFixedArrayVar("PressColorBar", colorBar, FUNCTIONBOX, 1, 4L);
		if (!toolBounds)
		{
		    return ObjFalse;
		}
		elements = ELEMENTS(toolBounds);
	
		boxL = l + (elements[0] - start) * (r - l) / (ddiff);
		boxR = l + (elements[1] - start) * (r - l) / (ddiff);
		boxB = b + elements[2] * (t - b) / 255.0;
		boxT = b + elements[3] * (t - b) / 255.0;

		if (!doubleP)
		{
		    DrawSkeleton(true);
		}

		/*Figure out which side is clicked on*/

		whichMove = 0;
		if (y >= boxB - (CBHANDLESIZE + 2) && y <= boxT + (CBHANDLESIZE + 2))
		{
		    if (x >= boxL - (CBHANDLESIZE + 2) && x <= boxL)
		    {
			whichMove = MOVEBOXLEFT;
			xOffset = x - boxL;
		    }
		    else if (x >= boxR && x <= boxR + (CBHANDLESIZE + 2))
		    {
			whichMove = MOVEBOXRIGHT;
			xOffset = x - boxR;
		    }
		}
		if (x >= boxL - (CBHANDLESIZE + 2) && x <= boxR + (CBHANDLESIZE + 2))
		{
		    if (y >= boxB - (CBHANDLESIZE + 2) && y <= boxB)
		    {
			whichMove = MOVEBOXBOTTOM;
			yOffset = y - boxB;
		    }
		    else if (y >= boxT && y <= boxT + (CBHANDLESIZE + 2))
		    {
			whichMove = MOVEBOXTOP;
			yOffset = y - boxT;
		    }
		} 
		if (!whichMove)
		{
		    whichMove = MOVEBOXLR;
		    xOffset = x - boxL;
		    yOffset = y - (boxT + boxB) / 2;
		}

		newElements[0] = elements[0];
		newElements[1] = elements[1];
		newElements[2] = elements[2];
		newElements[3] = elements[3];

		if (flags & F_SHIFTDOWN)
		{
		    /*Constrained motion, have to calc constraint stuff*/
		    halfSpace = (palette -> max - palette -> min) / (palette -> nColors - 4) * 0.5;
		    start = GetColorValue((ObjPtr) palette, index1) - halfSpace;
		    finish = GetColorValue((ObjPtr) palette, index2) + halfSpace;
		    ddiff = finish - start;
	
		    CalcGoodSteps(ddiff,
			      r - l,
			      CBSTEPPIXELS,
			      &majorWidth, &nTics);
		    minorWidth = majorWidth / nTics;
		}

		/*Now do it*/
		if (whichMove == MOVEBOXLEFT || whichMove == MOVEBOXRIGHT)
		{
		    lastSide = newElements[whichMove == MOVEBOXLEFT ? 0 : 1];
		    while (Mouse(&newX, &newY))
		    {		
			nextSide = start +
				(newX - xOffset - l) * (finish - start) / (r - l);
			if (flags & F_SHIFTDOWN)
			{
			    /*Constrain*/
			    temp = nextSide / minorWidth + 0.5;
			    nextSide = temp * minorWidth;
			}

			if (nextSide != lastSide)
			{
			    /*Change it*/

			    newElements[whichMove == MOVEBOXLEFT ? 0 : 1] = nextSide;
			    if (newElements[1] > newElements[0])
			    {
				toolBounds = NewRealArray(1, 4L);
				CArray2Array(toolBounds, newElements);
				SetFunctionBox(colorBar, toolBounds);
				InhibitLogging(true);
				ImposeColorFunction(colorBar);
				InhibitLogging(false);
				if (doubleP)
				{
				    DrawMe(colorBar);
				}
				else
				{
				    EraseAll();
				    DrawFunctionBox(newElements, l, r, b, t, start, finish);
				}
				lastSide = nextSide;
			    }
			}
		    }
		}
		else if (whichMove == MOVEBOXTOP || whichMove == MOVEBOXBOTTOM)
		{
		    lastAmp = newElements[whichMove == MOVEBOXBOTTOM ? 2 : 3] + 0.5;
		    while (Mouse(&newX, &newY))
		    {		
			newAmp = ((newY - yOffset - b) + 0.5) * 255.0 / (real) (t - b - 1);
			if (newAmp < 0) newAmp = 0;
			else if (newAmp > 255) newAmp = 255;
			if (newAmp != lastAmp)
			{
			    /*Change it*/

			    newElements[whichMove == MOVEBOXBOTTOM ? 2 : 3] = newAmp;
			    if (newElements[3] > newElements[2])
			    {
				toolBounds = NewRealArray(1, 4L);
				CArray2Array(toolBounds, newElements);
				SetFunctionBox(colorBar, toolBounds);
				InhibitLogging(true);
				ImposeColorFunction(colorBar);
				InhibitLogging(false);
				if (doubleP)
				{
				    DrawMe(colorBar);
				}
				else
				{
				    EraseAll();
				    DrawFunctionBox(newElements, l, r, b, t, start, finish);
				}
				lastAmp = newAmp;
			    }
			}
		    }
		}
		if (whichMove == MOVEBOXLR)
		{
		    lastSide = newElements[0];
		    while (Mouse(&newX, &newY))
		    {		
			nextSide = start +
				(newX - xOffset - l) * (finish - start) / (r - l);
			if (flags & F_SHIFTDOWN)
			{
			    /*Constrain*/
			    temp = nextSide / minorWidth + 0.5;
			    nextSide = temp * minorWidth;
			}
			if (nextSide != lastSide)
			{
			    /*Change it*/

			    newElements[0] = nextSide;
			    newElements[1] = nextSide + elements[1] - elements[0];
			    if (newElements[1] > newElements[0])
			    {
				toolBounds = NewRealArray(1, 4L);
				CArray2Array(toolBounds, newElements);
				SetFunctionBox(colorBar, toolBounds);
				InhibitLogging(true);
				ImposeColorFunction(colorBar);
				InhibitLogging(false);
				if (doubleP)
				{
				    DrawMe(colorBar);
				}
				else
				{
				    EraseAll();
				    DrawFunctionBox(newElements, l, r, b, t, start, finish);
				}
				lastSide = nextSide;
			    }
			}
		    }
		}
		if (!doubleP)
		{
		    DrawSkeleton(false);
		}
		ChangedValue(colorBar);
		SetVar((ObjPtr) palette, JUSTCOLORCHANGE, ObjTrue);
		ForAllVisWindows(ImInvalid);
	    }
	}

	return ObjTrue;
    }
    else
    {
	return ObjFalse;
    }
#else
    return ObjFalse;
#endif
}


ObjPtr NewColorBar(left, right, bottom, top, name)
int left, right, bottom, top;
char *name;
/*Makes a new hue/saturation control in left, right, bottom, top*/
{
    ObjPtr retVal;
    ObjPtr value;
    
    retVal = NewObject(colorBarClass, 0);
    Set2DIntBounds(retVal, left, right, bottom, top);
    SetVar(retVal, NAME, NewString(name));
    SetVar(retVal, TYPESTRING, NewString("color bar control"));
    value = NewRealArray(1, 3L);
    ((real *) ELEMENTS(value))[0] = 1.0;
    ((real *) ELEMENTS(value))[1] = 0.0;
    ((real *) ELEMENTS(value))[1] = 0.0;
    SetVar(retVal, VALUE, value);
    return retVal;
}

#ifdef PROTO
static void ChoosePaletteSliderValue(ObjPtr, PPtr, int, int, int);
#endif 

static void ChoosePaletteSliderValue(slider, palette, component, index1, index2)
ObjPtr slider;
PPtr palette;
int index1, index2, component;
/*Chooses a value for the slider based on colors index1 through index2 of
  palette.  Component is 0 for rgb, 1 for r, 2 for g, and 3 for b, */
{
    real min, max, testVal;
    short3 *colors;
    real hsv[3];
    int k;

    colors = palette -> colors;
    min = 1.0;
    max = 0.0;
    if (component)
    {
	for (k = index1; k <= index2; ++k)
	{
	    testVal = ((real) GetColorComponent(palette, k, component - 1)) / 255.0;
	    if (testVal < min)
	    {
		min = testVal;
	    }
	    if (testVal > max)
	    {
		max = testVal;
	    }
	}	
    }
    else
    {
	for (k = index1; k <= index2; ++k)
	{
	    RGB2HSV(&(hsv[0]), &(hsv[1]), &(hsv[2]),
		((real) colors[k][0]) / 255.0, 
		((real) colors[k][1]) / 255.0, 
		((real) colors[k][2]) / 255.0);
	    testVal = hsv[2];
	    if (testVal < min)
	    {
		min = testVal;
	    }
	    if (testVal > max)
	    {
		max = testVal;
	    }
	}
    }
    testVal = (min + max) * 0.5;
    if (testVal < 0.0) testVal = 0.0;
    if (testVal > 1.0) testVal = 1.0;
    SetSliderValue(slider, testVal);
    SetVar(slider, INITVALUE, NewReal(testVal));
    SetVar(slider, TEMPPALETTE, ClonePalette((ObjPtr) palette));
}

static ObjPtr ChangePaletteBar(colorBar)
ObjPtr colorBar;
/*Changed value for a color bar that controls a palette*/
{
    ObjPtr colorWheel, slider, button, buttons;
    ThingListPtr buttonList;
    PPtr palette;
    ObjPtr value, newValue;
    FuncTyp changedValue;
    real hsv[3];
    short3 *colors;
    int index;
    real *elements;

    value = GetFixedArrayVar("ChangePaletteBar", colorBar, VALUE, 1, 3L);
    colorWheel = GetObjectVar("ChangePaletteBar", colorBar, COLORWHEEL);
    palette = (PPtr) GetPaletteVar("ChangePaletteBar", colorBar, REPOBJ);
    slider = GetObjectVar("ChangePaletteBar", colorBar, SLIDER);

    if (!colorWheel || !palette || !value || !slider)
    {
	return ObjFalse;
    }

    elements = ELEMENTS(value);
    if (elements[0] == 1.0)
    {
	/*Active color wheel*/
	ActivateColorWheel(colorWheel, true);
	if (elements[1] == elements[2])
	{
	    /*Give it a value*/
	    colors = palette -> colors;

	    /*Update the left slider and color wheel*/
	    index = elements[1];

	    RGB2HSV(&(hsv[0]), &(hsv[1]), &(hsv[2]),
		    ((real) colors[index][0]) / 255.0, 
		    ((real) colors[index][1]) / 255.0, 
		    ((real) colors[index][2]) / 255.0);
	    if (hsv[2] < 0.0) hsv[2] = 0.0;
	    else if (hsv[2] > 1.0) hsv[2] = 1.0;
	}
	else
	{
	    /*No value*/
	    newValue = NULLOBJ;
	}
	changedValue = GetMethod(colorWheel, CHANGEDVALUE);
	SetMethod(colorWheel, CHANGEDVALUE, 0);
	newValue = NewRealArray(1, 2L);
	((real *) ELEMENTS(newValue))[0] = hsv[0];
	((real *) ELEMENTS(newValue))[1] = hsv[1];
	SetValue(colorWheel, newValue);
	SetMethod(colorWheel, CHANGEDVALUE, changedValue);
    }
    else
    {
	ActivateColorWheel(colorWheel, false);
    }

    buttons = GetVar(colorBar, FULLCOMPBUTTONS);
    if (buttons)
    {
	buttonList = LISTOF(buttons);
    }
    else
    {
	buttonList = 0;
    }

    if (elements[0] > 0.0)
    {
	/*Active Slider and buttons*/
	int index1, index2;
	int comp;

	changedValue = GetMethod(slider, CHANGEDVALUE);
	SetMethod(slider, CHANGEDVALUE, 0);
	ActivateSlider(slider, true);

	/*Get a value for the slider*/
	index1 = elements[1];
	index2 = elements[2];
	if (elements[0] == 1.0)
	{
	    comp = 0;
	}
	else
	{
	    comp = 5 - (int) elements[0];
	}
	ChoosePaletteSliderValue(slider, palette, comp,
		index1, index2);
	SetMethod(slider, CHANGEDVALUE, changedValue);

	/*Activate buttons*/
	while (buttonList)
	{
	    ActivateButton(buttonList -> thing, true);
	    buttonList = buttonList -> next;
	}
    }
    else
    {
	/*Inactive Slider*/
	ActivateSlider(slider, false);

	/*Deactivate buttons*/
	while (buttonList)
	{
	    ActivateButton(buttonList -> thing, false);
	    buttonList = buttonList -> next;
	}
    }
    buttons = GetVar(colorBar, COMPBUTTONS);
    if (buttons)
    {
	buttonList = LISTOF(buttons);
    }
    else
    {
	buttonList = 0;
    }
    if (elements[0] > 1.0 && elements[2] > elements[1])
    {
	/*Activate buttons*/
	while (buttonList)
	{
	    ActivateButton(buttonList -> thing, true);
	    buttonList = buttonList -> next;
	}
    }
    else
    {
	/*Deactivate buttons*/
	while (buttonList)
	{
	    ActivateButton(buttonList -> thing, false);
	    buttonList = buttonList -> next;
	}
    }

    button = GetVar(colorBar, FREEFORMBUTTON);
    if (elements[0] > 1.0)
    {
 	if (button)
	{
	    ActivateButton(button, true);
	}
    }
    else
    {
 	if (button)
	{
	    ActivateButton(button, false);
	}
    }

    ImInvalid(colorBar);
    return ObjTrue;
}

static ObjPtr ChangePaletteColorWheel(colorWheel)
ObjPtr colorWheel;
/*Changes a color wheel*/
{
    ObjPtr slider, colorBar, panel, button; 
    PPtr palette;
    ObjPtr var;
    real h, s, v;
    real r, g, b;
    int rs, gs, bs;
    short3 *colors;
    int index1, index2, k;
    real *elements;

    slider = GetObjectVar("ChangePaletteColorWheel", colorWheel, SLIDER);
    colorBar = GetObjectVar("ChangePaletteColorWheel", colorWheel, COLORBAR);

    if (!slider || !colorBar)
    {
	return ObjFalse;
    }

    palette = (PPtr) GetPaletteVar("ChangePaletteColorWheel", colorBar, REPOBJ);
    if (!palette)
    {
	return ObjFalse;
    }

    var = GetValue(colorBar);
    if (!var)
    {
	return ObjFalse;
    }
    if (((real *) ELEMENTS(var))[0] != 1.0)
    {
	return ObjFalse;
    }
    index1 = ((real *) ELEMENTS(var))[1];
    index2 = ((real *) ELEMENTS(var))[2];

    var = GetValue(colorWheel);
    if (!var)
    {
	return ObjFalse;
    }

    h = ((real *) ELEMENTS(var))[0];
    s = ((real *) ELEMENTS(var))[1];
    var = GetValue(slider);
    if (!var)
    {
	return ObjFalse;
    }
    v = GetReal(var);

    HSV2RGB(&r, &g, &b, h, s, v);

    /*Change the palette.  Tricky.*/
    rs = r * 255.0;
    gs = g * 255.0;
    bs = b * 255.0;
    if (rs > 255) rs = 255;
    else if (rs < 0) rs = 0;
    if (gs > 255) gs = 255;
    else if (gs < 0) gs = 0;
    if (bs > 255) rs = 255;
    else if (bs < 0) bs = 0;

    colors = palette -> colors;
    for (k = index1; k <= index2; ++k)
    {
	colors[k][0] = rs;
	colors[k][1] = gs;
	colors[k][2] = bs;
    }

    CopyColorsToComponents(palette, index1, index2);

#ifdef GRAPHICS
    MapPaletteColors(palette, index1, index2);
#endif


    SetVar((ObjPtr) palette, JUSTCOLORCHANGE, ObjTrue);
    panel = GetVar(colorWheel, PARENT);
    if (panel)
    {
	button = GetVar(panel, KEEPBUTTON);
	if (button)
	{
	    ActivateButton(button, true);
	}
	button = GetVar(panel, REVERTBUTTON);
	if (button)
	{
	    ActivateButton(button, true);
	}
    }

    SetVar(slider, TEMPPALETTE, ClonePalette((ObjPtr) palette));
/*    interactiveMoving = false;*/
    ForAllVisWindows(ImInvalid);
    DrawMe(colorBar);
    return ObjTrue;
}

static ObjPtr ChangePaletteSlider(slider)
ObjPtr slider;
/*Changes a palette's slider*/
{
    ObjPtr colorBar, panel, button; 
    PPtr palette, tempPalette;
    ObjPtr var;
    short rs, gs, bs;
    short3 *colors, *tempColors;
    int index1, index2, k, comp;
    real sliderValue, initValue;
    real change;
    ObjPtr radio;
    ObjPtr colorWheel, hsObj;

    colorBar = GetObjectVar("ChangePaletteSlider", slider, COLORBAR);
    tempPalette = (PPtr) GetPaletteVar("ChangePaletteSlider", slider, TEMPPALETTE);

    if (!colorBar || !tempPalette)
    {
	return ObjFalse;
    }

    palette = (PPtr) GetPaletteVar("ChangePaletteSlider", colorBar, REPOBJ);
    if (!palette)
    {
	return ObjFalse;
    }

    colorWheel = GetObjectVar("ChangePaletteSlider", colorBar, COLORWHEEL);
    if (!colorWheel)
    {
	return ObjFalse;
    }

    var = GetRealVar("ChangePaletteSlider", slider, INITVALUE);
    if (var)
    {
	initValue = GetReal(var);
    }
    else
    {
	return ObjFalse;
    }

    var = GetValue(colorBar);
    if (!var)
    {
	return ObjFalse;
    }
    
    comp = 5 - ((real *) ELEMENTS(var))[0];
    if (comp == 4) comp = 0;
    if (comp < 0)
    {
	return ObjFalse;
    }
    index1 = ((real *) ELEMENTS(var))[1];
    index2 = ((real *) ELEMENTS(var))[2];

    var = GetValue(slider);
    if (!var)
    {
	return ObjFalse;
    }
    sliderValue = GetReal(var);

    colors = palette -> colors;
    tempColors = tempPalette -> colors;

    if (sliderValue < initValue)
    {
	/*Attenuate value*/
	change = sliderValue / initValue;

	if (comp == 0)
	{
	    /*Full color*/
	    real h, s, v, dummy;
	    real r, g, b;

	    for (k = index1; k <= index2; ++k)
	    {
		r = tempColors[k][0] / 255.0;
		g = tempColors[k][1] / 255.0;
		b = tempColors[k][2] / 255.0;

		RGB2HSV(&h, &s, &v, r, g, b);
		if (index1 == index2)
		{
		    hsObj = GetValue(colorWheel);
		    if (!hsObj || !IsRealArray(hsObj) || RANK(hsObj) != 1 || DIMS(hsObj)[0] != 2)
		    {
			return ObjFalse;
		    }
		    /*Single color, refresh value from wheel*/
		    h = ((real *) ELEMENTS(hsObj))[0];
		    s = ((real *) ELEMENTS(hsObj))[1];
		    v = sliderValue;
		}
		else
		{
		    v *= change;
		}
		if (v > 1.0) v = 1.0;
		if (v < 0.0) v = 0.0;
		HSV2RGB(&r, &g, &b, h, s, v);

		rs = r * 255.0 + 0.5;
		gs = g * 255.0 + 0.5;
		bs = b * 255.0 + 0.5;
		colors[k][0] = rs;
		colors[k][1] = gs;
		colors[k][2] = bs;
	    }
	    CopyColorsToComponents(palette, index1, index2);
	}
	else
	{
	    real component;

	    for (k = index1; k <= index2; ++k)
	    {
		component = ((real) GetColorComponent(tempPalette, k, comp - 1)) / 255.0;
		component *= change;
		if (component > 1.0) component = 1.0;
		else if (component < 0.0) component = 0.0;
	    	SetColorComponent(palette, k, comp - 1, (int) (component * 255.0 + 0.5));
	    }
	    CopyComponentsToColors(palette, index1, index2);
	}
#ifdef GRAPHICS
	MapPaletteColors(palette, index1, index2);
#endif
    }
    else
    {
	/*Brighten value*/
	change = (1.0 - sliderValue) / (1.0 - initValue);
	if (comp == 0)
	{
	    /*Full color*/
	    real h, s, v, dummy;
	    real r, g, b;

	    for (k = index1; k <= index2; ++k)
	    {
		r = tempColors[k][0] / 255.0;
		g = tempColors[k][1] / 255.0;
		b = tempColors[k][2] / 255.0;

		RGB2HSV(&h, &s, &v, r, g, b);
		if (index1 == index2)
		{
		    /*Single color, refresh value from wheel*/
		    hsObj = GetValue(colorWheel);
		    if (!hsObj || !IsRealArray(hsObj) || RANK(hsObj) != 1 || DIMS(hsObj)[0] != 2)
		    {
			return ObjFalse;
		    }
		    h = ((real *) ELEMENTS(hsObj))[0];
		    s = ((real *) ELEMENTS(hsObj))[1];
		    v = sliderValue;
		}
		else
		{
		    v = 1.0 - (1.0 - v) * change;
		}
		if (v > 1.0) v = 1.0;
		if (v < 0.0) v = 0.0;
		HSV2RGB(&r, &g, &b, h, s, v);

		rs = r * 255.0 + 0.5;
		gs = g * 255.0 + 0.5;
		bs = b * 255.0 + 0.5;
		colors[k][0] = rs;
		colors[k][1] = gs;
		colors[k][2] = bs;
	    }
	    CopyColorsToComponents(palette, index1, index2);
	}
	else
	{
	    real component;

	    for (k = index1; k <= index2; ++k)
	    {
		component = ((real) GetColorComponent(tempPalette, k, comp - 1)) / 255.0;
		component = 1.0 - (1.0 - component) * change;
		if (component > 1.0) component = 1.0;
		else if (component < 0.0) component = 0.0;
	    	SetColorComponent(palette, k, comp - 1, (int) (component * 255.0 + 0.5));
	    }
	    CopyComponentsToColors(palette, index1, index2);
	}
#ifdef GRAPHICS
	MapPaletteColors(palette, index1, index2);
#endif
    }

    ResetColorBarTools(colorBar);

    SetVar((ObjPtr) palette, JUSTCOLORCHANGE, ObjTrue);
    panel = GetVar(slider, PARENT);
    if (panel)
    {
	button = GetVar(panel, KEEPBUTTON);
	if (button)
	{
	    ActivateButton(button, true);
	}
	button = GetVar(panel, REVERTBUTTON);
	if (button)
	{
	    ActivateButton(button, true);
	}
    }

/*    interactiveMoving = false;*/
    ForAllVisWindows(ImInvalid);
    DrawMe(colorBar);
    return ObjTrue;
}

void DoRevertPalette()
/*Reverts a palette in the top window*/
{
    ObjPtr repObj, keptPalette, colorBar, textBox, radio;

    Log("revert\n");
    if (!selWinInfo)
    {
	return;
    }

    repObj = GetPaletteVar("DoRevertPalette", (ObjPtr) selWinInfo, REPOBJ);
    if (!repObj)
    {
	return;
    }
    keptPalette = GetPaletteVar("DoRevertPalette", repObj, KEPTPALETTE);
    if (!keptPalette)
    {
	return;
    }
    CopyPalette(repObj, keptPalette);
    SetVar(repObj, CHANGED, ObjTrue);

    if (colorBar = GetObjectVar("DoRevertPalette", (ObjPtr) selWinInfo, COLORBAR))
    {
	ObjPtr var;
	real newValue[3];

	InhibitLogging(true);
	newValue[0] = newValue[1] = newValue[2] = 0.0;
	var = NewRealArray(1, 3L);
	CArray2Array(var, newValue);
	SetValue(colorBar, var);
	InhibitLogging(false);
    }

    /*Change the text boxes*/
    if (textBox = GetObjectVar("RevertButton", (ObjPtr) selWinInfo, NCOLORBOX))
    {
	sprintf(tempStr, "%d", ((PPtr) repObj) -> nColors);
	SetValue(textBox, NewString(tempStr));
    }
    if (textBox = GetObjectVar("RevertButton", (ObjPtr) selWinInfo, MINBOX))
    {
	sprintf(tempStr, "%g", ((PPtr) repObj) -> min);
	SetValue(textBox, NewString(tempStr));
    }
    if (textBox = GetObjectVar("RevertButton", (ObjPtr) selWinInfo, MAXBOX))
    {
	sprintf(tempStr, "%g", ((PPtr) repObj) -> max);
	SetValue(textBox, NewString(tempStr));
    }
    if (radio = GetObjectVar("RevertButton", (ObjPtr) selWinInfo, CMODELRADIO))
    {
	FuncTyp method;
	method = GetMethod(radio, CHANGEDVALUE);
	SetMethod(radio, CHANGEDVALUE, (FuncTyp) 0);
	SetValue(radio, GetVar(repObj, COLORMODEL));
	SetMethod(radio, CHANGEDVALUE, method);
    }

    /*DIKEO figure out a better way of doing this*/
    ForAllVisWindows(ImInvalid);
}

void DoKeepPalette()
/*Keeps the changes in the palette of the current window*/
{
    ObjPtr repObj, keptPalette, colorBar;

    Log("keep\n");
    if (!selWinInfo)
    {
	return;
    }

    repObj = GetPaletteVar("DoKeepPalette", (ObjPtr) selWinInfo, REPOBJ);
    if (!repObj)
    {
	return;
    }

    CopyPalette(keptPalette, repObj);

    return;
}

static void SimplePaletteFunction(whichFunc, palette, index1, index2, comp, colorBar)
int whichFunc;
PPtr palette;
int index1, index2, comp;
ObjPtr colorBar;
/*Does a simple function on palette*/
{
    int k;
    real r1, g1, b1, r2, g2, b2, r3, g3, b3, r, g, b, c;
    int rs, gs, bs, cs;

    if (logging)
    {
	Log("effect ");
	Log(spfNames[whichFunc]);
	Log("\n");
    }

    switch (whichFunc)
    {
	case PF_RAMP:
	    /*Change the palette to a ramp*/
	    for (k = index1 + 1; k < index2; ++k)
	    {
		if (comp == 0 || comp == 1)
		{
		    c = ((k - index1) * (GetColorComponent(palette, index2, 0) / 255.0)
		       + (index2 - k) * (GetColorComponent(palette, index1, 0) / 255.0)) / (index2 - index1);
	
		    cs = c * 255.0;

		    SetColorComponent(palette, k, 0, cs);
		}
		if (comp == 0 || comp == 2)
		{
		    c = ((k - index1) * (GetColorComponent(palette, index2, 1) / 255.0)
		       + (index2 - k) * (GetColorComponent(palette, index1, 1) / 255.0)) / (index2 - index1);
	
		    cs = c * 255.0;

		    SetColorComponent(palette, k, 1, cs);
		}
		if (comp == 0 || comp == 3)
		{
		    c = ((k - index1) * (GetColorComponent(palette, index2, 2) / 255.0)
		       + (index2 - k) * (GetColorComponent(palette, index1, 2) / 255.0)) / (index2 - index1);
	
		    cs = c * 255.0;

		    SetColorComponent(palette, k, 2, cs);
		}
	    }
	    break;
	case PF_REVERSE:
	    for (k = 0; k < (index2 - index1 + 1) / 2; ++k)
	    {
		int tempColor;

		if (comp == 0 || comp == 1)
		{
		    /*First component*/
		    tempColor = GetColorComponent(palette, index2 - k, 0);
		    SetColorComponent(palette, index2 - k, 0, 
			GetColorComponent(palette, index1 + k, 0));
		    SetColorComponent(palette, index1 + k, 0, tempColor);
		}

		if (comp == 0 || comp == 2)
		{
		    /*Secpmd component*/
		    tempColor = GetColorComponent(palette, index2 - k, 1);
		    SetColorComponent(palette, index2 - k, 1, 
			GetColorComponent(palette, index1 + k, 1));
		    SetColorComponent(palette, index1 + k, 1, tempColor);
		}

		if (comp == 0 || comp == 3)
		{
		    /*Third component*/
		    tempColor = GetColorComponent(palette, index2 - k, 2);
		    SetColorComponent(palette, index2 - k, 2, 
			GetColorComponent(palette, index1 + k, 2));
		    SetColorComponent(palette, index1 + k, 2, tempColor);
		}
	    }
	    break;
	case PF_RUFFLE:
	    {
		int left, right, bottom, top, l, r;
		long oldQuotient, newQuotient;
		double ddiff, majorWidth, minorWidth, halfSpace;
		real val;
		ObjPtr var;
		int nTics;

		Get2DIntBounds(colorBar, &left, &right, &bottom, &top);

		l = left + CBLBORDER + 2 * CBBOXWIDTH + 2 * CBHGAP + 1;
		r = right  - CBRBORDER - CBBOXWIDTH - CBHGAP - 1;
		halfSpace = (palette -> max - palette -> min) / (palette -> nColors - 4) * 0.5;
		ddiff = palette -> max - palette -> min + 2 * halfSpace;
		CalcGoodSteps(ddiff,
			r - l,
			CBSTEPPIXELS,
			&majorWidth, &nTics);

		minorWidth = majorWidth / nTics;

		l = index1;
		val = (l - 2) * ddiff / (palette -> nColors - 3);
		oldQuotient = val / minorWidth;
		for (r = l + 1; r <= index2; ++r)
		{
		    val = (r - 2) * ddiff / (palette -> nColors - 3);
		    newQuotient = val / minorWidth;
		    if (newQuotient != oldQuotient ||
			r == index2)
		    {
			/*Time for a reversal*/
			if (r == index2) ++r;
			oldQuotient = newQuotient;
		        for (k = 0; k < (r - l) / 2; ++k)
			{
			    int tempColor;

			    if (comp == 0 || comp == 1)
			    {
				/*Component*/
				tempColor = GetColorComponent(palette, r - 1 - k, 0);
				SetColorComponent(palette, r - 1 - k, 0,
					GetColorComponent(palette, l + k, 0));
				SetColorComponent(palette, l + k, 0, tempColor);
			    }

			    if (comp == 0 || comp == 2)
			    {
				/*Component*/
				tempColor = GetColorComponent(palette, r - 1 - k, 1);
				SetColorComponent(palette, r - 1 - k, 1,
					GetColorComponent(palette, l + k, 1));
				SetColorComponent(palette, l + k, 1, tempColor);
			    }

			    if (comp == 0 || comp == 3)
			    {
				/*Component*/
				tempColor = GetColorComponent(palette, r - 1 - k, 2);
				SetColorComponent(palette, r - 1 - k, 2,
					GetColorComponent(palette, l + k, 2));
				SetColorComponent(palette, l + k, 2, tempColor);
			    }
			}
			l = r;
		    } 
		}
	    }
	    break;
	case PF_SMOOTH:
	    if (index2 <= index1) break;
	    r1 = (GetColorComponent(palette, index1, 0) + 0.5) / 255.0;
	    g1 = (GetColorComponent(palette, index1, 1) + 0.5) / 255.0;
	    b1 = (GetColorComponent(palette, index1, 2) + 0.5) / 255.0;
	    r2 = (GetColorComponent(palette, index1 + 1, 0) + 0.5) / 255.0;
	    g2 = (GetColorComponent(palette, index1 + 1, 1) + 0.5) / 255.0;
	    b2 = (GetColorComponent(palette, index1 + 1, 2) + 0.5) / 255.0;
	    for (k = index1 + 2; k < index2; ++k)
	    {
		r3 = (GetColorComponent(palette, k + 1, 0) + 0.5) / 255.0;
		g3 = (GetColorComponent(palette, k + 1, 1) + 0.5) / 255.0;
		b3 = (GetColorComponent(palette, k + 1, 2) + 0.5) / 255.0;

		r = ((r1 + r3) * 0.5 + r2) * 0.5;
		g = ((g1 + g3) * 0.5 + g2) * 0.5;
		b = ((b1 + b3) * 0.5 + b2) * 0.5;

		rs = r * 255.0;
		gs = g * 255.0;
		bs = b * 255.0;

		if (comp == 0 || comp == 1)
		{
		    SetColorComponent(palette, k, 0, rs);
		}
		if (comp == 0 || comp == 2)
		{
		    SetColorComponent(palette, k, 1, gs);
		}
		if (comp == 0 || comp == 3)
		{
		    SetColorComponent(palette, k, 2, bs);
		}
		r1 = r2; r2 = r3;
		g1 = g2; g2 = g3;
		b1 = b2; b2 = b3;
	    }
	    break;
	case PF_SHARPEN:
	    if (index2 <= index1) break;
	    r1 = (GetColorComponent(palette, index1, 0) + 0.5) / 255.0;
	    g1 = (GetColorComponent(palette, index1, 1) + 0.5) / 255.0;
	    b1 = (GetColorComponent(palette, index1, 2) + 0.5) / 255.0;
	    r2 = (GetColorComponent(palette, index1 + 1, 0) + 0.5) / 255.0;
	    g2 = (GetColorComponent(palette, index1 + 1, 1) + 0.5) / 255.0;
	    b2 = (GetColorComponent(palette, index1 + 1, 2) + 0.5) / 255.0;
	    for (k = index1 + 2; k < index2; ++k)
	    {
		r3 = (GetColorComponent(palette, k + 1, 0) + 0.5) / 255.0;
		g3 = (GetColorComponent(palette, k + 1, 1) + 0.5) / 255.0;
		b3 = (GetColorComponent(palette, k + 1, 2) + 0.5) / 255.0;

		r = r2 + (r2 - (r1 + r3) * 0.5) * 2.0;
		if (r > 1.0) r = 1.0; else if (r < 0.0) r = 0.0;
		g = g2 + (g2 - (g1 + g3) * 0.5) * 2.0;
		if (g > 1.0) g = 1.0; else if (g < 0.0) g = 0.0;
		b = b2 + (b2 - (b1 + b3) * 0.5) * 2.0;
		if (b > 1.0) b = 1.0; else if (b < 0.0) b = 0.0;

		rs = r * 255.0;
		gs = g * 255.0;
		bs = b * 255.0;

		if (comp == 0 || comp == 1)
		{
		    SetColorComponent(palette, k, 0, rs);
		}
		if (comp == 0 || comp == 2)
		{
		    SetColorComponent(palette, k, 1, gs);
		}
		if (comp == 0 || comp == 3)
		{
		    SetColorComponent(palette, k, 2, bs);
		}
		r1 = r2; r2 = r3;
		g1 = g2; g2 = g3;
		b1 = b2; b2 = b3;
	    }
	    break;
    }
    CopyComponentsToColors(palette, index1, index2);
}

static ObjPtr SimpleFuncButton(button)
ObjPtr button;
/*Do a simple palette function*/
{
    ObjPtr colorBar, panel; 
    PPtr palette;
    ObjPtr var;
    int whichFunc;
    int index1, index2, comp, k;

    colorBar = GetObjectVar("SimpleFuncButton", button, COLORBAR);
    if (!colorBar)
    {
	return ObjFalse;
    }

    var = GetIntVar("SimpleFuncButton", button, PALETTEFUNC);
    if (!var)
    {
	return ObjFalse;
    }
    whichFunc = GetInt(var);

    palette = (PPtr) GetPaletteVar("SimpleFuncButton", colorBar, REPOBJ);
    if (!palette)
    {
	return false;
    }

    var = GetValue(colorBar);
    if (!var)
    {
	return ObjFalse;
    }
    if (((real *) ELEMENTS(var))[0] < 1.0)
    {
	return ObjFalse;
    }
    index1 = ((real *) ELEMENTS(var))[1];
    index2 = ((real *) ELEMENTS(var))[2];

    if (((real *) ELEMENTS(var))[0] == 1.0)
    {
	comp = 0;
    }
    else
    {
	comp = 5 - (int) ((real *) ELEMENTS(var))[0];
    }

    SimplePaletteFunction(whichFunc, palette, index1, index2, comp, colorBar);

#ifdef GRAPHICS
    MapPaletteColors(palette, index1, index2);
#endif

    SetVar((ObjPtr) palette, JUSTCOLORCHANGE, ObjTrue);
    panel = GetVar(button, PARENT);
    if (panel)
    {
	button = GetVar(panel, KEEPBUTTON);
	if (button)
	{
	    ActivateButton(button, true);
	}
	button = GetVar(panel, REVERTBUTTON);
	if (button)
	{
	    ActivateButton(button, true);
	}
    }

/*   interactiveMoving = false;*/
    InhibitLogging(true);
    ChangedValue(colorBar);
    InhibitLogging(false);
/*DIKEO figure out a better way of doing this*/
    ForAllVisWindows(ImInvalid);
    ImInvalid(colorBar);
    return ObjTrue;
}

void SimpleFuncFromMenu(whichFunc)
int whichFunc;
/*Do a simple palette function*/
{
    ObjPtr colorBar, panel; 
    PPtr palette;
    ObjPtr var;
    int index1, index2, comp, k;

    if (!selWinInfo)
    {
	return;
    }

#ifndef MENUSFROM0
    --whichFunc;
#endif

    colorBar = GetObjectVar("SimpleFuncFromMenu", (ObjPtr) selWinInfo, COLORBAR);
    if (!colorBar)
    {
	return;
    }

    palette = (PPtr) GetPaletteVar("SimpleFuncButton", (ObjPtr) selWinInfo, REPOBJ);
    if (!palette)
    {
	return;
    }

    var = GetValue(colorBar);
    if (!var)
    {
	return;
    }
    if (((real *) ELEMENTS(var))[0] < 1.0)
    {
	return;
    }
    index1 = ((real *) ELEMENTS(var))[1];
    index2 = ((real *) ELEMENTS(var))[2];

    if (((real *) ELEMENTS(var))[0] == 1.0)
    {
	comp = 0;
    }
    else
    {
	comp = 5 - (int) ((real *) ELEMENTS(var))[0];
    }

    SimplePaletteFunction(whichFunc, palette, index1, index2, comp, colorBar);

#ifdef GRAPHICS
    MapPaletteColors(palette, index1, index2);
#endif

    SetVar((ObjPtr) palette, JUSTCOLORCHANGE, ObjTrue);
    InhibitLogging(true);
    ChangedValue(colorBar);
    InhibitLogging(false);

    /*DIKEO figure out a better way*/
    ForAllVisWindows(ImInvalid);
    ImInvalid(colorBar);
    return;
}

#ifdef PROTO
void ActivateColorWheel(ObjPtr wheel, Bool whether)
#else
void ActivateColorWheel(wheel, whether)
ObjPtr wheel;
Bool whether;
#endif
/*Activates a color wheel*/
{
    SetVar(wheel, ACTIVATED, whether ? ObjTrue : ObjFalse);
    ImInvalid(wheel);
}

#ifdef PROTO
static int GetColorComponent(PPtr palette, int whichColor, int comp)
#else
static int GetColorComponent(palette, whichColor, comp)
PPtr palette;
int whichColor;
int comp;
#endif
/*Gets the color component comp from whichColor in palette*/
{
    return palette -> components[whichColor][comp];
}

#ifdef PROTO
static void SetColorComponent(PPtr palette, int whichColor, int comp, int cc)
#else
static void SetColorComponent(palette, whichColor, comp, cc)
PPtr palette, int whichColor; int comp; int cc;
#endif
/*Changes component comp in whichColor of palette to cc*/
{
    real rgb[3], hsv[3], hls[3], yiq[3];

    palette -> components[whichColor][comp] = cc;

    CopyComponentsToColors(palette, whichColor, whichColor);
}

ObjPtr ImposeColorFunction(colorBar)
ObjPtr colorBar;
/*Imposes a color function on colorBar.
  Function is given by EDITMODE
  Box is given by FUNCTIONBOX
*/
{
    ObjPtr value, var, palette, panel, button;
    real *elements;
    int comp;
    int index1, index2, k, cc;
    real beg, end, fieldVal;
    int min, max;
    int tool;
    int cm;

    value = GetValue(colorBar);
    if (!value)
    {
	return ObjFalse;
    }

    palette = GetPaletteVar("ImposeColorFunction", colorBar, REPOBJ);
    if (!palette)
    {
	return ObjFalse;
    }

    elements = ELEMENTS(value);
    if (elements[1] < 1.5)
    {
	/*Not on a component*/
	return ObjFalse;
    }

    var = GetIntVar("ImposeColorFunction", colorBar, EDITMODE);
    if (!var)
    {
	return ObjFalse;
    }
    tool = GetInt(var);

    comp = 4 - elements[0];

    index1 = elements[1] + 0.5;
    index2 = elements[2] + 0.5;

    var = GetVar(colorBar, FUNCTIONBOX);
    if (!var)
    {
	/*No box around function!*/
	return ObjFalse;
    }

    elements = ELEMENTS(var);
    beg = elements[0];
    end = elements[1];
    min = elements[2] + 0.5;
    max = elements[3] + 0.5;

    var = GetVar(palette, COLORMODEL);
    if (var)
    {
	cm = GetInt(var);
    }
    else
    {
	cm = CM_RGB;
    }

    for (k = index1; k <= index2; ++k)
    {
	fieldVal = GetColorValue(palette, k);
	while (fieldVal < beg)
	{
	    fieldVal += end - beg;
	}
	while (fieldVal > end)
	{
	    fieldVal -= end - beg;
	}
	fieldVal -= beg;
	fieldVal /= (end - beg);
	/*Now fieldVal is in [0, 1) */

	switch (tool)
	{
	    case PT_SINE:		/*Sine function*/
		cc = min + (max - min) * ((1.0 + rsin(fieldVal * 2.0 * M_PI)) * 0.5);
		break;
	    case PT_TRIANGLE:		/*Triangle function*/
		if (fieldVal < 0.25)
		{
		    cc = (max + min) / 2 + fieldVal * 2.0 * (max - min);
		}
		else if (fieldVal > 0.75)
		{
		    cc = min + (fieldVal - 0.75) * 2.0 * (max - min);
		}
		else
		{
		    cc = min + (0.75 - fieldVal) * 2.0 * (max - min);
		}
		break;
	    case PT_SAWTOOTH:		/*Sawtooth function*/
		cc = min + fieldVal * (max - min);
		break;
	    case PT_TOOTHSAW:		/*Toothsaw function*/
		cc = max - fieldVal * (max - min);
		break;
	    case PT_SQUARE:		/*Square wave function*/
		if (fieldVal < 0.5)
		{
		    cc = max;
		}
		else
		{
		    cc = min;
		}
		break;
	    default:
		cc = 0;
	}
	SetColorComponent((PPtr) palette, k, comp, cc);
    }
    CopyComponentsToColors((PPtr) palette, index1, index2);

    MapPaletteColors((PPtr) palette, index1, index2);

    panel = GetVar(colorBar, PARENT);
    if (panel)
    {
	button = GetVar(panel, KEEPBUTTON);
	if (button)
	{
	    ActivateButton(button, true);
	}
	button = GetVar(panel, REVERTBUTTON);
	if (button)
	{
	    ActivateButton(button, true);
	}
    }
    SetVar((ObjPtr) palette, JUSTCOLORCHANGE, ObjTrue);

    return ObjTrue;
}

static ObjPtr ChangeColorTool(group)
ObjPtr group;
/*Changes a color tool in response to a radio button group*/
{
    ObjPtr colorBar;
    PPtr palette;
    ObjPtr value;
    ObjPtr var;
    int tool;
    ObjPtr selection;
    real *elements;

    colorBar = GetObjectVar("ChangeColorTool", group, REPOBJ);
    if (!colorBar)
    {
	return ObjFalse;
    }

    selection = GetValue(colorBar);
    elements = ELEMENTS(selection);

    palette = (PPtr) GetPaletteVar("ChangeColorTool", colorBar, REPOBJ);
    if (!palette)
    {
	return ObjFalse;
    }

    value = GetValue(group);
    if (!value)
    {
	return false;
    }
    tool = GetInt(value);

    var = GetVar(colorBar, EDITMODE);
    if (var && GetInt(var) == tool)
    {
	/*Don't need to do anything*/
	return ObjFalse;
    }

    SetVar(colorBar, EDITMODE, NewInt(tool));

    if (tool > 0)
    {
	/*It's a function.*/
	var = GetVar(colorBar, FUNCTIONBOX);
	if (!var)
	{
	    /*Calculate initial function box*/
	    int left, right, bottom, top, l, r, b, t;
	    double majorWidth;
	    double halfSpace;
	    double start, finish, middle, ddiff;
	    int nTics;
	    long temp;

	    Get2DIntBounds(colorBar, &left, &right, &bottom, &top);

	    l = left + CBLBORDER + 1;
	    r = right - CBRBORDER - 1;

	    halfSpace = (palette -> max - palette -> min) / (palette -> nColors - 4) * 0.5;
	    start = GetColorValue((ObjPtr) palette, (int) elements[1]) - halfSpace;
	    finish = GetColorValue((ObjPtr) palette, (int) elements[2]) + halfSpace;
	    middle = (start + finish) * 0.5;
	    ddiff = finish - start;

	    CalcGoodSteps(ddiff,
		      r - l,
		      CBSTEPPIXELS,
		      &majorWidth, &nTics);


	    /*Minor and major tics first*/
	    temp = middle / majorWidth;
	    start = temp * majorWidth;
	    finish = start + majorWidth;

	    var = NewRealArray(1, 4L);
	    ((real *) ELEMENTS(var))[0] = start - majorWidth;
	    ((real *) ELEMENTS(var))[1] = finish;
	    ((real *) ELEMENTS(var))[2] = 0.0;
	    ((real *) ELEMENTS(var))[3] = 255.0;

	    InhibitLogging(true);
	    SetFunctionBox(colorBar, var);
	    InhibitLogging(false);
	}
	InhibitLogging(true);
	ImposeColorFunction(colorBar);
	SetVar((ObjPtr) palette, JUSTCOLORCHANGE, ObjTrue);
	ForAllVisWindows(ImInvalid);
	InhibitLogging(false);
    }
    else
    {
	/*No function box*/
	InhibitLogging(true);
	SetFunctionBox(colorBar, NULLOBJ);
	InhibitLogging(false);
    }

    ImInvalid(colorBar);
    return ObjTrue;
}

static ObjPtr MakePaletteBarHelp(colorBar, class)
ObjPtr colorBar;
ObjPtr class;
/*Makes help for a color bar*/
{
    ObjPtr help, temp;
    ObjPtr var;
    int editMode;
    real *elements;

    help = NewString("This control shows a color palette.  The color bar at \
the bottom shows the range of colors in the palette associated with the field \
values, plus colors for missing data, overflow, and underflow values.  \
Above the color bar are three bars showing the magnitude of the three components \
of each color, plus a magnified readout at the top.\n\
\n\
You can select a range of colors to edit by clicking in the color bar or one of the \
three component bars and dragging through the colors you want to edit.  When you \
select a range, controls in the window that can edit the color will become active.  \
Also, the selected range will be expanded to fill the magnified readout at the top of the control.  \
When a color component is selected, you can edit it using the Edit Component \
buttons to the left.");

    var = GetValue(colorBar);
    if (var)
    {
	elements = ELEMENTS(var);

	if (elements[0] > 1.5)
	{
	    /*A component is selected*/
	    var = GetIntVar("MakePaletteBarHelp", colorBar, EDITMODE);
	    if (var)
	    {
		ObjPtr palette;
		int cm;
		editMode = GetInt(var);

		palette = GetVar(colorBar, REPOBJ);
		if (!palette)
		{
		    return ObjFalse;
		}

		var = GetVar(palette, COLORMODEL);
		if (var)
		{
		    cm = GetInt(var);
		}
		else
		{
		    cm = CM_RGB;
		}

		sprintf(tempStr, "\n\nThe %s components of a range of colors are \
currently selected.  The Intensity slider on the left lets you change the brightness \
of this component of the range.  The buttons at the top of the window let you \
perform some simple functions over the entire range of colors.  Use Help In \
Context to find out what each of these buttons does.\n\n",
		componentNames[cm][4 - (int) (elements[0] + 0.5)]);
		temp = NewString(tempStr);
		help = ConcatStrings(help, temp);

		if (editMode == PT_FREEFORM)
		{
		    /*Free form tool*/
		    temp = NewString("The free form tool is selected.  \
You can modify the waveform shown at the top of the control by clicking and drawing \
within the box.  The Shift key will constrain motion in just the vertical or \
horizontal direction.\n");
		    help = ConcatStrings(help, temp);
		}
		else
		{
		    /*Waveform tool*/
		    sprintf(tempStr, "The %s tool is selected.  Notice \
that there is a yellow box within the magnified readout at \
the top of the control.  This box encloses one period of a %s which is used \
to fill the entire selected range of the component.  ",
		    toolNames[editMode], toolNames[editMode]);
		    temp = NewString(tempStr);
		    help = ConcatStrings(help, temp);
		    temp = NewString("Click and drag the square \
handles on the top or bottom to change the minimum and maximum values of the wave.  Click \
Click and drag the handles on the left or right to change the beginning and end \
of the wave as well as its wavelength.  The Shift key will constrain motion to \
the nearest tic mark.  Click and drag within the box to move the entire waveform \
left or right.");
		    help = ConcatStrings(help, temp);
		}
	    }
	}
	else if (elements[0] > 0.5)
	{
	    temp = NewString("\n\nA range of colors is \
currently selected.  The Intensity slider on the left lets you change the brightness \
of this component of the range.  The Color color wheel lets you change \
the hue and saturation of the entire range.  When you click on this \
control, the entire range will become a single color.  \
The buttons at the top of the window let you \
perform some simple functions over the entire range of colors.  Use Help In \
Context to find out what each of these buttons does.");
	    help = ConcatStrings(help, temp);
	}
    }

    SetVar(class, HELPSTRING, help);
    return ObjTrue;
}

static ObjPtr EnterMin(box)
ObjPtr box;
/*Enters the minimum value from box*/
{
    ObjPtr value, colorBar, panel, button;
    PPtr palette;
    float min;
    char *s;
    FuncTyp method;

    value = GetValue(box);
    if (!value)
    {
	return ObjFalse;
    }

    s = GetString(value);
    if (!ParseReal(&min, s))
    {
	return ObjFalse;
    }
#if 0
    if (min <= minusInf || min >= plusInf)
    {
	DoUniqueTask(DoInfinityError);
	return ObjFalse;
    }
#endif
    if (min == missingData)
    {
	DoUniqueTask(DoMissingError);
	return ObjFalse;
    }

    colorBar = GetObjectVar("EnterMin", box, REPOBJ);
    if (!colorBar)
    {
	return ObjFalse;
    }

    palette = (PPtr) GetVar(colorBar, REPOBJ);
    if (!palette)
    {
	return ObjTrue;
    }

    if (min >= palette -> max)
    {
	DoUniqueTask(DoRangeError);
	return ObjFalse;
    }

    palette -> min = min;
    ReinitColorBar(colorBar);
    ImInvalid(colorBar);
    SetVar((ObjPtr) palette, CHANGED, ObjTrue);
    ForAllVisWindows(ImInvalid);

    panel = GetVar(box, PARENT);
    if (panel)
    {
	button = GetVar(panel, KEEPBUTTON);
	if (button)
	{
	    ActivateButton(button, true);
	}
	button = GetVar(panel, REVERTBUTTON);
	if (button)
	{
	    ActivateButton(button, true);
	}
    }

    return ObjTrue;
}

static ObjPtr EnterMax(box)
ObjPtr box;
/*Enters the max value from box*/
{
    ObjPtr value, colorBar, panel, button;
    PPtr palette;
    float max;
    FuncTyp method;

    value = GetValue(box);
    if (!value)
    {
	return ObjFalse;
    }

    if (!ParseReal(&max, GetString(value)))
    {
	DoUniqueTask(DoNumberError);
	return ObjFalse;
    }
#if 0
    if (max <= minusInf || max >= plusInf)
    {
	DoUniqueTask(DoInfinityError);
	return ObjFalse;
    }
#endif
    if (max == missingData)
    {
	DoUniqueTask(DoMissingError);
    }

    colorBar = GetObjectVar("EnterMin", box, REPOBJ);
    if (!colorBar)
    {
	return ObjFalse;
    }

    palette = (PPtr) GetVar(colorBar, REPOBJ);
    if (!palette)
    {
	return ObjTrue;
    }

    if (max <= palette -> min)
    {
	DoUniqueTask(DoRangeError);
	return ObjFalse;
    }

    palette -> max = max;
    SetVar((ObjPtr) palette, CHANGED, ObjTrue);
    ForAllVisWindows(ImInvalid);
    ReinitColorBar(colorBar);
    ImInvalid(colorBar);

    panel = GetVar(box, PARENT);
    if (panel)
    {
	button = GetVar(panel, KEEPBUTTON);
	if (button)
	{
	    ActivateButton(button, true);
	}
	button = GetVar(panel, REVERTBUTTON);
	if (button)
	{
	    ActivateButton(button, true);
	}
    }

    return ObjTrue;
}

static ObjPtr EnterNColors(box)
ObjPtr box;
/*Enters the number of colors from box*/
{
    ObjPtr value, colorBar, panel, button;
    PPtr palette;
    int nColors;
    real *elements;
    int oldNColors; 

    value = GetValue(box);
    if (!value)
    {
	return ObjFalse;
    }

    if (!ParseInteger(&nColors, GetString(value)))
    {
	DoUniqueTask(DoNumberError);
	return ObjFalse;
    }

    colorBar = GetObjectVar("EnterNColors", box, REPOBJ);
    if (!colorBar)
    {
	return ObjFalse;
    }

    palette = (PPtr) GetVar(colorBar, REPOBJ);
    if (!palette)
    {
	return ObjTrue;
    }

    if (nColors < 5 || nColors > 2048)
    {
	DoUniqueTask(DoWrongNColorsError);
	return ObjFalse;
    }

    oldNColors = palette -> nColors;
    SetPaletteNColors((ObjPtr) palette, nColors);
    SetVar((ObjPtr) palette, CHANGED, ObjTrue);
    ForAllVisWindows(ImInvalid);
    panel = GetVar(box, PARENT);
    if (panel)
    {
	button = GetVar(panel, KEEPBUTTON);
	if (button)
	{
	    ActivateButton(button, true);
	}
	button = GetVar(panel, REVERTBUTTON);
	if (button)
	{
	    ActivateButton(button, true);
	}
    }

    value = GetValue(colorBar);
    if (value)
    {
	real newValue[4];
	elements = ELEMENTS(value);
	newValue[0] = elements[0];
	newValue[1] = elements[1];
	newValue[2] = elements[2];
	newValue[3] = elements[3];
	if (elements[0] > 0.5)
	{
	    int k;
	    for (k = 1; k <=2; ++k)
	    {
		if (((int) elements[k] + 0.5) >= oldNColors - 1)
		{
		    newValue[k] = nColors - 1;
		}
		else if (((int) elements[k] + 0.5) > 1)
		{
		    newValue[k] = 2.0 + 
			(elements[k] - 2.0) * (nColors - 3) / (oldNColors - 3);
		    if (((int) newValue[k] + 0.5) >= nColors - 1)
		    {
			newValue[k] = nColors - 2.0;
		    }
		    if (((int) newValue[k] + 0.5) < 2)
		    {
			newValue[k] = 2.0;
		    }
		}
	    }
	    if (newValue[3] <= newValue[2]) newValue[3] += 1.0;
	}
	value = NewRealArray(1, 3L);
	CArray2Array(value, newValue);
	InhibitLogging(true);
	SetValue(colorBar, value);
	InhibitLogging(false);
    }
    return ObjTrue;
}

ObjPtr ChangeTextColorWheel(wheel)
ObjPtr wheel;
/*Changes text colors according to a color wheel*/
{
    ObjPtr repObj, slider;
    ObjPtr value;
    real hsv[3], rgb[3];

    repObj = GetObjectVar("ChangeTextColorWheel", wheel, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    slider = GetObjectVar("ChangeTextColorWheel", wheel, SLIDER);
    if (!repObj)
    {
	return ObjFalse;
    }

    value = GetValue(wheel);
    if (!value)
    {
	return ObjFalse;
    }
    Array2CArray(hsv, value);

    value = GetValue(slider);
    if (!value)
    {
	return ObjFalse;
    }
    hsv[2] = GetReal(value);

    HSV2RGB(&(rgb[0]), &(rgb[1]), &(rgb[2]), hsv[0], hsv[1], hsv[2]);

    value = NewRealArray(1, 3L);
    CArray2Array(value, rgb);
    SetVar(repObj, COLOR, value);
    ImInvalid(repObj);

    return ObjTrue;
}

ObjPtr ChangeTextColorSlider(slider)
ObjPtr slider;
/*Changes text color according to a color slider*/
{
    ObjPtr repObj, wheel;
    ObjPtr value;
    real hsv[3], rgb[3];

    repObj = GetObjectVar("ChangeTextColorWheel", slider, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    wheel = GetObjectVar("ChangeTextColorWheel", slider, COLORWHEEL);
    if (!repObj)
    {
	return ObjFalse;
    }

    value = GetValue(wheel);
    if (!value)
    {
	return ObjFalse;
    }
    Array2CArray(hsv, value);

    value = GetValue(slider);
    if (!value)
    {
	return ObjFalse;
    }
    hsv[2] = GetReal(value);

    HSV2RGB(&(rgb[0]), &(rgb[1]), &(rgb[2]), hsv[0], hsv[1], hsv[2]);

    value = NewRealArray(1, 3L);
    CArray2Array(value, rgb);
    SetVar(repObj, COLOR, value);
    ImInvalid(repObj);

    return ObjTrue;
}

ObjPtr ChangeBackgroundColorWheel(wheel)
ObjPtr wheel;
/*Changes text colors according to a color wheel*/
{
    ObjPtr repObj, slider, checkBox;
    ObjPtr value;
    real hsv[3], rgb[3];

    repObj = GetObjectVar("ChangeBackgroundColorWheel", wheel, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    slider = GetObjectVar("ChangeBackgroundColorWheel", wheel, SLIDER);
    if (!repObj)
    {
	return ObjFalse;
    }

    value = GetValue(wheel);
    if (!value)
    {
	return ObjFalse;
    }
    Array2CArray(hsv, value);

    value = GetValue(slider);
    if (!value)
    {
	return ObjFalse;
    }
    hsv[2] = GetReal(value);

    HSV2RGB(&(rgb[0]), &(rgb[1]), &(rgb[2]), hsv[0], hsv[1], hsv[2]);

    value = NewRealArray(1, 3L);
    CArray2Array(value, rgb);
    SetVar(repObj, BACKGROUND, value);
    ImInvalid(repObj);

    checkBox = GetVar(wheel, CHECKBOX);
    if (checkBox)
    {
	FuncTyp method;
	method = GetMethod(checkBox, CHANGEDVALUE);
	SetMethod(checkBox, CHANGEDVALUE, 0);
	InhibitLogging(true);
	SetValue(checkBox, NewInt(0));
	InhibitLogging(false);
	SetMethod(checkBox, CHANGEDVALUE, method);
    }
    return ObjTrue;
}

ObjPtr ChangeBackgroundColorSlider(slider)
ObjPtr slider;
/*Changes background color according to a color slider*/
{
    ObjPtr repObj, wheel, checkBox;
    ObjPtr value;
    real hsv[3], rgb[3];

    repObj = GetObjectVar("ChangeBackgroundColorSlider", slider, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    wheel = GetObjectVar("ChangeBackgroundColorSlider", slider, COLORWHEEL);
    if (!repObj)
    {
	return ObjFalse;
    }

    value = GetValue(wheel);
    if (!value)
    {
	return ObjFalse;
    }
    Array2CArray(hsv, value);

    value = GetValue(slider);
    if (!value)
    {
	return ObjFalse;
    }
    hsv[2] = GetReal(value);

    HSV2RGB(&(rgb[0]), &(rgb[1]), &(rgb[2]), hsv[0], hsv[1], hsv[2]);
    value = NewRealArray(1, 3L);
    CArray2Array(value, rgb);
    SetVar(repObj, BACKGROUND, value);
    ImInvalid(repObj);

    checkBox = GetVar(slider, CHECKBOX);
    if (checkBox)
    {
	FuncTyp method;
	method = GetMethod(checkBox, CHANGEDVALUE);
	SetMethod(checkBox, CHANGEDVALUE, 0);
	InhibitLogging(true);
	SetValue(checkBox, NewInt(0));
	InhibitLogging(false);
	SetMethod(checkBox, CHANGEDVALUE, method);
    }

    return ObjTrue;
}

static ObjPtr ChangeColorByPalette(checkBox)
ObjPtr checkBox;
/*Changes an object's COLORBYFIELD according to checkBox*/
{
    ObjPtr repObj, value;

    repObj = GetObjectVar("ChangeColorByPalette", checkBox, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    value = GetValue(checkBox);
    if (!value)
    {
	return ObjFalse;
    }

    SetVar(repObj, COLORBYFIELD, value);
    ImInvalid(repObj);

    return ObjFalse;
}

ObjPtr ChangeNoBackground(checkBox)
ObjPtr checkBox;
/*Changes background color according to a check box*/
{
    ObjPtr repObj, wheel, slider;
    ObjPtr value;
    real hsv[3], rgb[3];

    repObj = GetObjectVar("ChangeBackgroundColorSlider", checkBox, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    value = GetValue(checkBox);
    if (!value)
    {
	return ObjFalse;
    }

    if (GetInt(value))
    {
	SetVar(repObj, BACKGROUND, NULLOBJ);
	ImInvalid(repObj);
	return ObjTrue;
    }

    wheel = GetObjectVar("ChangeBackgroundColorSlider", checkBox, COLORWHEEL);
    if (!repObj)
    {
	return ObjFalse;
    }

    slider = GetObjectVar("ChangeBackgroundColorSlider", checkBox, SLIDER);
    if (!repObj)
    {
	return ObjFalse;
    }

    value = GetValue(wheel);
    if (!value)
    {
	return ObjFalse;
    }
    Array2CArray(hsv, value);

    value = GetValue(slider);
    if (!value)
    {
	return ObjFalse;
    }
    hsv[2] = GetReal(value);

    HSV2RGB(&(rgb[0]), &(rgb[1]), &(rgb[2]), hsv[0], hsv[1], hsv[2]);

    value = NewRealArray(1, 3L);
    CArray2Array(value, rgb);
    SetVar(repObj, BACKGROUND, value);
    ImInvalid(repObj);

    return ObjTrue;
}

static ObjPtr ChangeShowMissing(checkBox)
ObjPtr checkBox;
/*Changes the SHOWMISSING according to a checkbox*/
{
    ObjPtr repObj;

    repObj = GetObjectVar("ChangeShowMissing", checkBox, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    SetVar(repObj, SHOWMISSING, GetValue(checkBox));
    ImInvalid(repObj);

    return ObjTrue;
}

static ObjPtr ChangeShowOverUnder(checkBox)
ObjPtr checkBox;
/*Changes the SHOWOVERUNDER according to a checkbox*/
{
    ObjPtr repObj;

    repObj = GetObjectVar("ChangeShowOverUnder", checkBox, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    SetVar(repObj, SHOWOVERUNDER, GetValue(checkBox));
    ImInvalid(repObj);

    return ObjTrue;
}

static ObjPtr ChangeShowMinorTics(checkBox)
ObjPtr checkBox;
/*Changes the SHOWMINORTICS according to a checkbox*/
{
    ObjPtr repObj;

    repObj = GetObjectVar("ChangeShowMinorTics", checkBox, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    SetVar(repObj, SHOWMINORTICS, GetValue(checkBox));
    ImInvalid(repObj);

    return ObjTrue;
}

static ObjPtr ChangeNumbersOnly(checkBox)
ObjPtr checkBox;
/*Changes the NUMBERSONLY according to a checkbox*/
{
    ObjPtr repObj;

    repObj = GetObjectVar("ChangeNumbersOnly", checkBox, REPOBJ);
    if (!repObj)
    {
	return ObjFalse;
    }

    SetVar(repObj, NUMBERSONLY, GetValue(checkBox));
    ImInvalid(repObj);

    return ObjTrue;
}

static ObjPtr ShowPaletteDisplayControls(display, windowName)
ObjPtr display;
char *windowName;
/*Makes a new control window to control a palette display*/
{
    WinInfoPtr controlWindow;
    ObjPtr var;
    ObjPtr panel;
    ObjPtr corral;
    ObjPtr contents;
    real rgb[3], hsv[3];
    WinInfoPtr dialogExists;
    Bool hasBackground;

    dialogExists = DialogExists((WinInfoPtr) display, NewString("Controls"));
    controlWindow = GetDialog((WinInfoPtr) display, NewString("Controls"), windowName, 
	DSPPALWINWIDTH, DSPPALWINHEIGHT, DSPPALWINWIDTH,
	DSPPALWINHEIGHT, WINDBUF + WINRGB + WINFIXEDSIZE);
    
    if (!dialogExists)
    {
	long info;
	ObjPtr value;
	
	ObjPtr checkBox, icon, name, colorBar, titleBox, textBox, button;
	ObjPtr colorWheel, slider, radioGroup;
	int left, right, bottom, top;

	SetVar((ObjPtr) controlWindow, REPOBJ, display);

	/*Set help string*/
	SetVar((ObjPtr) controlWindow, HELPSTRING, NewString("This window \
shows controls for a color palette legend.  For information about any of the controls \
in the window, use Help In Context on the control.\n"));

	/*Add in a panel*/
	panel = NewPanel(greyPanelClass, 0, DSPPALWINWIDTH, 0, DSPPALWINHEIGHT);
	ContentsExpectWindowSize(controlWindow, DSPPALWINWIDTH, DSPPALWINHEIGHT);

	if (!panel)
	{
	    return ObjFalse;
	}
	contents = GetVar((ObjPtr) controlWindow, CONTENTS);
	PrefixList(contents, panel);
	SetVar(panel, PARENT, (ObjPtr) controlWindow);

	contents = GetVar(panel, CONTENTS);

	/*Add in the group of controls for text color*/
	left = MAJORBORDER;
	top = DSPPALWINHEIGHT - MAJORBORDER;
	right = left + 4 * MAJORBORDER + COLORWHEELWIDTH + SLIDERWIDTH;
	
	titleBox = NewTitleBox(left, right,
			top - TITLEBOXTOP - MAJORBORDER - 2 * MINORBORDER - COLORWHEELWIDTH - CHECKBOXHEIGHT - TEXTBOXHEIGHT - TEXTBOXSEP,
			top, "Text and Lines");
	SetVar(titleBox, PARENT, panel);
	PrefixList(contents, titleBox);
	left += MAJORBORDER;
	right -= MAJORBORDER;
	top -= TITLEBOXTOP + MAJORBORDER;

	/*Get the color for priming the controls*/
	var = GetVar(display, COLOR);
	if (IsRealArray(var) && RANK(var) == 1 && DIMS(var)[0] == 3)
	{
	    Array2CArray(rgb, var);
	}
	else if (IsInt(var))
	{
	    rgb[0] = uiColors[GetInt(var)][0] / 255.0;
	    rgb[1] = uiColors[GetInt(var)][1] / 255.0;
	    rgb[2] = uiColors[GetInt(var)][2] / 255.0;
	}
	else
	{
	    ReportError("ShowPaletteDisplayControls", "Bad color");
	    rgb[0] = rgb[1] = rgb[2] = 1.0;
	}
	RGB2HSV(&(hsv[0]), &(hsv[1]), &(hsv[2]), (rgb[0]), (rgb[1]), (rgb[2]));

	/*Make the color wheel*/
	colorWheel = NewColorWheel(left, left + COLORWHEELWIDTH,
			top - COLORWHEELWIDTH, top, "Text Color");
	var = NewRealArray(1, 2L);
	CArray2Array(var, hsv);
	SetValue(colorWheel, var);
	SetVar(colorWheel, PARENT, panel);
	SetVar(colorWheel, REPOBJ, display);
	PrefixList(contents, colorWheel);
	SetMethod(colorWheel, CHANGEDVALUE, ChangeTextColorWheel);
	SetVar(colorWheel, HELPSTRING, NewString("This color wheel controls the \
hue and saturation of the color used to draw the text and lines in the palette legend.  \
The final color is a combination of this hue and saturation and the value, or brightness, \
given by the Value slider."));
	
	/*Make the text box below*/
	textBox = NewTextBox(left, left + COLORWHEELWIDTH,
			top - COLORWHEELWIDTH - TEXTBOXSEP - TEXTBOXHEIGHT,
			top - COLORWHEELWIDTH - TEXTBOXSEP,
			PLAIN, "Text Color Label", "Color");
	SetVar(textBox, PARENT, panel);
	PrefixList(contents, textBox);
	SetTextAlign(textBox, CENTERALIGN);

	/*Make the brightness slider*/
	slider = NewSlider(right - SLIDERWIDTH, right, 
		       top - COLORWHEELWIDTH, top,
		       PLAIN, "Text Color Value");
	SetVar(slider, PARENT, panel);
	PrefixList(contents, slider);
	SetSliderRange(slider, 1.0, 0.0, 0.0);
	SetSliderValue(slider, hsv[2]);
	SetVar(slider, REPOBJ, display);
	SetMethod(slider, CHANGEDVALUE, ChangeTextColorSlider);
	SetVar(slider, HELPSTRING, NewString("This slider controls the \
value, or brightness, of the color used to draw the text and lines in the palette legend.  \
The final color is a combination of this value and the hue and saturation \
given by the Color color wheel."));

	/*Make the text box below*/
	textBox = NewTextBox(right - SLIDERWIDTH - MINORBORDER, right + MINORBORDER,
			top - COLORWHEELWIDTH - TEXTBOXSEP - TEXTBOXHEIGHT,
			top - COLORWHEELWIDTH - TEXTBOXSEP,
			PLAIN, "Text Value Label", "Value");
	SetVar(textBox, PARENT, panel);
	PrefixList(contents, textBox);
	SetTextAlign(textBox, CENTERALIGN);

	/*Cross link the slider and color wheel*/
	SetVar(colorWheel, SLIDER, slider);
	SetVar(slider, COLORWHEEL, colorWheel);

	left -= MINORBORDER;	
	right += MINORBORDER;
	/*Make the check box*/
	top -= COLORWHEELWIDTH + TEXTBOXSEP + TEXTBOXHEIGHT + MINORBORDER;
	checkBox = NewCheckBox(left, right, 
		top - CHECKBOXHEIGHT, top,
		"Color text by palette", GetPredicate(display, COLORBYFIELD));
	SetVar(checkBox, PARENT, panel);
	SetVar(checkBox, REPOBJ, display);
	PrefixList(contents, checkBox);
	SetMethod(checkBox, CHANGEDVALUE, ChangeColorByPalette);
	SetVar(colorWheel, HELPSTRING, NewString("This check box controls \
how the numbers in the palette legend are colored.  If the box is checked, the \
numbers are colored according to the values they represent.  If the box is not \
checked, the numbers are colored with the same color used for the lines in the \
legend."));
	
	/*Make the background controls*/
	top = DSPPALWINHEIGHT - MAJORBORDER;
	right = DSPPALWINWIDTH - MAJORBORDER;
	left = right - (4 * MAJORBORDER + COLORWHEELWIDTH + SLIDERWIDTH);
	
	titleBox = NewTitleBox(left, right,
			top - TITLEBOXTOP - MAJORBORDER - 2 * MINORBORDER - COLORWHEELWIDTH - CHECKBOXHEIGHT - TEXTBOXHEIGHT - TEXTBOXSEP,
			top, "Background");
	SetVar(titleBox, PARENT, panel);
	PrefixList(contents, titleBox);
	left += MAJORBORDER;
	right -= MAJORBORDER;
	top -= TITLEBOXTOP + MAJORBORDER;

	/*Get the color for priming the controls*/
	var = GetVar(display, BACKGROUND);
	if (var && IsRealArray(var) && RANK(var) == 1 && DIMS(var)[0] == 3)
	{
	    Array2CArray(rgb, var);
	    hasBackground = true;
	}
	else if (var && IsInt(var))
	{
	    rgb[0] = uiColors[GetInt(var)][0] / 255.0;
	    rgb[1] = uiColors[GetInt(var)][1] / 255.0;
	    rgb[2] = uiColors[GetInt(var)][2] / 255.0;
	    hasBackground = true;
	}
	else
	{
	    rgb[0] = rgb[1] = rgb[2] = 0.5;
	    hasBackground = false;
	}
	RGB2HSV(&(hsv[0]), &(hsv[1]), &(hsv[2]), (rgb[0]), (rgb[1]), (rgb[2]));

	/*Make the color wheel*/
	colorWheel = NewColorWheel(left, left + COLORWHEELWIDTH,
			top - COLORWHEELWIDTH, top, "Background Color");
	var = NewRealArray(1, 2L);
	CArray2Array(var, hsv);
	SetValue(colorWheel, var);
	SetVar(colorWheel, PARENT, panel);
	SetVar(colorWheel, REPOBJ, display);
	PrefixList(contents, colorWheel);
	SetMethod(colorWheel, CHANGEDVALUE, ChangeBackgroundColorWheel);
	SetVar(colorWheel, HELPSTRING, NewString("This color wheel controls the \
hue and saturation of the color used to draw the background of the palette legend.  \
The final color is a combination of this hue and saturation and the value, or brightness, \
given by the Value slider."));
	
	/*Make the text box below*/
	textBox = NewTextBox(left, left + COLORWHEELWIDTH,
			top - COLORWHEELWIDTH - TEXTBOXSEP - TEXTBOXHEIGHT,
			top - COLORWHEELWIDTH - TEXTBOXSEP,
			PLAIN, "Background Color Label", "Color");
	SetVar(textBox, PARENT, panel);
	PrefixList(contents, textBox);
	SetTextAlign(textBox, CENTERALIGN);

	/*Make the brightness slider*/
	slider = NewSlider(right - SLIDERWIDTH, right, 
		       top - COLORWHEELWIDTH, top,
		       PLAIN, "Background Value");
	SetVar(slider, PARENT, panel);
	PrefixList(contents, slider);
	SetSliderRange(slider, 1.0, 0.0, 0.0);
	SetSliderValue(slider, hsv[2]);
	SetVar(slider, REPOBJ, display);
	SetMethod(slider, CHANGEDVALUE, ChangeBackgroundColorSlider);
	SetVar(slider, HELPSTRING, NewString("This slider controls the \
value, or brightness, of the color used to draw the background of the palette legend.  \
The final color is a combination of this value and the hue and saturation \
given by the Color color wheel."));

	/*Make the text box below*/
	textBox = NewTextBox(right - SLIDERWIDTH - MINORBORDER, right + MINORBORDER,
			top - COLORWHEELWIDTH - TEXTBOXSEP - TEXTBOXHEIGHT,
			top - COLORWHEELWIDTH - TEXTBOXSEP,
			PLAIN, "Background Value Label", "Value");
	SetVar(textBox, PARENT, panel);
	PrefixList(contents, textBox);
	SetTextAlign(textBox, CENTERALIGN);

	/*Cross link the slider and color wheel*/
	SetVar(colorWheel, SLIDER, slider);
	SetVar(slider, COLORWHEEL, colorWheel);

	left -= MINORBORDER;	
	right += MINORBORDER;
	/*Make the check box*/
	top -= COLORWHEELWIDTH + TEXTBOXSEP + TEXTBOXHEIGHT + MINORBORDER;
	checkBox = NewCheckBox(left, right, 
		top - CHECKBOXHEIGHT, top,
		"No background", hasBackground ? false : true);
	SetVar(checkBox, PARENT, panel);
	SetVar(checkBox, REPOBJ, display);
	SetVar(checkBox, SLIDER, slider);
	SetVar(checkBox, COLORWHEEL, colorWheel);
	PrefixList(contents, checkBox);
	SetMethod(checkBox, CHANGEDVALUE, ChangeNoBackground);
	SetVar(checkBox, HELPSTRING, NewString("This checkbox controls whether \
a background is shown.  If it is selected, no background is shown, and the \
objects behind can be seen."));

	/*Link the checkbox to the slider and color wheel*/
	SetVar(colorWheel, CHECKBOX, checkBox);
	SetVar(slider, CHECKBOX, checkBox);

	top -= CHECKBOXHEIGHT + MINORBORDER + MINORBORDER;
	left = MAJORBORDER;
	right = DSPPALWINWIDTH - MAJORBORDER;

	/*Make checkbox for show missing*/
	checkBox = NewCheckBox(left, (left + right) / 2, 
		top - CHECKBOXHEIGHT, top,
		"Show Missing", GetPredicate(display, SHOWMISSING));
	SetVar(checkBox, PARENT, panel);
	SetVar(checkBox, REPOBJ, display);
	PrefixList(contents, checkBox);
	SetMethod(checkBox, CHANGEDVALUE, ChangeShowMissing);
	SetVar(colorWheel, HELPSTRING, NewString("This check box controls \
whether an entry for Missing data is shown in the palette legend."));

	/*Make checkbox for show Over and Under*/
	checkBox = NewCheckBox((left + right) / 2, right, 
		top - CHECKBOXHEIGHT, top,
		"Show Over and Under", GetPredicate(display, SHOWOVERUNDER));
	SetVar(checkBox, PARENT, panel);
	SetVar(checkBox, REPOBJ, display);
	PrefixList(contents, checkBox);
	SetMethod(checkBox, CHANGEDVALUE, ChangeShowOverUnder);
	SetVar(colorWheel, HELPSTRING, NewString("This check box controls \
whether entries for Over and Under data are shown in the palette legend."));

	top -= CHECKBOXHEIGHT + MINORBORDER;

	/*Make checkbox for show minor tics*/
	checkBox = NewCheckBox(left, (left + right) / 2, 
		top - CHECKBOXHEIGHT, top,
		"Show Minor Tics", GetPredicate(display, SHOWMINORTICS));
	SetVar(checkBox, PARENT, panel);
	SetVar(checkBox, REPOBJ, display);
	PrefixList(contents, checkBox);
	SetMethod(checkBox, CHANGEDVALUE, ChangeShowMinorTics);
	SetVar(colorWheel, HELPSTRING, NewString("This check box controls \
whether minor tic marks are shown in the palette legend in addition to major \
tic marks and numbers."));

	/*Make checkbox for numbers only*/
	checkBox = NewCheckBox((left + right) / 2, right,
		top - CHECKBOXHEIGHT, top,
		"Numbers Only", GetPredicate(display, NUMBERSONLY));
	SetVar(checkBox, PARENT, panel);
	SetVar(checkBox, REPOBJ, display);
	PrefixList(contents, checkBox);
	SetMethod(checkBox, CHANGEDVALUE, ChangeNumbersOnly);
	SetVar(colorWheel, HELPSTRING, NewString("When this check box is down, \
only the numbers are shown in the palette legend.  This is useful in conjunction \
with the \"Color text by palette\" check box."));
    }
    return (ObjPtr) controlWindow;
}

void ColorModelFromMenu(whichColorModel)
int whichColorModel;
/*Sets the color model in the current window from whichColorModel*/
{
    ObjPtr colorBar, palette;

    if (!selWinInfo)
    {
	return;
    }

#ifndef MENUSFROM0
    --whichColorModel;
#endif

    if (logging)
    {
	Log("model ");
	Log(colorModelNames[whichColorModel]);
	Log("\n");
    }

    colorBar = GetVar((ObjPtr) selWinInfo, COLORBAR);
    if (!colorBar)
    {
	return;
    }

    palette = GetPaletteVar("ColorModelFromMenu", colorBar, REPOBJ);
    if (!colorBar)
    {
	return;
    }

    SetVar(palette, COLORMODEL, NewInt(whichColorModel));

    CopyColorsToComponents((PPtr) palette, 0, ((PPtr) palette) -> nColors - 1);

    ChangedValue(colorBar);
    ImInvalid(colorBar);

    /*Set the edit tool to free form*/
    ResetColorBarTools(colorBar);
}

#ifdef OLDCODE
static ObjPtr ChangeColorModel(radio)
ObjPtr radio;
/*Changes the color model based on the value of a radio group*/
{
    ObjPtr colorBar, palette;
    ObjPtr value;

    colorBar = GetObjectVar("ChangeColorModel", radio, REPOBJ);
    if (!colorBar)
    {
	return ObjFalse;
    }

    palette = GetVar(colorBar, REPOBJ);
    if (!colorBar)
    {
	return ObjFalse;
    }

    value = GetValue(radio);
    if (!value || !IsInt(value))
    {
	return ObjFalse;
    }

    SetVar(palette, COLORMODEL, value);

    CopyColorsToComponents((PPtr) palette, 0, ((PPtr) palette) -> nColors - 1);

    ChangedValue(colorBar);
    ImInvalid(colorBar);

    /*Set the edit tool to free form*/
    ResetColorBarTools(colorBar);

    return ObjTrue;
}
#endif

static ObjPtr ShowPaletteControls(palette, windowName)
ObjPtr palette;
char *windowName;
/*Makes a new control window to control a palette*/
{
    WinInfoPtr controlWindow;
    ObjPtr var;
    ObjPtr panel;
    ObjPtr corral;
    ObjPtr contents;
    WinInfoPtr dialogExists;
    int left, right, bottom, top;

    GetTemplateBounds(PaletteTemplate,  "Panel",  &left,  &right,  &bottom,  &top);
    dialogExists = DialogExists((WinInfoPtr) palette, NewString("Controls"));
    controlWindow = GetDialog((WinInfoPtr) palette, NewString("Controls"), windowName, 
	right - left, top - bottom, SCRWIDTH, SCRHEIGHT, WINDBUF + WINRGB);
    
    if (!dialogExists)
    {
	long info;
	ObjPtr value;
	int k;	
	ObjPtr checkBox, icon, name, colorBar, titleBox, textBox, button;
	ObjPtr colorWheel, slider, fullCompList, compList, radioGroup;
	char numBuf[200];
	char *s;

	ContentsExpectWindowSize(controlWindow, right - left, top - bottom);

	/*Add some menu items*/
	DefineMenuItem((ObjPtr) controlWindow, FILEMENU, "Save Palette", DoSaveObject);
	DefineMenuItem((ObjPtr) controlWindow, COLORMENU, "Save Palette", DoSaveObject);
	DefineMenuItem((ObjPtr) controlWindow, COLORMENU, "Keep Changes", DoKeepPalette);
	DefineMenuItem((ObjPtr) controlWindow, COLORMENU, "Revert to Original", DoRevertPalette);

	for (k = 0; k < NPALETTEFUNCS; ++k)
	{
	    strcpy(numBuf, spfNames[k]);
	    numBuf[0] = toupper(numBuf[0]);
	    DefineMenuItem((ObjPtr) controlWindow, EFFECTSMENU, numBuf, SimpleFuncFromMenu);
	}

	for (k = 0; k < NCOLORMODELS; ++k)
	{
	    strcpy(numBuf, colorModelNames[k]);
	    numBuf[0] = toupper(numBuf[0]);
	    DefineMenuItem((ObjPtr) controlWindow, COLORMODELMENU, numBuf, ColorModelFromMenu);
	}

	SetVar((ObjPtr) controlWindow, REPOBJ, palette);

	/*Set help string*/
	SetVar((ObjPtr) controlWindow, HELPSTRING, NewString("This window \
shows controls for a color palette.  For information about any of the controls \
in the window, use Help In Context on the control.\n"));

	/*Add in a panel*/
	panel = TemplatePanel(PaletteTemplate, "Panel");
	if (!panel)
	{
	    return ObjFalse;
	}
	contents = GetVar((ObjPtr) controlWindow, CONTENTS);
	PrefixList(contents, panel);
	SetVar(panel, PARENT, (ObjPtr) controlWindow);

	contents = GetVar(panel, CONTENTS);

	/*Create a color bar to add in later*/
	left = 2 * MAJORBORDER + PCWINLSIDE;
	right = PCWINWIDTH - MAJORBORDER;
	bottom = MAJORBORDER + PCBARUP;
	top = bottom + PCBARHEIGHT;

	colorBar = TemplateColorBar(PaletteTemplate, "Palette Colors");
	fullCompList = NewList();	/*List of buttons active on full or component*/
	compList = NewList();		/*List of buttons active on component*/
	SetVar(colorBar, FULLCOMPBUTTONS, fullCompList);
	SetVar(colorBar, COMPBUTTONS, compList);
	SetVar(colorBar, EDITMODE, NewInt(PT_FREEFORM));		/*Freeform editor*/
	SetVar((ObjPtr) controlWindow, COLORBAR, colorBar);
	SetVar(colorBar, STICKINESS, NewInt(STICKYLEFT + STICKYRIGHT + STICKYBOTTOM + STICKYTOP));

	/*Ramp button*/
 	button = TemplateButton(PaletteTemplate, "Ramp");
	PrefixList(contents, button);
	SetVar(button, PARENT, panel);
	SetVar(button, REPOBJ, palette);
	SetVar(button, COLORBAR, colorBar);
	SetMethod(button, CHANGEDVALUE, SimpleFuncButton);
	SetVar(button, PALETTEFUNC, NewInt(PF_RAMP));
	ActivateButton(button, false);
	PrefixList(fullCompList, button);
	SetVar(button, HELPSTRING,
		NewString("This button interpolates between the selected \
colors to make a smooth color ramp.  When a range in the full color bar is \
selected, all components will be interpolated.  When a range of a single component \
is selected, just that component will be interpolated.\n"));
	SetVar(button, HALTHELP, ObjTrue); 

	/*Reverse button*/
 	button = TemplateButton(PaletteTemplate, "Reverse");
	PrefixList(contents, button);
	SetVar(button, PARENT, panel);
	SetVar(button, REPOBJ, palette);
	SetVar(button, COLORBAR, colorBar);
	SetMethod(button, CHANGEDVALUE, SimpleFuncButton);
	SetVar(button, PALETTEFUNC, NewInt(PF_REVERSE));
	ActivateButton(button, false);
	PrefixList(fullCompList, button);
	SetVar(button, HELPSTRING,
		NewString("This button reverses the range of selected colors.  \
When a range in the full color bar is \
selected, all components will be reversed.  When a range of a single component \
is selected, just that component will be reversed.\n")); 
	SetVar(button, HALTHELP, ObjTrue); 

	/*Smooth button*/
 	button = TemplateButton(PaletteTemplate, "Smooth");
	PrefixList(contents, button);
	SetVar(button, PARENT, panel);
	SetVar(button, REPOBJ, palette);
	SetVar(button, COLORBAR, colorBar);
	SetMethod(button, CHANGEDVALUE, SimpleFuncButton);
	SetVar(button, PALETTEFUNC, NewInt(PF_SMOOTH));
	ActivateButton(button, false);
	PrefixList(fullCompList, button);
	SetVar(button, HELPSTRING,
		NewString("This button smooths the selected colors.  \
This will reduce the abrupt changes which can worsen artifacts such as Mach bands.  \
You can smooth a range more by pressing the button repeatedly.  \
When a range in the full color bar is \
selected, all components will be smoothed.  When a range of a single component \
is selected, just that component will be smoothed.\n")); 
	SetVar(button, HALTHELP, ObjTrue); 

	/*Sharpen button*/
 	button = TemplateButton(PaletteTemplate, "Sharpen");
	PrefixList(contents, button);
	SetVar(button, PARENT, panel);
	SetVar(button, REPOBJ, palette);
	SetVar(button, COLORBAR, colorBar);
	SetMethod(button, CHANGEDVALUE, SimpleFuncButton);
	SetVar(button, PALETTEFUNC, NewInt(PF_SHARPEN));
	ActivateButton(button, false);
	PrefixList(fullCompList, button);
	SetVar(button, HELPSTRING,
		NewString("This button sharpens color distinction within the selected colors.  \
This may increase artifacts such as Mach bands.  Sharpen and Smooth are not \
quite inverse operations, but they have roughly opposite effects.  Sharpen will \
amplify noise and variations in the colors, which will become evident after the \
button is pressed three or four times.  \
When a range in the full color bar is \
selected, all components will be sharpened.  When a range of a single component \
is selected, just that component will be sharpened.\n")); 
	SetVar(button, HALTHELP, ObjTrue); 

	/*Put in the min and max text boxes below*/
	sprintf(numBuf, "%g", ((PPtr) palette) -> min);
	textBox = TemplateTextBox(PaletteTemplate, "Field Min", EDITABLE + WITH_PIT + ONE_LINE, numBuf);
	SetVar(textBox, PARENT, panel);
	PrefixList(contents, textBox);
	SetTextAlign(textBox, RIGHTALIGN);
	SetVar(textBox, REPOBJ, colorBar);
	SetVar(panel, MINBOX, textBox);
	SetVar((ObjPtr) controlWindow, MINBOX, textBox);
	SetVar(textBox, HELPSTRING, NewString("This text box shows the maximum \
field value represented by the color table.  Any value above this will use the \
overflow color.  To change this value, enter the new number and press the Enter \
key."));
	SetMethod(textBox, CHANGEDVALUE, EnterMin);
	SetVar(textBox, STICKINESS, NewInt(FLOATINGLEFT + FLOATINGRIGHT + FLOATINGBOTTOM));

	/*Min legend*/
	textBox = TemplateTextBox(PaletteTemplate, "Field Min Legend", 0, "Minimum:");
	SetVar(textBox, PARENT, panel);
	PrefixList(contents, textBox);
	SetTextAlign(textBox, RIGHTALIGN);
	SetVar(textBox, STICKINESS, NewInt(FLOATINGLEFT + FLOATINGRIGHT + FLOATINGBOTTOM));

	/*Max text box*/
	sprintf(numBuf, "%g", ((PPtr) palette) -> max);
	textBox = TemplateTextBox(PaletteTemplate, "Field Max", EDITABLE + WITH_PIT + ONE_LINE, numBuf);
	SetVar(textBox, PARENT, panel);
	PrefixList(contents, textBox);
	SetTextAlign(textBox, RIGHTALIGN);
	SetVar(textBox, REPOBJ, colorBar);
	SetVar(panel, MAXBOX, textBox);
	SetVar((ObjPtr) controlWindow, MAXBOX, textBox);
	SetVar(textBox, HELPSTRING, NewString("This text box shows the minimum \
field value represented by the color table.  Any value below this will use the \
underflow color.  To change this value, enter the new number and press the Enter \
key."));
	SetMethod(textBox, CHANGEDVALUE, EnterMax);
	SetVar(textBox, STICKINESS, NewInt(FLOATINGLEFT + FLOATINGRIGHT + FLOATINGBOTTOM));

	/*Max legend*/
	textBox = TemplateTextBox(PaletteTemplate, "Field Max Legend", 0, "Maximum:");
	SetVar(textBox, PARENT, panel);
	PrefixList(contents, textBox);
	SetTextAlign(textBox, RIGHTALIGN);
	SetVar(textBox, STICKINESS, NewInt(FLOATINGLEFT + FLOATINGRIGHT + FLOATINGBOTTOM));

	/*nColors text box*/
	sprintf(numBuf, "%d", ((PPtr) palette) -> nColors);
	left = MAJORBORDER;
	textBox = TemplateTextBox(PaletteTemplate, "N Colors", EDITABLE + WITH_PIT + ONE_LINE, numBuf);
	SetVar(textBox, PARENT, panel);
	PrefixList(contents, textBox);
	SetTextAlign(textBox, RIGHTALIGN);
	SetVar(textBox, REPOBJ, colorBar);
	SetVar(panel, NCOLORBOX, textBox);
	SetVar((ObjPtr) controlWindow, NCOLORBOX, textBox);
	SetVar(textBox, HELPSTRING, NewString("This text box shows the number of colors \
in the palette, including the missing data, underflow, and overflow entries.  \
To change the number of colors, enter the new number and press the Enter \
key.  The palette will be resampled with the new number of colors.  When increasing \
the number of colors, it is sometimes useful to do a Smooth operation afterward \
to smooth out the changes."));
	SetMethod(textBox, CHANGEDVALUE, EnterNColors);
	SetVar(textBox, STICKINESS, NewInt(FLOATINGLEFT + FLOATINGRIGHT + FLOATINGBOTTOM));

	/*nColors legend*/
	textBox = TemplateTextBox(PaletteTemplate, "NColors Legend", 0, "Colors");
	SetVar(textBox, PARENT, panel);
	PrefixList(contents, textBox);
	SetVar(textBox, STICKINESS, NewInt(FLOATINGLEFT + FLOATINGRIGHT + FLOATINGBOTTOM));

	/*Add in the left color control group*/
	left = MINORBORDER;
	right = left + PCWINLSIDE;
	top = PCWINHEIGHT - MINORBORDER;

	SetVar(palette, KEPTPALETTE, ClonePalette(palette));

	bottom = MINORBORDER + PCBARUP;
	top = bottom + COLORWHEELWIDTH + TEXTBOXHEIGHT + TEXTBOXSEP;	
	/*Color wheel*/
	colorWheel = TemplateColorWheel(PaletteTemplate, "Color");
	SetVar(colorWheel, PARENT, panel);
	PrefixList(contents, colorWheel);
	SetVar(colorWheel, REPOBJ, palette);
	ActivateColorWheel(colorWheel, false);
	SetVar(colorWheel, VALUE, false);
SetVar(colorWheel, HELPSTRING,
	NewString("This color wheel controls the hue and saturation of the \
entire range of selected colors.  When you use this control, the entire range \
will be set to the new color."));
	SetVar(colorWheel, HALTHELP, ObjTrue);

	/*Color wheel text box*/
	textBox = TemplateTextBox(PaletteTemplate, "HS Text", 0, "Color");
	PrefixList(contents, textBox);
	SetVar(textBox, PARENT, panel);
	SetTextAlign(textBox, CENTERALIGN);

	/*Slider*/
	slider = TemplateSlider(PaletteTemplate, "Intensity", PLAIN);
	PrefixList(contents, slider);
	SetVar(slider, PARENT, panel);
	SetSliderRange(slider, 1.0, 0.0, 0.0);
	SetSliderValue(slider, 1.0);
	ActivateSlider(slider, false);
	SetVar(slider, HELPSTRING, NewString("This slider controls the intensity of \
the colors selected in the color bar control to the right.  When a range of colors is selected, this slider controls the value \
of the colors represented in the Hue/Saturation/Value color model.  \
When a range of a single color component is selected, it controls the intensity \
of that component."));
	SetVar(slider, HALTHELP, ObjTrue);

	/*Slider text box*/
	textBox = TemplateTextBox(PaletteTemplate, "Intensity Text", 0, "Intensity");
	PrefixList(contents, textBox);
	SetVar(textBox, PARENT, panel);
	SetTextAlign(textBox, CENTERALIGN);

	/*Link slider and color wheel*/
	SetVar(slider, COLORWHEEL, colorWheel);
	SetVar(colorWheel, SLIDER, slider);

	/*Link the color wheels to the color bar*/
	SetVar(slider, COLORBAR, colorBar);
	SetVar(colorWheel, COLORBAR, colorBar);

	/*Waveform tools*/
	top = MAJORBORDER + PCBARHEIGHT + PCBARUP;
	bottom = top - (TITLEBOXTOP + 2 * MINORBORDER + VWTOOLBORDER + 2 * SMALLICONBUTTONSIZE);
	titleBox = TemplateTitleBox(PaletteTemplate, "Edit Component");
	PrefixList(contents, titleBox);
	SetVar(titleBox, PARENT, panel);
	left += MINORBORDER;
	right -= MINORBORDER;
	bottom += MINORBORDER;
	top -= TITLEBOXTOP + MINORBORDER;
	
	/*Icons*/
	radioGroup = NewRadioButtonGroup("Waveform Tools");
	SetVar(colorBar, TOOLGROUP, radioGroup);
	SetVar(radioGroup, HELPSTRING,
		NewString("This is a group of tools that allow you to edit the \
currently selected color component.  \
To use one of these tools, \
first select a range in any one of the color components by dragging through the range \
and then press the button of the tool \
you want to use."));
	SetVar(radioGroup, PARENT, panel);
	PrefixList(contents, radioGroup);
	SetVar(radioGroup, HALTHELP, ObjTrue);

	/*Freeform*/
	button = TemplateIconButton(PaletteTemplate, "Free form", ICONFREEFORM, UIYELLOW, BS_PLAIN);
	AddRadioButton(radioGroup, button);
	ActivateButton(button, false);
	SetVar(colorBar, FREEFORMBUTTON, button);
	SetVar(button, HELPSTRING, NewString("This button selects the \
free form tool, which allows you to edit the waveform simply by clicking \
and drawing in the magnified readout at the top of the color bar to the right.  \
Hold down the Shift key to constrain motion to just the horizontal or \
vertical direction."));

	/*Sine*/
	button = TemplateIconButton(PaletteTemplate, "Sine", ICONSINE, UIYELLOW, BS_PLAIN);
	AddRadioButton(radioGroup, button);
	ActivateButton(button, false);
	PrefixList(compList, button);
	SetVar(button, HELPSTRING, NewString("This button selects the \
sine wave tool, which fills the entire selected range with a sine wave.  \
When this tool is selected, a yellow box in the magnified readout lets you \
change the period, phase, and position of the wave."));

	/*Triangle*/
	button = TemplateIconButton(PaletteTemplate, "Triangle", ICONTRIANGLE, UIYELLOW, BS_PLAIN);
	AddRadioButton(radioGroup, button);
	ActivateButton(button, false);
	PrefixList(compList, button);
	SetVar(button, HELPSTRING, NewString("This button selects the \
triangle wave tool, which fills the entire selected range with a triangle wave.  \
When this tool is selected, a yellow box in the magnified readout lets you \
change the period, phase, and position of the wave."));

	/*Sawtooth*/
	button = TemplateIconButton(PaletteTemplate, "Sawtooth", ICONSAWTOOTH, UIYELLOW, BS_PLAIN);
	AddRadioButton(radioGroup, button);
	ActivateButton(button, false);
	PrefixList(compList, button);
	SetVar(button, HELPSTRING, NewString("This button selects the \
sawtooth wave tool, which fills the entire selected range with a sawtooth wave.  \
When this tool is selected, a yellow box in the magnified readout lets you \
change the period, phase, and position of the wave."));

	/*Toothsaw*/
	button = TemplateIconButton(PaletteTemplate, "Toothsaw", ICONTOOTHSAW, UIYELLOW, BS_PLAIN);
	AddRadioButton(radioGroup, button);
	ActivateButton(button, false);
	PrefixList(compList, button);
	SetVar(button, HELPSTRING, NewString("This button selects the \
reverse sawtooth wave tool, which fills the entire selected range with a reverse sawtooth wave.  \
When this tool is selected, a yellow box in the magnified readout lets you \
change the period, phase, and position of the wave."));

	/*Square*/
	button = TemplateIconButton(PaletteTemplate, "Square", ICONSQUARE, UIYELLOW, BS_PLAIN);
	AddRadioButton(radioGroup, button);
	ActivateButton(button, false);
	PrefixList(compList, button);
	SetVar(button, HELPSTRING, NewString("This button selects the \
square wave tool, which fills the entire selected range with a square wave.  \
When this tool is selected, a yellow box in the magnified readout lets you \
change the period, phase, and position of the wave."));

	SetValue(radioGroup, NewInt(PT_FREEFORM));
	SetVar(radioGroup, REPOBJ, colorBar);
	SetMethod(radioGroup, CHANGEDVALUE, ChangeColorTool);

#ifdef OLDCODE
	/*Color model controls*/
	left -= MINORBORDER;
	right += MINORBORDER;
	top = top - 2 * ICONBUTTONSIZE - 3 * MINORBORDER;
	bottom = top - TITLEBOXTOP - 2 * MINORBORDER - 2 * CHECKBOXHEIGHT - CHECKBOXSPACING;
	titleBox = NewTitleBox(left, right, bottom, top, "Color Model");
	PrefixList(contents, titleBox);
	SetVar(titleBox, PARENT, panel);

	/*Create the radio button group*/
	radioGroup = NewRadioButtonGroup("Color Models");
	SetVar(radioGroup, PARENT, panel);
	SetVar(panel, CMODELRADIO, radioGroup);
	SetVar((ObjPtr) controlWindow, CMODELRADIO, radioGroup);
	PrefixList(contents, radioGroup);
	SetVar(radioGroup, REPOBJ, colorBar);
	SetVar(radioGroup, HELPSTRING,
	    NewString("This group controls which color model the palette editor \
uses.  It will affect the information shown in the three color component bars \
in the color bar.  SciAn always uses the RGB color model internally and converts \
to the selected color model for editing."));

	/*And the individual radio buttons*/
	left += MINORBORDER;
	right -= MINORBORDER;
	top -= TITLEBOXTOP + MINORBORDER;
	bottom += MINORBORDER;

	button = NewRadioButton(left, left + (right - left) / 2,
				top - CHECKBOXHEIGHT, top,
				"RGB");
	AddRadioButton(radioGroup, button);
	SetVar(button, HELPSTRING,
	    NewString("This button selects the RGB color model.  In this model, \
color is composed of red, green, and blue components, which are added together \
to produce the final color."));

	button = NewRadioButton(left, left + (right - left) / 2,
				bottom, bottom + CHECKBOXHEIGHT,
				"YIQ");
	AddRadioButton(radioGroup, button);
	SetVar(button, HELPSTRING,
	    NewString("This button selects the YIQ color model.  This color \
model is exactly the same model used to encode  NTSC composite television \
signals.  This color model uses properties of the visual system to optimize \
the amount of information that can be transmitted in the fixed bandwidth of \
the NTSC composite signal.\n\nWARNING: Not all possible YIQ combinations encode \
into valid RGB combinations.  This will only be apparent after you have edited the \
color components in the YIQ model, switch to another color model, and switch back."));

	button = NewRadioButton(left + (right - left) / 2, right,
				top - CHECKBOXHEIGHT, top,
				"HSV");
	AddRadioButton(radioGroup, button);
	SetVar(button, HELPSTRING,
	    NewString("This button selects the HSV color model.  In this model, \
color is composed of hue, saturation, and value.  The hue and saturation control \
the shade of the color, and the value controls its intensity.  This color model \
is the same color model which is usedused by the color wheels and intensity sliders in SciAn."));

	button = NewRadioButton(left + (right - left) / 2, right,
				bottom, bottom + CHECKBOXHEIGHT,
				"HLS");
	AddRadioButton(radioGroup, button);
	SetVar(button, HELPSTRING,
	    NewString("This button selects the HLS color model.  In this model, \
color is composed of hue, lightness, and saturation.  Hue and saturation control the \
basic shade of the color.  Lightness controls how much black or white is mixed in \
with the color."));

	var = GetIntVar("ShowPaletteControls", palette, COLORMODEL);
	if (var)
	{
	    SetValue(radioGroup, var);
	}

	SetMethod(radioGroup, CHANGEDVALUE, ChangeColorModel);
#endif

	/*Now add in the color bar*/
	SetMethod(colorBar, MAKE1HELPSTRING, MakePaletteBarHelp);
	SetVar(colorBar, HALTHELP, ObjTrue);
	PrefixList(contents, colorBar);
	SetVar(colorBar, PARENT, (ObjPtr) panel);
	SetVar(colorBar, REPOBJ, palette);
        SetVar(colorBar, SLIDER, slider);
	SetVar(colorBar, COLORWHEEL, colorWheel);
	SetVar(panel, COLORBAR, colorBar);
	value = NewRealArray(1, 3L);
	((real *) ELEMENTS(value))[0] = 0.0;
	((real *) ELEMENTS(value))[1] = 0.0;
	((real *) ELEMENTS(value))[2] = 0.0;
	SetMethod(colorBar, CHANGEDVALUE, ChangePaletteBar);
	SetValue(colorBar, value);
	ReinitColorBar(colorBar);

	/*Give the color wheels and sliders changedValue routines*/
	SetMethod(colorWheel, CHANGEDVALUE, ChangePaletteColorWheel);
	SetMethod(slider, CHANGEDVALUE, ChangePaletteSlider);
    }
    return (ObjPtr) controlWindow;
}

#ifdef PROTO
int ChooseSelectionColor(ObjPtr object)
#else
int ChooseSelectionColor(object)
ObjPtr object;
#endif
/*Chooses a selection color to contrast with object's COLOR and BACKGROUND*/
{
    ObjPtr color, background, oneOnly;
    float target[3];
    float r1, g1, b1, r2, g2, b2, t, b;
    real *elements;

    color = GetVar(object, COLOR);
    background = GetVar(object, BACKGROUND);

    if (!color && !background)
    {
	/*Nothing there, so use UIWHITE*/
	return UIWHITE;
    }

    if (color)
    {
	if (IsInt(color))
	{
	    int c;
	    c = GetInt(color);
	    r1 = ((float) uiColors[c][0]) / 255.0;
	    g1 = ((float) uiColors[c][1]) / 255.0;
	    b1 = ((float) uiColors[c][2]) / 255.0;
	}
	else if (IsRealArray(color) && RANK(color) == 1 && DIMS(color)[0] == 3)
	{
	    elements = ELEMENTS(color);
	    r1 = elements[0];
	    g1 = elements[1];
	    b1 = elements[2];
	}
	else
	{
	    ReportError("ChooseSelectionColor", "Bad color");
	    return UIWHITE;
	}
    }
    else
    {
	r1 = g1 = b1 = 0.0;
    }

    if (background)
    {
	if (IsInt(background))
	{
	    int c;
	    c = GetInt(background);
	    r2 = ((float) uiColors[c][0]) / 255.0;
	    g2 = ((float) uiColors[c][1]) / 255.0;
	    b2 = ((float) uiColors[c][2]) / 255.0;
	}
	else if (IsRealArray(background) && RANK(background) == 1 && DIMS(background)[0] == 3)
	{
	    elements = ELEMENTS(background);
	    r2 = elements[0];
	    g2 = elements[1];
	    b2 = elements[2];
	}
	else
	{
	    ReportError("ChooseSelectionColor", "Bad background");
	    return UIWHITE;
	}
    }
    else
    {
	r2 = g2 = b2 = 0.0;
    }

    /*Try to choose a good target color*/

    /*First r*/
    t = MAX(r1, r2);
    b = MIN(r1, r2);

    if (t < 0.75) target[0] = 1.0; else target[0] = 0.0;

    /*Now g*/
    t = MAX(g1, g2);
    b = MIN(g1, g2);

    if (t < 0.75) target[1] = 1.0; else target[1] = 0.0;

    /*Now b*/
    t = MAX(b1, b2);
    b = MIN(b1, b2);

    if (b > 0.25) target[2] = 0.0; else target[2] = 1.0;
    return ClosestUIColor(target);
}

void SetObjectColor(col)
ObjPtr col;
/*If col is an int, sets its ui color.
  If col is a real 3-array, sets its value
*/
{
#ifdef GRAPHICS
    if (overDraw)
    {
	if (IsInt(col) && GetInt(col) == UIBLACK)
	{
	    color(OVERCLEAR);
	}
	else
	{
	    color(OVERRED);
	}
    }
    else
    {
    if (IsInt(col))
    {
	SetUIColor(GetInt(col));
    }
    else if (IsRealArray(col) && RANK(col) == 1 && DIMS(col)[0] == 3)
    {
	float clr[3];
	real *elements;
	elements = ELEMENTS(col);
	if (drawingMode == DRAW_SCREEN)
	{
	clr[0] = elements[0];
	clr[1] = elements[1];
	clr[2] = elements[2];
	RGBC(clr);
	}
	else
	{
	    curRed = elements[0];
	    curGreen = elements[1];
	    curBlue = elements[2];
	}
    }
    else
    {
	ReportError("SetObjectColor", "Bad color value");
    }
    }
#endif
}

#define PALDISPLAYBORDER	20
#define PALDISPLAYTEXTWIDTH	0.6

#define PALDISPLAYVCOLORWIDTH	0.3
#define PALDISPLAYVCOLORHEIGHT	0.08

#define PALDISPLAYHCOLORWIDTH	0.12
#define PALDISPLAYHCOLORHEIGHT	0.6

ObjPtr DrawPaletteDisplay(display)
ObjPtr display;
/*Draws a display*/
{
#ifdef GRAPHICS
    int left, right, bottom, top;
    int l, r, b, t;
    int intLeft, intRight, intBottom, intTop;	/*Interior dims*/
    double halfSpace, ddiff, majorWidth, minorWidth, curValue;
    int pixel;
    long temp;
    int nTics;
    int k, diff, start;
    int alignment;
    Bool drawOverUnder;
    Bool drawMissing;
    Bool numbersOnly;
    Bool drawMinorTics;
    PPtr palette;
    ObjPtr var;
    char *textFont;
    short3 *colors;
    int beg;
    int nColors;
    int textSize;
    ObjPtr textColor;
    int minMajorStep;
    Bool colorByPalette;
    int boxWidth, boxHeight, stringWidth, stringHeight;


    Get2DIntBounds(display, &left, &right, &bottom, &top);

    intLeft = left + PALDISPLAYBORDER;
    intRight = right - PALDISPLAYBORDER;
    intBottom = bottom + PALDISPLAYBORDER;
    intTop = top - PALDISPLAYBORDER;

    textColor = GetVar(display, COLOR);

    palette = (PPtr) GetPaletteVar("DrawPaletteDisplay", display, REPOBJ);
    if (!palette)
    {
	return ObjFalse;
    }
    colors = ((PPtr) palette) -> colors;
    beg = ((PPtr) palette) -> beg;
    nColors = ((PPtr) palette) -> nColors;

    /*Get colorByPalette predicate*/
    colorByPalette = GetPredicate(display, COLORBYFIELD);

    /*Get draw portions predicates*/
    drawOverUnder = GetPredicate(display, SHOWOVERUNDER);
    drawMissing = GetPredicate(display, SHOWMISSING);
    numbersOnly = GetPredicate(display, NUMBERSONLY);
    drawMinorTics = GetPredicate(display, SHOWMINORTICS);

    /*Set the color palette*/
    SetPalette((ObjPtr) palette);

    /*Draw the background*/
    var = GetVar(display, BACKGROUND);
    if (var)
    {
	SetObjectColor(var);
	FillRect(left, right, bottom, top);
    }

    /*Draw the info on the palette itself*/
    SetObjectColor(textColor);

    var = GetIntVar("DrawPaletteDisplay", display, MINMAJORSTEP);
    if (var)
    {
	minMajorStep = GetInt(var);
    }
    else
    {
	minMajorStep = 30;
    }

    var = GetStringVar("DrawPaletteDisplay", display, TEXTFONT);
    if (var)
    {
	textFont = GetString(var);
    }
    else
    {
	textFont = "Helvetica";
    }

    var = GetIntVar("DrawPaletteDisplay", display, TEXTSIZE);
    if (var)
    {
	textSize = GetInt(var);
    }
    else
    {
	textSize = 18;
    }

    var = GetIntVar("DrawPaletteDisplay", display, ALIGNMENT);
    if (var)
    {
	alignment = GetInt(var);
    }
    else
    {
	alignment = CENTERALIGN;
    }

    SetupFont(textFont, textSize);

    stringWidth = (intRight - intLeft) * PALDISPLAYTEXTWIDTH;
    stringHeight = textSize;

    if (right - left < top - bottom)
    {
	/*It's vertical*/
	int rangeBot, rangeTop;
	int x;

	b = intBottom;
	t = intTop;

	boxWidth = (intRight - intLeft) * PALDISPLAYVCOLORWIDTH;
	boxHeight = (intTop - intBottom) * PALDISPLAYVCOLORHEIGHT;

	if (drawMissing)
	{
	    /*Draw the missing data box*/
	    t = b + boxHeight;
	    if (numbersOnly)
	    {
		l = intLeft;
		r = intRight;
	    }
	    else
	    {
	    r = intRight;
	    l = r - boxWidth;
	    FrameRect(l, r, b, t);
	    FrameRect(l - 1, r + 1, b - 1, t + 1);
	    if (rgbp)
	    {
		c3s(colors[0]);
	    }
	    else
	    {
		color(beg);
	    }
	    FillRect(l + 1, r - 1, b + 1, t - 1);
	    SetObjectColor(textColor);

	    r = intLeft + stringWidth;
	    DrawLine(l, (b + t) / 2, r, (b + t) / 2);
	    DrawLine(l, (b + t) / 2 + 1, r, (b + t) / 2 + 1);
	    l = intLeft;
	    }
	    strcpy(tempStr, "Missing");
	    switch(alignment)
	    {
		case LEFTALIGN:
		    x = l;
		    break;
		case CENTERALIGN:
		    x = (l + r - DSPPALETTESTL) / 2;
		    break;
		case RIGHTALIGN:
		    x = r - DSPPALETTESTL;
		    break;
	
	    }
	    if (colorByPalette)
	    {
		SetRealColor(missingData);
	    }
	    DrawAString(alignment, x, (b + t - textSize) / 2, tempStr);
	    if (colorByPalette)
	    {
		SetObjectColor(textColor);
	    }

	    b = t + MINORBORDER;
	    t = intTop;
	}

	if (drawOverUnder)
	{
	    /*Draw the underflow data box*/
	    t = b + boxHeight;
	    if (numbersOnly)
	    {
		l = intLeft;
		r = intRight;
	    }
	    else
	    {
	    r = intRight;
	    l = r - boxWidth;
	    FrameRect(l, r, b, t);
	    FrameRect(l - 1, r + 1, b - 1, t + 1);
	    if (rgbp)
	    {
		c3s(colors[1]);
	    }
	    else
	    {
		color(beg + 1);
	    }
	    FillRect(l + 1, r - 1, b + 1, t - 1);
	    SetObjectColor(textColor);

	    r = intLeft + stringWidth;
	    DrawLine(l, (b + t) / 2, r, (b + t) / 2);
	    DrawLine(l, (b + t) / 2 + 1, r, (b + t) / 2 + 1);
	    l = intLeft;
	    }
	    strcpy(tempStr, "Under");
	    switch(alignment)
	    {
		case LEFTALIGN:
		    x = l;
		    break;
		case CENTERALIGN:
		    x = (l + r - DSPPALETTESTL) / 2;
		    break;
		case RIGHTALIGN:
		    x = r - DSPPALETTESTL;
		    break;
	
	    }
	    if (colorByPalette)
	    {
		SetRealColor(MINUSINF);
	    }
	    DrawAString(alignment, x, (b + t - textSize) / 2, tempStr);
	    if (colorByPalette)
	    {
		SetObjectColor(textColor);
	    }

	    b = t + MINORBORDER;

	    /*Draw the overflow data box*/
	    t = intTop;
	    if (numbersOnly)
	    {
		l = intLeft;
		r = intRight;
	    }
	    else
	    {
	    r = intRight;
	    l = r - boxWidth;
	    FrameRect(l, r, t - boxHeight, t);
	    FrameRect(l - 1, r + 1, t - boxHeight - 1, t + 1);
	    if (rgbp)
	    {
		c3s(colors[nColors - 1]);
	    }
	    else
	    {
		color(beg + nColors - 1);
	    }
	    FillRect(l + 1, r - 1, t - boxHeight + 1, t - 1);
	    SetObjectColor(textColor);

	    r = intLeft + stringWidth;
	    DrawLine(l, t - boxHeight / 2, r, t - boxHeight / 2);
	    DrawLine(l, t - boxHeight / 2 + 1, r, t - boxHeight / 2 + 1);
	    l = intLeft;
	    }
	    strcpy(tempStr, "Over");
	    switch(alignment)
	    {
		case LEFTALIGN:
		    x = l;
		    break;
		case CENTERALIGN:
		    x = (l + r - DSPPALETTESTL) / 2;
		    break;
		case RIGHTALIGN:
		    x = r - DSPPALETTESTL;
		    break;
	
	    }
	    if (colorByPalette)
	    {
		SetRealColor(PLUSINF);
	    }
	    DrawAString(alignment, x, t - boxHeight / 2 - textSize / 2, tempStr);
	    if (colorByPalette)
	    {
		SetObjectColor(textColor);
	    }

	    t = intTop - boxHeight - MINORBORDER;
	}

	/*Draw the main section of the display*/
	r = intRight;
	l = r - boxWidth;
	if (!numbersOnly)
	{
	    FrameRect(l, r, b, t);
	    FrameRect(l - 1, r + 1, b - 1, t + 1);
	}

	/*Do the colors in the center*/
	
	rangeBot = b + 1;
	rangeTop = t - 1;
	diff = t - b - 2;
	if (diff > 0)
	{
	    b = start = rangeBot;
	    if (!numbersOnly)
	    {
		for (k = 2; k < nColors - 1; ++k)
		{
		    t = (k - 1) * diff / (nColors - 3) + start;
		    if (rgbp)
		    {
			c3s(colors[k]);
		    }
		    else
		    {
			color(beg + k);
		    }
		    FillRect(l + 1, r - 1, b, t);
		    b = t;
		}
	    }
	    SetObjectColor(textColor);

	    r = l - 1;
	    l = intLeft + stringWidth;

	    /*Draw all the tics in the middle*/
	    halfSpace = (palette -> max - palette -> min) / (palette -> nColors - 4) * 0.5;
	    ddiff = palette -> max - palette -> min;

	    CalcGoodSteps(ddiff,
		      rangeTop - rangeBot,
		      minMajorStep,
		      &majorWidth, &nTics);
	    minorWidth = majorWidth / nTics;

	    /*Minor and major tics first*/
	    temp = palette -> min / majorWidth;
	    curValue = temp * majorWidth;

	    while (curValue > palette -> min)
	    {
		curValue -= majorWidth;
	    }
	    k = 0;
	    while (curValue < palette -> min)
	    {
		++k;
		if (k >= nTics) k = 0;

		curValue += minorWidth;
	    }

	    /*Now actually draw them*/
	    if (ABS(curValue) < ddiff * 1.0E-6) curValue = 0.0;
	    while (curValue <= palette -> max + ddiff * 1.0E-6)
	    {
		pixel = rangeBot + (curValue - palette -> min) * (rangeTop - rangeBot) / (ddiff);
		if (k == 0)
		{
		    /*Major tic*/
		    if (numbersOnly)
		    {
			sprintf(tempStr, "%lg", curValue);
			switch(alignment)
			{
			    case LEFTALIGN:
				x = intLeft;
				break;
			    case CENTERALIGN:
				x = (intLeft + intRight - DSPPALETTESTL - StrWidth(tempStr)) / 2;
				break;
			    case RIGHTALIGN:
				x = intRight - DSPPALETTESTL - StrWidth(tempStr);
				break;
			}
		    }
		    else
		    {
			DrawLine(l, pixel, r, pixel);
			DrawLine(l, pixel + 1, r, pixel + 1);
			sprintf(tempStr, "%lg", curValue);
			switch(alignment)
			{
			    case LEFTALIGN:
				x = intLeft;
				break;
			    case CENTERALIGN:
				x = (intLeft + l - DSPPALETTESTL) / 2;
				break;
			    case RIGHTALIGN:
				x = l - DSPPALETTESTL;
				break;
			}
		    }
		    if (colorByPalette)
		    {
			SetRealColor(curValue);
		    }
		    DrawAString(alignment, x, pixel - textSize / 2, tempStr);
		    if (colorByPalette)
		    {
			SetObjectColor(textColor);
		    }
		}
		else if ((!numbersOnly) && drawMinorTics)
		{
		    /*Minor tic*/
		    DrawLine((l + r) / 2, pixel, r, pixel);
		    DrawLine((l + r) / 2, pixel + 1, r, pixel + 1);
		}

		curValue += minorWidth;
		if (ABS(curValue) < ddiff * 1.0E-6) curValue = 0.0;
		++k;
		if (k >= nTics) k = 0;
	    }
	}
    }
    else
    {
	/*It's horizontal*/
	int rangeLeft, rangeRight;
	int x;

	minMajorStep *= 3;
	l = intLeft;
	r = intRight;

	boxWidth = (intRight - intLeft) * PALDISPLAYHCOLORWIDTH;
	boxHeight = (intTop - intBottom) * PALDISPLAYHCOLORHEIGHT;

	if (drawMissing)
	{
	    /*Draw the missing data box*/
	    r = l + boxWidth;
	    if (numbersOnly)
	    {
		b = intBottom;
		t = intTop;
	    }
	    else
	    {
		t = intTop;
		b = t - boxHeight;
		FrameRect(l, r, b, t);
		FrameRect(l - 1, r + 1, b - 1, t + 1);
		if (rgbp)
		{
		    c3s(colors[0]);
		}
		else
		{
		    color(beg);
		}
		FillRect(l + 1, r - 1, b + 1, t - 1);
		SetObjectColor(textColor);

		b = intBottom + stringHeight;
		DrawLine((l + r) / 2, b, (l + r) / 2, t - boxHeight);
		DrawLine((l + r) / 2 + 1, b, (l + r) / 2 + 1, t - boxHeight);
		b = intBottom;
	    }
	    strcpy(tempStr, "Missing");
	    switch(alignment)
	    {
		case LEFTALIGN:
		    x = (l + r) / 2;
		    break;
		case CENTERALIGN:
		    x = (l + r) / 2;
		    break;
		case RIGHTALIGN:
		    x = (l + r) / 2;
		    break;
	
	    }
	    if (colorByPalette)
	    {
		SetRealColor(missingData);
	    }
	    DrawAString(alignment, x, b, tempStr);
	    if (colorByPalette)
	    {
		SetObjectColor(textColor);
	    }

	    l = r + MINORBORDER;
	    r = intRight;
	}

	if (drawOverUnder)
	{
	    /*Draw the underflow data box*/
	    r = l + boxWidth;
	    if (numbersOnly)
	    {
		b = intBottom;
		t = intTop;
	    }
	    else
	    {
		t = intTop;
		b = t - boxHeight;
		FrameRect(l, r, b, t);
		FrameRect(l - 1, r + 1, b - 1, t + 1);

		if (rgbp)
		{
		    c3s(colors[1]);
		}
		else
		{
		    color(beg + 1);
		}

		FillRect(l + 1, r - 1, b + 1, t - 1);
		SetObjectColor(textColor);

		b = intBottom + stringHeight;
		DrawLine((l + r) / 2, b, (l + r) / 2, t - boxHeight);
		DrawLine((l + r) / 2 + 1, b, (l + r) / 2 + 1, t - boxHeight);
		b = intBottom;
	    }
	    strcpy(tempStr, "Under");
	    switch(alignment)
	    {
		case LEFTALIGN:
		    x = (l + r) / 2;
		    break;
		case CENTERALIGN:
		    x = (l + r) / 2;
		    break;
		case RIGHTALIGN:
		    x = (l + r) / 2;
		    break;
	
	    }
	    if (colorByPalette)
	    {
		SetRealColor(MINUSINF);
	    }
	    DrawAString(alignment, x, b, tempStr);
	    if (colorByPalette)
	    {
		SetObjectColor(textColor);
	    }

	    l = r + MINORBORDER;
	    r = intRight;

	    /*Draw the overflow data box*/
	    if (numbersOnly)
	    {
		b = intBottom;
		t = intTop;
	    }
	    else
	    {
		t = intTop;
		b = t - boxHeight;
		FrameRect(r - boxWidth, r, b, t);
		FrameRect(r - boxWidth - 1, r + 1, b - 1, t + 1);
	    if (rgbp)
	    {
		c3s(colors[nColors - 1]);
	    }
	    else
	    {
		color(beg + nColors - 1);
	    }
		FillRect(r - boxWidth + 1, r - 1, b + 1, t - 1);
		SetObjectColor(textColor);

		b = intBottom + stringHeight;
		DrawLine((r - boxWidth + r) / 2, b, (r - boxWidth + r) / 2, t - boxHeight);
		DrawLine((r - boxWidth + r) / 2 + 1, b, (r - boxWidth + r) / 2 + 1, t - boxHeight);
		b = intBottom;
	    }
	    strcpy(tempStr, "Over");
	    switch(alignment)
	    {
		case LEFTALIGN:
		    x = (r - boxWidth + r) / 2;
		    break;
		case CENTERALIGN:
		    x = (r - boxWidth + r) / 2;
		    break;
		case RIGHTALIGN:
		    x = (r - boxWidth + r) / 2;
		    break;
	
	    }
	    if (colorByPalette)
	    {
		SetRealColor(PLUSINF);
	    }
	    DrawAString(alignment, x, b, tempStr);
	    if (colorByPalette)
	    {
		SetObjectColor(textColor);
	    }

	    r = intRight - boxWidth - MINORBORDER;
	}

	/*Draw the main section of the display*/
	t = intTop;
	b = t - boxHeight;

	if (!numbersOnly)
	{
	    FrameRect(l, r, b, t);
	    FrameRect(l - 1, r + 1, b - 1, t + 1);
	}

	/*Do the colors in the center*/
	
	rangeLeft = l + 1;
	rangeRight = r - 1;
	diff = r - l - 2;
	if (diff > 0)
	{
	    l = start = rangeLeft;
	    if (!numbersOnly)
	    {
		for (k = 2; k < nColors - 1; ++k)
		{
		    r = (k - 1) * diff / (nColors - 3) + start;
		    if (rgbp)
		    {
			c3s(colors[k]);
		    }
		    else
		    {
			color(beg + k);
		    }
		    FillRect(l, r, b + 1, t - 1);
		    l = r;
		}
	    }
	    SetObjectColor(textColor);

	    t = b - 1;
	    b = intBottom + stringHeight + 2;

	    /*Draw all the tics in the middle*/
	    halfSpace = (palette -> max - palette -> min) / (palette -> nColors - 4) * 0.5;
	    ddiff = palette -> max - palette -> min;

	    CalcGoodSteps(ddiff,
		      rangeRight - rangeLeft,
		      minMajorStep,
		      &majorWidth, &nTics);
	    minorWidth = majorWidth / nTics;

	    /*Minor and major tics first*/
	    temp = palette -> min / majorWidth;
	    curValue = temp * majorWidth;

	    while (curValue > palette -> min)
	    {
		curValue -= majorWidth;
	    }
	    k = 0;
	    while (curValue < palette -> min)
	    {
		++k;
		if (k >= nTics) k = 0;

		curValue += minorWidth;
	    }

	    /*Now actually draw them*/
	    if (ABS(curValue) < ddiff * 1.0E-6) curValue = 0.0;
	    while (curValue <= palette -> max + ddiff * 1.0E-6)
	    {
		pixel = rangeLeft + (curValue - palette -> min) * (rangeRight - rangeLeft) / (ddiff);
		if (k == 0)
		{
		    /*Major tic*/
		    if (numbersOnly)
		    {
			sprintf(tempStr, "%lg", curValue);
		    }
		    else
		    {
			DrawLine(pixel, b, pixel, t);
			DrawLine(pixel + 1, b, pixel + 1, t);
			sprintf(tempStr, "%lg", curValue);
		    }
		    if (colorByPalette)
		    {
			SetRealColor(curValue);
		    }
		    DrawAString(alignment, pixel, intBottom, tempStr);
		    if (colorByPalette)
		    {
			SetObjectColor(textColor);
		    }
		}
		else if ((!numbersOnly) && drawMinorTics)
		{
		    /*Minor tic*/
		    DrawLine(pixel, (t + b) / 2, pixel, t);
		    DrawLine(pixel + 1, (t + b) / 2, pixel + 1, t);
		}

		curValue += minorWidth;
		if (ABS(curValue) < ddiff * 1.0E-6) curValue = 0.0;
		++k;
		if (k >= nTics) k = 0;
	    }
	}
    }

    if (IsSelected(display))
    {
	int horCent = (left + right)/2;
	int vertCent = (bottom + top)/2;

	/* Draw incredibly fancy frame for moving and resizing palette display */
	if (1 || right - left < top - bottom)
	{
#ifdef OLDCODE
	    /*Draw bits for vertical box*/
	    if (!numbersOnly)
	    {
		/*First the string end*/
		DrawUILine(intLeft + stringWidth, bottom + INSET,
			   intLeft + stringWidth, top - INSET, UIBLACK);
		DrawUILine(intLeft + stringWidth - 1, bottom + INSET,
			   intLeft + stringWidth - 1, top - INSET, UIBLACK);
		DrawUILine(intLeft + stringWidth + 1, bottom + INSET,
			   intLeft + stringWidth + 1, top - INSET, UIBLACK);
		DrawUILine(intLeft + stringWidth - 2, bottom + INSET,
			   intLeft + stringWidth - 2, top - INSET, UIWHITE);
		DrawUILine(intLeft + stringWidth + 2, bottom + INSET,
			   intLeft + stringWidth + 2, top - INSET, UIWHITE);
		DrawUILine(intLeft + stringWidth - 3, bottom + INSET,
			   intLeft + stringWidth - 3, top - INSET, UIWHITE);
		DrawUILine(intLeft + stringWidth + 3, bottom + INSET,
			   intLeft + stringWidth + 3, top - INSET, UIWHITE);

		/*Now the box end*/
		DrawUILine(intRight - boxWidth, bottom + INSET,
			   intRight - boxWidth, top - INSET, UIBLACK);
		DrawUILine(intRight - boxWidth - 1, bottom + INSET,
			   intRight - boxWidth - 1, top - INSET, UIBLACK);
		DrawUILine(intRight - boxWidth + 1, bottom + INSET,
			   intRight - boxWidth + 1, top - INSET, UIBLACK);
		DrawUILine(intRight - boxWidth - 2, bottom + INSET,
			   intRight - boxWidth - 2, top - INSET, UIWHITE);
		DrawUILine(intRight - boxWidth + 2, bottom + INSET,
			   intRight - boxWidth + 2, top - INSET, UIWHITE);
		DrawUILine(intRight - boxWidth - 3, bottom + INSET,
			   intRight - boxWidth - 3, top - INSET, UIWHITE);
		DrawUILine(intRight - boxWidth + 3, bottom + INSET,
			   intRight - boxWidth + 3, top - INSET, UIWHITE);
	    }
#endif
	}

	FrameUIWideRect(left+INSET, right-INSET,
			bottom+INSET, top-INSET,
				OUTSIDEFRAMEWEIGHT, OUTSIDEFRAMECOLOR);
	FrameUIWideRect(left+INSET+OUTSIDEFRAMEWEIGHT,
			right-INSET-OUTSIDEFRAMEWEIGHT,
			bottom+INSET+OUTSIDEFRAMEWEIGHT,
			top-INSET-OUTSIDEFRAMEWEIGHT,
			INSIDEFRAMEWEIGHT, INSIDEFRAMECOLOR);
	FrameUIWideRect(left+INSET+OUTSIDEFRAMEWEIGHT+INSIDEFRAMEWEIGHT,
			right-INSET-OUTSIDEFRAMEWEIGHT-INSIDEFRAMEWEIGHT,
			bottom+INSET+OUTSIDEFRAMEWEIGHT+INSIDEFRAMEWEIGHT,
			top-INSET-OUTSIDEFRAMEWEIGHT-INSIDEFRAMEWEIGHT,
			OUTSIDEFRAMEWEIGHT, OUTSIDEFRAMECOLOR);

	/* Now draw the handles */
	/* center of sides */
	FillUIRect(left, left+HANDLESIZE,
			vertCent-HANDLESIZE/2, vertCent+HANDLESIZE/2, OUTSIDEFRAMECOLOR);
	FillUIRect(left+OUTSIDEFRAMEWEIGHT,
			left+HANDLESIZE-OUTSIDEFRAMEWEIGHT,
			vertCent-HANDLESIZE/2+OUTSIDEFRAMEWEIGHT,
			vertCent+HANDLESIZE/2-OUTSIDEFRAMEWEIGHT, INSIDEFRAMECOLOR);

	FillUIRect(right-HANDLESIZE, right,
			vertCent-HANDLESIZE/2, vertCent+HANDLESIZE/2, OUTSIDEFRAMECOLOR);
	FillUIRect(right-HANDLESIZE+OUTSIDEFRAMEWEIGHT,
			right-OUTSIDEFRAMEWEIGHT,
			vertCent-HANDLESIZE/2+OUTSIDEFRAMEWEIGHT,
			vertCent+HANDLESIZE/2-OUTSIDEFRAMEWEIGHT, INSIDEFRAMECOLOR);

	/* top edge */
	FillUIRect(horCent-HANDLESIZE/2, horCent+HANDLESIZE/2,
			top-HANDLESIZE, top, OUTSIDEFRAMECOLOR);
	FillUIRect(horCent-HANDLESIZE/2+OUTSIDEFRAMEWEIGHT,
			horCent+HANDLESIZE/2-OUTSIDEFRAMEWEIGHT,
			top-HANDLESIZE+OUTSIDEFRAMEWEIGHT,
			top-OUTSIDEFRAMEWEIGHT, INSIDEFRAMECOLOR);

	FillUIRect(left, left+HANDLESIZE,
			top-HANDLESIZE, top, OUTSIDEFRAMECOLOR);
	FillUIRect(left+OUTSIDEFRAMEWEIGHT,
			left+HANDLESIZE-OUTSIDEFRAMEWEIGHT,
			top-HANDLESIZE+OUTSIDEFRAMEWEIGHT,
			top-OUTSIDEFRAMEWEIGHT, INSIDEFRAMECOLOR);
		
	FillUIRect(right-HANDLESIZE, right,
			top-HANDLESIZE, top, OUTSIDEFRAMECOLOR);
	FillUIRect(right-HANDLESIZE+OUTSIDEFRAMEWEIGHT,
			right-OUTSIDEFRAMEWEIGHT,
			top-HANDLESIZE+OUTSIDEFRAMEWEIGHT,
			top-OUTSIDEFRAMEWEIGHT, INSIDEFRAMECOLOR);

	/* bottom edge */
	FillUIRect(horCent-HANDLESIZE/2, horCent+HANDLESIZE/2,
			bottom, bottom+HANDLESIZE, OUTSIDEFRAMECOLOR);
	FillUIRect(horCent-HANDLESIZE/2+OUTSIDEFRAMEWEIGHT,
			horCent+HANDLESIZE/2-OUTSIDEFRAMEWEIGHT,
			bottom+OUTSIDEFRAMEWEIGHT,
			bottom+HANDLESIZE-OUTSIDEFRAMEWEIGHT, INSIDEFRAMECOLOR);

	FillUIRect(left, left+HANDLESIZE,
			bottom, bottom+HANDLESIZE, OUTSIDEFRAMECOLOR);
	FillUIRect(left+OUTSIDEFRAMEWEIGHT,
			left+HANDLESIZE-OUTSIDEFRAMEWEIGHT,
			bottom+OUTSIDEFRAMEWEIGHT,
			bottom+HANDLESIZE-OUTSIDEFRAMEWEIGHT, INSIDEFRAMECOLOR);
		
	FillUIRect(right-HANDLESIZE, right,
			bottom, bottom+HANDLESIZE, OUTSIDEFRAMECOLOR);
	FillUIRect(right-HANDLESIZE+OUTSIDEFRAMEWEIGHT,
			right-OUTSIDEFRAMEWEIGHT,
			bottom+OUTSIDEFRAMEWEIGHT,
			bottom+HANDLESIZE-OUTSIDEFRAMEWEIGHT, INSIDEFRAMECOLOR);
    }
#endif
    return ObjTrue;
}

ObjPtr SelectPaletteDisplay(object, selectp)
ObjPtr object;
Bool selectp;
/*Selects an icon*/
{
    if (selectp)
    {
	MakeMeCurrent(object);
    }

    ImInvalid(object);
    return ObjTrue;
}

#define PDPICKSLOP 10

static ObjPtr PressPaletteDisplay(display, mouseX, mouseY, flags)
ObjPtr display;
int mouseX, mouseY;
long flags;
{
#ifdef INTERACTIVE
    int left, right, bottom, top, hCent, vCent;
    Bool ml, mr, mb, mt;
    int mX, mY;
    ObjPtr var, palette;

    Get2DIntBounds(display, &left, &right, &bottom, &top);

    /* return if mouse outside text box */
    if (mouseX < left || mouseX > right || mouseY < bottom 
	|| mouseY > top) return ObjFalse;

    if (TOOL(flags) == T_HELP) /* help mode? */
    {
	ContextHelp(display);
	return ObjTrue;
    }

    MakeMeCurrent(display);

    hCent = (left + right)/2;
    vCent = (bottom + top)/2;

    ml = mr = mb = mt = false;

    if (mouseX < left + HANDLESIZE) /* on left side */
    {
	if (mouseY > top - HANDLESIZE) /* top-left handle */
		mt = ml = true;
	else if (mouseY < bottom + HANDLESIZE) /* bottom-left handle */
		mb = ml = true;
	else if (mouseY > vCent - HANDLESIZE/2 && mouseY < vCent + HANDLESIZE/2)
		ml = true; /* bottom middle handle */
	else ml = mr = mb = mt = true; /* in frame */
    }
    else if (mouseX > right - HANDLESIZE) /* on right side */
    {
	if (mouseY > top - HANDLESIZE) /* top-right handle */
		mt = mr = true;
	else if (mouseY < bottom + HANDLESIZE) /* bottom-right handle */
		mb = mr = true;
	else if (mouseY > vCent - HANDLESIZE/2 && mouseY < vCent + HANDLESIZE/2)
		mr = true;
	else ml = mr = mb = mt = true; /* in frame */
    }
    else if (mouseY < bottom + HANDLESIZE) /* on bottom */
    {
	/* already handled (heh heh) corners */
	if (mouseX > hCent - HANDLESIZE/2 && mouseX < hCent + HANDLESIZE/2)
		mb = true; /* bottom middle handle */
	else ml = mr = mb = mt = true; /* in frame */
    }
    else if (mouseY > top - HANDLESIZE) /* on top */
    {
	/* already handled (heh heh) corners */
	if (mouseX > hCent - HANDLESIZE/2 && mouseX < hCent + HANDLESIZE/2)
		mt = true; /* middle top handle */
	else ml = mr = mb = mt = true; /* in frame */
    }

	{
	    if (!(flags & F_EXTEND) && !IsSelected(display))
	    {
		/*It's a new selection and not already selected.  Deselect the
		  rest*/
		DeselectAll();
	    }

	    if ((flags & F_EXTEND) && IsSelected(display))
	    {
		/*Deselect*/
		Select(display, false);
		return ObjTrue;
	    }
	    else if (!IsSelected(display))
	    {
		/*Must select it*/
		
		Select(display, true);
		DrawMe(display);
		UpdateDrawing();
	    }
	}

    if (mr || ml || mb || mt) /* drag the incredibly fancy frame around */
    {
	/* I am greatly indebted to my friend and colleague, Eric Pepke,
	   for the following code. Any errors or obfuscations are his. */
	/*Oh yeah?  Well, I stole it back!  So now the bugs are yours!*/
	int initX = mouseX, initY = mouseY;
	int newLeft, newRight, newBottom, newTop;
	int oldNewLeft, oldNewRight, oldNewBottom, oldNewTop;

	SaveForUndo(display);

	newLeft = oldNewLeft = left;
	newRight = oldNewRight = right;
	newBottom = oldNewBottom = bottom;
	newTop = oldNewTop = top;

	while (Mouse(&mX, &mY))
	{
	    if (ml) newLeft = left + mX - initX;
	    if (mr) newRight = right + mX - initX;
	    if (mb) newBottom = bottom + mY - initY;
	    if (mt) newTop = top + mY - initY;

	    if (flags & F_SHIFTDOWN)
	    {
		/*Grid drag*/
		if (ml && mr && mb && mt)
		{
		    /*Special case--whole object gridded
			Only grid top left*/
		    int width, height;
		    width = newRight - newLeft;
		    height = newTop - newBottom;
		    newLeft = GRIDX(newLeft);
		    newRight = newLeft + width;
		    newTop = top - (GRIDY(top - newTop));
		    newBottom = newTop - height;
		}
		else
		{
		    /*Normal case*/
		    if (ml) newLeft = GRIDX(newLeft);
		    if (mr) newRight = right - GRIDX(right - newRight);
		    if (mb) newBottom = GRIDY(newBottom);
		    if (mt) newTop = top - GRIDY(top - newTop);
		}
	    }
	    if (ml && newLeft + 3 * HANDLESIZE > newRight)
		newLeft = newRight - 3 * HANDLESIZE;
	    if (mr && newLeft + 3 * HANDLESIZE > newRight)
		newRight = newLeft + 3 * HANDLESIZE;
	    if (mb && newBottom + 3 * HANDLESIZE > newTop)
		newBottom = newTop - 3 * HANDLESIZE;
	    if (mt && newBottom + 3 * HANDLESIZE > newTop)
		newTop = newBottom + 3 * HANDLESIZE;
	    if ((newLeft != oldNewLeft ||
		 newRight != oldNewRight ||
		 newBottom != oldNewBottom ||
		 newTop != oldNewTop) &&
		 newLeft < newRight &&
		 newBottom < newTop)
	    {
		Set2DIntBounds(display, newLeft, newRight, newBottom, newTop);
		oldNewLeft = newLeft;
		oldNewRight = newRight;
		oldNewBottom = newBottom;
		oldNewTop = newTop;
		DrawMe(display);
	    }
	}
	if (logging)
	{
	    char cmd[256];
	    MakeObjectName(tempStr, display);
	    sprintf(cmd, "set bounds %s [%d %d %d %d]\n",
		    tempStr, newLeft, newRight,
		    newBottom, newTop);
	    Log(cmd);
	}
    }
    return ObjTrue;
#else
    return ObjFalse;
#endif
}


ObjPtr NewPaletteDisplay(left, right, bottom, top, name, palette)
int left, right, bottom, top;
char *name;
ObjPtr palette;
/*Makes a new palette display*/
{
    ObjPtr retVal;
    retVal = NewObject(paletteDisplayClass, 0);
    if (!retVal)
    {
	return NULLOBJ;
    }

    Set2DIntBounds(retVal, left, right, bottom, top);
    SetVar(retVal, NAME, NewString(name));
    SetVar(retVal, REPOBJ, palette);

    return retVal;
}

ObjPtr CompleteSavePalette(palette, whichButton)
ObjPtr palette;
int whichButton;
/*Completes a "save palette" operation*/
{
    SaveObjectControls(palette, GetVar(palette, DIRECTORY));
}

ObjPtr SavePalette(palette)
ObjPtr palette;
/*Asks for a palette to be saved*/
{
    ObjPtr name, directory;
    char *s, nameStr[401];

    name = GetStringVar("SavePalette", palette, NAME);
    if (!name)
    {
	return ObjFalse;
    }

    strcpy(nameStr, GetString(name));
    s = nameStr;
    while (*s) ++s;

    while (*s != ' ') --s;
    *s = 0;

    directory = GetVar(palette, DIRECTORY);

    if (directory)
    {
	sprintf(tempStr, "Save this palette for datasets named %s in directory %s?",
		nameStr, GetString(directory));
    }
    else
    {
	sprintf(tempStr, "Save this palette for datasets named %s in the current directory?",
		nameStr);
    }
    AlertUser(UICAUTIONALERT, (WinInfoPtr) palette, tempStr, CompleteSavePalette, 2, "Save", "Cancel");

    return ObjTrue;
}

ObjPtr SavePaletteControls(palette)
ObjPtr palette;
/*Saves a palette by logging all palette controls*/
{
    int k;

    sprintf(tempStr, "set value Field\\ Min \"%g\"\n", MINUSINF);
    Log(tempStr);
    sprintf(tempStr, "set value Field\\ Max \"%g\"\n", ((PPtr) palette) -> max);
    Log(tempStr);
    sprintf(tempStr, "set value Field\\ Min \"%g\"\n", ((PPtr) palette) -> min);
    Log(tempStr);
    sprintf(tempStr, "set value N\\ Colors \"%d\"\n", ((PPtr) palette) -> nColors);
    Log(tempStr);

    for (k = 0; k < ((PPtr) palette) -> nColors; ++k)
    {
	sprintf(tempStr, "set color Palette\\ Colors %d %d %d %d\n",
		k,  ((PPtr) palette) -> colors[k][0], ((PPtr) palette) -> colors[k][1], ((PPtr) palette) -> colors[k][2]);
	Log(tempStr);
    }
}

void InitColors()
/*Initialize the color system*/
{
    int k;
    int colorBeg, colorEnd;
    ObjPtr var;
    ObjPtr list;

    iconColorPalette = NewIcon(0, 0, ICONCTABLE, "Color Palette");
    AddToReferenceList(iconColorPalette);

    /*Make a color palette class*/
    paletteClass = NewObject(NULLOBJ, sizeof(Palette) - sizeof(Thing));
    AddToReferenceList(paletteClass);
    SETOBJTYPE(paletteClass -> flags, PALETTE);
    ((PPtr) paletteClass) -> colors = 0;
    ((PPtr) paletteClass) -> components = 0;
    ((PPtr) paletteClass) -> beg = 0;
    ((PPtr) paletteClass) -> nColors = 0;
    SetVar(paletteClass, DEFAULTICON, iconColorPalette);
    SetVar(paletteClass, COLORMODEL, NewInt(CM_RGB));
    SetVar(paletteClass, DOUBLECLICK, NewString(OF_SHOW_CONTROLS));
    SetMethod(paletteClass, NAME, MakePaletteName);
    SetMethod(paletteClass, CLONE, ClonePalette);
    SetMethod(paletteClass, CLEANUP, CleanupPalette);
    SetMethod(paletteClass, NEWCTLWINDOW, ShowPaletteControls);
    SetMethod(paletteClass, SHOWCONTROLS, NewControlWindow);
    SetVar(paletteClass, EXTENSION, NewString("pal"));
    SetMethod(paletteClass, SAVECPANEL, SavePalette);
    SetMethod(paletteClass, SAVEALLCONTROLS, SavePaletteControls);
    SetMethod(paletteClass, LOCALCOPY, MakeLocalCopy);

#ifdef GRAPHICS
    /*If there is a cmap mode, figure out the number of colors*/
    if (hasCmap)
    {
	char *nColorsString;
	char *colorBegString;
	colorEnd = 1;
	for (k = 0; k < cmapBitPlanes; ++k)
	{
	    colorEnd *= 2;
	}

	/*Trim off the top 512 if it's too big for the GTX version*/
	if (colorEnd >= 4096)
	{
	    colorEnd -= 512;
	}

	/*Determine beginning based on end*/
	if (colorEnd <= 512)
	{
	    colorBeg = COLORBEG8BITS;
	}
	else
	{
	    colorBeg = COLORBEGMOREBITS;
	}

	if (colorBegString = getenv("SCIAN_COLOR_BEG"))
	{
	    int temp;
	    if (1 == sscanf(colorBegString, "%d", &temp))
	    {
		colorBeg = temp;
		if (showConfig)
		fprintf(stderr, "Color beginning overridden to %d\n", temp);
	    }
	    else
	    {
		fprintf(stderr, "Bad color beginning: %s\n", colorBegString);
	    }
	}

	if (nColorsString = getenv("SCIAN_N_COLORS"))
	{
	    int nColors;
	    if (1 == sscanf(nColorsString, "%d", &nColors))
	    {
		colorEnd = colorBeg + nColors;
		if (showConfig)
		printf("Number of colors overridden to be %d\n", nColors);
	    }
	    else
	    {
		fprintf(stderr, "Bad value for environment variable SCIAN_NCOLORS: %s\n", nColorsString);
	    }
	}

	if (showConfig)
	{
	    printf("Visualization colors from %d to %d\n", colorBeg, colorEnd);
	}

	curUIColorIndex = colorBeg;

	nScavengeColors = colorBeg;
    }

    if (hasCmap && scavengeColors)
    {
	Colorindex i;

	nScavengeColors = colorBeg;
	colorsToScavenge = (short3 *) malloc(nScavengeColors * sizeof(short3));
	if (!colorsToScavenge)
	{
	    nScavengeColors = 0;
	}

	/*Load up the scavenged colors*/
	for (i = 0; i < nScavengeColors; ++i)
	{
	    getmcolor(i, 
		&(colorsToScavenge[i][0]),
		&(colorsToScavenge[i][1]),
		&(colorsToScavenge[i][2]));
	}
    }
    else
    {
	nScavengeColors = 0;
	colorsToScavenge = 0;
    }

    /*Initialize user interface colors*/
    MakeUIColor(UIBLACK, 0, 0, 0);
    MakeUIColor(UIGRAY12, 30, 30, 30);
    MakeUIColor(UIGRAY25, 61, 61, 61);
    MakeUIColor(UIGRAY37, 95, 95, 95);
    MakeUIColor(UIGRAY50, 128, 128, 128);
    MakeUIColor(UIGRAY62, 163, 163, 163);
    MakeUIColor(UIGRAY75, 193, 193, 193);
    MakeUIColor(UIGRAY87, 224, 224, 224);
    MakeUIColor(UIWHITE, 255, 255, 255);

    MakeUIColor(UIRED, 225, 0, 0);
    MakeUIColor(UIGREEN, 0, 225, 0);
    MakeUIColor(UIBLUE, 0, 0, 225);
    MakeUIColor(UIMAGENTA, 225, 0, 225);
    MakeUIColor(UIYELLOW, 245, 245, 0);
    MakeUIColor(UICYAN, 0, 225, 225);
    MakeUIColor(UIGOLD, 247, 188, 0);

    MakeUIColor(UIPRED, 182, 128, 128);
    MakeUIColor(UIPGREEN, 128, 171, 128);
    MakeUIColor(UIPBLUE, 128, 145, 171);
    MakeUIColor(UIPMAGENTA, 171, 128, 171);
    MakeUIColor(UIPYELLOW, 171, 171, 128);
    MakeUIColor(UIPCYAN, 128, 171, 171);

    if (colorsToScavenge)
    {
	free(colorsToScavenge);
	colorsToScavenge = 0;
    }

    /*Now colorBeg set to be above NUICOLORS*/
    if (hasCmap)
    {
	colorBeg = curUIColorIndex; 

	/*Make first color range*/
	colorRanges = newp(ColorRange);
	colorRanges -> beg = colorBeg;
	colorRanges -> end = colorEnd;
	colorRanges -> next = (ColorRange *) 0;
    }
    else
    {
	colorRanges = 0;
    }
#endif

    colorControlClass = NewObject(controlClass, 0);
    AddToReferenceList(colorControlClass);

    colorWheelClass = NewObject(colorControlClass, 0);
    AddToReferenceList(colorWheelClass);
#ifdef GRAPHICS
    SetMethod(colorWheelClass, DRAW, DrawColorWheel);
#endif
#ifdef INTERACTIVE
    SetMethod(colorWheelClass, PRESS, PressColorWheel);
#endif
    SetMethod(colorWheelClass, SETVAL, SetColorWheelVal);
    SetVar(colorWheelClass, TYPESTRING, NewString("color wheel"));
    SetVar(colorWheelClass, HELPSTRING, NewString("To select a color, click at the color you desire.  \
Colors around the edge of the circle are fully saturated; colors near the center are \
less saturated.  Hold down the Shift key while pressing to \
constrain to full or half saturation.  Double-click to snap to the closest full- or half-saturated \
color or white.")); 

    /*Create a color bar class*/
    colorBarClass = NewObject(controlClass, 0);
    AddToReferenceList(colorBarClass);
#ifdef GRAPHICS
    SetMethod(colorBarClass, DRAW, DrawColorBar);
#endif
#ifdef INTERACTIVE
    SetMethod(colorBarClass, PRESS, PressColorBar);
#endif
    SetVar(colorBarClass, OPAQUE, ObjTrue);
    SetMethod(colorBarClass, SETVAL, SetColorBarVal);

    /*Create a palette display class*/
    paletteDisplayClass = NewObject(controlClass, 0);
    AddToReferenceList(paletteDisplayClass);
    SetVar(paletteDisplayClass, SHOWMINORTICS, ObjTrue);
#ifdef GRAPHICS
    SetMethod(paletteDisplayClass, DRAW, DrawPaletteDisplay);
#endif
#ifdef INTERACTIVE
    SetMethod(paletteDisplayClass, PRESS, PressPaletteDisplay);
#endif
    SetMethod(paletteDisplayClass, NEWCTLWINDOW, ShowPaletteDisplayControls);
    SetMethod(paletteDisplayClass, SHOWCONTROLS, NewControlWindow);
    /*Make list of snapshot variables*/
    list = NewList();
    PrefixList(list, NewSymbol(BOUNDS));
    PrefixList(list, NewSymbol(TEXTFONT));
    PrefixList(list, NewSymbol(TEXTSIZE));
    PrefixList(list, NewSymbol(ALIGNMENT));
    SetVar(paletteDisplayClass, SNAPVARS, list);

    var = NewRealArray(1, 3L);
    ((real *) ELEMENTS(var))[0] = 1.0;
    ((real *) ELEMENTS(var))[1] = 1.0;
    ((real *) ELEMENTS(var))[2] = 1.0;
    SetVar(paletteDisplayClass, COLOR, var);
    SetTextFont(paletteDisplayClass, DSPPALETTEFONT);
    SetTextSize(paletteDisplayClass, DSPPALETTESIZE);
    SetMethod(paletteDisplayClass, SELECT, SelectPaletteDisplay);
    SetTextAlign(paletteDisplayClass, RIGHTALIGN);
    SetVar(paletteDisplayClass, TICDENSITY, NewReal(10.0));
    SetVar(paletteDisplayClass, MINMAJORSTEP, NewInt(30));
}

void KillColors()
{
    DeleteThing(paletteDisplayClass);
    DeleteThing(colorBarClass);
    DeleteThing(colorWheelClass);
    DeleteThing(colorControlClass);
    DeleteThing(paletteClass);
    DeleteThing(iconColorPalette);
}
