
/* Copyright (C) 1992  AHPCRC, Univeristy of Minnesota
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program in a file named 'Copying'; if not, write to
 * the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139.
 */

/* Author:
 *	Ken Chin-Purcell (ken@ahpcrc.umn.edu)
 *	Army High Performance Computing Research Center (AHPCRC)
 *	Univeristy of Minnesota
 *
 * $Header: /usr/people/ken/gvl/icol/RCS/icol.c,v 2.1 92/10/19 16:59:32 ken Exp $
 *
 * $Log:	icol.c,v $
 * Revision 2.1  92/10/19  16:59:32  ken
 * *** empty log message ***
 * 
 * Revision 1.1  92/10/19  16:58:12  ken
 * Initial revision
 * 
 */

#include <stdio.h>
#include <string.h>
#include <malloc.h>

#include <Xm/Xm.h>
#include <Xm/Text.h>
#include <Xm/ToggleB.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/Intrinsic.h>
#include <X11/Xatom.h>
#include <X11/Xproto.h>

#include "util.h"
#include "xtutil.h"
#include "cbar.h"
#include "file.h"
#include "abar.h"
#include "icol.h"
#include "setup.h"

extern void	exit(int); 

static void SetInterestColormap(void);


Icol	*icol;

    
static void SetColormap(int stomp)
{
    if (stomp)
	icol->cmap = icol->stompmap;
    else
	icol->cmap = icol->usualmap;
}


static void SetAllColors(int stomp, int axis)
{
    ColorBar	*cbar = icol->cbar;

    static Colormap	lastmap = 0;

    if (stomp)
	ColorBarStomp(cbar, icol->cmap);

    if (icol->bigmap || !stomp) {
	ColorBarStore(cbar, icol->cmap);
	if (axis)
	    AxisBarSetColors(icol->abar, cbar->color[cbar->focus].channel, 
			     cbar->rgbmode, icol->cmap);
    }

    if (lastmap != icol->cmap) {
	lastmap = icol->cmap;
	XSetWindowColormap(icol->display, XtWindow(icol->shell),
			   icol->cmap);
    }
}


static void SaveState(void)
{
    ColorBarSave(icol->cbar, 1);
    SetValue(XmNsensitive, (XtArgVal) 1, GetWidget("undo"));
}


static void PostFileError(char *error)
{
    XmString	xms;
    Widget	ebox = GetWidget("fileError");

    xms = MakeXmString(error);
    SetValue(XmNmessageString, (XtArgVal) xms, ebox);
    XmStringFree(xms);
    XtManageChild(ebox);
}


static void AutoSave(void)
{
    ++icol->dirty;

    if (XmToggleButtonGetState(GetWidget("autoi")))
	SetInterestColormap();
    if (!icol->autosave)
	return;

    if (!icol->savename)
	icol->savename = NewString("-", 0);
    
    if (FileSaveBar(icol->cbar, icol->savename, icol->format))
	icol->dirty = 0;
    else
	PostFileError("File error: cannot save");
}


static void SetScale(int value, Widget w)
{
    SetValue(XmNvalue, (XtArgVal) value, w);
}

static void UpdateCellLabel(int cell)
{
    char	buf[256];
    XmString	xms;
    float	x;
    
    cell = MAX(0, MIN(cell, icol->ncolor - 1));

    (void) sprintf(buf, "Color Cell %d", cell);
    xms = MakeXmString(buf);
    SetValue(XmNlabelString, (XtArgVal) xms, GetWidget("cellLabel"));
    XmStringFree(xms);
}


static int Round(float x)
{
    return x + 0.5;
}


static void UpdateScale(void)
{
    Color	*color = icol->cbar->color + icol->cbar->focus;
    
    static Widget	scale[3];
    
    if (!scale[0]) {
	scale[0] = GetWidget("redScale");
	scale[1] = GetWidget("grnScale");
	scale[2] = GetWidget("bluScale");
    }
    
    switch (icol->cbar->rgbmode) {
      case RGB_MODE:
	SetScale(Round(color->channel[0] * 255), scale[0]);
	SetScale(Round(color->channel[1] * 255), scale[1]);
	SetScale(Round(color->channel[2] * 255), scale[2]);
	break;
      case HSV_MODE:
	SetScale(Round(color->channel[0] * 360), scale[0]);
	SetScale(Round(color->channel[1] * 100), scale[1]);
	SetScale(Round(color->channel[2] * 100), scale[2]);
	break;
      case YUV_MODE:
	SetScale(Round(color->channel[0] * 100), scale[0]);
	SetScale(Round(color->channel[1] * 100 - 50), scale[1]);
	SetScale(Round(color->channel[2] * 100 - 50), scale[2]);
	break;
      case ALPHA_MODE:
	SetScale(Round(color->channel[0] * 100), scale[0]);
	break;
    }
    
    UpdateCellLabel(icol->cbar->focus);

    if (icol->colorAxis)
	AxisBarSetColors(icol->abar, color->channel, 
			 icol->cbar->rgbmode, icol->cmap);
}


