/****************************************************************************
**
*A  gapgraph.c                  XGAP Source                      Frank Celler
**
*H  @(#)$Id: gapgraph.c,v 1.3 1993/10/18 11:04:47 fceller Exp $
**
*Y  Copyright 1993-1995,   Frank Celler,   Lehrstuhl D fuer Mathematik,  RWTH
**
*H  $Log: gapgraph.c,v $
*H  Revision 1.3  1993/10/18  11:04:47  fceller
*H  added fast updated,  fixed timing problem
*H
*H  Revision 1.2  1993/08/12  14:34:00  fceller
*H  fixed missing parameter
*H
*H  Revision 1.1  1993/08/12  13:47:41  fceller
*H  fixed resize
*H
*H  Revision 1.0  1993/04/05  11:42:18  fceller
*H  Initial revision
*/
#include    <X11/X.h>
#include    <X11/Xlib.h>
#include    <X11/IntrinsicP.h>
#include    <X11/StringDefs.h>

#include    "utils.h"
#include    "gapgraph.h"


/****************************************************************************
**
*V  GcClear . . . . . . . . . . . . . . . . . . . . . . . used to clear areas
*/
static GC GcClear;


/****************************************************************************
**
*F  GapGraphInitialize( <request>, <new>, <args>, <nums> )  open a new window
*/
static void GapGraphInitialize ( request, new, args, nums )
    Widget              request;
    Widget              new;
    ArgList             args;
    Cardinal          * nums;
{
    GapGraphicWidget	w = (GapGraphicWidget) new;
    XRectangle          rec[1];
    XGCValues           val;
    Display           * dis;
    static int          first = 1;

    /* store width and height of our new window */
    w->gap_graphic.width  = request->core.width;
    w->gap_graphic.height = request->core.height;
    w->gap_graphic.update = True;

    /* create a new list for the graphic objects */
    w->gap_graphic.objs = List(0);

    /* create a pixmap the picture */
    dis = XtDisplay(new);

    /* set display */
    w->gap_graphic.display = dis;

    /* create a graphic context for this window */
    val.function      = GXcopy; 
    val.plane_mask    = AllPlanes;
    val.foreground    = BlackPixel( dis, DefaultScreen(dis) );
    val.background    = WhitePixel( dis, DefaultScreen(dis) );
    val.line_width    = 0;
    val.line_style    = LineSolid;
    val.cap_style     = CapRound;
    val.fill_style    = FillSolid;
    w->gap_graphic.gc = XCreateGC(
		           dis, DefaultRootWindow(dis),
                           GCFunction     | GCPlaneMask | GCForeground
                           | GCBackground | GCLineWidth | GCLineStyle
                           | GCCapStyle   | GCFillStyle,  &val );

    /* if this is the first initialize the global <GcClear> */
    if ( first )
    {
	first = 0;
	val.function   = GXcopy;
        val.plane_mask = AllPlanes;
	val.foreground = WhitePixel( dis, DefaultScreen(dis) );
	val.background = BlackPixel( dis, DefaultScreen(dis) );
	GcClear = XCreateGC(
		      dis, DefaultRootWindow(dis),
                      GCFunction|GCPlaneMask|GCForeground|GCBackground,
		      &val );
    }

    /* our viewport could be large then the window */
    rec->x = 0;
    rec->y = 0;
    rec->width  = w->gap_graphic.width;
    rec->height = w->gap_graphic.height;
    XSetClipRectangles( dis, w->gap_graphic.gc, 0, 0, rec, 1, YXSorted );
}


/****************************************************************************
**
*F  GapGraphDestroy( <w> )  . . . . . . . . . . . . . . . .  destroy a window
*/
static void GapGraphDestroy ( w )
    Widget              w;
{
    GGFreeGapGraphicObjects(w);
}


/****************************************************************************
**
*F  GapGraphResize( <w> ) . . . . . . . . . . . . . .  ignore resize requests
*/
static void GapGraphResize ( w )
    Widget              w;
{
    GapGraphicWidget	gap = (GapGraphicWidget) w;

    gap->core.width  = gap->gap_graphic.width;
    gap->core.height = gap->gap_graphic.height;
}


