/* ---------------------------------------------------------------
 File:                           Desktop.java
 Package:                        FLAP
 Author:                         Dr. Ratnesh Kumar and Greg Stamp
 V2.0 10/28/2006
 Initial Developers: Yana Ong, Bharath Oruganti and Magda & Octavian Procopiuc
 ---------------------------------------------------------------*/
 
/*
 * Dr. Ratnesh Kumar, Greg Stamp.
 * Department of Electrical and Computer Engineering
 * Iowa State University
 * October, 2006.
 *
 * Copyright (c) 2006
 * All rights reserved.
 */
package flap;


import	java.awt.*;
import	java.util.*;


/**
 * This is the canvas where the machines are built and displayed.
 * It is the main class involved in the interface part of 
 * the different
 * machines. The lists of transitions and states are kept here, 
 * and the actual painting of the transitions and states is performed
 * here also.
 *
 * @see		flap.State
 * @see		flap.Transition
 * @author	Magda & Octavian Procopiuc
 * @version	1.0, 15 July 1996
 */ 
public class Desktop extends Canvas {

	static final long serialVersionUID = 1L;
    /**
     * The start state of the machine.
     */
    public State		initialState = null;

    /**
     * The currently selected transition.
     */
    public Transition	selectedTransition; 

    /**
     * The currently selected state - babu   
     */
    public State	selectedState; 

    /**
     * The list of states currently on the desktop.
     * The machine will use them from here, too.
     */
    public Vector		theStates;

    /**
     * The list of transitions currently on the desktop.
     * The machine will use them from here, too.
     */
    public Vector		theTransitions;

    /**
     * Becomes true when the current machine is modified in any way.
     */
    public boolean	modified = false;

    /**
     * When the step run window is on, the desktop is disabled.
     */
    public boolean 	disabled;

    /**
     * When the user sets the auto relabel option,
     * this flag becomes true, and the states are
     * automatically relabeled each time one of them is removed.
     */
    boolean	autoRelabel = false;

    /**
     * The state label counter.
     */ 
    int		currentId = 0;

    /**
     * The transition label counter.
     */ 
    int		currenttrId = 0;

    /**
     * The x distance between the mouse pointer
     * and the center of the state circle, when
     * dragging a state.
     */
    protected int		x_offset = 0;

    /**
     * The x distance between the mouse pointer
     * and the center of the state circle, when
     * dragging a state.
     */
    protected int		y_offset = 0;
    protected Color	backcolor = Params._d_backcolor;
    protected Popup	menu;
    protected boolean	menuOn = false;

    /**
     * used to retain the beginning state when dragging.
     */
    protected State	firstState = null;

    /**
     * used when dragging.
     */
    protected Transition	firstTransition = null;
    protected Point	drag = null;
    protected int		draggingWhat;

    /**
     * the tokenized label of the current transition.
     */
    protected String[]	sublabel = new String[6];

    /**
     * the currently selected sublabel in the current
     * transition.
     */
    protected int		selectedSublabel = 0;

    /**
     * The number of sublabels in a label.
     */
    protected int		fieldCounter = 1;

    /**
     * The separators between the sublabels in a label.
     */
    protected String[]	separator = new String[6];

    /**
     * the tokenized label of the current transition.
     */
    protected String[]	ssublabel = new String[6];

    /**
     * the currently selected sublabel in the current
     * transition.
     */
    protected int		sselectedSublabel = 0;

    /**
     * The number of sublabels in a label.
     */
    protected int		sfieldCounter = 1;

    /**
     * The separators between the sublabels in a label.
     */
    protected String[]	sseparator = new String[6];

    /**
     * What are we currently dragging.
     */
    final static int DG_NOTHING = 0;
    final static int DG_STATE = 1;
    final static int DG_TRANS = 2;
    final static int DG_LABEL = 3;

    /**
     * Used for writing the current machine in the tabular form.
     * @see	toTabular()
     */
    final static String EMPTY_STR = "-----";
  
    /**
     * the offscreen image.
     */
    private Image		offscr = null;

    /**
     * the graphics context of the offscreen image.
     */
    private Graphics	offg = null;

    private int 	count = 0; 
    private int	off = 0;
    private int	offr = 4;
    private int	voff1 = 4;
    private int	voff2 = 2;
    private int	hoff1 = 2;
    private int	hoff2 = 3; 
    private int	arrow_length = 12;
    private int	arrow_width = 10;

    /**
     * distance factor between transitions
     */
    private double df = 1.5;

    /**
     * Builds a new Desktop with the specified type.
     * @param type	the type of the machine represented on the desktop.
     */
    public Desktop() {
        theStates = new Vector(10, 1);
        theTransitions = new Vector(30, 1);
        menu = new Popup(this);
        fieldCounter = 1;

        separator[fieldCounter - 1] = "";
    }

