/* $XConsortium: draw.c /main/5 1996/06/13 10:32:01 drk $ */
/*
 * @OPENGROUP_COPYRIGHT@
 * COPYRIGHT NOTICE
 * Copyright (c) 1990, 1991, 1992, 1993 Open Software Foundation, Inc.
 * Copyright (c) 1996, 1997, 1998, 1999, 2000 The Open Group
 * ALL RIGHTS RESERVED (MOTIF).  See the file named COPYRIGHT.MOTIF for
 * the full copyright text.
 * 
 * This software is subject to an open license. It may only be
 * used on, with or for operating systems which are themselves open
 * source systems. You must contact The Open Group for a license
 * allowing distribution and sublicensing of this software on, with,
 * or for operating systems which are not Open Source programs.
 * 
 * See http://www.opengroup.org/openmotif/license for full
 * details of the license agreement. Any use, reproduction, or
 * distribution of the program constitutes recipient's acceptance of
 * this agreement.
 * 
 * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS
 * PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY
 * WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY
 * OR FITNESS FOR A PARTICULAR PURPOSE
 * 
 * EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT
 * NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE
 * EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
 * 
 */
/*
 * HISTORY
 */
/*
**  This demo shows an AUTOMATIC scrolled window and some graphics
**  done in a DrawingArea.
**
**  It lets you draw polylines and buttons, resizes the picture,
**  and eventually save the data in an X resource format.
**
*/

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <Xm/XmAll.h>
#include <Xmd/RegEdit.h>   
#include <Xmd/Help.h>   
#include <X11/IntrinsicP.h>

/*-------------------------------------------------------------
**	Graphic data structure and API
*/

#define APP_CLASS "XmdDraw"

/* very simple data struct, static size, etc...
*/
#define MAX_POINT 100
typedef struct { 
    XPoint points[MAX_POINT] ; 
    Cardinal num_points ;
#define POLYLINE 1
    unsigned char type ;
} GraphicUnit ;

#define MAX_GRAPH 100
typedef struct { 
     GraphicUnit graphics[MAX_GRAPH] ;
     Cardinal num_graphics ;

     XPoint anchor_point ;
     XPoint drag_point ;
     GC drag_gc ;
     Boolean in_drag ;
     Dimension old_width, old_height ;

     Widget work_area ;
     Widget textf ;
} Graphic ;

static char drawTranslations[] = "#replace\n\
~s ~m ~a <Key>Return:DrawingAreaInput() ManagerParentActivate()\n\
<Key>Return:DrawingAreaInput() ManagerGadgetSelect()\n\
<Key>osfActivate:DrawingAreaInput() ManagerParentActivate()\n\
<Key>osfCancel:DrawingAreaInput() ManagerParentCancel()\n\
<Key>osfHelp:DrawingAreaInput() ManagerGadgetHelp()\n\
<Key>space:DrawingAreaInput() ManagerGadgetSelect()\n\
<Key>osfSelect:DrawingAreaInput() ManagerGadgetSelect()\n\
<KeyDown>:DrawingAreaInput() ManagerGadgetKeyInput()\n\
<KeyUp>:DrawingAreaInput()\n\
<BtnMotion>:DrawingAreaInput() ManagerGadgetButtonMotion()\n\
<Motion>:DrawingAreaInput() ManagerGadgetButtonMotion()\n\
<Btn1Down>:DrawingAreaInput() ManagerGadgetArm()\n\
<Btn1Up>:DrawingAreaInput() ManagerGadgetActivate()\n\
<Btn2Down>:DrawingAreaInput() ManagerGadgetDrag()\n\
<BtnDown>:DrawingAreaInput()\n\
<BtnUp>:DrawingAreaInput()";

/*-------------------------------------------------------------
**	 Application data stuff: this serve as the file saving format
*/


typedef struct {
  char * lines;
  char * towns;
} ApplicationData, * ApplicationDataPtr ;

