/*
 * 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.ribbon;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Arc2D;
import java.awt.geom.GeneralPath;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.*;
import java.util.List;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.BasicGraphicsUtils;

import org.pushingpixels.flamingo.api.common.*;
import org.pushingpixels.flamingo.api.common.JCommandButton.CommandButtonKind;
import org.pushingpixels.flamingo.api.common.popup.JPopupPanel;
import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager;
import org.pushingpixels.flamingo.api.common.popup.PopupPanelManager.PopupEvent;
import org.pushingpixels.flamingo.api.ribbon.*;
import org.pushingpixels.flamingo.api.ribbon.resize.RibbonBandResizePolicy;
import org.pushingpixels.flamingo.api.ribbon.resize.RibbonBandResizeSequencingPolicy;
import org.pushingpixels.flamingo.internal.ui.common.BasicCommandButtonUI;
import org.pushingpixels.flamingo.internal.ui.ribbon.appmenu.JRibbonApplicationMenuButton;
import org.pushingpixels.flamingo.internal.utils.*;

/**
 * Basic UI for ribbon {@link JRibbon}.
 * 
 * @author Kirill Grouchnikov
 */
public class BasicRibbonUI extends RibbonUI {
	/**
	 * Client property marking the ribbon component to indicate whether the task
	 * bar and contextual task group headers should be shown on the title pane
	 * of the window. This is only relevant for the {@link JRibbonFrame}.
	 */
	public static final String IS_USING_TITLE_PANE = "ribbon.internal.isUsingTitlePane";

	private static final String JUST_MINIMIZED = "ribbon.internal.justMinimized";

	/**
	 * The associated ribbon.
	 */
	protected JRibbon ribbon;

	/**
	 * Mouse wheel listener to switch between ribbon tasks.
	 */
	// protected MouseWheelListener mouseWheelListener;
	/**
	 * Taskbar panel.
	 */
	protected JPanel taskBarPanel;

	protected JScrollablePanel<BandHostPanel> bandScrollablePanel;

	protected JScrollablePanel<TaskToggleButtonsHostPanel> taskToggleButtonsScrollablePanel;

	protected JRibbonApplicationMenuButton applicationMenuButton;

	protected JCommandButton helpButton;

	/**
	 * Map of toggle buttons of all tasks.
	 */
	protected Map<RibbonTask, JRibbonTaskToggleButton> taskToggleButtons;

	/**
	 * Button group for task toggle buttons.
	 */
	protected CommandToggleButtonGroup taskToggleButtonGroup;

	/**
	 * Change listener.
	 */
	protected ChangeListener ribbonChangeListener;

	/**
	 * Property change listener.
	 */
	protected PropertyChangeListener propertyChangeListener;

	protected ContainerListener ribbonContainerListener;

	protected ComponentListener ribbonComponentListener;

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

	/**
	 * Creates a new basic ribbon UI delegate.
	 */
	public BasicRibbonUI() {
		this.taskToggleButtons = new HashMap<RibbonTask, JRibbonTaskToggleButton>();
		this.taskToggleButtonGroup = new CommandToggleButtonGroup();
		this.taskToggleButtonGroup.setAllowsClearingSelection(false);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.ComponentUI#installUI(javax.swing.JComponent)
	 */
	@Override
	public void installUI(JComponent c) {
		this.ribbon = (JRibbon) c;
		installDefaults();
		installComponents();
		installListeners();
	}

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

		this.ribbon = null;
	}

	/**
	 * Installs listeners on the associated ribbon.
	 */
	protected void installListeners() {
		// this.mouseWheelListener = new MouseWheelListener() {
		// public void mouseWheelMoved(MouseWheelEvent e) {
		// handleMouseWheelEvent(e);
		// }
		// };
		// this.taskToggleButtonsScrollablePanel.getView().addMouseWheelListener(
		// this.mouseWheelListener);
		//
		this.ribbonChangeListener = new ChangeListener() {
			@Override
			public void stateChanged(ChangeEvent e) {
				syncRibbonState();
			}
		};
		this.ribbon.addChangeListener(this.ribbonChangeListener);

		this.propertyChangeListener = new PropertyChangeListener() {
			@Override
			public void propertyChange(PropertyChangeEvent evt) {
				if ("selectedTask".equals(evt.getPropertyName())) {
					RibbonTask old = (RibbonTask) evt.getOldValue();
					final RibbonTask curr = (RibbonTask) evt.getNewValue();
					if ((old != null) && (taskToggleButtons.get(old) != null)) {
						taskToggleButtons.get(old).getActionModel()
								.setSelected(false);
					}
					if ((curr != null) && (taskToggleButtons.get(curr) != null)) {
						taskToggleButtons.get(curr).getActionModel()
								.setSelected(true);
					}

					if (isShowingScrollsForTaskToggleButtons()
							&& (curr != null)) {
						// scroll selected task as necessary so that it's
						// visible
						JRibbonTaskToggleButton toggleButton = taskToggleButtons
								.get(curr);
						if (toggleButton != null) {
							scrollAndRevealTaskToggleButton(toggleButton);
						}
					}

					// Special case for showing key tips of ribbon tasks.
					// When a ribbon task is selected with a key tip, its
					// showing and layout is deferred as a separate Runnable
					// on EDT. When the key chain for that task is created,
					// the command buttons are not at their final size yet
					// and no key tips are shown.
					// Here we schedule yet another Runnable
					// to recompute all keytips if the
					// originator is a task toggle button.
					SwingUtilities.invokeLater(new Runnable() {
						@Override
						public void run() {
							KeyTipManager ktm = KeyTipManager.defaultManager();
							if (ktm.isShowingKeyTips()) {
								KeyTipManager.KeyTipChain chain = ktm
										.getCurrentlyShownKeyTipChain();
								if (chain.chainParentComponent == taskToggleButtons
										.get(curr)) {
									ktm.refreshCurrentChain();
								}
							}
						}
					});
				}
				if ("applicationMenuRichTooltip".equals(evt.getPropertyName())) {
					syncApplicationMenuTips();
				}
				if ("applicationMenuKeyTip".equals(evt.getPropertyName())) {
					syncApplicationMenuTips();
				}
				if ("applicationMenu".equals(evt.getPropertyName())) {
					ribbon.revalidate();
					ribbon.doLayout();
					ribbon.repaint();
					Window windowAncestor = SwingUtilities
							.getWindowAncestor(ribbon);
					if (windowAncestor instanceof JRibbonFrame) {
						FlamingoUtilities
								.updateRibbonFrameIconImages((JRibbonFrame) windowAncestor);
					}
				}
				if ("minimized".equals(evt.getPropertyName())) {
					PopupPanelManager.defaultManager().hidePopups(null);
					ribbon.revalidate();
					ribbon.doLayout();
					ribbon.repaint();
				}
			}
		};
		this.ribbon.addPropertyChangeListener(this.propertyChangeListener);

		this.ribbonContainerListener = new ContainerAdapter() {
			@Override
			public void componentAdded(ContainerEvent e) {
				if (isUsingTitlePane())
					return;
				Component added = e.getComponent();
				if (added != applicationMenuButton) {
					ribbon.setComponentZOrder(applicationMenuButton, ribbon
							.getComponentCount() - 1);
				}
			}
		};
		this.ribbon.addContainerListener(this.ribbonContainerListener);

		this.ribbonComponentListener = new ComponentAdapter() {
			@Override
			public void componentResized(ComponentEvent e) {
				KeyTipManager.defaultManager().hideAllKeyTips();
			}
		};
		this.ribbon.addComponentListener(this.ribbonComponentListener);
	}

	/**
	 * Uninstalls listeners from the associated ribbon.
	 */
	protected void uninstallListeners() {
		// this.taskToggleButtonsScrollablePanel.getView()
		// .removeMouseWheelListener(this.mouseWheelListener);
		// this.mouseWheelListener = null;
		//
		this.ribbon.removeChangeListener(this.ribbonChangeListener);
		this.ribbonChangeListener = null;

		this.ribbon.removePropertyChangeListener(this.propertyChangeListener);
		this.propertyChangeListener = null;

		this.ribbon.removeContainerListener(this.ribbonContainerListener);
		this.ribbonContainerListener = null;

		this.ribbon.removeComponentListener(this.ribbonComponentListener);
		this.ribbonComponentListener = null;
	}

	/**
	 * Installs defaults on the associated ribbon.
	 */
	protected void installDefaults() {
		Border b = this.ribbon.getBorder();
		if (b == null || b instanceof UIResource) {
			Border toSet = UIManager.getBorder("Ribbon.border");
			if (toSet == null)
				toSet = new BorderUIResource.EmptyBorderUIResource(1, 2, 1, 2);
			this.ribbon.setBorder(toSet);
		}
	}

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

