Hogwarts Wand Docs: ../server/graph.c

Wand Sourcecode: ../server/graph.c

//
// graph.c - graphing utility functions using OpenGL
//
// Copyright (C) 2006  Nathan (Acorn) Pooley
// 
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// version 2 as published by the Free Software Foundation.
// 
// 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 (gpl.txt) for more details. 
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
// 
// You can contact the author, Nathan (Acorn) Pooley, by writing
// to Nathan (Acorn) Pooley, 949 Buckeye Drive, Sunnyvale, CA  94086, USA
// or through the form at http://www.rawbw.com/~acorn/wand/feedback.html
//

//#@DOC@ generic graphing utility functions

//###########################################################################
//############################### INCLUDES ##################################
//###########################################################################

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <X11/Xlib.h>
#include <GL/glx.h>
#include <GL/gl.h>

#include <unistd.h>

#include "graph.h"


//###########################################################################
//############################### DEFINES ###################################
//###########################################################################

#define ARG_FILENAME    "/tmp/graph_args.txt"
#define MAX_LINEARGS    2
#define MAX_FLOATARGS   2
#define MAX_FLOATS      20

#ifndef MAXFLOAT
#define MAXFLOAT    3.40282347e+38F
#endif

//###########################################################################
//############################### TYPEDEFS ##################################
//###########################################################################

typedef struct GWinLineNodeRec {
    struct GWinLineNodeRec *next;
    struct GWinLineNodeRec *prev;
} GWinLineNode;

typedef struct GWinLineRec {
    GWinLineNode    node;

    int id;
    char *name;
    int hidden;
    int selected;
    float r,g,b;
    float xscale;
    float yscale;
    float xoffset;
    float yoffset;
    int vertCnt;
    int vertAlloc;
    float *verts;
    int step;

    GWinLineFuncInfo    funcInfo;
    
    float   curs_d;     // used by cursor functions
    int     curs_vert;  // used by cursor functions
} GWinLine;

typedef struct GWinCursorRec {
    int         is_vertical;
    float       pos;
    float       color[3];
    struct GWinCursorRec    *next;
} GWinCursor;

typedef struct GWinArgInfoRec {
    GWinArg      arg;
    GWinArgDesc  desc;
    struct GWinArgInfoRec   *next;
    struct GWinArgInfoRec   *prev;
} GWinArgInfo;

struct GWindowRec {
    Display     *dpy;
    Window       win;
    GLXContext   gc;
    int          loop;      // 0=exit event loop
    int          redraw;
    int          resize;
    int          recalc;
    int          doubleBuffer;
    int          argc;
    char        **argv;
    char        *name;
    float       win_width;
    float       win_height;
    float       xmin, xmax, ymin, ymax;
    float       border_x, border_y;
    float       border_xmin, border_xmax, border_ymin, border_ymax;
    float       disp_width, disp_height;
    float       xscale, yscale;
    float       xoffset, yoffset;
    float       xoffset_max, yoffset_max;
    float       x_drag_scale;
    float       y_drag_scale;
    float       bgcolor[3];
    int         iswidth;
    int         isheight;

    GWinCBFunc  cb_func;
    void        *cb_data;

    GWinArgInfo argHead;
    GWinArgInfo *argCurrent;
    FILE        *argFile;
    
    int         x_drag, y_drag;
    int         is_drag;

    GWinCursor  *cursors;
    GWinCursor  *curs_prev_v;
    GWinCursor  *curs_current_v;
    GWinCursor  *curs_prev_h;
    GWinCursor  *curs_current_h;

    int          selectLine;
    GWinLineNode    line_head;
    GWinLine    **lines;
    int          lineCnt;
    int          lineAlloc;
    int          lineNameLen;
};

typedef enum GWinPickFlagsEnum {
    GW_PICKFLG_NORMAL       = 0,
    GW_PICKFLG_IGNORE_X     = 0x00000001,
    GW_PICKFLG_IGNORE_Y     = 0x00000002,
    GW_PICKFLG_INC_X        = 0x00000004,
    GW_PICKFLG_DEC_X        = 0x00000008,
    GW_PICKFLG_INC_Y        = 0x00000010,
    GW_PICKFLG_DEC_Y        = 0x00000020,
    GW_PICKFLG_NAMED        = 0x00000040,   // only named lines
    GW_PICKFLG_UNHIDDEN     = 0x00000080,   // only unhidden lines
    GW_PICKFLG_SELECTED     = 0x00000100,   // only selected line
    GW_PICKFLG_UNSELECTED   = 0x00000200,   // only unselected lines
    GW_PICKFLG_END
} GWinPickFlags;

//###########################################################################
//############################### CODE ######################################
//###########################################################################

//===========================================================================
// gwinLineGet()
//===========================================================================
static GWinLine *gwinGetLine(GWindow *gw, int line)
{
    if (line<0 || line>=gw->lineCnt) return 0;
    return gw->lines[line];
}

//===========================================================================
// gwinMakeCurrent()
//===========================================================================
static void gwinMakeCurrent(GWindow *gw)
{
    glXMakeCurrent(gw->dpy,gw->win,gw->gc);
}

//===========================================================================
// gwinXYToWin()
//===========================================================================
static float gwinXToWin(GWindow *gw, float x)
{
    return (x - gw->border_xmin - gw->xoffset) / gw->x_drag_scale;
}
static float gwinYToWin(GWindow *gw, float y)
{
    return (y - gw->border_ymin - gw->yoffset) / gw->y_drag_scale;
}

//===========================================================================
// gwinXYFromWin()
//===========================================================================
static float gwinXFromWin(GWindow *gw, float x)
{
    return  x * gw->x_drag_scale + gw->border_xmin + gw->xoffset;
}
static float gwinYFromWin(GWindow *gw, float y)
{
    return  y * gw->y_drag_scale + gw->border_ymin + gw->yoffset;
}

//===========================================================================
// gwinCalcScales()
//===========================================================================
void gwinCalcScales(GWindow *gw)
{
    float ooxs = 1.0f/gw->xscale;
    float ooys = 1.0f/gw->yscale;
    float border_x = 0.05 * (gw->xmax - gw->xmin) * ooxs;
    float border_y = 0.05 * (gw->ymax - gw->ymin) * ooys;

    gw->border_xmin = gw->xmin - border_x;
    gw->border_xmax = gw->xmax + border_x;
    gw->border_ymin = gw->ymin - border_y;
    gw->border_ymax = gw->ymax + border_y;

    gw->disp_width  = (gw->border_xmax - gw->border_xmin) * ooxs;
    gw->disp_height = (gw->border_ymax - gw->border_ymin) * ooys;

    gw->xoffset_max = (gw->border_xmax - gw->border_xmin) - gw->disp_width;
    gw->yoffset_max = (gw->border_ymax - gw->border_ymin) - gw->disp_height;

    gw->x_drag_scale = gw->disp_width  / gw->win_width;
    gw->y_drag_scale = gw->disp_height / gw->win_height;

    gw->resize = 0;
    gw->redraw = 1;
}


//===========================================================================
// gwinFuncCalc()
//===========================================================================
static void gwinFuncCalc(GWindow *gw, GWinLine *l)
{
    int i;
    int min = 0;
    int max = 0;
    for (i=0; i<l->funcInfo.argCnt; i++) {
        if (l->funcInfo.args[i]->type == GW_ARGTYPE_LINE) {
            GWinArgLine *al = &l->funcInfo.args[i]->l;
            GWinLine *l2 = gwinGetLine(gw,al->line);
            if (l2) {
                al->vertCnt = l2->vertCnt;
                al->verts   = l2->verts;
            } else {
                al->vertCnt = 0;
                al->verts   = 0;
            }
            if (max == 0) {
                min = max = al->vertCnt;
            } else {
                min = min < al->vertCnt ? min : al->vertCnt;
                max = max > al->vertCnt ? max : al->vertCnt;
            }
        }
    }

    l->funcInfo.vertCntMin = min;
    l->funcInfo.vertCntMax = max;
    l->funcInfo.func(&l->funcInfo);

    for (i=0; i<l->vertCnt; i++) {
        float x = l->verts[i*2+0];
        float y = l->verts[i*2+1];

        gw->xmax = x > gw->xmax ? x : gw->xmax;
        gw->ymax = y > gw->ymax ? y : gw->ymax;
        gw->xmin = x < gw->xmin ? x : gw->xmin;
        gw->ymin = y < gw->ymin ? y : gw->ymin;
    }
    gw->resize = 1;
}

