/*--------------------------------------------------------------------
 new_init_graphics.c

 Programmed by: Kenneth Sakai
 Company      : Lockheed Martin
 Date         : 3-6-00

 Note: The following code is not meant necessarily to be commercial grade
       code.  It is just meant to be better than the original grad-student
       version. K.S.
  --------------------------------------------------------------------
 */

#include <stdio.h>
#include "vizual.h"

#include <grass/config.h>

#include <Xm/Xm.h>
#include <Xm/Form.h>
/* include file for GL drawing widget */
#ifdef HAVE_GL_GLWMDRAWA_H
#include <GL/GLwMDrawA.h>
#else
#ifdef HAVE_X11_GLW_GLWMDRAWA_H
#include <X11/GLw/GLwMDrawA.h>
#endif
#endif
#include "Ball.h"

BallData Trackball;

#include "kns_defines.h"
#include "kns_globals.h"

#include <X11/Xlib.h>
#include <X11/extensions/XI.h>
#include <X11/extensions/XInput.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>

#define MAXTHRESHOLDS 200
static Widget Threshbutton[MAXTHRESHOLDS];
static Widget Button_plus;
static Widget Button_minus;
static Widget SingleToggle, MultipleToggle;
static Widget ThresholdRadio;
static Widget PlotSelected;
static int MultipleThresholdFlag;
static int ProceedStatus;
static GLuint FontBase;

static int Attributes[] = {GLX_RGBA, GLX_DEPTH_SIZE,   16,  GLX_RED_SIZE,
                           1,        GLX_GREEN_SIZE,   1,   GLX_BLUE_SIZE,
                           1,        GLX_DOUBLEBUFFER, None};

static int SingleAttributes[] = {GLX_RGBA, GLX_RED_SIZE,  1, GLX_GREEN_SIZE,
                                 1,        GLX_BLUE_SIZE, 1, GLX_DOUBLEBUFFER,
                                 None};

static int RotationEnabled;

void glinitCB(Widget widget, XtPointer client_data, XtPointer cdata);
void glexposeCB(Widget widget, XtPointer client_data, XtPointer cdata);
void glinputCB(Widget widget, XtPointer client_data, XtPointer cdata);
void glresizeCB(Widget widget, XtPointer client_data, XtPointer cdata);
void glexpose2CB(Widget widget, XtPointer client_data, XtPointer cdata);
void glresize2CB(Widget widget, XtPointer client_data, XtPointer cdata);
void ThresholdCB(Widget widget, XtPointer client_data, XtPointer call_data);
void PlusMinusCB(Widget widget, XtPointer client_data, XtPointer call_data);
void SingleMultipleCB(Widget widget, XtPointer client_data, XtPointer cdata);
void PlotSelectedCB(Widget widget, XtPointer client_data, XtPointer cdata);

void set_threshold_button(int iset);
void draw_ctable();
void rotateLoop();
void draw_multiple();

/*-------------------------------------------------------------------
  get_trackball_rotation_matrix()
  This functions retrieves the current trackball rotation matrix.
  -------------------------------------------------------------------
*/
void get_trackball_rotation_matrix(float mat[4][4])
{
    int i, j;
    HMatrix mNow;

    Ball_Value(&Trackball, mNow);
    for (i = 0; i < QuatLen; i++)
        for (j = 0; j < QuatLen; j++)
            mat[i][j] = mNow[i][j];
}

/*-------------------------------------------------------------------
  This functions set the trackball state based upon the set of rotation
  angles contained in the dspect struct.
  -------------------------------------------------------------------
*/
void set_trackball_rotations(struct dspec *D_spec)
{
    GLfloat tranmat[4][4];

    glPushMatrix();
    glLoadIdentity();
    glRotatef(.1 * (D_spec->yrot * 10), 0., 1., 0.);
    glRotatef(.1 * (D_spec->zrot * 10), 0., 0., 1.);
    glRotatef(.1 * (D_spec->xrot * 10), 1., 0., 0.);
    glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat *)tranmat);
    Ball_SetMatrix(&Trackball, tranmat);
    glPopMatrix();
}

/*-------------------------------------------------------------------
  new_swapbuffers()
  This is an OpenGL version of the IRISGL swapbuffers() function.
  -------------------------------------------------------------------
*/
void new_swapbuffers()
{
    glXSwapBuffers(XtDisplay(MainOGLWindow.widget), MainOGLWindow.window);
}

/*-------------------------------------------------------------------
  enableRotation()
  -------------------------------------------------------------------
*/
void enableRotation()
{
    RotationEnabled = 1;
}

/*-------------------------------------------------------------------
  disableRotation()
  -------------------------------------------------------------------
*/
void disableRotation()
{
    RotationEnabled = 0;
}

/*-------------------------------------------------------------------
  new_swapbuffers()
  This is a substitute for the IRISGL getsize() function.
  -------------------------------------------------------------------
*/
void new_getsize(long *x, long *y)
{
    *x = MainOGLWindow.width;
    *y = MainOGLWindow.height;
}

/*-------------------------------------------------------------------
  initializeFonts()
  This function initializes the OpenGL display lists required for drawing
  2D text in a 3D window.  FontBase will end up pointing to a sequence
  of display lists in which each individual display character in a font.
  The font chosen in a Courier Bold font chosen to match the font used
  by the IRISGL charstr() function.
  -------------------------------------------------------------------
*/
void initializeFonts()
{
    XFontStruct *fontInfo;
    Font id;
    GLuint base;
    unsigned int first, last;

    fontInfo = XLoadQueryFont(
        XtDisplay(MainOGLWindow.widget),
        "-adobe-courier-bold-r-normal--14-100-100-100-m-90-iso8859-1");
    id = fontInfo->fid;
    first = fontInfo->min_char_or_byte2;
    last = fontInfo->max_char_or_byte2;
    base = glGenLists((GLuint)last + 1);
    glXUseXFont(id, first, last - first + 1, base + first);
    XFreeFont(XtDisplay(MainOGLWindow.widget), fontInfo);
    FontBase = base;
}

