/* @(#)ShapeEditor.java 1.0  22 September 2005 */

import edu.neu.ccs.*;
import edu.neu.ccs.gui.*;

import java.awt.*;
import java.awt.event.*;

/**
 * Class <code>ShapeEditor</code> implements a simple shape editor based
 * on the facilities in <code>PathNode</code> and <code>PathList</code>
 * and their associated views. A <code>Paintable</code> is maintained in
 * the graphics windows associated with a <code>BufferedPanel</code>.
 * Suitable mouse actions are used to edit the shape interactively.
 */
public class ShapeEditor extends TablePanel
{
    ///// Constants /////
    
    /** The extra amount to add to stroke thickness to get the dot size. */
    private static final int DOT_EXTRA = 2;
    
    /** The gap for table panels. */
    private static final int gap = 5;
    
    /** The gap for radio buttons. */
    private static final int smallgap = 3;
    
    
    ///// Simple Actions /////
    
    /**
     * Read and check the PathList data from the GUI and
     * then create and paint the associated shape structure.
     */
    private SimpleAction paintShape =
        new SimpleAction("Paint Shape") {
            public void perform() { paintShape(); }
        };
    
    
    /** Repaint the shape structure. */
    private SimpleAction paintShapeAgain =
        new SimpleAction("Paint Shape") {
            public void perform() { paintShapeAgain(); }
        };
    
    
    /**
     * Read the PathList from a file but do not update what is being
     * painted.
     */
    private SimpleAction readPathListDataFromFile =
        new SimpleAction("Read Path Data From File") {
            public void perform() { 
                readPathListDataFromFile();
            }
        };
    
    
    /** Save the PathList to a file. */
    private SimpleAction savePathListDataToFile =
        new SimpleAction("Save Path Data To File") {
            public void perform() { 
                savePathListDataToFile();
            }
        };
    
    
    /**
     * Shows or hides the gray pixel grid in the graphics window
     * based on the setting of the draw-grid check box.
     */
    private SimpleAction drawGrid =
        new SimpleAction("Draw Grid") {
            public void perform() { drawGrid(); }
        };
    
    
    ///// Mouse Actions /////
    
    /** The mouse moved action. */
    private MouseAction mouseMoved = new MouseAction() {
        public void mouseActionPerformed(MouseEvent evt) {
            mouseMoved(evt);
        }
    };
    
    
    /** The mouse pressed action. */
    private MouseAction mousePressed = new MouseAction() {
        public void mouseActionPerformed(MouseEvent evt) {
            mousePressed(evt);
        }
    };
    
    
    /** The mouse dragged action. */
    private MouseAction mouseDragged = new MouseAction() {
        public void mouseActionPerformed(MouseEvent evt) {
            mouseDragged(evt);
        }
    };
    
    
    /** The mouse released action. */
    private MouseAction mouseReleased = new MouseAction() {
        public void mouseActionPerformed(MouseEvent evt) {
            mouseReleased(evt);
        }
    };
    
    
    ///// Data Model /////
    
    /** The PathList model */
    private PathList pathlist = new PathList();
    
    /** The view of the PathList model.*/
    private PathListView pathlistview = new PathListView();
    
    
    /**
     * The distance measure to be used during mouse edits.
     *
     * This value is reset from the current dot size.
     */
    private int epsilon = 1;
    
    /**
     * The array that holds the pair (path-node-index, slot-index) for
     * a vertex or control dot that is being manipulated by the mouse.
     *
     * The array is <code>null</code> if no dot is currently being
     * manipulated.
     */
    private int[] indices = null;
    
    
    ///// GUI Left Hand Side /////
    
    /** The strings for the RadioPanel with the Stroke size choices. */
    private static final String[] strokeStuff =
        new String[] { "1", "2", "3", "4", "5", "6" };
    
    /** The layout for the RadioPanel with the Stroke size choices. */
    private TableLayout strokeLayout =
        new TableLayout(1, 6, smallgap, smallgap, CENTER);
    
    /** The RadioPanel with the Stroke size choices. */
    private RadioPanel strokePanel =
        new RadioPanel(strokeStuff, paintShapeAgain, 1, strokeLayout);
    
    
    /** The ColorView for the shape fill color. */
    private ColorView shapeFillColorView =
        new ColorView(Colors.lime,         true);
    
    /** The ColorView for the shape draw color. */
    private ColorView shapeDrawColorView =
        new ColorView(Colors.black,        true);
    
    /** The ColorView for the vertex dots color. */
    private ColorView vertexDotsColorView =
        new ColorView(Colors.red,          true);
    
    /** The ColorView for the control dots color. */
    private ColorView controlDotsColorView =
        new ColorView(Colors.darkorange,   true);
    
    /** The ColorView for the vertex frame color. */
    private ColorView vertexFrameColorView =
        new ColorView(Colors.transparent,  true);
    
