/* +-------------------------------------------------------------------+ */
/* | Copyright 1992, 1993, David Koblas (koblas@netcom.com)            | */
/* | Copyright 1995, 1996 Torsten Martinsen (bullestock@dk-online.dk)  | */
/* |                                                                   | */
/* | Permission to use, copy, modify, and to distribute this software  | */
/* | and its documentation for any purpose is hereby granted without   | */
/* | fee, provided that the above copyright notice appear in all       | */
/* | copies and that both that copyright notice and this permission    | */
/* | notice appear in supporting documentation.  There is no           | */
/* | representations about the suitability of this software for        | */
/* | any purpose.  this software is provided "as is" without express   | */
/* | or implied warranty.                                              | */
/* |                                                                   | */
/* +-------------------------------------------------------------------+ */

/* $Id: operation.c,v 1.21 2000/09/02 21:46:11 torsten Exp $ */

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/Form.h>
#include <X11/Xaw/SmeBSB.h>
#include <X11/Xaw/Box.h>
#include <X11/Xaw/Toggle.h>
#include <X11/Xaw/Viewport.h>
#include <X11/cursorfont.h>
#include <xpm.h>
#include <stdio.h>
#ifndef NOSTDHDRS
#include <stdlib.h>
#include <unistd.h>
#endif

#include "xpaint.h"
#include "misc.h"
#include "menu.h"
#include "messages.h"
#include "Paint.h"
#include "text.h"
#include "ops.h"
#include "graphic.h"
#include "image.h"
#include "operation.h"
#include "protocol.h"
#include "region.h"

/* Pixmaps for toolbox icons */

#include "bitmaps/tools/brushOp.xpm"
#include "bitmaps/tools/eraseOp.xpm"
#include "bitmaps/tools/sprayOp.xpm"
#include "bitmaps/tools/pencilOp.xpm"
#include "bitmaps/tools/dotPenOp.xpm"
#include "bitmaps/tools/dynPenOp.xpm"
#include "bitmaps/tools/lineOp.xpm"
#include "bitmaps/tools/arcOp.xpm"
#include "bitmaps/tools/fillOp.xpm"
#include "bitmaps/tools/tfillOp.xpm"
#ifdef FEATURE_FRACTAL
#include "bitmaps/tools/ffillOp.xpm"
#endif
#include "bitmaps/tools/smearOp.xpm"
#include "bitmaps/tools/textOp.xpm"
#include "bitmaps/tools/selectOp.xpm"
#include "bitmaps/tools/boxOp.xpm"
/* #include "bitmaps/tools/rayOp.xpm" */
#include "bitmaps/tools/fboxOp.xpm"
#include "bitmaps/tools/ovalOp.xpm"
#include "bitmaps/tools/fovalOp.xpm"
#include "bitmaps/tools/lassoOp.xpm"
#include "bitmaps/tools/clineOp.xpm"
#include "bitmaps/tools/splineOp.xpm"
#include "bitmaps/tools/fsplineOp.xpm"
#include "bitmaps/tools/polyOp.xpm"
#include "bitmaps/tools/fpolyOp.xpm"
#include "bitmaps/tools/freehandOp.xpm"
#include "bitmaps/tools/ffreehandOp.xpm"
#include "bitmaps/tools/selpolyOp.xpm"

/* static void changeLineAction(Widget, XEvent *); */
static void changeFontAction(Widget, XEvent *);
static void changeDynPencilAction(Widget, XEvent *);
static void changeSprayAction(Widget, XEvent *);
static void changeTFillAction(Widget, XEvent *);
static void changeBrushAction(Widget, XEvent *);
static void changeBrushParmAction(Widget, XEvent *);
static void changeFractalParmAction(Widget, XEvent *);

static void 
setOperation(Widget w, XtPointer opArg, XtPointer junk2)
{
    OperationPair *op = (OperationPair *) opArg;
    OperationAddProc start;
    OperationRemoveProc stop;

    if (op == CurrentOp)
	return;

    start = op ? op->add : NULL;
    stop = CurrentOp ? CurrentOp->remove : NULL;

    GraphicSetOp((OperationRemoveProc) stop, (OperationAddProc) start);

    CurrentOp = op;
    GraphicAll(setToolIconPixmap, NULL);
}

/*
** Set brush parameters callbacks
 */
static char brushOpacityStr[10] = "20";
static void 
brushOkCallback(Widget w, XtPointer junk, XtPointer infoArg)
{
    TextPromptInfo *info = (TextPromptInfo *) infoArg;
    int opacity = atof(info->prompts[0].rstr);

    if (opacity < 0 || opacity > 100) {
	Notice(w, msgText[OPACITY_SHOULD_BE_BETWEEN_ZERO_AND_HUNDRED_PERCENT]);
	return;
    }
    BrushSetParameters(opacity / 100.0);

    sprintf(brushOpacityStr, "%d", opacity);
}

static void 
brushMenuCallback(Widget w)
{
    static TextPromptInfo info;
    static struct textPromptInfo value[1];

    value[0].prompt = msgText[OPACITY];
    value[0].str = brushOpacityStr;
    value[0].len = 4;
    info.prompts = value;
    info.title = msgText[ENTER_THE_DESIRED_BRUSH_PARAMETERS];
    info.nprompt = 1;

    TextPrompt(w, "brushparams", &info, brushOkCallback, NULL, NULL);
}

/*
** Set fractal parameter callbacks
 */
char fractalDensityStr[10] = "40";
static void 
fractalOkCallback(Widget w, XtPointer junk, XtPointer infoArg)
{
    TextPromptInfo *info = (TextPromptInfo *) infoArg;
    int opacity = atof(info->prompts[0].rstr);

    if (opacity < 0 || opacity > 100) {
	Notice(w, msgText[OPACITY_SHOULD_BE_BETWEEN_ZERO_AND_HUNDRED_PERCENT]);
	return;
    }
    BrushSetParameters(opacity / 100.0);

    sprintf(fractalDensityStr, "%d", opacity);
}

static void 
fractalMenuCallback(Widget w)
{
    static TextPromptInfo info;
    static struct textPromptInfo value[1];

    value[0].prompt = msgText[DENSITY];
    value[0].str = fractalDensityStr;
    value[0].len = 4;
    info.prompts = value;
    info.title = msgText[ENTER_THE_DESIRED_FRACTAL_DENSITY];
    info.nprompt = 1;

    TextPrompt(w, "fractalparams", &info, fractalOkCallback, NULL, NULL);
}

