/*
 * 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.Dimension;
import java.awt.Rectangle;
import java.util.*;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

import org.pushingpixels.flamingo.internal.ui.common.BasicCommandButtonPanelUI;
import org.pushingpixels.flamingo.internal.ui.common.CommandButtonPanelUI;

/**
 * Panel that hosts command buttons. Provides support for button groups, single
 * selection mode (for toggle command buttons), same icon state / dimension and
 * column-fill / row-fill layout.
 * 
 * <p>
 * Under the default {@link LayoutKind#ROW_FILL}, the buttons are laid out in
 * rows, never exceeding the available horizontal space. A vertical scroll bar
 * will kick in once there is not enough vertical space to show all the buttons.
 * The schematic below shows a row-fill command button panel:
 * </p>
 * 
 * <pre>
 * +-----------------------------+-+ 
 * |                             | |
 * | +----+ +----+ +----+ +----+ | |
 * | | 01 | | 02 | | 03 | | 04 | | |
 * | +----+ +----+ +----+ +----+ | |
 * |                             | |
 * | +----+ +----+ +----+ +----+ | |
 * | | 05 | | 06 | | 07 | | 07 | | |
 * | +----+ +----+ +----+ +----+ | |
 * |                             | |
 * | +----+ +----+ +----+ +----+ | |
 * | | 09 | | 10 | | 11 | | 12 | | |
 * | +----+ +----+ +----+ +----+ | |
 * |                             | |
 * | +----+ +----+ +----+ +----+ | |
 * | | 13 | | 14 | | 15 | | 16 | | |
 * +-----------------------------+-+
 * </pre>
 * 
 * <p>
 * Each row hosts four buttons, and the vertical scroll bar allows scrolling the
 * content down.
 * </p>
 * 
 * <p>
 * Under the {@link LayoutKind#COLUMN_FILL}, the buttons are laid out in
 * columns, never exceeding the available vertical space. A horizontal scroll
 * bar will kick in once there is not enough horizontal space to show all the
 * buttons. The schematic below shows a column-fill command button panel:
 * </p>
 * 
 * <pre>
 * +---------------------------------+ 
 * |                                 |
 * | +----+ +----+ +----+ +----+ +---|
 * | | 01 | | 04 | | 07 | | 10 | | 13|
 * | +----+ +----+ +----+ +----+ +---|
 * |                                 |
 * | +----+ +----+ +----+ +----+ +---|
 * | | 02 | | 05 | | 08 | | 11 | | 14|
 * | +----+ +----+ +----+ +----+ +---|
 * |                                 |
 * | +----+ +----+ +----+ +----+ +---|
 * | | 03 | | 06 | | 09 | | 12 | | 15|
 * | +----+ +----+ +----+ +----+ +---|
 * |                                 |
 * +---------------------------------+
 * +---------------------------------+
 * </pre>
 * 
 * <p>
 * Each column hosts three buttons, and the horizontal scroll bar allows
 * scrolling the content down.
 * </p>
 * 
 * @author Kirill Grouchnikov
 */
public class JCommandButtonPanel extends JPanel implements Scrollable {
	/**
	 * @see #getUIClassID
	 */
	public static final String uiClassID = "CommandButtonPanelUI";

	/**
	 * List of titles for all button groups.
	 * 
	 * @see #getGroupCount()
	 * @see #getGroupTitleAt(int)
	 */
	protected List<String> groupTitles;

	/**
	 * List of all button groups.
	 * 
	 * @see #getGroupCount()
	 * @see #getGroupButtons(int)
	 */
	protected List<List<AbstractCommandButton>> buttons;

	/**
	 * Maximum number of columns for this panel. Relevant only when the layout
	 * kind is {@link LayoutKind#ROW_FILL}.
	 * 
	 * @see #getMaxButtonColumns()
	 * @see #setMaxButtonColumns(int)
	 */
	protected int maxButtonColumns;

	/**
	 * Maximum number of rows for this panel. Relevant only when the layout kind
	 * is {@link LayoutKind#COLUMN_FILL}.
	 * 
	 * @see #getMaxButtonRows()
	 * @see #setMaxButtonRows(int)
	 */
	protected int maxButtonRows;