    /** The ColorView for the bezier frame color. */
    private ColorView bezierFrameColorView =
        new ColorView(Colors.darkorchid,   true);
    
    
    /**
     * The Object[][] for the color views and their labels
     * and the stroke thickness buttons and label.
     */
    private Object[][] optionsStuff =
        new Object[][]
            { { "Shape Fill Color", shapeFillColorView   },
              { "Shape Draw Color", shapeDrawColorView   },
              { "Vertex Dots",      vertexDotsColorView  },
              { "Control Dots",     controlDotsColorView },
              { "Vertex Frame",     vertexFrameColorView },
              { "Bezier Frame",     bezierFrameColorView },
              { "Stroke Thickness", strokePanel          } };
    
    /**
     * The TablePanel for the color views and their labels
     * and the stroke thickness buttons and label.
     */
    private TablePanel optionsPanel =
        new TablePanel(optionsStuff, gap, gap, WEST);
    
    
    /**
     * The draw-grid check box to determine whether to
     * show or hide the gray pixel grid in the graphics window.
     */
    private BooleanView drawGridView =
        new BooleanView("Draw Grid in Graphics Window", drawGrid, true);
    
    
    /**
     * The Object[] for the vertical display of the path list view,
     * the color views, the stroke thickness buttons, and the
     * draw-grid check box.
     */
    private Object[] controlsStuff =
        new Object[] { pathlistview, optionsPanel, drawGridView };
    
    /**
     * The TablePanel for the vertical display of the path list view,
     * the color views, the stroke thickness buttons, and the
     * draw-grid check box.
     */
    private TablePanel controlsPanel =
        new TablePanel(controlsStuff, VERTICAL, gap, gap, NORTH);
    
    
    ///// GUI Right Hand Side /////
    
    /** The graphics window. */
    private BufferedPanel window
        = new BufferedPanel(400, 400);
    
    
    /** The Object[] with the actions to read and save path list data. */
    private Object[] fileStuff =
        new Object[] { readPathListDataFromFile, savePathListDataToFile };
    
    /**
     * The TablePanel for the horizontal display of
     * the buttons to read and save path list data.
     */
    private TablePanel filePanel =
        new TablePanel(fileStuff, HORIZONTAL, gap, gap, CENTER);
    
    
    /** The text field view of the x-coordinate of the mouse. */
    private TextFieldView xTFV = new TextFieldView(60);
    
    /** The text field view of the y-coordinate of the mouse. */
    private TextFieldView yTFV = new TextFieldView(60);
    
    /** The Object[] with the coordinate views and their labels. */
    private Object[] coordinateStuff =
        new Object[] {"x:", xTFV, "y:", yTFV };
    
    /**
     * The TablePanel for the horizontal display of
     * the coordinate views and their labels.
     */
    private TablePanel coordinatePanel =
        new TablePanel(coordinateStuff, HORIZONTAL, 2*gap, 2*gap, CENTER);
    
    
    /**
     * The Object[] for the vertical display of the graphics window,
     * the paint shape button, the file buttons, and the coordinate
     * panel.
     */
    private Object[] graphicsStuff =
        new Object[] { window, paintShape, filePanel, coordinatePanel };
    
    /**
     * The TablePanel for the vertical display of the graphics window,
     * the paint shape button, the file buttons, and the coordinate
     * panel.
     */
    private TablePanel graphicsPanel =
        new TablePanel(graphicsStuff, VERTICAL, gap, gap, CENTER);
    
    
    ///// Constructor /////
    
    /** The ShapeEditor contructor. */
    public ShapeEditor() {
        // Installs the controls panel on the left and the graphics panel
        // on the right.
        super(1, 2, gap, gap, NORTH);
        addObject(controlsPanel, 0, 0);
        addObject(graphicsPanel, 0, 1);
        
        // Draws the grid in the graphics window.
        drawGrid();
        
        // Adds a placeholder for the single Paintable that will be in the
        // graphics window.
        window.addPaintable(null);
        
        // Adds listeners for changes in the color views.
        shapeFillColorView.addAction(paintShapeAgain);
        shapeDrawColorView.addAction(paintShapeAgain);
        vertexDotsColorView.addAction(paintShapeAgain);
        controlDotsColorView.addAction(paintShapeAgain);
        vertexFrameColorView.addAction(paintShapeAgain);
        bezierFrameColorView.addAction(paintShapeAgain);
        
        // Adds listeners for the mouse behavior.
        MouseActionAdapter adapter = window.getMouseActionAdapter();
        adapter.addMouseMovedAction   (mouseMoved);
        adapter.addMousePressedAction (mousePressed);
        adapter.addMouseDraggedAction (mouseDragged);
        adapter.addMouseReleasedAction(mouseReleased);
        
        // Frames this panel in the center of the screen.
        frame("Shape Editor");
    }
    
    
    ///// Methods /////
    
