/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.netbeans.swing.tabcontrol;

import javax.accessibility.Accessible;
import org.netbeans.swing.tabcontrol.event.TabActionEvent;
import org.netbeans.swing.tabcontrol.plaf.DefaultTabbedContainerUI;

import javax.swing.*;
import java.awt.*;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.accessibility.AccessibleRole;
import javax.swing.JComponent.AccessibleJComponent;
import org.openide.util.NbBundle;


/**
 * A tabbed container similar to a JTabbedPane.  The tabbed container is a
 * simple container which contains two components - the tabs displayer, and the
 * content displayer.  The tabs displayer is the thing that actually draws the
 * tabs; the content displayer contains the components that are being shown.
 * <p>
 * The first difference from a JTabbedPane is that it is entirely model driven -
 * the tab contents are in a data model owned by the displayer.  It is not
 * strictly necessary for the contained components to even be installed in the
 * AWT hierarchy when not displayed.
 * <p>
 * Other differences are more flexibility in the way tabs are displayed by
 * completely separating the implementation and UI for that from that of
 * displaying the contents.
 * <p>
 * Other interesting aspects are the ability of TabDataModel to deliver complex,
 * granular events in a single pass with no information loss.  Generally, great
 * effort has been gone to to conflate nothing - that is, adding a component
 * does not equal selecting it does not equal changing focus, et. cetera,
 * leaving these decisions more in the hands of the user of the control.
 * <p>
 * It is possible to implement a subclass which provides the API of JTabbedPane,
 * making it a drop-in replacement.
 * <p>
 * There are several UI styles a <code>TabbedContainer</code> can have.  The type
 * is passed as an argument to the constructor (support for changing these on the
 * fly may be added in the future, but such a change is a very heavyweight operation,
 * and is only desirable to enable use of this component in its various permutations
 * inside GUI designers).  The following styles are supported:
 * <ul>
 * <li><b>TYPE_VIEW</b> - These are tabs such as the Explorer window has in NetBeans -
 * all tabs are always displayed, with the available space equally divided between them.</li>
 * <li><b>TYPE_EDITOR</b> - Scrolling tabs, coupled with control buttons and mouse wheel
 * support for scrolling the visible tabs, and a popup which displays a list of tabs.</li>
 * <li><b>TYPE_SLIDING</b> - Tabs which are displayed as buttons, and may provide a
 * fade or sliding effect when displayed.  For this style, a second click on the selected
 * tab will hide the selected tab (setting the selection model's selected index to -1).</li></ul>
 * <p>
 * <h4>Customizing the appearance of tabs</h4>
 * Tabs are customized by providing a different UI delegate for the tab displayer component,
 * via UIManager, in the same manner as any standard Swing component; for <code>TYPE_SLIDING</code>
 * tabs, simply implementing an alternate UI delegate for the buttons used to represent tabs
 * is all that is needed.
 *
 * <h4>Managing user events on tabs</h4>
 * When a user clicks a tab, the TabbedContainer will fire an action event to all of its listeners.
 * This action event will always be an instance of <code>TabActionEvent</code>, which can provide
 * the index of the tab that was pressed, and the command name of the action that was performed.
 * A client which wants to handle the event itself (for example, the asking a user if they want
 * to save data, and possibly vetoing the closing of a tab) may veto (or take full responsibility
 * for performing) the action by consuming the TabActionEvent.
 *
 *<h4>Indication of focus and the &quot;activated&quot; state</h4>
 * The property <code>active</code> is provided to allow a tabbed container to indicate that it
 * contains the currently focused component. However, no effort is made to track focus on the
 * part of the tabbed control - there is too much variability possible (for example, if
 * a component inside a tab opens a modal dialog, is the tab active or not?).  In fact, using
 * keyboard focus at all to manage the activated state of the component turns out to be a potent
 * source of hard-to-fix, hard-to-reproduce bugs (especially when components are being added
 * and removed, or hidden and shown or components which do not reliably produce focus events).
 * What NetBeans does to solve the problem in a reliable way is the following:
 * <ol>
 * <li>Use an AWT even listener to track mouse clicks, and when the mouse is clicked,
 *     <ul>
 *     <li>Find the ancestor that is a tabbed container (if any)</li>
 *     <li>Set the activated state appropriately on it and the previously active container</li>
 *     <li>Ensure that keyboard focus moves into that container</li>
 *     </ul>
 * <li>Block ctrl-tab style keyboard based focus traversal out of tabbed containers</li>
 * <li>Provide keyboard actions, with menu items, which will change to a different container,
 *     activating it</li>
 * </ol>
 * This may seem complicated, and it probably is overkill for a small application (as is this
 * tabbed control - it wasn't designed for a small application).  It's primary advantage is
 * that it works.
 *
 * @see TabDisplayer
 * @author Tim Boudreau, Dafe Simonek
 */