/*-------------------------------------------------------------------
  new_charstr()
  This is an OpenGL version of the IRISGL charstr() function. The
  initializeFonts function must have been called prior to calling this function.
  -------------------------------------------------------------------
*/
void new_charstr(char *str)
{
    glPushAttrib(GL_LIST_BIT);
    glListBase(FontBase);
    glCallLists(strlen(str), GL_UNSIGNED_BYTE, (GLubyte *)str);
    glPopAttrib();
}

/*-------------------------------------------------------------------
  winset_main()
  This function is the equivalent of the IRISGL winset command for
  the main 3D drawing window.
  -------------------------------------------------------------------
*/
void winset_main()
{
    glXMakeCurrent(XtDisplay(MainOGLWindow.widget), MainOGLWindow.window,
                   MainOGLWindow.glx_context);
}

/*-------------------------------------------------------------------
  loadrect()
  This function loads a file created via the loadrect() function into the
  framebuffer ff the main 3D drawing window
  -------------------------------------------------------------------
*/
int loadrect(char *name)
{
    FILE *fp;
    unsigned long *buffer;

    float xd, yd, zd, trd;
    float aspect;
    int xsiz, ysiz;
    long xsiz2, ysiz2;

    new_getsize(&xsiz2, &ysiz2);

    winset_main();
    fp = fopen(name, "r");
    if (!fp) {
        fprintf(stderr, "Unable to open file <%s>\n", name);
        G_free(buffer);
        return 0;
    }
    fread(&xsiz, sizeof(int), 1, fp);
    fread(&ysiz, sizeof(int), 1, fp);
    if (NULL ==
        (buffer = (unsigned long *)G_malloc(xsiz * ysiz * sizeof(long)))) {
        fprintf(stderr, "Out of memory\n");
        fclose(fp);
        return -1;
    }

    if (ysiz != fread(buffer, xsiz * sizeof(long), ysiz, fp))
        fprintf(stderr, "Unable to write file <%s>\n", name);

    clear_screen();
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    glOrtho(0, xsiz2, 0, ysiz2, -100., 10);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glRasterPos2i(0, 0);
    glDrawPixels(xsiz, ysiz, GL_RGBA, GL_BYTE, buffer);

    glXSwapBuffers(XtDisplay(MainOGLWindow.widget), MainOGLWindow.window);
    fclose(fp);
    G_free(buffer);

    aspect = (float)MainOGLWindow.width / (float)MainOGLWindow.height;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45., aspect, .10, 1000.0);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    xd = Headfax.xdim * MainOGLWindow.ptr_D_spec->xscale;
    yd = Headfax.ydim * MainOGLWindow.ptr_D_spec->yscale;
    zd = Headfax.zdim * MainOGLWindow.ptr_D_spec->zscale;

    /* pick greatest dimension to use for translation of viewer from origin */
    if (xd < yd)
        trd = yd;
    else
        trd = xd;
    if (trd < zd)
        trd = zd;

    glTranslatef(0.0, 0.0, -trd * 1.6);

    return (0);
}

/*-------------------------------------------------------------------
  dumprect()
  This function dumps the framebuffer from the main 3D drawing window
  into a file.
  -------------------------------------------------------------------
*/
int dumprect(char *name)
{
    FILE *fp;
    long xsiz, ysiz;
    unsigned long *buffer;

    new_getsize(&xsiz, &ysiz);
    if (NULL ==
        (buffer = (unsigned long *)G_malloc(xsiz * ysiz * sizeof(long)))) {
        fprintf(stderr, "Out of memory\n");
        return -1;
    }
    winset_main();

    fp = fopen(name, "w");
    if (!fp) {
        fprintf(stderr, "Unable to open file <%s>\n", name);
        G_free(buffer);
        return 0;
    }

    /* write dims */
    fwrite(&xsiz, sizeof(int), 1, fp);
    fwrite(&ysiz, sizeof(int), 1, fp);

    glReadBuffer(GL_FRONT);

    glReadPixels(0, 0, xsiz, ysiz, GL_RGBA, GL_BYTE, buffer);
    if (ysiz != fwrite(buffer, xsiz * sizeof(long), ysiz, fp))
        fprintf(stderr, "Unable to write file <%s>\n", name);

    fclose(fp);
    G_free(buffer);

    return (0);
}

/*-------------------------------------------------------------------
  dumpgif()
  This function dumps the framebuffer from the main 3D drawing window
  into a GIF file.
  -------------------------------------------------------------------
*/
#define CPACKTORGBA(l, r, g, b, a) \
    (r) = ((l) >> 0) & 0xff;       \
    (g) = ((l) >> 8) & 0xff;       \
    (b) = ((l) >> 16) & 0xff;      \
    (a) = ((l) >> 24) & 0xff;