	/**
	 * Indicates the selection mode for the {@link JCommandToggleButton} in this
	 * panel.
	 * 
	 * @see #setSingleSelectionMode(boolean)
	 */
	protected boolean isSingleSelectionMode;

	/**
	 * If <code>true</code>, the panel will show group labels.
	 * 
	 * @see #setToShowGroupLabels(boolean)
	 * @see #isToShowGroupLabels()
	 */
	protected boolean toShowGroupLabels;

	/**
	 * The button group for the single selection mode.
	 */
	protected CommandToggleButtonGroup buttonGroup;

	/**
	 * Current icon dimension.
	 */
	protected int currDimension;

	/**
	 * Current icon state.
	 */
	protected CommandButtonDisplayState currState;

	/**
	 * Layout kind of this button panel.
	 * 
	 * @see #getLayoutKind()
	 * @see #setLayoutKind(LayoutKind)
	 */
	protected LayoutKind layoutKind;

	/**
	 * Enumerates the available layout kinds.
	 * 
	 * @author Kirill Grouchnikov
	 */
	public enum LayoutKind {
		/**
		 * The buttons are layed out in rows respecting the available width.
		 */
		ROW_FILL,

		/**
		 * The buttons are layed out in columns respecting the available height.
		 */
		COLUMN_FILL
	}

	/**
	 * Creates a new panel.
	 */
	protected JCommandButtonPanel() {
		this.buttons = new ArrayList<List<AbstractCommandButton>>();
		this.groupTitles = new ArrayList<String>();
		this.maxButtonColumns = -1;
		this.maxButtonRows = -1;
		this.isSingleSelectionMode = false;
		this.toShowGroupLabels = true;
		this.setLayoutKind(LayoutKind.ROW_FILL);
	}

	/**
	 * Creates a new panel.
	 * 
	 * @param startingDimension
	 *            Initial dimension for buttons.
	 */
	public JCommandButtonPanel(int startingDimension) {
		this();
		this.currDimension = startingDimension;
		this.currState = CommandButtonDisplayState.FIT_TO_ICON;
		this.updateUI();
	}

	/**
	 * Creates a new panel.
	 * 
	 * @param startingState
	 *            Initial state for buttons.
	 */
	public JCommandButtonPanel(CommandButtonDisplayState startingState) {
		this();
		this.currDimension = -1;
		this.currState = startingState;
		this.updateUI();
	}

	/**
	 * Adds a new button group at the specified index.
	 * 
	 * @param buttonGroupName
	 *            Button group name.
	 * @param groupIndex
	 *            Button group index.
	 * @see #addButtonGroup(String)
	 * @see #removeButtonGroup(String)
	 * @see #removeAllGroups()
	 */
	public void addButtonGroup(String buttonGroupName, int groupIndex) {
		this.groupTitles.add(groupIndex, buttonGroupName);
		List<AbstractCommandButton> list = new ArrayList<AbstractCommandButton>();
		this.buttons.add(groupIndex, list);
		this.fireStateChanged();
	}

	/**
	 * Adds a new button group after all the existing button groups.
	 * 
	 * @param buttonGroupName
	 *            Button group name.
	 * @see #addButtonGroup(String, int)
	 * @see #removeButtonGroup(String)
	 * @see #removeAllGroups()
	 */
	public void addButtonGroup(String buttonGroupName) {
		this.addButtonGroup(buttonGroupName, this.groupTitles.size());
	}

	/**
	 * Removes the specified button group.
	 * 
	 * @param buttonGroupName
	 *            Name of the button group to remove.
	 * @see #addButtonGroup(String)
	 * @see #addButtonGroup(String, int)
	 * @see #removeAllGroups()
	 */
	public void removeButtonGroup(String buttonGroupName) {
		int groupIndex = this.groupTitles.indexOf(buttonGroupName);
		if (groupIndex < 0)
			return;
		this.groupTitles.remove(groupIndex);
		List<AbstractCommandButton> list = this.buttons.get(groupIndex);
		if (list != null) {
			for (AbstractCommandButton button : list) {
				this.remove(button);
				if (this.isSingleSelectionMode
						&& (button instanceof JCommandToggleButton)) {
					this.buttonGroup.remove((JCommandToggleButton) button);
				}
			}
		}
		this.buttons.remove(groupIndex);
		this.fireStateChanged();
	}

