/*
 * Copyright (c) 2005-2009 Substance 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 Substance 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.jvnet.substance.utils;

import java.awt.*;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.awt.image.VolatileImage;
import java.net.URL;
import java.util.*;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.CompoundBorder;
import javax.swing.plaf.*;
import javax.swing.text.JTextComponent;

import org.jvnet.lafwidget.LafWidgetUtilities;
import org.jvnet.lafwidget.animation.*;
import org.jvnet.lafwidget.layout.TransitionLayout;
import org.jvnet.lafwidget.utils.TrackableThread;
import org.jvnet.substance.*;
import org.jvnet.substance.api.*;
import org.jvnet.substance.api.SubstanceConstants.*;
import org.jvnet.substance.api.combo.ComboPopupPrototypeCallback;
import org.jvnet.substance.api.tabbed.TabCloseCallback;
import org.jvnet.substance.colorscheme.*;
import org.jvnet.substance.painter.border.SubstanceBorderPainter;
import org.jvnet.substance.painter.decoration.SubstanceDecorationPainter;
import org.jvnet.substance.painter.gradient.SubstanceGradientPainter;
import org.jvnet.substance.shaper.SubstanceButtonShaper;
import org.jvnet.substance.utils.border.SubstanceBorder;
import org.jvnet.substance.utils.combo.SubstanceComboBoxButton;
import org.jvnet.substance.utils.combo.SubstanceComboPopup;
import org.jvnet.substance.utils.icon.*;
import org.jvnet.substance.utils.menu.SubstanceMenu;
import org.jvnet.substance.utils.scroll.SubstanceScrollButton;

/**
 * Various utility functions. This class is <b>for internal use only</b>.
 * 
 * @author Kirill Grouchnikov
 * @author Romain Guy
 */
public class SubstanceCoreUtilities {
	/**
	 * Client property name for the previous component state. The client
	 * property value should be an instance of {@link ComponentState}.
	 */
	private static final String PREV_COMPONENT_STATE = "substancelaf.internal.prevComponentState";

	/**
	 * Client property name for the previous component state. The client
	 * property value should be an instance of {@link ComponentState}.
	 */
	private static final String PREV_SEL_COMPONENT_STATE = "substancelaf.internal.prevSelComponentState";

	/**
	 * Client property name for the next component state. The client property
	 * value should be an instance of {@link ComponentState}.
	 */
	private static final String NEXT_COMPONENT_STATE = "substancelaf.internal.nextComponentState";

	/**
	 * Client property name for the next component state. The client property
	 * value should be an instance of {@link ComponentState}.
	 */
	private static final String NEXT_SEL_COMPONENT_STATE = "substancelaf.internal.nextSelComponentState";

	/**
	 * Client property name for marking components covered by lightweight
	 * popups. This is tracking the fix for issue 297. The client property value
	 * should be an instance of {@link Boolean}.
	 */
	public static final String IS_COVERED_BY_LIGHTWEIGHT_POPUPS = "substancelaf.internal.paint.isCoveredByLightweightPopups";

	/**
	 * Client property name to mark components that use highlights (such as
	 * lists, trees, menus, tables and table headers). The client property value
	 * should be an instance of {@link Boolean}.
	 */
	public static final String USE_HIGHLIGHT = "substancelaf.internal.highlightUse";

	/**
	 * Private constructor. Is here to enforce using static methods only.
	 */
	private SubstanceCoreUtilities() {
	}

	/**
	 * Clips string based on specified font metrics and available width (in
	 * pixels). Returns the clipped string, which contains the beginning and the
	 * end of the input string separated by ellipses (...) in case the string is
	 * too long to fit into the specified width, and the origianl string
	 * otherwise.
	 * 
	 * @param metrics
	 *            Font metrics.
	 * @param availableWidth
	 *            Available width in pixels.
	 * @param fullText
	 *            String to clip.
	 * @return The clipped string, which contains the beginning and the end of
	 *         the input string separated by ellipses (...) in case the string
	 *         is too long to fit into the specified width, and the origianl
	 *         string otherwise.
	 */
	public static String clipString(FontMetrics metrics, int availableWidth,
			String fullText) {

		if (metrics.stringWidth(fullText) <= availableWidth)
			return fullText;

		String ellipses = "...";
		int ellipsesWidth = metrics.stringWidth(ellipses);
		if (ellipsesWidth > availableWidth)
			return "";

		String starter = "";
		String ender = "";

		int w = fullText.length();
		int w2 = (w / 2) + (w % 2);
		String prevTitle = "";
		for (int i = 0; i < w2; i++) {
			String newStarter = starter + fullText.charAt(i);
			String newEnder = ender;
			if ((w - i) > w2)
				newEnder = fullText.charAt(w - i - 1) + newEnder;
			String newTitle = newStarter + ellipses + newEnder;
			if (metrics.stringWidth(newTitle) <= availableWidth) {
				starter = newStarter;
				ender = newEnder;
				prevTitle = newTitle;
				continue;
			}
			return prevTitle;
		}
		return fullText;
	}

	/**
	 * Checks whether the specified button has associated icon.
	 * 
	 * @param button
	 *            Button.
	 * @return If the button has associated icon, <code>true</code> is returned,
	 *         otherwise <code>false</code>.
	 */
	public static boolean hasIcon(AbstractButton button) {
		return (button.getIcon() != null);
	}

	/**
	 * Checks whether the specified button has associated text.
	 * 
	 * @param button
	 *            Button.
	 * @return If the button has associated text, <code>true</code> is returned,
	 *         otherwise <code>false</code>.
	 */
	public static boolean hasText(AbstractButton button) {
		String text = button.getText();
		if ((text != null) && (text.length() > 0)) {
			return true;
		}
		return false;
	}

	/**
	 * Checks and answers if the specified button is in a combo box.
	 * 
	 * @param button
	 *            the button to check
	 * @return <code>true</code> if in combo box, <code>false</code> otherwise
	 */
	public static boolean isComboBoxButton(AbstractButton button) {
		Container parent = button.getParent();
		return (parent != null)
				&& ((parent instanceof JComboBox) || (parent.getParent() instanceof JComboBox));
	}

	/**
	 * Checks and answers if the specified button is in a scroll bar.
	 * 
	 * @param button
	 *            the button to check
	 * @return <code>true</code> if in scroll bar, <code>false</code> otherwise
	 */
	public static boolean isScrollBarButton(AbstractButton button) {
		Container parent = button.getParent();
		return (parent != null)
				&& ((parent instanceof JScrollBar) || (parent.getParent() instanceof JScrollBar));
	}

	/**
	 * Checks and answers if the specified button is in a spinner.
	 * 
	 * @param button
	 *            the button to check
	 * @return <code>true</code> if in spinner, <code>false</code> otherwise
	 */
	public static boolean isSpinnerButton(AbstractButton button) {
		Container parent = button.getParent();
		if (!(button instanceof SubstanceSpinnerButton))
			return false;
		return (parent != null)
				&& ((parent instanceof JSpinner) || (parent.getParent() instanceof JSpinner));
	}

	/**
	 * Checks and answers if the specified button is in a toolbar.
	 * 
	 * @param component
	 *            the button to check
	 * @return <code>true</code> if in toolbar, <code>false</code> otherwise
	 */
	public static boolean isToolBarButton(JComponent component) {
		if (component instanceof SubstanceComboBoxButton)
			return false;
		if (component instanceof SubstanceSpinnerButton)
			return false;
		Container parent = component.getParent();
		return (parent != null)
				&& ((parent instanceof JToolBar) || (parent.getParent() instanceof JToolBar));
	}

	/**
	 * Checks answers if the specified component is a button in a scroll
	 * control, such as scroll bar or tabbed pane (as tab scroller).
	 * 
	 * @param comp
	 *            The component to check
	 * @return <code>true</code> if the specified component is a button in a
	 *         scroll control, <code>false</code> otherwise
	 */
	public static boolean isScrollButton(JComponent comp) {
		return (comp instanceof SubstanceScrollButton);
	}

	/**
	 * Checks whether the specified button never paints its background.
	 * 
	 * @param button
	 *            Button.
	 * @return <code>true</code> if the specified button never paints its
	 *         background, <code>false</code> otherwise.
	 * @see SubstanceLookAndFeel#BUTTON_PAINT_NEVER_PROPERTY
	 */
	public static boolean isButtonNeverPainted(JComponent button) {
		// small optimizations for checkboxes and radio buttons
		if (button instanceof JCheckBox)
			return false;
		if (button instanceof JRadioButton)
			return false;
		Component c = button;
		while (c != null) {
			if (c instanceof JComponent) {
				JComponent jc = (JComponent) c;
				Object prop = jc
						.getClientProperty(SubstanceLookAndFeel.BUTTON_PAINT_NEVER_PROPERTY);
				if (prop != null) {
					if (Boolean.TRUE.equals(prop))
						return true;
					if (Boolean.FALSE.equals(prop))
						return false;
				}
			}
			c = c.getParent();
		}

		return Boolean.TRUE.equals(UIManager
				.get(SubstanceLookAndFeel.BUTTON_PAINT_NEVER_PROPERTY));
	}

	/**
	 * Returns the focus ring kind of the specified component.
	 * 
	 * @param component
	 *            Component.
	 * @return The focus ring kind of the specified component.
	 * @see SubstanceLookAndFeel#FOCUS_KIND
	 */
	public static FocusKind getFocusKind(Component component) {
		while (component != null) {
			if (component instanceof JComponent) {
				JComponent jcomp = (JComponent) component;
				Object jcompFocusKind = jcomp
						.getClientProperty(SubstanceLookAndFeel.FOCUS_KIND);
				if (jcompFocusKind instanceof FocusKind)
					return (FocusKind) jcompFocusKind;
			}
			component = component.getParent();
		}
		Object globalFocusKind = UIManager.get(SubstanceLookAndFeel.FOCUS_KIND);
		if (globalFocusKind instanceof FocusKind)
			return (FocusKind) globalFocusKind;
		return FocusKind.ALL_INNER;
	}

	/**
	 * Returns indication whether the watermark should be drawn on the specified
	 * component.
	 * 
	 * @param component
	 *            Component.
	 * @return <code>true</code> if the watermark should be drawn on the
	 *         specified component, <code>false</code> otherwise.
	 * @see SubstanceLookAndFeel#WATERMARK_VISIBLE
	 */
	public static boolean toDrawWatermark(Component component) {
		Component c = component;
		while (c != null) {
			if (c instanceof JComponent) {
				JComponent jcomp = (JComponent) component;
				Object obj = jcomp
						.getClientProperty(SubstanceLookAndFeel.WATERMARK_VISIBLE);
				if (obj != null) {
					if (Boolean.TRUE.equals(obj))
						return true;
					if (Boolean.FALSE.equals(obj))
						return false;
				}
			}
			c = c.getParent();
		}
		Object obj = UIManager.get(SubstanceLookAndFeel.WATERMARK_VISIBLE);
		if (Boolean.TRUE.equals(obj))
			return true;
		if (Boolean.FALSE.equals(obj))
			return false;

		// special cases - lists, tables and trees that show watermarks only
		// when the WATERMARK_VISIBLE is set to Boolean.TRUE
		if (component instanceof JList)
			return false;
		if (component instanceof JTree)
			return false;
		if (component instanceof JTable)
			return false;
		if (component instanceof JTextComponent)
			return false;

		return true;
	}