public class TabbedContainer extends JComponent implements Accessible {
    /**
     * UIManager key for the UI Delegate to be used by tabbed containers.
     */
    public static final String TABBED_CONTAINER_UI_CLASS_ID = "TabbedContainerUI"; //NOI18N

    /**
     * Creates a &quot;view&quot; style displayer; typically this will have a
     * fixed width and a single row of tabs which get smaller as more tabs are
     * added, as seen in NetBeans&rsquo; Explorer window.
     */
    public static final int TYPE_VIEW = 0;
    /**
     * Creates a &quot;editor&quot; style displayer; typically this uses a
     * scrolling tabs UI for the tab displayer.  This is the most scalable of the available
     * UI styles - it can handle a very large number of tabs with minimal overhead, and
     * the standard UI implementations of it use a cell-renderer model for painting.
     */
    public static final int TYPE_EDITOR = 1;
    
    /** Creates a &quot;sliding&quot; view, typically with tabs rendered as
     * buttons along the left, bottom or right edge, with no scrolling behavior for tabs.
     * Significant about this UI style is that re-clicking the selected tab will
     * cause the component displayed to be hidden.
     * <p>
     * This is the least scalable of the available UI types, and is intended primarily for
     * use with a small, fixed set of tabs.  By default, the position of the tab displayer
     * will be determined based on the proximity of the container to the edges of its
     * parent window.  This can be turned off by setting the client property
     * PROP_MANAGE_TAB_POSITION to Boolean.FALSE.
     */
    public static final int TYPE_SLIDING = 2;
    
    /**
     * Creates a Toolbar-style displayer (the style used by the NetBeans Form Editor's
     * Component Inspector and a few other places in NetBeans).
     */
    public static final int TYPE_TOOLBAR = 3;

    /**
     * Property fired when <code>setActive()</code> is called
     */
    public static final String PROP_ACTIVE = "active"; //NOI18N
    
    /** Client property applicable only to TYPE_SLIDING tabs.  If set to 
     * Boolean.FALSE, the UI will not automatically try to determine a 
     * correct position for the tab displayer.
     */
    public static final String PROP_MANAGE_TAB_POSITION = "manageTabPosition";

    /**
     * Action command indicating that the action event signifies the user
     * clicking the Close button on a tab.
     */
    public static final String COMMAND_CLOSE = "close"; //NOI18N

    /**
     * Action command indicating that the action event fired signifies the user
     * selecting a tab
     */
    public static final String COMMAND_SELECT = "select"; //NOI18N

    /** Action command indicating that a popup menu should be shown */
    public static final String COMMAND_POPUP_REQUEST = "popup"; //NOI18N

    /** Command indicating a maximize (double-click) request */
    public static final String COMMAND_MAXIMIZE = "maximize"; //NOI18N

    public static final String COMMAND_CLOSE_ALL = "closeAll"; //NOI18N

    public static final String COMMAND_CLOSE_ALL_BUT_THIS = "closeAllButThis"; //NOI18N

    public static final String COMMAND_ENABLE_AUTO_HIDE = "enableAutoHide"; //NOI18N

    public static final String COMMAND_DISABLE_AUTO_HIDE = "disableAutoHide"; //NOI18N
    
    public static final String COMMAND_TOGGLE_TRANSPARENCY = "toggleTransparency"; //NOI18N
    /**
     * @since 1.27
     */
    public static final String COMMAND_MINIMIZE_GROUP = "minimizeGroup"; //NOI18N
    