	/**
	 * Adds a new button to the specified button group.
	 * 
	 * @param commandButton
	 *            Button to add.
	 * @return Returns the index of the button on the specified group, or -1 if
	 *         no such group exists.
	 * @see #addButtonToGroup(String, AbstractCommandButton)
	 * @see #addButtonToGroup(String, int, AbstractCommandButton)
	 * @see #removeButtonFromGroup(String, int)
	 */
	public int addButtonToLastGroup(AbstractCommandButton commandButton) {
		if (this.groupTitles.size() == 0)
			return -1;
		int groupIndex = this.groupTitles.size() - 1;
		commandButton.setDisplayState(this.currState);
		return this.addButtonToGroup(this.groupTitles.get(groupIndex),
				this.buttons.get(groupIndex).size(), commandButton);
	}

	/**
	 * Adds a new button to the specified button group.
	 * 
	 * @param buttonGroupName
	 *            Name of the button group.
	 * @param commandButton
	 *            Button to add.
	 * @return Returns the index of the button on the specified group, or -1 if
	 *         no such group exists.
	 * @see #addButtonToGroup(String, int, AbstractCommandButton)
	 * @see #addButtonToLastGroup(AbstractCommandButton)
	 * @see #removeButtonFromGroup(String, int)
	 */
	public int addButtonToGroup(String buttonGroupName,
			AbstractCommandButton commandButton) {
		int groupIndex = this.groupTitles.indexOf(buttonGroupName);
		if (groupIndex < 0)
			return -1;
		commandButton.setDisplayState(this.currState);
		return this.addButtonToGroup(buttonGroupName, this.buttons.get(
				groupIndex).size(), commandButton);
	}

	/**
	 * Adds a new button to the specified button group.
	 * 
	 * @param buttonGroupName
	 *            Name of the button group.
	 * @param indexInGroup
	 *            Index of the button in group.
	 * @param commandButton
	 *            Button to add.
	 * @return Returns the index of the button on the specified group, or -1 if
	 *         no such group exists.
	 * @see #addButtonToGroup(String, int, AbstractCommandButton)
	 * @see #addButtonToLastGroup(AbstractCommandButton)
	 * @see #removeButtonFromGroup(String, int)
	 */
	public int addButtonToGroup(String buttonGroupName, int indexInGroup,
			AbstractCommandButton commandButton) {
		int groupIndex = this.groupTitles.indexOf(buttonGroupName);
		if (groupIndex < 0)
			return -1;
		// commandButton.setState(ElementState.ORIG, true);
		this.add(commandButton);
		this.buttons.get(groupIndex).add(indexInGroup, commandButton);
		if (this.isSingleSelectionMode
				&& (commandButton instanceof JCommandToggleButton)) {
			this.buttonGroup.add((JCommandToggleButton) commandButton);
		}
		this.fireStateChanged();
		return indexInGroup;
	}

	/**
	 * Removes the button at the specified index from the specified button
	 * group.
	 * 
	 * @param buttonGroupName
	 *            Name of the button group.
	 * @param indexInGroup
	 *            Index of the button to remove.
	 * @see #addButtonToGroup(String, AbstractCommandButton)
	 * @see #addButtonToGroup(String, int, AbstractCommandButton)
	 * @see #addButtonToLastGroup(AbstractCommandButton)
	 */
	public void removeButtonFromGroup(String buttonGroupName, int indexInGroup) {
		int groupIndex = this.groupTitles.indexOf(buttonGroupName);
		if (groupIndex < 0)
			return;

		AbstractCommandButton removed = this.buttons.get(groupIndex).remove(
				indexInGroup);
		this.remove(removed);
		if (this.isSingleSelectionMode
				&& (removed instanceof JCommandToggleButton)) {
			this.buttonGroup.remove((JCommandToggleButton) removed);
		}
		this.fireStateChanged();
	}