	/**
	 * Installs subcomponents on the associated ribbon.
	 */
	protected void installComponents() {
		// taskbar panel
		this.taskBarPanel = new TaskbarPanel();
		this.taskBarPanel.setName("JRibbon Task Bar");
		this.taskBarPanel.setLayout(createTaskbarLayoutManager());
		this.ribbon.add(this.taskBarPanel);

		// band scrollable panel
		BandHostPanel bandHostPanel = createBandHostPanel();
		bandHostPanel.setLayout(createBandHostPanelLayoutManager());
		this.bandScrollablePanel = new JScrollablePanel<BandHostPanel>(
				bandHostPanel, JScrollablePanel.ScrollType.HORIZONTALLY);
		this.bandScrollablePanel.setScrollOnRollover(false);
		this.ribbon.add(this.bandScrollablePanel);

		// task toggle buttons scrollable panel
		TaskToggleButtonsHostPanel taskToggleButtonsHostPanel = createTaskToggleButtonsHostPanel();
		taskToggleButtonsHostPanel
				.setLayout(createTaskToggleButtonsHostPanelLayoutManager());
		this.taskToggleButtonsScrollablePanel = new JScrollablePanel<TaskToggleButtonsHostPanel>(
				taskToggleButtonsHostPanel,
				JScrollablePanel.ScrollType.HORIZONTALLY);
		this.taskToggleButtonsScrollablePanel.setScrollOnRollover(false);
		this.taskToggleButtonsScrollablePanel
				.addChangeListener(new ChangeListener() {
					@Override
					public void stateChanged(ChangeEvent e) {
						// need to repaint the entire ribbon since scrolling
						// the task toggle buttons affects the contour outline
						// of the ribbon
						ribbon.repaint();
					}
				});
		this.ribbon.add(this.taskToggleButtonsScrollablePanel);

		this.ribbon.setLayout(createLayoutManager());

		this.syncRibbonState();

		this.applicationMenuButton = new JRibbonApplicationMenuButton(
				this.ribbon);
		this.syncApplicationMenuTips();
		this.ribbon.add(applicationMenuButton);
		Window windowAncestor = SwingUtilities.getWindowAncestor(this.ribbon);
		if (windowAncestor instanceof JRibbonFrame) {
			FlamingoUtilities
					.updateRibbonFrameIconImages((JRibbonFrame) windowAncestor);
		}
	}

	protected LayoutManager createTaskToggleButtonsHostPanelLayoutManager() {
		return new TaskToggleButtonsHostPanelLayout();
	}

	protected TaskToggleButtonsHostPanel createTaskToggleButtonsHostPanel() {
		return new TaskToggleButtonsHostPanel();
	}

	protected BandHostPanel createBandHostPanel() {
		return new BandHostPanel();
	}

	protected LayoutManager createBandHostPanelLayoutManager() {
		return new BandHostPanelLayout();
	}

	/**
	 * Uninstalls subcomponents from the associated ribbon.
	 */
	protected void uninstallComponents() {
		this.taskBarPanel.removeAll();
		this.taskBarPanel.setLayout(null);
		this.ribbon.remove(this.taskBarPanel);

		BandHostPanel bandHostPanel = this.bandScrollablePanel.getView();
		bandHostPanel.removeAll();
		bandHostPanel.setLayout(null);
		this.ribbon.remove(this.bandScrollablePanel);

		TaskToggleButtonsHostPanel taskToggleButtonsHostPanel = this.taskToggleButtonsScrollablePanel
				.getView();
		taskToggleButtonsHostPanel.removeAll();
		taskToggleButtonsHostPanel.setLayout(null);
		this.ribbon.remove(this.taskToggleButtonsScrollablePanel);

		this.ribbon.remove(this.applicationMenuButton);
		if (this.helpButton != null)
			this.ribbon.remove(this.helpButton);

		this.ribbon.setLayout(null);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.ComponentUI#update(java.awt.Graphics,
	 * javax.swing.JComponent)
	 */
	@Override
	public void update(Graphics g, JComponent c) {
		Graphics2D g2d = (Graphics2D) g.create();
		RenderingUtils.installDesktopHints(g2d);
		super.update(g2d, c);
		g2d.dispose();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.ComponentUI#paint(java.awt.Graphics,
	 * javax.swing.JComponent)
	 */
	@Override
	public void paint(Graphics g, JComponent c) {
		this.paintBackground(g);

		if (!ribbon.isMinimized()) {
			Insets ins = c.getInsets();
			int extraHeight = getTaskToggleButtonHeight();
			if (!this.isUsingTitlePane())
				extraHeight += getTaskbarHeight();
			this.paintTaskArea(g, 0, ins.top + extraHeight, c.getWidth(), c
					.getHeight()
					- extraHeight - ins.top - ins.bottom);
		} else {
			this.paintMinimizedRibbonSeparator(g);
		}
	}

	protected void paintMinimizedRibbonSeparator(Graphics g) {
		Color borderColor = FlamingoUtilities.getBorderColor();
		g.setColor(borderColor);
		Insets ins = ribbon.getInsets();
		g.drawLine(0, ribbon.getHeight() - ins.bottom, ribbon.getWidth(),
				ribbon.getHeight() - ins.bottom);
	}

	/**
	 * Paints the ribbon background.
	 * 
	 * @param g
	 *            Graphics context.
	 */
	protected void paintBackground(Graphics g) {
		Graphics2D g2d = (Graphics2D) g.create();

		g2d.setColor(FlamingoUtilities.getColor(Color.lightGray,
				"Panel.background"));
		g2d.fillRect(0, 0, this.ribbon.getWidth(), this.ribbon.getHeight());

		g2d.dispose();
	}

	/**
	 * Paints the task border.
	 * 
	 * @param g
	 *            Graphics context.
	 * @param x
	 *            Left X of the tasks band bounds.
	 * @param y
	 *            Top Y of the tasks band bounds.
	 * @param width
	 *            Width of the tasks band bounds.
	 * @param height
	 *            Height of the tasks band bounds.
	 */
	protected void paintTaskArea(Graphics g, int x, int y, int width, int height) {
		if (ribbon.getTaskCount() == 0)
			return;

		JRibbonTaskToggleButton selectedTaskButton = this.taskToggleButtons
				.get(this.ribbon.getSelectedTask());
		Rectangle selectedTaskButtonBounds = selectedTaskButton.getBounds();
		Point converted = SwingUtilities.convertPoint(selectedTaskButton
				.getParent(), selectedTaskButtonBounds.getLocation(),
				this.ribbon);
		// System.out.println("Painted " + selectedTaskButtonBounds.x + "->" +
		// converted.x);
		Rectangle taskToggleButtonsViewportBounds = taskToggleButtonsScrollablePanel
				.getView().getParent().getBounds();
		taskToggleButtonsViewportBounds.setLocation(SwingUtilities
				.convertPoint(taskToggleButtonsScrollablePanel,
						taskToggleButtonsViewportBounds.getLocation(),
						this.ribbon));
		int startSelectedX = Math.max(converted.x + 1,
				(int) taskToggleButtonsViewportBounds.getMinX());
		startSelectedX = Math.min(startSelectedX,
				(int) taskToggleButtonsViewportBounds.getMaxX());
		int endSelectedX = Math.min(converted.x
				+ selectedTaskButtonBounds.width - 1,
				(int) taskToggleButtonsViewportBounds.getMaxX());
		endSelectedX = Math.max(endSelectedX,
				(int) taskToggleButtonsViewportBounds.getMinX());
		Shape outerContour = FlamingoUtilities.getRibbonBorderOutline(x + 1, x
				+ width - 3, startSelectedX, endSelectedX, converted.y, y, y
				+ height, 2);

		Graphics2D g2d = (Graphics2D) g.create();
		g2d.setColor(FlamingoUtilities.getBorderColor());
		g2d.draw(outerContour);

		// check whether the currently selected task is a contextual task
		RibbonTask selected = this.ribbon.getSelectedTask();
		RibbonContextualTaskGroup contextualGroup = selected
				.getContextualGroup();
		if (contextualGroup != null) {
			// paint a small gradient directly below the task area
			Insets ins = this.ribbon.getInsets();
			int topY = ins.top + getTaskbarHeight();
			int bottomY = topY + 5;
			Color hueColor = contextualGroup.getHueColor();
			Paint paint = new GradientPaint(0, topY, FlamingoUtilities
					.getAlphaColor(hueColor,
							(int) (255 * RibbonContextualTaskGroup.HUE_ALPHA)),
					0, bottomY, FlamingoUtilities.getAlphaColor(hueColor, 0));
			g2d.setPaint(paint);
			g2d.clip(outerContour);
			g2d.fillRect(0, topY, width, bottomY - topY + 1);
		}

		g2d.dispose();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.jvnet.flamingo.ribbon.ui.RibbonUI#getContextualGroupTabBounds(org
	 * .jvnet.flamingo.ribbon.RibbonContextualTaskGroup)
	 */
	@Override
	public Rectangle getContextualTaskGroupBounds(
			RibbonContextualTaskGroup group) {
		Rectangle rect = null;
		for (int j = 0; j < group.getTaskCount(); j++) {
			JRibbonTaskToggleButton button = taskToggleButtons.get(group
					.getTask(j));
			if (rect == null)
				rect = button.getBounds();
			else
				rect = rect.union(button.getBounds());
		}
		int buttonGap = getTabButtonGap();
		Point location = SwingUtilities.convertPoint(
				taskToggleButtonsScrollablePanel.getView(), rect.getLocation(),
				ribbon);
		return new Rectangle(location.x - buttonGap / 3, location.y - 1,
				rect.width + buttonGap * 2 / 3 - 1, rect.height + 1);
	}