#define XtNlines "lines"
#define XtCLines "Lines"
#define XtNtowns "towns"
#define XtCTowns "Towns"

/* if this stuff was as easily initializable, I would put it local to main */
static XtResource app_resources[] = {
  { XtNlines, XtCLines, XmRString, sizeof(String),
    XtOffset(ApplicationDataPtr, lines), XmRString, "" },
  { XtNtowns, XtCTowns, XmRString, sizeof(String),
    XtOffset(ApplicationDataPtr, towns), XmRString, "" },
};

/* if this stuff was as easily initializable, I would put it local to main */
/* better to have that in automatic mode */
static String fallback_resources[] = {
    "*help_manager.helpFile: draw",
    "*work_area.width:1000",
    "*work_area.height:1000",
    "*XmScale.minimum: 100",
    "*XmScale.maximum: 16000",
    ".geometry:600x500",
    NULL
    } ;

/*-------------------------------------------------------------
**	forwarded functions
*/
static void CreateApplication (Widget parent, Graphic *graph);

/*      Xt callbacks 
*/
static void SaveCB (Widget w, XtPointer client_data, XtPointer call_data);
static void OK_WriteCB (Widget w, XtPointer client_data, XtPointer call_data);
static void QuitCB (Widget w, XtPointer client_data, XtPointer call_data);
static void HelpCB (Widget w, XtPointer client_data, XtPointer call_data);
static void DrawCB (Widget w, XtPointer client_data, XtPointer call_data);
static void ValueCB (Widget w, XtPointer client_data, XtPointer call_data);
static void TravCB (Widget w, XtPointer client_data, XtPointer call_data);
static void PushCB (Widget w, XtPointer client_data, XtPointer call_data);

static void InitDraw (Graphic *graph, ApplicationData *app_data);
static void ReDraw (Graphic *graph, XEvent *event);
static void ReSize (Graphic *graph, Dimension widget, Dimension height);
static void StartUnit (Graphic *graph, Position x, Position y);
static void DragUnit (Graphic *graph, Position x, Position y);
static Boolean NearPoint (XPoint point, Position x, Position y);
static void EndUnit (Graphic *graph, Position x, Position y);
static void DeleteUnit (Graphic *graph, Position x, Position y);


/*-------------------------------------------------------------
**	    Main body
*/
int
main(int argc, char	*argv[])
{
    XtAppContext app_context;
    Widget      toplevel;
    ApplicationData app_data;
    Graphic legraph;

    toplevel = XtAppInitialize(&app_context, APP_CLASS, NULL, 0,
			       &argc, argv, fallback_resources, NULL, 0);

    /**  Plug in Editres protocol  */
    XmdRegisterEditres(toplevel);

    XtGetApplicationResources(toplevel, (XtPointer)&app_data,
			      app_resources, XtNumber(app_resources),
			      NULL, 0);

    CreateApplication (toplevel, &legraph);

    InitDraw(&legraph, &app_data);
    
    XtRealizeWidget(toplevel);
    XtAppMainLoop(app_context);

    /*NOTREACHED*/
    exit(0);
}