/*
** Set dynamic pencil parameters callbacks
*/
static char dynPencilWidthStr[10] = "10";
static char dynPencilMassStr[10] = "600";
static char dynPencilDragStr[10] = "15";
static void dynPencilOkCallback(Widget w, XtPointer junk, XtPointer infoArg)
{
    void DynPencilSetParameters(float, float, float);
    TextPromptInfo *info = (TextPromptInfo*)infoArg;
    float width = atof(info->prompts[0].rstr);
    float mass = atof(info->prompts[1].rstr);
    float drag = atof(info->prompts[2].rstr);
    
    if (width < 1 || width > 100) {
	Notice(w, msgText[WIDTH_SHOULD_BE_BETWEEN_ONE_AND_ONE_HUNDRED]);
	return;
    }

    if (mass < 10 || mass > 2000) {
	Notice(w, msgText[MASS_SHOULD_BE_BETWEEN_TEN_AND_TWO_THOUSAND]);
	return;
    }

    if (drag < 0 || drag > 50) {
	Notice(w, msgText[DRAG_SHOULD_BE_BETWEEN_ZERO_AND_FIFTY]);
	return;
    }

    DynPencilSetParameters(width, mass, drag);
  
    sprintf(dynPencilWidthStr, "%0.4g", width);
    sprintf(dynPencilMassStr, "%0.4g", mass);
    sprintf(dynPencilDragStr, "%0.4g", drag);
}

static void dynPencilMenuCallback(Widget w)
{
    static TextPromptInfo info;
    static struct textPromptInfo value[3];

    value[0].prompt = msgText[DYNAMIC_WIDTH];
    value[0].str = dynPencilWidthStr;
    value[0].len = 5;
    value[1].prompt = msgText[DYNAMIC_MASS];
    value[1].str = dynPencilMassStr;;
    value[1].len = 5;
    value[2].prompt = msgText[DYNAMIC_DRAG];
    value[2].str = dynPencilDragStr;;
    value[2].len = 5;
    info.prompts = value;
    info.title = msgText[ENTER_THE_DESIRED_DYNAMIC_PENCIL_PARAMETERS];
    info.nprompt = 3;

    TextPrompt(w, "dynpencilparams", &info, dynPencilOkCallback, NULL, NULL);
}

/*
** Set spray parameters callbacks
 */
static char sprayDensityStr[10] = "10";
static char sprayRadiusStr[10] = "10";
static char sprayRateStr[10] = "10";
static void 
sprayOkCallback(Widget w, XtPointer junk, XtPointer infoArg)
{
    TextPromptInfo *info = (TextPromptInfo *) infoArg;
    int radius = atoi(info->prompts[0].rstr);
    int density = atoi(info->prompts[1].rstr);
    int rate = atoi(info->prompts[2].rstr);

    if (radius < 1 || radius > 100) {
	Notice(w, msgText[RADIUS_SHOULD_BE_BETWEEN_ONE_AND_ONE_HUNDRED]);
	return;
    }
    if (density < 1 || density > 500) {
	Notice(w, msgText[DENSITY_SHOULD_BE_BETWEEN_ONE_AND_FIVE_HUNDRED]);
	return;
    }
    if (rate < 1 || rate > 500) {
	Notice(w, msgText[RATE_SHOULD_BE_BETWEEN_ONE_AND_FIVE_HUNDRED]);
	return;
    }
    SpraySetParameters(radius, density, rate);

    sprintf(sprayDensityStr, "%d", density);
    sprintf(sprayRadiusStr, "%d", radius);
    sprintf(sprayRateStr, "%d", rate);
}

static void 
sprayMenuCallback(Widget w)
{
    static TextPromptInfo info;
    static struct textPromptInfo value[3];

    value[0].prompt = msgText[SPRAY_RADIUS];
    value[0].str = sprayRadiusStr;
    value[0].len = 4;
    value[1].prompt = msgText[SPRAY_DENSITY];
    value[1].str = sprayDensityStr;
    value[1].len = 4;
    value[2].prompt = msgText[SPRAY_RATE];
    value[2].str = sprayRateStr;
    value[2].len = 4;
    info.prompts = value;
    info.title = msgText[ENTER_THE_DESIRED_SPRAY_PARAMETERS];
    info.nprompt = 3;

    TextPrompt(w, "sprayparams", &info, sprayOkCallback, NULL, NULL);
}

/*
** Gradient fill set parameters callbacks
 */
static char tfillAngleStr[10] = "0";
static char tfillPadStr[10] = "0";
static char tfillHOffsetStr[10] = "0";
static char tfillVOffsetStr[10] = "0";
static char tfillStepsStr[10] = "";

static void 
tfillOkCallback(Widget w, XtPointer junk, XtPointer infoArg)
{
    TextPromptInfo *info = (TextPromptInfo *) infoArg;
    int HOffset, VOffset, Pad, Angle, Steps;


    Angle = atoi(info->prompts[0].rstr);
    Pad = atoi(info->prompts[1].rstr);
    HOffset = atoi(info->prompts[2].rstr);
    VOffset = atoi(info->prompts[3].rstr);
    Steps = atoi(info->prompts[4].rstr);
    if (HOffset < -100 || HOffset > 100) {
	Notice(w, msgText[
            HORIZONTAL_OFFSET_SHOULD_BE_BETWEEN_PLUS_MINUS_HUNDRED_PERCENT]);
	return;
    }
    if (VOffset < -100 || VOffset > 100) {
	Notice(w, msgText[
            VERTICAL_OFFSET_SHOULD_BE_BETWEEN_PLUS_MINUS_HUNDRED_PERCENT]);
	return;
    }
    if (Pad < -49 || Pad > 49) {
	Notice(w, msgText[
            PAD_SHOULD_BE_BETWEEN_PLUS_MINUS_FOURTY_NINE_PERCENT]);
	return;
    }
    if (Angle < -360 || Angle > 360) {
	Notice(w, msgText[
            ANGLE_SHOULD_BE_BETWEEN_PLUS_MINUS_THREE_HUNDRED_SIXTY_DEGREES]);
	return;
    }
    if (Steps < 1 || Steps > 300) {
	Notice(w, msgText[
            NUMBER_OF_STEPS_SHOULD_BE_BETWEEN_ONE_AND_THREE_HUNDRED]);
	return;
    }
    TfillSetParameters(VOffset/100.0, HOffset/100.0, Pad/100.0, Angle, Steps);

    sprintf(tfillVOffsetStr, "%3d", VOffset);
    sprintf(tfillHOffsetStr, "%3d", HOffset);
    sprintf(tfillPadStr, "%3d", Pad);
    sprintf(tfillAngleStr, "%3d", Angle);
    sprintf(tfillStepsStr, "%3d", Steps);
}