    /**
     * @since 1.27
     */
    public static final String COMMAND_RESTORE_GROUP = "restoreGroup"; //NOI18N
    
    /**
     * @since 1.27
     */
    public static final String COMMAND_CLOSE_GROUP = "closeGroup"; //NOI18N
    
    //XXX support supressing close buttons
    
    /**
     * The data model which contains information about the tabs, such as the
     * corresponding component, the icon and the tooltip.  Currently this is
     * assigned in the constructor and cannot be modified later, though this
     * could be supported in the future (with substantial effort).
     *
     * @see TabData
     * @see TabDataModel
     */
    private TabDataModel model;

    /**
     * The type of this container, which determines what UI delegate is used for
     * the tab displayer
     */
    private final int type;

    /**
     * Holds the value of the active property, determining if the displayer
     * should be painted with the focused or unfocused colors
     */
    private boolean active = false;

    /**
     * Flag used to block the call to updateUI() from the superclass constructor
     * - at that time, none of our instance fields are set, so the UI can't yet
     * set up the tab displayer correctly
     */
    private boolean initialized = false;

    /**
     * Utility field holding list of ActionListeners.
     */
    private transient List<ActionListener> actionListenerList;

    /**
     * Content policy in which all components contained in the data model should immediately
     * be added to the AWT hierarchy at the time they appear in the data model.
     *
     * @see #setContentPolicy
     */
    public static final int CONTENT_POLICY_ADD_ALL = 1;
    /**
     * Content policy by which components contained in the data model are added to the AWT
     * hierarchy the first time they are shown, and remain their thereafter unless removed
     * from the data model.
     *
     * @see #setContentPolicy
     */
    public static final int CONTENT_POLICY_ADD_ON_FIRST_USE = 2;
    /**
     * Content policy by which components contained in the data model are added to the AWT
     * hierarchy the when they are shown, and removed immediately when the user changes tabs.
     */
    public static final int CONTENT_POLICY_ADD_ONLY_SELECTED = 3;

    private int contentPolicy = DEFAULT_CONTENT_POLICY ;

    /** The default content policy, currently CONTENT_POLICY_ADD_ALL.  To facilitate experimentation with
     * different settings application-wide, set the system property &quot;nb.tabcontrol.contentpolicy&quot;
     * to 1, 2 or 3 for ADD_ALL, ADD_ON_FIRST_USE or ADD_ONLY_SELECTED, respectively (note other values
     * will throw an <code>Error</code>).  Do not manipulate this value at runtime, it will likely become
     * a final field in a future release.  It is a protected field only to ensure its inclusion in documentation.
     *
     * @see #setContentPolicy
     */
    protected static int DEFAULT_CONTENT_POLICY = CONTENT_POLICY_ADD_ALL;
    
    /** The component converter which will tranlate TabData's from the model into
     * components. */
    private ComponentConverter converter = null;
    
    /** Winsys info needed for tab control or null if not available */
    private WinsysInfoForTabbed winsysInfo = null;
    
    /** Winsys info needed for tab control or null if not available */
    private WinsysInfoForTabbedContainer containerWinsysInfo = null;

    @Deprecated
    private LocationInformer locationInformer = null;

    /**
     * Create a new pane with the default model and tabs displayer
     */
    public TabbedContainer() {
        this(null, TYPE_VIEW);
    }

    /**
     * Create a new pane with asociated model and the default tabs displayer
     */
    public TabbedContainer(TabDataModel model) {
        this(model, TYPE_VIEW);
    }
    
    public TabbedContainer(int type) {
        this (null, type);
    }
    
    /**
     * Create a new pane with the specified model and displayer type
     *
     * @param model The model
     */
    public TabbedContainer(TabDataModel model, int type) {
        this (model, type, (WinsysInfoForTabbed)null);
    }

    /**
     * Deprecated, please use constructor with WinsysInfoForTabbed instead.
     */
    @Deprecated
    public TabbedContainer(TabDataModel model, int type, LocationInformer locationInformer) {
        this (model, type, (WinsysInfoForTabbed)null);
        this.locationInformer = locationInformer;
    }
        
