# Expert Java Programming



## JGuru (Dec 3, 2014)

*Expert Java Programming*

Learn how to program using the Java language at the next level - expert level.
This guide contains plenty of code that showcases how to use Java at the higher level.

The following demo shows how to implement a custom button with a custom color.
The button pulsates when the user clicks it.


```
/**
 * Created with IntelliJ IDEA.
 * User: Sowndar
 * Date: 10/4/14
 * Time: 5:05 PM
 * To change this template use File | Settings | File Templates.
 */
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.geom.Point2D;


class ColorButton extends JButton implements MouseListener {

    public ColorButton() {
        this.addMouseListener(this);
    }

    public Dimension getPreferredSize() {
        String text = getText();
        FontMetrics fm = this.getFontMetrics(getFont());
        float scale = (50f/30f)*this.getFont().getSize2D();
        int w = fm.stringWidth(text);
        w += (int)(scale*1.4f);
        int h = fm.getHeight();
        h += (int)(scale*.3f);
        return new Dimension(w,h);
    }

    public void paintComponent(Graphics g) {
        Graphics2D g2 = (Graphics2D)g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        g2.setColor(this.getBackground());
        g2.fillRect(0,0,this.getWidth(),this.getHeight());

        float scale = (50f/30f)*this.getFont().getSize2D();

        drawLiquidButton(this.getForeground(),
                this.getWidth(), this.getHeight(),
                getText(), scale,
                g2);
    }

    protected void drawLiquidButton(Color base,
                                    int width, int height,
                                    String text, float scale,
                                    Graphics2D g2) {

        // calculate inset
        int inset = (int)(scale*0.04f);
        int w = width - inset*2 - 1;
        int h = height - (int)(scale*0.1f) - 1;


        g2.translate(inset,0);
        drawDropShadow(w,h,scale,g2);

        if(pressed) {
            g2.translate(0, 0.04f*scale);
        }

        drawButtonBody(w,h,scale,base,g2);
        drawText(w,h,scale,text,g2);
        drawHighlight(w,h,scale,base,g2);
        drawBorder(w,h,scale,g2);

        if(pressed) {
            g2.translate(0, 0.04f*scale);
        }
        g2.translate(-inset,0);
    }

    protected void drawDropShadow(int w, int h, float scale, Graphics2D g2) {
        // draw the outer drop shadow
        g2.setColor(new Color(0,0,0,50));
        this.fillRoundRect(g2,
                (-.04f)*scale,
                (.02f)*scale,
                w+.08f*scale, h+0.08f*scale,
                scale*1.04f, scale*1.04f);
        g2.setColor(new Color(0,0,0,100));
        this.fillRoundRect(g2,0,0.06f*scale,w,h,scale,scale);
    }

    protected void drawButtonBody(int w, int h, float scale,
                                  Color base, Graphics2D g2) {
        // draw the button body
        Color grad_top = base.brighter();
        Color grad_bot = base.darker();
        GradientPaint bg = new GradientPaint(
                new Point(0,0), grad_top,
                new Point(0,h), grad_bot);
        g2.setPaint(bg);
        this.fillRoundRect(g2,
                (0)*scale,
                (0)*scale,
                w,h,1*scale,1*scale);

        // draw the inner color
        Color inner = base.brighter();
        inner = alphaColor(inner,75);
        g2.setColor(inner);
        this.fillRoundRect(g2,
                scale*(.4f),
                scale*(.4f),
                w-scale*.8f, h-scale*.5f,
                scale*.6f,scale*.4f);
    }

    protected void drawText(int w, int h, float scale,
                            String text, Graphics2D g2) {
        // calculate the width and height
        int fw = g2.getFontMetrics().stringWidth(text);
        int fh = g2.getFontMetrics().getAscent() -
                g2.getFontMetrics().getDescent();
        int textx = (w-fw)/2;
        int texty = h/2 + fh/2;

        // draw the text
        g2.setColor(new Color(0,0,0,70));
        g2.drawString(text,(int)((float)textx+scale*(0.04f)),
                (int)((float)texty + scale*(0.04f)));
        g2.setColor(Color.black);
        g2.drawString(text, textx, texty);
    }


    protected void drawHighlight(int w, int h, float scale,
                                 Color base, Graphics2D g2) {
        // create the highlight
        GradientPaint highlight = new GradientPaint(
                new Point2D.Float(scale*0.2f,scale*0.2f),
                new Color(255,255,255,175),
                new Point2D.Float(scale*0.2f,scale*0.55f),
                new Color(255,255,255,0)
        );
        g2.setPaint(highlight);
        this.fillRoundRect(g2, scale*0.2f, scale*0.1f,
                w-scale*0.4f, scale*0.4f, scale*0.8f, scale*0.4f);
        this.drawRoundRect(g2, scale*0.2f, scale*0.1f,
                w-scale*0.4f, scale*0.4f, scale*0.8f, scale*0.4f);
    }

    protected void drawBorder(int w, int h,
                              float scale, Graphics2D g2) {
        // draw the border
        g2.setColor(new Color(0,0,0,150));
        this.drawRoundRect(g2,
                scale*(0f),
                scale*(0f),
                w,h,scale,scale);
    }

    // float version of fill round rect
    protected static void fillRoundRect(Graphics2D g2,
                                        float x, float y,
                                        float w, float h,
                                        float ax, float ay) {
        g2.fillRoundRect(
                (int)x, (int)y,
                (int)w, (int)h,
                (int)ax, (int)ay
        );
    }

    // float version of draw round rect
    protected static void drawRoundRect(Graphics2D g2,
                                        float x, float y,
                                        float w, float h,
                                        float ax, float ay) {
        g2.drawRoundRect(
                (int)x, (int)y,
                (int)w, (int)h,
                (int)ax, (int)ay
        );
    }

    // generate the alpha version of this color
    protected static Color alphaColor(Color color, int alpha) {
        return new Color(color.getRed(), color.getGreen(),
                color.getBlue(), alpha);
    }


    /* mouse listener implementation */
    protected boolean pressed = false;
    public void mouseExited(MouseEvent evt) { }
    public void mouseEntered(MouseEvent evt) { }
    public void mouseClicked(MouseEvent evt) { }
    public void mouseReleased(MouseEvent evt) {
        pressed = false;
    }
    public void mousePressed(MouseEvent evt) {
        pressed = true;
    }



    public static void p(String s) {
        System.out.println(s);
    }
}

public class ColorButtonDemo {

    public static void main(String[] args) {
        JFrame frame = new JFrame("ColorButton");
        JButton vb = new ColorButton();
        vb.setFont(new Font("Dialog",Font.PLAIN,36));
        vb.setForeground(new Color(50,50,255));
        vb.setBackground(Color.white);
        vb.setForeground(new Color(50,255,0));
        vb.setText("Button");
        // vb.setMargin(new Insets(0,30,0,30));
        // No border for this Button
        vb.setBorderPainted(false);
        frame.getContentPane().add(vb);
        frame.pack();
        frame.setVisible(true);
        frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
    }

}
```

*s7.postimg.org/qs84tlmrb/Color_Button.jpg

Here is a Calculator that is implemented in Java Swing.
The typical calculator found in most of the Operating Systems.
Click on the number & click on the addition, subtraction, division, multiplication etc.,
The result of the equation will be calculated and shown.


```
import java.awt.*;
import java.awt.event.*;
import java.util.HashMap;
import java.util.Map;
import javax.swing.*;

/**
 * Calculator
 *
 * @author Pavel Porvatov
 */
public class Calculator extends JComponent {

    private static final String ZERO = "0";

    private static final char DECIMAL_SEPARATOR = ',';

    private final JTextField tfScreen = new JTextField();

    private enum State {

        INPUT_X,
        INPUT_X_FINISHED,
        INPUT_Y,
        INPUT_Y_FINISHED
    }

    private enum Operator {

        ADDITION,
        SUBTRACTION,
        MULTIPLICATION,
        DIVISION,
        SQRT,
        INVERSE,
        EQUALS
    }

    private final Map<Character, Operator> keyMapping = new HashMap<>();

    private String operand_x;

    private Operator operator;

    private State state;

    public Calculator() {
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | UnsupportedLookAndFeelException e) {
        }
        keyMapping.put('/', Operator.DIVISION);
        keyMapping.put('*', Operator.MULTIPLICATION);
        keyMapping.put('+', Operator.ADDITION);
        keyMapping.put('-', Operator.SUBTRACTION);
        keyMapping.put('\n', Operator.EQUALS);

        initUI();

        addKeyListener(new KeyAdapter() {
            @Override
            public void keyTyped(KeyEvent e) {
                char c = e.getKeyChar();

                if (Character.isDigit(c)) {
                    doProcessChar(c);
                } else if (c == '.' || c == ',') {
                    doProcessChar(DECIMAL_SEPARATOR);
                } else {
                    Operator operator = keyMapping.get(c);

                    if (operator != null) {
                        doProcessOperator(operator);
                    }
                }
            }

            @Override
            public void keyPressed(KeyEvent e) {
                switch (e.getKeyCode()) {
                    case KeyEvent.VK_BACK_SPACE:
                        doProcessBackspace();

                        break;

                    case KeyEvent.VK_DELETE:
                        doReset();
                }
            }
        });

        doReset();
    }

    private void initUI() {
        tfScreen.setHorizontalAlignment(JTextField.RIGHT);

        JButton btnBackspace = new JButton("Backspace");

        btnBackspace.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                doProcessBackspace();
            }
        });

        JButton btnReset = new JButton("C");

        btnReset.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                doReset();
            }
        });

        JPanel pnGridPanel = new JPanel(new GridLayout(1, 2, 8, 8));

        pnGridPanel.add(btnBackspace);
        pnGridPanel.add(btnReset);

        setLayout(new GridBagLayout());

        JButton btnSwapSign = new SquareButton("+/-");

        btnSwapSign.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                doSwapSign();
            }
        });

        addComp(tfScreen, 0, 0, 5, 1);
        addComp(pnGridPanel, 0, 1, 5, 1);

        addComp(new CharButton('7'), 0, 2, 1, 1);
        addComp(new CharButton('8'), 1, 2, 1, 1);
        addComp(new CharButton('9'), 2, 2, 1, 1);
        addComp(new OperatorButton(Operator.DIVISION, "/"), 3, 2, 1, 1);
        addComp(new OperatorButton(Operator.INVERSE, "1/x"), 4, 2, 1, 1);

        addComp(new CharButton('4'), 0, 3, 1, 1);
        addComp(new CharButton('5'), 1, 3, 1, 1);
        addComp(new CharButton('6'), 2, 3, 1, 1);
        addComp(new OperatorButton(Operator.MULTIPLICATION, "*"), 3, 3, 1, 1);
        addComp(new OperatorButton(Operator.SQRT, "sqrt"), 4, 3, 1, 1);

        addComp(new CharButton('1'), 0, 4, 1, 1);
        addComp(new CharButton('2'), 1, 4, 1, 1);
        addComp(new CharButton('3'), 2, 4, 1, 1);
        addComp(new OperatorButton(Operator.SUBTRACTION, "-"), 3, 4, 1, 1);

        addComp(new CharButton('0'), 0, 5, 1, 1);
        addComp(btnSwapSign, 1, 5, 1, 1);
        addComp(new CharButton(DECIMAL_SEPARATOR), 2, 5, 1, 1);
        addComp(new OperatorButton(Operator.ADDITION, "+"), 3, 5, 1, 1);
        addComp(new OperatorButton(Operator.EQUALS, "="), 4, 5, 1, 1);

        // Set focusable false
        resetFocusable(this);

        setFocusable(true);
    }

    private static void resetFocusable(Component component) {
        component.setFocusable(false);

        if (component instanceof Container) {
            for (Component c : ((Container) component).getComponents()) {
                resetFocusable(c);
            }
        }
    }

    private void doReset() {
        operand_x = null;
        operator = null;
        state = State.INPUT_X;

        tfScreen.setText(ZERO);
    }

    private void doProcessChar(char c) {
        String text = tfScreen.getText();

        String newValue;

        if (state == State.INPUT_X || state == State.INPUT_Y) {
            newValue = attachChar(text, c);

            if (stringToValue(newValue) == null) {
                return;
            }
        } else {
            newValue = attachChar("0", c);

            if (stringToValue(newValue) == null) {
                return;
            }

            if (operator == null) {
                operand_x = null;

                state = State.INPUT_X;
            } else {
                operand_x = text;

                state = State.INPUT_Y;
            }
        }

        tfScreen.setText(newValue);
    }

    private static String attachChar(String s, char c) {
        if (Character.isDigit(c)) {
            if (s.equals(ZERO)) {
                return Character.toString(c);
            }

            if (s.equals("-" + ZERO)) {
                return "-" + Character.toString(c);
            }

            return s + Character.toString(c);
        } else {
            return s + Character.toString(c);
        }
    }

    private void doSwapSign() {
        String text = tfScreen.getText();

        tfScreen.setText(text.startsWith("-") ? text.substring(1) : "-" + text);
    }

    private void doProcessBackspace() {
        String text = tfScreen.getText();

        if (text.length() > 0) {
            text = text.substring(0, text.length() - 1);
        }

        if (text.length() == 0 || text.equals("-")) {
            text = ZERO;
        }

        if (stringToValue(text) != null) {
            tfScreen.setText(text);
        }
    }

    private void doProcessOperator(Operator operator) {
        double y = stringToValue(tfScreen.getText());

        // Process unary operators
        boolean isUnary;

        switch (operator) {
            case SQRT:
                tfScreen.setText(valueToString(Math.sqrt(y)));

                isUnary = true;

                break;

            case INVERSE:
                tfScreen.setText(valueToString(1 / y));

                isUnary = true;

                break;

            default:
                isUnary = false;
        }

        if (isUnary) {
            if (state == State.INPUT_X) {
                state = State.INPUT_X_FINISHED;
            }

            if (state == State.INPUT_Y) {
                state = State.INPUT_Y_FINISHED;
            }

            return;
        }

        // Process binary operators
        if (state == State.INPUT_Y || state == State.INPUT_Y_FINISHED) {
            double x = stringToValue(operand_x);
            double result;

            switch (this.operator) {
                case ADDITION:
                    result = x + y;

                    break;

                case SUBTRACTION:
                    result = x - y;

                    break;

                case MULTIPLICATION:
                    result = x * y;

                    break;

                case DIVISION:
                    result = x / y;

                    break;

                default:
                    throw new IllegalStateException("Unsupported operation " + operator);
            }

            tfScreen.setText(valueToString(result));
        }

        this.operator = operator == Operator.EQUALS ? null : operator;
        operand_x = null;

        state = State.INPUT_X_FINISHED;
    }

    private static Double stringToValue(String value) {
        try {
            return new Double(value.replace(DECIMAL_SEPARATOR, '.'));
        } catch (NumberFormatException e) {
            // Continue convertion
        }

        if (value.endsWith(String.valueOf(DECIMAL_SEPARATOR))) {
            try {
                // Try convert uncompleted value
                return new Double(value.substring(0, value.length() - 1));
            } catch (NumberFormatException e) {
                // Continue convertion
            }
        }

        return null;
    }

    private static String valueToString(Double value) {
        if (value == null) {
            return ZERO;
        } else {
            String result = value.toString();

            if (result.endsWith(".0")) {
                result = result.substring(0, result.length() - 2);
            }

            if (result.equals("-0")) {
                result = ZERO;
            }

            return result;
        }
    }

    private void addComp(Component comp, int gridx, int gridy,
            int gridwidth, int gridheight) {
        add(comp, new GridBagConstraints(gridx, gridy, gridwidth, gridheight,
                1, 1, GridBagConstraints.CENTER, GridBagConstraints.BOTH,
                new Insets(4, 4, 4, 4), 0, 0));
    }

    private static class SquareButton extends JButton {

        private SquareButton(String text) {
            super(text);

            setMargin(new Insets(2, 0, 2, 0));
        }

        @Override
        public Dimension getMinimumSize() {
            Dimension result = super.getMinimumSize();

            if (result.width < result.height) {
                result.width = result.height;
            }

            return result;
        }

        @Override
        public Dimension getPreferredSize() {
            return getMinimumSize();
        }
    }

    private class CharButton extends SquareButton {

        private CharButton(final char c) {
            super(String.valueOf(c));

            addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    doProcessChar(c);
                }
            });
        }
    }

    private class OperatorButton extends SquareButton {

        private OperatorButton(final Operator operator, String text) {
            super(text);

            addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    doProcessOperator(operator);
                }
            });
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame f = new JFrame("Calculator");
                f.add(new Calculator());
                f.pack();
                f.setLocationRelativeTo(null);
                f.setVisible(true);
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            }
        });
    }
}
```

*s18.postimg.org/joot9k45h/Calculator.jpg

*Image Fill Demo*

The Base Class: Fill

The examples in this article use a simple base class called Fill that can be used to create reusable, combinable objects that paint a rectangular area. The Fill class is similar to Border -- it's really just an encapsulation of paint methods:


```
public class Fill {
    public void paintFill(Component c, Graphics g, Rectangle r) {...}
    public void paintFill(Component c, Graphics g) {...}
    public void paintFill(Component c, Graphics g, int x, ...) {...}
}
```

The first paintFill method just paints within the area defined by the rectangle r using the Graphics object g. Typically the component parameter, c, is the target of the Graphics object although it's not required to be. The other methods are convenience methods. The second one computes the coordinates of the component's insets rectangle and then passes them to the first method. The third one creates a Rectangle based on the parameters and passes it to the first method.

What the paintFill method paints can reflect the values of the specified component's properties. The default implementation of the paintFill method does this -- it just fills the specified rectangle with the component's background color.

To use a Fill object to paint a Swing component, you just create a subclass of the component's class and override the paintComponent method like this:


```
public void paintComponent(Graphics g) {
    Graphics gFill = g.create();
    myFillObject.paintFill(this, gFill);
    gFill.dispose();
}
```

Note that we've passed a copy of the Graphics object to the paintFill method. This insulates any other painting code we might add to the paintComponent method from changes made to the Graphics object by the paintFill method. 

Example: ImageFill

A more practical example of a Fill subclass is ImageFill, which paints a single BufferedImage. ImageFill's paintFill method scales the BufferedImage to match the width and height arguments. Since image scaling is expensive relative to painting, we cache a small number of the most recently used scaled copies of the original image. Caching scaled copies of the images will be a big benefit later on, when we apply the ImageFill class to painting borders.

To create an instance of an ImageFill object we need to create a BufferedImage. This can be done quite simply with the new (in JDK 1.4) ImageIO class:


```
BufferedImage image = ImageIO.read("background.jpg");
imageFill = new ImageFill(image);

The static ImageIO.read method reads the image file and creates the BufferedImage synchronously. There's no need for a MediaTracker or ImageObserver. The paintFill method for our ImageFill class looks like this:

public void paintFill(Component c, Graphics g, Rectangle r) {
    if ((r.width > 0) && (r.height > 0)) {
        BufferedImage fillImage = getFillImage(c, r.width, r.height);
        g.drawImage(fillImage, r.x, r.y, c);
    }
}
```

The private method getFillImage takes care of scaling and caching.

The screenshot below shows the TestImageFill demo application in action. Each panel is an instance of the TestImageFill class, which paints its interior with a single shared ImageFill object. To create new panels, just drag out a bounding rectangle with the mouse. 

Here is a Mouse drag demo that fills the Frame with Image fills!!
Run the program. Drag the Mouse top to down (diagonally), down to top (diagonally) on the Frame.
The Mouse drag draws the Image stretched using the coordinates specified by the user.


```
/**
 * Created with IntelliJ IDEA.
 * User: Sowndar
 * Date: 3/1/14
 * Time: 11:54 PM
 * To change this template use File | Settings | File Templates.
 */

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.net.URL;


class Fill {
    public void paintFill(Component c, Graphics g, Rectangle r) {
        // assert c, g, not null
        g.setColor(c.getBackground());
        g.fillRect(r.x, r.y, r.width, r.height);
    }

    public void paintFill(Container c, Graphics g) {
        // assert c, g, not null
        Insets insets = c.getInsets();
        int x = insets.left;
        int y = insets.top;
        int w = c.getWidth() - (insets.left + insets.right);
        int h = c.getHeight() - (insets.top + insets.bottom);
        paintFill(c, g, new Rectangle(x, y, w, h));
    }

    public void paintFill(Component c, Graphics g, int x, int y, int w, int h) {
        paintFill(c, g, new Rectangle(x, y, w, h));
    }
}


/**
 * Displays a single <code>BufferedImage</code>, scaled to fit the
 * <code>paintFill</code> rectangle.
 * <pre>
 *  BufferedImage image = ImageIO.read(new File("background.jpg"));
 *  final ImageFill imageFill = new ImageFill(image);
 *  JPanel p = new JPanel() {
 *      public c void paintComponent(Graphics g) {
 * 	    imageFill.paintFill(this, g);
 *    }
 *  };
 * </pre>
 * Note that animated gifs aren't supported as there's no image observer.
 */
class ImageFill extends Fill {
    private final static int IMAGE_CACHE_SIZE = 8;
    private BufferedImage image;
    private BufferedImage[] imageCache = new BufferedImage[IMAGE_CACHE_SIZE];
    private int imageCacheIndex = 0;


    /**
     * Creates an <code>ImageFill</code> that draws <i>image</i>
     * scaled to fit the <code>paintFill</code> rectangle
     * parameters.
     *
     * [MENTION=288550]see[/MENTION] #getImage
     * [MENTION=288550]see[/MENTION] #paintFill
     */
    public ImageFill(BufferedImage image) {
        this.image = image;
    }

    /**
     * Creates an "empty" ImageFill.  Before the ImageFill can be
     * drawn with the <code>paintFill</code> method, the
     * <code>image</code> property must be set.
     *
     * [MENTION=288550]see[/MENTION] #setImage
     * [MENTION=288550]see[/MENTION] #paintFill
     */
    public ImageFill() {
        this.image = null;
    }


    /**
     * Returns the image that the <code>paintFill</code> method draws.
     *
     * @return the value of the <code>image</code> property
     * [MENTION=288550]see[/MENTION] #setImage
     * [MENTION=288550]see[/MENTION] #paintFill
     */
    public BufferedImage getImage() {
        return image;
    }

    /**
     * Set the image that the <code>paintFill</code> method draws.
     *
     * [MENTION=9956]PARAM[/MENTION] image the new value of the <code>image</code> property
     * [MENTION=288550]see[/MENTION] #getImage
     * [MENTION=288550]see[/MENTION] #paintFill
     */
    public void setImage(BufferedImage image) {
        this.image = image;
        for (int i = 0; i < imageCache.length; i++) {
            imageCache[i] = null;
        }
    }


    /**
     * Returns the actual width of the <code>BufferedImage</code>
     * rendered by the <code>paintFill</code> method.  If the image
     * property hasn't been set, -1 is returned.
     *
     * @return the value of <code>getImage().getWidth()</code> or -1 if
     *         getImage() returns null
     * [MENTION=288550]see[/MENTION] #getHeight
     * [MENTION=288550]see[/MENTION] #setImage
     */
    public int getWidth() {
        BufferedImage image = getImage();
        return (image == null) ? -1 : image.getWidth();
    }

    /**
     * Returns the actual height of the <code>BufferedImage</code>
     * rendered by the <code>paintFill</code> method.  If the image
     * property hasn't been set, -1 is returned.
     *
     * @return the value of <code>getImage().getHeight()</code> or -1 if
     *         getImage() returns null
     * [MENTION=288550]see[/MENTION] #getWidth
     * [MENTION=288550]see[/MENTION] #setImage
     */
    public int getHeight() {
        BufferedImage image = getImage();
        return (image == null) ? -1 : image.getHeight();
    }


    /**
     * Create a copy of image scaled to width,height w,h and
     * add it to the null element of the imageCache array.  If
     * the imageCache array is full, then we replace the "least
     * recently used element", at imageCacheIndex.
     */
    private BufferedImage createScaledImage(Component c, int w, int h) {
        GraphicsConfiguration gc = c.getGraphicsConfiguration();
        BufferedImage newImage = gc.createCompatibleImage(w, h, Transparency.TRANSLUCENT);

        boolean cacheOverflow = true;
        for (int i = 0; i < imageCache.length; i++) {
            Image image = imageCache[i];
            if (image == null) {
                imageCache[i] = newImage;
                cacheOverflow = false;
                break;
            }
        }
        if (cacheOverflow) {
            imageCache[imageCacheIndex] = newImage;
            imageCacheIndex = (imageCacheIndex + 1) % imageCache.length;
        }

        Graphics g = newImage.getGraphics();
        int width = image.getWidth();
        int height = image.getHeight();
        g.drawImage(image, 0, 0, w, h, 0, 0, width, height, null);
        g.dispose();

        return newImage;
    }


    /**
     * Returns either the image itself or a cached scaled copy.
     */
    private BufferedImage getFillImage(Component c, int w, int h) {
        if ((w == getWidth()) && (h == getHeight())) {
            return image;
        }
        for (int i = 0; i < imageCache.length; i++) {
            BufferedImage cimage = imageCache[i];
            if (cimage == null) {
                break;
            }
            if ((cimage.getWidth(c) == w) && (cimage.getHeight(c) == h)) {
                return cimage;
            }
        }
        return createScaledImage(c, w, h);
    }


    /**
     * Draw the image at <i>r.x,r.y</i>, scaled to <i>r.width</i>
     * and <i>r.height</i>.
     */
    public void paintFill(Component c, Graphics g, Rectangle r) {
        if ((r.width > 0) && (r.height > 0)) {
            BufferedImage fillImage = getFillImage(c, r.width, r.height);
            g.drawImage(fillImage, r.x, r.y, c);

        }
    }
}


/**
 * Simple demonstration of the ImageFill class.  Scaled copies
 * of the background image can be created by dragging out rectangular
 * regions with the mouse.  Note that we don't draw the (scaled)
 * image during the drag, doing so is usally too slow because the
 * original image is quite large.
 */
public class TestImageFill extends JPanel {
    /**
     * Backing store for the showImage property.
     */
    private boolean showImage = true;

    /**
     * Initialized by the (first call to the) constructor.  This
     * Fill is used to paint the entire panel.
     */
    private static ImageFill imageFill = null;

    /**
     * Simple opaque line border - two concentric rectangles
     * drawn in the components foreground color, with a background
     * colored rectangle in between.
     */
    private static Border border = new Border() {
        public void paintBorder(Component c, Graphics g, int x, int y, int w, int h) {
            g.setColor(c.getForeground());
            g.drawRect(x, y, w - 1, h - 1);
            g.drawRect(x + 2, y + 2, w - 5, h - 5);
            g.setColor(c.getBackground());
            g.drawRect(x + 1, y + 1, w - 3, h - 3);
        }

        public Insets getBorderInsets(Component c) {
            return new Insets(3, 3, 3, 3); // top left bottom right
        }

        public boolean isBorderOpaque() {
            return true;
        }
    };


    /**
     * Hand press/drag/release by interactively resizing a child
     * TestImageFill instance.  During the drag we set the
     * showImage property to false to ensure that performance is
     * snappy.
     */
    private class MouseHandler implements MouseListener, MouseMotionListener {
        Point anchor = new Point();
        TestImageFill drag = null;

        public void mousePressed(MouseEvent e) {
            anchor.x = e.getX();
            anchor.y = e.getY();
        }

        public void mouseDragged(MouseEvent e) {
            int x = Math.min(anchor.x, e.getX());
            int y = Math.min(anchor.y, e.getY());
            int w = Math.abs(anchor.x - e.getX());
            int h = Math.abs(anchor.y - e.getY());
            if (drag == null) {
                drag = new TestImageFill();
                drag.setShowImage(true);
                add(drag, 0);
            }
            if ((w > 0) && (h > 0)) {
                drag.setBounds(x, y, w, h);
            }
        }

        public void mouseReleased(MouseEvent e) {
            if (drag != null) {
                drag.setShowImage(true);
                drag = null;
            }
        }

        public void mouseClicked(MouseEvent e) {
        }

        public void mouseEntered(MouseEvent e) {
        }

        public void mouseExited(MouseEvent e) {
        }

        public void mouseMoved(MouseEvent e) {
        }
    }


    /**
     * Create a TestImageFill instance that uses an ImageFill
     * object to draw "background.jpg".
     */
    public TestImageFill() {
        super(null);

        setBorder(border);
        setForeground(Color.black);
        setBackground(Color.white);

        MouseHandler mouseHandler = new MouseHandler();
        addMouseListener(mouseHandler);
        addMouseMotionListener(mouseHandler);

        if (imageFill == null) {

            try {
                URL file = new File("Images/Megan Fox1.jpg").toURL();
                BufferedImage image = ImageIO.read(file);
                imageFill = new ImageFill(image);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    /**
     * If false, the background.jpg image, isn't drawn.  This
     * property is temporarily set to false while press/drag/release
     * gesture is underway.  The default value of this property
     * is true.
     *
     * [MENTION=288550]see[/MENTION] #getShowImage
     */
    public void setShowImage(boolean showImage) {
        this.showImage = showImage;
        repaint();
    }

    /**
     * Returns true if the image should be drawn.
     *
     * @return the value of the showImage property.
     * [MENTION=288550]see[/MENTION] #setShowImage
     */
    public boolean getShowImage() {
        return showImage;
    }


    /**
     * Fills the component with the background.jpg image
     * using an ImageFill object.
     */
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (getShowImage()) {
            Graphics gFill = g.create();
            imageFill.paintFill(this, gFill);
            gFill.dispose();
        }
    }

    /**
     * The preferred size is equal to the size of the ImageFill
     * created for the "background.jpg" image plus the insets space
     * allocated to the border.
     *
     * @returns the size of the background image and the border.
     */
    public Dimension getPreferredSize() {
        Insets insets = getInsets();
        int w = imageFill.getWidth() + insets.left + insets.right;
        int h = imageFill.getHeight() + insets.top + insets.bottom;
        return new Dimension(w, h);
    }

    /**
     * Overridden to return false which means that children may overlap.
     * <p/>
     * This is a wee bit obscure.
     */
    public boolean isOptimizedDrawingEnabled() {
        return false;
    }


    public static void main(String[] args) throws Exception {
        JFrame f = new JFrame("Image Fill");

        f.getContentPane().add(new TestImageFill(), BorderLayout.CENTER);

        WindowListener l = new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        };
        f.addWindowListener(l);
        f.pack();
        f.setVisible(true);
    }
}
```

