/*
 * @(#)Zoo.java    1.3  22 May 2002
 *
 * Copyright 2004
 * College of Computer and Information Science
 * Northeastern University
 * Boston, MA  02115
 *
 * The Java Power Tools software may be used for educational
 * purposes as long as this copyright notice is retained intact
 * at the top of all source files.
 *
 * To discuss possible commercial use of this software, 
 * contact Richard Rasala at Northeastern University, 
 * College of Computer and Information Science,
 * 617-373-2462 or rasala@ccs.neu.edu.
 *
 * The Java Power Tools software has been designed and built
 * in collaboration with Viera Proulx and Jeff Raab.
 *
 * Should this software be modified, the words "Modified from 
 * Original" must be included as a comment below this notice.
 *
 * All publication rights are retained.  This software or its 
 * documentation may not be published in any media either
 * in whole or in part without explicit permission.
 *
 * This software was created with support from Northeastern 
 * University and from NSF grant DUE-9950829.
 */

package edu.neu.ccs.gui;

import edu.neu.ccs.gui.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;

/**
 * <P><CODE>{@link ZooContainer ZooContainer}</CODE> 
 * allowing selection and direct manipulation of components.</P>
 *
 * <P>A zoo is a container used to contain components
 * that can be moved and resized through direct manipulation.
 * In this way, a Zoo is like a desktop for components.</P>
 * 
 * <P>An individual component, or a collection of components,
 * can be selected through direct manipulation 
 * or through method calls.
 *
 * A selected component is painted
 * with a box surrounding its bounds.
 *
 * Selected components can be retrieved using 
 * various methods described below.
 *
 * A component is selected if it is clicked with the mouse.
 *
 * To select multiple components, hold the Shift key
 * when selecting the components.</P>
 *
 * <P>Components in a zoo are called <I>child items</I>.
 *
 * All child items are contained at the top level of the zoo.
 *
 * Components can be grouped into a
 * <CODE>{@link ZooGroup ZooGroup}</CODE>, which is treated
 * as a single entity in the zoo. 
 *
 * Components in a zoo that are contained within a group
 * are called <I>items</I>.
 *
 * Since a group can be recursively contained within a group,
 * there can be any number of levels to the containment hierarchy
 * for items in a zoo.
 *
 * Only the top level items are considered to be child items.
 *
 * A zoo could have only one child item, but many items,
 * if the single child item is a group of components.</P>
 *
 * <P>If a zoo is restricting the bounds of its components,
 * it will not allow a component to be moved or resized
 * so that the component is not completely contained
 * within the bounds of the zoo.
 *
 * If a zoo is not restricting the bounds of its components,
 * a component can be moved or resized to any extent.
 *
 * A zoo restricts bounds by default.</P>
 *
 * <P>If a zoo is in design mode, a child item can be
 * moved and resized by clicking and dragging a side or corner
 * of the component as noted by a rectangular anchor.
 *
 * An anchor is not shown if the component can not be resized.
 *
 * A component can not be resized to a width or height
 * out of the limits of its minimum and maximum sizes.
 *
 * If a zoo is not in design mode, 
 * all of its items behave normally.
 *
 * A zoo is in design mode by default.</P>
 *
 * <P>A 3rd dimension -- <I>z-order</I> -- is applied 
 * to the child items in a zoo.
 *
 * This z-order determines the order in which components are painted
 * and will cause one of two overlapping components 
 * to appear to be on top of the other.
 *
 * Several methods described below can modify the z-order
 * of child items contained in a zoo.
 *
 * Items in a group reside at the z-order of the group itself.
 *
 * The z-order within a group determines the order in which 
 * the components of the group are painted.</P>
 *
 * <BIG><STRONG>Technical details:</STRONG></BIG>
 *
 * <P>This panel assumes an <CODE>AbsoluteLayout</CODE>.
 *
 * Behavior is undefined if this panel is set to have a layout
 * other than an <CODE>AbsoluteLayout</CODE>.</P>
 *
 * <P>A grouped component resized through direct manipulation 
 * will appear to scale in size as the group is resized,
 * whether or not the component will actually scale in size
 * when the resize is completed.
 *
 * If the parent container for the grouped component 
 * does not scale its contents, the grouped component
 * will snap back to its original size 
 * when the resize is completed.</P>
 *
 * @author  Jeff Raab
 * @version 2.2
 * @since   1.1
 */
public class Zoo extends ZooContainer {

    /** Mouse action adapter for this zoo. */
    protected MouseActionAdapter mouseAdapter = null;

    /** Table mapping components to laminates. */
    protected Hashtable laminateTable = new Hashtable();
    
    /** Selection model for this zoo. */
    protected ListSelectionModel selectionModel = null;
    
    /** 
     * Whether or not this zoo 
     * is restricting its child components' bounds. 
     */
    protected boolean restrictingBounds = true;
    
    /**
     * Whether or not the child items in this zoo
     * can be moved.
     * @since 2.0
     */
    protected boolean moveable = true;
    
    /**
     * Whether or not the child items in this zoo
     * can be resized.
     * @since 2.0
     */
    protected boolean resizeable = true;

    /** Whether or not this zoo is in design mode. */
    protected boolean designMode = true;
    
    /** Stroke used to paint the lasso. */
    private Stroke dashedStroke = new BasicStroke(
        1f, 
        BasicStroke.CAP_SQUARE, 
        BasicStroke.JOIN_MITER, 
        1f, 
        new float[] {3f, 5f}, 
        0f);

    /** Point at which the current lasso originated. */
    protected Point lassoStart = null;
    
    /** Last point used to calculate the current lasso. */
    protected Point lassoLast = null;
    
    /**
     * Highlight color of selected components 
     * for when this zoo has the keyboard focus.
     *
     * The default value for this color is
     * <CODE>SystemColor.textHighlight</CODE>.
     *
     * @see #setFocusedHighlightColor(Color)
     * @see #getFocusedHighlightColor()
     * @since 2.1
     */
    protected Color focusedHighlightColor = 
        SystemColor.textHighlight;
    
    /**
     * Highlight color of selected components 
     * for when this zoo does not have the keyboard focus.
     *
     * The default value for this color is <CODE>null</CODE>,
     * which means that it is computed from the
     * highlight color for when this zoo has focus.
     *
     * @see #setUnfocusedHighlightColor(Color)
     * @see #getUnfocusedHighlightColor()
     * @since 2.1
     */
    protected Color unfocusedHighlightColor = null;
    
