

/*  Copyright (c) 1993 by Kenneth Duda <kkkken@athena.mit.edu>.  
 *  All rights reserved.
 *
 *  This program is distributed in the hope that it will be useful.
 *  Use and copying of this software and preparation of derivative works
 *  based upon this software are permitted, so long as the following
 *  conditions are met:
 *       o credit to the author is acknowledged 
 *       o no fees or compensation are charged for use, copies, or
 *         access to this software
 *       o this copyright notice is included intact.
 *  This software is made available AS IS, and no warranty is made about 
 *  the software or its performance. 
 * 
 *  Bug descriptions, use reports, comments or suggestions are welcome.
 *  Send them to <kkkken@athena.mit.edu>.
 */

#include <X11/Xlib.h>
#include <X11/StringDefs.h>
#include <X11/IntrinsicP.h>
#include <X11/Shell.h>
#include <X11/Xaw/XawInit.h>
#include <X11/Xaw/SimpleMenu.h>
#include <X11/Xaw/SmeBSB.h>
#include <X11/Xaw/SmeLine.h>
#include <X11/Xaw/MenuButton.h>
#include "GobanP.h"

#define offset(field) XtOffset(GobanWidget,goban.field)
#define goffset(field) XtOffset(Widget,core.field)

#define MINWIDTH 210
#define MINHEIGHT 210
#define DEFWIDTH 380
#define DEFHEIGHT 380

#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
#define abs(a) ((a) < 0 ? -(a) : (a))

static void GobanClassInitialize();
static void GobanInitialize(), GobanRealize(), GobanDestroy(), GobanResize(), GobanRedisplay();
static Boolean GobanSetValues();
static void Consider(), Adjust(), Commit(), Backup(), Forward();
static void First(), Current(), Last(), Handicap(), Undo(), New(), Clear(), Quit(), Pass(), Done();
static void do_backup(), do_forward(), do_clear(), do_new();
static void do_undo(), do_pass(), do_quit(), do_done(), do_current(), do_first(), do_last();
static void RollForward (GobanWidget g), MaybeCurrent(GobanWidget g);

static char defaultTranslations[] =
    "<Btn1Down>:       consider()            \n\
     <Btn3Down>:       consider()            \n\
     <BtnMotion>:      adjust()              \n\
     <BtnUp>:	       commit()              \n\
     <Key>b:           backup()              \n\
     <Key>f:           forward()             \n\
     <Key>c:           current()             \n\
     <Key>l:           last()                \n\
     <Key>s:           first()               \n\
     <Key>u:           undo()                \n\
     <Key>d:           done()                \n\
     <Key>p:           pass()                \n\
     <Key>n:           new()                 \n\
     <Key>q:           quit()                \n\
     <Key>2:           handicap(2)           \n\
     <Key>3:           handicap(3)           \n\
     <Key>4:           handicap(4)           \n\
     <Key>5:           handicap(5)           \n\
     <Key>6:           handicap(6)           \n\
     <Key>7:           handicap(7)           \n\
     <Key>8:           handicap(8)           \n\
     <Key>9:           handicap(9)           \n\
     <Btn2Down>:       XawPositionSimpleMenu(gobanMenu) MenuPopup(gobanMenu)\n";

static XtActionsRec actionsList[] = { 
  {"consider",		Consider},
  {"adjust",		Adjust},
  {"backup",            Backup}, 
  {"forward",           Forward}, 
  {"current",           Current}, 
  {"first",             First}, 
  {"last",              Last}, 
  {"commit",		Commit},
  {"handicap",		Handicap},
  {"undo",              Undo},
  {"pass",              Pass},
  {"done",              Done},
  {"clear",		Clear},
  {"new",		New},
  {"quit",		Quit},
};



static XtResource gobanResources[] = {
    {XtNwidth, XtCWidth, XtRDimension, sizeof(Dimension),
	 goffset(width), XtRImmediate, (caddr_t) 0},
    {XtNheight, XtCHeight, XtRDimension, sizeof(Dimension),
	goffset(height), XtRImmediate, (caddr_t) 0},
    {XtNblackColor, XtCForeground, XtRPixel, sizeof(Pixel),
	offset(blackPixel), XtRString, "black"},
    {XtNwhiteColor, XtCForeground, XtRPixel, sizeof(Pixel),
	offset(whitePixel), XtRString, "white"},
    {XtNlineColor, XtCForeground, XtRPixel, sizeof(Pixel),
	offset(linePixel), XtRString, "blue"},
    {XtNboardColor, XtCBackground, XtRPixel, sizeof(Pixel),
	goffset(background_pixel), XtRString, "gray"},
    {XtNboardSize, XtCParameter, XtRInt, sizeof(int),
	offset(bsize), XtRString, "19"},
    {XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
        offset(font), XtRString, "fixed"},
    {XtNmode, XtCParameter, XtRInt, sizeof(GobanMode),
        offset(mode), XtRString, "0"},
    {XtNshell, XtCShell, XtRWidget, sizeof(Widget),
        offset(shell), XtRWidget, NULL},
    {XtNclientData, XtCValue, XtRPointer, sizeof(XtPointer),
	 offset(clientData), XtRPointer, NULL},
    {XtNwhiteCapturesLabel, XtCCapturesLabel, XtRWidget, sizeof(Widget),
	 offset(whiteCapturesLabel), XtRWidget, NULL},
    {XtNblackCapturesLabel, XtCCapturesLabel, XtRWidget, sizeof(Widget),
	 offset(blackCapturesLabel), XtRWidget, NULL},
    
};