    /**
     * Deprecated, please use constructor with WinsysInfoForTabbed instead.
     */
    @Deprecated
    public TabbedContainer(TabDataModel model, int type, WinsysInfoForTabbed winsysInfo) {
        this( model, type, WinsysInfoForTabbedContainer.getDefault( winsysInfo ) );
    }
        
    /**
     * Create a new pane with the specified model, displayer type and extra
     * information from winsys
     */
    public TabbedContainer(TabDataModel model, int type, WinsysInfoForTabbedContainer winsysInfo) {
        switch (type) {
            case TYPE_VIEW:
            case TYPE_EDITOR:
            case TYPE_SLIDING:
            case TYPE_TOOLBAR:
                break;
            default :
                throw new IllegalArgumentException("Unknown UI type: " + type); //NOI18N
        }
        if (model == null) {
            model = new DefaultTabDataModel();
        }
        this.model = model;
        this.type = Boolean.getBoolean("nb.tabcontrol.alltoolbar") ? TYPE_TOOLBAR : type;
        this.winsysInfo = winsysInfo;
        this.containerWinsysInfo = winsysInfo;
        initialized = true;
        updateUI();
        //A few borders and such will check this
        //@see org.netbeans.swing.plaf.gtk.AdaptiveMatteBorder
        putClientProperty ("viewType", new Integer(type)); //NOI18N
    }
    
    /**
     * Overridden as follows:  When called by the superclass constructor (before
     * the <code>type</code> field is set), it will simply return; the  
     * TabbedContainer constructor will call updateUI() explicitly later.
     * <p>
     * Will first search UIManager for a matching UI class.  If non-null
     * (by default it is set in the core/swing/plaf library), it will compare
     * the found class name with the current UI.  If they are a match, it
     * will call TabbedContainerUI.shouldReplaceUI() to decide whether to
     * actually do anything or not (in most cases it would just replace an
     * instance of DefaultTabbedContainerUI with another one; but this call
     * allows DefaultTabbedContainerUI.uichange() to update the tab displayer
     * as needed).
     * <p>
     * If no UIManager UI class is defined, this method will silently use an
     * instance of DefaultTabbedContainerUI.
     */
    @Override
    public void updateUI() {
        if (!initialized) {
            //Block the superclass call to updateUI(), which comes before the
            //field is set to tell if we are a view tab control or an editor
            //tab control - the UI won't be able to set up the tab displayer
            //correctly until this is set.
            return;
        }
        TabbedContainerUI ui = null;
        String UIClass = (String) UIManager.get(getUIClassID());
        if (getUI() != null && (getUI().getClass().getName().equals(UIClass) | UIClass == null)) {
            if (!getUI().shouldReplaceUI()) {
                return;
            }
        }
        
        if (UIClass != null) { //Avoid a stack trace
            try {
                ui = (TabbedContainerUI) UIManager.getUI(this);
            } catch (Error e) {
                //do nothing
            }
        }
        if (ui != null) {
            setUI(ui);
        } else {
            setUI(DefaultTabbedContainerUI.createUI(this));
        }
    }
    
    /**
     * Get the type of this displayer - it is either TYPE_EDITOR or TYPE_VIEW.
     * This property is set in the constructor and is immutable
     */
    public final int getType() {
        return type;
    }

    /**
     * Returns <code>TabbedContainer.TABBED_CONTAINER_UI_CLASS_ID</code>
     */
    @Override
    public String getUIClassID() {
        return TABBED_CONTAINER_UI_CLASS_ID;
    }

    /** Get the ui delegate for this component */
    public TabbedContainerUI getUI() {
        return (TabbedContainerUI) ui;
    }
    
    /**
     * Set the converter that converts user objects in the data model into
     * components to display.  If set to null (the default), the user object
     * at the selected index in the data model will be cast as an instance
     * of JComponent when searching for what to show for a given tab.  
     * <p>
     * For use cases where a single component is to be displayed for more
     * than one tab, just reconfigured when the selection changes, simply
     * supply a ComponentConverter.Fixed with the component that should be
     * used for all tabs.
     */
    public final void setComponentConverter (ComponentConverter cc) {
        ComponentConverter old = converter;
        converter = cc;
        if (old instanceof ComponentConverter.Fixed && cc instanceof ComponentConverter.Fixed) {
            List<TabData> l = getModel().getTabs();
            if (!l.isEmpty()) {
                TabData[] td = l.toArray (new TabData[0]);
                getModel().setTabs (new TabData[0]);
                getModel().setTabs(td);
            }
        }
    }
    