[img=*s23.postimg.org/8ii0elfqv/Test_Image_Fill.jpg]

Here is a demo that creates a custom PopupMenu with a gradient pattern.
Class ImageCreator is a custom class that extends JMenuItem.
HeaderMenuItem class  is another helper class that draws the gradient pattern tiny-squares.
This produces the great-looking effect!!


```
/**
 * Created with IntelliJ IDEA.
 * User: Sowndar
 * Date: 10/4/14
 * Time: 5:22 PM
 * To change this template use File | Settings | File Templates.
 */
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
/**
 *
 * @author Sowndar
 */

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.UIManager.LookAndFeelInfo;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;

public class PopupMenuDemo2 extends JFrame {

    String path = "Images/";
    String []imgName;
    int MAX;
    Image[] icon;
    Image[] normal;
    JLabel label = new JLabel("", SwingConstants.CENTER);
    int scrWidth, scrHeight;
    JPopupMenu popup = new JPopupMenu("Choose your Picture");
    HeaderMenuItem []mItem;
    Dimension menuDim = new Dimension(236, 76);
    //Key Accelerator for the menu
    KeyStroke[] accelerator = {
            KeyStroke.getKeyStroke(KeyEvent.VK_A, KeyEvent.CTRL_MASK),
            KeyStroke.getKeyStroke(KeyEvent.VK_B, KeyEvent.CTRL_MASK),
            KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.CTRL_MASK),
            KeyStroke.getKeyStroke(KeyEvent.VK_E, KeyEvent.CTRL_MASK),
            KeyStroke.getKeyStroke(KeyEvent.VK_H, KeyEvent.CTRL_MASK),
            KeyStroke.getKeyStroke(KeyEvent.VK_K, KeyEvent.CTRL_MASK),
            KeyStroke.getKeyStroke(KeyEvent.VK_Y, KeyEvent.CTRL_MASK),
            KeyStroke.getKeyStroke(KeyEvent.VK_L, KeyEvent.CTRL_MASK),
            KeyStroke.getKeyStroke(KeyEvent.VK_M, KeyEvent.CTRL_MASK),
            KeyStroke.getKeyStroke(KeyEvent.VK_O, KeyEvent.CTRL_MASK),
            KeyStroke.getKeyStroke(KeyEvent.VK_N, KeyEvent.CTRL_MASK),
            KeyStroke.getKeyStroke(KeyEvent.VK_H, KeyEvent.CTRL_MASK)
    };

    public PopupMenuDemo2() {
        try {
            for (LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
            // If Nimbus is not available, you can set the GUI to another look and feel.
        }
        Dimension scrDim = Toolkit.getDefaultToolkit().getScreenSize();
        imgName = new File(path).list(new FilenameFilter() {
            String[] readFormat = ImageIO.getReaderFormatNames();

            @Override
            public boolean accept(File dir, String name) {
                name = name.toLowerCase();
                for (int i = 0; i < readFormat.length; i++) {
                    if (name.endsWith(readFormat[i])) {
                        return true;
                    }
                }
                return false;
            }
        });
        MAX = imgName.length;
        if(MAX > 12) {
            MAX = 12;
        }
        icon = new Image[MAX];
        normal = new Image[MAX];
        mItem = new HeaderMenuItem[MAX];
        scrWidth = scrDim.width;
        scrHeight = scrDim.height;
        for (int i = 0; i < MAX; i++) {
            //Load all the Images
            normal[i] = getImage(path + imgName[i]);
            // Create all Icons dynamically
            icon[i] = mini(normal[i]);
            // Enlarge the Image to fill the entire screen
            normal[i] = enlarged(normal[i]);
            mItem[i] = new HeaderMenuItem(imgName[i].substring(0, imgName[i].lastIndexOf(".")), new ImageIcon(icon[i]));
            mItem[i].setPreferredSize(menuDim);
            mItem[i].setIcon(new ImageIcon(icon[i]));
            mItem[i].setAccelerator(accelerator[i]);
            mItem[i].addActionListener(new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent ae) {
                    Object source = ae.getActionCommand();
                    int gIndex = 0;
                    // Find the Index at which this name occurs
                    for (int j = 0; j < imgName.length; j++) {
                        if (source.equals(imgName[j])) {
                            gIndex = j;
                        }
                    }
                    label.setIcon(new ImageIcon(normal[gIndex]));
                    //label.setToolTipText(text[gIndex]);
                }
            });
            popup.add(mItem[i]);
            popup.addSeparator();

        }
        // Show the first Image in the array
        label.setIcon(new ImageIcon(normal[0]));
        label.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent me) {
                if (me.isPopupTrigger()) {
                    //Show the Popup now
                    popup.show(me.getComponent(), me.getX(), me.getY());
                }
            }

            @Override
            public void mouseReleased(MouseEvent me) {
                if (me.isPopupTrigger()) {
                    popup.show(me.getComponent(), me.getX(), me.getY());
                }
            }
        });

        add(new JScrollPane(label));
        setSize(scrDim);
        setTitle("PopupMenuDemo2");
        setVisible(true);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    public Image getImage(String fileName) {
        try
        {
           return ImageIO.read(new File(fileName));
        } catch (IOException ioe) {
            System.out.println("Error loading Image : " + fileName + "!!");
        }
        return null;
    }

    public Image mini(Image image) {
        if (image != null) {
            int imgWidth = image.getWidth(null);
            int imgHeight = image.getHeight(null);
            int maxWidth, maxHeight;
            double aspect = ((double) imgWidth) / ((double) imgHeight);

            if (aspect > 1.3333) {
                // Fix the width as maxWidth, calculate the maxHeight
                maxWidth = 70;
                maxHeight = (int) (((double) maxWidth) / aspect);
            } else {
                // Fix the height as iconHeight, calculate the maxWidth for this
                maxHeight = 45;
                maxWidth = (int) (((double) maxHeight) * aspect);
            }
            return image.getScaledInstance(maxWidth, maxHeight, Image.SCALE_SMOOTH);
        }
        return null;
    }

    public Image enlarged(Image image) {
        if (image != null) {
            int imgWidth = image.getWidth(null);
            int imgHeight = image.getHeight(null);
            double aspect = ((double) imgWidth) / ((double) imgHeight);
            int maxWidth, maxHeight;
            if (imgWidth > imgHeight) {
                maxWidth = scrWidth;
                maxHeight = (int) (((double) maxWidth) / aspect);
            } else {
                maxHeight = image.getHeight(null);
                maxWidth = (int) (((double) maxHeight) * aspect);
            }
            return image.getScaledInstance(maxWidth, maxHeight, Image.SCALE_SMOOTH);
        }

        return null;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new PopupMenuDemo2();
            }
        });
    }
}


class ImageCreator extends JMenuItem {

    public static final Color mainMidColor = new Color(0, 64, 196);
    public static final Color mainUltraDarkColor = new Color(0, 0, 64);
    public static final int CUBE_DIMENSION = 5;
    private static final long serialVersionUID = 1L;

    //getGradientCubesImage function. This function is pretty straightforward:
    public static BufferedImage getGradientCubesImage(int width,
                                                      int height, Color leftColor, Color rightColor,
                                                      int transitionStart, int transitionEnd) {

        //First it fills the image with background. All pixels to the left of the transitionStart column are painted with leftColor,
        // all pixels to the right of the transitionEnd column are painted with rightColor, and the pixels in the transition area are
        // filled with the gradient:
        BufferedImage image = new BufferedImage(width, height,
                BufferedImage.TYPE_INT_ARGB);

        Graphics2D g2d = (Graphics2D) image.getGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);

        GradientPaint gradient = new GradientPaint(transitionStart, 0,
                leftColor, transitionEnd, 0, rightColor);
        g2d.setPaint(gradient);
        g2d.fillRect(transitionStart, 0,
                transitionEnd - transitionStart, height);

        g2d.setColor(leftColor);
        g2d.fillRect(0, 0, transitionStart, height);

        g2d.setColor(rightColor);
        g2d.fillRect(transitionEnd, 0, width - transitionEnd, height);

        //Now, the transition area is covered with squares of the predefined size. We compute how many rows and
        //columns of squares we have to paint in order to completely cover the transition area:
        //The computation itself:
        int cubeCountY = height / ImageCreator.CUBE_DIMENSION;
        int cubeCountX = 1 + (transitionEnd - transitionStart)
                / ImageCreator.CUBE_DIMENSION;
        int cubeStartY = (height % ImageCreator.CUBE_DIMENSION) / 2;
        int cubeStartX = transitionStart
                - (ImageCreator.CUBE_DIMENSION
                - ((transitionEnd - transitionStart) % ImageCreator.CUBE_DIMENSION));

        //Now, we iterate over cube rows and columns. A random decision is made - if random number is less than 0.5, no cube is drawn at that location:
        for (int col = 0; col < cubeCountX; col++) {
            for (int row = 0; row < cubeCountY; row++) {
                // decide if we should put a cube
                if (Math.random() < 0.5) {
                    continue;
                }
                //Now, a semi-random color is chosen. The base color is interpolated according to the current X position in the transition area. This color is then perturbed using a random number:

                // Make semi-random choice of color. It should lie
                // close to the interpolated color, but still appear
                // random
                double coef = 1.0 - (((double) col / (double) cubeCountX)
                        + 0.9 * (Math.random() - 0.5));
                coef = Math.max(0.0, coef);
                coef = Math.min(1.0, coef);

                // The interpolated color is computed:
                // Compute RGB components
                int r = (int) (coef * leftColor.getRed() + (1.0 - coef)
                        * rightColor.getRed());
                int g = (int) (coef * leftColor.getGreen() + (1.0 - coef)
                        * rightColor.getGreen());
                int b = (int) (coef * leftColor.getBlue() + (1.0 - coef)
                        * rightColor.getBlue());

                //And the corresponding square is filled:
                // fill cube
                g2d.setColor(new Color(r, g, b));
                g2d.fillRect(cubeStartX + col * ImageCreator.CUBE_DIMENSION,
                        cubeStartY + row * ImageCreator.CUBE_DIMENSION,
                        ImageCreator.CUBE_DIMENSION,
                        ImageCreator.CUBE_DIMENSION);

                //The last thing - the border of the square is painted with slightly brighter color (10% closer to the pure white):
                // draw cube's border in slightly brighter color
                g2d.setColor(new Color(
                        255 - (int) (0.95 * (255 - r)),
                        255 - (int) (0.9 * (255 - g)),
                        255 - (int) (0.9 * (255 - b))));
                g2d.drawRect(cubeStartX + col * ImageCreator.CUBE_DIMENSION,
                        cubeStartY + row * ImageCreator.CUBE_DIMENSION,
                        ImageCreator.CUBE_DIMENSION,
                        ImageCreator.CUBE_DIMENSION);
            }
        }
        return image;
    }
    //The last thing you can do with your regular menu item - draw it in anti-aliased text. All you have to do is extend the JMenuItem class and override the paintComponent function:

    @Override
    protected final void paintComponent(Graphics g) {
        Graphics2D g2d = (Graphics2D) g;
        Object oldHint = g2d.getRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING);
        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        super.paintComponent(g2d);
        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                oldHint);
    }
}

class HeaderMenuItem extends JMenuItem {

    private static final Font HEADER_FONT = new Font("Arial", Font.BOLD, 14);
    private static final long serialVersionUID = 1L;
    private ImageIcon icon;

    //The constructor is straightforward
    public HeaderMenuItem(String text) {
        super(text);
        this.setEnabled(true);
    }

    public HeaderMenuItem(String text, ImageIcon icon) {
        super(text);
        this.icon = icon;
        this.setEnabled(true);
    }

    //The main functionality lies in the paintComponent overriden function. It sets anti-aliasing hint on the graphics context, paints the background
//image (with the corresponding dimension) and paints the text with shadow:
    @Override
    protected final void paintComponent(Graphics g) {
        Graphics2D graphics = (Graphics2D) g;
        graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        // paint background image
        graphics.drawImage(ImageCreator.getGradientCubesImage(
                this.getWidth(), this.getHeight(),
                ImageCreator.mainMidColor,
                ImageCreator.mainUltraDarkColor,
                (int) (0.7 * this.getWidth()),
                (int) (0.9 * this.getWidth())), 0, 0, null);
        graphics.setFont(HEADER_FONT);
        // place the text slightly to the left of the center
        int x = (this.getWidth() - graphics.getFontMetrics().stringWidth(
                this.getText())) / 3; // 4
        int y = (int) (graphics.getFontMetrics().getLineMetrics(
                this.getText(), graphics).getHeight());

        // paint the text with black shadow
        graphics.setColor(Color.black);
        graphics.drawString(this.getText(), x + 1, y + 1);
        graphics.setColor(Color.white);
        graphics.drawString(this.getText(), x, y);
        // Align the Image in the Center
        graphics.drawImage(icon.getImage(), x + (getWidth() - icon.getIconWidth()) / 4, y + 1, this);
    }
}
```

*s27.postimg.org/usn4hfuzj/Popup_Menu_Demo2.jpg

The next demo shows how a JTextPane can be used to display the Java Source code. Highlighting the Java keywords in the 
document. We initialize the HashMap to store all the Java keywords. The method createAttributeSet() creates various
attributes (different color highlights). The method loadFile() loads the Java Source code from the disk. It parses
the given Java code checking character-by-character. And finally produces the lovely output shows all the Java keywords
in a document in a highlight color.


```
/**
 * Created with IntelliJ IDEA.
 * User: Sowndar
 * Date: 7/31/14
 * Time: 1:42 PM
 * To change this template use File | Settings | File Templates.
 */

import javax.swing.*;
import javax.swing.text.*;
import java.awt.*;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;

public class TextPaneDemo2 extends JFrame {
    private static final String STYLE_KEYWORD = "keyword";
    private static final String STYLE_LITERAL = "literal";
    private static final String STYLE_TYPE = "type";
    private static final String STYLE_COMMENT = "comment";
    private static final String STYLE_STRING = "string";

    private HashMap<Object, Object> stylesMap;
    private JTextPane textPane;
    private Document document;
    private HashMap<Object, Object> tokens;

    public TextPaneDemo2(String fileName) {
        super("TextPaneDemo2");
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {
            // Do nothing
        }
        initTokens();
        initAttributeSets();
        initUI();

        try {
            loadFile(fileName);
        } catch (IOException e) {

        }

        Dimension scrDim = Toolkit.getDefaultToolkit().getScreenSize();
        setSize(scrDim.width / 2, scrDim.height);
        setVisible(true);
        setLocationRelativeTo(null);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    private void loadFile(String fileName) throws IOException {
        File file = new File(fileName);
        if (!file.exists()) {
            System.err.println("The specified file doesn't exist!!");
            System.exit(-1);
        }
        FileReader in = new FileReader(file);

        int c = -1;
        boolean startWord = true;
        StringBuffer segment = new StringBuffer();

        while ((c = in.read()) != -1) {
            if (startWord) {
                if (Character.isJavaIdentifierStart(c)) {
                    segment.append((char) c);
                    startWord = false;
                } else if (c == '/') {
                    StringBuffer comment = new StringBuffer();
                    comment.append('/');

                    c = in.read();
                    comment.append((char) c);
                    if (c == '/') {
                        do {
                            c = in.read();
                            if (c != -1) {
                                if (c == '#') {
                                    StringBuffer image = new StringBuffer();
                                    do {
                                        c = in.read();
                                        if (c != '#') {
                                            image.append((char) c);
                                        }
                                    } while (c != '#');
                                    try {
                                        document.insertString(document.getLength(), comment.toString(), (AttributeSet) stylesMap.get(STYLE_COMMENT));
                                    } catch (BadLocationException e) {
                                    }
                                    comment = new StringBuffer();
                                    Icon imageIcon = new ImageIcon(image.toString());
                                    SimpleAttributeSet set = new SimpleAttributeSet();
                                    StyleConstants.setIcon(set, imageIcon);
                                    try {
                                        document.insertString(document.getLength(), " ", set);
                                    } catch (BadLocationException e) {
                                    }
                                } else {
                                    comment.append((char) c);
                                }
                            }
                        } while (c != -1 && c != '\n');
                    } else if (c == '*') {
                        boolean seenStar = false;
                        do {
                            c = in.read();
                            if (c == '*') {
                                seenStar = true;
                            } else if (c != '/') {
                                seenStar = false;
                            }
                            comment.append((char) c);
                        } while (!(seenStar && c == '/'));
                    }

                    try {
                        document.insertString(document.getLength(), comment.toString(), (AttributeSet) stylesMap.get(STYLE_COMMENT));
                    } catch (BadLocationException e) {
                    }
                } else if (c == '"') {
                    StringBuffer string = new StringBuffer();
                    string.append('"');
                    boolean noEscape = true;
                    do {
                        c = in.read();
                        string.append((char) c);
                        noEscape = (c != '\\');
                    } while (!(noEscape && c == '"'));
                    try {
                        document.insertString(document.getLength(), string.toString(), (AttributeSet) stylesMap.get(STYLE_STRING));
                    } catch (BadLocationException e) {
                    }
                } else {
                    try {
                        document.insertString(document.getLength(), String.valueOf((char) c), null);
                    } catch (BadLocationException e) {
                    }
                }
            } else {
                if (Character.isJavaIdentifierPart(c)) {
                    segment.append((char) c);
                } else {
                    try {
                        String fragment = segment.toString();
                        document.insertString(document.getLength(), fragment, getStyle(fragment));
                        document.insertString(document.getLength(), String.valueOf((char) c), null);
                    } catch (BadLocationException e) {
                    }

                    segment = new StringBuffer();
                    startWord = true;
                }
            }
        }
        in.close();
    }

    private SimpleAttributeSet getStyle(String word) {
        Object key = tokens.get(word);
        if (key != null) {
            return (SimpleAttributeSet) stylesMap.get(key);
        }
        return null;
    }

    private void initTokens() {
        tokens = new HashMap<>();
        tokens.put("package", STYLE_KEYWORD);
        tokens.put("import", STYLE_KEYWORD);
        tokens.put("byte", STYLE_TYPE);
        tokens.put("char", STYLE_TYPE);
        tokens.put("short", STYLE_TYPE);
        tokens.put("int", STYLE_TYPE);
        tokens.put("long", STYLE_TYPE);
        tokens.put("float", STYLE_TYPE);
        tokens.put("double", STYLE_TYPE);
        tokens.put("boolean", STYLE_TYPE);
        tokens.put("void", STYLE_TYPE);
        tokens.put("String", STYLE_TYPE);
        tokens.put("enum", STYLE_KEYWORD);
        tokens.put("class", STYLE_KEYWORD);
        tokens.put("interface", STYLE_KEYWORD);
        tokens.put("abstract", STYLE_KEYWORD);
        tokens.put("assert", STYLE_KEYWORD);
        tokens.put("final", STYLE_KEYWORD);
        tokens.put("strictfp", STYLE_KEYWORD);
        tokens.put("private", STYLE_KEYWORD);
        tokens.put("protected", STYLE_KEYWORD);
        tokens.put("public", STYLE_KEYWORD);
        tokens.put("static", STYLE_KEYWORD);
        tokens.put("synchronized", STYLE_KEYWORD);
        tokens.put("native", STYLE_KEYWORD);
        tokens.put("volatile", STYLE_KEYWORD);
        tokens.put("transient", STYLE_KEYWORD);
        tokens.put("break", STYLE_KEYWORD);
        tokens.put("case", STYLE_KEYWORD);
        tokens.put("continue", STYLE_KEYWORD);
        tokens.put("default", STYLE_KEYWORD);
        tokens.put("do", STYLE_KEYWORD);
        tokens.put("else", STYLE_KEYWORD);
        tokens.put("for", STYLE_KEYWORD);
        tokens.put("if", STYLE_KEYWORD);
        tokens.put("instanceof", STYLE_KEYWORD);
        tokens.put("new", STYLE_KEYWORD);
        tokens.put("return", STYLE_KEYWORD);
        tokens.put("switch", STYLE_KEYWORD);
        tokens.put("while", STYLE_KEYWORD);
        tokens.put("throw", STYLE_KEYWORD);
        tokens.put("try", STYLE_KEYWORD);
        tokens.put("catch", STYLE_KEYWORD);
        tokens.put("extends", STYLE_KEYWORD);
        tokens.put("finally", STYLE_KEYWORD);
        tokens.put("implements", STYLE_KEYWORD);
        tokens.put("throws", STYLE_KEYWORD);
        tokens.put("this", STYLE_LITERAL);
        tokens.put("null", STYLE_LITERAL);
        tokens.put("super", STYLE_LITERAL);
        tokens.put("true", STYLE_LITERAL);
        tokens.put("false", STYLE_LITERAL);
    }

    private void initUI() {
        textPane = new JTextPane();
        textPane.setPreferredSize(new Dimension(640, 480));
        textPane.setFont(new Font("Monospaced", Font.PLAIN, 12));
        document = textPane.getDocument();

        getContentPane().add(BorderLayout.CENTER, new JScrollPane(textPane));
    }

    private void initAttributeSets() {
        stylesMap = new HashMap<>();
        stylesMap.put(STYLE_KEYWORD, createAttributeSet(Color.magenta.darker().darker(), true, false));
        stylesMap.put(STYLE_LITERAL, createAttributeSet(new Color(101, 0, 153), true, false));
        stylesMap.put(STYLE_TYPE, createAttributeSet(Color.blue, false, false));
        stylesMap.put(STYLE_COMMENT, createAttributeSet(new Color(14, 153, 11), false, true));
        stylesMap.put(STYLE_STRING, createAttributeSet(new Color(14, 16, 255), false, false));
    }

    private SimpleAttributeSet createAttributeSet(Color color, boolean bold, boolean italic) {
        SimpleAttributeSet style = new SimpleAttributeSet();
        style.addAttribute(StyleConstants.Foreground, color);
        StyleConstants.setBold(style, bold);
        StyleConstants.setItalic(style, italic);
        return style;
    }

    public static void main(final String[] args) {
        if (args.length > 0) {
            SwingUtilities.invokeLater(new Runnable() {
                @Override
                public void run() {
                    new TextPaneDemo2(args[0]);
                }
            });
        } else {
            System.out.println("Usage: java TextPaneDemo2 <filename.java>");
        }

    }
}
```

*s8.postimg.org/q2as2acb5/Text_Pane_Demo2.jpg

*JScrollPane with Preview Corner*

In Java Swing-based GUIs, the JScrollPane class is commonly used to display large-sized components in a smaller screen area. In this UI mechanism, the component is placed inside a scroll pane, with both horizontal and vertical scrollbars for scrolling.
This approach is useful and good enough for small-sized components like a file tree explorer or a text editor. However, the scroll pane has its own limitations if the canvass is large. For example, while editing a picture or editing a UML diagram, or 
while defining a large E-R model, it is not easy for the user to keep the complete model in mind, which is essential for easy scrolling. Also, it is cumbersome to adjust the view port diagonally as a user needs to adjust both horizontal and vertical 
scrollbars separately.

PreviewCorner, a generic scroll-pane previewer, provides an alternate way to scan a large component contained in a JScrollPane (see Figure 1). It provides a miniature of the contained component at the scroll pane's corner and allows easy scrolling, 
simply by moving the mouse over a miniature of the component. It is generic and can be added to any JScrollPane with minimal effort by adding a couple of lines of code. This approach makes it a lot easier for browsing and editing a component having
a large canvass (or area).

To implement the functionality, the PreviewCorner component must create a miniature of the component contained in the JScrollPane. Since we want to make the component generic, the miniaturization code should also be as generic as possible. Fortunately,
it is very easy in Java Swing to create a miniature of a component. The key is the paint (Graphics g) method in the component class. This method paints the component using the input graphics context. This method can be used to create an image out of any
Java component. This code snippet shows how to use this method to create an image from a Component object:


```
public static BufferedImage 
  convertComponentToImage(
  Component c) {
  Dimension size = c.getSize();
  // Create a buffered image 
  // equal to the size of the 
  // component.
  BufferedImage bufferedImage = 
    new BufferedImage(
    size.width, size.height, 
    BufferedImage.TYPE_INT_RGB);
  // Get the graphics context of 
  // the image
  Graphics bufferedGraphics = 
    bufferedImage.
    createGraphics();
  // Request the component to 
  // paint itself on to the 
  // image's graphics. This 
  // method would not work if 
  // the component is not yet 
  // visualized.
  c.paint(bufferedGraphics);
  return bufferedImage;
}
```

The returned image can be scaled to a smaller size as required by using the getScaledInstance() method in the Image class:


```
getScaledInstance(
  zoomWindowImageWidth,
  zoomWindowImageHeight,
  Image.SCALE_SMOOTH);
```

The last parameter would ensure that the image-scaling algorithm used will give more priority to smoothness than scaling speed.
We could use the Image.SCALE_FAST option to improve the speed of scaling.

As you move the cursor over the bottom corner, the Label is correspondingly scrolled to match the preview Component.


```
/**
 * Created with IntelliJ IDEA.
 * User: Sowndar
 * Date: 8/7/14
 * Time: 2:38 PM
 * To change this template use File | Settings | File Templates.
 */

import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

public class PreviewScroller extends JFrame {

    private Image image;

    public PreviewScroller(String title, Image userImage) {
        super(title);
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | UnsupportedLookAndFeelException e) {
            // Do nothing
        }
        image = userImage;
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        JScrollPane imagePane = new JScrollPane(
                new JLabel(new ImageIcon(image)));

        PreviewCorner pCorner = new PreviewCorner(
                imagePane,  createIcon(), true, JScrollPane.LOWER_RIGHT_CORNER);
        imagePane.setCorner(JScrollPane.LOWER_RIGHT_CORNER, pCorner);

        add(imagePane);
        setSize(700, 500);
        setVisible(true);
        // Click the Preview button programmatically
        pCorner.doClick();
    }

    //Create an Icon
    public ImageIcon createIcon() {
        BufferedImage buff = new BufferedImage(16, 16, BufferedImage.TYPE_INT_RGB);
        Graphics gr = buff.getGraphics();
        gr.setColor(Color.blue);
        int num = 4;
        gr.fillRect(0, 0, num * num, num * num);
        gr.setColor(Color.yellow);
        gr.fillArc(num, num, (num * num) - (num * 2), (num * num) - (num * 2), 0, 360);
        gr.dispose();
        return new ImageIcon(buff);
    }

    public static BufferedImage getImage(String fileName) {
        try {
            return ImageIO.read(new File(fileName));
        } catch (IOException ioe) {
            System.err.println("Error loading Image!!");
            System.exit(-1);
        }
        return null;
    }

    public static void main(final String[] args) throws IOException {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                if (args.length != 1) {
                    System.err.println("Usage: java SampleApp <ImageFilePath>");
                    System.exit(-1);
                }
                Image myImage = getImage(args[0]);
                new PreviewScroller("Preview Scroller", myImage);
            }
        });
    }
}


/**
 * This is a button which is designed to be the corner component of a
 * <code>JScrollPane</code>. It triggers a popup menu which holds a scaled image
 * of the component contained inside the
 * <code>JScrollPane</code>.
 */
class PreviewCorner extends JButton implements ActionListener {

    private String corner;
    private PreviewPopup previewPopup;

    /**
     * [MENTION=9956]PARAM[/MENTION] scrollPane the <code>JScrollPane</code> to preview
     * [MENTION=9956]PARAM[/MENTION] zoomIcon the icon to use for the button
     * [MENTION=9956]PARAM[/MENTION] doCloseAfterClick When <code>true</code> the preview popup menu is
     * closed on mouse click.
     * [MENTION=9956]PARAM[/MENTION] corner Supposed to be one of the four corners of a
     * <code>JScrollPane</code>, like
     * <code>JScrollPane.LOWER_RIGHT_CORNER</code> for example, which should
     * match the position of the corner component of the scroll pane. Note: If
     * this parameter is not set correctly,
     * <code>JScrollPane.UPPER_LEFT_CORNER</code> will be used instead.
     */
    public PreviewCorner(
            JScrollPane scrollPane,
            ImageIcon zoomIcon,
            boolean doCloseAfterClick,
            String corner) {

        super(zoomIcon);
        this.corner = corner;

        // Creates the popup menu, containing the scaled image of the component.
        previewPopup = new PreviewPopup(scrollPane, doCloseAfterClick);

        setToolTipText("View a miniature of scrollpane content and navigate");

        // The action listener is used to trigger the popup menu.
        addActionListener(this);

    }

    public PreviewCorner(
            JScrollPane scrollPane,
            ImageIcon zoomIcon,
            String corner) {

        this(scrollPane, zoomIcon, false, corner);

    }

    @Override
    public void actionPerformed(ActionEvent e) {

        previewPopup.showUpInCorner(this, corner);
    }

}

class PreviewPopup extends JPopupMenu implements MouseListener, MouseMotionListener {

    private JScrollPane scrollPane;
    private JViewport viewPort;
    private JLabel zoomWindow; // the JLabel containing the scaled image
    private JLabel cursorLabel; // the JLabel mimicking the fake rectangle cursor

    // This component will hold both JLabels zoomWindow and cursorLabel,
    // the latter on top of the other.
    private JLayeredPane layeredPane;
    private int iconWidth;
    private int iconHeight;
    private boolean doCloseAfterClick;
    int ratio;
    // DELTA is the space between the scroll pane and the preview popup menu.
    private static int DELTA = 5;
    // SCALEFACTOR is the scale factor between the previewed component
    // and the viewport.
    private static int SCALEFACTOR = 4;
    private JLabel label;
    int vpWidth, vpHeight;

    public PreviewPopup(JScrollPane scroller, boolean doClick) {

        this.setBorder(BorderFactory.createEtchedBorder());

        doCloseAfterClick = doClick;
        scrollPane = scroller;
        viewPort = scrollPane.getViewport();

        zoomWindow = new JLabel();
        cursorLabel = createCursor();

        layeredPane = new JLayeredPane();

        layeredPane.add(zoomWindow, new Integer(0));
        layeredPane.add(cursorLabel, new Integer(1));

        // Creates a blank transparent cursor to be used as the cursor of
        // the popup menu.
        BufferedImage bim
                = new BufferedImage(1, 1, BufferedImage.TYPE_4BYTE_ABGR);
        setCursor(
                getToolkit().createCustomCursor(bim, (new Point(0, 0)), "HiddenM"));

        this.add(layeredPane);

        // Adds the mouse input listeners to the layeredPane to scroll the
        // viewport and to move the fake cursor (cursorLabel).
        layeredPane.addMouseListener(this);
        layeredPane.addMouseMotionListener(this);
    }

    /**
     * By default, the right corner of a popup menu is positionned at the right
     * of a mouse click. What we want is to have the preview popup menu
     * positionned <i>inside</i> the scroll pane, near the corner component. The
     * purpose of this method is to display the scaled image of the component of
     * the scroll pane, and to calculate the correct position of the preview
     * popup menu.
     */
    public void showUpInCorner(Component c, String corner) {

        if (viewPort.getComponentCount() == 0) {
            return;
        }
        // Get the Component this viewport holds
        label = (JLabel) viewPort.getComponent(0);
        vpWidth = viewPort.getWidth();
        vpHeight = viewPort.getHeight();
        if (vpWidth < (vpHeight * SCALEFACTOR)) {
            ratio
                    = label.getWidth()
                    / (vpWidth / SCALEFACTOR);
        } else {
            ratio
                    = label.getHeight()
                    / (vpHeight / SCALEFACTOR);
        }

        int zoomWidth = label.getWidth() / ratio;
        int zoomHeight = label.getHeight() / ratio;
        Image capture = captureComponentViewAsBufferedImage(label);
        // Scale the Image smoothly
        Image componentImage = capture.getScaledInstance(zoomWidth, zoomHeight, Image.SCALE_SMOOTH);

        // Converts the Image to an ImageIcon to be used with a JLabel.
        ImageIcon componentIcon = new ImageIcon(componentImage);

        iconWidth = componentIcon.getIconWidth();
        iconHeight = componentIcon.getIconHeight();

        zoomWindow.setIcon(componentIcon);

        zoomWindow.setBounds(0, 0, iconWidth, iconHeight);

        int cursorWidth = vpWidth / ratio;

        int cursorHeight = vpHeight / ratio;

        cursorLabel.setBounds(0, 0, cursorWidth, cursorHeight);

        layeredPane.setPreferredSize(new Dimension(iconWidth, iconHeight));

        int dx = componentIcon.getIconWidth() + DELTA;
        int dy = componentIcon.getIconHeight() + DELTA;

        if (corner == JScrollPane.UPPER_LEFT_CORNER); else if (corner == JScrollPane.UPPER_RIGHT_CORNER) {
            dx = -dx;
        } else if (corner == JScrollPane.LOWER_RIGHT_CORNER) {
            dx = -dx;
            dy = -dy;
        } else if (corner == JScrollPane.LOWER_LEFT_CORNER) {
            dy = -dy;
        }
        // Shows the popup menu at the right place (Right side bottom corner).
        this.show(c, dx, dy);
    }

    // Create a fake Cursor
    public JLabel createCursor() {
        JLabel label2 = new JLabel();
        label2.setBorder(BorderFactory.createLineBorder(Color.green, 1));
        label2.setVisible(false);
        return label2;
    }

    @Override
    public void mouseClicked(MouseEvent e) {
    }

    @Override
    public void mouseEntered(MouseEvent e) {
        // When the mouse enters the preview popup menu, set the visibility
        // of the fake cursor to true.
        cursorLabel.setVisible(true);
    }

    @Override
    public void mouseExited(MouseEvent e) {
        // When the mouse exits the preview popup menu, set the visibility
        // of the fake cursor to false.
        cursorLabel.setVisible(false);
    }

    @Override
    public void mousePressed(MouseEvent e) {
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        // When the mouse is released, set the visibility of the preview
        // popup menu to false only if doCloseAfterClick is set to true.
        if (doCloseAfterClick) {
            this.setVisible(false);
            cursorLabel.setVisible(false);
        }
    }

    public void scrollMe(MouseEvent e) {
        moveCursor(e.getX(), e.getY());
        scrollViewPort();
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        scrollMe(e);
    }

    @Override
    public void mouseMoved(MouseEvent e) {
        scrollMe(e);
    }

    /**
     * Centers the fake cursor (cursorLabel) position on the coordinates
     * specified in the parameters.
     */
    private void moveCursor(int x, int y) {
        int dx = x - cursorLabel.getWidth() / 2;
        int dy = y - cursorLabel.getHeight() / 2;
        cursorLabel.setLocation(dx, dy);
    }

    /**
     * Scrolls the viewport according to the fake cursor position in the preview
     * popup menu.
     */
    private void scrollViewPort() {
        Point cursorLocation = cursorLabel.getLocation();
        int dx = (int) Math.max(cursorLocation.getX(), 0);
        int dy = (int) Math.max(cursorLocation.getY(), 0);

        dx = dx * ratio;
        dy = dy * ratio;
        // Scroll the label Component
        label.scrollRectToVisible(new Rectangle(dx, dy, vpWidth, vpHeight));
    }

    /**
     * takes a java component and generates an image out of it.
     *
     * [MENTION=9956]PARAM[/MENTION] c the component for which image needs to be generated
     * @return the generated image
     */
    public static BufferedImage captureComponentViewAsBufferedImage(Component c) {
        if (c != null) {
            Dimension size = c.getSize();
            BufferedImage buffImage = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_RGB);
            Graphics buffGr = buffImage.createGraphics();
            c.paint(buffGr);
            return buffImage;
        }
        return null;
    }
}
```

*s28.postimg.org/ql144atl5/Preview_Scroller.jpg

To run the above example use:

java PreviewScroller <imagepath>

The next demo shows how to implement a ColorChooser dialog when showing a Bezier animation.
Click on the background button , select a background color & press OK.
Similarly you can change the gradient1, gradient2, & the perimeter.

Click on 

```
/**
 * Created with IntelliJ IDEA.
 * User: Sowndar
 * Date: 5/22/14
 * Time: 11:51 AM
 * To change this template use File | Settings | File Templates.
 */

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.HierarchyEvent;
import java.awt.event.HierarchyListener;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;

/**
 * JColorChooserDemo
 *
 * @author Jeff Dinkins
 * @version 1.1 07/16/99
 */
public class ColorChooserDemo extends JPanel implements ActionListener {

    private final BezierAnimationPanel bezAnim = new BezierAnimationPanel();
    private JButton outerColorButton;
    private JButton backgroundColorButton;
    private JButton gradientAButton;
    private JButton gradientBButton;
    // Color for the Curves
    private Color BACKGROUND = new Color(0, 0, 153);
    private Color OUTER = new Color(255, 255, 255);
    private Color GRADIENT_A = new Color(255, 0, 101);
    private Color GRADIENT_B = new Color(255, 255, 0);
    private static Dimension scrDim;

    /**
     * ColorChooserDemo Constructor
     */
    public ColorChooserDemo() {
        setLayout(new BorderLayout());
        scrDim = Toolkit.getDefaultToolkit().getScreenSize();
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {
        }
        outerColorButton = new JButton("Perimeter");
        backgroundColorButton = new JButton("Background");
        gradientAButton = new JButton("Gradient 1");
        gradientBButton = new JButton("Gradient 2");

        outerColorButton.setIcon(new ColorSwatch(OUTER));

        backgroundColorButton.setIcon(new ColorSwatch(BACKGROUND));

        gradientAButton.setIcon(new ColorSwatch(GRADIENT_A));

        gradientBButton.setIcon(new ColorSwatch(GRADIENT_B));

        outerColorButton.addActionListener(this);
        backgroundColorButton.addActionListener(this);
        gradientAButton.addActionListener(this);
        gradientBButton.addActionListener(this);

        // Add control buttons
        JPanel buttonPanel = new JPanel(new GridLayout(1, 4, 15, 0));

        buttonPanel.add(backgroundColorButton);
        buttonPanel.add(gradientAButton);
        buttonPanel.add(gradientBButton);
        buttonPanel.add(outerColorButton);

        // Add everything to the panel
        JGridPanel pnContent = new JGridPanel(1, 0, 1);

        pnContent.cell(buttonPanel, JGridPanel.Layout.CENTER).cell(bezAnim);

        pnContent.setBorder(new EmptyBorder(10, 0, 0, 0));

        add(pnContent);
    }

    @Override
    public void actionPerformed(ActionEvent ae) {
        Object source = ae.getSource();
        Color color = null;
        if (source == outerColorButton) {
            color = OUTER;
        } else if (source == gradientAButton) {
            color = GRADIENT_A;
        } else if (source == GRADIENT_B) {
            color = GRADIENT_B;
        } else {
            color = BACKGROUND;
        }
        final JColorChooser chooser = new JColorChooser(color);
        JDialog dialog = JColorChooser.createDialog(ColorChooserDemo.this,
                "Choose a Color",
                true,
                chooser,
                null,
                null);

        dialog.setVisible(true);
        if (source == outerColorButton) {
            OUTER = chooser.getColor();
            outerColorButton.setIcon(new ColorSwatch(OUTER));
        } else if (source == gradientAButton) {
            GRADIENT_A = chooser.getColor();
            gradientAButton.setIcon(new ColorSwatch(GRADIENT_A));
        } else if (source == gradientBButton) {
            GRADIENT_B = chooser.getColor();
            gradientBButton.setIcon(new ColorSwatch(GRADIENT_B));
        } else {
            BACKGROUND = chooser.getColor();
            backgroundColorButton.setIcon(new ColorSwatch(BACKGROUND));
        }
    }

    private class ColorSwatch implements Icon {

        private final Color bezierColor;

        public ColorSwatch(Color bezierColor) {
            this.bezierColor = bezierColor;
        }

        @Override
        public int getIconWidth() {
            return 11;
        }

        @Override
        public int getIconHeight() {
            return 11;
        }

        public Color getBezierColor() {
            return bezierColor;
        }

        @Override
        public void paintIcon(Component c, Graphics g, int x, int y) {
            g.setColor(Color.black);
            g.fillRect(x, y, getIconWidth(), getIconHeight());
            g.setColor(bezierColor);
            g.fillRect(x + 2, y + 2, getIconWidth() - 4, getIconHeight() - 4);
        }
    }

    class BezierAnimationPanel extends JPanel implements Runnable {

        private GradientPaint gradient = null;

        private static final int NUMPTS = 6;

        private final float[] animpts = new float[NUMPTS * 2];

        private final float[] deltas = new float[NUMPTS * 2];

        private BufferedImage img;

        private Thread anim;

        private final Object lock = new Object();

        /**
         * BezierAnimationPanel Constructor
         */
        public BezierAnimationPanel() {
            setOpaque(true);

            addHierarchyListener(new HierarchyListener() {
                @Override
                public void hierarchyChanged(HierarchyEvent e) {
                    if (isShowing()) {
                        start();
                    } else {
                        stop();
                    }
                }
            });
        }

        public void start() {
            Dimension size = getSize();
            for (int i = 0; i < animpts.length; i += 2) {
                animpts[i] = (float) (Math.random() * size.width);
                animpts[i + 1] = (float) (Math.random() * size.height);
                deltas[i] = (float) (Math.random() * 4.0 + 2.0);
                deltas[i + 1] = (float) (Math.random() * 4.0 + 2.0);
                if (animpts[i] > size.width / 6.0f) {
                    deltas[i] = -deltas[i];
                }
                if (animpts[i + 1] > size.height / 6.0f) {
                    deltas[i + 1] = -deltas[i + 1];
                }
            }
            anim = new Thread(this);
            anim.setPriority(Thread.MIN_PRIORITY);
            anim.start();
        }

        public synchronized void stop() {
            anim = null;
            notify();
        }

        private void animate(float[] pts, float[] deltas, int index, int limit) {
            float newpt = pts[index] + deltas[index];
            if (newpt <= 0) {
                newpt = -newpt;
                deltas[index] = (float) (Math.random() * 3.0 + 2.0);
            } else if (newpt >= (float) limit) {
                newpt = 2.0f * limit - newpt;
                deltas[index] = -(float) (Math.random() * 3.0 + 2.0);
            }
            pts[index] = newpt;
        }

        @Override
        public void run() {
            Thread me = Thread.currentThread();
            while (getSize().width <= 0) {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    return;
                }
            }

            Graphics2D g2d = null;
            Graphics2D bufferG2D = null;
            BasicStroke solid = new BasicStroke(9.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND, 9.0f);
            GeneralPath gp = new GeneralPath(GeneralPath.WIND_NON_ZERO);
            int rule = AlphaComposite.SRC_OVER;
            AlphaComposite opaque = AlphaComposite.SrcOver;
            AlphaComposite blend = AlphaComposite.getInstance(rule, 0.9f);
            AlphaComposite set = AlphaComposite.Src;
            Dimension oldSize = getSize();
            Shape clippath = null;
            while (anim == me) {
                Dimension size = getSize();
                if (size.width != oldSize.width || size.height != oldSize.height) {
                    img = null;
                    clippath = null;
                    if (bufferG2D != null) {
                        bufferG2D.dispose();
                        bufferG2D = null;
                    }
                }
                oldSize = size;

                if (img == null) {
                    img = (BufferedImage) createImage(size.width, size.height);
                }

                if (bufferG2D == null) {
                    bufferG2D = img.createGraphics();
                    bufferG2D.setRenderingHint(RenderingHints.KEY_RENDERING,
                            RenderingHints.VALUE_RENDER_DEFAULT);
                    bufferG2D.setClip(clippath);
                }
                g2d = bufferG2D;

                float[] ctrlpts;
                for (int i = 0; i < animpts.length; i += 2) {
                    animate(animpts, deltas, i, size.width);
                    animate(animpts, deltas, i + 1, size.height);
                }
                ctrlpts = animpts;
                int len = ctrlpts.length;
                gp.reset();
                float prevx = ctrlpts[len - 2];
                float prevy = ctrlpts[len - 1];
                float curx = ctrlpts[0];
                float cury = ctrlpts[1];
                float midx = (curx + prevx) / 2.0f;
                float midy = (cury + prevy) / 2.0f;
                gp.moveTo(midx, midy);
                for (int i = 2; i <= ctrlpts.length; i += 2) {
                    float x1 = (midx + curx) / 2.0f;
                    float y1 = (midy + cury) / 2.0f;
                    prevx = curx;
                    prevy = cury;
                    if (i < ctrlpts.length) {
                        curx = ctrlpts[i];
                        cury = ctrlpts[i + 1];
                    } else {
                        curx = ctrlpts[0];
                        cury = ctrlpts[1];
                    }
                    midx = (curx + prevx) / 2.0f;
                    midy = (cury + prevy) / 2.0f;
                    float x2 = (prevx + midx) / 2.0f;
                    float y2 = (prevy + midy) / 2.0f;
                    gp.curveTo(x1, y1, x2, y2, midx, midy);
                }
                gp.closePath();

                synchronized (lock) {
                    g2d.setComposite(set);
                    g2d.setBackground(BACKGROUND);
                    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                            RenderingHints.VALUE_ANTIALIAS_ON);

                    g2d.clearRect(0, 0, getWidth(), getHeight());

                    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                            RenderingHints.VALUE_ANTIALIAS_ON);
                    g2d.setColor(OUTER);
                    g2d.setComposite(opaque);
                    g2d.setStroke(solid);
                    g2d.draw(gp);
                    g2d.setPaint(gradient);

                    Rectangle bounds = gp.getBounds();

                    gradient = new GradientPaint(bounds.x, bounds.y, GRADIENT_A,
                            bounds.x + bounds.width, bounds.y + bounds.height,
                            GRADIENT_B, true);

                    g2d.setComposite(blend);
                    g2d.fill(gp);
                }

                repaint();

                Thread.yield();
            }
            if (g2d != null) {
                g2d.dispose();
            }
        }

        @Override
        public void paintComponent(Graphics g) {
            synchronized (lock) {
                Graphics2D g2d = (Graphics2D) g;
                if (img != null) {
                    g2d.setComposite(AlphaComposite.Src);
                    g2d.drawImage(img, null, 0, 0);
                }
            }
        }
    }

    /**
     * main method allows us to run as a standalone demo.
     *
     * [MENTION=9956]PARAM[/MENTION] args
     */
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame frame = new JFrame("ColorChooser Demo2");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new ColorChooserDemo());
                frame.setPreferredSize(new Dimension(scrDim.width / 2, scrDim.height / 2 + 150));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
}


/**
 * JGridPanel
 *
 * @author Pavel Porvatov
 */
class JGridPanel extends JPanel {
    public static final int DEFAULT_GAP = 5;

    public enum Layout {
        FIRST,
        CENTER,
        LAST,
        FILL
    }

    private enum ComponentType {
        NO_RESIZABLE,
        HORIZONTAL_FILL,
        FULL
    }

    private final int cols;

    private int bigCol = -1;

    private int bigRow = -1;

    private int vGap = -1;

    private int hGap = -1;

    public JGridPanel(int cols) {
        this(cols, -1, -1);
    }

    public JGridPanel(int cols, int bigCol) {
        this(cols, bigCol, -1);
    }

    public JGridPanel(int cols, int bigCol, int bigRow) {
        super(new GridBagLayout());

        assert cols > 0;
        assert bigCol >= -1 && bigCol < cols;
        assert bigRow >= -1;

        this.cols = cols;
        this.bigCol = bigCol;
        this.bigRow = bigRow;
    }

    public void setVGap(int vGap) {
        this.vGap = vGap;
    }

    public void setHGap(int hGap) {
        this.hGap = hGap;
    }

    public JGridPanel cell() {
        return cell(null, getHLayout(null), getVLayout(null), null);
    }

    public JGridPanel cell(Component component) {
        return cell(component, getHLayout(component), getVLayout(component), null);
    }

    public JGridPanel cell(Component component, Layout hLayout) {
        return cell(component, hLayout, getVLayout(component), null);
    }

    public JGridPanel cell(Component component, Layout hLayout, Layout vLayout) {
        return cell(component, hLayout, vLayout, null);
    }

    public JGridPanel cell(Component component, Insets insets) {
        assert insets != null;

        return cell(component, getHLayout(component), getVLayout(component), insets);
    }

    private JGridPanel cell(Component component, Layout hLayout, Layout vLayout, Insets insets) {
        int componentCount = getComponentCount();
        int x = componentCount % cols;
        int y = componentCount / cols;

        int weightx = x == bigCol || (bigCol < 0 && hLayout == Layout.FILL) ? 1 : 0;
        int weighty = y == bigRow || (bigRow < 0 && vLayout == Layout.FILL) ? 1 : 0;

        if (insets == null) {
            int topGap = y == 0 ? 0 : vGap;
            int leftGap = x == 0 ? 0 : hGap;

            insets = new Insets(topGap < 0 ? DEFAULT_GAP : topGap,
                    leftGap < 0 ? DEFAULT_GAP : leftGap, 0, 0);
        }

        add(component == null ? createSeparator() : component,
                new GridBagConstraints(x, y,
                        1, 1,
                        weightx, weighty,
                        getAnchor(hLayout, vLayout), getFill(hLayout, vLayout),
                        insets, 0, 0));

        return this;
    }

    public void setComponent(Component component, int col, int row) {
        assert col >= 0 && col < cols;
        assert row >= 0;

        GridBagLayout layout = (GridBagLayout) getLayout();

        for (int i = 0; i < getComponentCount(); i++) {
            Component oldComponent = getComponent(i);

            GridBagConstraints constraints = layout.getConstraints(oldComponent);

            if (constraints.gridx == col && constraints.gridy == row) {
                remove(i);

                add(component == null ? createSeparator() : component, constraints);

                validate();
                repaint();

                return;
            }
        }

        // Cell not found
        assert false;
    }

    private static JComponent createSeparator() {
        return new JLabel();
    }

    private static int getFill(Layout hLayout, Layout vLayout) {
        if (hLayout == Layout.FILL) {
            return vLayout == Layout.FILL ? GridBagConstraints.BOTH : GridBagConstraints.HORIZONTAL;
        }

        return vLayout == Layout.FILL ? GridBagConstraints.VERTICAL : GridBagConstraints.NONE;
    }

    private static ComponentType getComponentType(Component component) {
        if (component == null ||
                component instanceof JLabel ||
                component instanceof JRadioButton ||
                component instanceof JCheckBox ||
                component instanceof JButton) {
            return ComponentType.NO_RESIZABLE;
        }

        if (component instanceof JComboBox ||
                component instanceof JTextField) {
            return ComponentType.HORIZONTAL_FILL;
        }

        return ComponentType.FULL;
    }

    private static Layout getHLayout(Component component) {
        if (getComponentType(component) == ComponentType.NO_RESIZABLE) {
            return Layout.FIRST;
        } else {
            return Layout.FILL;
        }
    }

    private static Layout getVLayout(Component component) {
        if (getComponentType(component) == ComponentType.FULL) {
            return Layout.FILL;
        } else {
            return Layout.CENTER;
        }
    }

    private static final int[][] ANCHORS = new int[][]{
            {GridBagConstraints.NORTHWEST, GridBagConstraints.NORTH, GridBagConstraints.NORTHEAST},
            {GridBagConstraints.WEST, GridBagConstraints.CENTER, GridBagConstraints.EAST},
            {GridBagConstraints.SOUTHWEST, GridBagConstraints.SOUTH, GridBagConstraints.SOUTHEAST}
    };

    private static int getAnchorIndex(Layout layout) {
        if (layout == Layout.CENTER) {
            return 1;
        } else if (layout == Layout.LAST) {
            return 2;
        } else {
            return 0;
        }
    }

    private static int getAnchor(Layout hLayout, Layout vLayout) {
        return ANCHORS[getAnchorIndex(vLayout)][getAnchorIndex(hLayout)];
    }

    public void setBorderEqual(int border) {
        setBorder(BorderFactory.createEmptyBorder(border, border, border, border));
    }
}
```