static void ChangeMode(ColorMode rgbmode)
{
    ColorBar	*cbar = icol->cbar;
    int		i;
    
    static Widget	scale[3];
    static Widget	label[3];
    static Widget	snapChannel[3];

    if (!scale[0]) {
	scale[0] = GetWidget("redScale");
	scale[1] = GetWidget("grnScale");
	scale[2] = GetWidget("bluScale");
	label[0] = GetWidget("redLabel");
	label[1] = GetWidget("grnLabel");
	label[2] = GetWidget("bluLabel");
	snapChannel[0] = GetWidget("snap1");
	snapChannel[1] = GetWidget("snap2");
	snapChannel[2] = GetWidget("snap3");
    }
    
    icol->rgbmode = rgbmode;
    
    switch (rgbmode) {
      case RGB_MODE:
      case HSV_MODE:
      case YUV_MODE:
	SetValue(XmNbottomAttachment, (XtArgVal) XmATTACH_NONE, 
		 GetWidget("redLabel"));
	XtManageChildren(scale + 1, 2);
	XtManageChildren(label + 1, 2);
	XtManageChildren(snapChannel + 1, 2);
	break;
	
      case ALPHA_MODE:
	XtUnmanageChildren(scale + 1, 2);
	XtUnmanageChildren(label + 1, 2);
	XtUnmanageChildren(snapChannel + 1, 2);
	SetValue(XmNbottomAttachment, (XtArgVal) XmATTACH_FORM, 
		 GetWidget("redLabel"));
	break;
    }
	
    switch (rgbmode) {

      case RGB_MODE:
	for (i = 0; i < 3; i++) {
	    SetValue(XmNmaximum, (XtArgVal) 255, (Widget) NULL);
	    SetValue(XmNminimum, (XtArgVal) 0, (Widget) NULL);
	    SetValue(XmNvalue, (XtArgVal) 0, (Widget) NULL);
	    SetValue(XmNdecimalPoints, (XtArgVal) 0, scale[i]);
	    SetValue(XmNwidth, (XtArgVal) 52, (Widget) NULL);
	    SetValue(XmNlabelString, (XtArgVal) icol->scaleString[0][i], 
		     label[i]);
	    SetValue(XmNlabelString, (XtArgVal) icol->snapString[0][i], 
		     snapChannel[i]);
	}
	ColorBarRGB(cbar);
	break;

      case HSV_MODE:
	for (i = 0; i < 3; i++) {
	    switch (i) {
	      case 0: 
		SetValue(XmNmaximum, (XtArgVal) 360, (Widget) NULL); 
		SetValue(XmNdecimalPoints, (XtArgVal) 0, (Widget) NULL);
		break;
	      case 1: 
	      case 2: 
		SetValue(XmNmaximum, (XtArgVal) 100, (Widget) NULL);
		SetValue(XmNdecimalPoints, (XtArgVal) 2, (Widget) NULL);
		break;
	    }
	    SetValue(XmNminimum, (XtArgVal) 0, (Widget) NULL);
	    SetValue(XmNvalue, (XtArgVal) 0, scale[i]);
	    SetValue(XmNlabelString, (XtArgVal) icol->scaleString[1][i], 
		     label[i]);
	    SetValue(XmNlabelString, (XtArgVal) icol->snapString[1][i], 
		     snapChannel[i]);
	}
	ColorBarHSV(cbar);
	break;
	
      case YUV_MODE:
	for (i = 0; i < 3; i++) {
	    switch (i) {
	      case 0: 
		SetValue(XmNmaximum, (XtArgVal) 100, (Widget) NULL); 
		SetValue(XmNminimum, (XtArgVal) 0, (Widget) NULL);
		SetValue(XmNdecimalPoints, (XtArgVal) 2, (Widget) NULL);
		break;
	      case 1: 
	      case 2: 
		SetValue(XmNmaximum, (XtArgVal)  50, (Widget) NULL);
		SetValue(XmNminimum, (XtArgVal) -50, (Widget) NULL);
		SetValue(XmNdecimalPoints, (XtArgVal) 2, (Widget) NULL);
		break;
	    }
	    SetValue(XmNvalue, (XtArgVal) 0, scale[i]);
	    SetValue(XmNlabelString, (XtArgVal) icol->scaleString[2][i], 
		     label[i]);
	    SetValue(XmNlabelString, (XtArgVal) icol->snapString[2][i], 
		     snapChannel[i]);
	}
	ColorBarYUV(cbar);
	break;
	
      case ALPHA_MODE:
	SetValue(XmNmaximum, (XtArgVal) 100, (Widget) NULL); 
	SetValue(XmNminimum, (XtArgVal) 0, (Widget) NULL);
	SetValue(XmNdecimalPoints, (XtArgVal) 2, (Widget) NULL);
	SetValue(XmNvalue, (XtArgVal) 0, scale[0]);
	SetValue(XmNlabelString, (XtArgVal) icol->scaleString[3][0], 
		 label[0]);
	SetValue(XmNlabelString, (XtArgVal) icol->snapString[3][0], 
		 snapChannel[0]);
	ColorBarAlpha(cbar);
	break;
    }
}


/*ARGSUSED*/
void SnapCB(Widget w, XtPointer closure, XtPointer callData)
{
    int	message = (int) closure;

    SaveState();
    if (message == SNAP_ALL)
	ColorBarSnapAll(icol->cbar);
    else
	ColorBarSnap(icol->cbar, message);

    SetAllColors(icol->bigmap && icol->stomp, False);
    ColorBarClearGraph(icol->cbar);
    ColorBarDrawGraph(icol->cbar, False);
    if (icol->cbar->rgbmode == YUV_MODE)
	UpdateScale();
    AutoSave();
}