/****************************************************************************
**
*F  GapGraphExpose( <w>, <evt> )  . . . . . . . . . . . .  handle an exposure
*/
static void GapGraphExpose ( w, evt )
    Widget                  w;
    XExposeEvent          * evt;
{
    GapGraphicWidget        gap = (GapGraphicWidget) w;
    TypeList                objs = gap->gap_graphic.objs;
    TypeGapGraphicObject  * obj;
    long	            x1,  y1,  x2,  y2;
    long                    i;

    /* get the rectangle to be exposed */
    x1 = evt->x;           y1 = evt->y;
    x2 = x1+evt->width-1;  y2 = y1+evt->height-1;

    /* make a sanity check for the values */
    if ( x1 < 0 )  x1 = 0;
    if ( x2 < 0 )  return;
    if ( y1 < 0 )  y1 = 0;
    if ( y2 < 0 )  return;
    if ( gap->gap_graphic.width  <= x1 )  return;
    if ( gap->gap_graphic.width  <= x2 )  x2 = gap->gap_graphic.width-1;
    if ( gap->gap_graphic.height <= y1 )  return;
    if ( gap->gap_graphic.height <= y2 )  y2 = gap->gap_graphic.height-1;

    /* and clear it */
    XFillRectangle( gap->gap_graphic.display, XtWindow(gap), GcClear,
		    x1, y1, x2-x1+1, y2-y1+1 );

    /* redraw only objects inside the rectangle */
    for ( i = 0;  i < objs->len;  i++ )
	if ( objs->ptr[i] != 0 )
	{
	    obj = (TypeGapGraphicObject*) objs->ptr[i];
	    if (    obj->x+obj->w < x1
		 || obj->y+obj->h < y1
		 || x2 < obj->x
		 || y2 < obj->y )
		continue;
	    GGDrawObject( w, (TypeGapGraphicObject*) objs->ptr[i], True );
	}
    XFlush( gap->gap_graphic.display );
    return;
}


/****************************************************************************
**
*V  gapGraphicWidgetClass . . . . . . . . . . . . . . . . widget class record
*/
GapGraphicClassRec gapGraphicClassRec =
{
  { 
    /* core fields              */
    /* superclass		*/	(WidgetClass) &widgetClassRec,
    /* class_name		*/	"GapGraphic",
    /* widget_size		*/	sizeof(GapGraphicRec),
    /* class_initialize		*/	NULL,
    /* class_part_initialize	*/	NULL,
    /* class_inited		*/	FALSE,
    /* initialize		*/	GapGraphInitialize,
    /* initialize_hook		*/	NULL,
    /* realize			*/	XtInheritRealize,
    /* actions			*/	NULL,
    /* num_actions		*/	0,
    /* resources		*/	NULL,
    /* num_resources		*/	0,
    /* xrm_class		*/	NULLQUARK,
    /* compress_motion		*/	TRUE,
    /* compress_exposure	*/	TRUE,
    /* compress_enterleave	*/	TRUE,
    /* visible_interest		*/	FALSE,
    /* destroy			*/	GapGraphDestroy,
    /* resize			*/	GapGraphResize,
    /* expose			*/	GapGraphExpose,
    /* set_values		*/	NULL,
    /* set_values_hook		*/	NULL,
    /* set_values_almost	*/	XtInheritSetValuesAlmost,
    /* get_values_hook		*/	NULL,
    /* accept_focus		*/	NULL,
    /* version			*/	XtVersion,
    /* callback_private		*/	NULL,
    /* tm_table			*/	NULL,
    /* query_geometry		*/	XtInheritQueryGeometry,
    /* display_accelerator	*/	XtInheritDisplayAccelerator,
    /* extension		*/	NULL
  },

  {
    /* template fields          */
    /* dummy                    */      0
  }
};

WidgetClass gapGraphicWidgetClass = (WidgetClass)&gapGraphicClassRec;


/****************************************************************************
**

*F  GGDrawObject( <w>, <obj>, <flag> )  . . . . . . . . . . .  draw an object
*/
void GGDrawObject ( w, obj, flag )
    Widget	            w;
    TypeGapGraphicObject  * obj;
    Boolean                 flag;
{
    GapGraphicWidget        gap = (GapGraphicWidget) w;
    GC                      gc;

    if ( flag )
	gc = gap->gap_graphic.gc;
    else
	gc = GcClear;
    switch ( obj->type )
    {
	case T_LINE:
	    XSetLineAttributes( gap->gap_graphic.display, gc,
			        obj->desc.line.w, LineSolid,
			        CapButt, JoinRound );
	    XDrawLine( gap->gap_graphic.display, XtWindow(gap), gc,
		       obj->desc.line.x1, obj->desc.line.y1,
		       obj->desc.line.x2, obj->desc.line.y2 );
	    break;

	case T_CIRCLE:
	    XSetLineAttributes( gap->gap_graphic.display, gc,
			        obj->desc.circle.w, LineSolid,
			        CapButt, JoinRound );
	    XDrawArc( gap->gap_graphic.display, XtWindow(gap), gc,
		      obj->desc.circle.x, obj->desc.circle.y,
		      obj->desc.circle.r, obj->desc.circle.r,
		      0, 360*64 );
	    break;

	case T_DISC:
	    XFillArc( gap->gap_graphic.display, XtWindow(gap), gc,
		      obj->desc.disc.x, obj->desc.disc.y,
		      obj->desc.disc.r, obj->desc.disc.r,
		      0, 360*64 );
	    break;

	case T_RECT:
	    XSetLineAttributes( gap->gap_graphic.display, gc,
			        obj->desc.rect.w, LineSolid,
			        CapButt, JoinRound );
	    XDrawRectangle( gap->gap_graphic.display, XtWindow(gap), gc,
			    obj->desc.rect.x1,
			    obj->desc.rect.y1,
			    obj->desc.rect.x2-obj->desc.rect.x1,
			    obj->desc.rect.y2-obj->desc.rect.y1 );
	    break;

	case T_BOX:
	    XFillRectangle( gap->gap_graphic.display, XtWindow(gap), gc,
			    obj->desc.rect.x1,
			    obj->desc.rect.y1,
			    obj->desc.rect.x2-obj->desc.rect.x1+1,
			    obj->desc.rect.y2-obj->desc.rect.y1+1 );
	    break;

	case T_TEXT:
	    XSetFont( gap->gap_graphic.display, gc,
		      obj->desc.text.font );
	    XDrawString( gap->gap_graphic.display, XtWindow(gap), gc,
			 obj->desc.text.x, obj->desc.text.y,
			 obj->desc.text.str, obj->desc.text.len );
	    break;
    }
}


