/*
 * org.openmicroscopy.shoola.util.ui.clsf.TreeCheck
 *
 *------------------------------------------------------------------------------
 *  Copyright (C) 2006 University of Dundee. All rights reserved.
 *
 *
 * 	This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License along
 *  with this program; if not, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 *------------------------------------------------------------------------------
 */

package org.openmicroscopy.shoola.util.ui.clsf;

//Java imports
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import javax.swing.Icon;
import javax.swing.JCheckBox;
import javax.swing.JRadioButton;
import javax.swing.JTree;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

//Third-party libraries

//Application-internal dependencies

/** 
 * Displays an hierarchical data. Each node added to the structure
 * is an instance of {@link TreeCheckNode} i.e. a selection box to select the 
 * node is added to the node if the parameter <code>leafOnly</code> is set to 
 * <code>false</code>. If the parameter <code>leafOnly</code> is set to 
 * <code>true</code>, only the leaf nodes can be selected.
 * 
 *
 * @author  Jean-Marie Burel &nbsp;&nbsp;&nbsp;&nbsp;
 * 				<a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
 * @version 2.2
 * <small>
 * (<b>Internal version:</b> $Revision$ $Date$)
 * </small>
 * @since OME2.2
 */
public class TreeCheck
    extends JTree
{

    /** Bounds property to indicate a node is selected or deselected. */
    public static final String  NODE_SELECTED_PROPERTY = "nodeSelected";

    /** 
     * Flag to indicate that only one child can be selected in a given parent.
     * If <code>true</code> only one child can be selected at a time.
     * If <code>false</code> multiple children can be selected.
     */
    private boolean singleSelectionInParent;
    
    /** Fires a property change with the numbers of nodes selected. */
    private void fireNodeSelection()
    {
        DefaultTreeModel dtm = (DefaultTreeModel) getModel();
        TreeCheckNode root = (TreeCheckNode) dtm.getRoot();
        Enumeration nodes = root.breadthFirstEnumeration();
        TreeCheckNode node;
        int index = 0;
        //Determine the number of selected nodes.
        while (nodes.hasMoreElements()) {
            node = (TreeCheckNode) nodes.nextElement();
            if (node.isSelected()) index++;
        }
        firePropertyChange(NODE_SELECTED_PROPERTY, Integer.valueOf(-1), 
        		Integer.valueOf(index));
    }
    
    /**
     * Selects the specified node and deselects the other siblings.
     * 
     * @param node The node to select.
     */
    private void handleSingleSelection(TreeCheckNode node)
    {
        TreeCheckNode parent = node.getParentDisplay();
        if (parent == null) return;
        if (node.isSelected()) return; //node already selected.
        Set nodes = parent.getChildrenDisplay();
        Iterator i = nodes.iterator();
        TreeCheckNode child;
        while (i.hasNext()) {
            child = (TreeCheckNode) i.next();
            child.setSelected(child.equals(node));
        }
        fireNodeSelection();
    }
    
    /**
     * Initializes the tree i.e. set the model, the selection model, etc.
     * 
     * @param root      The root node of the tree.
     * @param leafOnly  Passed <code>true</code> to allow leaves selection only
     *                  <code>false</code> otherwise.
     */
    private void initialize(TreeCheckNode root, boolean leafOnly)
    {
        setCellRenderer(new TreeCheckRenderer(leafOnly));
        getSelectionModel().setSelectionMode(
                TreeSelectionModel.SINGLE_TREE_SELECTION);
        putClientProperty("JTree.lineStyle", "Angled");
        setShowsRootHandles(true);
        super.setModel(new TreeCheckModel(root));
        addMouseListener(new MouseAdapter() {
            
            /** Handles the node selection. */
            public void mouseClicked(MouseEvent me) {
                int row = getRowForLocation(me.getX(), me.getY());
                TreePath path = getPathForRow(row);
                if (path == null) return;
                Object o = path.getLastPathComponent();
                if (!(o instanceof TreeCheckNode)) return;
                TreeCheckNode node = (TreeCheckNode) o;
                if (singleSelectionInParent) handleSingleSelection(node);
                else {
                	node.setSelected(!node.isSelected());
                	fireNodeSelection();
                }
                    
                ((DefaultTreeModel) getModel()).nodeChanged(node);
                if (row == 0) {
                    revalidate();
                    repaint();
                }
            }
        });
    }
    
    /**
     * Creates a new instance.
     * 
     * @param rootObject    The object hosted by the root node.
     * @param rootIcon      The icon of the root node.
     */
    public TreeCheck(Object rootObject, Icon rootIcon)
    {
        initialize(new TreeCheckNode(rootObject, rootIcon), true);
    }
    
    /**
     * Creates a new instance.
     * 
     * @param root The root node.
     */
    public TreeCheck(TreeCheckNode root)
    {
        initialize(root, true);
    }
    
    /**
     * Creates a new instance.
     * 
     * @param rootObject    The object hosted by the root node.
     * @param rootIcon      The icon of the root node.
     * @param leafOnly      Passed <code>true</code> to allow leaves selection
     *                      only, <code>false</code> otherwise.               
     */
    public TreeCheck(Object rootObject, Icon rootIcon, boolean leafOnly)
    {
        initialize(new TreeCheckNode(rootObject, rootIcon), leafOnly);
    }
    
    /**
     * Sets the {@link #singleSelectionInParent} value.
     * 
     * @param b The value to set.
     */
    public void setSingleSelectionInParent(boolean b)
    {
        singleSelectionInParent = b;
        TreeCheckRenderer rnd = (TreeCheckRenderer) getCellRenderer();
        if (singleSelectionInParent) rnd.initToggleButton(JRadioButton.class);
        else rnd.initToggleButton(JCheckBox.class);
    }
    
    /**
     * Returns <code>true</code> if for a given parent, only one child
     * can be selected at a time, <code>false</code> otherwise.
     * 
     * @return See above.
     */
    public boolean isSingleSelectionInParent()
    { 
        return singleSelectionInParent;
    }
    
    /**
     * Returns a collection of selected {@link TreeCheckNode}s.
     * 
     * @return See above.
     */
    public Set getSelectedNodes()
    {
        HashSet set = new HashSet();
        DefaultTreeModel dtm = (DefaultTreeModel) getModel();
        TreeCheckNode root = (TreeCheckNode) dtm.getRoot();
        Enumeration nodes = root.breadthFirstEnumeration();
        TreeCheckNode node;
        while (nodes.hasMoreElements()) {
            node = (TreeCheckNode) nodes.nextElement();
            if (node.isSelected()) set.add(node);
        }
        return Collections.unmodifiableSet(set);
    }
    
    /** Selects all nodes. */
    public void selectAllNodes()
    {
        DefaultTreeModel dtm = (DefaultTreeModel) getModel();
        TreeCheckNode root = (TreeCheckNode) dtm.getRoot();
        Enumeration nodes = root.breadthFirstEnumeration();
        TreeCheckNode node;
        while (nodes.hasMoreElements()) {
            node = (TreeCheckNode) nodes.nextElement();
            node.setSelected(true);
        }
        fireNodeSelection();
        repaint();
    }
    
    /** Deselects all the nodes. */
    public void deselectAllNodes()
    {
        DefaultTreeModel dtm = (DefaultTreeModel) getModel();
        TreeCheckNode root = (TreeCheckNode) dtm.getRoot();
        Enumeration nodes = root.breadthFirstEnumeration();
        TreeCheckNode node;
        while (nodes.hasMoreElements()) {
            node = (TreeCheckNode) nodes.nextElement();
            node.setSelected(false);
        }
        fireNodeSelection();
        repaint();
    }
    
    /**
     * Overridden to make sure that the root node is a {@link TreeCheckNode}.
     * @see JTree#setModel(TreeModel)
     */
    public void setModel(TreeModel newModel) {}
    
}