static void 
tfillMenuCallback(Widget w)
{
    static TextPromptInfo info;
    static struct textPromptInfo value[5];

    if (!*tfillStepsStr) {
	 if (DefaultDepthOfScreen(XtScreen(GetShell(w))) <= 8)
	      strcpy(tfillStepsStr, "25");
	 else
	      strcpy(tfillStepsStr, "300");
    }

    value[0].prompt = msgText[GRADIENT_ANGLE];
    value[0].str = tfillAngleStr;
    value[0].len = 4;
    value[1].prompt = msgText[GRADIENT_PAD];
    value[1].str = tfillPadStr;
    value[1].len = 4;
    value[2].prompt = msgText[GRADIENT_HORIZONTAL_OFFSET];
    value[2].str = tfillHOffsetStr;
    value[2].len = 4;
    value[3].prompt = msgText[GRADIENT_VERTICAL_OFFSET];
    value[3].str = tfillVOffsetStr;
    value[3].len = 4;
    value[4].prompt = msgText[GRADIENT_STEPS];
    value[4].str = tfillStepsStr;
    value[4].len = 3;
    info.prompts = value;
    info.title = msgText[ENTER_THE_DESIRED_GRADIENT_FILL_PARAMETERS];
    info.nprompt = 5;

    TextPrompt(w, "tfillparams", &info, tfillOkCallback, NULL, NULL);
}

/*
**  Exit callback (simple)
 */
static void 
exitOkCallback(Widget w, XtPointer junk, XtPointer junk2)
{
#if 0
    XtDestroyWidget(GetToplevel(w));
    XtDestroyApplicationContext(XtWidgetToApplicationContext(w));
    Global.timeToDie = True;
#else
    exit(0);
#endif
}

static void 
exitCancelCallback(Widget paint, XtPointer junk, XtPointer junk2)
{
}

static void 
exitPaintCheck(Widget paint, void *sumArg)
{
    int *sum = (int *) sumArg;
    Boolean flg;
    XtVaGetValues(paint, XtNdirty, &flg, NULL);
    *sum += flg ? 1 : 0;
}

void 
exitPaint(Widget w, XtPointer junk, XtPointer junk2)
{
    int total = 0;

    GraphicAll(exitPaintCheck, (void *) &total);

    if (total == 0) {
	exitOkCallback(w, NULL, NULL);
	return;
    }
    AlertBox(w, msgText[UNSAVED_CHANGES_WISH_TO_QUIT],
	     exitOkCallback, exitCancelCallback, NULL);
}

/*
**
 */
void 
takeSnapshot(Widget w, XtPointer junk, XtPointer junk2)
{
    SnapshotImage(GetToplevel(w), junk, 0);
    /* Ugly work-around to refresh buttons on some X displays ... */
    XtPopup(XtParent(w), XtGrabExclusive);
    XtPopdown(XtParent(w));
}

/*
**  Button popups 
 */
void brushPopupCB(Widget w, XtPointer junk, XtPointer junk2);
static void erasePopupCB(Widget w, XtPointer junk, XtPointer junk2);
static void arcPopupCB(Widget w, XtPointer junk, XtPointer junk2);
static void fillPopupCB(Widget w, XtPointer junk, XtPointer junk2);
static void tfillPopupCB2(Widget w, XtPointer junk, XtPointer junk2);
#ifdef FEATURE_FRACTAL
static void ffillPopupCB2(Widget w, XtPointer junk, XtPointer junk2);
#endif
static void selectPopupCB(Widget w, XtPointer junk, XtPointer junk2);
static void dynPencilPopupCB(Widget w, XtPointer junk, XtPointer junk2);
static void selectShapePopupCB(Widget w, XtPointer junk, XtPointer junk2);
static void sprayPopupCB(Widget w, XtPointer junk, XtPointer junk2);
static void linePopupCB(Widget w, XtPointer junk, XtPointer junk2);
static void ovalPopupCB(Widget w, XtPointer junk, XtPointer junk2);
static void splinePopupCB(Widget w, XtPointer junk, XtPointer junk2);
static void boxPopupCB(Widget w, XtPointer junk, XtPointer junk2);

#define GENERATE_HELP(name, hlpstr)				\
		static PaintMenuItem name [] = {		\
			MI_SEPARATOR(),				\
			MI_SIMPLECB("help", HelpDialog, hlpstr)	\
		};

static PaintMenuItem brushPopup[] =
{
    MI_SEPARATOR(),
    MI_FLAGCB("opaque", MF_CHECKON | MF_GROUP1, brushPopupCB, 0),
    MI_FLAGCB("transparent", MF_CHECK | MF_GROUP1, brushPopupCB, 1),
    MI_SIMPLECB("select", BrushSelect, NULL),
    MI_SIMPLECB("parms", changeBrushParmAction, NULL),
    MI_SEPARATOR(),
    MI_SIMPLECB("help", HelpDialog, "toolbox.tools.brush"),
};
static PaintMenuItem erasePopup[] =
{
    MI_SEPARATOR(),
    MI_FLAGCB("original", MF_CHECKON, erasePopupCB, 0),
    MI_SIMPLECB("select", changeBrushAction, NULL),
    MI_SEPARATOR(),
    MI_SIMPLECB("help", HelpDialog, "toolbox.tools.erase"),
};

static PaintMenuItem dynPencilPopup[] = 
{
    MI_SEPARATOR(),
    MI_FLAGCB("autofinish", MF_CHECK, dynPencilPopupCB, 0),
    MI_SIMPLECB("select", changeDynPencilAction, NULL),
    MI_SEPARATOR(),
    MI_SIMPLECB("help", HelpDialog, "toolbox.tools.dynpencil"),
};