/****************************************************************************
**
*F  GGAddObject( <w>, <obj> )	. . . . . . . . . . add to widget and draw it
*/
long GGAddObject ( w, obj )
    Widget	            w;
    TypeGapGraphicObject  * obj;
{
    GapGraphicWidget        gap = (GapGraphicWidget) w;
    TypeList                objs = gap->gap_graphic.objs;
    long		    i;

    /* draw object */
    GGDrawObject( w, obj, True );

    /* find free position in object list */
    for ( i = 0;  i < objs->len;  i++ )
	if ( objs->ptr[i] == 0 )
	    break;
    if ( i < objs->len )
	objs->ptr[i] = (void*) obj;
    else
	AddList( objs, (void*) obj );
    return i;
}


/****************************************************************************
**
*F  GGFreeObject( <obj> )  . . . . . . . . . . . . free memory used by <obj>
*/
void GGFreeObject ( obj )
    TypeGapGraphicObject      * obj;
{
    switch ( obj->type )
    {
	case T_LINE:
	case T_CIRCLE:
	case T_DISC:
	    break;
	case T_TEXT:
	    XtFree(obj->desc.text.str);
	    break;
    }
    XtFree((char*)obj);
}
   

/****************************************************************************
**
*F  GGRemoveObject( <w>, <pos> ) . . . . . . . . . . remove and undraw <obj>
*/
int GGRemoveObject ( w, pos )
    Widget	            w;
    long                    pos;
{
    GapGraphicWidget        gap = (GapGraphicWidget) w;
    TypeList                objs = gap->gap_graphic.objs;
    TypeGapGraphicObject  * obj;
    XExposeEvent            evt;

    /* find graphic object and clear entry */
    if ( ( obj = (TypeGapGraphicObject*) objs->ptr[pos] ) == 0 )
	return 1;
    objs->ptr[pos] = 0;

    /* update this region */
    if ( gap->gap_graphic.fast_update )
    {
	if ( -1==gap->gap_graphic.lx || obj->x<gap->gap_graphic.lx )
	    gap->gap_graphic.lx = obj->x;
	if ( -1==gap->gap_graphic.ly || obj->y<gap->gap_graphic.ly )
	    gap->gap_graphic.ly = obj->y;
	if ( -1==gap->gap_graphic.hx || gap->gap_graphic.hx<=obj->x+obj->w )
	    gap->gap_graphic.hx = obj->x+obj->w+1;
	if ( -1==gap->gap_graphic.hy || gap->gap_graphic.hy<=obj->y+obj->h )
	    gap->gap_graphic.hy = obj->y+obj->h+1;
	GGDrawObject( w, obj, False );
    }
    else if ( gap->gap_graphic.update )
    {
	evt.x      = obj->x;
	evt.y      = obj->y;
	evt.width  = obj->w;
	evt.height = obj->h;
	GapGraphExpose( w, &evt );
    }
    else
    {
	if ( -1==gap->gap_graphic.lx || obj->x<gap->gap_graphic.lx )
	    gap->gap_graphic.lx = obj->x;
	if ( -1==gap->gap_graphic.ly || obj->y<gap->gap_graphic.ly )
	    gap->gap_graphic.ly = obj->y;
	if ( -1==gap->gap_graphic.hx || gap->gap_graphic.hx<=obj->x+obj->w )
	    gap->gap_graphic.hx = obj->x+obj->w+1;
	if ( -1==gap->gap_graphic.hy || gap->gap_graphic.hy<=obj->y+obj->h )
	    gap->gap_graphic.hy = obj->y+obj->h+1;
    }

    /* free memory and return */
    GGFreeObject(obj);
    return 0;
}