/*ARGSUSED*/
void PlaceKnotsCB(Widget w, XtPointer closure, XtPointer callData)
{
    SaveState(); 

    ColorBarPlaceKnots(icol->cbar, icol->slopediff);
    ColorBarClearGraph(icol->cbar);
    ColorBarDrawGraph(icol->cbar, False);
    ColorBarDrawButtons(icol->cbar);
    UpdateScale();
}


/*ARGSUSED*/
void UndoCB(Widget w, XtPointer closure, XtPointer callData)
{
    if (icol->cbar->undo->size > 0) {
	SetValue(XmNsensitive, (XtArgVal) True, GetWidget("redo"));
	ColorBarSave(icol->cbar, False);
	ColorBarRestore(icol->cbar, True);
	ChangeMode(icol->cbar->rgbmode);
	ColorBarClearGraph(icol->cbar);
	ColorBarDrawGraph(icol->cbar, False);
	ColorBarDrawButtons(icol->cbar);
	SetAllColors(icol->bigmap && icol->stomp, False);
	UpdateScale();
	AutoSave();
    }
}


/*ARGSUSED*/
void RedoCB(Widget w, XtPointer closure, XtPointer callData)
{
    if (icol->cbar->redo->size > 0) {
	SetValue(XmNsensitive, (XtArgVal) True, GetWidget("undo"));
	ColorBarSave(icol->cbar, True);
	ColorBarRestore(icol->cbar, False);
	ChangeMode(icol->cbar->rgbmode);
	ColorBarClearGraph(icol->cbar);
	ColorBarDrawGraph(icol->cbar, False);
	ColorBarDrawButtons(icol->cbar);
	SetAllColors(icol->bigmap && icol->stomp, False);
	UpdateScale();
	AutoSave();
    }
}


/*ARGSUSED*/
void PickCB(Widget w, XtPointer closure, XtPointer callData)
{
    Window	root = RootWindowOfScreen(XtScreen(icol->shell));
    XEvent 	ev;
    XImage	*ximg;
    Pixel	p;

    GrabUntilClick(icol->shell, &ev);
    ximg = XGetImage(icol->display, root, 
		     ev.xbutton.x_root, ev.xbutton.y_root, 1, 1, ~0, ZPixmap);

    p = *ximg->data & ((1 << ximg->bits_per_pixel) - 1);
	
    XtFree((XtPointer)ximg->data);
    XtFree((XtPointer)ximg);
    
    SaveState();
    ColorBarSetFocus(icol->cbar, p % icol->cbar->ncolor);
    ColorBarDrawButtons(icol->cbar);
    ColorBarDrawGraph(icol->cbar, False);
    UpdateScale();
}


static XErrorHandler	xHandler;

static int CatchColorError(Display *dpy, XErrorEvent *ee)
{
    if (ee->request_code == X_QueryColors  ||
	ee->request_code == X_StoreColors)
	return 0;

    xHandler(dpy, ee);
    return 0;
}


static void GrabWindowColormap(Window w, int butx, int buty)
{
    /* Get a colormap that is associated with another window.
     * Copy that into our own colormap
     */
    XWindowAttributes	wattr;
    Window		cwin;
    Window		root, parent, *child = NULL;
    unsigned int	nchild;
    int			i, x, y;
    unsigned int	width, height, bw, depth;
    Colormap		cmap = None;
    XColor		*xcol;
    float		cfact = 1.0 / 65535.0;
    
    /* XQueryColors may fail, since we can't really determine
     * what color cell entries are available.
     * This error handler ignores errors from QueryColors.
     */
    xHandler = XSetErrorHandler(CatchColorError);

    /* Walk down the window tree, grabbing the color maps of windows
     * underneath the pointer.
     */
    cwin = w;
    while (cwin != None) {

	/* Grab the colormap if window is InputOutput.
	 */
	if (XGetWindowAttributes(icol->display, cwin, &wattr))
	    if (wattr.class == InputOutput  &&  wattr.colormap != None)
		cmap = wattr.colormap;

	/* Examine the children top down, which is the reverse order
	 * of the list retruned by XQueryTree.
	 */
	if (child) 
	    XFree((XtPointer) child);
	child = NULL;
	if (XQueryTree(icol->display, cwin, 
		       &root, &parent, &child, &nchild)) {
	    cwin = None;
	    for (i = nchild - 1; i >= 0; --i)
		if (XGetGeometry(icol->display, child[i], &root, &x, &y,
				 &width, &height, &bw, &depth))
		    if (butx >= x  &&  butx <= x + width  &&
			buty >= y  &&  buty <= y + height) {
			butx -= x;
			buty -= y;
			cwin = child[i];
			break;
		    }
	} else
	    break;
    }
    if (child)
	XFree((XtPointer) child);
    if (cmap == None) {
	if (!XGetWindowAttributes(icol->display, w, &wattr))
	    return;
	cmap = DefaultColormapOfScreen(wattr.screen);
    }

    /* Extract colors from the X colormap.  
     * QueryColors may fail, but that's OK.
     */
    xcol = CallocType(XColor, icol->ncolor);
    MemCheck(xcol);

    for (i = 0; i < icol->ncolor; ++i) {
	xcol[i].flags = 0;
	xcol[i].pixel = i;
    }
    XQueryColors(icol->display, cmap, xcol, icol->ncolor);
    
    /* Put new colors in icol color table.
     */
    for (i = 0; i < icol->ncolor; ++i)
	if (xcol[i].flags)
	    PutRGB(icol->cbar, i,
		   xcol[i].red*cfact, xcol[i].green*cfact, xcol[i].blue*cfact);
    free(xcol);
    
    /* Restore previous error handler.
     */
    (void) XSetErrorHandler(xHandler);
}