    /** Get the component converter which is used to fetch a component
     * corresponding to an element in the data model.  If the value has
     * not been set, it will use ComponentConverter.DEFAULT, which simply
     * delegates to TabData.getComponent(). 
     */
    public final ComponentConverter getComponentConverter() {
        if (converter != null) {
            return converter;
        }
        return ComponentConverter.DEFAULT;
    }

    /** Experimental property - alter the policy by which the components in
     * the model are added to the container.  This may not remain suppported.
     * If used, it should be called before populating the data model.
     */
    public final void setContentPolicy(int i) {
        switch (i) {
            case CONTENT_POLICY_ADD_ALL :
            case CONTENT_POLICY_ADD_ON_FIRST_USE :
            case CONTENT_POLICY_ADD_ONLY_SELECTED :
                break;
            default :
                throw new IllegalArgumentException ("Unknown content policy: " 
                    + i);
        }
        
        if (i != contentPolicy) {
            int old = contentPolicy;
            contentPolicy = i;
            firePropertyChange ("contentPolicy", old, i); //NOI18N
        }
    }

    /** Determine the policy by which components are added to the container.
     * There are various pros and cons to each:
     * <ul>
     * <li>CONTENT_POLICY_ADD_ALL - All components in the data model are
     * automatically added to the container, and whenever the model changes,
     * components are added and removed as need be.  This is less scalable,
     * but absolutely reliable</li>
     * <li>CONTENT_POLICY_ADD_ON_FIRST_USE - Components are not added to the
     * container until the first time they are used, and then they remain in
     * the AWT hierarchy until their TabData elements are removed from the 
     * model.  This is more scalable, and probably has some startup time 
     * benefits</li>
     * <li>CONTENT_POLICY_ADD_ONLY_SELECTED - The only component that will
     * ever be in the AWT hierarchy is the one that is being displayed.  This
     * is safest in the case that heavyweight AWT components may be used </li>
     * </ul>
     */
    public int getContentPolicy() {
        return contentPolicy;
    }
    
    @Override
    public boolean isValidateRoot() {
        return true;
    }

    public boolean isPaintingOrigin() {
        return true;
    }
    

    public void setToolTipTextAt(int index, String toolTip) {
        //Do this quietly - no notification is needed
        TabData tabData = getModel().getTab(index);
        if (tabData != null) {
            tabData.tip = toolTip;
        }
    }

    /**
     * Get the data model that represents the tabs this component has.  All
     * programmatic manipulation of tabs should be done via the data model.
     *
     * @return The model
     */
    public final TabDataModel getModel() {
        return model;
    }

    /**
     * Get the selection model.  The selection model tracks the index of the
     * selected component, modifying this index appropriately when tabs are
     * added or removed.
     *
     * @return The model
     */
    public final SingleSelectionModel getSelectionModel() {
        return getUI().getSelectionModel();
    }
    
    /**
     * Fetch the rectangle of the tab for a given index, in the coordinate space
     * of this component, by reconfiguring the passed rectangle object
     */
    public final Rectangle getTabRect(int index, final Rectangle r) {
        return getUI().getTabRect(index, r);
    }

    /** Gets the index of the tab at point p, or -1 if no tab is there */
    public int tabForCoordinate (Point p) {
        return getUI().tabForCoordinate(p);
    }

    /**
     * Set the &quot;active&quot; state of this tab control - this affects the
     * way the tabs are displayed, to indicate focus.  Note that this method
     * will <i>never</i> be called automatically in stand-alone use of
     * TabbedContainer. While one would expect a component gaining keyboard
     * focus to be a good determinant, it actually turns out to be a potent
     * source of subtle and hard-to-fix bugs.
     * <p/>
     * NetBeans uses an AWTEventListener to track mouse clicks, and allows
     * components to become activated only via a mouse click or via a keyboard
     * action or menu item which activates the component.  This approach is far
     * more robust and is the recommended usage pattern.
     */
    public final void setActive(boolean active) {
        if (active != this.active) {
            this.active = active;
            firePropertyChange(PROP_ACTIVE, !active, active);
        }
    }
    
