/*
 * Copyright (c) 2005-2010 Flamingo Kirill Grouchnikov. All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 * 
 *  o Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer. 
 *     
 *  o Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution. 
 *     
 *  o Neither the name of Flamingo Kirill Grouchnikov nor the names of 
 *    its contributors may be used to endorse or promote products derived 
 *    from this software without specific prior written permission. 
 *     
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */
package org.pushingpixels.flamingo.api.common;

import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import javax.swing.event.*;

import org.pushingpixels.flamingo.api.common.icon.ResizableIcon;
import org.pushingpixels.flamingo.api.common.model.ActionRepeatableButtonModel;
import org.pushingpixels.flamingo.api.common.model.PopupButtonModel;
import org.pushingpixels.flamingo.api.common.popup.PopupPanelCallback;
import org.pushingpixels.flamingo.internal.ui.common.BasicCommandButtonUI;
import org.pushingpixels.flamingo.internal.ui.common.CommandButtonUI;

/**
 * Command button.
 * 
 * @author Kirill Grouchnikov
 */
public class JCommandButton extends AbstractCommandButton {
	/**
	 * The UI class ID string.
	 */
	public static final String uiClassID = "CommandButtonUI";

	/**
	 * Associated popup callback. May be <code>null</code>.
	 * 
	 * @see #setPopupCallback(PopupPanelCallback)
	 * @see #getPopupCallback()
	 */
	protected PopupPanelCallback popupCallback;

	/**
	 * The command button kind of this button.
	 * 
	 * @see #setCommandButtonKind(CommandButtonKind)
	 * @see #getCommandButtonKind()
	 */
	protected CommandButtonKind commandButtonKind;

	/**
	 * The popup orientation kind of this button.
	 * 
	 * @see #setPopupOrientationKind(CommandButtonPopupOrientationKind)
	 * @see #getPopupOrientationKind()
	 */
	protected CommandButtonPopupOrientationKind popupOrientationKind;

	/**
	 * Indicates the auto-repeat action mode. When the button is not in the
	 * auto-repeat action mode, the registered action listeners are activated
	 * when the mouse is released (just as with the base {@link AbstractButton}
	 * ). When the button is in auto-repeat mode, the registered action
	 * listeners are activated when the mouse is pressed. In addition, if the
	 * mouse is still pressed after {@link #getAutoRepeatInitialInterval()}, the
	 * action listeners will be activated every
	 * {@link #getAutoRepeatSubsequentInterval()} until the button is disabled
	 * or the mouse is released.
	 * 
	 * @see #autoRepeatInitialInterval
	 * @see #autoRepeatSubsequentInterval
	 * @see #setAutoRepeatAction(boolean)
	 * @see #isAutoRepeatAction()
	 */
	protected boolean isAutoRepeatAction;

	/**
	 * The initial interval for invoking the registered action listeners in the
	 * auto-repeat action mode.
	 * 
	 * @see #isAutoRepeatAction
	 * @see #autoRepeatSubsequentInterval
	 * @see #getAutoRepeatInitialInterval()
	 * @see #setAutoRepeatActionIntervals(int, int)
	 */
	protected int autoRepeatInitialInterval;

	/**
	 * The subsequent interval for invoking the registered action listeners in
	 * the auto-repeat action mode.
	 * 
	 * @see #isAutoRepeatAction
	 * @see #autoRepeatInitialInterval
	 * @see #getAutoRepeatSubsequentInterval()
	 * @see #setAutoRepeatActionIntervals(int, int)
	 */
	protected int autoRepeatSubsequentInterval;

	/**
	 * Indicates that rollover should result in firing the action. Used in
	 * conjunction with the {@link #isAutoRepeatAction} can model quick pan
	 * buttons such as breadcrumb bar scrollers.
	 * 
	 * @see #setFireActionOnRollover(boolean)
	 * @see #isFireActionOnRollover()
	 */
	protected boolean isFireActionOnRollover;

	/**
	 * Popup model of this button.
	 * 
	 * @see #setPopupModel(PopupButtonModel)
	 * @see #getPopupModel()
	 */
	protected PopupButtonModel popupModel;

	/**
	 * Default popup handler for this button.
	 */
	protected PopupHandler popupHandler;

