/*
 * @(#)JideTabbedPane.java	Oct 7, 2002
 *
 * Copyright 2002 JIDE Software Inc. All rights reserved.
 */
package com.jidesoft.swing;

import com.jidesoft.plaf.JideTabbedPaneUI;
import com.jidesoft.plaf.LookAndFeelFactory;
import com.jidesoft.plaf.UIDefaultsLookup;
import com.jidesoft.plaf.basic.BasicJideTabbedPaneUI;
import com.jidesoft.popup.JidePopup;
import com.jidesoft.utils.JideFocusTracker;
import com.jidesoft.utils.PortingUtils;
import com.jidesoft.utils.SystemInfo;

import javax.swing.*;
import javax.swing.plaf.TabbedPaneUI;
import javax.swing.plaf.UIResource;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * <code>JideTabbedPane</code> is an enhanced version of <code>JTabbedPane</code>. Different from
 * <code>JTabbedPane</code>, it <ul> <li> has an option to hide tab area if there is only one component in the tabbed
 * pane. <li> has an option to resize tab width so that all tabs can be fitted in one row. <li> has an option to show a
 * close button along with scroll left and scroll right buttons in tab area. </ul> Except methods to set additional
 * options specified above, the usage of <code>JideTabbedPane</code> is the same as <code>JTabbedPane</code>.
 */
public class JideTabbedPane extends JTabbedPane {

    private boolean _hideOneTab = false;

    private boolean _showTabButtons = false;

    private boolean _showCloseButton = false;
    private boolean _showCloseButtonOnTab = false;
    private boolean _showCloseButtonOnMouseOver = false;
    private boolean _useDefaultShowCloseButtonOnTab = false;
    private boolean _showTabArea = true;
    private boolean _showTabContent = true;

    private boolean _showIconsOnTab = true;
    private boolean _useDefaultShowIconsOnTab = true;

    private boolean _rightClickSelect;
    private boolean _dragOverDisabled;

    private boolean _scrollSelectedTabOnWheel = false;

    private int _tabAlignment = SwingConstants.LEADING;

    /**
     * Bound property name for shrink tabs.
     */
    public static final String SHRINK_TAB_PROPERTY = "shrinkTab";

    /**
     * Bound property name for hide tab area if there is only one tab.
     */
    public static final String HIDE_IF_ONE_TAB_PROPERTY = "hideIfOneTab";

    /**
     * Bound property name for show tab button.
     */
    public static final String SHOW_TAB_BUTTONS_PROPERTY = "showTabButtons";

    /**
     * Bound property name for box style
     */
    public static final String BOX_STYLE_PROPERTY = "boxStyle";

    /**
     * Bound property name for show icons on tab
     */
    public static final String SHOW_ICONS_PROPERTY = "showIconsOnTab";

    /**
     * Bound property name for using default show icons on tab value from UIDefaults
     */
    public static final String USE_DEFAULT_SHOW_ICONS_PROPERTY = "useDefaultShowIconsOnTab";

    /**
     * Bound property name for if showing close button on tab
     */
    public static final String SHOW_CLOSE_BUTTON_ON_TAB_PROPERTY = "showCloseButtonOnTab";

    /**
     * Bound property name for if showing close button
     */
    public static final String SHOW_CLOSE_BUTTON_PROPERTY = "showCloseButton";

    /**
     * Bound property name for if the tab area is visible.
     */
    public static final String SHOW_TAB_AREA_PROPERTY = "showTabArea";

    /**
     * Bound property name for if the tab area is visible.
     */
    public static final String SHOW_TAB_CONTENT_PROPERTY = "showTabContent";

    /**
     * Bound property name for tab closable.
     */
    public static final String TAB_CLOSABLE_PROPERTY = "tabClosable";

    /**
     * Bound property name for using default show close button on tab value from UIDefaults
     */
    public static final String USE_DEFAULT_SHOW_CLOSE_BUTTON_ON_TAB_PROPERTY = "useDefaultShowCloseButtonOnTab";

    /**
     * Bound property name for if the active tab title is in bold
     */
    public static final String BOLDACTIVETAB_PROPERTY = "boldActiveTab";

    /**
     * Bound property name for gripper.
     */
    public static final String GRIPPER_PROPERTY = "gripper";

    public static final String PROPERTY_TAB_SHAPE = "tabShape";
    public static final String PROPERTY_COLOR_THEME = "colorTheme";
    public static final String PROPERTY_TAB_RESIZE_MODE = "tabResizeMode";
    public static final String PROPERTY_TAB_LEADING_COMPONENT = "tabLeadingComponent";
    public static final String PROPERTY_TAB_TRAILING_COMPONENT = "tabTrailingComponent";
    public static final String PROPERTY_TAB_COLOR_PROVIDER = "tabColorProvider";
    public static final String PROPERTY_CONTENT_BORDER_INSETS = "contentBorderInsets";
    public static final String PROPERTY_TAB_AREA_INSETS = "tabAreaInsets";
    public static final String PROPERTY_TAB_INSETS = "tabInsets";
    public static final String PROPERTY_DRAG_OVER_DISABLED = "dragOverDisabled";
    public static final String SCROLL_TAB_ON_WHEEL_PROPERTY = "scrollTabOnWheel";
    public static final String PROPERTY_SELECTED_INDEX = "selectedIndex";
    public static final String PROPERTY_SHOW_CLOSE_BUTTON_ON_MOUSE_OVER = "showCloseButtonOnMouseOver";

    public static final int BUTTON_CLOSE = 0;
    public static final int BUTTON_EAST = 1;
    public static final int BUTTON_WEST = 2;
    public static final int BUTTON_NORTH = 3;
    public static final int BUTTON_SOUTH = 4;
    public static final int BUTTON_LIST = 5;

    /**
     * @see #getUIClassID
     * @see #readObject
     */
    private static final String uiClassID = "JideTabbedPaneUI";

    /**
     * If the gripper should be shown. Gripper is something on divider to indicate it can be dragged.
     */
    private boolean _showGripper = false;

    /**
     * A converter to shorten
     */
    private StringConverter _stringConverter;

    private boolean _boldActiveTab = false;

    /**
     * The Set for the tab closable. If there is an entry in the Set, it means the tab is NOT closable.
     */
    private Set<Object> _closableSet = new HashSet<Object>();

    private Hashtable<Component, Object> _pageLastFocusTrackers = new Hashtable<Component, Object>();

    private Font _selectedTabFont;

    /**
     * A tab resize mode. The default resize mode means it will use the resize mode of {@link
     * #getDefaultTabResizeMode()} which is defined in UIDefault "JideTabbedPane.defaultResizeMode". You can change this
     * in UIDefault. It will affect the resize mode of all <code>JideTabbedPane</code>s.
     */
    public static final int RESIZE_MODE_DEFAULT = 0;

    /**
     * A tab resize mode. The none resize mode means the tab will not resize when tabbed pane width changes.
     */
    public static final int RESIZE_MODE_NONE = 1;

    /**
     * A tab resize mode. The fit resize mode means the tabs will shrink if the tabbed pane width shrinks so there is no
     * way to display the full contents of the tabs.
     */
    public static final int RESIZE_MODE_FIT = 2;

    /**
     * A tab resize mode. All tabs will be at a fixed width. The fixed width is defined as UIDefault
     * "JideTabbedPane.fixedStyleRectSize" which is an integer.
     */
    public static final int RESIZE_MODE_FIXED = 3;

    /**
     * A tab resize mode. In this mode, the select tab will have full tab width. Non-selected tab will only display the
     * icon. The actual width of non-selected tab is determined by UIDefault "JideTabbedPane.compressedStyleNoIconRectSize"
     * which is an integer.
     */
    public static final int RESIZE_MODE_COMPRESSED = 4;

    private int _tabResizeMode = RESIZE_MODE_DEFAULT;

    /**
     * color style
     */
    public static final int COLOR_THEME_DEFAULT = 0;
    public static final int COLOR_THEME_WIN2K = 1;
    public static final int COLOR_THEME_OFFICE2003 = 2;
    public static final int COLOR_THEME_VSNET = 3;
    public static final int COLOR_THEME_WINXP = 4;

    // color style
    private int _colorTheme = COLOR_THEME_DEFAULT;

    // tab shape
    public static final int SHAPE_DEFAULT = 0;
    public static final int SHAPE_WINDOWS = 1;
    public static final int SHAPE_VSNET = 2;
    public static final int SHAPE_BOX = 3;
    public static final int SHAPE_OFFICE2003 = 4;
    public static final int SHAPE_FLAT = 5;
    public static final int SHAPE_ECLIPSE = 6;
    public static final int SHAPE_ECLIPSE3X = 7;
    public static final int SHAPE_EXCEL = 8;
    public static final int SHAPE_ROUNDED_VSNET = 9;
    public static final int SHAPE_ROUNDED_FLAT = 10;
    public static final int SHAPE_WINDOWS_SELECTED = 11;

    private int _tabShape = SHAPE_DEFAULT;

    private Component _tabLeadingComponent = null;
    private Component _tabTrailingComponent = null;
    private boolean _hideTrailingWhileNoButtons = false;

    // show close button on active tab only
    private boolean _showCloseButtonOnSelectedTab = false;

    private ListCellRenderer _tabListCellRenderer;

    private Insets _contentBorderInsets;
    private Insets _tabAreaInsets;
    private Insets _tabInsets;

    private static final Logger LOGGER_EVENT = Logger.getLogger(TabEditingEvent.class.getName());

    private boolean _closeTabOnMouseMiddleButton = false;
    private boolean _layoutTrailingComponentBeforeButtons = false;
    private JidePopup _tabListPopup;

    /**
     * Creates an empty <code>TabbedPane</code> with a default tab placement of <code>JTabbedPane.TOP</code>.
     *
     * @see #addTab
     */
    public JideTabbedPane() {
        this(JideTabbedPane.TOP, JideTabbedPane.SCROLL_TAB_LAYOUT);
    }

    /**
     * Creates an empty <code>TabbedPane</code> with the specified tab placement of either:
     * <code>JTabbedPane.TOP</code>, <code>JTabbedPane.BOTTOM</code>, <code>JTabbedPane.LEFT</code>, or
     * <code>JTabbedPane.RIGHT</code>.
     *
     * @param tabPlacement the placement for the tabs relative to the content
     * @see #addTab
     */
    public JideTabbedPane(int tabPlacement) {
        this(tabPlacement, JideTabbedPane.SCROLL_TAB_LAYOUT);
    }

    /**
     * Creates an empty <code>JideTabbedPane</code> with the specified tab placement and tab layout policy.  Tab
     * placement may be either: <code>JTabbedPane.TOP</code> or <code>JTabbedPane.BOTTOM</code> Tab layout policy should
     * always be <code>JTabbedPane.SCROLL_TAB_LAYOUT</code>. <code>JTabbedPane</code> also supports
     * <code>JTabbedPane.WRAP_TAB_LAYOUT</code>. However the style of tabs in <code>JideTabbedPane</code> doesn't match
     * with <code>JTabbedPane.WRAP_TAB_LAYOUT</code> very well, so we decided not to support it.
     *
     * @param tabPlacement    the placement for the tabs relative to the content
     * @param tabLayoutPolicy the policy for laying out tabs when all tabs will not fit on one run
     * @throws IllegalArgumentException if tab placement or tab layout policy are not one of the above supported values
     * @see #addTab
     */
    public JideTabbedPane(int tabPlacement, int tabLayoutPolicy) {
        super(tabPlacement, tabLayoutPolicy);
//        if(tabLayoutPolicy == WRAP_TAB_LAYOUT)
//            tabLayoutPolicy = SCROLL_TAB_LAYOUT;

        setModel(new IgnoreableSingleSelectionModel());
    }

