/*
 * Copyright (c) 2005-2010 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.pushingpixels.substance.internal.ui;

import java.awt.*;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;

import javax.swing.*;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import javax.swing.plaf.*;
import javax.swing.plaf.basic.BasicComboBoxUI;
import javax.swing.plaf.basic.ComboPopup;
import javax.swing.text.JTextComponent;

import org.pushingpixels.substance.api.SubstanceLookAndFeel;
import org.pushingpixels.substance.api.SubstanceConstants.Side;
import org.pushingpixels.substance.api.renderers.SubstanceDefaultComboBoxRenderer;
import org.pushingpixels.substance.api.shaper.ClassicButtonShaper;
import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
import org.pushingpixels.substance.internal.utils.*;
import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities.TextComponentAware;
import org.pushingpixels.substance.internal.utils.border.SubstanceTextComponentBorder;
import org.pushingpixels.substance.internal.utils.combo.*;
import org.pushingpixels.substance.internal.utils.icon.TransitionAwareIcon;

/**
 * UI for combo boxes in <b>Substance </b> look and feel.
 * 
 * @author Kirill Grouchnikov
 * @author Thomas Bierhance http://www.orbital-computer.de/JComboBox/
 * @author inostock
 */
public class SubstanceComboBoxUI extends BasicComboBoxUI implements
		TransitionAwareUI {
	/**
	 * Property change handler on <code>enabled</code> property,
	 * <code>componentOrientation</code> property and on
	 * {@link SubstanceLookAndFeel#COMBO_BOX_POPUP_FLYOUT_ORIENTATION} property.
	 */
	protected ComboBoxPropertyChangeHandler substanceChangeHandler;

	protected StateTransitionTracker stateTransitionTracker;

	/**
	 * Surrogate button model for tracking the state transitions.
	 */
	protected ButtonModel transitionModel;

	/**
	 * Listener for transition animations.
	 */
    protected RolloverTextControlListener substanceRolloverListener;

	/**
	 * Painting delegate.
	 */
    protected ComboBoxBackgroundDelegate delegate;

    protected Icon uneditableArrowIcon;

    protected Insets layoutInsets;

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
	 */
	public static ComponentUI createUI(JComponent comp) {
		SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
		SubstanceComboBoxUI ui = new SubstanceComboBoxUI((JComboBox) comp);
		ui.comboBox = (JComboBox) comp;
		ui.comboBox.setOpaque(false);
		return ui;
	}

	@Override
	public void installUI(JComponent c) {
		super.installUI(c);

		c.putClientProperty(SubstanceCoreUtilities.TEXT_COMPONENT_AWARE,
				new TextComponentAware<JComboBox>() {
					@Override
					public JTextComponent getTextComponent(JComboBox t) {
						if (t.isEditable()) {
							Component editorComp = t.getEditor()
									.getEditorComponent();
							if (editorComp instanceof JTextComponent) {
								return (JTextComponent) editorComp;
							}
						}
						return null;
					}
				});
	}

	@Override
	public void uninstallUI(JComponent c) {
		c.putClientProperty(SubstanceCoreUtilities.TEXT_COMPONENT_AWARE, null);

		super.uninstallUI(c);
	}

	public SubstanceComboBoxUI(JComboBox combo) {
		this.comboBox = combo;

		this.transitionModel = new DefaultButtonModel();
		this.transitionModel.setArmed(false);
		this.transitionModel.setSelected(false);
		this.transitionModel.setPressed(false);
		this.transitionModel.setRollover(false);
		this.transitionModel.setEnabled(combo.isEnabled());

		this.stateTransitionTracker = new StateTransitionTracker(this.comboBox,
				this.transitionModel);

		this.delegate = new ComboBoxBackgroundDelegate();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.basic.BasicComboBoxUI#createArrowButton()
	 */
	@Override
	protected JButton createArrowButton() {
		SubstanceDropDownButton result = new SubstanceDropDownButton(
				this.comboBox);
		result.setFont(this.comboBox.getFont());
		result.setIcon(getCurrentIcon(result));
		return result;
	}

	/**
	 * Returns the icon for the specified arrow button.
	 * 
	 * @param button
	 *            Arrow button.
	 * @return Icon for the specified button.
	 */
	private Icon getCurrentIcon(JButton button) {
		Icon icon = SubstanceCoreUtilities
				.getArrowIcon(button, SubstanceCoreUtilities
						.getPopupFlyoutOrientation(this.comboBox));
		return icon;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.basic.BasicComboBoxUI#createRenderer()
	 */
	@Override
	protected ListCellRenderer createRenderer() {
		return new SubstanceDefaultComboBoxRenderer.SubstanceUIResource(
				this.comboBox);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.basic.BasicComboBoxUI#installListeners()
	 */
	@Override
	protected void installListeners() {
		super.installListeners();
		this.substanceChangeHandler = new ComboBoxPropertyChangeHandler();
		this.comboBox.addPropertyChangeListener(this.substanceChangeHandler);

		this.substanceRolloverListener = new RolloverTextControlListener(
				this.comboBox, this, this.transitionModel);
		this.substanceRolloverListener.registerListeners();

		this.stateTransitionTracker.registerModelListeners();
		this.stateTransitionTracker.registerFocusListeners();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.basic.BasicComboBoxUI#uninstallListeners()
	 */
	@Override
	protected void uninstallListeners() {
		this.stateTransitionTracker.unregisterModelListeners();
		this.stateTransitionTracker.unregisterFocusListeners();

		this.comboBox.removePropertyChangeListener(this.substanceChangeHandler);
		this.substanceChangeHandler = null;

		this.substanceRolloverListener.unregisterListeners();
		this.substanceRolloverListener = null;

		super.uninstallListeners();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.basic.BasicComboBoxUI#installDefaults()
	 */
	@Override
	protected void installDefaults() {
		super.installDefaults();

		// this icon must be created after the font has been installed
		// on the combobox
		this.uneditableArrowIcon = SubstanceCoreUtilities.getArrowIcon(
				this.comboBox,
				new TransitionAwareIcon.TransitionAwareUIDelegate() {
					@Override
					public TransitionAwareUI getTransitionAwareUI() {
						return (TransitionAwareUI) comboBox.getUI();
					}
				}, SubstanceCoreUtilities
						.getPopupFlyoutOrientation(this.comboBox));
		this.updateComboBoxBorder();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.basic.BasicComboBoxUI#createLayoutManager()
	 */
	@Override
	protected LayoutManager createLayoutManager() {
		return new SubstanceComboBoxLayoutManager();
	}

	/**
	 * Layout manager for combo box.
	 * 
	 * @author Kirill Grouchnikov
	 */
	private class SubstanceComboBoxLayoutManager extends
			BasicComboBoxUI.ComboBoxLayoutManager {
		/*
		 * (non-Javadoc)
		 * 
		 * @see java.awt.LayoutManager#layoutContainer(java.awt.Container)
		 */
		@Override
		public void layoutContainer(Container parent) {
			JComboBox cb = (JComboBox) parent;

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

			Insets insets = layoutInsets;
			int buttonWidth = SubstanceSizeUtils
					.getScrollBarWidth(SubstanceSizeUtils
							.getComponentFontSize(comboBox));
			// buttonWidth = Math.max(buttonWidth,
			// arrowButton.getPreferredSize().width);

			if (arrowButton != null) {
				if (!comboBox.isEditable()) {
					arrowButton.setBounds(0, 0, 0, 0);
				} else {
					if (cb.getComponentOrientation().isLeftToRight()) {
						arrowButton.setBounds(width - buttonWidth
								- insets.right, 0, buttonWidth + insets.right,
								height);
					} else {
						arrowButton.setBounds(0, 0, buttonWidth + insets.left,
								height);
					}
				}
			}
			if (editor != null) {
				Rectangle r = rectangleForCurrentValue();
				editor.setBounds(r);
			}
		}
	}

	@Override
	protected Rectangle rectangleForCurrentValue() {
		int width = this.comboBox.getWidth();
		int height = this.comboBox.getHeight();
		Insets insets = this.layoutInsets;
		int buttonWidth = SubstanceSizeUtils
				.getScrollBarWidth(SubstanceSizeUtils
						.getComponentFontSize(comboBox));
		if (this.comboBox.getComponentOrientation().isLeftToRight()) {
			return new Rectangle(insets.left, insets.top, width - insets.left
					- insets.right - buttonWidth, height - insets.top
					- insets.bottom);
		} else {
			int startX = insets.left + buttonWidth;
			return new Rectangle(startX, insets.top, width - startX
					- insets.right, height - insets.top - insets.bottom);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.basic.BasicComboBoxUI#getDefaultSize()
	 */
	@Override
	protected Dimension getDefaultSize() {
		Component rend = new SubstanceDefaultComboBoxRenderer(this.comboBox)
				.getListCellRendererComponent(listBox, " ", -1, false, false);
		rend.setFont(this.comboBox.getFont());

		return rend.getPreferredSize();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * javax.swing.plaf.basic.BasicComboBoxUI#getMinimumSize(javax.swing.JComponent
	 * )
	 */
	@Override
	public Dimension getMinimumSize(JComponent c) {
		if (!this.isMinimumSizeDirty) {
			return new Dimension(this.cachedMinimumSize);
		}

		// Dimension size = null;
		//
		// if (!this.comboBox.isEditable() && this.arrowButton != null
		// && this.arrowButton instanceof SubstanceComboBoxButton) {
		//
		SubstanceDropDownButton button = (SubstanceDropDownButton) this.arrowButton;
		Insets buttonInsets = button.getInsets();
		Insets insets = this.comboBox.getInsets();

		Dimension size = this.getDisplaySize();

		size.width += insets.left + insets.right;
		size.width += buttonInsets.left + buttonInsets.right;
		size.width += button.getMinimumSize().getWidth();
		size.height += insets.top + insets.bottom;
		// } else if (this.comboBox.isEditable() && this.arrowButton != null
		// && this.editor != null) {
		// size = super.getMinimumSize(c);
		// } else {
		// size = super.getMinimumSize(c);
		// }

		this.cachedMinimumSize.setSize(size.width, size.height);
		this.isMinimumSizeDirty = false;

		return new Dimension(this.cachedMinimumSize);
	}

	/**
	 * This property change handler changes combo box arrow icon based on the
	 * enabled status of the combo box.
	 * 
	 * @author Kirill Grouchnikov
	 */
	public class ComboBoxPropertyChangeHandler extends PropertyChangeHandler {
		/*
		 * (non-Javadoc)
		 * 
		 * @seejavax.swing.plaf.basic.BasicComboBoxUI$PropertyChangeHandler#
		 * propertyChange(java.beans.PropertyChangeEvent)
		 */
		@Override
		public void propertyChange(final PropertyChangeEvent e) {
			String propertyName = e.getPropertyName();

			if (propertyName.equals("componentOrientation")) {
				SwingUtilities.invokeLater(new Runnable() {
					@Override
                    public void run() {
						if (SubstanceComboBoxUI.this.comboBox == null)
							return;
						final ComponentOrientation newOrientation = (ComponentOrientation) e
								.getNewValue();
						final ListCellRenderer cellRenderer = SubstanceComboBoxUI.this.comboBox
								.getRenderer();
						final ComboBoxEditor editor = SubstanceComboBoxUI.this.comboBox
								.getEditor();
						if (SubstanceComboBoxUI.this.popup instanceof Component) {
							final Component cPopup = (Component) SubstanceComboBoxUI.this.popup;
							cPopup.applyComponentOrientation(newOrientation);
							cPopup.doLayout();
						}
						if (cellRenderer instanceof Component) {
							((Component) cellRenderer)
									.applyComponentOrientation(newOrientation);
						}
						if ((editor != null)
								&& (editor.getEditorComponent() != null)) {
							(editor.getEditorComponent())
									.applyComponentOrientation(newOrientation);
						}
						if (SubstanceComboBoxUI.this.comboBox != null)
							SubstanceComboBoxUI.this.comboBox.repaint();

						configureArrowButtonStraightSide();
					}
				});
			}

			if (SubstanceLookAndFeel.COMBO_BOX_POPUP_FLYOUT_ORIENTATION
					.equals(propertyName)) {
				((SubstanceDropDownButton) arrowButton)
						.setIcon(SubstanceCoreUtilities
								.getArrowIcon(
										arrowButton,
										SubstanceCoreUtilities
												.getPopupFlyoutOrientation(SubstanceComboBoxUI.this.comboBox)));

			}

			if ("font".equals(propertyName)) {
				SwingUtilities.invokeLater(new Runnable() {
					@Override
                    public void run() {
						if (comboBox != null)
							comboBox.updateUI();
					}
				});
			}

			if ("background".equals(propertyName)) {
				if (comboBox.isEditable()) {
					comboBox.getEditor().getEditorComponent().setBackground(
							comboBox.getBackground());
					popup.getList().setBackground(comboBox.getBackground());
				}
			}

			if ("editable".equals(propertyName)) {
				updateComboBoxBorder();
				isMinimumSizeDirty = true;
			}

			if ("enabled".equals(propertyName)) {
				SubstanceComboBoxUI.this.transitionModel.setEnabled(comboBox
						.isEnabled());
			}
			// Do not call super - fix for bug 63
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.basic.BasicComboBoxUI#createPopup()
	 */
	@Override
	protected ComboPopup createPopup() {
		final ComboPopup sPopup = new SubstanceComboPopup(this.comboBox);

		final ComponentOrientation currOrientation = this.comboBox
				.getComponentOrientation();

		SwingUtilities.invokeLater(new Runnable() {
			@Override
            public void run() {
				if (SubstanceComboBoxUI.this.comboBox == null)
					return;

				if (sPopup instanceof Component) {
					final Component cPopup = (Component) sPopup;
					cPopup.applyComponentOrientation(currOrientation);
					cPopup.doLayout();
				}
				ListCellRenderer cellRenderer = SubstanceComboBoxUI.this.comboBox
						.getRenderer();
				if (cellRenderer instanceof Component) {
					((Component) cellRenderer)
							.applyComponentOrientation(currOrientation);
				}
				ComboBoxEditor editor = SubstanceComboBoxUI.this.comboBox
						.getEditor();
				if ((editor != null) && (editor.getEditorComponent() != null)) {
					(editor.getEditorComponent())
							.applyComponentOrientation(currOrientation);
				}
				SubstanceComboBoxUI.this.comboBox.repaint();
			}
		});
		return sPopup;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.basic.BasicComboBoxUI#paint(java.awt.Graphics,
	 * javax.swing.JComponent)
	 */
	@Override
	public void paint(Graphics g, JComponent c) {
		Graphics2D graphics = (Graphics2D) g.create();
		int width = this.comboBox.getWidth();
		int height = this.comboBox.getHeight();
		Insets insets = this.comboBox.getInsets();

		int componentFontSize = SubstanceSizeUtils
				.getComponentFontSize(this.comboBox);
		if (this.comboBox.isEditable()) {
			int borderDelta = (int) Math.floor(SubstanceSizeUtils
					.getBorderStrokeWidth(componentFontSize));
			Shape contour = SubstanceOutlineUtilities
					.getBaseOutline(
							width,
							height,
							Math
									.max(
											0,
											2.0f
													* SubstanceSizeUtils
															.getClassicButtonCornerRadius(componentFontSize)
													- borderDelta), null,
							borderDelta);

			graphics.setColor(SubstanceTextUtilities
					.getTextBackgroundFillColor(this.comboBox));
			graphics.fill(contour);
		} else {
			this.delegate.updateBackground(graphics, this.comboBox,
					this.transitionModel);

			Icon icon = this.uneditableArrowIcon;
			int iw = icon.getIconWidth();
			int ih = icon.getIconHeight();
			int origButtonWidth = SubstanceSizeUtils
					.getScrollBarWidth(componentFontSize);
			if (this.comboBox.getComponentOrientation().isLeftToRight()) {
				int iconX = width - origButtonWidth - insets.right / 2
						+ (origButtonWidth - iw) / 2;
				int iconY = insets.top
						+ (height - insets.top - insets.bottom - ih) / 2;
				icon.paintIcon(this.comboBox, graphics, iconX, iconY);
			} else {
				int iconX = insets.left / 2 + (origButtonWidth - iw) / 2;
				int iconY = insets.top
						+ (height - insets.top - insets.bottom - ih) / 2;
				icon.paintIcon(this.comboBox, graphics, iconX, iconY);
			}
		}

		hasFocus = comboBox.hasFocus();
		if (!comboBox.isEditable()) {
			Rectangle r = rectangleForCurrentValue();

			ListCellRenderer renderer = this.comboBox.getRenderer();
			Component rendererComponent;
			if (hasFocus) {
				rendererComponent = renderer.getListCellRendererComponent(
						this.listBox, this.comboBox.getSelectedItem(), -1,
						true, hasFocus);
			} else {
				rendererComponent = renderer.getListCellRendererComponent(
						this.listBox, this.comboBox.getSelectedItem(), -1,
						false, hasFocus);
			}
			rendererComponent.setFont(this.comboBox.getFont());

			// Fix for 4238829: should lay out the JPanel.
			boolean shouldValidate = false;
			if (rendererComponent instanceof JPanel) {
				shouldValidate = true;
			}

			// SubstanceCoreUtilities.workaroundBug6576507(graphics);

			if (this.comboBox.getComponentOrientation().isLeftToRight()) {
				this.currentValuePane.paintComponent(graphics,
						rendererComponent, this.comboBox, r.x, r.y, r.width,
						r.height, shouldValidate);
			} else {
				this.currentValuePane.paintComponent(graphics,
						rendererComponent, this.comboBox, r.x, r.y, r.width,
						r.height, shouldValidate);
			}
		}

		if (!this.comboBox.isEditable()) {
			Rectangle r = new Rectangle(insets.left, layoutInsets.top, width
					- insets.left - insets.right, height - layoutInsets.top
					- layoutInsets.bottom);
			this.paintFocus(graphics, r);
		}

		graphics.dispose();
	}

	/**
	 * Paints the focus indication.
	 * 
	 * @param g
	 *            Graphics.
	 * @param bounds
	 *            Bounds for text.
	 */
	protected void paintFocus(Graphics g, Rectangle bounds) {
		int fontSize = SubstanceSizeUtils.getComponentFontSize(this.comboBox);
		int focusRingPadding = SubstanceSizeUtils.getFocusRingPadding(fontSize) / 2;
		int x = bounds.x;
		int y = bounds.y;
		Graphics2D g2d = (Graphics2D) g.create();
		g2d.translate(x, y);

		SubstanceCoreUtilities.paintFocus(g2d, this.comboBox, this.comboBox,
				this, SubstanceOutlineUtilities.getBaseOutline(bounds.width,
						bounds.height, SubstanceSizeUtils
								.getClassicButtonCornerRadius(fontSize), null,
						0), bounds, 1.0f, focusRingPadding);

		g2d.dispose();
	}

	/**
	 * Returns the popup of the associated combobox.
	 * 
	 * @return The popup of the associated combobox.
	 */
	public ComboPopup getPopup() {
		return this.popup;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.basic.BasicComboBoxUI#configureArrowButton()
	 */
	@Override
	public void configureArrowButton() {
		super.configureArrowButton();
		// Mustang decided to make the arrow button focusable on
		// focusable comboboxes
		this.arrowButton.setFocusable(false);

		// this.substanceFocusListener = new FocusListener() {
		// public void focusGained(FocusEvent e) {
		// arrowButton.setSelected(true);
		// }
		//
		// public void focusLost(FocusEvent e) {
		// arrowButton.setSelected(false);
		// }
		// };
		// this.arrowButton.setSelected(this.comboBox.hasFocus());
		// this.comboBox.addFocusListener(this.substanceFocusListener);

		this.configureArrowButtonStraightSide();
	}

	/**
	 * Configures the straight side of the arrow button.
	 */
	protected void configureArrowButtonStraightSide() {
		this.arrowButton.putClientProperty(
				SubstanceLookAndFeel.BUTTON_SIDE_PROPERTY, this.comboBox
						.getComponentOrientation().isLeftToRight() ? Side.LEFT
						: Side.RIGHT);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.basic.BasicComboBoxUI#unconfigureArrowButton()
	 */
	@Override
	public void unconfigureArrowButton() {
		// this.comboBox.removeFocusListener(this.substanceFocusListener);
		// this.substanceFocusListener = null;
		super.unconfigureArrowButton();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.basic.BasicComboBoxUI#configureEditor()
	 */
	@Override
	protected void configureEditor() {
		super.configureEditor();
		// This for Mustang - setting Substance once again adds a border on
		// the text field in the combo editor.
		if (this.editor instanceof JComponent) {
			Insets ins = SubstanceSizeUtils
					.getComboTextBorderInsets(SubstanceSizeUtils
							.getComponentFontSize(this.editor));
			((JComponent) this.editor).setBorder(new EmptyBorder(ins.top,
					ins.left, ins.bottom, ins.right));
			this.editor.setBackground(this.comboBox.getBackground());
			// ((JComponent) this.editor).setBorder(new LineBorder(Color.red));
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see javax.swing.plaf.basic.BasicComboBoxUI#createEditor()
	 */
	@Override
	protected ComboBoxEditor createEditor() {
		return new SubstanceComboBoxEditor.UIResource();
	}

	private void updateComboBoxBorder() {
		Border b = this.comboBox.getBorder();
		if (b == null || b instanceof UIResource) {
			int comboFontSize = SubstanceSizeUtils
					.getComponentFontSize(this.comboBox);
			Insets comboBorderInsets = SubstanceSizeUtils
					.getComboBorderInsets(comboFontSize);
			if (this.comboBox.isEditable()) {
				this.comboBox.setBorder(new SubstanceTextComponentBorder(
						comboBorderInsets));
			} else {
				this.comboBox
						.setBorder(new BorderUIResource.EmptyBorderUIResource(
								comboBorderInsets));
				// BasicComboBoxUI does not invalidate display size when
				// combo becomes uneditable. However, this is not good
				// in Substance which has different preferred size for
				// editable and uneditable combos. Calling the method below
				// will trigger the path in BasicComboBoxUI.Handler that
				// will invalidate the cached sizes.
				this.comboBox.setPrototypeDisplayValue(this.comboBox
						.getPrototypeDisplayValue());
			}
			this.layoutInsets = SubstanceSizeUtils
					.getComboLayoutInsets(comboFontSize);
		} else {
			this.layoutInsets = new Insets(0, 0, 0, 0);
		}
	}

	@Override
	public StateTransitionTracker getTransitionTracker() {
		return this.stateTransitionTracker;
	}

	@Override
	public boolean isInside(MouseEvent me) {
		if (!SubstanceLookAndFeel.isCurrentLookAndFeel()) {
			return false;
		}
		SubstanceButtonShaper shaper = ClassicButtonShaper.INSTANCE;
		if (shaper == null)
			return false;
		Shape contour = SubstanceOutlineUtilities.getBaseOutline(this.comboBox,
				SubstanceSizeUtils
						.getClassicButtonCornerRadius(SubstanceSizeUtils
								.getComponentFontSize(this.comboBox)), null);
		return contour.contains(me.getPoint());
	}
}