static PaintMenuItem sprayPopup[] =
{
    MI_SEPARATOR(),
    MI_FLAGCB("gauss", MF_CHECKON, sprayPopupCB, 0),
    MI_SIMPLECB("select", changeSprayAction, NULL),
    MI_SEPARATOR(),
    MI_SIMPLECB("help", HelpDialog, "toolbox.tools.spray"),
};

static PaintMenuItem smearPopup[] =
{
    MI_SEPARATOR(),
    MI_SIMPLECB("select", BrushSelect, NULL),
    MI_SIMPLECB("help", HelpDialog, "toolbox.tools.smear"),
};

static PaintMenuItem linePopup[] =
{
    MI_SEPARATOR(),
    MI_FLAGCB("ray", MF_CHECK, linePopupCB, 0),
    MI_FLAGCB("arrow", MF_CHECK, linePopupCB, 1),
    MI_SEPARATOR(),
    MI_SIMPLECB("help", HelpDialog, "toolbox.tools.line"),
};

GENERATE_HELP(pencilPopup, "toolbox.tools.pencil")
GENERATE_HELP(dotPencilPopup, "toolbox.tools.dotPencil")

static PaintMenuItem arcPopup[] =
{
    MI_SEPARATOR(),
    MI_FLAGCB("connect", MF_CHECKON | MF_GROUP1, arcPopupCB, 0),
    MI_FLAGCB("quadrant", MF_CHECK | MF_GROUP1, arcPopupCB, 1),
    MI_FLAGCB("centered", MF_CHECK | MF_GROUP1, arcPopupCB, 2),
    MI_FLAGCB("boxed", MF_CHECK | MF_GROUP1, arcPopupCB, 3),
    MI_SEPARATOR(),
    MI_SIMPLECB("help", HelpDialog, "toolbox.tools.arc"),
};
static PaintMenuItem fillPopup[] =
{
    MI_SEPARATOR(),
    MI_FLAGCB("fill", MF_CHECKON | MF_GROUP1, fillPopupCB, 0),
    MI_FLAGCB("change", MF_CHECK | MF_GROUP1, fillPopupCB, 1),
    MI_FLAGCB("fill_range", MF_CHECK | MF_GROUP1, fillPopupCB, 2),
    MI_SEPARATOR(),
    MI_SIMPLECB("help", HelpDialog, "toolbox.tools.fill"),
};
static PaintMenuItem tfillPopup[] =
{
    MI_SEPARATOR(),
    MI_FLAGCB("fill", MF_CHECKON | MF_GROUP1, fillPopupCB, 0),
    MI_FLAGCB("change", MF_CHECK | MF_GROUP1, fillPopupCB, 1),
    MI_FLAGCB("fill_range", MF_CHECK | MF_GROUP1, fillPopupCB, 2),
    MI_SEPARATOR(),
    MI_FLAGCB("radial", MF_CHECKON | MF_GROUP2, tfillPopupCB2, TFILL_RADIAL),
    MI_FLAGCB("linear", MF_CHECK | MF_GROUP2, tfillPopupCB2, TFILL_LINEAR),
    MI_FLAGCB("conical", MF_CHECK | MF_GROUP2, tfillPopupCB2, TFILL_CONE),
    MI_FLAGCB("square", MF_CHECK | MF_GROUP2, tfillPopupCB2, TFILL_SQUARE),
    MI_SIMPLECB("parms", changeTFillAction, NULL),
    MI_SEPARATOR(),
    MI_SIMPLECB("help", HelpDialog, "toolbox.tools.tfill"),
};
#ifdef FEATURE_FRACTAL
static PaintMenuItem ffillPopup[] =
{
    MI_SEPARATOR(),
    MI_FLAGCB("fill", MF_CHECKON | MF_GROUP1, fillPopupCB, 0),
    MI_FLAGCB("change", MF_CHECK | MF_GROUP1, fillPopupCB, 1),
    MI_FLAGCB("fill_range", MF_CHECK | MF_GROUP1, fillPopupCB, 2),
    MI_SEPARATOR(),
    MI_FLAGCB("plasma", MF_CHECKON | MF_GROUP2, ffillPopupCB2, 0),
    MI_FLAGCB("clouds", MF_CHECK | MF_GROUP2, ffillPopupCB2, 1),
    MI_FLAGCB("landscape", MF_CHECK | MF_GROUP2, ffillPopupCB2, 2),
    MI_SIMPLECB("parms", changeFractalParmAction, NULL),
    MI_SEPARATOR(),
    MI_SIMPLECB("help", HelpDialog, "toolbox.tools.ffill"),
};
#endif
static PaintMenuItem textPopup[] =
{
    MI_SEPARATOR(),
    MI_SIMPLECB("select", changeFontAction, NULL),
    MI_SIMPLECB("help", HelpDialog, "toolbox.tools.text"),
};
GENERATE_HELP(polyPopup, "toolbox.tools.poly")
GENERATE_HELP(freehandPopup, "toolbox.tools.blob")
GENERATE_HELP(clinePopup, "toolbox.tools.cline")
static PaintMenuItem selectBPopup[] =
{
    MI_SEPARATOR(),
    MI_FLAGCB("shape", MF_CHECKON | MF_GROUP1, selectPopupCB, 0),
    MI_FLAGCB("not_color", MF_CHECK | MF_GROUP1, selectPopupCB, 1),
    MI_FLAGCB("only_color", MF_CHECK | MF_GROUP1, selectPopupCB, 2),
    MI_SEPARATOR(),
    MI_FLAGCB("rectangle", MF_CHECKON | MF_GROUP2, selectShapePopupCB, 0),
    MI_FLAGCB("ellipse", MF_CHECK | MF_GROUP2, selectShapePopupCB, 1),
    MI_SEPARATOR(),
    MI_SIMPLECB("help", HelpDialog, "toolbox.tools.select"),
};
static PaintMenuItem lassoPopup[] =
{
    MI_SEPARATOR(),
    MI_FLAGCB("shape", MF_CHECKON | MF_GROUP1, selectPopupCB, 0),
    MI_FLAGCB("not_color", MF_CHECK | MF_GROUP1, selectPopupCB, 1),
    MI_FLAGCB("only_color", MF_CHECK | MF_GROUP1, selectPopupCB, 2),
    MI_SEPARATOR(),
    MI_SIMPLECB("help", HelpDialog, "toolbox.tools.select"),
};
static PaintMenuItem selectAPopup[] =
{
    MI_SEPARATOR(),
    MI_FLAGCB("shape", MF_CHECKON | MF_GROUP1, selectPopupCB, 0),
    MI_FLAGCB("not_color", MF_CHECK | MF_GROUP1, selectPopupCB, 1),
    MI_FLAGCB("only_color", MF_CHECK | MF_GROUP1, selectPopupCB, 2),
    MI_SEPARATOR(),
    MI_SIMPLECB("help", HelpDialog, "toolbox.tools.select"),
};
static PaintMenuItem boxPopup[] =
{
    MI_SEPARATOR(),
    MI_FLAGCB("center", MF_CHECK, boxPopupCB, 0),
    MI_SIMPLECB("help", HelpDialog, "toolbox.tools.box"),
};
static PaintMenuItem fboxPopup[] =
{
    MI_SEPARATOR(),
    MI_FLAGCB("center", MF_CHECK, boxPopupCB, 0),
    MI_SIMPLECB("help", HelpDialog, "toolbox.tools.box"),
};
static PaintMenuItem ovalPopup[] =
{
    MI_SEPARATOR(),
    MI_FLAGCB("center", MF_CHECK, ovalPopupCB, 0),
    MI_SIMPLECB("help", HelpDialog, "toolbox.tools.oval"),
};
static PaintMenuItem fovalPopup[] =
{
    MI_SEPARATOR(),
    MI_FLAGCB("center", MF_CHECK, ovalPopupCB, 0),
    MI_SIMPLECB("help", HelpDialog, "toolbox.tools.oval"),
};
static PaintMenuItem splinePopup[] =
{
    MI_SEPARATOR(),
    MI_FLAGCB("open", MF_CHECKON | MF_GROUP1, splinePopupCB, 0),
    MI_FLAGCB("closed", MF_CHECK | MF_GROUP1, splinePopupCB, 1),
    MI_FLAGCB("closed_up", MF_CHECK | MF_GROUP1, splinePopupCB, 2),
    MI_SEPARATOR(),
    MI_SIMPLECB("help", HelpDialog, "toolbox.tools.spline"),
};
static PaintMenuItem fsplinePopup[] =
{
    MI_SEPARATOR(),
    MI_FLAGCB("open", MF_CHECKON | MF_GROUP1, splinePopupCB, 0),
    MI_FLAGCB("closed", MF_CHECK | MF_GROUP1, splinePopupCB, 1),
    MI_FLAGCB("closed_up", MF_CHECK | MF_GROUP1, splinePopupCB, 2),
    MI_SEPARATOR(),
    MI_SIMPLECB("help", HelpDialog, "toolbox.tools.spline"),
};