GobanClassRec gobanClassRec = {
        { /* core fields */
    /* superclass		*/	(WidgetClass) &simpleClassRec,
    /* class_name		*/	"Goban",
    /* widget_size		*/	sizeof(GobanRec),
    /* class_initialize		*/	GobanClassInitialize,
    /* class_part_initialize	*/	NULL,
    /* class_inited		*/	FALSE,
    /* initialize		*/	GobanInitialize,
    /* initialize_hook		*/	NULL,
    /* realize			*/	GobanRealize,
    /* actions			*/	actionsList,
    /* num_actions		*/	XtNumber(actionsList),
    /* resources		*/	gobanResources,
    /* resource_count		*/	XtNumber(gobanResources),
    /* xrm_class		*/	NULLQUARK,
    /* compress_motion		*/	TRUE,
    /* compress_exposure	*/	TRUE,
    /* compress_enterleave	*/	TRUE,
    /* visible_interest		*/	FALSE,
    /* destroy			*/	GobanDestroy,
    /* resize			*/	GobanResize,
    /* expose			*/	GobanRedisplay,
    /* set_values		*/	GobanSetValues,
    /* set_values_hook		*/	NULL,
    /* set_values_almost	*/	XtInheritSetValuesAlmost,
    /* get_values_hook		*/	NULL,
    /* accept_focus		*/	NULL,
    /* version			*/	XtVersion,
    /* callback_private		*/	NULL,
    /* tm_table			*/	defaultTranslations,
    /* query_geometry           */	XtInheritQueryGeometry,
    /* display_accelerator      */	XtInheritDisplayAccelerator,
    /* extension                */	NULL
    },
    { /* simple fields */
    /* change_sensitive         */      XtInheritChangeSensitive
    },
    { /* goban fields */
    /* ignore                   */      0
    }
};

WidgetClass gobanWidgetClass = (WidgetClass) &gobanClassRec;

static int otherColor[] = { NoPiece, BlackPiece, WhitePiece };
static char colNames[] = "ABCDEFGHJKLMNOPQRST";

static void GobanClassInitialize()
{
    XawInitializeWidgetSet();
}

static void checkvals (g)
     GobanWidget g;
{
    static int hcaph9[] = { 2, 4, 6, 2, 4, 6, 2, 4, 6 };
    static int hcapv9[] = { 2, 2, 2, 4, 4, 4, 6, 6, 6 };
    static int hcaph13[] = { 3, 6, 9, 3, 6, 9, 3, 6, 9 };
    static int hcapv13[] = { 3, 3, 3, 6, 6, 6, 9, 9, 9 };
    static int hcaph19[] = { 3, 9, 15, 3, 9, 15, 3, 9, 15 };
    static int hcapv19[] = { 3, 3, 3, 9, 9, 9, 15, 15, 15 };
    if (g->core.width < MINWIDTH)
	g->core.width = MINWIDTH;
    if (g->core.height < MINHEIGHT)
	g->core.height = MINHEIGHT;
    if ((g->goban.bsize < 2) || (g->goban.bsize > MAXBOARD)) {
	g->goban.bsize = 19;
    }
    switch (g->goban.bsize) {
      case 9:
	g->goban.hcapH = hcaph9;
	g->goban.hcapV = hcapv9;
	break;
      case 13:
	g->goban.hcapH = hcaph13;
	g->goban.hcapV = hcapv13;
	break;
      case 19:
	g->goban.hcapH = hcaph19;
	g->goban.hcapV = hcapv19;
	break;
      default:
	g->goban.hcapH = NULL;
	g->goban.hcapV = NULL;
	break;
    }
    g->goban.board1.bsize = g->goban.bsize;
    g->goban.board2.bsize = g->goban.bsize;
    g->goban.top = g->goban.lineHeight + 5;
    g->goban.rad = (min(g->core.width, g->core.height) - (2 * g->goban.top)) / (g->goban.bsize * 2);
    g->goban.bottom = g->goban.top + g->goban.rad * 2 * g->goban.bsize;
}


static void reset(g)
     GobanWidget g;
{
    bzero (g->goban.board->b, sizeof(g->goban.board->b));
    g->goban.whoseTurn = BlackPiece;
    g->goban.lastMove = NULL;
    g->goban.whiteCaptures = 0;
    g->goban.blackCaptures = 0;
    MaybeCurrent(g);
}    

static void reset_forget (g)
     GobanWidget g;
{
    g->goban.markH = -1;
    g->goban.numTests = 0;
    g->goban.curMove = 0;
    g->goban.numMoves = 0;
    reset(g);
}

static void GobanInitialize(request, new)
     Widget request;
     Widget new;
{
    GobanWidget g = (GobanWidget) new;
    XtGCMask vmask;
    XGCValues xgcv;
    Widget w;
    int n;
    struct menuentry {
	char *text;
	void (*function)();
    } *m;
    static struct menuentry entries[] = {
	"First move (s)", do_first,
	"Step back (b)", do_backup,
	"Goto current move (c)", do_current,
	"Step forward (f)", do_forward,
	"Last move (l)", do_last,
	"Undo (u)", do_undo,
	"Pass (p)", do_pass,
	"Done removing (d)", do_done,
	NULL, NULL,
	"New Goban (n)", do_new,
	"Clear board", do_clear,
	NULL, NULL,
	"Quit board (q)", do_quit,
    };
    static XtCallbackRec cb[2] = { { NULL, NULL } , { NULL, NULL } };
    static Arg arg = { XtNcallback, (XtArgVal) cb } ;

    w = XtCreatePopupShell (XtNgobanMenu, simpleMenuWidgetClass, new, NULL, 0);
    for (n = 0, m = entries; n < XtNumber(entries); n++, m++) {
	if (m->function) {
	    cb[0].callback = (XtCallbackProc) m->function;
	    cb[0].closure = (caddr_t) new;
	    XtCreateManagedWidget (m->text, smeBSBObjectClass, w, &arg, 1);
	} else {
	    XtCreateManagedWidget ("line", smeLineObjectClass, w, 0, 0);
	}
    }
    g->goban.gobanMenu = w;
    g->goban.mode = GobanScrewingAround;
    g->goban.removing = False;
    if (g->core.width == 0)
	g->core.width = DEFWIDTH;
    if (g->core.height == 0)
	g->core.height = DEFHEIGHT;
    vmask = GCForeground | GCBackground | GCFillStyle;
    if (g->goban.font) {		/* Is font OK? */
	xgcv.font = g->goban.font->fid;	/* then use it */
	vmask |= GCFont;
    } else {
	g->goban.font = XQueryFont(XtDisplay(new), /* Find out about server default */
				   XGContextFromGC(DefaultGCOfScreen(XtScreen(new))));
    }
    g->goban.digit = XTextWidth (g->goban.font, "0", 1);
    g->goban.lineHeight = g->goban.font->ascent + g->goban.font->descent;
    xgcv.background = g->core.background_pixel;
    xgcv.fill_style = FillSolid;
    xgcv.foreground = g->goban.whitePixel;
    g->goban.whiteGC = XtGetGC (new, vmask, &xgcv);
    xgcv.foreground = g->goban.blackPixel;
    g->goban.blackGC = XtGetGC (new, vmask, &xgcv);
    xgcv.foreground = g->goban.linePixel;
    g->goban.lineGC = XtGetGC (new, vmask, &xgcv);
    xgcv.foreground = xgcv.background;
    xgcv.background = g->goban.linePixel;
    g->goban.bgGC = XtGetGC (new, vmask, &xgcv);
    g->goban.board = &g->goban.board1;
    g->goban.alt = &g->goban.board2;
    g->goban.current = TRUE;
    g->goban.blackCDisplayed = -1;
    g->goban.whiteCDisplayed = -1;
    checkvals(g);
    reset_forget(g);
}