#include "togif.h"
int dumpgif(char *name)
{
    FILE *fp;
    long xwid, ywid;
    unsigned long *pixels;
    long numpixels;
    vgl_GIFWriter *writer;
    int i;
    unsigned short red, green, blue, alpha;

    new_getsize(&xwid, &ywid);
    if (xwid % 2)
        xwid--;
    if (ywid % 2)
        ywid--;

    if (NULL ==
        (pixels = (unsigned long *)G_malloc(xwid * ywid * sizeof(long)))) {
        fprintf(stderr, "Out of memory\n");
        return -1;
    }

    winset_main();

    fp = fopen(name, "wb");
    if (!fp) {
        fprintf(stderr, "Unable to open file <%s>\n", name);
        G_free(pixels);
        return 0;
    }

    glReadBuffer(GL_FRONT);
    glReadPixels(0, 0, xwid, ywid, GL_RGBA, GL_UNSIGNED_BYTE, pixels);

    numpixels = xwid * ywid;
    for (i = 0; i < numpixels; i++) {
        CPACKTORGBA(pixels[i], red, green, blue, alpha);
#ifdef WIN32
        pixels[i] = red | (green << 8) | (blue << 16) | (alpha << 24);
#else
        pixels[i] = alpha | (blue << 8) | (green << 16) | (red << 24);
#endif
    }

    writer = vgl_GIFWriterBegin();
    vgl_GIFWriterWriteGIFFile(writer, pixels, xwid, ywid, 0, fp);

    fclose(fp);
    G_free(pixels);

    return (0);
}

/*-------------------------------------------------------------------
  winset_colortable()
  This function is the equivalent of the IRISGL winset command for
  the colormap window.
  -------------------------------------------------------------------
*/
void winset_colortable()
{
    glXMakeCurrent(XtDisplay(ColormapWindow.widget), ColormapWindow.window,
                   ColormapWindow.glx_context);
}

/*-------------------------------------------------------------------
  init_graphics()
  This function creates both the main 3D drawing window and the 2D
  colormap window.
  -------------------------------------------------------------------
*/
#include <Xm/PushB.h>
#include <Xm/RowColumn.h>
#include <Xm/Label.h>
#include <Xm/ToggleB.h>
#include <Xm/Frame.h>
#include <Xm/SeparatoG.h>
#include <grass/gis.h>
static String Fallback_resources[] = {
    "*.fontList:-adobe-helvetica-bold-r-normal--12-120-75-75-p-70-iso8859-1",
    "*Thresholds.fontList:-adobe-helvetica-bold-r-normal--17-120-100-100-p-92-"
    "iso8859-1",
};