/*-------------------------------------------------------------
**	Create a Main Window with a menubar, a command panel containing
**      2 scales and a textfied, and a workarea.
**      Also put in the graphic structure the workarea info and the
**      textfield ids.
*/
static void 
CreateApplication (Widget parent, Graphic *	graph)
{
    Widget main_window, menu_bar, menu_pane, cascade, button, comw, scale;
    Arg args[5];
    int	n;
    XtTranslations xlations;

    /*	Create automatic MainWindow.
     */
    n = 0;
    XtSetArg (args[n], XmNscrollingPolicy, XmAUTOMATIC);  n++;
    main_window = XmCreateMainWindow (parent, "main_window", args, n);

    XtAddCallback (main_window, XmNtraverseObscuredCallback, TravCB, 
		   (XtPointer)graph);

    XtManageChild (main_window);


    /*	Create MenuBar in MainWindow.
     */
    n = 0;
    menu_bar = XmCreateMenuBar (main_window, "menu_bar", args, n); 
    XtManageChild (menu_bar);


    /*	Create "File" PulldownMenu with Save and Quit buttons
     */
    n = 0;
    menu_pane = XmCreatePulldownMenu (menu_bar, "menu_pane", args, n);

    n = 0;
    button = XmCreatePushButton (menu_pane, "Save...", args, n);
    XtManageChild (button);
    /* pass the graphic id to the save function */
    XtAddCallback (button, XmNactivateCallback, SaveCB, (XtPointer)graph);
    n = 0;
    button = XmCreatePushButton (menu_pane, "Quit", args, n);
    XtManageChild (button);
    XtAddCallback (button, XmNactivateCallback, QuitCB, NULL);

    n = 0;
    XtSetArg (args[n], XmNsubMenuId, menu_pane);  n++;
    cascade = XmCreateCascadeButton (menu_bar, "File", args, n);
    XtManageChild (cascade);


    /*	Create "Help" PulldownMenu with Help button.
     */
    n = 0;
    menu_pane = XmCreatePulldownMenu (menu_bar, "menu_pane", args, n);

    n = 0;
    button = XmCreatePushButton (menu_pane, "Overview", args, n);
    XtManageChild (button);
    XtAddCallback (button, XmNactivateCallback, HelpCB, NULL);

    n = 0;
    XtSetArg (args[n], XmNsubMenuId, menu_pane);  n++;
    cascade = XmCreateCascadeButton (menu_bar, "Help", args, n);
    XtManageChild (cascade);

    n = 0;
    XtSetArg (args[n], XmNmenuHelpWidget, cascade);  n++;
    XtSetValues (menu_bar, args, n);


    /*	Create work_area in MainWindow 
     */
    n = 0;
    XtSetArg (args[n], XmNresizePolicy, XmRESIZE_NONE); n++ ;
    XtSetArg (args[n], XmNmarginWidth, 0); n++ ;
    XtSetArg (args[n], XmNmarginHeight, 0); n++ ;
    /* hardcode this one, since its is required for the motion handling */
    xlations = XtParseTranslationTable(drawTranslations);
    XtSetArg (args[n], XmNtranslations, xlations); n++;
    graph->work_area = XmCreateDrawingArea(main_window, "work_area", args, n);
    XtAddCallback (graph->work_area, XmNexposeCallback, DrawCB, 
		   (XtPointer)graph);
    XtAddCallback (graph->work_area, XmNresizeCallback, DrawCB, 
		   (XtPointer)graph);
    XtAddCallback (graph->work_area, XmNinputCallback, DrawCB, 
		   (XtPointer)graph);
    XtManageChild (graph->work_area);


    /*	Create a commandWindow in MainWindow with text and scales 
     */
    n = 0;
    comw = XmCreateRowColumn(main_window, "comw", args, n);
    XtManageChild (comw);
    n = 0;
    XtSetArg (args[n], XmNcommandWindow, comw);  n++;
    XtSetValues (main_window, args, n);

    /* find the initial size of the work_area and report to the scales */
    n = 0;
    XtSetArg (args[n], XmNwidth, &graph->old_width);  n++;
    XtSetArg (args[n], XmNheight, &graph->old_height);  n++;
    XtGetValues (graph->work_area, args, n);
	
    n = 0;
    XtSetArg (args[n], XmNorientation, XmHORIZONTAL);  n++;
    XtSetArg (args[n], XmNshowValue, True);  n++;
    XtSetArg (args[n], XmNvalue, graph->old_width);  n++;
    scale = XmCreateScale(comw, "scale_w", args, n); /* scale_w is the name */
    XtAddCallback (scale, XmNvalueChangedCallback, ValueCB, 
		   (XtPointer)graph->work_area);
    XtManageChild (scale);

    n = 0;
    XtSetArg (args[n], XmNorientation, XmHORIZONTAL);  n++;
    XtSetArg (args[n], XmNshowValue, True);  n++;
    XtSetArg (args[n], XmNvalue, graph->old_height);  n++;
    scale = XmCreateScale(comw, "scale_h", args, n);
    XtAddCallback (scale, XmNvalueChangedCallback, ValueCB, 
		   (XtPointer)graph->work_area);
    XtManageChild (scale);

    n = 0;
    graph->textf = XmCreateTextField(comw, "textf", args, n);
    XtManageChild (graph->textf);
    

    /*	Set MainWindow areas 
     */
    XmMainWindowSetAreas (main_window, menu_bar, comw, NULL, NULL,
			  graph->work_area);

}