    /**
     * Read and check the PathList data from the GUI and
     * then create and paint the associated shape structure.
     */
    private void paintShape() {
        try {
            pathlist = pathlistview.requestPathList();
        }
        catch (CancelledException ex) {
            pathlist = new PathList();
            return;
        }
        
        paintShapeAgain();
    }
    
    
    /**
     * Repaint the shape structure.
     *
     * Uses makePaintableFromPathList().
     */
    private void paintShapeAgain() {
        Paintable paintable = makePaintableFromPathList();
        window.setPaintable(0, paintable);
        window.repaint();
    }
    
    
    /**
     * Read the GUI painting data, namely, the color settings
     * and the stroke thickness.
     *
     * Use the settings to paint the structure of the path list
     * by making a Paintable corresponding to the path list and
     * the painting data.
     *
     * Set the epsilon value for mouse editing of the vertex and
     * control points as (thickness+2) to correspond to the size
     * of the dots painted.
     */
    private Paintable makePaintableFromPathList()
    {
        Color fillColor        = shapeFillColorView.getColor();
        Color drawColor        = shapeDrawColorView.getColor();
        Color vertexDotsColor  = vertexDotsColorView.getColor();
        Color controlDotsColor = controlDotsColorView.getColor();
        Color vertexFrameColor = vertexFrameColorView.getColor();
        Color bezierFrameColor = bezierFrameColorView.getColor();
        int   thickness        = strokePanel.getSelectedIndex() + 1;
        
        epsilon = thickness + 2;
        
        return pathlist.makeStructurePaintable
            (fillColor, drawColor, vertexDotsColor, controlDotsColor,
             vertexFrameColor, bezierFrameColor, thickness);
    }
    
    
    /**
     * Read the PathList from a file but do not update what is being
     * painted.
     */
    private void readPathListDataFromFile() {
        PathList list = new PathList();
        
        if (list.readDataFromFile())
            pathlistview.setViewFromPathList(list);
    }
    
    
    /** Save the PathList to a file. */
    private void savePathListDataToFile() {
        try {
            pathlist = pathlistview.requestPathList();
        }
        catch (CancelledException ex) {
            return;
        }
        
        pathlist.saveDataToFile();
    }
    
    
    /**
     * Shows or hides the gray pixel grid in the graphics window
     * based on the setting of the draw-grid check box.
     */
    private void drawGrid() {
        window.clearPanel();
        
        if (drawGridView.getBooleanValue())
            window.drawGrid(50);
        
        window.repaint();
    }
    
    
    /**
     * The mouse moved behavior.
     *
     * Simply updates the x,y coordinate views in the GUI.
     */
    private void mouseMoved(MouseEvent evt) {
        int x = evt.getX();
        int y = evt.getY();
        
        xTFV.setViewState("" + x);
        yTFV.setViewState("" + y);
    }
    
    
    /**
     * The mouse pressed behavior.
     *
     * Uses the x,y coordinate of the mouse to determine
     * what dot if any has been hit with the mouse press.
     *
     * If a dot has been hit, then the pair with
     * (path-node-index, slot-index)
     * is saved for path editing in the dragged/released
     * methods.
     */
    private void mousePressed(MouseEvent evt) {
        int x = evt.getX();
        int y = evt.getY();
        
        indices = pathlist.nearSlot(x, y, epsilon);
    }
    
    
    /**
     * The mouse dragged behavior.
     *
     * First calls mouseMoved to update the x,y coordinate
     * views in the GUI.
     *
     * If the (path-node-index, slot-index) array is not null,
     * then updates that slot to the mouse x,y position and
     * paints the shape again.
     */
    private void mouseDragged(MouseEvent evt) {
        mouseMoved(evt);
        
        if (indices != null) {
            int x = evt.getX();
            int y = evt.getY();
            
            pathlist.setSlot(indices, x, y);
            paintShapeAgain();
        }
    }
    
    
    /**
     * The mouse dragged behavior.
     *
     * If the (path-node-index, slot-index) array is not null,
     * then updates that slot to the mouse x,y position and
     * paints the shape again.
     *
     * Also updates the data in the path list view to reflect
     * the results of the mouse editing.
     */
    private void mouseReleased(MouseEvent evt) {
        if (indices != null) {
            int x = evt.getX();
            int y = evt.getY();
            
            pathlist.setSlot(indices, x, y);
            pathlistview.setViewFromPathList(pathlist);
            
            paintShapeAgain();
        }
    }
    
    
    ///// Main /////
    
    /** The main method.  Simply calls <code>new ShapeEditor()</code>. */
    public static void main(String[] args) {
        new ShapeEditor();
    }
    
}