	/**
	 * Returns the layout gap for the bands in the associated ribbon.
	 * 
	 * @return The layout gap for the bands in the associated ribbon.
	 */
	protected int getBandGap() {
		return 2;
	}

	/**
	 * Returns the layout gap for the tab buttons in the associated ribbon.
	 * 
	 * @return The layout gap for the tab buttons in the associated ribbon.
	 */
	protected int getTabButtonGap() {
		return 6;
	}

	/**
	 * Invoked by <code>installUI</code> to create a layout manager object to
	 * manage the {@link JRibbon}.
	 * 
	 * @return a layout manager object
	 */
	protected LayoutManager createLayoutManager() {
		return new RibbonLayout();
	}

	/**
	 * Invoked by <code>installUI</code> to create a layout manager object to
	 * manage the {@link JRibbon} taskbar.
	 * 
	 * @return a layout manager object
	 */
	protected LayoutManager createTaskbarLayoutManager() {
		return new TaskbarLayout();
	}

	/**
	 * Returns the height of the taskbar area.
	 * 
	 * @return The height of the taskbar area.
	 */
	public int getTaskbarHeight() {
		return 24;
	}

	/**
	 * Returns the height of the task toggle button area.
	 * 
	 * @return The height of the task toggle button area.
	 */
	public int getTaskToggleButtonHeight() {
		return 22;
	}