    /**
     * Returns the UI object which implements the L&F for this component.
     *
     * @return a <code>TabbedPaneUI</code> object
     * @see #setUI
     */
    @Override
    public TabbedPaneUI getUI() {
        return (TabbedPaneUI) ui;
    }

    /**
     * Sets the UI object which implements the L&F for this component.
     *
     * @param ui the new UI object
     * @see UIDefaults#getUI
     */
    @Override
    public void setUI(TabbedPaneUI ui) {
        super.setUI(ui);
    }

    /**
     * Resets the UI property to a value from the current look and feel.
     *
     * @see JComponent#updateUI
     */
    @Override
    public void updateUI() {
        if (UIDefaultsLookup.get(uiClassID) == null) {
            LookAndFeelFactory.installJideExtension();
        }
        setUI((TabbedPaneUI) UIManager.getUI(JideTabbedPane.this));
    }


    /**
     * Returns the name of the UI class that implements the L&F for this component.
     *
     * @return the string "TabbedPaneUI"
     * @see JComponent#getUIClassID
     * @see UIDefaults#getUI
     */
    @Override
    public String getUIClassID() {
        return uiClassID;
    }

    /**
     * Checks if tab area will be hidden if there is only one tab. <br> If the showTabButtons option is true,
     * isHideOneTab will always return false so that there is a place to place those tab buttons.
     *
     * @return true if tab areas will be hidden if there is only one tab; false otherwise.
     */
    public boolean isHideOneTab() {
        return !isShowTabButtons() && _hideOneTab;
    }

    /**
     * Sets the value if tab area will be hidden if there is only one tab. PropertyChangeEvent of
     * HIDE_IF_ONE_TAB_PROPERTY will be fired. <br> If the showTabButtons option is true, no matter what option you pass
     * to setHideOneTab, isHideOneTab will always return false.
     *
     * @param hideOne true to hide tab areas if there is only one tab; false otherwise.
     */
    public void setHideOneTab(boolean hideOne) {
        boolean oldValue = _hideOneTab;

        if (oldValue != hideOne) {
            _hideOneTab = hideOne;
            firePropertyChange(HIDE_IF_ONE_TAB_PROPERTY, oldValue, _hideOneTab);
        }
    }

    /**
     * Checks if tab area is shown.
     *
     * @return true if tab area is visible; false otherwise.
     */
    public boolean isTabShown() {
        return isShowTabArea() && !(isHideOneTab() && getTabCount() <= 1);
    }

    /**
     * Checks if tab buttons are always visible. Tab buttons are scroll left button, scroll right button and close
     * button which appear to the right of tabs in tab area. <br> If the showTabButtons is set to true, isHideOneTab
     * will always return false so that there is a place to place those tab buttons.
     *
     * @return true if tab buttons are always visible; false otherwise.
     */
    public boolean isShowTabButtons() {
        return _showTabButtons;
    }

    /**
     * Sets the value if tab buttons are always visible. PropertyChangeEvent of SHOW_TAB_BUTTONS_PROPERTY will be
     * fired.
     *
     * @param showButtons true to always show tab buttons; false otherwise.
     */
    public void setShowTabButtons(boolean showButtons) {
        boolean oldValue = _showTabButtons;

        if (oldValue != showButtons) {
            _showTabButtons = showButtons;
            firePropertyChange(SHOW_TAB_BUTTONS_PROPERTY, oldValue, _showTabButtons);
        }
    }

    private Action _closeAction;

    /**
     * Sets default close action for close button.
     *
     * @param action the close action.
     */
    public void setCloseAction(Action action) {
        Action old = _closeAction;
        if (old != action) {
            _closeAction = action;
            firePropertyChange("closeTabAction", old, _closeAction);
        }
    }

    /**
     * Gets close action.
     *
     * @return close action
     */
    public Action getCloseAction() {
        return _closeAction;
    }

    public void setAutoFocusOnTabHideClose(boolean autoFocusonTabHideClose) {
        _autoFocusonTabHideClose = autoFocusonTabHideClose;
    }

    public boolean isAutoFocusOnTabHideClose() {
        return _autoFocusonTabHideClose;
    }

    boolean _autoFocusonTabHideClose = true;

    /**
     * Resets close action to default. Default action is to remove currently selected tab.
     */
    public void resetDefaultCloseAction() {
        setCloseAction(null);
    }

    private boolean _suppressStateChangedEvents = false;

    public void setSuppressStateChangedEvents(boolean suppress) {
        _suppressStateChangedEvents = suppress;
    }

    public boolean isSuppressStateChangedEvents() {
        return _suppressStateChangedEvents;
    }

    @Override
    protected void fireStateChanged() {
        if (_suppressStateChangedEvents)
            return;

        if (!isAutoFocusOnTabHideClose())
            clearVisComp();
        super.fireStateChanged();

    }


    // setSelectedIndex will be called during moving tab. So we use this flag to suppress it.
    private boolean _suppressSetSelectedIndex = false;

    public boolean isSuppressSetSelectedIndex() {
        return _suppressSetSelectedIndex;
    }

    public void setSuppressSetSelectedIndex(boolean suppressSetSelectedIndex) {
        _suppressSetSelectedIndex = suppressSetSelectedIndex;
    }

    @Override
    public void setSelectedIndex(int index) {
        if (_suppressSetSelectedIndex)
            return;

        boolean old = isFocusCycleRoot();
        setFocusCycleRoot(true);
        try {
            int oldIndex = getSelectedIndex();
            if (oldIndex != index) {
                super.setSelectedIndex(index);
                firePropertyChange(PROPERTY_SELECTED_INDEX, oldIndex, index);
            }
        }
        finally {
            setFocusCycleRoot(old);
        }
    }

    /*
    * This is called by the popup menu in the scrollrect area
    */

    public void popupSelectedIndex(int index) {
        setSelectedIndex(index);
    }

    public void setComponentAt(int index, Component c) {
        Component oldComponent = getComponentAt(index);
        if (oldComponent != null) {
            // JTabbedPane allows a null c, but doesn't really support it.
            PageLastFocusTracker tracker = (PageLastFocusTracker) _pageLastFocusTrackers.get(oldComponent);
            _pageLastFocusTrackers.remove(oldComponent);
            if (tracker != null) {
                tracker.setHeighestComponent(null); // Clear its listeners
            }
        }

        boolean contains = false;
        if (_closableSet.contains(oldComponent)) {
            contains = true;
        }

        super.setComponentAt(index, c);

        if (contains) {
            _closableSet.add(c);
        }

        if (!isAutoFocusOnTabHideClose())
            clearVisComp();
    }

    private boolean _autoRequestFocus = true;

    /**
     * Checks if the UI should automatically request focus on selected component when doing the layout. This method is
     * only used internally when the tab is being moved.
     *
     * @return true or false. Default is true.
     */
    public boolean isAutoRequestFocus() {
        return _autoRequestFocus;
    }

    public void setAutoRequestFocus(boolean autoRequestFocus) {
        _autoRequestFocus = autoRequestFocus;
    }

    /**
     * Moves selected tab from current position to the position specified in tabIndex.
     *
     * @param tabIndex new index
     */
    public void moveSelectedTabTo(int tabIndex) {
        int selectedIndex = getSelectedIndex();
        if (selectedIndex == tabIndex) { // do nothing
            return;
        }
        if (tabIndex == -1 || selectedIndex == -1)
            return;

        if (isTabEditing())
            stopTabEditing();

        Component selectedComponent = getComponentAt(selectedIndex);

        boolean old = isAutoRequestFocus();

        boolean shouldChangeFocus = false;
        // we will not let UI to auto request focus so we will have to do it here.
        // if the selected component has focus, we will request it after the tab is moved.
        if (selectedComponent != null) {
            if (JideSwingUtilities.isAncestorOfFocusOwner(selectedComponent) && isAutoFocusOnTabHideClose()) {
                shouldChangeFocus = true;
            }
        }

        try {
            _suppressStateChangedEvents = true;
            setAutoRequestFocus(false);

            if (selectedIndex - tabIndex == 1 || tabIndex - selectedIndex == 1) {
                Component c = getComponentAt(tabIndex);
                String title = getTitleAt(tabIndex);
                String tooltip = getToolTipTextAt(tabIndex);
                Icon icon = getIconAt(tabIndex);
                _suppressSetSelectedIndex = true;
                boolean closable = true;
                if (_closableSet != null) {
                    closable = isTabClosableAt(tabIndex);
                }
                try {
                    if (tabIndex > selectedIndex)
                        insertTab(title, icon, c, tooltip, selectedIndex);
                    else {
                        insertTab(title, icon, c, tooltip, selectedIndex + 1);
                    }
                    if (!closable) {
                        _closableSet.add(c);
                    }
                }
                finally {
                    _suppressSetSelectedIndex = false;
                }
            }
            else {
                Component c = getComponentAt(selectedIndex);
                String title = getTitleAt(selectedIndex);
                String tooltip = getToolTipTextAt(selectedIndex);
                Icon icon = getIconAt(selectedIndex);
                _suppressSetSelectedIndex = true;
                boolean closable = true;
                if (_closableSet != null) {
                    closable = isTabClosableAt(tabIndex);
                }
                try {
                    if (tabIndex > selectedIndex)
                        insertTab(title, icon, c, tooltip, tabIndex + 1);
                    else {
                        insertTab(title, icon, c, tooltip, tabIndex);
                    }
                    if (!closable) {
                        _closableSet.add(c);
                    }
                }
                finally {
                    _suppressSetSelectedIndex = false;
                }
            }

            if (!SystemInfo.isJdk15Above()) {
                // a workaround for Swing bug
                if (tabIndex == getTabCount() - 2) {
                    setSelectedIndex(getTabCount() - 1);
                }
            }

            setAutoRequestFocus(old);
            setSelectedIndex(tabIndex);
        }
        finally {
            _suppressStateChangedEvents = false;

            if (shouldChangeFocus) {
                if (!requestFocusForVisibleComponent()) {
//                    System.out.println("---tabpane.requestfocus41");
                    requestFocusInWindow();
                }
            }
        }
    }

    // mtf - review if this is still needed

    public boolean requestFocusForVisibleComponent() {
        return false;
/*
        if (true)
            return false;
//        System.out.println("---JideTabbedPane.requestFocusForVisibleComponent()");
        Component visibleComponent = getSelectedComponent();
        Component lastFocused = getLastFocusedComponent(visibleComponent);
        if (lastFocused != null && lastFocused.requestFocusInWindow()) {
            return true;
        }
        else {
            // Focus the next component in the focus cycle after the tab.
            Container nearestRoot = (isFocusCycleRoot()) ?
                    this : getFocusCycleRootAncestor();
            if (nearestRoot == null) {
                return false;
            }
            Component comp = nearestRoot.getFocusTraversalPolicy().getComponentAfter(nearestRoot, this);
            return comp != null && comp.requestFocusInWindow() || JideSwingUtilities.compositeRequestFocus(visibleComponent);
        }
*/
    }