	/**
	 * Removes all the button groups and buttons from this panel.
	 * 
	 * @see #addButtonGroup(String, int)
	 * @see #addButtonGroup(String)
	 * @see #removeButtonGroup(String)
	 * @see #removeButtonFromGroup(String, int)
	 */
	public void removeAllGroups() {
		for (List<AbstractCommandButton> ljcb : this.buttons) {
			for (AbstractCommandButton jcb : ljcb) {
				if (this.isSingleSelectionMode
						&& (jcb instanceof JCommandToggleButton)) {
					this.buttonGroup.remove((JCommandToggleButton) jcb);
				}
				this.remove(jcb);
			}
		}
		this.buttons.clear();
		this.groupTitles.clear();
		this.fireStateChanged();
	}

	/**
	 * Returns the number of button groups in this panel.
	 * 
	 * @return Number of button groups in this panel.
	 */
	public int getGroupCount() {
		if (this.groupTitles == null)
			return 0;
		return this.groupTitles.size();
	}

	/**
	 * Returns the number of buttons in this panel.
	 * 
	 * @return Number of buttons in this panel.
	 */
	public int getButtonCount() {
		int result = 0;
		for (List<AbstractCommandButton> ljcb : this.buttons) {
			result += ljcb.size();
		}
		return result;
	}

	/**
	 * Returns the title of the button group at the specified index.
	 * 
	 * @param index
	 *            Button group index.
	 * @return Title of the button group at the specified index.
	 */
	public String getGroupTitleAt(int index) {
		return this.groupTitles.get(index);
	}

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

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

	/**
	 * Sets the maximum button columns for this panel. When this panel is shown
	 * and the layout kind is {@link LayoutKind#ROW_FILL}, it will have no more
	 * than this number of buttons in each row. Fires a
	 * <code>maxButtonColumns</code> property change event.
	 * 
	 * @param maxButtonColumns
	 *            Maximum button columns for this panel.
	 * @see #getMaxButtonColumns()
	 * @see #setMaxButtonRows(int)
	 */
	public void setMaxButtonColumns(int maxButtonColumns) {
		if (maxButtonColumns != this.maxButtonColumns) {
			int oldValue = this.maxButtonColumns;
			this.maxButtonColumns = maxButtonColumns;
			this.firePropertyChange("maxButtonColumns", oldValue,
					this.maxButtonColumns);
		}
	}

	/**
	 * Returns the maximum button columns for this panel. The return value is
	 * relevant only when the layout kind is {@link LayoutKind#ROW_FILL}.
	 * 
	 * @return Maximum button columns for this panel.
	 * @see #setMaxButtonColumns(int)
	 * @see #getMaxButtonRows()
	 */
	public int getMaxButtonColumns() {
		return this.maxButtonColumns;
	}

	/**
	 * Sets the maximum button rows for this panel. When this panel is shown and
	 * the layout kind is {@link LayoutKind#COLUMN_FILL}, it will have no more
	 * than this number of buttons in each column. Fires a
	 * <code>maxButtonRows</code> property change event.
	 * 
	 * @param maxButtonRows
	 *            Maximum button rows for this panel.
	 * @see #getMaxButtonRows()
	 * @see #setMaxButtonColumns(int)
	 */
	public void setMaxButtonRows(int maxButtonRows) {
		if (maxButtonRows != this.maxButtonRows) {
			int oldValue = this.maxButtonRows;
			this.maxButtonRows = maxButtonRows;
			this.firePropertyChange("maxButtonRows", oldValue,
					this.maxButtonRows);
		}
	}

	/**
	 * Returns the maximum button rows for this panel. The return value is
	 * relevant only when the layout kind is {@link LayoutKind#COLUMN_FILL}.
	 * 
	 * @return Maximum button rows for this panel.
	 * @see #setMaxButtonRows(int)
	 * @see #getMaxButtonColumns()
	 */
	public int getMaxButtonRows() {
		return this.maxButtonRows;
	}

	/**
	 * Returns the list of all buttons in the specified button group.
	 * 
	 * @param groupIndex
	 *            Group index.
	 * @return Unmodifiable view on the list of all buttons in the specified
	 *         button group.
	 * @see #getGroupCount()
	 */
	public List<AbstractCommandButton> getGroupButtons(int groupIndex) {
		return Collections.unmodifiableList(this.buttons.get(groupIndex));
	}