	/**
	 * Layout for the ribbon.
	 * 
	 * @author Kirill Grouchnikov
	 */
	private class RibbonLayout implements LayoutManager {
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
		 * java.awt.Component)
		 */
		public void addLayoutComponent(String name, Component c) {
		}

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

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
		 */
		public Dimension preferredLayoutSize(Container c) {
			Insets ins = c.getInsets();
			int maxPrefBandHeight = 0;
			boolean isRibbonMinimized = ribbon.isMinimized();
			if (!isRibbonMinimized) {
				if (ribbon.getTaskCount() > 0) {
					RibbonTask selectedTask = ribbon.getSelectedTask();
					for (AbstractRibbonBand<?> ribbonBand : selectedTask
							.getBands()) {
						int bandPrefHeight = ribbonBand.getPreferredSize().height;
						Insets bandInsets = ribbonBand.getInsets();
						maxPrefBandHeight = Math.max(maxPrefBandHeight,
								bandPrefHeight + bandInsets.top
										+ bandInsets.bottom);
					}
				}
			}

			int extraHeight = getTaskToggleButtonHeight();
			if (!isUsingTitlePane())
				extraHeight += getTaskbarHeight();
			int prefHeight = maxPrefBandHeight + extraHeight + ins.top
					+ ins.bottom;
			// System.out.println("Ribbon pref = " + prefHeight);
			return new Dimension(c.getWidth(), prefHeight);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
		 */
		public Dimension minimumLayoutSize(Container c) {
			// go over all ribbon bands and sum the width
			// of ribbon buttons (of collapsed state)
			Insets ins = c.getInsets();
			int width = 0;
			int maxMinBandHeight = 0;
			int gap = getBandGap();

			int extraHeight = getTaskToggleButtonHeight();
			if (!isUsingTitlePane())
				extraHeight += getTaskbarHeight();

			if (ribbon.getTaskCount() > 0) {
				boolean isRibbonMinimized = ribbon.isMinimized();
				// minimum is when all the tasks are collapsed
				RibbonTask selectedTask = ribbon.getSelectedTask();
				for (AbstractRibbonBand ribbonBand : selectedTask.getBands()) {
					int bandPrefHeight = ribbonBand.getMinimumSize().height;
					Insets bandInsets = ribbonBand.getInsets();
					RibbonBandUI bandUI = ribbonBand.getUI();
					width += bandUI.getPreferredCollapsedWidth();
					if (!isRibbonMinimized) {
						maxMinBandHeight = Math.max(maxMinBandHeight,
								bandPrefHeight + bandInsets.top
										+ bandInsets.bottom);
					}
				}
				// add inter-band gaps
				width += gap * (selectedTask.getBandCount() - 1);
			} else {
				// fix for issue 44 (empty ribbon)
				width = 50;
			}
			return new Dimension(width, maxMinBandHeight + extraHeight
					+ ins.top + ins.bottom);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
		 */
		public void layoutContainer(Container c) {
			// System.out.println("Ribbon real = " + c.getHeight());

			Insets ins = c.getInsets();
			int tabButtonGap = getTabButtonGap();

			boolean ltr = ribbon.getComponentOrientation().isLeftToRight();

			// the top row - task bar components
			int width = c.getWidth();
			int taskbarHeight = getTaskbarHeight();
			int y = ins.top;

			boolean isUsingTitlePane = isUsingTitlePane();
			// handle taskbar only if it is not marked
			if (!isUsingTitlePane) {
				taskBarPanel.removeAll();
				for (Component regComp : ribbon.getTaskbarComponents()) {
					taskBarPanel.add(regComp);
				}
				// taskbar takes all available width
				taskBarPanel.setBounds(ins.left, ins.top, width - ins.left
						- ins.right, taskbarHeight);
				y += taskbarHeight;
			} else {
				taskBarPanel.setBounds(0, 0, 0, 0);
			}

			int taskToggleButtonHeight = getTaskToggleButtonHeight();

			int x = ltr ? ins.left : width - ins.right;
			// the application menu button
			int appMenuButtonSize = taskbarHeight + taskToggleButtonHeight;
			if (!isUsingTitlePane) {
				applicationMenuButton
						.setVisible(ribbon.getApplicationMenu() != null);
				if (ribbon.getApplicationMenu() != null) {
					if (ltr) {
						applicationMenuButton.setBounds(x, ins.top,
								appMenuButtonSize, appMenuButtonSize);
					} else {
						applicationMenuButton.setBounds(x - appMenuButtonSize,
								ins.top, appMenuButtonSize, appMenuButtonSize);
					}
				}
			} else {
				applicationMenuButton.setVisible(false);
			}
			x = ltr ? x + 2 : x - 2;
			if (FlamingoUtilities.getApplicationMenuButton(SwingUtilities
					.getWindowAncestor(ribbon)) != null) {
				x = ltr ? x + appMenuButtonSize : x - appMenuButtonSize;
			}

			// the help button
			if (helpButton != null) {
				Dimension preferred = helpButton.getPreferredSize();
				if (ltr) {
					helpButton.setBounds(width - ins.right - preferred.width,
							y, preferred.width, preferred.height);
				} else {
					helpButton.setBounds(ins.left, y, preferred.width,
							preferred.height);
				}
			}

			// task buttons
			if (ltr) {
				int taskButtonsWidth = (helpButton != null) ? (helpButton
						.getX()
						- tabButtonGap - x) : (c.getWidth() - ins.right - x);
				taskToggleButtonsScrollablePanel.setBounds(x, y,
						taskButtonsWidth, taskToggleButtonHeight);
			} else {
				int taskButtonsWidth = (helpButton != null) ? (x - tabButtonGap
						- helpButton.getX() - helpButton.getWidth())
						: (x - ins.left);
				taskToggleButtonsScrollablePanel.setBounds(
						x - taskButtonsWidth, y, taskButtonsWidth,
						taskToggleButtonHeight);
			}

			TaskToggleButtonsHostPanel taskToggleButtonsHostPanel = taskToggleButtonsScrollablePanel
					.getView();
			int taskToggleButtonsHostPanelMinWidth = taskToggleButtonsHostPanel
					.getMinimumSize().width;
			taskToggleButtonsHostPanel.setPreferredSize(new Dimension(
					taskToggleButtonsHostPanelMinWidth,
					taskToggleButtonsScrollablePanel.getBounds().height));
			taskToggleButtonsScrollablePanel.doLayout();

			y += taskToggleButtonHeight;

			int extraHeight = taskToggleButtonHeight;
			if (!isUsingTitlePane)
				extraHeight += taskbarHeight;

			if (bandScrollablePanel.getParent() == ribbon) {
				if (!ribbon.isMinimized() && (ribbon.getTaskCount() > 0)) {
					// y += ins.top;
					Insets bandInsets = (ribbon.getSelectedTask()
							.getBandCount() == 0) ? new Insets(0, 0, 0, 0)
							: ribbon.getSelectedTask().getBand(0).getInsets();
					bandScrollablePanel.setBounds(1 + ins.left, y
							+ bandInsets.top, c.getWidth() - 2 * ins.left - 2
							* ins.right - 1, c.getHeight() - extraHeight
							- ins.top - ins.bottom - bandInsets.top
							- bandInsets.bottom);
					// System.out.println("Scrollable : "
					// + bandScrollablePanel.getBounds());
					BandHostPanel bandHostPanel = bandScrollablePanel.getView();
					int bandHostPanelMinWidth = bandHostPanel.getMinimumSize().width;
					bandHostPanel.setPreferredSize(new Dimension(
							bandHostPanelMinWidth, bandScrollablePanel
									.getBounds().height));
					bandScrollablePanel.doLayout();
					bandHostPanel.doLayout();
				} else {
					bandScrollablePanel.setBounds(0, 0, 0, 0);
				}
			}
		}
	}

	/**
	 * Layout for the task bar.
	 * 
	 * @author Kirill Grouchnikov
	 */
	private class TaskbarLayout implements LayoutManager {
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
		 * java.awt.Component)
		 */
		public void addLayoutComponent(String name, Component c) {
		}

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

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
		 */
		public Dimension preferredLayoutSize(Container c) {
			Insets ins = c.getInsets();
			int pw = 0;
			int gap = getBandGap();
			for (Component regComp : ribbon.getTaskbarComponents()) {
				pw += regComp.getPreferredSize().width;
				pw += gap;
			}
			return new Dimension(pw + ins.left + ins.right, getTaskbarHeight()
					+ ins.top + ins.bottom);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
		 */
		public Dimension minimumLayoutSize(Container c) {
			return this.preferredLayoutSize(c);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
		 */
		public void layoutContainer(Container c) {
			Insets ins = c.getInsets();
			int gap = getBandGap();

			boolean ltr = c.getComponentOrientation().isLeftToRight();

			if (ltr) {
				int x = ins.left + 1;
				if (applicationMenuButton.isVisible()) {
					x += (applicationMenuButton.getX() + applicationMenuButton
							.getWidth());
				}

				for (Component regComp : ribbon.getTaskbarComponents()) {
					int pw = regComp.getPreferredSize().width;
					regComp.setBounds(x, ins.top + 1, pw, c.getHeight()
							- ins.top - ins.bottom - 2);
					x += (pw + gap);
				}
			} else {
				int x = c.getWidth() - ins.right - 1;
				if (applicationMenuButton.isVisible()) {
					x = applicationMenuButton.getX() - 1;
				}

				for (Component regComp : ribbon.getTaskbarComponents()) {
					int pw = regComp.getPreferredSize().width;
					regComp.setBounds(x - pw, ins.top + 1, pw, c.getHeight()
							- ins.top - ins.bottom - 2);
					x -= (pw + gap);
				}
			}
		}
	}

	/**
	 * The taskbar panel that holds the {@link JRibbon#getTaskbarComponents()}.
	 * 
	 * @author Kirill Grouchnikov
	 */
	private class TaskbarPanel extends JPanel {
		/**
		 * Creates the new taskbar panel.
		 */
		public TaskbarPanel() {
			super();
			this.setOpaque(false);
			this.setBorder(new EmptyBorder(1, 0, 1, 0));
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
		 */
		@Override
		protected void paintComponent(Graphics g) {
			Shape contour = getOutline(this);

			Graphics2D g2d = (Graphics2D) g.create();
			g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
					RenderingHints.VALUE_ANTIALIAS_ON);
			RenderingUtils.installDesktopHints(g2d);

			if (contour != null) {
				g2d.setComposite(AlphaComposite.SrcOver.derive(0.6f));
				g2d.setColor(FlamingoUtilities.getColor(Color.lightGray
						.brighter(), "Panel.background"));
				g2d.fill(contour);
				g2d.setColor(FlamingoUtilities.getBorderColor().darker());
				g2d.draw(contour);
			}

			boolean ltr = getComponentOrientation().isLeftToRight();
			int maxX = 0;
			int minX = getWidth();
			if (this.getComponentCount() == 0) {
				maxX = 1;
				minX = getWidth() - 1;
				if (applicationMenuButton.isVisible()) {
					maxX += applicationMenuButton.getX()
							+ applicationMenuButton.getWidth();
					minX = applicationMenuButton.getX() - 1;
				}
			} else {
				for (int i = 0; i < this.getComponentCount(); i++) {
					Component taskBarComp = this.getComponent(i);
					maxX = Math.max(maxX, taskBarComp.getX()
							+ taskBarComp.getWidth());
					minX = Math.min(minX, taskBarComp.getX());
				}
			}
			int height = getHeight();
			if (ltr) {
				g2d.drawLine(maxX, height - 1, getWidth(), height - 1);
			} else {
				g2d.drawLine(0, height - 1, minX, height - 1);
			}

			int contourMaxX = (contour != null) ? (int) contour.getBounds2D()
					.getMaxX() + 6 : 6;
			int contourMinX = (contour != null) ? (int) contour.getBounds2D()
					.getMinX() - 6 : 6;

			// contextual task group headers
			if (!isShowingScrollsForTaskToggleButtons()) {
				g2d.setComposite(AlphaComposite.SrcOver);
				// the taskbar panel is not at the zero X coordinate of the
				// ribbon
				g2d.translate(-this.getBounds().x, 0);
				for (int i = 0; i < ribbon.getContextualTaskGroupCount(); i++) {
					RibbonContextualTaskGroup taskGroup = ribbon
							.getContextualTaskGroup(i);
					if (!ribbon.isVisible(taskGroup))
						continue;
					Rectangle taskGroupBounds = getContextualTaskGroupBounds(taskGroup);

					Color hueColor = taskGroup.getHueColor();
					Paint paint = new GradientPaint(
							0,
							0,
							FlamingoUtilities.getAlphaColor(hueColor, 0),
							0,
							height,
							FlamingoUtilities
									.getAlphaColor(
											hueColor,
											(int) (255 * RibbonContextualTaskGroup.HUE_ALPHA)));
					// translucent gradient paint
					g2d.setPaint(paint);
					int startX = ltr ? taskGroupBounds.x : Math.min(
							contourMinX, taskGroupBounds.x);
					int width = ltr ? taskGroupBounds.x + taskGroupBounds.width
							- startX : Math.min(taskGroupBounds.x
							+ taskGroupBounds.width, contourMinX)
							- startX;

					if (width > 0) {
						g2d.fillRect(startX, 0, width, height);
						// and a solid line at the bottom
						g2d.setColor(hueColor);
						g2d.drawLine(startX + 1, height - 1, startX + width,
								height - 1);

						// task group title
						g2d.setColor(FlamingoUtilities.getColor(Color.black,
								"Button.foreground"));
						FontMetrics fm = this.getFontMetrics(ribbon.getFont());
						int yOffset = (height + fm.getHeight()) / 2
								- fm.getDescent();
						int availableTextWidth = width - 10;
						String titleToShow = taskGroup.getTitle();
						if (fm.stringWidth(titleToShow) > availableTextWidth) {
							while (true) {
								if (titleToShow.length() == 0)
									break;
								if (fm.stringWidth(titleToShow + "...") <= availableTextWidth)
									break;
								titleToShow = titleToShow.substring(0,
										titleToShow.length() - 1);
							}
							titleToShow += "...";
						}
						if (ltr) {
							BasicGraphicsUtils.drawString(g2d, titleToShow, -1,
									startX + 5, yOffset);
						} else {
							BasicGraphicsUtils.drawString(g2d, titleToShow, -1,
									startX + width - 5
											- fm.stringWidth(titleToShow),
									yOffset);
						}

						// separator lines
						Color color = FlamingoUtilities.getBorderColor();
						g2d.setPaint(new GradientPaint(0, 0, FlamingoUtilities
								.getAlphaColor(color, 0), 0, height, color));
						// left line
						g2d.drawLine(startX, 0, startX, height);
						// right line
						g2d.drawLine(startX + width, 0, startX + width, height);
					}
				}
			}

			g2d.dispose();

		}

		/**
		 * Returns the outline of this taskbar panel.
		 * 
		 * @param insets
		 *            Insets.
		 * @return The outline of this taskbar panel.
		 */
		protected Shape getOutline(TaskbarPanel taskbarPanel) {
			double height = this.getHeight() - 1;
			boolean ltr = taskbarPanel.getComponentOrientation()
					.isLeftToRight();
			if (this.getComponentCount() == 0) {
				if (applicationMenuButton.isVisible()) {
					// no taskbar components
					if (ltr) {
						int x = 1;
						if (applicationMenuButton.isVisible()) {
							x += applicationMenuButton.getX()
									+ applicationMenuButton.getWidth();
						}
						return new Arc2D.Double(x - 1 - 2 * height, 0,
								2 * height, 2 * height, 0, 90, Arc2D.OPEN);
					} else {
						int x = taskbarPanel.getWidth() - 1;
						if (applicationMenuButton.isVisible()) {
							x = applicationMenuButton.getX() - 1;
						}
						return new Arc2D.Double(x + 1, 0, 2 * height,
								2 * height, 90, 90, Arc2D.OPEN);
					}
				} else {
					return null;
				}
			} else {
				int minX = this.getWidth();
				int maxX = 0;
				for (int i = 0; i < this.getComponentCount(); i++) {
					Component taskBarComp = this.getComponent(i);
					minX = Math.min(minX, taskBarComp.getX());
					maxX = Math.max(maxX, taskBarComp.getX()
							+ taskBarComp.getWidth());
				}

				float radius = (float) height / 2.0f;

				GeneralPath outline = new GeneralPath();

				if (ltr) {
					// top left corner
					if (applicationMenuButton.isVisible()) {
						outline.moveTo(minX + 5 - 2 * radius, 0);
					} else {
						outline.moveTo(minX - 1, 0);
					}
					// top right corner
					outline.lineTo(maxX, 0);
					// right arc
					outline.append(new Arc2D.Double(maxX - radius, 0, height,
							height, 90, -180, Arc2D.OPEN), true);
					// bottom left corner
					outline.lineTo(minX - 1, height);
					if (applicationMenuButton.isVisible()) {
						// left arc
						outline.append(new Arc2D.Double(minX - 1 - 2 * height,
								0, 2 * height, 2 * height, 0, 90, Arc2D.OPEN),
								true);
					} else {
						outline.lineTo(minX - 1, 0);
					}
				} else {
					// top right corner
					if (applicationMenuButton.isVisible()) {
						outline.moveTo(maxX - 5 + 2 * radius, 0);
					} else {
						outline.moveTo(maxX - 1, 0);
					}
					// top left corner
					outline.lineTo(minX, 0);
					// left arc
					outline.append(new Arc2D.Double(minX - radius, 0, height,
							height, 90, 180, Arc2D.OPEN), true);
					// bottom right corner
					outline.lineTo(maxX - 1, height);
					if (applicationMenuButton.isVisible()) {
						outline.append(new Arc2D.Double(maxX - 1, 0,
								2 * height, 2 * height, 180, -90, Arc2D.OPEN),
								true);
					} else {
						outline.lineTo(maxX + 1, 0);
					}

				}

				return outline;
			}
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see javax.swing.JComponent#getPreferredSize()
		 */
		@Override
		public Dimension getPreferredSize() {
			Dimension result = super.getPreferredSize();
			return new Dimension(result.width + result.height / 2,
					result.height);
		}
	}

	protected static class BandHostPanel extends JPanel {
	}

	/**
	 * Layout for the band host panel.
	 * 
	 * @author Kirill Grouchnikov
	 */
	private class BandHostPanelLayout implements LayoutManager {
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
		 * java.awt.Component)
		 */
		public void addLayoutComponent(String name, Component c) {
		}

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

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
		 */
		public Dimension preferredLayoutSize(Container c) {
			// Insets ins = c.getInsets();
			int maxPrefBandHeight = 0;
			if (ribbon.getTaskCount() > 0) {
				RibbonTask selectedTask = ribbon.getSelectedTask();
				for (AbstractRibbonBand<?> ribbonBand : selectedTask.getBands()) {
					int bandPrefHeight = ribbonBand.getPreferredSize().height;
					Insets bandInsets = ribbonBand.getInsets();
					maxPrefBandHeight = Math
							.max(maxPrefBandHeight, bandPrefHeight
									+ bandInsets.top + bandInsets.bottom);
				}
			}

			return new Dimension(c.getWidth(), maxPrefBandHeight);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
		 */
		public Dimension minimumLayoutSize(Container c) {
			// go over all ribbon bands and sum the width
			// of ribbon buttons (of collapsed state)
			// Insets ins = c.getInsets();
			int width = 0;
			int maxMinBandHeight = 0;
			int gap = getBandGap();

			// minimum is when all the tasks are collapsed
			RibbonTask selectedTask = ribbon.getSelectedTask();
			// System.out.println(selectedTask.getTitle() + " min width");
			for (AbstractRibbonBand ribbonBand : selectedTask.getBands()) {
				int bandPrefHeight = ribbonBand.getMinimumSize().height;
				Insets bandInsets = ribbonBand.getInsets();
				RibbonBandUI bandUI = ribbonBand.getUI();
				int preferredCollapsedWidth = bandUI
						.getPreferredCollapsedWidth()
						+ bandInsets.left + bandInsets.right;
				width += preferredCollapsedWidth;
				// System.out.println("\t" + ribbonBand.getTitle() + ":" +
				// preferredCollapsedWidth);
				maxMinBandHeight = Math.max(maxMinBandHeight, bandPrefHeight
				// + bandInsets.top + bandInsets.bottom
						);
			}
			// add inter-band gaps
			width += gap * (selectedTask.getBandCount() + 1);
			// System.out.println("\t" + gap + "*" +
			// (selectedTask.getBandCount() + 1));

			// System.out.println(selectedTask.getTitle() + " min width:" +
			// width);

			// System.out.println("Returning min height of " +
			// maxMinBandHeight);

			return new Dimension(width, maxMinBandHeight);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
		 */
		public void layoutContainer(Container c) {
			// System.err.println("Layout of band host panel " + c.getWidth() +
			// ":" + c.getHeight());
			int bandGap = getBandGap();

			// the top row - task bar components
			int x = 0;
			int y = 0;

			RibbonTask selectedTask = ribbon.getSelectedTask();
			if (selectedTask == null)
				return;

			// check that the resize policies are still consistent
			for (AbstractRibbonBand<?> band : selectedTask.getBands()) {
				FlamingoUtilities.checkResizePoliciesConsistency(band);
			}

			// start with the most "permissive" resize policy for each band
			for (AbstractRibbonBand<?> band : selectedTask.getBands()) {
				List<RibbonBandResizePolicy> policies = band
						.getResizePolicies();
				RibbonBandResizePolicy last = policies.get(0);
				band.setCurrentResizePolicy(last);
			}

			int availableBandHeight = c.getHeight();
			int availableWidth = c.getWidth();
			if (selectedTask.getBandCount() > 0) {
				RibbonBandResizeSequencingPolicy resizeSequencingPolicy = selectedTask
						.getResizeSequencingPolicy();
				resizeSequencingPolicy.reset();
				AbstractRibbonBand<?> currToTakeFrom = resizeSequencingPolicy
						.next();
				while (true) {
					// check whether all bands have the current resize
					// policy as their last (most restrictive) registered policy
					boolean noMore = true;
					for (AbstractRibbonBand<?> band : selectedTask.getBands()) {
						RibbonBandResizePolicy currentResizePolicy = band
								.getCurrentResizePolicy();
						List<RibbonBandResizePolicy> resizePolicies = band
								.getResizePolicies();
						if (currentResizePolicy != resizePolicies
								.get(resizePolicies.size() - 1)) {
							noMore = false;
							break;
						}
					}
					if (noMore)
						break;

					// get the current preferred width of the bands
					int totalWidth = 0;
					// System.out.println("Iteration");
					for (AbstractRibbonBand<?> ribbonBand : selectedTask
							.getBands()) {
						RibbonBandResizePolicy currentResizePolicy = ribbonBand
								.getCurrentResizePolicy();

						Insets ribbonBandInsets = ribbonBand.getInsets();
						AbstractBandControlPanel controlPanel = ribbonBand
								.getControlPanel();
						if (controlPanel == null) {
							controlPanel = ribbonBand.getPopupRibbonBand()
									.getControlPanel();
						}
						Insets controlPanelInsets = controlPanel.getInsets();
						int controlPanelGap = controlPanel.getUI()
								.getLayoutGap();
						int ribbonBandHeight = availableBandHeight
								- ribbonBandInsets.top
								- ribbonBandInsets.bottom;
						int availableHeight = ribbonBandHeight
								- ribbonBand.getUI().getBandTitleHeight();
						if (controlPanel != null) {
							availableHeight = availableHeight
									- controlPanelInsets.top
									- controlPanelInsets.bottom;
						}
						int preferredWidth = currentResizePolicy
								.getPreferredWidth(availableHeight,
										controlPanelGap)
								+ ribbonBandInsets.left
								+ ribbonBandInsets.right;
						totalWidth += preferredWidth + bandGap;
						// System.out.println("\t"
						// + ribbonBand.getTitle()
						// + ":"
						// + currentResizePolicy.getClass()
						// .getSimpleName() + ":" + preferredWidth
						// + " under " + availableHeight + " with "
						// + controlPanel.getComponentCount()
						// + " children");
					}
					// System.out.println("\t:Total:" + totalWidth + "("
					// + availableWidth + ")");
					// System.out.println("\n");
					if (totalWidth < availableWidth)
						break;

					// try to take from the currently rotating band
					List<RibbonBandResizePolicy> policies = currToTakeFrom
							.getResizePolicies();
					int currPolicyIndex = policies.indexOf(currToTakeFrom
							.getCurrentResizePolicy());
					if (currPolicyIndex == (policies.size() - 1)) {
						// nothing to take
					} else {
						currToTakeFrom.setCurrentResizePolicy(policies
								.get(currPolicyIndex + 1));
					}
					currToTakeFrom = resizeSequencingPolicy.next();
				}
			}

			boolean ltr = c.getComponentOrientation().isLeftToRight();
			x = ltr ? 1 : c.getWidth() - 1;
			// System.out.println("Will get [" + availableWidth + "]:");
			for (AbstractRibbonBand<?> ribbonBand : selectedTask.getBands()) {
				Insets ribbonBandInsets = ribbonBand.getInsets();
				RibbonBandResizePolicy currentResizePolicy = ribbonBand
						.getCurrentResizePolicy();
				AbstractBandControlPanel controlPanel = ribbonBand
						.getControlPanel();
				if (controlPanel == null) {
					controlPanel = ribbonBand.getPopupRibbonBand()
							.getControlPanel();
				}
				Insets controlPanelInsets = controlPanel.getInsets();
				int controlPanelGap = controlPanel.getUI().getLayoutGap();
				int ribbonBandHeight = availableBandHeight;
				// - ribbonBandInsets.top - ribbonBandInsets.bottom;
				int availableHeight = ribbonBandHeight - ribbonBandInsets.top
						- ribbonBandInsets.bottom
						- ribbonBand.getUI().getBandTitleHeight();
				if (controlPanelInsets != null) {
					availableHeight = availableHeight - controlPanelInsets.top
							- controlPanelInsets.bottom;
				}

				int requiredBandWidth = currentResizePolicy.getPreferredWidth(
						availableHeight, controlPanelGap)
						+ ribbonBandInsets.left + ribbonBandInsets.right;

				if (ltr) {
					ribbonBand.setBounds(x, y, requiredBandWidth,
							ribbonBandHeight);
				} else {
					ribbonBand.setBounds(x - requiredBandWidth, y,
							requiredBandWidth, ribbonBandHeight);
				}

				// System.out.println("\t" + ribbonBand.getTitle() + ":"
				// + currentResizePolicy.getClass().getSimpleName() + ":"
				// + requiredBandWidth + " under " + ribbonBandHeight);

				if (ribbonBand.getHeight() > 0) {
					ribbonBand.doLayout();
				}

				if (ltr) {
					x += (requiredBandWidth + bandGap);
				} else {
					x -= (requiredBandWidth + bandGap);
				}

			}
			// System.out.println();
		}
	}

	protected class TaskToggleButtonsHostPanel extends JPanel {
		public static final String IS_SQUISHED = "flamingo.internal.ribbon.taskToggleButtonsHostPanel.isSquished";

		@Override
		protected void paintComponent(Graphics g) {
			super.paintComponent(g);

			this.paintContextualTaskGroupsOutlines(g);
			if (Boolean.TRUE.equals(this.getClientProperty(IS_SQUISHED))) {
				this.paintTaskOutlines(g);
			}
		}

		protected void paintTaskOutlines(Graphics g) {
			Graphics2D g2d = (Graphics2D) g.create();
			Color color = FlamingoUtilities.getBorderColor();
			Paint paint = new GradientPaint(0, 0, FlamingoUtilities
					.getAlphaColor(color, 0), 0, getHeight(), color);
			g2d.setPaint(paint);

			Set<RibbonTask> tasksWithTrailingSeparators = new HashSet<RibbonTask>();
			// add all regular tasks except the last
			for (int i = 0; i < ribbon.getTaskCount() - 1; i++) {
				RibbonTask task = ribbon.getTask(i);
				tasksWithTrailingSeparators.add(task);
				// System.out.println("Added " + task.getTitle());
			}
			// add all tasks of visible contextual groups except last task in
			// each group
			for (int i = 0; i < ribbon.getContextualTaskGroupCount(); i++) {
				RibbonContextualTaskGroup group = ribbon
						.getContextualTaskGroup(i);
				if (ribbon.isVisible(group)) {
					for (int j = 0; j < group.getTaskCount() - 1; j++) {
						RibbonTask task = group.getTask(j);
						tasksWithTrailingSeparators.add(task);
						// System.out.println("Added " + task.getTitle());
					}
				}
			}

			for (RibbonTask taskWithTrailingSeparator : tasksWithTrailingSeparators) {
				JRibbonTaskToggleButton taskToggleButton = taskToggleButtons
						.get(taskWithTrailingSeparator);
				Rectangle bounds = taskToggleButton.getBounds();
				int x = bounds.x + bounds.width + getTabButtonGap() / 2 - 1;
				g2d.drawLine(x, 0, x, getHeight());
				// System.out.println(taskWithTrailingSeparator.getTitle() + ":"
				// + x);
			}

			g2d.dispose();
		}

		/**
		 * Paints the outline of the contextual task groups.
		 * 
		 * @param g
		 *            Graphics context.
		 */
		protected void paintContextualTaskGroupsOutlines(Graphics g) {
			for (int i = 0; i < ribbon.getContextualTaskGroupCount(); i++) {
				RibbonContextualTaskGroup group = ribbon
						.getContextualTaskGroup(i);
				if (!ribbon.isVisible(group))
					continue;
				// go over all the tasks in this group and compute the union
				// of bounds of the matching tab buttons
				Rectangle rect = getContextualTaskGroupBounds(group);
				rect.setLocation(SwingUtilities.convertPoint(ribbon, rect
						.getLocation(), taskToggleButtonsScrollablePanel
						.getView()));
				this.paintContextualTaskGroupOutlines(g, group, rect);
			}
		}

		/**
		 * Paints the outline of the specified contextual task group.
		 * 
		 * @param g
		 *            Graphics context.
		 * @param group
		 *            Contextual task group.
		 * @param groupBounds
		 *            Contextual task group bounds.
		 */
		protected void paintContextualTaskGroupOutlines(Graphics g,
				RibbonContextualTaskGroup group, Rectangle groupBounds) {
			Graphics2D g2d = (Graphics2D) g.create();
			Color color = FlamingoUtilities.getBorderColor();

			Paint paint = new GradientPaint(0, groupBounds.y, color, 0,
					groupBounds.y + groupBounds.height, FlamingoUtilities
							.getAlphaColor(color, 0));
			g2d.setPaint(paint);
			// left line
			int x = groupBounds.x;
			g2d.drawLine(x, groupBounds.y, x, groupBounds.y
					+ groupBounds.height);
			// right line
			x = groupBounds.x + groupBounds.width;
			g2d.drawLine(x, groupBounds.y, x, groupBounds.y
					+ groupBounds.height);

			g2d.dispose();
		}

		// @Override
		// protected void paintComponent(Graphics g) {
		// //g.setColor(new Color(255, 200, 200));
		// //g.fillRect(0, 0, getWidth(), getHeight());
		// // g.setColor(Color.blue.darker());
		// // g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
		// //System.err.println(System.currentTimeMillis() + ": tt-repaint");
		// }
		//		
		// @Override
		// protected void paintBorder(Graphics g) {
		// }
		//
		// @Override
		// public void setBounds(int x, int y, int width, int height) {
		// System.out.println("Host : " + x + ":" + y + ":" + width + ":"
		// + height);
		// super.setBounds(x, y, width, height);
		// }
	}

	/**
	 * Layout for the band host panel.
	 * 
	 * @author Kirill Grouchnikov
	 */
	private class TaskToggleButtonsHostPanelLayout implements LayoutManager {
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#addLayoutComponent(java.lang.String,
		 * java.awt.Component)
		 */
		public void addLayoutComponent(String name, Component c) {
		}

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

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#preferredLayoutSize(java.awt.Container)
		 */
		public Dimension preferredLayoutSize(Container c) {
			int tabButtonGap = getTabButtonGap();
			int taskToggleButtonHeight = getTaskToggleButtonHeight();

			int totalTaskButtonsWidth = 0;
			List<RibbonTask> visibleTasks = getCurrentlyShownRibbonTasks();
			for (RibbonTask task : visibleTasks) {
				JRibbonTaskToggleButton tabButton = taskToggleButtons.get(task);
				int pw = tabButton.getPreferredSize().width;
				totalTaskButtonsWidth += (pw + tabButtonGap);
			}

			return new Dimension(totalTaskButtonsWidth, taskToggleButtonHeight);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#minimumLayoutSize(java.awt.Container)
		 */
		public Dimension minimumLayoutSize(Container c) {
			int tabButtonGap = getTabButtonGap();
			int taskToggleButtonHeight = getTaskToggleButtonHeight();

			int totalTaskButtonsWidth = 0;
			List<RibbonTask> visibleTasks = getCurrentlyShownRibbonTasks();
			for (RibbonTask task : visibleTasks) {
				JRibbonTaskToggleButton tabButton = taskToggleButtons.get(task);
				int pw = tabButton.getMinimumSize().width;
				totalTaskButtonsWidth += (pw + tabButtonGap);
			}

			return new Dimension(totalTaskButtonsWidth, taskToggleButtonHeight);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
		 */
		public void layoutContainer(Container c) {
			int y = 0;
			int tabButtonGap = getTabButtonGap();
			int taskToggleButtonHeight = getTaskToggleButtonHeight();

			int totalPrefWidth = 0;
			int totalMinWidth = 0;
			List<RibbonTask> visibleTasks = getCurrentlyShownRibbonTasks();
			Map<JRibbonTaskToggleButton, Integer> diffMap = new HashMap<JRibbonTaskToggleButton, Integer>();
			int totalDiff = 0;
			for (RibbonTask task : visibleTasks) {
				JRibbonTaskToggleButton tabButton = taskToggleButtons.get(task);
				int pw = tabButton.getPreferredSize().width;
				int mw = tabButton.getMinimumSize().width;
				diffMap.put(tabButton, pw - mw);
				totalDiff += (pw - mw);
				totalPrefWidth += pw;
				totalMinWidth += mw;
			}
			totalPrefWidth += tabButtonGap * visibleTasks.size();
			totalMinWidth += tabButtonGap * visibleTasks.size();

			boolean ltr = c.getComponentOrientation().isLeftToRight();

			// do we have enough width?
			if (totalPrefWidth <= c.getWidth()) {
				// compute bounds for the tab buttons
				int x = ltr ? 0 : c.getWidth();
				for (RibbonTask task : visibleTasks) {
					JRibbonTaskToggleButton tabButton = taskToggleButtons
							.get(task);
					int pw = tabButton.getPreferredSize().width;
					if (ltr) {
						tabButton.setBounds(x, y + 1, pw,
								taskToggleButtonHeight - 1);
						x += (pw + tabButtonGap);
					} else {
						tabButton.setBounds(x - pw, y + 1, pw,
								taskToggleButtonHeight - 1);
						x -= (pw + tabButtonGap);
					}
					tabButton.setActionRichTooltip(null);
				}
				((JComponent) c).putClientProperty(
						TaskToggleButtonsHostPanel.IS_SQUISHED, null);
			} else {
				if (totalMinWidth > c.getWidth()) {
					throw new IllegalStateException(
							"Available width not enough to host minimized task tab buttons");
				}
				int x = ltr ? 0 : c.getWidth();
				// how much do we need to take from each toggle button?
				int toDistribute = totalPrefWidth - c.getWidth() + 2;
				for (RibbonTask task : visibleTasks) {
					JRibbonTaskToggleButton tabButton = taskToggleButtons
							.get(task);
					int pw = tabButton.getPreferredSize().width;
					int delta = (toDistribute * diffMap.get(tabButton) / totalDiff);
					int finalWidth = pw - delta;
					if (ltr) {
						tabButton.setBounds(x, y + 1, finalWidth,
								taskToggleButtonHeight - 1);
						x += (finalWidth + tabButtonGap);
					} else {
						tabButton.setBounds(x - finalWidth, y + 1, finalWidth,
								taskToggleButtonHeight - 1);
						x -= (finalWidth + tabButtonGap);
					}
					// show the tooltip with the full title
					RichTooltip tooltip = new RichTooltip();
					tooltip.setTitle(task.getTitle());
					tabButton.setActionRichTooltip(tooltip);
				}
				((JComponent) c).putClientProperty(
						TaskToggleButtonsHostPanel.IS_SQUISHED, Boolean.TRUE);
			}
		}
	}

	protected void syncRibbonState() {
		// remove all existing ribbon bands
		BandHostPanel bandHostPanel = this.bandScrollablePanel.getView();
		bandHostPanel.removeAll();

		// remove all the existing task toggle buttons
		TaskToggleButtonsHostPanel taskToggleButtonsHostPanel = this.taskToggleButtonsScrollablePanel
				.getView();
		taskToggleButtonsHostPanel.removeAll();

		// remove the help button
		if (this.helpButton != null) {
			this.ribbon.remove(this.helpButton);
			this.helpButton = null;
		}

		// go over all visible ribbon tasks and create a toggle button
		// for each one of them
		List<RibbonTask> visibleTasks = this.getCurrentlyShownRibbonTasks();
		final RibbonTask selectedTask = this.ribbon.getSelectedTask();
		for (final RibbonTask task : visibleTasks) {
			final JRibbonTaskToggleButton taskToggleButton = new JRibbonTaskToggleButton(
					task);
			taskToggleButton.setKeyTip(task.getKeyTip());
			// wire listener to select the task when the button is
			// selected
			taskToggleButton.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							scrollAndRevealTaskToggleButton(taskToggleButton);

							ribbon.setSelectedTask(task);

							// System.out.println("Button click on "
							// + task.getTitle() + ", ribbon minimized? "
							// + ribbon.isMinimized());

							if (ribbon.isMinimized()) {
								if (Boolean.TRUE.equals(ribbon
										.getClientProperty(JUST_MINIMIZED))) {
									ribbon.putClientProperty(JUST_MINIMIZED,
											null);
									return;
								}

								// special case - do we have this task currently
								// shown in a popup?
								List<PopupPanelManager.PopupInfo> popups = PopupPanelManager
										.defaultManager().getShownPath();
								if (popups.size() > 0) {
									for (PopupPanelManager.PopupInfo popup : popups) {
										if (popup.getPopupOriginator() == taskToggleButton) {
											// hide all popups and return (hides
											// the task popup and does not
											// show any additional popup).
											PopupPanelManager.defaultManager()
													.hidePopups(null);
											return;
										}
									}
								}

								PopupPanelManager.defaultManager().hidePopups(
										null);
								ribbon.remove(bandScrollablePanel);

								int prefHeight = bandScrollablePanel.getView()
										.getPreferredSize().height;
								Insets ins = ribbon.getInsets();
								prefHeight += ins.top + ins.bottom;
								AbstractRibbonBand band = (ribbon
										.getSelectedTask().getBandCount() > 0) ? ribbon
										.getSelectedTask().getBand(0)
										: null;
								if (band != null) {
									Insets bandIns = band.getInsets();
									prefHeight += bandIns.top + bandIns.bottom;
								}

								// System.out.println(prefHeight
								// + ":"
								// + bandScrollablePanel.getView()
								// .getComponentCount());

								JPopupPanel popupPanel = new BandHostPopupPanel(
										bandScrollablePanel, new Dimension(
												ribbon.getWidth(), prefHeight));

								int x = ribbon.getLocationOnScreen().x;
								int y = ribbon.getLocationOnScreen().y
										+ ribbon.getHeight();

								// make sure that the popup stays in
								// bounds
								Rectangle scrBounds = ribbon
										.getGraphicsConfiguration().getBounds();
								int pw = popupPanel.getPreferredSize().width;
								if ((x + pw) > (scrBounds.x + scrBounds.width)) {
									x = scrBounds.x + scrBounds.width - pw;
								}
								int ph = popupPanel.getPreferredSize().height;
								if ((y + ph) > (scrBounds.y + scrBounds.height)) {
									y = scrBounds.y + scrBounds.height - ph;
								}

								// get the popup and show it
								popupPanel.setPreferredSize(new Dimension(
										ribbon.getWidth(), prefHeight));
								Popup popup = PopupFactory.getSharedInstance()
										.getPopup(taskToggleButton, popupPanel,
												x, y);
								PopupPanelManager.PopupListener tracker = new PopupPanelManager.PopupListener() {
									@Override
									public void popupShown(PopupEvent event) {
										JComponent originator = event
												.getPopupOriginator();
										if (originator instanceof JRibbonTaskToggleButton) {
											bandScrollablePanel.doLayout();
											bandScrollablePanel.repaint();
										}
									}

									@Override
									public void popupHidden(PopupEvent event) {
										JComponent originator = event
												.getPopupOriginator();
										if (originator instanceof JRibbonTaskToggleButton) {
											ribbon.add(bandScrollablePanel);
											PopupPanelManager.defaultManager()
													.removePopupListener(this);
											ribbon.revalidate();
											ribbon.doLayout();
											ribbon.repaint();
										}
									}
								};
								PopupPanelManager.defaultManager()
										.addPopupListener(tracker);
								PopupPanelManager.defaultManager().addPopup(
										taskToggleButton, popup, popupPanel);
							}
						}
					});
				}
			});
			// wire listener to toggle ribbon minimization on double
			// mouse click
			taskToggleButton.addMouseListener(new MouseAdapter() {
				@Override
				public void mouseClicked(MouseEvent e) {
					if ((ribbon.getSelectedTask() == task)
							&& (e.getClickCount() == 2)) {
						boolean wasMinimized = ribbon.isMinimized();
						ribbon.setMinimized(!wasMinimized);
						if (!wasMinimized) {
							// fix for issue 69 - mark the ribbon as
							// "just minimized" to prevent the action handler
							// of the toggle button to show the ribbon in
							// popup mode
							ribbon.putClientProperty(JUST_MINIMIZED,
									Boolean.TRUE);
						}
					}
				}
			});
			// set the background hue color on the tab buttons
			// of tasks in contextual groups
			if (task.getContextualGroup() != null) {
				taskToggleButton.setContextualGroupHueColor(task
						.getContextualGroup().getHueColor());
			}

			taskToggleButton.putClientProperty(
					BasicCommandButtonUI.DONT_DISPOSE_POPUPS, Boolean.TRUE);

			this.taskToggleButtonGroup.add(taskToggleButton);
			taskToggleButtonsHostPanel.add(taskToggleButton);
			this.taskToggleButtons.put(task, taskToggleButton);
		}