    /////////////
    // Actions //
    /////////////
    
    /**
     * Action to select all components in this zoo.
     *
     * @see #selectAll()
     * @since 2.1
     */
    public final Action SELECT_ALL_ACTION = 
        new SimpleAction("Select all") {
            public void perform() {
                selectAll();
            }
        };
    
    /** 
     * Action to deselect all components in this zoo.
     *
     * @see #deselectAll()
     * @since 2.1
     */
    public final Action DESELECT_ALL_ACTION = 
        new SimpleAction("Deselect all") {
            public void perform() {
                deselectAll();
            }
        };
    
    /** 
     * Action to group all selected components in this zoo.
     *
     * @see #groupSelection()
     * @since 2.1
     */
    public final Action GROUP_SELECTED_COMPONENTS_ACTION = 
        new SimpleAction("Group selected components") {
            public void perform() {
                groupSelection();
            }
        };
    
    /** 
     * Action to ungroup the first selected component in this zoo,
     * as long as it is an instance of <CODE>ZooGroup</CODE>.
     *
     * @see #ungroup(ZooGroup)
     * @see #getSelectedComponent()
     * @since 2.1
     */
    public final Action UNGROUP_SELECTED_COMPONENT_ACTION = 
        new SimpleAction("Ungroup selected component") {
            public void perform() {
                Component c = getSelectedComponent();
                if (c instanceof ZooGroup)
                    ungroup((ZooGroup)c);
            }
        };
    
    /** 
     * Action to ungroup all selected components in this zoo.
     *
     * @see #ungroupSelection()
     * @since 2.1
     */
    public final Action UNGROUP_SELECTED_COMPONENTS_ACTION = 
        new SimpleAction("Ungroup selected components") {
            public void perform() {
                ungroupSelection();
            }
        };
    
    /** 
     * Action to toggle design mode for this zoo.
     * @since 2.1
     */
    public final Action TOGGLE_DESIGN_MODE_ACTION = 
        new SimpleAction("Toggle design mode") {
            public void perform() {
                setInDesignMode(!isInDesignMode());
            }
        };
    
    /** 
     * Action to turn on design mode for this zoo.
     * @since 2.1
     */
    public final Action SET_DESIGN_MODE_ON_ACTION = 
        new SimpleAction("Set design mode on") {
            public void perform() {
                setInDesignMode(true);
            }
        };
    
    /** 
     * Action to turn off design mode for this zoo.
     * @since 2.1
     */
    public final Action SET_DESIGN_MODE_OFF_ACTION = 
        new SimpleAction("Set design mode off") {
            public void perform() {
                setInDesignMode(false);
            }
        };
    
    /** 
     * Action to send the first selected component in this zoo
     * to the back of the z-order.
     * @since 2.1
     */
    public final Action SEND_TO_BACK_ACTION = 
        new SimpleAction("Send to back") {
            public void perform() {
                sendToBack(getSelectedComponent());
            }
        };

    /** 
     * Action to bring the first selected component in this zoo
     * to the front of the z-order.
     * @since 2.1
     */
    public final Action BRING_TO_FRONT_ACTION = 
        new SimpleAction("Bring to front") {
            public void perform() {
                bringToFront(getSelectedComponent());
            }
        };

    /** 
     * Action to move the first selected component in this zoo
     * to the next higher z-order position.
     * @since 2.1
     */
    public final Action MOVE_UP_ACTION = 
        new SimpleAction("Move up") {
            public void perform() {
                moveUp(getSelectedComponent());
            }
        };

    /** 
     * Action to move the first selected component in this zoo
     * to the next deeper z-order position.
     * @since 2.1
     */
    public final Action MOVE_DOWN_ACTION = 
        new SimpleAction("Move down") {
            public void perform() {
                moveDown(getSelectedComponent());
            }
        };

    //////////////////
    // Constructors //
    //////////////////

    /** 
     * Constructs a new zoo 
     * that restricts its child components' bounds
     * to be contained within the bounds of this zoo. 
     */
    public Zoo() {
        this(true);
    }
    
    /** 
     * Constructs a new zoo 
     * that either restricts its child components' bounds
     * or does not restrict its child components' bounds. 
     *
     * @param isRestricting whether or not the zoo
     *      will restrict its child components' bounds
     */
    public Zoo(boolean isRestricting) {
        setRestrictingBounds(isRestricting);
        
        installSelectionModel();
        installMouseAdapter();
        installFocusAdapter();
        
        updateSelectionActions();
        updateDesignModeActions();
    }
    
    ////////////////
    // Public API //
    ////////////////

    /**
     * Adds the given component to this zoo
     * at the highest <I>z</I>-order position
     * and prepares the component 
     * for direct manipulation, grouping, and selection.
     *
     * @param c a component to be added to this zoo
     * @return the added component
     * @see #addChildItem(Component, int)
     */
    public Component addChildItem(Component c) {
        return addChildItemImpl(c, 0);
    }
    
    /**
     * Adds the given component to this zoo
     * at the given <I>z</I>-order position
     * and prepares the component 
     * for direct manipulation, grouping, and selection.
     *
     * @param c a component to be added to this zoo
     * @param z the <I>z</I>-order position at which 
     *      to add the component, or -1 if the component 
     *      is to be added at the deepest <I>z</I>-order position
     * @return the added component
     * @see #addChildItem(Component)
     */
    public Component addChildItem(Component c, int z) {
        return addChildItemImpl(c, z);
    }
    
    // getItem inherited
    
    /**
     * Returns an array containing 
     * all of the components contained in this zoo,
     * including components that are nested within groups.
     *
     * Components in the returned array 
     * are provided in the order of their <I>z</I>-order positions.
     *
     * Since components in top-level groups exist 
     * at the same <I>z</I>-order position,
     * such components appear in the returned array
     * in the order of their <I>z</I>-order positions
     * within the nested group.
     *
     * @see #getChildItems()
     * @see ZooContainer#getItem(int)
     * @see ZooContainer#getItemCount()
     */
    public Component[] getItems() {
        Vector items = new Vector();
        
        Component    c     = null;
        ZooGroup group = null;
        Component[]  child = null;
        for (int i = 0; i < getComponentCount(); i++) {
            c = getComponent(i);

            // recurse into groups
            if (c instanceof ZooGroup) {
                group = (ZooGroup)c;
                child = group.getItems();
                for (int j = 0; j < child.length; j++)
                    items.add(child[j]);
            }
            
            // add single components
            else if (!(c instanceof Laminate)) {
                items.add(c);
            }
        }
        
        // return the resulting array
        return (Component[])items.toArray(new Component[0]);
    }
    
