/*
 * $XConsortium: grid.c /main/30 1996/01/14 16:51:17 kaleb $
 *
 * 
Copyright (c) 1989  X Consortium

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
X CONSORTIUM BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Except as contained in this notice, the name of the X Consortium shall not be
used in advertising or otherwise to promote the sale, use or other dealings
in this Software without prior written authorization from the X Consortium.
 * *
 * Author:  Jim Fulton, MIT X Consortium
 */


#include <X11/IntrinsicP.h>
#include <X11/StringDefs.h>
#include <X11/Xaw/SimpleP.h>
#include <X11/Xmu/Converters.h>
#include "gridP.h"

#ifdef XKB
#include <X11/extensions/XKBbells.h>
#else
#define	XkbBI_MinorError		2
#define	XkbBI_Ignore			11
#endif

#ifdef XKB
#define Bell(w,n) XkbStdBell(XtDisplay(w), XtWindow(w), 50, n)
#else
#define Bell(w,n) XBell(XtDisplay(w), 0)
#endif


static void ClassInitialize(), Initialize(), Realize(), Redisplay(), Notify();
static void Destroy(), Resize(), paint_grid();
static Boolean SetValues();

#define Offset(field) XtOffsetOf(FontGridRec, fontgrid.field)

static XtResource resources[] = {
    { XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
	Offset(text_font), XtRString, (XtPointer) NULL },
    { XtNcellColumns, XtCCellColumns, XtRInt, sizeof(int),
	Offset(cell_cols), XtRImmediate, (XtPointer) 0 },
    { XtNcellRows, XtCCellRows, XtRInt, sizeof(int),
	Offset(cell_rows), XtRImmediate, (XtPointer) 0 },
    { XtNcellWidth, XtCCellWidth, XtRInt, sizeof(int),
	Offset(cell_width), XtRImmediate, (XtPointer) 0 },
    { XtNcellHeight, XtCCellHeight, XtRInt, sizeof(int),
	Offset(cell_height), XtRImmediate, (XtPointer) 0 },
    { XtNstartChar, XtCStartChar, XtRDimension, sizeof(Dimension),
	Offset(start_char), XtRImmediate, (XtPointer) 0xffff },
    { XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
	Offset(foreground_pixel), XtRString, (XtPointer) XtDefaultForeground },
    { XtNcenterChars, XtCCenterChars, XtRBoolean, sizeof(Boolean),
	Offset(center_chars), XtRImmediate, (XtPointer) FALSE },
    { XtNboxChars, XtCBoxChars, XtRBoolean, sizeof(Boolean),
	Offset(box_chars), XtRImmediate, (XtPointer) FALSE },
    { XtNboxColor, XtCForeground, XtRPixel, sizeof(Pixel),
	Offset(box_pixel), XtRString, (XtPointer) XtDefaultForeground },
    { XtNcallback, XtCCallback, XtRCallback, sizeof(XtPointer),
	Offset(callbacks), XtRCallback, (XtPointer) NULL },
    { XtNinternalPad, XtCInternalPad, XtRInt, sizeof(int),
	Offset(internal_pad), XtRImmediate, (XtPointer) 4 },
    { XtNgridWidth, XtCGridWidth, XtRInt, sizeof(int),
	Offset(grid_width), XtRImmediate, (XtPointer) 1 },
};

#undef Offset

static char defaultTranslations[] = 
  "<ButtonPress>:  notify()";

static XtActionsRec actions_list[] = {
    { "notify",		Notify },
};