/*ARGSUSED*/
void GrabColorCB(Widget w, XtPointer closure, XtPointer callData)
{
    XEvent 		ev;
    Window		root;
    unsigned int	width, height, bw, depth;
    int			x, y;

    GrabUntilClick(icol->shell, &ev);
    if (ev.xbutton.subwindow)
	if (XGetGeometry(icol->display, ev.xbutton.subwindow, &root, &x, &y,
			 &width, &height, &bw, &depth)) {
	    ev.xbutton.window = ev.xbutton.subwindow;
	    ev.xbutton.x -= x;
	    ev.xbutton.y -= y;
	}
	
    GrabWindowColormap(ev.xbutton.window, ev.xbutton.x, ev.xbutton.y);

    icol->dirty = False;
    ColorBarClearGraph(icol->cbar);
    ColorBarDrawGraph(icol->cbar, False);
    ColorBarDrawButtons(icol->cbar);
    SetAllColors(icol->bigmap && icol->stomp, False);
    UpdateScale();
}


static void SetWindowColormap(Window w, int butx, int buty)
{
    XWindowAttributes	wattr;
    Window		root, parent, cwin, *child = NULL;
    unsigned int	nchild, width, height, bw, depth;
    int			i, x, y;
    Colormap		cmap;
    Visual		*cvis;
    XColor		*xcol;
    float		r, g, b, cscale = 0xffff;
    
    /* XStoreColors may fail, since we can't really determine
     * what color cell entries are available.
     * This error handler ignores errors from StoreColors.
     */
    xHandler = XSetErrorHandler(CatchColorError);

    /* Create a new colormap using the visual of the chosen window.
     */
    if (!XGetWindowAttributes(icol->display, w, &wattr))
	return;
    cvis = wattr.visual;
    cmap = XCreateColormap(icol->display, w, cvis, AllocAll);
    
    /* Fill the new color map with icol's table.
     * XStoreColors may fail, but that's OK.
     */
    xcol = CallocType(XColor, icol->ncolor);
    MemCheck(xcol);
    for (i = 0; i < icol->ncolor; ++i) {
	GetRGB(icol->cbar, i, &r, &g, &b);
	xcol[i].pixel = i;
	xcol[i].flags = DoRed | DoGreen | DoBlue;
	xcol[i].red   = r * cscale;
	xcol[i].green = g * cscale;
	xcol[i].blue  = b * cscale;
    }
    XStoreColors(icol->display, cmap, xcol, icol->ncolor);
    free(xcol);

    /* Walk down the window tree, setting the color maps of windows
     * underneath the pointer.
     */
    cwin = w;
    while (cwin != None) {

	/* Set the colormap if window is InputOutput and
	 * the visual matches the Colormap's visual.
	 */
	if (XGetWindowAttributes(icol->display, cwin, &wattr))
	    if (wattr.class == InputOutput  &&  wattr.visual == cvis)
		XSetWindowColormap(icol->display, cwin, cmap);
	
	/* Examine the children top down, which is the reverse order
	 * of the list retruned by XQueryTree.
	 */
	if (child)
	    XFree((XtPointer) child);
	child = NULL;
	if (XQueryTree(icol->display, cwin, 
		       &root, &parent, &child, &nchild)) {
	    cwin = None;
	    for (i = nchild - 1; i >= 0; --i)
		if (XGetGeometry(icol->display, child[i], &root, &x, &y,
				 &width, &height, &bw, &depth))
		    if (butx >= x  &&  butx <= x + width  &&
			buty >= y  &&  buty <= y + height) {
			butx -= x;
			buty -= y;
			cwin = child[i];
			break;
		    }
	} else
	    break;
    }
    if (child)
	XFree((XtPointer) child);
    
    /* Restore the previous error handler.
     */
    (void) XSetErrorHandler(xHandler);
}


/*ARGSUSED*/
void SetColorCB(Widget w, XtPointer closure, XtPointer callData)
{
    XEvent 		ev;
    Window		root;
    unsigned int	width, height, bw, depth;
    int			x, y;

    GrabUntilClick(icol->shell, &ev);
    if (ev.xbutton.subwindow)
	if (XGetGeometry(icol->display, ev.xbutton.subwindow, &root, &x, &y,
			 &width, &height, &bw, &depth)) {
	    ev.xbutton.window = ev.xbutton.subwindow;
	    ev.xbutton.x -= x;
	    ev.xbutton.y -= y;
	}
	
    SetWindowColormap(ev.xbutton.window, ev.xbutton.x, ev.xbutton.y);
}


