import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

import java.util.ArrayList;


/**
 * This panel lets the user place colored rectangles on a canvas
 * by clicking the canvas.  The color of the rectangle is given
 * by a color palette that lies along the bottom of the applet.
 * Alt-clicking (or clicking with middle mouse button) on a rectangle
 * will delete it.  Shift-clicking a rectangle will move it out in
 * front of all the other rectangles.  The user can right-click-and-drag 
 * to move the rectangles around.  The rectangles are restricted so that 
 * they cannot be moved entirely off the canvas.
 * 
 * The main point of this class is to demonstrate the use of an
 * ArrayList.
 * 
 * This class contains a main() routine so that it can be run as
 * a standalone application.  The main routine simply opens a
 * window that uses a SimpleDrawRects panel as its content pane.
 * 
 * This program depends on RainbowPalette.java.
 */
public class SimpleDrawRects extends JPanel {
   
   /**
    * When this class is run as an application, this main() routine opens
    * a frame that sues a SimpleDrawRects panel as its content pane.
    */
   public static void main(String[] args) {
      JFrame window = new JFrame("SimpleDrawRects");
      SimpleDrawRects content = new SimpleDrawRects();
      window.setContentPane(content);
      window.pack();
      window.setLocation(100,100);
      window.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
      window.setResizable(false);  
      window.setVisible(true);
   }
   

   /**
    * A palette of colors that appears at the bottom of the applet.  The
    * RainbowPalette class is non-standard, and is defined in a separate file..
    */
   private RainbowPalette colorInput; 
   

   /**
    * Constuctror sets up the panel, with drawing area and palette.
    */
   public SimpleDrawRects() {
      setBackground(Color.GRAY);
      setBorder(BorderFactory.createLineBorder(Color.GRAY,2));
      setLayout(new BorderLayout(2,2));
      RectPanel canvas = new RectPanel();
      colorInput = new RainbowPalette();
      add(canvas, BorderLayout.CENTER);
      add(colorInput, BorderLayout.SOUTH);
   }
   