    // getItemCount inherited

    // inverseGetItem inherited
    
    /**
     * Returns the top-level component in this zoo
     * at the given <I>z</I>-order.
     *
     * @param z the <I>z</I>-order of the desired component
     * @throws ArrayIndexOutOfBoundsException
     *      if the given <I>z</I>-order position is invalid
     * @see ZooContainer#getItem(int)
     * @see #getChildItems()
     * @see ZooContainer#getChildItemCount()
     */
    public Component getChildItem(int z) {
    
        // eliminate negative z-order positions
        if (z < 0) {
            throw new ArrayIndexOutOfBoundsException(
                "Zoo z-order is invalid: " + z);
        }
        
        // search for the given z-order
        int         zTemp = z;
        Component   c     = null;
        for (int i = 0; i < getComponentCount(); i++) {
            c = getComponent(i);
            
            // only consider non-Laminates
            if (!(c instanceof Laminate)) {
            
                // if the z-order has been reached, return
                if (zTemp == 0)
                    return getComponent(i);
                
                // otherwise look deeper
                zTemp--;
            }
        }

        // z-order is unreachable
        throw new ArrayIndexOutOfBoundsException(
            "Zoo z-order is invalid: " + z);
    }

    /**
     * Returns an array containing 
     * all of the top-level components in this zoo.
     *
     * Components in the returned array 
     * are provided in order of their <I>z</I>-order positions.
     *
     * Since a group may be at the top-level,
     * the array may contain groups as well as single components.
     *
     * @see #getItems()
     * @see #getChildItem(int)
     * @see #getChildItemCount()
     */
    public Component[] getChildItems() {
        Vector child = new Vector();
        
        // add all child components except laminates
        for (int i = 0; i < getComponentCount(); i++) {
            if (!(getComponent(i) instanceof Laminate))
                child.add(getComponent(i));
        }
        
        return (Component[])child.toArray(new Component[0]);
    }

    /**
     * Returns the number of top-level components 
     * in this container.
     *
     * The returned value is one greater than 
     * the <I>z</I>-order position of the component
     * at the deepest <I>z</I>-order position.
     *
     * @see #getItemCount()
     * @see #getChildItem(int)
     * @see #getChildItems()
     */
    public int getChildItemCount() {
        int z = 0;
    
        // count all children except laminates
        for (int i = 0; i < getComponentCount(); i++) {
            if (!(getComponent(i) instanceof Laminate))
                z++;
        }
        
        return z;
    }
    
    // inverseGetChildItem inherited

    /**
     * Removes the given top-level component from this zoo.
     * 
     * If the given component is not at the top level
     * of this zoo, this zoo is not changed.
     *
     * If the given component is a group,
     * the group and its contents are removed.
     *
     * @param c a component to remove from this zoo
     * @return the removed component, or <CODE>null</CODE>
     *      if the given component is not at the top level
     *      of this zoo
     * @see #removeChildItem(int)
     * @see #removeAllItems()
     */
    public Component removeChildItem(Component c) {
        return removeChildItemImpl(c);
    }

    /**
     * Removes the top-level component 
     * at the given <I>z</I>-order position from this zoo.
     * 
     * If the top-level component 
     * at the given <I>z</I>-order position is a group, 
     * the group and its contents are removed.
     *
     * @param z the <I>z</I>-order position of a component
     *      to be removed from this container
     * @return the removed component
     * @throws ArrayIndexOutOfBoundsException
     *      if the given <I>z</I>-order position is invalid
     * @see #removeChildItem(Component)
     * @see #removeAllItems()
     */
    public Component removeChildItem(int z) {
        return removeChildItemImpl(getChildItem(z));
    }
    
    /** 
     * Removes all of the components from this container. 
     *
     * @see #removeChildItem(Component)
     * @see #removeChildItem(int)
     */
    public void removeAllItems() {
        super.removeAllItems();

        deselectAll();

        updateSelectionActions();
    }
    
    // bringToFront inherited

    // sendToBack inherited

    // moveUp inherited

    // moveDown inherited
    
    /**
     * Moves the given component 
     * to the given <I>z</I>-order position.
     *
     * The given <I>z</I>-order position is to be expressed
     * in terms of the <I>z</I>-order of this zoo
     * before the move.
     *
     * The resulting <I>z</I>-order position of the moved component
     * may not be equal to the given <I>z</I>-order position
     * after the component is moved.
     *
     * If the given component is not in this zoo,
     * this zoo is not changed.
     *
     * @param c the component to move
     * @param newZ the <I>z</I>-order position 
     *      to which to move the component
     * @return the <I>z</I>-order position of the component 
     *      after the move is completed, 
     *      or -1 if the component is not in this zoo
     * @throws ArrayIndexOutOfBoundsException
     *      if the given <I>z</I>-order is invalid
     * @see ZooContainer#bringToFront(Component)
     * @see ZooContainer#sendToBack(Component)
     * @see ZooContainer#moveUp(Component)
     * @see ZooContainer#moveDown(Component)
     * @see ZooContainer#nextHigherInZOrder(int)
     * @see ZooContainer#nextDeeperInZOrder(int)
     */
    public int moveToZOrder(Component c, int newZ) {
    
        // get current z-order of component
        int z = inverseGetChildItem(c);
        
        // quit if component is not in this zoo
        if (z == -1)
            return -1;
            
        // quit if component does not have to move
        if (z == newZ)
            return z;
        
        // establish selection status for component
        boolean select = selectionModel.isSelectedIndex(z);
        
        // move the component
        newZ = super.moveToZOrder(c, newZ);
        
        // select the component if appropriate
        if (select)
            addToSelection(c);
        updateSelection();

        // return the new z-order position
        return newZ;
    }

    // nextDeeperInZOrder inherited

    // nextHigherInZOrder inherited