	/**
	 * Rich tooltip for the popup area of this button.
	 * 
	 * @see #setPopupRichTooltip(RichTooltip)
	 * @see #getRichTooltip(MouseEvent)
	 */
	private RichTooltip popupRichTooltip;

	/**
	 * Key tip for the popup area of this button.
	 * 
	 * @see #setPopupKeyTip(String)
	 * @see #getPopupKeyTip()
	 */
	protected String popupKeyTip;

	/**
	 * Enumerates the available command button kinds.
	 * 
	 * @author Kirill Grouchnikov
	 */
	public static enum CommandButtonKind {
		/**
		 * Command button that has only action area.
		 */
		ACTION_ONLY(true, false),

		/**
		 * Command button that has only popup area.
		 */
		POPUP_ONLY(false, true),

		/**
		 * Command button that has both action and popup areas, with the main
		 * text click activating the action.
		 */
		ACTION_AND_POPUP_MAIN_ACTION(true, true),

		/**
		 * Command button that has both action and popup areas, with the main
		 * text click activating the popup.
		 */
		ACTION_AND_POPUP_MAIN_POPUP(true, true);

		/**
		 * <code>true</code> if the command button kind has an action.
		 */
		private boolean hasAction;

		/**
		 * <code>true</code> if the command button kind has a popup.
		 */
		private boolean hasPopup;

		/**
		 * Constructs a new command button kind.
		 * 
		 * @param hasAction
		 *            Indicates whether the command button kind has an action.
		 * @param hasPopup
		 *            Indicates whether the command button kind has a popup.
		 */
		private CommandButtonKind(boolean hasAction, boolean hasPopup) {
			this.hasAction = hasAction;
			this.hasPopup = hasPopup;
		}

		/**
		 * Returns indication whether this command button kind has an action.
		 * 
		 * @return <code>true</code> if the command button kind has an action,
		 *         <code>false</code> otherwise.
		 */
		public boolean hasAction() {
			return hasAction;
		}

		/**
		 * Returns indication whether this command button kind has a popup.
		 * 
		 * @return <code>true</code> if the command button kind has a popup,
		 *         <code>false</code> otherwise.
		 */
		public boolean hasPopup() {
			return hasPopup;
		}
	}

	/**
	 * Orientation kind for the popup.
	 * 
	 * @author Kirill Grouchnikov
	 */
	public static enum CommandButtonPopupOrientationKind {
		/**
		 * Indicates that the popup should be displayed below the button.
		 */
		DOWNWARD,

		/**
		 * Indicates that the popup should be displayed to the side of the
		 * button.
		 */
		SIDEWARD
	}