   //-------------------------- Nested classes -------------------
   
   
   /**
    * An object of type ColoredRect hods the data for one colored rectangle.
    */
   private static class ColoredRect {
      int x, y, width, height;   // Location and size of rect.
      Color color;               // Color of rect.
   }
   
   
   /**
    * This class defined the drawing surface that is used for displaying
    * the rectangles.  It also listens for mouse events on itself and
    * responds to them.  The user adds a rectangle by right-clicking on
    * the panel, can delete a rectangle by Alt-clicking, can move a 
    * rectangle to the front by shift-clicking it, and can drag a rectangle
    * by right-clicking it.
    */
   private class RectPanel extends JPanel
                         implements MouseListener, MouseMotionListener {
      
      /**
       * The colored rectangles are represented by objects of type ColoredRect 
       * that are stored in this ArrayList.
       */
      private ArrayList rects;
      
      
      /* Variables for implementing dragging. */
      
      private boolean dragging;      // This is true when dragging is in progress.
      private ColoredRect dragRect;  // The rect that is being dragged.
      private int offsetx, offsety;  // The distance from the upper left corner of
                                     // the dragRect to the point where the user 
                                     // clicked the rect.  This offset is maintained \
                                     // as the rect is dragged.
      
      /**
       * Constructor creates an ArrayList to hold the rectangle data and
       * sets up mouse listening.  Background color is set to white,
       * and preferred size to 262-by-262.
       */
      RectPanel() {
         setBackground(Color.WHITE);
         setPreferredSize(new Dimension(262,262));
         rects = new ArrayList();
         addMouseListener(this);
         addMouseMotionListener(this);
      }
      
      /**
       * Find the topmost rect that contains the point (x,y). Return null 
       * if no rect contains that point.  The rects in the ArrayList are 
       * considered in reverse order so that if one lies on top of another, 
       * the one on top is seen first and is returned.
       */
      ColoredRect findRect(int x, int y) {
         for (int i = rects.size() - 1;  i >= 0;  i--) {
            ColoredRect rect = (ColoredRect)rects.get(i);
            if ( x >= rect.x && x < rect.x + rect.width
                  && y >= rect.y && y < rect.y + rect.height )
               return rect;  // (x,y) is inside this rect.
         }
         return null;
      }
      
      /**
       * If rect != null, move it out in front of the other rects by moving 
       * it to the last position in the ArrayList.
       */
      void bringToFront(ColoredRect rect) {
         if (rect != null) {
            rects.remove(rect);  // Remove rect from current position.
            rects.add(rect);     // Put rect in the ArrayList in last position.
            repaint();
         }
      }
      
      /**
       * If rect != null, remove it from the ArrayList and from the screen.
       */
      void deleteRect(ColoredRect rect) {
         if (rect != null) {
            rects.remove(rect);
            repaint();
         }
      }
      
      /**
       * Draw all the rects in the ArrayList.  NOTE: A black border is drawn
       * around each rectangle.
       */
      public void paintComponent(Graphics g) {
         super.paintComponent(g);  // Fills with background color, white.
         for (int i = 0; i < rects.size(); i++) {
            ColoredRect rect = (ColoredRect)rects.get(i);
            g.setColor(rect.color);
            g.fillRect(rect.x, rect.y, rect.width, rect.height);
            g.setColor(Color.BLACK);
            g.drawRect(rect.x, rect.y, rect.width - 1, rect.height - 1);
         }
      }
      
      /**
       * Responds when the user clicks on the panel.
       */
      public void mousePressed(MouseEvent evt) { 
         
         if (dragging)  // If dragging is already in progress, just return.
            return;
         
         if (evt.isMetaDown() || evt.isControlDown()) {
               // User right-clicked (or Command-clicked or control-clicked).  
               // Start dragging the rect that the user clicked (if any).
            dragRect = findRect( evt.getX(), evt.getY() );
            if (dragRect != null) {
               dragging = true;   // Begin a drag operation.
               offsetx = evt.getX() - dragRect.x;
               offsety = evt.getY() - dragRect.y;
            }
         }
         else if (evt.isShiftDown()) {
               // User shift-clicked.  More the rect that the user
               // clicked (if any) to the front.  Note that findRect()
               // might return null, but bringToFront() accounts for that.
            bringToFront( findRect( evt.getX(), evt.getY() ) );
         }
         else if (evt.isAltDown()) {
               // User alt-clicked or middle-clicked.  Delete the rect
               // that the user clicked (if any).
            deleteRect( findRect( evt.getX(), evt.getY() ) );
         }
         else {
               // This is a simple left-click.  Make a new
               // rectangle and add it to the canvas.  Every rectangle is
               // 60 pixels wide and 30 pixels tall.  The point where the
               // user clicked is at the center of the rectangle.  It's
               // color is the selected color in the colorInput palette.
            ColoredRect rect = new ColoredRect();
            rect.x = evt.getX() - 30;
            rect.y = evt.getY() - 15;
            rect.width = 60;
            rect.height = 30;
            rect.color = colorInput.getSelectedColor();
            rects.add(rect);
            repaint();
         }
         
      } // end mousePressed()
      
      /**
       * Called when the user releases the mouse button.
       * End the current drag operation, if one is in progress.
       */
      public void mouseReleased(MouseEvent evt) { 
         if (dragging == false)
            return;
         dragRect = null;
         dragging = false;
      }
      
      /**
       * Called when the user drags the mouse.  Continue the drag operation if 
       * one is in progress.  Move the rect that is being dragged to the current
       * mouse position.  But clamp it so that it can't be more than halfway 
       * off the screen.
       */
      public void mouseDragged(MouseEvent evt) { 

         if (dragging == false)
            return;
         
         dragRect.x = evt.getX() - offsetx;  // Get new postion of rect.
         dragRect.y = evt.getY() - offsety;
         
         /* Clamp (x,y) to a permitted range, as described above. */
         
         if (dragRect.x < - dragRect.width / 2)
            dragRect.x = - dragRect.width / 2;
         else if (dragRect.x + dragRect.width/2 > getWidth())
            dragRect.x = getWidth() - dragRect.width / 2;
         if (dragRect.y < - dragRect.height / 2)
            dragRect.y = - dragRect.height / 2;
         else if (dragRect.y + dragRect.height/2 > getHeight())
            dragRect.y = getHeight() - dragRect.height / 2;
         
         /* Redraw the panel, with the rect in its new position. */
         
         repaint();
         
      }  // end mouseDragged()
      
      public void mouseClicked(MouseEvent evt) { }
      public void mouseEntered(MouseEvent evt) { }
      public void mouseExited(MouseEvent evt) { }
      public void mouseMoved(MouseEvent evt) { }
      
   }  // end nested class RectPanel
   
   
}  // end class SimpleDrawRects