void init_graphics(char *name, int argc, char **argv, struct dspec *dspecptr)
{
    int n;
    Arg args[20]; /* array used to set widget resources */
    XVisualInfo *vi;
    XEvent event;
    Widget toplevel, form;
    int i;
    char buffer[20];
    Widget separator;
    Widget gl_shell;
    Widget buttonrowcol, label1, radio;
    Widget frame;

    MainOGLWindow.ptr_D_spec = dspecptr;

    n = 0;
    XtSetArg(args[n], XmNwidth, 660);
    n++;
    XtSetArg(args[n], XmNheight, 600);
    n++;
    XtSetArg(args[n], XmNx, 10);
    n++;
    XtSetArg(args[n], XmNy, 100);
    n++;
    toplevel = XtAppInitialize(&App_context, "Showdspf", NULL, 0, &argc, argv,
                               Fallback_resources, args, n);
    /*
     *     Create the 3D drawing window
     */
    n = 0;
    XtSetArg(args[n], XmNwidth, 660);
    n++;
    XtSetArg(args[n], XmNheight, 600);
    n++;
    XtSetArg(args[n], XmNx, 10);
    n++;
    XtSetArg(args[n], XmNy, 100);
    n++;
    form = XmCreateForm(toplevel, "form", args, n);
    XtManageChild(form);
    vi = glXChooseVisual(XtDisplay(form), DefaultScreen(XtDisplay(form)),
                         Attributes);
    if (!vi) {
        fprintf(stderr, "Fatal error: Couldn't get a visual\n");
        fprintf(stderr, "See program developer.\n");
        exit(1);
    }

    n = 0;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM);
    n++;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM);
    n++;
    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM);
    n++;
    XtSetArg(args[n], XmNshadowThickness, 3);
    n++;
    XtSetArg(args[n], XmNshadowType, XmSHADOW_OUT);
    n++;
    frame = XmCreateFrame(form, "frame_r", args, n);
    XtManageChild(frame);

    n = 0;
    XtSetArg(args[n], XmNentryAlignment, XmALIGNMENT_CENTER);
    n++;
    XtSetArg(args[n], XmNpacking, XmPACK_TIGHT);
    n++;
    XtSetArg(args[n], XmNorientation, XmVERTICAL);
    n++;
    XtSetArg(args[n], XmNnumColumns, 1);
    n++;
    XtSetArg(args[n], XmNmarginHeight, 0);
    n++;
    XtSetArg(args[n], XmNmarginWidth, 0);
    n++;
    buttonrowcol = XmCreateRowColumn(frame, "rowcol", args, n);
    XtManageChild(buttonrowcol);
    label1 = XmCreateLabel(buttonrowcol, "Thresholds", args, n);
    XtManageChild(label1);

    n = 0;
    XtSetArg(args[n], XmNshadowThickness, 5);
    n++;
    separator = XmCreateSeparatorGadget(buttonrowcol, "separator1", args, n);

    XtManageChild(separator);
    n = 0;
    radio = XmCreateRadioBox(buttonrowcol, "radio", args, n);
    XtManageChild(radio);
    n = 0;
    SingleToggle = XtVaCreateManagedWidget("Single", xmToggleButtonWidgetClass,
                                           radio, NULL);
    XtAddCallback(SingleToggle, XmNarmCallback, SingleMultipleCB, (XtPointer)1);
    MultipleToggle = XtVaCreateManagedWidget(
        "Multiple", xmToggleButtonWidgetClass, radio, NULL);
    XtAddCallback(MultipleToggle, XmNarmCallback, SingleMultipleCB,
                  (XtPointer)0);
    XmToggleButtonSetState(SingleToggle, True, False);

    n = 0;
    XtSetArg(args[n], XmNshadowThickness, 5);
    n++;
    separator = XmCreateSeparatorGadget(buttonrowcol, "separator1", args, n);
    XtManageChild(separator);

    n = 0;
    Button_plus = XtCreateManagedWidget("Increase", xmPushButtonWidgetClass,
                                        buttonrowcol, args, n);
    XtAddCallback(Button_plus, XmNactivateCallback, PlusMinusCB, (XtPointer)1);
    Button_minus = XtCreateManagedWidget("Decrease", xmPushButtonWidgetClass,
                                         buttonrowcol, args, n);
    XtAddCallback(Button_minus, XmNactivateCallback, PlusMinusCB, (XtPointer)0);

    n = 0;
    PlotSelected = XmCreatePushButton(buttonrowcol, "Plot Selected", args, n);
    XtAddCallback(PlotSelected, XmNactivateCallback, PlotSelectedCB, NULL);

    n = 0;
    ThresholdRadio = XmCreateRadioBox(buttonrowcol, "radio", args, n);
    XtManageChild(ThresholdRadio);
    if (Headfax.linefax.nthres) {
        for (i = 1; i <= Headfax.linefax.nthres; i++) {
            n = 0;
            if (i <= MAXTHRESHOLDS) {
                sprintf(buffer, "Threshold %d", i);
                Threshbutton[i - 1] = XtVaCreateManagedWidget(
                    buffer, xmToggleButtonWidgetClass, ThresholdRadio, NULL);
                XtAddCallback(Threshbutton[i - 1], XmNarmCallback, ThresholdCB,
                              (XtPointer)(i - 1));
            }
        }
        XmToggleButtonSetState(Threshbutton[0], True, False);
    }

    n = 0;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_WIDGET);
    n++;
    XtSetArg(args[n], XmNrightWidget, frame);
    n++;
    XtSetArg(args[n], GLwNvisualInfo, vi);
    n++;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM);
    n++;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM);
    n++;
    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM);
    n++;
    MainOGLWindow.widget = GLwCreateMDrawingArea(form, "glwidget", args, n);

    XtManageChild(MainOGLWindow.widget);

    XtAddCallback(MainOGLWindow.widget, GLwNginitCallback, glinitCB, 0);

    XtAddCallback(MainOGLWindow.widget, GLwNexposeCallback, glexposeCB, 0);
    XtAddCallback(MainOGLWindow.widget, GLwNinputCallback, glinputCB, 0);
    XtAddCallback(MainOGLWindow.widget, GLwNresizeCallback, glresizeCB, 0);

    /*
     *     Create the colormap window
     */
    n = 0;
    XtSetArg(args[n], XmNwidth, 100);
    n++;
    XtSetArg(args[n], XmNheight, 500);
    n++;
    XtSetArg(args[n], XmNx, 5);
    n++;
    XtSetArg(args[n], XmNy, 5);
    n++;
    gl_shell =
        XtAppCreateShell("Colormap", "GL_window", topLevelShellWidgetClass,
                         XtDisplay(toplevel), args, n);

    n = 0;
    XtSetArg(args[n], XmNwidth, 100);
    n++;
    XtSetArg(args[n], XmNheight, 500);
    n++;
    XtSetArg(args[n], XmNx, 5);
    n++;
    XtSetArg(args[n], XmNy, 5);
    n++;
    form = XmCreateForm(gl_shell, "form", args, n);
    XtManageChild(form);
    vi = glXChooseVisual(XtDisplay(form), DefaultScreen(XtDisplay(form)),
                         SingleAttributes);
    if (!vi) {
        fprintf(stderr, "Fatal error: Couldn't get a visual\n");
        fprintf(stderr, "See program developer.\n");
        exit(1);
    }

    n = 0;
    XtSetArg(args[n], XmNleftAttachment, XmATTACH_FORM);
    n++;
    XtSetArg(args[n], GLwNvisualInfo, vi);
    n++;
    XtSetArg(args[n], XmNrightAttachment, XmATTACH_FORM);
    n++;
    XtSetArg(args[n], XmNtopAttachment, XmATTACH_FORM);
    n++;
    XtSetArg(args[n], XmNbottomAttachment, XmATTACH_FORM);
    n++;
    ColormapWindow.widget = GLwCreateMDrawingArea(form, "glwidget", args, n);

    XtManageChild(ColormapWindow.widget);

    XtAddCallback(ColormapWindow.widget, GLwNginitCallback, glinitCB,
                  (void *)1);

    XtAddCallback(ColormapWindow.widget, GLwNexposeCallback, glexpose2CB, 0);

    XtAddCallback(ColormapWindow.widget, GLwNresizeCallback, glresize2CB, 0);

    XtRealizeWidget(toplevel);
    XStoreName(XtDisplay(toplevel), XtWindow(toplevel), name);

    XtPopup(gl_shell, XtGrabNone);

    XMapRaised(XtDisplay(gl_shell), XtWindow(gl_shell));

    while (ProceedStatus < 4) {
        XtAppNextEvent(App_context, &event);
        XtDispatchEvent(&event);
    }
}

/*-------------------------------------------------------------------
  ThresholdCB()
  This is a Motif callback that is called when one of the threshold toggle
  buttons is hit.
  -------------------------------------------------------------------
*/
void ThresholdCB(Widget widget, XtPointer client_data, XtPointer call_data)
{
    int num = (int)client_data;

    if (MultipleThresholdFlag)
        return;
    MainOGLWindow.ptr_D_spec->Thresh = num;
    do_draw_immediate_mode();
}