/*ARGSUSED*/
void CascadeCB(Widget w, XtPointer closure, XtPointer callData)
{
    int		message = (int) closure;
    
    if (message == MENU_EDIT) {
	SetValue(XmNsensitive, (XtArgVal) (icol->cbar->undo->size), 
		 GetWidget("undo"));
	SetValue(XmNsensitive, (XtArgVal) (icol->cbar->redo->size),
		 GetWidget("redo"));

    } else if (message == MENU_MODE) {
	ColorMode	rgbmode = icol->cbar->rgbmode;

	SetValue(XmNset, (XtArgVal) (rgbmode == RGB_MODE), GetWidget("rgb"));
	SetValue(XmNset, (XtArgVal) (rgbmode == HSV_MODE), GetWidget("hsv"));
	SetValue(XmNset, (XtArgVal) (rgbmode == YUV_MODE), GetWidget("yuv"));
	SetValue(XmNset, (XtArgVal) (rgbmode == ALPHA_MODE), 
		 GetWidget("alpha"));

    } else if (message == MENU_BRUSH) {
	int		newbrush = icol->cbar->brush.kind;

	SetValue(XmNset, (XtArgVal) (newbrush == 0), GetWidget("square"));
	SetValue(XmNset, (XtArgVal) (newbrush == 1), GetWidget("circle"));
	SetValue(XmNset, (XtArgVal) (newbrush == 2), GetWidget("diamond"));
    }
}


/*ARGSUSED*/
void BrushCB(Widget w, XtPointer closure, XtPointer callData)
{
    icol->cbar->brush.kind = (int) closure;
}


/*ARGSUSED*/
void FormatCB(Widget w, XtPointer closure, XtPointer callData)
{
    XmToggleButtonCallbackStruct *toggle = 
	(XmToggleButtonCallbackStruct *) callData;
    
    if (toggle->reason != XmCR_ARM)
	return;

    icol->format = (FileFormat) closure;
}


/*ARGSUSED*/
void QuitCB(Widget w, XtPointer closure, XtPointer callData)
{
    if (closure)
	exit(0);

    if (icol->autosave) {
	AutoSave();
	exit(0);
    }
	
    if (icol->dirty)
	XtManageChild(GetWidget("quitBox"));
    else
	exit(0);
}


static void SetFormatToggle(void)
{
    SetValue(XmNset, (XtArgVal) (icol->format == ASCII_INDEX),
	     GetWidget("asciiIndex"));
    SetValue(XmNset, (XtArgVal) (icol->format == ASCII_PLAIN),
	     GetWidget("asciiPlain"));
    SetValue(XmNset, (XtArgVal) (icol->format == BIN_LEAVE),
	     GetWidget("binLeave"));
    SetValue(XmNset, (XtArgVal) (icol->format == BIN_ROW),
	     GetWidget("binRow"));
}


static void SetFileName(char *fname)
{
    XmString	xms = MakeXmString(fname);

    SetValue(XmNdirSpec, (XtArgVal) xms, GetWidget("fsBox"));
    XmStringFree(xms);
}


/*ARGSUSED*/
void FileCB(Widget w, XtPointer closure, XtPointer callData)
{
    FileReason	message = (FileReason) closure;

    static int	filemode = 0;
    
    if (message == FILE_SAVE) {
	filemode = 1;
	SetFormatToggle();
	if (icol->savename)
	    SetFileName(icol->savename);
	else
	    SetFileName("");

    } else if (message == FILE_OPEN) {
	filemode = 2;
	SetFormatToggle();
	if (icol->openname)
	    SetFileName(icol->openname);
	else
	    SetFileName("");

    } else if (message == FILE_QUIT) {
	filemode = 3;
	SetFormatToggle();
	if (icol->savename)
	    SetFileName(icol->savename);
	else
	    SetFileName("");

    } else if (message == FILE_OK) {
	XmFileSelectionBoxCallbackStruct *fsb = 
	    (XmFileSelectionBoxCallbackStruct *) callData;
	String	fname;

	if (!XmStringGetLtoR(fsb->value, XmSTRING_DEFAULT_CHARSET, &fname)) {
	    PostFileError("File error: cannot get file name");
	    return;
	}
	
	switch (filemode) {

	  case 1:
	    if (icol->savename)
		free(icol->savename);
	    icol->savename = NewString(fname, 0);
	    
	    if (XmToggleButtonGetState(GetWidget("autoi")))
		SetInterestColormap();
	    if (FileSaveBar(icol->cbar, icol->savename, icol->format)) {
		icol->dirty = 0;
		filemode = 0;
	    } else
		PostFileError("File error: cannot save");
	    break;

	  case 2:
	    if (icol->openname)
		free(icol->openname);
	    icol->openname = NewString(fname, 0);
	    
	    if (FileOpenBar(icol->cbar, icol->openname, icol->format)) {
		icol->dirty = 0;
		filemode = 0;
		ColorBarClearGraph(icol->cbar);
		ColorBarDrawGraph(icol->cbar, False);
		ColorBarDrawButtons(icol->cbar);
		SetAllColors(icol->bigmap && icol->stomp, False);
		UpdateScale();
		AutoSave();
	    } else
		PostFileError("File error: cannot open");
	    break;

	  case 3:
	    if (icol->savename)
		free(icol->savename);
	    icol->savename = NewString(fname, 0);

	    if (FileSaveBar(icol->cbar, fname, icol->format))
		exit(0);
	    PostFileError("File error: cannot save");
	    break;
	}

	free(fname);

    } else if (message == FILE_CANCEL)
	filemode = 0;
}