//===========================================================================
// gwinGlInit()
//===========================================================================
static void gwinGlInit(GWindow *gw)
{
    //glEnable(GL_DEPTH_TEST);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho( 0.0, 1.0,
             0.0, 1.0,
            -1.0, 1.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    glClearColor(gw->bgcolor[0], gw->bgcolor[1], gw->bgcolor[2], 1.0);
}

//===========================================================================
// gwinDraw()
//===========================================================================
void gwinDraw(GWindow *gw)
{
    int i;
    GWinLineNode *lnp;
    GWinCursor *curs;
    float disp_xmin, disp_ymin;

    gwinMakeCurrent(gw);

    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


    //
    // recalc functions
    //
    if (gw->recalc) {
        for (i=0; i<gw->lineCnt; i++) {
            GWinLine *l = gw->lines[i];
            if (!l) continue;
            if (l->funcInfo.func) {
                gwinFuncCalc(gw, l);
            }
        }
        gw->recalc = 0;
    }

    //
    // resize so all lines fit on screen at scale=1
    //
    if (gw->resize) {
        gwinCalcScales(gw);
    }

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    if (gw->xoffset < 0) gw->xoffset = 0;
    if (gw->yoffset < 0) gw->yoffset = 0;
    if (gw->xoffset > gw->xoffset_max) {
        gw->xoffset = gw->xoffset_max;
    }
    if (gw->yoffset > gw->yoffset_max) {
        gw->yoffset = gw->yoffset_max;
    }

    disp_xmin = gw->border_xmin + gw->xoffset;
    disp_ymin = gw->border_ymin + gw->yoffset;
    glOrtho(
            disp_xmin,
            disp_xmin + gw->disp_width,
            disp_ymin,
            disp_ymin + gw->disp_height,
            -1.0, 1.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glLineWidth(1.0);

    //
    // draw cursors
    //
    for (curs=gw->cursors; curs; curs = curs->next) {
        glBegin(GL_LINE_STRIP); {
            if (curs == gw->curs_current_v || curs == gw->curs_current_h) {
                glColor3f(1.0, 1.0, 1.0);
            } else if (curs == gw->curs_prev_v || curs == gw->curs_prev_h) {
                glColor3f(0.7, 0.7, 0.7);
            } else {
                glColor3f(curs->color[0],curs->color[1],curs->color[2]);
            }
            if (curs->is_vertical) {
                glVertex2f(curs->pos, gw->border_ymin);
                glVertex2f(curs->pos, gw->border_ymax);
            } else {
                glVertex2f(gw->border_xmin, curs->pos);
                glVertex2f(gw->border_xmax, curs->pos);
            }
        } glEnd();
    }

    //
    // draw lines
    //
    for (lnp=gw->line_head.next; lnp != &gw->line_head; lnp=lnp->next) {
        GWinLine *l = (GWinLine*)lnp;
        float *vp = l->verts;
        float *vpe = vp + (l->vertCnt * 2);
        float svert[2];
        int step = l->step;
        if (!l) continue;
        if (l->vertCnt < 2) continue;
        if (l->hidden) continue;
        
        glPushMatrix();
        glTranslatef(l->xoffset, l->yoffset, 0.0f);
        glScalef(l->xscale, l->yscale, 1.0f);

        glLineWidth( l->selected ? 2.0 : 1.0);

        svert[0] = vp[0];
        svert[1] = vp[1];
        glBegin(GL_LINE_STRIP); {
            glColor3f(l->r, l->g, l->b);
            for (; vp != vpe; vp+=2) {
                if (step) {
                    svert[0] = vp[0];
                    glVertex2fv(svert);
                    svert[1] = vp[1];
                }
                glVertex2fv(vp);
            }
        } glEnd();

        glPopMatrix();
    }
    glLineWidth(1.0);

#if 0
    //
    // ticks along bottom
    //
    for (i=0; ; i++) {
        float x = 0.1 * i;
        float y = (i % 10) ? 0.05 : 0.1;
        if (x > gw->xmax) break;
        glBegin(GL_LINE_STRIP); {
            glColor3f(0.0, 0.0, 0.0);
            glVertex2f(x, gw->ymin);
            glVertex2f(x, gw->ymin-y);
        } glEnd();
    }

    //
    // ticks along left
    //
    for (i=0; ; i++) {
        float y = 0.1 * i;
        float x = (i % 10) ? 0.05 : 0.1;
        if (y > gw->ymax) break;
        glBegin(GL_LINE_STRIP); {
            glColor3f(0.0, 0.0, 0.0);
            glVertex2f(gw->xmin,   y);
            glVertex2f(gw->xmin-x, y);
        } glEnd();
    }
#endif

    if (gw->doubleBuffer) {
        glXSwapBuffers(gw->dpy,gw->win);
    } else {
        glFlush();
    }

    gw->redraw = 0;
}

//===========================================================================
// gwinLineToFront()
//===========================================================================
void gwinLineToFront(GWindow *gw, int line)
{
    GWinLine *l = gwinGetLine(gw,line);
    if (!l) return;

    l->node.next->prev = l->node.prev;
    l->node.prev->next = l->node.next;

    l->node.next = &gw->line_head;
    l->node.prev = gw->line_head.prev;
    l->node.next->prev = &l->node;
    l->node.prev->next = &l->node;
}

//===========================================================================
// gwinLineToBack()
//===========================================================================
void gwinLineToBack(GWindow *gw, int line)
{
    GWinLine *l = gwinGetLine(gw,line);
    if (!l) return;

    l->node.next->prev = l->node.prev;
    l->node.prev->next = l->node.next;

    l->node.next = gw->line_head.next;
    l->node.prev = &gw->line_head;
    l->node.next->prev = &l->node;
    l->node.prev->next = &l->node;
}


//===========================================================================
// gwinLineCreate()
//===========================================================================
int gwinLineCreate(GWindow *gw)
{
    int line;
    char name[100];
    GWinLine *l = calloc(1,sizeof(GWinLine));
    if (gw->lineCnt >= gw->lineAlloc) {
        gw->lineAlloc = gw->lineCnt + 10;
        gw->lines = realloc(gw->lines, gw->lineAlloc * sizeof(GWinLine*));
    }
    line = gw->lineCnt++;
    gw->lines[line] = l;

    l->id = line;
    l->r = l->g = l->b = 1.0;
    l->vertAlloc = 10;
    l->verts = malloc(l->vertAlloc * 2 * sizeof(float));
    l->xscale = 1.0;
    l->yscale = 1.0;
    l->step = 1;
    
    sprintf(name,"%d",gw->lineCnt);
    l->name = strdup(name);

    l->node.next = l->node.prev = &l->node;
    gwinLineToFront(gw,line);

    return line;
}

//===========================================================================
// gwinLineDelete()
//===========================================================================
void gwinLineDelete(GWindow *gw, int line)
{
    GWinLine *l = gwinGetLine(gw,line);
    if (!l) return;
    l->node.next->prev = l->node.prev;
    l->node.prev->next = l->node.next;

    if (l->verts) free(l->verts);
    if (l->name)  free(l->name);
    gw->lines[line] = 0;
    free(l);
}

//===========================================================================
// gwinLineName()
//===========================================================================
void gwinLineName(GWindow *gw, int line, const char *name)
{
    int len = 0;
    GWinLine *l = gwinGetLine(gw,line);
    if (!l) return;
    if (l->name) free(l->name);
    
    l->name = 0;
    if (name) {
#if 0
        int l2 = gwinFindLine(gw, name);
        if (l2 >= 0) {
            fprintf(stderr,"WARNING: 2 lines named '%s'\n",name);
        }
#endif
        l->name = strdup(name);
        len = strlen(name);
    }
    if (len > gw->lineNameLen) {
        gw->lineNameLen = len;
    }
}

//===========================================================================
// gwinLineStep()
//===========================================================================
void gwinLineStep(GWindow *gw, int line, int step)
{
    GWinLine *l = gwinGetLine(gw,line);
    if (!l) return;
    l->step = step;
}

//===========================================================================
// gwinLineScaleOffset()
//===========================================================================
void gwinLineScaleOffset(
                        GWindow *gw, 
                        int line,
                        float xscale,
                        float yscale,
                        float xoffset,
                        float yoffset)
{

    GWinLine *l = gwinGetLine(gw,line);
    if (!l) return;

    l->xscale  = xscale;
    l->yscale  = yscale;
    l->xoffset = xoffset;
    l->yoffset = yoffset;
}

//===========================================================================
// gwinLineColor()
//===========================================================================
void gwinLineColor(GWindow *gw, int line, int color)
{
    GWinLine *l = gwinGetLine(gw,line);
    if (!l) return;
    l->r = ((color >> 16) & 0xff) * 1.0/255.0;
    l->g = ((color >>  8) & 0xff) * 1.0/255.0;
    l->b = ((color      ) & 0xff) * 1.0/255.0;
}

//===========================================================================
// gwinLinePointFlags()
//===========================================================================
void gwinLinePointFlags(GWindow *gw, int line, float x, float y, 
                        GWLineFlags flags)
{
    GWinLine *l = gwinGetLine(gw,line);
    if (!l) return;
    if (l->vertCnt+1 >= l->vertAlloc) {
        l->vertAlloc = l->vertCnt*2 + 1000;
        l->verts = realloc(l->verts, l->vertAlloc * 2 * sizeof(float));
    }

    //
    // GW_LFLG_STEPY: repeat last y value at new x value
    //
    if ((flags & GW_LFLG_STEPY) && l->vertCnt) {
        l->verts[l->vertCnt*2 + 0] = x;
        l->verts[l->vertCnt*2 + 1] = l->verts[(l->vertCnt-1)*2 + 1];
        l->vertCnt++;
    }

    l->verts[l->vertCnt*2 + 0] = x;
    l->verts[l->vertCnt*2 + 1] = y;
    l->vertCnt++;

    x = x * l->xscale + l->xoffset;
    y = y * l->yscale + l->yoffset;

    if (x > gw->xmax) {
        gw->xmax = x;
        gw->resize = 1;
    }
    if (x < gw->xmin) {
        gw->xmin = x;
        gw->resize = 1;
    }
    if (y > gw->ymax) {
        gw->ymax = y;
        gw->resize = 1;
    }
    if (y < gw->ymin) {
        gw->ymin = y;
        gw->resize = 1;
    }
    
    gw->redraw = 1;
    if ((l->vertCnt %100)==0) {
        gwinDraw(gw);
    }
}


//===========================================================================
// gwinLinePoint()
//===========================================================================
void gwinLinePoint(GWindow *gw, int line, float x, float y)
{
    gwinLinePointFlags(gw,line,x,y,0);
}

//===========================================================================
// gwinLinePointStep()
//===========================================================================
void gwinLinePointStep(GWindow *gw, int line, float x, float y)
{
    gwinLinePointFlags(gw,line,x,y,1);
}

//===========================================================================
// gwinLineHide()
//===========================================================================
void gwinLineHide(GWindow *gw, int line, int hidden)
{
    GWinLine *l = gwinGetLine(gw,line);
    if (l) l->hidden = hidden;
    gw->redraw = 1;
}

//===========================================================================
// gwinLineFuncVerts() - set cnt and get pointer
//===========================================================================
float *gwinLineFuncVerts(GWinLineFuncInfo *info, int cnt)
{
    GWinLine *l = gwinGetLine(info->gw,info->line);
    if (!l) {
        //
        // return dummy array
        //
        static float *dummy = 0;
        static int dummy_cnt = -1;
        if (dummy_cnt < cnt || !dummy) {
            if (dummy) free(dummy);
            cnt += 100;
            if (cnt<100) cnt = 100;
            dummy_cnt = dummy_cnt > cnt ? dummy_cnt : cnt;
            dummy = malloc(dummy_cnt * 2 * sizeof(float));
        }
        return dummy;
    }
    if (cnt > l->vertAlloc) {
        float *ptr = realloc(l->verts, cnt * 2 * sizeof(float));
        if (!ptr) return 0;
        l->verts = ptr;
        l->vertAlloc = cnt;
    }
    l->vertCnt = cnt;
    return l->verts;
}

//===========================================================================
// gwinFindLine()
//===========================================================================
int gwinFindLine(GWindow *gw, const char *name)
{
    int i;
    for (i=0; i<gw->lineCnt; i++) {
        GWinLine *l = gw->lines[i];
        if (!l) continue;
        if (!l->name) continue;
        if (!strcmp(name,l->name)) return i;
    }
    return -1;
}

//===========================================================================
// gwinArgCreate()
//===========================================================================
GWinArg *gwinArgCreate(GWindow *gw, int line, GWinArgDesc *args)
{
    GWinArgInfo *ai;

    for (ai=gw->argHead.next; ai!=&gw->argHead; ai=ai->next) {
        if (!strcmp(ai->desc.name, args->name) &&
                args->type == GW_ARGTYPE_VAR &&
                ai->desc.type == GW_ARGTYPE_VAR) {
            return &ai->arg;
        }
    }

    ai = calloc(1, sizeof(GWinArgInfo));

    ai->arg.type = args->type;
    switch(ai->arg.type) {
        case GW_ARGTYPE_VAR:
            ai->arg.v.val = args->init;
            gw->argCurrent = ai;
            break;
        case GW_ARGTYPE_LINE:
            ai->arg.l.line = gwinFindLine(gw,args->name);
            break;
        default:
            free(ai);
            return 0;
    }
    
    ai->desc = *args;
    ai->desc.name = strdup(args->name);
    ai->prev = gw->argHead.prev;
    ai->next = &gw->argHead;
    ai->prev->next = ai;
    ai->next->prev = ai;

    gw->recalc = 1;
    gw->redraw = 1;

    return &ai->arg;
}

//===========================================================================
// gwinLineFunc()
//===========================================================================
void gwinLineFunc(GWindow *gw, int line, GWinLineFunc func, GWinArgDesc *args)
{
    GWinArgDesc args1[1] = {{0,}};
    int cnt;
    GWinLine *l = gwinGetLine(gw,line);
    if (!l) return;

    if (!args) args = args1;

    for (cnt=0; args[cnt].type; cnt++);

    l->funcInfo.gw = gw;
    l->funcInfo.line = line;
    l->funcInfo.func = func;
    l->funcInfo.argCnt = cnt;
    l->funcInfo.args = calloc(cnt+1,sizeof(GWinArg*));

    for (cnt=0; args[cnt].type; cnt++) {
        l->funcInfo.args[cnt] = gwinArgCreate(gw, line, &args[cnt]);
    }
    gw->recalc = 1;
    gw->redraw = 1;
}

//===========================================================================
// gwinCreate()
//===========================================================================
GWindow *gwinCreate(void)
{
    static char *fakeArgs[] = { "program", 0 };
    GWindow *gw = calloc(1,sizeof(GWindow));
    gw->redraw = 1;
    gw->resize = 1;
    gw->doubleBuffer = 1;
    gw->argc = 1;
    gw->argv = fakeArgs;
    gw->name = strdup(fakeArgs[0]);

    gw->lineAlloc = 10;
    gw->lines = malloc(gw->lineAlloc * sizeof(GWinLine*));

    gw->xmin =  1.0;
    gw->xmax = -1.0;
    gw->ymin =  0.0;
    gw->ymax =  0.0;

    gw->xscale = 1.0;
    gw->yscale = 1.0;
    gw->win_width = 1;
    gw->win_height = 1;
    gw->resize = 1;
    gw->lineNameLen = 4;
    gw->selectLine = -1;

    gw->iswidth = 300;
    gw->isheight = 300;

    gw->bgcolor[0] = .2;
    gw->bgcolor[1] = .3;
    gw->bgcolor[2] = .1;

    gw->line_head.next = gw->line_head.prev = &gw->line_head;

    gw->argHead.next = gw->argHead.prev = &gw->argHead;

    gw->argFile = fopen(ARG_FILENAME,"r");
    if (!gw->argFile) {
        gw->argFile = fopen(ARG_FILENAME,"w");
        if (gw->argFile) {
            fclose(gw->argFile);
            gw->argFile = fopen(ARG_FILENAME,"r");
        }
    }

    return gw;
}

//===========================================================================
// gwinDelete()
//===========================================================================
void gwinDelete(GWindow *gw)
{
    int i;
    for (i=0; i<gw->lineCnt; i++) {
        gwinLineDelete(gw,i);
    }
    if (gw->lines) free(gw->lines);
    if (gw->name) free(gw->name);

    //
    // todo: destroy window
    //

    free(gw);
}

//===========================================================================
// gwinSetArgs()
//===========================================================================
void gwinSetArgs(GWindow *gw, int argc,char *argv[])
{
    gw->argc = argc;
    gw->argv = argv ? argv : gw->argv;
}

//===========================================================================
// gwinSetName()
//===========================================================================
void gwinSetName(GWindow *gw, const char *name)
{
    if (gw->name) free(gw->name);
    gw->name = strdup(name);
}

//===========================================================================
// gwinSetISize() - initial size
//===========================================================================
void gwinSetISize(GWindow *gw, int w, int h)
{
    gw->iswidth = w;
    gw->isheight = h;
}

//===========================================================================
// gwinSetRange()
//===========================================================================
void gwinSetRange(GWindow *gw, float min, float max)
{
    gw->xmin = min;
    gw->xmax = max;
}

//===========================================================================
// gwinSetDomain()
//===========================================================================
void gwinSetDomain(GWindow *gw, float min, float max)
{
    gw->ymin = min;
    gw->ymax = max;
}

//===========================================================================
// gwinDestroy()
//===========================================================================
void gwinDestroy(GWindow *gw)
{
}

//===========================================================================
// gwinOpen()
//===========================================================================
void gwinOpen(GWindow *gw)
{
    XVisualInfo *vi;
    Colormap     cmap;
    XSetWindowAttributes    swa;
    int dummy;
    char *name;

    static int sbuf[] = {
                        GLX_RGBA,
                        GLX_RED_SIZE,
                        1,
                        GLX_GREEN_SIZE,
                        1,
                        GLX_BLUE_SIZE,
                        1,
                        GLX_DEPTH_SIZE,
                        12,
                        None};

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

    // open X server connection
    gw->dpy = XOpenDisplay(NULL);
    if (gw->dpy == NULL) {
        fprintf(stderr,"Could not open display");
        gwinDestroy(gw);
        return;
    }

    // check for GLX extension
    if (!glXQueryExtension(gw->dpy,&dummy,&dummy)) {
        fprintf(stderr,"X server has no OpenGL GLX extension");
        gwinDestroy(gw);
        return;
    }

    // get visual
    vi = glXChooseVisual(gw->dpy,DefaultScreen(gw->dpy),dbuf);
    if (vi == NULL) {
        vi = glXChooseVisual(gw->dpy,DefaultScreen(gw->dpy),sbuf);
        if (vi == NULL) {
            fprintf(stderr,"No RGB visual with depth buffer");
            gwinDestroy(gw);
            return;
        }
        gw->doubleBuffer = 0;
    }

    if (vi->class != TrueColor) {
        fprintf(stderr,"TrueColor visual required and not found");
        gwinDestroy(gw);
        return;
    }

    // create openGL rendering context
    gw->gc = glXCreateContext(gw->dpy,vi,
        None,       // do not share display lists
        True);      // direct rendering if possible

    if (gw->gc == NULL) {
        fprintf(stderr,"could not create rendering context");
        gwinDestroy(gw);
        return;
    }

    name = gw->name ? gw->name :
            (gw->argv && gw->argv[0]) ? gw->argv[0] : "graph";

    // create X window and color map
    cmap = XCreateColormap(gw->dpy,RootWindow(gw->dpy,vi->screen),
                vi->visual,AllocNone);
    swa.colormap = cmap;
    swa.border_pixel = 0;
    swa.event_mask = ExposureMask |
                    ButtonPressMask |
                    ButtonReleaseMask   |
                    KeyPressMask    |
                    KeyReleaseMask  |
                    Button1MotionMask |
                    EnterWindowMask |
                    StructureNotifyMask;
    gw->win = XCreateWindow(gw->dpy,
                RootWindow(gw->dpy,vi->screen),
                0,0,gw->iswidth,gw->isheight,
                0,vi->depth,
                InputOutput,
                vi->visual,
                CWBorderPixel | CWColormap | CWEventMask,
                &swa);
    XSetStandardProperties(gw->dpy,gw->win,
            name,name,
            None,gw->argv,gw->argc,NULL);


    // bind context to window
    glXMakeCurrent(gw->dpy,gw->win,gw->gc);

    // display window
    XMapWindow(gw->dpy,gw->win);

    // config openGL
    gwinGlInit(gw);

}

//===========================================================================
// gwinLinePickPointSub()
//===========================================================================
static void gwinLinePickPointSub(
                    GWinPickInfo *info,
                    int     line,
                    float   win_x,
                    float   win_y,
                    unsigned int flags) // from enum GWinPickFlagsEnum
{
    int j;
    GWindow *gw = info->gw;
    GWinLine *l = gwinGetLine(gw,line);
    float sx,ox,sy,oy;
    if (!l) return;
    
    if (l->vertCnt < 1) return;
    if (!l->name && (flags & GW_PICKFLG_NAMED)) return;
    if (l->hidden && (flags & GW_PICKFLG_UNHIDDEN)) return;
    if (l->selected) {
        if (flags & GW_PICKFLG_UNSELECTED) return;
    } else {
        if (flags & GW_PICKFLG_SELECTED) return;
    }

    if (info->win_dist_squared < 0) {
        info->win_dist_squared = MAXFLOAT;
    }

    sx = l->xscale / gw->x_drag_scale;
    ox = (l->xoffset - gw->border_xmin - gw->xoffset) / gw->x_drag_scale;
    sy = l->yscale / gw->y_drag_scale;
    oy = (l->yoffset - gw->border_ymin - gw->yoffset) / gw->y_drag_scale;

    ox -= win_x;
    oy -= win_y;

    if (flags & GW_PICKFLG_IGNORE_X) {
        ox = sx = 0.0;
    }
    if (flags & GW_PICKFLG_IGNORE_Y) {
        oy = sy = 0.0;
    }

    flags &= 
            GW_PICKFLG_INC_X |
            GW_PICKFLG_DEC_X |
            GW_PICKFLG_INC_Y |
            GW_PICKFLG_DEC_Y;

    for (j=0; j<l->vertCnt; j++) {
        float dx = ox + sx * l->verts[j*2+0];
        float dy = oy + sy * l->verts[j*2+1];
        float d = dx * dx + dy * dy;

        if (d >= info->win_dist_squared) continue;
        if (flags) {
            if (flags & (GW_PICKFLG_INC_X|GW_PICKFLG_DEC_X)) {
                float pos = l->verts[j*2+0] * l->xscale + l->xoffset;
                if ((flags & GW_PICKFLG_INC_X) && pos <= info->old_pos) {
                    continue;
                }
                if ((flags & GW_PICKFLG_DEC_X) && pos >= info->old_pos) {
                    continue;
                }
            }
            if (flags & (GW_PICKFLG_INC_Y|GW_PICKFLG_DEC_Y)) {
                float pos = l->verts[j*2+1] * l->yscale + l->yoffset;
                if ((flags & GW_PICKFLG_INC_Y) && pos <= info->old_pos) {
                    continue;
                }
                if ((flags & GW_PICKFLG_DEC_Y) && pos >= info->old_pos) {
                    continue;
                }
            }
        }

        info->line = line;
        info->vert = j;
        info->win_dist_squared = d;
        info->win_x = dx + win_x;
        info->win_y = dy + win_y;
    }
}

//===========================================================================
// gwinLinePickPoint()
//===========================================================================
int gwinLinePickPoint(
                    GWindow *gw,
                    int     line,
                    GWinPickInfo *info,
                    float   win_x,
                    float   win_y,
                    unsigned int flags) // from GWinPickFlagsEnum
{
    info->gw = gw;
    info->line = -1;
    info->vert = -1;
    info->win_x = win_x;
    info->win_y = win_y;
    info->win_dist_squared = MAXFLOAT;

    gwinLinePickPointSub(info, line, win_x, win_y, flags);
    return (info->win_dist_squared < MAXFLOAT);
}

//===========================================================================
// gwinPickPoint()
//===========================================================================
int gwinPickPoint(
                    GWindow *gw,
                    GWinPickInfo *info,
                    float   win_x,
                    float   win_y,
                    unsigned int flags) // from GWinPickFlagsEnum
{
    GWinLineNode *lnp;
    info->gw = gw;
    info->line = -1;
    info->vert = -1;
    info->win_x = win_x;
    info->win_y = win_y;
    info->win_dist_squared = MAXFLOAT;

    for (lnp=gw->line_head.prev; lnp != &gw->line_head; lnp=lnp->prev) {
        GWinLine *l = (GWinLine*)lnp;
        gwinLinePickPointSub(info, l->id, win_x, win_y, flags);
    }
    return (info->win_dist_squared < MAXFLOAT);
}

//===========================================================================
// gwinCursorPrintLine()
//===========================================================================
static void gwinCursorPrintLine(
                GWindow *gw, 
                GWinCursor *curs, 
                int line,
                float x, float y)
{
    int r,g,b;
    GWinLine *l = gw->lines[line];
    if (!l->name) return;
    r = l->r * 255;
    g = l->g * 255;
    b = l->b * 255;
    r = r<0?0:r>255?255:r;
    g = g<0?0:g>255?255:g;
    b = b<0?0:b>255?255:b;
    r = (r<<16)|(g<<8)|b;
    printf("Cursor: [%2d] %-*s  x=%10.6f  y=%10.6f   %06x\n",
                    line,
                    gw->lineNameLen,l->name,
                    x,
                    y,
                    r);
}

//===========================================================================
// gwinCursorPrint()
//===========================================================================
static void gwinCursorPrint(GWindow *gw, GWinCursor *curs)
{
    GWinCursor *curs2;
    GWinPickInfo info[1];
    int i;
    unsigned int flags = 0;
    float win_x = 0;
    float win_y = 0;
    float dmin = MAXFLOAT;
    int n=0;

    memset(info,0,sizeof(info[0]));
    info->gw = gw;

    if (curs->is_vertical) {
        if (curs->pos > gw->xmax) {
            curs->pos = gw->xmax;
            gw->redraw = 1;
        }
        if (curs->pos < gw->xmin) {
            curs->pos = gw->xmin;
            gw->redraw = 1;
        }
        win_x = gwinXToWin(gw,curs->pos);
        flags |= GW_PICKFLG_IGNORE_Y;
    } else {
        if (curs->pos > gw->ymax) {
            curs->pos = gw->ymax;
            gw->redraw = 1;
        }
        if (curs->pos < gw->ymin) {
            curs->pos = gw->ymin;
            gw->redraw = 1;
        }
        win_y = gwinYToWin(gw,curs->pos);
        flags |= GW_PICKFLG_IGNORE_X;
    }


    //
    // find closest point on each line
    //
    for (i=0; i<gw->lineCnt; i++) {
        GWinLine *l = gw->lines[i];
        if (!l) continue;

        if (gw->selectLine >= 0 && !l->selected) continue;

        info->win_dist_squared = MAXFLOAT;
        info->line = -1;
        gwinLinePickPointSub(info, i, win_x, win_y, flags);
        l->curs_d = info->win_dist_squared;
        l->curs_vert = info->vert;
        dmin = dmin < l->curs_d ? dmin : l->curs_d;
    }

    //
    // print line points near cursor
    //
    if (dmin <= 4) {
        for (i=0; i<gw->lineCnt; i++) {
            int line;
            GWinLine *l = gw->lines[i];
            int j;
            if (!l) continue;
            if (!l->name) continue;

            if (l->curs_d < 0) continue;

            if (l->curs_d > dmin) continue;

            line = i;
            for (j=i+1; j<gw->lineCnt; j++) {
                GWinLine *l2 = gw->lines[j];
                if (!l2) continue;
                if (l2->curs_d < 0) continue;
                if (!l2->name) continue;
                if (strcmp(l->name, l2->name)) continue;

                if (l2->curs_d < l->curs_d) {
                    l->curs_d = -1.0;
                    l = l2;
                    line = j;
                } else {
                    l2->curs_d = -1.0;
                }
            }
            gwinCursorPrintLine( gw, curs, line,
                    l->verts[l->curs_vert*2+0],
                    l->verts[l->curs_vert*2+1]);
            n++;
            l->curs_d = -1.0;
        }
    }

    for (curs2=gw->cursors; curs2; curs2 = curs2->next) {
        if (curs2 == curs) continue;
        if (curs2->is_vertical != curs->is_vertical) continue;
        printf("Delta %s: %10.6f\n",
                    curs->is_vertical ? "x" : "y",
                    curs->pos - curs2->pos);
    }

    if (!n) {
        printf("Cursor: %s=%10.6f\n",
                    curs->is_vertical ? "x" : "y",
                    curs->pos);
    }

    printf("\n");
}

//===========================================================================
// gwinCursorSnap()
//===========================================================================
//
// showinfo:
//     0 = never
//     1 = if x changes
//     2 = always
//
void gwinCursorSnap(GWindow *gw,
                            GWinCursor *curs,
                            int inc,
                            int showinfo,
                            float win_x,
                            float win_y)
{
    float pos;
    GWinPickInfo info[1];
    GWinLine *l;
    unsigned int flags = GW_PICKFLG_NAMED;

    if (inc) {
        info->old_pos = curs->pos;
        if (curs->is_vertical) {
            win_x = gwinXToWin(gw, curs->pos);
            win_y = 0;
            flags |= (inc < 0) ? GW_PICKFLG_DEC_X : GW_PICKFLG_INC_X;
            flags |= GW_PICKFLG_IGNORE_Y;
        } else {
            win_x = 0;
            win_y = gwinYToWin(gw, curs->pos);
            flags |= (inc < 0) ? GW_PICKFLG_DEC_Y : GW_PICKFLG_INC_Y;
            flags |= GW_PICKFLG_IGNORE_X;
        }
    } else {

        if (curs->is_vertical) {
            flags |= GW_PICKFLG_IGNORE_Y;
        }
    }
    gwinPickPoint(gw, info, win_x, win_y, flags);

    l = gwinGetLine(gw,info->line);
    if (!l) {
        if (showinfo == 2) {
            printf("No vertex found\n");
        }
        return;
    }

    if (curs->is_vertical) {
        pos = l->verts[info->vert*2+0] * l->xscale + l->xoffset;
        pos = pos > gw->xmin ? pos : gw->xmin;
        pos = pos < gw->xmax ? pos : gw->xmax;
    } else {
        pos = l->verts[info->vert*2+1] * l->yscale + l->yoffset;
        pos = pos > gw->ymin ? pos : gw->ymin;
        pos = pos < gw->ymax ? pos : gw->ymax;
    }

    if (curs->pos != pos) {
        curs->pos  = pos;
        gw->redraw = 1;
    } else if (showinfo == 1) {
        return;
    }
    if (!showinfo) return;

    gwinCursorPrint(gw, curs);

}

//===========================================================================
// gwinCursorMove()
//===========================================================================
void gwinCursorMove(GWindow *gw, GWinCursor *curs, float pos)
{
    curs->pos = pos;
    gw->redraw = 1;
}


//===========================================================================
// gwinCursorCreate()
//===========================================================================
GWinCursor *gwinCursorCreate(GWindow *gw,
                                int vertical,
                                float x,
                                float y,
                                float r,
                                float g,
                                float b)
{
    GWinCursor *curs = calloc(1,sizeof(GWinCursor));
    curs->is_vertical      = vertical;
    curs->color[0] = r;
    curs->color[1] = g;
    curs->color[2] = b;

    curs->next = gw->cursors;
    gw->cursors= curs;

    gw->redraw = 1;

    if (vertical) {
        if (gw->curs_current_v) {
            gw->curs_prev_v = gw->curs_current_v;
        }
        gw->curs_current_v = curs;
        curs->pos      = x;
    } else {
        if (gw->curs_current_h) {
            gw->curs_prev_h = gw->curs_current_h;
        }
        gw->curs_current_h = curs;
        curs->pos      = y;
    }

    return curs;
}

//===========================================================================
// gwinCursorGetCurrent()
//===========================================================================
GWinCursor *gwinCursorGetCurrent(GWindow *gw, int vertical)
{
    GWinCursor *curs = vertical ? gw->curs_current_v : gw->curs_current_h;

    if (!curs) {
        curs = gwinCursorCreate(gw,vertical,
                        (gw->xmax + gw->xmin) * 0.5,
                        (gw->ymax + gw->ymin) * 0.5,
                        0,0,0);
    }
    return curs;
}

//===========================================================================
// gwinCursorDelete()
//===========================================================================
void gwinCursorDelete(GWindow *gw,GWinCursor *curs)
{
    GWinCursor **p = &gw->cursors;

    while(*p) {
        if (*p == curs) {
            *p = curs->next;
            break;
        }
        p = &(*p)->next;
    }
    if (gw->curs_current_h == curs) {
        gw->curs_current_h  = 0;
    }
    if (gw->curs_current_v == curs) {
        gw->curs_current_v  = 0;
    }
    free(curs);
    gw->redraw = 1;
}

//===========================================================================
// gwinLineSelect()
//===========================================================================
void gwinLineSelect(GWindow *gw, int line, int quiet)
{
    GWinLine *l = gwinGetLine(gw,line);
    float xmin, xmax;
    float ymin, ymax;
    double xsum, ysum;
    float *vp;
    GWinPickInfo info[1];
    int i;

    gw->redraw = 1;
    if (!l) {
        if (!quiet) {
            printf("No line selected\n");
        }
        gw->selectLine = -1;
        for (i=0; i<gw->lineCnt; i++) {
            GWinLine *l2 = gw->lines[i];
            if (l2) {
                l2->selected = 0;
            }
        }
        return;
    }

    printf("Select line [%d] %s\n",
        line,
        l->name);
    gw->selectLine = line;

    for (i=0; i<gw->lineCnt; i++) {
        GWinLine *l2 = gw->lines[i];
        if (!l2 || l2 == l) {
        } if (!l->name || !l2->name || strcmp(l2->name, l->name)) {
            l2->selected = 0;
        } else {
            l2->selected = 1;
            gwinLineToFront(gw, l2->id);
        }
    }

    l->selected = 1;
    gwinLineToFront(gw, line);

    if (quiet) return;

    if (gw->curs_prev_v &&
        gwinLinePickPoint(
                    gw,
                    line,
                    info,
                    gw->curs_prev_v->pos,
                    0,
                    GW_PICKFLG_IGNORE_Y)) {
        gwinCursorPrintLine( gw, gw->curs_prev_v, line,
                    l->verts[info->vert*2+0],
                    l->verts[info->vert*2+1]);
    }


    if (gw->curs_current_v &&
        gwinLinePickPoint(
                    gw,
                    line,
                    info,
                    gw->curs_current_v->pos,
                    0,
                    GW_PICKFLG_IGNORE_Y)) {
        gwinCursorPrintLine( gw, gw->curs_current_v, line,
                    l->verts[info->vert*2+0],
                    l->verts[info->vert*2+1]);
    }

    if (!gw->curs_current_v) return;
    if (!gw->curs_prev_v) return;

    xmax = gw->curs_current_v->pos;
    xmin = gw->curs_prev_v->pos;

    printf("  dx   = %10.6f\n",xmax-xmin);

    if (xmin > xmax) {
        float t = xmax;
        xmax = xmin;
        xmin = t;
    }
    
    ymin = 1.0;
    ymax = -1.0;
    vp = l->verts;
    for (i=0; i<l->vertCnt; i++, vp+=2) {
        float x  = vp[0];
        float y  = vp[1];
        float xn = vp[0];
        float yn = vp[1];
        float dx = 0;
        if (i<l->vertCnt-1) {
            xn = vp[2];
            yn = vp[3];
            if (x < xn) {
                dx = xn-x;
            }
        }
        if (xn >= xmin && x <= xmax) {
            if (ymin > ymax) {
                ymin = ymax = y;
                ysum = y * dx;
                xsum = dx;
            } else {
                ymin = (ymin<y)?ymin:y;
                ymax = (ymax>y)?ymax:y;
                ysum += y * dx;
                xsum += dx;
            }
        }

    }
    if (ymin <= ymax) {
        printf("  ymax = %10.6f\n",ymax);
        printf("  ymed = %10.6f\n",(ymax+ymin)*0.5);
        printf("  ymin = %10.6f\n",ymin);
        if (xsum <= 0) {
            ysum = 0;
            xsum = 1;
        }
        printf("  yavg = %10.6f\n",ysum/xsum);
    }
}

//===========================================================================
// gwinSetCallback()
//===========================================================================
void gwinSetCallback(GWindow *gw, GWinCBFunc func, void *data)
{
    gw->cb_func = func;
    gw->cb_data = data;
}

//===========================================================================
// gwinHandleEvent() - handle one event
//===========================================================================
// Note: call gwinMakeCurrent before calling this
static void gwinHandleEvent(GWindow *gw, XEvent *event)
{
    GWinCursor *curs;
    static int event_debug = 0;
    float wx, wy, pos;

    switch(event->type) {
        case ConfigureNotify:
            glViewport(0,0,
                event->xconfigure.width,
                event->xconfigure.height);
            gw->win_width = event->xconfigure.width;
            gw->win_height = event->xconfigure.height;
            gw->resize = 1;
            gw->redraw = 1;
            break;

        case KeyPress:
            if (event_debug) {
                printf("Event: key    pressed  state=0x%08x code=0x%0x\n",
                    event->xkey.state,
                    event->xkey.keycode);
            }
            break;
        case KeyRelease:
            if (event_debug) {
                printf("Event: key    released state=0x%08x code=0x%0x\n",
                    event->xkey.state,
                    event->xkey.keycode);
            }
            if (gw->resize) {
                gwinCalcScales(gw);
            }
            switch(event->xkey.keycode) {
                case 0x3d:  // ? or /
                    printf("\n\n"
                        " Key commands\n"
                        " ------------\n"
                        " q    quit\n"
                        " l    run callback func (look)\n"
                        " x/X  zoom in/out x\n"
                        " y/Y  zoom in/out y\n"
                        " z    reset zoom\n"
                        " c    center graph\n"
                        " v    vertical cursor (V=save)\n"
                        " h    horizontal cursor (H=save)\n"
                        " s/S  select/unselect line\n"
                        " o/O  obscure selected (O=unobscure all)\n"
                        " ->   cursor right\n"
                        " <-   cursor left\n"
                        " e    erase black cursors\n"
                        " E    erase all cursors\n"
                        " f/F  put selected line in front/back\n"
                        " 1-9  choose float variable to modify\n"
                        " </>  modify float variable\n"
                        " ?    help (/ works too)\n"
                        " ;    toggle event debug mode\n"
                        );
                    break;
                case 0x2f:  // ;
                    event_debug = !event_debug;
                    break;
                case 0x2e:  // l
                    if (gw->cb_func) {
                        gw->cb_func(gw,gw->cb_data);
                    }
                    break;
                case 0x62:  // up arrow
                    curs = gwinCursorGetCurrent(gw, 0);
                    if (event->xkey.state & 4) {
                        if (event->xkey.state & 1) {
                            curs->pos += gw->y_drag_scale * 10;
                        } else {
                            curs->pos += gw->y_drag_scale;
                        }
                        gwinCursorPrint(gw,curs);
                        gw->redraw = 1;
                    } else {
                        gwinCursorSnap(gw, curs,  1, 2, 0,0);
                    }
                    break;
                case 0x68:  // down
                    curs = gwinCursorGetCurrent(gw, 0);
                    if (event->xkey.state & 4) {
                        if (event->xkey.state & 1) {
                            curs->pos -= gw->y_drag_scale * 10;
                        } else {
                            curs->pos -= gw->y_drag_scale;
                        }
                        gwinCursorPrint(gw,curs);
                        gw->redraw = 1;
                    } else {
                        gwinCursorSnap(gw, curs, -1, 2, 0,0);
                    }
                    break;
                case 0x66:  // ->
                    curs = gwinCursorGetCurrent(gw, 1);
                    if (event->xkey.state & 4) {
                        if (event->xkey.state & 1) {
                            curs->pos += gw->x_drag_scale * 10;
                        } else {
                            curs->pos += gw->x_drag_scale;
                        }
                        gwinCursorPrint(gw,curs);
                        gw->redraw = 1;
                    } else {
                        gwinCursorSnap(gw, curs, 1, 2, 0,0);
                    }
                    break;
                case 0x64:  // <-
                    curs = gwinCursorGetCurrent(gw, 1);
                    if (event->xkey.state & 4) {
                        if (event->xkey.state & 1) {
                            curs->pos -= gw->x_drag_scale * 10;
                        } else {
                            curs->pos -= gw->x_drag_scale;
                        }
                        gwinCursorPrint(gw,curs);
                        gw->redraw = 1;
                    } else {
                        gwinCursorSnap(gw, curs, -1, 2, 0,0);
                    }
                    break;
                case 0x18:  // q
                    gw->loop = 0;
                    break;
                case 0x34:  // z
                    gw->xscale = 1.0;
                    gw->yscale = 1.0;
                    gw->xoffset = 0;
                    gw->yoffset = 0;
                    gw->redraw = 1;
                    gw->resize = 1;
                    break;
                case 0x35:  // x
                    wx = event->xkey.x;
                    pos = gwinXFromWin(gw, wx);
                    if (event->xkey.state & 1) {
                        gw->xscale *= 0.8f;
                        if (gw->xscale < 1.0) gw->xscale = 1.0;
                    } else {
                        gw->xscale *= 1.25;
                    }
                    gwinCalcScales(gw);
                    gw->xoffset -= gwinXFromWin(gw, wx) - pos;
                    gw->redraw = 1;
                    break;
                case 0x1d:  // y
                    wy = gw->win_height - event->xkey.y;
                    pos = gwinYFromWin(gw, wy);
                    if (event->xkey.state & 1) {
                        gw->yscale *= 0.8f;
                        if (gw->yscale < 1.0) gw->yscale = 1.0;
                    } else {
                        gw->yscale *= 1.25;
                    }
                    gwinCalcScales(gw);
                    gw->yoffset -= gwinYFromWin(gw, wy) - pos;
                    gw->redraw = 1;
                    break;
                case 0x36:  // c
                    gw->xoffset = gw->xoffset_max * 0.5;;
                    gw->yoffset = gw->yoffset_max * 0.5;;
                    gw->redraw = 1;
                    break;
                case 0x1a:  // e
                    if (event->xkey.state & 1) {
                        while(gw->cursors) {
                            gwinCursorDelete(gw,gw->cursors);
                        }
                    } else {
                        GWinCursor *curs = gw->cursors;
                        while(curs) {
                            if (curs == gw->curs_current_h ||
                                curs == gw->curs_current_v ||
                                curs == gw->curs_prev_h ||
                                curs == gw->curs_prev_v) {
                                curs = curs->next;
                            } else {
                                gwinCursorDelete(gw,curs);
                                curs = gw->cursors;
                            }
                        }
                    }
                    break;
                case 0x38:  // b
                    if (gw->curs_current_v) {
                        gw->curs_prev_v = gw->curs_current_v;
                    }
                    gw->curs_current_v = 0;
                    gw->redraw = 1;
                    break;
                case 0x37:  // v
                    wx = event->xkey.x;
                    wy = gw->win_height - event->xkey.y;
                    curs = gwinCursorGetCurrent(gw, 1);
                    gwinCursorSnap(gw, curs, 0, 1, wx, wy);
                    break;
                case 0x2c:  // j
                    if (gw->curs_current_h) {
                        gw->curs_prev_h = gw->curs_current_h;
                    }
                    gw->curs_current_h = 0;
                    gw->redraw = 1;
                    break;
                case 0x2b:  // h
                    wx = event->xkey.x;
                    wy = gw->win_height - event->xkey.y;
                    curs = gwinCursorGetCurrent(gw, 0);
                    gwinCursorSnap(gw, curs, 0, 1, wx, wy);
                    break;
                case 0x29:  // f
                    wx = event->xkey.x;
                    wy = gw->win_height - event->xkey.y;
                    {
                        int line = gw->selectLine;
                        if (line < 0) {
                            GWinPickInfo info[1];
            
                            gwinPickPoint(gw,info,wx,wy,0);
                            line = info->line;
                        }
                        if (line < 0) {
                            printf("No line found\n");
                        } else {
                            GWinLine *l = gwinGetLine(gw,line);
                            int toback = event->xkey.state & 1;
                            if (l == (GWinLine*)gw->line_head.prev) {
                                toback = 1;
                            }
                            printf("Move line [%d] %s to ",
                                line,
                                gw->lines[line]->name);
                            if (l && l->name) {
                                int i;
                                for (i=0; i<gw->lineCnt; i++) {
                                    GWinLine *l2 = gw->lines[i];
                                    if (!l || l2 == l) {
                                    } if (!l->name ||
                                        strcmp(l2->name, l->name)) {
                                    } else {
                                        if (toback) {
                                            gwinLineToBack(gw, l2->id);
                                        } else {
                                            gwinLineToFront(gw, l2->id);
                                        }
                                    }
                                }
                            }
                            if (toback) {
                                printf("Back\n");
                                gwinLineToBack(gw, line);
                            } else {
                                printf("Front\n");
                                gwinLineToFront(gw, line);
                            }
                            gw->redraw = 1;
                        }
                    }
                    break;
                case 0x27:  // s select
                    wx = event->xkey.x;
                    wy = gw->win_height - event->xkey.y;
                    gw->redraw = 1;
                    if ((event->xkey.state & 1) == 0) {
                        GWinPickInfo info[1];
                        int flags = GW_PICKFLG_UNHIDDEN |
                                    GW_PICKFLG_UNSELECTED;
        
                        if ((event->xkey.state & 4)==0) {
                            flags |= GW_PICKFLG_NAMED;
                        }
                        gwinPickPoint(gw,info,wx,wy,flags);
                        gwinLineSelect(gw,info->line,0);
                    } else {
                        gwinLineSelect(gw,-1,0);
                    }
                    break;
                case 0x20:  // o obscure
                    if (event->xkey.state & 1) {
                        int i;
                        for (i=0; i<gw->lineCnt; i++) {
                            gwinLineHide(gw, i, 0);
                        }
                    } else if (gw->selectLine != -1) {
                        int i;
                        for (i=0; i<gw->lineCnt; i++) {
                            GWinLine *l2 = gw->lines[i];
                            if (l2 && l2->selected) {
                                l2->selected = 0;
                                gwinLineHide(gw, l2->id, 1);
                            }
                        }
                        gw->selectLine = -1;
                    }
                    break;
                case 0x14:
                    if (gw->argCurrent) {
                        GWinArgInfo *ai = gw->argCurrent->prev;
                        while(ai->arg.type != GW_ARGTYPE_VAR &&
                            ai != gw->argCurrent) ai = ai->prev;
                        printf("Modify arg: %s\n",ai->desc.name);
                    }
                    break;
                case 0x15:
                    if (gw->argCurrent) {
                        GWinArgInfo *ai = gw->argCurrent->next;
                        while(ai->arg.type != GW_ARGTYPE_VAR &&
                            ai != gw->argCurrent) ai = ai->next;
                        printf("Modify arg: %s\n",ai->desc.name);
                    }
                    break;
                case 0x3b:
                    if (gw->argCurrent && 
                        gw->argCurrent->arg.type == GW_ARGTYPE_VAR) {
                        GWinArgInfo *ai = gw->argCurrent;

                        ai->arg.v.val *= 0.8;
                        printf("current var: %s = %10.5f\n",
                            ai->desc.name,
                            ai->arg.v.val);
                        gw->recalc = 1;
                        gw->redraw = 1;
                    }
                    break;
                case 0x3c:
                    if (gw->argCurrent && 
                        gw->argCurrent->arg.type == GW_ARGTYPE_VAR) {
                        GWinArgInfo *ai = gw->argCurrent;

                        ai->arg.v.val *= 1.25;
                        printf("current var: %s = %10.5f\n",
                            ai->desc.name,
                            ai->arg.v.val);
                        gw->recalc = 1;
                        gw->redraw = 1;
                    }
                    break;
                case 0x22:
                    gw->xoffset -= gw->x_drag_scale * (gw->win_width * 0.1);
                    gw->redraw = 1;
                    break;
                case 0x23:
                    gw->xoffset += gw->x_drag_scale * (gw->win_width * 0.1);
                    gw->redraw = 1;
                    break;
            }
            break;
        case ButtonPress:
            wx = event->xbutton.x;
            wy = gw->win_height - event->xbutton.y;
            if (event_debug) {
                printf("Event: button pressed  state=0x%08x butn=0x%0x "
                        "x=%-4d y=%d\n",
                    event->xbutton.state,
                    event->xbutton.button,
                    (int)wx,(int)wy);
            }
            if (event->xbutton.button == 1) {
                gw->x_drag = wx;
                gw->y_drag = wy;
                gw->is_drag = 1;
            }
            break;
        case ButtonRelease:
            wx = event->xbutton.x;
            wy = gw->win_height - event->xbutton.y;
            if (event_debug) {
                printf("Event: button released state=0x%08x butn=0x%0x "
                        "x=%-4d y=%d\n",
                    event->xbutton.state,
                    event->xbutton.button,
                    (int)wx,(int)wy);
            }
            if (!gw->is_drag) {
                break;
            }
            gw->is_drag = 0;

            if (0) {
        case MotionNotify:
                wx = event->xmotion.x;
                wy = gw->win_height - event->xmotion.y;
                if (event_debug) {
                    printf("Event: pointer moved x=%d y=%d  hint=%d\n",
                        (int)wx,
                        (int)wy,
                        event->xmotion.is_hint);
                }
                if (gw->resize) {
                    gwinCalcScales(gw);
                }
            }
            gw->xoffset += gw->x_drag_scale * (gw->x_drag - wx);
            gw->yoffset += gw->y_drag_scale * (gw->y_drag - wy);
            gw->x_drag = wx;
            gw->y_drag = wy;
            gw->redraw = 1;
            break;

        case Expose:
            gw->redraw = 1;
            break;
    }
}

//===========================================================================
// gwinHandleArgFile() - read arg file
//===========================================================================
void gwinHandleArgFile(GWindow *gw)
{
    if (gw->argFile) {
        while(1) {
            int c = getc(gw->argFile);
            if (c == EOF) break;
            printf("file:%c\n",c);
            gw->redraw = 1;
        }
    }
}

//===========================================================================
// gwinHandleEvents() - handle pending events and then return
//===========================================================================
void gwinHandleEvents(GWindow *gw)
{
    XEvent event;
    gwinMakeCurrent(gw);
    while(XPending(gw->dpy)) {
        XNextEvent(gw->dpy, &event);
        gwinHandleEvent(gw,&event);
    }

#if 0
    gwinHandleArgFile(gw);
#endif
    if (gw->redraw) {
        gwinDraw(gw);
    }
}

//===========================================================================
// gwinEventLoop() - wait for events and handle them
//===========================================================================
void gwinEventLoop(GWindow *gw)
{
    XEvent event;
    gwinMakeCurrent(gw);
    gw->loop = 1;
    while(gw->loop) {
        while(XPending(gw->dpy)) {
            XNextEvent(gw->dpy, &event);
            gwinHandleEvent(gw,&event);
        }
        if (!gw->loop) break;
        if (gw->redraw) {
            gwinDraw(gw);
        }
#if 0
        gwinHandleArgFile(gw);
#endif
        XNextEvent(gw->dpy, &event);
        gwinHandleEvent(gw,&event);
    }
}

#ifdef GRAPHTEST
//===========================================================================
// main()
//===========================================================================
int main(int argc, char *argv[])
{
    int la;
    int lb;
    int lc;
    GWindow *gw = gwinCreate();
    gwinSetArgs(gw, argc,argv);
    gwinOpen(gw);

    la = gwinLineCreate(gw);
    gwinLineColor(gw,la,0xff00ff);
    gwinLinePoint(gw,la,0.0,0.0);
    gwinLinePoint(gw,la,0.3,0.4);
    gwinLinePoint(gw,la,0.4,0.2);
    gwinLinePoint(gw,la,0.5,0.2);
    gwinLinePoint(gw,la,0.7,0.5);

    lb = gwinLineCreate(gw);
    gwinLineColor(gw,lb,0x0000ff);
    gwinLinePoint(gw,lb,0.0,0.2);
    gwinLinePoint(gw,lb,0.9,0.4);
    gwinLinePoint(gw,lb,2.4,0.2);

    lc = gwinLineCreate(gw);
    gwinLineColor(gw,lc,0xffff00);
    gwinLinePoint(gw,lc,0.0,0.9);
    gwinLinePoint(gw,lc,0.8,0.4);
    gwinLinePoint(gw,lc,1.4,0.2);

    while(1) {
        gwinHandleEvents(gw);
        usleep(1000000);
    }

    return 0;
}
#endif

This file Copyright (C) 2006 by Nathan (Acorn) Pooley
Go to TOP Wand page
Go to Acorn's personal webpage
Go to Hogwarts website: www.gotohogwarts.com
Snout: www.snout.org/game
Gadgets of Justice Unlimited
Snout GC (Wiki)
Snout Wiki
File created by do_doc at Wed May 30 03:28:13 PDT 2007