void 
brushPopupCB(Widget w, XtPointer junk, XtPointer junk2)
{
    int nm = (int) junk;

    BrushSetMode(nm);
    MenuCheckItem(brushPopup[nm + 1].widget, True);
}

static void 
ovalPopupCB(Widget w, XtPointer junk, XtPointer junk2)
{
    Boolean m = !CircleGetStyle();

    CircleSetStyle(m);
    MenuCheckItem(ovalPopup[1].widget, m);
    MenuCheckItem(fovalPopup[1].widget, m);
}

static void 
splinePopupCB(Widget w, XtPointer junk, XtPointer junk2)
{
    int nm = (int) junk;

    MenuCheckItem(splinePopup[nm + 1].widget, True);
    MenuCheckItem(fsplinePopup[nm + 1].widget, True);
    SplineSetMode(nm);
}

static void 
boxPopupCB(Widget w, XtPointer junk, XtPointer junk2)
{
    Boolean m = !BoxGetStyle();

    BoxSetStyle(m);
    MenuCheckItem(boxPopup[1].widget, m);
    MenuCheckItem(fboxPopup[1].widget, m);
}

static void 
arcPopupCB(Widget w, XtPointer junk, XtPointer junk2)
{
    int nm = (int) junk;

    ArcSetMode(nm);
    MenuCheckItem(w, True);
}

static void 
fillPopupCB(Widget w, XtPointer junk, XtPointer junk2)
{
    int nm = (int) junk;

    FillSetMode(nm);
    MenuCheckItem(w, True);
}
static void 
tfillPopupCB2(Widget w, XtPointer junk, XtPointer junk2)
{
    int nm = (int) junk;

    TFillSetMode(nm);
    MenuCheckItem(w, True);
}

#ifdef FEATURE_FRACTAL
static void 
ffillPopupCB2(Widget w, XtPointer junk, XtPointer junk2)
{
    int nm = (int) junk;

    FFillSetMode(nm);
    MenuCheckItem(w, True);
}

#endif
static void 
selectPopupCB(Widget w, XtPointer junk, XtPointer junk2)
{
    int n = (int) junk;

    MenuCheckItem(selectAPopup[n + 1].widget, True);
    MenuCheckItem(selectBPopup[n + 1].widget, True);
    MenuCheckItem(lassoPopup[n + 1].widget, True);
    SelectSetCutMode(n);
}
static void 
selectShapePopupCB(Widget w, XtPointer junk, XtPointer junk2)
{
    int n = (int) junk;

    MenuCheckItem(selectBPopup[n + 1 + 4].widget, True);
    SelectSetShapeMode(n);
}

void 
OperationSelectCallAcross(int n)
{
    MenuCheckItem(selectAPopup[n + 1].widget, True);
    MenuCheckItem(selectBPopup[n + 1].widget, True);
    MenuCheckItem(lassoPopup[n + 1].widget, True);
}
static void
dynPencilPopupCB(Widget w, XtPointer junk, XtPointer junk2)
{
    Boolean	m = !DynPencilGetFinish();

    DynPencilSetFinish(m);
    MenuCheckItem(w, m);
}
static void 
sprayPopupCB(Widget w, XtPointer junk, XtPointer junk2)
{
    Boolean m = !SprayGetStyle();

    SpraySetStyle(m);
    MenuCheckItem(w, m);
}
static void 
linePopupCB(Widget w, XtPointer junk, XtPointer junk2)
{
    int nm = (int) junk;
    Boolean m;

    if (nm == 0) {
         m = !RayGetStyle();
         RaySetStyle(m);
         MenuCheckItem(linePopup[1].widget, m);
    }
    if (nm == 1) {
         m = !ArrowGetStyle();
         ArrowSetStyle(m);
         MenuCheckItem(linePopup[2].widget, m);
    }
}
static void 
erasePopupCB(Widget w, XtPointer junk, XtPointer junk2)
{
    Boolean m = !EraseGetMode();

    EraseSetMode(m);
    MenuCheckItem(w, m);
}

