/* Copyright (C) 2000-2011 by George Williams */
/*
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:

 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.

 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.

 * The name of the author may not be used to endorse or promote products
 * derived from this software without specific prior written permission.

 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) 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 OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "fontforgeui.h"
#include <gkeysym.h>
#include <gresource.h>
#include <gresedit.h>
#include <string.h>
#include <ustring.h>
#include <utype.h>
#include <math.h>

int mv_width = 800, mv_height = 300;
int mvshowgrid = mv_hidemovinggrid;
int mv_type = mv_kernonly;
static int mv_antialias = true;
static double mv_scales[] = { 8.0, 4.0, 2.0, 1.5, 1.0, 2.0/3.0, .5, 1.0/3.0, .25, .2, 1.0/6.0, .125, .1 };
#define SCALE_INDEX_NORMAL	4

static Color widthcol = 0x808080;
static Color italicwidthcol = 0x909090;
static Color selglyphcol = 0x909090;
static Color kernlinecol = 0x008000;
static Color rbearinglinecol = 0x000080;

void MVColInit( void ) {
    static int cinit=false;
    GResStruct mvcolors[] = {
	{ "AdvanceWidthColor", rt_color, &widthcol },
	{ "ItalicAdvanceColor", rt_color, &italicwidthcol },
	{ "SelectedGlyphColor", rt_color, &selglyphcol },
	{ "KernLineColor", rt_color, &kernlinecol },
	{ "SideBearingLineColor", rt_color, &rbearinglinecol },
	{ NULL }
    };
    if ( !cinit ) {
	GResourceFind( mvcolors, "MetricsView.");
	cinit = true;
    }
}

static int MVSetVSb(MetricsView *mv);

static int MVShowGrid(MetricsView *mv) {
    if ( mv->showgrid==mv_hidegrid || (mv->showgrid==mv_hidemovinggrid && mv->pressed ))
return( false );

return( true );
}

static void MVDrawLine(MetricsView *mv,GWindow pixmap,
	int xtop, int top,int xbot,int bot,Color col) {
    if ( mv->showgrid == mv_partialgrid ) {
	int y1, y2;
	int x1, x2;
	y1 =  bot +   ( top- bot)/4;
	x1 = xbot +   (xtop-xbot)/4;
	y2 =  bot + 4*( top- bot)/5;
	x2 = xbot + 4*(xtop-xbot)/5;
	GDrawDrawLine(pixmap,xtop,top,x2,y2,col);
	GDrawDrawLine(pixmap,x1,y1,xbot,bot,col);
    } else
	GDrawDrawLine(pixmap,xtop,top,xbot,bot,col);
}

static void MVSubVExpose(MetricsView *mv, GWindow pixmap, GEvent *event) {
    /* Expose routine for vertical metrics */
    GRect *clip;
    int xbase, y, si, i, x, width, height;
    double iscale = mv->pixelsize_set_by_window ? 1.0 : mv_scales[mv->scale_index];
    int as = rint(iscale*mv->pixelsize*mv->sf->ascent/(double) (mv->sf->ascent+mv->sf->descent));
    BDFChar *bdfc;
    struct _GImage base;
    GImage gi;
    GClut clut;

    clip = &event->u.expose.rect;

    xbase = mv->vwidth/2;
    if ( mv->showgrid )
	GDrawDrawLine(pixmap,xbase,0,xbase,mv->vheight,widthcol);

    if ( mv->bdf==NULL && MVShowGrid(mv) ) {
	y = mv->perchar[0].dy-mv->yoff;
	MVDrawLine(mv,pixmap,0,y,mv->vwidth,y,widthcol);
    }

    si = -1;
    for ( i=0; i<mv->glyphcnt; ++i ) {
	if ( mv->perchar[i].selected ) si = i;
	y = mv->perchar[i].dy-mv->yoff;
	if ( mv->bdf==NULL &&  MVShowGrid(mv)) {
	    int yp = y+mv->perchar[i].dheight+mv->perchar[i].kernafter;
	    MVDrawLine(mv,pixmap,0, yp,mv->vwidth,yp,
		    mv->type==mv_kernonly  && i!=mv->glyphcnt-1 ?kernlinecol :
		    mv->type==mv_widthonly                      ?rbearinglinecol :
			widthcol);
	}
	y += mv->perchar[i].yoff;
	bdfc = mv->bdf==NULL ?	BDFPieceMealCheck(mv->show,mv->glyphs[i].sc->orig_pos) :
				BDFGetMergedChar( mv->bdf->glyphs[mv->glyphs[i].sc->orig_pos]);
	if ( bdfc==NULL )
    continue;
	y += as-rint(iscale * bdfc->ymax);
	if ( mv->perchar[i].selected )
	    y += mv->activeoff;
	x = xbase - rint(iscale * (mv->pixelsize/2 + bdfc->xmin) - mv->perchar[i].xoff);
	width = bdfc->xmax-bdfc->xmin+1; height = bdfc->ymax-bdfc->ymin+1;
	if ( clip->y+clip->height<y )
    break;
	if ( y+iscale*height>=clip->y && x<clip->x+clip->width && x+iscale*width >= clip->x ) {
	    memset(&gi,'\0',sizeof(gi));
	    memset(&base,'\0',sizeof(base));
	    memset(&clut,'\0',sizeof(clut));
	    gi.u.image = &base;
	    base.clut = &clut;
	    if ( !bdfc->byte_data ) {
		base.image_type = it_mono;
		clut.clut_len = 2;
		clut.clut[0] = 0xffffff;
		if ( mv->perchar[i].selected )
		    clut.clut[1] = selglyphcol;
	    } else {
		int scale, l;
		Color fg, bg;
		if ( mv->bdf!=NULL )
		    scale = BDFDepth(mv->bdf);
		else
		    scale = BDFDepth(mv->show);
		base.image_type = it_index;
		clut.clut_len = 1<<scale;
		bg = view_bgcol;
		fg = ( mv->perchar[i].selected ) ? selglyphcol : 0x000000;
		for ( l=0; l<(1<<scale); ++l )
		    clut.clut[l] =
			COLOR_CREATE(
			 COLOR_RED(bg) + (l*(COLOR_RED(fg)-COLOR_RED(bg)))/((1<<scale)-1),
			 COLOR_GREEN(bg) + (l*(COLOR_GREEN(fg)-COLOR_GREEN(bg)))/((1<<scale)-1),
			 COLOR_BLUE(bg) + (l*(COLOR_BLUE(fg)-COLOR_BLUE(bg)))/((1<<scale)-1) );
	    }
	    base.data = bdfc->bitmap;
	    base.bytes_per_line = bdfc->bytes_per_line;
	    base.width = width;
	    base.height = height;
	    if ( mv->pixelsize_set_by_window || mv->scale_index==SCALE_INDEX_NORMAL )
		GDrawDrawGlyph(pixmap,&gi,NULL,x,y);
	    else
		GDrawDrawImageMagnified(pixmap, &gi, NULL, x,y,
			(int) rint((width*mv_scales[mv->scale_index])),
			(int) rint((height*mv_scales[mv->scale_index])));
	}
	if ( mv->bdf!=NULL ) BDFCharFree( bdfc );
    }
    if ( si!=-1 && mv->bdf==NULL &&  MVShowGrid(mv) && mv->type==mv_kernwidth ) {
	y = mv->perchar[si].dy-mv->yoff;
	if ( si!=0 )
	    MVDrawLine(mv,pixmap,0,y,mv->vwidth,y,kernlinecol);
	y += mv->perchar[si].dheight+mv->perchar[si].kernafter;
	MVDrawLine(mv,pixmap,0,y,mv->vwidth,y,rbearinglinecol);
    }
}

static void MVSubExpose(MetricsView *mv, GWindow pixmap, GEvent *event) {
    GRect old, *clip;
    int x,y,ybase, width,height, i;
    BDFChar *bdfc;
    struct _GImage base;
    GImage gi;
    GClut clut;
    int si;
    double iscale = mv->pixelsize_set_by_window ? 1.0 : mv_scales[mv->scale_index];
    double s = sin(-mv->sf->italicangle*3.1415926535897932/180.);
    int x_iaoffh = 0, x_iaoffl = 0;

    clip = &event->u.expose.rect;
    GDrawPushClip(pixmap,clip,&old);
    GDrawSetLineWidth(pixmap,0);

    if ( mv->vertical ) {
	MVSubVExpose(mv,pixmap,event);
	GDrawPopClip(pixmap,&old);
return;
    }

    ybase = mv->ybaseline - mv->yoff;

    if ( mv->showgrid )
	GDrawDrawLine(pixmap,0,ybase,mv->dwidth,ybase,widthcol);

    if ( mv->bdf==NULL && MVShowGrid(mv) ) {
	x = mv->perchar[0].dx-mv->xoff;
	if ( mv->right_to_left )
	    x = mv->vwidth - x - mv->perchar[0].dwidth - mv->perchar[0].kernafter;
	MVDrawLine(mv,pixmap,x,0,x,mv->vheight,widthcol);
	x_iaoffh = rint(ybase*s), x_iaoffl = rint((mv->vheight-ybase)*s);
	if ( ItalicConstrained && x_iaoffh!=0 ) {
	    MVDrawLine(mv,pixmap,x+x_iaoffh,0,x-x_iaoffl,mv->vheight,italicwidthcol);
	}
    }
    si = -1;
    for ( i=0; i<mv->glyphcnt; ++i ) {
	if ( mv->perchar[i].selected ) si = i;
	x = mv->perchar[i].dx-mv->xoff;
	if ( mv->right_to_left )
	    x = mv->vwidth - x - mv->perchar[i].dwidth - mv->perchar[i].kernafter;
	if ( mv->bdf==NULL && MVShowGrid(mv) ) {
	    int xp = x+mv->perchar[i].dwidth+mv->perchar[i].kernafter;
	    MVDrawLine(mv,pixmap,xp, 0,xp,mv->vheight,
		    mv->type==mv_kernonly  && i!=mv->glyphcnt-1 ?kernlinecol :
		    mv->type==mv_widthonly                      ?rbearinglinecol :
			widthcol);
	    if ( ItalicConstrained && x_iaoffh!=0 ) {
		MVDrawLine(mv,pixmap,xp+x_iaoffh,0,xp-x_iaoffl,mv->vheight,italicwidthcol);
	    }
	}
	if ( mv->right_to_left )
	    x += mv->perchar[i].kernafter-mv->perchar[i].xoff;
	else
	    x += mv->perchar[i].xoff;
	bdfc = mv->bdf==NULL ?	BDFPieceMealCheck(mv->show,mv->glyphs[i].sc->orig_pos) :
				BDFGetMergedChar( mv->bdf->glyphs[mv->glyphs[i].sc->orig_pos]);
	if ( bdfc==NULL )
    continue;
	x += rint( iscale * bdfc->xmin );
	if ( mv->perchar[i].selected )
	    x += mv->activeoff;
	y = ybase - rint( iscale * bdfc->ymax ) - (iscale-1) - mv->perchar[i].yoff;
	width = bdfc->xmax-bdfc->xmin+1; height = bdfc->ymax-bdfc->ymin+1;
	if ( !mv->right_to_left && clip->x+clip->width<x )
    break;
	if ( x+rint(iscale*width)>=clip->x && y<clip->y+clip->height && y+rint(iscale*height) >= clip->y ) {
	    memset(&gi,'\0',sizeof(gi));
	    memset(&base,'\0',sizeof(base));
	    memset(&clut,'\0',sizeof(clut));
	    gi.u.image = &base;
	    base.clut = &clut;
	    if ( !bdfc->byte_data ) {
		base.image_type = it_mono;
		clut.clut_len = 2;
		clut.clut[0] = 0xffffff;
		if ( mv->perchar[i].selected )
		    clut.clut[1] = selglyphcol;
	    } else {
		int lscale = 3000/mv->pixelsize, l;
		Color fg, bg;
		int scale;
		if ( mv->bdf!=NULL )
		    lscale = BDFDepth(mv->bdf);
		else
		    lscale = BDFDepth(mv->show);
		base.image_type = it_index;
		scale = lscale==8?256:lscale==4?16:4;
		clut.clut_len = scale;
		bg = view_bgcol;
		fg = ( mv->perchar[i].selected ) ? selglyphcol : 0x000000;
		for ( l=0; l<scale; ++l )
		    clut.clut[l] =
			COLOR_CREATE(
			 COLOR_RED(bg) + ((int32) (l*(COLOR_RED(fg)-COLOR_RED(bg))))/(scale-1),
			 COLOR_GREEN(bg) + ((int32) (l*(COLOR_GREEN(fg)-COLOR_GREEN(bg))))/(scale-1),
			 COLOR_BLUE(bg) + ((int32) (l*(COLOR_BLUE(fg)-COLOR_BLUE(bg))))/(scale-1) );
	    }
	    base.data = bdfc->bitmap;
	    base.bytes_per_line = bdfc->bytes_per_line;
	    base.width = width;
	    base.height = height;
	    if ( mv->pixelsize_set_by_window || mv->scale_index==SCALE_INDEX_NORMAL )
		GDrawDrawGlyph(pixmap,&gi,NULL,x,y);
	    else
		GDrawDrawImageMagnified(pixmap, &gi, NULL, x,y,
			(int) rint((width*mv_scales[mv->scale_index])),
			(int) rint((height*mv_scales[mv->scale_index])));
	}
	if ( mv->bdf!=NULL ) BDFCharFree( bdfc );
    }
    if ( si!=-1 && mv->bdf==NULL && MVShowGrid(mv) && mv->type==mv_kernwidth ) {
	x = mv->perchar[si].dx-mv->xoff;
	if ( mv->right_to_left )
	    x = mv->vwidth - x;
	if ( si!=0 )
	    MVDrawLine(mv,pixmap,x,0,x,mv->vheight,kernlinecol);
	if ( mv->right_to_left )
	    x -= mv->perchar[si].dwidth+mv->perchar[si].kernafter;
	else
	    x += mv->perchar[si].dwidth+mv->perchar[si].kernafter;
	 MVDrawLine(mv,pixmap,x, 0,x,mv->vheight,rbearinglinecol);
    }
    GDrawPopClip(pixmap,&old);
}

static void MVExpose(MetricsView *mv, GWindow pixmap, GEvent *event) {
    GRect old, *clip;
    int x;
    int ke = mv->height-mv->sbh-(mv->fh+4);

    clip = &event->u.expose.rect;
    if ( clip->y+clip->height < mv->topend )
return;
    GDrawPushClip(pixmap,clip,&old);
    GDrawSetLineWidth(pixmap,0);
    for ( x=mv->mwidth; x<mv->width; x+=mv->mwidth ) {
	GDrawDrawLine(pixmap,x,mv->displayend,x,ke,0x000000);
	GDrawDrawLine(pixmap,x+mv->mwidth/2,ke,x+mv->mwidth/2,mv->height-mv->sbh,0x000000);
    }
    GDrawDrawLine(pixmap,0,mv->topend,mv->width,mv->topend,0x000000);
    GDrawDrawLine(pixmap,0,mv->displayend,mv->width,mv->displayend,0x000000);
    GDrawDrawLine(pixmap,0,mv->displayend+mv->fh+4,mv->width,mv->displayend+mv->fh+4,0x000000);
    GDrawDrawLine(pixmap,0,mv->displayend+2*(mv->fh+4),mv->width,mv->displayend+2*(mv->fh+4),0x000000);
    GDrawDrawLine(pixmap,0,mv->displayend+3*(mv->fh+4),mv->width,mv->displayend+3*(mv->fh+4),0x000000);
    GDrawDrawLine(pixmap,0,mv->displayend+4*(mv->fh+4),mv->width,mv->displayend+4*(mv->fh+4),0x000000);
    GDrawDrawLine(pixmap,0,mv->displayend+5*(mv->fh+4),mv->width,mv->displayend+5*(mv->fh+4),0x000000);
}

static void MVSetSubtables(SplineFont *sf) {
    GTextInfo **ti;
    OTLookup *otl;
    struct lookup_subtable *sub;
    int cnt, doit;
    MetricsView *mvs;
    int selected;

    if ( sf->cidmaster ) sf = sf->cidmaster;

    /* There might be more than one metricsview wandering around. Update them all */
    for ( mvs = sf->metrics; mvs!=NULL; mvs=mvs->next ) {
	selected = false;
	for ( doit = 0; doit<2; ++doit ) {
	    cnt = 0;
	    for ( otl=sf->gpos_lookups; otl!=NULL; otl=otl->next ) {
		if ( otl->lookup_type == gpos_pair && FeatureTagInFeatureScriptList(
			    mvs->vertical?CHR('v','k','r','n') : CHR('k','e','r','n'),
			    otl->features)) {
		    for ( sub=otl->subtables; sub!=NULL; sub=sub->next ) {
			if ( doit ) {
			    ti[cnt] = gcalloc(1,sizeof(GTextInfo));
			    ti[cnt]->text = utf82u_copy(sub->subtable_name);
			    ti[cnt]->userdata = sub;
			    if ( sub==mvs->cur_subtable )
				ti[cnt]->selected = selected = true;
			    ti[cnt]->disabled = sub->kc!=NULL;
			    ti[cnt]->fg = ti[cnt]->bg = COLOR_DEFAULT;
			}
			++cnt;
		    }
		}
	    }
	    if ( !doit )
		ti = gcalloc(cnt+3,sizeof(GTextInfo *));
	    else {
		if ( cnt!=0 ) {
		    ti[cnt] = gcalloc(1,sizeof(GTextInfo));
		    ti[cnt]->line = true;
		    ti[cnt]->fg = ti[cnt]->bg = COLOR_DEFAULT;
		    ++cnt;
		}
		ti[cnt] = gcalloc(1,sizeof(GTextInfo));
		ti[cnt]->text = utf82u_copy(_("New Lookup Subtable..."));
		ti[cnt]->userdata = NULL;
		ti[cnt]->fg = ti[cnt]->bg = COLOR_DEFAULT;
		ti[cnt]->selected = !selected;
		++cnt;
		ti[cnt] = gcalloc(1,sizeof(GTextInfo));
	    }
	}
	if ( !selected )
	    mvs->cur_subtable = NULL;

	GGadgetSetList(mvs->subtable_list,ti,false);
    }
}

static void MVSetFeatures(MetricsView *mv) {
    SplineFont *sf = mv->sf;
    int i, j, cnt;
    GTextInfo **ti=NULL;
    uint32 *tags = NULL, script, lang;
    char buf[16];
    uint32 *stds;
    const unichar_t *pt = _GGadgetGetTitle(mv->script);

    if ( sf->cidmaster ) sf=sf->cidmaster;

    script = DEFAULT_SCRIPT;
    lang = DEFAULT_LANG;
    if ( u_strlen(pt)>=4 )
	script = (pt[0]<<24) | (pt[1]<<16) | (pt[2]<<8) | pt[3];
    if ( pt[4]=='{' && u_strlen(pt)>=9 )
	lang = (pt[5]<<24) | (pt[6]<<16) | (pt[7]<<8) | pt[8];
    if ( mv->oldscript!=script || mv->oldlang!=lang )
	stds = StdFeaturesOfScript(script);
    else {		/* features list may have changed, but retain those set */
	int32 len, sc;
	ti = GGadgetGetList(mv->features,&len);
	stds = galloc((len+1)*sizeof(uint32));
	for ( i=sc=0; i<len; ++i ) if ( ti[i]->selected )
	    stds[sc++] = (uint32) (intpt) ti[i]->userdata;
	stds[sc] = 0;
    }

    tags = SFFeaturesInScriptLang(sf,-2,script,lang);
    /* Never returns NULL */
    for ( cnt=0; tags[cnt]!=0; ++cnt );

    /*qsort(tags,cnt,sizeof(uint32),tag_comp);*/ /* The glist will do this for us */

    ti = galloc((cnt+2)*sizeof(GTextInfo *));
    for ( i=0; i<cnt; ++i ) {
	ti[i] = gcalloc( 1,sizeof(GTextInfo));
	ti[i]->fg = ti[i]->bg = COLOR_DEFAULT;
	if ( (tags[i]>>24)<' ' || (tags[i]>>24)>0x7e )
	    sprintf( buf, "<%d,%d>", tags[i]>>16, tags[i]&0xffff );
	else {
	    buf[0] = tags[i]>>24; buf[1] = tags[i]>>16; buf[2] = tags[i]>>8; buf[3] = tags[i]; buf[4] = 0;
	}
	ti[i]->text = uc_copy(buf);
	ti[i]->userdata = (void *) (intpt) tags[i];
	for ( j=0; stds[j]!=0; ++j ) {
	    if ( stds[j] == tags[i] ) {
		ti[i]->selected = true;
	break;
	    }
	}
    }
    ti[i] = gcalloc(1,sizeof(GTextInfo));
    GGadgetSetList(mv->features,ti,false);
    mv->oldscript = script; mv->oldlang = lang;
}

static void MVSelectSubtable(MetricsView *mv, struct lookup_subtable *sub) {
    int32 len; int i;
    GTextInfo **old = GGadgetGetList(mv->subtable_list,&len);

    for ( i=0; i<len && (old[i]->userdata!=sub || old[i]->line); ++i );
    GGadgetSelectOneListItem(mv->subtable_list,i);
    if ( sub!=NULL )
	mv->cur_subtable = sub;
}

static void MVRedrawI(MetricsView *mv,int i,int oldxmin,int oldxmax) {
    GRect r;
    BDFChar *bdfc;
    int off = 0;

    if ( mv->right_to_left || mv->vertical ) {
	/* right to left clipping is hard to think about, it doesn't happen */
	/*  often enough (I think) for me to put the effort to make it efficient */
	GDrawRequestExpose(mv->v,NULL,false);
return;
    }
    if ( mv->perchar[i].selected )
	off = mv->activeoff;
    r.y = 0; r.height = mv->vheight;
    r.x = mv->perchar[i].dx-mv->xoff; r.width = mv->perchar[i].dwidth;
    if ( mv->perchar[i].kernafter>0 )
	r.width += mv->perchar[i].kernafter;
    if ( mv->perchar[i].xoff<0 ) {
	r.x += mv->perchar[i].xoff;
	r.width -= mv->perchar[i].xoff;
    } else
	r.width += mv->perchar[i].xoff;
    bdfc = mv->bdf==NULL ?  BDFPieceMealCheck(mv->show,mv->glyphs[i].sc->orig_pos) :
			    mv->bdf->glyphs[mv->glyphs[i].sc->orig_pos];
    if ( bdfc==NULL )
return;
    if ( bdfc->xmax+off+1>r.width ) r.width = bdfc->xmax+off+1;
    if ( oldxmax+1>r.width ) r.width = oldxmax+1;
    if ( bdfc->xmin+off<0 ) {
	r.x += bdfc->xmin+off;
	r.width -= bdfc->xmin+off;
    }
    if ( oldxmin<bdfc->xmin ) {
	r.width += (bdfc->xmin+off-oldxmin);
	r.x -= (bdfc->xmin+off-oldxmin);
    }
    if ( mv->right_to_left )
	r.x = mv->dwidth - r.x - r.width;
    GDrawRequestExpose(mv->v,&r,false);
    if ( mv->perchar[i].selected && i!=0 ) {
	struct lookup_subtable *sub = mv->glyphs[i].kp!=NULL ? mv->glyphs[i].kp->subtable : mv->glyphs[i].kc!=NULL && mv->glyphs[i].kc->offsets[mv->glyphs[i].kc_index]!=0 ? mv->glyphs[i].kc->subtable : NULL;
	if ( sub!=NULL )
	    MVSelectSubtable(mv,sub);
    }
}

static void MVDeselectChar(MetricsView *mv, int i) {

    mv->perchar[i].selected = false;
    if ( mv->perchar[i].name!=NULL )
	GGadgetSetEnabled(mv->perchar[i].name,mv->bdf==NULL);
    MVRedrawI(mv,i,0,0);
}

static void MVSelectSubtableForScript(MetricsView *mv,uint32 script) {
    int32 len;
    GTextInfo **ti = GGadgetGetList(mv->subtable_list,&len);
    struct lookup_subtable *sub;
    int i;

    if ( mv->cur_subtable != NULL && FeatureScriptTagInFeatureScriptList(
	    mv->vertical?CHR('v','k','r','n') : CHR('k','e','r','n'),script,
	    mv->cur_subtable->lookup->features ))
return;

    sub = NULL;
    for ( i=0; i<len; ++i )
	if ( ti[i]->userdata!=NULL &&
		FeatureScriptTagInFeatureScriptList(
		    mv->vertical?CHR('v','k','r','n') : CHR('k','e','r','n'),
		    script,((struct lookup_subtable *) (ti[i]->userdata))->lookup->features)) {
	    sub = ti[i]->userdata;
    break;
	}
    if ( sub!=NULL )
	MVSelectSubtable(mv,sub);
}

static void MVSelectChar(MetricsView *mv, int i) {

    if ( i>=mv->glyphcnt || i<0 )
return;
    mv->perchar[i].selected = true;
    if ( mv->perchar[i].name!=NULL )
	GGadgetSetEnabled(mv->perchar[i].name,false);
    if ( mv->glyphs[i].kp!=NULL )
	MVSelectSubtable(mv,mv->glyphs[i].kp->subtable);
    else if ( mv->glyphs[i].kc!=NULL && mv->glyphs[i].kc->offsets[mv->glyphs[i].kc_index]!=0 )
	MVSelectSubtable(mv,mv->glyphs[i].kc->subtable);
    else
	MVSelectSubtableForScript(mv,SCScriptFromUnicode(mv->glyphs[i].sc));
    MVRedrawI(mv,i,0,0);
}

static void MVDoSelect(MetricsView *mv, int i) {
    int j;

    for ( j=0; j<mv->glyphcnt; ++j )
	if ( j!=i && mv->perchar[j].selected )
	    MVDeselectChar(mv,j);
    MVSelectChar(mv,i);
}

void MVRefreshChar(MetricsView *mv, SplineChar *sc) {
    int i;
    for ( i=0; i<mv->glyphcnt; ++i ) if ( mv->glyphs[i].sc == sc )
	MVRedrawI(mv,i,0,0);
}