		JRibbonTaskToggleButton toSelect = this.taskToggleButtons
				.get(selectedTask);
		if (toSelect != null) {
			toSelect.getActionModel().setSelected(true);
		}

		for (int i = 0; i < this.ribbon.getTaskCount(); i++) {
			RibbonTask task = this.ribbon.getTask(i);
			for (AbstractRibbonBand band : task.getBands()) {
				bandHostPanel.add(band);
				band.setVisible(selectedTask == task);
			}
		}
		for (int i = 0; i < this.ribbon.getContextualTaskGroupCount(); i++) {
			RibbonContextualTaskGroup taskGroup = this.ribbon
					.getContextualTaskGroup(i);
			for (int j = 0; j < taskGroup.getTaskCount(); j++) {
				RibbonTask task = taskGroup.getTask(j);
				for (AbstractRibbonBand band : task.getBands()) {
					bandHostPanel.add(band);
					band.setVisible(selectedTask == task);
				}
			}
		}

		ActionListener helpListener = this.ribbon.getHelpActionListener();
		if (helpListener != null) {
			this.helpButton = new JCommandButton("", this.ribbon.getHelpIcon());
			this.helpButton.setDisplayState(CommandButtonDisplayState.SMALL);
			this.helpButton.setCommandButtonKind(CommandButtonKind.ACTION_ONLY);
			this.helpButton.getActionModel().addActionListener(helpListener);
			this.ribbon.add(this.helpButton);
		}

