import java.awt.*;
import java.awt.event.*;
import java.applet.*;
import java.util.Vector;

/**
 * Applet to demonstrate exponential increase by halfening its
 * painting area recursively. But this class can be run as an
 * application as well!
 *
 * If reading the source code, grep out for NICE FEATURE!
 */
public class Exponential extends Applet implements ActionListener {
    /**
     * Indicates whether an Applet is running, or an application.
     * In an application, Frames equipped with MenuBars do hold
     * the Applet Component.
     */
    private static boolean APPLET=true;
    /**
     * holds the currently visible Frames
     */
    private static Vector frames;
    /**
     * counts how many Frames ever have been opened
     */
    private static int count=0;
    private int DEPTH_LIMIT_MAX=16;
    private int DEPTH_LIMIT=DEPTH_LIMIT_MAX;
    /**
     * a status line
     */
    private TextField status;
    /**
     * input field to enter the recursion depth
     */
    private TextField input;
    /**
     * Canvas where recusive halfening will take place
     */
    private ExponentialCanvas flaeche;
    /**
     * open a new Frame, only in application
     */
    private MenuItem neu;
    /**
     * close the current Frame, only in application
     */
    private MenuItem close;
    /**
     * close all Frames and exit application, only in application
     */
    private MenuItem exit;
    /**
     * holds the menu bar of the frame enclosing this Applet,
     * only in application - bad place, by the way...
     */
    private MenuBar mb;
    // =====================================================================
    // -------------------------------------------------- applet-methods ---
    /**
     * Builds up the functionality necessary for an Applet,
     * i.e. input field, status line ans drawing area.
     */
    public void init() {
        setBackground(new Color(240, 240, 240));
        setLayout(new BorderLayout());
         
        // NICE FEATURE: assignment within method invokation
         
        add(flaeche=new ExponentialCanvas(DEPTH_LIMIT), BorderLayout.CENTER);
        Panel p=new Panel();
        p.add(new Label("Bis zu welcher Tiefe zeichnen? "));
        p.add(input=new TextField(2));
         
        // NICE FEATURE: this Object is its Components' ActionListener!
         
        input.addActionListener(this);
        add(p, BorderLayout.NORTH);
        add(status=new TextField(), BorderLayout.SOUTH);
        status.setEditable(false);
    }
    /**
     * Ugly, but in this context probably the only way to hold
     * several MenuBars each storing its own MenuItems...
     */
    private MenuBar getMenuBar() {
        return mb;
    }
    /**
     * An alternative way of event handling! The class Exponential
     * implements the ActionListener-Interface on its own! Thus
     * no additional anonymous inner classes are necessary to
     * react on ActionEvents. Any ActionEvent will be delivered
     * to this object's actionPerformed method.
     */
    public void actionPerformed(ActionEvent e) {
        if (e.getSource()==input) {
            readInt();
        } else if (APPLET==false) {
            // other events are only relevant for Frames
            frameActions(e);
        }
    }
    /**
     * Read recursion depth desired by the user and, if valid,
     * repaint drawing area's face.
     */
    private void readInt() {
        int cand=-1;
        boolean fehler=false;
        try {
            cand=Integer.parseInt(input.getText());
        } catch (NumberFormatException nfe) {
            // IMPORTANT: Never omit some action here!!!
            fehler=true;
        }
        input.setText("");
        if (cand>0 && cand<DEPTH_LIMIT_MAX) {
            DEPTH_LIMIT=cand;
            flaeche.setDepth(DEPTH_LIMIT);
        } else {
            fehler=true;
        }
        if (fehler) {
            status.setText("Bitte geben Sie eine ganze Zahl zwischen 1 und "+DEPTH_LIMIT_MAX+" ein.");
        } else {
            status.setText("Aktuelle Zeichentiefe: "+DEPTH_LIMIT+"  ("+(1<<DEPTH_LIMIT)+". Teil der Gesamtfläche)");
            flaeche.repaint();
        }
    }
    // =====================================================================
    // -------------------------------------------- application-specific ---
    /**
     * React on events that occur on a Frame's MenuItems.
     * Since we did not overwrite Frame ourselves, it is
     * a little bit ugly that this Applet knows its parent
     * Frame's MenuItems...
     * @see #openFrame()
     * @see #closeFrame(java.awt.Frame)
     * @see java.lang.System#exit(int)
     */
    private void frameActions(ActionEvent e) {
        if (e.getSource()==neu) {
            openFrame();
        } else if (e.getSource()==close) {
            closeFrame(getFrameFromMenuItem(close));
        } else if (e.getSource()==exit) {
            System.exit(0);
        }
    }
    public Exponential() {
        super();
        if (APPLET==false) {
            neu=new MenuItem("New");
            close=new MenuItem("Close");
            exit=new MenuItem("Exit");
            mb=new MenuBar();
            Menu m=new Menu("File");
            mb.add(m);
            m.add(neu);
            m.addSeparator();
            m.add(close);
            m.add(exit);
             
            // NICE FEATURE: this Object is its Components' ActionListener!
             
            neu.addActionListener(this);
            close.addActionListener(this);
            exit.addActionListener(this);
            init();
        }
    }
    /**
     * @param c the MenuItem all the Frames in the frame
     *         Vector are searched for
     * @return the Frame cand in the frame Vector, where
     *         getMenuItemFromFrame(cand)==c
     * @see #frame
     * @see #getMenuItemFromFrame(java.awt.Frame)
     */
    private static Frame getFrameFromMenuItem(MenuItem c) {
        for (int i=0; i<frames.size(); i++) {
            Frame cand=(Frame) (frames.elementAt(i));
            if (getMenuItemFromFrame(cand)==c) {
                return cand;
            }   
        }
        return null;
    }
    /**
     * @param f the Frame whose close MenuItem will be returned
     * @return the close MenuItem of the given Frame f, i.e.
     *         the third item in the first Menu of this Frame's
     *         MenuBar
     */
    private static MenuItem getMenuItemFromFrame(Frame f) {
        return f.getMenuBar().getMenu(0).getItem(2);
    }
    /**
     * Opens a new Frame that holds a new instance of Exponential!
     * The new Frame will be appended to the frames Vector.
     * @see #frames
     * @see Exponential()
     */
    private static void openFrame() {
        Frame f=new Frame("Demonstration exponentiellen Wachstums ("+(++count)+")");
        f.setLayout(new BorderLayout());
        Exponential e=new Exponential();
        f.add(e, BorderLayout.CENTER);
        f.setMenuBar(e.getMenuBar());
        f.setBounds(100+10*count, 100+10*count, 600, 500);
        f.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                closeFrame((Frame) (e.getWindow()));
            }
        });
        f.setVisible(true);
        if (frames.size()==1) {
            Frame g=(Frame) (frames.firstElement());
             
            // NICE FEATURE: non-static item depends on static item!
             
            getMenuItemFromFrame(g).setEnabled(true);
        }
         
        // NICE FEATURE: static Vector holds all visible Frames
         
        frames.addElement(f);
        if (frames.size()==1) {
            getMenuItemFromFrame(f).setEnabled(false);
        }
    }
    /**
     * Closes the given Frame and deletes it out of the frames Vector.
     * If f was the last Frame, the application will be terminated,
     * if f was the last but one Frame, the other Frame's close
     * MenuItem will be disabled.
     * @see #getMenuItemFromFrame(java.awt.Frame)
     * @see #frames
     */
    private static void closeFrame(Frame f) {
        frames.removeElement(f);
        switch (frames.size()) {
            case 0 : System.exit(0); break;
            case 1 : Frame other=(Frame) (frames.firstElement());
                      
                     // NICE FEATURE: non-static item depends on static item!
                      
                     getMenuItemFromFrame(other).setEnabled(false);
                      
                     // NICE FEATURE: case without break!
                      
            default: f.setVisible(false);
                     f.dispose();
        }
    }
    /**
     * Sets that we are running in an application context,
     * initializes a new Vector to hold every Frame that
     * is visible, and finally opens the first Frame.
     * @see #APPLET
     * @see #frames
     * @see #openFrame()
     */
    public static void main(String[] argv) {
        APPLET=false;
        frames=new Vector();
        openFrame();
    }
    // =====================================================================
    // ------------------------------ inner class: Canvas-specialization ---
    private class ExponentialCanvas extends Canvas {
        /**
         * maximally allowed total recursion depth
         */
        private int DEPTH_LIMIT_MAX=16;
        /**
         * current total recursion depth
         */
        private int depth;
        /**
         * For smart drawing only...
         */
        private final int GRAY_LIMIT=64;
        public ExponentialCanvas(int depth) {
            super();
            setDepth(depth);
        }
        /**
         * Paints this Canvas by recursively halfening its area.
         * @see #halfen(java.awt.Graphics, java.awt.Dimension, int)
         */
        public void paint(Graphics g) {
            g.setColor(Color.black);
            Dimension d=getSize();
             
            // NICE FEATURE: recursion start!
             
            halfen(g, d, 1);
            g.setColor(Color.black);
            g.drawRect(0, 0, d.width-1, d.height-1);
        }
        /**
         * To be called from Exponential, if depth value wanted changed.
         */
        public void setDepth(int depth) {
             
            // NICE FEATURE: conditional operator!
             
            this.depth=(depth>DEPTH_LIMIT_MAX ? DEPTH_LIMIT_MAX : depth);
        }
        /**
         * For smart drawing only...
         */
        private Color getGray(int depth) {
            int gray=224-depth*((224-GRAY_LIMIT)/DEPTH_LIMIT_MAX);
            return new Color(gray, gray, gray);
        }
        /**
         * Method to halfen this Canvas' drawing area recursively.
         * At every recursion level, a new tint is generated.
         * @param   d   the size of the upper left corner of this
         *              ExponentialCanvas to be halfened
         * @param   currentDepth the current recursion depth,
         *              i.e. an integer value between 1 and depth
         * @see #depth
         * @see #getGray(int)
         */
        private void halfen(Graphics g, Dimension d, int currentDepth) {
            if (d==null || currentDepth>depth) return;
            g.setColor(getGray(currentDepth));
            if (d.width>d.height) {
                g.fillRect(0, 0, d.width/2, d.height);
                g.setColor(Color.black);
                g.drawLine(d.width/2, 0, d.width/2, d.height);
                 
                // NICE FEATURE: recursion!!!
                 
                halfen(g, new Dimension(d.width/2, d.height), currentDepth+1);
            } else {
                g.fillRect(0, 0, d.width, d.height/2);
                g.setColor(Color.black);
                g.drawLine(0, d.height/2, d.width, d.height/2);
                 
                // NICE FEATURE: recursion!!!
                 
                halfen(g, new Dimension(d.width, d.height/2), currentDepth+1);
            }
        }
    }
}

