/*
 * @(#)ActionsPanel.java    2.0  26 September 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 java.awt.*;
import java.beans.*;
import java.util.*;
import javax.swing.*;

/**
 * <P>A panel containing <CODE>Action</CODE> objects represented
 * by buttons that initiate their respective actions. Aspects of
 * the action's user interface representation, for example,
 * the button's label or icon, are extracted from the properties
 * of the action object and updated if the properties of the
 * action object are later changed.</P>
 *
 * <P>An actions panel is designed to drive a GUI by containing
 * buttons that cause actions to be performed by the program
 * that makes use of the graphical interface.<P>
 *
 * <P>The functionality of the <CODE>ActionsPanel</CODE> class is
 * based on the functionality of Java <CODE>JToolBar</CODE> class.
 * An <CODE>ActionsPanel</CODE> may be added to a
 * <CODE>JToolBar</CODE> in order to make use of its floating and
 * docking functionality.</P>
 *
 * @author  Jeff Raab
 * @author  Richard Rasala
 * @version 2.2
 * @since   1.0
 * @see Action
 */
public class ActionsPanel extends DisplayPanel {

    /** 
     * Maps actions to their automatically created buttons, 
     * and vice-versa. 
     */
    private Hashtable map = new Hashtable();
    
    //////////////////
    // Constructors //
    //////////////////
    
    /**
     * Constructs an actions panel that contains no actions and
     * uses the default layout manager for a <CODE>JPanel</CODE>.
     *
     * @see #ActionsPanel(LayoutManager)
     * @see #ActionsPanel(Action[])
     * @see #ActionsPanel(Action[], LayoutManager)
     * @see #ActionsPanel(Action[], Object[], LayoutManager)
     */
    public ActionsPanel() {}
    
    /**
     * Constructs an actions panel that contains no actions and
     * uses the given layout manager.
     *
     * If the given layout manager is <CODE>null</CODE>, the
     * default layout manager for a <CODE>JPanel</CODE> is used.
     *
     * @param layout the desired layout manager for the panel
     * @see #ActionsPanel()
     * @see #ActionsPanel(Action[])
     * @see #ActionsPanel(Action[], LayoutManager)
     * @see #ActionsPanel(Action[], Object[], LayoutManager)
     */
    public ActionsPanel(LayoutManager layout) {
        this(null, null, layout);
    }
    
    /**
     * Constructs an actions panel that contains the given actions
     * and uses the default layout manager for a <CODE>JPanel</CODE>.
     *
     * If the given array of actions is <CODE>null</CODE>, no actions
     * are added to the panel.
     *
     * @param actions the actions to be added to the panel
     * @see #ActionsPanel()
     * @see #ActionsPanel(LayoutManager)
     * @see #ActionsPanel(Action[], LayoutManager)
     * @see #ActionsPanel(Action[], Object[], LayoutManager)
     */
    public ActionsPanel(Action[] actions) {
        this(actions, null, null);
    }
    
    /**
     * Constructs an actions panel that contains the given actions
     * and uses the given layout manager.
     *
     * If the given array of actions is <CODE>null</CODE>, no actions
     * are added to the panel.
     *
     * If the given layout manager is <CODE>null</CODE>, the
     * default layout manager for a <CODE>JPanel</CODE> is used.
     *
     * @param actions the actions to be added to the panel
     * @param layout the desired layout manager for the panel
     * @see #ActionsPanel()
     * @see #ActionsPanel(LayoutManager)
     * @see #ActionsPanel(Action[])
     * @see #ActionsPanel(Action[], Object[], LayoutManager)
     */
    public ActionsPanel(Action[] actions, LayoutManager layout) {
        this(actions, null, layout);
    }
        
    /**
     * Constructs an actions panel that contains the given actions
     * and uses the given layout manager with the given constraints.
     *
     * If the given array of actions is <CODE>null</CODE>, no actions
     * are added to the panel.
     *
     * If the given layout manager is <CODE>null</CODE>, the
     * default layout manager for a <CODE>JPanel</CODE> is used.
     *
     * @param actions the actions to be added to the panel
     * @param constraints the layout constraints for the button
     * @param layout the desired layout manager for the panel
     * @see #ActionsPanel()
     * @see #ActionsPanel(LayoutManager)
     * @see #ActionsPanel(Action[])
     * @see #ActionsPanel(Action[], LayoutManager)
     * @since 1.2
     */
    public ActionsPanel(
        Action[] actions, 
        Object[] constraints, 
        LayoutManager layout) 
    {
        if (layout != null)
            setLayout(layout);
        
        addActions(actions, constraints);
    }
        
