/*
 * org.openmicroscopy.shoola.util.ui.ButtonMenu
 *
 *------------------------------------------------------------------------------
 *  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;


//Java imports
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.LayoutManager;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.AbstractButton;
import javax.swing.DefaultButtonModel;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JPopupMenu;
import javax.swing.border.Border;
import javax.swing.plaf.basic.BasicArrowButton;

//Third-party libraries

//Application-internal dependencies

/** 
 * A drop-down button.
 * This button is actually composed by two sub-buttons:
 * <ul>
 *  <li>An <i>arrow button</i> to trigger the display of a popup menu.</li>
 *  <li>An <i>icon button</i> to forward mouse clicks on to an item in the
 * 		popup menu.</li>
 * </ul>
 * <p>Methods are provided to add/remove items to/from the popup menu.  When an
 * {@link AbstractButton} is {@link #addToMenu(AbstractButton, boolean) added}
 * to the menu, it's possible to specify whether mouse clicks should be tracked.
 * The <i>icon button</i> remembers which was the last item, among those that
 * have been requested to be tracked, to be clicked and sets its tooltip to be
 * that item's text &#151; that is, to the value returned by the item's 
 * <code>getText</code> method.  Any mouse click on the <i>icon button</i> will
 * then be forwarded to that menu item.</p>
 * <p>Note that the <i>icon button</i> doesn't necessarily track all menu items.
 * So it may happen that an item is clicked but not remembered.  Moreover, if
 * the lastly remembered item happens to be 
 * {@link #removeFromMenu(Component) removed} from the menu, the
 * <i>icon button</i> is reset.  This means the button doesn't point to any 
 * menu item and mouse clicks are just ignored.</p>
 *
 * @author  Jean-Marie Burel &nbsp;&nbsp;&nbsp;&nbsp;
 * 				<a href="mailto:j.burel@dundee.ac.uk">j.burel@dundee.ac.uk</a>
 * @author  <br>Andrea Falconi &nbsp;&nbsp;&nbsp;&nbsp;
 * 				<a href="mailto:a.falconi@dundee.ac.uk">
 * 					a.falconi@dundee.ac.uk</a>
 * @version 2.2
 * <small>
 * (<b>Internal version:</b> $Revision$ $Date$)
 * </small>
 * @since OME2.2
 */