static void GobanRealize(w, vmask, attrs)
     Widget w;
     XtValueMask *vmask;
     XSetWindowAttributes *attrs;
{
    (*XtSuperclass(w)->core_class.realize) (w, vmask, attrs);
    GobanResize(w);
}

static void GobanDestroy(w)
     Widget w;
{
    GobanWidget g = (GobanWidget) w;
    XtDestroyGC (g->goban.whiteGC);
    XtDestroyGC (g->goban.blackGC);
    XtDestroyGC (g->goban.lineGC);
    XtDestroyGC (g->goban.bgGC);
    XtDestroyWidget (g->goban.gobanMenu);
}

static void GobanResize(w)
     Widget w;
{
    GobanWidget g = (GobanWidget) w;
    checkvals(g);
}

static void DrawHcapPoint (g, h, v)
     GobanWidget g;
     int h;
     int v;
{
    Display *dpy = XtDisplay((Widget) g);
    Window win = XtWindow((Widget) g);
    GC gc = g->goban.lineGC;

    int r = g->goban.rad;
    int t = g->goban.top + r - 4;
    int d = r*2;
    
    XFillArc (dpy, win, gc, t + d * h, t + d * v, 8, 8, 0, 360*64);
}

static void DrawBoard (g)
     GobanWidget g;
{
    Display *dpy = XtDisplay((Widget) g);
    Window win = XtWindow((Widget) g);
    GC gc = g->goban.lineGC;
    int s = g->goban.bsize;
    int r = g->goban.rad;
    int t = g->goban.top;
    int b = g->goban.bottom;
    int dig = g->goban.digit;
    int ascent = g->goban.font->ascent;
    int h, x;
    int tl = t + r;
    int bl = b - r;
    char numbuf[4];
    int n;
    
    x = tl;
    for (h = 0; h < s; h++) {
	sprintf (numbuf, "%2d", s - h);
	XDrawString (dpy, win, gc, x - dig/2, t - 4, colNames + h, 1);
	XDrawString (dpy, win, gc, x - dig/2, b + ascent + 2, colNames + h, 1);
	XDrawString (dpy, win, gc, t - 3 - 2 * dig, x + ascent/2, numbuf, 2);
	XDrawString (dpy, win, gc, b + 4, x + ascent/2, numbuf, 2);
		     
	XDrawLine (dpy, win, gc, x, tl, x, bl);
	XDrawLine (dpy, win, gc, tl, x, bl, x);
	x += 2*r;
    }
    if (g->goban.hcapH) {
	for (n = 0; n < 9; n++)
	    DrawHcapPoint (g, g->goban.hcapH[n], g->goban.hcapV[n]);
    }
}

static void ErasePiece (g, h, v)
     GobanWidget g;
     int h;
     int v;
{
    Display *dpy = XtDisplay ((Widget) g);
    Window win = XtWindow ((Widget) g);
    GC gc = g->goban.lineGC;
    int r = g->goban.rad;
    int t = g->goban.top;
    int s = g->goban.bsize - 1;
    int d = 2 * r;
    int x = d * h + t;
    int y = d * v + t;
    int x1 = (h == 0) ? x + r : x;
    int x2 = (h == s) ? x + r : x + d;
    int y1 = (v == 0) ? y + r : y;
    int y2 = (v == s) ? y + r : y + d;
    int n;
    
    XClearArea (dpy, win, x, y, d, d, FALSE);
    
    XDrawLine (dpy, win, gc, x + r, y1, x + r, y2);
    XDrawLine (dpy, win, gc, x1, y + r, x2, y + r);

    if (g->goban.hcapH) {
	for (n = 0; n < 9; n++)
	    if (h == g->goban.hcapH[n] &&
		v == g->goban.hcapV[n])
		DrawHcapPoint (g, h, v);
    }
}


static void DrawGhost (g, h, v)
     GobanWidget g;
     int h;
     int v;
{
    Display *dpy = XtDisplay ((Widget) g);
    Window win = XtWindow ((Widget) g);
    GC fgc;
    int r = g->goban.rad;
    int t = g->goban.top;
    int d = 2 * r;
    int x = d * h + t;
    int y = d * v + t;
    fgc = g->goban.lineGC;
    XDrawArc (dpy, win, fgc, x, y, d - 1, d - 1, 0, 360*64);
    XDrawArc (dpy, win, fgc, x + 2, y + 2, d - 5, d - 5, 0, 360*64);
}

static void DrawPiece (g, h, v, piece)
     GobanWidget g;
     int h;
     int v;
     int piece;
{
    if (piece == NoPiece) {
	ErasePiece (g, h, v);
    } else if (piece == MarkerPiece) {
	DrawGhost (g, h, v);
    } else {
	Display *dpy = XtDisplay ((Widget) g);
	Window win = XtWindow ((Widget) g);
	GC fgc, bgc;
	int r = g->goban.rad;
	int t = g->goban.top;
	int d = 2 * r;
	int x = d * h + t;
	int y = d * v + t;
	if (piece == BlackTerritory || piece == WhiteTerritory) {
	    x += r / 2;
	    y += r / 2;
	    r = r / 2;
	    d = d / 2;
	    piece = (piece == BlackTerritory ? BlackPiece : WhitePiece);
	}
	if (piece == BlackPiece) {
	    fgc = g->goban.blackGC;
	    bgc = g->goban.whiteGC;
	} else {
	    fgc = g->goban.whiteGC;
	    bgc = g->goban.blackGC;
	}
	XFillArc (dpy, win, fgc, x, y, d - 1, d - 1, 0, 360*64);
	XDrawArc (dpy, win, bgc, x, y, d - 1, d - 1, 0, 360*64);
	if (d > 15) {
	    XDrawArc (dpy, win, bgc, x+5, y+5, d-10, d-10, 280*64, 45*64);
	}
	if (h == g->goban.markH &&
	    v == g->goban.markV) {
	    XFillArc (dpy, win, bgc, x+r/2, y+r/2, r/2 + 2, r/2 + 2, 0, 360*64);
	}
    }
}