	/**
	 * Extension of the default button model that supports the
	 * {@link PopupButtonModel} interface.
	 * 
	 * @author Kirill Grouchnikov
	 */
	private static class DefaultPopupButtonModel extends DefaultButtonModel
			implements PopupButtonModel {
		/**
		 * Timer for the auto-repeat action mode.
		 */
		protected Timer autoRepeatTimer;

		/**
		 * Identifies the "popup showing" bit in the bitmask, which indicates
		 * that the visibility status of the associated popup.
		 */
		public final static int POPUP_SHOWING = 1 << 8;

		/**
		 * Creates a new default popup button model.
		 */
		public DefaultPopupButtonModel() {
			super();
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.jvnet.flamingo.common.PopupButtonModel#addPopupActionListener
		 * (org.jvnet.flamingo.common.PopupActionListener)
		 */
		@Override
		public void addPopupActionListener(PopupActionListener l) {
			listenerList.add(PopupActionListener.class, l);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.jvnet.flamingo.common.PopupButtonModel#removePopupActionListener
		 * (org.jvnet.flamingo.common.PopupActionListener)
		 */
		@Override
		public void removePopupActionListener(PopupActionListener l) {
			listenerList.remove(PopupActionListener.class, l);
		}

		/**
		 * Notifies all listeners that have registered interest for notification
		 * on this event type.
		 * 
		 * @param e
		 *            the <code>ActionEvent</code> to deliver to listeners
		 * @see EventListenerList
		 */
		protected void firePopupActionPerformed(ActionEvent e) {
			// Guaranteed to return a non-null array
			Object[] listeners = listenerList.getListenerList();
			// Process the listeners last to first, notifying
			// those that are interested in this event
			for (int i = listeners.length - 2; i >= 0; i -= 2) {
				if (listeners[i] == PopupActionListener.class) {
					((PopupActionListener) listeners[i + 1]).actionPerformed(e);
				}
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.swing.DefaultButtonModel#setPressed(boolean)
		 */
		@Override
		public void setPressed(boolean b) {
			if ((isPressed() == b) || !isEnabled()) {
				return;
			}

			if (b) {
				stateMask |= PRESSED;
			} else {
				stateMask &= ~PRESSED;
			}

			if (isPressed() && isArmed()) {
				// fire the popup action on button press and not on button
				// release - like the comboboxes
				int modifiers = 0;
				AWTEvent currentEvent = EventQueue.getCurrentEvent();
				if (currentEvent instanceof InputEvent) {
					modifiers = ((InputEvent) currentEvent).getModifiers();
				} else if (currentEvent instanceof ActionEvent) {
					modifiers = ((ActionEvent) currentEvent).getModifiers();
				}
				firePopupActionPerformed(new ActionEvent(this,
						ActionEvent.ACTION_PERFORMED, getActionCommand(),
						EventQueue.getMostRecentEventTime(), modifiers));
			}

			fireStateChanged();
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.jvnet.flamingo.common.PopupButtonModel#isPopupShowing()
		 */
		@Override
		public boolean isPopupShowing() {
			return (stateMask & POPUP_SHOWING) != 0;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see
		 * org.jvnet.flamingo.common.PopupButtonModel#setPopupShowing(boolean)
		 */
		@Override
		public void setPopupShowing(boolean b) {
			// System.out.println(this.isPopupShowing() + "-->" + b);
			if (this.isPopupShowing() == b) {
				return;
			}

			if (b) {
				stateMask |= POPUP_SHOWING;
			} else {
				stateMask &= ~POPUP_SHOWING;
			}

			fireStateChanged();
		}
	}

	/**
	 * Creates a new command button with empty text
	 * 
	 * @param icon
	 *            Button icon.
	 */
	public JCommandButton(ResizableIcon icon) {
		this(null, icon);
	}

	/**
	 * Creates a new command button without an icon.
	 * 
	 * @param title
	 *            Button title. May contain any number of words.
	 */
	public JCommandButton(String title) {
		this(title, null);
	}

	/**
	 * Creates a new command button.
	 * 
	 * @param title
	 *            Button title. May contain any number of words.
	 * @param icon
	 *            Button icon.
	 */
	public JCommandButton(String title, ResizableIcon icon) {
		super(title, icon);

		this.setActionModel(new ActionRepeatableButtonModel(this));

		// important - handler creation must be done before setting
		// the popup model so that it can be registered to track the
		// changes
		this.popupHandler = new PopupHandler();
		this.setPopupModel(new DefaultPopupButtonModel());

		this.commandButtonKind = CommandButtonKind.ACTION_ONLY;
		this.popupOrientationKind = CommandButtonPopupOrientationKind.DOWNWARD;
		// this.displayState = CommandButtonDisplayState.CUSTOM;
		this.isAutoRepeatAction = false;
		this.autoRepeatInitialInterval = 500;
		this.autoRepeatSubsequentInterval = 100;

		this.updateUI();
	}

	/**
	 * Returns the command button kind of this button.
	 * 
	 * @return Command button kind of this button.
	 * @see #setCommandButtonKind(CommandButtonKind)
	 */
	public CommandButtonKind getCommandButtonKind() {
		return this.commandButtonKind;
	}

	/**
	 * Sets the kind for this button. Fires a <code>commandButtonKind</code>
	 * property change event.
	 * 
	 * @param commandButtonKind
	 *            The new button kind.
	 * @see #getCommandButtonKind()
	 */
	public void setCommandButtonKind(CommandButtonKind commandButtonKind) {
		CommandButtonKind old = this.commandButtonKind;
		this.commandButtonKind = commandButtonKind;
		if (old != this.commandButtonKind) {
			firePropertyChange("commandButtonKind", old, this.commandButtonKind);
		}
	}

	/**
	 * Returns the popup orientation kind of this button.
	 * 
	 * @return Popup orientation kind of this button.
	 * @see #setPopupOrientationKind(CommandButtonPopupOrientationKind)
	 */
	public CommandButtonPopupOrientationKind getPopupOrientationKind() {
		return this.popupOrientationKind;
	}

	/**
	 * Sets the popup orientation for this button. Fires a
	 * <code>popupOrientationKind</code> property change event.
	 * 
	 * @param popupOrientationKind
	 *            The new popup orientation kind.
	 * @see #getPopupOrientationKind()
	 */
	public void setPopupOrientationKind(
			CommandButtonPopupOrientationKind popupOrientationKind) {
		CommandButtonPopupOrientationKind old = this.popupOrientationKind;
		this.popupOrientationKind = popupOrientationKind;
		if (old != this.popupOrientationKind) {
			firePropertyChange("popupOrientationKind", old,
					this.popupOrientationKind);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.JComponent#updateUI()
	 */
	@Override
	public void updateUI() {
		if (UIManager.get(getUIClassID()) != null) {
			setUI((CommandButtonUI) UIManager.getUI(this));
		} else {
			setUI(BasicCommandButtonUI.createUI(this));
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.JComponent#getUIClassID()
	 */
	@Override
	public String getUIClassID() {
		return uiClassID;
	}

	/**
	 * Returns the associated popup callback.
	 * 
	 * @return The associated popup callback.
	 * @see #setPopupCallback(PopupPanelCallback)
	 */
	public PopupPanelCallback getPopupCallback() {
		return this.popupCallback;
	}

	/**
	 * Sets new popup callback for <code>this</code> button.
	 * 
	 * @param popupCallback
	 *            New popup callback for <code>this</code> button.
	 * @see #getPopupCallback()
	 */
	public void setPopupCallback(PopupPanelCallback popupCallback) {
		this.popupCallback = popupCallback;
	}

	/**
	 * Sets the auto-repeat action indication.
	 * 
	 * @param isAutoRepeatAction
	 *            If <code>true</code>, pressing the button will activate
	 *            auto-repeat action mode. When the button is not in the
	 *            auto-repeat action mode, the registered action listeners are
	 *            activated when the mouse is released (just as with the base
	 *            {@link AbstractButton}). When the button is in auto-repeat
	 *            mode, the registered action listeners are activated when the
	 *            mouse is pressed. In addition, is the mouse is still pressed
	 *            after {@link #getAutoRepeatInitialInterval()}, the action
	 *            listeners will be activated every
	 *            {@link #getAutoRepeatSubsequentInterval()} until the button is
	 *            disabled or the mouse is released.
	 * @see #setAutoRepeatActionIntervals(int, int)
	 * @see #isAutoRepeatAction()
	 */
	public void setAutoRepeatAction(boolean isAutoRepeatAction) {
		this.isAutoRepeatAction = isAutoRepeatAction;
	}

	/**
	 * Sets the intervals for the auto-repeat action mode.
	 * 
	 * @param initial
	 *            The initial interval for invoking the registered action
	 *            listeners in the auto-repeat action mode.
	 * @param subsequent
	 *            The subsequent interval for invoking the registered action
	 *            listeners in the auto-repeat action mode.
	 * @see #setAutoRepeatAction(boolean)
	 * @see #isAutoRepeatAction()
	 * @see #getAutoRepeatInitialInterval()
	 * @see #getAutoRepeatSubsequentInterval()
	 */
	public void setAutoRepeatActionIntervals(int initial, int subsequent) {
		this.autoRepeatInitialInterval = initial;
		this.autoRepeatSubsequentInterval = subsequent;
	}

	/**
	 * Returns indication whether the button is in auto-repeat action mode.
	 * 
	 * @return <code>true</code> if the button is in auto-repeat action mode,
	 *         <code>false</code> otherwise.
	 * @see #setAutoRepeatAction(boolean)
	 * @see #setAutoRepeatActionIntervals(int, int)
	 * @see #getAutoRepeatInitialInterval()
	 * @see #getAutoRepeatSubsequentInterval()
	 */
	public boolean isAutoRepeatAction() {
		return this.isAutoRepeatAction;
	}

	/**
	 * Returns the initial interval for invoking the registered action listeners
	 * in the auto-repeat action mode.
	 * 
	 * @return The initial interval for invoking the registered action listeners
	 *         in the auto-repeat action mode.
	 * @see #setAutoRepeatActionIntervals(int, int)
	 * @see #setAutoRepeatAction(boolean)
	 * @see #isAutoRepeatAction()
	 * @see #getAutoRepeatSubsequentInterval()
	 */
	public int getAutoRepeatInitialInterval() {
		return autoRepeatInitialInterval;
	}

	/**
	 * Returns the subsequent interval for invoking the registered action
	 * listeners in the auto-repeat action mode.
	 * 
	 * @return The subsequent interval for invoking the registered action
	 *         listeners in the auto-repeat action mode.
	 * @see #setAutoRepeatActionIntervals(int, int)
	 * @see #setAutoRepeatAction(boolean)
	 * @see #isAutoRepeatAction()
	 * @see #getAutoRepeatInitialInterval()
	 */
	public int getAutoRepeatSubsequentInterval() {
		return autoRepeatSubsequentInterval;
	}

	/**
	 * Sets action-on-rollover mode. When this mode is on, button will fire
	 * action events when it gets rollover (instead of press). Combine with
	 * {@link #setAutoRepeatAction(boolean)} passing <code>true</code> to get
	 * auto-repeat action fired on rollover (useful for quicker manipulation of
	 * scroller buttons, for example).
	 * 
	 * @param isFireActionOnRollover
	 *            If <code>true</code>, the button is moved into the
	 *            action-on-rollover mode.
	 * @see #isFireActionOnRollover()
	 */
	public void setFireActionOnRollover(boolean isFireActionOnRollover) {
		this.isFireActionOnRollover = isFireActionOnRollover;
	}

	/**
	 * Returns indication whether this button is in action-on-rollover mode.
	 * 
	 * @return <code>true</code> if this button is in action-on-rollover mode,
	 *         <code>false</code> otherwise.
	 * @see #setFireActionOnRollover(boolean)
	 */
	public boolean isFireActionOnRollover() {
		return this.isFireActionOnRollover;
	}

	/**
	 * Returns the popup model of this button.
	 * 
	 * @return The popup model of this button.
	 * @see #setPopupModel(PopupButtonModel)
	 */
	public PopupButtonModel getPopupModel() {
		return this.popupModel;
	}

	/**
	 * Sets the new popup model for this button. Fires a <code>popupModel</code>
	 * property change event.
	 * 
	 * @param newModel
	 *            The new popup model for this button.
	 * @see #getPopupModel()
	 */
	public void setPopupModel(PopupButtonModel newModel) {

		PopupButtonModel oldModel = getPopupModel();

		if (oldModel != null) {
			oldModel.removeChangeListener(this.popupHandler);
			oldModel.removeActionListener(this.popupHandler);
		}

		this.popupModel = newModel;

		if (newModel != null) {
			newModel.addChangeListener(this.popupHandler);
			newModel.addActionListener(this.popupHandler);
		}

		firePropertyChange("popupModel", oldModel, newModel);
		if (newModel != oldModel) {
			revalidate();
			repaint();
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.jvnet.flamingo.common.AbstractCommandButton#setEnabled(boolean)
	 */
	@Override
	public void setEnabled(boolean b) {
		if (!b && popupModel.isRollover()) {
			popupModel.setRollover(false);
		}
		super.setEnabled(b);
		popupModel.setEnabled(b);
	}

	/**
	 * Default popup handler.
	 * 
	 * @author Kirill Grouchnikov
	 */
	class PopupHandler implements PopupActionListener, ChangeListener {
		public void stateChanged(ChangeEvent e) {
			fireStateChanged();
			repaint();
		}

		public void actionPerformed(ActionEvent event) {
			firePopupActionPerformed(event);
		}
	}

	/**
	 * Notifies all listeners that have registered interest for notification on
	 * this event type. The event instance is lazily created using the
	 * <code>event</code> parameter.
	 * 
	 * @param event
	 *            the <code>ActionEvent</code> object
	 * @see EventListenerList
	 */
	protected void firePopupActionPerformed(ActionEvent event) {
		// Guaranteed to return a non-null array
		Object[] listeners = listenerList.getListenerList();
		ActionEvent e = null;
		// Process the listeners last to first, notifying
		// those that are interested in this event
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == PopupActionListener.class) {
				// Lazily create the event:
				if (e == null) {
					String actionCommand = event.getActionCommand();
					e = new ActionEvent(this, ActionEvent.ACTION_PERFORMED,
							actionCommand, event.getWhen(), event
									.getModifiers());
				}
				((PopupActionListener) listeners[i + 1]).actionPerformed(e);
			}
		}
	}

	@Override
	boolean hasRichTooltips() {
		return super.hasRichTooltips() || (this.popupRichTooltip != null);
	}

	/**
	 * Sets the rich tooltip for the popup area of this button.
	 * 
	 * @param richTooltip
	 *            Rich tooltip for the popup area of this button.
	 * @see #getRichTooltip(MouseEvent)
	 * @see #setActionRichTooltip(RichTooltip)
	 */
	public void setPopupRichTooltip(RichTooltip richTooltip) {
		this.popupRichTooltip = richTooltip;
		RichToolTipManager richToolTipManager = RichToolTipManager
				.sharedInstance();
		if (this.hasRichTooltips()) {
			richToolTipManager.registerComponent(this);
		} else {
			richToolTipManager.unregisterComponent(this);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.jvnet.flamingo.common.AbstractCommandButton#getRichTooltip(java.awt
	 * .event.MouseEvent)
	 */
	@Override
	public RichTooltip getRichTooltip(MouseEvent event) {
		CommandButtonUI ui = this.getUI();
		if (ui.getLayoutInfo().actionClickArea.contains(event.getPoint()))
			return super.getRichTooltip(event);
		if (ui.getLayoutInfo().popupClickArea.contains(event.getPoint()))
			return this.popupRichTooltip;
		return null;
	}

	/**
	 * Returns the key tip for the popup area of this button.
	 * 
	 * @return The key tip for the popup area of this button.
	 * @see #setPopupKeyTip(String)
	 * @see #getActionKeyTip()
	 */
	public String getPopupKeyTip() {
		return this.popupKeyTip;
	}

	/**
	 * Sets the key tip for the popup area of this button. Fires a
	 * <code>popupKeyTip</code> property change event.
	 * 
	 * @param popupKeyTip
	 *            The key tip for the popup area of this button.
	 * @see #getPopupKeyTip()
	 * @see #setActionKeyTip(String)
	 */
	public void setPopupKeyTip(String popupKeyTip) {
		if (!canHaveBothKeyTips() && (popupKeyTip != null)
				&& (this.actionKeyTip != null)) {
			throw new IllegalArgumentException(
					"Action *and* popup keytips are not supported at the same time");
		}

		String old = this.popupKeyTip;
		this.popupKeyTip = popupKeyTip;
		this.firePropertyChange("popupKeyTip", old, this.popupKeyTip);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.jvnet.flamingo.common.AbstractCommandButton#setActionKeyTip(java.
	 * lang.String)
	 */
	@Override
	public void setActionKeyTip(String actionKeyTip) {
		if (!canHaveBothKeyTips() && (popupKeyTip != null)
				&& (this.actionKeyTip != null)) {
			throw new IllegalArgumentException(
					"Action *and* popup keytips are not supported at the same time");
		}

		super.setActionKeyTip(actionKeyTip);
	}

	/**
	 * Returns indication whether key tips can be installed on both action and
	 * popup areas of this button. This method is for internal use only.
	 * 
	 * @return <code>true</code> if key tips can be installed on both action and
	 *         popup areas of this button, <code>false</code> otherwise.
	 */
	boolean canHaveBothKeyTips() {
		return false;
	}

	/**
	 * Programmatically perform a "click" on the popup area. This does the same
	 * thing as if the user had pressed and released the popup area of the
	 * button.
	 */
	public void doPopupClick() {
		Dimension size = getSize();
		PopupButtonModel popupModel = this.getPopupModel();
		popupModel.setArmed(true);
		popupModel.setPressed(true);
		paintImmediately(new Rectangle(0, 0, size.width, size.height));
		try {
			Thread.sleep(100);
		} catch (InterruptedException ie) {
		}
		popupModel.setPressed(false);
		popupModel.setArmed(false);
		popupModel.setPopupShowing(true);
		paintImmediately(new Rectangle(0, 0, size.width, size.height));
	}
}