    /**
     * Groups the given top-level components of this zoo.
     *
     * The group will exist at 
     * the relative <I>z</I>-order position
     * of the highest <I>z</I>-order position
     * among the components it contains.
     *
     * The resulting <I>z</I>-order position 
     * for the group may not be equal to
     * the actual <I>z</I>-order position 
     * of the highest <I>z</I>-order position
     * among the components the group contains.
     *
     * @param components the components to be grouped
     * @return the resulting group of components, 
     *      or <CODE>null</CODE> if there were 
     *      less than two components to be grouped
     * @throws NullPointerException 
     *      if the given array is <CODE>null</CODE>
     * @see #ungroup(ZooGroup)
     */
    public ZooGroup group(Component[] components) {
    
        // choose whether or not to select the group
        boolean select = false;
        int     temp   = 0;
        for (int i = 0; i < components.length; i++) {
            temp = inverseGetChildItem(components[i]);
            
            // check each valid component
            if (temp > -1) {
                
                // update selection status
                select |= selectionModel.isSelectedIndex(temp);
            }
            
            // quit loop if a selected component is found
            if (select)
                break;
        }
        
        // group the components
        ZooGroup group = super.group(components);

        // quit if group was not formed
        if (group == null)
            return null;

        // select the group if appropriate
        if (select)
            addToSelection(group);
        
        updateSelectionActions();
        
        // return the new group
        return group;        
    }
    
    /**
     * Disassociates the components in the given group.
     *
     * If the given group is not at the top level of this zoo,
     * this zoo is not changed.
     *
     * The individual components will exist at 
     * the relative <I>z</I>-order position
     * of the disassociated group.
     *
     * The resulting <I>z</I>-order positions 
     * for the individual components may not be equal to
     * the actual <I>z</I>-order position 
     * of the group prior to disassociation.
     *
     * @param group the group of components to disassociate
     * @return an array containing the disassociated components
     *      or a zero-length array if the given group 
     *      is not at the top level of this zoo
     * @see #group(Component[])
     */
    public Component[] ungroup(ZooGroup group) {

        // find z-order position of group
        int z = inverseGetChildItem(group);
        
        // quit if group not found
        if (z == -1)
            return new Component[0];
            
        // store selection status
        boolean select = selectionModel.isSelectedIndex(z);
        
        // ungroup components
        Component[] components = super.ungroup(group);

        // select ungrouped components if appropriate
        if (select) {
            for (int i = 0; i < components.length; i++)
                addToSelection(components[i]);
        }

        // ensure selection is correct
        updateSelection();
        updateSelectionActions();
        
        // return the disassociated components
        return components;
    }

    /** 
     * Selects all top-level components in this zoo.
     *
     * If this zoo contains no components,
     * or all the top-level components in this zoo are selected,
     * this method does nothing.
     *
     * @see #deselectAll()
     * @see #addToSelection(Component)
     * @see #removeFromSelection(Component)
     */
    public void selectAll() {

        // quit if zoo is empty
        if (selectionModel.isSelectionEmpty() &&
            getChildItemCount() == 0)
        {
            return;
        }
        
        // quit if all components are selected
        if (selectionModel.getMinSelectionIndex() == 0 &&
            selectionModel.getMaxSelectionIndex() == 
                getChildItemCount() - 1)
        {
            return;
        }
        
        // get focus if necessary
        if (!hasFocus())
            requestFocus();

        // select all
        selectionModel.setSelectionInterval(0, getChildItemCount() - 1);
        updateSelection();
        updateSelectionActions();
    }

    /** 
     * Deselects all top-level components in this zoo.
     *
     * If this zoo contains no components,
     * or no top-level components in this zoo are selected,
     * this method does nothing.
     *
     * @see #selectAll()
     * @see #removeFromSelection(Component)
     * @see #addToSelection(Component)
     */
    public void deselectAll() {

        // quit if zoo is empty
        if (selectionModel.isSelectionEmpty() &&
            getChildItemCount() == 0)
        {
            return;
        }
        
        // quit if no components are selected
        if (selectionModel.isSelectionEmpty())
            return;

        // get focus if necessary
        if (!hasFocus())
            requestFocus();
    
        // deselect all
        selectionModel.clearSelection();
        updateSelection();
        updateSelectionActions();

        repaint();
    }
    
    /** 
     * Adds the given top-level component 
     * to the list of selected components in this zoo.
     *
     * If the given component is not at the top level
     * of this zoo, this method does nothing.
     *
     * @param c the component to be selected
     * @see #removeFromSelection(Component)
     * @see #selectAll()
     * @see #deselectAll()
     */
    public void addToSelection(Component c) {

        // get z-order for component
        int z = inverseGetChildItem(c);

        // quit if component not in this zoo
        if (z == -1)
            return;
            
        // get focus if necessary
        if (!hasFocus())
            requestFocus();
    
        // add component to selection
        if (!selectionModel.isSelectedIndex(z)) {
            selectionModel.addSelectionInterval(z, z);

            Laminate glass = getLaminateFor(c);
            if (glass != null)
                glass.setSelected(true);

            updateSelectionActions();

            repaint();
        }
    }
    
    /** 
     * Removes the given top-level component 
     * from the list of selected components in this zoo.
     *
     * If the given component is not at the top level
     * of this zoo, this method does nothing
     *
     * @param c the component to be deselected
     * @see #addToSelection(Component)
     * @see #deselectAll()
     * @see #selectAll()
     */
    public void removeFromSelection(Component c) {

        // get z-order for component
        int z = inverseGetChildItem(c);

        // quit if component not in this zoo
        if (z == -1)
            return;
            
        // get focus if necessary
        if (!hasFocus())
            requestFocus();
    
        // remove component from selection
        if (selectionModel.isSelectedIndex(z)) {
            selectionModel.removeSelectionInterval(z, z);

            Laminate glass = getLaminateFor(c);
            if (glass != null)
                glass.setSelected(false);

            updateSelectionActions();

            repaint();
        }
    }
    
    /**
     * Sets the selection for this zoo
     * to only the the given component.
     *
     * If the given component is <CODE>null</CODE>,
     * or is not in this zoo, this method does nothing.
     *
     * @param c component to select
     * @see #addToSelection(Component)
     * @since 2.1
     */
    public void selectOnly(Component c) {
        
        // get z-order for component
        int z = inverseGetChildItem(c);

        // quit if component not in this zoo
        if (z == -1)
            return;
            
        // select only the component
        selectionModel.setSelectionInterval(z, z);
        updateSelection();
        updateSelectionActions();
    }
    