    public boolean keyDown(Event e, int key) {
        int len;
        String modString;

        requestFocus();
        if (selectedTransition == null && selectedState == null) {
            return super.keyDown(e, key);
        }

        if (selectedTransition != null && selectedTransition.rename) // user chooses to rename a transition
        {
            if (key >= 32 && key <= 126) {
                modString = selectedTransition.label + new Character((char) key);
                selectedTransition.label = null;
                selectedTransition.label = modString;
                modified = true;
            } else if (key == 8 || key == 127) // backspace or delete
            {
                if ((len = selectedTransition.label.length()) > 1) {
                    modString = selectedTransition.label.substring(0, len - 1);
                    selectedTransition.label = null;
                    selectedTransition.label = modString;
                    modified = true;
                } else if (len == 1) {
                    selectedTransition.label = "";
                    modified = true;
                }
            }
        } else if (selectedState != null && selectedState.rename) // user chooses to rename a state
        {
            if (key >= 32 && key <= 126) {
                modString = selectedState.label + new Character((char) key);
                selectedState.label = null;
                selectedState.label = modString;
                modified = true;
            } else if (key == 8 || key == 127) // backspace or delete
            {
                if ((len = selectedState.label.length()) > 1) {
                    modString = selectedState.label.substring(0, len - 1);
                    selectedState.label = null;
                    selectedState.label = modString;
                    modified = true;
                } else if (len == 1) {
                    selectedState.label = "";
                    modified = true;
                }
            }
        }
        repaint();
        return true;
    }

    public boolean mouseDown(Event e, int x, int y) {
        State	s;
        Transition	t;
    
        if (selectedTransition != null) {
            selectedTransition.rename = false;
        }
        if (selectedState != null) {
            selectedState.rename = false;
        }
    
        requestFocus();
        switch (e.modifiers) {
        case Event.ALT_MASK: // middle button down.
        case Event.SHIFT_MASK:
            if ((s = isInState(x, y)) != null) {
                draggingWhat = Desktop.DG_STATE;
                firstState = s;
                x_offset = x - s.p.x;
                y_offset = y - s.p.y;
                modified = true;
            } else if (isGoodLocation(x, y)) {
                s = addState(x, y);
                if (theStates.size() == 1) {
                    initialState = s;
                }
                draggingWhat = Desktop.DG_STATE;
                firstState = s;
                x_offset = 0;
                y_offset = 0;
                modified = true;
            } else {
                draggingWhat = Desktop.DG_NOTHING;
            }
            break;

        // end of case ALT_MASK...

        case 0: // left button down.
            if ((t = isInTransition(x, y)) != null) {
                draggingWhat = Desktop.DG_LABEL;
                firstTransition = t;
                Point pp = locateLabel(t);

                x_offset = x - pp.x;
                y_offset = y - pp.y;
                selectedTransition = t; // Modified by Yana - renaming of transition can only be done thru pop-up menu
            } else if ((s = isInState(x, y)) != null) {
                draggingWhat = Desktop.DG_TRANS;
                firstState = s;
                drag = new Point(x, y);
            } else {
                draggingWhat = Desktop.DG_NOTHING;
            }
            break;

        // end of case 0

        case Event.META_MASK: // right button down.
        case Event.CTRL_MASK:
            menuOn = true;
            menu.setLocation(x, y);
            menu.invalidateAll();
            if ((t = isInTransition(x, y)) != null) {
                menu.setTransition(t);
            } else if ((s = isInState(x, y)) != null) { 
                menu.setState(s);
            }       
            break;

        // end of case Event.MET..
        default:
            break;
        }	// end of switch
    
        repaint();
        return super.mouseDown(e, x, y);
    }	// end of method MouseDown.

    public boolean mouseDrag(Event e, int x, int y) {

        if (selectedTransition != null) {
            selectedTransition.rename = false;
        }
        if (selectedState != null) {
            selectedState.rename = false;
        }
  
        switch (e.modifiers) {
        case Event.ALT_MASK: // middle mouse down.
        case Event.SHIFT_MASK:
            if (draggingWhat == Desktop.DG_STATE) {
                firstState.setLocation(x - x_offset, y - y_offset);
            } else {}
            break;

        // end of case ALT_MASK...
        case 0: // left mouse down.
            if (draggingWhat == Desktop.DG_TRANS) {
                drag.setLocation(x, y);
            } else if (draggingWhat == Desktop.DG_LABEL) {
                if (firstTransition.from == firstTransition.to) {
                    moveLoop(firstTransition, x - x_offset, y - y_offset);
                }
            } else {}
            break;

        // end of case 0
        case Event.META_MASK:
        case Event.CTRL_MASK:
            if (x >= menu.p.x && x <= menu.p.x + menu.width) {
                int idx = (int) Math.floor((y - menu.p.y) / menu.height);

                if (y >= menu.p.y && idx < menu.itemsCounter) {
                    menu.hasFocus = idx;
                } else {
                    menu.hasFocus = -1;
                }
            } else {
                menu.hasFocus = -1;
            }
            break;
        }	// end of switch.

        repaint();
        return super.mouseDrag(e, x, y);
    }	// end of method MouseDrag.