/*-------------------------------------------------------------------
  PlusMinusCB()
  This is a Motif callback that is called when either the "Increase" or
  "Decrease" button is hit in the GUI interface.
  -------------------------------------------------------------------
*/
void PlusMinusCB(Widget widget, XtPointer client_data, XtPointer call_data)
{
    int flag = (int)client_data;

    if (flag) {
        MainOGLWindow.ptr_D_spec->Thresh++;
        if (MainOGLWindow.ptr_D_spec->Thresh > Headfax.linefax.nthres - 1)
            MainOGLWindow.ptr_D_spec->Thresh = 0;
        set_threshold_button(MainOGLWindow.ptr_D_spec->Thresh + 1);
        do_draw_immediate_mode();
    }
    else {
        MainOGLWindow.ptr_D_spec->Thresh--;
        if (MainOGLWindow.ptr_D_spec->Thresh < 0)
            MainOGLWindow.ptr_D_spec->Thresh = Headfax.linefax.nthres - 1;
        set_threshold_button(MainOGLWindow.ptr_D_spec->Thresh + 1);
        do_draw_immediate_mode();
    }
}

/*-------------------------------------------------------------------
  set_threshold_button(int iset)
  This function sets the GUI toggle button corresponding to a threshold value
  -------------------------------------------------------------------
*/
void set_threshold_button(int iset)
{
    int i;

    for (i = 1; i <= Headfax.linefax.nthres; i++) {
        if (i == iset) {
            if (i <= MAXTHRESHOLDS)
                XmToggleButtonSetState(Threshbutton[i - 1], True, False);
        }
        else {
            if ((i <= MAXTHRESHOLDS) && !MultipleThresholdFlag)
                XmToggleButtonSetState(Threshbutton[i - 1], False, False);
        }
    }
}

/*-------------------------------------------------------------------
  unset_threshold_button(int iset)
  This function unsets the GUI toggle button corresponding to a threshold value
  -------------------------------------------------------------------
*/
void unset_threshold_button(int iset)
{
    if (iset <= MAXTHRESHOLDS)
        XmToggleButtonSetState(Threshbutton[iset - 1], False, False);
}

/*-------------------------------------------------------------------
  rotateLoop()
  This function is executed while the user attempts to rotate/translate
  the 3D model in the main 3D drawing window
  -------------------------------------------------------------------
*/

void rotateLoop()
{
    XEvent event;
    XKeyPressedEvent *K;
    KeySym sym;
    int thresh = 1;

    for (;;) {
        XtAppNextEvent(App_context, &event);

        if (event.type == KeyPress) {
            K = (XKeyPressedEvent *)&event;
            sym = XKeycodeToKeysym(XtDisplay(MainOGLWindow.widget), K->keycode,
                                   0);
            if (sym == XK_Escape) {
                fprintf(stderr, "Escape\n");
                return;
            }
            else if (sym == XK_equal) {
                if (K->state & ShiftMask) {
                    MainOGLWindow.ptr_D_spec->Thresh++;
                    if (MainOGLWindow.ptr_D_spec->Thresh >
                        Headfax.linefax.nthres - 1)
                        MainOGLWindow.ptr_D_spec->Thresh = 0;
                    do_draw_immediate_mode();
                    set_threshold_button(MainOGLWindow.ptr_D_spec->Thresh + 1);
                }
            }
            else if (sym == XK_minus) {
                if (!(K->state & ShiftMask)) {
                    MainOGLWindow.ptr_D_spec->Thresh--;
                    if (MainOGLWindow.ptr_D_spec->Thresh < 0)
                        MainOGLWindow.ptr_D_spec->Thresh =
                            Headfax.linefax.nthres - 1;
                    do_draw_immediate_mode();
                    set_threshold_button(MainOGLWindow.ptr_D_spec->Thresh + 1);
                }
            }
            else if (sym == XK_C) {
                clear_screen();
            }
            else if (sym == XK_1) {
                thresh = 0;
                if (thresh < Headfax.linefax.nthres) {

                    MainOGLWindow.ptr_D_spec->Thresh = thresh;
                    do_draw_immediate_mode();
                    set_threshold_button(MainOGLWindow.ptr_D_spec->Thresh + 1);
                }
            }
            else if (sym == XK_2) {
                thresh = 1;
                if (thresh < Headfax.linefax.nthres) {

                    MainOGLWindow.ptr_D_spec->Thresh = thresh;
                    do_draw_immediate_mode();
                    set_threshold_button(MainOGLWindow.ptr_D_spec->Thresh + 1);
                }
            }
            else if (sym == XK_3) {
                thresh = 2;
                if (thresh < Headfax.linefax.nthres) {

                    MainOGLWindow.ptr_D_spec->Thresh = thresh;
                    do_draw_immediate_mode();
                    set_threshold_button(MainOGLWindow.ptr_D_spec->Thresh + 1);
                }
            }
            else if (sym == XK_4) {
                thresh = 3;
                if (thresh < Headfax.linefax.nthres) {

                    MainOGLWindow.ptr_D_spec->Thresh = thresh;
                    do_draw_immediate_mode();
                    set_threshold_button(MainOGLWindow.ptr_D_spec->Thresh + 1);
                }
            }
            else if (sym == XK_5) {
                thresh = 4;
                if (thresh < Headfax.linefax.nthres) {

                    MainOGLWindow.ptr_D_spec->Thresh = thresh;
                    do_draw_immediate_mode();
                    set_threshold_button(MainOGLWindow.ptr_D_spec->Thresh + 1);
                }
            }
            else if (sym == XK_6) {
                thresh = 5;
                if (thresh < Headfax.linefax.nthres) {

                    MainOGLWindow.ptr_D_spec->Thresh = thresh;
                    do_draw_immediate_mode();
                    set_threshold_button(MainOGLWindow.ptr_D_spec->Thresh + 1);
                }
            }
            else if (sym == XK_7) {
                thresh = 6;
                if (thresh < Headfax.linefax.nthres) {

                    MainOGLWindow.ptr_D_spec->Thresh = thresh;
                    do_draw_immediate_mode();
                    set_threshold_button(MainOGLWindow.ptr_D_spec->Thresh + 1);
                }
            }
            else if (sym == XK_8) {
                thresh = 7;
                if (thresh < Headfax.linefax.nthres) {

                    MainOGLWindow.ptr_D_spec->Thresh = thresh;
                    do_draw_immediate_mode();
                    set_threshold_button(MainOGLWindow.ptr_D_spec->Thresh + 1);
                }
            }
            else if (sym == XK_9) {
                thresh = 8;
                if (thresh < Headfax.linefax.nthres) {

                    MainOGLWindow.ptr_D_spec->Thresh = thresh;
                    do_draw_immediate_mode();
                    set_threshold_button(MainOGLWindow.ptr_D_spec->Thresh + 1);
                }
            }
            else if (sym == XK_Escape) {
                fprintf(stderr, "Escape\n");
                return;
            }
        }
        else if (MainOGLWindow.right_button_hit) {
            MainOGLWindow.right_button_hit = 0;
            return;
        }
        else {
            XtDispatchEvent(&event);
        }
    }
}