	/**
	 * Sets the selection mode for this panel. If <code>true</code> is passed as
	 * the parameter, all {@link JCommandToggleButton} in this panel are set to
	 * belong to the same button group.
	 * 
	 * @param isSingleSelectionMode
	 *            If <code>true</code>,all {@link JCommandToggleButton} in this
	 *            panel are set to belong to the same button group.
	 * @see #getSelectedButton()
	 */
	public void setSingleSelectionMode(boolean isSingleSelectionMode) {
		if (this.isSingleSelectionMode == isSingleSelectionMode)
			return;

		this.isSingleSelectionMode = isSingleSelectionMode;
		if (this.isSingleSelectionMode) {
			this.buttonGroup = new CommandToggleButtonGroup();
			for (List<AbstractCommandButton> ljrb : this.buttons) {
				for (AbstractCommandButton jrb : ljrb) {
					if (jrb instanceof JCommandToggleButton) {
						this.buttonGroup.add((JCommandToggleButton) jrb);
					}
				}
			}
		} else {
			for (List<AbstractCommandButton> ljrb : this.buttons) {
				for (AbstractCommandButton jrb : ljrb) {
					if (jrb instanceof JCommandToggleButton) {
						this.buttonGroup.remove((JCommandToggleButton) jrb);
					}
				}
			}
			this.buttonGroup = null;
		}
	}

	/**
	 * Sets indication whether button group labels should be shown. Fires a
	 * <code>toShowGroupLabels</code> property change event.
	 * 
	 * @param toShowGroupLabels
	 *            If <code>true</code>, this panel will show the labels of the
	 *            button groups.
	 * @see #isToShowGroupLabels()
	 */
	public void setToShowGroupLabels(boolean toShowGroupLabels) {
		if ((layoutKind == LayoutKind.COLUMN_FILL) && toShowGroupLabels) {
			throw new IllegalArgumentException(
					"Column fill layout is not supported when group labels are shown");
		}
		if (this.toShowGroupLabels != toShowGroupLabels) {
			boolean oldValue = this.toShowGroupLabels;
			this.toShowGroupLabels = toShowGroupLabels;
			this.firePropertyChange("toShowGroupLabels", oldValue,
					this.toShowGroupLabels);
		}
	}

	/**
	 * Returns indication whether button group labels should be shown.
	 * 
	 * @return If <code>true</code>, this panel shows the labels of the button
	 *         groups, and <code>false</code> otherwise.
	 * @see #setToShowGroupLabels(boolean)
	 */
	public boolean isToShowGroupLabels() {
		return this.toShowGroupLabels;
	}

	/**
	 * Sets the new dimension for the icons in this panel. The state for all the
	 * icons is set to {@link CommandButtonDisplayState#FIT_TO_ICON}.
	 * 
	 * @param dimension
	 *            New dimension for the icons in this panel.
	 * @see #setIconState(CommandButtonDisplayState)
	 */
	public void setIconDimension(int dimension) {
		this.currDimension = dimension;
		this.currState = CommandButtonDisplayState.FIT_TO_ICON;
		for (List<AbstractCommandButton> buttonList : this.buttons) {
			for (AbstractCommandButton button : buttonList) {
				button.updateCustomDimension(dimension);
			}
		}
		this.revalidate();
		this.doLayout();
		this.repaint();
	}

	/**
	 * Sets the new state for the icons in this panel. The dimension for all the
	 * icons is set to -1; this method should only be called with a state that
	 * has an associated default size (like
	 * {@link CommandButtonDisplayState#BIG},
	 * {@link CommandButtonDisplayState#TILE},
	 * {@link CommandButtonDisplayState#MEDIUM} and
	 * {@link CommandButtonDisplayState#SMALL}).
	 * 
	 * @param state
	 *            New state for the icons in this panel.
	 * @see #setIconDimension(int)
	 */
	public void setIconState(CommandButtonDisplayState state) {
		this.currDimension = -1;
		this.currState = state;
		for (List<AbstractCommandButton> ljrb : this.buttons) {
			for (AbstractCommandButton jrb : ljrb) {
				jrb.setDisplayState(state);
				jrb.revalidate();
				jrb.doLayout();
			}
		}
		this.revalidate();
		this.doLayout();
		this.repaint();
	}