/*ARGSUSED*/
void ModeCB(Widget w, XtPointer closure, XtPointer callData)
{
    ColorBar	*cbar = icol->cbar;
    ColorMode	rgbmode;
    XmToggleButtonCallbackStruct *toggle = 
	(XmToggleButtonCallbackStruct *) callData;
    IcolMode	message = (IcolMode) closure;

    if (message == MODE_AUTO) {
	icol->autosave = toggle->set;
	return;
    }

    if (message == MODE_STOMP) {
	icol->stomp = toggle->set;
	if (icol->bigmap) {
	    SetColormap(icol->stomp);
	    SetAllColors(icol->stomp, icol->colorAxis);
	}
	return;
    }

    else if (message == MODE_HSV)
	rgbmode = HSV_MODE;
    else if (message == MODE_YUV)
	rgbmode = YUV_MODE;
    else if (message == MODE_ALPHA)
	rgbmode = ALPHA_MODE;
    else
	rgbmode = RGB_MODE;
    
    if (rgbmode == cbar->rgbmode)
	return;

    SaveState();
    ChangeMode(rgbmode);
    
    ColorBarClearGraph(cbar);
    ColorBarDrawGraph(cbar, False);
    SetAllColors(icol->bigmap && icol->stomp, False);
    UpdateScale();
}


/*ARGSUSED*/
void ScaleCB(Widget w, XtPointer closure, XtPointer callData)
{
    ColorBar	*cbar = icol->cbar;
    XmScaleCallbackStruct *scale = (XmScaleCallbackStruct *) callData;
    int		channel = (int) closure;
    
    static int	dragging = False;

    if (scale->reason == XmCR_DRAG) {
	if (!dragging) {
	    /* Starting a drag sequence
	     */
	    dragging = True;
	    cbar->changeChannel = channel;
	    SaveState();
	    if (icol->stomp && !icol->bigmap) {
		SetColormap(True);
		SetAllColors(True, icol->colorAxis);
	    }
	    ColorBarDrawGraph(cbar, False);	/* refresh */
	}
	ColorBarDrawGraph(cbar, True);		/* xor off */
    } else
	if (!dragging)
	    SaveState();

    switch (cbar->rgbmode) {
      case RGB_MODE:
	ColorBarSet(cbar, channel, scale->value / 255.0);
	break;
      case HSV_MODE:
	if (channel == 0)
	    ColorBarSet(cbar, channel, scale->value / 360.0);
	else
	    ColorBarSet(cbar, channel, scale->value / 100.0);
	break;
      case YUV_MODE:
	if (channel == 0)
	    ColorBarSet(cbar, channel, scale->value / 100.0);
	else
	    ColorBarSet(cbar, channel, scale->value / 100.0 + 0.5);
	break;
      case ALPHA_MODE:
	ColorBarSet(cbar, channel, scale->value / 100.0);
	break;
    }

    SetAllColors(icol->stomp && dragging, icol->colorAxis);

    if (scale->reason == XmCR_DRAG) {
	ColorBarDrawGraph(cbar, True);	/* xor on */
    } else {
	if (dragging) {
	    dragging = False;
	    cbar->changeChannel = -1;
	    if (icol->stomp && !icol->bigmap) {
		SetColormap(False);
		SetAllColors(False, icol->colorAxis);
	    }
	}
	ColorBarClearGraph(cbar);
	ColorBarDrawGraph(cbar, False);
	AutoSave();
    }
}


/*ARGSUSED*/
void ChartCB(Widget w, XtPointer closure, XtPointer callData)
{
    ColorBar	*cbar = icol->cbar;
    XmDrawingAreaCallbackStruct	*area = 
	(XmDrawingAreaCallbackStruct *) callData;

    if (icol->showCopyright) {
	XtManageChild(GetWidget("copyright"));
	icol->showCopyright = 0;
    }

    switch (area->reason) {

      case XmCR_INPUT:
	if (area->event->type == ButtonPress) {
	    SaveState();
	    if (area->event->xbutton.state & ShiftMask) {
		int	focus;
		focus = ColorBarFindChart(cbar, area->event->xbutton.x);
		ColorBarSetFocus(cbar, focus);
		ColorBarDrawButtons(cbar);
		ColorBarDrawGraph(cbar, False);
		UpdateScale();
	    } else {
		ColorBarSetBrush(cbar, area->event->xbutton.button, 
				 area->event->xbutton.x,
				 area->event->xbutton.y);
		if (icol->stomp && !icol->bigmap)
		    SetColormap(True);
		ColorBarDrawGraph(cbar, False);	/* xor on */
		SetAllColors(icol->stomp, False);
		UpdateCellLabel(cbar->brush.index);
	    }
	} else if (area->event->type == MotionNotify) {
	    if (cbar->brush.channel >= 0) {
		ColorBarDrawGraph(cbar, True);	/* xor off */
		ColorBarMoveBrush(cbar, area->event->xbutton.x,
				  area->event->xbutton.y);
		ColorBarDrawGraph(cbar, True);	/* xor on */
		SetAllColors(icol->stomp, False);
		UpdateCellLabel(cbar->brush.index);
	    }
	} else if (area->event->type == ButtonRelease) {
	    if (cbar->brush.channel >= 0) {
		ColorBarDrawGraph(cbar, True);	/* xor off */
		ColorBarSetBrush(cbar, 0, 0, 0);
		ColorBarClearGraph(cbar);
		ColorBarDrawGraph(cbar, False);
		if (icol->stomp && !icol->bigmap)
		    SetColormap(False);
		SetAllColors(icol->stomp && icol->bigmap, False);
		UpdateScale();
		AutoSave();
	    }
	}
	break;

      case XmCR_RESIZE:
	if (!XtIsRealized(w))
	    break;
	XClearWindow(icol->display, XtWindow(w));
	ColorBarResizeChart(cbar);
	ColorBarDrawChart(cbar);
	if (icol->colorAxis)
	    AxisBarResize(icol->abar);
	break;

      case XmCR_EXPOSE:
	ColorBarDrawChart(cbar);
	break;
    }
}