/*-------------------------------------------------------------------
  glinputCB()
  This function handles all mouse inputs in the main 3D drawing window
  -------------------------------------------------------------------
*/
HVect vNow;
HVect vNowLast;
void glinputCB(Widget widget, XtPointer client_data, XtPointer cdata)
{
    long xpos, ypos;

    static int tr_first = 0;
    static int rot_first = 0;
    static int tr_oymouse;
    static int rot_oxmouse, rot_oymouse;
    int dymouse;

    GLwDrawingAreaCallbackStruct *call_data =
        (GLwDrawingAreaCallbackStruct *)cdata;

    if (!RotationEnabled)
        return;

    switch (call_data->event->type) {
    case ButtonPress:
        if (call_data->event->xbutton.button == Button1) {
            if (MainOGLWindow.middle_button_status == 0) {
                MainOGLWindow.left_button_status = 1;
                rot_first = 1;
                xpos = call_data->event->xbutton.x;
                ypos = MainOGLWindow.height - call_data->event->xbutton.y;
                vNow.x = (2 * (float)xpos / MainOGLWindow.width) - 1.;
                vNow.y = (2 * (float)ypos / MainOGLWindow.height) - 1.;
                Ball_Mouse(&Trackball, vNow);
                Ball_Update(&Trackball);

                Ball_BeginDrag(&Trackball);
            }
        }
        else if (call_data->event->xbutton.button == Button2) {
            if (MainOGLWindow.left_button_status == 0) {
                tr_first = 1;
                MainOGLWindow.middle_button_status = 1;
            }
        }
        else if (call_data->event->xbutton.button == Button3) {
            MainOGLWindow.right_button_hit = 1;
        }
        break;
    case ButtonRelease:
        if (call_data->event->xbutton.button == Button1) {
            MainOGLWindow.left_button_status = 0;
            Ball_EndDrag(&Trackball);
            Ball_Mouse(&Trackball, vNow);
            Ball_Update(&Trackball);
            do_draw_with_display_list(MainOGLWindow.ptr_D_spec);
        }
        else if (call_data->event->xbutton.button == Button2) {
            MainOGLWindow.middle_button_status = 0;
        }
        break;
    case MotionNotify:

        if (MainOGLWindow.left_button_status) {
            /* Rotate 3D scene */
            xpos = call_data->event->xmotion.x;
            ypos = MainOGLWindow.height - call_data->event->xmotion.y;
            if (rot_first) {
                rot_oxmouse = xpos;
                rot_oymouse = ypos;
                rot_first = 0;
            }
            vNow.x = (2 * (float)xpos / MainOGLWindow.width) - 1.;
            vNow.y = (2 * (float)ypos / MainOGLWindow.height) - 1.;

            Ball_Mouse(&Trackball, vNow);
            Ball_Update(&Trackball);
            if (((vNowLast.x - vNow.x) * (vNowLast.x - vNow.x) +
                 (vNowLast.y - vNow.y) * (vNowLast.y - vNow.y)) > .01) {
                vNowLast = vNow;
                do_draw_with_display_list(MainOGLWindow.ptr_D_spec);
            }
        }
        else if (MainOGLWindow.middle_button_status) {
            /* Translate 3D scene */
            xpos = call_data->event->xmotion.x;
            ypos = MainOGLWindow.height - call_data->event->xmotion.y;
            if (tr_first) {
                tr_oymouse = ypos;
                tr_first = 0;
            }
            dymouse = ypos - tr_oymouse;
            if (dymouse > 10 || dymouse < -10) {
                tr_oymouse = ypos;

                if (MainOGLWindow.ptr_D_spec) {
                    MainOGLWindow.ptr_D_spec->ztrans -= (float)dymouse;
                    do_draw_with_display_list(MainOGLWindow.ptr_D_spec);
                }
            }
        }
        break;
    }
}