    /**
     * <P>Sets the selection for this zoo
     * to exactly the components in the given array
     * that are not-null and are contained 
     * at top-level in this zoo.</P>
     *
     * <P>If the given array is <CODE>null</CODE>,
     * or no changes to the selection for this zoo
     * are to be made as a result of this operation,
     * this method does nothing.</P>
     *
     * <P>For this method, changes to the selection model
     * are made in exactly the following order:
     *
     * First, the selection is cleared.
     * Next, each discrete interval of components
     * in the given array are selected, by z-order.
     *
     * In this way, the number of changes to the selection model
     * will be at least 2, and at most 
     * the length of the given array.</P>
     *
     * @param c components to be selected
     * @see #getSelectedComponents()
     * @since 2.1
     */
    public void setSelectedComponents(Component[] c) {
        if (c == null)
            return;
        
        // get z-order indices of components to select
        int z;
        boolean[] select = new boolean[getChildItemCount()];
        for (int i = 0; i < c.length; i++) {
            z = inverseGetChildItem(c[i]);
            if (z != -1)
                select[z] = true;
        }
        
        // see if at least one change is to be made
        int i;
        for (i = 0; i < select.length; i++) {
        
            // and if so, rebuild the entire selection
            if (selectionModel.isSelectedIndex(i) != select[i])
                break;
        }
        
        // if no changes were found, quit
        if (i == select.length)
            return;

        // first, clear selection
        selectionModel.clearSelection();
        int start = 0, 
            end   = 0;
                
        // find first interval start
        while (start < select.length && !select[start])
            start++;
        end = start;

        // select all intervals
        while (start < select.length) {

            // find end of selection
            while (end < select.length && select[end])
                end++;

            // select interval
            end = (end == start ? start + 1 : end);
            selectionModel.addSelectionInterval(start, end - 1);
            
            // find next interval start
            start = end;
            while (start < select.length && !select[start])
                start++;
            end = start;
        }

        // update visualization of selection
        updateSelection();
        updateSelectionActions();
    }
    
    /**
     * Returns the selected component in this zoo
     * at the highest <I>z</I>-order position,
     * or <CODE>null</CODE> if the selection is empty.
     *
     * @see #getSelectedComponent(int)
     * @see #getSelectedComponents()
     * @see #getSelectedComponentCount()
     * @see #isSelectionEmpty()
     */
    public Component getSelectedComponent() {
        if (selectionModel.isSelectionEmpty())
            return null;
            
        return getChildItem(
            selectionModel.getMinSelectionIndex());
    }
    
    /**
     * Returns the selected component in this zoo
     * at the given index into the list of selected components,
     * or <CODE>null</CODE> if the selection is empty.
     *
     * @throws ArrayIndexOutOfBoundsException
     *      if the given index is out of bounds
     * @see #getSelectedComponent()
     * @see #getSelectedComponents()
     * @see #getSelectedComponentCount()
     * @see #isSelectionEmpty()
     */
    public Component getSelectedComponent(int index) {
    
        // return null if no selection
        if (selectionModel.isSelectionEmpty())
            return null;
            
        // eliminate negative indices
        if (index < 0) {
            throw new ArrayIndexOutOfBoundsException(
                "Index into selected components invalid: " + index);
        }

        // search for selected component at index
        int count = index;
        for (int i  = selectionModel.getMinSelectionIndex(); 
                 i <= selectionModel.getMaxSelectionIndex(); 
                 i++) 
        {
            if (selectionModel.isSelectedIndex(i)) {
                if (count == 0)
                    return getChildItem(i);
                else
                    count--;
            }
        }
        
        // index out of bounds
        throw new ArrayIndexOutOfBoundsException(
            "Index into selected components invalid: " + index);
    }

    /**
     * Returns an array containing 
     * the selected components in this zoo 
     * in the order of their <I>z</I>-order positions,
     * or a zero-length array if the selection is empty.
     *
     * @see #getSelectedComponent()
     * @see #getSelectedComponent(int)
     * @see #getSelectedComponentCount()
     * @see #isSelectionEmpty()
     */
    public Component[] getSelectedComponents() {
        Vector select = new Vector();
        
        Component[] child = getChildItems();
        for (int i  = selectionModel.getMinSelectionIndex(); 
                 i <= selectionModel.getMaxSelectionIndex(); 
                 i++) 
        {
            if (selectionModel.isSelectedIndex(i))
                select.add(child[i]);
        }
        
        return (Component[])select.toArray(new Component[0]);
    }
    
    /** 
     * Returns the number of selected components in this zoo. 
     *
     * @see #getSelectedComponent()
     * @see #getSelectedComponent(int)
     * @see #getSelectedComponents()
     * @see #isSelectionEmpty()
     */
    public int getSelectedComponentCount() {
        if (isSelectionEmpty())
            return 0;
            
        int numSelected = 0;
        for (int i  = selectionModel.getMinSelectionIndex(); 
                 i <= selectionModel.getMaxSelectionIndex(); 
                 i++) 
        {
            if (selectionModel.isSelectedIndex(i))
                numSelected++;
        }
        
        return numSelected;
    }
    
    /** 
     * Returns whether or not the selection is empty. 
     *
     * @see #getSelectedComponent()
     * @see #getSelectedComponent(int)
     * @see #getSelectedComponents()
     * @see #getSelectedComponentCount()
     */
    public boolean isSelectionEmpty() {
        return selectionModel.isSelectionEmpty();
    }
    
    /** Returns the selection model for this zoo. */
    public ListSelectionModel getSelectionModel() {
        return selectionModel;
    }
    
    /** 
     * Groups the currently selected components.
     *
     * If fewer than two components are selected,
     * no association is performed.
     *
     * @return the resulting group, or <CODE>null</CODE>
     *      if the selection is too few to be grouped
     * @see #ungroupSelection()
     */
    public ZooGroup groupSelection() {
        Component[] select = getSelectedComponents();

        if (select.length > 1)
            return group(select);
        
        return null;
    }

    /** 
     * Ungroups the currently selected components.
     *
     * Selected components that are not groups
     * are unaffected by this operation.
     *
     * @see #groupSelection()
     */
    public void ungroupSelection() {
        Component[] components = getSelectedComponents();
        
        for (int i = 0; i < components.length; i++) {
            if (components[i] instanceof ZooGroup) 
                ungroup((ZooGroup)components[i]);
        }
    }
    