    /**
     * Get the flag that if the trailing component should be hidden while no buttons are visible.
     * <p/>
     * Be default, the flag is false. If you want to connect visibility of those two components, please set it to true.
     *
     * @return true if the trailing component would be hidden while no buttons are visible. Otherwise false.
     * @see #isShowTabArea()
     * @see #isShowTabButtons()
     * @see #setHideTrailingWhileNoButtons(boolean)
     */
    public boolean isHideTrailingWhileNoButtons() {
        return _hideTrailingWhileNoButtons;
    }

    /**
     * Set the flag that if the trailing component should be hidden while no buttons are visible.
     *
     * @param hideTrailingWhileNoButtons the flag
     * @see #isHideTrailingWhileNoButtons()
     */
    public void setHideTrailingWhileNoButtons(boolean hideTrailingWhileNoButtons) {
        _hideTrailingWhileNoButtons = hideTrailingWhileNoButtons;
    }

    /**
     * Gets the flag indicating if the trailing component should be layout before the default buttons.
     *
     * @return true if the trailing component should be layout to the left/up. Otherwise false.
     * @see #setLayoutTrailingComponentBeforeButtons(boolean)
     */
    public boolean isLayoutTrailingComponentBeforeButtons() {
        return _layoutTrailingComponentBeforeButtons;
    }

    /**
     * Sets the flag indicating if the trailing component should be layout before the default buttons.
     * <p/>
     * The default value is false. If you want your trailing component preceding to the default buttons, please set this
     * flag to true.
     *
     * @param layoutTrailingComponentBeforeButtons
     *         the flag
     */
    public void setLayoutTrailingComponentBeforeButtons(boolean layoutTrailingComponentBeforeButtons) {
        _layoutTrailingComponentBeforeButtons = layoutTrailingComponentBeforeButtons;
    }

    /*
      * Used to allow the tabswitching to be delayed until after drag/reorder opperations are done.
      */

    protected class IgnoreableSingleSelectionModel extends DefaultSingleSelectionModel {
        private static final long serialVersionUID = -4321082126384337792L;

        @Override
        protected void fireStateChanged() {
            if (!_suppressStateChangedEvents) {
                super.fireStateChanged();
            }
        }
    }

    @SuppressWarnings({"UnusedDeclaration"})
    public void processMouseSelection(int tabIndex, MouseEvent e) {

    }

    /**
     * Gets tab height.
     *
     * @return height of tab
     */
    public int getTabHeight() {
        if (getTabPlacement() == TOP || getTabPlacement() == BOTTOM) {
            return ((JideTabbedPaneUI) getUI()).getTabPanel().getHeight();
        }
        else {
            return ((JideTabbedPaneUI) getUI()).getTabPanel().getWidth();
        }
    }

    /**
     * Returns true if you want right click on unselected tab will select that tab.
     *
     * @return true if right click on unselected tab will select that tab
     */
    public boolean isRightClickSelect() {
        return _rightClickSelect;
    }

    /**
     * Sets if you want right click on unselected tab will select that tab.
     *
     * @param rightClickSelect true if right click on unselected tab will select that tab
     */
    public void setRightClickSelect(boolean rightClickSelect) {
        _rightClickSelect = rightClickSelect;
    }

    public int getTabAtLocation(int x, int y) {
        int tabCount = getTabCount();
        int i = getUI().tabForCoordinate(this, x, y);
        return i < 0 || i >= tabCount ? -1 : i;
    }


    /**
     * If the grip is visible.
     *
     * @return true if grip is visible
     */
    public boolean isShowGripper() {
        return _showGripper;
    }

    /**
     * Sets the visibility of grip.
     *
     * @param showGripper true to show grip
     */
    public void setShowGripper(boolean showGripper) {
        boolean oldShowGripper = _showGripper;
        if (oldShowGripper != showGripper) {
            _showGripper = showGripper;
            firePropertyChange(GRIPPER_PROPERTY, oldShowGripper, _showGripper);
        }
    }

    /**
     * Checks if the icon will be shown on tab.
     *
     * @return true if the icon will be shown on tab.
     */
    public boolean isShowIconsOnTab() {
        return _showIconsOnTab;
    }

    /**
     * Sets to true if the icon will be shown on tab. The value set to this method will be used only when
     * isUseDefaultShowIconsOnTab() returns false.
     *
     * @param showIconsOnTab true or false.
     */
    public void setShowIconsOnTab(boolean showIconsOnTab) {
        boolean oldShowIconsOnTab = _showIconsOnTab;
        if (oldShowIconsOnTab != showIconsOnTab) {
            _showIconsOnTab = showIconsOnTab;
            firePropertyChange(SHOW_ICONS_PROPERTY, oldShowIconsOnTab, _showIconsOnTab);
        }
    }

    /**
     * If the return is true, the value set to setShowIconsOnTab() will be ignored.
     *
     * @return if use default value from UIDefaults in L&F.
     */
    public boolean isUseDefaultShowIconsOnTab() {
        return _useDefaultShowIconsOnTab;
    }

    /**
     * Set if use the default value from UIDefaults.
     *
     * @param useDefaultShowIconsOnTab true or false.
     */
    public void setUseDefaultShowIconsOnTab(boolean useDefaultShowIconsOnTab) {
        boolean oldUseDefaultShowIconsOnTab = _useDefaultShowIconsOnTab;
        if (oldUseDefaultShowIconsOnTab != useDefaultShowIconsOnTab) {
            _useDefaultShowIconsOnTab = useDefaultShowIconsOnTab;
            firePropertyChange(USE_DEFAULT_SHOW_ICONS_PROPERTY, oldUseDefaultShowIconsOnTab, _useDefaultShowIconsOnTab);
        }
    }

    /**
     * Checks if the close button will be shown on tab.
     *
     * @return true if close button will be shown on tab.
     */
    public boolean isShowCloseButtonOnTab() {
        return _showCloseButtonOnTab;
    }

    /**
     * Sets to true if the close button will be shown on tab. If you ever call this method, we will automatically call
     * setUseDefaultShowCloseButtonOnTab(false). It will also automatically call setShowCloseButton(true) if the
     * showCloseButtonOnTab parameter is true.
     *
     * @param showCloseButtonOnTab true or false.
     */
    public void setShowCloseButtonOnTab(boolean showCloseButtonOnTab) {
        boolean oldShowCloseButtonOnTab = _showCloseButtonOnTab;
        if (oldShowCloseButtonOnTab != showCloseButtonOnTab) {
            _showCloseButtonOnTab = showCloseButtonOnTab;
            firePropertyChange(SHOW_CLOSE_BUTTON_ON_TAB_PROPERTY, oldShowCloseButtonOnTab, _showCloseButtonOnTab);
            if (_showCloseButtonOnTab) {
                setShowCloseButton(true);
            }
        }
        setUseDefaultShowCloseButtonOnTab(false);
    }

    /**
     * If the return is true, the value set to setShowCloseButtonOnTab() will be ignored.
     *
     * @return if use default value from UIDefaults in L&F.
     */
    public boolean isUseDefaultShowCloseButtonOnTab() {
        return _useDefaultShowCloseButtonOnTab;
    }

    /**
     * Set if use the default value from UIDefaults.
     *
     * @param useDefaultShowCloseButtonOnTab true or false.
     */
    public void setUseDefaultShowCloseButtonOnTab(boolean useDefaultShowCloseButtonOnTab) {
        boolean oldUseDefaultShowCloseButtonOnTab = _useDefaultShowCloseButtonOnTab;
        if (oldUseDefaultShowCloseButtonOnTab != useDefaultShowCloseButtonOnTab) {
            _useDefaultShowCloseButtonOnTab = useDefaultShowCloseButtonOnTab;
            firePropertyChange(USE_DEFAULT_SHOW_CLOSE_BUTTON_ON_TAB_PROPERTY, oldUseDefaultShowCloseButtonOnTab, _useDefaultShowCloseButtonOnTab);
        }
    }

    // below is the code to allow editing the tab title directly
    transient protected boolean _tabEditingAllowed = false;

    /**
     * Sets the value if the tab editing is allowed. Tab editing allows user to edit the tab title directly by double
     * clicking on the tab.
     *
     * @param allowed true or false.
     */
    public void setTabEditingAllowed(boolean allowed) {
        _tabEditingAllowed = allowed;
    }


    /**
     * Checks if the tab editing is allowed.
     *
     * @return true if tab editing is allowed. Otherwise false.
     */
    public boolean isTabEditingAllowed() {
        return _tabEditingAllowed && getTabLayoutPolicy() == SCROLL_TAB_LAYOUT;
    }

    transient protected TabEditingValidator _tabEditValidator;

    public void setTabEditingValidator(TabEditingValidator tabEditValidator) {
        _tabEditValidator = tabEditValidator;
    }

    public TabEditingValidator getTabEditingValidator() {
        return _tabEditValidator;
    }

    /**
     * If close button is visible.
     *
     * @return true if the close button is visible.
     */
    public boolean isShowCloseButton() {
        return _showCloseButton;
    }

    /**
     * Sets if the close button is visible. Close button can be either side by side with scroll buttons, or on each tab.
     * If you call setShowCloseButton(false), it will hide close buttons for both cases.
     *
     * @param showCloseButton true or false.
     */
    public void setShowCloseButton(boolean showCloseButton) {
        boolean oldShowCloseButton = _showCloseButton;
        if (oldShowCloseButton != showCloseButton) {
            _showCloseButton = showCloseButton;
            firePropertyChange(SHOW_CLOSE_BUTTON_PROPERTY, oldShowCloseButton, _showCloseButton);
        }
    }

    /**
     * If the tab area is visible.
     *
     * @return true if the tab area is visible.
     */
    public boolean isShowTabArea() {
        return _showTabArea;
    }

    /**
     * Sets if the tab area is visible. If not visible, you can programatically call setSelectedIndex to change ta. User
     * will not be able to do it by clicking on tabs since they are not visible.
     *
     * @param showTabArea true or false.
     */
    public void setShowTabArea(boolean showTabArea) {
        boolean oldShowTabArea = _showTabArea;
        if (oldShowTabArea != showTabArea) {
            _showTabArea = showTabArea;
            firePropertyChange(SHOW_TAB_AREA_PROPERTY, oldShowTabArea, _showTabArea);
        }
    }

    /**
     * If the tab content is visible.
     *
     * @return true if the tab content is visible.
     */
    public boolean isShowTabContent() {
        return _showTabContent;
    }

    /**
     * Sets if the tab content is visible.
     *
     * @param showTabContent true or false.
     */
    public void setShowTabContent(boolean showTabContent) {
        boolean oldShowTabContent = _showTabContent;
        if (oldShowTabContent != showTabContent) {
            _showTabContent = showTabContent;
            firePropertyChange(SHOW_TAB_CONTENT_PROPERTY, oldShowTabContent, _showTabContent);
        }
    }