    public boolean mouseUp(Event e, int x, int y) {
        State	s;
        Transition	t;

        switch (e.modifiers) {
        case Event.ALT_MASK: // middle button up.
        case Event.SHIFT_MASK:
            if (draggingWhat == Desktop.DG_STATE) {
                draggingWhat = Desktop.DG_NOTHING;
                // what if there's another state here???
                firstState.setLocation(x - x_offset, y - y_offset);
                x_offset = 0;
                y_offset = 0;
                firstState = null;
            } else {}
            break;

        // end of case ALT_MASK...
        case 0: // left button up.
            if (draggingWhat == Desktop.DG_LABEL) {
                draggingWhat = Desktop.DG_NOTHING;
                if (firstTransition.from == firstTransition.to) {
                    moveLoop(firstTransition, x - x_offset, y - y_offset);
                }
                x_offset = 0;
                y_offset = 0;
                firstTransition = null;
            } else if (draggingWhat == Desktop.DG_TRANS) {
                if ((s = isInState(x, y)) != null) {
                    t = addTransition(firstState, s);
                    selectedTransition = t;
                    if (firstState == s) { // for loop transitions
                        Transition tt = sameStatesTransition(t);

                        if (tt == null) {
                            t.distance = Params._t_ltheight; // distance is the height.
                        } else {
                            t.distance = tt.distance;
                        }
                    }
                    modified = true;
                } else {}
                drag = null;
                firstState = null;
                draggingWhat = Desktop.DG_NOTHING;
            } else {}

        case Event.META_MASK:
        case Event.CTRL_MASK:
            break;
        }    // end of switch 
        if (menuOn) { // it's not under switch due to differences in events on
            // different platforms. bugs...
            menuOn = false;
            if (menu.takeAction()) {
                modified = true;
            }
        }
        repaint();
        return super.mouseUp(e, x, y);
    }	// end of method MouseUp.

    /**
     * Moves the label of a transition having
     * the same from and to states.
     */
    private void moveLoop(Transition t, int x, int y) {
        boolean done = false;
        Transition tt = null;
        int sz = theTransitions.size();
        int i = 0;

        while (!done && i < sz) {
            tt = (Transition) theTransitions.elementAt(i);
            if (tt == t) {
                done = true;
            } else {
                i++;
            }
        }
        if (tt == t) {
            for (Enumeration ee = theTransitions.elements(); ee.hasMoreElements();) {
                tt = (Transition) ee.nextElement();
                if (tt.from == firstTransition.from
                        && tt.to == firstTransition.to) {
                    tt.setLocation(x, y);
                }
            }     
        }
    }	// end of method moveLoop.

    /**
     * Returns the first transition in the list of transitions
     * which has the same from and to states with the given transition t,
     * but is different from it.
     */
    private Transition sameStatesTransition(Transition t) {
        boolean done = false;
        Transition tt = null;
        int sz = theTransitions.size();
        int i = 0;

        while (!done && i < sz) {
            tt = (Transition) theTransitions.elementAt(i);
            if ((tt.from == t.from) && (tt.to == t.to) && (tt != t)) {
                done = true;
            } else {
                i++;
            }
        }
        return (done) ? tt : null;
    }	// end of method sameStatesTransition

    /**
     * Sets the transition field of a given State to be the list
     * of transitions having that state as the from state.
     * @param s	the state.
     * @param tokenize	whether or not to tokenize a FSA label.
     */
    void setTransitions(State s, boolean tokenize) {
        Transition t;

        s.transitions.removeAllElements();
        for (Enumeration et = theTransitions.elements(); et.hasMoreElements();) {
            t = (Transition) et.nextElement();     
            if (t.from == s) {
                if (tokenize) {
                    StringTokenizer stk = new StringTokenizer(t.label, ",",
                            false);

                    while (stk.hasMoreTokens()) {
                        String label = stk.nextToken().trim();

                        s.transitions.addElement(
                                new Transition(t.id, s, t.to, label));
                    }
                } else {
                    s.transitions.addElement(t);
                }
            }
        }
        s.transitionCount = s.transitions.size();
    }