/*ARGSUSED*/
void ButtonCB(Widget w, XtPointer closure, XtPointer callData)
{
    ColorBar	*cbar = icol->cbar;
    XmDrawingAreaCallbackStruct	*area = 
	(XmDrawingAreaCallbackStruct *) callData;
    int		button;

    static SaveBar	*pressState = NULL;
    static int		lastButton;
    static int		deleteOnRelease;
    static int		needSave;
    
    if (!pressState) {
	pressState = MallocType(SaveBar);
	MemCheck(pressState);
	pressState->color = CallocType(SaveColor, icol->ncolor);
	MemCheck(pressState->color);
    }

    switch (area->reason) {
	
      case XmCR_INPUT:
	if (area->event->type == ButtonPress) {
	    lastButton = ColorBarFindButton(cbar, area->event->xbutton.x, 
					    area->event->xbutton.y);
	    deleteOnRelease = lastButton == cbar->focus;
	    needSave = 0;
	    if (lastButton >= 0) {
		SaveState();
		if (lastButton != cbar->focus) {
		    ColorBarSetFocus(cbar, lastButton);
		    if (area->event->xbutton.state & ShiftMask) {
			ColorBarSnap(cbar, -1);
			deleteOnRelease = 0;
			needSave = 1;
		    }
		    ColorBarDrawButtons(cbar);
		    ColorBarClearGraph(cbar);
		    ColorBarDrawGraph(cbar, False);
		    UpdateScale();
		}
		SaveBarWrite(pressState, cbar);
	    }
	    
	} else if (area->event->type == MotionNotify) {
	    button = ColorBarFindButton(cbar, area->event->xbutton.x, 
					area->event->xbutton.y);
	    if (button >= 0  &&  button != lastButton) {
		int	sourceCell = pressState->focus;

		deleteOnRelease = False;
		needSave = 1;
		SaveBarRead(pressState, cbar);
		if (cbar->focus > 0  &&  cbar->focus < cbar->ncolor - 1)
		    cbar->color[cbar->focus].knot = False;
		cbar->focus = lastButton;
		ColorBarSetFocus(cbar, button);
		ColorBarSet(cbar, 0, cbar->color[sourceCell].channel[0]);
		ColorBarSet(cbar, 1, cbar->color[sourceCell].channel[1]);
		ColorBarSet(cbar, 2, cbar->color[sourceCell].channel[2]);
		if (!(area->event->xbutton.state & ShiftMask))
		    ColorBarSnap(cbar, -1);
		SetAllColors(icol->bigmap && icol->stomp, False);
		ColorBarDrawButtons(cbar);
		ColorBarClearGraph(cbar);
		ColorBarDrawGraph(cbar, False);
		UpdateScale();
		lastButton = button;
	    }
	    
	} else if (area->event->type == ButtonRelease) {
	    button = ColorBarFindButton(cbar, area->event->xbutton.x, 
					area->event->xbutton.y);
	    if (deleteOnRelease  &&  button == cbar->focus) {
		ColorBarUnsetFocus(cbar);
		ColorBarDrawButtons(cbar);
		ColorBarClearGraph(cbar);
		ColorBarDrawGraph(cbar, False);
		UpdateScale();
	    } else if (needSave)
		AutoSave();
	}
	
	break;
	
      case XmCR_RESIZE:
	if (!XtIsRealized(w))
	    break;
	XClearWindow(icol->display, XtWindow(w));
	ColorBarResizeButtons(cbar);
	ColorBarDrawButtons(cbar);
	break;
	
      case XmCR_EXPOSE:
	ColorBarDrawButtons(cbar);
	break;
    }
}


/*ARGSUSED*/
void BugCB(Widget w, XtPointer closure, XtPointer callData)
{
    String	bugblab;
    char	mailcmd[256];
    FILE	*mailfile;

    bugblab = XmTextGetString(GetWidget("bugText"));
    if (!bugblab)
	return;

    (void) strncat(strcpy(mailcmd, "mail "), icol->bugAddr, 240);
    mailfile = popen(mailcmd, "w");
    if (!mailfile)
	return;

    (void) fprintf(mailfile, "To: %s\n", icol->bugAddr);
    (void) fprintf(mailfile, "Subject: Icol Bug Report\n\n");
    (void) fprintf(mailfile, "%s\n", bugblab);
    (void) fclose(mailfile);
    
    XtFree(bugblab);
}


