0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041:
0042: package org.netbeans.swing.tabcontrol.plaf;
0043:
0044: import java.awt.AlphaComposite;
0045: import java.awt.BorderLayout;
0046: import java.awt.Component;
0047: import java.awt.Composite;
0048: import java.awt.Container;
0049: import java.awt.Dimension;
0050: import java.awt.Graphics;
0051: import java.awt.Graphics2D;
0052: import java.awt.GraphicsEnvironment;
0053: import java.awt.Image;
0054: import java.awt.Insets;
0055: import java.awt.LayoutManager;
0056: import java.awt.Point;
0057: import java.awt.Polygon;
0058: import java.awt.Rectangle;
0059: import java.awt.event.ActionEvent;
0060: import java.awt.event.ActionListener;
0061: import java.awt.event.ComponentAdapter;
0062: import java.awt.event.ComponentEvent;
0063: import java.awt.event.ComponentListener;
0064: import java.awt.event.HierarchyEvent;
0065: import java.awt.event.HierarchyListener;
0066: import java.awt.event.MouseEvent;
0067: import java.awt.event.MouseListener;
0068: import java.awt.geom.AffineTransform;
0069: import java.awt.image.BufferedImage;
0070: import java.beans.PropertyChangeEvent;
0071: import java.beans.PropertyChangeListener;
0072: import java.util.HashSet;
0073: import java.util.Iterator;
0074: import java.util.List;
0075: import java.util.Set;
0076: import javax.swing.JComponent;
0077: import javax.swing.JPanel;
0078: import javax.swing.SingleSelectionModel;
0079: import javax.swing.SwingUtilities;
0080: import javax.swing.Timer;
0081: import javax.swing.UIManager;
0082: import javax.swing.border.Border;
0083: import javax.swing.event.ChangeEvent;
0084: import javax.swing.event.ChangeListener;
0085: import javax.swing.event.ListDataEvent;
0086: import javax.swing.plaf.ComponentUI;
0087: import org.netbeans.swing.tabcontrol.TabData;
0088: import org.netbeans.swing.tabcontrol.TabDisplayer;
0089: import org.netbeans.swing.tabcontrol.TabbedContainer;
0090: import org.netbeans.swing.tabcontrol.TabbedContainerUI;
0091: import org.netbeans.swing.tabcontrol.WinsysInfoForTabbed;
0092: import org.netbeans.swing.tabcontrol.event.ArrayDiff;
0093: import org.netbeans.swing.tabcontrol.event.ComplexListDataEvent;
0094: import org.netbeans.swing.tabcontrol.event.ComplexListDataListener;
0095: import org.netbeans.swing.tabcontrol.event.TabActionEvent;
0096: import org.netbeans.swing.tabcontrol.event.VeryComplexListDataEvent;
0097:
0098: /**
0099: * Default UI implementation for tabbed containers. Manages installing the tab
0100: * contentDisplayer and content contentDisplayer components, and the
0101: * relationship between the data models and selection models of the UI.
0102: * <p>
0103: * Note that there is typically little reasons to subclass this - to affect the display
0104: * or behavior of the tabs, implement {@link org.netbeans.swing.tabcontrol.TabDisplayerUI},
0105: * the UI delegate for the embedded component which displays the tabs.
0106: *
0107: * @author Tim Boudreau
0108: */
0109: public class DefaultTabbedContainerUI extends TabbedContainerUI {
0110: /**
0111: * Action listener which receives actions from the tab displayer and propagates them through
0112: * action listeners on the tab displayer, to allow clients to consume the events.
0113: */
0114: private ActionListener actionListener = null;
0115: /**
0116: * Flag to ensure listeners attached, since ComponentShown notifications are not always
0117: * reliable.
0118: */
0119: /** UIManager key for the border of the tab displayer in editor ui type. */
0120: public static final String KEY_EDITOR_CONTENT_BORDER = "TabbedContainer.editor.contentBorder"; //NOI18N
0121: /** UIManager key for the border of the tab displayer in editor ui type */
0122: public static final String KEY_EDITOR_TABS_BORDER = "TabbedContainer.editor.tabsBorder"; //NOI18N
0123: /** UIManager key for the border of the entire tabbed container in editor ui type*/
0124: public static final String KEY_EDITOR_OUTER_BORDER = "TabbedContainer.editor.outerBorder"; //NOI18N
0125:
0126: /** UIManager key for the border of the tab displayer in view ui type. */
0127: public static final String KEY_VIEW_CONTENT_BORDER = "TabbedContainer.view.contentBorder"; //NOI18N
0128: /** UIManager key for the border of the tab displayer in view ui type. */
0129: public static final String KEY_VIEW_TABS_BORDER = "TabbedContainer.view.tabsBorder"; //NOI18N
0130: /** UIManager key for the border of the entire tabbed container in view ui type.*/
0131: public static final String KEY_VIEW_OUTER_BORDER = "TabbedContainer.view.outerBorder"; //NOI18N
0132:
0133: /** UIManager key for the border of the tab displayer in sliding ui type. */
0134: public static final String KEY_SLIDING_CONTENT_BORDER = "TabbedContainer.sliding.contentBorder"; //NOI18N
0135: /** UIManager key for the border of the tab displayer in sliding ui type */
0136: public static final String KEY_SLIDING_TABS_BORDER = "TabbedContainer.sliding.tabsBorder"; //NOI18N
0137: /** UIManager key for the border of the entire tabbed container in sliding ui type*/
0138: public static final String KEY_SLIDING_OUTER_BORDER = "TabbedContainer.sliding.outerBorder"; //NOI18N
0139:
0140: /** UIManager key for the border of the tab displayer in toolbar ui type. */
0141: public static final String KEY_TOOLBAR_CONTENT_BORDER = "TabbedContainer.toolbar.contentBorder"; //NOI18N
0142: /** UIManager key for the border of the tab displayer in toolbar ui type */
0143: public static final String KEY_TOOLBAR_TABS_BORDER = "TabbedContainer.toolbar.tabsBorder"; //NOI18N
0144: /** UIManager key for the border of the entire tabbed container in toolbar ui type*/
0145: public static final String KEY_TOOLBAR_OUTER_BORDER = "TabbedContainer.toolbar.outerBorder"; //NOI18N
0146:
0147: /** Component listener which listens on the container, and attaches/detaches listeners.
0148: * <strong>do not alter the value in this field. To provide a different implementation,
0149: * override the appropriate creation method.</strong>
0150: **/
0151: protected ComponentListener componentListener = null;
0152: /** Change listener which tracks changes in the selection model and changes the displayed
0153: * component to reflect the selected tab
0154: * <strong>do not alter the value in this field. To provide a different implementation,
0155: * override the appropriate creation method.</strong>
0156: */
0157: protected ChangeListener selectionListener = null;
0158: /** Listener on the data model, which handles updating the contained components to keep them
0159: * in sync with the contents of the data model.
0160: * <strong>do not alter the value in this field. To provide a different implementation,
0161: * override the appropriate creation method.</strong>
0162: * @see TabbedContainer#setContentPolicy
0163: */
0164: protected ComplexListDataListener modelListener = null;
0165: /**
0166: * Layout manager which will handle layout of the tabbed container.
0167: * <strong>do not alter the value in this field. To provide a different implementation,
0168: * override the appropriate creation method.</strong>
0169: */
0170: protected LayoutManager contentDisplayerLayout = null;
0171: /** Property change listener which detects changes on the tabbed container, such as its active state, which
0172: * should be propagated to the tab displayer.
0173: * <strong>do not alter the value in this field. To provide a different implementation,
0174: * override the appropriate creation method.</strong>
0175: */
0176: protected PropertyChangeListener propertyChangeListener = null;
0177: /**
0178: * FxProvider which will provide transition effects when tabs are changed. By default, only used for
0179: * TabbedContainer.TYPE_SLIDE tabs.
0180: * <strong>do not alter the value in this field. To provide a different implementation,
0181: * override the appropriate creation method.</strong>
0182: */
0183: protected FxProvider slideEffectManager = null;
0184:
0185: /**
0186: * The component which displays the selected component in the tabbed container.
0187: * <strong>do not alter the value in this field. To provide a different implementation,
0188: * override the appropriate creation method.</strong>
0189: */
0190: protected JComponent contentDisplayer = null;
0191: /**
0192: * The Displayer for the tabs. Normally an instance of <code>TabDisplayer</code>. To get the actual
0193: * GUI component that is showing tabs, call its <code>getComponent()</code> method.
0194: * <strong>do not alter the value in this field. To provide a different implementation,
0195: * override the appropriate creation method.</strong>
0196: */
0197: protected TabDisplayer tabDisplayer = null;
0198:
0199: private HierarchyListener hierarchyListener = null;
0200:
0201: /**
0202: * Creates a new instance of DefaultTabbedContainerUI
0203: */
0204: public DefaultTabbedContainerUI(TabbedContainer c) {
0205: super (c);
0206: }
0207:
0208: public static ComponentUI createUI(JComponent c) {
0209: return new DefaultTabbedContainerUI((TabbedContainer) c);
0210: }
0211:
0212: /** This method is final. Subclasses which need to provide additional initialization should override
0213: * <code>install()</code>
0214: *
0215: * @param c A JComponent, which must == the displayer field initialized in the constructor
0216: */
0217: public final void installUI(JComponent c) {
0218: assert c == container;
0219: container.setLayout(createLayout());
0220: contentDisplayer = createContentDisplayer();
0221: tabDisplayer = createTabDisplayer();
0222: selectionListener = createSelectionListener();
0223: modelListener = createModelListener();
0224: componentListener = createComponentListener();
0225: propertyChangeListener = createPropertyChangeListener();
0226: contentDisplayerLayout = createContentDisplayerLayout();
0227: slideEffectManager = createFxProvider();
0228: actionListener = createDisplayerActionListener();
0229: container.setLayout(createLayout());
0230: hierarchyListener = new ContainerHierarchyListener();
0231: forward = new ForwardingMouseListener(container);
0232: installContentDisplayer();
0233: installTabDisplayer();
0234: installBorders();
0235: installListeners();
0236: install();
0237: //#41681, #44278, etc. FOCUS related. the UI needs to be the first listener to be notified
0238: // that the selection has changed. Otherwise strange focus effects kick in, eg. when the winsys snapshot gets undated beforehand..
0239: tabDisplayer.getSelectionModel().addChangeListener(
0240: selectionListener);
0241:
0242: }
0243:
0244: /** Used by unit tests */
0245: TabDisplayer getTabDisplayer() {
0246: return tabDisplayer;
0247: }
0248:
0249: private MouseListener forward = null;
0250:
0251: /** This method is final. Subclasses which need to provide additional initialization should override
0252: * <code>uninstall()</code>
0253: *
0254: * @param c A JComponent, which must == the displayer field initialized in the constructor
0255: */
0256: public final void uninstallUI(JComponent c) {
0257: assert c == container;
0258: tabDisplayer.getSelectionModel().removeChangeListener(
0259: selectionListener);
0260: uninstall();
0261: uninstallListeners();
0262: uninstallDisplayers();
0263:
0264: container = null;
0265: contentDisplayer = null;
0266: tabDisplayer = null;
0267: selectionListener = null;
0268: modelListener = null;
0269: componentListener = null;
0270: propertyChangeListener = null;
0271: contentDisplayerLayout = null;
0272: actionListener = null;
0273: forward = null;
0274: }
0275:
0276: /** Subclasses may override this method to do anything they need to do on installUI(). It will
0277: * be called after listeners, the displayer, etc. (all pseudo-final protected fields) have been
0278: * initialized.
0279: */
0280: protected void install() {
0281:
0282: }
0283:
0284: /** Subclasses may override this method to do anything they need to do on uninstallUI(). It will
0285: * be called before the protected fields of the instance have been nulled.
0286: */
0287: protected void uninstall() {
0288:
0289: }
0290:
0291: protected boolean uichange() {
0292: installBorders();
0293: return false;
0294: }
0295:
0296: /**
0297: * Installs the content displayer component and its layout manager
0298: */
0299: protected void installContentDisplayer() {
0300: contentDisplayer.setLayout(contentDisplayerLayout);
0301: container.add(contentDisplayer, BorderLayout.CENTER);
0302: }
0303:
0304: /**
0305: * Installs the tab displayer component into the container. By default, installs it using the
0306: * constraint <code>BorderLayout.NORTH</code>.
0307: */
0308: protected void installTabDisplayer() {
0309: container.add(tabDisplayer, BorderLayout.NORTH);
0310: tabDisplayer.registerShortcuts(container);
0311: }
0312:
0313: /**
0314: * Installs borders on the container, content displayer and tab displayer
0315: */
0316: protected void installBorders() {
0317: String tabsKey;
0318: String contentKey;
0319: String outerKey;
0320: switch (container.getType()) {
0321: case TabbedContainer.TYPE_EDITOR:
0322: tabsKey = KEY_EDITOR_TABS_BORDER;
0323: contentKey = KEY_EDITOR_CONTENT_BORDER;
0324: outerKey = KEY_EDITOR_OUTER_BORDER;
0325: break;
0326: case TabbedContainer.TYPE_VIEW:
0327: tabsKey = KEY_VIEW_TABS_BORDER;
0328: contentKey = KEY_VIEW_CONTENT_BORDER;
0329: outerKey = KEY_VIEW_OUTER_BORDER;
0330: break;
0331: case TabbedContainer.TYPE_SLIDING:
0332: tabsKey = KEY_SLIDING_TABS_BORDER;
0333: contentKey = KEY_SLIDING_CONTENT_BORDER;
0334: outerKey = KEY_SLIDING_OUTER_BORDER;
0335: break;
0336: case TabbedContainer.TYPE_TOOLBAR:
0337: tabsKey = KEY_TOOLBAR_TABS_BORDER;
0338: contentKey = KEY_TOOLBAR_CONTENT_BORDER;
0339: outerKey = KEY_TOOLBAR_OUTER_BORDER;
0340: break;
0341: default:
0342: throw new IllegalStateException("Unknown type: "
0343: + container.getType());
0344: }
0345: try {
0346: Border b = (Border) UIManager.get(contentKey);
0347: contentDisplayer.setBorder(b);
0348: b = (Border) UIManager.get(tabsKey);
0349: tabDisplayer.setBorder(b);
0350: b = (Border) UIManager.get(outerKey);
0351: container.setBorder(b);
0352: } catch (ClassCastException cce) {
0353: System.err.println("Expected a border from UIManager for "
0354: + tabsKey + "," + contentKey + "," + outerKey);
0355: }
0356: }
0357:
0358: /**
0359: * Installs a component listener on the component. Listeners on the data
0360: * model and selection model are installed when the component is shown, as
0361: * detected by the component listener.
0362: */
0363: protected void installListeners() {
0364: container.addComponentListener(componentListener);
0365: container.addHierarchyListener(hierarchyListener);
0366: //Allow mouse events to be forwarded as if they came from the
0367: //container
0368: tabDisplayer.addMouseListener(forward);
0369: contentDisplayer.addMouseListener(forward);
0370: }
0371:
0372: /**
0373: * Begin listening to the model for changes in the selection, which should
0374: * cause us to update the displayed component in the content
0375: * contentDisplayer. Listening starts when the component is first shown, and
0376: * stops when it is hidden; if you override <code>createComponentListener()</code>,
0377: * you will need to call this method when the component is shown.
0378: */
0379: protected void attachModelAndSelectionListeners() {
0380: container.getModel().addComplexListDataListener(modelListener);
0381: container.addPropertyChangeListener(propertyChangeListener);
0382: tabDisplayer.setActive(container.isActive());
0383: tabDisplayer.addActionListener(actionListener);
0384: }
0385:
0386: /**
0387: * Stop listening to the model for changes in the selection, which should
0388: * cause us to update the displayed component in the content
0389: * contentDisplayer, and changes in the data model which can affect the
0390: * displayed component. Listening starts when the component is first shown,
0391: * and stops when it is hidden; if you override <code>createComponentListener()</code>,
0392: * you will need to call this method when the component is hidden.
0393: */
0394: protected void detachModelAndSelectionListeners() {
0395: container.getModel().removeComplexListDataListener(
0396: modelListener);
0397: container.removePropertyChangeListener(propertyChangeListener);
0398: tabDisplayer.removeActionListener(actionListener);
0399: }
0400:
0401: /**
0402: * Uninstalls the component listener installed in <code>installListeners()</code>
0403: */
0404: protected void uninstallListeners() {
0405: container.removeComponentListener(componentListener);
0406: container.removeHierarchyListener(hierarchyListener);
0407: componentListener = null;
0408: propertyChangeListener = null;
0409: tabDisplayer.removeMouseListener(forward);
0410: contentDisplayer.removeMouseListener(forward);
0411: }
0412:
0413: /**
0414: * Uninstalls and nulls references to the content contentDisplayer and tab
0415: * contentDisplayer, and removes all components from the content
0416: * contentDisplayer.
0417: */
0418: protected void uninstallDisplayers() {
0419: container.remove(contentDisplayer);
0420: container.remove(tabDisplayer);
0421: tabDisplayer.unregisterShortcuts(container);
0422: contentDisplayer.removeAll();
0423: contentDisplayer = null;
0424: tabDisplayer = null;
0425: }
0426:
0427: /**
0428: * Create the component which will display the tabs.
0429: */
0430: protected TabDisplayer createTabDisplayer() {
0431: TabDisplayer result = null;
0432: WinsysInfoForTabbed winsysInfo = container.getWinsysInfo();
0433: if (winsysInfo != null) {
0434: result = new TabDisplayer(container.getModel(), container
0435: .getType(), winsysInfo);
0436: } else {
0437: result = new TabDisplayer(container.getModel(), container
0438: .getType(), container.getLocationInformer());
0439: }
0440: result.setName("Tab Displayer"); //NOI18N
0441: return result;
0442: }
0443:
0444: /**
0445: * Create the component which will contain the content (the components which
0446: * correspond to tabs). The default implementation simply returns a
0447: * vanilla, unadorned <code>JPanel</code>.
0448: */
0449: protected JPanel createContentDisplayer() {
0450: JPanel result = new JPanel();
0451: result.setName("Content displayer"); //NOI18N
0452: return result;
0453: }
0454:
0455: /**
0456: * Create an FxProvider instance which will provide transition effects when tabs are selected.
0457: * By default creates a no-op instance for all displayer types except TYPE_SLIDING.
0458: *
0459: * @return An instance of FxProvider
0460: */
0461: protected FxProvider createFxProvider() {
0462: if (NO_EFFECTS
0463: || (tabDisplayer.getType() != TabDisplayer.TYPE_SLIDING && !EFFECTS_EVERYWHERE)) {
0464: return new NoOpFxProvider();
0465: } else {
0466: if (ADD_TO_GLASSPANE) {
0467: return new LiveComponentSlideFxProvider();
0468: } else {
0469: return new ImageSlideFxProvider();
0470: }
0471: }
0472: }
0473:
0474: /**
0475: * Creates the content contentDisplayer's layout manager, responsible for
0476: * ensuring that the correct component is on top and is the only one
0477: * showing
0478: */
0479: protected LayoutManager createContentDisplayerLayout() {
0480: return new StackLayout();
0481: }
0482:
0483: /**
0484: * Create the layout manager that will manage the layout of the
0485: * TabbedContainer. A TabbedContainer contains two components - the tabs
0486: * contentDisplayer, and the component contentDisplayer.
0487: * <p/>
0488: * The layout manager determines the position of the tabs relative to the
0489: * contentDisplayer component which displays the tab contents.
0490: * <p/>
0491: * The default implementation uses BorderLayout. If you override this, you
0492: * should probably override <code>installDisplayer()</code> as well.
0493: */
0494: protected LayoutManager createLayout() {
0495: if (container.getType() == TabbedContainer.TYPE_SLIDING) {
0496: return new SlidingTabsLayout();
0497: } else if (container.getType() == TabbedContainer.TYPE_TOOLBAR) {
0498: return new ToolbarTabsLayout();
0499: } else {
0500: return new BorderLayout();
0501: }
0502: }
0503:
0504: /**
0505: * Create a component listener responsible for initializing the
0506: * contentDisplayer component when the tabbed container is shown
0507: */
0508: protected ComponentListener createComponentListener() {
0509: return new ContainerComponentListener();
0510: }
0511:
0512: /**
0513: * Create a property change listener which will update the tab displayer in
0514: * accordance with property changes on the container. Currently the only
0515: * property change of interest is calls to <code>TabbedContainer.setActive()</code>,
0516: * which simply cause the active state to be set on the displayer.
0517: */
0518: protected PropertyChangeListener createPropertyChangeListener() {
0519: return new ContainerPropertyChangeListener();
0520: }
0521:
0522: /** Creates an action listener to catch actions performed on the tab displayer
0523: * and forward them to listeners on the container for consumption.
0524: *
0525: * @return An action listener
0526: */
0527: private ActionListener createDisplayerActionListener() {
0528: return new DisplayerActionListener();
0529: }
0530:
0531: /**
0532: * Ensures that the component the selection model says is selected is the
0533: * one that is showing.
0534: */
0535: protected void ensureSelectedComponentIsShowing() {
0536: int i = tabDisplayer.getSelectionModel().getSelectedIndex();
0537: if (i != -1) {
0538: TabData td = container.getModel().getTab(i);
0539: showComponent(toComp(td));
0540: }
0541: }
0542:
0543: /** Convenience method for fetching a component from a TabData object
0544: * via the container's ComponentConverter */
0545: protected final Component toComp(TabData data) {
0546: return container.getComponentConverter().getComponent(data);
0547: }
0548:
0549: /**
0550: * Shows the passed component. <strong>This method does not communicate with
0551: * the data model in any way shape or form, it just moves the passed
0552: * component to the front. It should only be called in response to an event
0553: * from the data model or selection model.
0554: * <p/>
0555: * If you override <code>createContentDisplayerLayoutModel()</code> to
0556: * provide your own layout manager to arrange the displayed component, you
0557: * need to override this to tell the layout (or do whatever is needed) to
0558: * change the component that is shown.
0559: *
0560: * @param c The component to be shown
0561: * @return The previously showing component, or null if no change was made
0562: */
0563: protected Component showComponent(Component c) {
0564: if (contentDisplayerLayout instanceof StackLayout) {
0565: StackLayout stack = ((StackLayout) contentDisplayerLayout);
0566: Component last = stack.getVisibleComponent();
0567: stack.showComponent(c, contentDisplayer);
0568: if (c != null) {
0569: Integer offset = (Integer) ((JComponent) c)
0570: .getClientProperty("MultiViewBorderHack.topOffset");
0571: contentDisplayer.putClientProperty(
0572: "MultiViewBorderHack.topOffset", offset);
0573: } else {
0574: contentDisplayer.putClientProperty(
0575: "MultiViewBorderHack.topOffset", null);
0576: }
0577: if (last != c) {
0578: maybeRemoveLastComponent(last);
0579: return last;
0580: }
0581: }
0582: return null;
0583: }
0584:
0585: /**
0586: * Shows a component in the control, using the <code>FxProvider</code> created in
0587: * <code>createFxProvider()</code> to manage showing it. Equivalent to calling <code>showComponent</code>,
0588: * but there may be a delay while the effect is performed. If no <code>FxProvider</code> is installed,
0589: * this will simply delegate to <code>showComponent</code>; if not, the <code>FxProvider</code> is expected
0590: * to do that when its effect is completed.
0591: *
0592: * @param c The component to be shown.
0593: */
0594: protected final void showComponentWithFxProvider(Component c) {
0595: if (slideEffectManager == null || !container.isShowing()
0596: || (!(c instanceof JComponent))) {
0597: Component last = showComponent(c);
0598: maybeRemoveLastComponent(last);
0599: } else {
0600: slideEffectManager.start((JComponent) c, container
0601: .getRootPane(), tabDisplayer
0602: .getClientProperty(TabDisplayer.PROP_ORIENTATION));
0603: }
0604: }
0605:
0606: /**
0607: * Removes the passed component from the AWT hierarchy if the container's content policy is
0608: * CONTENT_POLICY_ADD_ONLY_SELECTED.
0609: *
0610: * @param c The component that should be removed
0611: */
0612: private final void maybeRemoveLastComponent(Component c) {
0613: if (c != null
0614: && container.getContentPolicy() == TabbedContainer.CONTENT_POLICY_ADD_ONLY_SELECTED) {
0615: contentDisplayer.remove(c);
0616: }
0617: }
0618:
0619: /**
0620: * Fills contentDisplayer container with components retrieved from model.
0621: */
0622: protected void initDisplayer() {
0623: if (container.getContentPolicy() == TabbedContainer.CONTENT_POLICY_ADD_ALL) {
0624: List tabs = container.getModel().getTabs();
0625: Component curC = null;
0626: for (Iterator iter = tabs.iterator(); iter.hasNext();) {
0627: curC = toComp((TabData) iter.next());
0628: // string parameter is needed for StackLayout to kick in correctly
0629: contentDisplayer.add(curC, "");
0630: }
0631: } else {
0632: int i = tabDisplayer.getSelectionModel().getSelectedIndex();
0633: if (i != -1) {
0634: TabData td = container.getModel().getTab(i);
0635: contentDisplayer.add(toComp(td), "");
0636: }
0637: }
0638: updateActiveState();
0639: }
0640:
0641: /**
0642: * Create a listener for the TabDataModel. This listener is responsible for
0643: * keeping the state of the contained components in the displayer in sync
0644: * with the contents of the data model. Note that it is not necessary for
0645: * this listener to adjust the selection - DefaultTabSelectionModel handles
0646: * cases such as removing the selected component appropriately, so if such a
0647: * model change happens, a selection change will be immediately forthcoming
0648: * to handle it.
0649: * <p/>
0650: * Note that it is important that this listener be added to the data model
0651: * <i>after</i> the DefaultSelectionModel has added its listener. It is
0652: * important to create the displayer component before adding this listener.
0653: * Some support for privilged listeners may be added to DefaultTabDataModel
0654: * in the future to avoid this issue entirely.
0655: */
0656: protected ComplexListDataListener createModelListener() {
0657: return new ModelListener();
0658: }
0659:
0660: /**
0661: * Create a ChangeListener which will listen to the selection model of the
0662: * tab displayer, and update the displayed component in the displayer when
0663: * the selection model changes.
0664: */
0665: protected ChangeListener createSelectionListener() {
0666: return new SelectionListener();
0667: }
0668:
0669: /** Sets the active state of the displayer to match that of the container */
0670: private void updateActiveState() {
0671: //#45630 - more of a hack than a fix.
0672: //apparently uninstallUI() was called before the the ContainerPropertyChangeListener instance was removed in
0673: //ContainerHierarchyListener's hierarchyChanged method.
0674: // for such case the property change should be a noop.
0675: TabDisplayer displ = tabDisplayer;
0676: TabbedContainer cont = container;
0677: if (displ != null && cont != null) {
0678: displ.setActive(cont.isActive());
0679: }
0680:
0681: }
0682:
0683: public Rectangle getTabRect(int tab, Rectangle r) {
0684: if (r == null) {
0685: r = new Rectangle();
0686: }
0687: tabDisplayer.getTabRect(tab, r);
0688: Point p = tabDisplayer.getLocation();
0689: r.x += p.x;
0690: r.y += p.y;
0691: return r;
0692: }
0693:
0694: protected void requestAttention(int tab) {
0695: tabDisplayer.requestAttention(tab);
0696: }
0697:
0698: protected void cancelRequestAttention(int tab) {
0699: tabDisplayer.cancelRequestAttention(tab);
0700: }
0701:
0702: public void setShowCloseButton(boolean val) {
0703: tabDisplayer.setShowCloseButton(val);
0704: }
0705:
0706: public boolean isShowCloseButton() {
0707: return tabDisplayer.isShowCloseButton();
0708: }
0709:
0710: /**
0711: * Scroll pane-like border, good general border around windows. Used if no
0712: * border is provided via UIDefaults.
0713: */
0714: private static final class DefaultWindowBorder implements Border {
0715: private static final Insets insets = new Insets(1, 1, 2, 2);
0716:
0717: public void paintBorder(Component c, Graphics g, int x, int y,
0718: int w, int h) {
0719: g.translate(x, y);
0720:
0721: g.setColor(UIManager.getColor("controlShadow")); //NOI18N
0722: g.drawRect(0, 0, w - 2, h - 2);
0723: g.setColor(UIManager.getColor("controlHighlight")); //NOI18N
0724: g.drawLine(w - 1, 1, w - 1, h - 1);
0725: g.drawLine(1, h - 1, w - 1, h - 1);
0726:
0727: g.translate(-x, -y);
0728: }
0729:
0730: public Insets getBorderInsets(Component c) {
0731: return insets;
0732: }
0733:
0734: public boolean isBorderOpaque() {
0735: return true;
0736: }
0737: } // end of DefaultWindowBorder
0738:
0739: protected class ContainerPropertyChangeListener implements
0740: PropertyChangeListener {
0741:
0742: public void propertyChange(PropertyChangeEvent evt) {
0743: if (TabbedContainer.PROP_ACTIVE.equals(evt
0744: .getPropertyName())) {
0745: updateActiveState();
0746: }
0747: }
0748: }
0749:
0750: /** Checks the position of the tabbed container relative to its parent
0751: * window, and potentially updates its orientation client property.
0752: *
0753: * @see TabDisplayer#PROP_ORIENTATION
0754: */
0755: protected final void updateOrientation() {
0756: if (!container.isDisplayable()) {
0757: return;
0758: }
0759: if (Boolean.FALSE
0760: .equals(container
0761: .getClientProperty(TabbedContainer.PROP_MANAGE_TAB_POSITION))) {
0762: //The client has specified that it does not want automatic management
0763: //of the displayer orientation
0764: return;
0765: }
0766: Object currOrientation = tabDisplayer
0767: .getClientProperty(TabDisplayer.PROP_ORIENTATION);
0768: Container window = container.getTopLevelAncestor();
0769:
0770: Rectangle containerBounds = container.getBounds();
0771: containerBounds = SwingUtilities.convertRectangle(container,
0772: containerBounds, window);
0773:
0774: boolean longestIsVertical = containerBounds.width < containerBounds.height;
0775:
0776: int distanceToLeft = containerBounds.x;
0777: int distanceToTop = containerBounds.y;
0778: int distanceToRight = window.getWidth()
0779: - (containerBounds.x + containerBounds.width);
0780: int distanceToBottom = window.getHeight()
0781: - (containerBounds.y + containerBounds.height);
0782:
0783: Object orientation;
0784: if (!longestIsVertical) {
0785: if (distanceToBottom > distanceToTop) {
0786: orientation = TabDisplayer.ORIENTATION_NORTH;
0787: } else {
0788: orientation = TabDisplayer.ORIENTATION_SOUTH;
0789: }
0790: } else {
0791: if (distanceToLeft > distanceToRight) {
0792: orientation = TabDisplayer.ORIENTATION_EAST;
0793: } else {
0794: orientation = TabDisplayer.ORIENTATION_WEST;
0795: }
0796: }
0797:
0798: if (currOrientation != orientation) {
0799: tabDisplayer.putClientProperty(
0800: TabDisplayer.PROP_ORIENTATION, orientation);
0801: container.validate();
0802: }
0803: }
0804:
0805: public int tabForCoordinate(Point p) {
0806: p = SwingUtilities.convertPoint(container, p, tabDisplayer);
0807: return tabDisplayer.tabForCoordinate(p);
0808: }
0809:
0810: public void makeTabVisible(int tab) {
0811: tabDisplayer.makeTabVisible(tab);
0812: }
0813:
0814: public SingleSelectionModel getSelectionModel() {
0815: return tabDisplayer.getSelectionModel();
0816: }
0817:
0818: public Image createImageOfTab(int idx) {
0819: return tabDisplayer.getUI().createImageOfTab(idx);
0820: }
0821:
0822: public Polygon getExactTabIndication(int idx) {
0823: Polygon result = tabDisplayer.getUI()
0824: .getExactTabIndication(idx);
0825: scratchPoint.setLocation(0, 0);
0826: Point p = SwingUtilities.convertPoint(tabDisplayer,
0827: scratchPoint, container);
0828: result.translate(-p.x, -p.y);
0829: return appendContentBoundsTo(result);
0830: }
0831:
0832: private Point scratchPoint = new Point();
0833:
0834: public Polygon getInsertTabIndication(int idx) {
0835: Polygon result = tabDisplayer.getUI().getInsertTabIndication(
0836: idx);
0837: scratchPoint.setLocation(0, 0);
0838: Point p = SwingUtilities.convertPoint(tabDisplayer,
0839: scratchPoint, container);
0840: result.translate(-p.x, -p.y);
0841: return appendContentBoundsTo(result);
0842: }
0843:
0844: private Polygon appendContentBoundsTo(Polygon p) {
0845: int width = contentDisplayer.getWidth();
0846: int height = contentDisplayer.getHeight();
0847:
0848: int[] xpoints = new int[p.npoints + 4];
0849: int[] ypoints = new int[xpoints.length];
0850:
0851: //XXX not handling this correctly for non-top orientations
0852:
0853: int pos = 0;
0854: Object orientation = tabDisplayer
0855: .getClientProperty(TabDisplayer.PROP_ORIENTATION);
0856:
0857: int tabsHeight = tabDisplayer.getHeight();
0858: if (orientation == null
0859: || orientation == TabDisplayer.ORIENTATION_NORTH) {
0860:
0861: xpoints[pos] = 0;
0862: ypoints[pos] = tabsHeight;
0863:
0864: pos++;
0865:
0866: xpoints[pos] = p.xpoints[p.npoints - 1];
0867: ypoints[pos] = tabsHeight;
0868: pos++;
0869:
0870: for (int i = 0; i < p.npoints - 2; i++) {
0871: xpoints[pos] = p.xpoints[i];
0872: ypoints[pos] = p.ypoints[i];
0873: pos++;
0874: }
0875:
0876: xpoints[pos] = xpoints[pos - 1];
0877: ypoints[pos] = tabsHeight;
0878:
0879: pos++;
0880:
0881: xpoints[pos] = width - 1;
0882: ypoints[pos] = tabsHeight;
0883:
0884: pos++;
0885:
0886: xpoints[pos] = width - 1;
0887: ypoints[pos] = height - 1;
0888:
0889: pos++;
0890:
0891: xpoints[pos] = 0;
0892: ypoints[pos] = height - 1;
0893: } else if (orientation == TabDisplayer.ORIENTATION_SOUTH) {
0894: int yxlate = contentDisplayer.getHeight() * 2;
0895:
0896: xpoints[pos] = 0;
0897: ypoints[pos] = 0;
0898:
0899: pos++;
0900:
0901: xpoints[pos] = container.getWidth();
0902: ypoints[pos] = 0;
0903:
0904: pos++;
0905:
0906: xpoints[pos] = container.getWidth();
0907: ypoints[pos] = container.getHeight() - tabsHeight;
0908:
0909: pos++;
0910:
0911: int upperRight = 0;
0912: //Search backward for the upper right corner - we only know
0913: //the location of the left upper corner
0914: int highestFound = Integer.MIN_VALUE;
0915: for (int i = p.npoints - 2; i >= 0; i--) {
0916: if (highestFound < p.ypoints[i]) {
0917: upperRight = i;
0918: highestFound = p.ypoints[i];
0919: } else if (highestFound == p.ypoints[i]) {
0920: break;
0921: }
0922: }
0923:
0924: int curr = upperRight - 1;
0925: for (int i = p.npoints - 1; i >= 0; i--) {
0926: xpoints[pos] = p.xpoints[curr];
0927: if (ypoints[pos] == highestFound) {
0928: ypoints[pos] = Math.min(
0929: tabDisplayer.getLocation().y,
0930: p.ypoints[curr] + yxlate);
0931: } else {
0932: ypoints[pos] = p.ypoints[curr] + yxlate;
0933: }
0934: pos++;
0935: curr++;
0936: if (curr == p.npoints - 1) {
0937: curr = 0;
0938: }
0939: }
0940:
0941: xpoints[pos] = 0;
0942: ypoints[pos] = container.getHeight() - tabsHeight;
0943: } else {
0944: //Punt on side tabs for now
0945: xpoints = p.xpoints;
0946: ypoints = p.ypoints;
0947: }
0948:
0949: Polygon result = new EqualPolygon(xpoints, ypoints,
0950: xpoints.length);
0951: return result;
0952: }
0953:
0954: public Rectangle getContentArea() {
0955: return contentDisplayer.getBounds();
0956: }
0957:
0958: public Rectangle getTabsArea() {
0959: return tabDisplayer.getBounds();
0960: }
0961:
0962: public int dropIndexOfPoint(Point p) {
0963: Point p2 = SwingUtilities.convertPoint(container, p,
0964: tabDisplayer);
0965: return tabDisplayer.getUI().dropIndexOfPoint(p2);
0966: }
0967:
0968: public Rectangle getTabsArea(Rectangle dest) {
0969: return tabDisplayer.getBounds();
0970: }
0971:
0972: /**
0973: * A ComponentListener which listens for show/hide to add and remove the
0974: * selection and model listeners
0975: */
0976: protected class ContainerComponentListener extends ComponentAdapter {
0977: public ContainerComponentListener() {
0978: }
0979:
0980: public void componentMoved(ComponentEvent e) {
0981: if (container.getType() == TabbedContainer.TYPE_SLIDING) {
0982: updateOrientation();
0983: }
0984: }
0985:
0986: public void componentResized(ComponentEvent e) {
0987: if (container.getType() == TabbedContainer.TYPE_SLIDING) {
0988: updateOrientation();
0989: }
0990: }
0991: }
0992:
0993: private boolean bug4924561knownShowing = false;
0994:
0995: /**
0996: * Calls <code>initDisplayer()</code>, then <code>attachModelAndSelectionListeners</code>,
0997: * then <code>ensureSelectedComponentIsShowing</code>
0998: */
0999: private class ContainerHierarchyListener implements
1000: HierarchyListener {
1001: public ContainerHierarchyListener() {
1002: }
1003:
1004: public void hierarchyChanged(HierarchyEvent e) {
1005: if ((e.getChangeFlags() & HierarchyEvent.SHOWING_CHANGED) != 0) {
1006: boolean showing = container.isShowing();
1007: if (showing != bug4924561knownShowing) {
1008: if (container.isShowing()) {
1009: initDisplayer();
1010: attachModelAndSelectionListeners();
1011: ensureSelectedComponentIsShowing();
1012: if (container.getType() == TabbedContainer.TYPE_SLIDING) {
1013: updateOrientation();
1014: }
1015: } else {
1016: detachModelAndSelectionListeners();
1017: if (container.getType() == TabbedContainer.TYPE_SLIDING) {
1018: updateOrientation();
1019: }
1020: }
1021: }
1022: bug4924561knownShowing = showing;
1023: }
1024: }
1025: }
1026:
1027: private class ToolbarTabsLayout implements LayoutManager {
1028:
1029: public void layoutContainer(Container container) {
1030: Dimension tabSize = tabDisplayer.getPreferredSize();
1031: Insets ins = container.getInsets();
1032: int w = container.getWidth() - (ins.left + ins.right);
1033: tabDisplayer
1034: .setBounds(ins.left, ins.top, w, tabSize.height);
1035: contentDisplayer.setBounds(ins.left, ins.top
1036: + tabSize.height, w, container.getHeight()
1037: - (ins.top + ins.bottom + tabSize.height));
1038: }
1039:
1040: public Dimension minimumLayoutSize(Container container) {
1041: Dimension tabSize = tabDisplayer.getMinimumSize();
1042: Dimension contentSize = contentDisplayer.getMinimumSize();
1043: Insets ins = container.getInsets();
1044: Dimension result = new Dimension(ins.left + ins.top,
1045: ins.right + ins.bottom);
1046: result.width += Math.max(tabSize.width, contentSize.width);
1047: result.height += tabSize.height + contentSize.height;
1048: return result;
1049: }
1050:
1051: public Dimension preferredLayoutSize(Container container) {
1052: Dimension tabSize = tabDisplayer.getPreferredSize();
1053: Dimension contentSize = contentDisplayer.getPreferredSize();
1054: Insets ins = container.getInsets();
1055: Dimension result = new Dimension(ins.left + ins.top,
1056: ins.right + ins.bottom);
1057: result.width += Math.max(tabSize.width, contentSize.width);
1058: result.height += tabSize.height + contentSize.height;
1059: return result;
1060: }
1061:
1062: public void removeLayoutComponent(Component component) {
1063: //do nothing
1064: }
1065:
1066: public void addLayoutComponent(String str, Component component) {
1067: //do nothing
1068: }
1069: }
1070:
1071: /**
1072: * A ChangeListener which updates the component displayed in the content
1073: * displayer with the selected component in the tab displayer's selection
1074: * model
1075: */
1076: protected class SelectionListener implements ChangeListener {
1077: public SelectionListener() {
1078: }
1079:
1080: public void stateChanged(ChangeEvent e) {
1081: if (container.isShowing()
1082: //a special case for property sheet dialog window - the selection
1083: //change must be processed otherwise the tabbed container may have
1084: //undefined preferred size so the property window will be too small
1085: || container.getClientProperty("tc") != null) { //NOI18N
1086: int idx = tabDisplayer.getSelectionModel()
1087: .getSelectedIndex();
1088: if (idx != -1) {
1089: Component c = toComp(container.getModel().getTab(
1090: idx));
1091: c.setBounds(0, 0, contentDisplayer.getWidth(),
1092: contentDisplayer.getHeight());
1093: showComponentWithFxProvider(c);
1094: } else {
1095: showComponent(null);
1096: }
1097: }
1098: }
1099: }
1100:
1101: /**
1102: * This class does the heavy lifting of keeping the content of the content
1103: * displayer up-to-date with the contents of the data model.
1104: */
1105: protected class ModelListener implements ComplexListDataListener {
1106: public ModelListener() {
1107: }
1108:
1109: /**
1110: * DefaultTabDataModel will always call this method with an instance of
1111: * ComplexListDataEvent.
1112: */
1113: public void contentsChanged(ListDataEvent e) {
1114: //Only need to reread components on setTab (does winsys even use it?)
1115: if (e instanceof ComplexListDataEvent) {
1116: ComplexListDataEvent clde = (ComplexListDataEvent) e;
1117: int index = clde.getIndex0();
1118: if (clde.isUserObjectChanged() && index != -1) {
1119: Component comp = contentDisplayer
1120: .getComponent(index);
1121: Component nue = toComp(tabDisplayer.getModel()
1122: .getTab(index));
1123: contentDisplayer.remove(comp);
1124:
1125: boolean add = container.getContentPolicy() == TabbedContainer.CONTENT_POLICY_ADD_ALL
1126: || index == container.getSelectionModel()
1127: .getSelectedIndex();
1128:
1129: if (add) {
1130: contentDisplayer.add(nue, index);
1131: }
1132: }
1133: if (clde.isTextChanged()) {
1134: maybeMakeSelectedTabVisible(clde);
1135: }
1136: }
1137: }
1138:
1139: /**
1140: * This method is called to scroll the selected tab into view if its
1141: * title changes (it may be scrolled offscreen). NetBeans' editor uses
1142: * this to ensure that the user can see what file they're editing when
1143: * the user starts typing (this triggers a * being appended to the tab
1144: * title, thus triggering this call).
1145: */
1146: private void maybeMakeSelectedTabVisible(
1147: ComplexListDataEvent clde) {
1148: if (!container.isShowing() || container.getWidth() < 10) {
1149: //Java module fires icon changes from badging before the
1150: //main window has been validated for the first time
1151: return;
1152: }
1153: if (tabDisplayer.getType() == TabDisplayer.TYPE_EDITOR) {
1154: int idx = tabDisplayer.getSelectionModel()
1155: .getSelectedIndex();
1156: //If more than one tab changed, it's probably not an event we want.
1157: //Only do this if there is only one.
1158: if ((clde.getIndex0() == clde.getIndex1())
1159: && clde.getIndex0() == idx) {
1160: (tabDisplayer).makeTabVisible(idx);
1161: }
1162: }
1163: }
1164:
1165: public void intervalAdded(ListDataEvent e) {
1166: if (container.getContentPolicy() == TabbedContainer.CONTENT_POLICY_ADD_ALL) {
1167: Component curC = null;
1168: for (int i = e.getIndex0(); i <= e.getIndex1(); i++) {
1169: curC = toComp(container.getModel().getTab(i));
1170: contentDisplayer.add(curC, "");
1171: }
1172: }
1173: }
1174:
1175: public void intervalRemoved(ListDataEvent e) {
1176: // we know that it must be complex data event
1177: ComplexListDataEvent clde = (ComplexListDataEvent) e;
1178: TabData[] removedTabs = clde.getAffectedItems();
1179: Component curComp;
1180: for (int i = 0; i < removedTabs.length; i++) {
1181: curComp = toComp(removedTabs[i]);
1182: contentDisplayer.remove(curComp);
1183: }
1184: }
1185:
1186: public void indicesAdded(ComplexListDataEvent e) {
1187: Component curC = null;
1188: if (container.getContentPolicy() == TabbedContainer.CONTENT_POLICY_ADD_ALL) {
1189: int[] indices = e.getIndices();
1190: for (int i = 0; i < indices.length; i++) {
1191: curC = toComp(container.getModel().getTab(
1192: indices[i]));
1193: contentDisplayer.add(curC, "");
1194: }
1195: }
1196: }
1197:
1198: public void indicesRemoved(ComplexListDataEvent e) {
1199: int[] indices = e.getIndices();
1200: TabData[] removedTabs = e.getAffectedItems();
1201: Component curComp;
1202: for (int i = 0; i < indices.length; i++) {
1203: curComp = toComp(removedTabs[i]);
1204: // TBD - add assertion curComp.getParent() != contentDisplayer
1205: contentDisplayer.remove(curComp);
1206: }
1207: }
1208:
1209: public void indicesChanged(ComplexListDataEvent e) {
1210: //XXX - if we keep contentPolicies, this could be simplified for
1211: //the non ADD_ALL policies
1212: if (e instanceof VeryComplexListDataEvent) {
1213: ArrayDiff dif = ((VeryComplexListDataEvent) e)
1214: .getDiff();
1215:
1216: //Get the deleted and added indices
1217: Set deleted = dif.getDeletedIndices();
1218: Set added = dif.getAddedIndices();
1219:
1220: //Get the TabData array from before the change
1221: TabData[] old = dif.getOldData();
1222: //Get the TabData array from after the change
1223: TabData[] nue = dif.getNewData();
1224:
1225: //Now we need to fetch the set of components we should end up
1226: //displaying. We need to do this because TabData.equals is only
1227: //true if the text *and* the component match. So if the winsys
1228: //called setTabs just to change the title of a tab, we would
1229: //end up removing and re-adding the component for no reason.
1230: Set<Component> components = new HashSet<Component>();
1231: if (container.getContentPolicy() == TabbedContainer.CONTENT_POLICY_ADD_ALL) {
1232: for (int i = 0; i < nue.length; i++) {
1233: components.add(toComp(nue[i]));
1234: }
1235: }
1236: boolean changed = false;
1237:
1238: synchronized (contentDisplayer.getTreeLock()) {
1239: //See if we've got anything to delete
1240: if (!deleted.isEmpty()) {
1241: Iterator i = deleted.iterator();
1242: while (i.hasNext()) {
1243: //Get the index into the old array of a deleted tab
1244: Integer idx = (Integer) i.next();
1245: //Find the TabData object for it
1246: TabData del = old[idx.intValue()];
1247: //Make sure its component is not one we'll be adding
1248: if (!components.contains(toComp(del))) {
1249: //remove it
1250: contentDisplayer.remove(toComp(del));
1251: changed = true;
1252: }
1253: }
1254: }
1255:
1256: if (container.getContentPolicy() == TabbedContainer.CONTENT_POLICY_ADD_ALL) {
1257:
1258: //See if we've got anything to add
1259: if (!added.isEmpty()) {
1260: Iterator i = added.iterator();
1261: while (i.hasNext()) {
1262: //Get the index into the new array of the added tab
1263: Integer idx = (Integer) i.next();
1264: //Find the TabData object that was added
1265: TabData add = nue[idx.intValue()];
1266: //Make sure it's not already showing so we don't do
1267: //extra work
1268: if (!contentDisplayer
1269: .isAncestorOf(toComp(add))) {
1270: contentDisplayer.add(toComp(add),
1271: "");
1272: changed = true;
1273: }
1274: }
1275: }
1276: }
1277: }
1278: //repaint
1279: if (changed) {
1280: contentDisplayer.revalidate();
1281: contentDisplayer.repaint();
1282: }
1283: }
1284: }
1285: }
1286:
1287: /** An action listener which listens on action events from the tab displayer (select, close, etc.)
1288: * and propagates them to the tabbed container's action posting mechanism, so listeners on it also
1289: * have an opportunity to veto undesired actions, or handle actions themselves.
1290: */
1291: private class DisplayerActionListener implements ActionListener {
1292: public void actionPerformed(ActionEvent ae) {
1293: TabActionEvent tae = (TabActionEvent) ae;
1294: if (!shouldPerformAction(tae.getActionCommand(), tae
1295: .getTabIndex(), tae.getMouseEvent())) {
1296: tae.consume();
1297: }
1298: }
1299: }
1300:
1301: private class SlidingTabsLayout implements LayoutManager {
1302:
1303: public void addLayoutComponent(String name, Component comp) {
1304: //do nothing
1305: }
1306:
1307: public void layoutContainer(Container parent) {
1308: JComponent c = tabDisplayer;
1309:
1310: Object orientation = c
1311: .getClientProperty(TabDisplayer.PROP_ORIENTATION);
1312:
1313: Dimension d = tabDisplayer.getPreferredSize();
1314: Insets ins = container.getInsets();
1315: int width = parent.getWidth() - (ins.left + ins.right);
1316: int height = parent.getHeight() - (ins.top + ins.bottom);
1317:
1318: if (orientation == TabDisplayer.ORIENTATION_NORTH) {
1319: c.setBounds(ins.left, ins.top, width, d.height);
1320:
1321: contentDisplayer.setBounds(ins.left,
1322: ins.top + d.height, width, parent.getHeight()
1323: - (d.height + ins.top + ins.bottom));
1324:
1325: } else if (orientation == TabDisplayer.ORIENTATION_SOUTH) {
1326: contentDisplayer.setBounds(ins.top, ins.left, width,
1327: parent.getHeight()
1328: - (d.height + ins.top + ins.bottom));
1329:
1330: c.setBounds(ins.left, parent.getHeight()
1331: - (d.height + ins.top + ins.bottom), width,
1332: d.height);
1333: } else if (orientation == TabDisplayer.ORIENTATION_EAST) {
1334: contentDisplayer.setBounds(ins.left, ins.top, width
1335: - d.width, height);
1336:
1337: c.setBounds(parent.getWidth() - (ins.right + d.width),
1338: ins.top, d.width, height);
1339:
1340: } else if (orientation == TabDisplayer.ORIENTATION_WEST) {
1341: c.setBounds(ins.left, ins.top, d.width, height);
1342:
1343: contentDisplayer.setBounds(ins.left + d.width, ins.top,
1344: width - d.width, height);
1345:
1346: } else {
1347: throw new IllegalArgumentException(
1348: "Unknown orientation: " + orientation);
1349: }
1350: }
1351:
1352: public Dimension minimumLayoutSize(Container parent) {
1353: JComponent c = tabDisplayer;
1354:
1355: Object orientation = c
1356: .getClientProperty(TabDisplayer.PROP_ORIENTATION);
1357:
1358: Dimension tabSize = tabDisplayer.getPreferredSize();
1359: Insets ins = container.getInsets();
1360:
1361: Dimension result = new Dimension();
1362:
1363: Dimension contentSize = contentDisplayer.getPreferredSize();
1364: if (tabDisplayer.getSelectionModel().getSelectedIndex() == -1) {
1365: contentSize.width = 0;
1366: contentSize.height = 0;
1367: }
1368:
1369: if (orientation == TabDisplayer.ORIENTATION_NORTH
1370: || orientation == TabDisplayer.ORIENTATION_SOUTH) {
1371: result.height = ins.top + ins.bottom
1372: + contentSize.height + tabSize.height;
1373: result.width = ins.left + ins.right
1374: + Math.max(contentSize.width, tabSize.width);
1375: } else {
1376: result.width = ins.left + ins.right + contentSize.width
1377: + tabSize.width;
1378: result.height = ins.top + ins.bottom
1379: + Math.max(contentSize.height, tabSize.height);
1380: }
1381: return result;
1382: }
1383:
1384: public Dimension preferredLayoutSize(Container parent) {
1385: return minimumLayoutSize(parent);
1386: }
1387:
1388: public void removeLayoutComponent(Component comp) {
1389: //do nothing
1390: }
1391: }
1392:
1393: /** A FxProvider which simply calls finish() from its start
1394: * method, providing no effects whatsoever */
1395: private final class NoOpFxProvider extends FxProvider {
1396:
1397: public void cleanup() {
1398: //Do nothing
1399: }
1400:
1401: protected void doFinish() {
1402: showComponent(comp);
1403:
1404: }
1405:
1406: protected void doStart() {
1407: finish();
1408: }
1409: }
1410:
1411: private final class ImageSlideFxProvider extends FxProvider
1412: implements ActionListener {
1413: private Timer timer = null;
1414: private Component prevGlassPane = null;
1415: private Dimension d = null;
1416:
1417: protected void doStart() {
1418: if (timer == null) {
1419: timer = new Timer(TIMER, this );
1420: timer.setRepeats(true);
1421: }
1422:
1423: prevGlassPane = root.getGlassPane();
1424:
1425: if (prevGlassPane.isVisible() && prevGlassPane.isShowing()) {
1426: //Probably a drag and drop operation - don't interfere
1427: doFinish();
1428: return;
1429: }
1430:
1431: initSize();
1432: img = createImageOfComponent();
1433:
1434: ImageScalingGlassPane cp = getCustomGlassPane();
1435: root.setGlassPane(cp);
1436: cp.setIncrement(0.1f);
1437: cp.setBounds(root.getBounds());
1438: cp.setVisible(true);
1439: cp.revalidate();
1440: timer.start();
1441: }
1442:
1443: public void cleanup() {
1444: timer.stop();
1445: root.setGlassPane(prevGlassPane);
1446: prevGlassPane.setVisible(false);
1447: if (img != null) {
1448: img.flush();
1449: }
1450: img = null;
1451: }
1452:
1453: protected void doFinish() {
1454: showComponent(comp);
1455: }
1456:
1457: private void initSize() {
1458: d = comp.getPreferredSize();
1459:
1460: Dimension d2 = contentDisplayer.getSize();
1461:
1462: d.width = Math.max(d2.width, d.width);
1463: d.height = Math.max(d2.height, d.height);
1464:
1465: boolean flip = orientation == TabDisplayer.ORIENTATION_EAST
1466: || orientation == TabDisplayer.ORIENTATION_WEST;
1467:
1468: if (d.width == 0 || d.height == 0) {
1469: if (flip) {
1470: d.width = root.getWidth();
1471: d.height = tabDisplayer.getHeight();
1472: } else {
1473: d.width = tabDisplayer.getWidth();
1474: d.height = root.getHeight();
1475: }
1476: } else {
1477: if (flip) {
1478: d.height = Math.max(d.height, tabDisplayer
1479: .getHeight());
1480: } else {
1481: d.width = Math
1482: .max(d.width, tabDisplayer.getWidth());
1483: }
1484: }
1485: }
1486:
1487: private BufferedImage img = null;
1488:
1489: private BufferedImage createImageOfComponent() {
1490: if (USE_SWINGPAINTING) {
1491: return null;
1492: }
1493: if (d.width == 0 || d.height == 0) {
1494: //Avoid problems in native graphics engine scaling if we should
1495: //end up with crazy values
1496: finish();
1497: }
1498:
1499: BufferedImage img = GraphicsEnvironment
1500: .getLocalGraphicsEnvironment()
1501: .getDefaultScreenDevice().getDefaultConfiguration()
1502: .createCompatibleImage(d.width, d.height);
1503:
1504: Graphics2D g2d = img.createGraphics();
1505: JComponent c = tabDisplayer;
1506:
1507: c.setBounds(0, 0, d.width, d.height);
1508: comp.paint(g2d);
1509:
1510: return img;
1511: }
1512:
1513: public void actionPerformed(java.awt.event.ActionEvent e) {
1514: float inc = customGlassPane.getIncrement();
1515: if (inc >= 1.0f) {
1516: finish();
1517: } else {
1518: customGlassPane.setIncrement(inc + INCREMENT);
1519: }
1520: }
1521:
1522: private ImageScalingGlassPane customGlassPane = null;
1523:
1524: private ImageScalingGlassPane getCustomGlassPane() {
1525: if (customGlassPane == null) {
1526: customGlassPane = new ImageScalingGlassPane();
1527: customGlassPane.setOpaque(false);
1528: }
1529: return customGlassPane;
1530: }
1531:
1532: private class ImageScalingGlassPane extends JPanel {
1533: private float inc = 0f;
1534: private Rectangle rect = new Rectangle();
1535: private Rectangle r2 = new Rectangle();
1536: private boolean changed = true;
1537:
1538: private void setIncrement(float inc) {
1539: this .inc = inc;
1540: changed = true;
1541: if (isShowing()) {
1542: Rectangle r = getImageBounds();
1543: if (SYNCHRONOUS_PAINTING) {
1544: paintImmediately(r.x, r.y, r.width, r.height);
1545: } else {
1546: repaint(r.x, r.y, r.width, r.height);
1547: }
1548: }
1549: }
1550:
1551: private float getIncrement() {
1552: return inc;
1553: }
1554:
1555: private Rectangle getImageBounds() {
1556: if (!changed) {
1557: return rect;
1558: }
1559: Component c = tabDisplayer;
1560: r2.setBounds(0, 0, c.getWidth(), c.getHeight());
1561:
1562: Rectangle dispBounds = SwingUtilities.convertRectangle(
1563: c, r2, this );
1564:
1565: if (orientation == TabDisplayer.ORIENTATION_WEST) {
1566: rect.x = dispBounds.x + dispBounds.width;
1567: rect.y = dispBounds.y;
1568: rect.width = Math.round(inc * d.width);
1569: rect.height = dispBounds.height;
1570: } else if (orientation == TabDisplayer.ORIENTATION_EAST) {
1571: rect.width = Math.round(inc * d.width);
1572: rect.height = dispBounds.height;
1573: rect.x = dispBounds.x - rect.width;
1574: rect.y = dispBounds.y;
1575: } else if (orientation == TabDisplayer.ORIENTATION_SOUTH) {
1576: rect.width = dispBounds.width;
1577: rect.height = Math.round(inc * d.height);
1578: rect.x = dispBounds.x;
1579: rect.y = dispBounds.y - rect.height;
1580: } else if (orientation == TabDisplayer.ORIENTATION_NORTH) {
1581: rect.x = dispBounds.x;
1582: rect.y = dispBounds.y + dispBounds.height;
1583: rect.width = dispBounds.width;
1584: rect.height = Math.round(inc * d.height);
1585: }
1586: changed = false;
1587: return rect;
1588: }
1589:
1590: public void paint(Graphics g) {
1591: try {
1592: if (USE_SWINGPAINTING) {
1593: SwingUtilities.paintComponent(g, comp, this ,
1594: getImageBounds());
1595: } else {
1596: Graphics2D g2d = (Graphics2D) g;
1597: Composite comp = null;
1598: if (true) {
1599: comp = g2d.getComposite();
1600: g2d.setComposite(AlphaComposite
1601: .getInstance(
1602: AlphaComposite.SRC_OVER,
1603: Math.min(0.99f, inc)));
1604: }
1605: Rectangle r = getImageBounds();
1606: if (NO_SCALE) {
1607: AffineTransform at = AffineTransform
1608: .getTranslateInstance(r.x, r.y);
1609: g2d.drawRenderedImage(img, at);
1610: } else {
1611: g2d.drawImage(img, r.x, r.y, r.x + r.width,
1612: r.y + r.height, 0, 0, d.width,
1613: d.height, getBackground(), null);
1614: }
1615: if (comp != null) {
1616: g2d.setComposite(comp);
1617: }
1618: }
1619: } catch (Exception e) {
1620: //Some problem in Apple's graphics scaling engine
1621: e.printStackTrace();
1622: finish();
1623: }
1624: }
1625: }
1626: }
1627:
1628: private final class LiveComponentSlideFxProvider extends FxProvider
1629: implements ActionListener {
1630: private Timer timer = null;
1631: private Component prevGlassPane = null;
1632: private Dimension d = null;
1633:
1634: protected void doStart() {
1635: if (timer == null) {
1636: timer = new Timer(TIMER, this );
1637: timer.setRepeats(true);
1638: }
1639:
1640: prevGlassPane = root.getGlassPane();
1641: if (prevGlassPane.isVisible() && prevGlassPane.isShowing()) {
1642: //Probably a drag and drop operation - don't interfere
1643: doFinish();
1644: return;
1645: }
1646:
1647: initSize();
1648: LiveComponentResizingGlassPane cp = getCustomGlassPane();
1649: root.setGlassPane(cp);
1650: cp.setIncrement(0.1f);
1651: cp.setBounds(root.getBounds());
1652: cp.setVisible(true);
1653: cp.revalidate();
1654: timer.start();
1655: }
1656:
1657: private void initSize() {
1658: d = comp.getPreferredSize();
1659:
1660: Dimension d2 = contentDisplayer.getSize();
1661:
1662: d.width = Math.max(d2.width, d.width);
1663: d.height = Math.max(d2.height, d.height);
1664:
1665: boolean flip = orientation == TabDisplayer.ORIENTATION_EAST
1666: || orientation == TabDisplayer.ORIENTATION_WEST;
1667:
1668: if (d.width == 0 || d.height == 0) {
1669: if (flip) {
1670: d.width = root.getWidth();
1671: d.height = tabDisplayer.getHeight();
1672: } else {
1673: d.width = tabDisplayer.getWidth();
1674: d.height = root.getHeight();
1675: }
1676: } else {
1677: if (flip) {
1678: d.height = Math.max(d.height, tabDisplayer
1679: .getHeight());
1680: } else {
1681: d.width = Math
1682: .max(d.width, tabDisplayer.getWidth());
1683: }
1684: }
1685: }
1686:
1687: public void cleanup() {
1688: timer.stop();
1689: root.setGlassPane(prevGlassPane);
1690: prevGlassPane.setVisible(false);
1691: customGlassPane.remove(comp);
1692: }
1693:
1694: protected void doFinish() {
1695: showComponent(comp);
1696: }
1697:
1698: public void actionPerformed(java.awt.event.ActionEvent e) {
1699: float inc = customGlassPane.getIncrement();
1700: if (inc >= 1.0f) {
1701: finish();
1702: } else {
1703: customGlassPane.setIncrement(inc + INCREMENT);
1704: }
1705: }
1706:
1707: private LiveComponentResizingGlassPane customGlassPane = null;
1708:
1709: private LiveComponentResizingGlassPane getCustomGlassPane() {
1710: if (customGlassPane == null) {
1711: customGlassPane = new LiveComponentResizingGlassPane();
1712: customGlassPane.setOpaque(false);
1713: }
1714: return customGlassPane;
1715: }
1716:
1717: private class LiveComponentResizingGlassPane extends JPanel {
1718: private float inc = 0f;
1719: private Rectangle rect = new Rectangle();
1720: private Rectangle r2 = new Rectangle();
1721: private boolean changed = true;
1722:
1723: private void setIncrement(float inc) {
1724: this .inc = inc;
1725: changed = true;
1726: if (isShowing()) {
1727: if (comp.getParent() != this ) {
1728: add(comp);
1729: comp.setVisible(true);
1730: }
1731: }
1732: doLayout();
1733: }
1734:
1735: public void doLayout() {
1736: Rectangle r = getImageBounds();
1737: comp.setBounds(r.x, r.y, r.width, r.height);
1738: }
1739:
1740: private float getIncrement() {
1741: return inc;
1742: }
1743:
1744: private Rectangle getImageBounds() {
1745: if (!changed) {
1746: return rect;
1747: }
1748: Component c = tabDisplayer;
1749: r2.setBounds(0, 0, c.getWidth(), c.getHeight());
1750:
1751: Rectangle dispBounds = SwingUtilities.convertRectangle(
1752: c, r2, this );
1753:
1754: if (orientation == TabDisplayer.ORIENTATION_WEST) {
1755: rect.x = dispBounds.x + dispBounds.width;
1756: rect.y = dispBounds.y;
1757: rect.width = Math.round(inc * d.width);
1758: rect.height = dispBounds.height;
1759: } else if (orientation == TabDisplayer.ORIENTATION_EAST) {
1760: rect.width = Math.round(inc * d.width);
1761: rect.height = dispBounds.height;
1762: rect.x = dispBounds.x - rect.width;
1763: rect.y = dispBounds.y;
1764: } else if (orientation == TabDisplayer.ORIENTATION_SOUTH) {
1765: rect.width = dispBounds.width;
1766: rect.height = Math.round(inc * d.height);
1767: rect.x = dispBounds.x;
1768: rect.y = dispBounds.y - rect.height;
1769: } else if (orientation == TabDisplayer.ORIENTATION_NORTH) {
1770: rect.x = dispBounds.x;
1771: rect.y = dispBounds.y + dispBounds.height;
1772: rect.width = dispBounds.width;
1773: rect.height = Math.round(inc * d.height);
1774: }
1775: changed = false;
1776: return rect;
1777: }
1778: }
1779: }
1780:
1781: //*** A bunch of options for testing
1782:
1783: /** Sysprop to turn off all sliding effects */
1784: static final boolean NO_EFFECTS = Boolean
1785: .getBoolean("nb.tabcontrol.no.fx"); //NOI18N
1786: /** Sysprop to turn off scaling of the slide image */
1787: static final boolean NO_SCALE = Boolean
1788: .getBoolean("nb.tabcontrol.fx.no.scaling"); //NOI18N
1789: /** Sysprop to turn use SwingUtilities.paintComponent() instead of an image buffer for sliding effects */
1790: static final boolean USE_SWINGPAINTING = Boolean
1791: .getBoolean("nb.tabcontrol.fx.swingpainting"); //NOI18N
1792: /** Sysprop to turn add the component being scaled to the glasspane and alter its size on a
1793: * timer to accomplish growing the component */
1794: static final boolean ADD_TO_GLASSPANE = Boolean
1795: .getBoolean("nb.tabcontrol.fx.use.resizing"); //NOI18N
1796: /** For those who <strong>really</strong> love the sliding effect and want to see it on all
1797: * tab controls of all types */
1798: static final boolean EFFECTS_EVERYWHERE = Boolean
1799: .getBoolean("nb.tabcontrol.fx.everywhere")
1800: || Boolean.getBoolean("nb.tabcontrol.fx.gratuitous"); //NOI18N
1801:
1802: /** Also have the scaled image be partially transparent as it's drawn */
1803: static final boolean USE_ALPHA = Boolean
1804: .getBoolean("nb.tabcontrol.fx.use.alpha")
1805: || Boolean.getBoolean("nb.tabcontrol.fx.gratuitous"); //NOI18N
1806:
1807: static boolean SYNCHRONOUS_PAINTING = Boolean
1808: .getBoolean("nb.tabcontrol.fx.synchronous"); //NOI18N
1809:
1810: static float INCREMENT = 0.07f;
1811:
1812: static int TIMER = 25;
1813: static {
1814: boolean gratuitous = Boolean
1815: .getBoolean("nb.tabcontrol.fx.gratuitous"); //NOI18N
1816: String s = System.getProperty("nb.tabcontrol.fx.increment"); //NOI18N
1817: if (s != null) {
1818: try {
1819: INCREMENT = Float.parseFloat(s);
1820: } catch (Exception e) {
1821: System.err.println("Bad float value specified: \"" + s
1822: + "\""); //NOI18N
1823: }
1824: } else if (gratuitous) {
1825: INCREMENT = 0.02f;
1826: }
1827:
1828: s = System.getProperty("nb.tabcontrol.fx.timer"); //NOI18N
1829: if (s != null) {
1830: try {
1831: TIMER = Integer.parseInt(s);
1832: } catch (Exception e) {
1833: System.err.println("Bad integer value specified: \""
1834: + s + "\""); //NOI18N
1835: }
1836: } else if (gratuitous) {
1837: TIMER = 7;
1838: }
1839: if (gratuitous) {
1840: SYNCHRONOUS_PAINTING = true;
1841: }
1842: }
1843:
1844: private static final class ForwardingMouseListener implements
1845: MouseListener {
1846: private final Container c;
1847:
1848: public ForwardingMouseListener(Container c) {
1849: this .c = c;
1850: }
1851:
1852: public void mousePressed(MouseEvent me) {
1853: forward(me);
1854: }
1855:
1856: public void mouseReleased(MouseEvent me) {
1857: forward(me);
1858: }
1859:
1860: public void mouseClicked(MouseEvent me) {
1861: forward(me);
1862: }
1863:
1864: public void mouseEntered(MouseEvent me) {
1865: forward(me);
1866: }
1867:
1868: public void mouseExited(MouseEvent me) {
1869: forward(me);
1870: }
1871:
1872: private void forward(MouseEvent me) {
1873: MouseListener[] ml = c.getMouseListeners();
1874: if (ml.length == 0 || me.isConsumed()) {
1875: return;
1876: }
1877: MouseEvent me2 = SwingUtilities.convertMouseEvent(
1878: (Component) me.getSource(), me, c);
1879:
1880: for (int i = 0; i < ml.length; i++) {
1881: switch (me2.getID()) {
1882: case MouseEvent.MOUSE_ENTERED:
1883: ml[i].mouseEntered(me2);
1884: break;
1885: case MouseEvent.MOUSE_EXITED:
1886: ml[i].mouseExited(me2);
1887: break;
1888: case MouseEvent.MOUSE_PRESSED:
1889: ml[i].mousePressed(me2);
1890: break;
1891: case MouseEvent.MOUSE_RELEASED:
1892: ml[i].mouseReleased(me2);
1893: break;
1894: case MouseEvent.MOUSE_CLICKED:
1895: ml[i].mouseClicked(me2);
1896: break;
1897: default:
1898: assert false;
1899: }
1900: }
1901: }
1902:
1903: }
1904: }
|