    public void  paint(Graphics g) {

        Dimension	d = getSize();

        g.setColor(backcolor);
        g.fillRect(0, 0, d.width, d.height);

        g.setColor(Params._d_forecolor);
        g.draw3DRect(0, 0, d.width - 1, d.height - 1, true);

        for (Enumeration e = theTransitions.elements(); e.hasMoreElements();) {
            paintLine(g, (Transition) e.nextElement());
        }
 
        // although it's very time-consuming to draw everything when dragging,
        // it seems the only reliable solution. For whatever reason,
        // setXORMode is not working properly.
        if (drag != null) {
            g.setColor(Params._t_selectedcolor);
            g.drawLine(firstState.p.x, firstState.p.y, drag.x, drag.y);
        }

        for (Enumeration e = theStates.elements(); e.hasMoreElements();) {
            paintState(g, (State) e.nextElement());
        }

        if (initialState != null) {
            paintInitial(g, initialState);
        }

        g.setFont(Params._t_font);
        for (Enumeration e = theTransitions.elements(); e.hasMoreElements();) {
            paintTransition(g, (Transition) e.nextElement());
        }
        if (selectedTransition != null) {
            if (theTransitions.indexOf(selectedTransition) != -1) {
                paintTransition(g, selectedTransition);
            }
        }

        if (menuOn) {
            paintPopup(g);
        }

    }	// end of method paint.

    public void update(Graphics g) {

        Dimension	d = getSize();

        if (offscr == null) {
            offscr = createImage(d.width, d.height);
            offg = offscr.getGraphics();
        }
        if (offscr.getWidth(this) != d.width
                || offscr.getHeight(this) != d.height) {
            offscr = createImage(d.width, d.height);
            offg = offscr.getGraphics();
        }

        if (offg == null) { 
            paint(g);
        } else {
            paint(offg);
            g.drawImage(offscr, 0, 0, null);
        }
    }	// end of method update.

    /**
     * Paints the line of a transition.
     */ 
    private void paintLine(Graphics g, Transition t) {
        // old int hw = 5;
        int hw = 8;
        Color tc = ((t == selectedTransition)
                ? Params._t_selectedcolor
                : ((t.st == Transition.NORMAL)
                        ? Params._t_normalcolor
                        : Params._t_focusedcolor));

        g.setColor(tc);
        // maybe test the counter for multiple lines
        if (t.from == t.to) { 
            g.drawOval(t.from.p.x - hw, t.from.p.y - t.distance, 2 * hw,
                    t.distance);
        } else {
            g.drawLine(t.from.p.x, t.from.p.y, t.to.p.x, t.to.p.y);
        }
    }

    /**
     * Computes the points of the little triangle which shows
     * the direction of the transition.
     * @param t	the transition
     * @param pl	the upper left corner of the transition's label.
     * @param xpoints	the x coordinates of the points representing the arrow.
     * @param ypoints	the y coordinates of the points representing the arrow.
     */
    private void computeArrow(Transition t, Point pl, int[] xPoints, int[] yPoints) {
        int x1 = t.from.p.x;
        int y1 = t.from.p.y;
        int x2 = t.to.p.x;
        int y2 = t.to.p.y;

        if (Math.abs(x2 - x1) > Math.abs(y2 - y1)) {
            off = hoff1 + arrow_length + 2; 
            if (x2 > x1) { // right-arrow
                xPoints[0] = xPoints[1] = pl.x + hoff1;
                xPoints[2] = pl.x + hoff1 + arrow_length;
                yPoints[0] = pl.y + voff1;
                yPoints[1] = pl.y + voff1 + arrow_width;
                yPoints[2] = pl.y + voff1 + (int) (arrow_width / 2);
            } else { // left-arrow
                xPoints[0] = xPoints[1] = pl.x + hoff1 + arrow_length;
                xPoints[2] = pl.x + hoff1;
                yPoints[0] = pl.y + voff1;
                yPoints[1] = pl.y + voff1 + arrow_width;
                yPoints[2] = pl.y + voff1 + (int) (arrow_width / 2);
            } 
        } else {
            off = hoff2 + arrow_width + 2;
            if (y2 > y1) { // down-arrow
                xPoints[0] = pl.x + hoff2;
                xPoints[1] = pl.x + hoff2 + arrow_width;
                xPoints[2] = pl.x + hoff2 + (int) (arrow_width / 2);
                yPoints[0] = yPoints[1] = pl.y + voff2;
                yPoints[2] = pl.y + voff2 + arrow_length;
            } else { // up-arrow
                xPoints[0] = pl.x + hoff2;
                xPoints[1] = pl.x + hoff2 + arrow_width;
                xPoints[2] = pl.x + hoff2 + (int) (arrow_width / 2);
                yPoints[0] = yPoints[1] = pl.y + voff2 + arrow_length;
                yPoints[2] = pl.y + voff2;
            }
        }
    }	// end of method computeArrow.