static void MVRefreshValues(MetricsView *mv, int i) {
    char buf[40];
    DBounds bb;
    SplineChar *sc = mv->glyphs[i].sc;
    int kern_offset;

    SplineCharFindBounds(sc,&bb);

    GGadgetSetTitle8(mv->perchar[i].name,sc->name);

    sprintf(buf,"%d",mv->vertical ? sc->vwidth : sc->width);
    GGadgetSetTitle8(mv->perchar[i].width,buf);

    sprintf(buf,"%.2f",mv->vertical ? sc->parent->ascent-(double) bb.maxy : (double) bb.minx);
    if ( buf[strlen(buf)-1]=='0' ) {
	buf[strlen(buf)-1] = '\0';
	if ( buf[strlen(buf)-1]=='0' ) {
	    buf[strlen(buf)-1] = '\0';
	    if ( buf[strlen(buf)-1]=='.' )
		buf[strlen(buf)-1] = '\0';
	}
    }
    GGadgetSetTitle8(mv->perchar[i].lbearing,buf);

    sprintf(buf,"%.2f",(double) (mv->vertical ? sc->vwidth-(sc->parent->ascent-bb.miny) : sc->width-bb.maxx));
    if ( buf[strlen(buf)-1]=='0' ) {
	buf[strlen(buf)-1] = '\0';
	if ( buf[strlen(buf)-1]=='0' ) {
	    buf[strlen(buf)-1] = '\0';
	    if ( buf[strlen(buf)-1]=='.' )
		buf[strlen(buf)-1] = '\0';
	}
    }
    GGadgetSetTitle8(mv->perchar[i].rbearing,buf);

    kern_offset = 0x7ffffff;
    if ( mv->glyphs[i].kp!=NULL )
	kern_offset = mv->glyphs[i].kp->off;
    else if ( mv->glyphs[i].kc!=NULL )
	kern_offset = mv->glyphs[i].kc->offsets[ mv->glyphs[i].kc_index ];
    if ( kern_offset!=0x7ffffff && i!=mv->glyphcnt-1 ) {
	sprintf(buf,"%d",kern_offset);
	GGadgetSetTitle8(mv->perchar[i+1].kern,buf);
    } else if ( i!=mv->glyphcnt-1 )
	GGadgetSetTitle8(mv->perchar[i+1].kern,"");
}

static void MVMakeLabels(MetricsView *mv) {
    static GBox small = { 0 };
    GGadgetData gd;
    GTextInfo label;

    small.main_background = small.main_foreground = COLOR_DEFAULT;
    memset(&gd,'\0',sizeof(gd));
    memset(&label,'\0',sizeof(label));

    mv->mwidth = GGadgetScale(60);
    mv->displayend = mv->height- mv->sbh - 5*(mv->fh+4);
    /* We might not have set mv->vheight-2 yet */
    if ( mv->pixelsize_set_by_window )
	mv->pixelsize = mv_scales[mv->scale_index]*(mv->displayend - mv->topend - 4);

    label.text = (unichar_t *) _("Name:");
    label.text_is_1byte = true;
    label.font = mv->font;
    gd.pos.x = 2; gd.pos.width = mv->mwidth-4;
    gd.pos.y = mv->displayend+2;
    gd.pos.height = mv->fh;
    gd.label = &label;
    gd.box = &small;
    gd.flags = gg_visible | gg_enabled | gg_pos_in_pixels | gg_dontcopybox;
    mv->namelab = GLabelCreate(mv->gw,&gd,NULL);

    label.text = (unichar_t *) (mv->vertical ? _("Height:") : _("Width:") );
    gd.pos.y += mv->fh+4;
    mv->widthlab = GLabelCreate(mv->gw,&gd,NULL);

/* GT: Top/Left (side) bearing */
    label.text = (unichar_t *) (mv->vertical ? _("TBearing:") : _("LBearing:") );
    gd.pos.y += mv->fh+4;
    mv->lbearinglab = GLabelCreate(mv->gw,&gd,NULL);

/* GT: Bottom/Right (side) bearing */
    label.text = (unichar_t *) (mv->vertical ? _("BBearing:") : _("RBearing:") );
    gd.pos.y += mv->fh+4;
    mv->rbearinglab = GLabelCreate(mv->gw,&gd,NULL);

    label.text = (unichar_t *) (mv->vertical ? _("VKern:") : _("Kern:"));
    gd.pos.y += mv->fh+4;
    mv->kernlab = GLabelCreate(mv->gw,&gd,NULL);
}

static int MV_KernChanged(GGadget *g, GEvent *e);
static int MV_RBearingChanged(GGadget *g, GEvent *e);
static int MV_LBearingChanged(GGadget *g, GEvent *e);
static int MV_WidthChanged(GGadget *g, GEvent *e);

static void MVCreateFields(MetricsView *mv,int i) {
    static GBox small = { 0 };
    GGadgetData gd;
    GTextInfo label;
    static unichar_t nullstr[1] = { 0 };
    int j;
    extern GBox _GGadget_gtextfield_box;

    small = _GGadget_gtextfield_box;
    small.flags = 0;
    small.border_type = bt_none;
    small.border_shape = bs_rect;
    small.border_width = 0;
    small.padding = 0;

    memset(&gd,'\0',sizeof(gd));
    memset(&label,'\0',sizeof(label));
    label.text = nullstr;
    label.font = mv->font;
    mv->perchar[i].mx = gd.pos.x = mv->mbase+(i+1-mv->coff)*mv->mwidth+2;
    mv->perchar[i].mwidth = gd.pos.width = mv->mwidth-4;
    gd.pos.y = mv->displayend+2;
    gd.pos.height = mv->fh;
    gd.label = &label;
    gd.box = &small;
    gd.flags = gg_visible | gg_pos_in_pixels | gg_dontcopybox;
    if ( mv->bdf==NULL )
	gd.flags |= gg_enabled;
    mv->perchar[i].name = GLabelCreate(mv->gw,&gd,(void *) (intpt) i);
    if ( mv->perchar[i].selected )
	GGadgetSetEnabled(mv->perchar[i].name,false);

    gd.pos.y += mv->fh+4;
    gd.handle_controlevent = MV_WidthChanged;
    mv->perchar[i].width = GTextFieldCreate(mv->gw,&gd,(void *) (intpt) i);

    gd.pos.y += mv->fh+4;
    gd.handle_controlevent = MV_LBearingChanged;
    mv->perchar[i].lbearing = GTextFieldCreate(mv->gw,&gd,(void *) (intpt) i);

    gd.pos.y += mv->fh+4;
    gd.handle_controlevent = MV_RBearingChanged;
    mv->perchar[i].rbearing = GTextFieldCreate(mv->gw,&gd,(void *) (intpt) i);

    if ( i!=0 ) {
	gd.pos.y += mv->fh+4;
	gd.pos.x -= mv->mwidth/2;
	gd.handle_controlevent = MV_KernChanged;
	mv->perchar[i].kern = GTextFieldCreate(mv->gw,&gd,(void *) (intpt) i);

	if ( i>=mv->glyphcnt ) {
	    for ( j=mv->glyphcnt+1; j<=i ; ++ j )
		mv->perchar[j].dx = mv->perchar[j-1].dx;
	    mv->glyphcnt = i+1;
	}
    }

    GWidgetIndicateFocusGadget(mv->text);
}

static void MVSetSb(MetricsView *mv);
static int MVSetVSb(MetricsView *mv);

static void MVRemetric(MetricsView *mv) {
    SplineChar *anysc, *goodsc;
    int i, cnt, x, y, goodpos;
    const unichar_t *_script = _GGadgetGetTitle(mv->script);
    uint32 script, lang, *feats;
    char buf[20];
    int32 len;
    GTextInfo **ti;
    SplineChar *sc;
    BDFChar *bdfc;
    double iscale = mv->pixelsize_set_by_window ? 1.0 : mv_scales[mv->scale_index];
    double scale = iscale*mv->pixelsize/(double) (mv->sf->ascent+mv->sf->descent);
    SplineFont *sf;

    anysc = goodsc = NULL; goodpos = -1;
    for ( i=0; mv->chars[i] && i<mv->clen; ++i ) {
	if ( anysc==NULL ) anysc = mv->chars[i];
	if ( SCScriptFromUnicode(mv->chars[i])!=DEFAULT_SCRIPT ) {
	    goodsc = mv->chars[i];
	    goodpos = i;
    break;
	}
    }
    if ( _script[0]=='D' && _script[1]=='F' && _script[2]=='L' && _script[3]=='T' ) {
	if ( goodsc!=NULL ) {
	    /* Set the script */ /* Remember if we get here the script is DFLT */
	    script = SCScriptFromUnicode(goodsc);
	    buf[0] = script>>24; buf[1] = script>>16; buf[2] = script>>8; buf[3] = script;
	    strcpy(buf+4,"{dflt}");
	    GGadgetSetTitle8(mv->script,buf);
	    MVSelectSubtableForScript(mv,script);
	    MVSetFeatures(mv);
	}
    } else {
	if ( anysc==NULL ) {
	    /* If we get here the script is not DFLT */
	    GGadgetSetTitle8(mv->script,"DFLT{dflt}");
	    MVSetFeatures(mv);
	}
    }
    _script = _GGadgetGetTitle(mv->script);
    script = DEFAULT_SCRIPT; lang = DEFAULT_LANG;
    if ( u_strlen(_script)>=4 && (u_strchr(_script,'{')==NULL || u_strchr(_script,'{')-_script>=4)) {
	unichar_t *pt;
	script = (_script[0]<<24) | (_script[1]<<16) | (_script[2]<<8) | _script[3];
	if ( (pt = u_strchr(_script,'{'))!=NULL && u_strlen(pt+1)>=4 &&
		(u_strchr(pt+1,'}')==NULL || u_strchr(pt+1,'}')-(pt+1)>=4 ))
	    lang = (pt[1]<<24) | (pt[2]<<16) | (pt[3]<<8) | pt[4];
    }

    ti = GGadgetGetList(mv->features,&len);
    for ( i=cnt=0; i<len; ++i )
	if ( ti[i]->selected ) ++cnt;
    feats = gcalloc(cnt+1,sizeof(uint32));
    for ( i=cnt=0; i<len; ++i )
	if ( ti[i]->selected )
	    feats[cnt++] = (intpt) ti[i]->userdata;

    free(mv->glyphs);
    sf = mv->sf;
    if ( sf->cidmaster ) sf = sf->cidmaster;
    mv->glyphs = ApplyTickedFeatures(sf,feats,script, lang, mv->pixelsize, mv->chars);
    free(feats);
    if ( goodsc!=NULL )
	mv->right_to_left = SCRightToLeft(goodsc)?1:0;

    for ( cnt=0; mv->glyphs[cnt].sc!=NULL; ++cnt );
    if ( cnt>=mv->max ) {
	int oldmax=mv->max;
	mv->max = cnt+10;
	mv->perchar = grealloc(mv->perchar,mv->max*sizeof(struct metricchar));
	memset(mv->perchar+oldmax,'\0',(mv->max-oldmax)*sizeof(struct metricchar));
    }
    for ( i=cnt; i<mv->glyphcnt; ++i ) {
	static unichar_t nullstr[] = { 0 };
	GGadgetSetTitle(mv->perchar[i].name,nullstr);
	GGadgetSetTitle(mv->perchar[i].width,nullstr);
	GGadgetSetTitle(mv->perchar[i].lbearing,nullstr);
	GGadgetSetTitle(mv->perchar[i].rbearing,nullstr);
	if ( mv->perchar[i].kern!=NULL )
	    GGadgetSetTitle(mv->perchar[i].kern,nullstr);
    }
    mv->glyphcnt = cnt;
    for ( i=0; i<cnt; ++i ) {
	if ( mv->perchar[i].width==NULL ) {
	    MVCreateFields(mv,i);
	}
    }
    x = 10; y = 10;
    for ( i=0; i<cnt; ++i ) {
	MVRefreshValues(mv,i);
	sc = mv->glyphs[i].sc;
	bdfc = mv->bdf!=NULL ? mv->bdf->glyphs[sc->orig_pos] : BDFPieceMealCheck(mv->show,sc->orig_pos);
	mv->perchar[i].dwidth = rint(iscale * bdfc->width);
	mv->perchar[i].dx = x;
	mv->perchar[i].xoff = rint(iscale * mv->glyphs[i].vr.xoff);
	mv->perchar[i].yoff = rint(iscale * mv->glyphs[i].vr.yoff);
	mv->perchar[i].kernafter = rint(iscale * mv->glyphs[i].vr.h_adv_off);
	x += mv->perchar[i].dwidth + mv->perchar[i].kernafter;

	mv->perchar[i].dheight = rint(sc->vwidth*scale);
	mv->perchar[i].dy = y;
	if ( mv->vertical ) {
	    mv->perchar[i].kernafter = rint( iscale * mv->glyphs[i].vr.v_adv_off);
	    y += mv->perchar[i].dheight + mv->perchar[i].kernafter;
	}
    }
    MVSetVSb(mv);
    MVSetSb(mv);
}

void MVReKern(MetricsView *mv) {
    MVRemetric(mv);
    GDrawRequestExpose(mv->v,NULL,false);
}

void MVRegenChar(MetricsView *mv, SplineChar *sc) {
    int i;

    if ( mv->bdf==NULL && sc->orig_pos<mv->show->glyphcnt ) {
	BDFCharFree(mv->show->glyphs[sc->orig_pos]);
	mv->show->glyphs[sc->orig_pos] = NULL;
    }
    for ( i=0; i<mv->glyphcnt; ++i ) {
	if ( mv->glyphs[i].sc == sc )
    break;
    }
    if ( i>=mv->glyphcnt )
return;		/* Not displayed */
    MVRemetric(mv);
    GDrawRequestExpose(mv->v,NULL,false);
}

static void MVChangeDisplayFont(MetricsView *mv, BDFFont *bdf) {
    int i;

    if ( mv->bdf==bdf )
return;
    if ( (mv->bdf==NULL) != (bdf==NULL) ) {
	for ( i=0; i<mv->max; ++i ) if ( mv->perchar[i].width!=NULL ) {
	    GGadgetSetEnabled(mv->perchar[i].width,bdf==NULL);
	    GGadgetSetEnabled(mv->perchar[i].lbearing,bdf==NULL);
	    GGadgetSetEnabled(mv->perchar[i].rbearing,bdf==NULL);
	    if ( i!=0 )
		GGadgetSetEnabled(mv->perchar[i].kern,bdf==NULL);
	}
    }
    if ( mv->bdf==NULL ) {
	BDFFontFree(mv->show);
	mv->show = NULL;
    } else if ( bdf==NULL ) {
	BDFFontFree(mv->show);
	mv->show = SplineFontPieceMeal(mv->sf,mv->layer,mv->ptsize,mv->dpi,
		mv->antialias?(pf_antialias|pf_ft_recontext):pf_ft_recontext,NULL);
    }
    mv->bdf = bdf;
    MVRemetric(mv);
}

static int MV_WidthChanged(GGadget *g, GEvent *e) {
    MetricsView *mv = GDrawGetUserData(GGadgetGetWindow(g));
    int which = (intpt) GGadgetGetUserData(g);
    int i;

    if ( e->type!=et_controlevent )
return( true );
    if ( which>=mv->glyphcnt )
return( true );
    if ( e->u.control.subtype == et_textchanged ) {
	unichar_t *end;
	int val = u_strtol(_GGadgetGetTitle(g),&end,10);
	SplineChar *sc = mv->glyphs[which].sc;
	if ( *end && !(*end=='-' && end[1]=='\0'))
	    GDrawBeep(NULL);
	else if ( !mv->vertical && val!=sc->width ) {
	    SCPreserveWidth(sc);
	    SCSynchronizeWidth(sc,val,sc->width,NULL);
	    SCCharChangedUpdate(sc,ly_none);
	} else if ( mv->vertical && val!=sc->vwidth ) {
	    SCPreserveVWidth(sc);
	    sc->vwidth = val;
	    SCCharChangedUpdate(sc,ly_none);
	}
    } else if ( e->u.control.subtype == et_textfocuschanged &&
	    e->u.control.u.tf_focus.gained_focus ) {
	for ( i=0 ; i<mv->glyphcnt; ++i )
	    if ( i!=which && mv->perchar[i].selected )
		MVDeselectChar(mv,i);
	MVSelectChar(mv,which);
    }
return( true );
}

static int MV_LBearingChanged(GGadget *g, GEvent *e) {
    MetricsView *mv = GDrawGetUserData(GGadgetGetWindow(g));
    int which = (intpt) GGadgetGetUserData(g);
    int i;

    if ( e->type!=et_controlevent )
return( true );
    if ( which>=mv->glyphcnt )
return( true );
    if ( e->u.control.subtype == et_textchanged ) {
	unichar_t *end;
	double val = u_strtod(_GGadgetGetTitle(g),&end);
	SplineChar *sc = mv->glyphs[which].sc;
	DBounds bb;
	SplineCharFindBounds(sc,&bb);
	if ( *end && !(*end=='-' && end[1]=='\0'))
	    GDrawBeep(NULL);
	else if ( !mv->vertical && val!=bb.minx ) {
	    real transform[6];
	    transform[0] = transform[3] = 1.0;
	    transform[1] = transform[2] = transform[5] = 0;
	    transform[4] = val-bb.minx;
	    FVTrans( (FontViewBase *)mv->fv,sc,transform,NULL,fvt_dontmovewidth);
	} else if ( mv->vertical && val!=sc->parent->ascent-bb.maxy ) {
	    real transform[6];
	    transform[0] = transform[3] = 1.0;
	    transform[1] = transform[2] = transform[4] = 0;
	    transform[5] = sc->parent->ascent-bb.maxy-val;
	    FVTrans( (FontViewBase *)mv->fv,sc,transform,NULL,fvt_dontmovewidth);
	}
    } else if ( e->u.control.subtype == et_textfocuschanged &&
	    e->u.control.u.tf_focus.gained_focus ) {
	for ( i=0 ; i<mv->glyphcnt; ++i )
	    if ( i!=which && mv->perchar[i].selected )
		MVDeselectChar(mv,i);
	MVSelectChar(mv,which);
    }
return( true );
}

static int MV_RBearingChanged(GGadget *g, GEvent *e) {
    MetricsView *mv = GDrawGetUserData(GGadgetGetWindow(g));
    int which = (intpt) GGadgetGetUserData(g);
    int i;

    if ( e->type!=et_controlevent )
return( true );
    if ( which>=mv->glyphcnt )
return( true );
    if ( e->u.control.subtype == et_textchanged ) {
	unichar_t *end;
	int val = u_strtod(_GGadgetGetTitle(g),&end);
	SplineChar *sc = mv->glyphs[which].sc;
	DBounds bb;
	SplineCharFindBounds(sc,&bb);
	if ( *end && !(*end=='-' && end[1]=='\0'))
	    GDrawBeep(NULL);
	else if ( !mv->vertical && rint(val+bb.maxx)!=sc->width ) {
	    int newwidth = rint(bb.maxx+val);
	    SCPreserveWidth(sc);
	    /* Width is an integer. Adjust the lbearing so that the rbearing */
	    /*  remains what was just typed in */
	    if ( newwidth!=bb.maxx+val ) {
		real transform[6];
		transform[0] = transform[3] = 1.0;
		transform[1] = transform[2] = transform[5] = 0;
		transform[4] = newwidth-val-bb.maxx;
		FVTrans( (FontViewBase *)mv->fv,sc,transform,NULL,fvt_dontmovewidth);
	    }
	    SCSynchronizeWidth(sc,newwidth,sc->width,NULL);
	    SCCharChangedUpdate(sc,ly_none);
	} else if ( mv->vertical && val!=sc->vwidth-(sc->parent->ascent-bb.miny) ) {
	    double vw = val+(sc->parent->ascent-bb.miny);
	    SCPreserveWidth(sc);
	    sc->vwidth = rint(vw);
	    /* Width is an integer. Adjust the lbearing so that the rbearing */
	    /*  remains what was just typed in */
	    if ( sc->width!=vw ) {
		real transform[6];
		transform[0] = transform[3] = 1.0;
		transform[1] = transform[2] = transform[4] = 0;
		transform[5] = vw-sc->vwidth;
		FVTrans( (FontViewBase *)mv->fv,sc,transform,NULL,fvt_dontmovewidth);
	    }
	    SCCharChangedUpdate(sc,ly_none);
	}
    } else if ( e->u.control.subtype == et_textfocuschanged &&
	    e->u.control.u.tf_focus.gained_focus ) {
	for ( i=0 ; i<mv->glyphcnt; ++i )
	    if ( i!=which && mv->perchar[i].selected )
		MVDeselectChar(mv,i);
	MVSelectChar(mv,which);
    }
return( true );
}

static int AskNewKernClassEntry(SplineChar *fsc,SplineChar *lsc,int first_is_0,int second_is_0) {
    char *yesno[3];
    yesno[0] = _("_Alter Class");
    yesno[1] = _("_Create Pair");
    yesno[2] = NULL;
return( gwwv_ask(_("Use Kerning Class?"),(const char **) yesno,0,1,
	_("This kerning pair (%.20s and %.20s) is currently part of a kerning class with a 0 offset for this combination. Would you like to alter this kerning class entry (or create a kerning pair for just these two glyphs)?"),
	first_is_0  ? _("{Everything Else}") : fsc->name,
	second_is_0 ? _("{Everything Else}") : lsc->name)==0 );
}

static int MV_ChangeKerning(MetricsView *mv, int which, int offset, int is_diff) {
    SplineChar *sc = mv->glyphs[which].sc;
    SplineChar *psc = mv->glyphs[which-1].sc;
    KernPair *kp;
    KernClass *kc; int index;
    int i;
    struct lookup_subtable *sub = GGadgetGetListItemSelected(mv->subtable_list)->userdata;
    double iscale = mv->pixelsize_set_by_window ? 1.0 : mv_scales[mv->scale_index];

    kp = mv->glyphs[which-1].kp;
    kc = mv->glyphs[which-1].kc;
    index = mv->glyphs[which-1].kc_index;
    if ( kc!=NULL ) {
	if ( index==-1 )
	    kc = NULL;
	else if ( kp != NULL && kp->off != 0 )
	    kc=NULL;
	else if ( (!is_diff && offset==kc->offsets[index]) ||
		  ( is_diff && offset==0))
return( true );		/* No change, don't bother user */
	/* If there is already a kerning pair, then assume it takes the precedence over the kerning class */
	else if ( kc->offsets[index]==0 && !AskNewKernClassEntry(psc,sc,mv->glyphs[which-1].prev_kc0,mv->glyphs[which-1].next_kc0))
	    kc=NULL;
	else
	    offset = kc->offsets[index] = is_diff ? kc->offsets[index]+offset : offset;
    }
    if ( kc==NULL ) {
	if ( sub!=NULL && sub->kc!=NULL ) {
	    /* If the subtable we were given contains a kern class, and for some reason */
	    /*  we can't, or don't want to, use that kern class, then see */
	    /*  if the lookup contains another subtable with no kern classes */
	    /*  and use that */
	    struct lookup_subtable *s;
	    for ( s = sub->lookup->subtables; s!=NULL && s->kc!=NULL; s=s->next );
	    sub = s;
	}
	if ( sub==NULL ) {
	    struct subtable_data sd;
	    memset(&sd,0,sizeof(sd));
	    sd.flags = (mv->vertical ? sdf_verticalkern : sdf_horizontalkern ) |
		    sdf_kernpair;
	    sub = SFNewLookupSubtableOfType(psc->parent,gpos_pair,&sd,mv->layer);
	    if ( sub==NULL )
return( false );
	    mv->cur_subtable = sub;
	    MVSetSubtables(mv->sf);
	    MVSetFeatures(mv);
	}

#ifdef FONTFORGE_CONFIG_DEVICETABLES
	/* If we change the kerning offset, then any pixel corrections*/
	/*  will no longer apply (they only had meaning with the old  */
	/*  offset) so free the device table, if any */
	if ( kp != NULL && ((!is_diff && kp->off!=offset) || ( is_diff && offset!=0)) ) {
	    DeviceTableFree(kp->adjust);
	    kp->adjust = NULL;
	}
#endif

	offset = is_diff && kp != NULL ? kp->off+offset : offset;
	/* If kern offset has been set to zero by user, then cleanup this kerning pair */
	if ( kp != NULL && offset == 0 ) {
	    KernPair *kpcur, *kpprev;
	    KernPair **kphead = mv->vertical ? &psc->vkerns : &psc->kerns;
	    if ( kp == *kphead ) {
		*kphead = kp->next;
	    } else {
		kpprev = *kphead;
		for ( kpcur=kpprev->next; kpcur != NULL; kpcur = kpcur->next ) {
		    if ( kpcur == kp ) {
			kpprev->next = kp->next;
		break;
		    }
		    kpprev = kpcur;
		}
	    }
	    chunkfree( kp,sizeof(KernPair) );
	    kp = mv->glyphs[which-1].kp = NULL;
	} else if ( offset != 0 ) {
	    if ( kp==NULL ) {
		kp = chunkalloc(sizeof(KernPair));
		kp->sc = sc;
		if ( !mv->vertical ) {
		    kp->next = psc->kerns;
		    psc->kerns = kp;
		} else {
		    kp->next = psc->vkerns;
		    psc->vkerns = kp;
		}
		mv->glyphs[which-1].kp = kp;
	    }
	    kp->off = offset;
	    kp->subtable = sub;
	    if ( !mv->vertical )
		MMKern(sc->parent,psc,sc,is_diff?offset:offset-kp->off,sub,kp);
	}
    }
    mv->perchar[which-1].kernafter = iscale * (offset*mv->pixelsize)/
	    (mv->sf->ascent+mv->sf->descent);
    if ( mv->vertical ) {
	for ( i=which; i<mv->glyphcnt; ++i ) {
	    mv->perchar[i].dy = mv->perchar[i-1].dy+mv->perchar[i-1].dheight +
		    mv->perchar[i-1].kernafter ;
	}
    } else {
	for ( i=which; i<mv->glyphcnt; ++i ) {
	    mv->perchar[i].dx = mv->perchar[i-1].dx + mv->perchar[i-1].dwidth +
		    mv->perchar[i-1].kernafter;
	}
    }
    mv->sf->changed = true;
    GDrawRequestExpose(mv->v,NULL,false);
return( true );
}