    ////////////////
    // Public API //
    ////////////////

    /**
     * Adds the given action to the <CODE>ActionsPanel</CODE> 
     * by creating a <CODE>JButton</CODE> that, when clicked, 
     * will cause the performance of the given action.
     *
     * @param action the action to be added to the panel
     * @return the created button that causes the action to be
     *      performed
     * @see #addAction(Action, Object)
     * @see #addActions(Action[])
     * @see #addActions(Action[], Object[])
     */
    public JButton addAction(Action action) {
        return addAction(action, null);
    }

    /**
     * Adds the given action to the <CODE>ActionsPanel</CODE>
     * with the given layout constraint.
     *
     * @param action the action to be added to the panel
     * @param constraint the layout constraint for the button
     * @return the created button that causes the action to be
     *      performed
     * @see #addAction(Action)
     * @see #addActions(Action[])
     * @see #addActions(Action[], Object[])
     */
    public JButton addAction(Action action, Object constraint) {
        // null check
        if (action == null)
            return null;
            
        // create a button for the action
        JButton button = new JButton(action); 
        
        // map the action to the button and vice-versa
        map.put(action, button);
        map.put(button, action);

        // add the button to the panel with the layout constraint
        add(button, constraint);
        
        // return the created button, for possible configuration
        return button;
    }
    
    /**
     * Adds the given array of actions to this <CODE>ActionsPanel</CODE>
     * by creating <CODE>JButton</CODE>s that, when clicked, will cause
     * the performance of the corresponding action.
     *
     * @param actions the actions to be added to the panel
     * @return the created buttons that causes the actions to be
     *      performed
     * @see #addAction(Action)
     * @see #addAction(Action, Object)
     * @see #addActions(Action[], Object[])
     * @since 1.2
     */
    public JButton[] addActions(Action[] actions) {
        return addActions(actions, null);
    }
    
    /**
     * Adds the given actions to this <CODE>ActionsPanel</CODE>
     * with the given layout constraints.
     *
     * @param actions the action to be added to the panel
     * @param constraints the layout constraints for the button
     * @return the created buttons that causes the actions to be
     *      performed
     * @see #addAction(Action)
     * @see #addAction(Action, Object)
     * @see #addActions(Action[])
     * @since 1.2
     */
    public JButton[] addActions(Action[] actions, Object[] constraints) {
        if (actions == null)
            return null;
        
        int actionslength = actions.length;
        int constraintslength = 
            (constraints != null) ? constraints.length : -1;
        
        JButton[] buttons = new JButton[actionslength];
        
        for (int i = 0; i < actionslength; i++) {
            if (actions[i] != null) {
                if (i < constraintslength)
                    buttons[i] = addAction(actions[i], constraints[i]);
                else
                    buttons[i] = addAction(actions[i], null);
            }
        }
        
        return buttons;
    }
    
    /**
     * Returns the action in this <CODE>ActionsPanel</CODE>
     * corresponding with the given button,
     * or <CODE>null</CODE> if the given button
     * is <CODE>null</CODE> or not in this panel.
     *
     * @param button the button whose action is desired
     * @return the action corresponding to the button
     * @see #getButton(Action)
     * @see #getActions()
     * @see #getActionButtons()
     * @since 1.2
     */
    public Action getAction(JButton button) {
        if (button == null)
            return null;
        
        return (Action) map.get(button);
    }
    
    /**
     * Returns the button in this <CODE>ActionsPanel</CODE>
     * corresponding with the given action,
     * or <CODE>null</CODE> if the given action
     * is <CODE>null</CODE> or not in this panel.
     *
     * @param action the action whose button is desired
     * @return the button corresponding to the action
     * @see #getAction(JButton)
     * @see #getActions()
     * @see #getActionButtons()
     * @since 1.2
     */
    public JButton getButton(Action action) {
        if (action == null)
            return null;
        
        return (JButton) map.get(action);
    }
    
    /**
     * Returns an array containing the actions in this
     * <CODE>ActionsPanel</CODE>.
     *
     * @see #getAction(JButton)
     * @see #getButton(Action)
     * @see #getActionButtons()
     * @since 1.1
     */
    public Action[] getActions() {
        JButton[] buttons = getActionButtons();
        
        if (buttons == null)
            return null;
        
        int buttonslength = buttons.length;
        
        Action[]  actions = new Action[buttonslength];
        
        for (int i = 0; i < buttonslength; i++)
            actions[i] = (Action) map.get(buttons[i]);
        
        return actions;
    }