    /**
     * Sets whether or not this zoo is in design mode.
     *
     * @param designing whether or not this zoo
     *      is in design mode
     * @see #isInDesignMode()
     */
    public void setInDesignMode(boolean designing) {
        designMode = designing;

        // enable or disable mouse adapter
        if (designMode)
            mouseAdapter.addAsListenerTo(this);
        else
            mouseAdapter.removeAsListenerTo(this);

        // activate or deactivate laminates
        Laminate    glass = null;
        Component[] child = getChildItems();

        for (int i = 0; i < child.length; i++) {
            glass = getLaminateFor(child[i]);
            if (glass != null) {
                glass.setActivated(designMode);
                if (!designMode)
                    glass.setSelected(false);
            }
        }
        
        updateDesignModeActions();
        
        if (designMode)
            updateSelection();

        repaint();
    }
    
    /** 
     * Returns whether or not this zoo is in design mode. 
     *
     * @see #setInDesignMode(boolean)
     */
    public boolean isInDesignMode() {
        return designMode;
    }
    
    /**
     * Sets whether or not this zoo
     * is restricting its child components' bounds
     * to be contained within the bounds of this zoo.
     *
     * @param isRestricting whether or not this zoo 
     *      is restricting its child components' bounds
     * @see #isRestrictingBounds()
     */
    public void setRestrictingBounds(boolean isRestricting) {
        restrictingBounds = isRestricting;
    }

    /** 
     * Returns whether or not this zoo
     * is restricting its child components' bounds
     * to be contained within the bounds of this zoo.
     *
     * @see #setRestrictingBounds(boolean)
     */
    public boolean isRestrictingBounds() {
        return restrictingBounds;
    }

    /**
     * Sets whether or not the child items of this zoo
     * can be moved using direct manipulation.
     *
     * Methods such as <CODE>setLocation</CODE> are not affected
     * by the value of this property.
     *
     * @param allowsMove whether or not this zoo allows child items
     *      to be moved using direct manipulation
     * @see #allowsMove()
     * @since 2.0
     */
    public void setAllowsMove(boolean allowsMove) {
        moveable = allowsMove;
    }
    
    /**
     * Returns whether or not the child items in this zoo
     * can be moved using direct manipulation.
     *
     * Methods such as <CODE>setLocation</CODE> are not affected
     * by the value of this property.
     *
     * @see #setAllowsMove(boolean)
     * @since 2.0
     */
    public boolean allowsMove() {
        return moveable;
    }

    /**
     * Sets whether or not the child items of this zoo
     * can be resized using direct manipulation.
     *
     * Methods such as <CODE>setSize</CODE> are not affected
     * by the value of this property.
     *
     * @param allowsResize whether or not this zoo allows child items
     *      to be resized using direct manipulation
     * @see #allowsResize()
     * @since 2.0
     */
    public void setAllowsResize(boolean allowsResize) {
        resizeable = allowsResize;
    }

    /**
     * Returns whether or not the child items in this zoo
     * can be resized using direct manipulation.
     *
     * Methods such as <CODE>setSize</CODE> are not affected
     * by the value of this property.
     *
     * @see #setAllowsResize(boolean)
     * @since 2.0
     */
    public boolean allowsResize() {
        return resizeable;
    }
    
    /**
     * Sets the color used to highlight a selected component
     * when this zoo has the keyboard focus.
     *
     * If the given color is <CODE>null</CODE>,
     * the highlight color is not changed.
     *
     * @param c highlight color for when this zoo has focus
     * @see #getFocusedHighlightColor()
     * @see #setUnfocusedHighlightColor(Color)
     * @since 2.1
     */
    public void setFocusedHighlightColor(Color c) {
        if (c == null)
            return;

        focusedHighlightColor = c;
    }
    
    /**
     * Returns the color used to highlight a selected component
     * when this zoo has the keyboard focus.
     *
     * @see #getUnfocusedHighlightColor()
     * @see #setFocusedHighlightColor(Color)
     * @since 2.1
     */
    public Color getFocusedHighlightColor() {
        return focusedHighlightColor;
    }
     
    /**
     * Sets the color used to highlight a selected component
     * when this zoo does not have the keyboard focus.
     *
     * If the given color is <CODE>null</CODE>,
     * the highlight color is computed from 
     * the highlight color for when this zoo has focus.
     *
     * @param c highlight color for when this zoo does not have focus
     * @see #setFocusedHighlightColor(Color)
     * @see #getUnfocusedHighlightColor()
     * @since 2.1
     */
    public void setUnfocusedHighlightColor(Color c) {
        if (c == null)
            return;

        unfocusedHighlightColor = c;
    }

    /**
     * Returns the color used to highlight a selected component
     * when this zoo does not have the keyboard focus.
     *
     * If this property is set to <CODE>null</CODE>,
     * the color returned is a color computed
     * from the highlight color for when this zoo has focus.
     *
     * @see #setUnfocusedHighlightColor(Color)
     * @see #getFocusedHighlightColor()
     * @since 2.1
     */
    public Color getUnfocusedHighlightColor() {
        if (unfocusedHighlightColor == null)
            return computeUnfocusedHighlightColor(
                getFocusedHighlightColor());

        return unfocusedHighlightColor;
    }
     
    /**
     * Returns the laminate for the given component,
     * or <CODE>null</CODE> if the given component
     * is not a child item of this zoo.
     *
     * @param c the component whose laminate is to be returned
     * @since 2.0
     */
    public Laminate getLaminateFor(Component c) {
        Object o = laminateTable.get(c);
        
        if (o instanceof Laminate)
            return (Laminate)o;

        // laminate not found
        return null;
    }

    /** 
     * Returns <CODE>true</CODE> if this zoo is in design mode,
     * or <CODE>false</CODE> otherwise. 
     *
     * @see Component#isFocusTraversable()
     */
    public boolean isFocusTraversable() {
        return isInDesignMode();
    }

    /** 
     * Returns <CODE>true</CODE> if this zoo is in design mode,
     * or <CODE>false</CODE> otherwise. 
     *
     * @see JComponent#isManagingFocus()
     */
    public boolean isManagingFocus() {
        return isInDesignMode();
    }
    
    ///////////////////////
    // Protected methods //
    ///////////////////////
    