/*-------------------------------------------------------------
**	PushCB		- callback for push button inside the workarea
*/
static void 
PushCB (Widget		w,		/*  widget id		*/
	XtPointer	client_data,	/*  data from application   */
	XtPointer	call_data)	/*  data from widget class  */
{
    XmPushButtonCallbackStruct * pbcs =
	(XmPushButtonCallbackStruct *) call_data ;

    if ((pbcs->event->type == ButtonRelease) &&
	(pbcs->event->xbutton.state & ShiftMask))
	XtDestroyWidget(w);
/*    else 
	printf("Activate %s\n",XtName(w));*/
}

/*-------------------------------------------------------------
**	TravCB		- callback for traverseObscure
*/
static void 
TravCB (Widget		w,		/*  widget id		*/
	XtPointer	client_data,	/*  data from application   */
	XtPointer	call_data)	/*  data from widget class  */
{
    XmTraverseObscuredCallbackStruct * tocs =
	(XmTraverseObscuredCallbackStruct *) call_data ;
    Graphic * graph = (Graphic *) client_data ;

    if (tocs->traversal_destination != graph->work_area)
	XmScrollVisible(w, tocs->traversal_destination, 20, 20) ; 
}

/*-------------------------------------------------------------
**	ValueCB		- callback for scales
*/
static void 
ValueCB (Widget		w,		/*  widget id		*/
	 XtPointer	client_data,	/*  data from application   */
	 XtPointer	call_data)	/*  data from widget class  */
{
    
    Arg args[5];	
    int	n ;		   
    int value ;
    Widget workarea = (Widget) client_data ;

    /* get the value outof the Scale */
    n = 0;
    XtSetArg (args[n], XmNvalue, &value);  n++;
    XtGetValues (w, args, n);

    n = 0;
    if (strcmp(XtName(w), "scale_w") == 0 ) { /* width scale */
	XtSetArg (args[n], XmNwidth, value);  n++;
    } else {
	XtSetArg (args[n], XmNheight, value);  n++;
    }
    XtSetValues (workarea, args, n);
}



/*-------------------------------------------------------------
**	SaveCB			- callback for Save button
*/
static void 
SaveCB (Widget		w,		/*  widget id		*/
	XtPointer	client_data,	/*  data from application   */
	XtPointer	call_data)	/*  data from widget class  */
{
	static Widget fsb_box = NULL ;

	if (!fsb_box) {
	    fsb_box = XmCreateFileSelectionDialog (w, "Save graphics", 
						   NULL, 0);
	    /* just propagate the graphic information */
	    XtAddCallback (fsb_box, XmNokCallback, OK_WriteCB, client_data);
	}    


	XtManageChild (fsb_box);
}


/*-------------------------------------------------------------
**	QuitCB			- callback for quit button
*/
static void 
QuitCB (Widget		w,		/*  widget id		*/
	XtPointer	client_data,	/*  data from applicaiton   */
	XtPointer	call_data)	/*  data from widget class  */
{
    exit (0);
}


/*-------------------------------------------------------------
**	HelpCB			- callback for help button
*/
static void 
HelpCB (Widget		w,		/*  widget id		*/
	XtPointer	client_data,	/*  data from application   */
	XtPointer	call_data)	/*  data from widget class  */
{
    static Widget help_widget = NULL ;

    if (!help_widget)
	help_widget = XmdCreateHelpDialog(w, "help_manager", NULL, 0);

    XtManageChild(help_widget);   
}

