/*
Program to display coordinates of handwriting and apply a time delay
shift interactively to show effects of roundness manipulation. 
The XY coordinates are loaded via a URL.

Copyright, Lambert Schomaker/NICI, March 1996.

Please, refer to the author when using this code by:

Schomaker, L.R.B. (1993).
Using Stroke- or Character-based Self-organiZg Maps
in the Recognition of On-line, Connected Cursive Script.
{\em Pattern Recognition}, 26(3), 443-450.

Schomaker, L.R.B., & Teulings, H.-L. (1990).
A Handwriting Recognition System based on the Properties and Architectures 
of the Human Motor System.
{\em Proceedings of the International Workshop on Frontiers in Handwriting
Recognition (IWFHR)}. (pp. 195-211). Montreal: CENPARMI Concordia.
 */

import java.awt.*;
import java.applet.*;


/*
 * Can be run either as a standalone application by
 * typing "java hwround" or as an applet in the AppletViewer.
 */

public class hwround extends Applet {
    HwrRoundControls controls;

    public void init() {
	setLayout(new BorderLayout());
	HwrRoundCanvas c = new HwrRoundCanvas();
	add("Center", c);
	add("South", controls = new HwrRoundControls(c));
        c.H = new hwrCoord(4);
        c.update();
    }

    public void start() {
	controls.enable();
    }

    public void stop() {
	controls.disable();
    }

    public boolean handleEvent(Event e) {
	if (e.id == Event.WINDOW_DESTROY) {
	    System.exit(0);
        }
	return false;
    }

    public static void main(String args[]) {
	Frame f = new Frame("hwround");
	hwround hwrRound = new hwround();

	hwrRound.init();
	hwrRound.start();

	f.add("Center", hwrRound);
	f.resize(500, 300);
	f.show();
    }
}
    

class HwrRoundCanvas extends Canvas {

    /* begin object attributes */

    double      delay = 0.;
    Font	font;
    double      xmin, ymin, xmax, ymax, xoff, yoff, xscale, yscale;
    double      box_xmargin = 0.05; /* LR symmetrical of graphbox on canvas */
    double      box_ymargin = 0.1;  /* bottom margin of graphbox on canvas */
    double      hwrmargin = 0.02;   /* all sides within graphics box */
    double      xpix, ypix, wpix, hpix;

    int         npoints;

    hwrCoord	H;

    double xarr[];
    double yarr[];
    double zarr[];

    TextField txt_delay;

    /* end object attributes */

/* actual curve stuff */

    public void update() {
        xarr = new double [H.Npoints];
        yarr = new double [H.Npoints];
        zarr = new double [H.Npoints];
    }

    public void calc(double delay_ms) {
        int i, k;
        int ist, iend;
        double delay, t;
	
	delay = delay_ms / 10.;

        if(delay >= 0.0) {
            ist = (int) delay + 1;
            iend = H.Npoints - 1;
        } else {
            ist = 1;
            iend = (H.Npoints - 5 -1);
        }

        k = 0;
        for(i = ist; i < iend; ++i) {
            t = (double) i;
            xarr[k] = 0.33 * f(H.X, t-delay-1., H.Npoints)
                    + 0.33 * f(H.X, t-delay,    H.Npoints) 
                    + 0.33 * f(H.X, t-delay+1., H.Npoints);
            yarr[k] = 0.33 * f(H.Y, t - 1., H.Npoints)
                    + 0.33 * f(H.Y, t,      H.Npoints)
                    + 0.33 * f(H.Y, t + 1., H.Npoints);
            zarr[k] = H.Z[i];
            ++k;
        }
        npoints = k;
    }

    private double f(double r[], double t, int n)
    {
       double ret_val, d;
       int i1, i2;
       
       if(t <= 0) {
                ret_val = r[0];
       } else if(t >= n-1) {
                ret_val = r[n-1];
       } else {
                i1 = (int) t;
                i2 = i1 + 1;
                d = t - (double) i1;
                ret_val = (1.-d) * r[i1] + d * r[i2];
       }
       return(ret_val);
    }

    private double rmean(double a[], int n)
    {
        int i;
        double sum;
                
        sum = 0.0;
        for(i = 0; i < n; ++i) {
                sum += a[i];
        }
        return(sum/(double) n);
    }
 


    void scalexyz(Rectangle box) {
         int kk;

         xmin = xmax = xarr[0];
         ymin = ymax = yarr[0];
         for(kk = 1; kk < npoints; ++kk) {
                if( xarr[kk] < xmin) {
                    xmin = xarr[kk];
                }
                if( yarr[kk] < ymin) {
                    ymin = yarr[kk];
                }
                if( xarr[kk] > xmax) {
                    xmax = xarr[kk];
                }
                if( yarr[kk] > ymax) {
                    ymax = yarr[kk];
                }
                ++kk;
         }

         double xscale_in;
         double yscale_in;
         xscale_in = xmax - xmin;
         yscale_in = ymax - ymin;
           if(wpix/xscale_in > hpix/yscale_in) {
              yscale = yscale_in
                             / (1.- box_ymargin - 2. * hwrmargin);
              xscale = wpix/hpix * yscale;
           } else {
              xscale = xscale_in
                              / (1.- 2. * (box_xmargin + hwrmargin));
              yscale = hpix/wpix * xscale;
           }

         xoff = (xmax + xmin)/ 2.0;
         yoff = (ymax + ymin)/ 2.0;
    } 