    /**
     * Cause the tab at the specified index to blink or otherwise suggest that
     * the user should click it.
     */
    public final void requestAttention (int tab) {
        getUI().requestAttention(tab);
    }
    
    public final void cancelRequestAttention (int tab) {
        getUI().cancelRequestAttention(tab);
    }

    /**
     * Turn tab highlight on/off
     * @param tab
     * @since 1.38
     */
    public final void setAttentionHighlight (int tab, boolean highlight) {
        getUI().setAttentionHighlight(tab, highlight);
    }
    
    /**
     * Cause the specified tab to blink or otherwisse suggest that the user should
     * click it.
     */
    public final boolean requestAttention (TabData data) {
        int idx = getModel().indexOf(data);
        boolean result = idx >= 0;
        if (result) {
            requestAttention (idx);
        }
        return result;
    }    

    public final void cancelRequestAttention (TabData data) {
        int idx = getModel().indexOf(data);
        if (idx != -1) {
            cancelRequestAttention(idx);
        }
    }

    /**
     *
     * @param data
     * @since 1.38
     */
    public final void setAttentionHighlight (TabData data, boolean highlight) {
        int idx = getModel().indexOf(data);
        if (idx != -1) {
            setAttentionHighlight(idx, highlight);
        }
    }

    /**
     * Determine if this component thinks it is &quot;active&quot;, which
     * affects how the tabs are painted - typically used to indicate that
     * keyboard focus is somewhere within the component
     */
    public final boolean isActive() {
        return active;
    }

    /**
     * Register an ActionListener.  TabbedContainer and TabDisplayer guarantee
     * that the type of event fired will always be TabActionEvent.  There are
     * two special things about TabActionEvent: <ol> <li>There are methods on
     * TabActionEvent to find the index of the tab the event was performed on,
     * and if present, retrieve the mouse event that triggered it, for clients
     * that wish to provide different handling for different mouse buttons</li>
     * <li>TabActionEvents can be consumed.  If a listener consumes the event,
     * the UI will take no action - the selection will not be changed, the tab
     * will not be closed.  Consuming the event means taking responsibility for
     * doing whatever would normally happen automatically.  This is useful for,
     * for example, showing a dialog and possibly aborting closing a tab if it
     * contains unsaved data, for instance.</li> </ol> Action events will be
     * fired <strong>before</strong> any action has been taken to alter the
     * state of the control to match the action, so that they may be vetoed or
     * modified by consuming the event.
     *
     * @param listener The listener to register.
     */
    public final synchronized void addActionListener(ActionListener listener) {
        if (actionListenerList == null) {
            actionListenerList = new ArrayList<ActionListener>();
        }
        actionListenerList.add(listener);
    }

    /**
     * Remove an action listener.
     *
     * @param listener The listener to remove.
     */
    public final synchronized void removeActionListener(
            ActionListener listener) {
        if (actionListenerList != null) {
            actionListenerList.remove(listener);
            if (actionListenerList.isEmpty()) {
                actionListenerList = null;
            }
        }
    }

    /**
     * Used by the UI to post action events for selection and close operations.
     * If the event is consumed, the UI should take no action to change the
     * selection or close the tab, and will presume that the receiver of the
     * event is handling performing whatever action is appropriate.
     *
     * @param event The event to be fired
     */
    protected final void postActionEvent(TabActionEvent event) {
        List<ActionListener> list;
        synchronized (this) {
            if (actionListenerList == null)
                return;
            list = Collections.unmodifiableList(actionListenerList);
        }
        for( ActionListener l : list ) {
            l.actionPerformed(event);
        }
    }

    public void setIconAt(int index, Icon icon) {
        getModel().setIcon(index, icon);
    }

    public void setTitleAt(int index, String title) {
        getModel().setText(index, title);
    }

    /** Create an image of a single tab, suitable for use in drag and drop operations */
    public Image createImageOfTab(int idx) {
        return getUI().createImageOfTab (idx);
    }
    
    /** Get the number of tabs.  Equivalent to <code>getModel().size()</code> */
    public int getTabCount() {
        return getModel().size();
    }
    