/*-------------------------------------------------------------------
  glinitCB()
  This function is called during initialization of both the main 3D
  drawing window and colormap window
  -------------------------------------------------------------------
*/
void glinitCB(Widget widget, XtPointer client_data, XtPointer cdata)
{
    float xd, yd, zd, trd;
    Arg args[10];
    XVisualInfo *vi;
    int height, width;
    float aspect;
    GLwDrawingAreaCallbackStruct *call_data =
        (GLwDrawingAreaCallbackStruct *)cdata;
    int id = (int)client_data;

    XtSetArg(args[0], GLwNvisualInfo, &vi);
    XtGetValues(widget, args, 1);

    if (id == 0) {
        /* Main 3D window */
        MainOGLWindow.window = XtWindow(widget);
        MainOGLWindow.glx_context =
            glXCreateContext(XtDisplay(widget), vi, 0, GL_TRUE);
        glXMakeCurrent(XtDisplay(widget), MainOGLWindow.window,
                       MainOGLWindow.glx_context);
        glDepthRange(0x0000, 0x7fffff);

        height = (GLuint)call_data->height - 1;
        width = (GLuint)call_data->width - 1;
        aspect = (float)width / (float)height;

        do_lights();
        clear_screen();

        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective(45., aspect, .10, 1000.0);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        xd = Headfax.xdim * MainOGLWindow.ptr_D_spec->xscale;
        yd = Headfax.ydim * MainOGLWindow.ptr_D_spec->yscale;
        zd = Headfax.zdim * MainOGLWindow.ptr_D_spec->zscale;

        /* pick greatest dimension to use for translation of viewer from origin
         */
        if (xd < yd)
            trd = yd;
        else
            trd = xd;
        if (trd < zd)
            trd = zd;

        glTranslatef(0.0, 0.0, -trd * 1.6);
        glXSwapBuffers(XtDisplay(widget), MainOGLWindow.window);
        initializeFonts();

        MainDlist = glGenLists(1);
        glNewList(MainDlist, GL_COMPILE_AND_EXECUTE);
        glEndList();

        Ball_Init(&Trackball);
        Ball_Place(&Trackball, qOne, .90);

        ProceedStatus++;
    }
    else {
        ColormapWindow.window = XtWindow(widget);
        ColormapWindow.glx_context =
            glXCreateContext(XtDisplay(widget), vi, 0, GL_TRUE);

        glXMakeCurrent(XtDisplay(widget), ColormapWindow.window,
                       ColormapWindow.glx_context);
        clear_screen();

        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluOrtho2D(0, 100, 0, 1000);
        glMatrixMode(GL_MODELVIEW);
        draw_ctable();
        winset_main();
        ProceedStatus++;
    }
}

/*-------------------------------------------------------------------
  glexposeCB()
  This function is called when the main 3D drawing window is exposed
  -------------------------------------------------------------------
*/
void glexposeCB(Widget widget, XtPointer client_data, XtPointer cdata)
{
    Window wind;
    static int First = 1;
    GLwDrawingAreaCallbackStruct *call_data =
        (GLwDrawingAreaCallbackStruct *)cdata;
    wind = XtWindow(widget);
    glXMakeCurrent(XtDisplay(widget), wind, MainOGLWindow.glx_context);
    MainOGLWindow.height = (GLuint)(call_data->height);
    MainOGLWindow.width = (GLuint)(call_data->width);
    glViewport(0, 0, (GLuint)(call_data->width), (GLuint)(call_data->height));
    glScissor(0, 0, (GLuint)(call_data->width), (GLuint)(call_data->height));
    glEnable(GL_DEPTH_TEST);

    clear_screen();
    if (First) {
        do_draw_immediate_mode();
        First = 0;
    }
    else {
        do_draw_with_display_list(MainOGLWindow.ptr_D_spec);
    }

    ProceedStatus++;
}

/*-------------------------------------------------------------------
  glresizeCB()
  This function is called to clear the colomap window prior to redrawing
  -------------------------------------------------------------------
*/
void glresizeCB(Widget widget, XtPointer client_data, XtPointer cdata)
{
    Window wind;
    GLdouble aspect;
    GLwDrawingAreaCallbackStruct *call_data =
        (GLwDrawingAreaCallbackStruct *)cdata;

    wind = XtWindow(widget);
    glXMakeCurrent(XtDisplay(widget), wind, MainOGLWindow.glx_context);

    glViewport(0, 0, (GLuint)(call_data->width), (GLuint)(call_data->height));
    glScissor(0, 0, (GLuint)(call_data->width), (GLuint)(call_data->height));

    MainOGLWindow.height = (GLuint)(call_data->height);
    MainOGLWindow.width = (GLuint)(call_data->width);

    aspect = (float)MainOGLWindow.width / (float)MainOGLWindow.height;

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45., aspect, .10, 1000.0);
    glMatrixMode(GL_MODELVIEW);
    do_draw_with_display_list(MainOGLWindow.ptr_D_spec);
}

/*-------------------------------------------------------------------
  clear_screen2()
  This function is called to clear the colomap window prior to redrawing
  -------------------------------------------------------------------
*/
void clear_screen2()
{

    glDisable(GL_LIGHTING);
    glClearColor(1., 1., 1., 1.);
    glClear(GL_COLOR_BUFFER_BIT);
    glClearDepth(1.);
    glClear(GL_DEPTH_BUFFER_BIT);
}

/*-------------------------------------------------------------------
  glexpose2CB()
  This function is called when the colormap window gets an expose event
  -------------------------------------------------------------------
*/
void glexpose2CB(Widget widget, XtPointer client_data, XtPointer cdata)
{
    Window wind;
    GLwDrawingAreaCallbackStruct *call_data =
        (GLwDrawingAreaCallbackStruct *)cdata;

    wind = XtWindow(widget);
    winset_colortable();

    glXMakeCurrent(XtDisplay(widget), wind, ColormapWindow.glx_context);

    glDisable(GL_DEPTH_TEST);
    ColormapWindow.height = (GLuint)(call_data->height);
    ColormapWindow.width = (GLuint)(call_data->width);

    glViewport(0, 0, (GLuint)(call_data->width), (GLuint)(call_data->height));
    glScissor(0, 0, (GLuint)(call_data->width), (GLuint)(call_data->height));

    clear_screen2();
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, 100, 0, 1000, -10000., 1000.);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    draw_ctable();

    glXSwapBuffers(XtDisplay(ColormapWindow.widget), ColormapWindow.window);

    ProceedStatus++;
    winset_main();
}