*s27.postimg.org/qstraf8nz/Color_Chooser_Demo2.jpg

Here is a demo that shows how to implement a image zoom operation in a JSlider.
As you move the slider that image is zoomed on the fly.


```
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
/**
 *
 * @author Sowndar
 */
//Image ZoomIn/ZoomOut thro a Slider
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class SliderDemo4 extends JFrame {

    private JLabel label = new JLabel();
    private int min = 0, max = 1600, value = 100;
    private JSlider slider;
    private int factor, width, height;
    private BufferedImage buffImage;
    private Graphics2D g2d;
    private Image image;
    private int newWidth, newHeight;
    private Dimension scr;
    private String[] name;
    private String path = "Images/";
    private File directory = new File(path);
    private String[] readFormat = ImageIO.getReaderFormatNames();
    private int scrollX, scrollY, scrollWidth, scrollHeight;
    private JViewport viewport;
    private JScrollPane scrollPane;

    public SliderDemo4() {

        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
        }
        slider = new JSlider(min, max, value);
        scr = Toolkit.getDefaultToolkit().getScreenSize();
        if (!directory.exists()) {
            System.err.println("The specified directory doesn't exist!!");
            System.exit(1);
        }

        name = directory.list(new FilenameFilter() {

            @Override
            public boolean accept(File dir, String name) {
                for (int i = 0; i < readFormat.length; i++) {
                    if (name.endsWith(readFormat[i])) {
                        return true;
                    }
                }
                return false;
            }
        });

        // Choose a random Image every time
        int rand = (int) (name.length * Math.random());
        String fileName = name[rand];
        buffImage = getImage(path + fileName);
        if (buffImage != null) {
            width = buffImage.getWidth();
            height = buffImage.getHeight();
            image = buffImage;
        }

        label.setHorizontalAlignment(SwingConstants.CENTER);
        label.setVerticalAlignment(SwingConstants.CENTER);
        label.setIcon(new ImageIcon(buffImage));
        scrollPane = new JScrollPane(label);
        add(scrollPane, BorderLayout.CENTER);
        JPanel panel = new JPanel();
        panel.setLayout(new FlowLayout(FlowLayout.CENTER));
        slider.setMinorTickSpacing(15);
        slider.setPreferredSize(new Dimension(850, 50));
        slider.setPaintTicks(true);
        slider.setPaintLabels(true);
        slider.setPaintTrack(true);
        slider.setMajorTickSpacing(100);
        // Listen to slider's knob drag
        slider.addChangeListener(new ChangeListener() {

            @Override
            public void stateChanged(ChangeEvent ce) {
                new Thread(new Runnable() {

                    @Override
                    public void run() {
                        try {
                            // Don't update when the user is dragging the Slider
                            // Update only when the user has finished dragging!!
                            if (!slider.getValueIsAdjusting()) {
                                factor = slider.getValue();
                                slider.setToolTipText("" + factor + "%");
                                newWidth = (width * factor) / 100;
                                newHeight = (height * factor) / 100;
                                if (newWidth > 0 && newHeight > 0) {
                                    buffImage = new BufferedImage(newWidth, newHeight, BufferedImage.TRANSLUCENT);
                                    g2d = buffImage.createGraphics();

                                    g2d.drawImage(image, 0, 0, newWidth, newHeight, null);
                                    label.setIcon(new ImageIcon(buffImage));
                                    //Scroll the Label
                                    scrollX = newWidth / 4 + 150;
                                    scrollY = newHeight / 4 + 150;
                                    viewport = scrollPane.getViewport();
                                    if (viewport != null) {
                                        scrollWidth = viewport.getWidth();
                                        scrollHeight = viewport.getHeight();
                                        label.scrollRectToVisible(new Rectangle(scrollX, scrollY, scrollWidth, scrollHeight));
                                    }
                                    setTitle("Image Zooming - " + factor + "%");
                                }
                            }
                        } catch (OutOfMemoryError ome) {
                            JOptionPane.showMessageDialog(null, "Oops, JVM running short of heap Memory!!\n Start the JVM with args -Xms128m -Xmx950m", "Error", JOptionPane.ERROR_MESSAGE);
                        }

                    }
                }).start();
            }
        });
        panel.add(slider);
        add(panel, BorderLayout.SOUTH);
        setTitle("Image Zooming - Slider");
        setSize(scr);
        setVisible(true);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    public BufferedImage getImage(String fileName) {
        try {
            return ImageIO.read(new File(fileName));
        } catch (IOException ioe) {
            System.err.println("Error loading Image : " + fileName + "!!");
        }
        return null;
    }

    public static void main(String[] s) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new SliderDemo4();
            }
        });
    }
}
```

*s30.postimg.org/w7ossme65/Slider_Demo4.jpg

*JComboBox auto-completion*

Searching for an item in a combo box can be cumbersome if the box contains a lot of items.

If the user knows what he is looking for, it should be sufficient to enter the desired item. The user should be supported with automatic completion. This article shows how to implement this feature
using the standard JComboBox without modifying it or inheriting from it. Although the following covers JComboBox, you should be able to auto-complete enable any combo box that follows Sun's design
paradigm including data-aware combo boxes from 3rd parties.

The user can type into the combo box and his input will be automatically completed to the next matching item in the combo box. Except for this added auto-completion the combo box will behave as usual. Let us assume that the currently selected item is "Ester" but the user is looking for "Jorge" - he or she starts typing away..
ComboBox is implemented as a compound component. It basically consists of a JButton with an arrow that pops up a JPopupMenu that contains all items. One can select an item that then can be edited in a JTextField.
Using the methods getSize and getElementAt one can easily iterate over the items. The selected item is controlled by the methods getSelectedItem and setSelectedItem. ComboBoxModel is an interface, details can be found in the default implementation DefaultComboBoxModel.

JComboBox offers convenience methods that call their counterparts in the model (getItemCount, getItemAt, setSelectedItem, getSelectedItem)
The editor is represented by the interface ComboBoxEditor. It can be assumed that the editor component is a JTextComponent. Once again following MVC the text is contained in a separated model (interface Document).

When the user enters or deletes text using his keyboard, the corresponding methods (insertString, remove) are called on the text component's document model.

*Adding automatic selection*

To actually select an item in the combo box access to the combo box' model is needed inside our document. It can be passed as a parameter to the constructor (omitted here, see sourcecode). Adding some kind of automatic selection inside insertString...


```
public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
  // insert the string into the document
  super.insertString(offs, str, a);
  // get the resulting string
  String content = getText(0, getLength());
  // lookup a matching item
  Object item = lookupItem(content);
  // select the item (or deselect if null)
  model.setSelectedItem(item);
}

private Object lookupItem(String pattern) {
  // iterate over all items
  for (int i=0, n=model.getSize(); i < n; i++) {
    Object currentItem = model.getElementAt(i);
    // current item starts with the pattern?
    if (currentItem.toString().startsWith(pattern)) {
      return currentItem;
    }
  }
  // no item starts with the pattern => return null
  return null;
}
```

This automatic selection works, but it is rather confusing. This is due to the fact, that some magic already seems to do the automatic completion. Watch the console: if the users types 'J' the output looks like...

insert J at 0
insert Jordi at 0

However the user only inserted 'J' and the program does not contain any explicit call to insert the string 'Jordi'.

It comes out, that the call to setSelectedItem provokes another call to insertString with the newly selected item. This should normally lead to an infinite loop. On Sun's JDKs this won't happen when using DefaultComboBoxModel, as it only calls insertString if the selected item has not been selected before. However, when using a custom model or a JDK from Apple or IBM the call to setSelectedItem will not return. A flag can be set to indicate that setSelectedItem has been called from insertString. Any subsequent call will then return immediately without further processing when the flag is set.


```
// return immediately when selecting an item
  if (selecting) return;
  [...]
  selecting=true;
  model.setSelectedItem(item);
  selecting=false;
```
 
  Adding automatic completion

  Now, to do automatic completion instead of just automatic selection access to the combo box' editor is needed. Otherwise it would not be possible to highlight the completed part using selection (see above). I added the whole JComboBox to the constructor. The selection should start right after the last character that was inserted (at position offs+str.length()).

  See how the highlighting works inside insertString...


```
// remove all text and insert the completed text
  remove(0, getLength());
  super.insertString(0, item.toString(), a);
  
  // select the completed part
  JTextComponent editor = (JTextComponent) comboBox.getEditor().getEditorComponent();
  editor.setSelectionStart(offs+str.length());
  editor.setSelectionEnd(getLength());
```
 
  Although this works, it is not a robust implementation. However, it is a good starting point. You can strengthen the implementation on your own or follow the article...

  Case insensitive matching

  The typical usecases for an autocompleting combo box will hardly need case sensitive matches. It is easy to modify the lookup mechanism to ignore case, so that the user does not have to care about typing 'E' or 'e'. This short method does the trick...


```
private boolean startsWithIgnoreCase(String str1, String str2) {
    return str1.toUpperCase().startsWith(str2.toUpperCase());
  }
```
 
  Ignore input that does not match

  If the user entered a key that does not match any item you get a NullPointerException. Of course this is not acceptable. A small enhancement in the insertString method will ignore 'invalid' input.


```
// lookup and select a matching item
  Object item = lookupItem(getText(0, getLength()));
  if (item != null) {
    comboBox.setSelectedItem(item);
  } else {
    // keep old item selected if there is no match
    item = comboBox.getSelectedItem();
    // imitate no insert
    // (later on offs will be incremented by str.length(): selection won't move forward)
    offs = offs-str.length();
    // provide feedback to the user that his input has been received but can not be accepted
    // normally a "beep" that is
    UIManager.getLookAndFeel().provideErrorFeedback(comboBox);
  }
```
 
  The solution is straight forward, although you might wonder why it is needed to decrement the offset. Try it yourself without the decrement (the cursor will move on as if you had typed the right letter).

  In the first place there was no error feedback (normally a "beep") - I added it after a usability test. Users would sometimes wonder why the combo box was not accepting their input. The beep indicates to the user that his input has been received and processed but can not be accepted. Again, these little things make a big difference to the user experience!

  I found that experienced users (aka power users) don't like their computer to beep and are tempted to turn this off. Don't do it - future users of your application (likely less experienced) will appreciate it.

  Highlight complete text when the user selects an item via mouse

  When the user selects an item via mouse or using the cursor keys, the text is not highlighted. When the user select an item directly, the insertString method is called once with the complete string. Inside this method only completed text is highlighted which in this case is none, as the call already contained the complete string.

  A solution is to highlight the complete text whenever an item gets selected and this selection was not initiated by the autocompletion mechanism. This can be achieved using an ActionListener...


```
comboBox.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
      if (!selecting) highlightCompletedText(0);
    }
 });
```
 
 Popup the item list when the user starts typing

 One information that the user would like to have is the "neighbourhood" of the selected item (as has been described at the beginning). It would be nice to have the item list to popup when the user starts typing. The easiest solution is easy to add a KeyListener and call JComboBox.setPopupVisible whenever a keystroke is detected...


```
editor.addKeyListener(new KeyAdapter() {
   public void keyPressed(KeyEvent e) {
     if (comboBox.isDisplayable()) comboBox.setPopupVisible(true);
   }
 });
```
 
 Note that calls to setPopupVisible() might fail (IllegalComponentStateException) when the combo box is not visible. A check is done to prevent this from happening although a keystroke on the editor is unlikely to occur when the combo box is not visible 

 Maximum Match

This feature is about reducing the amount of key strokes a user has to do to select an item. In our five name example it is obvious that when the user types 'J' he either means "Jordi", "Jordina" or "Jorge". All three of them start with "Jor". This can be taken into account when making the selection of the completed text. A picture makes it clear how this mechanism is supposed to work...

The user needs only two keystrokes instead of four to select "Jorge". This feature is not a really big advantage in the five name case from this article, but imagine a combo box with a list of chemical substances to choose from - like...

    ...
    2-Chloro-2-methylbutane
    2-Chloro-2-methylpropane, Reagent
    2-Chloro-3-pyridinol
    2-Chloro-5-nitrobenzaldehyde
    2-Chloro-5-nitropyridine
    2-Chloro-5-methylphenol
    2-Chloromethylquinoline Hydrochloride
    2-Chlorophenylhydrazine Hydrochloride
    2-Chloropropane
    ...

In cases like this one you might consider the maximum match feature (e.g. it takes 4 keystrokes instead of 18 to select the second entry).

However, take care in other circumstances: users don't like things happening out of their control. This feature can produce more confusion than clarity.

Enough warnings - how is it implemented?

As before, an item has been looked up in regard to the user's input. Now an iteration is done over all items again and other items that would have matched the user's input are collected. All these candidates are compared to find out if they have a common starting text. The common starting text won't be highlighted after the completion as if it was entered by the user. The main method to consider does the comparison...


```
// calculates how many characters are predetermined by the given pattern.
private int getMaximumMatchingOffset(String pattern, Object selectedItem) {
  String selectedAsString=selectedItem.toString();
  int match=selectedAsString.length();
  // look for items that match the given pattern
  for (int i=0, n=model.getSize(); i < n; i++) {
    String itemAsString = model.getElementAt(i).toString();
    if (startsWithIgnoreCase(itemAsString, pattern)) {
      // current item matches the pattern
      // how many leading characters have the selected and the current item in common?
      int tmpMatch=equalStartLength(itemAsString, selectedAsString);
      if (tmpMatch < match) match=tmpMatch;
    }
  }
  return match;
}
```

Other modifications have been made to the insert method.

Non-strict matching

I was asked if it is possible to do non-strict matching - allowing the user to enter an item that is not (yet) contained in the combo box. This one is fairly easy to implement. Instead of rejecting non-matching input, accept it is accepted. The rejecting section inside the insert method...


```
if (item != null) {
  setSelectedItem(item);
} else {
  // keep old item selected if there is no match
  item = comboBox.getSelectedItem();
  // imitate no insert
  // (later on offs will be incremented by str.length(): selection won't move forward)
  offs = offs-str.length();
  // provide feedback to the user that his input has been received but can not be accepted
  UIManager.getLookAndFeel().provideErrorFeedback(comboBox);
}
setText(item.toString());
// select the completed part
highlightCompletedText(offs+str.length());

...to accept all input it can be replaced by...

boolean listContainsSelectedItem = true;
if (item == null) {
  // no item matches => use the current input as selected item
  item=getText(0, getLength());
  listContainsSelectedItem=false;
}
setText(item.toString());
// select the completed part
if (listContainsSelectedItem) highlightCompletedText(offs+str.length());;
```

This works, but has some inconveniences:

    The case-insensitive match makes it impossible to enter new items with correct case. Suppose you want to enter "EsTonto". Entering 'E' will result in "Ester" being selected. You enter 's','T',... resulting in "Estonto". But you wanted an uppercase 'T'...
    The backspace key should not only move the selection backwards when editing "new" items. Suppose you had entered "Esters" and you want to delete the trailing 's'. Hitting backspace will highlight the 's' but won't delete it.

The first issue can't be resolved very easily. Each insert would be written to a protocoll and later the original string could be reconstructed (here's the difficult part). I did a hack that is not really worth to be published but you can contact me, if you can't live without this feature.

Here is the entire source code for JComboBox auto-completion.

As you type in the input the JComboBox automatically completes the word to match the user input!!


```
/**
 * Created with IntelliJ IDEA.
 * User: Sowndar
 * Date: 10/4/14
 * Time: 5:56 PM
 * To change this template use File | Settings | File Templates.
 */

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import javax.swing.text.PlainDocument;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
/**
 *
 import java.beans.PropertyChangeEvent;
 import java.beans.PropertyChangeListener;
 import javax.swing.*;
 import javax.swing.text.*; * @author Sowndar
 */


class AutoCompletion extends PlainDocument {

    JComboBox<String> comboBox;
    ComboBoxModel<String> model;
    JTextComponent editor;
    // flag to indicate if setSelectedItem has been called
    // subsequent calls to remove/insertString should be ignored
    boolean selecting = false;
    boolean hidePopupOnFocusLoss;
    boolean hitBackspace = false;
    boolean hitBackspaceOnSelection;
    KeyListener editorKeyListener;
    FocusListener editorFocusListener;

    public AutoCompletion(final JComboBox<String> comboBox) {
        this.comboBox = comboBox;
        model = comboBox.getModel();
        comboBox.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                if (!selecting) {
                    highlightCompletedText(0);
                }
            }
        });
        comboBox.addPropertyChangeListener(new PropertyChangeListener() {

            @Override
            [MENTION=139512]Sup[/MENTION]pressWarnings("unchecked")
            public void propertyChange(PropertyChangeEvent e) {
                if (e.getPropertyName().equals("editor")) {
                    configureEditor((ComboBoxEditor) e.getNewValue());
                }
                if (e.getPropertyName().equals("model")) {
                    model = (ComboBoxModel<String>) e.getNewValue();
                }
            }
        });
        editorKeyListener = new KeyAdapter() {

            @Override
            public void keyPressed(KeyEvent e) {
                if (comboBox.isDisplayable()) {
                    comboBox.setPopupVisible(true);
                }
                hitBackspace = false;
                switch (e.getKeyCode()) {
                    // determine if the pressed key is backspace (needed by the remove method)
                    case KeyEvent.VK_BACK_SPACE:
                        hitBackspace = true;
                        hitBackspaceOnSelection = editor.getSelectionStart() != editor.getSelectionEnd();
                        break;
                    // ignore delete key
                    case KeyEvent.VK_DELETE:
                        e.consume();
                        comboBox.getToolkit().beep();
                        break;
                }
            }
        };
        // Bug 5100422 on Java 1.5: Editable JComboBox won't hide popup when tabbing out
        hidePopupOnFocusLoss = System.getProperty("java.version").startsWith("1.5");
        // Highlight whole text when gaining focus
        editorFocusListener = new FocusAdapter() {

            @Override
            public void focusGained(FocusEvent e) {
                highlightCompletedText(0);
            }

            @Override
            public void focusLost(FocusEvent e) {
                // Workaround for Bug 5100422 - Hide Popup on focus loss
                if (hidePopupOnFocusLoss) {
                    comboBox.setPopupVisible(false);
                }
            }
        };
        configureEditor(comboBox.getEditor());
        // Handle initially selected object
        Object selected = comboBox.getSelectedItem();
        if (selected != null) {
            setText(selected.toString());
        }
        highlightCompletedText(0);
    }

    public static void enable(JComboBox<String> comboBox) {
        // has to be editable
        comboBox.setEditable(true);
        // change the editor's document
        new AutoCompletion(comboBox);
    }

    void configureEditor(ComboBoxEditor newEditor) {
        if (editor != null) {
            editor.removeKeyListener(editorKeyListener);
            editor.removeFocusListener(editorFocusListener);
        }

        if (newEditor != null) {
            editor = (JTextComponent) newEditor.getEditorComponent();
            editor.addKeyListener(editorKeyListener);
            editor.addFocusListener(editorFocusListener);
            editor.setDocument(this);
        }
    }

    @Override
    public void remove(int offs, int len) throws BadLocationException {
        // return immediately when selecting an item
        if (selecting) {
            return;
        }
        if (hitBackspace) {
            // user hit backspace => move the selection backwards
            // old item keeps being selected
            if (offs > 0) {
                if (hitBackspaceOnSelection) {
                    offs--;
                }
            } else {
                // User hit backspace with the cursor positioned on the start => beep
                comboBox.getToolkit().beep(); // when available use: UIManager.getLookAndFeel().provideErrorFeedback(comboBox);
            }
            highlightCompletedText(offs);
        } else {
            super.remove(offs, len);
        }
    }

    @Override
    public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
        // return immediately when selecting an item
        if (selecting) {
            return;
        }
        // insert the string into the document
        super.insertString(offs, str, a);
        // lookup and select a matching item
        Object item = lookupItem(getText(0, getLength()));
        if (item != null) {
            setSelectedItem(item);
        } else {
            // keep old item selected if there is no match
            item = comboBox.getSelectedItem();
            // imitate no insert (later on offs will be incremented by str.length(): selection won't move forward)
            offs = offs - str.length();
            // provide feedback to the user that his input has been received but can not be accepted
            comboBox.getToolkit().beep(); // when available use: UIManager.getLookAndFeel().provideErrorFeedback(comboBox);
        }
        setText(item.toString());
        // select the completed part
        highlightCompletedText(offs + str.length());
    }

    private void setText(String text) {
        try {
            // remove all text and insert the completed string
            super.remove(0, getLength());
            super.insertString(0, text, null);
        } catch (BadLocationException e) {
            throw new RuntimeException(e.toString());
        }
    }

    private void highlightCompletedText(int start) {
        try {
            editor.setCaretPosition(getLength());
            editor.moveCaretPosition(start);
        } catch (Exception e) {
        }
    }

    private void setSelectedItem(Object item) {
        selecting = true;
        model.setSelectedItem(item);
        selecting = false;
    }

    private Object lookupItem(String pattern) {
        Object selectedItem = model.getSelectedItem();
        // only search for a different item if the currently selected does not match
        if (selectedItem != null && startsWithIgnoreCase(selectedItem.toString(), pattern)) {
            return selectedItem;
        } else {
            // iterate over all items
            for (int i = 0, n = model.getSize(); i < n; i++) {
                Object currentItem = model.getElementAt(i);
                // current item starts with the pattern?
                if (currentItem != null && startsWithIgnoreCase(currentItem.toString(), pattern)) {
                    return currentItem;
                }
            }
        }
        // no item starts with the pattern => return null
        return null;
    }

    // checks if str1 starts with str2 - ignores case
    private boolean startsWithIgnoreCase(String str1, String str2) {
        return str1.toUpperCase().startsWith(str2.toUpperCase());
    }
}

/**
 * JComboBox Demo
 *
 * @author Jeff Dinkins
 * @version 1.13 11/17/05
 */

// Shows the use of AutoCompletion JComboBox
public class ComboBoxDemo2 extends JPanel implements ActionListener {

    private String path = "Images/";
    private File directory;
    private JComboBox<String> comboBox;
    private String[] name;
    private String[] imgName;
    private JLabel label = new JLabel();
    private JButton back;
    private JButton forward;
    private int index;
    private String fullPath;

    public ComboBoxDemo2() {
        try {
            for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | UnsupportedLookAndFeelException e) {
            // Do nothing
        }
        setLayout(new BorderLayout());
        back = new JButton(new ImageIcon("resources/back.png"));
        forward = new JButton(new ImageIcon("resources/forward.png"));
        // Convert the URL to String path
        try {
            fullPath = path;//getClass().getResource(path).toURI().toString();
        } catch (Exception e) {
        }

        fullPath = path;
        directory = new File(fullPath);
        // Filter out Images
        name = directory.list(new FilenameFilter() {
            String[] readFormat = ImageIO.getReaderFormatNames();

            @Override
            public boolean accept(File dir, String name) {
                for (int i = 0; i < readFormat.length; i++) {
                    if (name.endsWith(readFormat[i])) {
                        return true;
                    }
                }
                return false;
            }
        });
        // Only filename without the extension
        imgName = new String[name.length];
        for (int i = 0; i < name.length; i++) {
            imgName[i] = name[i];
            imgName[i] = imgName[i].substring(0, imgName[i].lastIndexOf("."));
        }
        if (!directory.exists()) {
            System.err.println("The specified directory doesn't exist!!");
            System.exit(1);
        }
        back.setToolTipText("Previous");
        back.setBorderPainted(false);
        back.setContentAreaFilled(false);
        forward.setToolTipText("Next");
        forward.setBorderPainted(false);
        forward.setContentAreaFilled(false);
        // Add the listeners to the buttons
        back.addActionListener(this);
        forward.addActionListener(this);
        comboBox = new JComboBox<>(imgName);
        // Bold Font
        Font font = comboBox.getFont();
        int size = font.getSize();
        comboBox.setFont(new Font(font.getName(), Font.BOLD, size));
        // Enable auto completion for this ComboBox
        AutoCompletion.enable(comboBox);
        comboBox.addItemListener(new ItemListener() {

            @Override
            public void itemStateChanged(ItemEvent ie) {
                new Thread(new Runnable() {
                    // Concurrent modification exception
                    @Override
                    public void run() {

                        int index = comboBox.getSelectedIndex();
                        String selected = name[index];
                        label.setIcon(createImageIcon(selected, selected));
                    }
                }).start();
            }
        });
        JPanel panel = new JPanel(new FlowLayout(FlowLayout.CENTER));
        panel.add(new JLabel("JComboBox Auto-completion "));
        panel.add(new JLabel(" Search File :"));
        panel.add(comboBox);
        add(panel, BorderLayout.NORTH);
        label.setHorizontalAlignment(SwingConstants.CENTER);
        label.setVerticalAlignment(SwingConstants.CENTER);
        // Show the first Image
        String fileName = "";
        fileName = name[0];
        label.setIcon(createImageIcon(fileName, fileName));
        add(new JScrollPane(label), BorderLayout.CENTER);
        JPanel bottom = new JPanel(new FlowLayout(FlowLayout.CENTER, 150, 5));
        bottom.add(back);
        bottom.add(forward);
        add(bottom, BorderLayout.SOUTH);
        Dimension scrDim = Toolkit.getDefaultToolkit().getScreenSize();
        setPreferredSize(new Dimension(scrDim.width / 2 + 150, scrDim.height));
    }

    @Override
    public void actionPerformed(ActionEvent ae) {
        Object source = ae.getSource();
        if (source == back) {
            index = comboBox.getSelectedIndex();
            if (index <= 0) {
                index = name.length;
            }
            index--;
            comboBox.setSelectedIndex(index);
        } else if (source == forward) {
            index = comboBox.getSelectedIndex();
            index++;
            if (index >= name.length) {
                index = 0;
            }
            comboBox.setSelectedIndex(index);
        }
    }

    public ImageIcon createImageIcon(String filename, String description) {
        Image image = null;
        try
        {
            image = ImageIO.read(new File(path + filename));
            return new ImageIcon(image);
        } catch (IOException ioe) {

        }
        return null;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame f = new JFrame("ComboBoxDemo2");
                f.add(new ComboBoxDemo2());
                f.pack();
                f.setVisible(true);
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            }
        });
    }
}
```

*s27.postimg.org/qn3141ifz/Combo_Box_Demo2.jpg