    public void paint(Graphics g) {
	Rectangle r = bounds();

	g.setColor(Color.black);
	g.setFont(font);

        drawbox(g, r);

        calc(delay);

        scalexyz(r);

        drawxyz(g);

        g.drawString(H.Inkfilename, (int) xpix
                                     , (int) (ypix + hpix + 18.));

    }

    void drawbox(Graphics g, Rectangle box) /* init and draw graphics box */
    {
        int x1,y1,x2,y2;

/* Around canvas */

        x1 = 0; /* is left */
        y1 = 0; /* is top */

        x2 = box.width - 1;
        y2 = box.height - 1;

	g.drawLine(x1, y1, x1, y2 );
	g.drawLine(x1, y2, x2, y2);
	g.drawLine(x2, y2, x2, y1);
	g.drawLine(x2, y1, x1, y1);

/* Around graphics area */

        x1 = (int) (box_xmargin * box.width);
        y1 = (int) (0.04 * box.height); /* is top */

        x2 = (int) ((1. - box_xmargin) * box.width);
        y2 = (int) ((1. - box_ymargin) * box.height);

	g.drawLine(x1, y1, x1, y2 );
	g.drawLine(x1, y2, x2, y2);
	g.drawLine(x2, y2, x2, y1);
	g.drawLine(x2, y1, x1, y1);

/* Remember that this is the graphics box */

        xpix = x1;
        ypix = y1;
        wpix = x2 - x1 + 1;
        hpix = y2 - y1 + 1;

	g.setColor(Color.blue);

    }

    void drawxyz(Graphics g) 
    {

        for (int i =  1; i < (npoints - 1) ; i++) {
            if(zarr[i+1] > 0 && zarr[i] > 0) {
                    g.drawLine(scalex(xarr[i]), scaley(yarr[i])
                             , scalex(xarr[i + 1]), scaley(yarr[i+1]));
            } else {
                g.drawLine(scalex(xarr[i + 1]), scaley(yarr[i+1]),
                           scalex(xarr[i + 1]), scaley(yarr[i+1]));
            }
        }
    }

    int scalex(double r)
    {
        double rr;
        rr = xpix + 0.5 * wpix + 
             wpix * (r - xoff)/xscale;
        return((int) rr);
    }

    int scaley(double r)
    {
        double rr;
        rr = ypix + hpix * (0.5 - (r - yoff)/yscale);
        return((int) rr);
    }

    public void redraw(Double delay) {
	this.delay = delay.doubleValue();

        if(this.delay < -30.) {
            this.delay  = -30.;
        }
        if(this.delay > 30.) {
            this.delay = 30.;
        }

        txt_delay.setText(Double.toString(this.delay));

        repaint();
    }

}

class HwrRoundControls extends Panel {
    HwrRoundCanvas canvas;
    Scrollbar delay_slider;

    protected void makebutton(String name,
                              GridBagLayout gridbag,
                              GridBagConstraints c) {
        Button button = new Button(name);
        gridbag.setConstraints(button, c);
        add(button);
    }


    public HwrRoundControls(HwrRoundCanvas canvas) {
	this.canvas = canvas;

        GridBagLayout gridbag = new GridBagLayout();
        GridBagConstraints rules = new GridBagConstraints();
        setLayout(gridbag);

        rules.fill = GridBagConstraints.BOTH;
        rules.weightx = 1.0;

        delay_slider = new Scrollbar(Scrollbar.HORIZONTAL, 0, 1, -30, 30);
        gridbag.setConstraints(delay_slider, rules);
        add(delay_slider);

        rules.weightx = 0.0;
	this.canvas.txt_delay = 
              new TextField(Double.toString(this.canvas.delay), 6);
        gridbag.setConstraints(this.canvas.txt_delay, rules);
        add(this.canvas.txt_delay);

	makebutton("Apply", gridbag, rules);
	makebutton("Previous", gridbag, rules);
	makebutton("Next", gridbag, rules);
    }

    public boolean handleEvent(Event ev) {
        if (ev.target instanceof Scrollbar) {
            this.canvas.delay = (double) delay_slider.getValue();
            this.canvas.txt_delay.setText(Double.toString(this.canvas.delay));

	    canvas.redraw(
               Double.valueOf(this.canvas.txt_delay.getText().trim())
            );

	    return true;
	}

	if (ev.target instanceof Button) {

            int idum;

            if ( "Next".equals(ev.arg) ) {
                   idum = this.canvas.H.next();
                   this.canvas.update();
            }

            if ( "Previous".equals(ev.arg) ) {
                   idum = this.canvas.H.previous();
                   this.canvas.update();
            }

	    canvas.redraw(
               Double.valueOf(this.canvas.txt_delay.getText().trim())
            );

	    return true;

	}

	return false;
    }

}