    /**
     * Set whether or not close buttons should be shown.
     * This can be defaulted with the system property
     * <code>nb.tabs.suppressCloseButton</code>; if the system
     * property is not set, the default is true.
     */
    public final void setShowCloseButton (boolean val) {
        boolean wasShow = isShowCloseButton();
        if (val != wasShow) {
            getUI().setShowCloseButton(val);
            firePropertyChange ("showCloseButton", wasShow, val);
        }
    }
    
    /**
     * Determine whether or not close buttons are being shown. 
     */
    public final boolean isShowCloseButton () {
        return getUI().isShowCloseButton();
    }

    /** Get the index of a component */
    public int indexOf (Component comp) {
        int max = getModel().size();
        TabDataModel mdl = getModel();
        for (int i=0; i < max; i++) {
            if (getComponentConverter().getComponent(mdl.getTab(i)) == comp) {
                return i;
            }
        }
        return -1;
    }

    /** The index at which a tab should be inserted if a drop operation
     * occurs at this point.
     *
     * @param location A point anywhere on the TabbedContainer
     * @return A tab index, or -1
     */
    public int dropIndexOfPoint (Point location) {
        return getUI().dropIndexOfPoint(location);
    }

    /**
     * Get a shape appropriate for drawing on the window's glass pane to indicate
     * where a component should appear in the tab order if it is dropped here.
     *
     * @param dragged An object being dragged, or null. The object may be an instance
     *        of <code>TabData</code> or <code>Component</code>, in which case a check
     *        will be done of whether the dragged object is already in the data model,
     *        so that attempts to drop the object over the place it already is in the
     *        model will always return the exact indication of that tab's position.
     *
     * @param location A point
     * @return Drop indication drawing
     */
    public Shape getDropIndication(Object dragged, Point location) {
        int ix;
        if (dragged instanceof Component) {
            ix = indexOf((Component)dragged);
        } else if (dragged instanceof TabData) {
            ix = getModel().indexOf((TabData) dragged);
        } else {
            ix = -1;
        }

        int over = dropIndexOfPoint(location);

        if (over == ix && ix != -1) {
            return getUI().getExactTabIndication(over);
        } else {
            return getUI().getInsertTabIndication(over);
        }
    }
    
    @Deprecated
    public LocationInformer getLocationInformer() {
        return locationInformer;
    }
    
    @Deprecated
    public WinsysInfoForTabbed getWinsysInfo() {
        return winsysInfo;
    }

    public WinsysInfoForTabbedContainer getContainerWinsysInfo() {
        return containerWinsysInfo;
    }
    
    static {
        //Support for experimenting with different content policies in NetBeans
        String s = System.getProperty("nb.tabcontrol.contentpolicy"); //NOI18N
        if (s != null) {
            try {
                DEFAULT_CONTENT_POLICY = Integer.parseInt (s);
                switch (DEFAULT_CONTENT_POLICY) {
                    case CONTENT_POLICY_ADD_ALL :
                    case CONTENT_POLICY_ADD_ON_FIRST_USE :
                    case CONTENT_POLICY_ADD_ONLY_SELECTED :
                        System.err.println("Using custom content policy: " + DEFAULT_CONTENT_POLICY);
                        break;
                    default :
                        throw new Error ("Bad value for default content " +
                                "policy: " + s + " only values 1, 2 or 3" +
                                "are meaningful"); //NOI18N
                }
                System.err.println ("Default content policy is " + DEFAULT_CONTENT_POLICY);
            } catch (Exception e) {
                System.err.println ("Error parsing default content " +
                    "policy: \"" + s + "\""); //NOI18N
            }
        }
    }

    @Override
    public javax.accessibility.AccessibleContext getAccessibleContext() {
        if( null == accessibleContext ) {
            accessibleContext = new AccessibleJComponent() {
                        @Override
                        public AccessibleRole getAccessibleRole() {
                            return AccessibleRole.PAGE_TAB_LIST;
                        }
                    };
        
            accessibleContext.setAccessibleName( NbBundle.getMessage(TabbedContainer.class, "ACS_TabbedContainer") );
            accessibleContext.setAccessibleDescription( NbBundle.getMessage(TabbedContainer.class, "ACSD_TabbedContainer") );
        }
        
        return accessibleContext;
    }
    
