
/* 
   This applet lets the user sketch freehand colors in various colors.
   When "symmetry" is turned on, the curves that the user draws are
   reflected horizontally, vertically, and/or diagonally to give a
   pretty, kaleidascopic effect.  Checkboxes are provided to select
   which symmetries are applied.  A "Clear to Color" button will fill
   the drawing area with the color specified in a choice menu.  The
   applet will also be cleared if it is resized.  A second choice
   menu is used to select the drawing color.

   FOR USE IN CS124, LAB 9. THE init() METHOD STILL HAS TO BE WRITTEN.

*/

import java.awt.*;
import java.awt.event.*;
import java.applet.Applet;

public class KaleidaDraw extends Applet
                         implements ItemListener, ActionListener {
                          
   Choice drawColorChoice;       // Menu for selecting drawing color.
   Choice backColorChoice;       // Menu for specifying the color that will
                                 //   be used to fill the screen when the
                                 //   use clicks the clear button.
   Button clearButton;           // Button that the user clicks to clear the drawing.

   Checkbox horizontalSymmetry;  // Checkbox that controls whether the user's
                                 //   curves are reflected horizontally.
   Checkbox verticalSymmetry;    // Checkbox that controls whether the user's
                                 //   curves are reflected vertically.
   Checkbox diagonalSymmetry;    // Checkbox that controls whether the user's
                                 //   curves are reflected diagonally.


   SymmetryCanvas canvas;       // The drawing area on which the user draws.
   
   static String[] colorNames = {  // Names to be displayed in the two Choice menus.
       "Black", "Gray", "White",
       "Red", "Green", "Blue",
       "Cyan", "Magenta", "Yellow",
       "Dark Red", "Dark Green", "Dark Blue",
       "Dark Cyan", "Dark Magenta", "Dark Yellow"
   };
   
   static Color[] pallette = {     // Actual colors corresponding to color names.
       Color.black, Color.gray, Color.white,
       Color.red, Color.green, Color.blue,
       Color.cyan, Color.magenta, Color.yellow,
       new Color(180,0,0), new Color(0,180,0), new Color(0,0,180),
       new Color(0,180,180), new Color(180,0,180), new Color(180,180,0)
   };       
   
   public void init() {
        // Initialize the applet.  Create the components that the applet contains.
        // Lay out the applet and register the applet to listen for ActionEvents
        // from the button and ItemEvents from drawColorChoice and from the
        // three checkboxes.  Note that the overall layout of the applet is
        // a BorderLayout with the canvas in the "Center" and with Panels in the 
        // "North" and "South" positions to hold the other components.
     
   } // end init();
   
   public Insets getInsets() {
        // Specify space between the edges of the applet and the components
        // that it contains.
      return new Insets(2,2,2,2);
   }
   
   public void actionPerformed(ActionEvent evt) {
        // Called when an action event occurs.  In this case, the only
        // possible action event is from the Clear button, but I check
        // that that button is in fact the source of the event, just
        // to be sure.  Then call the canvas's clear method.
      if (evt.getSource() == clearButton) {
         int colorIndex = backColorChoice.getSelectedIndex();
         canvas.clearToColor(pallette[colorIndex]);
      }
   }
   
   public void itemStateChanged(ItemEvent evt) {
        // Called when the state of a checkbox or choice menu changes.
        // Check the source, and call an appropriate method in the canvas
        // to handle the event.
      Object source = evt.getSource();
      if (source == drawColorChoice) {
         int colorIndex = drawColorChoice.getSelectedIndex();
         canvas.setDrawColor(pallette[colorIndex]);
      }
      else if (source == horizontalSymmetry) {
         boolean checked = horizontalSymmetry.getState();
         canvas.setHorizontalSymmetry(checked);
      }
      else if (source == verticalSymmetry) {
         boolean checked = verticalSymmetry.getState();
         canvas.setVerticalSymmetry(checked);
      }
      else if (source == diagonalSymmetry) {
         boolean checked = diagonalSymmetry.getState();
         canvas.setDiagonalSymmetry(checked);
      }
   }
   
} // end class KaleidaDraw