static int MV_KernChanged(GGadget *g, GEvent *e) {
    MetricsView *mv = GDrawGetUserData(GGadgetGetWindow(g));
    int which = (intpt) GGadgetGetUserData(g);
    int i;

    if ( e->type!=et_controlevent )
return( true );
    if ( which>mv->glyphcnt-1 || which==0 )
return( true );
    if ( e->u.control.subtype == et_textchanged ) {
	unichar_t *end;
	int val = u_strtol(_GGadgetGetTitle(g),&end,10);

	if ( *end && !(*end=='-' && end[1]=='\0'))
	    GDrawBeep(NULL);
	else {
	    MV_ChangeKerning(mv,which,val, false);
	}
    } else if ( e->u.control.subtype == et_textfocuschanged &&
	    e->u.control.u.tf_focus.gained_focus ) {
	for ( i=0 ; i<mv->glyphcnt; ++i )
	    if ( i!=which && mv->perchar[i].selected )
		MVDeselectChar(mv,i);
	MVSelectChar(mv,which);
    }
return( true );
}

static void MVToggleVertical(MetricsView *mv) {
    int size;

    mv->vertical = !mv->vertical;

    GGadgetSetTitle8( mv->widthlab, mv->vertical ? "Height:" : "Width:" );
    GGadgetSetTitle8( mv->lbearinglab, mv->vertical ? "TBearing:" : "LBearing:" );
    GGadgetSetTitle8( mv->rbearinglab, mv->vertical ? "BBearing:" : "RBearing:" );
    GGadgetSetTitle8( mv->kernlab, mv->vertical ? "VKern:" : "Kern:" );

    if ( mv->vertical )
	if ( mv->scale_index<4 ) mv->scale_index = 4;

    if ( mv->pixelsize_set_by_window ) {
	size = (mv->displayend - mv->topend - 4);
	if ( mv->dwidth-20<size )
	    size = mv->dwidth-20;
	size *= mv_scales[mv->scale_index];
	if ( mv->pixelsize != size ) {
	    mv->pixelsize = size;
	    mv->dpi = 72;
	    if ( mv->bdf==NULL ) {
		BDFFontFree(mv->show);
		mv->show = SplineFontPieceMeal(mv->sf,mv->layer,mv->pixelsize,72,mv->antialias?(pf_antialias|pf_ft_recontext):pf_ft_recontext,NULL);
	    }
	    MVRemetric(mv);
	}
    }
}

static SplineChar *MVSCFromUnicode(MetricsView *mv, SplineFont *sf, EncMap *map, int ch,BDFFont *bdf) {
    int i;
    SplineChar *sc;

    if ( mv->fake_unicode_base && ch>=mv->fake_unicode_base &&
	    ch<=mv->fake_unicode_base+mv->sf->glyphcnt )
return( mv->sf->glyphs[ch-mv->fake_unicode_base] );

    i = SFFindSlot(sf,map,ch,NULL);
    if ( i==-1 )
return( NULL );
    else {
	sc = SFMakeChar(sf,map,i);
	if ( bdf!=NULL )
	    BDFMakeChar(bdf,map,i);
    }
return( sc );
}

static void MVMoveFieldsBy(MetricsView *mv,int diff) {
    int i;
    int y,x;

    for ( i=0; i<mv->max && mv->perchar[i].width!=NULL; ++i ) {
	y = mv->displayend+2;
	x = mv->perchar[i].mx-diff;
	if ( x<mv->mbase+mv->mwidth ) x = -2*mv->mwidth;
	GGadgetMove(mv->perchar[i].name,x,y);
	y += mv->fh+4;
	GGadgetMove(mv->perchar[i].width,x,y);
	y += mv->fh+4;
	GGadgetMove(mv->perchar[i].lbearing,x,y);
	y += mv->fh+4;
	GGadgetMove(mv->perchar[i].rbearing,x,y);
	y += mv->fh+4;
	if ( i!=0 )
	    GGadgetMove(mv->perchar[i].kern,x-mv->mwidth/2,y);
    }
}

static int MVDisplayedCnt(MetricsView *mv) {
    int i, wid = mv->mbase;

    for ( i=mv->coff; i<mv->glyphcnt; ++i ) {
	wid += mv->perchar[i].dwidth;
	if ( wid>mv->dwidth )
return( i-mv->coff );
    }
return( i-mv->coff );		/* There's extra room. don't know exactly how much but allow for some */
}

static void MVSetSb(MetricsView *mv) {
    int cnt = (mv->dwidth-mv->mbase-mv->mwidth)/mv->mwidth;
    int dcnt = MVDisplayedCnt(mv);

    if ( cnt>dcnt ) cnt = dcnt;
    if ( cnt==0 ) cnt = 1;

    GScrollBarSetBounds(mv->hsb,0,mv->glyphcnt,cnt);
    GScrollBarSetPos(mv->hsb,mv->coff);
}

static int MVSetVSb(MetricsView *mv) {
    int max, min, ret, yoff;
    int fudge;

    if ( mv->displayend==0 )
return(0);		/* Setting the scroll bar is premature */

    if ( mv->vertical ) {
	min = max = 0;
	if ( mv->glyphcnt!=0 )
	    max = mv->perchar[mv->glyphcnt-1].dy + mv->perchar[mv->glyphcnt-1].dheight;
	fudge = 10;
    } else {
	SplineFont *sf = mv->sf;
	int pixels = mv->pixelsize_set_by_window ? mv->vheight : mv->pixelsize;
	fudge = pixels/4;
	min = -(pixels*sf->descent)/(sf->ascent+sf->descent);
	max = pixels + min;
	min *= mv_scales[mv->scale_index];
	max *= mv_scales[mv->scale_index];
    }
    mv->ybaseline = max;
    max += fudge*mv_scales[mv->scale_index] + mv->vheight;
    min -= fudge*mv_scales[mv->scale_index];
    GScrollBarSetBounds(mv->vsb,min,max,mv->vheight);
    yoff = mv->yoff;
    if ( yoff+mv->vheight > max )
	yoff = max - mv->vheight;
    if ( yoff<min ) yoff = min;
    ret = yoff!=mv->yoff;
    mv->yoff = yoff;
    GScrollBarSetPos(mv->vsb,yoff);
return( ret );
}

static void MVHScroll(MetricsView *mv,struct sbevent *sb) {
    int newpos = mv->coff;
    int cnt = (mv->dwidth-mv->mbase-mv->mwidth)/mv->mwidth;
    int dcnt = MVDisplayedCnt(mv);

    if ( cnt>dcnt ) cnt = dcnt;
    if ( cnt==0 ) cnt = 1;

    switch( sb->type ) {
      case et_sb_top:
        newpos = 0;
      break;
      case et_sb_uppage:
        newpos -= cnt;
      break;
      case et_sb_up:
        --newpos;
      break;
      case et_sb_down:
        ++newpos;
      break;
      case et_sb_downpage:
        newpos += cnt;
      break;
      case et_sb_bottom:
        newpos = mv->glyphcnt-cnt;
      break;
      case et_sb_thumb:
      case et_sb_thumbrelease:
        newpos = sb->pos;
      break;
    }
    if ( newpos>mv->glyphcnt-cnt )
        newpos = mv->glyphcnt-cnt;
    if ( newpos<0 ) newpos =0;
    if ( newpos!=mv->coff ) {
	int old = mv->coff;
	int diff = newpos-mv->coff;
	int charsize = mv->perchar[newpos].dx-mv->perchar[old].dx;
	GRect fieldrect, charrect;

	mv->coff = newpos;
	charrect.x = 0; charrect.width = mv->vwidth;
	charrect.y = 0; charrect.height = mv->vheight;
	fieldrect.x = mv->mbase+mv->mwidth; fieldrect.width = mv->width-mv->mbase;
	fieldrect.y = mv->displayend; fieldrect.height = mv->height-mv->sbh-mv->displayend;
	GScrollBarSetBounds(mv->hsb,0,mv->glyphcnt,cnt);
	GScrollBarSetPos(mv->hsb,mv->coff);
	MVMoveFieldsBy(mv,newpos*mv->mwidth);
	GDrawScroll(mv->gw,&fieldrect,-diff*mv->mwidth,0);
	mv->xoff = mv->perchar[newpos].dx-mv->perchar[0].dx;
	if ( mv->right_to_left ) {
	    charsize = -charsize;
	}
	GDrawScroll(mv->v,&charrect,-charsize,0);
    }
}

static void MVVScroll(MetricsView *mv,struct sbevent *sb) {
    int newpos = mv->yoff;
    int32 min, max, page;

    GScrollBarGetBounds(mv->vsb,&min,&max,&page);
    switch( sb->type ) {
      case et_sb_top:
        newpos = 0;
      break;
      case et_sb_uppage:
        newpos -= page;
      break;
      case et_sb_up:
        newpos -= (page)/15;
      break;
      case et_sb_down:
        newpos += (page)/15;
      break;
      case et_sb_downpage:
        newpos += page;
      break;
      case et_sb_bottom:
        newpos = max-page;
      break;
      case et_sb_thumb:
      case et_sb_thumbrelease:
        newpos = sb->pos;
      break;
    }
    if ( newpos>max-page )
        newpos = max-page;
    if ( newpos<min ) newpos = min;
    if ( newpos!=mv->yoff ) {
	int diff = newpos-mv->yoff;
	GRect charrect;

	mv->yoff = newpos;
	charrect.x = 0; charrect.width = mv->vwidth;
	charrect.y = 0; charrect.height = mv->vheight;
	GScrollBarSetPos(mv->vsb,mv->yoff);
	GDrawScroll(mv->v,&charrect,0,diff);
    }
}

#ifdef UNICHAR_16
# define MVFakeUnicodeOfSc(mv,sc)	0xfffd
# define MVOddMatch(mv,uni,sc)		(uni==0xfffd && sc->unicodeenc==-1)
#else
static int MVFakeUnicodeOfSc(MetricsView *mv, SplineChar *sc) {

    if ( sc->unicodeenc!=-1 )
return( sc->unicodeenc );

    if ( mv->fake_unicode_base==0 ) {		/* Not set */
	/* If they have nothing in Supplementary Private Use Area-A use it */
	/* If they have nothing in Supplementary Private Use Area-B use it */
	/* else just use 0xfffd */
	int a, al, ah, b, bl, bh;
	int gid,k,max;
	SplineChar *test;
	SplineFont *_sf, *sf;
	sf = mv->sf;
	if ( sf->cidmaster ) sf = sf->cidmaster;
	k=0;
	a = al = ah = b = bl = bh = 0;
	max = 0;
	do {
	    _sf =  ( sf->subfontcnt==0 ) ? sf : sf->subfonts[k];
	    for ( gid=0; gid<_sf->glyphcnt; ++gid ) if ( (test=_sf->glyphs[gid])!=NULL ) {
		if ( test->unicodeenc>=0xf0000 && test->unicodeenc<=0xfffff ) {
		    a = true;
		    if ( test->unicodeenc<0xf8000 )
			al = true;
		    else
			ah = true;
		} else if ( test->unicodeenc>=0x100000 && test->unicodeenc<=0x10ffff ) {
		    b = true;
		    if ( test->unicodeenc<0x108000 )
			bl = true;
		    else
			bh = true;
		}
	    }
	    if ( gid>max ) max = gid;
	    ++k;
	} while ( k<sf->subfontcnt );
	if ( !a )		/* Nothing in SPUA-A */
	    mv->fake_unicode_base = 0xf0000;
	else if ( !b )
	    mv->fake_unicode_base = 0x100000;
	else if ( max<0x8000 ) {
	    if ( !al )
		mv->fake_unicode_base = 0xf0000;
	    else if ( !ah )
		mv->fake_unicode_base = 0xf8000;
	    else if ( !bl )
		mv->fake_unicode_base = 0x100000;
	    else if ( !bh )
		mv->fake_unicode_base = 0x108000;
	}
	if ( mv->fake_unicode_base==0 )
	    mv->fake_unicode_base = -1;
    }

    if ( mv->fake_unicode_base==-1 )
return( 0xfffd );
    else
return( mv->fake_unicode_base+sc->orig_pos );
}

static int MVOddMatch(MetricsView *mv,int uni,SplineChar *sc) {
    if ( sc->unicodeenc!=-1 )
return( false );
    else if ( mv->fake_unicode_base<=0 )
return( uni==0xfffd );
    else
return( uni>=mv->fake_unicode_base && sc->orig_pos == uni-mv->fake_unicode_base );
}
#endif

void MVSetSCs(MetricsView *mv, SplineChar **scs) {
    /* set the list of characters being displayed to those in scs */
    int len;
    unichar_t *ustr;

    for ( len=0; scs[len]!=NULL; ++len );
    if ( len>=mv->cmax )
	mv->chars = realloc(mv->chars,(mv->cmax=len+10)*sizeof(SplineChar *));
    memcpy(mv->chars,scs,(len+1)*sizeof(SplineChar *));
    mv->clen = len;

    ustr = galloc((len+1)*sizeof(unichar_t));
    for ( len=0; scs[len]!=NULL; ++len )
#ifdef UNICHAR_16
	if ( scs[len]->unicodeenc>0 && scs[len]->unicodeenc<0x10000 )
#else
	if ( scs[len]->unicodeenc>0 )
#endif
	    ustr[len] = scs[len]->unicodeenc;
	else
	    ustr[len] = MVFakeUnicodeOfSc(mv,scs[len]);
    ustr[len] = 0;
    GGadgetSetTitle(mv->text,ustr);
    free(ustr);

    MVRemetric(mv);

    GDrawRequestExpose(mv->v,NULL,false);
}

static void MVTextChanged(MetricsView *mv) {
    const unichar_t *ret, *pt, *ept, *tpt;
    int i,ei, j, start=0, end=0;
    int missing;
    int direction_change = false;
    SplineChar **hold = NULL;

    ret = _GGadgetGetTitle(mv->text);
    if (( ret[0]<0x10000 && isrighttoleft(ret[0]) && !mv->right_to_left ) ||
	    ( ret[0]<0x10000 && !isrighttoleft(ret[0]) && mv->right_to_left )) {
	direction_change = true;
	mv->right_to_left = !mv->right_to_left;
    }
    for ( pt=ret, i=0; i<mv->clen && *pt!='\0'; ++i, ++pt )
	if ( *pt!=mv->chars[i]->unicodeenc &&
		!MVOddMatch(mv,*pt,mv->chars[i]))
    break;
    if ( i==mv->clen && *pt=='\0' )
return;					/* Nothing changed */
    for ( ept=ret+u_strlen(ret)-1, ei=mv->clen-1; ; --ei, --ept )
	if ( ei<0 || ept<ret || (*ept!=mv->chars[ei]->unicodeenc &&
		!MVOddMatch(mv,*ept,mv->chars[ei]))) {
	    ++ei; ++ept;
    break;
	} else if ( ei<i || ept<pt ) {
	    ++ei; ++ept;
    break;
	}
    if ( ei==i && ept==pt )
	IError("No change when there should have been one in MV_TextChanged");
    if ( u_strlen(ret)>=mv->cmax ) {
	int oldmax=mv->cmax;
	mv->cmax = u_strlen(ret)+10;
	mv->chars = grealloc(mv->chars,mv->cmax*sizeof(SplineChar *));
	memset(mv->chars+oldmax,'\0',(mv->cmax-oldmax)*sizeof(SplineChar *));
    }

    missing = 0;
    for ( tpt=pt; tpt<ept; ++tpt )
	if ( mv->fake_unicode_base>0 && *tpt>=mv->fake_unicode_base &&
		*tpt<=mv->fake_unicode_base+mv->sf->glyphcnt )
	    /* That's ok */;
	else if ( SFFindSlot(mv->sf,mv->fv->b.map,*tpt,NULL)==-1 )
	    ++missing;

    if ( ept-pt-missing > ei-i ) {
	if ( i<mv->clen ) {
	    int diff = (ept-pt-missing) - (ei-i);
	    hold = galloc((mv->clen+diff+6)*sizeof(SplineChar *));
	    for ( j=mv->clen-1; j>=ei; --j )
		hold[j+diff] = mv->chars[j];
	    start = ei+diff; end = mv->clen+diff;
	}
    } else if ( ept-pt-missing != ei-i ) {
	int diff = (ept-pt-missing) - (ei-i);
	for ( j=ei; j<mv->clen; ++j )
	    if ( j+diff>=0 )
		mv->chars[j+diff] = mv->chars[j];
    }
    for ( j=i; pt<ept; ++pt ) {
	SplineChar *sc;
	sc = MVSCFromUnicode(mv,mv->sf,mv->fv->b.map,*pt,mv->bdf);
	if ( sc!=NULL )
	    mv->chars[j++] = sc;
    }
    if ( hold!=NULL ) {
	/* We had to figure out what sc's there were before we wrote over them*/
	/*  but we couldn't put them where they belonged until everything before*/
	/*  them was set properly */
	for ( j=start; j<end; ++j )
	    mv->chars[j] = hold[j];
	free(hold);
    }
    mv->clen = u_strlen(ret)-missing;
    mv->chars[mv->clen] = NULL;
    MVRemetric(mv);
    GDrawRequestExpose(mv->v,NULL,false);
}

GTextInfo mv_text_init[] = {
    { (unichar_t *) "", NULL, 0, 0, NULL, NULL, 0, 0, 0, 0, 1, 0, 1},
    { NULL, NULL, 0, 0, NULL, NULL, 0, 0, 0, 0, 0, 1},
    { (unichar_t *) N_("Load Word List..."), NULL, 0, 0, (void *) -1, NULL, 0, 0, 0, 0, 0, 0, 1},
    { (unichar_t *) N_("Load Glyph Name List..."), NULL, 0, 0, (void *) -2, NULL, 0, 0, 0, 0, 0, 0, 1},
    { NULL }
};

static void MVFigureGlyphNames(MetricsView *mv,const unichar_t *names) {
    char buffer[400], *pt, *start;
    SplineChar *founds[40];
    int i,cnt,ch;
    unichar_t *newtext;

    u2utf8_strcpy(buffer,names);
    start = buffer;
    for ( i=0; *start; ) {
	while ( *start==' ' ) ++start;
	if ( *start=='\0' )
    break;
	for ( pt=start; *pt && *pt!=' '; ++pt );
	ch = *pt; *pt = '\0';
	if ( i>=40 )
    break;
	if ( (founds[i]=SFGetChar(mv->sf,-1,start))!=NULL )
	    ++i;
	*pt = ch;
	start = pt;
    }
    cnt = i;

    if ( cnt>=mv->cmax ) {
	mv->cmax = mv->clen+cnt+10;
	mv->chars = grealloc(mv->chars,mv->cmax*sizeof(SplineChar *));
    }
    newtext = galloc((cnt+1)*sizeof(unichar_t));
    for ( i=0; i<cnt; ++i ) {
#ifdef UNICHAR_16
	newtext[i] = founds[i]->unicodeenc==-1 || founds[i]->unicodeenc>=0x10000?
#else
	newtext[i] = founds[i]->unicodeenc==-1 ?
#endif
						MVFakeUnicodeOfSc(mv,founds[i]) :
						founds[i]->unicodeenc;
	mv->chars[i] = founds[i];
    }
    newtext[i] = 0;
    mv->chars[i] = NULL;
    mv->clen = cnt;
    MVRemetric(mv);

    GGadgetSetTitle(mv->text,newtext);
    free(newtext);

    GDrawRequestExpose(mv->v,NULL,false);
}

static void MVLoadWordList(MetricsView *mv,int type) {
    GTextInfo **words;
    int cnt;
    char buffer[300], *pt;
    int ch;
    char *filename, *temp;
    FILE *file;

    filename = gwwv_open_filename(type==-1 ? "File of Kerning Words":"File of glyphname lists",NULL,"*.txt",NULL);
    if ( filename==NULL ) {
	GGadgetSetTitle8(mv->text,"");
return;
    }
    temp = utf82def_copy(filename);
    file = fopen( temp,"r" );
    free(temp);
    if ( file==NULL ) {
	ff_post_error("Could not open", "Could not open %s", filename );
	GGadgetSetTitle8(mv->text,"");
return;
    }
    free(filename);

    words = galloc(1002*sizeof(GTextInfo *));

    cnt = 0;
    if ( type==-1 ) {
	ch = getc(file);
	while ( ch!=EOF ) {
	    while ( isspace(ch) && ch<0x80 ) ch=getc(file);
	    for ( pt = buffer; ch!=EOF && !(isspace(ch) && ch<0x80) ; ch=getc(file))
		if ( pt<buffer+sizeof(buffer)-2)
		    *pt++=ch;
	    *pt = '\0';
	    if ( buffer[0]=='\0' )
	break;
	    if ( cnt>1000-3 )
	break;
	    words[cnt] = gcalloc(1,sizeof(GTextInfo));
	    words[cnt]->fg = words[cnt]->bg = COLOR_DEFAULT;
	    words[cnt]->text = (unichar_t *) utf82def_copy( buffer );
	    words[cnt++]->text_is_1byte = true;
	}
    } else {
	strcpy(buffer,"​");		/* Zero width space: 0x200b, I use as a flag */
	while ( fgets(buffer+3,sizeof(buffer)-3,file)!=NULL ) {
	    if ( buffer[3]=='\n' || buffer[3]=='#' )
	continue;
	    if ( cnt>1000-3 )
	break;
	    if ( buffer[strlen(buffer)-1]=='\n' )
		buffer[strlen(buffer)-1] = '\0';
	    words[cnt] = gcalloc(1,sizeof(GTextInfo));
	    words[cnt]->fg = words[cnt]->bg = COLOR_DEFAULT;
	    words[cnt]->text = (unichar_t *) copy( buffer );
	    words[cnt++]->text_is_1byte = true;
	}
    }
    fclose(file);
    if ( cnt!=0 ) {
	words[cnt] = gcalloc(1,sizeof(GTextInfo));
	words[cnt]->fg = words[cnt]->bg = COLOR_DEFAULT;
	words[cnt++]->line = true;
	words[cnt] = gcalloc(1,sizeof(GTextInfo));
	words[cnt]->fg = words[cnt]->bg = COLOR_DEFAULT;
	words[cnt]->text = (unichar_t *) copy( _("Load Word List...") );
	words[cnt]->text_is_1byte = true;
	words[cnt++]->userdata = (void *) -1;
	words[cnt] = gcalloc(1,sizeof(GTextInfo));
	words[cnt]->fg = words[cnt]->bg = COLOR_DEFAULT;
	words[cnt]->text = (unichar_t *) copy( _("Load Glyph Name List...") );
	words[cnt]->text_is_1byte = true;
	words[cnt++]->userdata = (void *) -2;
	words[cnt] = gcalloc(1,sizeof(GTextInfo));
	GGadgetSetList(mv->text,words,true);
	GGadgetSetTitle8(mv->text,(char *) (words[0]->text));
	if ( type==-2 )
	    MVFigureGlyphNames(mv,_GGadgetGetTitle(mv->text)+1);
	GTextInfoArrayFree(words);
	mv->word_index = 0;
    } else {
	GGadgetSetTitle8(mv->text,"");
	free(words);
    }
}

static int MV_TextChanged(GGadget *g, GEvent *e) {

    if ( e->type==et_controlevent && e->u.control.subtype == et_textchanged ) {
	MetricsView *mv = GGadgetGetUserData(g);
	int pos = e->u.control.u.tf_changed.from_pulldown;
	if ( pos!=-1 ) {
	    int32 len;
	    GTextInfo **ti = GGadgetGetList(g,&len);
	    GTextInfo *cur = ti[pos];
	    int type = (intpt) cur->userdata;
	    if ( type < 0 )
		MVLoadWordList(mv,type);
	    else if ( cur->text!=NULL ) {
		mv->word_index = pos;
		if ( cur->text[0]==0x200b )	/* Zero width space, flag for glyph names */
		    MVFigureGlyphNames(mv,cur->text+1);
	    }
	}
	MVTextChanged(mv);
    }
return( true );
}

static int MV_ScriptLangChanged(GGadget *g, GEvent *e) {

    if ( e->type==et_controlevent && e->u.control.subtype == et_textchanged ) {
	const unichar_t *sstr = _GGadgetGetTitle(g);
	MetricsView *mv = GGadgetGetUserData(g);
	if ( e->u.control.u.tf_changed.from_pulldown!=-1 ) {
	    GGadgetSetTitle8(g,mv->scriptlangs[e->u.control.u.tf_changed.from_pulldown].userdata );
	    sstr = _GGadgetGetTitle(g);
	} else {
	    if ( u_strlen(sstr)<4 || !isalpha(sstr[0]) || !isalnum(sstr[1]) /*|| !isalnum(sstr[2]) || !isalnum(sstr[3])*/ )
return( true );
	    if ( u_strlen(sstr)==4 )
		/* No language, we'll use default */;
	    else if ( u_strlen(sstr)!=10 || sstr[4]!='{' || sstr[9]!='}' ||
		    !isalpha(sstr[5]) || !isalpha(sstr[6]) || !isalpha(sstr[7])  )
return( true );
	}
	MVSetFeatures(mv);
	if ( mv->clen!=0 )/* if there are no chars, remetricking will set the script field to DFLT */
	    MVRemetric(mv);
	GDrawRequestExpose(mv->v,NULL,false);
    }
return( true );
}

static int MV_FeaturesChanged(GGadget *g, GEvent *e) {

    if ( e->type==et_controlevent && e->u.control.subtype == et_listselected ) {
	MetricsView *mv = GGadgetGetUserData(g);
	MVRemetric(mv);
	GDrawRequestExpose(mv->v,NULL,false);
    }
return( true );
}

void MV_FriendlyFeatures(GGadget *g, int pos) {
    int32 len;
    GTextInfo **ti = GGadgetGetList(g,&len);

    if ( pos<0 || pos>=len )
	GGadgetEndPopup();
    else {
	const unichar_t *pt = ti[pos]->text;
	uint32 tag;
	int i;
	tag = (pt[0]<<24) | (pt[1]<<16) | (pt[2]<<8) | pt[3];
	LookupUIInit();
	for ( i=0; friendlies[i].friendlyname!=NULL; ++i )
	    if ( friendlies[i].tag==tag )
	break;
	if ( friendlies[i].friendlyname!=NULL )
	    GGadgetPreparePopup8(GGadgetGetWindow(g),friendlies[i].friendlyname);
    }
}