    /**
     * Returns an array containing the buttons in this
     * <CODE>ActionsPanel</CODE> created as a result of
     * <CODE>addAction</CODE> or <CODE>addActions</CODE>.
     *
     * @see #getAction(JButton)
     * @see #getButton(Action)
     * @see #getActions()
     * @since 1.1
     */
    public JButton[] getActionButtons() {
        Vector buttons = new Vector();
        
        Component[] child = getComponents();

        // for each of the components in the panel
        for (int i = 0; i < child.length; i++)
            if (child[i] instanceof JButton)
                if (map.containsKey(child[i]))
                    buttons.add(child[i]);
        
        // return the array of buttons
        return (JButton[])buttons.toArray(new JButton[0]);
    }
    
    /**
     * Returns the base action in this <CODE>ActionsPanel</CODE>
     * that represents the given action, or <CODE>null</CODE>
     * if there is no such base action.
     *
     * If the given action is itself in this panel,
     * the given action is returned.
     *
     * @param action the action that we seek to match in the panel
     * @see #findMatchedButton(Action)
     * @see ActionWrapper#getBaseAction(Action)
     * @since 2.0
     */
    public Action findMatchedAction(Action action) {
        if (action == null)
            return null;
            
        // first check if the action itself is in the panel
        if (map.containsKey(action))
            return action;
        
        // do a thorough search
        Action base = ActionWrapper.getBaseAction(action);
        
        Action[] actions = getActions();
        
        for (int i = 0; i < actions.length; i++)
            if (base == ActionWrapper.getBaseAction(actions[i]))
                return actions[i];
        
        // if not successful then return null
        return null;
    }
    
    /**
     * Returns the button in this <CODE>ActionsPanel</CODE>
     * associated with the base action for the given action, 
     * or <CODE>null</CODE> if there is no such button.
     *
     * @param action the action that we seek to match in the panel
     * @see #findMatchedAction(Action)
     * @see ActionWrapper#getBaseAction(Action)
     * @since 2.0
     */
    public JButton findMatchedButton(Action action) {
        return getButton(findMatchedAction(action));
    }
    
    /**
     * Returns the first action in this <CODE>ActionsPanel</CODE>
     * whose <CODE>NAME</CODE> property is equal to the given name.
     *
     * @param name the name that we seek to match in the panel
     * @see #findMatchedButton(String)
     * @since 2.0
     */
    public Action findMatchedAction(String name) {
        if (name == null)
            return null;
        
        // do a thorough search
        Action[] actions = getActions();
        
        for (int i = 0; i < actions.length; i++)
            if (name.equals((String) actions[i].getValue(Action.NAME)))
                return actions[i];
        
        // if not successful then return null
        return null;
    }
    
    /**
     * Returns the button in this <CODE>ActionsPanel</CODE>
     * associated with the first action found in this panel
     * whose <CODE>NAME</CODE> property is equal to the given name.
     *
     * @param name the name that we seek to match in the panel
     * @see #findMatchedAction(String)
     * @since 2.0
     */
    public JButton findMatchedButton(String name) {
        return getButton(findMatchedAction(name));
    }
    
    /**
     * Sets the given button to be the default button
     * for the root pane containing this <CODE>ActionsPanel</CODE>
     * if this panel has a root pane parent,
     * and the given button is in this panel.
     *
     * @param button the button to install as the default button
     * @see #setDefaultButton(Action)
     * @see #setDefaultButton(String)
     * @since 2.0
     */
    public void setDefaultButton(JButton button) {
        JRootPane pane = getRootPane();
        
        if (pane != null)
            pane.setDefaultButton(button);
    }
    
    /**
     * Sets the button associated with the given action
     * to be the default button for the root pane 
     * containing this <CODE>ActionsPanel</CODE>
     * if this panel has a root pane parent,
     * and the given button is in this panel.
     *
     * @param action the action whose matched button 
     *      will be the default button
     * @see #setDefaultButton(JButton)
     * @see #setDefaultButton(String)
     * @since 2.0
     */
    public void setDefaultButton(Action action) {
        setDefaultButton(findMatchedButton(action));
    }
         
    /**
     * Sets the button associated with the first action
     * found in this <CODE>ActionsPanel</CODE>
     * whose <CODE>NAME</CODE> property is equal to the given name
     * to be the default button for the root pane 
     * containing this <CODE>ActionsPanel</CODE>
     * if this panel has a root pane parent,
     * and the given button is in this panel.
     *
     * @param name the name whose matched button 
     *      will be the default button
     * @see #setDefaultButton(JButton)
     * @see #setDefaultButton(Action)
     */
    public void setDefaultButton(String name) {
        setDefaultButton(findMatchedButton(name));
    }
         