static void DrawCurrentIndicator (g)
     GobanWidget g;
{
    Display *dpy = XtDisplay ((Widget) g);
    Window win = XtWindow ((Widget) g);
    int t = g->goban.top - 2;
    int s = g->goban.bottom - t + 1;
    GC gc = (g->goban.current) ? g->goban.bgGC : g->goban.lineGC;
    XSetLineAttributes (dpy, gc, 0, LineOnOffDash, CapNotLast, JoinMiter);
    XDrawRectangle (dpy, win, gc, t, t, s, s);
    XSetLineAttributes (dpy, gc, 0, LineSolid, CapNotLast, JoinMiter);
}

static void DrawMark (g)
     GobanWidget g;
{
    if (g->goban.markH >= 0) {
	DrawPiece (g, g->goban.markH, g->goban.markV,
		   g->goban.board->b[g->goban.markH][g->goban.markV]);
    }
}

static void SetMark (g, h, v)
     GobanWidget g;
     int h;
     int v;
{
    int oh = g->goban.markH;
    int ov = g->goban.markV;
    if (h != oh || v != ov) {
	g->goban.markH = h;
	g->goban.markV = v;
	if (oh >= 0) {
	    DrawPiece (g, oh, ov, g->goban.board->b[oh][ov]);
	}
	DrawMark(g);
    }
}

static void SetMarkByMove (g, m)
     GobanWidget g;
     GMove *m;
{
    if (!m ||
	m->type == GMPass ||
	m->type == GMHandicap)
	SetMark (g, -1, -1);
    else
	SetMark (g, m->h, m->v);
}
	


static void MaybeCurrent (g)
     GobanWidget g;
{
    Boolean c = (g->goban.numTests == 0 &&
		 g->goban.curMove == g->goban.numMoves);
    if (g->goban.current == c) return;
    g->goban.current = c;
    DrawCurrentIndicator(g);
}

static void MaybeUpdateCaptures(label, caps, oldcaps)
     Widget label;
     int caps;
     int *oldcaps;
{
    static char buf[10];
    static Arg args[] = { XtNlabel, (XtArgVal) buf,
			  };
    if (caps == *oldcaps) return;
    sprintf (buf, "%3d", caps);
    XtSetValues (label, args, XtNumber(args));
    *oldcaps = caps;
}

static void UpdateCaptures (g)
     GobanWidget g;
{
    MaybeUpdateCaptures(g->goban.whiteCapturesLabel, g->goban.whiteCaptures, &g->goban.whiteCDisplayed);
    MaybeUpdateCaptures(g->goban.blackCapturesLabel, g->goban.blackCaptures, &g->goban.blackCDisplayed);
}

static void UpdateBoard (g, display)
     GobanWidget g;
     Boolean display;
{
    GBoard *from = g->goban.board;
    GBoard *to = g->goban.alt;

    if (display) {
	int h, v;
	for (h = 0; h < to->bsize; h++) {
	    for (v = 0; v < to->bsize; v++)
		if (from->b[h][v] != to->b[h][v]) {
		    DrawPiece (g, h, v, to->b[h][v]);
		}
	}
    }
    g->goban.alt = from;
    g->goban.board = to;
    if (display) {
	SetMarkByMove (g, g->goban.lastMove);
	UpdateCaptures (g);
    }
}

static void DrawPieces (g)
     GobanWidget g;
{
    int s = g->goban.bsize;
    int h, v;

    for (h = 0; h < s; h++)
	for (v = 0; v < s; v++)
	    if (g->goban.board->b[h][v] != NoPiece)
		DrawPiece (g, h, v, g->goban.board->b[h][v]);
}


static void DrawAll (g)
     GobanWidget g;
{
    DrawBoard(g);
    DrawPieces(g);
    DrawCurrentIndicator(g);
}

static void GobanRedisplay(w, event, region)
     Widget w;
     XEvent *event;
     Region region;
{
    GobanWidget g = (GobanWidget) w;
    DrawAll(g);
}

static Boolean GobanSetValues(gcurrent, grequest, gnew)
     Widget gcurrent;
     Widget grequest;
     Widget gnew;
{
    GobanWidget gc = (GobanWidget) gcurrent;
    GobanWidget gn = ( GobanWidget) gnew;
    checkvals (gn);
    if (gn->goban.whiteCapturesLabel != gc->goban.whiteCapturesLabel) {
	gn->goban.whiteCDisplayed = -1;
    }
    if (gn->goban.blackCapturesLabel != gc->goban.blackCapturesLabel) {
	gn->goban.blackCDisplayed = -1;
    }
    UpdateCaptures(gn);
    return (gn->goban.bsize != gc->goban.bsize ||
	    gn->goban.rad != gc->goban.rad);
}

static void LocatePoint (g, x, y, h, v)
     GobanWidget g;
     int x;
     int y;
     int *h;
     int *v;
{
    int t = g->goban.top;
    int d = 2 *g->goban.rad;
	
    *h = (x - t) / d;
    *v = (y - t) / d;
    if (*h < 0 || *v < 0 ||
	*h >= g->goban.bsize ||
	*v >= g->goban.bsize) {
	*h = -1;
	*v = -1;
    }
}



/***********************************************************************
 *
 * GO logic
 *
 */



static void MarkGroup (b, c, h, v)
     GBoard *b;
     int c;
     int h;
     int v;
{
    b->b[h][v] = MarkerPiece;
    if (h > 0 && b->b[h-1][v] == c)
	MarkGroup (b, c, h-1, v);
    if (h+1 < b->bsize && b->b[h+1][v] == c)
	MarkGroup (b, c, h+1, v);
    if (v > 0 && b->b[h][v-1] == c)
	MarkGroup (b, c, h, v-1);
    if (v+1 < b->bsize && b->b[h][v+1] == c)
	MarkGroup (b, c, h, v+1);
}


