/*
 * 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.internal.ui.common;

import java.awt.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.UIResource;

import org.pushingpixels.flamingo.api.common.AbstractCommandButton;
import org.pushingpixels.flamingo.api.common.JCommandButtonPanel;
import org.pushingpixels.flamingo.api.common.JCommandButtonPanel.LayoutKind;
import org.pushingpixels.flamingo.internal.utils.FlamingoUtilities;

/**
 * Basic UI for command button panel {@link JCommandButtonPanel}.
 * 
 * @author Kirill Grouchnikov
 */
public class BasicCommandButtonPanelUI extends CommandButtonPanelUI {
	/**
	 * The associated command button panel.
	 */
	protected JCommandButtonPanel buttonPanel;

	/**
	 * Labels of the button panel groups.
	 */
	protected JLabel[] groupLabels;

	/**
	 * Bounds of button panel groups.
	 */
	protected Rectangle[] groupRects;

	/**
	 * Property change listener on {@link #buttonPanel}.
	 */
	protected PropertyChangeListener propertyChangeListener;

	/**
	 * Change listener on {@link #buttonPanel}.
	 */
	protected ChangeListener changeListener;

	/**
	 * Default insets of button panel groups.
	 */
	protected static final Insets GROUP_INSETS = new Insets(4, 4, 4, 4);

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
	 */
	public static ComponentUI createUI(JComponent c) {
		return new BasicCommandButtonPanelUI();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent)
	 */
	@Override
	public void installUI(JComponent c) {
		this.buttonPanel = (JCommandButtonPanel) c;

		installDefaults();
		installComponents();
		installListeners();
	}

	/**
	 * Installs defaults on the associated button panel.
	 */
	protected void installDefaults() {
		this.buttonPanel.setLayout(this.createLayoutManager());
		Font currFont = this.buttonPanel.getFont();
		if ((currFont == null) || (currFont instanceof UIResource)) {
			Font titleFont = FlamingoUtilities.getFont(null,
					"CommandButtonPanel.font", "Button.font", "Panel.font");
			this.buttonPanel.setFont(titleFont);
		}
	}

	/**
	 * Installs sub-components on the associated button panel.
	 */
	protected void installComponents() {
		this.recomputeGroupHeaders();
	}

	/**
	 * Installs listeners on the associated button panel.
	 */
	protected void installListeners() {
		this.propertyChangeListener = new PropertyChangeListener() {
			public void propertyChange(PropertyChangeEvent evt) {
				if ("maxButtonColumns".equals(evt.getPropertyName())
						|| "maxButtonRows".equals(evt.getPropertyName())
						|| "toShowGroupLabels".equals(evt.getPropertyName())) {
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							if (buttonPanel != null) {
								recomputeGroupHeaders();
								buttonPanel.revalidate();
								buttonPanel.doLayout();
							}
						}
					});
				}