/*
**  Done with operation popup menus
 */

#define GENERATE_OP(name)				\
	static OperationPair  CONCAT(name,Op) = {	\
	  CONCAT(name,Add), CONCAT(name,Remove)		\
	};

GENERATE_OP(DotPencil)
GENERATE_OP(Pencil)
GENERATE_OP(DynPencil)
GENERATE_OP(Box)
GENERATE_OP(FBox)
GENERATE_OP(Line)
GENERATE_OP(Arc)
GENERATE_OP(Oval)
GENERATE_OP(FOval)
GENERATE_OP(Erase)
GENERATE_OP(Brush)
GENERATE_OP(Font)
GENERATE_OP(Smear)
GENERATE_OP(Spray)
GENERATE_OP(Poly)
GENERATE_OP(FPoly)
GENERATE_OP(Freehand)
GENERATE_OP(FFreehand)
GENERATE_OP(CLine)
GENERATE_OP(Fill)
GENERATE_OP(TFill)
GENERATE_OP(Spline)
GENERATE_OP(FSpline)
#ifdef FEATURE_FRACTAL
GENERATE_OP(FFill)
#endif

GENERATE_OP(SelectBox)
GENERATE_OP(Lasso)
GENERATE_OP(SelectPoly)
#define	XPMMAP(name)	(char **) CONCAT(name,Op_xpm)
#define MENU(name)	XtNumber(CONCAT(name,Popup)), CONCAT(name,Popup)

typedef struct {
    char *name;
    char **xpmmap;
    OperationPair *data;
    char *translations;
    int nitems;
    PaintMenuItem *popupMenu;
    Pixmap icon;
} IconListItem;

static Widget iconListWidget;
static IconListItem iconList[] =
{
    /* Vertical arrangement */

    {"pencil", XPMMAP(pencil), &PencilOp,
     NULL, MENU(pencil), None},
    {"dynpencil", XPMMAP(dynPen), &DynPencilOp, 	
     "<BtnDown>(2): changeDynPencil()", MENU(dynPencil), None},
    {"dotPencil", XPMMAP(dotPen), &DotPencilOp,
     NULL, MENU(dotPencil), None},

    {"brush", XPMMAP(brush), &BrushOp,
     "<BtnDown>(2): changeBrush()", MENU(brush), None},
    {"spray", XPMMAP(spray), &SprayOp,
     "<BtnDown>(2): changeSpray()", MENU(spray), None},
    {"smear", XPMMAP(smear), &SmearOp,
     "<BtnDown>(2): changeBrush()", MENU(smear), None},

    {"box", XPMMAP(box), &BoxOp,
     NULL, MENU(box), None},
    {"fBox", XPMMAP(fbox), &FBoxOp,
     NULL, MENU(fbox), None},
    {"line", XPMMAP(line), &LineOp,
     NULL, MENU(line), None},

    {"oval", XPMMAP(oval), &OvalOp,
     NULL, MENU(oval), None},
    {"fOval", XPMMAP(foval), &FOvalOp,
     NULL, MENU(foval), None},
    {"arc", XPMMAP(arc), &ArcOp,
     NULL, MENU(arc), None},

    {"polygon", XPMMAP(poly), &PolyOp,
     NULL, MENU(poly), None},
    {"fPolygon", XPMMAP(fpoly), &FPolyOp,
     NULL, MENU(poly), None},
    {"cLine", XPMMAP(cline), &CLineOp,
     NULL, MENU(cline), None},

    {"Spline", XPMMAP(spline), &SplineOp,
     NULL, MENU(spline), None},
    {"fSpline", XPMMAP(fspline), &FSplineOp,
     NULL, MENU(fspline), None},
    {"text", XPMMAP(text), &FontOp,
     "<BtnDown>(2): changeFont()", MENU(text), None},

    {"freehand", XPMMAP(freehand), &FreehandOp,
     NULL, MENU(freehand), None},
    {"fFreehand", XPMMAP(ffreehand), &FFreehandOp,
     NULL, MENU(freehand), None},
    {"erase", XPMMAP(erase), &EraseOp,
     "<BtnDown>(2): changeBrush()", MENU(erase), None},

    {"fill", XPMMAP(fill), &FillOp,
     NULL, MENU(fill), None},
    {"tfill", XPMMAP(tfill), &TFillOp,
     "<BtnDown>(2): changeTFillParms()", MENU(tfill), None},
#ifdef FEATURE_FRACTAL
    {"ffill", XPMMAP(ffill), &FFillOp,
     NULL, MENU(ffill), None},
#endif

    {"selectBox", XPMMAP(select), &SelectBoxOp,
     NULL, MENU(selectB), None},
    {"lasso", XPMMAP(lasso), &LassoOp,
     NULL, MENU(lasso), None},
    {"selectPoly", XPMMAP(selpoly), &SelectPolyOp,
     NULL, MENU(selectA), None},

    /* Vertical arrangement */
};

static int 
getIndexOp()
{
    int i;
    for (i=0; i<XtNumber(iconList); i++)
         if (CurrentOp == iconList[i].data) return i;
    return 0;
}

static void
permuteIcons()
{
#define NUM XtNumber(iconList)
    int i;
    IconListItem tempList[NUM];

    if (ToolsAreHorizontal()) {
    for (i=0; i<NUM; i++)
        tempList[i] = iconList[3*(i%9)+(i/9)];
    for (i=0; i<NUM; i++)
        iconList[i] = tempList[i];
    }
}