Here is a InternalFrame demo that can tile , cascade InternalFrames, it can also bring the selected internal frame to the front.(using the Menu)
From the menu File -> Open . Select an Image. It's opened in an internal frame.
Select atleast 4 or 5 Images. (An Image every time the FileChooser dialog shows up).


```
/**
 * Created with IntelliJ IDEA.
 * User: Sowndar
 * Date: 10/4/14
 * Time: 6:22 PM
 * To change this template use File | Settings | File Templates.
 */
// A simple image viewing program

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.border.BevelBorder;
import javax.swing.border.EmptyBorder;
import javax.swing.event.MenuEvent;
import javax.swing.event.MenuListener;
import javax.swing.filechooser.FileFilter;
import javax.swing.plaf.ColorUIResource;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.metal.DefaultMetalTheme;
import javax.swing.plaf.metal.MetalLookAndFeel;
import javax.swing.plaf.metal.MetalScrollBarUI;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyVetoException;
import java.io.File;
import java.io.IOException;

public class InternalFrameDemo2 extends JFrame {

    // Variables declaration
    private int x = 10, y = 10;
    private JFileChooser fc;
    private JMenuItem open;
    private JMenu file, help, view;
    private JSeparator jSeparator1;
    private MDIDesktopPane desktop;
    private JMenuItem exit, about;
    private JMenuBar jMenuBar1;
    // End of variables declaration

    public InternalFrameDemo2() {

        MetalLookAndFeel.setCurrentTheme(new ToyFactoryTheme());
        try {
            UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");

        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | UnsupportedLookAndFeelException ex) {
            System.out.println("Failed loading Metal");
            System.out.println(ex);
        }

        initComponents();
        setSize(Toolkit.getDefaultToolkit().getScreenSize());
        setVisible(true);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }

    /**
     * This method is called from within the constructor to initialize the form.
     */
    private void initComponents() {
        fc = new JFileChooser(".");
        fc.setPreferredSize(new Dimension(700, 500));
        fc.setAccessory(new ImagePreview(fc));
        fc.setAcceptAllFileFilterUsed(false);
        final String[] readFormat = ImageIO.getReaderFormatNames();
        fc.addChoosableFileFilter(new FileFilter() {

            @Override
            public boolean accept(File file) {
                String fileName = file.getName();
                for (int i = 0; i < readFormat.length; i++) {
                    // Show directory & images only
                    if (fileName.endsWith(readFormat[i]) || file.isDirectory()) {
                        return true;
                    }
                }
                return false;
            }

            @Override
            public String getDescription() {
                return "Images Filter";
            }
        });
        desktop = new MDIDesktopPane();
        jMenuBar1 = new JMenuBar();

        file = new JMenu();
        //view = new JMenu("View");
        help = new JMenu("Help");

        open = new JMenuItem();
        jSeparator1 = new JSeparator();
        exit = new JMenuItem();
        about = new JMenuItem("About");
        help.add(about);

        about.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                JOptionPane.showMessageDialog(null, "Images using MDI using Java 2 Platform. \n Uses exciting new ScrollBar interfaces!!", "Info", JOptionPane.INFORMATION_MESSAGE);
                return;
            }
        });

        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setTitle("InternalFrameDemo2");
        add(desktop, BorderLayout.CENTER);

        file.setText("File");
        file.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                fileMenuActionPerformed(evt);
            }
        });

        open.setText("Open...");
        open.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                open();
            }
        });

        file.add(open);
        file.add(jSeparator1);
        exit.setText("Exit");
        exit.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent evt) {
                System.exit(0);
            }
        });

        file.add(exit);
        jMenuBar1.add(file);
        //jMenuBar1.add(view);
        jMenuBar1.add(new WindowMenu(desktop));
        jMenuBar1.add(help);
        setJMenuBar(jMenuBar1);

    }

    private void fileMenuActionPerformed(ActionEvent evt) {
        // Add your handling code here:
    }

    private void open() {
        // Add your handling code here:
        int result = fc.showOpenDialog(InternalFrameDemo2.this);

        if (result != 0) {
            return;
        }

        File f = fc.getSelectedFile();

        if (f == null) {
            return;
        } else {
            //The user has selected some file!
            ImageFrame ifr = new ImageFrame(f.toString());
            desktop.add(ifr);
            ifr.setVisible(true);
            ifr.setSize(700, 600);
            ifr.setLocation(x, y);
            x += 50;
            y += 50;
        }
    }

    /**
     * [MENTION=9956]PARAM[/MENTION] args the command line arguments
     */
    public static void main(String args[]) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                new InternalFrameDemo2();
            }
        });
    }
}

//An InternalFrame for displaying Images in MDI format!

class ImageFrame extends JInternalFrame {

    String imgName;
    int imageW, imageH;
    Timer imgProcessor;
    int labelWidth, labelHeight;
    boolean error = false;
    int newWidth, newHeight, Percentage;

    /**
     * Creates new form ImageFrame
     */
    public ImageFrame(String imageName) {
        final ImageIcon icon = new ImageIcon(getImage(imageName));
        icon.setDescription(imageName);
        final JLabel imageLabel = new JLabel("", JLabel.CENTER);
        initComponents(icon, imageLabel);
        imgName = imageName;
        String separator = System.getProperty("file.separator");
        int sIndex = imgName.lastIndexOf(separator);
        imgName = imgName.substring(sIndex + 1);
        sIndex = imgName.lastIndexOf(".");
        imgName = imgName.substring(0, sIndex);
        setTitle(imgName + " @ 100%");
        Dimension scr = Toolkit.getDefaultToolkit().getScreenSize();
        labelHeight = scr.height - 90;
        labelWidth = scr.width - 37;
        imageLabel.setIcon(icon);
        imageW = icon.getIconWidth();
        imageH = icon.getIconHeight();
    }

    public Image getImage(String path) {
        try {
            return ImageIO.read(new File(path));
        } catch (IOException ioe) {
        }
        return null;
    }

    //Initialize the Components
    private void initComponents(ImageIcon icon, JLabel label) {
        //Re-initialize error flag to 'false'
        error = false;
        final ImageIcon imgIcon = icon;
        final JLabel imgLabel = label;

        JScrollPane scroller = new JScrollPane();
        imgLabel.setBorder(new BevelBorder(BevelBorder.LOWERED));
        setIconifiable(true);
        setResizable(true);
        setMaximizable(true);
        setClosable(true);
        scroller.setViewportView(imgLabel);
        add(scroller, BorderLayout.CENTER);
    }
}

//Make the Image preview load faster (using a separate process)

class ImagePreview extends JComponent implements PropertyChangeListener {

    ImageIcon thumbnail = null;
    int width = 180;

    public ImagePreview(JFileChooser fc) {
        setPreferredSize(new Dimension(width, 50));
        fc.addPropertyChangeListener(this);
        setBorder(new BevelBorder(BevelBorder.LOWERED));
    }

    public void loadImage(File f) {
        if (f == null) {
            thumbnail = null;
        } else {
            ImageIcon tmpIcon = new ImageIcon(getImage(f.getPath()));
            if (tmpIcon.getIconWidth() > width) {
                thumbnail = new ImageIcon(
                        tmpIcon.getImage().getScaledInstance(width, -1, Image.SCALE_SMOOTH));
            } else {
                thumbnail = tmpIcon;
            }
        }
    }

    public BufferedImage getImage(String fileName) {
        try {
            return ImageIO.read(new File(fileName));
        } catch (IOException ioe) {
            System.err.println("Error loading Image!!");
        }
        return null;
    }

    @Override
    public void propertyChange(PropertyChangeEvent e) {
        String prop = e.getPropertyName();
        if (prop == JFileChooser.SELECTED_FILE_CHANGED_PROPERTY) {
            if (isShowing()) {
                loadImage((File) e.getNewValue());
                repaint();
            }
        }
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        if (thumbnail != null) {
            int x = getWidth() / 2 - thumbnail.getIconWidth() / 2;
            int y = getHeight() / 2 - thumbnail.getIconHeight() / 2;
            if (y < 0) {
                y = 0;
            }

            if (x < 5) {
                x = 5;
            }
            thumbnail.paintIcon(this, g, x, y);
        }
    }
}


//ToyFactory theme
/**
 * This class describes a theme using "yellow" colors.
 *
 * 1.5 12/03/01
 * @author Sowndar
 */
class ToyFactoryTheme extends DefaultMetalTheme
{

    public String getName()
    {
        return "ToyFactory";
    }

    private final ColorUIResource primary1 = new ColorUIResource(255, 247, 150);
    private final ColorUIResource primary2 = new ColorUIResource(254,203,0);
    private final ColorUIResource primary3 = new ColorUIResource(255, 164, 20);

    private final ColorUIResource secondary1 = new ColorUIResource(255,247,150);
    private final ColorUIResource secondary2 = new ColorUIResource(254,203,0);
    private final ColorUIResource secondary3 = new ColorUIResource(255,154,20);

    protected ColorUIResource getPrimary1() { return primary1; }
    protected ColorUIResource getPrimary2() { return primary2; }
    protected ColorUIResource getPrimary3() { return primary3; }

    protected ColorUIResource getSecondary1() { return secondary1; }
    protected ColorUIResource getSecondary2() { return secondary2; }
    protected ColorUIResource getSecondary3() { return secondary3; }


}



/**
 * An extension of WDesktopPane that supports often used MDI functionality. This
 * class also handles setting scroll bars for when windows move too far to the left or
 * bottom, providing the MDIDesktopPane is in a ScrollPane.
 */
class MDIDesktopPane extends JDesktopPane
{
    private static int FRAME_OFFSET=20;
    private MDIDesktopManager manager;

    public MDIDesktopPane()
    {
        MetalLookAndFeel.setCurrentTheme(new ToyFactoryTheme());
        manager = new MDIDesktopManager(this);
        setDesktopManager(manager);
        setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
    }

    public void setBounds(int x, int y, int w, int h)
    {
        super.setBounds(x,y,w,h);
        checkDesktopSize();
    }

    public Component add(JInternalFrame frame)
    {
        JInternalFrame[] array = getAllFrames();
        Point p;
        int width;
        int height;

        Component retval=super.add(frame);
        checkDesktopSize();
        if (array.length > 0)
        {
            p = array[0].getLocation();
            p.x = p.x + FRAME_OFFSET;
            p.y = p.y + FRAME_OFFSET;
        }
        else
        {
            p = new Point(0, 0);
        }
        frame.setLocation(p.x, p.y);
        if (frame.isResizable())
        {
            width = getWidth() - (getWidth()/3);
            height = getHeight() - (getHeight()/3);
            if (width < frame.getMinimumSize().getWidth())
            {
                width = (int)frame.getMinimumSize().getWidth();
            }

            if (height < frame.getMinimumSize().getHeight())
            {
                height = (int)frame.getMinimumSize().getHeight();
            }
            frame.setSize(width, height);
        }
        moveToFront(frame);
        frame.setVisible(true);
        try {
            frame.setSelected(true);
        } catch (PropertyVetoException e) {
            frame.toBack();
        }
        return retval;
    }

    public void remove(Component c)
    {
        super.remove(c);
        checkDesktopSize();
    }

    /**
     * Cascade all internal frames
     */
    public void cascadeFrames()
    {
        int x = 0;
        int y = 0;
        JInternalFrame allFrames[] = getAllFrames();

        manager.setNormalSize();
        int frameHeight = (getBounds().height - 5) - allFrames.length * FRAME_OFFSET;
        int frameWidth = (getBounds().width - 5) - allFrames.length * FRAME_OFFSET;
        for (int i = allFrames.length - 1; i >= 0; i--)
        {
            allFrames[i].setSize(frameWidth,frameHeight);
            allFrames[i].setLocation(x,y);
            x = x + FRAME_OFFSET;
            y = y + FRAME_OFFSET;
        }
    }

    /**
     * Tile all internal frames
     */
    public void tileFrames()
    {
        Component allFrames[] = getAllFrames();
        manager.setNormalSize();
        int frameHeight = getBounds().height/allFrames.length;
        int frameWidth = getBounds().width/allFrames.length;
        int wholeWidth = getBounds().width;
        int wholeHeight = getBounds().height;
        int x = 0, y = 0;
        for (int i = 0; i < allFrames.length; i++)
        {
            allFrames[i].setSize(frameWidth,wholeHeight);
            allFrames[i].setLocation(x,0);
            x = x + frameWidth;
            y = y + frameHeight;
        }
    }

    /**
     * Sets all component size properties ( maximum, minimum, preferred)
     * to the given dimension.
     */
    public void setAllSize(Dimension d)
    {
        setMinimumSize(d);
        setMaximumSize(d);
        setPreferredSize(d);
    }

    /**
     * Sets all component size properties ( maximum, minimum, preferred)
     * to the given width and height.
     */
    public void setAllSize(int width, int height)
    {
        setAllSize(new Dimension(width,height));
    }

    private void checkDesktopSize()
    {
        if (getParent()!=null && isVisible())
            manager.resizeDesktop();
    }
}

/**
 * Private class used to replace the standard DesktopManager for JDesktopPane.
 * Used to provide scrollbar functionality.
 */
class MDIDesktopManager extends DefaultDesktopManager
{
    private MDIDesktopPane desktop;

    public MDIDesktopManager(MDIDesktopPane desktop)
    {
        this.desktop = desktop;
    }

    public void endResizingFrame(JComponent f)
    {
        super.endResizingFrame(f);
        resizeDesktop();
    }

    public void endDraggingFrame(JComponent f)
    {
        super.endDraggingFrame(f);
        resizeDesktop();
    }

    public void setNormalSize()
    {
        JScrollPane scrollPane=getScrollPane();
        int x = 0;
        int y = 0;
        Insets scrollInsets = getScrollPaneInsets();

        if (scrollPane != null)
        {
            Dimension d = scrollPane.getVisibleRect().getSize();
            if (scrollPane.getBorder() != null)
            {
                d.setSize(d.getWidth() - scrollInsets.left - scrollInsets.right,
                        d.getHeight() - scrollInsets.top - scrollInsets.bottom);
            }

            d.setSize(d.getWidth() - 20, d.getHeight() - 20);
            desktop.setAllSize(x,y);
            scrollPane.invalidate();
            scrollPane.validate();
        }
    }

    private Insets getScrollPaneInsets()
    {
        JScrollPane scrollPane=getScrollPane();
        if (scrollPane==null)
            return new Insets(0,0,0,0);
        else
            return getScrollPane().getBorder().getBorderInsets(scrollPane);
    }

    private JScrollPane getScrollPane()
    {
        if (desktop.getParent() instanceof JViewport)
        {
            JViewport viewPort = (JViewport)desktop.getParent();
            if (viewPort.getParent() instanceof JScrollPane)
                return (JScrollPane)viewPort.getParent();
        }
        return null;
    }

    protected void resizeDesktop()
    {
        int x = 0;
        int y = 0;
        JScrollPane scrollPane = getScrollPane();
        Insets scrollInsets = getScrollPaneInsets();

        if (scrollPane != null)
        {
            JInternalFrame allFrames[] = desktop.getAllFrames();
            for (int i = 0; i < allFrames.length; i++)
            {
                if (allFrames[i].getX()+allFrames[i].getWidth()>x)
                {
                    x = allFrames[i].getX() + allFrames[i].getWidth();
                }
                if (allFrames[i].getY()+allFrames[i].getHeight()>y)
                {
                    y = allFrames[i].getY() + allFrames[i].getHeight();
                }
            }
            Dimension d=scrollPane.getVisibleRect().getSize();
            if (scrollPane.getBorder() != null)
            {
                d.setSize(d.getWidth() - scrollInsets.left - scrollInsets.right,
                        d.getHeight() - scrollInsets.top - scrollInsets.bottom);
            }

            if (x <= d.getWidth())
                x = ((int)d.getWidth()) - 20;
            if (y <= d.getHeight())
                y = ((int)d.getHeight()) - 20;
            desktop.setAllSize(x,y);
            scrollPane.invalidate();
            scrollPane.validate();
        }
    }
}

/**
 * Menu component that handles the functionality expected of a standard
 * "Windows" menu for MDI applications.
 */
class WindowMenu extends JMenu {
    private MDIDesktopPane desktop;
    private JMenuItem cascade=new JMenuItem("Cascade");
    private JMenuItem tile=new JMenuItem("Tile");
    private ImageIcon icon;
    private static String frameTitle;

    public WindowMenu(MDIDesktopPane desktop) {
        this.desktop=desktop;
        setText("Window");
        cascade.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                WindowMenu.this.desktop.cascadeFrames();
            }
        });
        tile.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                WindowMenu.this.desktop.tileFrames();
            }
        });
        addMenuListener(new MenuListener() {
            public void menuCanceled (MenuEvent e) {}

            public void menuDeselected (MenuEvent e) {
                removeAll();
            }

            public void menuSelected (MenuEvent e) {
                buildChildMenus();
            }
        });
    }

    /* Sets up the children menus depending on the current desktop state */
    private void buildChildMenus() {
        int i;
        ChildMenuItem menu;
        JInternalFrame[] array = desktop.getAllFrames();

        add(cascade);
        add(tile);
        if (array.length > 0) addSeparator();
        cascade.setEnabled(array.length > 0);
        tile.setEnabled(array.length > 0);

        for (i = 0; i < array.length; i++)
        {
            menu = new ChildMenuItem(array[i]);
            menu.setSelected(i == 0);
            menu.addActionListener(new ActionListener()
            {
                public void actionPerformed(ActionEvent ae)
                {
                    JInternalFrame frame = ((ChildMenuItem)ae.getSource()).getFrame();
                    frameTitle = frame.getTitle();
                    System.out.println("Frame Selected = " + frameTitle);
                    frame.moveToFront();
                    try
                    {
                        frame.setSelected(true);
                    } catch (PropertyVetoException e) {  e.printStackTrace(); }
                }
            });
            menu.setIcon(array[i].getFrameIcon());
            add(menu);
        }
    }

    /* This JCheckBoxMenuItem descendant is used to track the child frame that corresponds
       to a give menu. */
    class ChildMenuItem extends JCheckBoxMenuItem
    {
        private JInternalFrame frame;
        private int index;
        private String title;
        private int duplicate;

        public ChildMenuItem(JInternalFrame frame)
        {
            this.frame=frame;
            //If the user loads the same Image twice , the first one should be marked as (sampleImg [1]) and second one as (SampleImg [2])
            JInternalFrame []allFrames = desktop.getAllFrames();
            String [] names = new String[allFrames.length];
            for(int i=0;i<allFrames.length;i++)
            {
                names[i] = allFrames[i].getTitle();
                index = names[i].lastIndexOf("@");
                names[i] = names[i].substring(0, index);
                names[i] = names[i].trim();
            }
            title = frame.getTitle();
            index = title.lastIndexOf("@");
            title = title.substring(0, index);
            title = title.trim();
            for(int j=0;j<names.length;j++)
            {
                if(title.equals(names[j]))
                {
                    duplicate+=2;
                    setText(title + " " + "[" + duplicate + "]");
                    return;
                }
            }
            setText(title);
            //Reset duplicate flag
            duplicate = 0;
            setBorder(new EmptyBorder(2,2,2,2));
        }

        public JInternalFrame getFrame()
        {
            return frame;
        }
    }
}
```

*s27.postimg.org/bsnmhcnmn/Internal_Frame_Demo2.jpg

*JFileChooser with thumbnail icons of Images*

A simple implementation of the FileView class that provides a 120 X 90 image of
each supported Image file for its icon. This could be SLOW for large images, as we
simply load the real image and then scale it on the fly.
The class ThumbNailFileView extends FileView to create a custom filview with Image preview.


```
/**
 * Created with IntelliJ IDEA.
 * User: Sowndar
 * Date: 10/4/14
 * Time: 5:14 PM
 * To change this template use File | Settings | File Templates.
 */

// FileChooserDemo.java
// An example that uses custom file views to show thumbnails of graphic files
// rather than the regular file icon.  (see ThumbnailFileView.java)
//

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.filechooser.FileSystemView;
import javax.swing.filechooser.FileView;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

public class FileChooserDemo2 extends JFrame {

    private JFrame parent;
    private JFileChooser chooser;
    private String fileName;
    private Image image;
    private String[] readFormat = ImageIO.getReaderFormatNames();
    // Big enough to display Image preview
    private int width = 700, height = 500;

    // Get supported Image formats (remove duplicate entries)
    public String[] getReaderFormats() {
        String[] rFormat = ImageIO.getReaderFormatNames();
        Set<String> set = new HashSet<String>();
        String imgformat = "";
        // Remove duplicate entries
        for (int i = 0; i < rFormat.length; i++) {
            imgformat = rFormat[i];
            imgformat = imgformat.toLowerCase();
            set.add(imgformat);
        }
        String[] frmt = new String[set.size()];
        return set.toArray(frmt);
    }

    public FileChooserDemo2() {
        super("FileChooserDemo - ImagePreview");
        // Set the look'n feel
        try {
            try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch (ClassNotFoundException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            } catch (InstantiationException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            } catch (IllegalAccessException e) {
                e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
            }
        } catch (UnsupportedLookAndFeelException e) {
            System.err.println("Error loading Substance theme!!");
        }

        chooser = new JFileChooser();
        chooser.setFileHidingEnabled(true);
        chooser.setCurrentDirectory(new File("Images/"));
        chooser.setPreferredSize(new Dimension(width, height));
        // Ok, set up our own file view for the chooser
        chooser.setFileView(new ThumbNailFileView(FileChooserDemo2.this));
        // Filter files (directory & Images)
        chooser.setFileFilter(new javax.swing.filechooser.FileFilter() {

            @Override
            public boolean accept(File f) {
                String name = f.getName();
                for (int i = 0; i < readFormat.length; i++) {
                    if (f.isDirectory() || name.endsWith(readFormat[i])) {
                        return true;
                    }
                }
                return false;
            }

            // Show something like "*.jpg,*.jpeg,*.bmp,*.tiff,*.gif,*.jpeg2000" etc.,
            @Override
            public String getDescription() {
                readFormat = getReaderFormats();
                String str = "";
                for (int i = 0; i < readFormat.length; i++) {
                    // Last entry should not end with ","
                    if (i == readFormat.length - 1) {
                        str += "*." + readFormat[i];
                    } else {
                        str += "*." + readFormat[i] + " ,";
                    }
                }
                return str;
            }
        });
        // Bug : Details view (Table) rowHeight is not enough to display the Image preview
        UIManager.put("Table.rowHeight", new Integer(32));
        setSize(350, 200);
        setResizable(false);
        setVisible(true);
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        parent = this;
        JPanel top = new JPanel(new FlowLayout(FlowLayout.LEFT));

        JButton openButton = new JButton("Open");
        final JLabel statusbar = new JLabel("Ready");
        final JLabel label = new JLabel();

        openButton.addActionListener(new ActionListener() {

            public void actionPerformed(ActionEvent ae) {
                new Thread(new Runnable() {

                    public void run() {
                        Thread.currentThread().setPriority(Thread.MAX_PRIORITY);

                        int option = chooser.showOpenDialog(parent);
                        // If the user selected "OK" button
                        if (option == JFileChooser.APPROVE_OPTION) {
                            new Thread(new Runnable() {

                                public void run() {
                                    fileName = chooser.getSelectedFile().toString();
                                    image = getImage(fileName);
                                    label.setIcon(new ImageIcon(image));
                                    statusbar.setText("You chose " + fileName);
                                    pack();
                                }
                            }).start();

                        } else {
                            // If the user selected "Cancel" button
                            fileName = "";
                            label.setIcon(null);
                            statusbar.setText("You cancelled.");
                            setSize(350, 200);
                        }
                    }
                }).start();
            }
        });
        top.add(openButton);
        add(top, BorderLayout.NORTH);
        add(new JScrollPane(label), BorderLayout.CENTER);
        add(statusbar, BorderLayout.SOUTH);
    }

    public Image getImage(String fileName) {
        try {
            return ImageIO.read(new File(fileName));
        } catch (IOException ioe) {
            System.err.println("Error loading Image : " + fileName);
        }
        return null;
    }

    public static void main(String args[]) {
        SwingUtilities.invokeLater(new Runnable() {

            public void run() {
                new FileChooserDemo2();
            }
        });
    }
}


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
/**
 *
 * @author Sowndar
 */
// ThumbNailFileView.java
// A simple implementation of the FileView class that provides a 120 X 90 image of
// each supported Image file for its icon. This could be SLOW for large images, as we
// simply load the real image and then scale it on the fly.
//

class ThumbNailFileView extends FileView implements MouseListener {

    private Component observer;
    final JPopupMenu popup = new JPopupMenu("Popup");
    JMenuItem properties = new JMenuItem("Properties");
    JMenuItem rename = new JMenuItem("Rename");
    JMenuItem delete = new JMenuItem("Delete");
    String[] format = ImageIO.getReaderFormatNames();
    String extension = "";
    int dotIndex = -1;
    // Use the System icons
    FileSystemView sView = FileSystemView.getFileSystemView();
    ImageIcon icon;
    Image image;

    public ThumbNailFileView(Component c) {
        // We need a component around to create our icon's image
        observer = c;
        popup.add(properties);
        popup.add(rename);
        popup.add(delete);
    }

    public void mousePressed(MouseEvent me) {
        if (me.isPopupTrigger()) {
            popup.show(me.getComponent(), me.getX(), me.getY());
        }
    }

    public void mouseReleased(MouseEvent me) {
        if (me.isPopupTrigger()) {
            popup.show(me.getComponent(), me.getX(), me.getY());
        }
    }

    public void mouseClicked(MouseEvent me) {
    }

    public void mouseEntered(MouseEvent me) {
    }

    public void mouseExited(MouseEvent me) {
    }

    @Override
    public String getDescription(File file) {
        // We won't store individual descriptions, so just return the
        // type description.
        return getTypeDescription(file);
    }

    @Override
    public Icon getIcon(File file) {
        // Is it a folder?
        if (file.isDirectory()) {
            // Bigger Icons
            icon = (ImageIcon) sView.getSystemIcon(file);
            image = icon.getImage();
            image = image.getScaledInstance(32, 32, Image.SCALE_SMOOTH);
            return new ImageIcon(image);
        }
        // Ok, it's a file, so return a custom icon if it's an image file
        String name = file.getName();
        // If it's a Image file
        for (int i = 0; i < format.length; i++) {
            if (name.endsWith(format[i])) {
                return new IconPreview(file.getAbsolutePath());
            }
        }
        // Return the O.S's Icon
        icon = (ImageIcon) sView.getSystemIcon(file);
        image = icon.getImage();
        image = image.getScaledInstance(32, 32, Image.SCALE_SMOOTH);
        return new ImageIcon(image);
    }

    @Override
    public String getName(File file) {
        String name = file.getName();
        return name.equals("") ? file.getPath() : name;
    }

    @Override
    public String getTypeDescription(File file) {
        String name = file.getName().toLowerCase();
        dotIndex = name.lastIndexOf(".");
        extension = name.substring(dotIndex + 1);
        if (file.isDirectory()) {
            return "Folder";
        }
        if (name.endsWith(".jpg") || name.endsWith(".jpeg")) {
            return "JPEG Image";
        } else if (name.endsWith(".gif")) {
            return "GIF Image";
        } else if (name.endsWith(".png")) {
            return "PNG Image";
        } else if (name.endsWith(extension)) {
            for (int i = 0; i < format.length; i++) {
                if (extension.equals(format[i])) {
                    return extension.toUpperCase() + " Image";
                }
            }
        }
        return "Generic File";
    }

    @Override
    public Boolean isTraversable(File file) {
        // We'll mark all directories as traversable
        return file.isDirectory() ? Boolean.TRUE : Boolean.FALSE;
    }

    public class IconPreview extends ImageIcon {

        Image image;
        int width, maxWidth = 120;
        int height, maxHeight;
        double aspect;
        String fileName;
        String fileName2;
        BufferedImage buffImage;

        public IconPreview(String filename) {
            // Get the last name
            fileName = filename;
            fileName2 = filename.toLowerCase();
            if (fileName2.endsWith("jpg") || fileName2.endsWith("jpeg") || fileName2.endsWith("png")) {
                image = new ImageIcon(filename).getImage();
            } else {
                try {
                    image = ImageIO.read(new File(filename));

                } catch (IOException ioe) {
                }
            }
            width = image.getWidth(null);
            height = image.getHeight(null);
            aspect = ((double) width) / ((double) height);

            if (width > height) {
                // Fix the width as maxWidth, calculate the maxHeight
                maxWidth = 80;
                maxHeight = (int) (((double) maxWidth) / aspect);
            } else {
                // Fix the height , calculate the maxWidth for this
                maxHeight = 70;
                maxWidth = (int) (((double) maxHeight) * aspect);
            }

            image.setAccelerationPriority(0.9f);
            buffImage = new BufferedImage(maxWidth, maxHeight, BufferedImage.TYPE_INT_RGB);
            Graphics2D g2d = buffImage.createGraphics();
            // Set the rendering hints
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
            g2d.drawImage(image, 0, 0, maxWidth, maxHeight, null);
            g2d.dispose();

            // Faster scaling
            setImage(buffImage);
        }

        @Override
        public int getIconHeight() {
            return maxHeight;
        }

        @Override
        public int getIconWidth() {
            return maxWidth;
        }

        @Override
        public void paintIcon(Component c, Graphics g, int x, int y) {
            g.drawImage(buffImage, x, y, c);
        }
    }
}
```