/*-------------------------------------------------------------
**	OK_WriteCB	- callback for saving the graphics in a file
*/
static void 
OK_WriteCB (Widget	w,		/*  widget id		*/
	    XtPointer	client_data,	/*  data from application   */
	    XtPointer	call_data)	/*  data from widget class  */
{
    Widget text ;
    char * filename ;
    FILE  *out_file ;
    Cardinal i,j ;
    Arg args[5];	
    int	n ;		   
    Widget * children ;
    Cardinal num_children ;
    Graphic * graph = (Graphic *) client_data ;
    Position x,y ;

    text = XmFileSelectionBoxGetChild(w, XmDIALOG_TEXT);
    filename = XmTextGetString(text);

    if ((out_file=fopen(filename, "w")) == NULL) {
	printf("Can't open file: %s ", filename);
	return ;
    }

    fprintf(out_file, "draw.lines: ");
    for (i=0; i < graph->num_graphics; i++) {
	for (j=0; j < graph->graphics[i].num_points; j++) {
	    fprintf(out_file, "%d_%d", graph->graphics[i].points[j].x,
		                       graph->graphics[i].points[j].y) ;
	    if (j == graph->graphics[i].num_points - 1) {
		if (i != graph->num_graphics - 1) fprintf(out_file, ". ") ;
	    } else fprintf(out_file, ", ") ;
	    
	}
    }

    fprintf(out_file, "\n");
    fprintf(out_file, "draw.towns: ");
    
    n = 0;
    XtSetArg (args[n], XmNnumChildren, &num_children);  n++;
    XtSetArg (args[n], XmNchildren, &children);  n++;
    XtGetValues (graph->work_area, args, n);

    for (i=0; i < num_children; i++) {
	fprintf(out_file, "%s", XtName(children[i]));
	if (i != num_children - 1) fprintf(out_file, ", ") ;
    }

    fprintf(out_file, "\n");

    for (i=0; i < num_children; i++) {
	n = 0;
	XtSetArg (args[n], XmNx, &x);  n++;
	XtSetArg (args[n], XmNy, &y);  n++;
	XtGetValues (children[i], args, n);
	fprintf(out_file, "draw*%s.x: %d\n",XtName(children[i]),x);
	fprintf(out_file, "draw*%s.y: %d\n",XtName(children[i]),y);
    }

    fclose(out_file);

}


 
 