	/**
	 * Returns the button shaper of the specified button.
	 * 
	 * @param comp
	 *            The button.
	 * @return The button shaper of the specified button.
	 * @see SubstanceLookAndFeel#BUTTON_SHAPER_PROPERTY
	 * @see SubstanceSkin#getButtonShaper()
	 */
	public static SubstanceButtonShaper getButtonShaper(Component comp) {
		if (comp instanceof JComponent) {
			Object prop = ((JComponent) comp)
					.getClientProperty(SubstanceLookAndFeel.BUTTON_SHAPER_PROPERTY);
			if (prop instanceof SubstanceButtonShaper)
				return (SubstanceButtonShaper) prop;
		}
		SubstanceSkin skin = SubstanceCoreUtilities.getSkin(comp);
		if (skin == null)
			return null;
		return skin.getButtonShaper();
	}

	/**
	 * Returns the gradient painter of the specified component.
	 * 
	 * @param comp
	 *            Component.
	 * @return The gradient painter of the specified component.
	 * @see SubstanceSkin#getGradientPainter()
	 */
	public static SubstanceGradientPainter getGradientPainter(Component comp) {
		return SubstanceCoreUtilities.getSkin(comp).getGradientPainter();
	}

	/**
	 * Retrieves the <code>modified</code> state for the specified component in
	 * a tabbed pane.
	 * 
	 * @param tabComponent
	 *            The associated tab component.
	 * @return <code>true</code> if the specified component in a tabbed pane is
	 *         marked as modified, <code>false</code> otherwise.
	 * @see SubstanceLookAndFeel#WINDOW_MODIFIED
	 */
	public static boolean isTabModified(Component tabComponent) {
		boolean isWindowModified = false;
		Component comp = tabComponent;
		if (comp instanceof JComponent) {
			JComponent jc = (JComponent) comp;

			isWindowModified = Boolean.TRUE.equals(jc
					.getClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED));
		}
		return isWindowModified;
	}

	/**
	 * Retrieves the <code>modified</code> state for the specified root pane.
	 * 
	 * @param rootPane
	 *            The root pane.
	 * @return <code>true</code> if the specified root pane is marked as
	 *         modified, <code>false</code> otherwise.
	 * @see SubstanceLookAndFeel#WINDOW_MODIFIED
	 */
	public static boolean isRootPaneModified(JRootPane rootPane) {
		return Boolean.TRUE.equals(rootPane
				.getClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED));
	}

	/**
	 * Retrieves the <code>modified</code> state for the specified internal
	 * frame.
	 * 
	 * @param internalFrame
	 *            The internal frame.
	 * @return <code>true</code> if the specified internal frame is marked as
	 *         modified, <code>false</code> otherwise.
	 * @see SubstanceLookAndFeel#WINDOW_MODIFIED
	 */
	public static boolean isInternalFrameModified(JInternalFrame internalFrame) {
		Object frameProp = internalFrame
				.getClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED);
		if (Boolean.TRUE.equals(frameProp))
			return true;
		if (Boolean.FALSE.equals(frameProp))
			return false;
		return Boolean.TRUE.equals(internalFrame.getRootPane()
				.getClientProperty(SubstanceLookAndFeel.WINDOW_MODIFIED));
	}

	/**
	 * Checks whether the specified tab has a close button.
	 * 
	 * @param tabbedPane
	 *            Tabbed pane.
	 * @param tabIndex
	 *            Tab index.
	 * @return <code>true</code> if the specified tab has a close button,
	 *         <code>false</code> otherwise.
	 * @see SubstanceLookAndFeel#TABBED_PANE_CLOSE_BUTTONS_PROPERTY
	 */
	public static boolean hasCloseButton(JTabbedPane tabbedPane, int tabIndex) {
		int tabCount = tabbedPane.getTabCount();
		if ((tabIndex < 0) || (tabIndex >= tabCount))
			return false;

		// if tab is disabled, it doesn't have a close button
		if (!tabbedPane.isEnabledAt(tabIndex))
			return false;

		// check property on tab component
		Component tabComponent = tabbedPane.getComponentAt(tabIndex);
		if (tabComponent instanceof JComponent) {
			Object compProp = ((JComponent) tabComponent)
					.getClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_PROPERTY);
			if (Boolean.TRUE.equals(compProp))
				return true;
			if (Boolean.FALSE.equals(compProp))
				return false;
		}
		// check property on tabbed pane
		Object tabProp = tabbedPane
				.getClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_PROPERTY);
		if (Boolean.TRUE.equals(tabProp))
			return true;
		if (Boolean.FALSE.equals(tabProp))
			return false;
		// check property in UIManager
		return UIManager
				.getBoolean(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_PROPERTY);
	}

	/**
	 * Returns the size of the close button for a tab in the specified tabbed
	 * pane.
	 * 
	 * @param tabbedPane
	 *            Tabbed pane.
	 * @param tabIndex
	 *            Tab index.
	 * @return The size of the close button for a tab in the specified tabbed
	 *         pane.
	 */
	public static int getCloseButtonSize(JTabbedPane tabbedPane, int tabIndex) {
		if (!SubstanceCoreUtilities.hasCloseButton(tabbedPane, tabIndex))
			return 0;
		return SubstanceSizeUtils.getTabCloseIconSize(SubstanceSizeUtils
				.getComponentFontSize(tabbedPane));
	}

	/**
	 * Returns the content border kind of the specified tabbed pane.
	 * 
	 * @param tabbedPane
	 *            Tabbed pane.
	 * @return Content border kind of the specified tabbed pane.
	 * @see SubstanceLookAndFeel#TABBED_PANE_CONTENT_BORDER_KIND
	 */
	public static TabContentPaneBorderKind getContentBorderKind(
			JTabbedPane tabbedPane) {
		// check property on tabbed pane
		Object tabProp = tabbedPane
				.getClientProperty(SubstanceLookAndFeel.TABBED_PANE_CONTENT_BORDER_KIND);
		if (tabProp instanceof TabContentPaneBorderKind)
			return (TabContentPaneBorderKind) tabProp;
		// check property in UIManager
		Object globalProp = UIManager
				.get(SubstanceLookAndFeel.TABBED_PANE_CONTENT_BORDER_KIND);
		if (globalProp instanceof TabContentPaneBorderKind)
			return (TabContentPaneBorderKind) globalProp;
		return TabContentPaneBorderKind.DOUBLE_FULL;
	}

	/**
	 * Checks whether the specified tab should show modified animation only on
	 * its close button.
	 * 
	 * @param tabbedPane
	 *            Tabbed pane.
	 * @param tabIndex
	 *            Tab index.
	 * @return <code>true</code> if the specified tab should show modified
	 *         animation only on its close button, <code>false</code> otherwise.
	 * @see SubstanceLookAndFeel#TABBED_PANE_CLOSE_BUTTONS_MODIFIED_ANIMATION
	 */
	public static boolean toAnimateCloseIconOfModifiedTab(
			JTabbedPane tabbedPane, int tabIndex) {
		int tabCount = tabbedPane.getTabCount();
		if ((tabIndex < 0) || (tabIndex >= tabCount))
			return false;

		if (!hasCloseButton(tabbedPane, tabIndex))
			return false;

		// check property on tab component
		Component tabComponent = tabbedPane.getComponentAt(tabIndex);
		if (tabComponent instanceof JComponent) {
			Object compProp = ((JComponent) tabComponent)
					.getClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_MODIFIED_ANIMATION);
			if (Boolean.TRUE.equals(compProp))
				return true;
			if (Boolean.FALSE.equals(compProp))
				return false;
		}
		// check property on tabbed pane
		Object tabProp = tabbedPane
				.getClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_MODIFIED_ANIMATION);
		if (Boolean.TRUE.equals(tabProp))
			return true;
		if (Boolean.FALSE.equals(tabProp))
			return false;
		// check property in UIManager
		return UIManager
				.getBoolean(SubstanceLookAndFeel.TABBED_PANE_CLOSE_BUTTONS_MODIFIED_ANIMATION);
	}

	/**
	 * Retrieves transparent image of specified dimension.
	 * 
	 * @param width
	 *            Image width.
	 * @param height
	 *            Image height.
	 * @return Transparent image of specified dimension.
	 */
	public static BufferedImage getBlankImage(int width, int height) {
		if (MemoryAnalyzer.isRunning()) {
			// see if the request is unusual
			if ((width >= 100) || (height >= 100)) {
				StackTraceElement[] stack = Thread.currentThread()
						.getStackTrace();
				StringBuffer sb = new StringBuffer();
				int count = 0;
				for (StackTraceElement stackEntry : stack) {
					if (count++ > 8)
						break;
					sb.append(stackEntry.getClassName() + ".");
					sb.append(stackEntry.getMethodName() + " [");
					sb.append(stackEntry.getLineNumber() + "]");
					sb.append("\n");
				}
				MemoryAnalyzer.enqueueUsage("Blank " + width + "*" + height
						+ "\n" + sb.toString());
			}
		}

		GraphicsEnvironment e = GraphicsEnvironment
				.getLocalGraphicsEnvironment();
		GraphicsDevice d = e.getDefaultScreenDevice();
		GraphicsConfiguration c = d.getDefaultConfiguration();
		BufferedImage compatibleImage = c.createCompatibleImage(width, height,
				Transparency.TRANSLUCENT);
		return compatibleImage;
	}

	/**
	 * Retrieves transparent image of specified dimension.
	 * 
	 * @param width
	 *            Image width.
	 * @param height
	 *            Image height.
	 * @return Transparent image of specified dimension.
	 */
	public static VolatileImage getBlankVolatileImage(int width, int height) {
		if (MemoryAnalyzer.isRunning()) {
			// see if the request is unusual
			if ((width >= 100) || (height >= 100)) {
				StackTraceElement[] stack = Thread.currentThread()
						.getStackTrace();
				StringBuffer sb = new StringBuffer();
				int count = 0;
				for (StackTraceElement stackEntry : stack) {
					if (count++ > 8)
						break;
					sb.append(stackEntry.getClassName() + ".");
					sb.append(stackEntry.getMethodName() + " [");
					sb.append(stackEntry.getLineNumber() + "]");
					sb.append("\n");
				}
				MemoryAnalyzer.enqueueUsage("Blank " + width + "*" + height
						+ "\n" + sb.toString());
			}
		}

		GraphicsEnvironment e = GraphicsEnvironment
				.getLocalGraphicsEnvironment();
		GraphicsDevice d = e.getDefaultScreenDevice();
		GraphicsConfiguration c = d.getDefaultConfiguration();
		VolatileImage compatibleImage = c.createCompatibleVolatileImage(width,
				height, Transparency.TRANSLUCENT);
		return compatibleImage;
	}

	/**
	 * Checks whether the specified button should have minimal size.
	 * 
	 * @param button
	 *            Button.
	 * @return <code>false</code> if the specified button should have minimal
	 *         size, <code>true</code> otherwise.
	 * @see SubstanceLookAndFeel#BUTTON_NO_MIN_SIZE_PROPERTY
	 */
	public static boolean hasNoMinSizeProperty(AbstractButton button) {
		Object noMinSizeProperty = button
				.getClientProperty(SubstanceLookAndFeel.BUTTON_NO_MIN_SIZE_PROPERTY);
		if (Boolean.TRUE.equals(noMinSizeProperty))
			return true;
		if (Boolean.FALSE.equals(noMinSizeProperty))
			return false;
		Container parent = button.getParent();
		if (parent instanceof JComponent) {
			noMinSizeProperty = ((JComponent) parent)
					.getClientProperty(SubstanceLookAndFeel.BUTTON_NO_MIN_SIZE_PROPERTY);
			if (Boolean.TRUE.equals(noMinSizeProperty))
				return true;
			if (Boolean.FALSE.equals(noMinSizeProperty))
				return false;
		}
		return (Boolean.TRUE.equals(UIManager
				.get(SubstanceLookAndFeel.BUTTON_NO_MIN_SIZE_PROPERTY)));
	}

	/**
	 * Checks whether the specified component is flat.
	 * 
	 * @param comp
	 *            Component.
	 * @param defaultValue
	 *            The value to return if there is no
	 *            {@link SubstanceLookAndFeel#FLAT_PROPERTY} defined on button
	 *            hierarchy or {@link UIManager}.
	 * @return <code>false</code> if the specified button is flat,
	 *         <code>true</code> otherwise.
	 * @see SubstanceLookAndFeel#FLAT_PROPERTY
	 */
	public static boolean hasFlatAppearance(Component comp, boolean defaultValue) {
		// small optimizations for checkboxes and radio buttons
		if (comp instanceof JCheckBox)
			return defaultValue;
		if (comp instanceof JRadioButton)
			return defaultValue;
		Component c = comp;
		while (c != null) {
			if (c instanceof JComponent) {
				JComponent jcomp = (JComponent) c;
				Object flatProperty = jcomp
						.getClientProperty(SubstanceLookAndFeel.FLAT_PROPERTY);
				if (flatProperty != null) {
					if (Boolean.TRUE.equals(flatProperty))
						return true;
					if (Boolean.FALSE.equals(flatProperty))
						return false;
				}
			}
			c = c.getParent();
		}
		Object flatProperty = UIManager.get(SubstanceLookAndFeel.FLAT_PROPERTY);
		if (flatProperty != null) {
			if (Boolean.TRUE.equals(flatProperty))
				return true;
			if (Boolean.FALSE.equals(flatProperty))
				return false;
		}
		return defaultValue;
	}

	/**
	 * Computes whether the specified button has flat appearance.
	 * 
	 * @param button
	 *            Button.
	 * @return <code>true</code> if the button has flat appearance,
	 *         <code>false</code> otherwise.
	 */
	public static boolean hasFlatAppearance(AbstractButton button) {
		// small optimizations for checkboxes and radio buttons
		if (button instanceof JCheckBox)
			return false;
		if (button instanceof JRadioButton)
			return false;
		return ((SubstanceCoreUtilities.isToolBarButton(button) && SubstanceCoreUtilities
				.hasFlatAppearance(button, true)) || SubstanceCoreUtilities
				.hasFlatAppearance(button, false));
	}

	/**
	 * Returns the popup flyout orientation for the specified combobox.
	 * 
	 * @param combobox
	 *            Combobox.
	 * @return The popup flyout orientation for the specified combobox.
	 * @see SubstanceLookAndFeel#COMBO_BOX_POPUP_FLYOUT_ORIENTATION
	 */
	public static int getPopupFlyoutOrientation(JComboBox combobox) {
		Object comboProperty = combobox
				.getClientProperty(SubstanceLookAndFeel.COMBO_BOX_POPUP_FLYOUT_ORIENTATION);
		if (comboProperty instanceof Integer)
			return (Integer) comboProperty;
		Object globalProperty = UIManager
				.get(SubstanceLookAndFeel.COMBO_BOX_POPUP_FLYOUT_ORIENTATION);
		if (globalProperty instanceof Integer)
			return (Integer) globalProperty;
		return SwingConstants.SOUTH;
	}

	/**
	 * Makes the specified component and all its descendants non-opaque.
	 * 
	 * @param comp
	 *            Component.
	 * @param opacitySnapshot
	 *            The "snapshot" map that will contain the original opacity
	 *            status of the specified component and all its descendants.
	 */
	public static void makeNonOpaque(Component comp,
			Map<Component, Boolean> opacitySnapshot) {
		if (comp instanceof JComponent) {
			JComponent jcomp = (JComponent) comp;
			opacitySnapshot.put(comp, jcomp.isOpaque());
			jcomp.setOpaque(false);
		}
		if (comp instanceof Container) {
			Container cont = (Container) comp;
			for (int i = 0; i < cont.getComponentCount(); i++)
				makeNonOpaque(cont.getComponent(i), opacitySnapshot);
		}
	}

	/**
	 * Restores the opacity of the specified component and all its descendants.
	 * 
	 * @param comp
	 *            Component.
	 * @param opacitySnapshot
	 *            The "snapshot" map that contains the original opacity status
	 *            of the specified component and all its descendants.
	 */
	public static void restoreOpaque(Component comp,
			Map<Component, Boolean> opacitySnapshot) {
		if (comp instanceof JComponent) {
			JComponent jcomp = (JComponent) comp;
			// fix for defect 159 - snapshot may not contain
			// opacity for table header of a table when it's used
			// inside tree cell renderer (wrapper in a scroll pane).
			if (opacitySnapshot.containsKey(comp))
				jcomp.setOpaque(opacitySnapshot.get(comp));
			else
				jcomp.setOpaque(true);
		}
		if (comp instanceof Container) {
			Container cont = (Container) comp;
			for (int i = 0; i < cont.getComponentCount(); i++)
				restoreOpaque(cont.getComponent(i), opacitySnapshot);
		}
	}

	/**
	 * Makes the specified component and all its descendants non-double
	 * buffered.
	 * 
	 * @param comp
	 *            Component.
	 * @param dbSnapshot
	 *            The "snapshot" map that will contain the original double
	 *            buffer status of the specified component and all its
	 *            descendants.
	 */
	public static void makeNonDoubleBuffered(Component comp,
			Map<Component, Boolean> dbSnapshot) {
		if (comp instanceof JComponent) {
			JComponent jcomp = (JComponent) comp;
			dbSnapshot.put(comp, jcomp.isDoubleBuffered());
			jcomp.setDoubleBuffered(false);
		}
		if (comp instanceof Container) {
			Container cont = (Container) comp;
			for (int i = 0; i < cont.getComponentCount(); i++)
				makeNonDoubleBuffered(cont.getComponent(i), dbSnapshot);
		}
	}

	/**
	 * Restores the double buffer of the specified component and all its
	 * descendants.
	 * 
	 * @param comp
	 *            Component.
	 * @param dbSnapshot
	 *            The "snapshot" map that contains the original double buffer
	 *            status of the specified component and all its descendants.
	 */
	public static void restoreDoubleBuffered(Component comp,
			Map<Component, Boolean> dbSnapshot) {
		if (comp instanceof JComponent) {
			JComponent jcomp = (JComponent) comp;
			// fix for defect 159 - snapshot may not contain
			// opacity for table header of a table when it's used
			// inside tree cell renderer (wrapper in a scroll pane).
			if (dbSnapshot.containsKey(comp))
				jcomp.setDoubleBuffered(dbSnapshot.get(comp));
			else
				jcomp.setOpaque(true);
		}
		if (comp instanceof Container) {
			Container cont = (Container) comp;
			for (int i = 0; i < cont.getComponentCount(); i++)
				restoreDoubleBuffered(cont.getComponent(i), dbSnapshot);
		}
	}

	/**
	 * Creates a compatible image (for efficient processing and drawing).
	 * 
	 * @param image
	 *            The original image.
	 * @return Compatible version of the original image.
	 * @author Romain Guy
	 */
	public static BufferedImage createCompatibleImage(BufferedImage image) {
		GraphicsEnvironment e = GraphicsEnvironment
				.getLocalGraphicsEnvironment();
		GraphicsDevice d = e.getDefaultScreenDevice();
		GraphicsConfiguration c = d.getDefaultConfiguration();
		BufferedImage compatibleImage = c.createCompatibleImage(image
				.getWidth(), image.getHeight(), Transparency.TRANSLUCENT);
		Graphics g = compatibleImage.getGraphics();
		g.drawImage(image, 0, 0, null);
		g.dispose();
		return compatibleImage;
	}

	/**
	 * Checks whether the specified component will show scheme-colorized icon in
	 * the default state.
	 * 
	 * @param comp
	 *            Component.
	 * @return <code>true</code> if the specified component will show
	 *         scheme-colorized icon in the default state, <code>false</code>
	 *         otherwise.
	 * @see SubstanceLookAndFeel#USE_THEMED_DEFAULT_ICONS
	 */
	public static boolean useThemedDefaultIcon() {
		return Boolean.TRUE.equals(UIManager
				.get(SubstanceLookAndFeel.USE_THEMED_DEFAULT_ICONS));
	}

	/**
	 * Returns the callback to be called upon tab closing (using the tab close
	 * button).
	 * 
	 * @param me
	 *            Mouse event.
	 * @param tabbedPane
	 *            Tabbed pane.
	 * @param tabIndex
	 *            Tab index.
	 * @return Callback to be called upon tab closing (using the tab close
	 *         button).
	 * @see SubstanceLookAndFeel#TABBED_PANE_CLOSE_CALLBACK
	 */
	public static TabCloseCallback getTabCloseCallback(MouseEvent me,
			JTabbedPane tabbedPane, int tabIndex) {
		int tabCount = tabbedPane.getTabCount();
		if ((tabIndex < 0) || (tabIndex >= tabCount))
			return null;

		// check property on tab component
		Component tabComponent = tabbedPane.getComponentAt(tabIndex);
		if (tabComponent instanceof JComponent) {
			Object compProp = ((JComponent) tabComponent)
					.getClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_CALLBACK);
			if (compProp instanceof TabCloseCallback)
				return (TabCloseCallback) compProp;
		}

		// check property on tabbed pane
		Object tabProp = tabbedPane
				.getClientProperty(SubstanceLookAndFeel.TABBED_PANE_CLOSE_CALLBACK);
		if (tabProp instanceof TabCloseCallback)
			return (TabCloseCallback) tabProp;

		Object globProp = UIManager
				.get(SubstanceLookAndFeel.TABBED_PANE_CLOSE_CALLBACK);
		if (globProp instanceof TabCloseCallback)
			return (TabCloseCallback) globProp;

		return null;
	}

	/**
	 * Blends two images along Y-axis.
	 * 
	 * @param imageTop
	 *            The left image.
	 * @param imageBottom
	 *            The right image.
	 * @param start
	 *            Relative start of the blend area (in 0.0-1.0 range).
	 * @param end
	 *            Relative end of the blend area (in 0.0-1.0 range).
	 * @return Blended image.
	 */
	public static BufferedImage blendImagesVertical(BufferedImage imageTop,
			BufferedImage imageBottom, double start, double end) {
		int width = imageTop.getWidth();
		if (width != imageBottom.getWidth())
			throw new IllegalArgumentException("Widths are not the same: "
					+ imageTop.getWidth() + " and " + imageBottom.getWidth());
		int height = imageTop.getHeight();
		if (height != imageBottom.getHeight())
			throw new IllegalArgumentException("Heights are not the same: "
					+ imageTop.getHeight() + " and " + imageBottom.getHeight());

		BufferedImage result = getBlankImage(width, height);
		Graphics2D graphics = (Graphics2D) result.getGraphics().create();

		int startY = (int) (start * height);
		int endY = (int) (end * height);
		int rampHeight = endY - startY;

		if (rampHeight == 0) {
			graphics.drawImage(imageTop, 0, 0, width, startY, 0, 0, width,
					startY, null);
			graphics.drawImage(imageBottom, 0, startY, width, height, 0,
					startY, width, height, null);
		} else {
			BufferedImage rampBottom = getBlankImage(width, rampHeight);
			Graphics2D rampBottomG = (Graphics2D) rampBottom.getGraphics();
			rampBottomG.setPaint(new GradientPaint(new Point(0, 0), new Color(
					0, 0, 0, 255), new Point(0, rampHeight), new Color(0, 0, 0,
					0)));
			rampBottomG.fillRect(0, 0, width, rampHeight);

			BufferedImage tempBottom = getBlankImage(width, height - startY);
			Graphics2D tempBottomG = (Graphics2D) tempBottom.getGraphics();
			tempBottomG.drawImage(imageBottom, 0, 0, width, height - startY, 0,
					startY, width, height, null);
			tempBottomG.setComposite(AlphaComposite.DstOut);
			tempBottomG.drawImage(rampBottom, 0, 0, null);

			tempBottomG.setComposite(AlphaComposite.SrcOver);
			graphics.drawImage(imageTop, 0, 0, null);
			graphics.drawImage(tempBottom, 0, startY, null);
		}
		graphics.dispose();
		return result;
	}

	/**
	 * Blends two images along X-axis.
	 * 
	 * @param imageLeft
	 *            The left image.
	 * @param imageRight
	 *            The right image.
	 * @param start
	 *            Relative start of the blend area (in 0.0-1.0 range).
	 * @param end
	 *            Relative end of the blend area (in 0.0-1.0 range).
	 * @return Blended image.
	 */
	public static BufferedImage blendImagesHorizontal(BufferedImage imageLeft,
			BufferedImage imageRight, double start, double end) {
		int width = imageLeft.getWidth();
		if (width != imageRight.getWidth())
			throw new IllegalArgumentException("Widths are not the same: "
					+ imageLeft.getWidth() + " and " + imageRight.getWidth());
		int height = imageLeft.getHeight();
		if (height != imageRight.getHeight())
			throw new IllegalArgumentException("Heights are not the same: "
					+ imageLeft.getHeight() + " and " + imageRight.getHeight());

		BufferedImage result = getBlankImage(width, height);
		Graphics2D graphics = (Graphics2D) result.getGraphics().create();

		int startX = (int) (start * width);
		int endX = (int) (end * width);
		int rampWidth = endX - startX;

		if (rampWidth == 0) {
			graphics.drawImage(imageLeft, 0, 0, startX, height, 0, 0, startX,
					height, null);
			graphics.drawImage(imageRight, startX, 0, width, height, startX, 0,
					width, height, null);
		} else {
			BufferedImage rampRight = getBlankImage(rampWidth, height);
			Graphics2D rampRightG = (Graphics2D) rampRight.getGraphics();
			rampRightG
					.setPaint(new GradientPaint(new Point(0, 0), new Color(0,
							0, 0, 255), new Point(rampWidth, 0), new Color(0,
							0, 0, 0)));
			rampRightG.fillRect(0, 0, rampWidth, height);

			BufferedImage tempRight = getBlankImage(width - startX, height);
			Graphics2D tempRightG = (Graphics2D) tempRight.getGraphics();
			tempRightG.drawImage(imageRight, 0, 0, width - startX, height,
					startX, 0, width, height, null);
			tempRightG.setComposite(AlphaComposite.DstOut);
			tempRightG.drawImage(rampRight, 0, 0, null);

			tempRightG.setComposite(AlphaComposite.SrcOver);
			graphics.drawImage(imageLeft, 0, 0, null);
			graphics.drawImage(tempRight, startX, 0, null);
		}
		graphics.dispose();
		return result;
	}

	/**
	 * Returns the color scheme for the icon of option panes with the specified
	 * message type.
	 * 
	 * @param messageType
	 *            Option pane message type.
	 * @param mainScheme
	 *            Main color scheme.
	 * @return Color scheme for the icon of option panes with the specified
	 *         message type.
	 */
	public static SubstanceColorScheme getOptionPaneColorScheme(
			int messageType, SubstanceColorScheme mainScheme) {
		if (!SubstanceLookAndFeel.isToUseConstantThemesOnDialogs())
			return mainScheme;

		switch (messageType) {
		case JOptionPane.INFORMATION_MESSAGE:
			return new BottleGreenColorScheme();
		case JOptionPane.QUESTION_MESSAGE:
			return new LightAquaColorScheme();
		case JOptionPane.WARNING_MESSAGE:
			return new SunsetColorScheme();
		case JOptionPane.ERROR_MESSAGE:
			return new SunfireRedColorScheme();
		}
		return null;
	}

	/**
	 * Returns the popup prototype display value for the specified combo box.
	 * This value is used to compute the width of the combo popup.
	 * 
	 * @param combo
	 *            Combo box.
	 * @return The popup prototype display value for the specified combo box.
	 * @see SubstanceLookAndFeel#COMBO_POPUP_PROTOTYPE
	 */
	public static Object getComboPopupPrototypeDisplayValue(JComboBox combo) {
		Object objProp = combo
				.getClientProperty(SubstanceLookAndFeel.COMBO_POPUP_PROTOTYPE);
		if (objProp == null)
			objProp = UIManager.get(SubstanceLookAndFeel.COMBO_POPUP_PROTOTYPE);
		if (objProp == null)
			return null;

		if (objProp instanceof ComboPopupPrototypeCallback) {
			ComboPopupPrototypeCallback callback = (ComboPopupPrototypeCallback) objProp;
			return callback.getPopupPrototypeDisplayValue(combo);
		}

		// check if this object is in the model ???
		return objProp;
	}

	/**
	 * Returns the scroll bar buttons kind of the specified scroll bar.
	 * 
	 * @param scrollBar
	 *            Scroll bar.
	 * @return The scroll bar buttons kind of the specified scroll bar.
	 * @see SubstanceLookAndFeel#SCROLL_PANE_BUTTONS_POLICY
	 */
	public static ScrollPaneButtonPolicyKind getScrollPaneButtonsPolicyKind(
			JScrollBar scrollBar) {
		Component parent = scrollBar.getParent();
		if (parent instanceof JScrollPane) {
			Object jspKind = ((JScrollPane) parent)
					.getClientProperty(SubstanceLookAndFeel.SCROLL_PANE_BUTTONS_POLICY);
			if (jspKind instanceof ScrollPaneButtonPolicyKind)
				return (ScrollPaneButtonPolicyKind) jspKind;
		}
		Object globalJspKind = UIManager
				.get(SubstanceLookAndFeel.SCROLL_PANE_BUTTONS_POLICY);
		if (globalJspKind instanceof ScrollPaneButtonPolicyKind)
			return (ScrollPaneButtonPolicyKind) globalJspKind;
		return ScrollPaneButtonPolicyKind.OPPOSITE;
	}

	/**
	 * Returns the set of sides registered on the specified button.
	 * 
	 * @param component
	 *            Button.
	 * @param propertyName
	 *            Client property name for retrieving the registered sides.
	 * @return Set of sides registered on the specified button.
	 */
	@SuppressWarnings("unchecked")
	public static Set<Side> getSides(JComponent component, String propertyName) {
		Object prop = component.getClientProperty(propertyName);

		if (prop instanceof Set) {
			return (Set<Side>) prop;
		}

		Set<Side> result = EnumSet.noneOf(Side.class);
		if (prop != null) {
			if (prop instanceof Side) {
				result.add((Side) prop);
			}
		}
		return result;
	}

	/**
	 * Returns the corner radius of the specified toolbar button.
	 * 
	 * @param button
	 *            Toolbar button.
	 * @param insets
	 *            Button insets.
	 * @return Corner radius of the specified toolbar button.
	 * @see SubstanceLookAndFeel#CORNER_RADIUS
	 */
	public static float getToolbarButtonCornerRadius(JComponent button,
			Insets insets) {

		JToolBar toolbar = null;
		Component c = button.getParent();
		while (c != null) {
			if (c instanceof JToolBar) {
				toolbar = (JToolBar) c;
				break;
			}
			c = c.getParent();
		}
		if (toolbar == null)
			return 2.0f;

		int width = button.getWidth();
		int height = button.getHeight();

		if (insets != null) {
			width -= (insets.left + insets.right);
			height -= (insets.top + insets.bottom);
		}
		float maxRadius = (width > height) ? (height) / 2.0f : (width) / 2.0f;

		Object buttonProp = button
				.getClientProperty(SubstanceLookAndFeel.CORNER_RADIUS);
		if (buttonProp instanceof Float)
			return Math.min(maxRadius, ((Float) buttonProp).floatValue());

		Object toolbarProp = toolbar
				.getClientProperty(SubstanceLookAndFeel.CORNER_RADIUS);
		if (toolbarProp instanceof Float)
			return Math.min(maxRadius, ((Float) toolbarProp).floatValue());

		Object globalProp = UIManager.get(SubstanceLookAndFeel.CORNER_RADIUS);
		if (globalProp instanceof Float)
			return Math.min(maxRadius, ((Float) globalProp).floatValue());

		return 2.0f;
	}

	/**
	 * Returns the number of echo characters per each password chanaracter.
	 * 
	 * @param jpf
	 *            Password field.
	 * @return The number of echo characters per each password chanaracter.
	 * @see SubstanceLookAndFeel#PASSWORD_ECHO_PER_CHAR
	 */
	public static int getEchoPerChar(JPasswordField jpf) {
		Object obj = jpf
				.getClientProperty(SubstanceLookAndFeel.PASSWORD_ECHO_PER_CHAR);
		if ((obj != null) && (obj instanceof Integer)) {
			int result = (Integer) obj;
			if (result >= 1)
				return result;
		}

		obj = UIManager.get(SubstanceLookAndFeel.PASSWORD_ECHO_PER_CHAR);
		if ((obj != null) && (obj instanceof Integer)) {
			int result = (Integer) obj;
			if (result >= 1)
				return result;
		}
		return 1;
	}

	/**
	 * Creates a clip image for soft-clipping. Code taken from <a href=
	 * "http://weblogs.java.net/blog/campbell/archive/2006/07/java_2d_tricker_2.html"
	 * >here</a>.
	 * 
	 * @param s
	 *            Clip shape.
	 * @param width
	 *            Image width.
	 * @param height
	 *            Image height.
	 * @return Clip image.
	 * @author Chris Campbell.
	 */
	public static BufferedImage createClipImage(Shape s, int width, int height) {
		// Create a translucent intermediate image in which we can perform
		// the soft clipping
		GraphicsEnvironment e = GraphicsEnvironment
				.getLocalGraphicsEnvironment();
		GraphicsDevice d = e.getDefaultScreenDevice();
		GraphicsConfiguration c = d.getDefaultConfiguration();

		BufferedImage img = c.createCompatibleImage(width, height,
				Transparency.TRANSLUCENT);
		Graphics2D g2 = img.createGraphics();

		// Clear the image so all pixels have zero alpha
		g2.setComposite(AlphaComposite.Clear);
		g2.fillRect(0, 0, width, height);

		// Render our clip shape into the image. Note that we enable
		// antialiasing to achieve the soft clipping effect. Try
		// commenting out the line that enables antialiasing, and
		// you will see that you end up with the usual hard clipping.
		g2.setComposite(AlphaComposite.Src);
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
		g2.setColor(Color.WHITE);
		g2.fill(s);
		g2.dispose();

		return img;
	}

	/**
	 * Checks whether the specified component has extra Substance-specific UI
	 * elements.
	 * 
	 * @param component
	 *            Component.
	 * @return <code>true</code> if the specified component has extra
	 *         Substance-specific UI elements, <code>false</code> otherwise.
	 * @see SubstanceLookAndFeel#SHOW_EXTRA_WIDGETS
	 */
	public static boolean toShowExtraWidgets(Component component) {
		Component c = component;
		while (c != null) {
			if (c instanceof JComponent) {
				JComponent jcomp = (JComponent) c;
				Object componentProp = jcomp
						.getClientProperty(SubstanceLookAndFeel.SHOW_EXTRA_WIDGETS);
				if (componentProp != null) {
					if (Boolean.TRUE.equals(componentProp))
						return false;
					if (Boolean.FALSE.equals(componentProp))
						return true;
				}
			}
			c = c.getParent();
		}
		return Boolean.TRUE.equals(UIManager
				.get(SubstanceLookAndFeel.SHOW_EXTRA_WIDGETS));
	}

	/**
	 * Returns indication whether the specified component's border is a
	 * Substance-specific border.
	 * 
	 * @param c
	 *            Component.
	 * @return <code>true</code> if the specified component's border is a
	 *         Substance-specific border, <code>false</code> otherwise.
	 */
	public static boolean hasSubstanceBorder(JComponent c) {
		if (c == null)
			return false;

		Border border = c.getBorder();
		if (border instanceof SubstanceBorder)
			return true;

		if (border instanceof CompoundBorder) {
			CompoundBorder cb = (CompoundBorder) border;
			if (cb.getOutsideBorder() instanceof SubstanceBorder)
				return true;
		}

		return false;
	}

	/**
	 * Returns the active icon of the specified button.
	 * 
	 * @param origIcon
	 *            Original icon.
	 * @param button
	 *            Button.
	 * @param glowingIcon
	 *            Glowing icon.
	 * @param ignoreRolloverSetting
	 *            If <code>true</code>, the rollover status of the specified
	 *            button is ignored.
	 * @return Active icon for the button.
	 */
	public static Icon getActiveIcon(Icon origIcon, AbstractButton button,
			Icon glowingIcon, boolean ignoreRolloverSetting) {
		if (origIcon == null)
			return null;
		ButtonModel model = button.getModel();
		Icon result = origIcon;
		if (!(button instanceof JRadioButton) && !(button instanceof JCheckBox)
				&& SubstanceCoreUtilities.useThemedDefaultIcon()) {
			boolean useThemedVersion = !model.isArmed()
					&& !model.isPressed()
					&& !model.isSelected()
					&& !origIcon.getClass().isAnnotationPresent(
							TransitionAware.class);
			if (!ignoreRolloverSetting)
				useThemedVersion = useThemedVersion && !model.isRollover();
			if (useThemedVersion) {
				result = getThemedIcon(button, result);
			}
		}
		if (FadeConfigurationManager.getInstance().fadeAllowed(
				FadeKind.ICON_GLOW, button)
				&& model.isRollover() && (glowingIcon != null)) {
			result = glowingIcon;
		}
		return result;
	}

	public static Icon getThemedIcon(Component comp, Icon orig) {
		SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
				.getColorScheme(comp, ComponentState.DEFAULT);
		float brightnessFactor = colorScheme.isDark() ? 0.2f : 0.8f;
		return new ImageIcon(SubstanceImageCreator.getColorSchemeImage(comp,
				orig, colorScheme, brightnessFactor));
	}

	public static Icon getThemedIcon(JTabbedPane tab, int tabIndex, Icon orig) {
		SubstanceColorScheme colorScheme = SubstanceColorSchemeUtilities
				.getColorScheme(tab, tabIndex, ColorSchemeAssociationKind.TAB,
						ComponentState.DEFAULT);
		float brightnessFactor = colorScheme.isDark() ? 0.2f : 0.8f;
		return new ImageIcon(SubstanceImageCreator.getColorSchemeImage(tab,
				orig, colorScheme, brightnessFactor));
	}

	/**
	 * Returns the current icon for the specified button.
	 * 
	 * @param b
	 *            Button.
	 * @param defaultIcon
	 *            The default icon.
	 * @param glowingIcon
	 *            The glowing icon.
	 * @param ignoreRolloverSetting
	 *            If <code>true</code>, the rollover status of the specified
	 *            button is ignored.
	 * @return Icon for the specified button.
	 */
	public static Icon getIcon(AbstractButton b, Icon defaultIcon,
			Icon glowingIcon, boolean ignoreRolloverSetting) {
		ButtonModel model = b.getModel();
		Icon icon = getActiveIcon(b.getIcon() == null ? defaultIcon : b
				.getIcon(), b, glowingIcon, ignoreRolloverSetting);
		if (icon == null)
			return null;

		if (icon.getClass().isAnnotationPresent(TransitionAware.class))
			return icon;

		Icon tmpIcon = null;

		if (icon != null) {
			if (!model.isEnabled()) {
				if (model.isSelected()) {
					tmpIcon = b.getDisabledSelectedIcon();
				} else {
					tmpIcon = b.getDisabledIcon();
				}
			} else if (model.isPressed() && model.isArmed()) {
				tmpIcon = b.getPressedIcon();
			} else if (b.isRolloverEnabled() && model.isRollover()) {
				if (model.isSelected()) {
					tmpIcon = b.getRolloverSelectedIcon();
				} else {
					tmpIcon = b.getRolloverIcon();
				}
			} else if (model.isSelected()) {
				tmpIcon = b.getSelectedIcon();
			}

			if (tmpIcon != null) {
				icon = tmpIcon;
			}
		}
		return icon;
	}

	/**
	 * Returns the global menu gutter fill kind.
	 * 
	 * @return The global menu gutter fill kind.
	 * @see SubstanceLookAndFeel#MENU_GUTTER_FILL_KIND
	 */
	public static MenuGutterFillKind getMenuGutterFillKind() {
		Object globalSetting = UIManager
				.get(SubstanceLookAndFeel.MENU_GUTTER_FILL_KIND);
		if (globalSetting instanceof MenuGutterFillKind)
			return (MenuGutterFillKind) globalSetting;
		return MenuGutterFillKind.HARD;
	}

	/**
	 * Given a component, returns the parent for computing the
	 * {@link SubstanceDecorationPainter}.
	 * 
	 * @param c
	 *            Component.
	 * @return The parent for computing the {@link SubstanceDecorationPainter}.
	 */
	public static Container getHeaderParent(Component c) {
		Component comp = c.getParent();
		Container result = null;
		while (comp != null) {
			// the second part fixes the incorrect alignments on
			// internal frames.
			if ((comp instanceof JLayeredPane) && (result == null))
				result = (Container) comp;
			if ((result == null) && (comp instanceof Window))
				result = (Container) comp;
			comp = comp.getParent();
		}
		return result;
	}

	/**
	 * Paints the focus ring on the specified component.
	 * 
	 * @param g
	 *            Graphics context.
	 * @param mainComp
	 *            The main component for the focus painting.
	 * @param focusedComp
	 *            The actual component that has the focus. For example, the main
	 *            component can be a {@link JSpinner}, while the focused
	 *            component is a text field inside the the spinner editor.
	 * @param focusShape
	 *            Focus shape. May be <code>null</code> - in this case, the
	 *            bounds of <code>mainComp</code> will be used.
	 * @param textRect
	 *            Text rectangle (if relevant).
	 * @param maxAlphaCoef
	 *            Maximum alhpa coefficient for painting the focus. Values lower
	 *            than 1.0 will result in a translucent focus ring (can be used
	 *            to paint a focus ring that doesn't draw too much attention
	 *            away from the content, for example on text components).
	 * @param extraPadding
	 *            Extra padding between the component bounds and the focus ring
	 *            painting.
	 */
	public static void paintFocus(Graphics g, Component mainComp,
			Component focusedComp, Shape focusShape, Rectangle textRect,
			float maxAlphaCoef, int extraPadding) {
		FadeTracker fadeTracker = FadeTracker.getInstance();
		FocusKind focusKind = SubstanceCoreUtilities.getFocusKind(mainComp);
		if ((focusKind == FocusKind.NONE)
				&& (!fadeTracker.isTracked(focusedComp, FadeKind.FOCUS)))
			return;

		Graphics2D graphics = (Graphics2D) g.create();
		graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);

		float alpha = 1.0f;
		if (fadeTracker.isTracked(focusedComp, FadeKind.FOCUS)) {
			alpha = fadeTracker.getFade(focusedComp, FadeKind.FOCUS);
		}
		alpha *= maxAlphaCoef;
		graphics.setComposite(TransitionLayout.getAlphaComposite(mainComp,
				alpha, g));

		Color color = SubstanceColorUtilities.getFocusColor(mainComp);
		graphics.setColor(color);
		focusKind.paintFocus(mainComp, focusedComp, graphics, focusShape,
				textRect, extraPadding);
		graphics.dispose();
	}

	/**
	 * Paints the focus ring on the specified component.
	 * 
	 * @param g
	 *            Graphics context.
	 * @param mainComp
	 *            The main component for the focus painting.
	 * @param focusedComp
	 *            The actual component that has the focus. For example, the main
	 *            component can be a {@link JSpinner}, while the focused
	 *            component is a text field inside the the spinner editor.
	 * @param textRect
	 *            Text rectangle (if relevant).
	 */
	public static void paintFocus(Graphics g, Component mainComp,
			Component focusedComp, Rectangle textRect) {
		paintFocus(g, mainComp, focusedComp, null, textRect, 1.0f, 0);
	}

	/**
	 * Returns indication whether the specified button is a close button on some
	 * title pane.
	 * 
	 * @param ab
	 *            Button.
	 * @return <code>true</code> if the specified button is a close button on
	 *         some title pane, <code>false</code> otherwise.
	 */
	public static boolean isTitleCloseButton(JComponent ab) {
		if ((ab instanceof SubstanceTitleButton)
				&& Boolean.TRUE
						.equals(ab
								.getClientProperty(SubstanceButtonUI.IS_TITLE_CLOSE_BUTTON)))
			return true;
		return false;
	}

	/**
	 * Uninstalls the specified menu item.
	 * 
	 * @param menuItem
	 *            Menu item.
	 */
	public static void uninstallMenu(JMenuItem menuItem) {
		if (menuItem instanceof JMenu) {
			JMenu menu = (JMenu) menuItem;
			for (Component comp : menu.getMenuComponents())
				if (comp instanceof JMenuItem)
					SubstanceCoreUtilities.uninstallMenu((JMenuItem) comp);
		}

		ButtonUI menuItemUI = menuItem.getUI();
		if (menuItemUI instanceof SubstanceMenu) {
			SubstanceMenu sMenu = (SubstanceMenu) menuItemUI;
			if (sMenu.getAssociatedMenuItem() != null) {
				menuItemUI.uninstallUI(menuItem);
			}
		}

		for (ActionListener actionListener : menuItem.getActionListeners())
			menuItem.removeActionListener(actionListener);

		menuItem.removeAll();
	}

	/**
	 * Returns an icon pointed to by the specified string.
	 * 
	 * @param iconResource
	 *            Resource location string.
	 * @return Icon.
	 */
	public static Icon getIcon(String iconResource) {
		ClassLoader cl = getClassLoaderForResources();
		URL iconUrl = cl.getResource(iconResource);
		if (iconUrl == null)
			return null;
		return new IconUIResource(new ImageIcon(iconUrl));
	}

	/**
	 * Returns the class loader for loading the resource files. It is a fix by
	 * Dag Joar and Christian Schlichtherle for application running with
	 * -Xbootclasspath VM flag. In this case, the using
	 * MyClass.class.getClassLoader() would return null, but the context class
	 * loader will function properly that classes will be properly loaded
	 * regardless of whether the lib is added to the system class path, the
	 * extension class path and regardless of the class loader architecture set
	 * up by some frameworks.
	 * 
	 * @return The class loader for loading the resource files.
	 */
	public static ClassLoader getClassLoaderForResources() {
		ClassLoader cl = (ClassLoader) UIManager.get("ClassLoader");
		if (cl == null)
			cl = Thread.currentThread().getContextClassLoader();
		return cl;
	}

	/**
	 * Returns the fade callback for the specified button.
	 * 
	 * @param button
	 *            Button.
	 * @param toRepaintParent
	 *            if <code>true</code>, button's parent will be repainted on
	 *            each iteration cycle. Otherwise, the button itself will be
	 *            repainted.
	 * @return Fade callback for the specified button.
	 */
	public static FadeTrackerCallback getFadeCallback(
			final AbstractButton button, boolean toRepaintParent) {
		return getFadeCallback(button, button.getModel(), false,
				toRepaintParent, button);
	}

	/**
	 * Returns the fade callback for the specified component.
	 * 
	 * @param component
	 *            Component.
	 * @param model
	 *            Model for tracking the transitions. For button components,
	 *            pass the {@link AbstractButton#getModel()}, for other controls
	 *            pass a dummy (synthesized) model.
	 * @param toIgnoreSelection
	 *            If <code>true</code>, the {@link ButtonModel#isSelected()}
	 *            will not be checked. This can be used for tracking transitions
	 *            on menu items that use <code>armed</code> state instead, when
	 *            we don't want to use different rollover color schemes for
	 *            selected and unselected checkbox and radio button menu items
	 *            (to preserve consistent visual appearence of highlights).
	 * @param toRepaintParent
	 *            if <code>true</code>, component's parent will be repainted on
	 *            each iteration cycle. Otherwise, the
	 *            <code>componentToRepaint</code> will be repainted.
	 * @param componentToRepaint
	 *            If <code>toRepaintParent</code> is <code>false</code>, this
	 *            component will be repainted.
	 * @return Fade callback for the specified component.
	 */
	public static FadeTrackerCallback getFadeCallback(
			final JComponent component, final ButtonModel model,
			final boolean toIgnoreSelection, final boolean toRepaintParent,
			final Component componentToRepaint) {
		return getFadeCallback(component, model, null, toIgnoreSelection,
				toRepaintParent, componentToRepaint);
	}

	/**
	 * Returns the fade callback for the specified component.
	 * 
	 * @param component
	 *            Component.
	 * @param model
	 *            Model for tracking the transitions. For button components,
	 *            pass the {@link AbstractButton#getModel()}, for other controls
	 *            pass a dummy (synthesized) model.
	 * @param extraKey
	 *            Extra key.
	 * @param toIgnoreSelection
	 *            If <code>true</code>, the {@link ButtonModel#isSelected()}
	 *            will not be checked. This can be used for tracking transitions
	 *            on menu items that use <code>armed</code> state instead, when
	 *            we don't want to use different rollover color schemes for
	 *            selected and unselected checkbox and radio button menu items
	 *            (to preserve consistent visual appearence of highlights).
	 * @param toRepaintParent
	 *            if <code>true</code>, component's parent will be repainted on
	 *            each iteration cycle. Otherwise, the
	 *            <code>componentToRepaint</code> will be repainted.
	 * @param componentToRepaint
	 *            If <code>toRepaintParent</code> is <code>false</code>, this
	 *            component will be repainted.
	 * @return Fade callback for the specified component.
	 */
	public static FadeTrackerCallback getFadeCallback(
			final JComponent component, final ButtonModel model,
			String extraKey, final boolean toIgnoreSelection,
			final boolean toRepaintParent, final Component componentToRepaint) {

		final String prevComponentStateKey = (extraKey == null) ? SubstanceCoreUtilities.PREV_COMPONENT_STATE
				: SubstanceCoreUtilities.PREV_COMPONENT_STATE + ":" + extraKey;
		final String prevSelComponentStateKey = (extraKey == null) ? SubstanceCoreUtilities.PREV_SEL_COMPONENT_STATE
				: SubstanceCoreUtilities.PREV_SEL_COMPONENT_STATE + ":"
						+ extraKey;
		final String nextComponentStateKey = (extraKey == null) ? SubstanceCoreUtilities.NEXT_COMPONENT_STATE
				: SubstanceCoreUtilities.NEXT_COMPONENT_STATE + ":" + extraKey;
		final String nextSelComponentStateKey = (extraKey == null) ? SubstanceCoreUtilities.NEXT_SEL_COMPONENT_STATE
				: SubstanceCoreUtilities.NEXT_SEL_COMPONENT_STATE + ":"
						+ extraKey;

		// fix for issue 351 - ComponentState.getState may access
		// MenuSelectionManager APIs and as such it needs to run on EDT.
		FadeTrackerCallback callback = new UIThreadFadeTrackerAdapter() {
			@Override
			public void fadeReversed(FadeKind fadeKind, boolean isFadingIn,
					float fadeCycle10) {
				component.putClientProperty(prevComponentStateKey, component
						.getClientProperty(nextComponentStateKey));
				component.putClientProperty(prevSelComponentStateKey, component
						.getClientProperty(nextSelComponentStateKey));
				// String text = (component instanceof AbstractButton) ?
				// ((AbstractButton)
				// component)
				// .getText()
				// : "";
				// System.out.println(component.getClass().getSimpleName()
				// + "["
				// + text
				// + "]"
				// + " : "
				// + fadeKind.toString()
				// + " - tracking prev as "
				// + ((ComponentState) component
				// .getClientProperty(PREV_COMPONENT_STATE))
				// .name());
				if (!toRepaintParent) {
					componentToRepaint.repaint();
				} else {
					Container parent = component.getParent();
					if (parent != null) {
						parent.repaint();
					}
				}
			}

			/*
			 * (non-Javadoc)
			 * 
			 * @see
			 * org.jvnet.lafwidget.animation.FadeTrackerAdapter#fadeEnded(org
			 * .jvnet.lafwidget.animation.FadeKind)
			 */
			@Override
			public void fadeEnded(final FadeKind fadeKind) {
				// System.out.println(toIgnoreSelection);
				component.putClientProperty(prevComponentStateKey,
						ComponentState.getState(model, component,
								toIgnoreSelection));
				component.putClientProperty(prevSelComponentStateKey,
						ComponentState.getState(model, component, false));
				component.putClientProperty(nextComponentStateKey, null);
				component.putClientProperty(nextSelComponentStateKey, null);
				// String text = (component instanceof AbstractButton) ?
				// ((AbstractButton)
				// component)
				// .getText()
				// : "";
				// ComponentState prevState = (ComponentState) component
				// .getClientProperty(PREV_COMPONENT_STATE);
				// System.out.println(component.getClass().getSimpleName() + "["
				// + text + "]" + " : " + fadeKind.toString()
				// + " - tracking prev as " + prevState.name());
				if (!toRepaintParent) {
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							if (component instanceof JMenuItem) {
								if (isCoveredByLightweightPopups(component)) {
									component
											.putClientProperty(
													SubstanceCoreUtilities.IS_COVERED_BY_LIGHTWEIGHT_POPUPS,
													Boolean.TRUE);
								} else {
									component
											.putClientProperty(
													SubstanceCoreUtilities.IS_COVERED_BY_LIGHTWEIGHT_POPUPS,
													null);
								}
							}
							// System.out.println("Repainted "
							// + componentToRepaint.getClass()
							// .getSimpleName() + " on "
							// + fadeKind.toString() + " end");
							componentToRepaint.repaint();
						}
					});
				} else {
					Container parent = component.getParent();
					if (parent != null) {
						parent.repaint();
					}
				}
			}

			@Override
			public void fadePerformed(FadeKind fadeKind, float fadeCycle10) {
				component.putClientProperty(nextComponentStateKey,
						ComponentState.getState(model, component,
								toIgnoreSelection));
				component.putClientProperty(nextSelComponentStateKey,
						ComponentState.getState(model, component, false));
				if (!toRepaintParent) {
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							if (component instanceof JMenuItem) {
								if (isCoveredByLightweightPopups(component)) {
									component
											.putClientProperty(
													SubstanceCoreUtilities.IS_COVERED_BY_LIGHTWEIGHT_POPUPS,
													Boolean.TRUE);
								} else {
									component
											.putClientProperty(
													SubstanceCoreUtilities.IS_COVERED_BY_LIGHTWEIGHT_POPUPS,
													null);
								}
							}
							componentToRepaint.repaint();
						}
					});
				} else {
					Container parent = component.getParent();
					if (parent != null) {
						parent.repaint();
					}
				}
			}

			private boolean isCoveredByLightweightPopups(final Component comp) {
				JRootPane rootPane = SwingUtilities.getRootPane(comp);
				if (rootPane == null)
					return false;
				Component[] popups = rootPane.getLayeredPane()
						.getComponentsInLayer(JLayeredPane.POPUP_LAYER);
				if (popups == null)
					return false;

				// System.out.println("Has " + popups.length + " popups");

				// Convert the component bounds to the layered pane.
				// Can't use the SwingUtilities.convertRectangle() as that
				// eventually will try to acquire the lock on the container
				Rectangle compBoundsConverted = SwingUtilities
						.convertRectangle(comp.getParent(), comp.getBounds(),
								rootPane.getLayeredPane());

				// System.out.println("Bounds : \n\torig : " + comp.getBounds()
				// + "\n\ttrans:" + compBoundsConverted);

				int popupIndexToStartWith = getPopupParentIndexOf(comp, popups) - 1;
				// String txt = (comp instanceof JMenuItem) ? ((JMenuItem) comp)
				// .getText() : "";
				// System.out.println("Starting scan from popup "
				// + popupIndexToStartWith + " for " + txt);
				for (int i = popupIndexToStartWith; i >= 0; i--) {
					Component popup = popups[i];
					// System.out.println("Popup " +
					// popup.getClass().getName());
					// System.out.println("Popup bounds " + popup.getBounds());
					if (compBoundsConverted.intersects(popup.getBounds())) {
						return true;
					}
				}
				return false;
			}
		};
		return callback;
	}

	/**
	 * Gets a component and a list of popups and returns the index of the popup
	 * that is a parent of the specified component. Is used to track issue 297
	 * and prevent visual artifacts.
	 * 
	 * @param comp
	 *            Component.
	 * @param popups
	 *            List of popups.
	 * @return Index of the popup which is component's parent if any, or the
	 *         popup list length otherwise.
	 */
	public static int getPopupParentIndexOf(Component comp, Component[] popups) {
		for (int i = 0; i < popups.length; i++) {
			Component popup = popups[i];

			Component currComp = comp;
			while (currComp != null) {
				if (currComp == popup) {
					return i;
				}
				currComp = currComp.getParent();
			}
		}
		return popups.length;
	}

	/**
	 * Returns the previous state of the specified component.
	 * 
	 * @param comp
	 *            Component.
	 * @return The previous state of the specified component.
	 */
	public static ComponentState getPrevComponentState(JComponent comp) {
		ComponentState result = (ComponentState) comp
				.getClientProperty(PREV_COMPONENT_STATE);
		if (result == null) {
			result = ComponentState.DEFAULT;
		}
		return result;
	}

	/**
	 * Returns the previous state of the specified component.
	 * 
	 * @param comp
	 *            Component.
	 * @param extraKey
	 *            Extra key.
	 * @return The previous state of the specified component.
	 */
	public static ComponentState getPrevComponentState(JComponent comp,
			String extraKey) {
		String prevComponentStateKey = (extraKey == null) ? SubstanceCoreUtilities.PREV_COMPONENT_STATE
				: SubstanceCoreUtilities.PREV_COMPONENT_STATE + ":" + extraKey;
		ComponentState result = (ComponentState) comp
				.getClientProperty(prevComponentStateKey);
		if (result == null) {
			result = ComponentState.DEFAULT;
		}
		return result;
	}

	/**
	 * Returns the previous state of the specified component.
	 * 
	 * @param comp
	 *            Component.
	 * @return The previous state of the specified component.
	 */
	public static ComponentState getPrevSelComponentState(JComponent comp) {
		ComponentState result = (ComponentState) comp
				.getClientProperty(PREV_SEL_COMPONENT_STATE);
		if (result == null) {
			result = ComponentState.DEFAULT;
		}
		return result;
	}

	/**
	 * Checks whether a component has the specified client property set to the
	 * specified value.
	 * 
	 * @param comp
	 *            Component.
	 * @param propertyName
	 *            Client property name.
	 * @param expectedValue
	 *            Expected value.
	 * @param checkHierarchy
	 *            if <code>true</code>, the entire component hierarchy is
	 *            traversed.
	 * @return <code>true</code> if the component has the specified client
	 *         property set to the specified value, <code>false</code>
	 *         otherwise.
	 */
	public static boolean hasPropertySetTo(Component comp, String propertyName,
			boolean expectedValue, boolean checkHierarchy) {
		if (!checkHierarchy) {
			if (comp instanceof JComponent) {
				JComponent jcomp = (JComponent) comp;
				Object componentProp = jcomp.getClientProperty(propertyName);
				if (componentProp != null) {
					if (componentProp.equals(expectedValue))
						return true;
				}
			}
		} else {
			Component c = comp;
			while (c != null) {
				if (c instanceof JComponent) {
					JComponent jcomp = (JComponent) c;
					Object componentProp = jcomp
							.getClientProperty(propertyName);
					if (componentProp != null) {
						if (componentProp.equals(expectedValue))
							return true;
					}
				}
				c = c.getParent();
			}
		}
		Object globalProp = UIManager.get(propertyName);
		if (globalProp != null) {
			return globalProp.equals(expectedValue);
		}
		return false;
	}

	/**
	 * Returns the resource bundle for the specified component.
	 * 
	 * @param jcomp
	 *            Component.
	 * @return Resource bundle for the specified component.
	 */
	public static ResourceBundle getResourceBundle(JComponent jcomp) {
		if (LafWidgetUtilities.toIgnoreGlobalLocale(jcomp)) {
			return SubstanceLookAndFeel.getLabelBundle(jcomp.getLocale());
		} else {
			return SubstanceLookAndFeel.getLabelBundle();
		}
	}

	/**
	 * Returns the border painter for the specified component.
	 * 
	 * @param comp
	 *            Component.
	 * @return Border painter for the specified component.
	 * @see SubstanceSkin#getBorderPainter()
	 */
	public static SubstanceBorderPainter getBorderPainter(Component comp) {
		return SubstanceCoreUtilities.getSkin(comp).getBorderPainter();
	}

	/**
	 * Returns the highlight border painter for the specified component.
	 * 
	 * @param comp
	 *            Component.
	 * @return Highlight border painter for the specified component.
	 * @see SubstanceSkin#getBorderPainter()
	 * @see SubstanceSkin#getHighlightBorderPainter()
	 */
	public static SubstanceBorderPainter getHighlightBorderPainter(
			Component comp) {
		SubstanceBorderPainter result = SubstanceCoreUtilities.getSkin(comp)
				.getHighlightBorderPainter();
		if (result != null)
			return result;
		return getBorderPainter(comp);
	}

	/**
	 * Returns the component hierarchy.
	 * 
	 * @param comp
	 *            Component.
	 * @return Component hierarchy string.
	 */
	public static String getHierarchy(Component comp) {
		StringBuffer buffer = new StringBuffer();
		getHierarchy(comp, buffer, 0);
		while (true) {
			if (comp instanceof Window) {
				Window w = (Window) comp;
				comp = w.getOwner();
				if (comp != null) {
					buffer.append("Owner --->\n");
					getHierarchy(comp, buffer, 0);
				}
			} else {
				break;
			}
		}
		return buffer.toString();
	}

	/**
	 * Computes the component hierarchy.
	 * 
	 * @param comp
	 *            Component.
	 * @param buffer
	 *            Hierarchy representation buffer.
	 * @param level
	 *            Hierarchy level.
	 */
	public static void getHierarchy(Component comp, StringBuffer buffer,
			int level) {
		for (int i = 0; i < level; i++)
			buffer.append("   ");
		String name = comp.getName();
		if (comp instanceof Dialog)
			name = ((Dialog) comp).getTitle();
		if (comp instanceof Frame)
			name = ((Frame) comp).getTitle();
		buffer.append(comp.getClass().getName() + "[" + name + "]\n");
		if (comp instanceof Container) {
			Container cont = (Container) comp;
			for (int i = 0; i < cont.getComponentCount(); i++)
				getHierarchy(cont.getComponent(i), buffer, level + 1);
		}
	}

	/**
	 * Returns the title pane of the specified root pane.
	 * 
	 * @param rootPane
	 *            Root pane.
	 * @return The title pane of the specified root pane.
	 */
	public static JComponent getTitlePane(JRootPane rootPane) {
		JInternalFrame jif = (JInternalFrame) SwingUtilities
				.getAncestorOfClass(JInternalFrame.class, rootPane);
		if (jif != null) {
			SubstanceInternalFrameUI ui = (SubstanceInternalFrameUI) jif
					.getUI();
			return ui.getTitlePane();
		}
		SubstanceRootPaneUI ui = (SubstanceRootPaneUI) rootPane.getUI();
		if (ui == null)
			return null;
		return ui.getTitlePane();
	}

	/**
	 * Returns the arrow icon.
	 * 
	 * @param comp
	 *            Component.
	 * @param button
	 *            Button.
	 * @param orientation
	 *            Arrow orientation.
	 * @return Arrow icon.
	 */
	public static Icon getArrowIcon(final AbstractButton button,
			final int orientation) {
		Icon result = new ArrowButtonTransitionAwareIcon(button, orientation);
		return result;
	}

	/**
	 * Returns the arrow icon.
	 * 
	 * @param comp
	 *            Component.
	 * @param button
	 *            Button.
	 * @param orientation
	 *            Arrow orientation.
	 * @return Arrow icon.
	 */
	public static Icon getDoubleArrowIcon(final Component comp,
			final AbstractButton button, final int orientation) {
		Icon result = new TransitionAwareIcon(button,
				new TransitionAwareIcon.Delegate() {
					public Icon getColorSchemeIcon(SubstanceColorScheme scheme) {
						int fontSize = SubstanceSizeUtils
								.getComponentFontSize(comp);
						return SubstanceImageCreator.getDoubleArrowIconDelta(
								fontSize, 0, 2, 0, orientation, scheme);
					}
				}, "substance.general.doubleArrow");
		return result;
	}

	/**
	 * Returns the colorization factor for the specified component.
	 * 
	 * @param c
	 *            Component.
	 * @return The colorization factor for the specified component.
	 * @see SubstanceLookAndFeel#COLORIZATION_FACTOR
	 */
	public static double getColorizationFactor(Component c) {
		JPopupMenu popupMenu = null;
		while (c != null) {
			if (c instanceof JComponent) {
				JComponent jcomp = (JComponent) c;
				Object compProp = jcomp
						.getClientProperty(SubstanceLookAndFeel.COLORIZATION_FACTOR);
				if (compProp instanceof Double)
					return (Double) compProp;
			}
			if (c instanceof JPopupMenu) {
				popupMenu = (JPopupMenu) c;
			}
			c = c.getParent();
		}

		if (popupMenu != null) {
			Component invoker = popupMenu.getInvoker();
			if (popupMenu != invoker)
				return getColorizationFactor(popupMenu.getInvoker());
		}

		Object globalProp = UIManager
				.get(SubstanceLookAndFeel.COLORIZATION_FACTOR);
		if (globalProp instanceof Double) {
			return (Double) globalProp;
		}

		return 0.5;
	}

	/**
	 * Returns the skin of the specified component.
	 * 
	 * @param c
	 *            Component.
	 * @return The skin of the specified component.
	 * @see SubstanceLookAndFeel#SKIN_PROPERTY
	 */
	public static SubstanceSkin getSkin(Component c) {
		if (!SubstanceLookAndFeel.isCurrentLookAndFeel())
			return null;

		SubstanceComboPopup comboPopup = (SubstanceComboPopup) SwingUtilities
				.getAncestorOfClass(SubstanceComboPopup.class, c);
		if (comboPopup != null) {
			// special case for combobox popup - take the skin
			// of the combobox itself - issue 439
			return getSkin(comboPopup.getCombobox());
		}

		JRootPane rootPane = SwingUtilities.getRootPane(c);

		if (c instanceof SubstanceInternalFrameTitlePane) {
			// use correct root pane for painting the
			// title panes of internal frames
			Component frame = c.getParent();
			if ((frame != null) && (frame instanceof JInternalFrame)) {
				rootPane = ((JInternalFrame) frame).getRootPane();
			}
		}
		if ((c != null)
				&& (c.getParent() instanceof SubstanceInternalFrameTitlePane)) {
			// use correct root pane for painting the
			// title buttons of internal frames
			Component frame = c.getParent().getParent();
			if ((frame != null) && (frame instanceof JInternalFrame)) {
				rootPane = ((JInternalFrame) frame).getRootPane();
			}
		}
		if (rootPane != null) {
			Object skinProp = rootPane
					.getClientProperty(SubstanceLookAndFeel.SKIN_PROPERTY);
			if (skinProp instanceof SubstanceSkin)
				return (SubstanceSkin) skinProp;
		}
		return SubstanceLookAndFeel.getCurrentSkin();
	}

	/**
	 * Returns a hash key for the specified parameters.
	 * 
	 * @param objects
	 *            Key components.
	 * @return Hash key.
	 */
	public static HashMapKey getHashKey(Object... objects) {
		return new HashMapKey(objects);
	}

	/**
	 * Stops all Substance threads. Improper use may result in UI artifacts and
	 * runtime exceptions.
	 */
	public static void stopThreads() {
		PulseTracker.stopAllTimers();
		FadeTracker.getInstance().stopAllTimers();
		TrackableThread.requestStopAllThreads();
	}

	/**
	 * Retrieves a single parameter from the VM flags.
	 * 
	 * @param parameterName
	 *            Parameter name.
	 * @return Parameter value.
	 */
	public static String getVmParameter(String parameterName) {
		String paramValue = null;
		try {
			paramValue = System.getProperty(parameterName);
			return paramValue;
		} catch (Exception exc) {
			// probably running in unsecure JNLP - ignore
			return null;
		}
	}

	/**
	 * Checks whether the specified component uses highlights.
	 * 
	 * @param comp
	 *            Component.
	 * @return <code>true</code> if the specified component uses highlights,
	 *         <code>false</code> otherwise.
	 */
	public static boolean toUseHighlightColorScheme(Component comp) {
		return hasPropertySetTo(comp, USE_HIGHLIGHT, true, true);
	}

	/**
	 * Tests UI threading violations on creating the specified component.
	 * 
	 * @param comp
	 *            Component.
	 * @throws UiThreadingViolationException
	 *             If the component is created off Event Dispatch Thread.
	 */
	public static void testComponentCreationThreadingViolation(Component comp) {
		if (!SwingUtilities.isEventDispatchThread()) {
			UiThreadingViolationException uiThreadingViolationError = new UiThreadingViolationException(
					"Component creation must be done on Event Dispatch Thread");
			uiThreadingViolationError.printStackTrace(System.err);
			throw uiThreadingViolationError;
		}
	}

	/**
	 * Tests UI threading violations on changing the state the specified
	 * component.
	 * 
	 * @param comp
	 *            Component.
	 * @throws UiThreadingViolationException
	 *             If the component is changing state off Event Dispatch Thread.
	 */
	public static void testComponentStateChangeThreadingViolation(Component comp) {
		if (!SwingUtilities.isEventDispatchThread()) {
			UiThreadingViolationException uiThreadingViolationError = new UiThreadingViolationException(
					"Component state change must be done on Event Dispatch Thread");
			uiThreadingViolationError.printStackTrace(System.err);
			throw uiThreadingViolationError;
		}
	}

	public static void traceSubstanceApiUsage(Component comp, String message) {
		Window w = SwingUtilities.getWindowAncestor(comp);
		String wTitle = null;
		if (w instanceof Frame) {
			wTitle = ((Frame) w).getTitle();
		}
		if (w instanceof Dialog) {
			wTitle = ((Dialog) w).getTitle();
		}
		throw new IllegalArgumentException(message + " [component "
				+ comp.getClass().getSimpleName() + " in window "
				+ w.getClass().getSimpleName() + ":'" + wTitle + "' under "
				+ UIManager.getLookAndFeel().getName() + "]");
	}

	/**
	 * Scans {@code imageList} for best-looking image of specified dimensions.
	 * Image can be scaled and/or padded with transparency.
	 */
	public static BufferedImage getScaledIconImage(
			java.util.List<Image> imageList, int width, int height) {
		if (width == 0 || height == 0) {
			return null;
		}
		Image bestImage = null;
		int bestWidth = 0;
		int bestHeight = 0;
		double bestSimilarity = 3; // Impossibly high value
		for (Iterator<Image> i = imageList.iterator(); i.hasNext();) {
			// Iterate imageList looking for best matching image.
			// 'Similarity' measure is defined as good scale factor and small
			// insets.
			// best possible similarity is 0 (no scale, no insets).
			// It's found while the experiments that good-looking result is
			// achieved
			// with scale factors x1, x3/4, x2/3, xN, x1/N.
			Image im = i.next();
			if (im == null) {
				continue;
			}
			int iw;
			int ih;
			try {
				iw = im.getWidth(null);
				ih = im.getHeight(null);
			} catch (Exception e) {
				continue;
			}
			if (iw > 0 && ih > 0) {
				// Calc scale factor
				double scaleFactor = Math.min((double) width / (double) iw,
						(double) height / (double) ih);
				// Calculate scaled image dimensions
				// adjusting scale factor to nearest "good" value
				int adjw = 0;
				int adjh = 0;
				double scaleMeasure = 1; // 0 - best (no) scale, 1 - impossibly
				// bad
				if (scaleFactor >= 2) {
					// Need to enlarge image more than twice
					// Round down scale factor to multiply by integer value
					scaleFactor = Math.floor(scaleFactor);
					adjw = iw * (int) scaleFactor;
					adjh = ih * (int) scaleFactor;
					scaleMeasure = 1.0 - 0.5 / scaleFactor;
				} else if (scaleFactor >= 1) {
					// Don't scale
					scaleFactor = 1.0;
					adjw = iw;
					adjh = ih;
					scaleMeasure = 0;
				} else if (scaleFactor >= 0.75) {
					// Multiply by 3/4
					scaleFactor = 0.75;
					adjw = iw * 3 / 4;
					adjh = ih * 3 / 4;
					scaleMeasure = 0.3;
				} else if (scaleFactor >= 0.6666) {
					// Multiply by 2/3
					scaleFactor = 0.6666;
					adjw = iw * 2 / 3;
					adjh = ih * 2 / 3;
					scaleMeasure = 0.33;
				} else {
					// Multiply size by 1/scaleDivider
					// where scaleDivider is minimum possible integer
					// larger than 1/scaleFactor
					double scaleDivider = Math.ceil(1.0 / scaleFactor);
					scaleFactor = 1.0 / scaleDivider;
					adjw = (int) Math.round(iw / scaleDivider);
					adjh = (int) Math.round(ih / scaleDivider);
					scaleMeasure = 1.0 - 1.0 / scaleDivider;
				}
				double similarity = ((double) width - (double) adjw) / width
						+ ((double) height - (double) adjh) / height + // Large
						// padding
						// is
						// bad
						scaleMeasure; // Large rescale is bad
				if (similarity < bestSimilarity) {
					bestSimilarity = similarity;
					bestImage = im;
					bestWidth = adjw;
					bestHeight = adjh;
				}
				if (similarity == 0)
					break;
			}
		}
		if (bestImage == null) {
			// No images were found, possibly all are broken
			return null;
		}
		BufferedImage bimage = new BufferedImage(width, height,
				BufferedImage.TYPE_INT_ARGB);
		Graphics2D g = bimage.createGraphics();
		g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
				RenderingHints.VALUE_INTERPOLATION_BILINEAR);

		// BufferedImage bestBuffered = getBlankImage(bestImage.getWidth(null),
		// bestImage.getHeight(null));
		// bestBuffered.getGraphics().drawImage(bestImage, 0, 0, null);
		// BufferedImage scaled =
		// LafWidgetUtilities.createThumbnail(bestBuffered,
		// bestWidth);

		try {
			int x = (width - bestWidth) / 2;
			int y = (height - bestHeight) / 2;
			g.drawImage(bestImage, x, y, bestWidth, bestHeight, null);
		} finally {
			g.dispose();
		}
		return bimage;
	}

	public static boolean canReplaceChildBackgroundColor(Color background) {
		return (background instanceof UIResource)
				|| (background instanceof SubstanceColorResource);
	}
}