public class ButtonMenu
	extends AbstractButton
	implements ActionListener
{
	
//TODO: provide an ad-hoc ButtonModel implementation.
//We have two buttons but we want external classes to see this as a single
//button.  This is expecially true when we add the button to a JToolBar, 
//which modifies buttons appearence if its rollover property is set to true. 
	
	
	/** Fixed width of the arrow button. */
	private static final int	ARROW_BUTTON_WIDTH = 18;
	

	/** Custom layout manager to display the arrow and icon buttons properly. */
	private class DefaultLayoutManager 
		implements LayoutManager
	{
        /**
         * Creates a new instance.
         * 
         * @param parent The container the layout is for.
         */
		public void layoutContainer(Container parent) 
		{
			Dimension d = iconButton.getPreferredSize();
			iconButton.setBounds(0, 0, d.width, d.height);
			arrowButton.setBounds(d.width, 0, ARROW_BUTTON_WIDTH, d.height);
		}
        
        /**
         * Implemented as specified by the I/F.
         * @see LayoutManager#minimumLayoutSize(Container)
         */
		public Dimension minimumLayoutSize(Container parent)
		{ 
            return preferredLayoutSize(parent);
        }
        
        /**
         * Implemented as specified by the I/F.
         * @see LayoutManager#preferredLayoutSize(Container)
         */
		public Dimension preferredLayoutSize(Container parent)
		{
            return getPreferredSize();
        }
        
        /**
         * Required by the I/F but no-op implementation in our case.
         * @see LayoutManager#removeLayoutComponent(Component)
         */
		public void removeLayoutComponent(Component comp) {}
        
        /**
         * Required by the I/F but no-op implementation in our case.
         * @see LayoutManager#addLayoutComponent(String, Component)
         */
		public void addLayoutComponent(String name, Component comp) {}	
	}
	
	/** The arrow button to trigger the display of the drop-down menu. */
	private JButton			arrowButton;
	
	/** The icon button to trigger a click?? */
	private JButton			iconButton;
	
	/** The drop-down menu. */
	private JPopupMenu		menu;
	
	/** 
	 * Remembers the last tracked item that was clicked in the menu.
	 * Not all items in the menu are necessarily tracked.  In fact, items are
	 * tracked only on request when they're 
	 * {@link #addToMenu(AbstractButton, boolean) added} to the menu.
	 * If the lastly tracked item happens to be 
	 * {@link #removeFromMenu(Component) removed} from the menu, this field is
	 * reset to <code>null</code>. 
	 */
	private AbstractButton	lastClickedItem;
	
	/** Builds and lays out the GUI. */
	private void buildGUI()
	{
		setLayout(new DefaultLayoutManager());
		setBorder(null);
		setMargin(null);
		add(iconButton);
		add(arrowButton);
	}
	
	/** Forwards the click to the {@link #lastClickedItem} if any. */
	private void handleIconButtonClick() 
	{
		if (lastClickedItem != null) lastClickedItem.doClick();
	}
	
	/** Brings up the drop-down menu. */
	private void handleArrowButtonClick()
	{
		//Avoid displaying an empty menu, kinda ugly.
		if (menu.getComponents().length == 0) return;
		
		Dimension d = iconButton.getPreferredSize();
		menu.show(iconButton, 0, d.height);
	}
	
	/** 
     * Sets {@link #lastClickedItem} to <code>src</code>. 
     * 
     * @param src The source of the event. 
     */
	private void handleMenuButtonClick(AbstractButton src) 
	{
		lastClickedItem = src;
		iconButton.setToolTipText(UIUtilities.formatToolTipText(src.getText()));
	}
	
	/**
	 * Overridden to make sure that no component other than the
	 * {@link #iconButton} or {@link #arrowButton} can be added.
     * @see AbstractButton#addImpl(Component, Object, int)
	 */
	protected void addImpl(Component comp, Object constraints, int index)
	{
		if (comp == iconButton || comp == arrowButton)
			super.addImpl(comp, constraints, index);
	}
	//NOTE: we may want to override the remove methods from Container as well.
	
	/**
	 * Creates a new instance.
	 * 
	 * @param icon	An icon for the <i>icon button</i>.  Mustn't be
	 * 				<code>null</code>. 
	 */
	public ButtonMenu(Icon icon) 
	{
		if (icon == null) throw new NullPointerException("No icon.");
		iconButton = new JButton(icon);
		iconButton.addActionListener(this);
		arrowButton = new BasicArrowButton(BasicArrowButton.SOUTH);
		arrowButton.addActionListener(this);
		menu = new JPopupMenu();
		setModel(new DefaultButtonModel());  //TODO: replace with ad-hoc model.
		buildGUI();
	}

	/**
	 * Adds the specified component to the drop-down menu.
	 * 
	 * @param item	The component to add. Normally a <code>JMenuItem</code> or
	 * 				a <code>JSeparator</code>.
	 */
	public void addToMenu(Component item)
	{
		if (item != null) menu.add(item);
	}
	
	/**
	 * Adds the specified button to the drop-down menu.
	 * 
	 * @param btn	The button to add.  Normally a <code>JMenuItem</code>.
	 * @param trackClick Specifies whether mouse clicks should be tracked.
	 */
	public void addToMenu(AbstractButton btn, boolean trackClick)
	{
		if (btn != null) menu.add(btn);
		if (trackClick) btn.addActionListener(this);
	}
	
	/** Appends a new separator at the end of the drop-down menu. */
	public void addSeparator() { menu.addSeparator(); }
	
	/**
	 * Removes the specified component from the drop-down menu.
	 * 
	 * @param item	The component to remove.
	 */
	public void removeFromMenu(Component item) 
	{ 
		if (item == null) return;
		menu.remove(item);
		if (item instanceof AbstractButton) {
			((AbstractButton) item).removeActionListener(this);
		//NOTE: addToMenu(AbstrctButton, boolean) might have registred with
		//this button.  If so, we have to unsubscribe.  If not, unsubscribing
		//will be harmless, but will avoid the pain of tracking the buttons
		//we actually registred with.
		
			if (item == lastClickedItem) {
				lastClickedItem = null;
				iconButton.setToolTipText("");
			} 
		}
	}
	
	/** Removes all components from the drop-down menu. */
	public void clearMenu()
	{
		Component[] items = menu.getComponents();
		for (int i = 0; i < items.length; ++i) removeFromMenu(items[i]);
	}
	
	/**
     * Demultiplexes the event to the right handler. 
     * @see ActionListener#actionPerformed(ActionEvent)
     */
	public void actionPerformed(ActionEvent ae) 
	{	
		Object src = ae.getSource();
		if (src == iconButton) handleIconButtonClick();
		else if (src == arrowButton) handleArrowButtonClick();
		else handleMenuButtonClick((AbstractButton) src);
		//NOTE: we only register with AbstractButtons, so this is safe.
	}
	
	/** 
     * Overridden to return the right size. 
     * @see AbstractButton#getPreferredSize()
     */
	public Dimension getPreferredSize()
	{
		Dimension d = iconButton.getPreferredSize();
		return new Dimension(d.width+ARROW_BUTTON_WIDTH, d.height);
	}
	
	/** 
     * Overridden to return the preferred size. 
     * @see AbstractButton#getMaximumSize()
     */
	public Dimension getMaximumSize() { return getPreferredSize(); }
	
	/** 
     * Overridden to return the preferred size. 
     * @see AbstractButton#getMinimumSize()
     */
	public Dimension getMinimumSize() { return getPreferredSize(); }
	
	/** 
     * Overridden to trigger the display of the drop-down menu. 
     * @see AbstractButton#doClick()
     */
	public void doClick() { arrowButton.doClick(); }
	
	/**
     * Overridden to trigger the display of the drop-down menu. 
     * @see AbstractButton#doClick(int)
     */
	public void doClick(int pressTime) { arrowButton.doClick(pressTime); }
	
	/** 
     * Overridden to set the border of the two sub-buttons. 
     * @see AbstractButton#setBorder(Border)
     */
	public void setBorder(Border b)
	{
		iconButton.setBorder(b);
		arrowButton.setBorder(b);
	}
	
	/**
     * Overridden to set the rollover property of the two sub-buttons. 
     * @see AbstractButton#setRolloverEnabled(boolean)
     */
	public void setRolloverEnabled(boolean enable)
	{
		super.setRolloverEnabled(enable);
		iconButton.setRolloverEnabled(enable);
		arrowButton.setRolloverEnabled(enable);
	}

}