    /**
     * Paints the label of a transition.
     */
    private void paintTransition(Graphics g, Transition t) {
        int		cx;
        FontMetrics fm = g.getFontMetrics();
        int promptLength = (t == selectedTransition) ? fm.stringWidth("_") : 0;
        Color tc = ((t == selectedTransition)
                ? Params._t_selectedcolor
                : ((t.st == Transition.NORMAL)
                        ? Params._t_normalcolor
                        : Params._t_focusedcolor));

        Point pl = locateLabel(t);

        int h = fm.getAscent();

        int[] xPoints = new int[3];
        int[] yPoints = new int[3];

        computeArrow(t, pl, xPoints, yPoints);
 
        g.setColor(Params._t_interiorcolor);
        g.fillRect(pl.x, pl.y,
                fm.stringWidth(t.label) + off + offr + promptLength,
                Params._t_height);   
        g.setColor(Params._t_makecontcolor);
        g.drawRect(pl.x, pl.y,
                fm.stringWidth(t.label) + off + offr + promptLength,
                Params._t_height);
        if (t.from == t.to) { 
            g.fillOval(pl.x + hoff1, pl.y + voff1, arrow_width, arrow_width);
        } else {
            g.fillPolygon(xPoints, yPoints, 3);
        }

        int yStrBase = (int) (pl.y - 2 + (Params._t_height + h) / 2);

        if (t != selectedTransition) {
            g.drawString(t.label, pl.x + off, yStrBase);
        } else {
            cx = pl.x + off;
            g.setColor(Params._t_selectedcolor);
            g.drawString(t.label, cx, yStrBase);
            g.setColor(Params._t_normalcolor);
        }  // end else.

    }	// end of method paintTransition

    /**
     * Paints a state. Since we use it only in the desktop, we figured it's
     * a good idea to put it here.
     */
    private void paintState(Graphics g, State s) {
        int diameter = 2 * Params._s_radius;
        int off = 2; // for outer circle of marked state

        Color sc = (s.ss == State.NORMAL)
                ? Params._s_normalcolor
                : Params._s_focusedcolor;

        g.setColor(sc);
        g.fillOval(s.p.x - Params._s_radius, s.p.y - Params._s_radius, diameter,
                diameter);
        g.setColor(Params._s_forecolor);
        g.drawOval(s.p.x - Params._s_radius, s.p.y - Params._s_radius, diameter,
                diameter); // outer ring of state

        g.setFont(Params._s_font);
        FontMetrics fm = g.getFontMetrics();
        String str = "q" + Integer.toString(s.id);

        if (s.label == "" && !s.rename) { // if not labelled, displays its id for the first time.
            s.label = str;
        }
        int l = fm.stringWidth(str);
        int h = fm.getAscent();

        if (selectedState != null) {
            String str2 = s.label; // babu

            g.drawString(str2, s.p.x - ((int) l / 2), s.p.y + ((int) h / 2));
            str2 = "";
        } else {
            g.drawString(s.label, s.p.x - ((int) l / 4), s.p.y + ((int) h / 2));
        }

        if (s.isFinal) {
            g.drawOval(s.p.x - Params._s_radius - off,
                    s.p.y - Params._s_radius - off, diameter + 2 * off,
                    diameter + 2 * off);
        }
    }

    /**
     * Paints the triangle that represents the initial state.
     */
    private void paintInitial(Graphics g, State s) {
        int r = Params._s_radius;
        int xPoints[] = { s.p.x - r, s.p.x - 2 * r, s.p.x - 2 * r, s.p.x - r};
        int yPoints[] = { s.p.y, s.p.y - r, s.p.y + r, s.p.y};

        g.setColor(Params._s_forecolor);
        g.drawPolygon(xPoints, yPoints, 4);    
    }

    /**
     * Paints the popup menu.
     */
    private void paintPopup(Graphics g) {
        Point	w;

        g.setFont(Params._p_font);
        g.setColor(Params._p_backcolor);
        g.fill3DRect(menu.p.x, menu.p.y - 5, menu.width,
                menu.height * menu.itemsCounter + 10, true);
        if (menu.hasFocus >= 0) {
            if (menu.isValid[menu.hasFocus]) {
                g.setColor(Params._p_focusedcolor);
                g.fillRect(menu.p.x + 5,
                        menu.p.y + 1 + menu.hasFocus * menu.height,
                        menu.width - 10, menu.height - 2); 
            }
        }
    
        /* NEW CODE BY Yana - modified to display different color for different set of menu items */
        g.setColor(
                Params._p_foreStateColor);
        int j = 0;

        for (j = 0; j < menu.stateItemsCounter; j++) {
            if (menu.isValid[j]) {
                w = menu.writeHere(j);
                g.drawString(menu.theItems[j], w.x, w.y);
            }
        }
        g.setColor(Params._p_foreTransColor);
        for (; j < menu.itemsCounter; j++) {
            if (menu.isValid[j]) {
                w = menu.writeHere(j);
                g.drawString(menu.theItems[j], w.x, w.y);
            }
        }
    
        g.setColor(Params._p_invalidcolor);
        for (int i = 0; i < menu.itemsCounter; i++) {
            if (!menu.isValid[i]) {
                w = menu.writeHere(i);
                g.drawString(menu.theItems[i], w.x, w.y);
            }
        }
   
    }

    /**
     * Creates a new state and adds it to the list of states.
     */
    public State addState(int x, int y) {
        State s = new State(currentId++, false, x, y);

        theStates.addElement(s);
        return s;
    }