		this.ribbon.revalidate();
		this.ribbon.repaint();
	}

	/**
	 * Returns the list of currently shown ribbon tasks. This method is for
	 * internal use only.
	 * 
	 * @return The list of currently shown ribbon tasks.
	 */
	protected List<RibbonTask> getCurrentlyShownRibbonTasks() {
		List<RibbonTask> result = new ArrayList<RibbonTask>();

		// add all regular tasks
		for (int i = 0; i < this.ribbon.getTaskCount(); i++) {
			RibbonTask task = this.ribbon.getTask(i);
			result.add(task);
		}
		// add all tasks of visible contextual groups
		for (int i = 0; i < this.ribbon.getContextualTaskGroupCount(); i++) {
			RibbonContextualTaskGroup group = this.ribbon
					.getContextualTaskGroup(i);
			if (this.ribbon.isVisible(group)) {
				for (int j = 0; j < group.getTaskCount(); j++) {
					RibbonTask task = group.getTask(j);
					result.add(task);
				}
			}
		}

		return result;
	}

	protected boolean isUsingTitlePane() {
		return Boolean.TRUE.equals(ribbon
				.getClientProperty(IS_USING_TITLE_PANE));
	}

	protected void syncApplicationMenuTips() {
		this.applicationMenuButton.setPopupRichTooltip(this.ribbon
				.getApplicationMenuRichTooltip());
		this.applicationMenuButton.setPopupKeyTip(this.ribbon
				.getApplicationMenuKeyTip());
	}

	@Override
	public boolean isShowingScrollsForTaskToggleButtons() {
		return this.taskToggleButtonsScrollablePanel.isShowingScrollButtons();
	}

	@Override
	public boolean isShowingScrollsForBands() {
		return this.bandScrollablePanel.isShowingScrollButtons();
	}

	public Map<RibbonTask, JRibbonTaskToggleButton> getTaskToggleButtons() {
		return Collections.unmodifiableMap(taskToggleButtons);
	}

	protected static class BandHostPopupPanel extends JPopupPanel {
		/**
		 * The main component of <code>this</code> popup panel. Can be
		 * <code>null</code>.
		 */
		// protected Component component;
		public BandHostPopupPanel(Component component, Dimension originalSize) {
			// this.component = component;
			this.setLayout(new BorderLayout());
			this.add(component, BorderLayout.CENTER);
			// System.out.println("Popup dim is " + originalSize);
			this.setPreferredSize(originalSize);
			this.setSize(originalSize);
		}
	}

	@Override
	public void handleMouseWheelEvent(MouseWheelEvent e) {
		// no mouse wheel scrolling when the ribbon is minimized
		if (ribbon.isMinimized())
			return;

		// get the visible tasks
		final List<RibbonTask> visibleTasks = getCurrentlyShownRibbonTasks();
		if (visibleTasks.size() == 0)
			return;

		int delta = e.getWheelRotation();
		if (delta == 0)
			return;

		// find the index of the currently selected task
		int currSelectedTaskIndex = visibleTasks.indexOf(ribbon
				.getSelectedTask());

		// compute the next task
		if (!ribbon.getComponentOrientation().isLeftToRight())
			delta = -delta;
		int newSelectedTaskIndex = currSelectedTaskIndex
				+ ((delta > 0) ? 1 : -1);
		if (newSelectedTaskIndex < 0)
			return;
		if (newSelectedTaskIndex >= visibleTasks.size())
			return;

		final int indexToSet = newSelectedTaskIndex;
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				ribbon
						.setCursor(Cursor
								.getPredefinedCursor(Cursor.WAIT_CURSOR));
				ribbon.setSelectedTask(visibleTasks.get(indexToSet));
				ribbon.setCursor(Cursor
						.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
			}
		});
	}

	protected void scrollAndRevealTaskToggleButton(
			final JRibbonTaskToggleButton taskToggleButton) {
		// scroll the viewport of the scrollable panel
		// so that the button is fully viewed.
		Point loc = SwingUtilities.convertPoint(taskToggleButton.getParent(),
				taskToggleButton.getLocation(),
				taskToggleButtonsScrollablePanel.getView());
		taskToggleButtonsScrollablePanel.scrollToIfNecessary(loc.x,
				taskToggleButton.getWidth());
	}
}