/*-------------------------------------------------------------
**	DrawCB			- callback for drawing area
*/
static void 
DrawCB (Widget		w,		/*  widget id		*/
	XtPointer	client_data,	/*  data from application   */
	XtPointer	call_data)	/*  data from widget class  */
{
    
    XmDrawingAreaCallbackStruct * dacs =
	(XmDrawingAreaCallbackStruct *) call_data ;
    Arg args[5];	
    int	n ;	
    Dimension width, height ;
    String name ;
    Widget newpush ;
    XSetWindowAttributes xswa;
    Graphic * graph = (Graphic *) client_data ;

    static Boolean first_time = True ;

    switch (dacs->reason) {
    case XmCR_EXPOSE: 
	if (first_time) {
	    /* Change once the bit gravity of the Drawing Area; default
	       is north west and we want forget, so that resize 
	       always generates exposure events */
	    first_time = False ;
	    xswa.bit_gravity = ForgetGravity ;
	    XChangeWindowAttributes(XtDisplay(w), XtWindow(w),
				    CWBitGravity, &xswa);
	}
	ReDraw(graph, dacs->event) ;

	break ;
    case XmCR_RESIZE: 
	n = 0;
	XtSetArg (args[n], XmNwidth, &width);  n++;
	XtSetArg (args[n], XmNheight, &height);  n++;
	XtGetValues (w, args, n);
	ReSize(graph, width, height) ;

	break ;
    case XmCR_INPUT:
	if (dacs->event->type == ButtonPress) {
	    name = XmTextFieldGetString(graph->textf) ; /* textfield */
	    if (strcmp ("", name) != 0) {
		n = 0;
		XtSetArg (args[n], XmNx, dacs->event->xbutton.x);  n++;
		XtSetArg (args[n], XmNy, dacs->event->xbutton.y);  n++;
		newpush = XmCreatePushButton(w, name, args, n);
		XtAddCallback (newpush, XmNactivateCallback, PushCB, NULL);
		XtManageChild (newpush);
	    } else 
	    if ((dacs->event->xbutton.state & ShiftMask) && 
		(!graph->in_drag)) {
		DeleteUnit (graph, dacs->event->xbutton.x,
			    dacs->event->xbutton.y);
	    } else {
		if (!graph->in_drag) {
		    StartUnit(graph, dacs->event->xbutton.x,
			      dacs->event->xbutton.y);
		} else {
		    EndUnit(graph, dacs->event->xbutton.x,
			    dacs->event->xbutton.y);
		}
	    }
	    XtFree(name);
    	} else  /* need to get motion events here: app_default should
		   modified DrawingArea translation with both Motion
		   and BtnMotion addition */
	if (dacs->event->type == MotionNotify) {
	    /* this one just exits if in_drag is False */
	    DragUnit(graph, dacs->event->xbutton.x,
		     dacs->event->xbutton.y);
	}
	break ;
    }
}


/*************************** GRAPHICS **********************************/

static void 
InitDraw(Graphic * 	   graph,
	 ApplicationData * app_data)
{
    XGCValues val ;
    Arg args[5];	
    int	n ;		   
    Cardinal i ;
    Dimension width, height ;
    String pstr, wstr ;
    int x, y ;
    Widget newpush ;

    /* create the gc used for the rudder banding effect */
    n = 0;
    XtSetArg (args[n], XmNforeground, &val.foreground);  n++;
    XtSetArg (args[n], XmNbackground, &val.background);  n++;
    XtGetValues (graph->work_area, args, n);
    
    val.foreground = val.foreground ^ val.background ;
    val.function = GXxor ;
    graph->drag_gc = XtGetGC(graph->work_area, 
			     GCForeground | GCBackground | GCFunction, &val);

    /* initialize the graphic stuff */
    graph->in_drag = False ;
    
    graph->num_graphics = 0 ;
    for (i=0; i < MAX_GRAPH; i++) {
	graph->graphics[i].num_points = 0 ;
    }

    /* polylines syntax: 
         draw.lines: 10_10, 20_30, 28_139. 11_112, 145_60. 211_112, 45_60
       would be nice to use nested strtok but the sucker is not reentrant...
    */
    wstr = XtNewString(app_data->lines)  ;
    for(pstr = (char *) strtok(wstr, ".,") ; pstr ;	
	pstr = (char *) strtok( NULL, ".,")) {
	while (*pstr && isspace(*pstr)) pstr++;
	if (*pstr == '\0') break;
	
	sscanf(pstr, "%d_%d", &x, &y);
	graph->graphics[graph->num_graphics].points
	    [graph->graphics[graph->num_graphics].num_points].x = x ;
	graph->graphics[graph->num_graphics].points
	    [graph->graphics[graph->num_graphics].num_points].y = y ;
	graph->graphics[graph->num_graphics].num_points ++ ;
	graph->graphics[graph->num_graphics].type = POLYLINE ;

	/* look in the original to see if it is a new unit */
	if (app_data->lines[pstr - wstr + strlen(pstr)] == '.')
	    graph->num_graphics ++ ;
    }
    XtFree(wstr);

    if (strlen(app_data->lines)) graph->num_graphics ++ ;
   
    /* Towns syntax:
         draw.towns: Boston, Woburn, SanJose
         draw*Boston.x: 30
         draw*Boston.y: 30
         draw*Woburn.x: 130
         draw*Woburn.y: 30
         draw*SanJose.x: 30
         draw*SanJose.y: 130
    */
    wstr = XtNewString(app_data->towns)  ;
    for(pstr = (char *) strtok(wstr, ".,") ; pstr ;
	pstr = (char *) strtok( NULL, ".,")) {
        while (*pstr && isspace(*pstr)) pstr++;
        if (*pstr == '\0') break;
	newpush = XmCreatePushButton(graph->work_area, pstr, NULL, 0);
	XtAddCallback (newpush, XmNactivateCallback, PushCB, NULL);
	XtManageChild (newpush);
    }
    XtFree(wstr);
}