    /**
     * Checks to see if the given position is in a state.
     * To speed it up, we check if the position is in the
     * rectangle surrounding the state's circle.
     * @return	the state found, or null, if none was found.
     */
    public State isInState(int x, int y) {
        State	s = null;
        boolean	found = false;
        Enumeration	e = theStates.elements();

        while ((e.hasMoreElements()) && (!found)) {
            s = (State) e.nextElement();
            if ((Math.abs(s.p.x - x) < Params._s_radius)
                    && (Math.abs(s.p.y - y) < Params._s_radius)) {
                found = true;
            }
        }
        if (found) {
            return s;
        } else {
            return null;
        }
    }	// end of method isInState.

    /**
     * Creates a new transition and adds it to the list of transitions.
     */
    public Transition addTransition(State from, State to) {
        String label = "";

        Transition t = new Transition(currenttrId++, from, to, label);

        theTransitions.addElement(t);
        return t;
    }

    /**
     * Removes the given transition from the list of transitions.
     */
    public void removeTransition(Transition t) {
        if (selectedTransition == t) {
            selectedTransition = null;
        }
        theTransitions.removeElement(t);
    }

    /**
     * Returns the position of the label of the given transition.
     */
    public Point locateLabel(Transition t) {
        int idx = theTransitions.indexOf(t);
        Transition tt;
        int x1 = t.from.p.x;
        int y1 = t.from.p.y;
        int x2 = t.to.p.x;
        int y2 = t.to.p.y;
        int x, y;

        count = 0;

        for (int i = 0; i < idx; i++) {
            tt = (Transition) theTransitions.elementAt(i);
            if (tt.from == t.from && tt.to == t.to) {
                count++;
            }
        }

        if (t.from == t.to) {
            y = (int) (y1 - t.distance + count * Params._t_height * df);
            y = Math.min(y, y1 - Params._t_distance);
            x = x1;
        } else {
            double len = Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
            double d = (t.distance + count * (Params._t_height * df));

            d = Math.min(d, (len / 2 - Params._t_height / 2 + 1));
            d = d / len;
            x = (int) (d * x2 + (1 - d) * x1);
            y = (int) (d * y2 + (1 - d) * y1);

            /*****/
            if ((x1 > x2) && (y1 > y2)) {
                x += 8;
                y += 8;
            }     
        }

        return(new Point(x, y));
    }	// end of method locateLabel.

     /**
     * Checks to see if the given position is in a label.
     * @return	the transition found, or null if none was found.
     */
    public Transition isInTransition(int x, int y) {
        Transition	t = null;
        boolean	found = false;
        int		tx, ty;
        Point 	pl;
        FontMetrics	fm = getFontMetrics(Params._t_font);
        Vector	rt = new Vector(theTransitions.size()); // will keep trans. in reversed order.

        for (Enumeration e = theTransitions.elements(); e.hasMoreElements();) { 
            rt.insertElementAt(e.nextElement(), 0);
        }  
        Enumeration	e = rt.elements();

        while ((e.hasMoreElements()) && (!found)) {
            t = (Transition) e.nextElement();
            pl = locateLabel(t);
            tx = pl.x;
            ty = pl.y;
            if (((x - tx) <= fm.stringWidth(t.label) + off + offr)
                    && ((x - tx) >= 0) && ((y - ty) <= Params._t_height)
                    && ((y - ty) >= 0)) {
                found = true;
            }
        }
        if (found) {
            return t;
        } else {
            return null;
        }
    }	// end of method isInTransition.

    /**
     * Checks to see if the given position is a good
     * location for a new state. Not implemented.
     */
    boolean isGoodLocation(int x, int y) {
        return true;
    }	// end of method isGoodLocation.

    /**
     * Changes the aspect of the desktop.
     * It becomes faded when fade is true, and unfaded when fade is false.
     */
    public void changeAspect(boolean fade) {
        State s;

        if (fade) { // upper window on; faded desktop
            selectedTransition = null;
            backcolor = Params._d_fadedbackcolor;
        } else { // upper window closed; sharp desktop
            backcolor = Params._d_backcolor;
            for (Enumeration e = theStates.elements(); e.hasMoreElements();) {
                s = (State) e.nextElement();
                s.ss = State.NORMAL;
            }
        } 
        repaint();
    }  // end of method changeAspect
  
    /**
     * Relabels the states to have consecutive labels.
     */
    public void relabel() {
        int newid = 0;
        int newtrid = 0;
        State s = null;
        Transition t = null;

        for (Enumeration e = theStates.elements(); e.hasMoreElements();) {
            s = (State) e.nextElement();
            s.id = newid++;
        }
        currentId = newid;
        for (Enumeration e = theTransitions.elements(); e.hasMoreElements();) {
            t = (Transition) e.nextElement();
            t.id = newtrid++;
        }
        currenttrId = newtrid;
    }	// end of method relabel.
   