*s29.postimg.org/lh0m12r83/File_Chooser_Demo2.jpg

*JTreeTable*

Assembles the UI. The UI consists of a JTreeTable and a status label. As
nodes are loaded by the FileSystemModel2, in a background thread, the status
label updates as well as the renderer to draw the node that is being loaded
differently.

FileSystemModel2 class will recursively load all the children from the path it is created with.
The loading is done with the method reloadChildren, and happens in another
thread. The method isReloading can be invoked to check if there are active
threads. The total size of all the files are also accumulated.


```
/**
 * Created with IntelliJ IDEA.
 * User: Sowndar
 * Date: 10/4/14
 * Time: 5:33 PM
 * To change this template use File | Settings | File Templates.
 */
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
import javax.swing.*;
import javax.swing.border.BevelBorder;
import javax.swing.event.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.tree.*;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.IOException;
import java.text.NumberFormat;
import java.util.Date;
import java.util.EventObject;
import java.util.Stack;

/**
 * Assembles the UI. The UI consists of a JTreeTable and a status label. As
 * nodes are loaded by the FileSystemModel2, in a background thread, the status
 * label updates as well as the renderer to draw the node that is being loaded
 * differently.
 *
 * @author Scott Violet
 * @author Philip Milne
 */
public class TreeTableExample {

    /**
     * Number of instances of TreeTableExample2.
     */
    protected static int ttCount;

    /**
     * Model for the JTreeTable.
     */
    protected FileSystemModel2 model;
    /**
     * Used to represent the model.
     */
    protected JTreeTable treeTable;
    /**
     * Row the is being reloaded.
     */
    protected int reloadRow;
    /**
     * TreePath being reloaded.
     */
    protected TreePath reloadPath;
    /**
     * A counter increment as the Timer fies and the same path is being
     * reloaded.
     */
    protected int reloadCounter;
    /**
     * Timer used to update reload state.
     */
    protected Timer timer;
    /**
     * Used to indicate status.
     */
    protected JLabel statusLabel;
    /**
     * Frame containing everything.
     */
    protected JFrame frame;
    /**
     * Path created with.
     */
    protected String path;

    public TreeTableExample(String path) {
        this.path = path;
        ttCount++;

        frame = createFrame();

        Container cPane = frame.getContentPane();
        JMenuBar mb = createMenuBar();

        model = createModel(path);
        treeTable = createTreeTable();
        statusLabel = createStatusLabel();
        cPane.add(new JScrollPane(treeTable));
        cPane.add(statusLabel, BorderLayout.SOUTH);

        reloadRow = -1;
        frame.setJMenuBar(mb);
        Dimension scrDim = Toolkit.getDefaultToolkit().getScreenSize();
        frame.setSize(scrDim.width / 2 + 100, scrDim.height / 2 + 150);
        frame.setVisible(true);

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                reload(model.getRoot());
            }
        });
    }

    /**
     * Creates and return a JLabel that is used to indicate the status of
     * loading.
     */
    protected JLabel createStatusLabel() {
        JLabel retLabel = new JLabel(" ");

        retLabel.setHorizontalAlignment(JLabel.RIGHT);
        retLabel.setBorder(new BevelBorder(BevelBorder.LOWERED));
        return retLabel;
    }

    /**
     * Creates and returns the instanceof JTreeTable that will be used. This
     * also creates, but does not start, the Timer that is used to update the
     * display as files are loaded.
     */
    protected JTreeTable createTreeTable() {
        JTreeTable treeTable = new JTreeTable(model);

        treeTable.getColumnModel().getColumn(1).setCellRenderer(new IndicatorRenderer());

        Reloader rl = new Reloader();

        timer = new Timer(700, rl);
        timer.setRepeats(true);
        treeTable.getTree().addTreeExpansionListener(rl);
        return treeTable;
    }

    /**
     * Creates the FileSystemModel2 that will be used.
     */
    protected FileSystemModel2 createModel(String path) {
        return new FileSystemModel2(path);
    }

    /**
     * Creates the JFrame that will contain everything.
     */
    protected JFrame createFrame() {
        JFrame retFrame = new JFrame("TreeTable II");

        retFrame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent we) {
                if (--ttCount == 0) {
                    System.exit(0);
                }
            }
        });
        return retFrame;
    }

    /**
     * Creates a menu bar.
     */
    protected JMenuBar createMenuBar() {
        JMenu fileMenu = new JMenu("File");
        JMenuItem menuItem;

        menuItem = new JMenuItem("Open");
        menuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                JFileChooser fc = new JFileChooser(path);

                fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

                int result = fc.showOpenDialog(frame);

                if (result == JFileChooser.APPROVE_OPTION) {
                    String newPath = fc.getSelectedFile().getPath();

                    new TreeTableExample(newPath);
                }
            }
        });
        fileMenu.add(menuItem);
        fileMenu.addSeparator();

        menuItem = new JMenuItem("Reload");
        menuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                TreePath path = treeTable.getTree().getSelectionPath();

                if (path != null) {
                    model.stopLoading();
                    reload(path.getLastPathComponent());
                }
            }
        });
        fileMenu.add(menuItem);

        menuItem = new JMenuItem("Stop");
        menuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                model.stopLoading();
            }
        });
        fileMenu.add(menuItem);

        fileMenu.addSeparator();

        menuItem = new JMenuItem("Exit");
        menuItem.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                System.exit(0);
            }
        });
        fileMenu.add(menuItem);

        // Create a menu bar
        JMenuBar menuBar = new JMenuBar();

        menuBar.add(fileMenu);

        // Menu for the look and feels (lafs).
        UIManager.LookAndFeelInfo[] lafs = UIManager.
                getInstalledLookAndFeels();
        ButtonGroup lafGroup = new ButtonGroup();

        JMenu optionsMenu = new JMenu("Options");

        menuBar.add(optionsMenu);

        for (int i = 0; i < lafs.length; i++) {
            JRadioButtonMenuItem rb = new JRadioButtonMenuItem(lafs[i].
                    getName());
            optionsMenu.add(rb);
            rb.setSelected(UIManager.getLookAndFeel().getName().equals(lafs[i].getName()));
            rb.putClientProperty("UIKey", lafs[i]);
            rb.addItemListener(new ItemListener() {
                public void itemStateChanged(ItemEvent ae) {
                    JRadioButtonMenuItem rb2 = (JRadioButtonMenuItem) ae.
                            getSource();
                    if (rb2.isSelected()) {
                        UIManager.LookAndFeelInfo info
                                = (UIManager.LookAndFeelInfo) rb2.getClientProperty("UIKey");
                        try {
                            UIManager.setLookAndFeel(info.getClassName());
                            SwingUtilities.updateComponentTreeUI(frame);
                        } catch (Exception e) {
                            System.err.println("unable to set UI "
                                    + e.getMessage());
                        }
                    }
                }
            });
            lafGroup.add(rb);
        }
        return menuBar;
    }

    /**
     * Invoked to reload the children of a particular node. This will also
     * restart the timer.
     */
    protected void reload(Object node) {
        model.reloadChildren(node);
        if (!timer.isRunning()) {
            timer.start();
        }
    }

    /**
     * Updates the status label based on reloadRow.
     */
    protected void updateStatusLabel() {
        if (reloadPath != null) {
            statusLabel.setText("Reloading: " + model.getPath(reloadPath.getLastPathComponent()));
            if ((reloadCounter % 4) < 2) {
                statusLabel.setForeground(Color.red);
            } else {
                statusLabel.setForeground(Color.blue);
            }
        } else if (!model.isReloading()) {
            statusLabel.setText("Total Size: " + NumberFormat.getInstance().
                    format(model.getTotalSize(model.getRoot())));
            statusLabel.setForeground(Color.black);
        }
    }

    /**
     * Reloader is the ActionListener used in the Timer. In response to the
     * timer updating it will reset the reloadRow/reloadPath and generate the
     * necessary event so that the display will update. It also implements the
     * TreeExpansionListener so that if the tree is altered while loading the
     * reloadRow is updated accordingly.
     */
    class Reloader implements ActionListener, TreeExpansionListener {

        public void actionPerformed(ActionEvent ae) {
            if (!model.isReloading()) {
                // No longer loading.
                timer.stop();
                if (reloadRow != -1) {
                    generateChangeEvent(reloadRow);
                }
                reloadRow = -1;
                reloadPath = null;
            } else {
                // Still loading, see if paths changed.
                TreePath newPath = model.getPathLoading();

                if (newPath == null) {
                    // Hmm... Will usually indicate the reload thread
                    // completed between time we asked if reloading.
                    if (reloadRow != -1) {
                        generateChangeEvent(reloadRow);
                    }
                    reloadRow = -1;
                    reloadPath = null;
                } else {
                    // Ok, valid path, see if matches last path.
                    int newRow = treeTable.getTree().getRowForPath(newPath);

                    if (newPath.equals(reloadPath)) {
                        reloadCounter = (reloadCounter + 1) % 8;
                        if (newRow != reloadRow) {
                            int lastRow = reloadRow;

                            reloadRow = newRow;
                            generateChangeEvent(lastRow);
                        }
                        generateChangeEvent(reloadRow);
                    } else {
                        int lastRow = reloadRow;

                        reloadCounter = 0;
                        reloadRow = newRow;
                        reloadPath = newPath;
                        if (lastRow != reloadRow) {
                            generateChangeEvent(lastRow);
                        }
                        generateChangeEvent(reloadRow);
                    }
                }
            }
            updateStatusLabel();
        }

        /**
         * Generates and update event for the specified row. FileSystemModel2
         * could do this, but it would not know when the row has changed as a
         * result of expanding/collapsing nodes in the tree.
         */
        protected void generateChangeEvent(int row) {
            if (row != -1) {
                AbstractTableModel tModel = (AbstractTableModel) treeTable.
                        getModel();

                tModel.fireTableChanged(new TableModelEvent(tModel, row, row, 1));
            }
        }

        //
        // TreeExpansionListener
        //
        /**
         * Invoked when the tree has expanded.
         */
        public void treeExpanded(TreeExpansionEvent te) {
            updateRow();
        }

        /**
         * Invoked when the tree has collapsed.
         */
        public void treeCollapsed(TreeExpansionEvent te) {
            updateRow();
        }

        /**
         * Updates the reloadRow and path, this does not genernate a change
         * event.
         */
        protected void updateRow() {
            reloadPath = model.getPathLoading();

            if (reloadPath != null) {
                reloadRow = treeTable.getTree().getRowForPath(reloadPath);
            }
        }
    }

    /**
     * A renderer that will give an indicator when a cell is being reloaded.
     */
    class IndicatorRenderer extends DefaultTableCellRenderer {

        /**
         * Makes sure the number of displayed in an internationalized manner.
         */
        protected NumberFormat formatter;
        /**
         * Row that is currently being painted.
         */
        protected int lastRow;

        IndicatorRenderer() {
            setHorizontalAlignment(JLabel.RIGHT);
            formatter = NumberFormat.getInstance();
        }

        /**
         * Invoked as part of DefaultTableCellRenderers implemention. Sets the
         * text of the label.
         */
        public void setValue(Object value) {
            setText((value == null) ? "---" : formatter.format(value));
        }

        /**
         * Returns this.
         */
        public Component getTableCellRendererComponent(JTable table,
                                                       Object value, boolean isSelected, boolean hasFocus,
                                                       int row, int column) {
            super.getTableCellRendererComponent(table, value, isSelected,
                    hasFocus, row, column);
            lastRow = row;
            return this;
        }

        /**
         * If the row being painted is also being reloaded this will draw a
         * little indicator.
         */
        @Override
        public void paint(Graphics g) {
            if (lastRow == reloadRow) {
                int width = getWidth();
                int height = getHeight();

                g.setColor(getBackground());
                g.fillRect(0, 0, width, height);
                g.setColor(getForeground());

                int diameter = Math.min(width, height);

                if (reloadCounter < 5) {
                    g.fillArc((width - diameter) / 2, (height - diameter) / 2,
                            diameter, diameter, 90, -(reloadCounter * 90));
                } else {
                    g.fillArc((width - diameter) / 2, (height - diameter) / 2,
                            diameter, diameter, 90,
                            (4 - reloadCounter % 4) * 90);
                }
            } else {
                super.paint(g);
            }
        }
    }

    public static void main(final String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                if (args.length > 0) {
                    for (int counter = args.length - 1; counter >= 0; counter--) {
                        new TreeTableExample(args[counter]);
                    }
                } else {
                    String path;

                    try {
                        path = System.getProperty("user.home");
                        if (path != null) {
                            new TreeTableExample(path);
                        }
                    } catch (SecurityException se) {
                        path = null;
                    }
                    if (path == null) {
                        System.out.println("Could not determine home directory");
                    }
                }
            }
        });
    }
}


class AbstractCellEditor implements CellEditor {

    protected EventListenerList listenerList = new EventListenerList();

    public Object getCellEditorValue() {
        return null;
    }

    public boolean isCellEditable(EventObject e) {
        return true;
    }

    public boolean shouldSelectCell(EventObject anEvent) {
        return false;
    }

    public boolean stopCellEditing() {
        return true;
    }

    public void cancelCellEditing() {
    }

    public void addCellEditorListener(CellEditorListener l) {
        listenerList.add(CellEditorListener.class, l);
    }

    public void removeCellEditorListener(CellEditorListener l) {
        listenerList.remove(CellEditorListener.class, l);
    }

    /*
     * Notify all listeners that have registered interest for
     * notification on this event type.
     * [MENTION=288550]see[/MENTION] EventListenerList
     */
    protected void fireEditingStopped() {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == CellEditorListener.class) {
                ((CellEditorListener) listeners[i + 1]).editingStopped(new ChangeEvent(this));
            }
        }
    }

    /*
     * Notify all listeners that have registered interest for
     * notification on this event type.
     * [MENTION=288550]see[/MENTION] EventListenerList
     */
    protected void fireEditingCanceled() {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == CellEditorListener.class) {
                ((CellEditorListener) listeners[i + 1]).editingCanceled(new ChangeEvent(this));
            }
        }
    }
}



/**
 * @version 1.2 10/27/98 An abstract implementation of the TreeTableModel
 * interface, handling the list of listeners.
 * @author Philip Milne
 */
abstract class AbstractTreeTableModel implements TreeTableModel {

    protected Object root;
    protected EventListenerList listenerList = new EventListenerList();

    public AbstractTreeTableModel(Object root) {
        this.root = root;
    }

    //
    // Default implmentations for methods in the TreeModel interface.
    //
    public Object getRoot() {
        return root;
    }

    public boolean isLeaf(Object node) {
        return getChildCount(node) == 0;
    }

    public void valueForPathChanged(TreePath path, Object newValue) {
    }

    // This is not called in the JTree's default mode: use a naive implementation.
    public int getIndexOfChild(Object parent, Object child) {
        for (int i = 0; i < getChildCount(parent); i++) {
            if (getChild(parent, i).equals(child)) {
                return i;
            }
        }
        return -1;
    }

    public void addTreeModelListener(TreeModelListener l) {
        listenerList.add(TreeModelListener.class, l);
    }

    public void removeTreeModelListener(TreeModelListener l) {
        listenerList.remove(TreeModelListener.class, l);
    }

    /*
     * Notify all listeners that have registered interest for
     * notification on this event type.  The event instance
     * is lazily created using the parameters passed into
     * the fire method.
     * [MENTION=288550]see[/MENTION] EventListenerList
     */
    protected void fireTreeNodesChanged(Object source, Object[] path,
                                        int[] childIndices,
                                        Object[] children) {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        TreeModelEvent e = null;
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == TreeModelListener.class) {
                // Lazily create the event:
                if (e == null) {
                    e = new TreeModelEvent(source, path,
                            childIndices, children);
                }
                ((TreeModelListener) listeners[i + 1]).treeNodesChanged(e);
            }
        }
    }

    /*
     * Notify all listeners that have registered interest for
     * notification on this event type.  The event instance
     * is lazily created using the parameters passed into
     * the fire method.
     * [MENTION=288550]see[/MENTION] EventListenerList
     */
    protected void fireTreeNodesInserted(Object source, Object[] path,
                                         int[] childIndices,
                                         Object[] children) {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        TreeModelEvent e = null;
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == TreeModelListener.class) {
                // Lazily create the event:
                if (e == null) {
                    e = new TreeModelEvent(source, path,
                            childIndices, children);
                }
                ((TreeModelListener) listeners[i + 1]).treeNodesInserted(e);
            }
        }
    }

    /*
     * Notify all listeners that have registered interest for
     * notification on this event type.  The event instance
     * is lazily created using the parameters passed into
     * the fire method.
     * [MENTION=288550]see[/MENTION] EventListenerList
     */
    protected void fireTreeNodesRemoved(Object source, Object[] path,
                                        int[] childIndices,
                                        Object[] children) {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        TreeModelEvent e = null;
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == TreeModelListener.class) {
                // Lazily create the event:
                if (e == null) {
                    e = new TreeModelEvent(source, path,
                            childIndices, children);
                }
                ((TreeModelListener) listeners[i + 1]).treeNodesRemoved(e);
            }
        }
    }

    /*
     * Notify all listeners that have registered interest for
     * notification on this event type.  The event instance
     * is lazily created using the parameters passed into
     * the fire method.
     * [MENTION=288550]see[/MENTION] EventListenerList
     */
    protected void fireTreeStructureChanged(Object source, Object[] path,
                                            int[] childIndices,
                                            Object[] children) {
        // Guaranteed to return a non-null array
        Object[] listeners = listenerList.getListenerList();
        TreeModelEvent e = null;
        // Process the listeners last to first, notifying
        // those that are interested in this event
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == TreeModelListener.class) {
                // Lazily create the event:
                if (e == null) {
                    e = new TreeModelEvent(source, path,
                            childIndices, children);
                }
                ((TreeModelListener) listeners[i + 1]).treeStructureChanged(e);
            }
        }
    }

    //
    // Default impelmentations for methods in the TreeTableModel interface.
    //
    public Class getColumnClass(int column) {
        return Object.class;
    }

    /**
     * By default, make the column with the Tree in it the only editable one.
     * Making this column editable causes the JTable to forward mouse and
     * keyboard events in the Tree column to the underlying JTree.
     */
    public boolean isCellEditable(Object node, int column) {
        return getColumnClass(column) == TreeTableModel.class;
    }

    public void setValueAt(Object aValue, Object node, int column) {
    }

    // Left to be implemented in the subclass:

    /*
     *   public Object getChild(Object parent, int index)
     *   public int getChildCount(Object parent)
     *   public int getColumnCount()
     *   public String getColumnName(Object node, int column)
     *   public Object getValueAt(Object node, int column)
     */
}

/**
 * FileSystemModel2 is a TreeTableModel representing a hierarchical file
 * system.<p>
 * This will recursively load all the children from the path it is created with.
 * The loading is done with the method reloadChildren, and happens in another
 * thread. The method isReloading can be invoked to check if there are active
 * threads. The total size of all the files are also accumulated.
 * <p>
 * By default links are not descended. java.io.File does not have a way to
 * distinguish links, so a file is assumed to be a link if its canonical path
 * does not start with its parent path. This may not cover all cases, but works
 * for the time being.
 * <p>
 * Reloading happens such that all the files of the directory are loaded and
 * immediately available. The background thread then recursively loads all the
 * files of each of those directories. When each directory has finished loading
 * all its sub files they are attached and an event is generated in the event
 * dispatching thread. A more ambitious approach would be to attach each set of
 * directories as they are loaded and generate an event. Then, once all the
 * direct descendants of the directory being reloaded have finished loading, it
 * is resorted based on total size.
 * <p>
 * While you can invoke reloadChildren as many times as you want, care should be
 * taken in doing this. You should not invoke reloadChildren on a node that is
 * already being reloaded, or going to be reloaded (meaning its parent is
 * reloading but it hasn't started reloading that directory yet). If this is
 * done odd results may happen. FileSystemModel2 does not enforce any policy in
 * this manner, and it is up to the user of FileSystemModel2 to ensure it
 * doesn't happen.
 *
 * @version 1.12 05/12/98
 * @author Philip Milne
 * @author Scott Violet
 */
class FileSystemModel2 extends AbstractTreeTableModel {

    // Names of the columns.
    static protected String[] cNames = {"Name", "Size", "Type", "Modified"};

    // Types of the columns.
    static protected Class[] cTypes = {TreeTableModel.class,
            Integer.class, String.class,
            Date.class};

    // The the returned file length for directories.
    public static final Integer ZERO = new Integer(0);

    /**
     * An array of MergeSorter sorters, that will sort based on size.
     */
    static Stack sorters = new Stack();

    /**
     * True if the receiver is valid, once set to false all Threads loading
     * files will stop.
     */
    protected boolean isValid;

    /**
     * Node currently being reloaded, this becomes somewhat muddy if reloading
     * is happening in multiple threads.
     */
    protected FileNode reloadNode;

    /**
     * > 0 indicates reloading some nodes.
     */
    int reloadCount;

    /**
     * Returns true if links are to be descended.
     */
    protected boolean descendLinks;

    /**
     * Returns a MergeSort that can sort on the totalSize of a FileNode.
     */
    protected static MergeSort getSizeSorter() {
        synchronized (sorters) {
            if (sorters.size() == 0) {
                return new SizeSorter();
            }
            return (MergeSort) sorters.pop();
        }
    }

    /**
     * Should be invoked when a MergeSort is no longer needed.
     */
    protected static void recycleSorter(MergeSort sorter) {
        synchronized (sorters) {
            sorters.push(sorter);
        }
    }

    /**
     * Creates a FileSystemModel2 rooted at File.separator, which is usually the
     * root of the file system. This does not load it, you should invoke
     * <code>reloadChildren</code> with the root to start loading.
     */
    public FileSystemModel2() {
        this(File.separator);
    }

    /**
     * Creates a FileSystemModel2 with the root being
     * <code>rootPath</code>. This does not load it, you should invoke
     * <code>reloadChildren</code> with the root to start loading.
     */
    public FileSystemModel2(String rootPath) {
        super(null);
        isValid = true;
        root = new FileNode(new File(rootPath));
    }

    //
    // The TreeModel interface
    //
    /**
     * Returns the number of children of
     * <code>node</code>.
     */
    public int getChildCount(Object node) {
        Object[] children = getChildren(node);
        return (children == null) ? 0 : children.length;
    }

    /**
     * Returns the child of
     * <code>node</code> at index
     * <code>i</code>.
     */
    public Object getChild(Object node, int i) {
        return getChildren(node)[i];
    }

    /**
     * Returns true if the passed in object represents a leaf, false otherwise.
     */
    public boolean isLeaf(Object node) {
        return ((FileNode) node).isLeaf();
    }

    //
    //  The TreeTableNode interface.
    //
    /**
     * Returns the number of columns.
     */
    public int getColumnCount() {
        return cNames.length;
    }

    /**
     * Returns the name for a particular column.
     */
    public String getColumnName(int column) {
        return cNames[column];
    }

    /**
     * Returns the class for the particular column.
     */
    public Class getColumnClass(int column) {
        return cTypes[column];
    }

    /**
     * Returns the value of the particular column.
     */
    public Object getValueAt(Object node, int column) {
        FileNode fn = (FileNode) node;

        try {
            switch (column) {
                case 0:
                    return fn.getFile().getName();
                case 1:
                    if (fn.isTotalSizeValid()) {
                        return new Integer((int) ((FileNode) node).totalSize());
                    }
                    return null;
                case 2:
                    return fn.isLeaf() ? "File" : "Directory";
                case 3:
                    return fn.lastModified();
            }
        } catch (SecurityException se) {
        }

        return null;
    }

    //
    // Some convenience methods.
    //
    /**
     * Reloads the children of the specified node.
     */
    public void reloadChildren(Object node) {
        FileNode fn = (FileNode) node;

        synchronized (this) {
            reloadCount++;
        }
        fn.resetSize();
        new Thread(new FileNodeLoader((FileNode) node)).start();
    }

    /**
     * Stops and waits for all threads to finish loading.
     */
    public void stopLoading() {
        isValid = false;
        synchronized (this) {
            while (reloadCount > 0) {
                try {
                    wait();
                } catch (InterruptedException ie) {
                }
            }
        }
        isValid = true;
    }

    /**
     * If
     * <code>newValue</code> is true, links are descended. Odd results may
     * happen if you set this while other threads are loading.
     */
    public void setDescendsLinks(boolean newValue) {
        descendLinks = newValue;
    }

    /**
     * Returns true if links are to be automatically descended.
     */
    public boolean getDescendsLinks() {
        return descendLinks;
    }

    /**
     * Returns the path
     * <code>node</code> represents.
     */
    public String getPath(Object node) {
        return ((FileNode) node).getFile().getPath();
    }

    /**
     * Returns the total size of the receiver.
     */
    public long getTotalSize(Object node) {
        return ((FileNode) node).totalSize();
    }

    /**
     * Returns true if the receiver is loading any children.
     */
    public boolean isReloading() {
        return (reloadCount > 0);
    }

    /**
     * Returns the path to the node that is being loaded.
     */
    public TreePath getPathLoading() {
        FileNode rn = reloadNode;

        if (rn != null) {
            return new TreePath(rn.getPath());
        }
        return null;
    }

    /**
     * Returns the node being loaded.
     */
    public Object getNodeLoading() {
        return reloadNode;
    }

    protected File getFile(Object node) {
        FileNode fileNode = ((FileNode) node);
        return fileNode.getFile();
    }

    protected Object[] getChildren(Object node) {
        FileNode fileNode = ((FileNode) node);
        return fileNode.getChildren();
    }

    protected static FileNode[] EMPTY_CHILDREN = new FileNode[0];

    // Used to sort the file names.
    static private MergeSort fileMS = new MergeSort() {
        public int compareElementsAt(int beginLoc, int endLoc) {
            return ((String) toSort[beginLoc]).compareTo((String) toSort[endLoc]);
        }
    };

    /**
     * A FileNode is a derivative of the File class - though we delegate to the
     * File object rather than subclassing it. It is used to maintain a cache of
     * a directory's children and therefore avoid repeated access to the
     * underlying file system during rendering.
     */
    class FileNode {

        /**
         * java.io.File the receiver represents.
         */
        protected File file;
        /**
         * Parent FileNode of the receiver.
         */
        private FileNode parent;
        /**
         * Children of the receiver.
         */
        protected FileNode[] children;
        /**
         * Size of the receiver and all its children.
         */
        protected long totalSize;
        /**
         * Valid if the totalSize has finished being calced.
         */
        protected boolean totalSizeValid;
        /**
         * Path of the receiver.
         */
        protected String canonicalPath;
        /**
         * True if the canonicalPath of this instance does not start with the
         * canonical path of the parent.
         */
        protected boolean isLink;
        /**
         * Date last modified.
         */
        protected Date lastModified;

        protected FileNode(File file) {
            this(null, file);
        }

        protected FileNode(FileNode parent, File file) {
            this.parent = parent;
            this.file = file;
            try {
                canonicalPath = file.getCanonicalPath();
            } catch (IOException ioe) {
                canonicalPath = "";
            }
            if (parent != null) {
                isLink = !canonicalPath.startsWith(parent.getCanonicalPath());
            } else {
                isLink = false;
            }
            if (isLeaf()) {
                totalSize = file.length();
                totalSizeValid = true;
            }
        }

        /**
         * Returns the date the receiver was last modified.
         */
        public Date lastModified() {
            if (lastModified == null && file != null) {
                lastModified = new Date(file.lastModified());
            }
            return lastModified;
        }

        /**
         * Returns the the string to be used to display this leaf in the JTree.
         */
        public String toString() {
            return file.getName();
        }

        /**
         * Returns the java.io.File the receiver represents.
         */
        public File getFile() {
            return file;
        }

        /**
         * Returns size of the receiver and all its children.
         */
        public long totalSize() {
            return totalSize;
        }

        /**
         * Returns the parent of the receiver.
         */
        public FileNode getParent() {
            return parent;
        }

        /**
         * Returns true if the receiver represents a leaf, that is it is isn't a
         * directory.
         */
        public boolean isLeaf() {
            return file.isFile();
        }

        /**
         * Returns true if the total size is valid.
         */
        public boolean isTotalSizeValid() {
            return totalSizeValid;
        }

        /**
         * Clears the date.
         */
        protected void resetLastModified() {
            lastModified = null;
        }

        /**
         * Sets the size of the receiver to be 0.
         */
        protected void resetSize() {
            alterTotalSize(-totalSize);
        }

        /**
         * Loads the children, caching the results in the children instance
         * variable.
         */
        protected FileNode[] getChildren() {
            return children;
        }

        /**
         * Recursively loads all the children of the receiver.
         */
        protected void loadChildren(MergeSort sorter) {
            totalSize = file.length();
            children = createChildren(null);
            for (int counter = children.length - 1; counter >= 0; counter--) {
                Thread.yield(); // Give the GUI CPU time to draw itself.
                if (!children[counter].isLeaf()
                        && (descendLinks || !children[counter].isLink())) {
                    children[counter].loadChildren(sorter);
                }
                totalSize += children[counter].totalSize();
                if (!isValid) {
                    counter = 0;
                }
            }
            if (isValid) {
                if (sorter != null) {
                    sorter.sort(children);
                }
                totalSizeValid = true;
            }
        }

        /**
         * Loads the children of of the receiver.
         */
        protected FileNode[] createChildren(MergeSort sorter) {
            FileNode[] retArray = null;

            try {
                String[] files = file.list();
                if (files != null) {
                    if (sorter != null) {
                        sorter.sort(files);
                    }
                    retArray = new FileNode[files.length];
                    String path = file.getPath();
                    for (int i = 0; i < files.length; i++) {
                        File childFile = new File(path, files[i]);
                        retArray[i] = new FileNode(this, childFile);
                    }
                }
            } catch (SecurityException se) {
            }
            if (retArray == null) {
                retArray = EMPTY_CHILDREN;
            }
            return retArray;
        }

        /**
         * Returns true if the children have been loaded.
         */
        protected boolean loadedChildren() {
            return (file.isFile() || (children != null));
        }

        /**
         * Gets the path from the root to the receiver.
         */
        public FileNode[] getPath() {
            return getPathToRoot(this, 0);
        }

        /**
         * Returns the canonical path for the receiver.
         */
        public String getCanonicalPath() {
            return canonicalPath;
        }

        /**
         * Returns true if the receiver's path does not begin with the parent's
         * canonical path.
         */
        public boolean isLink() {
            return isLink;
        }

        protected FileNode[] getPathToRoot(FileNode aNode, int depth) {
            FileNode[] retNodes;

            if (aNode == null) {
                if (depth == 0) {
                    return null;
                } else {
                    retNodes = new FileNode[depth];
                }
            } else {
                depth++;
                retNodes = getPathToRoot(aNode.getParent(), depth);
                retNodes[retNodes.length - depth] = aNode;
            }
            return retNodes;
        }

        /**
         * Sets the children of the receiver, updates the total size, and if
         * generateEvent is true a tree structure changed event is created.
         */
        protected void setChildren(FileNode[] newChildren,
                                   boolean generateEvent) {
            long oldSize = totalSize;

            totalSize = file.length();
            children = newChildren;
            for (int counter = children.length - 1; counter >= 0;
                 counter--) {
                totalSize += children[counter].totalSize();
            }

            if (generateEvent) {
                FileNode[] path = getPath();

                fireTreeStructureChanged(FileSystemModel2.this, path, null,
                        null);

                FileNode parent = getParent();

                if (parent != null) {
                    parent.alterTotalSize(totalSize - oldSize);
                }
            }
        }

        protected synchronized void alterTotalSize(long sizeDelta) {
            if (sizeDelta != 0 && (parent = getParent()) != null) {
                totalSize += sizeDelta;
                nodeChanged();
                parent.alterTotalSize(sizeDelta);
            } else {
                // Need a way to specify the root.
                totalSize += sizeDelta;
            }
        }

        /**
         * This should only be invoked on the event dispatching thread.
         */
        protected synchronized void setTotalSizeValid(boolean newValue) {
            if (totalSizeValid != newValue) {
                nodeChanged();
                totalSizeValid = newValue;

                FileNode parent = getParent();

                if (parent != null) {
                    parent.childTotalSizeChanged(this);
                }
            }
        }

        /**
         * Marks the receivers total size as valid, but does not invoke node
         * changed, nor message the parent.
         */
        protected synchronized void forceTotalSizeValid() {
            totalSizeValid = true;
        }

        /**
         * Invoked when a childs total size has changed.
         */
        protected synchronized void childTotalSizeChanged(FileNode child) {
            if (totalSizeValid != child.isTotalSizeValid()) {
                if (totalSizeValid) {
                    setTotalSizeValid(false);
                } else {
                    FileNode[] children = getChildren();

                    for (int counter = children.length - 1; counter >= 0;
                         counter--) {
                        if (!children[counter].isTotalSizeValid()) {
                            return;
                        }
                    }
                    setTotalSizeValid(true);
                }
            }

        }

        /**
         * Can be invoked when a node has changed, will create the appropriate
         * event.
         */
        protected void nodeChanged() {
            FileNode parent = getParent();

            if (parent != null) {
                FileNode[] path = parent.getPath();
                int[] index = {getIndexOfChild(parent, this)};
                Object[] children = {this};

                fireTreeNodesChanged(FileSystemModel2.this, path, index,
                        children);
            }
        }
    }

    /**
     * FileNodeLoader can be used to reload all the children of a particular
     * node. It first resets the children of the FileNode it is created with,
     * and in its run method will reload all of that nodes children.
     * FileNodeLoader may not be running in the event dispatching thread. As
     * swing is not thread safe it is important that we don't generate events in
     * this thread. SwingUtilities.invokeLater is used so that events are
     * generated in the event dispatching thread.
     */
    class FileNodeLoader implements Runnable {

        /**
         * Node creating children for.
         */
        FileNode node;
        /**
         * Sorter.
         */
        MergeSort sizeMS;

        FileNodeLoader(FileNode node) {
            this.node = node;
            node.resetLastModified();
            node.setChildren(node.createChildren(fileMS), true);
            node.setTotalSizeValid(false);
        }

        public void run() {
            FileNode[] children = node.getChildren();

            sizeMS = getSizeSorter();
            for (int counter = children.length - 1; counter >= 0; counter--) {
                if (!children[counter].isLeaf()) {
                    reloadNode = children[counter];
                    loadChildren(children[counter]);
                    reloadNode = null;
                }
                if (!isValid) {
                    counter = 0;
                }
            }
            recycleSorter(sizeMS);
            if (isValid) {
                SwingUtilities.invokeLater(new Runnable() {
                    public void run() {
                        MergeSort sorter = getSizeSorter();

                        sorter.sort(node.getChildren());
                        recycleSorter(sorter);
                        node.setChildren(node.getChildren(), true);
                        synchronized (FileSystemModel2.this) {
                            reloadCount--;
                            FileSystemModel2.this.notifyAll();
                        }
                    }
                });
            } else {
                synchronized (FileSystemModel2.this) {
                    reloadCount--;
                    FileSystemModel2.this.notifyAll();
                }
            }
        }

        protected void loadChildren(FileNode node) {
            if (!node.isLeaf() && (descendLinks || !node.isLink())) {
                final FileNode[] children = node.createChildren(null);

                for (int counter = children.length - 1; counter >= 0;
                     counter--) {
                    if (!children[counter].isLeaf()) {
                        if (descendLinks || !children[counter].isLink()) {
                            children[counter].loadChildren(sizeMS);
                        } else {
                            children[counter].forceTotalSizeValid();
                        }
                    }
                    if (!isValid) {
                        counter = 0;
                    }
                }
                if (isValid) {
                    final FileNode fn = node;

                    // Reset the children
                    SwingUtilities.invokeLater(new Runnable() {
                        public void run() {
                            MergeSort sorter = getSizeSorter();

                            sorter.sort(children);
                            recycleSorter(sorter);
                            fn.setChildren(children, true);
                            fn.setTotalSizeValid(true);
                            fn.nodeChanged();
                        }
                    });
                }
            } else {
                node.forceTotalSizeValid();
            }
        }
    }

    /**
     * Sorts the contents, which must be instances of FileNode based on
     * totalSize.
     */
    static class SizeSorter extends MergeSort {

        public int compareElementsAt(int beginLoc, int endLoc) {
            long firstSize = ((FileNode) toSort[beginLoc]).totalSize();
            long secondSize = ((FileNode) toSort[endLoc]).totalSize();

            if (firstSize != secondSize) {
                return (int) (secondSize - firstSize);
            }
            return ((FileNode) toSort[beginLoc]).toString().compareTo(((FileNode) toSort[endLoc]).toString());
        }
    }
}

/**
 * This example shows how to create a simple JTreeTable component, by using a
 * JTree as a renderer (and editor) for the cells in a particular column in the
 * JTable.
 *
 * @version 1.2 10/27/98
 *
 * @author Philip Milne
 * @author Scott Violet
 */
class JTreeTable extends JTable {

    /**
     * A subclass of JTree.
     */
    protected TreeTableCellRenderer tree;

    public JTreeTable(TreeTableModel treeTableModel) {
        super();

        // Create the tree. It will be used as a renderer and editor.
        tree = new TreeTableCellRenderer(treeTableModel);

        // Install a tableModel representing the visible rows in the tree.
        super.setModel(new TreeTableModelAdapter(treeTableModel, tree));

        // Force the JTable and JTree to share their row selection models.
        ListToTreeSelectionModelWrapper selectionWrapper = new ListToTreeSelectionModelWrapper();
        tree.setSelectionModel(selectionWrapper);
        setSelectionModel(selectionWrapper.getListSelectionModel());

        // Install the tree editor renderer and editor.
        setDefaultRenderer(TreeTableModel.class, tree);
        setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());

        // No grid.
        setShowGrid(false);

        // No intercell spacing
        setIntercellSpacing(new Dimension(0, 0));

        // And update the height of the trees row to match that of
        // the table.
        if (tree.getRowHeight() < 1) {
            // Metal looks better like this.
            setRowHeight(18);
        }
    }

    /**
     * Overridden to message super and forward the method to the tree. Since the
     * tree is not actually in the component hieachy it will never receive this
     * unless we forward it in this manner.
     */
    public void updateUI() {
        super.updateUI();
        if (tree != null) {
            tree.updateUI();
        }
        // Use the tree's default foreground and background colors in the
        // table.
        LookAndFeel.installColorsAndFont(this, "Tree.background",
                "Tree.foreground", "Tree.font");
    }

    /* Workaround for BasicTableUI anomaly. Make sure the UI never tries to
     * paint the editor. The UI currently uses different techniques to
     * paint the renderers and editors and overriding setBounds() below
     * is not the right thing to do for an editor. Returning -1 for the
     * editing row in this case, ensures the editor is never painted.
     */
    public int getEditingRow() {
        return (getColumnClass(editingColumn) == TreeTableModel.class) ? -1
                : editingRow;
    }

    /**
     * Overridden to pass the new rowHeight to the tree.
     */
    public void setRowHeight(int rowHeight) {
        super.setRowHeight(rowHeight);
        if (tree != null && tree.getRowHeight() != rowHeight) {
            tree.setRowHeight(getRowHeight());
        }
    }

    /**
     * Returns the tree that is being shared between the model.
     */
    public JTree getTree() {
        return tree;
    }

    /**
     * A TreeCellRenderer that displays a JTree.
     */
    public class TreeTableCellRenderer extends JTree implements
            TableCellRenderer {

        /**
         * Last table/tree row asked to renderer.
         */
        protected int visibleRow;

        public TreeTableCellRenderer(TreeModel model) {
            super(model);
        }

        /**
         * updateUI is overridden to set the colors of the Tree's renderer to
         * match that of the table.
         */
        public void updateUI() {
            super.updateUI();
            // Make the tree's cell renderer use the table's cell selection
            // colors.
            TreeCellRenderer tcr = getCellRenderer();
            if (tcr instanceof DefaultTreeCellRenderer) {
                DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer) tcr);
                // For 1.1 uncomment this, 1.2 has a bug that will cause an
                // exception to be thrown if the border selection color is
                // null.
                // dtcr.setBorderSelectionColor(null);
                dtcr.setTextSelectionColor(UIManager.getColor("Table.selectionForeground"));
                dtcr.setBackgroundSelectionColor(UIManager.getColor("Table.selectionBackground"));
            }
        }

        /**
         * Sets the row height of the tree, and forwards the row height to the
         * table.
         */
        public void setRowHeight(int rowHeight) {
            if (rowHeight > 0) {
                super.setRowHeight(rowHeight);
                if (JTreeTable.this != null
                        && JTreeTable.this.getRowHeight() != rowHeight) {
                    JTreeTable.this.setRowHeight(getRowHeight());
                }
            }
        }

        /**
         * This is overridden to set the height to match that of the JTable.
         */
        public void setBounds(int x, int y, int w, int h) {
            super.setBounds(x, 0, w, JTreeTable.this.getHeight());
        }

        /**
         * Sublcassed to translate the graphics such that the last visible row
         * will be drawn at 0,0.
         */
        public void paint(Graphics g) {
            g.translate(0, -visibleRow * getRowHeight());
            super.paint(g);
        }

        /**
         * TreeCellRenderer method. Overridden to update the visible row.
         */
        public Component getTableCellRendererComponent(JTable table,
                                                       Object value,
                                                       boolean isSelected,
                                                       boolean hasFocus,
                                                       int row, int column) {
            if (isSelected) {
                setBackground(table.getSelectionBackground());
            } else {
                setBackground(table.getBackground());
            }

            visibleRow = row;
            return this;
        }
    }

    /**
     * TreeTableCellEditor implementation. Component returned is the JTree.
     */
    public class TreeTableCellEditor extends AbstractCellEditor implements
            TableCellEditor {

        public Component getTableCellEditorComponent(JTable table,
                                                     Object value,
                                                     boolean isSelected,
                                                     int r, int c) {
            return tree;
        }

        /**
         * Overridden to return false, and if the event is a mouse event it is
         * forwarded to the tree.<p>
         * The behavior for this is debatable, and should really be offered as a
         * property. By returning false, all keyboard actions are implemented in
         * terms of the table. By returning true, the tree would get a chance to
         * do something with the keyboard events. For the most part this is ok.
         * But for certain keys, such as left/right, the tree will
         * expand/collapse where as the table focus should really move to a
         * different column. Page up/down should also be implemented in terms of
         * the table. By returning false this also has the added benefit that
         * clicking outside of the bounds of the tree node, but still in the
         * tree column will select the row, whereas if this returned true that
         * wouldn't be the case.
         * <p>
         * By returning false we are also enforcing the policy that the tree
         * will never be editable (at least by a key sequence).
         */
        public boolean isCellEditable(EventObject e) {
            if (e instanceof MouseEvent) {
                for (int counter = getColumnCount() - 1; counter >= 0;
                     counter--) {
                    if (getColumnClass(counter) == TreeTableModel.class) {
                        MouseEvent me = (MouseEvent) e;
                        MouseEvent newME = new MouseEvent(tree, me.getID(),
                                me.getWhen(), me.getModifiers(),
                                me.getX() - getCellRect(0, counter, true).x,
                                me.getY(), me.getClickCount(),
                                me.isPopupTrigger());
                        tree.dispatchEvent(newME);
                        break;
                    }
                }
            }
            return false;
        }
    }

    /**
     * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel to
     * listen for changes in the ListSelectionModel it maintains. Once a change
     * in the ListSelectionModel happens, the paths are updated in the
     * DefaultTreeSelectionModel.
     */
    class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel {

        /**
         * Set to true when we are updating the ListSelectionModel.
         */
        protected boolean updatingListSelectionModel;

        public ListToTreeSelectionModelWrapper() {
            super();
            getListSelectionModel().addListSelectionListener(createListSelectionListener());
        }

        /**
         * Returns the list selection model. ListToTreeSelectionModelWrapper
         * listens for changes to this model and updates the selected paths
         * accordingly.
         */
        ListSelectionModel getListSelectionModel() {
            return listSelectionModel;
        }

        /**
         * This is overridden to set
         * <code>updatingListSelectionModel</code> and message super. This is
         * the only place DefaultTreeSelectionModel alters the
         * ListSelectionModel.
         */
        public void resetRowSelection() {
            if (!updatingListSelectionModel) {
                updatingListSelectionModel = true;
                try {
                    super.resetRowSelection();
                } finally {
                    updatingListSelectionModel = false;
                }
            }
            // Notice how we don't message super if
            // updatingListSelectionModel is true. If
            // updatingListSelectionModel is true, it implies the
            // ListSelectionModel has already been updated and the
            // paths are the only thing that needs to be updated.
        }

        /**
         * Creates and returns an instance of ListSelectionHandler.
         */
        protected ListSelectionListener createListSelectionListener() {
            return new ListSelectionHandler();
        }

        /**
         * If
         * <code>updatingListSelectionModel</code> is false, this will reset the
         * selected paths from the selected rows in the list selection model.
         */
        protected void updateSelectedPathsFromSelectedRows() {
            if (!updatingListSelectionModel) {
                updatingListSelectionModel = true;
                try {
                    // This is way expensive, ListSelectionModel needs an
                    // enumerator for iterating.
                    int min = listSelectionModel.getMinSelectionIndex();
                    int max = listSelectionModel.getMaxSelectionIndex();

                    clearSelection();
                    if (min != -1 && max != -1) {
                        for (int counter = min; counter <= max; counter++) {
                            if (listSelectionModel.isSelectedIndex(counter)) {
                                TreePath selPath = tree.getPathForRow(counter);

                                if (selPath != null) {
                                    addSelectionPath(selPath);
                                }
                            }
                        }
                    }
                } finally {
                    updatingListSelectionModel = false;
                }
            }
        }

        /**
         * Class responsible for calling updateSelectedPathsFromSelectedRows
         * when the selection of the list changes.
         */
        class ListSelectionHandler implements ListSelectionListener {

            public void valueChanged(ListSelectionEvent e) {
                updateSelectedPathsFromSelectedRows();
            }
        }
    }
}


/**
 * An implementation of MergeSort, needs to be subclassed to compare the terms.
 *
 * @author Scott Violet
 */
abstract class MergeSort extends Object {

    protected Object toSort[];
    protected Object swapSpace[];

    public void sort(Object array[]) {
        if (array != null && array.length > 1) {
            int maxLength;

            maxLength = array.length;
            swapSpace = new Object[maxLength];
            toSort = array;
            this.mergeSort(0, maxLength - 1);
            swapSpace = null;
            toSort = null;
        }
    }

    public abstract int compareElementsAt(int beginLoc, int endLoc);

    protected void mergeSort(int begin, int end) {
        if (begin != end) {
            int mid;

            mid = (begin + end) / 2;
            this.mergeSort(begin, mid);
            this.mergeSort(mid + 1, end);
            this.merge(begin, mid, end);
        }
    }

    protected void merge(int begin, int middle, int end) {
        int firstHalf, secondHalf, count;

        firstHalf = count = begin;
        secondHalf = middle + 1;
        while ((firstHalf <= middle) && (secondHalf <= end)) {
            if (this.compareElementsAt(secondHalf, firstHalf) < 0) {
                swapSpace[count++] = toSort[secondHalf++];
            } else {
                swapSpace[count++] = toSort[firstHalf++];
            }
        }
        if (firstHalf <= middle) {
            while (firstHalf <= middle) {
                swapSpace[count++] = toSort[firstHalf++];
            }
        } else {
            while (secondHalf <= end) {
                swapSpace[count++] = toSort[secondHalf++];
            }
        }
        for (count = begin; count <= end; count++) {
            toSort[count] = swapSpace[count];
        }
    }
}


/**
 * TreeTableModel is the model used by a JTreeTable. It extends TreeModel to add
 * methods for getting inforamtion about the set of columns each node in the
 * TreeTableModel may have. Each column, like a column in a TableModel, has a
 * name and a type associated with it. Each node in the TreeTableModel can
 * return a value for each of the columns and set that value if isCellEditable()
 * returns true.
 *
 * @author Philip Milne
 * @author Scott Violet
 */
interface TreeTableModel extends TreeModel {

    /**
     * Returns the number of available column.
     */
    public int getColumnCount();

    /**
     * Returns the name for column number
     * <code>column</code>.
     */
    public String getColumnName(int column);

    /**
     * Returns the type for column number
     * <code>column</code>.
     */
    public Class getColumnClass(int column);

    /**
     * Returns the value to be displayed for node
     * <code>node</code>, at column number
     * <code>column</code>.
     */
    public Object getValueAt(Object node, int column);

    /**
     * Indicates whether the the value for node
     * <code>node</code>, at column number
     * <code>column</code> is editable.
     */
    public boolean isCellEditable(Object node, int column);

    /**
     * Sets the value for node
     * <code>node</code>, at column number
     * <code>column</code>.
     */
    public void setValueAt(Object aValue, Object node, int column);
}

/**
 * This is a wrapper class takes a TreeTableModel and implements the table model
 * interface. The implementation is trivial, with all of the event dispatching
 * support provided by the superclass: the AbstractTableModel.
 *
 * @version 1.2 10/27/98
 *
 * @author Philip Milne
 * @author Scott Violet
 */
class TreeTableModelAdapter extends AbstractTableModel {

    JTree tree;
    TreeTableModel treeTableModel;

    public TreeTableModelAdapter(TreeTableModel treeTableModel, JTree tree) {
        this.tree = tree;
        this.treeTableModel = treeTableModel;

        tree.addTreeExpansionListener(new TreeExpansionListener() {
            // Don't use fireTableRowsInserted() here; the selection model
            // would get updated twice.
            public void treeExpanded(TreeExpansionEvent event) {
                fireTableDataChanged();
            }

            public void treeCollapsed(TreeExpansionEvent event) {
                fireTableDataChanged();
            }
        });

        // Install a TreeModelListener that can update the table when
        // tree changes. We use delayedFireTableDataChanged as we can
        // not be guaranteed the tree will have finished processing
        // the event before us.
        treeTableModel.addTreeModelListener(new TreeModelListener() {
            public void treeNodesChanged(TreeModelEvent e) {
                delayedFireTableDataChanged();
            }

            public void treeNodesInserted(TreeModelEvent e) {
                delayedFireTableDataChanged();
            }

            public void treeNodesRemoved(TreeModelEvent e) {
                delayedFireTableDataChanged();
            }

            public void treeStructureChanged(TreeModelEvent e) {
                delayedFireTableDataChanged();
            }
        });
    }

    // Wrappers, implementing TableModel interface.
    public int getColumnCount() {
        return treeTableModel.getColumnCount();
    }

    public String getColumnName(int column) {
        return treeTableModel.getColumnName(column);
    }

    public Class getColumnClass(int column) {
        return treeTableModel.getColumnClass(column);
    }

    public int getRowCount() {
        return tree.getRowCount();
    }

    protected Object nodeForRow(int row) {
        TreePath treePath = tree.getPathForRow(row);
        return treePath.getLastPathComponent();
    }

    public Object getValueAt(int row, int column) {
        return treeTableModel.getValueAt(nodeForRow(row), column);
    }

    public boolean isCellEditable(int row, int column) {
        return treeTableModel.isCellEditable(nodeForRow(row), column);
    }

    public void setValueAt(Object value, int row, int column) {
        treeTableModel.setValueAt(value, nodeForRow(row), column);
    }

    /**
     * Invokes fireTableDataChanged after all the pending events have been
     * processed. SwingUtilities.invokeLater is used to handle this.
     */
    protected void delayedFireTableDataChanged() {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                fireTableDataChanged();
            }
        });
    }
}
```