static void 
ReDraw(Graphic * graph,
       XEvent * event)
{
    Cardinal i ;
    Widget w = graph->work_area ;

    /* the expose event region could be used for better performance */
    for (i=0; i < graph->num_graphics; i++) {
	if (graph->graphics[i].type == POLYLINE)
	    XDrawLines(XtDisplay(w), XtWindow(w), 
		       XDefaultGC(XtDisplay(w),XDefaultScreen(XtDisplay(w))),
		       graph->graphics[i].points, 
		       graph->graphics[i].num_points, 
		       CoordModeOrigin);
    }
}

static void 
ReSize(Graphic * graph,
       Dimension width,
       Dimension height)
{
    /* Note: very bad design: resize should not modify the data since
       you lose everything when you resize tiny tiny tiny, but hey, what
       do you expect for a demo ? This way at least I can use the
       resource interface for creating the buttons in place (see InitDraw) */

    Widget w = graph->work_area ;
    Cardinal i,j ;
    Arg args[5];	
    int	n ;		   
    Widget * children ;
    Cardinal num_children ;
    Position x,y ;
    float xratio = (float) width / graph->old_width, 
          yratio = (float) height / graph->old_height ;

    /* reposition and resize the graphic units */
    for (i=0; i < graph->num_graphics; i++) {
	for (j=0; j < graph->graphics[i].num_points; j++) {
	    graph->graphics[i].points[j].x *= xratio ;
	    graph->graphics[i].points[j].y *= yratio ;
	}
    }

    /* reposition the pushbutton children */
    /* I can use XtMoveWidget here since it's like being part of the
       widget resize class method... */
    n = 0;
    XtSetArg (args[n], XmNnumChildren, &num_children);  n++;
    XtSetArg (args[n], XmNchildren, &children);  n++;
    XtGetValues (w, args, n);
    for (i=0; i < num_children; i++) {
	n = 0;
	XtSetArg (args[n], XmNx, &x);  n++;
	XtSetArg (args[n], XmNy, &y);  n++;
	XtGetValues (children[i], args, n);
	XtMoveWidget(children[i], 
		     (Position) (x * xratio),
		     (Position) (y * yratio));
    }
    
    graph->old_width = width ;
    graph->old_height = height ;
}


static void 
StartUnit(Graphic * graph,
	  Position x,
	  Position y)
{
    Widget w = graph->work_area ;

    graph->drag_point.x = graph->anchor_point.x = x ;
    graph->drag_point.y = graph->anchor_point.y = y ;
    graph->in_drag = True ;
    XDrawLine(XtDisplay(w), XtWindow(w), 
	      graph->drag_gc,
	      graph->anchor_point.x, graph->anchor_point.y,
	      graph->drag_point.x, graph->drag_point.y);
}

static void 
DragUnit(Graphic * graph,
	 Position x, 
	 Position y)
{
    Widget w = graph->work_area ;

    if (!graph->in_drag) return ;

    XDrawLine(XtDisplay(w), XtWindow(w), 
	      graph->drag_gc,
	      graph->anchor_point.x, graph->anchor_point.y,
	      graph->drag_point.x, graph->drag_point.y);

    graph->drag_point.x = x ;
    graph->drag_point.y = y ;

    XDrawLine(XtDisplay(w), XtWindow(w), 
	      graph->drag_gc,
	      graph->anchor_point.x, graph->anchor_point.y,
	      graph->drag_point.x, graph->drag_point.y);
}