    /**
     * Paints this component to the given graphics context.
     *
     * @param g the graphics context to which to paint
     */
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);

        if (hasFocus() && isInDesignMode()) {
            Graphics2D g2 = (Graphics2D)g;

            // show lasso if appropriate
            Rectangle lasso = getLassoBounds();
            if (lasso != null) {
                Stroke temp = g2.getStroke();

                g2.setColor(Color.black);
                g2.setStroke(dashedStroke);
                g2.draw(lasso);
                g2.setStroke(temp);
            }

            // otherwise show focus if necessary
            else if (isSelectionEmpty()) {
                Insets in = getInsets();
                
                // draw a box around the edge of this zoo
                g2.setColor(SystemColor.textHighlight);
                g2.drawRect(
                    in.left, 
                    in.top, 
                    getWidth()  - 1 - in.left - in.right, 
                    getHeight() - 1 - in.top  - in.bottom);
            }
        }
    }

    /** 
     * Returns a new laminate for the given component. 
     *
     * @param c the component to be laminated
     */
    protected Laminate createLaminateFor(Component c) {
        return new Laminate(c, isInDesignMode());
    }

    /** Installs the mouse adapter for this zoo. */
    protected void installMouseAdapter() {
        mouseAdapter = new MouseActionAdapter(this);
        
        mouseAdapter.addMousePressedAction(new MouseAction() {
            public void mouseActionPerformed(MouseEvent evt) {
                startLasso(evt);
            }
        });

        mouseAdapter.addMouseDraggedAction(new MouseAction() {
            public void mouseActionPerformed(MouseEvent evt) {
                resizeLasso(evt);
            }
        });

        mouseAdapter.addMouseReleasedAction(new MouseAction() {
            public void mouseActionPerformed(MouseEvent evt) {
                endLasso(evt);
            }
        });
    }
    
    /** installs the selection model for this zoo. */
    protected void installSelectionModel() {
        selectionModel = new DefaultListSelectionModel();
        selectionModel.setSelectionMode(
            ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
    }
    
    /** Installs the focus adapter for this zoo. */
    protected void installFocusAdapter() {
        FocusActionAdapter focusAdapter = new FocusActionAdapter();
        addFocusListener(focusAdapter);
        
        SimpleAction repaint = new SimpleAction() {
            public void perform() {
                repaint();
            }
        };
        
        focusAdapter.addFocusGainedAction(repaint);
        focusAdapter.addFocusLostAction(repaint);
    }
    
    /**
     * Selects or deselects the appropriate components
     * based on the state of the selection model.
     */
    protected void updateSelection() {
        Component[] child = getChildItems();
        for (int i = 0; i < child.length; i++) {
            Laminate glass = getLaminateFor(child[i]);

            if (glass != null)
                glass.setSelected(selectionModel.isSelectedIndex(i));
        }
        
        repaint();
    }
    
    /** 
     * Returns the bounds of the current lasso, 
     * or <CODE>null</CODE> if there is no current lasso.
     */
    protected Rectangle getLassoBounds() {
        if (lassoStart == null)
            return null;
    
        int x1 = (int)Math.min(lassoStart.x, lassoLast.x),
            y1 = (int)Math.min(lassoStart.y, lassoLast.y),
            x2 = (int)Math.max(lassoStart.x, lassoLast.x),
            y2 = (int)Math.max(lassoStart.y, lassoLast.y);

        return new Rectangle(x1, y1, x2 - x1, y2 - y1);
    }

    /**
     * Starts a new lasso given the mouse event
     * that prompted its origination.
     *
     * @param evt the mouse event that prompted a lasso
     */
    protected void startLasso(MouseEvent evt) {
        lassoStart = new Point(evt.getX(), evt.getY());
        lassoLast  = lassoStart;

        // only deselect if not adding to selection        
        if (!evt.isShiftDown())
            deselectAll();
    }
    
    /**
     * Resizes the current lasso given the mouse event
     * that prompted its resize.
     *
     * @param evt the mouse event that prompted a lasso resize
     */
    protected void resizeLasso(MouseEvent evt) {
        lassoLast = new Point(evt.getX(), evt.getY());

        Rectangle lasso = getLassoBounds();
        
        Component[] child = getChildItems();
        for (int i = 0; i < child.length; i++) {
            if (lasso.contains(child[i].getBounds())) {
                addToSelection(child[i]);
            }
            else if (!evt.isShiftDown()) {
                removeFromSelection(child[i]);
            }
        }
        
        repaint();
    }
    
    /**
     * Ends the current lasso given the mouse event
     * that prompted its destruction.
     *
     * @param evt the mouse event that prompted
     *      the end of the current lasso
     */
    protected void endLasso(MouseEvent evt) {
        lassoStart = null;
        lassoLast  = null;
        
        repaint();
    }

    /**
     * Enables or disables selection actions
     * based on the current selection.
     *
     * <P><CODE>SELECT_ALL_ACTION</CODE> is enabled if
     * there is at least one top-level component not selected,
     * and is disabled otherwise.</P>
     *
     * <P><CODE>DESELECT_ALL_ACTION</CODE> is enabled if
     * there is at least one top-level component selected,
     * and is disabled otherwise.</P>
     *
     * <P><CODE>GROUP_SELECTED_COMPONENTS_ACTION</CODE> is enabled if
     * there are at least two top-level components selected,
     * and is disabled otherwise.</P>
     *
     * <P><CODE>UNGROUP_SELECTED_COMPONENT_ACTION</CODE> is enabled if
     * there is at least one top-level component selected
     * and the first selected component 
     * is an instance of <CODE>ZooGroup</CODE>,
     * and is disabled otherwise.</P>
     *
     * <P><CODE>UNGROUP_SELECTED_COMPONENTS_ACTION</CODE> is enabled if
     * there is at least one top-level component selected
     * and all of those components 
     * are instances of <CODE>ZooGroup</CODE>,
     * and is disabled otherwise.</P>
     *
     * <P><CODE>SEND_TO_BACK_ACTION</CODE> is enabled if
     * there is only one top-level component selected
     * and the selected component is not at the deepest z-order,
     * and is disabled otherwise.</P>
     *
     * <P><CODE>BRING_TO_FRONT_ACTION</CODE> is enabled if
     * there is only one top-level component selected
     * and the selected component is not at the highest z-order,
     * and is disabled otherwise.</P>
     *
     * <P><CODE>MOVE_UP_ACTION</CODE> is enabled if
     * there is only one top-level component selected
     * and the selected component is not at the highest z-order,
     * and is disabled otherwise.</P>
     *
     * <P><CODE>MOVE_DOWN_ACTION</CODE> is enabled if
     * there is only one top-level component selected
     * and the selected component is not at the deepest z-order,
     * and is disabled otherwise.</P>
     *
     * @see #SELECT_ALL_ACTION
     * @see #DESELECT_ALL_ACTION
     * @see #GROUP_SELECTED_COMPONENTS_ACTION
     * @see #UNGROUP_SELECTED_COMPONENT_ACTION
     * @see #UNGROUP_SELECTED_COMPONENTS_ACTION
     * @see #SEND_TO_BACK_ACTION
     * @see #BRING_TO_FRONT_ACTION
     * @see #MOVE_UP_ACTION
     * @see #MOVE_DOWN_ACTION
     * @since 2.1
     */
    protected void updateSelectionActions() {
        SELECT_ALL_ACTION.setEnabled(
            getSelectedComponentCount() != getChildItemCount());
        
        DESELECT_ALL_ACTION.setEnabled(
            !isSelectionEmpty());
            
        Component[] selected = getSelectedComponents();
        Component c = getSelectedComponent();
        
        GROUP_SELECTED_COMPONENTS_ACTION.setEnabled(
            selected.length > 1);
        
        UNGROUP_SELECTED_COMPONENT_ACTION.setEnabled(
            c instanceof ZooGroup);
        
        int z = inverseGetChildItem(c);
        
        SEND_TO_BACK_ACTION.setEnabled(
            (selected.length == 1) &&
            (z < getChildItemCount() - 1) &&
            (z != -1));
        
        BRING_TO_FRONT_ACTION.setEnabled(
            (selected.length == 1) &&
            (z > 0));

        MOVE_UP_ACTION.setEnabled(
            (selected.length == 1) &&
            (z > 0));

        MOVE_DOWN_ACTION.setEnabled(
            (selected.length == 1) &&
            (z < getChildItemCount() - 1) &&
            (z != -1));
        
        if (selected.length < 1) {
            UNGROUP_SELECTED_COMPONENTS_ACTION.setEnabled(false);
            return;
        }

        for (int i = 0; i < selected.length; i++) {
            if (!(selected[i] instanceof ZooGroup)) {
                UNGROUP_SELECTED_COMPONENTS_ACTION.setEnabled(false);
                return;
            }
        }

        UNGROUP_SELECTED_COMPONENTS_ACTION.setEnabled(true);
    }

    /**
     * Enables or disables design mode actions
     * based on the current design mode.
     *
     * <CODE>SET_DESIGN_MODE_ON_ACTION</CODE> is enabled if
     * this zoo is not in design mode,
     * and is disabled otherwise.
     *
     * <CODE>SET_DESIGN_MODE_OFF_ACTION</CODE> is enabled if
     * this zoo is in design mode,
     * and is disabled otherwise.
     *
     * @see #SET_DESIGN_MODE_ON_ACTION
     * @see #SET_DESIGN_MODE_OFF_ACTION
     */
    protected void updateDesignModeActions() {
        SET_DESIGN_MODE_ON_ACTION.setEnabled(
            !isInDesignMode());
        
        SET_DESIGN_MODE_OFF_ACTION.setEnabled(
            isInDesignMode());
    }
    
    /**
     * Computes a suitable color 
     * of the highlight of selected components 
     * for when this zoo does not have focus,
     * from the given color.
     *
     * If the given color is <CODE>null</CODE>,
     * this method returns the color black.
     *
     * @param c color used to compute the suitable color
     * @since 2.1
     */
    protected Color computeUnfocusedHighlightColor(Color c) {
        if (c == null)
            return Color.black;
        
        int intensity = Math.max(c.getRed(), Math.max(
                                 c.getBlue(), c.getGreen()));
        
        // if color is too dark, return a lighter color
        if (intensity / 255.0 < .1)
            return c.brighter();

        // otherwise return darker color        
        return c.darker();
    }
    
    /////////////////////
    // Private methods //
    /////////////////////

    /** 
     * Adds the given component at the given z-order position. 
     *
     * @param c a component to be added
     * @param z the desired z-order position of the component
     * @throws ArrayIndexOutOfBoundsException
     *      if the given z-order position is invalid
     */
    private Component addChildItemImpl(Component c, int z) {
        if (c == null)
            return null;
            
        // fix z-order for add at lowest z-order
        if (z == -1)
            z = getChildItemCount();
            
        // check for invalid z-order
        if ((z < 0) || (z > getChildItemCount())) {
            throw new ArrayIndexOutOfBoundsException(
                "Zoo z-order invalid: " + z);
        }
        
        // set component size if not already set
        if ((c.getHeight() == 0) && (c.getWidth() == 0))
            c.setSize(c.getPreferredSize());
                
        // add the component and its laminate
        Laminate glass = createLaminateFor(c);
        laminateTable.put(c, glass);
        
        // add the component and laminate
        int index = zOrderToIndex(z);
        add(glass, index);
        add(c, index + 1);

        // fix selection model
        selectionModel.insertIndexInterval(z, 1, true);
        selectionModel.removeSelectionInterval(z, z);
        updateSelection();
        updateSelectionActions();
        
        return c;
    }
    
    /** 
     * Removes the given component from this zoo. 
     *
     * @param c a component to be removed
     */
    private Component removeChildItemImpl(Component c) {
        
        // search through components with z-order
        Component[] child = getChildItems();
        for (int i = 0; i < child.length; i++) {
        
            // if the component is not in a group
            // remove the component and its laminate
            if (child[i] == c) {
                removeComponentImpl(c, i);
                updateSelection();
                return c;
            }
        }
        
        // return null if component not found
        return null;
    }

    /** 
     * Removes the given component and its laminate. 
     *
     * @param c a component to be removed
     * @param z the z-order position of the component,
     *      or -1 if the z-order position is not known
     */
    private void removeComponentImpl(Component c, int z) {
        if (c instanceof Laminate)
            return;
            
        // if z-order is not already known, find it
        if (z == -1)
            z = inverseGetChildItem(c);
            
        // if z-order is still not known, quit
        if (z == -1)
            return;
            
        // get the laminate and delaminate the component
        Laminate glass = getLaminateFor(c);
        c.removeComponentListener(glass);
        glass.setTarget(null);
        
        // remove the components
        remove(glass);
        remove(c);

        // fix the selection model
        selectionModel.removeIndexInterval(z, z);
        updateSelectionActions();
    }
    
    /** 
     * Returns the Java container index 
     * of the component at the given z-order.
     *
     * @param z the z-order position of the component
     *      whose Java container index is desired
     */     
    private int zOrderToIndex(int z) {
        for (int i = 0; i < getComponentCount(); i++) {
            if (z == 0)
                return i;
            
            if (!(getComponent(i) instanceof Laminate))
                z--;
        }
        
        return getComponentCount();
    }
}