*s17.postimg.org/twm3hmhsb/Tree_Table_Example.jpg

*Text Editor*

The next demo shows how to implement File read progress, and File write progress using JProgressBar.
The FileLoader class handles the File read progress. It uses a separate Thread to show File read progress.
The class FileSaver is another class to show File save progress, it uses a separate Thread to achieve it.

Here is the complete Source code.


```
/**
 * Created with IntelliJ IDEA.
 * User: Sowndar
 * Date: 10/4/14
 * Time: 9:13 PM
 * To change this template use File | Settings | File Templates.
 */
import javax.swing.*;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.*;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.*;
import java.net.URL;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;


/**
 * Sample application using the simple text editor component that
 * supports only one font.
 *
 * @author  Timothy Prinzing
 */
 [MENTION=139512]Sup[/MENTION]pressWarnings("serial")
public class TextEditor extends JPanel {

    protected static Properties properties;
    private static ResourceBundle resources;
    private final static String EXIT_AFTER_PAINT = "-exit";
    private static boolean exitAfterFirstPaint;

    private static final String[] MENUBAR_KEYS = {"file", "edit", "debug"};
    private static final String[] TOOLBAR_KEYS = {"new", "open", "save", "-", "cut", "copy", "paste"};
    private static final String[] FILE_KEYS = {"new", "open", "save", "-", "exit"};
    private static final String[] EDIT_KEYS = {"cut", "copy", "paste", "-", "undo", "redo"};
    private static final String[] DEBUG_KEYS = {"dump", "showElementTree"};

    static {
        try {
            properties = new Properties();
            properties.load(TextEditor.class.getResourceAsStream(
                    "resources/NotepadSystem.properties"));
            resources = ResourceBundle.getBundle("resources.Notepad",
                    Locale.getDefault());
        } catch (MissingResourceException | IOException  e) {
            System.err.println("resources/Notepad.properties "
                    + "or resources/NotepadSystem.properties not found");
            //System.exit(1);
        }
    }

    @Override
    public void paintChildren(Graphics g) {
        super.paintChildren(g);
        if (exitAfterFirstPaint) {
            System.exit(0);
        }
    }

    [MENTION=139512]Sup[/MENTION]pressWarnings("OverridableMethodCallInConstructor")
    TextEditor() {
        super(true);

        // Trying to set Nimbus look and feel
        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
        } catch (Exception e) {
            // Do nothing
        }

        setBorder(BorderFactory.createEtchedBorder());
        setLayout(new BorderLayout());

        // create the embedded JTextComponent
        editor = createEditor();
        // Add this as a listener for undoable edits.
        editor.getDocument().addUndoableEditListener(undoHandler);

        // install the command table
        commands = new HashMap<Object, Action>();
        Action[] actions = getActions();
        for (Action a : actions) {
            commands.put(a.getValue(Action.NAME), a);
        }

        JScrollPane scroller = new JScrollPane();
        JViewport port = scroller.getViewport();
        port.add(editor);

        String vpFlag = getProperty("ViewportBackingStore");
        if (vpFlag != null) {
            Boolean bs = Boolean.valueOf(vpFlag);
            port.setScrollMode(bs
                    ? JViewport.BACKINGSTORE_SCROLL_MODE
                    : JViewport.BLIT_SCROLL_MODE);
        }

        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        panel.add("North", createToolbar());
        panel.add("Center", scroller);
        add("Center", panel);
        add("South", createStatusbar());
    }

    public static void main(String[] args) throws Exception {
        if (args.length > 0 && args[0].equals(EXIT_AFTER_PAINT)) {
            exitAfterFirstPaint = true;
        }
        SwingUtilities.invokeAndWait(new Runnable() {

            public void run() {
                JFrame frame = new JFrame();
                frame.setTitle("TextEditor");
                frame.setBackground(Color.lightGray);
                frame.getContentPane().setLayout(new BorderLayout());
                TextEditor notepad = new TextEditor();
                frame.getContentPane().add("Center", notepad);
                frame.setJMenuBar(notepad.createMenubar());
                frame.addWindowListener(new AppCloser());
                frame.pack();
                frame.setSize(500, 600);
                frame.setVisible(true);
            }
        });
    }

    /**
     * Fetch the list of actions supported by this
     * editor.  It is implemented to return the list
     * of actions supported by the embedded JTextComponent
     * augmented with the actions defined locally.
     */
    public Action[] getActions() {
        return TextAction.augmentList(editor.getActions(), defaultActions);
    }

    /**
     * Create an editor to represent the given document.
     */
    protected JTextComponent createEditor() {
        JTextComponent c = new JTextArea();
        c.setDragEnabled(true);
        c.setFont(new Font("monospaced", Font.PLAIN, 12));
        return c;
    }

    /**
     * Fetch the editor contained in this panel
     */
    protected JTextComponent getEditor() {
        return editor;
    }


    /**
     * To shutdown when run as an application.  This is a
     * fairly lame implementation.   A more self-respecting
     * implementation would at least check to see if a save
     * was needed.
     */
    protected static final class AppCloser extends WindowAdapter {

        @Override
        public void windowClosing(WindowEvent e) {
            System.exit(0);
        }
    }

    /**
     * Find the hosting frame, for the file-chooser dialog.
     */
    protected Frame getFrame() {
        for (Container p = getParent(); p != null; p = p.getParent()) {
            if (p instanceof Frame) {
                return (Frame) p;
            }
        }
        return null;
    }

    /**
     * This is the hook through which all menu items are
     * created.
     */
    protected JMenuItem createMenuItem(String cmd) {
        JMenuItem mi = new JMenuItem(getResourceString(cmd + labelSuffix));
        URL url = getResource(cmd + imageSuffix);
        if (url != null) {
            mi.setHorizontalTextPosition(JButton.RIGHT);
            mi.setIcon(new ImageIcon(url));
        }
        String astr = getProperty(cmd + actionSuffix);
        if (astr == null) {
            astr = cmd;
        }
        mi.setActionCommand(astr);
        Action a = getAction(astr);
        if (a != null) {
            mi.addActionListener(a);
            a.addPropertyChangeListener(createActionChangeListener(mi));
            mi.setEnabled(a.isEnabled());
        } else {
            mi.setEnabled(false);
        }
        return mi;
    }

    protected Action getAction(String cmd) {
        return commands.get(cmd);
    }

    protected String getProperty(String key) {
        return properties.getProperty(key);
    }

    protected String getResourceString(String nm) {
        String str;
        try {
            str = resources.getString(nm);
        } catch (MissingResourceException mre) {
            str = null;
        }
        return str;
    }

    protected URL getResource(String key) {
        String name = getResourceString(key);
        if (name != null) {
            return this.getClass().getResource(name);
        }
        return null;
    }

    /**
     * Create a status bar
     */
    protected Component createStatusbar() {
        // need to do something reasonable here
        status = new StatusBar();
        return status;
    }

    /**
     * Resets the undo manager.
     */
    protected void resetUndoManager() {
        undo.discardAllEdits();
        undoAction.update();
        redoAction.update();
    }

    /**
     * Create the toolbar.  By default this reads the
     * resource file for the definition of the toolbar.
     */
    private Component createToolbar() {
        toolbar = new JToolBar();
        for (String toolKey: getToolBarKeys()) {
            if (toolKey.equals("-")) {
                toolbar.add(Box.createHorizontalStrut(5));
            } else {
                toolbar.add(createTool(toolKey));
            }
        }
        toolbar.add(Box.createHorizontalGlue());
        return toolbar;
    }

    /**
     * Hook through which every toolbar item is created.
     */
    protected Component createTool(String key) {
        return createToolbarButton(key);
    }

    /**
     * Create a button to go inside of the toolbar.  By default this
     * will load an image resource.  The image filename is relative to
     * the classpath (including the '.' directory if its a part of the
     * classpath), and may either be in a JAR file or a separate file.
     *
     * [MENTION=9956]PARAM[/MENTION] key The key in the resource file to serve as the basis
     *  of lookups.
     */
    protected JButton createToolbarButton(String key) {
        URL url = getResource(key + imageSuffix);
        JButton b = new JButton(new ImageIcon(url)) {

            @Override
            public float getAlignmentY() {
                return 0.5f;
            }
        };
        b.setRequestFocusEnabled(false);
        b.setMargin(new Insets(1, 1, 1, 1));

        String astr = getProperty(key + actionSuffix);
        if (astr == null) {
            astr = key;
        }
        Action a = getAction(astr);
        if (a != null) {
            b.setActionCommand(astr);
            b.addActionListener(a);
        } else {
            b.setEnabled(false);
        }

        String tip = getResourceString(key + tipSuffix);
        if (tip != null) {
            b.setToolTipText(tip);
        }

        return b;
    }

    /**
     * Create the menubar for the app.  By default this pulls the
     * definition of the menu from the associated resource file.
     */
    protected JMenuBar createMenubar() {
        JMenuBar mb = new JMenuBar();
        for(String menuKey: getMenuBarKeys()){
            JMenu m = createMenu(menuKey);
            if (m != null) {
                mb.add(m);
            }
        }
        return mb;
    }

    /**
     * Create a menu for the app.  By default this pulls the
     * definition of the menu from the associated resource file.
     */
    protected JMenu createMenu(String key) {
        JMenu menu = new JMenu(getResourceString(key + labelSuffix));
        for (String itemKey: getItemKeys(key)) {
            if (itemKey.equals("-")) {
                menu.addSeparator();
            } else {
                JMenuItem mi = createMenuItem(itemKey);
                menu.add(mi);
            }
        }
        return menu;
    }

    /**
     *  Get keys for menus
     */
    protected String[] getItemKeys(String key) {
        switch (key) {
            case "file":
                return FILE_KEYS;
            case "edit":
                return EDIT_KEYS;
            case "debug":
                return DEBUG_KEYS;
            default:
                return null;
        }
    }

    protected String[] getMenuBarKeys() {
        return MENUBAR_KEYS;
    }

    protected String[] getToolBarKeys() {
        return TOOLBAR_KEYS;
    }

    // Yarked from JMenu, ideally this would be public.
    protected PropertyChangeListener createActionChangeListener(JMenuItem b) {
        return new ActionChangedListener(b);
    }

    // Yarked from JMenu, ideally this would be public.

    private class ActionChangedListener implements PropertyChangeListener {

        JMenuItem menuItem;

        ActionChangedListener(JMenuItem mi) {
            super();
            this.menuItem = mi;
        }

        public void propertyChange(PropertyChangeEvent e) {
            String propertyName = e.getPropertyName();
            if (e.getPropertyName().equals(Action.NAME)) {
                String text = (String) e.getNewValue();
                menuItem.setText(text);
            } else if (propertyName.equals("enabled")) {
                Boolean enabledState = (Boolean) e.getNewValue();
                menuItem.setEnabled(enabledState.booleanValue());
            }
        }
    }
    private JTextComponent editor;
    private Map<Object, Action> commands;
    private JToolBar toolbar;
    private JComponent status;
    private JFrame elementTreeFrame;

    /**
     * Listener for the edits on the current document.
     */
    protected UndoableEditListener undoHandler = new UndoHandler();
    /** UndoManager that we add edits to. */
    protected UndoManager undo = new UndoManager();
    /**
     * Suffix applied to the key used in resource file
     * lookups for an image.
     */
    public static final String imageSuffix = "Image";
    /**
     * Suffix applied to the key used in resource file
     * lookups for a label.
     */
    public static final String labelSuffix = "Label";
    /**
     * Suffix applied to the key used in resource file
     * lookups for an action.
     */
    public static final String actionSuffix = "Action";
    /**
     * Suffix applied to the key used in resource file
     * lookups for tooltip text.
     */
    public static final String tipSuffix = "Tooltip";
    public static final String openAction = "open";
    public static final String newAction = "new";
    public static final String saveAction = "save";
    public static final String exitAction = "exit";
    public static final String showElementTreeAction = "showElementTree";


    class UndoHandler implements UndoableEditListener {

        /**
         * Messaged when the Document has created an edit, the edit is
         * added to <code>undo</code>, an instance of UndoManager.
         */
        public void undoableEditHappened(UndoableEditEvent e) {
            undo.addEdit(e.getEdit());
            undoAction.update();
            redoAction.update();
        }
    }


    /**
     * FIXME - I'm not very useful yet
     */
    class StatusBar extends JComponent {

        public StatusBar() {
            super();
            setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
        }

        @Override
        public void paint(Graphics g) {
            super.paint(g);
        }
    }
    // --- action implementations -----------------------------------
    private UndoAction undoAction = new UndoAction();
    private RedoAction redoAction = new RedoAction();
    /**
     * Actions defined by the Notepad class
     */
    private Action[] defaultActions = {
            new NewAction(),
            new OpenAction(),
            new SaveAction(),
            new ExitAction(),
            undoAction,
            redoAction
    };


    class UndoAction extends AbstractAction {

        public UndoAction() {
            super("Undo");
            setEnabled(false);
        }

        public void actionPerformed(ActionEvent e) {
            try {
                undo.undo();
            } catch (CannotUndoException ex) {
                Logger.getLogger(UndoAction.class.getName()).log(Level.SEVERE,
                        "Unable to undo", ex);
            }
            update();
            redoAction.update();
        }

        protected void update() {
            if (undo.canUndo()) {
                setEnabled(true);
                putValue(Action.NAME, undo.getUndoPresentationName());
            } else {
                setEnabled(false);
                putValue(Action.NAME, "Undo");
            }
        }
    }


    class RedoAction extends AbstractAction {

        public RedoAction() {
            super("Redo");
            setEnabled(false);
        }

        public void actionPerformed(ActionEvent e) {
            try {
                undo.redo();
            } catch (CannotRedoException ex) {
                Logger.getLogger(RedoAction.class.getName()).log(Level.SEVERE,
                        "Unable to redo", ex);
            }
            update();
            undoAction.update();
        }

        protected void update() {
            if (undo.canRedo()) {
                setEnabled(true);
                putValue(Action.NAME, undo.getRedoPresentationName());
            } else {
                setEnabled(false);
                putValue(Action.NAME, "Redo");
            }
        }
    }


    class OpenAction extends NewAction {

        OpenAction() {
            super(openAction);
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            Frame frame = getFrame();
            JFileChooser chooser = new JFileChooser();
            int ret = chooser.showOpenDialog(frame);

            if (ret != JFileChooser.APPROVE_OPTION) {
                return;
            }

            File f = chooser.getSelectedFile();
            if (f.isFile() && f.canRead()) {
                Document oldDoc = getEditor().getDocument();
                if (oldDoc != null) {
                    oldDoc.removeUndoableEditListener(undoHandler);
                }

                getEditor().setDocument(new PlainDocument());
                frame.setTitle(f.getName());
                Thread loader = new FileLoader(f, editor.getDocument());
                loader.start();
            } else {
                JOptionPane.showMessageDialog(getFrame(),
                        "Could not open file: " + f,
                        "Error opening file",
                        JOptionPane.ERROR_MESSAGE);
            }
        }
    }


    class SaveAction extends AbstractAction {

        SaveAction() {
            super(saveAction);
        }

        public void actionPerformed(ActionEvent e) {
            Frame frame = getFrame();
            JFileChooser chooser = new JFileChooser();
            int ret = chooser.showSaveDialog(frame);

            if (ret != JFileChooser.APPROVE_OPTION) {
                return;
            }

            File f = chooser.getSelectedFile();
            frame.setTitle(f.getName());
            Thread saver = new FileSaver(f, editor.getDocument());
            saver.start();
        }
    }


    class NewAction extends AbstractAction {

        NewAction() {
            super(newAction);
        }

        NewAction(String nm) {
            super(nm);
        }

        public void actionPerformed(ActionEvent e) {
            Document oldDoc = getEditor().getDocument();
            if (oldDoc != null) {
                oldDoc.removeUndoableEditListener(undoHandler);
            }
            getEditor().setDocument(new PlainDocument());
            getEditor().getDocument().addUndoableEditListener(undoHandler);
            resetUndoManager();
            getFrame().setTitle(resources.getString("Title"));
            revalidate();
        }
    }


    /**
     * Really lame implementation of an exit command
     */
    class ExitAction extends AbstractAction {

        ExitAction() {
            super(exitAction);
        }

        public void actionPerformed(ActionEvent e) {
            System.exit(0);
        }
    }


    /**
     * Thread to load a file into the text storage model
     */
    class FileLoader extends Thread {

        FileLoader(File f, Document doc) {
            setPriority(4);
            this.f = f;
            this.doc = doc;
        }

        @Override
        public void run() {
            try {
                // initialize the statusbar
                status.removeAll();
                JProgressBar progress = new JProgressBar();
                progress.setMinimum(0);
                progress.setMaximum((int) f.length());
                status.add(progress);
                status.revalidate();

                // try to start reading
                Reader in = new FileReader(f);
                char[] buff = new char[4096];
                int nch;
                while ((nch = in.read(buff, 0, buff.length)) != -1) {
                    doc.insertString(doc.getLength(), new String(buff, 0, nch),
                            null);
                    progress.setValue(progress.getValue() + nch);
                }
            } catch (IOException e) {
                final String msg = e.getMessage();
                SwingUtilities.invokeLater(new Runnable() {

                    public void run() {
                        JOptionPane.showMessageDialog(getFrame(),
                                "Could not open file: " + msg,
                                "Error opening file",
                                JOptionPane.ERROR_MESSAGE);
                    }
                });
            } catch (BadLocationException e) {
                System.err.println(e.getMessage());
            }
            doc.addUndoableEditListener(undoHandler);
            // we are done... get rid of progressbar
            status.removeAll();
            status.revalidate();

            resetUndoManager();

        }
        Document doc;
        File f;
    }


    /**
     * Thread to save a document to file
     */
    class FileSaver extends Thread {

        Document doc;
        File f;

        FileSaver(File f, Document doc) {
            setPriority(4);
            this.f = f;
            this.doc = doc;
        }

        @Override
        [MENTION=139512]Sup[/MENTION]pressWarnings("SleepWhileHoldingLock")
        public void run() {
            try {
                // initialize the statusbar
                status.removeAll();
                JProgressBar progress = new JProgressBar();
                progress.setMinimum(0);
                progress.setMaximum(doc.getLength());
                status.add(progress);
                status.revalidate();

                // start writing
                Writer out = new FileWriter(f);
                Segment text = new Segment();
                text.setPartialReturn(true);
                int charsLeft = doc.getLength();
                int offset = 0;
                while (charsLeft > 0) {
                    doc.getText(offset, Math.min(4096, charsLeft), text);
                    out.write(text.array, text.offset, text.count);
                    charsLeft -= text.count;
                    offset += text.count;
                    progress.setValue(offset);
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        Logger.getLogger(FileSaver.class.getName()).log(
                                Level.SEVERE,
                                null, e);
                    }
                }
                out.flush();
                out.close();
            } catch (IOException e) {
                final String msg = e.getMessage();
                SwingUtilities.invokeLater(new Runnable() {

                    public void run() {
                        JOptionPane.showMessageDialog(getFrame(),
                                "Could not save file: " + msg,
                                "Error saving file",
                                JOptionPane.ERROR_MESSAGE);
                    }
                });
            } catch (BadLocationException e) {
                System.err.println(e.getMessage());
            }
            // we are done... get rid of progressbar
            status.removeAll();
            status.revalidate();
        }
    }
}
```