	/**
	 * Returns the selected button of this panel. Only relevant for single
	 * selection mode (set by {@link #setSingleSelectionMode(boolean)}),
	 * returning <code>null</code> otherwise.
	 * 
	 * @return The selected button of this panel.
	 * @see #setSingleSelectionMode(boolean)
	 */
	public JCommandToggleButton getSelectedButton() {
		if (this.isSingleSelectionMode) {
			for (List<AbstractCommandButton> ljrb : this.buttons) {
				for (AbstractCommandButton jrb : ljrb) {
					if (jrb instanceof JCommandToggleButton) {
						JCommandToggleButton jctb = (JCommandToggleButton) jrb;
						if (jctb.getActionModel().isSelected())
							return jctb;
					}
				}
			}
		}
		return null;
	}

	/**
	 * Returns the layout kind of this panel.
	 * 
	 * @return Layout kind of this panel.
	 * @see #setLayoutKind(LayoutKind)
	 */
	public LayoutKind getLayoutKind() {
		return layoutKind;
	}

	/**
	 * Sets the new layout kind for this panel. Fires a <code>layoutKind</code>
	 * property change event.
	 * 
	 * @param layoutKind
	 *            New layout kind for this panel.
	 * @see #getLayoutKind()
	 */
	public void setLayoutKind(LayoutKind layoutKind) {
		if (layoutKind == null)
			throw new IllegalArgumentException("Layout kind cannot be null");
		if ((layoutKind == LayoutKind.COLUMN_FILL)
				&& this.isToShowGroupLabels()) {
			throw new IllegalArgumentException(
					"Column fill layout is not supported when group labels are shown");
		}
		if (layoutKind != this.layoutKind) {
			LayoutKind old = this.layoutKind;
			this.layoutKind = layoutKind;
			this.firePropertyChange("layoutKind", old, this.layoutKind);
		}
	}

	/**
	 * Adds the specified change listener to this button panel.
	 * 
	 * @param l
	 *            Change listener to add.
	 * @see #removeChangeListener(ChangeListener)
	 */
	public void addChangeListener(ChangeListener l) {
		this.listenerList.add(ChangeListener.class, l);
	}

	/**
	 * Removes the specified change listener from this button panel.
	 * 
	 * @param l
	 *            Change listener to remove.
	 * @see #addChangeListener(ChangeListener)
	 */
	public void removeChangeListener(ChangeListener l) {
		this.listenerList.remove(ChangeListener.class, l);
	}

	/**
	 * Notifies all registered listener that the state of this command button
	 * panel has changed.
	 */
	protected void fireStateChanged() {
		// 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
		ChangeEvent event = new ChangeEvent(this);
		for (int i = listeners.length - 2; i >= 0; i -= 2) {
			if (listeners[i] == ChangeListener.class) {
				((ChangeListener) listeners[i + 1]).stateChanged(event);
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.Scrollable#getPreferredScrollableViewportSize()
	 */
	public Dimension getPreferredScrollableViewportSize() {
		return this.getPreferredSize();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * javax.swing.Scrollable#getScrollableBlockIncrement(java.awt.Rectangle,
	 * int, int)
	 */
	public int getScrollableBlockIncrement(Rectangle visibleRect,
			int orientation, int direction) {
		return 30;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.Scrollable#getScrollableTracksViewportHeight()
	 */
	public boolean getScrollableTracksViewportHeight() {
		return (this.layoutKind == LayoutKind.COLUMN_FILL);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.Scrollable#getScrollableTracksViewportWidth()
	 */
	public boolean getScrollableTracksViewportWidth() {
		return (this.layoutKind == LayoutKind.ROW_FILL);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * javax.swing.Scrollable#getScrollableUnitIncrement(java.awt.Rectangle,
	 * int, int)
	 */
	public int getScrollableUnitIncrement(Rectangle visibleRect,
			int orientation, int direction) {
		return 10;
	}
}