/****************************************************************************
**
*F  GGStartRemove( <w> ) . . . . . . . . . . . . start a sequence of removes
*/
void GGStartRemove ( w )
    Widget	            w;
{
    GapGraphicWidget        gap = (GapGraphicWidget) w;

    gap->gap_graphic.update = False;
    gap->gap_graphic.lx     = -1;
    gap->gap_graphic.hx     = -1;
    gap->gap_graphic.ly     = -1;
    gap->gap_graphic.hy     = -1;
}


/****************************************************************************
**
*F  GGStopRemove( <w> )  . . .  stop a sequence of removes and update window
*/
void GGStopRemove ( w )
    Widget	            w;
{
    GapGraphicWidget        gap = (GapGraphicWidget) w;
    XExposeEvent            evt;

    gap->gap_graphic.update = True;
    evt.x      = gap->gap_graphic.lx;
    evt.y      = gap->gap_graphic.ly;
    evt.width  = gap->gap_graphic.hx - gap->gap_graphic.lx + 1;
    evt.height = gap->gap_graphic.hy - gap->gap_graphic.ly + 1;
    GapGraphExpose( w, &evt );
}


/****************************************************************************
**
*F  GGFastUpdate( <w>, <flag> ) . . . . . . . . . . .  en/disable fast update
*/
void GGFastUpdate ( w, flag )
    Widget		w;
    Boolean		flag;
{
    GapGraphicWidget        gap = (GapGraphicWidget) w;
    XExposeEvent            evt;

    if ( gap->gap_graphic.fast_update == flag || !gap->gap_graphic.update )
	return;
    gap->gap_graphic.fast_update = flag;
    if ( !flag )
    {
	evt.x      = gap->gap_graphic.lx;
	evt.y      = gap->gap_graphic.ly;
	evt.width  = gap->gap_graphic.hx - gap->gap_graphic.lx + 1;
	evt.height = gap->gap_graphic.hy - gap->gap_graphic.ly + 1;
	GapGraphExpose( w, &evt );
    }
    else
    {
	gap->gap_graphic.lx = -1;
	gap->gap_graphic.hx = -1;
	gap->gap_graphic.ly = -1;
	gap->gap_graphic.hy = -1;
    }

}


/****************************************************************************
**
*F  GGFreeAllObjects( <w> )  . . . . .  remove and undraw all window objects
*/
void GGFreeAllObjects ( w )
    Widget	        w;
{
    GapGraphicWidget    gap = (GapGraphicWidget) w;
    TypeList            objs = gap->gap_graphic.objs;
    long                i;

    for ( i = 0;  i < objs->len;  i++ )
	if ( objs->ptr[i] != 0 )
	    GGFreeObject(objs->ptr[i]);
    XFillRectangle( gap->gap_graphic.display, XtWindow(gap), GcClear,
		    0, 0, gap->gap_graphic.width, gap->gap_graphic.height );
    objs->len = 0;
}


/****************************************************************************
**
*F  GGFreeGapGraphicObjects( <w> ) free all objects/list associated with <w>
*/
void GGFreeGapGraphicObjects ( w )
    Widget	w;
{
    GapGraphicWidget        gap = (GapGraphicWidget) w;
    TypeList                objs = gap->gap_graphic.objs;

    if ( objs == 0 )
	return;
    GGFreeAllObjects(w);
    XtFree((char*)(objs->ptr));
    XtFree((char*)(objs));
    gap->gap_graphic.objs = 0;
}


/****************************************************************************
**
*F  GGResize( <w> )  . . . . . . . . . . . . . . . . . . . . . .  resize <w>
*/
void GGResize ( w, width, height )
    Widget	            w;
    long                    width;
    long                    height;
{
    GapGraphicWidget        gap = (GapGraphicWidget) w;
    XtWidgetGeometry        req;
    XRectangle              rec[1];

    /* enter new dimensions */
    req.width  = gap->gap_graphic.width  = width;
    req.height = gap->gap_graphic.height = height;
    req.request_mode = CWWidth | CWHeight;

    /* and make request, ignore the result */
    XtMakeGeometryRequest( w, &req, 0 );
    XtResizeWidget( w, (Dimension) width, (Dimension) height, 0 );
    gap->gap_graphic.width  = gap->core.width  = width;
    gap->gap_graphic.height = gap->core.height = height;

    /* and set new clipping */
    rec->x = 0;
    rec->y = 0;
    rec->width  = gap->gap_graphic.width;
    rec->height = gap->gap_graphic.height;
    XSetClipRectangles(XtDisplay(w),gap->gap_graphic.gc,0,0,rec,1,YXSorted);
}