    /**
     * Removes the highlights from all the states.
     */
    public void unfocusAll() {
        State	s = null;

        for (Enumeration e = theStates.elements(); e.hasMoreElements();) {
            s = (State) e.nextElement();
            s.ss = State.NORMAL;
        }
    }

    /**
     * Returns a string representing the tabular form of the current machine.
     * It is the only method we could find to print the machine on all platforms.
     */
    public String toTabular() {
  
        int		cols = theStates.size() + 1;
        int		rows = theTransitions.size() + theStates.size();
        String[][]	table = new String[rows][cols];
        String[]	header = new String[cols];
        int[]	colWidth = new int[cols];
        int		currentCol = 0;
        int		currentRow = 0;
        int		rowSpan = 0;
        State	s;
        Transition	t;
        String	answer = "";
        String	finalstates = "Final States: ";
        String	initialstate = "Start state: " 
                + ((initialState != null) ? "q" + initialState.id : "");

        if (theStates.size() == 0) {
            return " ";
        }
    
        for (int i = 0; i < cols; i++) {
            for (int j = 0; j < rows; j++) {
                table[j][i] = EMPTY_STR;
            }
            colWidth[i] = 0;
        }
    
        for (Enumeration e1 = theStates.elements(); e1.hasMoreElements();) {
            s = (State) e1.nextElement();
            if (s.isFinal) {
                finalstates += " q" + s.id;
            }
            table[currentRow][0] = "q" + s.id + "\t|";
            rowSpan = 1;
            setTransitions(s, false);
            for (Enumeration e2 = s.transitions.elements(); e2.hasMoreElements();) {
                t = (Transition) e2.nextElement();
                currentCol = theStates.indexOf(t.to) + 1;
                if (t.label.length() > colWidth[currentCol]) {
                    colWidth[currentCol] = t.label.length();
                }
                int i = 0;

                while (!table[currentRow + i][currentCol].equals(EMPTY_STR)) {
                    i++;
                    table[currentRow + i][0] = "\t|";
                }
                table[currentRow + i][currentCol] = t.label.trim();
                if (i + 1 > rowSpan) {
                    rowSpan = i + 1;
                }
            }
            currentRow += rowSpan;
        }
        rows = currentRow;
    
        String str;
        int totalWidth = 0;

        answer = "";
        answer += initialstate + "\n";
        answer += finalstates + "\n";
        answer += "Transition function: " + "\n";
        answer += "\n\t ";
        for (Enumeration e1 = theStates.elements(); e1.hasMoreElements();) {
            s = (State) e1.nextElement();
            str = "q" + s.id;
            currentCol = theStates.indexOf(s) + 1;
            header[currentCol] = " " + toLength(str, colWidth[currentCol]) + " ";
            answer += header[currentCol];
            totalWidth += Math.max(colWidth[currentCol], 3) + 2;
        }
        answer += "\n";
        answer += "\t-";
        for (int i = 0; i < totalWidth; i++) { 
            answer += "-";
        }
        answer += "\n";
        for (int i = 0; i < rows; i++) {
            if (!table[i][0].equals("\t|") && (i > 0)) {
                answer += "\t|";
                for (int ii = 0; ii < totalWidth; ii++) { 
                    answer += "-";
                }
                answer += "\n";
            }
            answer += " " + table[i][0];
            for (int j = 1; j < cols; j++) {
                table[i][j] = " " + toLength(table[i][j], colWidth[j]) + "|";
                answer += table[i][j];
            }
            answer += "\n";
        }
        answer += "\t-";
        for (int i = 0; i < totalWidth; i++) { 
            answer += "-";
        }
        answer += "\n";
        return answer;
    }	// end of method toTabular.
  
    /**
     * Makes the String str have length length by adding white spaces.
     * Used in method toTabular.
     */
    protected String toLength(String str, int length) {
        String	answer = new String(str);
        int		actuall = Math.max(3, length);
    
        if (answer.equals(EMPTY_STR)) {
            answer = "-";
        }
        int l = answer.length();

        for (int i = l; i < actuall; i++) { 
            answer += " ";
        }
        return answer;
    }

    /**
     * Tokenizes the given label.
     * The result is stored in the sublabel array.
     * @see	sublabel
     */
    protected void tokenizeIt(String label) {
        String	separators = "";

        for (int i = 0; i < fieldCounter; i++) {
            separators += separator[i];
        }
        StringTokenizer stk = new StringTokenizer(label, separators, false);

        for (int i = 0; i < fieldCounter; i++) {
            sublabel[i] = stk.nextToken();
        }
    }	// end of method tokenizeIt.

}	// end of class Desktop.


/**
 * This class implements the popup menu that appears on the canvas
 * when the right mouse is kept down. It doesn't have a graphic context,
 * so the actual painting of the menu is done in the Desktop class.
 * Maybe make it extend Window and be independent...
 *
 * @author	Octavian Procopiuc
 * @version	1.0 15 July 96
 */