static int MV_SubtableChanged(GGadget *g, GEvent *e) {

    if ( e->type==et_controlevent && e->u.control.subtype == et_listselected ) {
	MetricsView *mv = GGadgetGetUserData(g);
	int32 len;
	GTextInfo **ti = GGadgetGetList(g,&len);
	int i;
	KernPair *kp;
	struct lookup_subtable *sub;
	SplineFont *sf = mv->sf;

	if ( sf->cidmaster!=NULL ) sf = sf->cidmaster;

	if ( ti[len-1]->selected ) {/* New lookup subtable */
	    struct subtable_data sd;
	    memset(&sd,0,sizeof(sd));
	    sd.flags = (mv->vertical ? sdf_verticalkern : sdf_horizontalkern ) |
		    sdf_kernpair | sdf_dontedit;
	    sub = SFNewLookupSubtableOfType(sf,gpos_pair,&sd,mv->layer);
	    if ( sub==NULL )
return( true );
	    mv->cur_subtable = sub;
	    MVSetSubtables(mv->sf);
	    MVSetFeatures(mv);		/* Is this needed? */
	} else if ( ti[len-2]->selected ) {	/* Idiots. They selected the line, can't have that */
	    MVSetSubtables(mv->sf);
	    sub = mv->cur_subtable;
	} else
	    mv->cur_subtable = GGadgetGetListItemSelected(mv->subtable_list)->userdata;

	for ( i=0; i<mv->glyphcnt; ++i ) {
	    if ( mv->perchar[i].selected )
	break;
	}
	kp = mv->glyphs[i].kp;
	if ( kp!=NULL )
	    kp->subtable = mv->cur_subtable;
    }
return( true );
}

#define MID_ZoomIn	2002
#define MID_ZoomOut	2003
#define MID_Next	2005
#define MID_Prev	2006
#define MID_Outline	2007
#define MID_ShowGrid	2008
#define MID_HideGrid	2009
#define MID_PartialGrid 2010
#define MID_HideGridWhenMoving	2011
#define MID_NextDef	2012
#define MID_PrevDef	2013
#define MID_AntiAlias	2014
#define MID_FindInFontView	2015
#define MID_Ligatures	2020
#define MID_KernPairs	2021
#define MID_AnchorPairs	2022
#define MID_Vertical	2023
#define MID_ReplaceChar	2024
#define MID_InsertCharB	2025
#define MID_InsertCharA	2026
#define MID_Layers	2027
#define MID_PointSize	2028
#define MID_Bigger	2029
#define MID_Smaller	2030
#define MID_SizeWindow	2031
#define MID_CharInfo	2201
#define MID_FindProblems 2216
#define MID_Transform	2202
#define MID_Stroke	2203
#define MID_RmOverlap	2204
#define MID_Simplify	2205
#define MID_Correct	2206
#define MID_BuildAccent	2208
#define MID_AvailBitmaps	2210
#define MID_RegenBitmaps	2211
#define MID_Autotrace	2212
#define MID_Round	2213
#define MID_ShowDependents	2222
#define MID_AddExtrema	2224
#define MID_CleanupGlyph	2225
#define MID_TilePath	2226
#define MID_BuildComposite	2227
#define MID_Intersection	2229
#define MID_FindInter	2230
#define MID_Effects	2231
#define MID_SimplifyMore	2232
#define MID_Center	2600
#define MID_OpenBitmap	2700
#define MID_OpenOutline	2701
#define MID_Cut		2101
#define MID_Copy	2102
#define MID_Paste	2103
#define MID_Clear	2104
#define MID_SelAll	2106
#define MID_ClearSel	2105
#define MID_UnlinkRef	2108
#define MID_Undo	2109
#define MID_Redo	2110
#define MID_CopyRef	2107
#define MID_CopyWidth	2111
#define MID_CopyLBearing	2125
#define MID_CopyRBearing	2126
#define MID_CopyVWidth	2127
#define MID_Join	2128
#define MID_Center	2600
#define MID_Thirds	2604
#define MID_VKernClass	2605
#define MID_VKernFromHKern	2606
#define MID_KernOnly	2607
#define MID_WidthOnly	2608
#define MID_BothKernWidth	2609
#define MID_Recent	2703

#define MID_Warnings	3000

static void MVMenuClose(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    GDrawDestroyWindow(gw);
}

static void MVMenuOpenBitmap(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    EncMap *map;
    int i;

    if ( mv->sf->bitmaps==NULL )
return;
    for ( i=0; i<mv->glyphcnt; ++i )
	if ( mv->perchar[i].selected )
    break;
    map = mv->fv->b.map;
    if ( i!=mv->glyphcnt )
	BitmapViewCreatePick(map->backmap[mv->glyphs[i].sc->orig_pos],mv->fv);
}

static void MVMenuMergeKern(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    MergeKernInfo(mv->sf,mv->fv->b.map);
}

static void MVMenuOpenOutline(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;

    for ( i=0; i<mv->glyphcnt; ++i )
	if ( mv->perchar[i].selected )
    break;
    if ( i!=mv->glyphcnt )
	CharViewCreate(mv->glyphs[i].sc,mv->fv,-1);
}

static void MVMenuSave(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    _FVMenuSave(mv->fv);
}

static void MVMenuSaveAs(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    _FVMenuSaveAs(mv->fv);
}

static void MVMenuGenerate(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    _FVMenuGenerate(mv->fv,false);
}

static void MVMenuGenerateFamily(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    _FVMenuGenerate(mv->fv,gf_macfamily);
}

static void MVMenuGenerateTTC(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    _FVMenuGenerate(mv->fv,gf_ttc);
}

static void MVMenuPrint(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    PrintDlg(NULL,NULL,mv);
}

static void MVUndo(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;

    if ( GGadgetActiveGadgetEditCmd(mv->gw,ec_undo) )
	/* MVTextChanged(mv) */;
    else {
	for ( i=mv->glyphcnt-1; i>=0; --i )
	    if ( mv->perchar[i].selected )
	break;
	if ( i==-1 )
return;
	if ( mv->glyphs[i].sc->layers[mv->layer].undoes!=NULL )
	    SCDoUndo(mv->glyphs[i].sc,mv->layer);
    }
}

static void MVRedo(GWindow gw,struct gmenuitem *mi, GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;

    if ( GGadgetActiveGadgetEditCmd(mv->gw,ec_redo) )
	/* MVTextChanged(mv) */;
    else {
	for ( i=mv->glyphcnt-1; i>=0; --i )
	    if ( mv->perchar[i].selected )
	break;
	if ( i==-1 )
return;
	if ( mv->glyphs[i].sc->layers[mv->layer].redoes!=NULL )
	    SCDoRedo(mv->glyphs[i].sc,mv->layer);
    }
}

static void MVClear(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;
    SplineChar *sc;
    BDFFont *bdf;
    extern int onlycopydisplayed;

    if ( GGadgetActiveGadgetEditCmd(mv->gw,ec_clear) )
	/* MVTextChanged(mv) */;
    else {
	for ( i=mv->glyphcnt-1; i>=0; --i )
	    if ( mv->perchar[i].selected )
	break;
	if ( i==-1 )
return;
	sc = mv->glyphs[i].sc;
	if ( sc->dependents!=NULL ) {
	    int yes;
	    char *buts[4];
	    buts[1] = _("_Unlink");
	    buts[0] = _("_Yes");
	    buts[2] = _("_Cancel");
	    buts[3] = NULL;
	    yes = gwwv_ask(_("Bad Reference"),(const char **) buts,1,2,_("You are attempting to clear %.30s which is referred to by\nanother character. Are you sure you want to clear it?"),sc->name);
	    if ( yes==2 )
return;
	    if ( yes==1 )
		UnlinkThisReference(NULL,sc,mv->layer);
	}

	if ( onlycopydisplayed && mv->bdf==NULL ) {
	    SCClearAll(sc,mv->layer);
	} else if ( onlycopydisplayed ) {
	    BCClearAll(mv->bdf->glyphs[sc->orig_pos]);
	} else {
	    SCClearAll(sc,mv->layer);
	    for ( bdf=mv->sf->bitmaps; bdf!=NULL; bdf = bdf->next )
		BCClearAll(bdf->glyphs[sc->orig_pos]);
	}
    }
}

static void MVCut(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;

    if ( GGadgetActiveGadgetEditCmd(mv->gw,ec_cut) )
	/* MVTextChanged(mv) */;
    else {
	for ( i=mv->glyphcnt-1; i>=0; --i )
	    if ( mv->perchar[i].selected )
	break;
	if ( i==-1 )
return;
	MVCopyChar(&mv->fv->b,mv->bdf,mv->glyphs[i].sc,ct_fullcopy);
	MVClear(gw,mi,e);
    }
}

static void MVCopy(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;

    if ( GGadgetActiveGadgetEditCmd(mv->gw,ec_copy) )
	/* MVTextChanged(mv) */;
    else {
	for ( i=mv->glyphcnt-1; i>=0; --i )
	    if ( mv->perchar[i].selected )
	break;
	if ( i==-1 )
return;
	MVCopyChar(&mv->fv->b,mv->bdf,mv->glyphs[i].sc,ct_fullcopy);
    }
}

static void MVMenuCopyRef(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;

    if ( GWindowGetFocusGadgetOfWindow(gw)!=NULL )
return;
    for ( i=mv->glyphcnt-1; i>=0; --i )
	if ( mv->perchar[i].selected )
    break;
    if ( i==-1 )
return;
    MVCopyChar(&mv->fv->b,mv->bdf,mv->glyphs[i].sc,ct_reference);
}

static void MVMenuCopyWidth(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;

    if ( GWindowGetFocusGadgetOfWindow(gw)!=NULL )
return;
    for ( i=mv->glyphcnt-1; i>=0; --i )
	if ( mv->perchar[i].selected )
    break;
    if ( i==-1 )
return;
    SCCopyWidth(mv->glyphs[i].sc,
		   mi->mid==MID_CopyWidth?ut_width:
		   mi->mid==MID_CopyVWidth?ut_vwidth:
		   mi->mid==MID_CopyLBearing?ut_lbearing:
					 ut_rbearing);
}

static void MVMenuJoin(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i, changed;
    extern float joinsnap;

    if ( GWindowGetFocusGadgetOfWindow(gw)!=NULL )
return;
    for ( i=mv->glyphcnt-1; i>=0; --i )
	if ( mv->perchar[i].selected )
    break;
    if ( i==-1 )
return;
    SCPreserveLayer(mv->glyphs[i].sc,mv->layer,false);
    mv->glyphs[i].sc->layers[mv->layer].splines =
	    SplineSetJoin(mv->glyphs[i].sc->layers[mv->layer].splines,true,joinsnap,&changed);
    if ( changed )
	SCCharChangedUpdate(mv->glyphs[i].sc,mv->layer);
}

static void MVPaste(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;

    if ( GGadgetActiveGadgetEditCmd(mv->gw,ec_paste) )
	/*MVTextChanged(mv)*/;		/* Should get an event now */
    else {
	for ( i=mv->glyphcnt-1; i>=0; --i )
	    if ( mv->perchar[i].selected )
	break;
	if ( i==-1 )
return;
	PasteIntoMV(&mv->fv->b,mv->bdf,mv->glyphs[i].sc,true);
    }
}

static void MVUnlinkRef(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;
    SplineChar *sc;
    RefChar *rf, *next;

    for ( i=mv->glyphcnt-1; i>=0; --i )
	if ( mv->perchar[i].selected )
    break;
    if ( i==-1 )
return;
    sc = mv->glyphs[i].sc;
    SCPreserveLayer(sc,mv->layer,false);
    for ( rf=sc->layers[mv->layer].refs; rf!=NULL ; rf=next ) {
	next = rf->next;
	SCRefToSplines(sc,rf,mv->layer);
    }
    SCCharChangedUpdate(sc,mv->layer);
}

static void MVSelectAll(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    GGadgetActiveGadgetEditCmd(mv->gw,ec_selectall);
}

static void MVClearSelection(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;

    GWindowClearFocusGadgetOfWindow(mv->gw);
    for ( i=0; i<mv->glyphcnt; ++i )
	if ( mv->perchar[i].selected )
	    MVDeselectChar(mv,i);
}

static void MVMenuFontInfo(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    DelayEvent(FontMenuFontInfo,mv->fv);
}

static void MVMenuCharInfo(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;

    for ( i=mv->glyphcnt-1; i>=0; --i )
	if ( mv->perchar[i].selected )
    break;
    if ( i!=-1 )
	SCCharInfo(mv->glyphs[i].sc,mv->layer,mv->fv->b.map,-1);
}

static void MVMenuShowDependents(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;

    for ( i=mv->glyphcnt-1; i>=0; --i )
	if ( mv->perchar[i].selected )
    break;
    if ( i!=-1 )
return;
    if ( mv->glyphs[i].sc->dependents==NULL )
return;
    SCRefBy(mv->glyphs[i].sc);
}

static void MVMenuFindProblems(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;

    for ( i=mv->glyphcnt-1; i>=0; --i )
	if ( mv->perchar[i].selected )
    break;
    if ( i!=-1 )
	FindProblems(mv->fv,NULL,mv->glyphs[i].sc);
}

static void MVMenuBitmaps(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;

    for ( i=0; i<mv->glyphcnt; ++i )
	if ( mv->perchar[i].selected )
    break;
    if ( i!=mv->glyphcnt )
	BitmapDlg(mv->fv,mv->glyphs[i].sc,mi->mid==MID_AvailBitmaps );
    else if ( mi->mid==MID_AvailBitmaps )
	BitmapDlg(mv->fv,NULL,true );
}

static int getorigin(void *d,BasePoint *base,int index) {
    SplineChar *sc = (SplineChar *) d;
    DBounds bb;

    base->x = base->y = 0;
    switch ( index ) {
      case 0:		/* Character origin */
	/* all done */
      break;
      case 1:		/* Center of selection */
	SplineCharFindBounds(sc,&bb);
	base->x = (bb.minx+bb.maxx)/2;
	base->y = (bb.miny+bb.maxy)/2;
      break;
      default:
return( false );
    }
return( true );
}

static void MVTransFunc(void *_sc,real transform[6],int otype, BVTFunc *bvts,
	enum fvtrans_flags flags ) {
    SplineChar *sc = _sc;

    FVTrans( (FontViewBase *)sc->parent->fv,sc,transform, NULL,flags);
}

static void MVMenuTransform(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;

    for ( i=mv->glyphcnt-1; i>=0; --i )
	if ( mv->perchar[i].selected )
    break;
    if ( i!=-1 )
	TransformDlgCreate( mv->glyphs[i].sc,MVTransFunc,getorigin,true,cvt_none );
}

#ifdef FONTFORGE_CONFIG_TILEPATH
static void MVMenuTilePath(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;

    for ( i=mv->glyphcnt-1; i>=0; --i )
	if ( mv->perchar[i].selected )
    break;
    if ( i!=-1 )
	SCTile(mv->glyphs[i].sc,mv->layer);
}
#endif

static void _MVMenuOverlap(MetricsView *mv,enum overlap_type ot) {
    int i;

    for ( i=mv->glyphcnt-1; i>=0; --i )
	if ( mv->perchar[i].selected )
    break;
    if ( i!=-1 ) {
	SplineChar *sc = mv->glyphs[i].sc;
	if ( !SCRoundToCluster(sc,mv->layer,false,.03,.12))
	    SCPreserveLayer(sc,mv->layer,false);
	MinimumDistancesFree(sc->md);
	sc->md = NULL;
	sc->layers[mv->layer].splines = SplineSetRemoveOverlap(sc,sc->layers[mv->layer].splines,ot);
	SCCharChangedUpdate(sc,mv->layer);
    }
}

static void MVMenuOverlap(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    _MVMenuOverlap(mv,mi->mid==MID_RmOverlap ? over_remove :
		      mi->mid==MID_Intersection ? over_intersect :
			   over_findinter);
}

static void MVMenuInline(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);

    OutlineDlg(NULL,NULL,mv,true);
}

static void MVMenuOutline(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);

    OutlineDlg(NULL,NULL,mv,false);
}

static void MVMenuShadow(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);

    ShadowDlg(NULL,NULL,mv,false);
}

static void MVMenuWireframe(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);

    ShadowDlg(NULL,NULL,mv,true);
}

static void MVSimplify( MetricsView *mv,int type ) {
    int i;
    static struct simplifyinfo smpls[] = {
	    { sf_normal },
	    { sf_normal,.75,.05,0,-1 },
	    { sf_normal,.75,.05,0,-1 }};
    struct simplifyinfo *smpl = &smpls[type+1];

    if ( smpl->linelenmax==-1 ) {
	smpl->err = (mv->sf->ascent+mv->sf->descent)/1000.;
	smpl->linelenmax = (mv->sf->ascent+mv->sf->descent)/100.;
    }

    if ( type==1 ) {
	if ( !SimplifyDlg(mv->sf,smpl))
return;
	if ( smpl->set_as_default )
	    smpls[1] = *smpl;
    }

    for ( i=mv->glyphcnt-1; i>=0; --i )
	if ( mv->perchar[i].selected )
    break;
    if ( i!=-1 ) {
	SplineChar *sc = mv->glyphs[i].sc;
	SCPreserveLayer(sc,mv->layer,false);
	sc->layers[mv->layer].splines = SplineCharSimplify(sc,sc->layers[mv->layer].splines,smpl);
	SCCharChangedUpdate(sc,mv->layer);
    }
}

static void MVMenuSimplify(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    MVSimplify(mv,false);
}

static void MVMenuSimplifyMore(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    MVSimplify(mv,true);
}

static void MVMenuCleanup(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    MVSimplify(mv,-1);
}

static void MVMenuAddExtrema(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;
    SplineFont *sf = mv->sf;
    int emsize = sf->ascent+sf->descent;

    for ( i=mv->glyphcnt-1; i>=0; --i )
	if ( mv->perchar[i].selected )
    break;
    if ( i!=-1 ) {
	SplineChar *sc = mv->glyphs[i].sc;
	SCPreserveLayer(sc,mv->layer,false);
	SplineCharAddExtrema(sc,sc->layers[mv->layer].splines,ae_only_good,emsize);
	SCCharChangedUpdate(sc,mv->layer);
    }
}

static void MVMenuRound2Int(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;

    for ( i=mv->glyphcnt-1; i>=0; --i )
	if ( mv->perchar[i].selected )
    break;
    if ( i!=-1 ) {
	SCPreserveLayer(mv->glyphs[i].sc,mv->layer,false);
	SCRound2Int( mv->glyphs[i].sc,mv->layer,1.0);
    }
}

static void MVMenuAutotrace(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;
    GCursor ct;

    for ( i=mv->glyphcnt-1; i>=0; --i )
	if ( mv->perchar[i].selected )
    break;
    if ( i!=-1 ) {
	ct = GDrawGetCursor(mv->gw);
	GDrawSetCursor(mv->gw,ct_watch);
	ff_progress_allow_events();
	SCAutoTrace(mv->glyphs[i].sc,mv->layer,e!=NULL && (e->u.mouse.state&ksm_shift));
	GDrawSetCursor(mv->gw,ct);
    }
}

static void MVMenuCorrectDir(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;

    for ( i=mv->glyphcnt-1; i>=0; --i )
	if ( mv->perchar[i].selected )
    break;
    if ( i!=-1 ) {
	SplineChar *sc = mv->glyphs[i].sc;
	int changed = false, refchanged=false;
	RefChar *ref;
	int asked=-1;

	for ( ref=sc->layers[mv->layer].refs; ref!=NULL; ref=ref->next ) {
	    if ( ref->transform[0]*ref->transform[3]<0 ||
		    (ref->transform[0]==0 && ref->transform[1]*ref->transform[2]>0)) {
		if ( asked==-1 ) {
		    char *buts[4];
		    buts[0] = _("_Unlink");
		    buts[1] = _("_No");
		    buts[2] = _("_Cancel");
		    buts[3] = NULL;
		    asked = gwwv_ask(_("Flipped Reference"),(const char **) buts,0,2,_("%.50s contains a flipped reference. This cannot be corrected as is. Would you like me to unlink it and then correct it?"), sc->name );
		    if ( asked==2 )
return;
		    else if ( asked==1 )
	break;
		}
		if ( asked==0 ) {
		    if ( !refchanged ) {
			refchanged = true;
			SCPreserveLayer(sc,mv->layer,false);
		    }
		    SCRefToSplines(sc,ref,mv->layer);
		}
	    }
	}

	if ( !refchanged )
	    SCPreserveLayer(sc,mv->layer,false);
	sc->layers[mv->layer].splines = SplineSetsCorrect(sc->layers[mv->layer].splines,&changed);
	if ( changed || refchanged )
	    SCCharChangedUpdate(sc,mv->layer);
    }
}

static void _MVMenuBuildAccent(MetricsView *mv,int onlyaccents) {
    int i;
    extern int onlycopydisplayed;

    for ( i=mv->glyphcnt-1; i>=0; --i )
	if ( mv->perchar[i].selected )
    break;
    if ( i!=-1 ) {
	SplineChar *sc = mv->glyphs[i].sc;
	if ( SFIsSomethingBuildable(mv->sf,sc,mv->layer,onlyaccents) )
	    SCBuildComposit(mv->sf,sc,mv->layer,NULL,onlycopydisplayed);
    }
}

static void MVMenuBuildAccent(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    _MVMenuBuildAccent(mv,false);
}

static void MVMenuBuildComposite(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    _MVMenuBuildAccent(mv,true);
}

static void MVResetText(MetricsView *mv) {
    unichar_t *new, *pt;
    int i;

    new = galloc((mv->clen+1)*sizeof(unichar_t));
    for ( pt=new, i=0; i<mv->clen; ++i ) {
#ifdef UNICHAR_16
	if ( mv->chars[i]->unicodeenc==-1 || mv->chars[i]->unicodeenc>=0x10000 )
#else
	if ( mv->chars[i]->unicodeenc==-1 )
#endif
	    *pt++ = MVFakeUnicodeOfSc(mv,mv->chars[i]);
	else
	    *pt++ = mv->chars[i]->unicodeenc;
    }
    *pt = '\0';
    GGadgetSetTitle(mv->text,new);
    free(new );
}

static void MVMenuLigatures(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    SFShowLigatures(mv->sf,NULL);
}

static void MVMenuKernPairs(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    SFShowKernPairs(mv->sf,NULL,NULL,mv->layer);
}

static void MVMenuAnchorPairs(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    SFShowKernPairs(mv->sf,NULL,mi->ti.userdata,mv->layer);
}

static void _MVMenuScale( MetricsView *mv, int mid ) {

    if ( mid==MID_ZoomIn ) {
	if ( --mv->scale_index<0 ) mv->scale_index = 0;
    } else {
	if ( ++mv->scale_index >= sizeof(mv_scales)/sizeof(mv_scales[0]) )
	    mv->scale_index = sizeof(mv_scales)/sizeof(mv_scales[0])-1;
    }

    if ( mv->pixelsize_set_by_window ) {
	mv->pixelsize = mv_scales[mv->scale_index]*(mv->vheight - 2);
	if ( mv->bdf==NULL ) {
	    BDFFontFree(mv->show);
	    mv->show = SplineFontPieceMeal(mv->sf,mv->layer,mv->pixelsize,72,mv->antialias?(pf_antialias|pf_ft_recontext):pf_ft_recontext,NULL);
	} else
	    mv->pixelsize_set_by_window = false;
    }
    MVReKern(mv);
    MVSetVSb(mv);
}

static void MVMenuScale(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    _MVMenuScale(mv,mi->mid);
}

static void MVMenuInsertChar(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    SplineFont *sf = mv->sf;
    int i, j, pos = GotoChar(sf,mv->fv->b.map,NULL);

    if ( pos==-1 || pos>=mv->fv->b.map->enccount )
return;

    for ( i=0; i<mv->glyphcnt; ++i )
	if ( mv->perchar[i].selected )
    break;
    if ( i!=mv->glyphcnt )	/* Something selected */
	/* Ok... */;
    else if ( mi->mid==MID_InsertCharA )
	i = mv->glyphcnt;
    else
	i = 0;
    if ( mi->mid==MID_InsertCharA ) {
	if ( i!=mv->glyphcnt )
	    ++i;
    } else {
	if ( i==mv->glyphcnt ) i = 0;
    }
    if ( i==mv->glyphcnt )
	i = mv->clen;
    else
	i = mv->glyphs[i].orig_index;		/* Index in the string of chars, not glyphs */

    if ( mv->clen+1>=mv->cmax ) {
	int oldmax=mv->cmax;
	mv->cmax = mv->clen+10;
	mv->chars = grealloc(mv->chars,mv->cmax*sizeof(SplineChar *));
	memset(mv->chars+oldmax,'\0',(mv->cmax-oldmax)*sizeof(SplineChar *));
    }
    for ( j=mv->clen; j>i; --j )
	mv->chars[j] = mv->chars[j-1];
    mv->chars[i] = SFMakeChar(sf,mv->fv->b.map,pos);
    ++mv->clen;
    MVRemetric(mv);
    for ( j=0; j<mv->glyphcnt; ++j )
	if ( mv->glyphs[j].orig_index==i ) {
	    MVDoSelect(mv,j);
    break;
	}
    GDrawRequestExpose(mv->v,NULL,false);
    MVResetText(mv);
}

static void MVMenuChangeChar(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    SplineFont *sf = mv->sf;
    SplineChar *sc;
    EncMap *map = mv->fv->b.map;
    int i, pos, gid;

    for ( i=0; i<mv->glyphcnt; ++i )
	if ( mv->perchar[i].selected )
    break;
    if ( i!=mv->glyphcnt ) {
	pos = -1;
	i = mv->glyphs[i].orig_index;
	sc = mv->chars[ i ];
	if ( mi->mid == MID_Next ) {
	    pos = map->backmap[sc->orig_pos]+1;
	} else if ( mi->mid==MID_Prev ) {
	    pos = map->backmap[sc->orig_pos]-1;
	} else if ( mi->mid==MID_NextDef ) {
	    for ( pos = map->backmap[sc->orig_pos]+1;
		    pos<map->enccount && ((gid=map->map[pos])==-1 || sf->glyphs[gid]==NULL); ++pos );
	    if ( pos>=map->enccount )
return;
	} else if ( mi->mid==MID_PrevDef ) {
	    for ( pos = map->backmap[sc->orig_pos]-1;
		    pos<map->enccount && ((gid=map->map[pos])==-1 || sf->glyphs[gid]==NULL); --pos );
	    if ( pos<0 )
return;
	} else if ( mi->mid==MID_ReplaceChar ) {
	    pos = GotoChar(sf,mv->fv->b.map,NULL);
	    if ( pos<0 || pos>=mv->fv->b.map->enccount)
return;
	}
	if ( pos>=0 && pos<map->enccount ) {
	    mv->chars[i] = SFMakeChar(sf,mv->fv->b.map,pos);
	    MVRemetric(mv);
	    MVResetText(mv);
	    GDrawRequestExpose(mv->v,NULL,false);
	}
    }
}