static int CountLiberties (b)
     GBoard *b;
{
    int lib = 0;
    int h, v;
    
    for (h = 0; h < b->bsize; h++) {
	for (v = 0; v < b->bsize; v++) {
	    if (b->b[h][v] == NoPiece &&
		(((h > 0) && (b->b[h-1][v] == MarkerPiece)) ||
		 ((v > 0) && (b->b[h][v-1] == MarkerPiece)) ||
		 ((h+1 < b->bsize) && (b->b[h+1][v] == MarkerPiece)) ||
		 ((v+1 < b->bsize) && (b->b[h][v+1] == MarkerPiece))))
		lib++;
	}
    }
    return lib;
}

static int UnmarkGroup (b, c)
     GBoard *b;
     int c;
{
    int h, v;
    int size = 0;
    
    for (h = 0; h < b->bsize; h++) {
	for (v = 0; v < b->bsize; v++) {
	    if (b->b[h][v] == MarkerPiece) {
		size++;
		b->b[h][v] = c;
	    }
	}
    }
    return size;
}
    

static int KillGroup (b)
     GBoard *b;
{
    return UnmarkGroup (b, NoPiece);
}


static void MaybeKill (b, moving, h, v, caps, type, whichway)
     GBoard *b;
     int moving;
     int h;
     int v;
     int *caps;
     int *type;
     int whichway;
{
    int c = b->b[h][v];
    int n;
    
    if (c != otherColor[moving]) return;
    MarkGroup (b, c, h, v);
    if (CountLiberties(b)) {
	UnmarkGroup (b, c);
	return;
    } else {
	if (n = KillGroup(b)) {
	    *caps += n;
	    if (*caps == 1)
		*type = whichway;
	    else 
		*type = GMCapture;
	}
    }
}

static Boolean MoveLegal (g, h, v, caps, type)
     GobanWidget g;
     int h;
     int v;
     int *caps;
     int *type;
{
    GBoard *b = g->goban.alt;
    GMove *m;
    int moving = g->goban.whoseTurn;
    int libs;
    int cap, typ;
    
    if (g->goban.board->b[h][v] != NoPiece)	/* Space already occupied? */
	return FALSE;
    if (m = g->goban.lastMove) {
	switch (m->type) {		/* Enforce Ko rule */
	  case GMKoUp:
	    if (m->h == h && m->v-1 == v) return FALSE;
	    break;
	  case GMKoDown:
	    if (m->h == h && m->v+1 == v) return FALSE;
	    break;
	  case GMKoLeft:
	    if (m->h-1 == h && m->v == v) return FALSE;
	    break;
	  case GMKoRight:
	    if (m->h+1 == h && m->v == v) return FALSE;
	    break;
	}
    }
    bcopy (g->goban.board, b, sizeof(GBoard));
    b->b[h][v] = moving;
    cap = 0;
    typ = GMMove;
    if (h > 0) MaybeKill (b, moving, h-1, v, &cap, &typ, GMKoLeft);
    if (h+1 < b->bsize) MaybeKill (b, moving, h+1, v, &cap, &typ, GMKoRight);
    if (v > 0) MaybeKill (b, moving, h, v-1, &cap, &typ, GMKoUp);
    if (v+1 < b->bsize) MaybeKill (b, moving, h, v+1, &cap, &typ, GMKoDown);
    MarkGroup (b, moving, h, v);
    libs = CountLiberties(b);
    UnmarkGroup (b, moving);
    if (!libs)
	return FALSE;			/* Enforce suicide rule */
    *caps = cap;
    *type = typ;
    return TRUE;
}

Boolean RemoveLegal (g, h, v, caps)
     GobanWidget g;
     int h;
     int v;
     int *caps;
{
    int c = g->goban.board->b[h][v];
    GBoard *b = g->goban.alt;

    *caps = 0;
    if (c == NoPiece) return FALSE;
    bcopy (g->goban.board, b, sizeof(GBoard));
    MarkGroup (b, c, h, v);
    *caps = UnmarkGroup (b, NoPiece);
    return TRUE;
}
    
    

Boolean HandicapLegal (g, hcap)
     GobanWidget g;
     int hcap;
{
    GBoard *b = g->goban.alt;
    int *h = g->goban.hcapH;
    int *v = g->goban.hcapV;
    bcopy (g->goban.board, b, sizeof(GBoard));
    b->b[h[6]][v[6]] = BlackPiece;
    b->b[h[2]][v[2]] = BlackPiece;
    if (hcap == 2) return TRUE;
    b->b[h[0]][v[0]] = BlackPiece;
    if (hcap == 3) return TRUE;
    b->b[h[8]][v[8]] = BlackPiece;
    if (hcap == 4) return TRUE;
    if (hcap == 5 || hcap == 7 || hcap == 9) 
	b->b[h[4]][v[4]] = BlackPiece;
    if (hcap < 6) return TRUE;
    b->b[h[3]][v[3]] = BlackPiece;
    b->b[h[5]][v[5]] = BlackPiece;
    if (hcap < 8) return TRUE;
    b->b[h[1]][v[1]] = BlackPiece;
    b->b[h[7]][v[7]] = BlackPiece;
    return TRUE;
}    


static GMove *AddMove (g, type, h, v, test)
     GobanWidget g;
     int type;
     int h;
     int v;
     int test;
{
    GMove *m;
    if (test) {
	m = &g->goban.tests[g->goban.numTests];
	g->goban.numTests++;
	MaybeCurrent(g);		/* Actually I know we're not, but this does the right thing */
    } else {
	m = &g->goban.moves[g->goban.numMoves];
	g->goban.numMoves++;
	if (g->goban.current) {
	    g->goban.curMove = g->goban.numMoves;
	}
	if (type == GMPass && g->goban.numMoves >= 3 &&
	    m[-1].type == GMPass &&
	    m[-2].type == GMPass) {
	    g->goban.removing = True;
	}
    }
    m->type = type;
    m->h = h;
    m->v = v;
    return m;
}