class Popup {
    
    final static int MAKE_INITIAL = 0;
    final static int MAKE_FINAL = 1;
    final static int MAKE_NONFINAL = 2;
    final static int RENAME_STATE = 3;
    final static int REMOVE_STATE = 4;
    final static int RENAME_TRANSITION = 5;
    final static int REMOVE_TRANSITION = 6;

    static int NO_OF_TRANS_ITEMS = 2;
    static int NO_OF_STATE_ITEMS = 5;

    Desktop	parent;
    int		offx = 9;
    int		offy = 5;
    String[]	theItems = {
        "Make This State Initial", "Make This State Final",
        "Make This State Non-Final", "Rename This State", "Remove This State",
        "Rename This Transition", "Remove This Transition"};
    boolean[]	isValid;
    int		stateItemsCounter; // # of menu items for state 
    int		transItemsCounter; // # of menu items for transition
    int		itemsCounter; // the # of items.
    int		height; // the hight of an item.
    int		width; // the width of the menu.
    int		hasFocus; // the item that has the focus.
   
    Point		p; // the upper left point of the menu.
    State		s = null;
    Transition	t = null;

    boolean rename = false; // set valid if user chooses RENAME_TRANSITION

    Popup(Desktop parent) {
        this.parent = parent;
        itemsCounter = theItems.length;
        stateItemsCounter = NO_OF_STATE_ITEMS;
        transItemsCounter = NO_OF_TRANS_ITEMS;
        isValid = new boolean[itemsCounter];
        width = Params._p_width;
        p = new Point(0, 0);
        height = Params._p_height;
        invalidateAll();

    }

    public void setState(State s) {
        this.s = s;
        if (s.isFinal) {
            isValid[MAKE_NONFINAL] = true;
        } else {
            isValid[MAKE_FINAL] = true;
        }
        if (s != parent.initialState) {
            isValid[MAKE_INITIAL] = true;
        }
        isValid[REMOVE_STATE] = true;
        isValid[RENAME_STATE] = true;
    }

    public void setTransition(Transition t) {
        this.t = t;
        isValid[RENAME_TRANSITION] = true;
        isValid[REMOVE_TRANSITION] = true;
    }

    public void invalidateAll() {
        hasFocus = -1;
        s = null;
        t = null;
        for (int i = 0; i < itemsCounter; i++) {
            isValid[i] = false;
        }
    }

    public boolean takeAction() {
        boolean answer = false; // is made true if any action was taken.
        Transition tt;

        switch (hasFocus) {
        case MAKE_FINAL:
            if (isValid[MAKE_FINAL] && s != null) {
                s.isFinal = true;
                s.rename = false;
                answer = true;
            }
            break;

        case MAKE_NONFINAL:
            if (isValid[MAKE_NONFINAL] && s != null) {
                s.isFinal = false;
                s.rename = false;
                answer = true;
            }
            break;

        case MAKE_INITIAL:
            if (isValid[MAKE_INITIAL] && s != null) {
                parent.initialState = s;
                s.rename = false;
                answer = true;
            }
            break;

        case REMOVE_STATE:
            if (isValid[REMOVE_STATE] && s != null) {
                int i = 0;

                while (i < parent.theTransitions.size()) {
                    tt = (Transition) parent.theTransitions.elementAt(i);
                    if (tt.from == s || tt.to == s) { 
                        parent.removeTransition(tt);
                    } else {
                        i++;
                    }
                }
                if (s == parent.initialState) {
                    parent.initialState = null;
                }
                parent.theStates.removeElement(s);
                s.rename = false;
                answer = true;
            }
            if (parent.autoRelabel) {
                parent.relabel();
            }
            break;

        case REMOVE_TRANSITION:
            if (isValid[REMOVE_TRANSITION] && t != null) {
                parent.removeTransition(t);
                t.rename = false;
                answer = true;
            }
            break;

        case RENAME_STATE:
            if (isValid[RENAME_STATE] && s != null) {
                parent.selectedState = s;
                parent.selectedState.rename = true;
                answer = true;
            }
            rename = false;
            break;

        case RENAME_TRANSITION: // addition by Yana
            if (isValid[RENAME_TRANSITION] && t != null) {
                parent.selectedTransition = t;
                parent.selectedTransition.rename = true;
                answer = true;
            }
            break;

        default:
            if (s != null) {
                s.rename = false;
            }
            if (t != null) {
                t.rename = false;
            }
            break; 
        // comment ended here
        }	// end of switch hasFocus.
        return answer;
    }
     
    public void setLocation(int x, int y) {
        p.x = x;
        p.y = y;
    }

    public Point writeHere(int index) {
        int x = p.x + offx;
        int y = p.y + (index + 1) * height - offy;

        return new Point(x, y);
    }

}	// end of class Popup.