				if ("layoutKind".equals(evt.getPropertyName())) {
					SwingUtilities.invokeLater(new Runnable() {
						@Override
						public void run() {
							if (buttonPanel != null) {
								buttonPanel.setLayout(createLayoutManager());
								buttonPanel.revalidate();
								buttonPanel.doLayout();
							}
						}
					});
				}
			}
		};
		this.buttonPanel.addPropertyChangeListener(this.propertyChangeListener);

		this.changeListener = new ChangeListener() {
			@Override
			public void stateChanged(ChangeEvent e) {
				recomputeGroupHeaders();
				buttonPanel.revalidate();
				buttonPanel.doLayout();
			}
		};
		this.buttonPanel.addChangeListener(this.changeListener);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.ComponentUI#uninstallUI(javax.swing.JComponent)
	 */
	@Override
	public void uninstallUI(JComponent c) {
		c.setLayout(null);

		uninstallListeners();
		uninstallComponents();
		uninstallDefaults();
		this.buttonPanel = null;
	}

	/**
	 * Uninstalls defaults from the associated button panel.
	 */
	protected void uninstallDefaults() {
	}

	/**
	 * Uninstalls sub-components from the associated button panel.
	 */
	protected void uninstallComponents() {
		if (this.groupLabels != null) {
			for (JLabel groupLabel : this.groupLabels) {
				this.buttonPanel.remove(groupLabel);
			}
			// for (JSeparator groupSeparator : this.groupSeparators) {
			// this.buttonPanel.remove(groupSeparator);
			// }
		}
	}

	/**
	 * Uninstalls listeners from the associated button panel.
	 */
	protected void uninstallListeners() {
		this.buttonPanel
				.removePropertyChangeListener(this.propertyChangeListener);
		this.propertyChangeListener = null;

		this.buttonPanel.removeChangeListener(this.changeListener);
		this.changeListener = null;
	}

	/**
	 * Returns the layout manager for the associated button panel.
	 * 
	 * @return The layout manager for the associated button panel.
	 */
	protected LayoutManager createLayoutManager() {
		if (this.buttonPanel.getLayoutKind() == LayoutKind.ROW_FILL)
			return new RowFillLayout();
		else
			return new ColumnFillLayout();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.ComponentUI#paint(java.awt.Graphics,
	 * javax.swing.JComponent)
	 */
	@Override
	public void paint(Graphics g, JComponent c) {
		Color bg = this.buttonPanel.getBackground();
		g.setColor(bg);
		g.fillRect(0, 0, c.getWidth(), c.getHeight());

		for (int i = 0; i < this.buttonPanel.getGroupCount(); i++) {
			Rectangle groupRect = this.groupRects[i];
			this.paintGroupBackground(g, i, groupRect.x, groupRect.y,
					groupRect.width, groupRect.height);

			if (this.groupLabels[i].isVisible()) {
				Rectangle groupTitleBackground = this.groupLabels[i]
						.getBounds();
				this.paintGroupTitleBackground(g, i, groupRect.x,
						groupTitleBackground.y - getGroupInsets().top,
						groupRect.width, groupTitleBackground.height
								+ getGroupInsets().top + getLayoutGap());
			}
		}
	}

	/**
	 * Paints the background of the specified button panel group.
	 * 
	 * @param g
	 *            Graphics context.
	 * @param groupIndex
	 *            Group index.
	 * @param x
	 *            X coordinate of the button group bounds.
	 * @param y
	 *            Y coordinate of the button group bounds.
	 * @param width
	 *            Width of the button group bounds.
	 * @param height
	 *            Height of the button group bounds.
	 */
	protected void paintGroupBackground(Graphics g, int groupIndex, int x,
			int y, int width, int height) {
		Color c = this.buttonPanel.getBackground();
		if ((c == null) || (c instanceof UIResource)) {
			c = UIManager.getColor("Panel.background");
			if (c == null)
				c = new Color(190, 190, 190);
			if (groupIndex % 2 == 1) {
				double coef = 0.95;
				c = new Color((int) (c.getRed() * coef),
						(int) (c.getGreen() * coef), (int) (c.getBlue() * coef));
			}
		}
		g.setColor(c);
		g.fillRect(x, y, width, height);
	}

	/**
	 * Paints the background of the title of specified button panel group.
	 * 
	 * @param g
	 *            Graphics context.
	 * @param groupIndex
	 *            Group index.
	 * @param x
	 *            X coordinate of the button group title bounds.
	 * @param y
	 *            Y coordinate of the button group title bounds.
	 * @param width
	 *            Width of the button group title bounds.
	 * @param height
	 *            Height of the button group title bounds.
	 */
	protected void paintGroupTitleBackground(Graphics g, int groupIndex, int x,
			int y, int width, int height) {
		FlamingoUtilities.renderSurface(g, this.buttonPanel, new Rectangle(x,
				y, width, height), false, (groupIndex > 0), true);
	}

	/**
	 * Returns the height of the group title strip.
	 * 
	 * @param groupIndex
	 *            Group index.
	 * @return The height of the title strip of the specified group.
	 */
	protected int getGroupTitleHeight(int groupIndex) {
		return this.groupLabels[groupIndex].getPreferredSize().height;
	}

	/**
	 * Returns the insets of button panel groups.
	 * 
	 * @return The insets of button panel groups.
	 */
	protected Insets getGroupInsets() {
		return GROUP_INSETS;
	}

	/**
	 * Row-fill layout for the button panel.
	 * 
	 * @author Kirill Grouchnikov
	 */
	protected class RowFillLayout implements LayoutManager {
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
		 * java.awt.Component)
		 */
		public void addLayoutComponent(String name, Component comp) {
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
		 */
		public void removeLayoutComponent(Component comp) {
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
		 */
		public void layoutContainer(Container parent) {
			Insets bInsets = parent.getInsets();
			Insets groupInsets = getGroupInsets();
			int left = bInsets.left;
			int right = bInsets.right;

			int y = bInsets.top;

			JCommandButtonPanel panel = (JCommandButtonPanel) parent;
			boolean ltr = panel.getComponentOrientation().isLeftToRight();

			// compute max width of buttons
			int maxButtonWidth = 0;
			int maxButtonHeight = 0;
			int groupCount = panel.getGroupCount();
			for (int i = 0; i < groupCount; i++) {
				for (AbstractCommandButton button : panel.getGroupButtons(i)) {
					maxButtonWidth = Math.max(maxButtonWidth, button
							.getPreferredSize().width);
					maxButtonHeight = Math.max(maxButtonHeight, button
							.getPreferredSize().height);
				}
			}
			groupRects = new Rectangle[groupCount];

			int gap = getLayoutGap();
			int maxWidth = parent.getWidth() - bInsets.left - bInsets.right
					- groupInsets.left - groupInsets.right;
			// for N buttons, there are N-1 gaps. Add the gap to the
			// available width and divide by the max button width + gap.
			int buttonsInRow = (maxButtonWidth == 0) ? 0 : (maxWidth + gap)
					/ (maxButtonWidth + gap);
			int maxButtonColumnsToUse = panel.getMaxButtonColumns();
			if (maxButtonColumnsToUse > 0) {
				buttonsInRow = Math.min(buttonsInRow, maxButtonColumnsToUse);
			}

			// System.out.println("Layout : " + buttonsInRow);
			for (int i = 0; i < groupCount; i++) {
				int topGroupY = y;
				y += groupInsets.top;

				JLabel groupLabel = groupLabels[i];
				if (buttonPanel.isToShowGroupLabels()) {
					int labelWidth = groupLabel.getPreferredSize().width;
					int labelHeight = getGroupTitleHeight(i);
					if (groupLabel.getComponentOrientation().isLeftToRight()) {
						groupLabel.setBounds(left + groupInsets.left, y,
								labelWidth, labelHeight);
					} else {
						groupLabel.setBounds(parent.getWidth() - right
								- groupInsets.right - labelWidth, y,
								labelWidth, labelHeight);
					}
					y += labelHeight + gap;
				}

				int buttonRows = (buttonsInRow == 0) ? 0 : (int) (Math
						.ceil((double) panel.getGroupButtons(i).size()
								/ buttonsInRow));
				if (maxButtonColumnsToUse > 0) {
					buttonsInRow = Math
							.min(buttonsInRow, maxButtonColumnsToUse);
				}

				// spread the buttons so that we don't have extra space
				// on the right
				int actualButtonWidth = (buttonRows > 1) ? (maxWidth - (buttonsInRow - 1)
						* gap)
						/ buttonsInRow
						: maxButtonWidth;
				if (maxButtonColumnsToUse == 1)
					actualButtonWidth = maxWidth;

				if (ltr) {
					int currX = left + groupInsets.left;
					for (AbstractCommandButton button : panel
							.getGroupButtons(i)) {
						int endX = currX + actualButtonWidth;
						if (endX > (parent.getWidth() - right - groupInsets.right)) {
							currX = left + groupInsets.left;
							y += maxButtonHeight;
							y += gap;
						}
						button.setBounds(currX, y, actualButtonWidth,
								maxButtonHeight);
						// System.out.println(button.getText() + ":"
						// + button.isVisible() + ":" + button.getBounds());
						currX += actualButtonWidth;
						currX += gap;
					}
				} else {
					int currX = parent.getWidth() - right - groupInsets.right;
					for (AbstractCommandButton button : panel
							.getGroupButtons(i)) {
						int startX = currX - actualButtonWidth;
						if (startX < (left + groupInsets.left)) {
							currX = parent.getWidth() - right
									- groupInsets.right;
							y += maxButtonHeight;
							y += gap;
						}
						button.setBounds(currX - actualButtonWidth, y,
								actualButtonWidth, maxButtonHeight);
						// System.out.println(button.getText() + ":"
						// + button.isVisible() + ":" + button.getBounds());
						currX -= actualButtonWidth;
						currX -= gap;
					}
				}

				y += maxButtonHeight + groupInsets.bottom;
				int bottomGroupY = y;
				groupRects[i] = new Rectangle(left, topGroupY, (parent
						.getWidth()
						- left - right), (bottomGroupY - topGroupY));
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
		 */
		public Dimension minimumLayoutSize(Container parent) {
			return new Dimension(20, 20);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
		 */
		public Dimension preferredLayoutSize(Container parent) {
			JCommandButtonPanel panel = (JCommandButtonPanel) parent;

			int maxButtonColumnsToUse = panel.getMaxButtonColumns();

			Insets bInsets = parent.getInsets();
			Insets groupInsets = getGroupInsets();
			int insetsWidth = bInsets.left + groupInsets.left + bInsets.right
					+ groupInsets.right;

			// compute max width of buttons
			int maxButtonWidth = 0;
			int maxButtonHeight = 0;
			int groupCount = panel.getGroupCount();
			for (int i = 0; i < groupCount; i++) {
				for (AbstractCommandButton button : panel.getGroupButtons(i)) {
					maxButtonWidth = Math.max(maxButtonWidth, button
							.getPreferredSize().width);
					maxButtonHeight = Math.max(maxButtonHeight, button
							.getPreferredSize().height);
				}
			}

			// total height
			int gap = getLayoutGap();
			boolean usePanelWidth = (maxButtonColumnsToUse <= 0);
			int availableWidth = panel.getWidth();
			availableWidth -= insetsWidth;

			if (usePanelWidth) {
				// this hasn't been set. Compute using the available
				// width
				maxButtonColumnsToUse = (availableWidth + gap)
						/ (maxButtonWidth + gap);
			}
			int height = bInsets.top + bInsets.bottom;
			// System.out.print(height + "[" + maxButtonColumnsToUse + "]");
			for (int i = 0; i < groupCount; i++) {
				if (groupLabels[i].isVisible()) {
					height += (getGroupTitleHeight(i) + gap);
				}

				height += (groupInsets.top + groupInsets.bottom);

				int buttonRows = (int) (Math.ceil((double) panel
						.getGroupButtons(i).size()
						/ maxButtonColumnsToUse));
				height += buttonRows * maxButtonHeight + (buttonRows - 1) * gap;
				// System.out.print(" " + height);
			}
			int prefWidth = usePanelWidth ? availableWidth
					: maxButtonColumnsToUse * maxButtonWidth
							+ (maxButtonColumnsToUse - 1) * gap + bInsets.left
							+ bInsets.right + groupInsets.left
							+ groupInsets.right;
			// System.out.println(" : " + height);
			return new Dimension(Math.max(10, prefWidth), Math.max(10, height));
		}
	}

	/**
	 * Column-fill layout for the button panel.
	 * 
	 * @author Kirill Grouchnikov
	 */
	protected class ColumnFillLayout implements LayoutManager {
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
		 * java.awt.Component)
		 */
		public void addLayoutComponent(String name, Component comp) {
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#removeLayoutComponent(java.awt.Component)
		 */
		public void removeLayoutComponent(Component comp) {
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
		 */
		public void layoutContainer(Container parent) {
			Insets bInsets = parent.getInsets();
			Insets groupInsets = getGroupInsets();
			int top = bInsets.top;
			int bottom = bInsets.bottom;

			JCommandButtonPanel panel = (JCommandButtonPanel) parent;
			boolean ltr = panel.getComponentOrientation().isLeftToRight();

			// compute max width of buttons
			int maxButtonWidth = 0;
			int maxButtonHeight = 0;
			int groupCount = panel.getGroupCount();
			for (int i = 0; i < groupCount; i++) {
				for (AbstractCommandButton button : panel.getGroupButtons(i)) {
					maxButtonWidth = Math.max(maxButtonWidth, button
							.getPreferredSize().width);
					maxButtonHeight = Math.max(maxButtonHeight, button
							.getPreferredSize().height);
				}
			}
			groupRects = new Rectangle[groupCount];

			int gap = getLayoutGap();
			int maxHeight = parent.getHeight() - bInsets.top - bInsets.bottom
					- groupInsets.top - groupInsets.bottom;
			// for N buttons, there are N-1 gaps. Add the gap to the
			// available width and divide by the max button width + gap.
			int buttonsInRow = (maxButtonHeight == 0) ? 0 : (maxHeight + gap)
					/ (maxButtonHeight + gap);

			if (ltr) {
				int x = bInsets.left + groupInsets.left;
				for (int i = 0; i < groupCount; i++) {
					int leftGroupX = x;
					x += groupInsets.left;
					int currY = top + groupInsets.top;

					int buttonColumns = (buttonsInRow == 0) ? 0 : (int) (Math
							.ceil((double) panel.getGroupButtons(i).size()
									/ buttonsInRow));
					// spread the buttons so that we don't have extra space
					// on the bottom
					int actualButtonHeight = (buttonColumns > 1) ? (maxHeight - (buttonsInRow - 1)
							* gap)
							/ buttonsInRow
							: maxButtonWidth;

					for (AbstractCommandButton button : panel
							.getGroupButtons(i)) {
						int endY = currY + actualButtonHeight;
						if (endY > (parent.getHeight() - bottom - groupInsets.bottom)) {
							currY = top + groupInsets.top;
							x += maxButtonWidth;
							x += gap;
						}
						button.setBounds(x, currY, maxButtonWidth,
								actualButtonHeight);
						// System.out.println(button.getText() + ":"
						// + button.isVisible() + ":" + button.getBounds());
						currY += actualButtonHeight;
						currY += gap;
					}
					x += maxButtonWidth + groupInsets.bottom;
					int rightGroupX = x;
					groupRects[i] = new Rectangle(leftGroupX, top,
							(rightGroupX - leftGroupX), (parent.getHeight()
									- top - bottom));
				}
			} else {
				int x = panel.getWidth() - bInsets.right - groupInsets.right;
				for (int i = 0; i < groupCount; i++) {
					int rightGroupX = x;
					x -= groupInsets.left;
					int currY = top + groupInsets.top;

					int buttonColumns = (buttonsInRow == 0) ? 0 : (int) (Math
							.ceil((double) panel.getGroupButtons(i).size()
									/ buttonsInRow));
					// spread the buttons so that we don't have extra space
					// on the bottom
					int actualButtonHeight = (buttonColumns > 1) ? (maxHeight - (buttonsInRow - 1)
							* gap)
							/ buttonsInRow
							: maxButtonWidth;

					for (AbstractCommandButton button : panel
							.getGroupButtons(i)) {
						int endY = currY + actualButtonHeight;
						if (endY > (parent.getHeight() - bottom - groupInsets.bottom)) {
							currY = top + groupInsets.top;
							x -= maxButtonWidth;
							x -= gap;
						}
						button.setBounds(x - maxButtonWidth, currY,
								maxButtonWidth, actualButtonHeight);
						// System.out.println(button.getText() + ":"
						// + button.isVisible() + ":" + button.getBounds());
						currY += actualButtonHeight;
						currY += gap;
					}
					x -= (maxButtonWidth + groupInsets.bottom);
					int leftGroupX = x;
					groupRects[i] = new Rectangle(leftGroupX, top,
							(rightGroupX - leftGroupX), (parent.getHeight()
									- top - bottom));
				}
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
		 */
		public Dimension minimumLayoutSize(Container parent) {
			return new Dimension(20, 20);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
		 */
		public Dimension preferredLayoutSize(Container parent) {
			JCommandButtonPanel panel = (JCommandButtonPanel) parent;

			int maxButtonRowsToUse = panel.getMaxButtonRows();

			Insets bInsets = parent.getInsets();
			Insets groupInsets = getGroupInsets();
			int insetsHeight = bInsets.top + groupInsets.top + bInsets.bottom
					+ groupInsets.bottom;

			// compute max width of buttons
			int maxButtonWidth = 0;
			int maxButtonHeight = 0;
			int groupCount = panel.getGroupCount();
			for (int i = 0; i < groupCount; i++) {
				for (AbstractCommandButton button : panel.getGroupButtons(i)) {
					maxButtonWidth = Math.max(maxButtonWidth, button
							.getPreferredSize().width);
					maxButtonHeight = Math.max(maxButtonHeight, button
							.getPreferredSize().height);
				}
			}

			// total width
			int gap = getLayoutGap();
			boolean usePanelHeight = (maxButtonRowsToUse <= 0);

			int availableHeight = panel.getHeight();
			availableHeight -= insetsHeight;
			if (usePanelHeight) {
				// this hasn't been set. Compute using the available
				// height
				maxButtonRowsToUse = (availableHeight + gap)
						/ (maxButtonHeight + gap);
			}
			// go over all groups and see how many columns each one needs
			int width = bInsets.left + bInsets.right;
			for (int i = 0; i < groupCount; i++) {
				width += (groupInsets.left + groupInsets.right);

				int buttonColumns = (int) (Math.ceil((double) panel
						.getGroupButtons(i).size()
						/ maxButtonRowsToUse));
				width += buttonColumns * maxButtonWidth + (buttonColumns - 1)
						* gap;
			}
			int prefHeight = usePanelHeight ? availableHeight
					: maxButtonRowsToUse * maxButtonWidth
							+ (maxButtonRowsToUse - 1) * gap + bInsets.top
							+ bInsets.bottom + groupInsets.top
							+ groupInsets.bottom;
			return new Dimension(Math.max(10, width), Math.max(10, prefHeight));
		}
	}

	/**
	 * Returns the layout gap for button panel components.
	 * 
	 * @return The layout gap for button panel components.
	 */
	protected int getLayoutGap() {
		return 4;
	}

	/**
	 * Recomputes the components for button group headers.
	 */
	protected void recomputeGroupHeaders() {
		if (this.groupLabels != null) {
			for (JLabel groupLabel : this.groupLabels) {
				this.buttonPanel.remove(groupLabel);
			}
		}

		int groupCount = this.buttonPanel.getGroupCount();
		this.groupLabels = new JLabel[groupCount];
		for (int i = 0; i < groupCount; i++) {
			this.groupLabels[i] = new JLabel(this.buttonPanel
					.getGroupTitleAt(i));
			this.groupLabels[i].setComponentOrientation(this.buttonPanel
					.getComponentOrientation());

			this.buttonPanel.add(this.groupLabels[i]);

			this.groupLabels[i].setVisible(this.buttonPanel
					.isToShowGroupLabels());
		}
	}

	/**
	 * Returns the preferred size of the associated button panel for the
	 * specified parameters.
	 * 
	 * @param buttonVisibleRows
	 *            Target number of visible button rows.
	 * @param titleVisibleRows
	 *            Target number of visible group title rows.
	 * @return The preferred size of the associated button panel for the
	 *         specified parameters.
	 */
	public int getPreferredHeight(int buttonVisibleRows, int titleVisibleRows) {
		Insets bInsets = this.buttonPanel.getInsets();
		Insets groupInsets = getGroupInsets();
		int maxButtonHeight = 0;
		int groupCount = this.buttonPanel.getGroupCount();
		for (int i = 0; i < groupCount; i++) {
			for (AbstractCommandButton button : this.buttonPanel
					.getGroupButtons(i)) {
				maxButtonHeight = Math.max(maxButtonHeight, button
						.getPreferredSize().height);
			}
		}

		// total height
		int gap = getLayoutGap();

		// panel insets
		int totalHeight = bInsets.top + bInsets.bottom;
		// height of icon rows
		totalHeight += buttonVisibleRows * maxButtonHeight;
		// gaps between icon rows
		totalHeight += (buttonVisibleRows - 1) * gap;
		// title height
		totalHeight += titleVisibleRows * getGroupTitleHeight(0);
		// title insets
		totalHeight += (titleVisibleRows - 1)
				* (groupInsets.top + groupInsets.bottom);

		return totalHeight;
	}
}