/*ARGSUSED*/
static void InterestWindowCB(Widget w, XtPointer closure, Atom *selection, 
			     Atom *type, XtPointer value, 
			     unsigned long *vlength, int *format)
{
    Window		cwin;
    unsigned char	*map;
    unsigned		i, j, k;
    float		r, g, b;
    
    if (!value || *type != XA_WINDOW)
	return;
    
    memcpy(&cwin, value, sizeof(Window));

    if (icol->rgbmode == ALPHA_MODE) {
	k = icol->ncolor;
	map = CallocType(unsigned char, k);
	MemCheck(map);
	for (i = 0; i < k; ++i) {
	    GetRGB(icol->cbar, i, &r, &g, &b);
	    map[i] = (0.299*r + 0.587*g + 0.114*b) * 255.0;
	}
	XChangeProperty(icol->display, cwin, icol->xaAlphamap, 
			icol->xaAlphamap, 8, PropModeReplace, map, k);
	free(map);
	
    } else {
	k = 3*icol->ncolor;
	map = CallocType(unsigned char, k);
	MemCheck(map);
	for (i = 0, j = 0; j < k; ++i, j += 3) {
	    GetRGB(icol->cbar, i, &r, &g, &b);
	    map[j  ] = r * 255.0;
	    map[j+1] = g * 255.0;
	    map[j+2] = b * 255.0;
	}
	XChangeProperty(icol->display, cwin, icol->xaColormap, 
			icol->xaColormap, 8, PropModeReplace, map, k);
	free(map);
    }

    XtFree(value);
}


static void SetInterestColormap(void)
{
    XtGetSelectionValue(GetWidget("chart"), icol->xaInterest, XA_WINDOW,
			InterestWindowCB, NULL, 
			XtLastTimestampProcessed(icol->display));
}


/*ARGSUSED*/
static void InterestColormapCB(Widget w, XtPointer closure, Atom *selection, 
			       Atom *type, XtPointer value, 
			       unsigned long *vlength, int *format)
{
    Window		cwin;
    unsigned char	*map;
    unsigned		i, j, maplen;
    
    if (!value || (*type != icol->xaColormap && *type != icol->xaAlphamap))
	return;
    
    map = (unsigned char *) value;
    maplen = *vlength * *format / 8;

    if (*type == icol->xaColormap) {
	maplen /= 3;
	maplen = MIN(maplen, icol->ncolor);
	for (i = 0, j = 0; i < maplen; ++i, j += 3)
	    PutRGB(icol->cbar, i, 
		   map[j]/255.0, map[j+1]/255.0, map[j+2]/255.0);

    } else {
	maplen = MIN(maplen, icol->ncolor);
	for (i = 0; i < maplen; ++i)
	    PutRGB(icol->cbar, i, 
		   map[i]/255.0, map[i]/255.0, map[i]/255.0);
    }
    
    icol->dirty = False;
    ColorBarClearGraph(icol->cbar);
    ColorBarDrawGraph(icol->cbar, False);
    ColorBarDrawButtons(icol->cbar);
    SetAllColors(icol->bigmap && icol->stomp, False);
    UpdateScale();
}


static void GetInterestColormap(void)
{
    if (icol->rgbmode == ALPHA_MODE)
	XtGetSelectionValue(GetWidget("chart"), icol->xaInterest,
			    icol->xaAlphamap, InterestColormapCB, NULL, 
			    XtLastTimestampProcessed(icol->display));
    else
	XtGetSelectionValue(GetWidget("chart"), icol->xaInterest,
			    icol->xaColormap, InterestColormapCB, NULL, 
			    XtLastTimestampProcessed(icol->display));
}


/*ARGSUSED*/
void PropCB(Widget w, XtPointer closure, XtPointer callData)
{
    switch ((PropReason) closure) {
      case PROP_SET:
	SetInterestColormap();
	break;
      case PROP_GET:
	GetInterestColormap();
	break;
    }
}

static void SetupSelection(void)
{
    icol->xaColormap = XInternAtom(icol->display, "IcolColormap", False);
    icol->xaAlphamap = XInternAtom(icol->display, "IcolAlphamap", False);
    icol->xaInterest = XInternAtom(icol->display, "IcolInterest", False);
}


main(unsigned int argc, char *argv[])
{
    SetupIcol(&argc, argv);

    icol->cbar = ColorBarNew(icol->shell, icol->cmap, icol->mapsize,
			     icol->ncolor, icol->maxundo, icol->quickSeg,
			     icol->stompbase);

    XtRealizeWidget(icol->shell);
    XFlush(icol->display);

    ColorBarSetWidget(icol->cbar, GetWidget("chart"), GetWidget("button"));
    if (icol->colorAxis)
	AxisBarResize(icol->abar);
    MirrorColormap(icol->display, icol->cmap, icol->stompmap,
		   icol->cbar->xcolor[icol->cbar->nxcolor-1].pixel);

    if (argc > 1) {
	icol->openname = NewString(argv[1], 0);
	(void) FileOpenBar(icol->cbar, icol->openname, icol->format);
    }

    if (icol->rgbmode != RGB_MODE)
	ChangeMode(icol->rgbmode);

    SetupSelection();
    if (icol->autosave) {
	SetValue(XmNset, (XtArgVal) True, GetWidget("auto"));
	AutoSave();
	icol->dirty = 0;
    }

    if (icol->stomp) {
	SetValue(XmNset, (XtArgVal) True, GetWidget("stomp"));
	if (icol->bigmap)
	    SetColormap(True);
    }

    SetAllColors(icol->stomp && icol->bigmap, False);
    UpdateScale();
    
    XtAppMainLoop(icol->app);
    
    return 0;
}