    //+++++++++++++++++++++++++
    //Begin: Transparency support 
    //+++++++++++++++++++++++++
    private static final float ALPHA_TRESHOLD = 0.1f;
    private float currentAlpha = 1.0f;
    
    /**
     * @return True if the container is transparent, i.e. painted if Alpha set to 0.2 or less.
     */
    public boolean isTransparent() {
        return isSliding() && currentAlpha <= ALPHA_TRESHOLD;
    }
    
    /**
     * Turn container transparency on/off
     * @param transparent True to make the container transparent
     */
    public void setTransparent( boolean transparent ) {
        _setTransparent( transparent );
        inTransparentMode = transparent;
    }

    private void _setTransparent( boolean transparent ) {
        if( isSliding() ) {
            //#129444 - AWT events may be retargeted icorrectly sometimes
            float oldAlpha = currentAlpha;
            currentAlpha = transparent ? ALPHA_TRESHOLD : 1.0f;
            if( oldAlpha != currentAlpha ) {
                repaint();
            }
        }
    }

    private boolean inTransparentMode = false;
    AWTEventListener awtListener = null;
    private AWTEventListener getAWTListener() {
        if( null == awtListener ) {
            awtListener = new AWTEventListener() {
                public void eventDispatched(AWTEvent event) {
                    if( !isSliding() )
                        return;
                    if( event.getID() == MouseEvent.MOUSE_PRESSED 
                            || event.getID() == MouseEvent.MOUSE_RELEASED
                            || event.getID() == MouseEvent.MOUSE_WHEEL) {
                        //ignore mouse clicks outside this container
                        if( event.getSource() instanceof Component
                            && !SwingUtilities.isDescendingFrom((Component)event.getSource(), TabbedContainer.this) )
                            return;
                        _setTransparent( false );
                    } else if( event.getID() == KeyEvent.KEY_PRESSED ) {
                        KeyEvent ke = (KeyEvent)event;
                        //TODO make shortcut configurable
                        if( ke.getKeyCode() == KeyEvent.VK_NUMPAD0 && ke.isControlDown() && !inTransparentMode ) {
                            setTransparent( true );
                            ke.consume();
                            return;
                        }
                    } else if( event.getID() == KeyEvent.KEY_RELEASED ) {
                        setTransparent( false );
                        return;
                    }
                }
            };
        }
        return awtListener;
    }

    /**
     * @return True if the container holds slided-in window.
     */
    private boolean isSliding() {
        boolean res = false;
        if( getModel().size() == 1 ) {
            Component c = getModel().getTab(0).getComponent();
            if( c instanceof JComponent ) {
                Object val = ((JComponent)c).getClientProperty("isSliding"); //NOI18N
                res = null != val && val instanceof Boolean && ((Boolean)val).booleanValue();
            }
        }
        return res;
    }
    
    @Override
    public void addNotify() {
        super.addNotify();
        if( isSliding() ) {
            //register AWT listener in case the inner top component has its only mouse listener
            Toolkit.getDefaultToolkit().addAWTEventListener( getAWTListener(), 
                    MouseEvent.MOUSE_WHEEL_EVENT_MASK+MouseEvent.MOUSE_EVENT_MASK+KeyEvent.KEY_EVENT_MASK );
        }
    }
    
    @Override
    public void removeNotify() {
        if( null != awtListener ) {
            Toolkit.getDefaultToolkit().removeAWTEventListener( awtListener );
            awtListener = null;
        }
        super.removeNotify();
        currentAlpha = 1.0f;
    }

    @Override
    public void paint(Graphics g) {
        if( isSliding() && currentAlpha != 1.0f ) {
            Graphics2D g2d = (Graphics2D)g;
            Composite oldComposite = g2d.getComposite();
            g2d.setComposite( AlphaComposite.getInstance(AlphaComposite.SRC_OVER, currentAlpha) );
            super.paint(g);
            g2d.setComposite(oldComposite);
        } else {
            super.paint(g);
        }
    }
    //+++++++++++++++++++++++++
    //End: Transparency support 
    //+++++++++++++++++++++++++    
}