class SymmetryCanvas extends Canvas
                     implements MouseListener, MouseMotionListener {
     // A canvas on which the user can sketch curves with the mouse.
     // The curve can be reflected horizontally, veritcally, and/or
     // diagonally as it is drawn.  Instance methods are provided to
     // control which symmetries are in effect and to set the drawing
     // color and to fill the canvas with a specified color.  When the
     // canvas is contructed, there are no symmetries in effect, the
     // drawing color is black, and the background fill color is white.
     // An offscreen canvas is used to save a copy of the drawing,
     // so that it can be restored when necessary by the paint()
     // method.

   private boolean hSym, vSym, dSym;      // Which symmetries are in effect.
   private Color drawColor;
   
   private Image OSC;     // Off-screen canvas to store copy of the drawing.
   private Graphics OSG;  // Graphics context for OSC.
   
   private int width, height;  // Width and height of OSC. (These are used
                               // so that any resizing of the applet can be
                               // detected and a new OSC created.)
   
   SymmetryCanvas() {
        // Constructor.  Set the background and drawing colors and register
        // the canvas to listen for Mouse events from itself.
      drawColor = Color.black;
      setBackground(Color.white);
      addMouseListener(this);
      addMouseMotionListener(this);
   }
   
   void setHorizontalSymmetry(boolean symmetric) {
      hSym = symmetric;
   }

   void setVerticalSymmetry(boolean symmetric) {
      vSym = symmetric;
   }

   void setDiagonalSymmetry(boolean symmetric) {
      dSym = symmetric;
   }
   
   void setDrawColor(Color c) {
      if (c != null)
        drawColor = c;
   }
   
   void clearToColor(Color c) {
        // Fill the canvas (and OSC) with the specified color and store the
        // color as the background color of the canvas for future use.
      if (c != null) {
         setBackground(c);
         if (OSC != null) {
            OSG.setColor(c);
            OSG.fillRect(0,0,width,height);
         }
         repaint();
      }
   }
   
   public void update(Graphics g) {
        // Redefine update so that it just calls paint().
      paint(g);
   }
   
   public void paint(Graphics g) {
        // Copy OSC to the canvas, after first calling checkOSC.
      checkOSC();
      g.drawImage(OSC,0,0,this);
   }
   
   private void checkOSC() {
        // Make sure the off-screen canvas exists and is the correct size to match
        // the size of the canvas.  If not, create OSC and OSG and fill OSC with
        // the current background color of the canvas.
      if (OSC == null || width != getSize().width || height != getSize().height) {
         width = getSize().width;
         height = getSize().height;
         OSC = null;
         OSC = createImage(width,height);
         OSG = OSC.getGraphics();
         OSG.setColor(getBackground());
         OSG.fillRect(0,0,width,height);
      }
   }
   
   private void drawSymmetricLines(Graphics g, int x1, int y1, int x2, int y2) {
        // Draw a line from (x1,y1) to (x2,y2) in the graphics context g,
        // along with any reflections of that line that are required by the
        // current settings of hSym, vSym, and dSym.  Lines are drawn in
        // the current drawing color.
      g.setColor(drawColor);
      g.drawLine(x1,y1,x2,y2);
      if (hSym) // horizontal reflection
         g.drawLine(width-x1,y1,width-x2,y2);
      if (vSym) // vertical reflection
         g.drawLine(x1,height-y1,x2,height-y2);
      if (hSym && vSym)         // double reflection
         g.drawLine(width-x1,height-y1,width-x2,height-y2);
      if (!dSym)
         return;
      double scale = (double)height / (double) width;
      int b1 = height - (int)(x1*scale);
      int a1 = width - (int)(y1/scale);
      int b2 = height - (int)(x2*scale);
      int a2 = width - (int)(y2/scale);
      g.drawLine(a1,b1,a2,b2);
      if (hSym) // horizontal reflection
         g.drawLine(width-a1,b1,width-a2,b2);
      if (vSym) // vertical reflection
         g.drawLine(a1,height-b1,a2,height-b2);
      if (hSym && vSym) // diagonal reflection
         g.drawLine(width-a1,height-b1,width-a2,height-b2);
   }
   
   private void doLine(int x1, int y1, int x2, int y2) {
         // Draw a line from (x1,y1) to (x2,y2), along with any necessary
         // reflections, both onto the canvas and onto the off-screen canvas.
      drawSymmetricLines(OSG,x1,y1,x2,y2);
      Graphics g = getGraphics();
      drawSymmetricLines(g,x1,y1,x2,y2);
      g.dispose();
   }
   
   //----------- Variables and methods to respond to user mouse-drags ----------

   int prevX, prevY;  // The previous location of the mouse, during a drag.
   
   public void mousePressed(MouseEvent evt) {
        // User has pressed a mouse button.  Store the staring position
        // of the mouse drag operation.
      prevX = evt.getX();
      prevY = evt.getY();
   }
   
   public void mouseDragged(MouseEvent evt) {
        // The mouse has moved while the user is holding down the button.
        // Get the current mouse position, and draw a line from the previous
        // position to the current position (along with its reflections).
        // Then, to get ready for the next line, set prevX and prevY to
        // the current mouse position.
      int curX = evt.getX();
      int curY = evt.getY();
      doLine(prevX, prevY, curX, curY);
      prevX = curX;
      prevY = curY;
   }
   
   public void mouseReleased(MouseEvent evt) {
        // When user releases the mouse button, complete the curve with
        // a final line segment.
      int curX = evt.getX();
      int curY = evt.getY();
      doLine(prevX, prevY, curX, curY);
   }
   
   public void mouseMoved(MouseEvent evt) { }    // Other methods required by interfaces.
   public void mouseEntered(MouseEvent evt) { }
   public void mouseExited(MouseEvent evt) { }
   public void mouseClicked(MouseEvent evt) { }

} // end class Symmetry Canvas