FontGridClassRec fontgridClassRec = {
  { /* core fields */
    /* superclass               */      (WidgetClass) &simpleClassRec,
    /* class_name               */      "FontGrid",
    /* widget_size              */      sizeof(FontGridRec),
    /* class_initialize         */      ClassInitialize,
    /* class_part_initialize    */      NULL,
    /* class_inited             */      FALSE,
    /* initialize               */      Initialize,
    /* initialize_hook          */      NULL,
    /* realize                  */      Realize,
    /* actions                  */      actions_list,
    /* num_actions              */      XtNumber(actions_list),
    /* resources                */      resources,
    /* num_resources            */      XtNumber(resources),
    /* xrm_class                */      NULLQUARK,
    /* compress_motion          */      TRUE,
    /* compress_exposure        */      TRUE,
    /* compress_enterleave      */      TRUE,
    /* visible_interest         */      FALSE,
    /* destroy                  */      Destroy,
    /* resize                   */      Resize,
    /* expose                   */      Redisplay,
    /* set_values               */      SetValues,
    /* 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
  }
};

WidgetClass fontgridWidgetClass = (WidgetClass) &fontgridClassRec;


/*
 * public routines
 */

void GetFontGridCellDimensions (w, startp, ncolsp, nrowsp)
    Widget w;
    Dimension *startp;
    int *ncolsp, *nrowsp;
{
    FontGridWidget fgw = (FontGridWidget) w;
    *startp = (long)fgw->fontgrid.start_char;
    *ncolsp = fgw->fontgrid.cell_cols;
    *nrowsp = fgw->fontgrid.cell_rows;
}


void GetPrevNextStates (w, prevvalidp, nextvalidp)
    Widget w;
    Bool *prevvalidp, *nextvalidp;
{
    FontGridWidget fgw = (FontGridWidget) w;

    XFontStruct *fs = fgw->fontgrid.text_font;
    long minn = (long) ((fs->min_byte1 << 0) | fs->min_char_or_byte2);
    long maxn = (long) ((fs->max_byte1 << 8) | fs->max_char_or_byte2);

    *prevvalidp = ((long)fgw->fontgrid.start_char > minn);
    *nextvalidp = (((long)fgw->fontgrid.start_char +
		    (fgw->fontgrid.cell_cols * fgw->fontgrid.cell_rows))
		   < maxn);
}



/*
 * private routines and methods
 */


static GC get_gc (fgw, fore)
    FontGridWidget fgw;
    Pixel fore;
{
    XtGCMask mask;
    XGCValues gcv;

    mask = (GCForeground | GCBackground | GCFunction | GCFont);
    gcv.foreground = fore;
    gcv.background = fgw->core.background_pixel;
    gcv.function = GXcopy;
    gcv.font = fgw->fontgrid.text_font->fid;
    gcv.cap_style = CapProjecting;
    mask |= GCCapStyle;
    if (fgw->fontgrid.grid_width > 0) {
	mask |= GCLineWidth;
	gcv.line_width = ((fgw->fontgrid.grid_width < 2) ? 0 : 
			  fgw->fontgrid.grid_width);
    }
    return (XtGetGC ((Widget) fgw, mask, &gcv));
}


static void ClassInitialize ()
{
    XtAddConverter (XtRString, XtRLong, XmuCvtStringToLong, NULL, 0);
}


static void Initialize (request, new, args, num_args)
    Widget request, new;
    ArgList args;
    Cardinal *num_args;
{
    FontGridWidget reqfg = (FontGridWidget) request;
    FontGridWidget newfg = (FontGridWidget) new;
    XFontStruct *fs = newfg->fontgrid.text_font;
    unsigned maxn;

    if (reqfg->fontgrid.cell_cols <= 0)
      newfg->fontgrid.cell_cols = 16;

    if (reqfg->fontgrid.cell_rows <= 0) {
	if (fs && fs->max_byte1 == 0) {
	    newfg->fontgrid.cell_rows = (fs->max_char_or_byte2 / 
					 newfg->fontgrid.cell_cols) + 1;
	    if (newfg->fontgrid.cell_rows > 16)
	      newfg->fontgrid.cell_rows = 16;
	} else
	  newfg->fontgrid.cell_rows = 16;
    }

    if (reqfg->fontgrid.cell_width <= 0)
      newfg->fontgrid.cell_width = (fs ? DefaultCellWidth (newfg) : 1);
    if (reqfg->fontgrid.cell_height <= 0)
      newfg->fontgrid.cell_height = (fs ? DefaultCellHeight (newfg) : 1);

    /* give a nice size that fits one screen full */
    if (newfg->core.width == 0)
      newfg->core.width = (newfg->fontgrid.cell_width *
			   newfg->fontgrid.cell_cols +
			   newfg->fontgrid.grid_width *
			   (newfg->fontgrid.cell_cols + 1));

    if (newfg->core.height == 0) 
      newfg->core.height = (newfg->fontgrid.cell_height * 
			    newfg->fontgrid.cell_rows +
			    newfg->fontgrid.grid_width *
			    (newfg->fontgrid.cell_rows + 1));

    /*
     * select the first character
     */

    if (newfg->fontgrid.start_char == 0xffff) {
	newfg->fontgrid.start_char = (fs ? (unsigned)(fs->min_byte1 << 8) : 0);
    }
    if (fs) {
	maxn = ((fs->max_byte1 << 8) | fs->max_char_or_byte2);
	if (newfg->fontgrid.start_char > maxn) 
	  newfg->fontgrid.start_char = (maxn + 1 - 
					(newfg->fontgrid.cell_cols * 
					 newfg->fontgrid.cell_rows));
    }
}

static void Realize (gw, valueMask, attributes)
    Widget gw;
    Mask *valueMask;
    XSetWindowAttributes *attributes;
{
    FontGridWidget fgw = (FontGridWidget) gw;
    FontGridPart *p = &fgw->fontgrid;

    p->text_gc = get_gc (fgw, p->foreground_pixel);
    p->box_gc = get_gc (fgw, p->box_pixel);
    Resize (gw);

    (*(XtSuperclass(gw)->core_class.realize)) (gw, valueMask, attributes);
    return;
}



static void Destroy (gw)
    Widget gw;
{
    FontGridWidget fgw = (FontGridWidget) gw;

    XtReleaseGC (gw, fgw->fontgrid.text_gc);
    XtReleaseGC (gw, fgw->fontgrid.box_gc);
}


static void Resize (gw)
    Widget gw;
{
    FontGridWidget fgw = (FontGridWidget) gw;

    /* recompute in case we've changed size */
    fgw->fontgrid.cell_width = CellWidth (fgw);
    if (fgw->fontgrid.cell_width <= 0)
	fgw->fontgrid.cell_width = 1;
    fgw->fontgrid.cell_height = CellHeight (fgw);
    if (fgw->fontgrid.cell_height <= 0)
	fgw->fontgrid.cell_height = 1;
    fgw->fontgrid.xoff = (fgw->fontgrid.cell_width -
			    DefaultCellWidth (fgw)) / 2;
    fgw->fontgrid.yoff = (fgw->fontgrid.cell_height -
			    DefaultCellHeight (fgw)) / 2;

}


/* ARGSUSED */
static void Redisplay (gw, event, region)
    Widget gw;
    XEvent *event;
    Region region;
{
    FontGridWidget fgw = (FontGridWidget) gw;
    XRectangle rect;			/* bounding rect for region */
    int left, right, top, bottom;	/* which cells were damaged */
    int cw, ch;				/* cell size */

    if (!fgw->fontgrid.text_font) {
	Bell (gw, XkbBI_BadValue);
	return;
    }

    /*
     * compute the left, right, top, and bottom cells that were damaged
     */
    XClipBox (region, &rect);
    cw = fgw->fontgrid.cell_width + fgw->fontgrid.grid_width;
    ch = fgw->fontgrid.cell_height + fgw->fontgrid.grid_width;
    if ((left = (((int) rect.x) / cw)) < 0) left = 0;
    right = (((int) (rect.x + rect.width - 1)) / cw);
    if ((top = (((int) rect.y) / ch)) < 0) top = 0;
    bottom = (((int) (rect.y + rect.height - 1)) / ch);

    paint_grid (fgw, left, top, right - left + 1, bottom - top + 1);
}


static void paint_grid (fgw, col, row, ncols, nrows)
    FontGridWidget fgw;			/* widget in which to draw */
    int col, row;			/* where to start */
    int ncols, nrows;			/* number of cells */
{
    FontGridPart *p = &fgw->fontgrid;
    int i, j;
    Display *dpy = XtDisplay(fgw);
    Window wind = XtWindow(fgw);
    int cw = p->cell_width + p->grid_width;
    int ch = p->cell_height + p->grid_width;
    int tcols = p->cell_cols;
    int trows = p->cell_rows;
    int x1, y1, x2, y2, x, y;
    unsigned maxn = ((p->text_font->max_byte1 << 8) |
		     p->text_font->max_char_or_byte2);
    unsigned n, prevn;
    int startx;

    if (col + ncols >= tcols) {
	ncols = tcols - col;
	if (ncols < 1) return;
    }

    if (row + nrows >= trows) {
	nrows = trows - row;
	if (nrows < 1) return;
    }

    /*
     * paint the grid lines for the indicated rows 
     */
    if (p->grid_width > 0) {
	int half_grid_width = p->grid_width >> 1;
	x1 = col * cw + half_grid_width;
	y1 = row * ch + half_grid_width;
	x2 = x1 + ncols * cw;
	y2 = y1 + nrows * ch;
	for (i = 0, x = x1; i <= ncols; i++, x += cw) {
	    XDrawLine (dpy, wind, p->box_gc, x, y1, x, y2);
	}
	for (i = 0, y = y1; i <= nrows; i++, y += ch) {
	    XDrawLine (dpy, wind, p->box_gc, x1, y, x2, y);
	}
    }

	
    /*
     * Draw a character in every box; treat all fonts as if they were 16bit
     * fonts.  Store the high eight bits in byte1 and the low eight bits in 
     * byte2.
     */
    prevn = p->start_char + col + row * tcols;
    startx = col * cw + p->internal_pad + p->grid_width;
    for (j = 0,
	 y = row * ch + p->internal_pad + p->grid_width + p->text_font->ascent;
	 j < nrows; j++, y += ch) {
	n = prevn;
	for (i = 0, x = startx; i < ncols; i++, x += cw) {
	    XChar2b thechar;
	    int xoff = p->xoff, yoff = p->yoff;

	    if (n > maxn) goto done;	/* no break out of nested */

	    thechar.byte1 = (n >> 8);	/* high eight bits */
	    thechar.byte2 = (n & 255);	/* low eight bits */
	    if (p->box_chars || p->center_chars) {
		XCharStruct metrics;
		int direction, fontascent, fontdescent;

		XTextExtents16 (p->text_font, &thechar, 1, &direction,
				&fontascent, &fontdescent, &metrics);

		if (p->center_chars) {
		    /*
		     * We want to move the origin by enough to center the ink
		     * within the cell.  The left edge will then be at 
		     * (cell_width - (rbearing - lbearing)) / 2; so we subtract
		     * the lbearing to find the origin.  Ditto for vertical.
		     */
		    xoff = (((p->cell_width -
			      (metrics.rbearing - metrics.lbearing)) / 2) -
			    p->internal_pad - metrics.lbearing);
		    yoff = (((p->cell_height - 
			      (metrics.descent + metrics.ascent)) / 2) -
			    p->internal_pad -
			    p->text_font->ascent + metrics.ascent);
		}
		if (p->box_chars) {
		    XDrawRectangle (dpy, wind, p->box_gc,
				    x + xoff, y + yoff - p->text_font->ascent, 
				    metrics.width - 1,
				    fontascent + fontdescent - 1);
		}
	    }
	    XDrawString16 (dpy, wind, p->text_gc, x + xoff, y + yoff,
			   &thechar, 1);
	    n++;
	}
	prevn += tcols;
    }

  done:
    return;
}

/*ARGSUSED*/
static Boolean SetValues (current, request, new, args, num_args)
    Widget current, request, new;
    ArgList args;
    Cardinal *num_args;
{
    FontGridWidget curfg = (FontGridWidget) current;
    FontGridWidget newfg = (FontGridWidget) new;
    Boolean redisplay = FALSE;

    if (curfg->fontgrid.text_font != newfg->fontgrid.text_font ||
	curfg->fontgrid.internal_pad != newfg->fontgrid.internal_pad) {
	newfg->fontgrid.cell_width = DefaultCellWidth (newfg);
	newfg->fontgrid.cell_height = DefaultCellHeight (newfg);
	redisplay = TRUE;
    }

    if (curfg->fontgrid.foreground_pixel != newfg->fontgrid.foreground_pixel) {
	XtReleaseGC (new, curfg->fontgrid.text_gc);
	newfg->fontgrid.text_gc = get_gc (newfg,
					  newfg->fontgrid.foreground_pixel);
	redisplay = TRUE;
    }

    if (curfg->fontgrid.box_pixel != newfg->fontgrid.box_pixel) {
	XtReleaseGC (new, curfg->fontgrid.text_gc);
	newfg->fontgrid.box_gc = get_gc (newfg, newfg->fontgrid.box_pixel);
	redisplay = TRUE;
    }

    if (curfg->fontgrid.center_chars != newfg->fontgrid.center_chars ||
	curfg->fontgrid.box_chars != newfg->fontgrid.box_chars)
      redisplay = TRUE;

    if (curfg->fontgrid.start_char != newfg->fontgrid.start_char) {
	XFontStruct *fs = newfg->fontgrid.text_font;
	unsigned maxn = ((fs->max_byte1 << 8) | fs->max_char_or_byte2);

	if (newfg->fontgrid.start_char > maxn) 
	  newfg->fontgrid.start_char = (maxn + 1 - 
					(newfg->fontgrid.cell_cols * 
					 newfg->fontgrid.cell_rows));

	redisplay = (curfg->fontgrid.start_char != newfg->fontgrid.start_char);
    }

    return redisplay;
}


/* ARGSUSED */
static void Notify (gw, event, params, nparams)
    Widget gw;
    XEvent *event;
    String *params;
    Cardinal *nparams;
{
    FontGridWidget fgw = (FontGridWidget) gw;
    int x, y;				/* where the event happened */
    FontGridCharRec rec;		/* callback data */

    /*
     * only allow events with (x,y)
     */
    switch (event->type) {
      case KeyPress:
      case KeyRelease:
	x = event->xkey.x;
	y = event->xkey.y;
	break;
      case ButtonPress:
      case ButtonRelease:
	x = event->xbutton.x;
	y = event->xbutton.y;
	break;
      case MotionNotify:
	x = event->xmotion.x;
	y = event->xmotion.y;
	break;
      default:
	Bell (gw, XkbBI_Ignore);
	return;
    }

    /*
     * compute the callback data
     */
    {
	int cw = fgw->fontgrid.cell_width + fgw->fontgrid.grid_width;
	int ch = fgw->fontgrid.cell_height + fgw->fontgrid.grid_width;
	unsigned n;

	if (x > (fgw->fontgrid.cell_cols * cw)) {
	    Bell (gw, XkbBI_InvalidLocation);
	    return;
	}

	n= (fgw->fontgrid.start_char + 
	    ((y / ch) * fgw->fontgrid.cell_cols) + (x / cw));

	rec.thefont = fgw->fontgrid.text_font;
	rec.thechar.byte1 = (n >> 8);
	rec.thechar.byte2 = (n & 255);
    }

    XtCallCallbacks (gw, XtNcallback, (XtPointer) &rec);
}