/*-------------------------------------------------------------------
  glresize2CB()
  This function is called when the colormap window is resized
  -------------------------------------------------------------------
*/
void glresize2CB(Widget widget, XtPointer client_data, XtPointer cdata)
{
    Window wind;
    GLwDrawingAreaCallbackStruct *call_data =
        (GLwDrawingAreaCallbackStruct *)cdata;

    wind = XtWindow(widget);
    glXMakeCurrent(XtDisplay(widget), wind, ColormapWindow.glx_context);
    glViewport(0, 0, (GLuint)(call_data->width), (GLuint)(call_data->height));
    glScissor(0, 0, (GLuint)(call_data->width), (GLuint)(call_data->height));
    ColormapWindow.height = (GLuint)(call_data->height);
    ColormapWindow.width = (GLuint)(call_data->width);

    clear_screen2();
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(0, 100, 0, 1000, -10000., 1000.);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    draw_ctable();
    winset_main();
}

/*-------------------------------------------------------------------
  rotate_model()
  This function is called when the program is put into "Rotation Mode"
  where the user is modifying the view using mouse input.
  -------------------------------------------------------------------
*/
void rotate_model(struct dspec *D_spec)
{
    enableRotation();
    rotateLoop();
    disableRotation();
}

/*-------------------------------------------------------------------
  SingleMultipleCB()
  -------------------------------------------------------------------
*/
void SingleMultipleCB(Widget widget, XtPointer client_data, XtPointer cdata)
{
    int flag = (int)client_data;
    Arg args[10];
    int n;
    int i;

    if (flag == 1) {
        MultipleThresholdFlag = 0;
        n = 0;
        XtSetArg(args[n], XmNradioBehavior, True);
        n++;
        XtSetValues(ThresholdRadio, args, n);
        XtManageChild(Button_plus);
        XtManageChild(Button_minus);
        XtUnmanageChild(PlotSelected);
        for (i = 1; i <= Headfax.linefax.nthres; i++) {
            if (i <= MAXTHRESHOLDS)
                XmToggleButtonSetState(Threshbutton[i - 1], False, False);
        }
    }
    else {
        MultipleThresholdFlag = 1;
        for (i = 1; i <= Headfax.linefax.nthres; i++) {
            if (i <= MAXTHRESHOLDS)
                XmToggleButtonSetState(Threshbutton[i - 1], False, False);
        }
        n = 0;
        XtSetArg(args[n], XmNradioBehavior, False);
        n++;
        XtSetValues(ThresholdRadio, args, n);
        XtUnmanageChild(Button_plus);
        XtUnmanageChild(Button_minus);
        XtManageChild(PlotSelected);
    }
}

/*-------------------------------------------------------------------
  PlotSelectedCB()
  This function is the callback function for the "Plot Selected" button.
  A plot containing thresholds corresponding to all selected Threshold
  toggle buttons will be drawn.
  -------------------------------------------------------------------
*/
void PlotSelectedCB(Widget widget, XtPointer client_data, XtPointer cdata)
{
    int i;
    int selectedList[200];
    int numSelected = 0;

    for (i = 1; i <= Headfax.linefax.nthres; i++) {
        if (i <= MAXTHRESHOLDS) {
            if (XmToggleButtonGetState(Threshbutton[i - 1]) == True) {
                selectedList[numSelected] = i;
                numSelected++;
            }
        }
    }
    for (i = 0; i < numSelected; i++) {
        MainOGLWindow.ptr_D_spec->t[i] = selectedList[i] - 1;
    }
    MainOGLWindow.ptr_D_spec->nt = numSelected;
    draw_multiple();
}

/*-------------------------------------------------------------------
  setSingleSelectionMode()
  This function causes the Motif GUI to act in Single Selection Mode
  with regard to Threshold levels
  -------------------------------------------------------------------
*/
void setSingleSelectionMode()
{
    Arg args[10];
    int n;
    int i;

    XmToggleButtonSetState(SingleToggle, True, True);
    MultipleThresholdFlag = 0;
    n = 0;
    XtSetArg(args[n], XmNradioBehavior, True);
    n++;
    XtSetValues(ThresholdRadio, args, n);
    XtManageChild(Button_plus);
    XtManageChild(Button_minus);
    XtUnmanageChild(PlotSelected);
    for (i = 1; i <= Headfax.linefax.nthres; i++) {
        if (i <= MAXTHRESHOLDS)
            XmToggleButtonSetState(Threshbutton[i - 1], False, False);
    }
}

/*-------------------------------------------------------------------
  setSingleSelectionMode()
  This function causes the Motif GUI to act in Multiple Selection Mode
  with regard to Threshold levels
  -------------------------------------------------------------------
*/
void setMultipleSelectionMode()
{
    Arg args[10];
    int n;
    int i;

    MultipleThresholdFlag = 1;
    XmToggleButtonSetState(MultipleToggle, True, True);
    for (i = 1; i <= Headfax.linefax.nthres; i++) {
        if (i <= MAXTHRESHOLDS)
            XmToggleButtonSetState(Threshbutton[i - 1], False, False);
    }
    n = 0;
    XtSetArg(args[n], XmNradioBehavior, False);
    n++;
    XtSetValues(ThresholdRadio, args, n);
    XtUnmanageChild(Button_plus);
    XtUnmanageChild(Button_minus);
    XtManageChild(PlotSelected);
}

/*-------------------------------------------------------------------
  isSingleSelectionMode()
  This function returns 1 if the GUI is currently behaving in Single
  Selection Mode or returns 0 if the GUI is currently behaving in
  Multiple selection mode.
  -------------------------------------------------------------------
*/
int isSingleSelectionMode()
{
    return !MultipleThresholdFlag;
}