static void DoneMove (g, m, caps, display)
     GobanWidget g;
     GMove *m;
     int caps;
     Boolean display;
{
    g->goban.lastMove = m;
    if (g->goban.whoseTurn == WhitePiece)
	g->goban.whiteCaptures += caps;
    else
	g->goban.blackCaptures += caps;
    g->goban.whoseTurn = otherColor[g->goban.whoseTurn];
    UpdateBoard (g, display);
}


static void GobanMoveCommand (g, cmd)
     GobanWidget g;
     char *cmd;
{
    if (g->goban.mode == GobanPlaying) {
	QueueForServer (cmd, strlen(cmd));
    }
}

Boolean GobanPass (g)
     GobanWidget g;
{
    bcopy (g->goban.board, g->goban.alt, sizeof(GBoard));
    DoneMove (g, AddMove (g, GMPass, 0, 0, 0), 0, TRUE);
    return TRUE;
}

Boolean GobanMove (g, h, v, test)
     GobanWidget g;
     int h;
     int v;
     int test;
{
    int moving = g->goban.whoseTurn;
    int caps, type;
    
    if (g->goban.numMoves >= MAXMOVES)
	return FALSE;
    if (!MoveLegal (g, h, v, &caps, &type))
	return FALSE;
    DoneMove (g, AddMove(g, type, h, v, test), caps, TRUE);
    return TRUE;
}    

Boolean GobanRemove (g, h, v) /* Never a test */
     GobanWidget g;
     int h;
     int v;
{
    int caps;
    if (!RemoveLegal(g, h, v, &caps)) return FALSE;
    DoneMove (g, AddMove(g, GMRemove, h, v, FALSE), caps, TRUE);
    return TRUE;
}

Boolean GobanHandicap (g, hcap, test)
     GobanWidget g;
     int hcap;
     int test;
{
    if (g->goban.curMove ||		/* Must be first move */
	g->goban.numTests ||
        !g->goban.hcapH ||		/* Unsupported board size */
	hcap < 2 ||			/* Out of range */
	hcap > 9) return FALSE;
    if (!HandicapLegal (g, hcap))
	return False;
    DoneMove (g, AddMove (g, GMHandicap, hcap, 0, test), 0, TRUE);
    return TRUE;
}
    
void XGobanSetPiece (w, h, v, piece)
     Widget w;
     int h;
     int v;
     int piece;
{
    GobanWidget g = (GobanWidget) w;
    g->goban.board->b[h][v] = piece;
    DrawPiece (g, h, v, piece);
}

Boolean XGobanReceivedHandicap (w, move, hcap)
     Widget w;
     int move;
     int hcap;
{
    GobanWidget g = (GobanWidget) w;
    if (g->goban.numMoves == 1 && move == 0 && /* Repeat of most recent move? */
	g->goban.moves[0].type == GMHandicap &&
	g->goban.moves[0].h == hcap) return TRUE;
    if (g->goban.numMoves != 0 || move != 0) return FALSE;
    if (g->goban.current) {
	GobanHandicap (g, hcap, FALSE);
    } else {
	AddMove (g, GMHandicap, hcap, 0, FALSE);
    }
    return TRUE;
}

Boolean XGobanReceivedPass (w, move)
     Widget w;
     int move;
{
    GobanWidget g = (GobanWidget) w;
    if (g->goban.current) {
	GobanPass (g);
    } else {
	AddMove (g, GMPass, 0, 0, FALSE);
    }
    return TRUE;
}

Boolean XGobanReceivedUndo (w)
     Widget w;
{
    GobanWidget g = (GobanWidget) w;
    if (g->goban.numMoves == 0) return FALSE;	/* huh? */
    g->goban.numMoves--;
    if (g->goban.curMove > g->goban.numMoves) {
	if (g->goban.numTests) {	/* The person has been playing ahead from a move now undone. */
					/* Do something magic. */
	    g->goban.curMove = g->goban.numMoves;
	    if (g->goban.numTests == MAXMOVES) {
		g->goban.numTests = 0;	/* Handle weird special case cheaply and easily */
		MaybeCurrent(g);	
		RollForward(g);
	    } else {
		int i;			/* Move the undone move to the front of the tests!! */
		bcopy (g->goban.tests, g->goban.tests + 1, g->goban.numTests * sizeof(GMove));
		g->goban.lastMove = g->goban.tests + g->goban.numTests;
		g->goban.numTests++;
		bcopy (g->goban.moves + g->goban.numMoves, g->goban.tests, sizeof(GMove));
					/* No drawing necessary */
	    }
	} else {
	    Backup(w, NULL, NULL, NULL);
	}
    }
    return TRUE;
}
	
    
Boolean XGobanReceivedMove (w, move, h, v)
     Widget w;
     int move;
     int h;
     int v;
{
    GobanWidget g = (GobanWidget) w;
    if (g->goban.numMoves - 1 == move && /* Is this a repeat of the most recent move? */
	g->goban.moves[move].h == h &&
	g->goban.moves[move].v == v) return TRUE;
    if (g->goban.numMoves == move) {
	if (g->goban.current) {
	    GobanMove (g, h, v, FALSE);
	} else {
	    AddMove (g, GMUnknown, h, v, FALSE);
	}
	return TRUE;
    } else {
	return FALSE;
    }
}
    
Boolean XGobanReceivedRemove (w, h, v)
     Widget w;
     int h;
     int v;
{
    int caps;
    GobanWidget g = (GobanWidget) w;
    if (RemoveLegal (g, h, v, &caps)) {
	if (g->goban.current) {
	    GobanRemove (g, h, v);
	} else {
	    AddMove (g, GMRemove, h, v, FALSE);
	}
	return TRUE;
    } else {
	return FALSE;
    }
}

void XGobanResetGame (w, m)
     Widget w;
     GobanMode m;
{
    GobanWidget g = (GobanWidget) w;
    
    Clear(w, NULL, NULL, NULL);
    g->goban.mode = m;
}