void 
OperationSet(String names[], int num)
{
    IconListItem *match = NULL;
    int i, j;

    for (i = 0; i < XtNumber(iconList); i++) {
	for (j = 0; j < num; j++) {
	    if (strcmp(names[j], iconList[i].name) == 0) {
		if (match == NULL)
		    match = &iconList[i];
		if (CurrentOp == iconList[i].data)
		    return;
	    }
	}
    }
    if (match != NULL)
	XawToggleSetCurrent(iconListWidget, (XtPointer) match->name);
}

#if 0
static PaintMenuItem lineMenu[] =
{
#if 0
    MI_FLAGCB("0", MF_CHECK | MF_GROUP1, lineWidth, NULL),
#endif
    MI_FLAGCB("1", MF_CHECK | MF_GROUP1, lineWidth, NULL),
    MI_FLAGCB("2", MF_CHECK | MF_GROUP1, lineWidth, NULL),
    MI_FLAGCB("4", MF_CHECK | MF_GROUP1, lineWidth, NULL),
    MI_FLAGCB("6", MF_CHECK | MF_GROUP1, lineWidth, NULL),
    MI_FLAGCB("8", MF_CHECK | MF_GROUP1, lineWidth, NULL),
#define LW_SELECT	5
    MI_FLAGCB("select", MF_CHECK | MF_GROUP1, lineWidth, NULL),
};

static PaintMenuItem fontMenu[] =
{
    MI_FLAGCB("Times 8", MF_CHECK | MF_GROUP1,
	      fontSet, "-*-times-medium-r-normal-*-*-80-*-*-p-*-*-*"),
    MI_FLAGCB("Times 12", MF_CHECK | MF_GROUP1,
	      fontSet, "-*-times-medium-r-normal-*-*-120-*-*-p-*-*-*"),
    MI_FLAGCB("Times 18", MF_CHECK | MF_GROUP1,
	      fontSet, "-*-times-medium-r-normal-*-*-180-*-*-p-*-*-*"),
    MI_FLAGCB("Times Bold 12", MF_CHECK | MF_GROUP1,
	      fontSet, "-*-times-bold-r-normal-*-*-120-*-*-p-*-*-*"),
    MI_FLAGCB("Times Italic 12", MF_CHECK | MF_GROUP1,
	      fontSet, "-*-times-bold-i-normal-*-*-120-*-*-p-*-*-*"),
    MI_FLAGCB("Lucida 12", MF_CHECK | MF_GROUP1,
	      fontSet, "-*-lucida-medium-r-normal-*-*-120-*-*-p-*-*-*"),
    MI_FLAGCB("Helvetica 12", MF_CHECK | MF_GROUP1,
	    fontSet, "-*-helvetica-medium-r-normal-*-*-120-*-*-p-*-*-*"),
    MI_FLAGCB("Helvetica Bold 12", MF_CHECK | MF_GROUP1,
	      fontSet, "-*-helvetica-bold-r-normal-*-*-120-*-*-p-*-*-*"),
    MI_FLAGCB("Fixed 12", MF_CHECK | MF_GROUP1,
	      fontSet, "-*-fixed-medium-r-normal-*-*-120-*-*-m-*-*-*"),
    MI_FLAGCB("Courier 12", MF_CHECK | MF_GROUP1,
	      fontSet, "-*-courier-medium-r-normal-*-*-120-*-*-m-*-*-*"),
    MI_SEPARATOR(),
#define FM_SELECT	11
    MI_FLAGCB("select", MF_CHECK | MF_GROUP1, fontSet, NULL),
};
#endif

#if 0
static PaintMenuItem otherMenu[] =
{
    MI_SIMPLECB("brushSelect", BrushSelect, NULL),
#define SP_SELECT	1
    MI_SIMPLECB("sprayEdit", sprayMenuCallback, NULL),
};
#endif

static PaintMenuItem canvasMenu[] =
{
    MI_SIMPLECB("new", GraphicCreate, 0),
    MI_SIMPLECB("new-size", GraphicCreate, 2),
    MI_SIMPLECB("open", GraphicCreate, 1),
    MI_SIMPLECB("snapshot", takeSnapshot, NULL),
    MI_SEPARATOR(),
    MI_SIMPLECB("quit", exitPaint, NULL),
};

static PaintMenuItem helpMenu[] =
{
    MI_SIMPLECB("intro", HelpDialog, "introduction"),
    MI_SIMPLECB("tools", HelpDialog, "toolbox.tools"),
    MI_SIMPLECB("canvas", HelpDialog, "canvas"),
    MI_SEPARATOR(),
    MI_SIMPLECB("about", HelpDialog, "about"),
    MI_SIMPLECB("copyright", HelpDialog, "copyright"),
};

static PaintMenuBar menuBar[] =
{
    {None, "canvas", XtNumber(canvasMenu), canvasMenu},
#if 0
    {None, "other", XtNumber(otherMenu), otherMenu},
    {None, "line", XtNumber(lineMenu), lineMenu},
    {None, "font", XtNumber(fontMenu), fontMenu},
#endif
    {None, "help", XtNumber(helpMenu), helpMenu},
};

/*
**  Now for the callback functions
 */
static int argListLen = 0;
static Arg *argList;

void 
OperationSetPaint(Widget paint)
{
    XtSetValues(paint, argList, argListLen);
}

void 
OperationAddArg(Arg arg)
{
    int i;

    for (i = 0; i < argListLen; i++) {
	if (strcmp(argList[i].name, arg.name) == 0) {
	    argList[i].value = arg.value;
	    return;
	}
    }

    if (argListLen == 0)
	argList = (Arg *) XtMalloc(sizeof(Arg) * 2);
    else
	argList = (Arg *) XtRealloc((XtPointer) argList,
				    sizeof(Arg) * (argListLen + 2));

    argList[argListLen++] = arg;
}

/*
**  Double click action callback functions.
 */
void 
changeFontAction(Widget w, XEvent * event)
{
    FontSelect(w, None);
}

static void
changeDynPencilAction(Widget w, XEvent *event)
{
    dynPencilMenuCallback(w);
}
static void 
changeSprayAction(Widget w, XEvent * event)
{
    sprayMenuCallback(w);
}
static void 
changeTFillAction(Widget w, XEvent * event)
{
    tfillMenuCallback(w);
}
static void 
changeBrushAction(Widget w, XEvent * event)
{
    BrushSelect(w);
}
static void 
changeBrushParmAction(Widget w, XEvent * event)
{
    brushMenuCallback(w);
}
static void 
changeFractalParmAction(Widget w, XEvent * event)
{
    fractalMenuCallback(w);
}