    /**
     * Gets the string converter that converts the tab title to the display title.
     *
     * @return the converter that converts the tab title to the display title.
     */
    public StringConverter getStringConverter() {
        return _stringConverter;
    }

    /**
     * Sets the string converter.
     *
     * @param stringConverter the StringConverter.
     * @see #getStringConverter()
     */
    public void setStringConverter(StringConverter stringConverter) {
        _stringConverter = stringConverter;
    }

    /**
     * Gets the display title. Display title is result of using string converter that converts from the title to a
     * display title. There is no setter for display title. You control the value by using a different string
     * converter.
     *
     * @param index the index to display
     * @return the display title.
     */
    public String getDisplayTitleAt(int index) {
        if (_stringConverter != null) {
            return _stringConverter.convert(super.getTitleAt(index));
        }
        else {
            return super.getTitleAt(index);
        }
    }

    /**
     * If the active tab is in bold.
     *
     * @return if the active tab is in bold.
     */
    public boolean isBoldActiveTab() {
        return _boldActiveTab;
    }

    /**
     * Sets if the active tab is in bold.
     *
     * @param boldActiveTab the flag
     */
    public void setBoldActiveTab(boolean boldActiveTab) {
        boolean old = _boldActiveTab;
        if (old != boldActiveTab) {
            _boldActiveTab = boldActiveTab;
            firePropertyChange(BOLDACTIVETAB_PROPERTY, old, _boldActiveTab);
        }
    }

    @Override
    public void removeTabAt(int index) {
        int tabCount = getTabCount();
        int selected = getSelectedIndex();
        boolean enforce = false;
        if (selected == index && selected < tabCount - 1) {
            // since JDK5 fixed this, we only need to enforce the event when it is not JDK5 and above.
            // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6368047
            enforce = !SystemInfo.isJdk15Above();
        }

        Component c = getComponentAt(index);
        boolean contains = false;
        if (_closableSet.contains(c)) {
            contains = true;
        }

        if (!isAutoFocusOnTabHideClose())
            clearVisComp();

        super.removeTabAt(index);

        if (contains) {
            _closableSet.remove(c);
        }
        if (c != null) {
            // JTabbedPane allows a null c, but doesn't really support it.
            PageLastFocusTracker tracker = (PageLastFocusTracker) _pageLastFocusTrackers.get(c);
            _pageLastFocusTrackers.remove(c);
            if (tracker != null) {
                tracker.setHeighestComponent(null); // Clear its listeners
            }
        }

        // We need to fire events
        if (enforce) {
            try {
                fireStateChanged();
            }
            catch (Throwable th) {
                th.printStackTrace();
            }
        }

        updateUI(); // force calling updateUI so that the tab buttons will be updated
        if (getUI() instanceof BasicJideTabbedPaneUI) {
            ((BasicJideTabbedPaneUI) getUI()).ensureActiveTabIsVisible(true);
            if (isAutoFocusOnTabHideClose()) {
                ((BasicJideTabbedPaneUI) getUI()).requestFocusForVisibleComponent();
            }
        }
    }

    /**
     * Checks if the tab at tabIndex should show the close button. This is only a valid if showCloseButtonOnTab
     * attribute is true.
     * <p/>
     * By default, this method always return true. Subclass can override this method to return a different value.
     *
     * @param tabIndex the tab index
     * @return the flag.
     * @throws IndexOutOfBoundsException if index is out of range (index < 0 || index >= tab count)
     */
    public boolean isTabClosableAt(int tabIndex) {
        return !_closableSet.contains(getComponentAt(tabIndex));
    }

    /**
     * Checks if the tab at tabIndex should show the close button. This is only a valid if showCloseButtonOnTab
     * attribute is true.
     * <p/>
     * By default, this method always return true. Subclass can override this method to return a different value.
     * <p/>
     * Please note, this attribute has effect only when {@link #isShowCloseButtonOnTab()} return true.
     *
     * @param tabIndex the tab index
     * @param closable the flag indicating if the tab is closable
     * @throws IndexOutOfBoundsException if index is out of range (index < 0 || index >= tab count)
     */
    public void setTabClosableAt(int tabIndex, boolean closable) {
        if (closable) {
            _closableSet.remove(getComponentAt(tabIndex));
        }
        else {
            _closableSet.add(getComponentAt(tabIndex));
        }
        firePropertyChange(TAB_CLOSABLE_PROPERTY, !closable, closable);
    }

    protected Hashtable getPageLastFocusTrackers() {
        return _pageLastFocusTrackers;
    }

    /**
     * Gets the last focused component of a particular page.
     *
     * @param pageComponent the page component
     * @return the last focused component of a particular page.
     */

    public Component getLastFocusedComponent(Component pageComponent) {
        if (pageComponent == null) {
            return null;
        }
//        System.out.println("---JideTabbedPane.getLastFocusedComponent()" + pageComponent);

        PageLastFocusTracker tracker = (PageLastFocusTracker) (
                getPageLastFocusTrackers().get(pageComponent));

//        System.out.println("---JideTabbedPane.getLastFocusedComponent()" + componentReturn);
//        if (false) {
//            Component compTest = new JPanel() {
//                public void requestFocus() {
////                    System.out.println("---.requestFocus()22");
//                    componentReturn.requestFocus();
//                }
//
//                public boolean isRequestFocusEnabled() {
////                    System.out.println("---.isRequestFocusEnabled()");
//                    return true;
//                }
//
//                public Container getParent() {
////                    System.out.println("---.getParent()");
//                    return (Container) componentReturn;
//                }
//            };
//            if (componentReturn != null)
//                ((Container) componentReturn).add(compTest);
//            return compTest;
//        }
        return ((tracker != null) ? tracker.getLastFocusedComponent() : null);
    }

    protected void clearVisComp() {
        // this is done so that the super removetab and fireselection do not attempt to manage focus
        // A very dirty hack to access a private variable is jtabpane. Note - this only works on 1.6
        try {
            java.lang.reflect.Field field = JTabbedPane.class.getDeclaredField("visComp");
            // set accessible true
            field.setAccessible(true);
            field.set(this, null);
//			superVisComp = (Component) field.get(this);
        }
        catch (Exception e) {
            // null
        }
    }

    /**
     * Overridden to add a <code>PageLastFocusTracker</code> to each page, used to update the page's last focused
     * component.
     */
    @Override
    public void insertTab(String title, Icon icon, Component component, String tip, int index) {
        // set the component to visible false initially because the layout manager will set it to visible when
        // appropriate. This also limits the flicker from mixing lightweight/heavyweight components.
        if (component == getTabLeadingComponent() || component == getTabTrailingComponent()) {
            return;
        }
        if (component != null && !component.isVisible())
            component.setVisible(false);

        super.insertTab(title, icon, component, tip, index);

        if (component != null) {
            // JTabbedPane allows a null component, but doesn't really support it.
            _pageLastFocusTrackers.put(component, new PageLastFocusTracker(component));
        }

//        fireStateChanged();
    }

    protected class PageLastFocusTracker extends JideFocusTracker {
        // keep track of last focused component
        private Component _lastFocusedComponent;

        private FocusListener _lastFocusedListener;

        protected PageLastFocusTracker(Component pageComp) {
            this.setHeighestComponent(pageComp);
        }

        protected Component getLastFocusedComponent() {
            return _lastFocusedComponent;
        }

        @Override
        public void setHeighestComponent(Component compHeighest) {
            if (compHeighest == null) {
                if (_lastFocusedListener != null) {
                    this.removeFocusListener(_lastFocusedListener);
                    _lastFocusedListener = null;
                }
            }
            else {
                if (_lastFocusedListener == null) {
                    _lastFocusedListener = new FocusAdapter() {
                        @Override
                        public void focusGained(FocusEvent e) {
                            _lastFocusedComponent = e.getComponent();
                        }
                    };
                    this.addFocusListener(_lastFocusedListener);
                }
            }
            super.setHeighestComponent(compHeighest);
        }
    }

    /**
     * Gets the font for selected tab.
     *
     * @return the font for selected tab.
     */
    public Font getSelectedTabFont() {
        return _selectedTabFont;
    }

    /**
     * Sets the font for selected tab.
     *
     * @param selectedTabFont new font for selected tab.
     */
    public void setSelectedTabFont(Font selectedTabFont) {
        _selectedTabFont = selectedTabFont;
    }

    public int getColorTheme() {
        if (_colorTheme == COLOR_THEME_DEFAULT) {
            return getDefaultColorTheme();
        }
        else {
            return _colorTheme;
        }
    }

    public int getDefaultColorTheme() {
        return UIDefaultsLookup.getInt("JideTabbedPane.defaultTabColorTheme");
    }

    public void setColorTheme(int colorTheme) {
        int old = _colorTheme;
        if (old != colorTheme) {
            _colorTheme = colorTheme;
            firePropertyChange(PROPERTY_COLOR_THEME, old, colorTheme);
        }
    }

    public int getTabResizeMode() {
        if (_tabResizeMode == RESIZE_MODE_DEFAULT) {
            return getDefaultTabResizeMode();
        }
        else {
            return _tabResizeMode;
        }
    }

    /**
     * Sets the tab resize mode. There are five resize modes. - {@link #RESIZE_MODE_DEFAULT}, {@link #RESIZE_MODE_NONE},
     * {@link #RESIZE_MODE_FIT}, {@link #RESIZE_MODE_FIXED} and {@link #RESIZE_MODE_COMPRESSED}.
     *
     * @param resizeMode the new resize mode.
     */
    public void setTabResizeMode(int resizeMode) {
        int old = _tabResizeMode;
        if (old != resizeMode) {
            _tabResizeMode = resizeMode;
            firePropertyChange(PROPERTY_TAB_RESIZE_MODE, old, resizeMode);
        }
    }

    public int getDefaultTabResizeMode() {
        return UIDefaultsLookup.getInt("JideTabbedPane.defaultResizeMode");
    }


    public int getTabShape() {
        if (_tabShape == SHAPE_DEFAULT) {
            return getDefaultTabStyle();
        }
        else {
            return _tabShape;
        }
    }

    public int getDefaultTabStyle() {
        return UIDefaultsLookup.getInt("JideTabbedPane.defaultTabShape");
    }

    public void setTabShape(int tabShape) {
        int old = _tabShape;
        if (old != tabShape) {
            _tabShape = tabShape;
            firePropertyChange(PROPERTY_TAB_SHAPE, old, _tabShape);
        }
    }

    /**
     * Sets the tab leading component. The tab leading component will appear before the tabs in the tab area. Please
     * note, you must implement UIResource for the component you want to use as tab leading component.
     *
     * @param component the tab leading component
     * @throws IllegalArgumentException if the component doesn't implement UIResource.
     */
    public void setTabLeadingComponent(Component component) {
//        if (component != null && !(component instanceof UIResource)) {
//            throw new IllegalArgumentException("TabLeadingComponent must implement javax.swing.plaf.UIResource interface.");
//        }
        Component old = _tabLeadingComponent;
        _tabLeadingComponent = component;
        firePropertyChange(PROPERTY_TAB_LEADING_COMPONENT, old, component);
    }

    public Component getTabLeadingComponent() {
        return _tabLeadingComponent;
    }