static Boolean 
NearPoint (XPoint point,
	   Position x, 
	   Position y)
{
#define ERROR 5
    if ((point.x > x - ERROR) &&
	(point.x < x + ERROR) &&
	(point.y > y - ERROR) &&
	(point.y < y + ERROR)) return True ;
    else return False ;
}


static void 
EndUnit(Graphic * graph,
	Position x, 
	Position y)
{
    Widget w = graph->work_area ;
    Cardinal num_points ;

    /* no matter what happens, we need to remove the current rubber band */
    XDrawLine(XtDisplay(w), XtWindow(w), 
	      graph->drag_gc,
	      graph->anchor_point.x, graph->anchor_point.y,
	      graph->drag_point.x, graph->drag_point.y);

    /* if the given point if the same as the anchor, we're done with
       this polyline, exit drag mode and be ready for the next 
       graphic unit, i.e increment num_graphics */

    if (NearPoint(graph->anchor_point, x, y)) {
	graph->in_drag = False ;
	/* now see if a new unit needs to be created */
	if (graph->graphics[graph->num_graphics].num_points) {
	    graph->graphics[graph->num_graphics].type = POLYLINE ;
	    if (graph->num_graphics < MAX_GRAPH) graph->num_graphics ++ ;
	    else printf("The graphic buffer is full, overwrite the last...\n");
	}
    } else {

	/* draw the real line and store it in the structure */
	XDrawLine(XtDisplay(w), XtWindow(w), 
		  XDefaultGC(XtDisplay(w),XDefaultScreen(XtDisplay(w))),
		  graph->anchor_point.x, graph->anchor_point.y,
		  x, y);

	/* first point in a unit is actually special */
	num_points = graph->graphics[graph->num_graphics].num_points ;
	if (num_points == 0) {
	    graph->graphics[graph->num_graphics].points[num_points].x = 
		graph->anchor_point.x ;
	    graph->graphics[graph->num_graphics].points[num_points].y = 
		graph->anchor_point.y ;
	    graph->graphics[graph->num_graphics].num_points ++ ;
	    num_points ++ ;
	}
	graph->graphics[graph->num_graphics].points[num_points].x = x ;
	graph->graphics[graph->num_graphics].points[num_points].y = y ;
	if (graph->graphics[graph->num_graphics].num_points < MAX_POINT) 
	    graph->graphics[graph->num_graphics].num_points ++ ;
	else printf("The unit buffer is full, overwrite the last...\n");

	/* now start the new unit */
	graph->drag_point.x = graph->anchor_point.x = x ;
	graph->drag_point.y = graph->anchor_point.y = y ;
	XDrawLine(XtDisplay(w), XtWindow(w), 
		  graph->drag_gc,
		  graph->anchor_point.x, graph->anchor_point.y,
		  graph->drag_point.x, graph->drag_point.y);
    }
}

static void 
DeleteUnit(Graphic * graph,
	   Position x, 
	   Position y)
{
    Widget w = graph->work_area ;
    Cardinal i,j ;
    int a = -1 ;

    /* try to find a unit under this point */
    for (i=0; (i < graph->num_graphics) && (a == -1); i++) {
	for (j=0; j < graph->graphics[i].num_points; j++) {
	    if (NearPoint(graph->graphics[i].points[j], x, y)) {
		a = i ;
		break ;
	    }
	}
    }

    if (a != -1) { 
	/* found a unit under the current point, delete and redisplay */
	for (i = a ; i < graph->num_graphics ; i++) {
	    graph->graphics[i] = graph->graphics[i+1] ;
	}
	graph->num_graphics -- ;

	XClearArea(XtDisplay(w), XtWindow(w), 
		   0, 0, graph->old_width, graph->old_height, True);
    }
}