    ////////////////////////////////////////////////
    // Static Helper Functions for Action Objects //
    ////////////////////////////////////////////////
    
    /**
     * Sets the text (<CODE>NAME</CODE>)
     * associated with the given action
     * to the given text.
     *
     * @param action the action to modify
     * @param text the text to set
     * @see #getText(Action)
     * @see #setIcon(Action, Icon)
     * @see #setToolTipText(Action, String)
     * @see #getIcon(Action)
     * @see #getToolTipText(Action)
     * @see Action#NAME
     * @since 1.2
     */
    public static void setText(Action action, String text) {
        if (action == null)
            return;
        
        action.putValue(Action.NAME, text);
    }
    
    /**
     * Sets the icon (<CODE>SMALL_ICON</CODE>)
     * associated with the given action
     * to the given icon.
     *
     * @param action the action to modify
     * @param icon the icon to set
     * @see #getIcon(Action)
     * @see #setText(Action, String)
     * @see #setToolTipText(Action, String)
     * @see #getText(Action)
     * @see #getToolTipText(Action)
     * @see Action#SMALL_ICON
     * @since 1.2
     */
    public static void setIcon(Action action, Icon icon) {
        if (action == null)
            return;
        
        action.putValue(Action.SMALL_ICON, icon);
    }
    
    /**
     * Sets the tool tip text (<CODE>SHORT_DESCRIPTION</CODE>)
     * associated with the given action
     * to the given text.
     *
     * @param action the action to modify
     * @param text the tool tip text to set
     * @see #getToolTipText(Action)
     * @see #setText(Action, String)
     * @see #setIcon(Action, Icon)
     * @see #getText(Action)
     * @see #getIcon(Action)
     * @see Action#SHORT_DESCRIPTION
     * @since 1.2
     */
    public static void setToolTipText(Action action, String text) {
        if (action == null)
            return;
        
        action.putValue(Action.SHORT_DESCRIPTION, text);
    }

    /**
     * Returns the text (<CODE>NAME</CODE>)
     * associated with the given action.
     *
     * If the given action is <CODE>null</CODE>,
     * the <CODE>NAME</CODE> property is not set,
     * or it is not set to a <CODE>String</CODE>,
     * this method returns <CODE>null</CODE>.
     *
     * @param action the action to query
     * @see #setText(Action, String)
     * @see #getIcon(Action)
     * @see #getToolTipText(Action)
     * @see #setIcon(Action, Icon)
     * @see #setToolTipText(Action, String)
     * @see Action#NAME
     * @since 2.0
     */
    public static String getText(Action action) {
        if (action == null)
            return null;
        
        Object o = action.getValue(Action.NAME);
        
        if (o instanceof String)
            return (String)o;
        
        return null;
    }
    
    /**
     * Returns the icon (<CODE>SMALL_ICON</CODE>)
     * associated with the given action.
     *
     * If the given action is <CODE>null</CODE>,
     * the <CODE>SMALL_ICON</CODE> property is not set,
     * or it is not set to an <CODE>Icon</CODE>,
     * this method returns <CODE>null</CODE>.
     *
     * @param action the action to query
     * @see #setIcon(Action, Icon)
     * @see #getText(Action)
     * @see #getToolTipText(Action)
     * @see #setText(Action, String)
     * @see #setToolTipText(Action, String)
     * @see Action#SMALL_ICON
     * @since 2.0
     */
    public static Icon getIcon(Action action) {
        if (action == null)
            return null;
        
        Object o = action.getValue(Action.SMALL_ICON);
        
        if (o instanceof Icon)
            return (Icon)o;
        
        return null;
    }
    
    /**
     * Returns the tool tip text (<CODE>SHORT_DESCRIPTION</CODE>)
     * associated with the given action.
     *
     * If the given action is <CODE>null</CODE>,
     * the <CODE>SHORT_DESCRIPTION</CODE> property is not set,
     * or it is not set to a <CODE>String</CODE>,
     * this method returns <CODE>null</CODE>.
     *
     * @param action the action to query
     * @see #setToolTipText(Action, String)
     * @see #getText(Action)
     * @see #getIcon(Action)
     * @see #setText(Action, String)
     * @see #setIcon(Action, Icon)
     * @see Action#SHORT_DESCRIPTION
     * @since 2.0
     */
    public static String getToolTipText(Action action) {
        if (action == null)
            return null;
        
        Object o = action.getValue(Action.SHORT_DESCRIPTION);
        
        if (o instanceof String)
            return (String)o;
        
        return null;
    }
}