static void RollMove (g, m, display)
     GobanWidget g;
     GMove *m;
     Boolean display;
{
    int caps, type;

    switch (m->type) {
      case GMMove:			/* Optimization for common case */
	g->goban.board->b[m->h][m->v] = g->goban.whoseTurn;
	if (display) {
	    SetMark (g, m->h, m->v);	/* Causes piece to be drawn too */
	}
	g->goban.lastMove = m;
	g->goban.whoseTurn = otherColor[g->goban.whoseTurn];
	break;
      case GMCapture:			/* Do the general thing, don't try to optimize */
      case GMKoUp:
      case GMKoDown:
      case GMKoLeft:
      case GMKoRight:
	MoveLegal (g, m->h, m->v, &caps, &type);
	DoneMove (g, m, caps, display);
	break;
      case GMHandicap:
	HandicapLegal (g, m->h);
	DoneMove (g, m, 0, display);
	break;
      case GMPass:
	bcopy (g->goban.board, g->goban.alt, sizeof(GBoard));
	DoneMove (g, m, 0, display);
	break;
      case GMRemove:
	RemoveLegal (g, m->h, m->v, &caps);
	DoneMove (g, m, caps, display);
	break;
      case GMUnknown:
	MoveLegal (g, m->h, m->v, &caps, &type);
	m->type = type;
	DoneMove (g, m, caps, display);
	break;
    }
}

static void RollForward (g)
     GobanWidget g;
{
    GBoard displayed, *b;
    int m;

    bcopy (g->goban.board, &displayed, sizeof(GBoard));
    reset(g);
    for (m = 0; m < g->goban.curMove; m++)
	RollMove (g, g->goban.moves + m, FALSE);
    for (m = 0; m < g->goban.numTests; m++)
	RollMove (g, g->goban.tests + m, FALSE);
    b = g->goban.board;
    g->goban.board = g->goban.alt;
    g->goban.alt = b;
    bcopy (&displayed, g->goban.board, sizeof(GBoard));
    UpdateBoard (g, TRUE);
}
       






static void MouseAt (g, x, y)
     GobanWidget g;
     int x;
     int y;
{
    int caps, type;
    int h, v;
	
    LocatePoint (g, x, y, &h, &v);

    if (g->goban.btnH >= 0) {
	DrawPiece (g, g->goban.btnH, g->goban.btnV, g->goban.board->b[g->goban.btnH][g->goban.btnV]);
    }
    g->goban.btnH = -1;
    g->goban.btnV = -1;
    if (h >= 0) {
	int removing = g->goban.removing && g->goban.btnDown == 1;
	if ((removing && RemoveLegal (g, h, v, &caps)) ||
	    (!removing && MoveLegal (g, h, v, &caps, &type))) {
	    g->goban.btnH = h;
	    g->goban.btnV = v;
	    DrawGhost (g, h, v);
	}
    }
}

static void Consider(w, event, params, num_params)	
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *num_params;
{
    GobanWidget g = (GobanWidget) w;
    
    if (event->type != ButtonPress) return; /* Huh? */
    if (g->goban.btnDown) return;	/* Hmm.  pressing two buttons at once I guess */
    switch (event->xbutton.button) {
      case 1:			       
	if (!g->goban.current || g->goban.mode == GobanObserving)
	    return;
	break;
      case 2:				
	return;
      case 3:
	break;
    }
    g->goban.btnDown = event->xbutton.button;
    g->goban.btnH = -1;
    g->goban.btnV = -1;
    MouseAt (g, event->xbutton.x, event->xbutton.y);
}

static void Adjust(w, event, params, num_params)	
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *num_params;
{
    GobanWidget g = (GobanWidget) w;
    if (event->type != MotionNotify) return;
    if (!g->goban.btnDown) return;	/* Weird */
    MouseAt (g, event->xmotion.x, event->xmotion.y);
}

static void Commit(w, event, params, num_params)
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *num_params;
{
    GobanWidget g = (GobanWidget) w;
    int test, removing;
    
    if (event->type != ButtonRelease) return; 
    if (!g->goban.btnDown) return;
    if (event->xbutton.button != g->goban.btnDown) return;
    MouseAt (g, event->xbutton.x, event->xbutton.y);
    if (g->goban.btnH >= 0) {
	DrawPiece (g, g->goban.btnH, g->goban.btnV, g->goban.board->b[g->goban.btnH][g->goban.btnV]);
	removing = g->goban.removing && g->goban.btnDown == 1;
	test = (g->goban.btnDown == 3);
	if (test || g->goban.mode == GobanScrewingAround) {
	    if (removing)
		GobanRemove (g, g->goban.btnH, g->goban.btnV);
	    else 
		GobanMove (g, g->goban.btnH, g->goban.btnV, test);
	} else {
	    char cmd[6];
	    sprintf (cmd, "%c%d\n", colNames[g->goban.btnH], g->goban.bsize - g->goban.btnV);
	    GobanMoveCommand (g, cmd);
	}
    }
    g->goban.btnDown = 0;
}


static void Handicap(w, event, params, num_params)
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *num_params;
{
    GobanWidget g = (GobanWidget) w;
    char cmd[15];
    int hcap = atoi(params[0]);
    int test = g->goban.numMoves != 0;

    if (test || g->goban.mode == GobanScrewingAround) {
	GobanHandicap (g, hcap, test);
    } else {
	sprintf (cmd, "handicap %d\n", hcap);
	GobanMoveCommand (g, cmd);
    }
}
    
     
static void Backup(w, event, params, num_params)
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *num_params;
{
    GobanWidget g = (GobanWidget) w;
    GMove *undo;

    undo = g->goban.lastMove;
    if (g->goban.numTests > 0) {
	g->goban.numTests--;
    } else if (g->goban.curMove > 0) {
	g->goban.curMove--;
    } else
	return;
    MaybeCurrent(g);
    switch (undo->type) {
      case GMMove:
	g->goban.board->b[undo->h][undo->v] = NoPiece;
	ErasePiece (g, undo->h, undo->v);
					/* fall through */
      case GMPass:
	g->goban.whoseTurn = otherColor[g->goban.whoseTurn];
	if (g->goban.numTests > 0) {
	    g->goban.lastMove = g->goban.tests + g->goban.numTests - 1;
	} else if (g->goban.curMove > 0) {
	    g->goban.lastMove = g->goban.moves + g->goban.curMove - 1;
	} else
	    g->goban.lastMove = NULL;
	SetMarkByMove (g, g->goban.lastMove);
	break;
      default:
	RollForward(g);
    }
}
	