static void MVMenuFindInFontView(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;

    for ( i=0; i<mv->glyphcnt; ++i ) {
	if ( mv->perchar[i].selected ) {
	    FVChangeChar(mv->fv,mv->fv->b.map->backmap[mv->glyphs[i].sc->orig_pos]);
	    GDrawSetVisible(mv->fv->gw,true);
	    GDrawRaise(mv->fv->gw);
    break;
	}
    }
}

static void MVMenuShowGrid(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    if ( mi->mid == MID_ShowGrid )
	mv->showgrid = mv_showgrid;
    else if ( mi->mid == MID_HideGrid )
	mv->showgrid = mv_hidegrid;
    else if ( mi->mid == MID_PartialGrid )
	mv->showgrid = mv_partialgrid;
    else if ( mi->mid == MID_HideGridWhenMoving )
	mv->showgrid = mv_hidemovinggrid;
    mvshowgrid = mv->showgrid;
    SavePrefs(true);
    GDrawRequestExpose(mv->v,NULL,false);
}

static void MVMenuAA(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);

    mv_antialias = mv->antialias = !mv->antialias;
    mv->bdf = NULL;
    BDFFontFree(mv->show);
    mv->show = SplineFontPieceMeal(mv->sf,mv->layer,mv->ptsize,mv->dpi,
		mv->antialias?(pf_antialias|pf_ft_recontext):pf_ft_recontext,NULL);
    GDrawRequestExpose(mv->v,NULL,false);
}

static void MVWindowTitle(char *buffer, int bufsize, MetricsView *mv) {

    snprintf(buffer,bufsize,
	    mv->type == mv_kernonly ?  _("Kerning Metrics For %.50s") :
	    mv->type == mv_widthonly ? _("Advance Width Metrics For %.50s") :
	                               _("Metrics For %.50s"),
		  mv->sf->fontname);
}

static void MVMenuWindowType(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    char buf[120];

    mv_type = mv->type = mi->mid==MID_KernOnly  ? mv_kernonly :
			 mi->mid==MID_WidthOnly ? mv_widthonly :
				 mv_kernwidth;
    MVWindowTitle(buf,sizeof(buf),mv);
    GDrawSetWindowTitles8(mv->gw, buf, buf);
    GDrawRequestExpose(mv->v,NULL,false);
    GDrawRequestExpose(mv->gw,NULL,false);
}

static void MVMenuVertical(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);

    if ( !mv->sf->hasvmetrics ) {
	if ( mv->vertical )
	    MVToggleVertical(mv);
    } else
	MVToggleVertical(mv);
    GDrawRequestExpose(mv->gw,NULL,false);
    GDrawRequestExpose(mv->v,NULL,false);
}

static void MVMenuShowBitmap(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    BDFFont *bdf = mi->ti.userdata;

    if ( mv->bdf!=bdf ) {
	mv->pixelsize_set_by_window = bdf==NULL;
	if ( bdf!=NULL ) {
	    mv->pixelsize = mv->ptsize = bdf->pixelsize;
	    mv->dpi = 72;
	}
	MVChangeDisplayFont(mv,bdf);
	GDrawRequestExpose(mv->v,NULL,false);
    }
}

#define CID_DPI		1002
#define CID_Size	1003

struct pxsz {
    MetricsView *mv;
    GWindow gw;
    int done;
};

static int PXSZ_OK(GGadget *g, GEvent *e) {

    if ( e->type==et_controlevent && e->u.control.subtype == et_buttonactivate ) {
	struct pxsz *pxsz = GDrawGetUserData(GGadgetGetWindow(g));
	MetricsView *mv = pxsz->mv;
	int ptsize, dpi, err=0;

	ptsize = GetInt8( pxsz->gw, CID_Size, _("Point Size"), &err );
	dpi = GetInt8( pxsz->gw, CID_DPI, _("DPI"), &err );
	if ( err )
return(true);
	if ( ptsize<3 || ptsize>300 || dpi<10 || dpi > 2000 ) {
	    ff_post_error(_("Number out of range"),_("Number out of range"));
return( true );
	}
	mv->pixelsize_set_by_window = false;
	mv->ptsize = ptsize;
	mv->dpi = dpi;
	mv->pixelsize = rint( (ptsize*dpi)/72.0 );
	if ( mv->bdf==NULL )
	    BDFFontFree(mv->show);
	mv->bdf = NULL;
	mv->show = SplineFontPieceMeal(mv->sf,mv->layer,mv->ptsize,mv->dpi,
		mv->antialias?(pf_antialias|pf_ft_recontext):pf_ft_recontext,NULL);

	MVReKern(mv);
	MVSetVSb(mv);
	pxsz->done = 2;
    }
return( true );
}

static int PXSZ_Cancel(GGadget *g, GEvent *e) {

    if ( e->type==et_controlevent && e->u.control.subtype == et_buttonactivate ) {
	struct pxsz *pxsz = GDrawGetUserData(GGadgetGetWindow(g));
	pxsz->done = true;
    }
return( true );
}

static int pxsz_e_h(GWindow gw, GEvent *event) {
    struct pxsz *pxsz = GDrawGetUserData(gw);

    switch ( event->type ) {
      case et_char:
return( false );
      case et_close:
	pxsz->done = true;
      break;
    }
return( true );
}

static void MVMenuPointSize(GWindow mgw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(mgw);
    struct pxsz pxsz;
    GRect pos;
    GWindow gw;
    GWindowAttrs wattrs;
    GGadgetCreateData gcd[7], *hvarray[5][3], *barray[8], boxes[3];
    GTextInfo label[7];
    int i,k;
    double iscale = mv->pixelsize_set_by_window ? 1.0 : mv_scales[mv->scale_index];
    char buffer[20], dbuffer[20];

    memset(&pxsz,0,sizeof(pxsz));
    pxsz.mv = mv;

    memset(&wattrs,0,sizeof(wattrs));
    memset(&gcd,0,sizeof(gcd));
    memset(&label,0,sizeof(label));
    memset(&boxes,0,sizeof(boxes));

    wattrs.mask = wam_events|wam_cursor|wam_utf8_wtitle|wam_undercursor|wam_isdlg|wam_restrict;
    wattrs.event_masks = ~(1<<et_charup);
    wattrs.restrict_input_to_me = true;
    wattrs.undercursor = 1;
    wattrs.cursor = ct_pointer;
    wattrs.utf8_window_title = _("Set Point Size");
    wattrs.is_dlg = true;
    pos.x = pos.y = 0;
    pos.width = 100;
    pos.height = 100;
    pxsz.gw = gw = GDrawCreateTopWindow(NULL,&pos,pxsz_e_h,&pxsz,&wattrs);

    k = i = 0;

    label[k].text = (unichar_t *) _("Point Size:");
    label[k].text_is_1byte = true;
    gcd[k].gd.label = &label[k];
    gcd[k].gd.flags = gg_visible|gg_enabled ;
    /*gcd[k].gd.handle_controlevent = SS_ScriptChanged;*/
    gcd[k++].creator = GLabelCreate;
    hvarray[i][0] = &gcd[k-1];

    sprintf( buffer, "%d", (int) rint( mv->ptsize/iscale ));
    label[k].text = (unichar_t *) buffer;
    label[k].text_is_1byte = true;
    gcd[k].gd.label = &label[k];
    gcd[k].gd.flags = gg_visible|gg_enabled;
    gcd[k].gd.cid = CID_Size;
    gcd[k++].creator = GTextFieldCreate;
    hvarray[i][1] = &gcd[k-1]; hvarray[i++][2] = NULL;

    label[k].text = (unichar_t *) _("DPI:");
    label[k].text_is_1byte = true;
    gcd[k].gd.label = &label[k];
    gcd[k].gd.flags = gg_visible|gg_enabled ;
    /*gcd[k].gd.handle_controlevent = SS_ScriptChanged;*/
    gcd[k++].creator = GLabelCreate;
    hvarray[i][0] = &gcd[k-1];

    sprintf( dbuffer, "%d", mv->dpi );
    label[k].text = (unichar_t *) dbuffer;
    label[k].text_is_1byte = true;
    gcd[k].gd.label = &label[k];
    gcd[k].gd.flags = gg_visible|gg_enabled;
    gcd[k].gd.cid = CID_DPI;
    gcd[k++].creator = GTextFieldCreate;
    hvarray[i][1] = &gcd[k-1]; hvarray[i++][2] = NULL;

    label[k].text = (unichar_t *) _("_OK");
    label[k].text_is_1byte = true;
    label[k].text_in_resource = true;
    gcd[k].gd.label = &label[k];
    gcd[k].gd.flags = gg_visible|gg_enabled | gg_but_default;
    gcd[k].gd.handle_controlevent = PXSZ_OK;
    gcd[k++].creator = GButtonCreate;

    label[k].text = (unichar_t *) _("_Cancel");
    label[k].text_is_1byte = true;
    label[k].text_in_resource = true;
    gcd[k].gd.label = &label[k];
    gcd[k].gd.flags = gg_visible|gg_enabled | gg_but_cancel;
    gcd[k].gd.handle_controlevent = PXSZ_Cancel;
    gcd[k++].creator = GButtonCreate;

    barray[0] = barray[2] = barray[3] = barray[4] = barray[6] = GCD_Glue; barray[7] = NULL;
    barray[1] = &gcd[k-2]; barray[5] = &gcd[k-1];
    hvarray[i][0] = &boxes[2]; hvarray[i][1] = GCD_ColSpan; hvarray[i++][2] = NULL;
    hvarray[i][0] = NULL;

    memset(boxes,0,sizeof(boxes));
    boxes[0].gd.pos.x = boxes[0].gd.pos.y = 2;
    boxes[0].gd.flags = gg_enabled|gg_visible;
    boxes[0].gd.u.boxelements = hvarray[0];
    boxes[0].creator = GHVGroupCreate;

    boxes[2].gd.flags = gg_enabled|gg_visible;
    boxes[2].gd.u.boxelements = barray;
    boxes[2].creator = GHBoxCreate;

    GGadgetsCreate(gw,boxes);
    GHVBoxSetExpandableCol(boxes[2].ret,gb_expandgluesame);
    GHVBoxSetExpandableRow(boxes[0].ret,gb_expandglue);

    GHVBoxFitWindow(boxes[0].ret);

    GDrawSetVisible(gw,true);
    while ( !pxsz.done )
	GDrawProcessOneEvent(NULL);
    GDrawDestroyWindow(gw);
}

static void MVMenuSizeWindow(GWindow mgw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(mgw);
    mv->pixelsize_set_by_window = true;
    mv->pixelsize = mv_scales[mv->scale_index]*(mv->vheight - 2);
    mv->dpi = 72;
    mv->ptsize = mv->pixelsize;
    if ( mv->bdf==NULL ) {
	BDFFontFree(mv->show);
	mv->show = SplineFontPieceMeal(mv->sf,mv->layer,mv->pixelsize,72,mv->antialias?(pf_antialias|pf_ft_recontext):pf_ft_recontext,NULL);
    }
    MVReKern(mv);
    MVSetVSb(mv);
}

static void MVMenuChangePointSize(GWindow mgw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(mgw);

    if ( mv->pixelsize_set_by_window )
return;
    if ( mi->mid==MID_Bigger )
	++(mv->ptsize);
    else
	--(mv->ptsize);
    mv->pixelsize = rint( (mv->ptsize*mv->dpi)/72.0 );
    if ( mv->bdf==NULL )
	BDFFontFree(mv->show);
    mv->bdf = NULL;
    mv->show = SplineFontPieceMeal(mv->sf,mv->layer,mv->ptsize,mv->dpi,
	    mv->antialias?(pf_antialias|pf_ft_recontext):pf_ft_recontext,NULL);

    MVReKern(mv);
    MVSetVSb(mv);
}

static void MVMenuChangeLayer(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);

    mv->layer = mi->mid;
    BDFFontFree(mv->show);
    mv->show = SplineFontPieceMeal(mv->sf,mv->layer,mv->ptsize,mv->dpi,
		mv->antialias?(pf_antialias|pf_ft_recontext):pf_ft_recontext,NULL);
    MVRemetric(mv);
    GDrawRequestExpose(mv->v,NULL,false);
}

static void MVMenuCenter(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;
    DBounds bb;
    real transform[6];
    SplineChar *sc;

    for ( i=0; i<mv->glyphcnt; ++i )
	if ( mv->perchar[i].selected )
    break;
    if ( i!=mv->glyphcnt ) {
	sc = mv->glyphs[i].sc;
	transform[0] = transform[3] = 1.0;
	transform[1] = transform[2] = transform[5] = 0.0;
	SplineCharFindBounds(sc,&bb);
	if ( mi->mid==MID_Center )
	    transform[4] = (sc->width-(bb.maxx-bb.minx))/2 - bb.minx;
	else
	    transform[4] = (sc->width-(bb.maxx-bb.minx))/3 - bb.minx;
	if ( transform[4]!=0 )
	    FVTrans( (FontViewBase *)mv->fv,sc,transform,NULL,fvt_dontmovewidth);
    }
}

static void MVMenuKernByClasses(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);

    ShowKernClasses(mv->sf,mv,mv->layer,false);
}

static void MVMenuVKernByClasses(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);

    ShowKernClasses(mv->sf,mv,mv->layer,true);
}

static void MVMenuVKernFromHKern(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    FVVKernFromHKern((FontViewBase *) mv->fv);
}

static void MVMenuKPCloseup(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    SplineChar *sc1=NULL, *sc2=NULL;
    int i;

    for ( i=0; i<mv->glyphcnt; ++i )
	if ( mv->perchar[i].selected ) {
	    sc1 = mv->glyphs[i].sc;
	    if ( i+1<mv->glyphcnt )
		sc2 = mv->glyphs[i+1].sc;
    break;
	}
    KernPairD(mv->sf,sc1,sc2,mv->layer,mv->vertical);
}