    /**
     * Sets the tab trailing component. The tab trailing component will appear after the tabs in the tab area. Please
     * note, you must implement UIResource for the component you want to use as tab trailing component.
     *
     * @param component the tab trailing component
     * @throws IllegalArgumentException if the component doesn't implement UIResource.
     */
    public void setTabTrailingComponent(Component component) {
//        if (component != null && !(component instanceof UIResource)) {
//            throw new IllegalArgumentException("TabLeadingComponent must implement javax.swing.plaf.UIResource interface.");
//        }
        Component old = _tabTrailingComponent;
        _tabTrailingComponent = component;
        firePropertyChange(PROPERTY_TAB_TRAILING_COMPONENT, old, component);
    }

    @Override
    public Component add(Component component) {
        if (!(component instanceof UIResource) && component != getTabTrailingComponent() && component != getTabLeadingComponent()) {
            addTab(component.getName(), component);
        }
        else {
            addImpl(component, null, -1);
        }
        return component;
    }

    public Component getTabTrailingComponent() {
        return _tabTrailingComponent;
    }

    public boolean isShowCloseButtonOnSelectedTab() {
        return _showCloseButtonOnSelectedTab;
    }

    /**
     * Shows the close button on the selected tab only. You also need to setShowCloseButtonOnTab(true) and
     * setShowCloseButton(true) if you want to setShowCloseButtonOnSelectedTab(true).
     *
     * @param i the flag indicating if close button should be shown in the selected tab
     */
    public void setShowCloseButtonOnSelectedTab(boolean i) {
        _showCloseButtonOnSelectedTab = i;
    }

    /**
     * Gets the flag indicating if the close button should only be displayed when the mouse is over the tab.
     *
     * @return true if the close button should only be displayed when the mouse is over the tab. Otherwise false.
     * @see #setShowCloseButtonOnMouseOver(boolean)
     * @since 3.3.3
     */
    public boolean isShowCloseButtonOnMouseOver() {
        return _showCloseButtonOnMouseOver;
    }

    /**
     * Sets the flag indicating if the close button should only be displayed when the mouse is over the tab.
     * <p/>
     * The default value of the flag is false to keep default behavior not changed.
     *
     * @param showCloseButtonOnMouseOverOnly the flag
     * @since 3.3.3
     */
    public void setShowCloseButtonOnMouseOver(boolean showCloseButtonOnMouseOverOnly) {
        if (_showCloseButtonOnMouseOver != showCloseButtonOnMouseOverOnly) {
            boolean old = _showCloseButtonOnMouseOver;
            _showCloseButtonOnMouseOver = showCloseButtonOnMouseOverOnly;
            firePropertyChange(PROPERTY_SHOW_CLOSE_BUTTON_ON_MOUSE_OVER, old, _showCloseButtonOnMouseOver);
        }
    }


    private ColorProvider _tabColorProvider;

    /**
     * An interface to provide colors for tab background and foreground.
     */
    public static interface ColorProvider {
        /**
         * Gets the tab background for the tab at the specified index.
         *
         * @param tabIndex the index of the tab
         * @return the tab background for the tab at the specified index.
         */
        Color getBackgroundAt(int tabIndex);

        /**
         * Gets the tab foreground for the tab at the specified index.
         *
         * @param tabIndex the index of the tab
         * @return the tab foreground for the tab at the specified index.
         */
        Color getForegroundAt(int tabIndex);

        /**
         * Gets the gradient ratio. We will use this ratio to provide another color in order to paint gradient.
         *
         * @param tabIndex the index of the tab
         * @return the gradient ratio. The value should be between 0 and 1. 0 will produce the darkest and color and 1
         *         will produce the lightest color. 0.5 will provide the same color.
         */
        float getGradientRatio(int tabIndex);
    }

    /**
     * A ColorProvider that can supports gradient tab background. The ColorProvider can also do gradient but the other
     * color has to be be a lighter or darker version of the color of getBackgroundAt. GradientColorProvider allows you
     * to specify an independent color as the start color.
     */
    public static interface GradientColorProvider extends ColorProvider {
        /**
         * Gets the tab background at the top (or other direction depending on the tab placement) of the tab. The
         * JideTabbedPaneUI will paint a gradient using this color and the color of getBackgroundAt.
         *
         * @param tabIndex the index of the tab
         * @return the top background color.
         */
        Color getTopBackgroundAt(int tabIndex);
    }

    private static Color[] ONENOTE_COLORS = {
            new Color(138, 168, 228), // blue
            new Color(238, 149, 151), // pink
            new Color(180, 158, 222), // purple
            new Color(145, 186, 174), // cyan
            new Color(246, 176, 120), // gold
            new Color(255, 216, 105), // yellow
            new Color(183, 201, 151)  // green
    };

    public static ColorProvider ONENOTE_COLOR_PROVIDER = new OneNoteColorProvider();

    private static class OneNoteColorProvider implements ColorProvider {
        public Color getBackgroundAt(int index) {
            return ONENOTE_COLORS[index % ONENOTE_COLORS.length];
        }

        public Color getForegroundAt(int index) {
            return Color.BLACK;
        }

        public float getGradientRatio(int tabIndex) {
            return 0.86f;
        }

    }

    /**
     * Gets the tab color provider.
     *
     * @return tab color provider.
     */
    public ColorProvider getTabColorProvider() {
        return _tabColorProvider;
    }

    /**
     * Sets the tab color provider.It allows you to set the background color of each tab. The reason to use this way
     * instead of {@link #setBackgroundAt(int, java.awt.Color)} method is because this way queries the color. So it can
     * support unlimited number of tabs. When you don't know exactly how many tabs it will be, this way can still handle
     * it very well. There is {@link #ONENOTE_COLOR_PROVIDER} which provides the tab color as you see in Microsoft
     * OneNote 2003. You can also define your own ColorProvider to fit your application color theme.
     *
     * @param tabColorProvider the tab color provider
     */
    public void setTabColorProvider(ColorProvider tabColorProvider) {
        ColorProvider old = _tabColorProvider;
        if (old != tabColorProvider) {
            _tabColorProvider = tabColorProvider;
            firePropertyChange(PROPERTY_TAB_COLOR_PROVIDER, old, tabColorProvider);
        }
    }

    /**
     * Starts tab editing. This works only when {@link #setTabEditingAllowed(boolean)} is set to true.
     *
     * @param tabIndex the index of the tab
     */
    public void editTabAt(int tabIndex) {
        boolean started = ((JideTabbedPaneUI) getUI()).editTabAt(tabIndex);
        if (started) {
            fireTabEditing(TabEditingEvent.TAB_EDITING_STARTED, tabIndex, getTitleAt(tabIndex), null);
        }
    }

    /**
     * Checks if tab is in editing mode.
     *
     * @return true if editing.
     */
    public boolean isTabEditing() {
        return ((JideTabbedPaneUI) getUI()).isTabEditing();
    }

    public void stopTabEditing() {
        int tabIndex = getEditingTabIndex();
        if (tabIndex != -1 && tabIndex < getTabCount()) {
            String oldTitle = getTitleAt(tabIndex);
            ((JideTabbedPaneUI) getUI()).stopTabEditing();
            String newTitle = getTitleAt(tabIndex);
            fireTabEditing(TabEditingEvent.TAB_EDITING_STOPPED, tabIndex, oldTitle, newTitle);
        }
    }

    public void cancelTabEditing() {
        int tabIndex = getEditingTabIndex();
        if (tabIndex != -1) {
            ((JideTabbedPaneUI) getUI()).cancelTabEditing();
            fireTabEditing(TabEditingEvent.TAB_EDITING_CANCELLED, tabIndex, getTitleAt(tabIndex), getTitleAt(tabIndex));
        }
    }

    public int getEditingTabIndex() {
        return ((JideTabbedPaneUI) getUI()).getEditingTabIndex();
    }

    protected PropertyChangeListener _focusChangeListener;