static void Forward(w, event, params, num_params)
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *num_params;
{
    GobanWidget g = (GobanWidget) w;
    
    if (g->goban.numTests > 0)
	return;
    if (g->goban.curMove == g->goban.numMoves)
	return;
    RollMove (g, g->goban.moves + g->goban.curMove, TRUE);
    g->goban.curMove++;
    MaybeCurrent(g);
}
	
static void Current(w, event, params, num_params)
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *num_params;
{
    GobanWidget g = (GobanWidget) w;
    if (g->goban.numTests == 0) return;
    g->goban.numTests = 0;
    RollForward(g);
}

static void First(w, event, params, num_params)
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *num_params;
{
    GobanWidget g = (GobanWidget) w;
    if (g->goban.curMove == 0 && g->goban.numTests == 0) return;
    g->goban.curMove = 0;
    g->goban.numTests = 0;
    RollForward(g);
}

static void Last(w, event, params, num_params)
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *num_params;
{
    GobanWidget g = (GobanWidget) w;
    if (g->goban.curMove == g->goban.numMoves && g->goban.numTests == 0) return;
    g->goban.curMove = g->goban.numMoves;
    g->goban.numTests = 0;
    RollForward(g);
}


static void Pass(w, event, params, num_params)
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *num_params;
{
    GobanWidget g = (GobanWidget) w;
    if (!g->goban.current)		/* are we at the current game position */
	return;				/* If not, user has no reason to be passing */
    if (g->goban.mode == GobanScrewingAround) {
	GobanPass (g);
    } else {
	GobanMoveCommand(g, "pass\n");
    }
}

static void Done(w, event, params, num_params)
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *num_params;
{
    GobanWidget g = (GobanWidget) w;
    if (g->goban.removing)
	GobanMoveCommand (g, "done\n");
}

static void Undo(w, event, params, num_params)
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *num_params;
{
    GobanWidget g = (GobanWidget) w;
    if (!g->goban.current)		/* are we at the current game position */
	return;				/* If not, user should use backup */
    
    if (g->goban.mode == GobanScrewingAround) {
	if (g->goban.numMoves == 0) return;
	g->goban.numMoves--;
	Backup (w, NULL, NULL, NULL);
    } else {
	GobanMoveCommand(g, "undo\n");
    }
}


static void Clear(w, event, params, num_params)
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *num_params;
{
    GobanWidget g = (GobanWidget) w;
    reset_forget(g);
    if (XtIsRealized(w))
	XClearArea(XtDisplay(w), XtWindow(w), 0, 0, 0, 0, TRUE);
}

static void New(w, event, params, num_params)
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *num_params;
{
    PopupGoban(NULL, "Scratch game", 19);	/* Widget abstraction - kerpow! */
}
	
static void Quit(w, event, params, num_params)
     Widget w;
     XEvent *event;
     String *params;
     Cardinal *num_params;
{
    GobanWidget g = (GobanWidget) w;
    
    if (g->goban.shell)
	XtDestroyWidget (g->goban.shell); /* Matricide */
}


void XGobanNew (w)
     Widget w;
{
    New (w, NULL, NULL, NULL);
}

void XGobanClear (w)
     Widget w;
{
    Clear (w, NULL, NULL, NULL);
}

void XGobanQuit (w)
     Widget w;
{
    Quit (w, NULL, NULL, NULL);
}

void XGobanDone (w)
     Widget w;
{
    Done (w, NULL, NULL, NULL);
}

void XGobanUndo (w)
     Widget w;
{
    Undo (w, NULL, NULL, NULL);
}

void XGobanPass (w)
     Widget w;
{
    Pass (w, NULL, NULL, NULL);
}


void XGobanForward (w)
     Widget w;
{
    Forward (w, NULL, NULL, NULL);
}

void XGobanFirst (w)
     Widget w;
{
    First (w, NULL, NULL, NULL);
}

void XGobanLast (w)
     Widget w;
{
    Last (w, NULL, NULL, NULL);
}

void XGobanBackward (w)
     Widget w;
{
    Backup (w, NULL, NULL, NULL);
}

void XGobanCurrent (w)
     Widget w;
{
    Current (w, NULL, NULL, NULL);
}

void XGobanHandicap (w, hcap)
     Widget w;
     int hcap;
{
    Cardinal param = 1;
    static char buf[10];
    static String params[1] = { buf };
    sprintf (buf, "%d", hcap);
    
    Handicap (w, NULL, params, &param);
}

static void do_backup(gw, closure, data)
     Widget gw;
     caddr_t closure;
     caddr_t data;
{
    Backup((Widget) closure, NULL, NULL, NULL);
}

static void do_forward(gw, closure, data)
     Widget gw;
     caddr_t closure;
     caddr_t data;
{
    Forward((Widget) closure, NULL, NULL, NULL);
}

static void do_current(gw, closure, data)
     Widget gw;
     caddr_t closure;
     caddr_t data;
{
    Current((Widget) closure, NULL, NULL, NULL);
}

static void do_first(gw, closure, data)
     Widget gw;
     caddr_t closure;
     caddr_t data;
{
    First((Widget) closure, NULL, NULL, NULL);
}

static void do_last(gw, closure, data)
     Widget gw;
     caddr_t closure;
     caddr_t data;
{
    Last((Widget) closure, NULL, NULL, NULL);
}

static void do_undo(gw, closure, data)
     Widget gw;
     caddr_t closure;
     caddr_t data;
{
    Undo((Widget) closure, NULL, NULL, NULL);
}

static void do_pass(gw, closure, data)
     Widget gw;
     caddr_t closure;
     caddr_t data;
{
    Pass((Widget) closure, NULL, NULL, NULL);
}

static void do_clear(gw, closure, data)
     Widget gw;
     caddr_t closure;
     caddr_t data;
{
    Clear((Widget) closure, NULL, NULL, NULL);
}

static void do_quit(gw, closure, data)
     Widget gw;
     caddr_t closure;
     caddr_t data;
{
    Quit ((Widget) closure, NULL, NULL, NULL);
}

static void do_new(gw, closure, data)
     Widget gw;
     caddr_t closure;
     caddr_t data;
{
    New ((Widget) closure, NULL, NULL, NULL);
}

static void do_done(gw, closure, data)
     Widget gw;
     caddr_t closure;
     caddr_t data;
{
    Done ((Widget) closure, NULL, NULL, NULL);
}