static GMenuItem2 wnmenu[] = {
    { { (unichar_t *) N_("New O_utline Window"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'u' }, H_("New Outline Window|Ctl+H"), NULL, NULL, MVMenuOpenOutline, MID_OpenOutline },
    { { (unichar_t *) N_("New _Bitmap Window"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'B' }, H_("New Bitmap Window|Ctl+J"), NULL, NULL, MVMenuOpenBitmap, MID_OpenBitmap },
    { { (unichar_t *) N_("New _Metrics Window"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 1, 1, 0, 0, 0, 0, 1, 1, 0, 'M' }, H_("New Metrics Window|Ctl+K"), NULL, NULL, /* No function, never avail */NULL },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("Warnings"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'M' }, H_("Warnings|No Shortcut"), NULL, NULL, _MenuWarnings, MID_Warnings },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { NULL }
};

static void MVWindowMenuBuild(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;
    SplineChar *sc;
    struct gmenuitem *wmi;

    WindowMenuBuild(gw,mi,e);

    for ( i=mv->glyphcnt-1; i>=0; --i )
	if ( mv->perchar[i].selected )
    break;
    if ( i==-1 ) sc = NULL; else sc = mv->glyphs[i].sc;

    for ( wmi = mi->sub; wmi->ti.text!=NULL || wmi->ti.line ; ++wmi ) {
	switch ( wmi->mid ) {
	  case MID_OpenOutline:
	    wmi->ti.disabled = sc==NULL;
	  break;
	  case MID_OpenBitmap:
	    mi->ti.disabled = mv->sf->bitmaps==NULL || sc==NULL;
	  break;
	  case MID_Warnings:
	    wmi->ti.disabled = ErrorWindowExists();
	  break;
	}
    }
}

static GMenuItem2 dummyitem[] = { { (unichar_t *) N_("Font|_New"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'N' }, NULL };
static GMenuItem2 fllist[] = {
    { { (unichar_t *) N_("Font|_New"), (GImage *) "filenew.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'N' }, H_("New|Ctl+N"), NULL, NULL, MenuNew },
    { { (unichar_t *) N_("_Open"), (GImage *) "fileopen.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'O' }, H_("Open|Ctl+O"), NULL, NULL, MenuOpen },
    { { (unichar_t *) N_("Recen_t"), (GImage *) "filerecent.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 't' }, NULL, dummyitem, MenuRecentBuild, NULL, MID_Recent },
    { { (unichar_t *) N_("_Close"), (GImage *) "fileclose.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'C' }, H_("Close|Ctl+Shft+Q"), NULL, NULL, MVMenuClose },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("_Save"), (GImage *) "filesave.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'S' }, H_("Save|Ctl+S"), NULL, NULL, MVMenuSave },
    { { (unichar_t *) N_("S_ave as..."), (GImage *) "filesaveas.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'a' }, H_("Save as...|Ctl+Shft+S"), NULL, NULL, MVMenuSaveAs },
    { { (unichar_t *) N_("_Generate Fonts..."), (GImage *) "filegenerate.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'G' }, H_("Generate Fonts...|Ctl+Shft+G"), NULL, NULL, MVMenuGenerate },
    { { (unichar_t *) N_("Generate Mac _Family..."), (GImage *) "filegeneratefamily.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'F' }, H_("Generate Mac Family...|Alt+Ctl+G"), NULL, NULL, MVMenuGenerateFamily },
    { { (unichar_t *) N_("Generate TTC..."), (GImage *) "filegeneratefamily.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'F' }, H_("Generate TTC...|No Shortcut"), NULL, NULL, MVMenuGenerateTTC },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("_Merge Feature Info..."), (GImage *) "filemergefeature.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'M' }, H_("Merge Kern Info...|Ctl+Shft+K"), NULL, NULL, MVMenuMergeKern },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("_Print..."), (GImage *) "fileprint.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'P' }, H_("Print...|Ctl+P"), NULL, NULL, MVMenuPrint },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("Pr_eferences..."), (GImage *) "fileprefs.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'e' }, H_("Preferences...|No Shortcut"), NULL, NULL, MenuPrefs },
    { { (unichar_t *) N_("_X Resource Editor..."), (GImage *) "menuempty.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'e' }, H_("X Resource Editor...|No Shortcut"), NULL, NULL, MenuXRes },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("_Quit"), (GImage *) "filequit.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'Q' }, H_("Quit|Ctl+Q"), NULL, NULL, MenuExit },
    { NULL }
};

static GMenuItem2 edlist[] = {
    { { (unichar_t *) N_("_Undo"), (GImage *) "editundo.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'U' }, H_("Undo|Ctl+Z"), NULL, NULL, MVUndo, MID_Undo },
    { { (unichar_t *) N_("_Redo"), (GImage *) "editredo.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'R' }, H_("Redo|Ctl+Y"), NULL, NULL, MVRedo, MID_Redo },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("Cu_t"), (GImage *) "editcut.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 't' }, H_("Cut|Ctl+X"), NULL, NULL, MVCut, MID_Cut },
    { { (unichar_t *) N_("_Copy"), (GImage *) "editcopy.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'C' }, H_("Copy|Ctl+C"), NULL, NULL, MVCopy, MID_Copy },
    { { (unichar_t *) N_("C_opy Reference"), (GImage *) "editcopyref.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'o' }, H_("Copy Reference|Ctl+G"), NULL, NULL, MVMenuCopyRef, MID_CopyRef },
    { { (unichar_t *) N_("Copy _Width"), (GImage *) "editcopywidth.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'W' }, H_("Copy Width|Ctl+W"), NULL, NULL, MVMenuCopyWidth, MID_CopyWidth },
    { { (unichar_t *) N_("Copy _VWidth"), (GImage *) "editcopyvwidth.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'V' }, H_("Copy VWidth|No Shortcut"), NULL, NULL, MVMenuCopyWidth, MID_CopyVWidth },
    { { (unichar_t *) N_("Co_py LBearing"), (GImage *) "editcopylbearing.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'p' }, H_("Copy LBearing|No Shortcut"), NULL, NULL, MVMenuCopyWidth, MID_CopyLBearing },
    { { (unichar_t *) N_("Copy RBearin_g"), (GImage *) "editcopyrbearing.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'g' }, H_("Copy RBearing|No Shortcut"), NULL, NULL, MVMenuCopyWidth, MID_CopyRBearing },
    { { (unichar_t *) N_("_Paste"), (GImage *) "editpaste.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'P' }, H_("Paste|Ctl+V"), NULL, NULL, MVPaste, MID_Paste },
    { { (unichar_t *) N_("C_lear"), (GImage *) "editclear.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 1, 1, 0, 0, 0, 0, 1, 1, 0, 'l' }, H_("Clear|No Shortcut"), NULL, NULL, MVClear, MID_Clear },
    { { (unichar_t *) N_("_Join"), (GImage *) "editjoin.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'J' }, H_("Join|Ctl+Shft+J"), NULL, NULL, MVMenuJoin, MID_Join },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("Select _All"), (GImage *) "editselect.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 1, 1, 0, 0, 0, 0, 1, 1, 0, 'A' }, H_("Select All|Ctl+A"), NULL, NULL, MVSelectAll, MID_SelAll },
    { { (unichar_t *) N_("_Deselect All"), (GImage *) "menuempty.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'D' }, H_("Clear Selection|Escape"), NULL, NULL, MVClearSelection, MID_ClearSel },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("U_nlink Reference"), (GImage *) "editunlink.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'U' }, H_("Unlink Reference|Ctl+U"), NULL, NULL, MVUnlinkRef, MID_UnlinkRef },
    { NULL }
};

static GMenuItem2 smlist[] = {
    { { (unichar_t *) N_("_Simplify"), (GImage *) "elementsimplify.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'S' }, H_("Simplify|Ctl+Shft+M"), NULL, NULL, MVMenuSimplify, MID_Simplify },
    { { (unichar_t *) N_("Simplify More..."), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'S' }, H_("Simplify More...|Alt+Ctl+Shft+M"), NULL, NULL, MVMenuSimplifyMore, MID_SimplifyMore },
    { { (unichar_t *) N_("Clea_nup Glyph"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'n' }, H_("Cleanup Glyph|No Shortcut"), NULL, NULL, MVMenuCleanup, MID_CleanupGlyph },
    { NULL }
};

static GMenuItem2 rmlist[] = {
    { { (unichar_t *) N_("_Remove Overlap"), (GImage *) "overlaprm.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, true, 0, 0, 0, 0, 1, 1, 0, 'O' }, H_("Remove Overlap|Ctl+Shft+O"), NULL, NULL, MVMenuOverlap, MID_RmOverlap },
    { { (unichar_t *) N_("_Intersect"), (GImage *) "overlapintersection.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, true, 0, 0, 0, 0, 1, 1, 0, 'I' }, H_("Intersect|No Shortcut"), NULL, NULL, MVMenuOverlap, MID_Intersection },
    { { (unichar_t *) N_("_Find Intersections"), (GImage *) "overlapfindinter.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, true, 0, 0, 0, 0, 1, 1, 0, 'O' }, H_("Find Intersections|No Shortcut"), NULL, NULL, MVMenuOverlap, MID_FindInter },
    { NULL }
};

static GMenuItem2 eflist[] = {
    { { (unichar_t *) N_("_Inline"), (GImage *) "stylesinline.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, true, 0, 0, 0, 0, 1, 1, 0, 'O' }, H_("Inline|No Shortcut"), NULL, NULL, MVMenuInline },
    { { (unichar_t *) N_("_Outline"), (GImage *) "stylesoutline.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, true, 0, 0, 0, 0, 1, 1, 0, 'I' }, H_("Outline|No Shortcut"), NULL, NULL, MVMenuOutline },
    { { (unichar_t *) N_("_Shadow"), (GImage *) "stylesshadow.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, true, 0, 0, 0, 0, 1, 1, 0, 'S' }, H_("Shadow|No Shortcut"), NULL, NULL, MVMenuShadow },
    { { (unichar_t *) N_("_Wireframe"), (GImage *) "styleswireframe.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, true, 0, 0, 0, 0, 1, 1, 0, 'W' }, H_("Wireframe|No Shortcut"), NULL, NULL, MVMenuWireframe },
    { NULL }
};

static GMenuItem2 balist[] = {
    { { (unichar_t *) N_("_Build Accented Glyph"), (GImage *) "elementbuildaccent.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'B' }, H_("Build Accented Glyph|Ctl+Shft+A"), NULL, NULL, MVMenuBuildAccent, MID_BuildAccent },
    { { (unichar_t *) N_("Build _Composite Glyph"), (GImage *) "elementbuildcomposite.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'B' }, H_("Build Composite Glyph|No Shortcut"), NULL, NULL, MVMenuBuildComposite, MID_BuildComposite },
    { NULL }
};

static void balistcheck(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;
    SplineChar *sc;

    for ( i=mv->glyphcnt-1; i>=0; --i )
	if ( mv->perchar[i].selected )
    break;
    if ( i==-1 ) sc = NULL; else sc = mv->glyphs[i].sc;

    for ( mi = mi->sub; mi->ti.text!=NULL || mi->ti.line ; ++mi ) {
	switch ( mi->mid ) {
	  case MID_BuildAccent:
	    mi->ti.disabled = sc==NULL || !SFIsSomethingBuildable(sc->parent,sc,mv->layer,true);
	  break;
	  case MID_BuildComposite:
	    mi->ti.disabled = sc==NULL || !SFIsSomethingBuildable(sc->parent,sc,mv->layer,false);
	  break;
        }
    }
}

static GMenuItem2 ellist[] = {
    { { (unichar_t *) N_("_Font Info..."), (GImage *) "elementfontinfo.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'F' }, H_("Font Info...|Ctl+Shft+F"), NULL, NULL, MVMenuFontInfo },
    { { (unichar_t *) N_("Glyph _Info..."), (GImage *) "elementglyphinfo.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'I' }, H_("Glyph Info...|Ctl+I"), NULL, NULL, MVMenuCharInfo, MID_CharInfo },
    { { (unichar_t *) N_("S_how Dependent"), (GImage *) "elementshowdep.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'D' }, H_("Show Dependent|Alt+Ctl+I"), NULL, NULL, MVMenuShowDependents, MID_ShowDependents },
    { { (unichar_t *) N_("Find Pr_oblems..."), (GImage *) "elementfindprobs.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'o' }, H_("Find Problems...|Ctl+E"), NULL, NULL, MVMenuFindProblems, MID_FindProblems },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("Bitm_ap Strikes Available..."), (GImage *) "elementbitmapsavail.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'A' }, H_("Bitmap Strikes Available...|Ctl+Shft+B"), NULL, NULL, MVMenuBitmaps, MID_AvailBitmaps },
    { { (unichar_t *) N_("Regenerate _Bitmap Glyphs..."), (GImage *) "elementregenbitmaps.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'B' }, H_("Regenerate Bitmap Glyphs...|Ctl+B"), NULL, NULL, MVMenuBitmaps, MID_RegenBitmaps },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("_Transform..."), (GImage *) "elementtransform.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'T' }, H_("Transform...|Ctl+\\"), NULL, NULL, MVMenuTransform, MID_Transform },
#ifdef FONTFORGE_CONFIG_TILEPATH
    { { (unichar_t *) N_("Tile _Path..."), (GImage *) "elementtilepath.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'P' }, H_("Tile Path...|No Shortcut"), NULL, NULL, MVMenuTilePath, MID_TilePath },
#endif
    { { (unichar_t *) N_("_Remove Overlap"), (GImage *) "rmoverlap.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'O' }, NULL, rmlist, NULL, NULL, MID_RmOverlap },
    { { (unichar_t *) N_("_Simplify"), (GImage *) "elementsimplify.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'S' }, NULL, smlist, NULL, NULL, MID_Simplify },
    { { (unichar_t *) N_("Add E_xtrema"), (GImage *) "elementaddextrema.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'x' }, H_("Add Extrema|Ctl+Shft+X"), NULL, NULL, MVMenuAddExtrema, MID_AddExtrema },
    { { (unichar_t *) N_("To _Int"), (GImage *) "elementround.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'I' }, H_("To Int|Ctl+Shft+_"), NULL, NULL, MVMenuRound2Int, MID_Round },
    { { (unichar_t *) N_("Effects"), (GImage *) "elementstyles.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, '\0' }, NULL, eflist, NULL, NULL, MID_Effects },
    { { (unichar_t *) N_("Autot_race"), (GImage *) "elementautotrace.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'r' }, H_("Autotrace|Ctl+Shft+T"), NULL, NULL, MVMenuAutotrace, MID_Autotrace },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("_Correct Direction"), (GImage *) "elementcorrectdir.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'D' }, H_("Correct Direction|Ctl+Shft+D"), NULL, NULL, MVMenuCorrectDir, MID_Correct },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("B_uild"), (GImage *) "elementbuildaccent.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'B' }, NULL, balist, balistcheck, NULL, MID_BuildAccent },
    { NULL }
};

static GMenuItem2 dummyall[] = {
    { { (unichar_t *) N_("All"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 1, 1, 0, 0, 0, 0, 1, 1, 0, 'K' }, H_("All|No Shortcut"), NULL, NULL, NULL },
    NULL
};

/* Builds up a menu containing all the anchor classes */
static void aplistbuild(GWindow base,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(base);
    extern void GMenuItemArrayFree(GMenuItem *mi);

    GMenuItemArrayFree(mi->sub);
    mi->sub = NULL;

    _aplistbuild(mi,mv->sf,MVMenuAnchorPairs);
}

static GMenuItem2 cblist[] = {
    { { (unichar_t *) N_("_Kern Pairs"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'K' }, H_("Kern Pairs|No Shortcut"), NULL, NULL, MVMenuKernPairs, MID_KernPairs },
    { { (unichar_t *) N_("_Anchored Pairs"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'K' }, H_("Anchored Pairs|No Shortcut"), dummyall, aplistbuild, MVMenuAnchorPairs, MID_AnchorPairs },
    { { (unichar_t *) N_("_Ligatures"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'L' }, H_("Ligatures|No Shortcut"), NULL, NULL, MVMenuLigatures, MID_Ligatures },
    NULL
};

static void cblistcheck(GWindow gw,struct gmenuitem *mi, GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    SplineFont *sf = mv->sf;
    int i, anyligs=0, anykerns=0;
    PST *pst;

    for ( i=0; i<sf->glyphcnt; ++i ) if ( sf->glyphs[i]!=NULL ) {
	for ( pst=sf->glyphs[i]->possub; pst!=NULL; pst=pst->next ) {
	    if ( pst->type==pst_ligature ) {
		anyligs = true;
		if ( anykerns )
    break;
	    }
	}
	if ( (mv->vertical ? sf->glyphs[i]->vkerns : sf->glyphs[i]->kerns)!=NULL ) {
	    anykerns = true;
	    if ( anyligs )
    break;
	}
    }

    for ( mi = mi->sub; mi->ti.text!=NULL || mi->ti.line ; ++mi ) {
	switch ( mi->mid ) {
	  case MID_Ligatures:
	    mi->ti.disabled = !anyligs;
	  break;
	  case MID_KernPairs:
	    mi->ti.disabled = !anykerns;
	  break;
	  case MID_AnchorPairs:
	    mi->ti.disabled = sf->anchor==NULL;
	  break;
	}
    }
}

static GMenuItem2 lylist[] = {
    { { (unichar_t *) N_("Layer|Foreground"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 1, 1, 1, 1, 0, 0, 1, 1, 0, '\0' }, NULL, NULL, NULL, MVMenuChangeLayer, ly_fore },
    NULL
};

static void lylistcheck(GWindow gw,struct gmenuitem *mi, GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    SplineFont *sf = mv->fv->b.sf;
    extern void GMenuItemArrayFree(GMenuItem *mi);
    int ly;
    GMenuItem *sub;

    sub = gcalloc(sf->layer_cnt+1,sizeof(GMenuItem));
    for ( ly=ly_fore; ly<sf->layer_cnt; ++ly ) {
	sub[ly-1].ti.text = utf82u_copy(sf->layers[ly].name);
	sub[ly-1].ti.checkable = true;
	sub[ly-1].ti.checked = ly == mv->layer;
	sub[ly-1].invoke = MVMenuChangeLayer;
	sub[ly-1].mid = ly;
	sub[ly-1].ti.fg = sub[ly-1].ti.bg = COLOR_DEFAULT;
    }
    GMenuItemArrayFree(mi->sub);
    mi->sub = sub;
}

static GMenuItem2 gdlist[] = {
    { { (unichar_t *) N_("_Show"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 1, 0, 0, 0, 1, 1, 0, 'C' }, H_("Show Grid|No Shortcut"), NULL, NULL, MVMenuShowGrid, MID_ShowGrid },
    { { (unichar_t *) N_("_Partial"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 1, 0, 0, 0, 1, 1, 0, 'C' }, H_("Partial Grid|No Shortcut"), NULL, NULL, MVMenuShowGrid, MID_PartialGrid },
    { { (unichar_t *) N_("Hide when _Moving"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 1, 0, 0, 0, 1, 1, 0, 'C' }, H_("Hide Grid when Moving|No Shortcut"), NULL, NULL, MVMenuShowGrid, MID_HideGridWhenMoving },
    { { (unichar_t *) N_("_Hide"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 1, 0, 0, 0, 1, 1, 0, 'C' }, H_("Hide Grid|No Shortcut"), NULL, NULL, MVMenuShowGrid, MID_HideGrid },
    { NULL }
};

static void gdlistcheck(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);

    for ( mi = mi->sub; mi->ti.text!=NULL || mi->ti.line ; ++mi ) {
	switch ( mi->mid ) {
	  case MID_ShowGrid:
	    mi->ti.checked = mv->showgrid == mv_showgrid;
	  break;
	  case MID_HideGrid:
	    mi->ti.checked = mv->showgrid == mv_hidegrid;
	  break;
	  case MID_PartialGrid:
	    mi->ti.checked = mv->showgrid == mv_partialgrid;
	  break;
	  case MID_HideGridWhenMoving:
	    mi->ti.checked = mv->showgrid == mv_hidemovinggrid;
	  break;
	}
    }
}

static GMenuItem2 vwlist[] = {
    { { (unichar_t *) N_("Z_oom out"), (GImage *) "viewzoomout.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'o' }, H_("Zoom out|Alt+Ctl+-"), NULL, NULL, MVMenuScale, MID_ZoomOut },
    { { (unichar_t *) N_("Zoom _in"), (GImage *) "viewzoomin.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'i' }, H_("Zoom in|Alt+Ctl+Shft++"), NULL, NULL, MVMenuScale, MID_ZoomIn },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("Insert Glyph _After..."), (GImage *) "viewinsertafter.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'C' }, H_("Insert Glyph After...|No Shortcut"), NULL, NULL, MVMenuInsertChar, MID_InsertCharA },
    { { (unichar_t *) N_("Insert Glyph _Before..."), (GImage *) "viewinsertbefore.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'B' }, H_("Insert Glyph Before...|No Shortcut"), NULL, NULL, MVMenuInsertChar, MID_InsertCharB },
    { { (unichar_t *) N_("_Replace Glyph..."), (GImage *) "viewreplace.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'R' }, H_("Replace Glyph...|Ctl+G"), NULL, NULL, MVMenuChangeChar, MID_ReplaceChar },
    { { (unichar_t *) N_("_Next Glyph"), (GImage *) "viewnext.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'N' }, H_("Next Glyph|Ctl+]"), NULL, NULL, MVMenuChangeChar, MID_Next },
    { { (unichar_t *) N_("_Prev Glyph"), (GImage *) "viewprev.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'P' }, H_("Prev Glyph|Ctl+["), NULL, NULL, MVMenuChangeChar, MID_Prev },
    { { (unichar_t *) N_("Next _Defined Glyph"), (GImage *) "viewnextdef.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'D' }, H_("Next Defined Glyph|Alt+Ctl+]"), NULL, NULL, MVMenuChangeChar, MID_NextDef },
    { { (unichar_t *) N_("Prev Defined Gl_yph"), (GImage *) "viewprevdef.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'a' }, H_("Prev Defined Glyph|Alt+Ctl+["), NULL, NULL, MVMenuChangeChar, MID_PrevDef },
    { { (unichar_t *) N_("Find In Font _View"), (GImage *) "viewfindinfont.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'V' }, H_("Find In Font View|Ctl+Shft+<"), NULL, NULL, MVMenuFindInFontView, MID_FindInFontView },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("_Layers"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, '\0' }, NULL, lylist, lylistcheck },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("Com_binations"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'b' }, NULL, cblist, cblistcheck },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("Show _Grid"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'G' }, NULL, gdlist, gdlistcheck, MVMenuShowGrid, MID_ShowGrid },
    { { (unichar_t *) N_("_Anti Alias"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 1, 0, 0, 0, 1, 1, 0, 'A' }, H_("Anti Alias|Ctl+="), NULL, NULL, MVMenuAA, MID_AntiAlias },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("_Vertical"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 1, 0, 0, 0, 1, 1, 0, '\0' }, H_("Vertical|No Shortcut"), NULL, NULL, MVMenuVertical, MID_Vertical },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("Size set from _Window"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 1, 0, 0, 0, 1, 1, 0, 'O' }, H_("Size set from Window|No Shortcut"), NULL, NULL, MVMenuSizeWindow, MID_SizeWindow },
    { { (unichar_t *) N_("Set Point _Size"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 1, 0, 0, 0, 1, 1, 0, 'O' }, H_("Set Point Size|No Shortcut"), NULL, NULL, MVMenuPointSize, MID_PointSize },
    { { (unichar_t *) N_("_Bigger Point Size"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'B' }, H_("Bigger Point Size|Ctl+Shft++"), NULL, NULL, MVMenuChangePointSize, MID_Bigger },
    { { (unichar_t *) N_("_Smaller Point Size"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'S' }, H_("Smaller Point Size|Ctl+-"), NULL, NULL, MVMenuChangePointSize, MID_Smaller },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("_Outline"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 1, 0, 0, 0, 1, 1, 0, 'O' }, H_("Outline|No Shortcut"), NULL, NULL, MVMenuShowBitmap, MID_Outline },
    { NULL },			/* Some extra room to show bitmaps */
    { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
    { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
    { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL }, { NULL },
    { NULL }
};

static void MVMenuContextualHelp(GWindow base,struct gmenuitem *mi,GEvent *e) {
    help("metricsview.html");
}

static GMenuItem2 tylist[] = {
    { { (unichar_t *) N_("_Kerning only"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 1, 0, 0, 0, 1, 1, 0, 'C' }, H_("Kerning only|No Shortcut"), NULL, NULL, MVMenuWindowType, MID_KernOnly },
    { { (unichar_t *) N_("_Advance Width only"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 1, 0, 0, 0, 1, 1, 0, 'T' }, H_("Advance Width Only|No Shortcut"), NULL, NULL, MVMenuWindowType, MID_WidthOnly },
    { { (unichar_t *) N_("_Both"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 1, 0, 0, 0, 1, 1, 0, 'T' }, H_("Both|No Shortcut"), NULL, NULL, MVMenuWindowType, MID_BothKernWidth },
    { NULL }
};

static void tylistcheck(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);

    for ( mi = mi->sub; mi->ti.text!=NULL || mi->ti.line ; ++mi ) {
	switch ( mi->mid ) {
	  case MID_KernOnly:
	    mi->ti.checked = mv->type == mv_kernonly;
	  break;
	  case MID_WidthOnly:
	    mi->ti.checked = mv->type == mv_widthonly;
	  break;
	  case MID_BothKernWidth:
	    mi->ti.checked = mv->type == mv_kernwidth;
	  break;
	}
    }
}

static GMenuItem2 mtlist[] = {
    { { (unichar_t *) N_("_Center in Width"), (GImage *) "metricscenter.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'C' }, H_("Center in Width|No Shortcut"), NULL, NULL, MVMenuCenter, MID_Center },
    { { (unichar_t *) N_("_Thirds in Width"), (GImage *) "menuempty.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'T' }, H_("Thirds in Width|No Shortcut"), NULL, NULL, MVMenuCenter, MID_Thirds },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("_Window Type"), (GImage *) "menuempty.png", COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'T' }, NULL, tylist, tylistcheck },
    { { NULL, NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 1, 0, 0, }},
    { { (unichar_t *) N_("Ker_n By Classes..."), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'T' }, H_("Kern By Classes...|No Shortcut"), NULL, NULL, MVMenuKernByClasses },
    { { (unichar_t *) N_("VKern By Classes..."), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'T' }, H_("VKern By Classes...|No Shortcut"), NULL, NULL, MVMenuVKernByClasses, MID_VKernClass },
    { { (unichar_t *) N_("VKern From HKern"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'T' }, H_("VKern From HKern|No Shortcut"), NULL, NULL, MVMenuVKernFromHKern, MID_VKernFromHKern },
    { { (unichar_t *) N_("Kern Pair Closeup..."), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'T' }, H_("Kern Pair Closeup...|No Shortcut"), NULL, NULL, MVMenuKPCloseup },
    { NULL }
};

static void fllistcheck(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    /*MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);*/

    for ( mi = mi->sub; mi->ti.text!=NULL || mi->ti.line ; ++mi ) {
	switch ( mi->mid ) {
	  case MID_Recent:
	    mi->ti.disabled = !RecentFilesAny();
	  break;
	}
    }
}

static void edlistcheck(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i;

    if ( GWindowGetFocusGadgetOfWindow(gw)!=NULL )
	i = -1;
    else
	for ( i=mv->glyphcnt-1; i>=0; --i )
	    if ( mv->perchar[i].selected )
	break;

    for ( mi = mi->sub; mi->ti.text!=NULL || mi->ti.line ; ++mi ) {
	switch ( mi->mid ) {
	  case MID_Cut: case MID_Copy:
#if 0	/* Copy/Cut should always be enabled so textfields will get the event */
	    mi->ti.disabled = i==-1;
#endif
	  break;
	  case MID_Join:
	  case MID_CopyRef: case MID_CopyWidth:
	  case MID_CopyLBearing: case MID_CopyRBearing:
	  case MID_Clear:
	    mi->ti.disabled = i==-1;
	  break;
	  case MID_CopyVWidth:
	    mi->ti.disabled = i==-1 || !mv->sf->hasvmetrics;
	  break;
#if 0	/* Undo/Redo should always be enabled so textfields will get the event */
	  case MID_Undo:
	    mi->ti.disabled = i==-1 || mv->glyphs[i].sc->layers[mv->layer].undoes==NULL;
	  break;
	  case MID_Redo:
	    mi->ti.disabled = i==-1 || mv->glyphs[i].sc->layers[mv->layer].redoes==NULL;
	  break;
#endif
	  case MID_UnlinkRef:
	    mi->ti.disabled = i==-1 || mv->glyphs[i].sc->layers[mv->layer].refs==NULL;
	  break;
	  case MID_Paste:
#if 0	/* Paste should always be enabled so textfields will get the event */
	    mi->ti.disabled = i==-1 ||
		(!CopyContainsSomething() &&
#ifndef _NO_LIBPNG
		    !GDrawSelectionHasType(mv->gw,sn_clipboard,"image/png") &&
#endif
#ifndef _NO_LIBXML
		    !GDrawSelectionHasType(mv->gw,sn_clipboard,"image/svg+xml") &&
		    !GDrawSelectionHasType(mv->gw,sn_clipboard,"image/svg-xml") &&
		    !GDrawSelectionHasType(mv->gw,sn_clipboard,"image/svg") &&
#endif
		    !GDrawSelectionHasType(mv->gw,sn_clipboard,"image/bmp") &&
		    !GDrawSelectionHasType(mv->gw,sn_clipboard,"image/ps") &&
		    !GDrawSelectionHasType(mv->gw,sn_clipboard,"image/eps"));
#endif
	  break;
	}
    }
}

static void ellistcheck(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i, anybuildable;
    SplineChar *sc;
    int order2 = mv->sf->layers[mv->layer].order2;

    for ( i=mv->glyphcnt-1; i>=0; --i )
	if ( mv->perchar[i].selected )
    break;
    if ( i==-1 ) sc = NULL; else sc = mv->glyphs[i].sc;

    for ( mi = mi->sub; mi->ti.text!=NULL || mi->ti.line ; ++mi ) {
	switch ( mi->mid ) {
	  case MID_RegenBitmaps:
	    mi->ti.disabled = mv->sf->bitmaps==NULL;
	  break;
	  case MID_CharInfo:
	    mi->ti.disabled = sc==NULL /*|| mv->fv->b.cidmaster!=NULL*/;
	  break;
	  case MID_ShowDependents:
	    mi->ti.disabled = sc==NULL || sc->dependents == NULL;
	  break;
	  case MID_FindProblems:
	  case MID_Transform:
	    mi->ti.disabled = sc==NULL;
	  break;
	  case MID_Effects:
	    mi->ti.disabled = sc==NULL || mv->sf->onlybitmaps || order2;
	  break;
	  case MID_RmOverlap: case MID_Stroke:
	    mi->ti.disabled = sc==NULL || mv->sf->onlybitmaps;
	  break;
	  case MID_AddExtrema:
	  case MID_Round: case MID_Correct:
	    mi->ti.disabled = sc==NULL || mv->sf->onlybitmaps;
	  break;
#ifdef FONTFORGE_CONFIG_TILEPATH
	  case MID_TilePath:
	    mi->ti.disabled = sc==NULL || mv->sf->onlybitmaps || ClipBoardToSplineSet()==NULL || order2;
	  break;
#endif
	  case MID_Simplify:
	    mi->ti.disabled = sc==NULL || mv->sf->onlybitmaps;
	  break;
	  case MID_BuildAccent:
	    anybuildable = false;
	    if ( sc!=NULL && SFIsSomethingBuildable(mv->sf,sc,mv->layer,false) )
		anybuildable = true;
	    mi->ti.disabled = !anybuildable;
	  break;
	  case MID_Autotrace:
	    mi->ti.disabled = !(FindAutoTraceName()!=NULL && sc!=NULL &&
		    sc->layers[ly_back].images!=NULL );
	  break;
	}
    }
}

static void vwlistcheck(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    int i, j, base, aselection;
    BDFFont *bdf;
    char buffer[60];
    extern void GMenuItemArrayFree(GMenuItem *mi);
    extern GMenuItem *GMenuItem2ArrayCopy(GMenuItem2 *mi, uint16 *cnt);

    aselection = false;
    for ( j=0; j<mv->glyphcnt; ++j )
	if ( mv->perchar[j].selected ) {
	    aselection = true;
    break;
	}

    for ( i=0; vwlist[i].mid!=MID_Outline; ++i )
	switch ( vwlist[i].mid ) {
	  case MID_ZoomIn:
	    vwlist[i].ti.disabled = mv->scale_index==0;
	  break;
	  case MID_ZoomOut:
	    vwlist[i].ti.disabled = mv->scale_index>=sizeof(mv_scales)/sizeof(mv_scales[0])-1;
	  break;
	  case MID_AntiAlias:
	    vwlist[i].ti.checked = mv->antialias;
	    vwlist[i].ti.disabled = mv->bdf!=NULL;
	  break;
	  case MID_SizeWindow:
	    vwlist[i].ti.disabled = mv->pixelsize_set_by_window;
	    vwlist[i].ti.checked = mv->pixelsize_set_by_window;
	  break;
	  case MID_Bigger:
	  case MID_Smaller:
	    vwlist[i].ti.disabled = mv->pixelsize_set_by_window;
	  break;
	  case MID_ReplaceChar:
	  case MID_FindInFontView:
	  case MID_Next:
	  case MID_Prev:
	  case MID_NextDef:
	  case MID_PrevDef:
	    vwlist[i].ti.disabled = !aselection;
	  break;
	  case MID_Vertical:
	    vwlist[i].ti.checked = mv->vertical;
	    vwlist[i].ti.disabled = !mv->sf->hasvmetrics;
	  break;
	  case MID_Layers:
	    vwlist[i].ti.disabled = mv->sf->layer_cnt<=2 || mv->sf->multilayer;
	  break;
	}
    vwlist[i].ti.checked = mv->bdf==NULL;
    base = i+1;
    for ( i=base; vwlist[i].ti.text!=NULL || vwlist[i].ti.line; ++i ) {
	free( vwlist[i].ti.text);
	vwlist[i].ti.text = NULL;
    }

    if ( mv->sf->bitmaps!=NULL ) {
	for ( bdf = mv->sf->bitmaps, i=base;
		i<sizeof(vwlist)/sizeof(vwlist[0])-1 && bdf!=NULL;
		++i, bdf = bdf->next ) {
	    if ( BDFDepth(bdf)==1 )
		sprintf( buffer, _("%d pixel bitmap"), bdf->pixelsize );
	    else
		sprintf( buffer, _("%d@%d pixel bitmap"),
			bdf->pixelsize, BDFDepth(bdf) );
	    vwlist[i].ti.text = utf82u_copy(buffer);
	    vwlist[i].ti.checkable = true;
	    vwlist[i].ti.checked = bdf==mv->bdf;
	    vwlist[i].ti.userdata = bdf;
	    vwlist[i].invoke = MVMenuShowBitmap;
	    vwlist[i].ti.fg = vwlist[i].ti.bg = COLOR_DEFAULT;
	}
    }
    GMenuItemArrayFree(mi->sub);
    mi->sub = GMenuItem2ArrayCopy(vwlist,NULL);
}

static void mtlistcheck(GWindow gw,struct gmenuitem *mi,GEvent *e) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);

    for ( mi = mi->sub; mi->ti.text!=NULL || mi->ti.line ; ++mi ) {
	switch ( mi->mid ) {
	  case MID_VKernClass:
	  case MID_VKernFromHKern:
	    mi->ti.disabled = !mv->sf->hasvmetrics;
	  break;
	}
    }
}

static GMenuItem2 mblist[] = {
    { { (unichar_t *) N_("_File"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'F' }, NULL, fllist, fllistcheck },
    { { (unichar_t *) N_("_Edit"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'E' }, NULL, edlist, edlistcheck },
    { { (unichar_t *) N_("E_lement"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'l' }, NULL, ellist, ellistcheck },
    { { (unichar_t *) N_("_View"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'V' }, NULL, vwlist, vwlistcheck },
    { { (unichar_t *) N_("_Metrics"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'M' }, NULL, mtlist, mtlistcheck },
    { { (unichar_t *) N_("_Window"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'W' }, NULL, wnmenu, MVWindowMenuBuild, NULL },
    { { (unichar_t *) N_("_Help"), NULL, COLOR_DEFAULT, COLOR_DEFAULT, NULL, NULL, 0, 1, 0, 0, 0, 0, 1, 1, 0, 'H' }, NULL, helplist, NULL },
    { NULL }
};

static void MVResize(MetricsView *mv) {
    GRect pos, wsize;
    int i;
    int size;

    GDrawGetSize(mv->gw,&wsize);
    if ( wsize.height < mv->topend+20 + mv->height-mv->displayend ||
	    wsize.width < 30 ) {
	int width= wsize.width < 30 ? 30 : wsize.width;
	int height;

	if ( wsize.height < mv->topend+20 + mv->height-mv->displayend )
	    height = mv->topend+20 + mv->height-mv->displayend;
	else
	    height = wsize.height;
	GDrawResize(mv->gw,width,height);
return;
    }

    mv->width = wsize.width;
    mv->displayend = wsize.height - (mv->height-mv->displayend);
    mv->height = wsize.height;

    mv_width = wsize.width; mv_height = wsize.height;
    SavePrefs(true);

    pos.width = wsize.width;
    pos.height = mv->sbh;
    pos.y = wsize.height - pos.height; pos.x = 0;
    GGadgetResize(mv->hsb,pos.width,pos.height);
    GGadgetMove(mv->hsb,pos.x,pos.y);

    mv->dwidth = mv->width - mv->sbh;
    GGadgetResize(mv->vsb,mv->sbh,mv->displayend-mv->topend);
    GGadgetMove(mv->vsb,wsize.width-mv->sbh,mv->topend);

    GGadgetResize(mv->features,mv->xstart,mv->displayend - mv->topend);

    size = (mv->displayend - mv->topend - 4);
    if ( mv->dwidth-20<size )
	size = mv->dwidth-20;
    if ( mv->pixelsize_set_by_window ) {
	mv->ptsize = mv->pixelsize = mv_scales[mv->scale_index]*size;
	mv->dpi = 72;
	if ( mv->bdf==NULL ) {
	    BDFFontFree(mv->show);
	    mv->show = SplineFontPieceMeal(mv->sf,mv->layer,mv->ptsize,mv->dpi,
		    mv->antialias?(pf_antialias|pf_ft_recontext):pf_ft_recontext,NULL);
	}
    }

    for ( i=0; i<mv->max; ++i ) if ( mv->perchar[i].width!=NULL ) {
	GGadgetMove(mv->perchar[i].name,mv->perchar[i].mx,mv->displayend+2);
	GGadgetMove(mv->perchar[i].width,mv->perchar[i].mx,mv->displayend+2+mv->fh+4);
	GGadgetMove(mv->perchar[i].lbearing,mv->perchar[i].mx,mv->displayend+2+2*(mv->fh+4));
	GGadgetMove(mv->perchar[i].rbearing,mv->perchar[i].mx,mv->displayend+2+3*(mv->fh+4));
	if ( mv->perchar[i].kern!=NULL )
	    GGadgetMove(mv->perchar[i].kern,mv->perchar[i].mx-mv->perchar[i].mwidth/2,mv->displayend+2+4*(mv->fh+4));
    }
    GGadgetMove(mv->namelab,2,mv->displayend+2);
    GGadgetMove(mv->widthlab,2,mv->displayend+2+mv->fh+4);
    GGadgetMove(mv->lbearinglab,2,mv->displayend+2+2*(mv->fh+4));
    GGadgetMove(mv->rbearinglab,2,mv->displayend+2+3*(mv->fh+4));
    GGadgetMove(mv->kernlab,2,mv->displayend+2+4*(mv->fh+4));

    mv->vwidth = mv->dwidth-mv->xstart;
    mv->vheight = mv->displayend-mv->topend-2;
    GDrawResize(mv->v,mv->vwidth, mv->vheight);
    MVRemetric(mv);
    GDrawRequestExpose(mv->gw,NULL,true);
    GDrawRequestExpose(mv->v,NULL,true);
}

static void MVChar(MetricsView *mv,GEvent *event) {
    if ( event->u.chr.keysym=='s' &&
	    (event->u.chr.state&ksm_control) &&
	    (event->u.chr.state&ksm_meta) )
	MenuSaveAll(NULL,NULL,NULL);
    else if ( event->u.chr.keysym=='I' &&
	    (event->u.chr.state&ksm_shift) &&
	    (event->u.chr.state&ksm_meta) )
	MVMenuCharInfo(mv->gw,NULL,NULL);
    else if ( event->u.chr.keysym == GK_Help ) {
	MenuHelp(NULL,NULL,NULL);	/* Menu does F1 */
    } else if ( event->u.chr.keysym == GK_Up || event->u.chr.keysym==GK_KP_Up ||
	    event->u.chr.keysym == GK_Down || event->u.chr.keysym==GK_KP_Down ) {
	int dir = ( event->u.chr.keysym == GK_Up || event->u.chr.keysym==GK_KP_Up ) ? -1 : 1;
	if ( mv->word_index!=-1 ) {
	    int32 len;
	    GTextInfo **ti = GGadgetGetList(mv->text,&len);
	    /* We subtract 3 because: There are two lines saying "load * list" */
	    /*  and then a line with a rule on it which we don't want access to */
	    if ( mv->word_index+dir >=0 && mv->word_index+dir<len-3 ) {
		const unichar_t *tit;
		mv->word_index += dir;
		GGadgetSelectOneListItem(mv->text,mv->word_index);
		tit = _GGadgetGetTitle(mv->text);
		if ( tit!=NULL && tit[0]==0x200b )
		    MVFigureGlyphNames(mv,tit+1);
		else
		    MVTextChanged(mv);
		ti = NULL;
	    }
	}
    }
}

static int hitsbit(BDFChar *bc, int x, int y) {
    if ( bc->byte_data )
return( bc->bitmap[y*bc->bytes_per_line+x] );
    else
return( bc->bitmap[y*bc->bytes_per_line+(x>>3)]&(1<<(7-(x&7))) );
}

static void _MVSubVMouse(MetricsView *mv,GEvent *event) {
    int i, x, y, j, within, xbase;
    SplineChar *sc;
    int diff;
    int onwidth, onkern;
    SplineFont *sf = mv->sf;
    double iscale = mv->pixelsize_set_by_window ? 1.0 : mv_scales[mv->scale_index];
    double scale = iscale*mv->pixelsize/(double) (sf->ascent+sf->descent);
    int as = rint(sf->ascent*scale);

    xbase = mv->vwidth/2;
    within = -1;
    for ( i=0; i<mv->glyphcnt; ++i ) {
	y = mv->perchar[i].dy + mv->perchar[i].yoff;
	x = xbase - mv->pixelsize*iscale/2 - mv->perchar[i].xoff;
	if ( mv->bdf==NULL ) {
	    BDFChar *bdfc = BDFPieceMealCheck(mv->show,mv->glyphs[i].sc->orig_pos);
	    if ( event->u.mouse.x >= x+bdfc->xmin &&
		event->u.mouse.x <= x+bdfc->xmax &&
		event->u.mouse.y <= (y+as)-bdfc->ymin &&
		event->u.mouse.y >= (y+as)-bdfc->ymax &&
		hitsbit(bdfc,rint(iscale*(event->u.mouse.x-x-bdfc->xmin)),
			rint(iscale*(event->u.mouse.y-(y+as-bdfc->ymax)))) )
    break;
	}
	y += -mv->perchar[i].yoff;
	if ( event->u.mouse.y >= y && event->u.mouse.y < y+mv->perchar[i].dheight+ mv->perchar[i].kernafter )
	    within = i;
    }
    if ( i==mv->glyphcnt )
	sc = NULL;
    else
	sc = mv->glyphs[i].sc;

    diff = event->u.mouse.y-mv->pressed_y;
    onwidth = onkern = false;
    if ( sc==NULL ) {
	if ( mv->type == mv_kernonly ) {
	    if ( within!=-1 && within+1<mv->glyphcnt &&
		    event->u.mouse.y>mv->perchar[within+1].dy-3 ) {
		onkern = true;			/* subsequent char */
		++within;
	    } else if ( within>0 &&
		    event->u.mouse.y<mv->perchar[within].dy+3 )
		onkern = true;
	} else if ( mv->type == mv_widthonly ) {
	    if ( within!=-1 && within+1<mv->glyphcnt &&
		    event->u.mouse.y>mv->perchar[within+1].dy-3 )
		onwidth = true;			/* subsequent char */
	    else if ( within>=0 &&
		    event->u.mouse.y>mv->perchar[within].dy+mv->perchar[within].dheight+mv->perchar[within].kernafter-3 ) {
		onwidth = true;
	    }
	} else {
	    if ( within>0 && mv->perchar[within-1].selected &&
		    event->u.mouse.y<mv->perchar[within].dy+3 )
		onwidth = true;		/* previous char */
	    else if ( within!=-1 && within+1<mv->glyphcnt &&
		    mv->perchar[within+1].selected &&
		    event->u.mouse.y>mv->perchar[within+1].dy-3 ) {
		onkern = true;			/* subsequent char */
		++within;
	    } else if ( within>0 && mv->perchar[within].selected &&
		    event->u.mouse.y<mv->perchar[within].dy+3 )
		onkern = true;
	    else if ( within>=0 &&
		    event->u.mouse.y>mv->perchar[within].dy+mv->perchar[within].dheight+mv->perchar[within].kernafter-3 ) {
		onwidth = true;
	    }
	}
    }

    if ( event->type != et_mousemove || !mv->pressed ) {
	int ct = -1;
	if ( mv->bdf!=NULL ||
		( mv->type==mv_kernonly && !onkern ) ||
		( mv->type==mv_widthonly && !onwidth )) {
	    if ( mv->cursor!=ct_mypointer )
		ct = ct_mypointer;
	} else if ( sc!=NULL ) {
	    if ( mv->cursor!=ct_lbearing )
		ct = ct_lbearing;
	} else if ( onwidth ) {
	    if ( mv->cursor!=ct_rbearing )
		ct = ct_rbearing;
	} else if ( onkern ) {
	    if ( mv->cursor!=ct_kerning )
		ct = ct_kerning;
	} else {
	    if ( mv->cursor!=ct_mypointer )
		ct = ct_mypointer;
	}
	if ( ct!=-1 ) {
	    GDrawSetCursor(mv->gw,ct);
	    mv->cursor = ct;
	}
    }

    if ( event->type == et_mousemove && !mv->pressed ) {
	if ( sc==NULL && within!=-1 )
	    sc = mv->glyphs[within].sc;
	if ( sc!=NULL )
	    SCPreparePopup(mv->gw,sc,mv->fv->b.map->remap,mv->fv->b.map->backmap[sc->orig_pos],sc->unicodeenc);
/* Don't allow any editing when displaying a bitmap font */
    } else if ( event->type == et_mousedown && mv->bdf==NULL ) {
	CVPaletteDeactivate();
	if ( sc!=NULL ) {
	    for ( j=0; j<mv->glyphcnt; ++j )
		if ( j!=i && mv->perchar[j].selected )
		    MVDeselectChar(mv,j);
	    MVSelectChar(mv,i);
	    GWindowClearFocusGadgetOfWindow(mv->gw);
	    mv->pressed = true;
	} else if ( within!=-1 ) {
	    mv->pressedwidth = onwidth;
	    mv->pressedkern = onkern;
	    if ( mv->pressedwidth || mv->pressedkern ) {
		mv->pressed = true;
		if ( !mv->perchar[within].selected ) {
		    MVDoSelect(mv,within);
		}
	    }
#if 0
	} else if ( event->u.mouse.y<mv->perchar[mv->glyphcnt-1].dy+
				    mv->perchar[mv->glyphcnt-1].dheight+
			            mv->perchar[mv->glyphcnt-1].kernafter+3 &&
			            mv->type!=mv_kernonly ) {
	    mv->pressed = mv->pressedwidth = true;
	    GDrawSetCursor(mv->gw,ct_rbearing);
	    mv->cursor = ct_rbearing;
	    if ( !mv->perchar[mv->glyphcnt-1].selected )
		    MVDoSelect(mv,mv->glyphcnt-1);
#endif
	}
	mv->pressed_y = event->u.mouse.y;
    } else if ( event->type == et_mousemove && mv->pressed ) {
	for ( i=0; i<mv->glyphcnt && !mv->perchar[i].selected; ++i );
	if ( mv->pressedwidth ) {
	    int ow = mv->perchar[i].dwidth;
	    mv->perchar[i].dwidth = rint(mv->glyphs[i].sc->vwidth*scale) + diff;
	    if ( ow!=mv->perchar[i].dwidth ) {
		for ( j=i+1; j<mv->glyphcnt; ++j )
		    mv->perchar[j].dy = mv->perchar[j-1].dy+mv->perchar[j-1].dheight+
			    mv->perchar[j-1].kernafter;
		GDrawRequestExpose(mv->v,NULL,false);
	    }
	} else if ( mv->pressedkern ) {
	    int ow = mv->perchar[i-1].kernafter;
	    KernPair *kp;
	    int kpoff;
	    KernClass *kc;
	    kp = mv->glyphs[i-1].kp;
	    if ( kp!=NULL )
		kpoff = kp->off;
	    else if ((kc=mv->glyphs[i-1].kc)!=NULL )
		kpoff = kc->offsets[mv->glyphs[i-1].kc_index];
	    else
		kpoff = 0;
	    kpoff = kpoff * mv->pixelsize*iscale /
			(mv->sf->descent+mv->sf->ascent);
	    mv->perchar[i-1].kernafter = kpoff + diff;
	    if ( ow!=mv->perchar[i-1].kernafter ) {
		for ( j=i; j<mv->glyphcnt; ++j )
		    mv->perchar[j].dy = mv->perchar[j-1].dy+mv->perchar[j-1].dheight+
			    mv->perchar[j-1].kernafter;
		GDrawRequestExpose(mv->v,NULL,false);
	    }
	} else if ( mv->type!=mv_kernonly ) {
	    int olda = mv->activeoff;
	    BDFChar *bdfc = BDFPieceMealCheck(mv->show,mv->glyphs[i].sc->orig_pos);
	    mv->activeoff = diff;
	    MVRedrawI(mv,i,bdfc->xmin+olda,bdfc->xmax+olda);
	}
    } else if ( event->type == et_mouseup && event->u.mouse.clicks>1 &&
	    (within!=-1 || sc!=NULL)) {
	mv->pressed = false; mv->activeoff = 0;
	mv->pressedwidth = mv->pressedkern = false;
	if ( within==-1 ) within = i;
	if ( mv->bdf==NULL )
	    CharViewCreate(mv->glyphs[within].sc,mv->fv,-1);
	else
	    BitmapViewCreate(mv->bdf->glyphs[mv->glyphs[within].sc->orig_pos],mv->bdf,mv->fv,-1);
	if ( mv->showgrid==mv_hidemovinggrid )
	    GDrawRequestExpose(mv->v,NULL,false);
    } else if ( event->type == et_mouseup && mv->pressed ) {
	for ( i=0; i<mv->glyphcnt && !mv->perchar[i].selected; ++i );
	mv->pressed = false;
	mv->activeoff = 0;
	sc = mv->glyphs[i].sc;
	if ( mv->pressedwidth ) {
	    mv->pressedwidth = false;
	    diff = diff*(mv->sf->ascent+mv->sf->descent)/(mv->pixelsize*iscale);
	    if ( diff!=0 ) {
		SCPreserveWidth(sc);
		sc->vwidth += diff;
		SCCharChangedUpdate(sc,ly_none);
		for ( ; i<mv->glyphcnt; ++i )
		    mv->perchar[i].dy = mv->perchar[i-1].dy+mv->perchar[i-1].dheight +
			    mv->perchar[i-1].kernafter ;
		GDrawRequestExpose(mv->v,NULL,false);
	    } else if ( mv->showgrid==mv_hidemovinggrid )
		GDrawRequestExpose(mv->v,NULL,false);
	} else if ( mv->pressedkern ) {
	    mv->pressedkern = false;
	    diff = diff*(mv->sf->ascent+mv->sf->descent)/(mv->pixelsize*iscale);
	    if ( diff!=0 )
		MV_ChangeKerning(mv, i, diff, true);
	    MVRefreshValues(mv,i-1);
	} else if ( mv->type!=mv_kernonly ) {
	    real transform[6];
	    DBounds bb;
	    SplineCharFindBounds(sc,&bb);
	    transform[0] = transform[3] = 1.0;
	    transform[1] = transform[2] = transform[4] = 0;
	    transform[5] = -diff*
		    (mv->sf->ascent+mv->sf->descent)/(mv->pixelsize*iscale);
	    if ( transform[5]!=0 )
		FVTrans( (FontViewBase *)mv->fv,sc,transform,NULL,false);
	}
	mv->pressedwidth = false;
	mv->pressedkern = false;
    } else if ( event->type == et_mouseup && mv->bdf!=NULL && within!=-1 ) {
	for ( j=0; j<mv->glyphcnt; ++j )
	    if ( j!=within && mv->perchar[j].selected )
		MVDeselectChar(mv,j);
	MVSelectChar(mv,within);
	if ( mv->showgrid==mv_hidemovinggrid )
	    GDrawRequestExpose(mv->v,NULL,false);
    }
}

static void MVSubMouse(MetricsView *mv,GEvent *event) {
    int i, x, y, j, within, ybase;
    SplineChar *sc;
    int diff;
    int onwidth, onkern;
    BDFChar *bdfc;
    double iscale = mv->pixelsize_set_by_window ? 1.0 : mv_scales[mv->scale_index];

    GGadgetEndPopup();

    if ( event->type==et_mouseup ) {
	event->type = et_mousemove;
	MVSubMouse(mv,event);
	event->u.mouse.x -= mv->xoff;
	event->u.mouse.y -= mv->yoff;
	event->type = et_mouseup;
    }

    event->u.mouse.x += mv->xoff;
    event->u.mouse.y += mv->yoff;
    if ( mv->vertical ) {
	_MVSubVMouse(mv,event);
return;
    }

    ybase = mv->ybaseline - mv->yoff;
    within = -1;
    for ( i=0; i<mv->glyphcnt; ++i ) {
	x = mv->perchar[i].dx + mv->perchar[i].xoff;
	if ( mv->right_to_left )
	    x = mv->vwidth - x - mv->perchar[i].dwidth - mv->perchar[i].kernafter;
	y = ybase - mv->perchar[i].yoff;
	if ( mv->bdf==NULL ) {
	    bdfc = BDFPieceMealCheck(mv->show,mv->glyphs[i].sc->orig_pos);
	    if ( event->u.mouse.x >= x+bdfc->xmin &&
		event->u.mouse.x <= x+bdfc->xmax &&
		event->u.mouse.y <= y-bdfc->ymin &&
		event->u.mouse.y >= y-bdfc->ymax &&
		hitsbit(bdfc,rint(iscale*(event->u.mouse.x-x-bdfc->xmin)),
			rint(iscale*(bdfc->ymax-(y-event->u.mouse.y)))) )
    break;
	}
	x += mv->right_to_left ? mv->perchar[i].xoff : -mv->perchar[i].xoff;
	if ( event->u.mouse.x >= x && event->u.mouse.x < x+mv->perchar[i].dwidth+ mv->perchar[i].kernafter )
	    within = i;
    }
    if ( i==mv->glyphcnt )
	sc = NULL;
    else
	sc = mv->glyphs[i].sc;

    diff = event->u.mouse.x-mv->pressed_x;
    /*if ( mv->right_to_left ) diff = -diff;*/
    onwidth = onkern = false;
    if ( sc==NULL ) {
	if ( !mv->right_to_left ) {
	    if ( mv->type == mv_kernonly ) {
		if ( within>=0 && within+1<mv->glyphcnt &&
			event->u.mouse.x>mv->perchar[within+1].dx-3 ) {
		    onkern = true;			/* subsequent char */
		    ++within;
		} else if ( within>0 &&
			event->u.mouse.x<mv->perchar[within].dx+3 )
		    onkern = true;
	    } else if ( mv->type == mv_widthonly ) {
		if ( within>=0 && within+1<mv->glyphcnt &&
			event->u.mouse.x>mv->perchar[within+1].dx-3 )
		    onwidth = true;			/* subsequent char */
		else if ( within>=0 &&
			event->u.mouse.x>mv->perchar[within].dx+mv->perchar[within].dwidth+mv->perchar[within].kernafter-3 ) {
		    onwidth = true;
		}
	    } else {
		if ( within>0 && mv->perchar[within-1].selected &&
			event->u.mouse.x<mv->perchar[within].dx+3 )
		    onwidth = true;		/* previous char */
		else if ( within!=-1 && within+1<mv->glyphcnt &&
			mv->perchar[within+1].selected &&
			event->u.mouse.x>mv->perchar[within+1].dx-3 ) {
		    onkern = true;			/* subsequent char */
		    ++within;
		} else if ( within>0 && mv->perchar[within].selected &&
			event->u.mouse.x<mv->perchar[within].dx+3 )
		    onkern = true;
		else if ( within>=0 &&
			event->u.mouse.x>mv->perchar[within].dx+mv->perchar[within].dwidth+mv->perchar[within].kernafter-3 ) {
		    onwidth = true;
		}
	    }
	} else {
	    if ( mv->type == mv_kernonly ) {
		if ( within>=0 && within+1<mv->glyphcnt &&
			event->u.mouse.x<mv->dwidth-(mv->perchar[within+1].dx-3) ) {
		    onkern = true;			/* subsequent char */
		    ++within;
		} else if ( within>0 &&
			event->u.mouse.x>mv->dwidth-(mv->perchar[within].dx+3) )
		    onkern = true;
	    } else if ( mv->type == mv_widthonly ) {
		if ( within>=0 && within+1<mv->glyphcnt &&
			event->u.mouse.x<mv->dwidth-(mv->perchar[within+1].dx-3) )
		    onwidth = true;			/* subsequent char */
		else if ( within>=0 &&
			event->u.mouse.x<mv->dwidth-(mv->perchar[within].dx+mv->perchar[within].dwidth+mv->perchar[within].kernafter-3) ) {
		    onwidth = true;
		}
	    } else {
		if ( within>0 && mv->perchar[within-1].selected &&
			event->u.mouse.x>mv->dwidth-(mv->perchar[within].dx+3) )
		    onwidth = true;		/* previous char */
		else if ( within!=-1 && within+1<mv->glyphcnt &&
			mv->perchar[within+1].selected &&
			event->u.mouse.x<mv->dwidth-(mv->perchar[within+1].dx-3) ) {
		    onkern = true;			/* subsequent char */
		    ++within;
		} else if ( within>0 && mv->perchar[within].selected &&
			event->u.mouse.x>mv->dwidth-(mv->perchar[within].dx+3) )
		    onkern = true;
		else if ( within>=0 &&
			event->u.mouse.x<mv->dwidth-(mv->perchar[within].dx+mv->perchar[within].dwidth+mv->perchar[within].kernafter-3) ) {
		    onwidth = true;
		}
	    }
	}
    }

    if ( event->type != et_mousemove || !mv->pressed ) {
	int ct = -1;
	if ( mv->bdf!=NULL ||
		( mv->type==mv_kernonly && !onkern ) ||
		( mv->type==mv_widthonly && !onwidth )) {
	    if ( mv->cursor!=ct_mypointer )
		ct = ct_mypointer;
	} else if ( sc!=NULL ) {
	    if ( mv->cursor!=ct_lbearing )
		ct = ct_lbearing;
	} else if ( onwidth ) {
	    if ( mv->cursor!=ct_rbearing )
		ct = ct_rbearing;
	} else if ( onkern ) {
	    if ( mv->cursor!=ct_kerning )
		ct = ct_kerning;
	} else {
	    if ( mv->cursor!=ct_mypointer )
		ct = ct_mypointer;
	}
	if ( ct!=-1 ) {
	    GDrawSetCursor(mv->gw,ct);
	    mv->cursor = ct;
	}
    }

    if ( event->type == et_mousemove && !mv->pressed ) {
	if ( sc==NULL && within!=-1 )
	    sc = mv->glyphs[within].sc;
	if ( sc!=NULL )
	    SCPreparePopup(mv->gw,sc,mv->fv->b.map->remap,mv->fv->b.map->backmap[sc->orig_pos],sc->unicodeenc);
/* Don't allow any editing when displaying a bitmap font */
    } else if ( event->type == et_mousedown && mv->bdf==NULL ) {
	CVPaletteDeactivate();
	if ( sc!=NULL ) {
	    for ( j=0; j<mv->glyphcnt; ++j )
		if ( j!=i && mv->perchar[j].selected )
		    MVDeselectChar(mv,j);
	    MVSelectChar(mv,i);
	    GWindowClearFocusGadgetOfWindow(mv->gw);
	    mv->pressed = true;
	} else if ( within!=-1 ) {
	    mv->pressedwidth = onwidth;
	    mv->pressedkern = onkern;
	    if ( mv->pressedwidth || mv->pressedkern ) {
		mv->pressed = true;
		if ( !mv->perchar[within].selected ) {
		    MVDoSelect(mv,within);
		}
	    }
#if 0
	} else if ( !mv->right_to_left && mv->glyphcnt>=1 &&
		event->u.mouse.x<mv->perchar[mv->glyphcnt-1].dx+mv->perchar[mv->glyphcnt-1].dwidth+mv->perchar[mv->glyphcnt-1].kernafter+3 &&
		mv->type!=mv_kernonly ) {
	    mv->pressed = mv->pressedwidth = true;
	    GDrawSetCursor(mv->gw,ct_rbearing);
	    mv->cursor = ct_rbearing;
	    if ( !mv->perchar[mv->glyphcnt-1].selected )
		    MVDoSelect(mv,mv->glyphcnt-1);
	} else if ( mv->right_to_left && mv->glyphcnt>=1 &&
		event->u.mouse.x>mv->dwidth - (mv->perchar[mv->glyphcnt-1].dx+mv->perchar[mv->glyphcnt-1].dwidth+mv->perchar[mv->glyphcnt-1].kernafter+3) &&
		mv->type!=mv_kernonly ) {
	    mv->pressed = mv->pressedwidth = true;
	    GDrawSetCursor(mv->gw,ct_rbearing);
	    mv->cursor = ct_rbearing;
	    if ( !mv->perchar[mv->glyphcnt-1].selected )
		    MVDoSelect(mv,mv->glyphcnt-1);
#endif
	}
	mv->pressed_x = event->u.mouse.x;
    } else if ( event->type == et_mousemove && mv->pressed ) {
	for ( i=0; i<mv->glyphcnt && !mv->perchar[i].selected; ++i );
	if ( mv->pressedwidth ) {
	    int ow = mv->perchar[i].dwidth;
	    if ( mv->right_to_left ) diff = -diff;
	    bdfc = BDFPieceMealCheck(mv->show,mv->glyphs[i].sc->orig_pos);
	    mv->perchar[i].dwidth = bdfc->width + diff;
	    if ( ow!=mv->perchar[i].dwidth ) {
		for ( j=i+1; j<mv->glyphcnt; ++j )
		    mv->perchar[j].dx = mv->perchar[j-1].dx+mv->perchar[j-1].dwidth+ mv->perchar[j-1].kernafter;
		GDrawRequestExpose(mv->v,NULL,false);
	    }
	} else if ( mv->pressedkern ) {
	    int ow = mv->perchar[i-1].kernafter;
	    KernPair *kp;
	    int kpoff;
	    KernClass *kc;
	    int index;
	    for ( kp = mv->glyphs[i-1].sc->kerns; kp!=NULL && kp->sc!=mv->glyphs[i].sc; kp = kp->next );
	    if ( kp!=NULL )
		kpoff = kp->off;
	    else if ((kc=SFFindKernClass(mv->sf,mv->glyphs[i-1].sc,mv->glyphs[i].sc,&index,false))!=NULL )
		kpoff = kc->offsets[index];
	    else
		kpoff = 0;
	    kpoff = kpoff * mv->pixelsize*iscale /
			(mv->sf->descent+mv->sf->ascent);
	    if ( mv->right_to_left ) diff = -diff;
	    mv->perchar[i-1].kernafter = kpoff + diff;
	    if ( ow!=mv->perchar[i-1].kernafter ) {
		for ( j=i; j<mv->glyphcnt; ++j )
		    mv->perchar[j].dx = mv->perchar[j-1].dx+mv->perchar[j-1].dwidth+ mv->perchar[j-1].kernafter;
		GDrawRequestExpose(mv->v,NULL,false);
	    }
	} else if ( mv->type!=mv_kernonly ) {
	    int olda = mv->activeoff;
	    bdfc = BDFPieceMealCheck(mv->show,mv->glyphs[i].sc->orig_pos);
	    mv->activeoff = diff;
	    MVRedrawI(mv,i,bdfc->xmin+olda,bdfc->xmax+olda);
	}
    } else if ( event->type == et_mouseup && event->u.mouse.clicks>1 &&
	    (within!=-1 || sc!=NULL)) {
	mv->pressed = false; mv->activeoff = 0;
	mv->pressedwidth = mv->pressedkern = false;
	if ( within==-1 ) within = i;
	if ( mv->bdf==NULL )
	    CharViewCreate(mv->glyphs[within].sc,mv->fv,-1);
	else
	    BitmapViewCreate(mv->bdf->glyphs[mv->glyphs[within].sc->orig_pos],mv->bdf,mv->fv,-1);
	if ( mv->showgrid==mv_hidemovinggrid )
	    GDrawRequestExpose(mv->v,NULL,false);
    } else if ( event->type == et_mouseup && mv->pressed ) {
	for ( i=0; i<mv->glyphcnt && !mv->perchar[i].selected; ++i );
	mv->pressed = false;
	mv->activeoff = 0;
	sc = mv->glyphs[i].sc;
	if ( mv->pressedwidth ) {
	    mv->pressedwidth = false;
	    if ( mv->right_to_left ) diff = -diff;
	    diff = diff*(mv->sf->ascent+mv->sf->descent)/(mv->pixelsize*iscale);
	    if ( diff!=0 ) {
		SCPreserveWidth(sc);
		SCSynchronizeWidth(sc,sc->width+diff,sc->width,NULL);
		SCCharChangedUpdate(sc,ly_none);
	    }
	} else if ( mv->pressedkern ) {
	    mv->pressedkern = false;
	    diff = diff*(mv->sf->ascent+mv->sf->descent)/(mv->pixelsize*iscale);
	    if ( diff!=0 ) {
		if ( mv->right_to_left ) diff = -diff;
		MV_ChangeKerning(mv, i, diff, true);
		MVRefreshValues(mv,i-1);
	    }
	} else if ( mv->type!=mv_kernonly ) {
	    real transform[6];
	    transform[0] = transform[3] = 1.0;
	    transform[1] = transform[2] = transform[5] = 0;
	    transform[4] = diff*
		    (mv->sf->ascent+mv->sf->descent)/(mv->pixelsize*iscale);
	    if ( transform[4]!=0 )
		FVTrans( (FontViewBase *)mv->fv,sc,transform,NULL,false);
	}
	mv->pressedwidth = false;
	mv->pressedkern = false;
	if ( mv->showgrid==mv_hidemovinggrid )
	    GDrawRequestExpose(mv->v,NULL,false);
    } else if ( event->type == et_mouseup && mv->bdf!=NULL && within!=-1 ) {
	for ( j=0; j<mv->glyphcnt; ++j )
	    if ( j!=within && mv->perchar[j].selected )
		MVDeselectChar(mv,j);
	MVSelectChar(mv,within);
	if ( mv->showgrid==mv_hidemovinggrid )
	    GDrawRequestExpose(mv->v,NULL,false);
    }
}

static void MVMouse(MetricsView *mv,GEvent *event) {
    int i;

    if ( event->u.mouse.y< mv->topend || event->u.mouse.y >= mv->displayend ) {
	if ( event->u.mouse.y >= mv->displayend &&
		event->u.mouse.y<mv->height-mv->sbh ) {
	    event->u.mouse.x += (mv->coff*mv->mwidth);
	    for ( i=0; i<mv->glyphcnt; ++i ) {
		if ( event->u.mouse.x >= mv->perchar[i].mx &&
			event->u.mouse.x < mv->perchar[i].mx+mv->perchar[i].mwidth )
	    break;
	    }
	    if ( i<mv->glyphcnt )
		SCPreparePopup(mv->gw,mv->glyphs[i].sc,mv->fv->b.map->remap,
			mv->fv->b.map->backmap[mv->glyphs[i].sc->orig_pos],
			mv->glyphs[i].sc->unicodeenc);
	}
	if ( mv->cursor!=ct_mypointer ) {
	    GDrawSetCursor(mv->gw,ct_mypointer);
	    mv->cursor = ct_mypointer;
	}
return;
    }
}

static void MVDrop(MetricsView *mv,GEvent *event) {
    int x,ex = event->u.drag_drop.x + mv->xoff;
    int y,ey = event->u.drag_drop.y + mv->yoff;
    int within, i, cnt, ch;
    int32 len;
    char *cnames, *start, *pt;
    unichar_t *newtext;
    const unichar_t *oldtext;
    SplineChar **founds;
    /* We should get a list of character names. Add them before the character */
    /*  on which they are dropped */

    if ( !GDrawSelectionHasType(mv->gw,sn_drag_and_drop,"STRING"))
return;
    cnames = GDrawRequestSelection(mv->gw,sn_drag_and_drop,"STRING",&len);
    if ( cnames==NULL )
return;

    within = mv->glyphcnt;
    if ( !mv->vertical ) {
	for ( i=0; i<mv->glyphcnt; ++i ) {
	    x = mv->perchar[i].dx;
	    if ( mv->right_to_left )
		x = mv->dwidth - x - mv->perchar[i].dwidth - mv->perchar[i].kernafter ;
	    if ( ex >= x && ex < x+mv->perchar[i].dwidth+ mv->perchar[i].kernafter ) {
		within = i;
	break;
	    }
	}
    } else {
	for ( i=0; i<mv->glyphcnt; ++i ) {
	    y = mv->perchar[i].dy;
	    if ( ey >= y && ey < y+mv->perchar[i].dheight+
		    mv->perchar[i].kernafter ) {
		within = i;
	break;
	    }
	}
    }

    founds = galloc(len*sizeof(SplineChar *));	/* Will be a vast over-estimate */
    start = cnames;
    for ( i=0; *start; ) {
	while ( *start==' ' ) ++start;
	if ( *start=='\0' )
    break;
	for ( pt=start; *pt && *pt!=' '; ++pt );
	ch = *pt; *pt = '\0';
	if ( (founds[i]=SFGetChar(mv->sf,-1,start))!=NULL )
	    ++i;
	*pt = ch;
	start = pt;
    }
    cnt = i;
    free( cnames );
    if ( cnt==0 )
return;
    if ( within<mv->glyphcnt )
	within = mv->glyphs[within].orig_index;
    else
	within = mv->clen;

    if ( mv->clen+cnt+1>=mv->cmax ) {
	mv->cmax = mv->clen+cnt+10;
	mv->chars = grealloc(mv->chars,mv->cmax*sizeof(SplineChar *));
    }
    oldtext = _GGadgetGetTitle(mv->text);
    newtext = galloc((mv->clen+cnt+1)*sizeof(unichar_t));
    u_strcpy(newtext,oldtext);
    newtext[mv->clen+cnt]='\0';
    for ( i=mv->clen+cnt; i>=within+cnt; --i ) {
	newtext[i] = newtext[i-cnt];
	mv->chars[i] = mv->chars[i-cnt];
    }
    for ( i=within; i<within+cnt; ++i ) {
	mv->chars[i] = founds[i-within];
#ifdef UNICHAR_16
	newtext[i] = (founds[i-within]->unicodeenc>=0 && founds[i-within]->unicodeenc<0x10000)?
#else
	newtext[i] = founds[i-within]->unicodeenc>=0 ?
#endif
		founds[i-within]->unicodeenc : MVFakeUnicodeOfSc(mv,founds[i-within]);
    }
    mv->clen += cnt;
    MVRemetric(mv);
    free(founds);

    GGadgetSetTitle(mv->text,newtext);
    free(newtext);

    GDrawRequestExpose(mv->v,NULL,false);
}

static int mv_v_e_h(GWindow gw, GEvent *event) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);

    switch ( event->type ) {
      case et_expose:
	GDrawSetLineWidth(gw,0);
	MVSubExpose(mv,gw,event);
      break;
      case et_char:
	MVChar(mv,event);
      break;
      case et_mouseup: case et_mousemove: case et_mousedown:
	if (( event->type==et_mouseup || event->type==et_mousedown ) &&
		(event->u.mouse.button>=4 && event->u.mouse.button<=7) ) {
	    int ish = event->u.mouse.button>5;
	    if ( event->u.mouse.state&ksm_shift ) ish = !ish;
	    if ( event->u.mouse.state&ksm_control ) {	/* bind control to magnify/minify */
		if ( event->type==et_mousedown ) {
		    if ( event->u.mouse.button==4 || event->u.mouse.button==6 )
			_MVMenuScale(mv,MID_ZoomIn);
		    else
			_MVMenuScale(mv,MID_ZoomOut);
		}
	    } else if ( ish ) {		/* bind shift to horizontal scroll */
return( GGadgetDispatchEvent(mv->hsb,event));
	    } else {
return( GGadgetDispatchEvent(mv->vsb,event));
	    }
return( true );
	}
	if ( mv->gwgic!=NULL && event->type==et_mousedown)
	    GDrawSetGIC(mv->gw,mv->gwgic,0,20);
	MVSubMouse(mv,event);
      break;
      case et_drop:
	MVDrop(mv,event);
      break;
    }
return( true );
}

static int mv_e_h(GWindow gw, GEvent *event) {
    MetricsView *mv = (MetricsView *) GDrawGetUserData(gw);
    SplineFont *sf;

    switch ( event->type ) {
      case et_selclear:
	ClipboardClear();
      break;
      case et_expose:
	GDrawSetLineWidth(gw,0);
	MVExpose(mv,gw,event);
      break;
      case et_resize:
	if ( event->u.resize.sized )
	    MVResize(mv);
      break;
      case et_char:
	MVChar(mv,event);
      break;
      case et_mouseup: case et_mousemove: case et_mousedown:
	if (( event->type==et_mouseup || event->type==et_mousedown ) &&
		(event->u.mouse.button>=4 && event->u.mouse.button<=7) ) {
	    int ish = event->u.mouse.button>5;
	    if ( event->u.mouse.state&ksm_shift ) ish = !ish;
	    if ( event->u.mouse.state&ksm_control ) {	/* bind control to magnify/minify */
		if ( event->type==et_mousedown ) {
		    if ( event->u.mouse.button==4 || event->u.mouse.button==6 )
			_MVMenuScale(mv,MID_ZoomIn);
		    else
			_MVMenuScale(mv,MID_ZoomOut);
		}
	    } else if ( ish ) {	/* bind shift to horizontal scroll */
return( GGadgetDispatchEvent(mv->hsb,event));
	    } else {
return( GGadgetDispatchEvent(mv->vsb,event));
	    }
return( true );
	}
	if ( mv->gwgic!=NULL && event->type==et_mousedown)
	    GDrawSetGIC(mv->gw,mv->gwgic,0,20);
	MVMouse(mv,event);
      break;
      case et_drop:
	MVDrop(mv,event);
      break;
      case et_controlevent:
	switch ( event->u.control.subtype ) {
	  case et_scrollbarchange:
	    if ( event->u.control.g==mv->hsb )
		MVHScroll(mv,&event->u.control.u.sb);
	    else
		MVVScroll(mv,&event->u.control.u.sb);
	  break;
	}
      break;
      case et_close:
	MVMenuClose(gw,NULL,NULL);
      break;
      case et_destroy:
	sf = mv->sf;
	if ( sf->cidmaster ) sf = sf->cidmaster;
	if ( sf->metrics==mv )
	    sf->metrics = mv->next;
	else {
	    MetricsView *n;
	    for ( n=sf->metrics; n->next!=mv; n=n->next );
	    n->next = mv->next;
	}
	KCLD_MvDetach(sf->kcld,mv);
	MetricsViewFree(mv);
      break;
      case et_focus:
#if 0
	if ( event->u.focus.gained_focus )
	    CVPaletteDeactivate();
#endif
      break;
    }
return( true );
}

GTextInfo *SLOfFont(SplineFont *sf) {
    uint32 *scripttags, *langtags;
    int s, l, i, k, cnt;
    extern GTextInfo scripts[], languages[];
    GTextInfo *ret = NULL;
    char *sname, *lname, *temp;
    char sbuf[8], lbuf[8];

    LookupUIInit();
    scripttags = SFScriptsInLookups(sf,-1);
    if ( scripttags==NULL )
return( NULL );

    for ( k=0; k<2; ++k ) {
	cnt = 0;
	for ( s=0; scripttags[s]!=0; ++s ) {
	    if ( k ) {
		for ( i=0; scripts[i].text!=NULL; ++i )
		    if ( scripttags[s] == (intpt) (scripts[i].userdata))
		break;
		sname = (char *) (scripts[i].text);
		sbuf[0] = scripttags[s]>>24;
		sbuf[1] = scripttags[s]>>16;
		sbuf[2] = scripttags[s]>>8;
		sbuf[3] = scripttags[s];
		sbuf[4] = 0;
		if ( sname==NULL )
		    sname = sbuf;
	    }
	    langtags = SFLangsInScript(sf,-1,scripttags[s]);
	    /* This one can't be NULL */
	    for ( l=0; langtags[l]!=0; ++l ) {
		if ( k ) {
		    for ( i=0; languages[i].text!=NULL; ++i )
			if ( langtags[l] == (intpt) (languages[i].userdata))
		    break;
		    lname = (char *) (languages[i].text);
		    lbuf[0] = langtags[l]>>24;
		    lbuf[1] = langtags[l]>>16;
		    lbuf[2] = langtags[l]>>8;
		    lbuf[3] = langtags[l];
		    lbuf[4] = 0;
		    if ( lname==NULL )
			lname = lbuf;
		    temp = galloc(strlen(sname)+strlen(lname)+3);
		    strcpy(temp,sname); strcat(temp,"{"); strcat(temp,lname); strcat(temp,"}");
		    ret[cnt].text = (unichar_t *) temp;
		    ret[cnt].text_is_1byte = true;
		    temp = galloc(11);
		    strcpy(temp,sbuf); temp[4] = '{'; strcpy(temp+5,lbuf); temp[9]='}'; temp[10] = 0;
		    ret[cnt].userdata = temp;
		}
		++cnt;
	    }
	    free(langtags);
	}
	if ( !k )
	    ret = gcalloc((cnt+1),sizeof(GTextInfo));
    }
    free(scripttags);
return( ret );
}

#define metricsicon_width 16
#define metricsicon_height 16
static unsigned char metricsicon_bits[] = {
   0x04, 0x10, 0xf0, 0x03, 0x24, 0x12, 0x20, 0x00, 0x24, 0x10, 0xe0, 0x00,
   0x24, 0x10, 0x20, 0x00, 0x24, 0x10, 0x20, 0x00, 0x74, 0x10, 0x00, 0x00,
   0x55, 0x55, 0x00, 0x00, 0x04, 0x10, 0x00, 0x00};

static void MetricsViewInit(void ) {
    static int inited = false;

    if ( !inited ) {
	mv_text_init[2].text = (unichar_t *) _((char *) mv_text_init[2].text);
	mb2DoGetText(mblist);
	MVColInit();
    }
}

MetricsView *MetricsViewCreate(FontView *fv,SplineChar *sc,BDFFont *bdf) {
    GRect pos;
    GWindow gw;
    GWindowAttrs wattrs;
    GGadgetData gd;
    GRect gsize;
    MetricsView *mv = gcalloc(1,sizeof(MetricsView));
    FontRequest rq;
    static GWindow icon = NULL;
    extern int _GScrollBar_Width;
    char buf[120], *pt;
    GTextInfo label;
    int i,j,cnt;
    int as,ds,ld;
    static GFont *mvfont=NULL;
    SplineFont *master = fv->b.sf->cidmaster ? fv->b.sf->cidmaster : fv->b.sf;

    MetricsViewInit();

    if ( icon==NULL )
	icon = GDrawCreateBitmap(NULL,metricsicon_width,metricsicon_height,metricsicon_bits);

    mv->fv = fv;
    mv->sf = fv->b.sf;
    mv->bdf = bdf;
    mv->showgrid = mvshowgrid;
    mv->antialias = mv_antialias;
    mv->scale_index = SCALE_INDEX_NORMAL;
    mv->next = master->metrics;
    master->metrics = mv;
    mv->layer = fv->b.active_layer;
    mv->type = mv_type;
    mv->pixelsize_set_by_window = true;
    mv->dpi = 72;

    memset(&wattrs,0,sizeof(wattrs));
    wattrs.mask = wam_events|wam_cursor|wam_utf8_wtitle|wam_icon;
    wattrs.event_masks = ~(1<<et_charup);
    wattrs.cursor = ct_mypointer;
    MVWindowTitle(buf,sizeof(buf),mv);
    wattrs.utf8_window_title = buf;
    wattrs.icon = icon;
    pos.x = pos.y = 0;
    pos.width = mv_width;
    pos.height = mv_height;
    mv->gw = gw = GDrawCreateTopWindow(NULL,&pos,mv_e_h,mv,&wattrs);
    mv->width = pos.width; mv->height = pos.height;
    mv->gwgic = GDrawCreateInputContext(mv->gw,gic_root|gic_orlesser);
    GDrawSetGIC(gw,mv->gwgic,0,20);

    memset(&gd,0,sizeof(gd));
    gd.flags = gg_visible | gg_enabled;
    helplist[0].invoke = MVMenuContextualHelp;
    gd.u.menu2 = mblist;
    mv->mb = GMenu2BarCreate( gw, &gd, NULL);
    GGadgetGetSize(mv->mb,&gsize);
    mv->mbh = gsize.height;

    gd.pos.height = GDrawPointsToPixels(gw,_GScrollBar_Width);
    gd.pos.y = pos.height-gd.pos.height;
    gd.pos.x = 0; gd.pos.width = pos.width;
    gd.flags = gg_visible|gg_enabled|gg_pos_in_pixels;
    mv->hsb = GScrollBarCreate(gw,&gd,mv);
    GGadgetGetSize(mv->hsb,&gsize);
    mv->sbh = gsize.height;
    mv->dwidth = mv->width-mv->sbh;

    gd.pos.width = mv->sbh;
    gd.pos.y = 0; gd.pos.height = pos.height;	/* we'll fix these later */
    gd.pos.x = pos.width-gd.pos.width;
    gd.flags = gg_visible|gg_enabled|gg_pos_in_pixels|gg_sb_vert;
    mv->vsb = GScrollBarCreate(gw,&gd,mv);

    if ( mvfont==NULL ) {
	memset(&rq,0,sizeof(rq));
	rq.utf8_family_name = SANS_UI_FAMILIES;
	rq.point_size = -12;
	rq.weight = 400;
	mvfont = GDrawInstanciateFont(GDrawGetDisplayOfWindow(gw),&rq);
	mvfont = GResourceFindFont("MetricsView.Font",mvfont);
    }
    mv->font = mvfont;
    GDrawFontMetrics(mv->font,&as,&ds,&ld);
    mv->fh = as+ds; mv->as = as;

    pt = buf;
    mv->chars = gcalloc(mv->cmax=20,sizeof(SplineChar *));
    if ( sc!=NULL ) {
	mv->chars[mv->clen++] = sc;
    } else {
	EncMap *map = fv->b.map;
	for ( j=1; (j<=fv->sel_index || j<1) && mv->clen<15; ++j ) {
	    for ( i=0; i<map->enccount && mv->clen<15; ++i ) {
		int gid = map->map[i];
		if ( gid!=-1 && fv->b.selected[i]==j && fv->b.sf->glyphs[gid]!=NULL ) {
		    mv->chars[mv->clen++] = fv->b.sf->glyphs[gid];
		}
	    }
	}
    }
    mv->chars[mv->clen] = NULL;

    for ( cnt=0; cnt<mv->clen; ++cnt )
	pt = utf8_idpb(pt,
#ifdef UNICHAR_16
		mv->chars[cnt]->unicodeenc==-1 || mv->chars[cnt]->unicodeenc>=0x10000?
#else
		mv->chars[cnt]->unicodeenc==-1?
#endif
		MVFakeUnicodeOfSc(mv,mv->chars[cnt]): mv->chars[cnt]->unicodeenc);
    *pt = '\0';

    memset(&gd,0,sizeof(gd));
    memset(&label,0,sizeof(label));
    gd.pos.y = mv->mbh+2; gd.pos.x = 10;
    gd.pos.width = GDrawPointsToPixels(mv->gw,100);
    gd.label = &label;
    label.text = (unichar_t *) "DFLT{dflt}";
    label.text_is_1byte = true;
    gd.flags = gg_visible|gg_enabled|gg_pos_in_pixels;
    gd.u.list = mv->scriptlangs = SLOfFont(mv->sf);
    gd.handle_controlevent = MV_ScriptLangChanged;
    mv->script = GListFieldCreate(gw,&gd,mv);
    GGadgetGetSize(mv->script,&gsize);
    mv->topend = gsize.y + gsize.height + 2;

    gd.pos.x = gd.pos.x+gd.pos.width+10;
    gd.pos.width = GDrawPointsToPixels(mv->gw,200);
    gd.pos.height = gsize.height;
    label.text = (unichar_t *) buf;
    gd.flags = gg_visible|gg_enabled|gg_pos_in_pixels|gg_text_xim;
    gd.handle_controlevent = MV_TextChanged;
    gd.u.list = mv_text_init;
    mv->text = GListFieldCreate(gw,&gd,mv);

    gd.pos.x = gd.pos.x+gd.pos.width+10; --gd.pos.y;
    gd.pos.width += 30;
    gd.flags = gg_visible|gg_enabled|gg_pos_in_pixels;
    gd.handle_controlevent = MV_SubtableChanged;
    gd.label = NULL;
    gd.u.list = NULL;
    mv->subtable_list = GListButtonCreate(gw,&gd,mv);
    MVSetSubtables(master);

    gd.pos.y = mv->topend; gd.pos.x = 0;
    gd.flags = gg_visible|gg_enabled|gg_pos_in_pixels|gg_pos_use0|gg_list_multiplesel|gg_list_alphabetic;
    gd.pos.width = GDrawPointsToPixels(mv->gw,50);
    gd.handle_controlevent = MV_FeaturesChanged;
    mv->features = GListCreate(gw,&gd,mv);
    GListSetSBAlwaysVisible(mv->features,true);
    GListSetPopupCallback(mv->features,MV_FriendlyFeatures);
    mv->xstart = gd.pos.width;

    pos.x = mv->xstart; pos.width = mv->dwidth - mv->xstart;
    pos.y = mv->topend+2; pos.height = mv->displayend - mv->topend - 2;
    memset(&wattrs,0,sizeof(wattrs));
    wattrs.mask = wam_events|wam_backcol;
    wattrs.background_color = view_bgcol;
    wattrs.event_masks = -1;
    wattrs.cursor = ct_mypointer;
    mv->v = GWidgetCreateSubWindow(mv->gw,&pos,mv_v_e_h,mv,&wattrs);
    MVSetFeatures(mv);
    MVMakeLabels(mv);
    MVResize(mv);

    GDrawSetVisible(mv->v,true);
    GDrawSetVisible(gw,true);
    /*GWidgetHidePalettes();*/
return( mv );
}

void MetricsViewFree(MetricsView *mv) {

    if ( mv->scriptlangs!=NULL ) {
	int i;
	for ( i=0; mv->scriptlangs[i].text!=NULL ; ++i )
	    free(mv->scriptlangs[i].userdata );
	GTextInfoListFree(mv->scriptlangs);
    }
    BDFFontFree(mv->show);
    /* the fields will free themselves */
    free(mv->chars);
    free(mv->glyphs);
    free(mv->perchar);
    free(mv);
}

void MVRefreshAll(MetricsView *mv) {

    if ( mv!=NULL ) {
	MVRemetric(mv);
	GDrawRequestExpose(mv->v,NULL,false);
    }
}

/******************************************************************************/
static int MV_GlyphCnt(struct metricsview *mv) {
return( mv->glyphcnt );
}

static SplineChar *MV_Glyph(struct metricsview *mv,int i) {
    if ( i<0 || i>=mv->glyphcnt )
return( NULL );

return( mv->glyphs[i].sc );
}

static void MV_ReKernAll(struct splinefont *sf) {
    MetricsView *mv;

    for ( mv=sf->metrics; mv!=NULL; mv=mv->next )
	MVReKern(mv);
}

static void MV_ReFeatureAll(struct splinefont *sf) {
    MetricsView *mv;

    MVSetSubtables(sf);
    for ( mv=sf->metrics; mv!=NULL; mv=mv->next )
	MVSetFeatures(mv);
}

static void MV_CloseAll(struct splinefont *sf) {
    MetricsView *mv, *mvnext;
    for ( mv=sf->metrics; mv!=NULL; mv=mvnext ) {
	mvnext = mv->next;
	GDrawDestroyWindow(mv->gw);
    }
    GDrawSync(NULL);
    GDrawProcessPendingEvents(NULL);
}

struct mv_interface gdraw_mv_interface = {
    MV_GlyphCnt,
    MV_Glyph,
    MV_ReKernAll,
    MV_ReFeatureAll,
    MV_CloseAll
};

static struct resed metricsview_re[] = {
    {N_("Advance Width Col"), "AdvanceWidthColor", rt_color, &widthcol, N_("Color used to draw the advance width line of a glyph")},
    {N_("Italic Advance Col"), "ItalicAdvanceColor", rt_color, &widthcol, N_("Color used to draw the italic advance width line of a glyph")},
    {N_("Kern Line Color"), "KernLineColor", rt_color, &kernlinecol, N_("Color used to draw the kerning line")},
    {N_("Side Bearing Color"), "SideBearingLineColor", rt_color, &rbearinglinecol, N_("Color used to draw the left side bearing")},
    {N_("Selected Glyph Col"), "SelectedGlyphColor", rt_color, &selglyphcol, N_("Color used to mark the selected glyph")},
    NULL
};
extern GResInfo view_ri;
GResInfo metricsview_ri = {
    &view_ri, NULL,NULL, NULL,
    NULL,
    NULL,
    NULL,
    metricsview_re,
    N_("MetricsView"),
    N_("This window displays metrics information about a font"),
    "MetricsView",
    "fontforge",
    false
};