*s27.postimg.org/9dw7gaobj/Text_Editor.jpg

*JSplitPane*

Our next demo shows a JSplitPane in action.
It loads two Images Purple Crocuses1.jpg & Red Gerbera Daisy1.jpg & shows them both in two JLabels.
You need these two images in the 'Images' directory to run this example.
You can download the Images (wallpapers) from www.hdwallpapers.in


```
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.URL;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

/**
 * Split Pane demo
 *
 * @version 1.12 11/17/05
 * @author Scott Violet
 * @author Jeff Dinkins
 */
public class SplitPaneDemo extends JPanel {

    private static final Insets insets = new Insets(4, 8, 4, 8);
    private final JSplitPane splitPane;
    private final JLabel purpleCrocus;
    private final JLabel redGerbera;
    private JPanel controlPanel;
    private GridBagLayout gridbag;
    private GridBagConstraints c;

    /**
     * SplitPaneDemo Constructor
     */
    public SplitPaneDemo() {
        setLayout(new BorderLayout());
        try {
            for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
                if ("Nimbus".equals(info.getName())) {
                    UIManager.setLookAndFeel(info.getClassName());
                    break;
                }
            }
        } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | UnsupportedLookAndFeelException e) {
            // Do nothing
        }
        purpleCrocus = new JLabel(createImageIcon("Purple Crocuses1.jpg",
                "Purple Crocus"));
        redGerbera = new JLabel(createImageIcon("Red Gerbera Daisy1.jpg",
                "Red Gerbera"));

        splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, purpleCrocus, redGerbera);

        splitPane.setContinuousLayout(true);

        splitPane.setOneTouchExpandable(true);

        splitPane.setDividerLocation(200);

        purpleCrocus.setMinimumSize(new Dimension(20, 20));
        redGerbera.setMinimumSize(new Dimension(20, 20));

        add(splitPane, BorderLayout.CENTER);
        setBackground(Color.black);

        add(createSplitPaneControls(), BorderLayout.SOUTH);
    }

    public ImageIcon createImageIcon(String filename, String description) {
        String path = "Images/" + filename;

        URL imageURL = getClass().getResource(path);

        if (imageURL == null) {
            System.err.println("unable to access image file: " + path);
            return null;
        } else {
            return new ImageIcon(imageURL, description);
        }
    }

    /**
     * Creates controls to alter the JSplitPane.
     *
     * @return
     */
    protected JPanel createSplitPaneControls() {

        gridbag = new GridBagLayout();
        c = new GridBagConstraints();
        controlPanel = new JPanel(gridbag);

        Box box = Box.createHorizontalBox();
        ButtonGroup group = new ButtonGroup();

        OrientationListener orientationListener = new OrientationListener();

        JRadioButton button = new JRadioButton("Vertical Split");
        button.setActionCommand("vertical");
        button.addActionListener(orientationListener);
        group.add(button);
        box.add(button);

        button = new JRadioButton("Horizontal Split");
        button.setActionCommand("horizontal");
        button.setSelected(true);
        button.addActionListener(orientationListener);
        group.add(button);
        box.add(button);

        addToGridbag(box, 0, 0, 1, 1,
                GridBagConstraints.NONE, GridBagConstraints.WEST);

        JCheckBox checkBox = new JCheckBox("Continuous Layout");
        checkBox.setSelected(true);

        checkBox.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                splitPane.setContinuousLayout(
                        ((JCheckBox) e.getSource()).isSelected());
            }
        });

        c.gridy++;
        addToGridbag(checkBox, 0, 1, 1, 1,
                GridBagConstraints.NONE, GridBagConstraints.WEST);

        checkBox = new JCheckBox("One-Touch expandable");
        checkBox.setSelected(true);

        checkBox.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent e) {
                splitPane.setOneTouchExpandable(
                        ((JCheckBox) e.getSource()).isSelected());
            }
        });

        addToGridbag(checkBox, 0, 2, 1, 1,
                GridBagConstraints.NONE, GridBagConstraints.WEST);

        JSeparator separator = new JSeparator(JSeparator.VERTICAL);
        addToGridbag(separator, 1, 0, 1, 3,
                GridBagConstraints.VERTICAL, GridBagConstraints.CENTER);

        final JSpinner spinner = new JSpinner(
                new SpinnerNumberModel(splitPane.getDividerSize(), 5, 50, 2));

        spinner.addChangeListener(new ChangeListener() {
            @Override
            public void stateChanged(ChangeEvent event) {
                SpinnerNumberModel model = (SpinnerNumberModel) spinner.getModel();
                splitPane.setDividerSize(model.getNumber().intValue());
            }
        });

        JLabel label = new JLabel("Divider Size");
        label.setLabelFor(spinner);
        addToGridbag(label, 2, 0, 1, 1,
                GridBagConstraints.NONE, GridBagConstraints.EAST);
        addToGridbag(spinner, 3, 0, 1, 1,
                GridBagConstraints.NONE, GridBagConstraints.WEST);

        JSpinner minSizeSpinner = new JSpinner(
                new SpinnerNumberModel(purpleCrocus.getMinimumSize().width, 0, 300, 10));

        minSizeSpinner.addChangeListener(new MinimumSizeListener(purpleCrocus));

        label = new JLabel("Purple Crocus's Minimum Size");
        label.setLabelFor(minSizeSpinner);
        addToGridbag(label, 2, 1, 1, 1,
                GridBagConstraints.NONE, GridBagConstraints.EAST);
        addToGridbag(minSizeSpinner, 3, 1, 1, 1,
                GridBagConstraints.NONE, GridBagConstraints.WEST);

        minSizeSpinner = new JSpinner(
                new SpinnerNumberModel(redGerbera.getMinimumSize().width, 0, 300, 10));

        minSizeSpinner.addChangeListener(new MinimumSizeListener(redGerbera));

        label = new JLabel("Red Gerbera's Minimum Size");
        label.setLabelFor(minSizeSpinner);
        addToGridbag(label, 2, 2, 1, 1,
                GridBagConstraints.NONE, GridBagConstraints.EAST);
        addToGridbag(minSizeSpinner, 3, 2, 1, 1,
                GridBagConstraints.NONE, GridBagConstraints.WEST);

        return controlPanel;
    }

    protected void addToGridbag(JComponent child, int gx, int gy,
            int gwidth, int gheight, int fill, int anchor) {
        c.insets = insets;
        c.gridx = gx;
        c.gridy = gy;
        c.gridwidth = gwidth;
        c.gridheight = gheight;
        c.fill = fill;
        c.anchor = anchor;
        gridbag.addLayoutComponent(child, c);
        controlPanel.add(child);

    }

    private class OrientationListener implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent event) {
            splitPane.setOrientation(event.getActionCommand().equals("vertical")
                    ? JSplitPane.VERTICAL_SPLIT : JSplitPane.HORIZONTAL_SPLIT);
        }

    }

    public class MinimumSizeListener implements ChangeListener {

        private final JComponent component;

        public MinimumSizeListener(JComponent c) {
            this.component = c;
        }

        @Override
        public void stateChanged(ChangeEvent event) {
            JSpinner spinner = (JSpinner) event.getSource();
            SpinnerNumberModel model = (SpinnerNumberModel) spinner.getModel();
            int min = model.getNumber().intValue();
            component.setMinimumSize(new Dimension(min, min));
        }
    }

    /**
     * main method allows us to run as a standalone demo.
     *
     * [MENTION=9956]PARAM[/MENTION] args
     */
    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {

            @Override
            public void run() {
                JFrame frame = new JFrame("SplitPaneDemo");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new SplitPaneDemo());
                Dimension scrDim = Toolkit.getDefaultToolkit().getScreenSize();
                frame.setPreferredSize(new Dimension(scrDim.width - 100, scrDim.height - 100));
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
}
```

*s11.postimg.org/nwth2su9b/Split_Pane_Demo.jpg

This wraps up Part I of the Expert Java programming.


----------



## Flash (Dec 3, 2014)

[MENTION=10170]JGuru[/MENTION] - Please mention the source too, while sharing the contents to give credits to the original maker.
For JComboBox, i've given here the link which was written by the Author: Thomas Bierhance
Inside JComboBox: adding automatic completion


----------



## JGuru (Dec 4, 2014)

Flash, You are right. JComboBox auto-completion was written by Thomas Bierhance. Thanks for the link.


----------



## pinimbus (Nov 1, 2016)

Great post, straight to the point.This is very helpful. Thanks Flash for providing the link to Thomas.


----------