static void 
switchtoCanvasCallback(Widget w, XtPointer junk, XEvent * event, Boolean * flg)
{
    if (Global.canvas && event->type == ButtonRelease)
       XMapRaised(XtDisplay(Global.canvas), XtWindow(Global.canvas));
}

/*
**  The real init function
 */
void 
OperationInit(Widget toplevel)
{
    static XtActionsRec acts[] =
    {
      /*	{"changeLine", (XtActionProc) changeLineAction}, */
	{"changeBrush", (XtActionProc) changeBrushAction},
	{"changeFont", (XtActionProc) changeFontAction},
	{"changeDynPencil", (XtActionProc) changeDynPencilAction},
	{"changeSpray", (XtActionProc) changeSprayAction},
	{"changeTFillParms", (XtActionProc) changeTFillAction},
    };
    int i;
    Pixmap pix;
    Widget form, vport, box, icon, bar, firstIcon = None;
    char *defTrans = "<BtnDown>,<BtnUp>: set() notify()\n";
    XtTranslations trans = XtParseTranslationTable(defTrans);
    IconListItem *cur;
    XpmAttributes attributes;

    form = XtVaCreateManagedWidget("toolbox",
				   formWidgetClass, toplevel,
				   XtNborderWidth, 0,
				   NULL);
    XtAppAddActions(XtWidgetToApplicationContext(toplevel),
		    acts, XtNumber(acts));

    /*
    **  Create the menu bar
     */
    bar = MenuBarCreate(form, XtNumber(menuBar), menuBar);
    XtVaSetValues(bar, XtNhorizDistance, 0, NULL);
    
    /*
    **  Create the operation icon list
     */
    vport = XtVaCreateManagedWidget("vport",
				    viewportWidgetClass, form,
#ifdef XAW95
				    XtNwidth, 171, XtNheight, 460,
#else
				    XtNwidth, 165, XtNheight, 454,
#endif
				    XtNallowVert, True,
				    XtNuseRight, True,
				    XtNfromVert, bar,
				    NULL);
    box = XtVaCreateManagedWidget("box",
				  boxWidgetClass, vport,
				  XtNtop, XtChainTop,
				  NULL);

    Global.back = XtVaCreateManagedWidget("!", labelWidgetClass, form,
                                      XtNfromHoriz, vport,
				      XtNright, XtChainRight,
				      XtNwidth, 12,
				      XtNvertDistance, 8,
				      XtNhorizDistance, -16,
                                      XtNborderWidth, 1,
                                      XtNsensitive, False,
                                      NULL);

    XtAddEventHandler(Global.back, ButtonPressMask | ButtonReleaseMask, False,
        (XtEventHandler) switchtoCanvasCallback, NULL);

    permuteIcons();

    attributes.valuemask = XpmCloseness;
    attributes.closeness =  40000;

    for (i = 0; i < XtNumber(iconList); i++) {
	cur = &iconList[i];

	icon = XtVaCreateManagedWidget(cur->name,
				       toggleWidgetClass, box,
				       XtNtranslations, trans,
				       XtNradioGroup, firstIcon,
				       XtNradioData, cur->name,
				       NULL);

	if (cur->xpmmap != NULL) {
	    XpmCreatePixmapFromData(XtDisplay(box),
				    DefaultRootWindow(XtDisplay(box)),
				    cur->xpmmap,
				    &pix, NULL, &attributes);
	} else {
	    pix = None;
	}

	if (pix == None) {
	    fprintf(stderr, msgText[UNABLE_TO_ALLOCATE_SUFFICIENT_COLOR_ENTRIES]);
	    fprintf(stderr, msgText[TRY_EXITING_OTHER_COLOR_INTENSIVE_APPLICATIONS]);
	    exit(1);
	}
	
	cur->icon = pix;

	if (pix != None)
	    XtVaSetValues(icon, XtNbitmap, pix, NULL);
	if (cur->translations != NULL) {
	    XtTranslations moreTrans;

	    moreTrans = XtParseTranslationTable(cur->translations);
	    XtAugmentTranslations(icon, moreTrans);
	}
	if (firstIcon == NULL) {
	    XtVaSetValues(icon, XtNstate, True, NULL);
	    setOperation(icon, cur->data, NULL);

	    firstIcon = icon;
	    iconListWidget = icon;
	}
	XtAddCallback(icon, XtNcallback,
		      setOperation, cur->data);

	if (cur->nitems != 0 && cur->popupMenu != NULL)
	    MenuPopupCreate(icon, "popup-menu", cur->nitems, cur->popupMenu);
    }

    if (ToolsAreHorizontal())
        XtVaSetValues(vport,
#ifdef XAW95
#ifdef BIGTOOLICONS
	          XtNwidth, 496, XtNheight, 160,
#else
	          XtNwidth, 351, XtNheight, 111,
#endif
#else
#ifdef BIGTOOLICONS
		  XtNwidth, 490, XtNheight, 154,
#else
		  XtNwidth, 346, XtNheight, 106,
#endif
#endif
	         NULL);   
    else
        XtVaSetValues(vport,
#ifdef XAW95
#ifdef BIGTOOLICONS
	          XtNwidth, 171, XtNheight, 460,
#else
	          XtNwidth, 123, XtNheight, 315,
#endif
#else
#ifdef BIGTOOLICONS
		  XtNwidth, 166, XtNheight, 454,
#else
	          XtNwidth, 118, XtNheight, 310,
#endif
#endif
	         NULL);   
    AddDestroyCallback(toplevel, (DestroyCallbackFunc) exitPaint, NULL);
}

Image *
OperationIconImage(Widget w, char *name)
{
    int i;

    for (i = 0; i < XtNumber(iconList); i++)
	if (strcmp(name, iconList[i].name) == 0)
	    break;
    if (i == XtNumber(iconList) || iconList[i].icon == None)
	return NULL;

    return PixmapToImage(w, iconList[i].icon, None);
}

void setToolIconOnWidget(Widget w)
{
    if (w != None)
        XtVaSetValues(w, XtNbitmap, iconList[getIndexOp()].icon, NULL);
}