    protected PropertyChangeListener createFocusChangeListener() {
        return new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                final boolean hadFocus = JideTabbedPane.this.isAncestorOf((Component) evt.getOldValue()) || JideTabbedPane.this == evt.getOldValue();
                boolean hasFocus = JideTabbedPane.this == evt.getNewValue() || JideTabbedPane.this.hasFocusComponent();
                if (hasFocus != hadFocus) {
                    repaintTabAreaAndContentBorder();
                }
            }
        };
    }

    /**
     * Repaints the tab area and the content border if any. This is mainly for the focus border in JideTabbedPane
     * Office2003 and Eclipse3x style.
     */
    public void repaintTabAreaAndContentBorder() {
        int delay = 200;
        ((JideTabbedPaneUI) getUI()).getTabPanel().repaint(delay);

        Insets contentinsets = getContentBorderInsets();
        if (contentinsets == null) {
            LookAndFeelFactory.installJideExtension();
            contentinsets = getContentBorderInsets();
        }

        if (contentinsets != null && (contentinsets.top != 0 || contentinsets.bottom != 0 || contentinsets.left != 0 || contentinsets.right != 0)) {
            Insets insets = new Insets(0, 0, 0, 0);
            BasicJideTabbedPaneUI.rotateInsets(contentinsets, insets, tabPlacement);
            switch (getTabPlacement()) {
                case TOP:
                    insets.top += getTabHeight();
                    break;
                case BOTTOM:
                    insets.bottom += getTabHeight();
                    break;
                case LEFT:
                    insets.left += getTabHeight();
                    break;
                case RIGHT:
                    insets.right += getTabHeight();
                    break;
            }
            if (insets.top != 0) {
                repaintContentBorder(0, 0, getWidth(), insets.top);
            }
            if (insets.left != 0) {
                repaintContentBorder(0, 0, insets.left, getHeight());
            }
            if (insets.right != 0) {
                repaintContentBorder(getWidth() - insets.right, 0, insets.right, getHeight());
            }
            if (insets.bottom != 0) {
                repaintContentBorder(0, getHeight() - insets.bottom, getWidth(), insets.bottom);
            }
        }
    }

    /**
     * Calls repaint on the specified rectangular area.
     *
     * @param x      the <i>x</i> coordinate
     * @param y      the <i>y</i> coordinate
     * @param width  the width
     * @param height the height
     */
    protected void repaintContentBorder(int x, int y, int width, int height) {
        repaint(x, y, width, height);
    }

    @Override
    public void addNotify() {
        super.addNotify();
        if (_focusChangeListener == null) {
            _focusChangeListener = createFocusChangeListener();
            KeyboardFocusManager.getCurrentKeyboardFocusManager().addPropertyChangeListener("focusOwner", _focusChangeListener);
        }

    }

    @Override
    public void removeNotify() {
        super.removeNotify();
        if (_focusChangeListener != null) {
            KeyboardFocusManager.getCurrentKeyboardFocusManager().removePropertyChangeListener("focusOwner", _focusChangeListener);
            _focusChangeListener = null;
        }
    }

    /**
     * Gets the tab list cell renderer. This renderer is used to render the list in the popup when tab list button is
     * pressed.
     *
     * @return the tab list cell renderer.
     * @see #setTabListCellRenderer(javax.swing.ListCellRenderer)
     */
    public ListCellRenderer getTabListCellRenderer() {
        if (_tabListCellRenderer != null) {
            return _tabListCellRenderer;
        }
        else {
            return new TabListCellRenderer();
        }
    }

    /**
     * The default tab list cell renderer used to renderer the list in the popup when tab list button is pressed.
     */
    public static class TabListCellRenderer extends DefaultListCellRenderer {
        @Override
        public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
            if (value instanceof JideTabbedPane) {
                JideTabbedPane tabbedPane = (JideTabbedPane) value;
                String title = tabbedPane.getTitleAt(index);
                String tooltip = tabbedPane.getToolTipTextAt(index);
                Icon icon = tabbedPane.getIconForTab(index);
                JLabel label = (JLabel) super.getListCellRendererComponent(list, title, index, isSelected, cellHasFocus);
                label.setToolTipText(tooltip);
                Font fnt;
                if (tabbedPane.getSelectedIndex() == index && tabbedPane.getSelectedTabFont() != null) {
                    fnt = tabbedPane.getSelectedTabFont();
                }
                else {
                    fnt = tabbedPane.getFont();
                }

                if (tabbedPane.getSelectedIndex() == index && tabbedPane.isBoldActiveTab() && fnt.getStyle() != Font.BOLD) {
                    fnt = fnt.deriveFont(Font.BOLD);
                }
                label.setFont(fnt);
                label.setIcon(icon);
                label.setEnabled(tabbedPane.isEnabledAt(index));
                return label;
            }
            else {
                return super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
            }
        }
    }

    /**
     * Sets the tab list cell renderer. This renderer is used to render the list in the popup when tab list button is
     * pressed. In this list cell renderer, the value will always be the JideTabbedPane. The index will tell you which
     * tab it is. See below for the default cell renderer we used.
     * <code><pre>
     * public static class TabListCellRenderer extends DefaultListCellRenderer {
     *     public Component getListCellRendererComponent(JList list, Object value, int index,
     * boolean isSelected, boolean cellHasFocus) {
     *         if (value instanceof JideTabbedPane) { // will always be true
     *             JideTabbedPane tabbedPane = (JideTabbedPane) value;
     *             String title = tabbedPane.getTitleAt(index);
     *             Icon icon = tabbedPane.getIconAt(index);
     *             JLabel label = (JLabel) super.getListCellRendererComponent(list, title, index,
     * isSelected, cellHasFocus);
     *             label.setIcon(icon);
     *             return label;
     *         }
     *         else {
     *             return super.getListCellRendererComponent(list, value, index, isSelected,
     * cellHasFocus);
     *         }
     *     }
     * }
     * </code></pre>
     * You can create your own cell renderer either extending {@link TabListCellRenderer} or starting from scratch.
     *
     * @param tabListCellRenderer the cell renderer
     */
    public void setTabListCellRenderer(ListCellRenderer tabListCellRenderer) {
        _tabListCellRenderer = tabListCellRenderer;
    }

    /**
     * Checks if the JideTabbedPane has the focus component. If true, in some styles such as Office2003 style, we will
     * paint a background on the insets to indicate the tabbed pane has focus.
     *
     * @return true if the JideTabbedPane has the focus component. Otherwise false.
     */
    public boolean hasFocusComponent() {
        return JideSwingUtilities.isAncestorOfFocusOwner(this);
    }

    public Insets getContentBorderInsets() {
        return _contentBorderInsets;
    }

    /**
     * Sets the content border insets. It's the inserts around the JideTabbedPane's content. The direction of the insets
     * is when the tabs are on top. We will rotate it automatically when the tabs are on other directions.
     *
     * @param contentBorderInsets the content border insets
     */
    public void setContentBorderInsets(Insets contentBorderInsets) {
        Insets old = _contentBorderInsets;
        _contentBorderInsets = contentBorderInsets;
        firePropertyChange(PROPERTY_CONTENT_BORDER_INSETS, old, _contentBorderInsets);
    }

    public Insets getTabAreaInsets() {
        return _tabAreaInsets;
    }

    /**
     * Sets the tab area insets. It's the inserts around the tabs. The direction of the insets is when the tabs are on
     * top. We will rotate it automatically when the tabs are on other directions.
     *
     * @param tabAreaInsets the content border insets
     */
    public void setTabAreaInsets(Insets tabAreaInsets) {
        Insets old = _tabAreaInsets;
        _tabAreaInsets = tabAreaInsets;
        firePropertyChange(PROPERTY_TAB_AREA_INSETS, old, _tabAreaInsets);
    }

    public Insets getTabInsets() {
        return _tabInsets;
    }

    /**
     * Sets the tab insets. It's the inserts around the JideTabbedPane's tab. The direction of the insets is when the
     * tabs are on top. We will rotate it automatically when the tabs are on other directions.
     *
     * @param tabInsets the content border insets
     */
    public void setTabInsets(Insets tabInsets) {
        Insets old = _tabInsets;
        _tabInsets = tabInsets;
        firePropertyChange(PROPERTY_TAB_INSETS, old, _tabInsets);
    }

    /**
     * Checks the dragOverDisabled property. By default it is false.
     *
     * @return true or false.
     * @see #setDragOverDisabled(boolean)
     */
    public boolean isDragOverDisabled() {
        return _dragOverDisabled;
    }

    /**
     * Sets the dragOverDisabled property. Default is false. It means when you drag something over an unselected tab,
     * the tab will be selected automatically. You may want to set it to true if you want to add your own drop listener
     * to the tabs.
     *
     * @param dragOverDisabled the flag indicating if drag over is disabled
     */
    public void setDragOverDisabled(boolean dragOverDisabled) {
        boolean old = _dragOverDisabled;
        if (old != dragOverDisabled) {
            _dragOverDisabled = dragOverDisabled;
            firePropertyChange(PROPERTY_DRAG_OVER_DISABLED, old, dragOverDisabled);
        }
    }

    /**
     * Scroll the selected tab visible in case the tab is outside of the viewport.
     *
     * @param scrollLeft true to scroll the first tab visible first then scroll left to make the selected tab visible.
     *                   This will get a more consistent result. If false, it will simple scroll the selected tab
     *                   visible. Sometimes the tab will appear as the first visible tab or the last visible tab
     *                   depending on the previous viewport position.
     */
    public void scrollSelectedTabToVisible(boolean scrollLeft) {
        ((JideTabbedPaneUI) getUI()).ensureActiveTabIsVisible(scrollLeft);
    }

    /**
     * Adds a <code>TabEditingListener</code> to this tabbedpane.
     *
     * @param l the <code>TabEditingListener</code> to add
     * @see #fireTabEditing
     * @see #removeTabEditingListener(TabEditingListener)
     * @see #getTabEditingListeners()
     */
    public void addTabEditingListener(TabEditingListener l) {
        listenerList.add(TabEditingListener.class, l);
    }

    /**
     * Removes a <code>TabEditingListener</code> from this tabbedpane.
     *
     * @param l the <code>TabEditingListener</code> to remove
     * @see #fireTabEditing
     * @see #addTabEditingListener
     */
    public void removeTabEditingListener(TabEditingListener l) {
        listenerList.remove(TabEditingListener.class, l);
    }

    /**
     * Returns an array of all the <code>TabEditingListener</code>s added to this <code>JTabbedPane</code> with
     * <code>addTabEditingListener</code>.
     *
     * @return all of the <code>TabEditingListener</code>s added or an empty array if no listeners have been added
     */
    public TabEditingListener[] getTabEditingListeners() {
        return listenerList.getListeners(TabEditingListener.class);
    }

    protected void fireTabEditing(int id, int index, String oldTitle, String newTitle) {
        if (LOGGER_EVENT.isLoggable(Level.FINE)) {
            switch (id) {
                case TabEditingEvent.TAB_EDITING_STARTED:
                    LOGGER_EVENT.fine("TabEditing Started at tab \"" + index + "\"; the current title is " + oldTitle);
                    break;
                case TabEditingEvent.TAB_EDITING_STOPPED:
                    LOGGER_EVENT.fine("TabEditing Stopped at tab \"" + index + "\"; the old title is " + oldTitle + "; the new title is " + newTitle);
                    break;
                case TabEditingEvent.TAB_EDITING_CANCELLED:
                    LOGGER_EVENT.fine("TabEditing Cancelled at tab \"" + index + "\"; the current title remains " + oldTitle);
                    break;
            }
        }
        Object[] listeners = listenerList.getListenerList();
        for (int i = listeners.length - 2; i >= 0; i -= 2) {
            if (listeners[i] == TabEditingListener.class) {
                TabEditingEvent tabEditingEvent = new TabEditingEvent(this, id, index, oldTitle, newTitle);
                if (id == TabEditingEvent.TAB_EDITING_STARTED) {
                    ((TabEditingListener) listeners[i + 1]).editingStarted(tabEditingEvent);
                }
                else if (id == TabEditingEvent.TAB_EDITING_CANCELLED) {
                    ((TabEditingListener) listeners[i + 1]).editingCanceled(tabEditingEvent);
                }
                else if (id == TabEditingEvent.TAB_EDITING_STOPPED) {
                    ((TabEditingListener) listeners[i + 1]).editingStopped(tabEditingEvent);
                }
            }
        }
    }

    /**
     * Gets the icon for the tab after looking at the UIDefault "JideTabbedPane.showIconOnTab" and {@link
     * #isShowIconsOnTab()}. Note that getIconAt method will always return the tab even though the icon is not displayed
     * because the two flags above.
     *
     * @param tabIndex the tab index.
     * @return the icon for the tab at the specified index.
     */
    public Icon getIconForTab(int tabIndex) {
        boolean _showIconOnTab = UIDefaultsLookup.getBoolean("JideTabbedPane.showIconOnTab");
        if (isUseDefaultShowIconsOnTab()) {
            if (_showIconOnTab) {
                return (!isEnabled() || !isEnabledAt(tabIndex)) ? getDisabledIconAt(tabIndex) : getIconAt(tabIndex);
            }
            else {
                return null;
            }
        }
        else if (isShowIconsOnTab()) {
            return (!isEnabled() || !isEnabledAt(tabIndex)) ? getDisabledIconAt(tabIndex) : getIconAt(tabIndex);
        }
        else {
            return null;
        }
    }

    /**
     * Checks if the selected tab will be changed on mouse wheel event.
     *
     * @return true or false.
     */
    public boolean isScrollSelectedTabOnWheel() {
        return _scrollSelectedTabOnWheel;
    }

    /**
     * If true, the selected tab will be changed on mouse wheel. It is false by default.
     *
     * @param scrollSelectedTabOnWheel the flag
     */
    public void setScrollSelectedTabOnWheel(boolean scrollSelectedTabOnWheel) {
        boolean oldValue = isScrollSelectedTabOnWheel();
        if (oldValue != scrollSelectedTabOnWheel) {
            _scrollSelectedTabOnWheel = scrollSelectedTabOnWheel;
            firePropertyChange(SCROLL_TAB_ON_WHEEL_PROPERTY, oldValue, _scrollSelectedTabOnWheel);
        }
    }

    /**
     * Get the flag if clicking middle mouse button can close the tab. It is false by default.
     *
     * @return the flag.
     */
    public boolean isCloseTabOnMouseMiddleButton() {
        return _closeTabOnMouseMiddleButton;
    }

    /**
     * Set the flag if clicking middle mouse button can close the tab. It is false by default.
     *
     * @param closeTabOnMouseMiddleButton the flag
     */
    public void setCloseTabOnMouseMiddleButton(boolean closeTabOnMouseMiddleButton) {
        this._closeTabOnMouseMiddleButton = closeTabOnMouseMiddleButton;
    }

    /**
     * Returns the alignment of the tabs for this tabbed pane.
     *
     * @return the alignment of the tabs for this tabbed pane.
     * @see #setTabAlignment(int)
     */
    public int getTabAlignment() {
        return _tabAlignment;
    }

    /**
     * Sets the tab alignment for the tabs of a tabbed pane. Currently it only supports top and bottom tab placement.
     * Possible values are:<ul> <li><code>JideTabbedPane.LEADING</code> <li><code>JideTabbedPane.CENTER</code> </ul> The
     * default value, if not set, is <code>JideTabbedPane.LEADING</code>.
     *
     * @param tabAlignment the alignment for the tabs relative to the content
     * @throws IllegalArgumentException if tab alignment value isn't one of the above valid values
     */
    public void setTabAlignment(int tabAlignment) {
        if (tabAlignment != LEADING && tabAlignment != CENTER) {
            throw new IllegalArgumentException("illegal tab alignment: must be LEADING or CENTER");
        }
        if (_tabAlignment != tabAlignment) {
            int oldValue = _tabAlignment;
            _tabAlignment = tabAlignment;
            firePropertyChange("tabAlignment", oldValue, tabAlignment);
            revalidate();
            repaint();
        }
    }

    /**
     * Gets the resource string used in JideTabbedPane. Subclass can override it to provide their own strings.
     *
     * @param key the resource key
     * @return the localized string.
     */
    public String getResourceString(String key) {
        return com.jidesoft.plaf.basic.Resource.getResourceBundle(getLocale()).getString(key);
    }

    /**
     * Creates tab list popup.
     *
     * @return the tab list popup instance.
     * @since 3.2.2
     */
    protected JidePopup createTabListPopup() {
        return new JidePopup();
    }

    /**
     * Checks if the tab list popup is visible.
     *
     * @return true if the tab list popup is visible. Otherwise false.
     * @since 3.2.2
     */
    public boolean isTabListPopupVisible() {
        return _tabListPopup != null && _tabListPopup.isPopupVisible();
    }

    /**
     * Hides the tab list popup if it's visible.
     *
     * @since 3.2.2
     */
    public void hideTabListPopup() {
        if (_tabListPopup != null) {
            if (_tabListPopup.isPopupVisible()) {
                _tabListPopup.hidePopupImmediately();
            }
            _tabListPopup = null;
        }
    }

    /**
     * Shows the tab list popup by clicking on the list button.
     *
     * @param listButton the list button being clicked.
     * @since 3.2.2
     */
    public void showTabListPopup(JButton listButton) {
        JPanel panel = new JPanel(new BorderLayout());
        panel.setBackground(UIDefaultsLookup.getColor("JideTabbedPane.tabListBackground"));
        panel.setOpaque(true);
        panel.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));

        JList list = createTabList(panel.getInsets());
        JScrollPane scroller = new JScrollPane(list);
        scroller.setBorder(BorderFactory.createEmptyBorder());
        scroller.getViewport().setOpaque(false);
        scroller.setOpaque(false);
        panel.add(scroller);

        hideTabListPopup();
        _tabListPopup = createTabListPopup();
        _tabListPopup.setComponentOrientation(getComponentOrientation());
        _tabListPopup.setPopupBorder(BorderFactory.createLineBorder(UIDefaultsLookup.getColor("JideTabbedPane.darkShadow")));
        _tabListPopup.add(panel);
        _tabListPopup.addExcludedComponent(listButton);
        _tabListPopup.setDefaultFocusComponent(list);


        _tabListPopup.setOwner(this);
        _tabListPopup.removeExcludedComponent(this);

        Point point = calculateTabListPopupPosition(listButton);
        _tabListPopup.showPopup(point.x, point.y);
    }

    /**
     * Calculates the position where the tab list popup is to be displayed based on the list button being clicked.
     *
     * @param listButton the list button being clicked.
     * @return the point.
     * @since 3.2.2
     */
    protected Point calculateTabListPopupPosition(JButton listButton) {
        Dimension size = _tabListPopup.getPreferredSize();
        Rectangle bounds = listButton.getBounds();
        Point p = listButton.getLocationOnScreen();
        bounds.x = p.x;
        bounds.y = p.y;
        int x;
        int y;
        switch (getTabPlacement()) {
            case TOP:
            default:
                if (getComponentOrientation().isLeftToRight()) {
                    x = bounds.x + bounds.width - size.width;
                }
                else {
                    x = bounds.x;
                }
                y = bounds.y + bounds.height + 2;
                break;
            case BOTTOM:
                if (getComponentOrientation().isLeftToRight()) {
                    x = bounds.x + bounds.width - size.width;
                }
                else {
                    x = bounds.x;
                }
                y = bounds.y - size.height - 2;
                break;
            case LEFT:
                x = bounds.x + bounds.width + 2;
                y = bounds.y + bounds.height - size.height;
                break;
            case RIGHT:
                x = bounds.x - size.width - 2;
                y = bounds.y + bounds.height - size.height;
                break;
        }

        Rectangle screenBounds = PortingUtils.getScreenBounds(this);
        int right = x + size.width + 3;
        int bottom = y + size.height + 3;

        if (right > screenBounds.x + screenBounds.width) {
            x -= right - screenBounds.x - screenBounds.width; // move left so that the whole popup can fit in
        }

        if (x < screenBounds.x) {
            x = screenBounds.x; // move right so that the whole popup can fit in
        }

        if (bottom > screenBounds.height) {
            y -= bottom - screenBounds.height;
        }

        if (y < screenBounds.y) {
            y = screenBounds.y;
        }
        return new Point(x, y);
    }

    /**
     * Creates the tab list.
     *
     * @param insets the insets of its parent container which helps determine the visible row count of the list.
     * @return the created list instance.
     * @since 3.2.2
     */
    protected JList createTabList(Insets insets) {
        final JList list = new JList() {
            // override this method to disallow deselect by ctrl-click
            @Override
            public void removeSelectionInterval(int index0, int index1) {
                super.removeSelectionInterval(index0, index1);
                if (getSelectedIndex() == -1) {
                    setSelectedIndex(index0);
                }
            }

            @Override
            public Dimension getPreferredScrollableViewportSize() {
                Dimension preferredScrollableViewportSize = super.getPreferredScrollableViewportSize();
                if (preferredScrollableViewportSize.width < 150) {
                    preferredScrollableViewportSize.width = 150;
                }
                int screenWidth = PortingUtils.getScreenSize(this).width;
                if (preferredScrollableViewportSize.width >= screenWidth) {
                    preferredScrollableViewportSize.width = screenWidth;
                }
                return preferredScrollableViewportSize;
            }

            @Override
            public Dimension getPreferredSize() {
                Dimension preferredSize = super.getPreferredSize();
                int screenWidth = PortingUtils.getScreenSize(this).width;
                if (preferredSize.width >= screenWidth) {
                    preferredSize.width = screenWidth;
                }
                return preferredSize;
            }
        };
        DefaultListModel listModel = new DefaultListModel();

        // drop down menu items
        int selectedIndex = getSelectedIndex();
        int totalCount = getTabCount();
        for (int i = 0; i < totalCount; i++) {
            listModel.addElement(this);
        }
        list.setCellRenderer(getTabListCellRenderer());
        list.setModel(listModel);
        list.setSelectedIndex(selectedIndex);
        list.addKeyListener(new KeyListener() {
            public void keyTyped(KeyEvent e) {
            }

            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                    componentSelected(list);
                }
            }

            public void keyReleased(KeyEvent e) {
            }
        });
        list.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseReleased(MouseEvent e) {
                componentSelected(list);
            }
        });

        list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        int max = (PortingUtils.getLocalScreenSize(this).height - insets.top - insets.bottom) / list.getCellBounds(0, 0).height;
        if (listModel.getSize() > max) {
            list.setVisibleRowCount(max);
        }
        else {
            list.setVisibleRowCount(listModel.getSize());
        }
        new Sticky(list);
        list.setBackground(UIDefaultsLookup.getColor("JideTabbedPane.tabListBackground"));
        return list;
    }

    private void componentSelected(JList list) {
        int tabIndex = list.getSelectedIndex();
        if (tabIndex != -1 && isEnabledAt(tabIndex)) {
            if (tabIndex == getSelectedIndex() && JideSwingUtilities.isAncestorOfFocusOwner(this)) {
                if (isAutoFocusOnTabHideClose() && isRequestFocusEnabled()) {
                    Runnable runnable = new Runnable() {
                        public void run() {
                            requestFocus();
                        }
                    };
                    SwingUtilities.invokeLater(runnable);
                }
            }
            else {
                setSelectedIndex(tabIndex);
                final Component comp = getComponentAt(tabIndex);
                if (isAutoFocusOnTabHideClose() && !comp.isVisible() && SystemInfo.isJdk15Above() && !SystemInfo.isJdk6Above()) {
                    comp.addComponentListener(new ComponentAdapter() {
                        @Override
                        public void componentShown(ComponentEvent e) {
                            // remove the listener
                            comp.removeComponentListener(this);

                            final Component lastFocused = getLastFocusedComponent(comp);
                            Runnable runnable = new Runnable() {
                                public void run() {
                                    if (lastFocused != null) {
                                        lastFocused.requestFocus();
                                    }
                                    else if (isRequestFocusEnabled()) {
                                        requestFocus();
                                    }
                                }
                            };
                            SwingUtilities.invokeLater(runnable);
                        }
                    });
                }
                else {
                    final Component lastFocused = getLastFocusedComponent(comp);
                    if (lastFocused != null) {
                        Runnable runnable = new Runnable() {
                            public void run() {
                                lastFocused.requestFocus();
                            }
                        };
                        SwingUtilities.invokeLater(runnable);
                    }
                    else {
                        Container container;
                        if (comp instanceof Container) {
                            container = (Container) comp;
                        }
                        else {
                            container = comp.getFocusCycleRootAncestor();
                        }
                        FocusTraversalPolicy traversalPolicy = container.getFocusTraversalPolicy();
                        Component focusComponent;
                        if (traversalPolicy != null) {
                            focusComponent = traversalPolicy.getDefaultComponent(container);
                            if (focusComponent == null) {
                                focusComponent = traversalPolicy.getFirstComponent(container);
                            }
                        }
                        else if (comp instanceof Container) {
                            // not sure if it is correct
                            focusComponent = findFocusableComponent((Container) comp);
                        }
                        else {
                            focusComponent = comp;
                        }
                        if (focusComponent != null) {
                            final Component theComponent = focusComponent;
                            Runnable runnable = new Runnable() {
                                public void run() {
                                    theComponent.requestFocus();
                                }
                            };
                            SwingUtilities.invokeLater(runnable);
                        }
                    }
                }
            }
            if (getUI() instanceof BasicJideTabbedPaneUI) {
                ((BasicJideTabbedPaneUI) getUI()).ensureActiveTabIsVisible(false);
            }
            hideTabListPopup();
        }
    }

    private Component findFocusableComponent(Container parent) {
        FocusTraversalPolicy traversalPolicy = parent.getFocusTraversalPolicy();
        Component focusComponent = null;
        if (traversalPolicy != null) {
            focusComponent = traversalPolicy.getDefaultComponent(parent);
            if (focusComponent == null) {
                focusComponent = traversalPolicy.getFirstComponent(parent);
            }
        }
        if (focusComponent != null) {
            return focusComponent;
        }
        int i = 0;
        while (i < parent.getComponentCount()) {
            Component comp = parent.getComponent(i);
            if (comp instanceof Container) {
                focusComponent = findFocusableComponent((Container) comp);
                if (focusComponent != null) {
                    return focusComponent;
                }
            }
            else if (comp.isFocusable()) {
                return comp;
            }
            i++;
        }
        if (parent.isFocusable()) {
            return parent;
        }
        return null;
    }

    /**
     * Creates no focus buttons for JideTabbedPane.
     *
     * @param type the button type, it could be {@link #BUTTON_LIST}, {@link #BUTTON_CLOSE}, {@link #BUTTON_EAST},
     *             {@link #BUTTON_WEST}, {@link #BUTTON_NORTH} or {@link #BUTTON_SOUTH}
     * @return the button instance.
     */
    public NoFocusButton createNoFocusButton(int type) {
        return new NoFocusButton(type);
    }

    public class NoFocusButton extends JButton implements MouseMotionListener, MouseListener, UIResource {
        private int _type;
        private int _index = -1;
        private boolean _mouseOver = false;
        private boolean _mousePressed = false;

        /**
         * Resets the UI property to a value from the current look and feel.
         *
         * @see JComponent#updateUI
         */
        @Override
        public void updateUI() {
            super.updateUI();
            setMargin(new Insets(0, 0, 0, 0));
            setBorder(BorderFactory.createEmptyBorder());
            setFocusPainted(false);
            setFocusable(false);
            setRequestFocusEnabled(false);
            String name = getName();
            if (name != null) setToolTipText(getResourceString(name));
        }

        public NoFocusButton() {
            this(BUTTON_CLOSE);
        }

        public NoFocusButton(int type) {
            addMouseMotionListener(this);
            addMouseListener(this);
            setFocusPainted(false);
            setFocusable(false);
            setType(type);
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(16, 16);
        }

        @Override
        public Dimension getMinimumSize() {
            return new Dimension(5, 5);
        }

        public int getIndex() {
            return _index;
        }

        public void setIndex(int index) {
            _index = index;
        }

        @Override
        public Dimension getMaximumSize() {
            return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
        }

        @Override
        protected void paintComponent(Graphics g) {
            if (getIcon() != null) {
                super.paintComponent(g);
                return;
            }
            if (!isEnabled()) {
                setMouseOver(false);
                setMousePressed(false);
            }
            if (isMouseOver() && isMousePressed()) {
                g.setColor(getPressedShadowColor());
                g.drawLine(0, 0, getWidth() - 1, 0);
                g.drawLine(0, getHeight() - 2, 0, 1);
                g.setColor(getShadowColor());
                g.drawLine(getWidth() - 1, 1, getWidth() - 1, getHeight() - 2);
                g.drawLine(getWidth() - 1, getHeight() - 1, 0, getHeight() - 1);
            }
            else if (isMouseOver()) {
                g.setColor(getShadowColor());
                g.drawLine(0, 0, getWidth() - 1, 0);
                g.drawLine(0, getHeight() - 2, 0, 1);
                g.setColor(getPressedShadowColor());
                g.drawLine(getWidth() - 1, 1, getWidth() - 1, getHeight() - 2);
                g.drawLine(getWidth() - 1, getHeight() - 1, 0, getHeight() - 1);
            }
            g.setColor(getForegroundColor());
            int centerX = getWidth() >> 1;
            int centerY = getHeight() >> 1;
            int type = getType();
            if ((getTabPlacement() == TOP || getTabPlacement() == BOTTOM) && !JideTabbedPane.this.getComponentOrientation().isLeftToRight()) {
                if (type == BUTTON_EAST) {
                    type = BUTTON_WEST;
                }
                else if (type == BUTTON_WEST) {
                    type = BUTTON_EAST;
                }
            }
            switch (type) {
                case BUTTON_CLOSE:
                    if (isShowCloseButtonOnMouseOver() && !isMouseOver()) {
                        Object property = JideTabbedPane.this.getClientProperty("JideTabbedPane.mouseOverTabIndex");
                        if (property instanceof Integer && getIndex() >= 0 && (Integer) property != getIndex()) {
                            return;
                        }
                    }
                    if (isEnabled()) {
                        g.drawLine(centerX - 3, centerY - 3, centerX + 3, centerY + 3);
                        g.drawLine(centerX - 4, centerY - 3, centerX + 2, centerY + 3);
                        g.drawLine(centerX + 3, centerY - 3, centerX - 3, centerY + 3);
                        g.drawLine(centerX + 2, centerY - 3, centerX - 4, centerY + 3);
                    }
                    else {
                        g.drawLine(centerX - 3, centerY - 3, centerX + 3, centerY + 3);
                        g.drawLine(centerX + 3, centerY - 3, centerX - 3, centerY + 3);
                    }
                    break;
                case BUTTON_EAST:
                    //
                    // |
                    // ||
                    // |||
                    // ||||
                    // ||||*
                    // ||||
                    // |||
                    // ||
                    // |
                    //
                {
                    if (getTabPlacement() == TOP || getTabPlacement() == BOTTOM) {
                        int x = centerX + 2, y = centerY; // start point. mark as * above
                        if (isEnabled()) {
                            g.drawLine(x - 4, y - 4, x - 4, y + 4);
                            g.drawLine(x - 3, y - 3, x - 3, y + 3);
                            g.drawLine(x - 2, y - 2, x - 2, y + 2);
                            g.drawLine(x - 1, y - 1, x - 1, y + 1);
                            g.drawLine(x, y, x, y);
                        }
                        else {
                            g.drawLine(x - 4, y - 4, x, y);
                            g.drawLine(x - 4, y - 4, x - 4, y + 4);
                            g.drawLine(x - 4, y + 4, x, y);
                        }

                    }
                    else {
                        int x = centerX + 3, y = centerY - 2; // start point. mark as * above
                        if (isEnabled()) {
                            g.drawLine(x - 8, y, x, y);
                            g.drawLine(x - 7, y + 1, x - 1, y + 1);
                            g.drawLine(x - 6, y + 2, x - 2, y + 2);
                            g.drawLine(x - 5, y + 3, x - 3, y + 3);
                            g.drawLine(x - 4, y + 4, x - 4, y + 4);
                        }
                        else {
                            g.drawLine(x - 8, y, x, y);
                            g.drawLine(x - 8, y, x - 4, y + 4);
                            g.drawLine(x - 4, y + 4, x, y);
                        }
                    }
                }
                break;
                case BUTTON_WEST: {
                    //
                    //     |
                    //    ||
                    //   |||
                    //  ||||
                    // *||||
                    //  ||||
                    //   |||
                    //    ||
                    //     |
                    //
                    {
                        if (getTabPlacement() == TOP || getTabPlacement() == BOTTOM) {
                            int x = centerX - 3, y = centerY; // start point. mark as * above
                            if (isEnabled()) {
                                g.drawLine(x, y, x, y);
                                g.drawLine(x + 1, y - 1, x + 1, y + 1);
                                g.drawLine(x + 2, y - 2, x + 2, y + 2);
                                g.drawLine(x + 3, y - 3, x + 3, y + 3);
                                g.drawLine(x + 4, y - 4, x + 4, y + 4);
                            }
                            else {
                                g.drawLine(x, y, x + 4, y - 4);
                                g.drawLine(x, y, x + 4, y + 4);
                                g.drawLine(x + 4, y - 4, x + 4, y + 4);
                            }
                        }
                        else {

                            int x = centerX - 5, y = centerY + 3; // start point. mark as * above
                            if (isEnabled()) {
                                g.drawLine(x, y, x + 8, y);
                                g.drawLine(x + 1, y - 1, x + 7, y - 1);
                                g.drawLine(x + 2, y - 2, x + 6, y - 2);
                                g.drawLine(x + 3, y - 3, x + 5, y - 3);
                                g.drawLine(x + 4, y - 4, x + 4, y - 4);
                            }
                            else {
                                g.drawLine(x, y, x + 8, y);
                                g.drawLine(x, y, x + 4, y - 4);
                                g.drawLine(x + 8, y, x + 4, y - 4);
                            }
                        }
                    }
                    break;
                }
                case BUTTON_LIST: {
                    int x = centerX + 2, y = centerY; // start point. mark as
                    // * above
                    g.drawLine(x - 6, y - 4, x - 6, y + 4);
                    g.drawLine(x + 1, y - 4, x + 1, y + 4);
                    g.drawLine(x - 6, y - 4, x + 1, y - 4);
                    g.drawLine(x - 4, y - 2, x - 1, y - 2);
                    g.drawLine(x - 4, y, x - 1, y);
                    g.drawLine(x - 4, y + 2, x - 1, y + 2);
                    g.drawLine(x - 6, y + 4, x + 1, y + 4);
                    break;
                }
            }
        }

        protected Color getForegroundColor() {
            return UIDefaultsLookup.getColor("JideTabbedPane.foreground");
        }

        protected Color getShadowColor() {
            return UIDefaultsLookup.getColor("control");
        }

        protected Color getPressedShadowColor() {
            return UIDefaultsLookup.getColor("controlDkShadow");
        }

        @Override
        public boolean isFocusable() {
            return false;
        }

        @Override
        public void requestFocus() {
        }

        @Override
        public boolean isOpaque() {
            return false;
        }

        public void mouseDragged(MouseEvent e) {
        }

        public void mouseMoved(MouseEvent e) {
            if (!isEnabled()) return;
            setMouseOver(true);
            repaint();
        }

        public void mouseClicked(MouseEvent e) {
            if (!isEnabled()) return;
            setMouseOver(true);
            setMousePressed(false);
        }

        public void mousePressed(MouseEvent e) {
            if (!isEnabled()) return;
            setMousePressed(true);
            repaint();
        }

        public void mouseReleased(MouseEvent e) {
            if (!isEnabled()) return;
            setMousePressed(false);
            setMouseOver(false);
        }

        public void mouseEntered(MouseEvent e) {
            if (!isEnabled()) return;
            setMouseOver(true);
            repaint();
        }

        public void mouseExited(MouseEvent e) {
            if (!isEnabled()) return;
            setMouseOver(false);
            setMousePressed(false);
            repaint();
            JideTabbedPane.this.repaint();
        }

        public int getType() {
            return _type;
        }

        public void setType(int type) {
            _type = type;
        }

        public boolean isMouseOver() {
            return _mouseOver;
        }

        public void setMouseOver(boolean mouseOver) {
            _mouseOver = mouseOver;
        }

        public boolean isMousePressed() {
            return _mousePressed;
        }

        public void setMousePressed(boolean mousePressed) {
            _mousePressed = mousePressed;
        }
    }
}