--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicTabbedPaneUI.java Tue Sep 12 19:03:39 2017 +0200
@@ -0,0 +1,4378 @@
+/*
+ * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package javax.swing.plaf.basic;
+
+import sun.swing.SwingUtilities2;
+
+import javax.swing.*;
+import javax.swing.event.*;
+import javax.swing.plaf.*;
+import javax.swing.text.View;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.beans.PropertyChangeListener;
+import java.beans.PropertyChangeEvent;
+import java.util.Vector;
+import java.util.Hashtable;
+
+import sun.swing.DefaultLookup;
+import sun.swing.UIAction;
+
+/**
+ * A Basic L&F implementation of TabbedPaneUI.
+ *
+ * @author Amy Fowler
+ * @author Philip Milne
+ * @author Steve Wilson
+ * @author Tom Santos
+ * @author Dave Moore
+ */
+public class BasicTabbedPaneUI extends TabbedPaneUI implements SwingConstants {
+
+
+// Instance variables initialized at installation
+
+ /** The tab pane */
+ protected JTabbedPane tabPane;
+
+ /** Highlight color */
+ protected Color highlight;
+ /** Light highlight color */
+ protected Color lightHighlight;
+ /** Shadow color */
+ protected Color shadow;
+ /** Dark shadow color */
+ protected Color darkShadow;
+ /** Focus color */
+ protected Color focus;
+ private Color selectedColor;
+
+ /** Text icon gap */
+ protected int textIconGap;
+ /** Tab run overlay */
+ protected int tabRunOverlay;
+
+ /** Tab insets */
+ protected Insets tabInsets;
+ /** Selected tab insets */
+ protected Insets selectedTabPadInsets;
+ /** Tab area insets */
+ protected Insets tabAreaInsets;
+ /** Content border insets */
+ protected Insets contentBorderInsets;
+ private boolean tabsOverlapBorder;
+ private boolean tabsOpaque = true;
+ private boolean contentOpaque = true;
+
+ /**
+ * As of Java 2 platform v1.3 this previously undocumented field is no
+ * longer used.
+ * Key bindings are now defined by the LookAndFeel, please refer to
+ * the key bindings specification for further details.
+ *
+ * @deprecated As of Java 2 platform v1.3.
+ */
+ @Deprecated
+ protected KeyStroke upKey;
+ /**
+ * As of Java 2 platform v1.3 this previously undocumented field is no
+ * longer used.
+ * Key bindings are now defined by the LookAndFeel, please refer to
+ * the key bindings specification for further details.
+ *
+ * @deprecated As of Java 2 platform v1.3.
+ */
+ @Deprecated
+ protected KeyStroke downKey;
+ /**
+ * As of Java 2 platform v1.3 this previously undocumented field is no
+ * longer used.
+ * Key bindings are now defined by the LookAndFeel, please refer to
+ * the key bindings specification for further details.
+ *
+ * @deprecated As of Java 2 platform v1.3.
+ */
+ @Deprecated
+ protected KeyStroke leftKey;
+ /**
+ * As of Java 2 platform v1.3 this previously undocumented field is no
+ * longer used.
+ * Key bindings are now defined by the LookAndFeel, please refer to
+ * the key bindings specification for further details.
+ *
+ * @deprecated As of Java 2 platform v1.3.
+ */
+ @Deprecated
+ protected KeyStroke rightKey;
+
+
+// Transient variables (recalculated each time TabbedPane is layed out)
+ /** Tab runs */
+ protected int tabRuns[] = new int[10];
+ /** Run count */
+ protected int runCount = 0;
+ /** Selected run */
+ protected int selectedRun = -1;
+ /** Tab rects */
+ protected Rectangle rects[] = new Rectangle[0];
+ /** Maximum tab height */
+ protected int maxTabHeight;
+ /** Maximum tab width */
+ protected int maxTabWidth;
+
+// Listeners
+
+ /** Tab change listener */
+ protected ChangeListener tabChangeListener;
+ /** Property change listener */
+ protected PropertyChangeListener propertyChangeListener;
+ /** Mouse change listener */
+ protected MouseListener mouseListener;
+ /** Focus change listener */
+ protected FocusListener focusListener;
+
+// Private instance data
+
+ private Insets currentPadInsets = new Insets(0,0,0,0);
+ private Insets currentTabAreaInsets = new Insets(0,0,0,0);
+
+ private Component visibleComponent;
+ // PENDING(api): See comment for ContainerHandler
+ private Vector<View> htmlViews;
+
+ private Hashtable<Integer, Integer> mnemonicToIndexMap;
+
+ /**
+ * InputMap used for mnemonics. Only non-null if the JTabbedPane has
+ * mnemonics associated with it. Lazily created in initMnemonics.
+ */
+ private InputMap mnemonicInputMap;
+
+ // For use when tabLayoutPolicy = SCROLL_TAB_LAYOUT
+ private ScrollableTabSupport tabScroller;
+
+ private TabContainer tabContainer;
+
+ /**
+ * A rectangle used for general layout calculations in order
+ * to avoid constructing many new Rectangles on the fly.
+ */
+ protected transient Rectangle calcRect = new Rectangle(0,0,0,0);
+
+ /**
+ * Tab that has focus.
+ */
+ private int focusIndex;
+
+ /**
+ * Combined listeners.
+ */
+ private Handler handler;
+
+ /**
+ * Index of the tab the mouse is over.
+ */
+ private int rolloverTabIndex;
+
+ /**
+ * This is set to true when a component is added/removed from the tab
+ * pane and set to false when layout happens. If true it indicates that
+ * tabRuns is not valid and shouldn't be used.
+ */
+ private boolean isRunsDirty;
+
+ private boolean calculatedBaseline;
+ private int baseline;
+
+// UI creation
+
+ /**
+ * Create a UI.
+ * @param c a component
+ * @return a UI
+ */
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicTabbedPaneUI();
+ }
+
+ static void loadActionMap(LazyActionMap map) {
+ map.put(new Actions(Actions.NEXT));
+ map.put(new Actions(Actions.PREVIOUS));
+ map.put(new Actions(Actions.RIGHT));
+ map.put(new Actions(Actions.LEFT));
+ map.put(new Actions(Actions.UP));
+ map.put(new Actions(Actions.DOWN));
+ map.put(new Actions(Actions.PAGE_UP));
+ map.put(new Actions(Actions.PAGE_DOWN));
+ map.put(new Actions(Actions.REQUEST_FOCUS));
+ map.put(new Actions(Actions.REQUEST_FOCUS_FOR_VISIBLE));
+ map.put(new Actions(Actions.SET_SELECTED));
+ map.put(new Actions(Actions.SELECT_FOCUSED));
+ map.put(new Actions(Actions.SCROLL_FORWARD));
+ map.put(new Actions(Actions.SCROLL_BACKWARD));
+ }
+
+// UI Installation/De-installation
+
+ public void installUI(JComponent c) {
+ this.tabPane = (JTabbedPane)c;
+
+ calculatedBaseline = false;
+ rolloverTabIndex = -1;
+ focusIndex = -1;
+ c.setLayout(createLayoutManager());
+ installComponents();
+ installDefaults();
+ installListeners();
+ installKeyboardActions();
+ }
+
+ public void uninstallUI(JComponent c) {
+ uninstallKeyboardActions();
+ uninstallListeners();
+ uninstallDefaults();
+ uninstallComponents();
+ c.setLayout(null);
+
+ this.tabPane = null;
+ }
+
+ /**
+ * Invoked by <code>installUI</code> to create
+ * a layout manager object to manage
+ * the <code>JTabbedPane</code>.
+ *
+ * @return a layout manager object
+ *
+ * @see TabbedPaneLayout
+ * @see javax.swing.JTabbedPane#getTabLayoutPolicy
+ */
+ protected LayoutManager createLayoutManager() {
+ if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
+ return new TabbedPaneScrollLayout();
+ } else { /* WRAP_TAB_LAYOUT */
+ return new TabbedPaneLayout();
+ }
+ }
+
+ /* In an attempt to preserve backward compatibility for programs
+ * which have extended BasicTabbedPaneUI to do their own layout, the
+ * UI uses the installed layoutManager (and not tabLayoutPolicy) to
+ * determine if scrollTabLayout is enabled.
+ */
+ private boolean scrollableTabLayoutEnabled() {
+ return (tabPane.getLayout() instanceof TabbedPaneScrollLayout);
+ }
+
+ /**
+ * Creates and installs any required subcomponents for the JTabbedPane.
+ * Invoked by installUI.
+ *
+ * @since 1.4
+ */
+ protected void installComponents() {
+ if (scrollableTabLayoutEnabled()) {
+ if (tabScroller == null) {
+ tabScroller = new ScrollableTabSupport(tabPane.getTabPlacement());
+ tabPane.add(tabScroller.viewport);
+ }
+ }
+ installTabContainer();
+ }
+
+ private void installTabContainer() {
+ for (int i = 0; i < tabPane.getTabCount(); i++) {
+ Component tabComponent = tabPane.getTabComponentAt(i);
+ if (tabComponent != null) {
+ if(tabContainer == null) {
+ tabContainer = new TabContainer();
+ }
+ tabContainer.add(tabComponent);
+ }
+ }
+ if(tabContainer == null) {
+ return;
+ }
+ if (scrollableTabLayoutEnabled()) {
+ tabScroller.tabPanel.add(tabContainer);
+ } else {
+ tabPane.add(tabContainer);
+ }
+ }
+
+ /**
+ * Creates and returns a JButton that will provide the user
+ * with a way to scroll the tabs in a particular direction. The
+ * returned JButton must be instance of UIResource.
+ *
+ * @param direction One of the SwingConstants constants:
+ * SOUTH, NORTH, EAST or WEST
+ * @return Widget for user to
+ * @see javax.swing.JTabbedPane#setTabPlacement
+ * @see javax.swing.SwingConstants
+ * @throws IllegalArgumentException if direction is not one of
+ * NORTH, SOUTH, EAST or WEST
+ * @since 1.5
+ */
+ protected JButton createScrollButton(int direction) {
+ if (direction != SOUTH && direction != NORTH && direction != EAST &&
+ direction != WEST) {
+ throw new IllegalArgumentException("Direction must be one of: " +
+ "SOUTH, NORTH, EAST or WEST");
+ }
+ return new ScrollableTabButton(direction);
+ }
+
+ /**
+ * Removes any installed subcomponents from the JTabbedPane.
+ * Invoked by uninstallUI.
+ *
+ * @since 1.4
+ */
+ protected void uninstallComponents() {
+ uninstallTabContainer();
+ if (scrollableTabLayoutEnabled()) {
+ tabPane.remove(tabScroller.viewport);
+ tabPane.remove(tabScroller.scrollForwardButton);
+ tabPane.remove(tabScroller.scrollBackwardButton);
+ tabScroller = null;
+ }
+ }
+
+ private void uninstallTabContainer() {
+ if(tabContainer == null) {
+ return;
+ }
+ // Remove all the tabComponents, making sure not to notify
+ // the tabbedpane.
+ tabContainer.notifyTabbedPane = false;
+ tabContainer.removeAll();
+ if(scrollableTabLayoutEnabled()) {
+ tabContainer.remove(tabScroller.croppedEdge);
+ tabScroller.tabPanel.remove(tabContainer);
+ } else {
+ tabPane.remove(tabContainer);
+ }
+ tabContainer = null;
+ }
+
+ /**
+ * Install the defaults.
+ */
+ protected void installDefaults() {
+ LookAndFeel.installColorsAndFont(tabPane, "TabbedPane.background",
+ "TabbedPane.foreground", "TabbedPane.font");
+ highlight = UIManager.getColor("TabbedPane.light");
+ lightHighlight = UIManager.getColor("TabbedPane.highlight");
+ shadow = UIManager.getColor("TabbedPane.shadow");
+ darkShadow = UIManager.getColor("TabbedPane.darkShadow");
+ focus = UIManager.getColor("TabbedPane.focus");
+ selectedColor = UIManager.getColor("TabbedPane.selected");
+
+ textIconGap = UIManager.getInt("TabbedPane.textIconGap");
+ tabInsets = UIManager.getInsets("TabbedPane.tabInsets");
+ selectedTabPadInsets = UIManager.getInsets("TabbedPane.selectedTabPadInsets");
+ tabAreaInsets = UIManager.getInsets("TabbedPane.tabAreaInsets");
+ tabsOverlapBorder = UIManager.getBoolean("TabbedPane.tabsOverlapBorder");
+ contentBorderInsets = UIManager.getInsets("TabbedPane.contentBorderInsets");
+ tabRunOverlay = UIManager.getInt("TabbedPane.tabRunOverlay");
+ tabsOpaque = UIManager.getBoolean("TabbedPane.tabsOpaque");
+ contentOpaque = UIManager.getBoolean("TabbedPane.contentOpaque");
+ Object opaque = UIManager.get("TabbedPane.opaque");
+ if (opaque == null) {
+ opaque = Boolean.FALSE;
+ }
+ LookAndFeel.installProperty(tabPane, "opaque", opaque);
+
+ // Fix for 6711145 BasicTabbedPanuUI should not throw a NPE if these
+ // keys are missing. So we are setting them to there default values here
+ // if the keys are missing.
+ if (tabInsets == null) tabInsets = new Insets(0,4,1,4);
+ if (selectedTabPadInsets == null) selectedTabPadInsets = new Insets(2,2,2,1);
+ if (tabAreaInsets == null) tabAreaInsets = new Insets(3,2,0,2);
+ if (contentBorderInsets == null) contentBorderInsets = new Insets(2,2,3,3);
+ }
+
+ /**
+ * Uninstall the defaults.
+ */
+ protected void uninstallDefaults() {
+ highlight = null;
+ lightHighlight = null;
+ shadow = null;
+ darkShadow = null;
+ focus = null;
+ tabInsets = null;
+ selectedTabPadInsets = null;
+ tabAreaInsets = null;
+ contentBorderInsets = null;
+ }
+
+ /**
+ * Install the listeners.
+ */
+ protected void installListeners() {
+ if ((propertyChangeListener = createPropertyChangeListener()) != null) {
+ tabPane.addPropertyChangeListener(propertyChangeListener);
+ }
+ if ((tabChangeListener = createChangeListener()) != null) {
+ tabPane.addChangeListener(tabChangeListener);
+ }
+ if ((mouseListener = createMouseListener()) != null) {
+ tabPane.addMouseListener(mouseListener);
+ }
+ tabPane.addMouseMotionListener(getHandler());
+ if ((focusListener = createFocusListener()) != null) {
+ tabPane.addFocusListener(focusListener);
+ }
+ tabPane.addContainerListener(getHandler());
+ if (tabPane.getTabCount()>0) {
+ htmlViews = createHTMLVector();
+ }
+ }
+
+ /**
+ * Uninstall the listeners.
+ */
+ protected void uninstallListeners() {
+ if (mouseListener != null) {
+ tabPane.removeMouseListener(mouseListener);
+ mouseListener = null;
+ }
+ tabPane.removeMouseMotionListener(getHandler());
+ if (focusListener != null) {
+ tabPane.removeFocusListener(focusListener);
+ focusListener = null;
+ }
+
+ tabPane.removeContainerListener(getHandler());
+ if (htmlViews!=null) {
+ htmlViews.removeAllElements();
+ htmlViews = null;
+ }
+ if (tabChangeListener != null) {
+ tabPane.removeChangeListener(tabChangeListener);
+ tabChangeListener = null;
+ }
+ if (propertyChangeListener != null) {
+ tabPane.removePropertyChangeListener(propertyChangeListener);
+ propertyChangeListener = null;
+ }
+ handler = null;
+ }
+
+ /**
+ * Creates a mouse listener.
+ * @return a mouse listener
+ */
+ protected MouseListener createMouseListener() {
+ return getHandler();
+ }
+
+ /**
+ * Creates a focus listener.
+ * @return a focus listener
+ */
+ protected FocusListener createFocusListener() {
+ return getHandler();
+ }
+
+ /**
+ * Creates a change listener.
+ * @return a change listener
+ */
+ protected ChangeListener createChangeListener() {
+ return getHandler();
+ }
+
+ /**
+ * Creates a property change listener.
+ * @return a property change listener
+ */
+ protected PropertyChangeListener createPropertyChangeListener() {
+ return getHandler();
+ }
+
+ private Handler getHandler() {
+ if (handler == null) {
+ handler = new Handler();
+ }
+ return handler;
+ }
+
+ /**
+ * Installs the keyboard actions.
+ */
+ protected void installKeyboardActions() {
+ InputMap km = getInputMap(JComponent.
+ WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+
+ SwingUtilities.replaceUIInputMap(tabPane, JComponent.
+ WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
+ km);
+ km = getInputMap(JComponent.WHEN_FOCUSED);
+ SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED, km);
+
+ LazyActionMap.installLazyActionMap(tabPane, BasicTabbedPaneUI.class,
+ "TabbedPane.actionMap");
+ updateMnemonics();
+ }
+
+ InputMap getInputMap(int condition) {
+ if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
+ return (InputMap)DefaultLookup.get(tabPane, this,
+ "TabbedPane.ancestorInputMap");
+ }
+ else if (condition == JComponent.WHEN_FOCUSED) {
+ return (InputMap)DefaultLookup.get(tabPane, this,
+ "TabbedPane.focusInputMap");
+ }
+ return null;
+ }
+
+ /**
+ * Uninstalls the keyboard actions.
+ */
+ protected void uninstallKeyboardActions() {
+ SwingUtilities.replaceUIActionMap(tabPane, null);
+ SwingUtilities.replaceUIInputMap(tabPane, JComponent.
+ WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
+ null);
+ SwingUtilities.replaceUIInputMap(tabPane, JComponent.WHEN_FOCUSED,
+ null);
+ SwingUtilities.replaceUIInputMap(tabPane,
+ JComponent.WHEN_IN_FOCUSED_WINDOW,
+ null);
+ mnemonicToIndexMap = null;
+ mnemonicInputMap = null;
+ }
+
+ /**
+ * Reloads the mnemonics. This should be invoked when a memonic changes,
+ * when the title of a mnemonic changes, or when tabs are added/removed.
+ */
+ private void updateMnemonics() {
+ resetMnemonics();
+ for (int counter = tabPane.getTabCount() - 1; counter >= 0;
+ counter--) {
+ int mnemonic = tabPane.getMnemonicAt(counter);
+
+ if (mnemonic > 0) {
+ addMnemonic(counter, mnemonic);
+ }
+ }
+ }
+
+ /**
+ * Resets the mnemonics bindings to an empty state.
+ */
+ private void resetMnemonics() {
+ if (mnemonicToIndexMap != null) {
+ mnemonicToIndexMap.clear();
+ mnemonicInputMap.clear();
+ }
+ }
+
+ /**
+ * Adds the specified mnemonic at the specified index.
+ */
+ private void addMnemonic(int index, int mnemonic) {
+ if (mnemonicToIndexMap == null) {
+ initMnemonics();
+ }
+ mnemonicInputMap.put(KeyStroke.getKeyStroke(mnemonic, BasicLookAndFeel.getFocusAcceleratorKeyMask()),
+ "setSelectedIndex");
+ mnemonicToIndexMap.put(Integer.valueOf(mnemonic), Integer.valueOf(index));
+ }
+
+ /**
+ * Installs the state needed for mnemonics.
+ */
+ private void initMnemonics() {
+ mnemonicToIndexMap = new Hashtable<Integer, Integer>();
+ mnemonicInputMap = new ComponentInputMapUIResource(tabPane);
+ mnemonicInputMap.setParent(SwingUtilities.getUIInputMap(tabPane,
+ JComponent.WHEN_IN_FOCUSED_WINDOW));
+ SwingUtilities.replaceUIInputMap(tabPane,
+ JComponent.WHEN_IN_FOCUSED_WINDOW,
+ mnemonicInputMap);
+ }
+
+ /**
+ * Sets the tab the mouse is over by location. This is a cover method
+ * for <code>setRolloverTab(tabForCoordinate(x, y, false))</code>.
+ */
+ private void setRolloverTab(int x, int y) {
+ // NOTE:
+ // This calls in with false otherwise it could trigger a validate,
+ // which should NOT happen if the user is only dragging the
+ // mouse around.
+ setRolloverTab(tabForCoordinate(tabPane, x, y, false));
+ }
+
+ /**
+ * Sets the tab the mouse is currently over to <code>index</code>.
+ * <code>index</code> will be -1 if the mouse is no longer over any
+ * tab. No checking is done to ensure the passed in index identifies a
+ * valid tab.
+ *
+ * @param index Index of the tab the mouse is over.
+ * @since 1.5
+ */
+ protected void setRolloverTab(int index) {
+ rolloverTabIndex = index;
+ }
+
+ /**
+ * Returns the tab the mouse is currently over, or {@code -1} if the mouse is no
+ * longer over any tab.
+ *
+ * @return the tab the mouse is currently over, or {@code -1} if the mouse is no
+ * longer over any tab
+ * @since 1.5
+ */
+ protected int getRolloverTab() {
+ return rolloverTabIndex;
+ }
+
+ public Dimension getMinimumSize(JComponent c) {
+ // Default to LayoutManager's minimumLayoutSize
+ return null;
+ }
+
+ public Dimension getMaximumSize(JComponent c) {
+ // Default to LayoutManager's maximumLayoutSize
+ return null;
+ }
+
+ /**
+ * Returns the baseline.
+ *
+ * @throws NullPointerException {@inheritDoc}
+ * @throws IllegalArgumentException {@inheritDoc}
+ * @see javax.swing.JComponent#getBaseline(int, int)
+ * @since 1.6
+ */
+ public int getBaseline(JComponent c, int width, int height) {
+ super.getBaseline(c, width, height);
+ int baseline = calculateBaselineIfNecessary();
+ if (baseline != -1) {
+ int placement = tabPane.getTabPlacement();
+ Insets insets = tabPane.getInsets();
+ Insets tabAreaInsets = getTabAreaInsets(placement);
+ switch(placement) {
+ case JTabbedPane.TOP:
+ baseline += insets.top + tabAreaInsets.top;
+ return baseline;
+ case JTabbedPane.BOTTOM:
+ baseline = height - insets.bottom -
+ tabAreaInsets.bottom - maxTabHeight + baseline;
+ return baseline;
+ case JTabbedPane.LEFT:
+ case JTabbedPane.RIGHT:
+ baseline += insets.top + tabAreaInsets.top;
+ return baseline;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns an enum indicating how the baseline of the component
+ * changes as the size changes.
+ *
+ * @throws NullPointerException {@inheritDoc}
+ * @see javax.swing.JComponent#getBaseline(int, int)
+ * @since 1.6
+ */
+ public Component.BaselineResizeBehavior getBaselineResizeBehavior(
+ JComponent c) {
+ super.getBaselineResizeBehavior(c);
+ switch(tabPane.getTabPlacement()) {
+ case JTabbedPane.LEFT:
+ case JTabbedPane.RIGHT:
+ case JTabbedPane.TOP:
+ return Component.BaselineResizeBehavior.CONSTANT_ASCENT;
+ case JTabbedPane.BOTTOM:
+ return Component.BaselineResizeBehavior.CONSTANT_DESCENT;
+ }
+ return Component.BaselineResizeBehavior.OTHER;
+ }
+
+ /**
+ * Returns the baseline for the specified tab.
+ *
+ * @param tab index of tab to get baseline for
+ * @exception IndexOutOfBoundsException if index is out of range
+ * (index < 0 || index >= tab count)
+ * @return baseline or a value < 0 indicating there is no reasonable
+ * baseline
+ * @since 1.6
+ */
+ protected int getBaseline(int tab) {
+ if (tabPane.getTabComponentAt(tab) != null) {
+ int offset = getBaselineOffset();
+ if (offset != 0) {
+ // The offset is not applied to the tab component, and so
+ // in general we can't get good alignment like with components
+ // in the tab.
+ return -1;
+ }
+ Component c = tabPane.getTabComponentAt(tab);
+ Dimension pref = c.getPreferredSize();
+ Insets tabInsets = getTabInsets(tabPane.getTabPlacement(), tab);
+ int cellHeight = maxTabHeight - tabInsets.top - tabInsets.bottom;
+ return c.getBaseline(pref.width, pref.height) +
+ (cellHeight - pref.height) / 2 + tabInsets.top;
+ }
+ else {
+ View view = getTextViewForTab(tab);
+ if (view != null) {
+ int viewHeight = (int)view.getPreferredSpan(View.Y_AXIS);
+ int baseline = BasicHTML.getHTMLBaseline(
+ view, (int)view.getPreferredSpan(View.X_AXIS), viewHeight);
+ if (baseline >= 0) {
+ return maxTabHeight / 2 - viewHeight / 2 + baseline +
+ getBaselineOffset();
+ }
+ return -1;
+ }
+ }
+ FontMetrics metrics = getFontMetrics();
+ int fontHeight = metrics.getHeight();
+ int fontBaseline = metrics.getAscent();
+ return maxTabHeight / 2 - fontHeight / 2 + fontBaseline +
+ getBaselineOffset();
+ }
+
+ /**
+ * Returns the amount the baseline is offset by. This is typically
+ * the same as <code>getTabLabelShiftY</code>.
+ *
+ * @return amount to offset the baseline by
+ * @since 1.6
+ */
+ protected int getBaselineOffset() {
+ switch(tabPane.getTabPlacement()) {
+ case JTabbedPane.TOP:
+ if (tabPane.getTabCount() > 1) {
+ return 1;
+ }
+ else {
+ return -1;
+ }
+ case JTabbedPane.BOTTOM:
+ if (tabPane.getTabCount() > 1) {
+ return -1;
+ }
+ else {
+ return 1;
+ }
+ default: // RIGHT|LEFT
+ return (maxTabHeight % 2);
+ }
+ }
+
+ private int calculateBaselineIfNecessary() {
+ if (!calculatedBaseline) {
+ calculatedBaseline = true;
+ baseline = -1;
+ if (tabPane.getTabCount() > 0) {
+ calculateBaseline();
+ }
+ }
+ return baseline;
+ }
+
+ private void calculateBaseline() {
+ int tabCount = tabPane.getTabCount();
+ int tabPlacement = tabPane.getTabPlacement();
+ maxTabHeight = calculateMaxTabHeight(tabPlacement);
+ baseline = getBaseline(0);
+ if (isHorizontalTabPlacement()) {
+ for(int i = 1; i < tabCount; i++) {
+ if (getBaseline(i) != baseline) {
+ baseline = -1;
+ break;
+ }
+ }
+ }
+ else {
+ // left/right, tabs may be different sizes.
+ FontMetrics fontMetrics = getFontMetrics();
+ int fontHeight = fontMetrics.getHeight();
+ int height = calculateTabHeight(tabPlacement, 0, fontHeight);
+ for(int i = 1; i < tabCount; i++) {
+ int newHeight = calculateTabHeight(tabPlacement, i,fontHeight);
+ if (height != newHeight) {
+ // assume different baseline
+ baseline = -1;
+ break;
+ }
+ }
+ }
+ }
+
+// UI Rendering
+
+ public void paint(Graphics g, JComponent c) {
+ int selectedIndex = tabPane.getSelectedIndex();
+ int tabPlacement = tabPane.getTabPlacement();
+
+ ensureCurrentLayout();
+
+ // Paint content border and tab area
+ if (tabsOverlapBorder) {
+ paintContentBorder(g, tabPlacement, selectedIndex);
+ }
+ // If scrollable tabs are enabled, the tab area will be
+ // painted by the scrollable tab panel instead.
+ //
+ if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT
+ paintTabArea(g, tabPlacement, selectedIndex);
+ }
+ if (!tabsOverlapBorder) {
+ paintContentBorder(g, tabPlacement, selectedIndex);
+ }
+ }
+
+ /**
+ * Paints the tabs in the tab area.
+ * Invoked by paint().
+ * The graphics parameter must be a valid <code>Graphics</code>
+ * object. Tab placement may be either:
+ * <code>JTabbedPane.TOP</code>, <code>JTabbedPane.BOTTOM</code>,
+ * <code>JTabbedPane.LEFT</code>, or <code>JTabbedPane.RIGHT</code>.
+ * The selected index must be a valid tabbed pane tab index (0 to
+ * tab count - 1, inclusive) or -1 if no tab is currently selected.
+ * The handling of invalid parameters is unspecified.
+ *
+ * @param g the graphics object to use for rendering
+ * @param tabPlacement the placement for the tabs within the JTabbedPane
+ * @param selectedIndex the tab index of the selected component
+ *
+ * @since 1.4
+ */
+ protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex) {
+ int tabCount = tabPane.getTabCount();
+
+ Rectangle iconRect = new Rectangle(),
+ textRect = new Rectangle();
+ Rectangle clipRect = g.getClipBounds();
+
+ // Paint tabRuns of tabs from back to front
+ for (int i = runCount - 1; i >= 0; i--) {
+ int start = tabRuns[i];
+ int next = tabRuns[(i == runCount - 1)? 0 : i + 1];
+ int end = (next != 0? next - 1: tabCount - 1);
+ for (int j = start; j <= end; j++) {
+ if (j != selectedIndex && rects[j].intersects(clipRect)) {
+ paintTab(g, tabPlacement, rects, j, iconRect, textRect);
+ }
+ }
+ }
+
+ // Paint selected tab if its in the front run
+ // since it may overlap other tabs
+ if (selectedIndex >= 0 && rects[selectedIndex].intersects(clipRect)) {
+ paintTab(g, tabPlacement, rects, selectedIndex, iconRect, textRect);
+ }
+ }
+
+ /**
+ * Paints a tab.
+ * @param g the graphics
+ * @param tabPlacement the tab placement
+ * @param rects rectangles
+ * @param tabIndex the tab index
+ * @param iconRect the icon rectangle
+ * @param textRect the text rectangle
+ */
+ protected void paintTab(Graphics g, int tabPlacement,
+ Rectangle[] rects, int tabIndex,
+ Rectangle iconRect, Rectangle textRect) {
+ Rectangle tabRect = rects[tabIndex];
+ int selectedIndex = tabPane.getSelectedIndex();
+ boolean isSelected = selectedIndex == tabIndex;
+
+ if (tabsOpaque || tabPane.isOpaque()) {
+ paintTabBackground(g, tabPlacement, tabIndex, tabRect.x, tabRect.y,
+ tabRect.width, tabRect.height, isSelected);
+ }
+
+ paintTabBorder(g, tabPlacement, tabIndex, tabRect.x, tabRect.y,
+ tabRect.width, tabRect.height, isSelected);
+
+ String title = tabPane.getTitleAt(tabIndex);
+ Font font = tabPane.getFont();
+ FontMetrics metrics = SwingUtilities2.getFontMetrics(tabPane, g, font);
+ Icon icon = getIconForTab(tabIndex);
+
+ layoutLabel(tabPlacement, metrics, tabIndex, title, icon,
+ tabRect, iconRect, textRect, isSelected);
+
+ if (tabPane.getTabComponentAt(tabIndex) == null) {
+ String clippedTitle = title;
+
+ if (scrollableTabLayoutEnabled() && tabScroller.croppedEdge.isParamsSet() &&
+ tabScroller.croppedEdge.getTabIndex() == tabIndex && isHorizontalTabPlacement()) {
+ int availTextWidth = tabScroller.croppedEdge.getCropline() -
+ (textRect.x - tabRect.x) - tabScroller.croppedEdge.getCroppedSideWidth();
+ clippedTitle = SwingUtilities2.clipStringIfNecessary(null, metrics, title, availTextWidth);
+ } else if (!scrollableTabLayoutEnabled() && isHorizontalTabPlacement()) {
+ clippedTitle = SwingUtilities2.clipStringIfNecessary(null, metrics, title, textRect.width);
+ }
+
+ paintText(g, tabPlacement, font, metrics,
+ tabIndex, clippedTitle, textRect, isSelected);
+
+ paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
+ }
+ paintFocusIndicator(g, tabPlacement, rects, tabIndex,
+ iconRect, textRect, isSelected);
+ }
+
+ private boolean isHorizontalTabPlacement() {
+ return tabPane.getTabPlacement() == TOP || tabPane.getTabPlacement() == BOTTOM;
+ }
+
+ /* This method will create and return a polygon shape for the given tab rectangle
+ * which has been cropped at the specified cropline with a torn edge visual.
+ * e.g. A "File" tab which has cropped been cropped just after the "i":
+ * -------------
+ * | ..... |
+ * | . |
+ * | ... . |
+ * | . . |
+ * | . . |
+ * | . . |
+ * --------------
+ *
+ * The x, y arrays below define the pattern used to create a "torn" edge
+ * segment which is repeated to fill the edge of the tab.
+ * For tabs placed on TOP and BOTTOM, this righthand torn edge is created by
+ * line segments which are defined by coordinates obtained by
+ * subtracting xCropLen[i] from (tab.x + tab.width) and adding yCroplen[i]
+ * to (tab.y).
+ * For tabs placed on LEFT or RIGHT, the bottom torn edge is created by
+ * subtracting xCropLen[i] from (tab.y + tab.height) and adding yCropLen[i]
+ * to (tab.x).
+ */
+ private static int xCropLen[] = {1,1,0,0,1,1,2,2};
+ private static int yCropLen[] = {0,3,3,6,6,9,9,12};
+ private static final int CROP_SEGMENT = 12;
+
+ private static Polygon createCroppedTabShape(int tabPlacement, Rectangle tabRect, int cropline) {
+ int rlen;
+ int start;
+ int end;
+ int ostart;
+
+ switch(tabPlacement) {
+ case LEFT:
+ case RIGHT:
+ rlen = tabRect.width;
+ start = tabRect.x;
+ end = tabRect.x + tabRect.width;
+ ostart = tabRect.y + tabRect.height;
+ break;
+ case TOP:
+ case BOTTOM:
+ default:
+ rlen = tabRect.height;
+ start = tabRect.y;
+ end = tabRect.y + tabRect.height;
+ ostart = tabRect.x + tabRect.width;
+ }
+ int rcnt = rlen/CROP_SEGMENT;
+ if (rlen%CROP_SEGMENT > 0) {
+ rcnt++;
+ }
+ int npts = 2 + (rcnt*8);
+ int xp[] = new int[npts];
+ int yp[] = new int[npts];
+ int pcnt = 0;
+
+ xp[pcnt] = ostart;
+ yp[pcnt++] = end;
+ xp[pcnt] = ostart;
+ yp[pcnt++] = start;
+ for(int i = 0; i < rcnt; i++) {
+ for(int j = 0; j < xCropLen.length; j++) {
+ xp[pcnt] = cropline - xCropLen[j];
+ yp[pcnt] = start + (i*CROP_SEGMENT) + yCropLen[j];
+ if (yp[pcnt] >= end) {
+ yp[pcnt] = end;
+ pcnt++;
+ break;
+ }
+ pcnt++;
+ }
+ }
+ if (tabPlacement == JTabbedPane.TOP || tabPlacement == JTabbedPane.BOTTOM) {
+ return new Polygon(xp, yp, pcnt);
+
+ } else { // LEFT or RIGHT
+ return new Polygon(yp, xp, pcnt);
+ }
+ }
+
+ /* If tabLayoutPolicy == SCROLL_TAB_LAYOUT, this method will paint an edge
+ * indicating the tab is cropped in the viewport display
+ */
+ private void paintCroppedTabEdge(Graphics g) {
+ int tabIndex = tabScroller.croppedEdge.getTabIndex();
+ int cropline = tabScroller.croppedEdge.getCropline();
+ int x,y;
+ switch(tabPane.getTabPlacement()) {
+ case LEFT:
+ case RIGHT:
+ x = rects[tabIndex].x;
+ y = cropline;
+ int xx = x;
+ g.setColor(shadow);
+ while(xx <= x+rects[tabIndex].width) {
+ for (int i=0; i < xCropLen.length; i+=2) {
+ g.drawLine(xx+yCropLen[i],y-xCropLen[i],
+ xx+yCropLen[i+1]-1,y-xCropLen[i+1]);
+ }
+ xx+=CROP_SEGMENT;
+ }
+ break;
+ case TOP:
+ case BOTTOM:
+ default:
+ x = cropline;
+ y = rects[tabIndex].y;
+ int yy = y;
+ g.setColor(shadow);
+ while(yy <= y+rects[tabIndex].height) {
+ for (int i=0; i < xCropLen.length; i+=2) {
+ g.drawLine(x-xCropLen[i],yy+yCropLen[i],
+ x-xCropLen[i+1],yy+yCropLen[i+1]-1);
+ }
+ yy+=CROP_SEGMENT;
+ }
+ }
+ }
+
+ /**
+ * Laysout a label.
+ * @param tabPlacement the tab placement
+ * @param metrics the font metric
+ * @param tabIndex the tab index
+ * @param title the title
+ * @param icon the icon
+ * @param tabRect the tab rectangle
+ * @param iconRect the icon rectangle
+ * @param textRect the text rectangle
+ * @param isSelected selection status
+ */
+ protected void layoutLabel(int tabPlacement,
+ FontMetrics metrics, int tabIndex,
+ String title, Icon icon,
+ Rectangle tabRect, Rectangle iconRect,
+ Rectangle textRect, boolean isSelected ) {
+ textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
+
+ View v = getTextViewForTab(tabIndex);
+ if (v != null) {
+ tabPane.putClientProperty("html", v);
+ }
+
+ SwingUtilities.layoutCompoundLabel(tabPane,
+ metrics, title, icon,
+ SwingUtilities.CENTER,
+ SwingUtilities.CENTER,
+ SwingUtilities.CENTER,
+ SwingUtilities.TRAILING,
+ tabRect,
+ iconRect,
+ textRect,
+ textIconGap);
+
+ tabPane.putClientProperty("html", null);
+
+ int xNudge = getTabLabelShiftX(tabPlacement, tabIndex, isSelected);
+ int yNudge = getTabLabelShiftY(tabPlacement, tabIndex, isSelected);
+ iconRect.x += xNudge;
+ iconRect.y += yNudge;
+ textRect.x += xNudge;
+ textRect.y += yNudge;
+ }
+
+ /**
+ * Paints an icon.
+ * @param g the graphics
+ * @param tabPlacement the tab placement
+ * @param tabIndex the tab index
+ * @param icon the icon
+ * @param iconRect the icon rectangle
+ * @param isSelected selection status
+ */
+ protected void paintIcon(Graphics g, int tabPlacement,
+ int tabIndex, Icon icon, Rectangle iconRect,
+ boolean isSelected ) {
+ if (icon != null) {
+ // Clip the icon within iconRect bounds
+ Shape oldClip = g.getClip();
+ ((Graphics2D)g).clip(iconRect);
+ icon.paintIcon(tabPane, g, iconRect.x, iconRect.y);
+ g.setClip(oldClip);
+ }
+ }
+
+ /**
+ * Paints text.
+ * @param g the graphics
+ * @param tabPlacement the tab placement
+ * @param font the font
+ * @param metrics the font metrics
+ * @param tabIndex the tab index
+ * @param title the title
+ * @param textRect the text rectangle
+ * @param isSelected selection status
+ */
+ protected void paintText(Graphics g, int tabPlacement,
+ Font font, FontMetrics metrics, int tabIndex,
+ String title, Rectangle textRect,
+ boolean isSelected) {
+
+ g.setFont(font);
+
+ View v = getTextViewForTab(tabIndex);
+ if (v != null) {
+ // html
+ v.paint(g, textRect);
+ } else {
+ // plain text
+ int mnemIndex = tabPane.getDisplayedMnemonicIndexAt(tabIndex);
+
+ if (tabPane.isEnabled() && tabPane.isEnabledAt(tabIndex)) {
+ Color fg = tabPane.getForegroundAt(tabIndex);
+ if (isSelected && (fg instanceof UIResource)) {
+ Color selectedFG = UIManager.getColor(
+ "TabbedPane.selectedForeground");
+ if (selectedFG != null) {
+ fg = selectedFG;
+ }
+ }
+ g.setColor(fg);
+ SwingUtilities2.drawStringUnderlineCharAt(tabPane, g,
+ title, mnemIndex,
+ textRect.x, textRect.y + metrics.getAscent());
+
+ } else { // tab disabled
+ g.setColor(tabPane.getBackgroundAt(tabIndex).brighter());
+ SwingUtilities2.drawStringUnderlineCharAt(tabPane, g,
+ title, mnemIndex,
+ textRect.x, textRect.y + metrics.getAscent());
+ g.setColor(tabPane.getBackgroundAt(tabIndex).darker());
+ SwingUtilities2.drawStringUnderlineCharAt(tabPane, g,
+ title, mnemIndex,
+ textRect.x - 1, textRect.y + metrics.getAscent() - 1);
+
+ }
+ }
+ }
+
+ /**
+ * Returns the tab label shift x.
+ * @param tabPlacement the tab placement
+ * @param tabIndex the tab index
+ * @param isSelected selection status
+ * @return the tab label shift x
+ */
+ protected int getTabLabelShiftX(int tabPlacement, int tabIndex, boolean isSelected) {
+ Rectangle tabRect = rects[tabIndex];
+ String propKey = (isSelected ? "selectedLabelShift" : "labelShift");
+ int nudge = DefaultLookup.getInt(
+ tabPane, this, "TabbedPane." + propKey, 1);
+
+ switch (tabPlacement) {
+ case LEFT:
+ return nudge;
+ case RIGHT:
+ return -nudge;
+ case BOTTOM:
+ case TOP:
+ default:
+ return tabRect.width % 2;
+ }
+ }
+
+ /**
+ * Returns the tab label shift y.
+ * @param tabPlacement the tab placement
+ * @param tabIndex the tab index
+ * @param isSelected selection status
+ * @return the tab label shift y
+ */
+ protected int getTabLabelShiftY(int tabPlacement, int tabIndex, boolean isSelected) {
+ Rectangle tabRect = rects[tabIndex];
+ int nudge = (isSelected ? DefaultLookup.getInt(tabPane, this, "TabbedPane.selectedLabelShift", -1) :
+ DefaultLookup.getInt(tabPane, this, "TabbedPane.labelShift", 1));
+
+ switch (tabPlacement) {
+ case BOTTOM:
+ return -nudge;
+ case LEFT:
+ case RIGHT:
+ return tabRect.height % 2;
+ case TOP:
+ default:
+ return nudge;
+ }
+ }
+
+ /**
+ * Paints the focus indicator.
+ * @param g the graphics
+ * @param tabPlacement the tab placement
+ * @param rects rectangles
+ * @param tabIndex the tab index
+ * @param iconRect the icon rectangle
+ * @param textRect the text rectangle
+ * @param isSelected selection status
+ */
+ protected void paintFocusIndicator(Graphics g, int tabPlacement,
+ Rectangle[] rects, int tabIndex,
+ Rectangle iconRect, Rectangle textRect,
+ boolean isSelected) {
+ Rectangle tabRect = rects[tabIndex];
+ if (tabPane.hasFocus() && isSelected) {
+ int x, y, w, h;
+ g.setColor(focus);
+ switch(tabPlacement) {
+ case LEFT:
+ x = tabRect.x + 3;
+ y = tabRect.y + 3;
+ w = tabRect.width - 5;
+ h = tabRect.height - 6;
+ break;
+ case RIGHT:
+ x = tabRect.x + 2;
+ y = tabRect.y + 3;
+ w = tabRect.width - 5;
+ h = tabRect.height - 6;
+ break;
+ case BOTTOM:
+ x = tabRect.x + 3;
+ y = tabRect.y + 2;
+ w = tabRect.width - 6;
+ h = tabRect.height - 5;
+ break;
+ case TOP:
+ default:
+ x = tabRect.x + 3;
+ y = tabRect.y + 3;
+ w = tabRect.width - 6;
+ h = tabRect.height - 5;
+ }
+ BasicGraphicsUtils.drawDashedRect(g, x, y, w, h);
+ }
+ }
+
+ /**
+ * this function draws the border around each tab
+ * note that this function does now draw the background of the tab.
+ * that is done elsewhere
+ *
+ * @param g the graphics context in which to paint
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @param tabIndex the index of the tab with respect to other tabs
+ * @param x the x coordinate of tab
+ * @param y the y coordinate of tab
+ * @param w the width of the tab
+ * @param h the height of the tab
+ * @param isSelected a {@code boolean} which determines whether or not
+ * the tab is selected
+ */
+ protected void paintTabBorder(Graphics g, int tabPlacement,
+ int tabIndex,
+ int x, int y, int w, int h,
+ boolean isSelected ) {
+ g.setColor(lightHighlight);
+
+ switch (tabPlacement) {
+ case LEFT:
+ g.drawLine(x+1, y+h-2, x+1, y+h-2); // bottom-left highlight
+ g.drawLine(x, y+2, x, y+h-3); // left highlight
+ g.drawLine(x+1, y+1, x+1, y+1); // top-left highlight
+ g.drawLine(x+2, y, x+w-1, y); // top highlight
+
+ g.setColor(shadow);
+ g.drawLine(x+2, y+h-2, x+w-1, y+h-2); // bottom shadow
+
+ g.setColor(darkShadow);
+ g.drawLine(x+2, y+h-1, x+w-1, y+h-1); // bottom dark shadow
+ break;
+ case RIGHT:
+ g.drawLine(x, y, x+w-3, y); // top highlight
+
+ g.setColor(shadow);
+ g.drawLine(x, y+h-2, x+w-3, y+h-2); // bottom shadow
+ g.drawLine(x+w-2, y+2, x+w-2, y+h-3); // right shadow
+
+ g.setColor(darkShadow);
+ g.drawLine(x+w-2, y+1, x+w-2, y+1); // top-right dark shadow
+ g.drawLine(x+w-2, y+h-2, x+w-2, y+h-2); // bottom-right dark shadow
+ g.drawLine(x+w-1, y+2, x+w-1, y+h-3); // right dark shadow
+ g.drawLine(x, y+h-1, x+w-3, y+h-1); // bottom dark shadow
+ break;
+ case BOTTOM:
+ g.drawLine(x, y, x, y+h-3); // left highlight
+ g.drawLine(x+1, y+h-2, x+1, y+h-2); // bottom-left highlight
+
+ g.setColor(shadow);
+ g.drawLine(x+2, y+h-2, x+w-3, y+h-2); // bottom shadow
+ g.drawLine(x+w-2, y, x+w-2, y+h-3); // right shadow
+
+ g.setColor(darkShadow);
+ g.drawLine(x+2, y+h-1, x+w-3, y+h-1); // bottom dark shadow
+ g.drawLine(x+w-2, y+h-2, x+w-2, y+h-2); // bottom-right dark shadow
+ g.drawLine(x+w-1, y, x+w-1, y+h-3); // right dark shadow
+ break;
+ case TOP:
+ default:
+ g.drawLine(x, y+2, x, y+h-1); // left highlight
+ g.drawLine(x+1, y+1, x+1, y+1); // top-left highlight
+ g.drawLine(x+2, y, x+w-3, y); // top highlight
+
+ g.setColor(shadow);
+ g.drawLine(x+w-2, y+2, x+w-2, y+h-1); // right shadow
+
+ g.setColor(darkShadow);
+ g.drawLine(x+w-1, y+2, x+w-1, y+h-1); // right dark-shadow
+ g.drawLine(x+w-2, y+1, x+w-2, y+1); // top-right shadow
+ }
+ }
+
+ /**
+ * Paints the tab background.
+ * @param g the graphics context in which to paint
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @param tabIndex the index of the tab with respect to other tabs
+ * @param x the x coordinate of tab
+ * @param y the y coordinate of tab
+ * @param w the width of the tab
+ * @param h the height of the tab
+ * @param isSelected a {@code boolean} which determines whether or not
+ * the tab is selected
+ */
+ protected void paintTabBackground(Graphics g, int tabPlacement,
+ int tabIndex,
+ int x, int y, int w, int h,
+ boolean isSelected ) {
+ g.setColor(!isSelected || selectedColor == null?
+ tabPane.getBackgroundAt(tabIndex) : selectedColor);
+ switch(tabPlacement) {
+ case LEFT:
+ g.fillRect(x+1, y+1, w-1, h-3);
+ break;
+ case RIGHT:
+ g.fillRect(x, y+1, w-2, h-3);
+ break;
+ case BOTTOM:
+ g.fillRect(x+1, y, w-3, h-1);
+ break;
+ case TOP:
+ default:
+ g.fillRect(x+1, y+1, w-3, h-1);
+ }
+ }
+
+ /**
+ * Paints the content border.
+ * @param g the graphics context in which to paint
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @param selectedIndex the tab index of the selected component
+ */
+ protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) {
+ int width = tabPane.getWidth();
+ int height = tabPane.getHeight();
+ Insets insets = tabPane.getInsets();
+ Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
+
+ int x = insets.left;
+ int y = insets.top;
+ int w = width - insets.right - insets.left;
+ int h = height - insets.top - insets.bottom;
+
+ switch(tabPlacement) {
+ case LEFT:
+ x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
+ if (tabsOverlapBorder) {
+ x -= tabAreaInsets.right;
+ }
+ w -= (x - insets.left);
+ break;
+ case RIGHT:
+ w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
+ if (tabsOverlapBorder) {
+ w += tabAreaInsets.left;
+ }
+ break;
+ case BOTTOM:
+ h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
+ if (tabsOverlapBorder) {
+ h += tabAreaInsets.top;
+ }
+ break;
+ case TOP:
+ default:
+ y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
+ if (tabsOverlapBorder) {
+ y -= tabAreaInsets.bottom;
+ }
+ h -= (y - insets.top);
+ }
+
+ if ( tabPane.getTabCount() > 0 && (contentOpaque || tabPane.isOpaque()) ) {
+ // Fill region behind content area
+ Color color = UIManager.getColor("TabbedPane.contentAreaColor");
+ if (color != null) {
+ g.setColor(color);
+ }
+ else if ( selectedColor == null || selectedIndex == -1 ) {
+ g.setColor(tabPane.getBackground());
+ }
+ else {
+ g.setColor(selectedColor);
+ }
+ g.fillRect(x,y,w,h);
+ }
+
+ paintContentBorderTopEdge(g, tabPlacement, selectedIndex, x, y, w, h);
+ paintContentBorderLeftEdge(g, tabPlacement, selectedIndex, x, y, w, h);
+ paintContentBorderBottomEdge(g, tabPlacement, selectedIndex, x, y, w, h);
+ paintContentBorderRightEdge(g, tabPlacement, selectedIndex, x, y, w, h);
+
+ }
+
+ /**
+ * Paints the content border top edge.
+ * @param g the graphics context in which to paint
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @param selectedIndex the tab index of the selected component
+ * @param x the x coordinate of tab
+ * @param y the y coordinate of tab
+ * @param w the width of the tab
+ * @param h the height of the tab
+ */
+ protected void paintContentBorderTopEdge(Graphics g, int tabPlacement,
+ int selectedIndex,
+ int x, int y, int w, int h) {
+ Rectangle selRect = selectedIndex < 0? null :
+ getTabBounds(selectedIndex, calcRect);
+
+ g.setColor(lightHighlight);
+
+ // Draw unbroken line if tabs are not on TOP, OR
+ // selected tab is not in run adjacent to content, OR
+ // selected tab is not visible (SCROLL_TAB_LAYOUT)
+ //
+ if (tabPlacement != TOP || selectedIndex < 0 ||
+ (selRect.y + selRect.height + 1 < y) ||
+ (selRect.x < x || selRect.x > x + w)) {
+ g.drawLine(x, y, x+w-2, y);
+ } else {
+ // Break line to show visual connection to selected tab
+ g.drawLine(x, y, selRect.x - 1, y);
+ if (selRect.x + selRect.width < x + w - 2) {
+ g.drawLine(selRect.x + selRect.width, y,
+ x+w-2, y);
+ } else {
+ g.setColor(shadow);
+ g.drawLine(x+w-2, y, x+w-2, y);
+ }
+ }
+ }
+
+ /**
+ * Paints the content border left edge.
+ * @param g the graphics context in which to paint
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @param selectedIndex the tab index of the selected component
+ * @param x the x coordinate of tab
+ * @param y the y coordinate of tab
+ * @param w the width of the tab
+ * @param h the height of the tab
+ */
+ protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement,
+ int selectedIndex,
+ int x, int y, int w, int h) {
+ Rectangle selRect = selectedIndex < 0? null :
+ getTabBounds(selectedIndex, calcRect);
+
+ g.setColor(lightHighlight);
+
+ // Draw unbroken line if tabs are not on LEFT, OR
+ // selected tab is not in run adjacent to content, OR
+ // selected tab is not visible (SCROLL_TAB_LAYOUT)
+ //
+ if (tabPlacement != LEFT || selectedIndex < 0 ||
+ (selRect.x + selRect.width + 1 < x) ||
+ (selRect.y < y || selRect.y > y + h)) {
+ g.drawLine(x, y, x, y+h-2);
+ } else {
+ // Break line to show visual connection to selected tab
+ g.drawLine(x, y, x, selRect.y - 1);
+ if (selRect.y + selRect.height < y + h - 2) {
+ g.drawLine(x, selRect.y + selRect.height,
+ x, y+h-2);
+ }
+ }
+ }
+
+ /**
+ * Paints the content border bottom edge.
+ * @param g the graphics context in which to paint
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @param selectedIndex the tab index of the selected component
+ * @param x the x coordinate of tab
+ * @param y the y coordinate of tab
+ * @param w the width of the tab
+ * @param h the height of the tab
+ */
+ protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement,
+ int selectedIndex,
+ int x, int y, int w, int h) {
+ Rectangle selRect = selectedIndex < 0? null :
+ getTabBounds(selectedIndex, calcRect);
+
+ g.setColor(shadow);
+
+ // Draw unbroken line if tabs are not on BOTTOM, OR
+ // selected tab is not in run adjacent to content, OR
+ // selected tab is not visible (SCROLL_TAB_LAYOUT)
+ //
+ if (tabPlacement != BOTTOM || selectedIndex < 0 ||
+ (selRect.y - 1 > h) ||
+ (selRect.x < x || selRect.x > x + w)) {
+ g.drawLine(x+1, y+h-2, x+w-2, y+h-2);
+ g.setColor(darkShadow);
+ g.drawLine(x, y+h-1, x+w-1, y+h-1);
+ } else {
+ // Break line to show visual connection to selected tab
+ g.drawLine(x+1, y+h-2, selRect.x - 1, y+h-2);
+ g.setColor(darkShadow);
+ g.drawLine(x, y+h-1, selRect.x - 1, y+h-1);
+ if (selRect.x + selRect.width < x + w - 2) {
+ g.setColor(shadow);
+ g.drawLine(selRect.x + selRect.width, y+h-2, x+w-2, y+h-2);
+ g.setColor(darkShadow);
+ g.drawLine(selRect.x + selRect.width, y+h-1, x+w-1, y+h-1);
+ }
+ }
+
+ }
+
+ /**
+ * Paints the content border right edge.
+ * @param g the graphics context in which to paint
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @param selectedIndex the tab index of the selected component
+ * @param x the x coordinate of tab
+ * @param y the y coordinate of tab
+ * @param w the width of the tab
+ * @param h the height of the tab
+ */
+ protected void paintContentBorderRightEdge(Graphics g, int tabPlacement,
+ int selectedIndex,
+ int x, int y, int w, int h) {
+ Rectangle selRect = selectedIndex < 0? null :
+ getTabBounds(selectedIndex, calcRect);
+
+ g.setColor(shadow);
+
+ // Draw unbroken line if tabs are not on RIGHT, OR
+ // selected tab is not in run adjacent to content, OR
+ // selected tab is not visible (SCROLL_TAB_LAYOUT)
+ //
+ if (tabPlacement != RIGHT || selectedIndex < 0 ||
+ (selRect.x - 1 > w) ||
+ (selRect.y < y || selRect.y > y + h)) {
+ g.drawLine(x+w-2, y+1, x+w-2, y+h-3);
+ g.setColor(darkShadow);
+ g.drawLine(x+w-1, y, x+w-1, y+h-1);
+ } else {
+ // Break line to show visual connection to selected tab
+ g.drawLine(x+w-2, y+1, x+w-2, selRect.y - 1);
+ g.setColor(darkShadow);
+ g.drawLine(x+w-1, y, x+w-1, selRect.y - 1);
+
+ if (selRect.y + selRect.height < y + h - 2) {
+ g.setColor(shadow);
+ g.drawLine(x+w-2, selRect.y + selRect.height,
+ x+w-2, y+h-2);
+ g.setColor(darkShadow);
+ g.drawLine(x+w-1, selRect.y + selRect.height,
+ x+w-1, y+h-2);
+ }
+ }
+ }
+
+ private void ensureCurrentLayout() {
+ if (!tabPane.isValid()) {
+ tabPane.validate();
+ }
+ /* If tabPane doesn't have a peer yet, the validate() call will
+ * silently fail. We handle that by forcing a layout if tabPane
+ * is still invalid. See bug 4237677.
+ */
+ if (!tabPane.isValid()) {
+ TabbedPaneLayout layout = (TabbedPaneLayout)tabPane.getLayout();
+ layout.calculateLayoutInfo();
+ }
+ }
+
+
+// TabbedPaneUI methods
+
+ /**
+ * Returns the bounds of the specified tab index. The bounds are
+ * with respect to the JTabbedPane's coordinate space.
+ */
+ public Rectangle getTabBounds(JTabbedPane pane, int i) {
+ ensureCurrentLayout();
+ Rectangle tabRect = new Rectangle();
+ return getTabBounds(i, tabRect);
+ }
+
+ public int getTabRunCount(JTabbedPane pane) {
+ ensureCurrentLayout();
+ return runCount;
+ }
+
+ /**
+ * Returns the tab index which intersects the specified point
+ * in the JTabbedPane's coordinate space.
+ */
+ public int tabForCoordinate(JTabbedPane pane, int x, int y) {
+ return tabForCoordinate(pane, x, y, true);
+ }
+
+ private int tabForCoordinate(JTabbedPane pane, int x, int y,
+ boolean validateIfNecessary) {
+ if (validateIfNecessary) {
+ ensureCurrentLayout();
+ }
+ if (isRunsDirty) {
+ // We didn't recalculate the layout, runs and tabCount may not
+ // line up, bail.
+ return -1;
+ }
+ Point p = new Point(x, y);
+
+ if (scrollableTabLayoutEnabled()) {
+ translatePointToTabPanel(x, y, p);
+ Rectangle viewRect = tabScroller.viewport.getViewRect();
+ if (!viewRect.contains(p)) {
+ return -1;
+ }
+ }
+ int tabCount = tabPane.getTabCount();
+ for (int i = 0; i < tabCount; i++) {
+ if (rects[i].contains(p.x, p.y)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the bounds of the specified tab in the coordinate space
+ * of the JTabbedPane component. This is required because the tab rects
+ * are by default defined in the coordinate space of the component where
+ * they are rendered, which could be the JTabbedPane
+ * (for WRAP_TAB_LAYOUT) or a ScrollableTabPanel (SCROLL_TAB_LAYOUT).
+ * This method should be used whenever the tab rectangle must be relative
+ * to the JTabbedPane itself and the result should be placed in a
+ * designated Rectangle object (rather than instantiating and returning
+ * a new Rectangle each time). The tab index parameter must be a valid
+ * tabbed pane tab index (0 to tab count - 1, inclusive). The destination
+ * rectangle parameter must be a valid <code>Rectangle</code> instance.
+ * The handling of invalid parameters is unspecified.
+ *
+ * @param tabIndex the index of the tab
+ * @param dest the rectangle where the result should be placed
+ * @return the resulting rectangle
+ *
+ * @since 1.4
+ */
+ protected Rectangle getTabBounds(int tabIndex, Rectangle dest) {
+ dest.width = rects[tabIndex].width;
+ dest.height = rects[tabIndex].height;
+
+ if (scrollableTabLayoutEnabled()) { // SCROLL_TAB_LAYOUT
+ // Need to translate coordinates based on viewport location &
+ // view position
+ Point vpp = tabScroller.viewport.getLocation();
+ Point viewp = tabScroller.viewport.getViewPosition();
+ dest.x = rects[tabIndex].x + vpp.x - viewp.x;
+ dest.y = rects[tabIndex].y + vpp.y - viewp.y;
+
+ } else { // WRAP_TAB_LAYOUT
+ dest.x = rects[tabIndex].x;
+ dest.y = rects[tabIndex].y;
+ }
+ return dest;
+ }
+
+ /**
+ * Returns the index of the tab closest to the passed in location, note
+ * that the returned tab may not contain the location x,y.
+ */
+ private int getClosestTab(int x, int y) {
+ int min = 0;
+ int tabCount = Math.min(rects.length, tabPane.getTabCount());
+ int max = tabCount;
+ int tabPlacement = tabPane.getTabPlacement();
+ boolean useX = (tabPlacement == TOP || tabPlacement == BOTTOM);
+ int want = (useX) ? x : y;
+
+ while (min != max) {
+ int current = (max + min) / 2;
+ int minLoc;
+ int maxLoc;
+
+ if (useX) {
+ minLoc = rects[current].x;
+ maxLoc = minLoc + rects[current].width;
+ }
+ else {
+ minLoc = rects[current].y;
+ maxLoc = minLoc + rects[current].height;
+ }
+ if (want < minLoc) {
+ max = current;
+ if (min == max) {
+ return Math.max(0, current - 1);
+ }
+ }
+ else if (want >= maxLoc) {
+ min = current;
+ if (max - min <= 1) {
+ return Math.max(current + 1, tabCount - 1);
+ }
+ }
+ else {
+ return current;
+ }
+ }
+ return min;
+ }
+
+ /**
+ * Returns a point which is translated from the specified point in the
+ * JTabbedPane's coordinate space to the coordinate space of the
+ * ScrollableTabPanel. This is used for SCROLL_TAB_LAYOUT ONLY.
+ */
+ private Point translatePointToTabPanel(int srcx, int srcy, Point dest) {
+ Point vpp = tabScroller.viewport.getLocation();
+ Point viewp = tabScroller.viewport.getViewPosition();
+ dest.x = srcx - vpp.x + viewp.x;
+ dest.y = srcy - vpp.y + viewp.y;
+ return dest;
+ }
+
+// BasicTabbedPaneUI methods
+
+ /**
+ * Returns the visible component.
+ * @return the visible component
+ */
+ protected Component getVisibleComponent() {
+ return visibleComponent;
+ }
+
+ /**
+ * Sets the visible component.
+ * @param component the component
+ */
+ protected void setVisibleComponent(Component component) {
+ if (visibleComponent != null
+ && visibleComponent != component
+ && visibleComponent.getParent() == tabPane
+ && visibleComponent.isVisible()) {
+
+ visibleComponent.setVisible(false);
+ }
+ if (component != null && !component.isVisible()) {
+ component.setVisible(true);
+ }
+ visibleComponent = component;
+ }
+
+ /**
+ * Assure the rectangles are created.
+ * @param tabCount the tab count
+ */
+ protected void assureRectsCreated(int tabCount) {
+ int rectArrayLen = rects.length;
+ if (tabCount != rectArrayLen ) {
+ Rectangle[] tempRectArray = new Rectangle[tabCount];
+ System.arraycopy(rects, 0, tempRectArray, 0,
+ Math.min(rectArrayLen, tabCount));
+ rects = tempRectArray;
+ for (int rectIndex = rectArrayLen; rectIndex < tabCount; rectIndex++) {
+ rects[rectIndex] = new Rectangle();
+ }
+ }
+
+ }
+
+ /**
+ * Expands the tab runs array.
+ */
+ protected void expandTabRunsArray() {
+ int rectLen = tabRuns.length;
+ int[] newArray = new int[rectLen+10];
+ System.arraycopy(tabRuns, 0, newArray, 0, runCount);
+ tabRuns = newArray;
+ }
+
+ /**
+ * Returns the run for a tab.
+ * @param tabCount the tab count
+ * @param tabIndex the tab index.
+ * @return the run for a tab
+ */
+ protected int getRunForTab(int tabCount, int tabIndex) {
+ for (int i = 0; i < runCount; i++) {
+ int first = tabRuns[i];
+ int last = lastTabInRun(tabCount, i);
+ if (tabIndex >= first && tabIndex <= last) {
+ return i;
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the last tab in a run.
+ * @param tabCount the tab count
+ * @param run the run
+ * @return the last tab in a run
+ */
+ protected int lastTabInRun(int tabCount, int run) {
+ if (runCount == 1) {
+ return tabCount - 1;
+ }
+ int nextRun = (run == runCount - 1? 0 : run + 1);
+ if (tabRuns[nextRun] == 0) {
+ return tabCount - 1;
+ }
+ return tabRuns[nextRun]-1;
+ }
+
+ /**
+ * Returns the tab run overlay.
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @return the tab run overlay
+ */
+ protected int getTabRunOverlay(int tabPlacement) {
+ return tabRunOverlay;
+ }
+
+ /**
+ * Returns the tab run indent.
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @param run the tab run
+ * @return the tab run indent
+ */
+ protected int getTabRunIndent(int tabPlacement, int run) {
+ return 0;
+ }
+
+ /**
+ * Returns whether or not the tab run should be padded.
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @param run the tab run
+ * @return whether or not the tab run should be padded
+ */
+ protected boolean shouldPadTabRun(int tabPlacement, int run) {
+ return runCount > 1;
+ }
+
+ /**
+ * Returns whether or not the tab run should be rotated.
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @return whether or not the tab run should be rotated
+ */
+ protected boolean shouldRotateTabRuns(int tabPlacement) {
+ return true;
+ }
+
+ /**
+ * Returns the icon for a tab.
+ * @param tabIndex the index of the tab
+ * @return the icon for a tab
+ */
+ protected Icon getIconForTab(int tabIndex) {
+ return (!tabPane.isEnabled() || !tabPane.isEnabledAt(tabIndex))?
+ tabPane.getDisabledIconAt(tabIndex) : tabPane.getIconAt(tabIndex);
+ }
+
+ /**
+ * Returns the text View object required to render stylized text (HTML) for
+ * the specified tab or null if no specialized text rendering is needed
+ * for this tab. This is provided to support html rendering inside tabs.
+ *
+ * @param tabIndex the index of the tab
+ * @return the text view to render the tab's text or null if no
+ * specialized rendering is required
+ *
+ * @since 1.4
+ */
+ protected View getTextViewForTab(int tabIndex) {
+ if (htmlViews != null) {
+ return htmlViews.elementAt(tabIndex);
+ }
+ return null;
+ }
+
+ /**
+ * Calculates the tab height.
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @param tabIndex the index of the tab with respect to other tabs
+ * @param fontHeight the font height
+ * @return the tab height
+ */
+ protected int calculateTabHeight(int tabPlacement, int tabIndex, int fontHeight) {
+ int height = 0;
+ Component c = tabPane.getTabComponentAt(tabIndex);
+ if (c != null) {
+ height = c.getPreferredSize().height;
+ } else {
+ View v = getTextViewForTab(tabIndex);
+ if (v != null) {
+ // html
+ height += (int) v.getPreferredSpan(View.Y_AXIS);
+ } else {
+ // plain text
+ height += fontHeight;
+ }
+ Icon icon = getIconForTab(tabIndex);
+
+ if (icon != null) {
+ height = Math.max(height, icon.getIconHeight());
+ }
+ }
+ Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
+ height += tabInsets.top + tabInsets.bottom + 2;
+ return height;
+ }
+
+ /**
+ * Calculates the maximum tab height.
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @return the maximum tab height
+ */
+ protected int calculateMaxTabHeight(int tabPlacement) {
+ FontMetrics metrics = getFontMetrics();
+ int tabCount = tabPane.getTabCount();
+ int result = 0;
+ int fontHeight = metrics.getHeight();
+ for(int i = 0; i < tabCount; i++) {
+ result = Math.max(calculateTabHeight(tabPlacement, i, fontHeight), result);
+ }
+ return result;
+ }
+
+ /**
+ * Calculates the tab width.
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @param tabIndex the index of the tab with respect to other tabs
+ * @param metrics the font metrics
+ * @return the tab width
+ */
+ protected int calculateTabWidth(int tabPlacement, int tabIndex, FontMetrics metrics) {
+ Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
+ int width = tabInsets.left + tabInsets.right + 3;
+ Component tabComponent = tabPane.getTabComponentAt(tabIndex);
+ if (tabComponent != null) {
+ width += tabComponent.getPreferredSize().width;
+ } else {
+ Icon icon = getIconForTab(tabIndex);
+ if (icon != null) {
+ width += icon.getIconWidth() + textIconGap;
+ }
+ View v = getTextViewForTab(tabIndex);
+ if (v != null) {
+ // html
+ width += (int) v.getPreferredSpan(View.X_AXIS);
+ } else {
+ // plain text
+ String title = tabPane.getTitleAt(tabIndex);
+ width += SwingUtilities2.stringWidth(tabPane, metrics, title);
+ }
+ }
+ return width;
+ }
+
+ /**
+ * Calculates the maximum tab width.
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @return the maximum tab width
+ */
+ protected int calculateMaxTabWidth(int tabPlacement) {
+ FontMetrics metrics = getFontMetrics();
+ int tabCount = tabPane.getTabCount();
+ int result = 0;
+ for(int i = 0; i < tabCount; i++) {
+ result = Math.max(calculateTabWidth(tabPlacement, i, metrics), result);
+ }
+ return result;
+ }
+
+ /**
+ * Calculates the tab area height.
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @param horizRunCount horizontal run count
+ * @param maxTabHeight maximum tab height
+ * @return the tab area height
+ */
+ protected int calculateTabAreaHeight(int tabPlacement, int horizRunCount, int maxTabHeight) {
+ Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
+ int tabRunOverlay = getTabRunOverlay(tabPlacement);
+ return (horizRunCount > 0?
+ horizRunCount * (maxTabHeight-tabRunOverlay) + tabRunOverlay +
+ tabAreaInsets.top + tabAreaInsets.bottom :
+ 0);
+ }
+
+ /**
+ * Calculates the tab area width.
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @param vertRunCount vertical run count
+ * @param maxTabWidth maximum tab width
+ * @return the tab area width
+ */
+ protected int calculateTabAreaWidth(int tabPlacement, int vertRunCount, int maxTabWidth) {
+ Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
+ int tabRunOverlay = getTabRunOverlay(tabPlacement);
+ return (vertRunCount > 0?
+ vertRunCount * (maxTabWidth-tabRunOverlay) + tabRunOverlay +
+ tabAreaInsets.left + tabAreaInsets.right :
+ 0);
+ }
+
+ /**
+ * Returns the tab insets.
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @param tabIndex the tab index
+ * @return the tab insets
+ */
+ protected Insets getTabInsets(int tabPlacement, int tabIndex) {
+ return tabInsets;
+ }
+
+ /**
+ * Returns the selected tab pad insets.
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @return the selected tab pad insets
+ */
+ protected Insets getSelectedTabPadInsets(int tabPlacement) {
+ rotateInsets(selectedTabPadInsets, currentPadInsets, tabPlacement);
+ return currentPadInsets;
+ }
+
+ /**
+ * Returns the tab area insets.
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @return the pad area insets
+ */
+ protected Insets getTabAreaInsets(int tabPlacement) {
+ rotateInsets(tabAreaInsets, currentTabAreaInsets, tabPlacement);
+ return currentTabAreaInsets;
+ }
+
+ /**
+ * Returns the content border insets.
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @return the content border insets
+ */
+ protected Insets getContentBorderInsets(int tabPlacement) {
+ return contentBorderInsets;
+ }
+
+ /**
+ * Returns the font metrics.
+ * @return the font metrics
+ */
+ protected FontMetrics getFontMetrics() {
+ Font font = tabPane.getFont();
+ return tabPane.getFontMetrics(font);
+ }
+
+
+// Tab Navigation methods
+
+ /**
+ * Navigate the selected tab.
+ * @param direction the direction
+ */
+ protected void navigateSelectedTab(int direction) {
+ int tabPlacement = tabPane.getTabPlacement();
+ int current = DefaultLookup.getBoolean(tabPane, this,
+ "TabbedPane.selectionFollowsFocus", true) ?
+ tabPane.getSelectedIndex() : getFocusIndex();
+ int tabCount = tabPane.getTabCount();
+ boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane);
+
+ // If we have no tabs then don't navigate.
+ if (tabCount <= 0) {
+ return;
+ }
+
+ int offset;
+ switch(tabPlacement) {
+ case LEFT:
+ case RIGHT:
+ switch(direction) {
+ case NEXT:
+ selectNextTab(current);
+ break;
+ case PREVIOUS:
+ selectPreviousTab(current);
+ break;
+ case NORTH:
+ selectPreviousTabInRun(current);
+ break;
+ case SOUTH:
+ selectNextTabInRun(current);
+ break;
+ case WEST:
+ offset = getTabRunOffset(tabPlacement, tabCount, current, false);
+ selectAdjacentRunTab(tabPlacement, current, offset);
+ break;
+ case EAST:
+ offset = getTabRunOffset(tabPlacement, tabCount, current, true);
+ selectAdjacentRunTab(tabPlacement, current, offset);
+ break;
+ default:
+ }
+ break;
+ case BOTTOM:
+ case TOP:
+ default:
+ switch(direction) {
+ case NEXT:
+ selectNextTab(current);
+ break;
+ case PREVIOUS:
+ selectPreviousTab(current);
+ break;
+ case NORTH:
+ offset = getTabRunOffset(tabPlacement, tabCount, current, false);
+ selectAdjacentRunTab(tabPlacement, current, offset);
+ break;
+ case SOUTH:
+ offset = getTabRunOffset(tabPlacement, tabCount, current, true);
+ selectAdjacentRunTab(tabPlacement, current, offset);
+ break;
+ case EAST:
+ if (leftToRight) {
+ selectNextTabInRun(current);
+ } else {
+ selectPreviousTabInRun(current);
+ }
+ break;
+ case WEST:
+ if (leftToRight) {
+ selectPreviousTabInRun(current);
+ } else {
+ selectNextTabInRun(current);
+ }
+ break;
+ default:
+ }
+ }
+ }
+
+ /**
+ * Select the next tab in the run.
+ * @param current the current tab
+ */
+ protected void selectNextTabInRun(int current) {
+ int tabCount = tabPane.getTabCount();
+ int tabIndex = getNextTabIndexInRun(tabCount, current);
+
+ while(tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
+ tabIndex = getNextTabIndexInRun(tabCount, tabIndex);
+ }
+ navigateTo(tabIndex);
+ }
+
+ /**
+ * Select the previous tab in the run.
+ * @param current the current tab
+ */
+ protected void selectPreviousTabInRun(int current) {
+ int tabCount = tabPane.getTabCount();
+ int tabIndex = getPreviousTabIndexInRun(tabCount, current);
+
+ while(tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
+ tabIndex = getPreviousTabIndexInRun(tabCount, tabIndex);
+ }
+ navigateTo(tabIndex);
+ }
+
+ /**
+ * Select the next tab.
+ * @param current the current tab
+ */
+ protected void selectNextTab(int current) {
+ int tabIndex = getNextTabIndex(current);
+
+ while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
+ tabIndex = getNextTabIndex(tabIndex);
+ }
+ navigateTo(tabIndex);
+ }
+
+ /**
+ * Select the previous tab.
+ * @param current the current tab
+ */
+ protected void selectPreviousTab(int current) {
+ int tabIndex = getPreviousTabIndex(current);
+
+ while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
+ tabIndex = getPreviousTabIndex(tabIndex);
+ }
+ navigateTo(tabIndex);
+ }
+
+ /**
+ * Selects an adjacent run of tabs.
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @param tabIndex the index of the tab with respect to other tabs
+ * @param offset selection offset
+ */
+ protected void selectAdjacentRunTab(int tabPlacement,
+ int tabIndex, int offset) {
+ if ( runCount < 2 ) {
+ return;
+ }
+ int newIndex;
+ Rectangle r = rects[tabIndex];
+ switch(tabPlacement) {
+ case LEFT:
+ case RIGHT:
+ newIndex = tabForCoordinate(tabPane, r.x + r.width/2 + offset,
+ r.y + r.height/2);
+ break;
+ case BOTTOM:
+ case TOP:
+ default:
+ newIndex = tabForCoordinate(tabPane, r.x + r.width/2,
+ r.y + r.height/2 + offset);
+ }
+ if (newIndex != -1) {
+ while (!tabPane.isEnabledAt(newIndex) && newIndex != tabIndex) {
+ newIndex = getNextTabIndex(newIndex);
+ }
+ navigateTo(newIndex);
+ }
+ }
+
+ private void navigateTo(int index) {
+ if (DefaultLookup.getBoolean(tabPane, this,
+ "TabbedPane.selectionFollowsFocus", true)) {
+ tabPane.setSelectedIndex(index);
+ } else {
+ // Just move focus (not selection)
+ setFocusIndex(index, true);
+ }
+ }
+
+ void setFocusIndex(int index, boolean repaint) {
+ if (repaint && !isRunsDirty) {
+ repaintTab(focusIndex);
+ focusIndex = index;
+ repaintTab(focusIndex);
+ }
+ else {
+ focusIndex = index;
+ }
+ }
+
+ /**
+ * Repaints the specified tab.
+ */
+ private void repaintTab(int index) {
+ // If we're not valid that means we will shortly be validated and
+ // painted, which means we don't have to do anything here.
+ if (!isRunsDirty && index >= 0 && index < tabPane.getTabCount()) {
+ tabPane.repaint(getTabBounds(tabPane, index));
+ }
+ }
+
+ /**
+ * Makes sure the focusIndex is valid.
+ */
+ private void validateFocusIndex() {
+ if (focusIndex >= tabPane.getTabCount()) {
+ setFocusIndex(tabPane.getSelectedIndex(), false);
+ }
+ }
+
+ /**
+ * Returns the index of the tab that has focus.
+ *
+ * @return index of tab that has focus
+ * @since 1.5
+ */
+ protected int getFocusIndex() {
+ return focusIndex;
+ }
+
+ /**
+ * Returns the tab run offset.
+ * @param tabPlacement the placement (left, right, bottom, top) of the tab
+ * @param tabCount the tab count
+ * @param tabIndex the index of the tab with respect to other tabs
+ * @param forward forward or not
+ * @return the tab run offset
+ */
+ protected int getTabRunOffset(int tabPlacement, int tabCount,
+ int tabIndex, boolean forward) {
+ int run = getRunForTab(tabCount, tabIndex);
+ int offset;
+ switch(tabPlacement) {
+ case LEFT: {
+ if (run == 0) {
+ offset = (forward?
+ -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth) :
+ -maxTabWidth);
+
+ } else if (run == runCount - 1) {
+ offset = (forward?
+ maxTabWidth :
+ calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth);
+ } else {
+ offset = (forward? maxTabWidth : -maxTabWidth);
+ }
+ break;
+ }
+ case RIGHT: {
+ if (run == 0) {
+ offset = (forward?
+ maxTabWidth :
+ calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth);
+ } else if (run == runCount - 1) {
+ offset = (forward?
+ -(calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth)-maxTabWidth) :
+ -maxTabWidth);
+ } else {
+ offset = (forward? maxTabWidth : -maxTabWidth);
+ }
+ break;
+ }
+ case BOTTOM: {
+ if (run == 0) {
+ offset = (forward?
+ maxTabHeight :
+ calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight);
+ } else if (run == runCount - 1) {
+ offset = (forward?
+ -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight) :
+ -maxTabHeight);
+ } else {
+ offset = (forward? maxTabHeight : -maxTabHeight);
+ }
+ break;
+ }
+ case TOP:
+ default: {
+ if (run == 0) {
+ offset = (forward?
+ -(calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight) :
+ -maxTabHeight);
+ } else if (run == runCount - 1) {
+ offset = (forward?
+ maxTabHeight :
+ calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight)-maxTabHeight);
+ } else {
+ offset = (forward? maxTabHeight : -maxTabHeight);
+ }
+ }
+ }
+ return offset;
+ }
+
+ /**
+ * Returns the previous tab index.
+ * @param base the base
+ * @return the previous tab index
+ */
+ protected int getPreviousTabIndex(int base) {
+ int tabIndex = (base - 1 >= 0? base - 1 : tabPane.getTabCount() - 1);
+ return (tabIndex >= 0? tabIndex : 0);
+ }
+
+ /**
+ * Returns the next tab index.
+ * @param base the base
+ * @return the next tab index
+ */
+ protected int getNextTabIndex(int base) {
+ return (base+1)%tabPane.getTabCount();
+ }
+
+ /**
+ * Returns the next tab index in the run.
+ * @param tabCount the tab count
+ * @param base the base
+ * @return the next tab index in the run
+ */
+ protected int getNextTabIndexInRun(int tabCount, int base) {
+ if (runCount < 2) {
+ return getNextTabIndex(base);
+ }
+ int currentRun = getRunForTab(tabCount, base);
+ int next = getNextTabIndex(base);
+ if (next == tabRuns[getNextTabRun(currentRun)]) {
+ return tabRuns[currentRun];
+ }
+ return next;
+ }
+
+ /**
+ * Returns the previous tab index in the run.
+ * @param tabCount the tab count
+ * @param base the base
+ * @return the previous tab index in the run
+ */
+ protected int getPreviousTabIndexInRun(int tabCount, int base) {
+ if (runCount < 2) {
+ return getPreviousTabIndex(base);
+ }
+ int currentRun = getRunForTab(tabCount, base);
+ if (base == tabRuns[currentRun]) {
+ int previous = tabRuns[getNextTabRun(currentRun)]-1;
+ return (previous != -1? previous : tabCount-1);
+ }
+ return getPreviousTabIndex(base);
+ }
+
+ /**
+ * Returns the previous tab run.
+ * @param baseRun the base run
+ * @return the previous tab run
+ */
+ protected int getPreviousTabRun(int baseRun) {
+ int runIndex = (baseRun - 1 >= 0? baseRun - 1 : runCount - 1);
+ return (runIndex >= 0? runIndex : 0);
+ }
+
+ /**
+ * Returns the next tab run.
+ * @param baseRun the base run
+ * @return the next tab run
+ */
+ protected int getNextTabRun(int baseRun) {
+ return (baseRun+1)%runCount;
+ }
+
+ /**
+ * Rotates the insets.
+ * @param topInsets the top insets
+ * @param targetInsets the target insets
+ * @param targetPlacement the target placement
+ */
+ protected static void rotateInsets(Insets topInsets, Insets targetInsets, int targetPlacement) {
+
+ switch(targetPlacement) {
+ case LEFT:
+ targetInsets.top = topInsets.left;
+ targetInsets.left = topInsets.top;
+ targetInsets.bottom = topInsets.right;
+ targetInsets.right = topInsets.bottom;
+ break;
+ case BOTTOM:
+ targetInsets.top = topInsets.bottom;
+ targetInsets.left = topInsets.left;
+ targetInsets.bottom = topInsets.top;
+ targetInsets.right = topInsets.right;
+ break;
+ case RIGHT:
+ targetInsets.top = topInsets.left;
+ targetInsets.left = topInsets.bottom;
+ targetInsets.bottom = topInsets.right;
+ targetInsets.right = topInsets.top;
+ break;
+ case TOP:
+ default:
+ targetInsets.top = topInsets.top;
+ targetInsets.left = topInsets.left;
+ targetInsets.bottom = topInsets.bottom;
+ targetInsets.right = topInsets.right;
+ }
+ }
+
+ // REMIND(aim,7/29/98): This method should be made
+ // protected in the next release where
+ // API changes are allowed
+ boolean requestFocusForVisibleComponent() {
+ return SwingUtilities2.tabbedPaneChangeFocusTo(getVisibleComponent());
+ }
+
+ private static class Actions extends UIAction {
+ static final String NEXT = "navigateNext";
+ static final String PREVIOUS = "navigatePrevious";
+ static final String RIGHT = "navigateRight";
+ static final String LEFT = "navigateLeft";
+ static final String UP = "navigateUp";
+ static final String DOWN = "navigateDown";
+ static final String PAGE_UP = "navigatePageUp";
+ static final String PAGE_DOWN = "navigatePageDown";
+ static final String REQUEST_FOCUS = "requestFocus";
+ static final String REQUEST_FOCUS_FOR_VISIBLE =
+ "requestFocusForVisibleComponent";
+ static final String SET_SELECTED = "setSelectedIndex";
+ static final String SELECT_FOCUSED = "selectTabWithFocus";
+ static final String SCROLL_FORWARD = "scrollTabsForwardAction";
+ static final String SCROLL_BACKWARD = "scrollTabsBackwardAction";
+
+ Actions(String key) {
+ super(key);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ String key = getName();
+ JTabbedPane pane = (JTabbedPane)e.getSource();
+ BasicTabbedPaneUI ui = (BasicTabbedPaneUI)BasicLookAndFeel.
+ getUIOfType(pane.getUI(), BasicTabbedPaneUI.class);
+
+ if (ui == null) {
+ return;
+ }
+ if (key == NEXT) {
+ ui.navigateSelectedTab(SwingConstants.NEXT);
+ }
+ else if (key == PREVIOUS) {
+ ui.navigateSelectedTab(SwingConstants.PREVIOUS);
+ }
+ else if (key == RIGHT) {
+ ui.navigateSelectedTab(SwingConstants.EAST);
+ }
+ else if (key == LEFT) {
+ ui.navigateSelectedTab(SwingConstants.WEST);
+ }
+ else if (key == UP) {
+ ui.navigateSelectedTab(SwingConstants.NORTH);
+ }
+ else if (key == DOWN) {
+ ui.navigateSelectedTab(SwingConstants.SOUTH);
+ }
+ else if (key == PAGE_UP) {
+ int tabPlacement = pane.getTabPlacement();
+ if (tabPlacement == TOP|| tabPlacement == BOTTOM) {
+ ui.navigateSelectedTab(SwingConstants.WEST);
+ } else {
+ ui.navigateSelectedTab(SwingConstants.NORTH);
+ }
+ }
+ else if (key == PAGE_DOWN) {
+ int tabPlacement = pane.getTabPlacement();
+ if (tabPlacement == TOP || tabPlacement == BOTTOM) {
+ ui.navigateSelectedTab(SwingConstants.EAST);
+ } else {
+ ui.navigateSelectedTab(SwingConstants.SOUTH);
+ }
+ }
+ else if (key == REQUEST_FOCUS) {
+ pane.requestFocus();
+ }
+ else if (key == REQUEST_FOCUS_FOR_VISIBLE) {
+ ui.requestFocusForVisibleComponent();
+ }
+ else if (key == SET_SELECTED) {
+ String command = e.getActionCommand();
+
+ if (command != null && command.length() > 0) {
+ int mnemonic = (int)e.getActionCommand().charAt(0);
+ if (mnemonic >= 'a' && mnemonic <='z') {
+ mnemonic -= ('a' - 'A');
+ }
+ Integer index = ui.mnemonicToIndexMap.get(Integer.valueOf(mnemonic));
+ if (index != null && pane.isEnabledAt(index.intValue())) {
+ pane.setSelectedIndex(index.intValue());
+ }
+ }
+ }
+ else if (key == SELECT_FOCUSED) {
+ int focusIndex = ui.getFocusIndex();
+ if (focusIndex != -1) {
+ pane.setSelectedIndex(focusIndex);
+ }
+ }
+ else if (key == SCROLL_FORWARD) {
+ if (ui.scrollableTabLayoutEnabled()) {
+ ui.tabScroller.scrollForward(pane.getTabPlacement());
+ }
+ }
+ else if (key == SCROLL_BACKWARD) {
+ if (ui.scrollableTabLayoutEnabled()) {
+ ui.tabScroller.scrollBackward(pane.getTabPlacement());
+ }
+ }
+ }
+ }
+
+ /**
+ * This class should be treated as a "protected" inner class.
+ * Instantiate it only within subclasses of BasicTabbedPaneUI.
+ */
+ public class TabbedPaneLayout implements LayoutManager {
+
+ public void addLayoutComponent(String name, Component comp) {}
+
+ public void removeLayoutComponent(Component comp) {}
+
+ public Dimension preferredLayoutSize(Container parent) {
+ return calculateSize(false);
+ }
+
+ public Dimension minimumLayoutSize(Container parent) {
+ return calculateSize(true);
+ }
+
+ /**
+ * Returns the calculated size.
+ * @param minimum use the minimum size or preferred size
+ * @return the calculated size
+ */
+ protected Dimension calculateSize(boolean minimum) {
+ int tabPlacement = tabPane.getTabPlacement();
+ Insets insets = tabPane.getInsets();
+ Insets contentInsets = getContentBorderInsets(tabPlacement);
+ Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
+
+ Dimension zeroSize = new Dimension(0,0);
+ int height = 0;
+ int width = 0;
+ int cWidth = 0;
+ int cHeight = 0;
+
+ // Determine minimum size required to display largest
+ // child in each dimension
+ //
+ for (int i = 0; i < tabPane.getTabCount(); i++) {
+ Component component = tabPane.getComponentAt(i);
+ if (component != null) {
+ Dimension size = minimum ? component.getMinimumSize() :
+ component.getPreferredSize();
+
+ if (size != null) {
+ cHeight = Math.max(size.height, cHeight);
+ cWidth = Math.max(size.width, cWidth);
+ }
+ }
+ }
+ // Add content border insets to minimum size
+ width += cWidth;
+ height += cHeight;
+ int tabExtent;
+
+ // Calculate how much space the tabs will need, based on the
+ // minimum size required to display largest child + content border
+ //
+ switch(tabPlacement) {
+ case LEFT:
+ case RIGHT:
+ height = Math.max(height, calculateMaxTabHeight(tabPlacement));
+ tabExtent = preferredTabAreaWidth(tabPlacement, height - tabAreaInsets.top - tabAreaInsets.bottom);
+ width += tabExtent;
+ break;
+ case TOP:
+ case BOTTOM:
+ default:
+ width = Math.max(width, calculateMaxTabWidth(tabPlacement));
+ tabExtent = preferredTabAreaHeight(tabPlacement, width - tabAreaInsets.left - tabAreaInsets.right);
+ height += tabExtent;
+ }
+ return new Dimension(width + insets.left + insets.right + contentInsets.left + contentInsets.right,
+ height + insets.bottom + insets.top + contentInsets.top + contentInsets.bottom);
+
+ }
+
+ /**
+ * Returns the preferred tab area height.
+ * @param tabPlacement the tab placement
+ * @param width the width
+ * @return the preferred tab area height
+ */
+ protected int preferredTabAreaHeight(int tabPlacement, int width) {
+ FontMetrics metrics = getFontMetrics();
+ int tabCount = tabPane.getTabCount();
+ int total = 0;
+ if (tabCount > 0) {
+ int rows = 1;
+ int x = 0;
+
+ int maxTabHeight = calculateMaxTabHeight(tabPlacement);
+
+ for (int i = 0; i < tabCount; i++) {
+ int tabWidth = calculateTabWidth(tabPlacement, i, metrics);
+
+ if (x != 0 && x + tabWidth > width) {
+ rows++;
+ x = 0;
+ }
+ x += tabWidth;
+ }
+ total = calculateTabAreaHeight(tabPlacement, rows, maxTabHeight);
+ }
+ return total;
+ }
+
+ /**
+ * Returns the preferred tab area width.
+ * @param tabPlacement the tab placement
+ * @param height the height
+ * @return the preferred tab area widty
+ */
+ protected int preferredTabAreaWidth(int tabPlacement, int height) {
+ FontMetrics metrics = getFontMetrics();
+ int tabCount = tabPane.getTabCount();
+ int total = 0;
+ if (tabCount > 0) {
+ int columns = 1;
+ int y = 0;
+ int fontHeight = metrics.getHeight();
+
+ maxTabWidth = calculateMaxTabWidth(tabPlacement);
+
+ for (int i = 0; i < tabCount; i++) {
+ int tabHeight = calculateTabHeight(tabPlacement, i, fontHeight);
+
+ if (y != 0 && y + tabHeight > height) {
+ columns++;
+ y = 0;
+ }
+ y += tabHeight;
+ }
+ total = calculateTabAreaWidth(tabPlacement, columns, maxTabWidth);
+ }
+ return total;
+ }
+
+ /** {@inheritDoc} */
+ @SuppressWarnings("deprecation")
+ public void layoutContainer(Container parent) {
+ /* Some of the code in this method deals with changing the
+ * visibility of components to hide and show the contents for the
+ * selected tab. This is older code that has since been duplicated
+ * in JTabbedPane.fireStateChanged(), so as to allow visibility
+ * changes to happen sooner (see the note there). This code remains
+ * for backward compatibility as there are some cases, such as
+ * subclasses that don't fireStateChanged() where it may be used.
+ * Any changes here need to be kept in synch with
+ * JTabbedPane.fireStateChanged().
+ */
+
+ setRolloverTab(-1);
+
+ int tabPlacement = tabPane.getTabPlacement();
+ Insets insets = tabPane.getInsets();
+ int selectedIndex = tabPane.getSelectedIndex();
+ Component visibleComponent = getVisibleComponent();
+
+ calculateLayoutInfo();
+
+ Component selectedComponent = null;
+ if (selectedIndex < 0) {
+ if (visibleComponent != null) {
+ // The last tab was removed, so remove the component
+ setVisibleComponent(null);
+ }
+ } else {
+ selectedComponent = tabPane.getComponentAt(selectedIndex);
+ }
+ int cx, cy, cw, ch;
+ int totalTabWidth = 0;
+ int totalTabHeight = 0;
+ Insets contentInsets = getContentBorderInsets(tabPlacement);
+
+ boolean shouldChangeFocus = false;
+
+ // In order to allow programs to use a single component
+ // as the display for multiple tabs, we will not change
+ // the visible compnent if the currently selected tab
+ // has a null component. This is a bit dicey, as we don't
+ // explicitly state we support this in the spec, but since
+ // programs are now depending on this, we're making it work.
+ //
+ if(selectedComponent != null) {
+ if(selectedComponent != visibleComponent &&
+ visibleComponent != null) {
+ if(SwingUtilities.findFocusOwner(visibleComponent) != null) {
+ shouldChangeFocus = true;
+ }
+ }
+ setVisibleComponent(selectedComponent);
+ }
+
+ Rectangle bounds = tabPane.getBounds();
+ int numChildren = tabPane.getComponentCount();
+
+ if(numChildren > 0) {
+
+ switch(tabPlacement) {
+ case LEFT:
+ totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
+ cx = insets.left + totalTabWidth + contentInsets.left;
+ cy = insets.top + contentInsets.top;
+ break;
+ case RIGHT:
+ totalTabWidth = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
+ cx = insets.left + contentInsets.left;
+ cy = insets.top + contentInsets.top;
+ break;
+ case BOTTOM:
+ totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
+ cx = insets.left + contentInsets.left;
+ cy = insets.top + contentInsets.top;
+ break;
+ case TOP:
+ default:
+ totalTabHeight = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
+ cx = insets.left + contentInsets.left;
+ cy = insets.top + totalTabHeight + contentInsets.top;
+ }
+
+ cw = bounds.width - totalTabWidth -
+ insets.left - insets.right -
+ contentInsets.left - contentInsets.right;
+ ch = bounds.height - totalTabHeight -
+ insets.top - insets.bottom -
+ contentInsets.top - contentInsets.bottom;
+
+ for(int i = 0; i < numChildren; i++) {
+ Component child = tabPane.getComponent(i);
+ if(child == tabContainer) {
+
+ int tabContainerWidth = totalTabWidth == 0 ? bounds.width :
+ totalTabWidth + insets.left + insets.right +
+ contentInsets.left + contentInsets.right;
+ int tabContainerHeight = totalTabHeight == 0 ? bounds.height :
+ totalTabHeight + insets.top + insets.bottom +
+ contentInsets.top + contentInsets.bottom;
+
+ int tabContainerX = 0;
+ int tabContainerY = 0;
+ if(tabPlacement == BOTTOM) {
+ tabContainerY = bounds.height - tabContainerHeight;
+ } else if(tabPlacement == RIGHT) {
+ tabContainerX = bounds.width - tabContainerWidth;
+ }
+ child.setBounds(tabContainerX, tabContainerY, tabContainerWidth, tabContainerHeight);
+ } else {
+ child.setBounds(cx, cy, cw, ch);
+ }
+ }
+ }
+ layoutTabComponents();
+ if(shouldChangeFocus) {
+ if(!requestFocusForVisibleComponent()) {
+ tabPane.requestFocus();
+ }
+ }
+ }
+
+ /**
+ * Calculates the layout info.
+ */
+ public void calculateLayoutInfo() {
+ int tabCount = tabPane.getTabCount();
+ assureRectsCreated(tabCount);
+ calculateTabRects(tabPane.getTabPlacement(), tabCount);
+ isRunsDirty = false;
+ }
+
+ private void layoutTabComponents() {
+ if (tabContainer == null) {
+ return;
+ }
+ Rectangle rect = new Rectangle();
+ Point delta = new Point(-tabContainer.getX(), -tabContainer.getY());
+ if (scrollableTabLayoutEnabled()) {
+ translatePointToTabPanel(0, 0, delta);
+ }
+ for (int i = 0; i < tabPane.getTabCount(); i++) {
+ Component c = tabPane.getTabComponentAt(i);
+ if (c == null) {
+ continue;
+ }
+ getTabBounds(i, rect);
+ Dimension preferredSize = c.getPreferredSize();
+ Insets insets = getTabInsets(tabPane.getTabPlacement(), i);
+ int outerX = rect.x + insets.left + delta.x;
+ int outerY = rect.y + insets.top + delta.y;
+ int outerWidth = rect.width - insets.left - insets.right;
+ int outerHeight = rect.height - insets.top - insets.bottom;
+ //centralize component
+ int x = outerX + (outerWidth - preferredSize.width) / 2;
+ int y = outerY + (outerHeight - preferredSize.height) / 2;
+ int tabPlacement = tabPane.getTabPlacement();
+ boolean isSeleceted = i == tabPane.getSelectedIndex();
+ c.setBounds(x + getTabLabelShiftX(tabPlacement, i, isSeleceted),
+ y + getTabLabelShiftY(tabPlacement, i, isSeleceted),
+ preferredSize.width, preferredSize.height);
+ }
+ }
+
+ /**
+ * Calculate the tab rectangles.
+ * @param tabPlacement the tab placement
+ * @param tabCount the tab count
+ */
+ protected void calculateTabRects(int tabPlacement, int tabCount) {
+ FontMetrics metrics = getFontMetrics();
+ Dimension size = tabPane.getSize();
+ Insets insets = tabPane.getInsets();
+ Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
+ int fontHeight = metrics.getHeight();
+ int selectedIndex = tabPane.getSelectedIndex();
+ int tabRunOverlay;
+ int i, j;
+ int x, y;
+ int returnAt;
+ boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
+ boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane);
+
+ //
+ // Calculate bounds within which a tab run must fit
+ //
+ switch(tabPlacement) {
+ case LEFT:
+ maxTabWidth = calculateMaxTabWidth(tabPlacement);
+ x = insets.left + tabAreaInsets.left;
+ y = insets.top + tabAreaInsets.top;
+ returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
+ break;
+ case RIGHT:
+ maxTabWidth = calculateMaxTabWidth(tabPlacement);
+ x = size.width - insets.right - tabAreaInsets.right - maxTabWidth;
+ y = insets.top + tabAreaInsets.top;
+ returnAt = size.height - (insets.bottom + tabAreaInsets.bottom);
+ break;
+ case BOTTOM:
+ maxTabHeight = calculateMaxTabHeight(tabPlacement);
+ x = insets.left + tabAreaInsets.left;
+ y = size.height - insets.bottom - tabAreaInsets.bottom - maxTabHeight;
+ returnAt = size.width - (insets.right + tabAreaInsets.right);
+ break;
+ case TOP:
+ default:
+ maxTabHeight = calculateMaxTabHeight(tabPlacement);
+ x = insets.left + tabAreaInsets.left;
+ y = insets.top + tabAreaInsets.top;
+ returnAt = size.width - (insets.right + tabAreaInsets.right);
+ break;
+ }
+
+ tabRunOverlay = getTabRunOverlay(tabPlacement);
+
+ runCount = 0;
+ selectedRun = -1;
+
+ if (tabCount == 0) {
+ return;
+ }
+
+ // Run through tabs and partition them into runs
+ Rectangle rect;
+ for (i = 0; i < tabCount; i++) {
+ rect = rects[i];
+
+ if (!verticalTabRuns) {
+ // Tabs on TOP or BOTTOM....
+ if (i > 0) {
+ rect.x = rects[i-1].x + rects[i-1].width;
+ } else {
+ tabRuns[0] = 0;
+ runCount = 1;
+ maxTabWidth = 0;
+ rect.x = x;
+ }
+ rect.width = calculateTabWidth(tabPlacement, i, metrics);
+ maxTabWidth = Math.max(maxTabWidth, rect.width);
+
+ // Never move a TAB down a run if it is in the first column.
+ // Even if there isn't enough room, moving it to a fresh
+ // line won't help.
+ if (rect.x != x && rect.x + rect.width > returnAt) {
+ if (runCount > tabRuns.length - 1) {
+ expandTabRunsArray();
+ }
+ tabRuns[runCount] = i;
+ runCount++;
+ rect.x = x;
+ }
+ // Initialize y position in case there's just one run
+ rect.y = y;
+ rect.height = maxTabHeight/* - 2*/;
+
+ } else {
+ // Tabs on LEFT or RIGHT...
+ if (i > 0) {
+ rect.y = rects[i-1].y + rects[i-1].height;
+ } else {
+ tabRuns[0] = 0;
+ runCount = 1;
+ maxTabHeight = 0;
+ rect.y = y;
+ }
+ rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
+ maxTabHeight = Math.max(maxTabHeight, rect.height);
+
+ // Never move a TAB over a run if it is in the first run.
+ // Even if there isn't enough room, moving it to a fresh
+ // column won't help.
+ if (rect.y != y && rect.y + rect.height > returnAt) {
+ if (runCount > tabRuns.length - 1) {
+ expandTabRunsArray();
+ }
+ tabRuns[runCount] = i;
+ runCount++;
+ rect.y = y;
+ }
+ // Initialize x position in case there's just one column
+ rect.x = x;
+ rect.width = maxTabWidth/* - 2*/;
+
+ }
+ if (i == selectedIndex) {
+ selectedRun = runCount - 1;
+ }
+ }
+
+ if (runCount > 1) {
+ // Re-distribute tabs in case last run has leftover space
+ normalizeTabRuns(tabPlacement, tabCount, verticalTabRuns? y : x, returnAt);
+
+ selectedRun = getRunForTab(tabCount, selectedIndex);
+
+ // Rotate run array so that selected run is first
+ if (shouldRotateTabRuns(tabPlacement)) {
+ rotateTabRuns(tabPlacement, selectedRun);
+ }
+ }
+
+ // Step through runs from back to front to calculate
+ // tab y locations and to pad runs appropriately
+ for (i = runCount - 1; i >= 0; i--) {
+ int start = tabRuns[i];
+ int next = tabRuns[i == (runCount - 1)? 0 : i + 1];
+ int end = (next != 0? next - 1 : tabCount - 1);
+ if (!verticalTabRuns) {
+ for (j = start; j <= end; j++) {
+ rect = rects[j];
+ rect.y = y;
+ rect.x += getTabRunIndent(tabPlacement, i);
+ }
+ if (shouldPadTabRun(tabPlacement, i)) {
+ padTabRun(tabPlacement, start, end, returnAt);
+ }
+ if (tabPlacement == BOTTOM) {
+ y -= (maxTabHeight - tabRunOverlay);
+ } else {
+ y += (maxTabHeight - tabRunOverlay);
+ }
+ } else {
+ for (j = start; j <= end; j++) {
+ rect = rects[j];
+ rect.x = x;
+ rect.y += getTabRunIndent(tabPlacement, i);
+ }
+ if (shouldPadTabRun(tabPlacement, i)) {
+ padTabRun(tabPlacement, start, end, returnAt);
+ }
+ if (tabPlacement == RIGHT) {
+ x -= (maxTabWidth - tabRunOverlay);
+ } else {
+ x += (maxTabWidth - tabRunOverlay);
+ }
+ }
+ }
+
+ // Pad the selected tab so that it appears raised in front
+ padSelectedTab(tabPlacement, selectedIndex);
+
+ // if right to left and tab placement on the top or
+ // the bottom, flip x positions and adjust by widths
+ if (!leftToRight && !verticalTabRuns) {
+ int rightMargin = size.width
+ - (insets.right + tabAreaInsets.right);
+ for (i = 0; i < tabCount; i++) {
+ rects[i].x = rightMargin - rects[i].x - rects[i].width;
+ }
+ }
+ }
+
+
+ /**
+ * Rotates the run-index array so that the selected run is run[0].
+ * @param tabPlacement the tab placement
+ * @param selectedRun the selected run
+ */
+ protected void rotateTabRuns(int tabPlacement, int selectedRun) {
+ for (int i = 0; i < selectedRun; i++) {
+ int save = tabRuns[0];
+ for (int j = 1; j < runCount; j++) {
+ tabRuns[j - 1] = tabRuns[j];
+ }
+ tabRuns[runCount-1] = save;
+ }
+ }
+
+ /**
+ * Normalizes the tab runs.
+ * @param tabPlacement the tab placement
+ * @param tabCount the tab count
+ * @param start the start
+ * @param max the max
+ */
+ protected void normalizeTabRuns(int tabPlacement, int tabCount,
+ int start, int max) {
+ boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
+ int run = runCount - 1;
+ boolean keepAdjusting = true;
+ double weight = 1.25;
+
+ // At this point the tab runs are packed to fit as many
+ // tabs as possible, which can leave the last run with a lot
+ // of extra space (resulting in very fat tabs on the last run).
+ // So we'll attempt to distribute this extra space more evenly
+ // across the runs in order to make the runs look more consistent.
+ //
+ // Starting with the last run, determine whether the last tab in
+ // the previous run would fit (generously) in this run; if so,
+ // move tab to current run and shift tabs accordingly. Cycle
+ // through remaining runs using the same algorithm.
+ //
+ while (keepAdjusting) {
+ int last = lastTabInRun(tabCount, run);
+ int prevLast = lastTabInRun(tabCount, run-1);
+ int end;
+ int prevLastLen;
+
+ if (!verticalTabRuns) {
+ end = rects[last].x + rects[last].width;
+ prevLastLen = (int)(maxTabWidth*weight);
+ } else {
+ end = rects[last].y + rects[last].height;
+ prevLastLen = (int)(maxTabHeight*weight*2);
+ }
+
+ // Check if the run has enough extra space to fit the last tab
+ // from the previous row...
+ if (max - end > prevLastLen) {
+
+ // Insert tab from previous row and shift rest over
+ tabRuns[run] = prevLast;
+ if (!verticalTabRuns) {
+ rects[prevLast].x = start;
+ } else {
+ rects[prevLast].y = start;
+ }
+ for (int i = prevLast+1; i <= last; i++) {
+ if (!verticalTabRuns) {
+ rects[i].x = rects[i-1].x + rects[i-1].width;
+ } else {
+ rects[i].y = rects[i-1].y + rects[i-1].height;
+ }
+ }
+
+ } else if (run == runCount - 1) {
+ // no more room left in last run, so we're done!
+ keepAdjusting = false;
+ }
+ if (run - 1 > 0) {
+ // check previous run next...
+ run -= 1;
+ } else {
+ // check last run again...but require a higher ratio
+ // of extraspace-to-tabsize because we don't want to
+ // end up with too many tabs on the last run!
+ run = runCount - 1;
+ weight += .25;
+ }
+ }
+ }
+
+ /**
+ * Pads the tab run.
+ * @param tabPlacement the tab placement
+ * @param start the start
+ * @param end the end
+ * @param max the max
+ */
+ protected void padTabRun(int tabPlacement, int start, int end, int max) {
+ Rectangle lastRect = rects[end];
+ if (tabPlacement == TOP || tabPlacement == BOTTOM) {
+ int runWidth = (lastRect.x + lastRect.width) - rects[start].x;
+ int deltaWidth = max - (lastRect.x + lastRect.width);
+ float factor = (float)deltaWidth / (float)runWidth;
+
+ for (int j = start; j <= end; j++) {
+ Rectangle pastRect = rects[j];
+ if (j > start) {
+ pastRect.x = rects[j-1].x + rects[j-1].width;
+ }
+ pastRect.width += Math.round((float)pastRect.width * factor);
+ }
+ lastRect.width = max - lastRect.x;
+ } else {
+ int runHeight = (lastRect.y + lastRect.height) - rects[start].y;
+ int deltaHeight = max - (lastRect.y + lastRect.height);
+ float factor = (float)deltaHeight / (float)runHeight;
+
+ for (int j = start; j <= end; j++) {
+ Rectangle pastRect = rects[j];
+ if (j > start) {
+ pastRect.y = rects[j-1].y + rects[j-1].height;
+ }
+ pastRect.height += Math.round((float)pastRect.height * factor);
+ }
+ lastRect.height = max - lastRect.y;
+ }
+ }
+
+ /**
+ * Pads selected tab.
+ * @param tabPlacement the tab placement
+ * @param selectedIndex the selected index
+ */
+ protected void padSelectedTab(int tabPlacement, int selectedIndex) {
+
+ if (selectedIndex >= 0) {
+ Rectangle selRect = rects[selectedIndex];
+ Insets padInsets = getSelectedTabPadInsets(tabPlacement);
+ selRect.x -= padInsets.left;
+ selRect.width += (padInsets.left + padInsets.right);
+ selRect.y -= padInsets.top;
+ selRect.height += (padInsets.top + padInsets.bottom);
+
+ if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT
+ // do not expand selected tab more then necessary
+ Dimension size = tabPane.getSize();
+ Insets insets = tabPane.getInsets();
+
+ if ((tabPlacement == LEFT) || (tabPlacement == RIGHT)) {
+ int top = insets.top - selRect.y;
+ if (top > 0) {
+ selRect.y += top;
+ selRect.height -= top;
+ }
+ int bottom = (selRect.y + selRect.height) + insets.bottom - size.height;
+ if (bottom > 0) {
+ selRect.height -= bottom;
+ }
+ } else {
+ int left = insets.left - selRect.x;
+ if (left > 0) {
+ selRect.x += left;
+ selRect.width -= left;
+ }
+ int right = (selRect.x + selRect.width) + insets.right - size.width;
+ if (right > 0) {
+ selRect.width -= right;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private class TabbedPaneScrollLayout extends TabbedPaneLayout {
+
+ protected int preferredTabAreaHeight(int tabPlacement, int width) {
+ return calculateMaxTabHeight(tabPlacement);
+ }
+
+ protected int preferredTabAreaWidth(int tabPlacement, int height) {
+ return calculateMaxTabWidth(tabPlacement);
+ }
+
+ @SuppressWarnings("deprecation")
+ public void layoutContainer(Container parent) {
+ /* Some of the code in this method deals with changing the
+ * visibility of components to hide and show the contents for the
+ * selected tab. This is older code that has since been duplicated
+ * in JTabbedPane.fireStateChanged(), so as to allow visibility
+ * changes to happen sooner (see the note there). This code remains
+ * for backward compatibility as there are some cases, such as
+ * subclasses that don't fireStateChanged() where it may be used.
+ * Any changes here need to be kept in synch with
+ * JTabbedPane.fireStateChanged().
+ */
+
+ setRolloverTab(-1);
+
+ int tabPlacement = tabPane.getTabPlacement();
+ int tabCount = tabPane.getTabCount();
+ Insets insets = tabPane.getInsets();
+ int selectedIndex = tabPane.getSelectedIndex();
+ Component visibleComponent = getVisibleComponent();
+
+ calculateLayoutInfo();
+
+ Component selectedComponent = null;
+ if (selectedIndex < 0) {
+ if (visibleComponent != null) {
+ // The last tab was removed, so remove the component
+ setVisibleComponent(null);
+ }
+ } else {
+ selectedComponent = tabPane.getComponentAt(selectedIndex);
+ }
+
+ if (tabPane.getTabCount() == 0) {
+ tabScroller.croppedEdge.resetParams();
+ tabScroller.scrollForwardButton.setVisible(false);
+ tabScroller.scrollBackwardButton.setVisible(false);
+ return;
+ }
+
+ boolean shouldChangeFocus = false;
+
+ // In order to allow programs to use a single component
+ // as the display for multiple tabs, we will not change
+ // the visible compnent if the currently selected tab
+ // has a null component. This is a bit dicey, as we don't
+ // explicitly state we support this in the spec, but since
+ // programs are now depending on this, we're making it work.
+ //
+ if(selectedComponent != null) {
+ if(selectedComponent != visibleComponent &&
+ visibleComponent != null) {
+ if(SwingUtilities.findFocusOwner(visibleComponent) != null) {
+ shouldChangeFocus = true;
+ }
+ }
+ setVisibleComponent(selectedComponent);
+ }
+ int tx, ty, tw, th; // tab area bounds
+ int cx, cy, cw, ch; // content area bounds
+ Insets contentInsets = getContentBorderInsets(tabPlacement);
+ Rectangle bounds = tabPane.getBounds();
+ int numChildren = tabPane.getComponentCount();
+
+ if(numChildren > 0) {
+ switch(tabPlacement) {
+ case LEFT:
+ // calculate tab area bounds
+ tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
+ th = bounds.height - insets.top - insets.bottom;
+ tx = insets.left;
+ ty = insets.top;
+
+ // calculate content area bounds
+ cx = tx + tw + contentInsets.left;
+ cy = ty + contentInsets.top;
+ cw = bounds.width - insets.left - insets.right - tw -
+ contentInsets.left - contentInsets.right;
+ ch = bounds.height - insets.top - insets.bottom -
+ contentInsets.top - contentInsets.bottom;
+ break;
+ case RIGHT:
+ // calculate tab area bounds
+ tw = calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
+ th = bounds.height - insets.top - insets.bottom;
+ tx = bounds.width - insets.right - tw;
+ ty = insets.top;
+
+ // calculate content area bounds
+ cx = insets.left + contentInsets.left;
+ cy = insets.top + contentInsets.top;
+ cw = bounds.width - insets.left - insets.right - tw -
+ contentInsets.left - contentInsets.right;
+ ch = bounds.height - insets.top - insets.bottom -
+ contentInsets.top - contentInsets.bottom;
+ break;
+ case BOTTOM:
+ // calculate tab area bounds
+ tw = bounds.width - insets.left - insets.right;
+ th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
+ tx = insets.left;
+ ty = bounds.height - insets.bottom - th;
+
+ // calculate content area bounds
+ cx = insets.left + contentInsets.left;
+ cy = insets.top + contentInsets.top;
+ cw = bounds.width - insets.left - insets.right -
+ contentInsets.left - contentInsets.right;
+ ch = bounds.height - insets.top - insets.bottom - th -
+ contentInsets.top - contentInsets.bottom;
+ break;
+ case TOP:
+ default:
+ // calculate tab area bounds
+ tw = bounds.width - insets.left - insets.right;
+ th = calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
+ tx = insets.left;
+ ty = insets.top;
+
+ // calculate content area bounds
+ cx = tx + contentInsets.left;
+ cy = ty + th + contentInsets.top;
+ cw = bounds.width - insets.left - insets.right -
+ contentInsets.left - contentInsets.right;
+ ch = bounds.height - insets.top - insets.bottom - th -
+ contentInsets.top - contentInsets.bottom;
+ }
+
+ for(int i = 0; i < numChildren; i++) {
+ Component child = tabPane.getComponent(i);
+
+ if(tabScroller != null && child == tabScroller.viewport) {
+ JViewport viewport = (JViewport) child;
+ Rectangle viewRect = viewport.getViewRect();
+ int vw = tw;
+ int vh = th;
+ Dimension butSize = tabScroller.scrollForwardButton.getPreferredSize();
+ switch(tabPlacement) {
+ case LEFT:
+ case RIGHT:
+ int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height;
+ if(totalTabHeight > th) {
+ // Allow space for scrollbuttons
+ vh = (th > 2 * butSize.height) ? th - 2 * butSize.height : 0;
+ if(totalTabHeight - viewRect.y <= vh) {
+ // Scrolled to the end, so ensure the viewport size is
+ // such that the scroll offset aligns with a tab
+ vh = totalTabHeight - viewRect.y;
+ }
+ }
+ break;
+ case BOTTOM:
+ case TOP:
+ default:
+ int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width;
+ if(totalTabWidth > tw) {
+ // Need to allow space for scrollbuttons
+ vw = (tw > 2 * butSize.width) ? tw - 2 * butSize.width : 0;
+ if(totalTabWidth - viewRect.x <= vw) {
+ // Scrolled to the end, so ensure the viewport size is
+ // such that the scroll offset aligns with a tab
+ vw = totalTabWidth - viewRect.x;
+ }
+ }
+ }
+ child.setBounds(tx, ty, vw, vh);
+
+ } else if(tabScroller != null &&
+ (child == tabScroller.scrollForwardButton ||
+ child == tabScroller.scrollBackwardButton)) {
+ Component scrollbutton = child;
+ Dimension bsize = scrollbutton.getPreferredSize();
+ int bx = 0;
+ int by = 0;
+ int bw = bsize.width;
+ int bh = bsize.height;
+ boolean visible = false;
+
+ switch(tabPlacement) {
+ case LEFT:
+ case RIGHT:
+ int totalTabHeight = rects[tabCount - 1].y + rects[tabCount - 1].height;
+ if(totalTabHeight > th) {
+ visible = true;
+ bx = (tabPlacement == LEFT ? tx + tw - bsize.width : tx);
+ by = (child == tabScroller.scrollForwardButton) ?
+ bounds.height - insets.bottom - bsize.height :
+ bounds.height - insets.bottom - 2 * bsize.height;
+ }
+ break;
+
+ case BOTTOM:
+ case TOP:
+ default:
+ int totalTabWidth = rects[tabCount - 1].x + rects[tabCount - 1].width;
+
+ if(totalTabWidth > tw) {
+ visible = true;
+ bx = (child == tabScroller.scrollForwardButton) ?
+ bounds.width - insets.left - bsize.width :
+ bounds.width - insets.left - 2 * bsize.width;
+ by = (tabPlacement == TOP ? ty + th - bsize.height : ty);
+ }
+ }
+ child.setVisible(visible);
+ if(visible) {
+ child.setBounds(bx, by, bw, bh);
+ }
+
+ } else {
+ // All content children...
+ child.setBounds(cx, cy, cw, ch);
+ }
+ }
+ super.layoutTabComponents();
+ layoutCroppedEdge();
+ if(shouldChangeFocus) {
+ if(!requestFocusForVisibleComponent()) {
+ tabPane.requestFocus();
+ }
+ }
+ }
+ }
+
+ private void layoutCroppedEdge() {
+ tabScroller.croppedEdge.resetParams();
+ Rectangle viewRect = tabScroller.viewport.getViewRect();
+ int cropline;
+ for (int i = 0; i < rects.length; i++) {
+ Rectangle tabRect = rects[i];
+ switch (tabPane.getTabPlacement()) {
+ case LEFT:
+ case RIGHT:
+ cropline = viewRect.y + viewRect.height;
+ if ((tabRect.y < cropline) && (tabRect.y + tabRect.height > cropline)) {
+ tabScroller.croppedEdge.setParams(i, cropline - tabRect.y - 1,
+ -currentTabAreaInsets.left, 0);
+ }
+ break;
+ case TOP:
+ case BOTTOM:
+ default:
+ cropline = viewRect.x + viewRect.width;
+ if ((tabRect.x < cropline - 1) && (tabRect.x + tabRect.width > cropline)) {
+ tabScroller.croppedEdge.setParams(i, cropline - tabRect.x - 1,
+ 0, -currentTabAreaInsets.top);
+ }
+ }
+ }
+ }
+
+ protected void calculateTabRects(int tabPlacement, int tabCount) {
+ FontMetrics metrics = getFontMetrics();
+ Dimension size = tabPane.getSize();
+ Insets insets = tabPane.getInsets();
+ Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
+ int fontHeight = metrics.getHeight();
+ int selectedIndex = tabPane.getSelectedIndex();
+ int i;
+ boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
+ boolean leftToRight = BasicGraphicsUtils.isLeftToRight(tabPane);
+ int x = tabAreaInsets.left;
+ int y = tabAreaInsets.top;
+ int totalWidth = 0;
+ int totalHeight = 0;
+
+ //
+ // Calculate bounds within which a tab run must fit
+ //
+ switch(tabPlacement) {
+ case LEFT:
+ case RIGHT:
+ maxTabWidth = calculateMaxTabWidth(tabPlacement);
+ break;
+ case BOTTOM:
+ case TOP:
+ default:
+ maxTabHeight = calculateMaxTabHeight(tabPlacement);
+ }
+
+ runCount = 0;
+ selectedRun = -1;
+
+ if (tabCount == 0) {
+ return;
+ }
+
+ selectedRun = 0;
+ runCount = 1;
+
+ // Run through tabs and lay them out in a single run
+ Rectangle rect;
+ for (i = 0; i < tabCount; i++) {
+ rect = rects[i];
+
+ if (!verticalTabRuns) {
+ // Tabs on TOP or BOTTOM....
+ if (i > 0) {
+ rect.x = rects[i-1].x + rects[i-1].width;
+ } else {
+ tabRuns[0] = 0;
+ maxTabWidth = 0;
+ totalHeight += maxTabHeight;
+ rect.x = x;
+ }
+ rect.width = calculateTabWidth(tabPlacement, i, metrics);
+ totalWidth = rect.x + rect.width;
+ maxTabWidth = Math.max(maxTabWidth, rect.width);
+
+ rect.y = y;
+ rect.height = maxTabHeight/* - 2*/;
+
+ } else {
+ // Tabs on LEFT or RIGHT...
+ if (i > 0) {
+ rect.y = rects[i-1].y + rects[i-1].height;
+ } else {
+ tabRuns[0] = 0;
+ maxTabHeight = 0;
+ totalWidth = maxTabWidth;
+ rect.y = y;
+ }
+ rect.height = calculateTabHeight(tabPlacement, i, fontHeight);
+ totalHeight = rect.y + rect.height;
+ maxTabHeight = Math.max(maxTabHeight, rect.height);
+
+ rect.x = x;
+ rect.width = maxTabWidth/* - 2*/;
+
+ }
+ }
+
+ if (tabsOverlapBorder) {
+ // Pad the selected tab so that it appears raised in front
+ padSelectedTab(tabPlacement, selectedIndex);
+ }
+
+ // if right to left and tab placement on the top or
+ // the bottom, flip x positions and adjust by widths
+ if (!leftToRight && !verticalTabRuns) {
+ int rightMargin = size.width
+ - (insets.right + tabAreaInsets.right);
+ for (i = 0; i < tabCount; i++) {
+ rects[i].x = rightMargin - rects[i].x - rects[i].width;
+ }
+ }
+ tabScroller.tabPanel.setPreferredSize(new Dimension(totalWidth, totalHeight));
+ tabScroller.tabPanel.invalidate();
+ }
+ }
+
+ private class ScrollableTabSupport implements ActionListener,
+ ChangeListener {
+ public ScrollableTabViewport viewport;
+ public ScrollableTabPanel tabPanel;
+ public JButton scrollForwardButton;
+ public JButton scrollBackwardButton;
+ public CroppedEdge croppedEdge;
+ public int leadingTabIndex;
+
+ private Point tabViewPosition = new Point(0,0);
+
+ ScrollableTabSupport(int tabPlacement) {
+ viewport = new ScrollableTabViewport();
+ tabPanel = new ScrollableTabPanel();
+ viewport.setView(tabPanel);
+ viewport.addChangeListener(this);
+ croppedEdge = new CroppedEdge();
+ createButtons();
+ }
+
+ /**
+ * Recreates the scroll buttons and adds them to the TabbedPane.
+ */
+ void createButtons() {
+ if (scrollForwardButton != null) {
+ tabPane.remove(scrollForwardButton);
+ scrollForwardButton.removeActionListener(this);
+ tabPane.remove(scrollBackwardButton);
+ scrollBackwardButton.removeActionListener(this);
+ }
+ int tabPlacement = tabPane.getTabPlacement();
+ if (tabPlacement == TOP || tabPlacement == BOTTOM) {
+ scrollForwardButton = createScrollButton(EAST);
+ scrollBackwardButton = createScrollButton(WEST);
+
+ } else { // tabPlacement = LEFT || RIGHT
+ scrollForwardButton = createScrollButton(SOUTH);
+ scrollBackwardButton = createScrollButton(NORTH);
+ }
+ scrollForwardButton.addActionListener(this);
+ scrollBackwardButton.addActionListener(this);
+ tabPane.add(scrollForwardButton);
+ tabPane.add(scrollBackwardButton);
+ }
+
+ public void scrollForward(int tabPlacement) {
+ Dimension viewSize = viewport.getViewSize();
+ Rectangle viewRect = viewport.getViewRect();
+
+ if (tabPlacement == TOP || tabPlacement == BOTTOM) {
+ if (viewRect.width >= viewSize.width - viewRect.x) {
+ return; // no room left to scroll
+ }
+ } else { // tabPlacement == LEFT || tabPlacement == RIGHT
+ if (viewRect.height >= viewSize.height - viewRect.y) {
+ return;
+ }
+ }
+ setLeadingTabIndex(tabPlacement, leadingTabIndex+1);
+ }
+
+ public void scrollBackward(int tabPlacement) {
+ if (leadingTabIndex == 0) {
+ return; // no room left to scroll
+ }
+ setLeadingTabIndex(tabPlacement, leadingTabIndex-1);
+ }
+
+ public void setLeadingTabIndex(int tabPlacement, int index) {
+ leadingTabIndex = index;
+ Dimension viewSize = viewport.getViewSize();
+ Rectangle viewRect = viewport.getViewRect();
+
+ switch(tabPlacement) {
+ case TOP:
+ case BOTTOM:
+ tabViewPosition.x = leadingTabIndex == 0? 0 : rects[leadingTabIndex].x;
+
+ if ((viewSize.width - tabViewPosition.x) < viewRect.width) {
+ // We've scrolled to the end, so adjust the viewport size
+ // to ensure the view position remains aligned on a tab boundary
+ Dimension extentSize = new Dimension(viewSize.width - tabViewPosition.x,
+ viewRect.height);
+ viewport.setExtentSize(extentSize);
+ }
+ break;
+ case LEFT:
+ case RIGHT:
+ tabViewPosition.y = leadingTabIndex == 0? 0 : rects[leadingTabIndex].y;
+
+ if ((viewSize.height - tabViewPosition.y) < viewRect.height) {
+ // We've scrolled to the end, so adjust the viewport size
+ // to ensure the view position remains aligned on a tab boundary
+ Dimension extentSize = new Dimension(viewRect.width,
+ viewSize.height - tabViewPosition.y);
+ viewport.setExtentSize(extentSize);
+ }
+ }
+ viewport.setViewPosition(tabViewPosition);
+ }
+
+ public void stateChanged(ChangeEvent e) {
+ updateView();
+ }
+
+ private void updateView() {
+ int tabPlacement = tabPane.getTabPlacement();
+ int tabCount = tabPane.getTabCount();
+ assureRectsCreated(tabCount);
+ Rectangle vpRect = viewport.getBounds();
+ Dimension viewSize = viewport.getViewSize();
+ Rectangle viewRect = viewport.getViewRect();
+
+ leadingTabIndex = getClosestTab(viewRect.x, viewRect.y);
+
+ // If the tab isn't right aligned, adjust it.
+ if (leadingTabIndex + 1 < tabCount) {
+ switch (tabPlacement) {
+ case TOP:
+ case BOTTOM:
+ if (rects[leadingTabIndex].x < viewRect.x) {
+ leadingTabIndex++;
+ }
+ break;
+ case LEFT:
+ case RIGHT:
+ if (rects[leadingTabIndex].y < viewRect.y) {
+ leadingTabIndex++;
+ }
+ break;
+ }
+ }
+ Insets contentInsets = getContentBorderInsets(tabPlacement);
+ switch(tabPlacement) {
+ case LEFT:
+ tabPane.repaint(vpRect.x+vpRect.width, vpRect.y,
+ contentInsets.left, vpRect.height);
+ scrollBackwardButton.setEnabled(
+ viewRect.y > 0 && leadingTabIndex > 0);
+ scrollForwardButton.setEnabled(
+ leadingTabIndex < tabCount-1 &&
+ viewSize.height-viewRect.y > viewRect.height);
+ break;
+ case RIGHT:
+ tabPane.repaint(vpRect.x-contentInsets.right, vpRect.y,
+ contentInsets.right, vpRect.height);
+ scrollBackwardButton.setEnabled(
+ viewRect.y > 0 && leadingTabIndex > 0);
+ scrollForwardButton.setEnabled(
+ leadingTabIndex < tabCount-1 &&
+ viewSize.height-viewRect.y > viewRect.height);
+ break;
+ case BOTTOM:
+ tabPane.repaint(vpRect.x, vpRect.y-contentInsets.bottom,
+ vpRect.width, contentInsets.bottom);
+ scrollBackwardButton.setEnabled(
+ viewRect.x > 0 && leadingTabIndex > 0);
+ scrollForwardButton.setEnabled(
+ leadingTabIndex < tabCount-1 &&
+ viewSize.width-viewRect.x > viewRect.width);
+ break;
+ case TOP:
+ default:
+ tabPane.repaint(vpRect.x, vpRect.y+vpRect.height,
+ vpRect.width, contentInsets.top);
+ scrollBackwardButton.setEnabled(
+ viewRect.x > 0 && leadingTabIndex > 0);
+ scrollForwardButton.setEnabled(
+ leadingTabIndex < tabCount-1 &&
+ viewSize.width-viewRect.x > viewRect.width);
+ }
+ }
+
+ /**
+ * ActionListener for the scroll buttons.
+ */
+ public void actionPerformed(ActionEvent e) {
+ ActionMap map = tabPane.getActionMap();
+
+ if (map != null) {
+ String actionKey;
+
+ if (e.getSource() == scrollForwardButton) {
+ actionKey = "scrollTabsForwardAction";
+ }
+ else {
+ actionKey = "scrollTabsBackwardAction";
+ }
+ Action action = map.get(actionKey);
+
+ if (action != null && action.isEnabled()) {
+ action.actionPerformed(new ActionEvent(tabPane,
+ ActionEvent.ACTION_PERFORMED, null, e.getWhen(),
+ e.getModifiers()));
+ }
+ }
+ }
+
+ public String toString() {
+ return "viewport.viewSize=" + viewport.getViewSize() + "\n" +
+ "viewport.viewRectangle="+viewport.getViewRect()+"\n"+
+ "leadingTabIndex="+leadingTabIndex+"\n"+
+ "tabViewPosition=" + tabViewPosition;
+ }
+
+ }
+
+ @SuppressWarnings("serial") // Superclass is not serializable across versions
+ private class ScrollableTabViewport extends JViewport implements UIResource {
+ public ScrollableTabViewport() {
+ super();
+ setName("TabbedPane.scrollableViewport");
+ setScrollMode(SIMPLE_SCROLL_MODE);
+ setOpaque(tabPane.isOpaque());
+ Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground");
+ if (bgColor == null) {
+ bgColor = tabPane.getBackground();
+ }
+ setBackground(bgColor);
+ }
+ }
+
+ @SuppressWarnings("serial") // Superclass is not serializable across versions
+ private class ScrollableTabPanel extends JPanel implements UIResource {
+ public ScrollableTabPanel() {
+ super(null);
+ setOpaque(tabPane.isOpaque());
+ Color bgColor = UIManager.getColor("TabbedPane.tabAreaBackground");
+ if (bgColor == null) {
+ bgColor = tabPane.getBackground();
+ }
+ setBackground(bgColor);
+ }
+ public void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ BasicTabbedPaneUI.this.paintTabArea(g, tabPane.getTabPlacement(),
+ tabPane.getSelectedIndex());
+ if (tabScroller.croppedEdge.isParamsSet() && tabContainer == null) {
+ Rectangle croppedRect = rects[tabScroller.croppedEdge.getTabIndex()];
+ g.translate(croppedRect.x, croppedRect.y);
+ tabScroller.croppedEdge.paintComponent(g);
+ g.translate(-croppedRect.x, -croppedRect.y);
+ }
+ }
+
+ public void doLayout() {
+ if (getComponentCount() > 0) {
+ Component child = getComponent(0);
+ child.setBounds(0, 0, getWidth(), getHeight());
+ }
+ }
+ }
+
+ @SuppressWarnings("serial") // Superclass is not serializable across versions
+ private class ScrollableTabButton extends BasicArrowButton implements UIResource,
+ SwingConstants {
+ public ScrollableTabButton(int direction) {
+ super(direction,
+ UIManager.getColor("TabbedPane.selected"),
+ UIManager.getColor("TabbedPane.shadow"),
+ UIManager.getColor("TabbedPane.darkShadow"),
+ UIManager.getColor("TabbedPane.highlight"));
+ }
+ }
+
+
+// Controller: event listeners
+
+ private class Handler implements ChangeListener, ContainerListener,
+ FocusListener, MouseListener, MouseMotionListener,
+ PropertyChangeListener {
+ //
+ // PropertyChangeListener
+ //
+ public void propertyChange(PropertyChangeEvent e) {
+ JTabbedPane pane = (JTabbedPane)e.getSource();
+ String name = e.getPropertyName();
+ boolean isScrollLayout = scrollableTabLayoutEnabled();
+ if (name == "mnemonicAt") {
+ updateMnemonics();
+ pane.repaint();
+ }
+ else if (name == "displayedMnemonicIndexAt") {
+ pane.repaint();
+ }
+ else if (name =="indexForTitle") {
+ calculatedBaseline = false;
+ Integer index = (Integer) e.getNewValue();
+ updateHtmlViews(index, false);
+ } else if (name == "tabLayoutPolicy") {
+ BasicTabbedPaneUI.this.uninstallUI(pane);
+ BasicTabbedPaneUI.this.installUI(pane);
+ calculatedBaseline = false;
+ } else if (name == "tabPlacement") {
+ if (scrollableTabLayoutEnabled()) {
+ tabScroller.createButtons();
+ }
+ calculatedBaseline = false;
+ } else if (name == "opaque" && isScrollLayout) {
+ boolean newVal = ((Boolean)e.getNewValue()).booleanValue();
+ tabScroller.tabPanel.setOpaque(newVal);
+ tabScroller.viewport.setOpaque(newVal);
+ } else if (name == "background" && isScrollLayout) {
+ Color newVal = (Color)e.getNewValue();
+ tabScroller.tabPanel.setBackground(newVal);
+ tabScroller.viewport.setBackground(newVal);
+ Color newColor = selectedColor == null ? newVal : selectedColor;
+ tabScroller.scrollForwardButton.setBackground(newColor);
+ tabScroller.scrollBackwardButton.setBackground(newColor);
+ } else if (name == "indexForTabComponent") {
+ if (tabContainer != null) {
+ tabContainer.removeUnusedTabComponents();
+ }
+ Component c = tabPane.getTabComponentAt(
+ (Integer)e.getNewValue());
+ if (c != null) {
+ if (tabContainer == null) {
+ installTabContainer();
+ } else {
+ tabContainer.add(c);
+ }
+ }
+ tabPane.revalidate();
+ tabPane.repaint();
+ calculatedBaseline = false;
+ } else if (name == "indexForNullComponent") {
+ isRunsDirty = true;
+ updateHtmlViews((Integer)e.getNewValue(), true);
+ } else if (name == "font") {
+ calculatedBaseline = false;
+ }
+ }
+
+ private void updateHtmlViews(int index, boolean inserted) {
+ String title = tabPane.getTitleAt(index);
+ boolean isHTML = BasicHTML.isHTMLString(title);
+ if (isHTML) {
+ if (htmlViews==null) { // Initialize vector
+ htmlViews = createHTMLVector();
+ } else { // Vector already exists
+ View v = BasicHTML.createHTMLView(tabPane, title);
+ setHtmlView(v, inserted, index);
+ }
+ } else { // Not HTML
+ if (htmlViews!=null) { // Add placeholder
+ setHtmlView(null, inserted, index);
+ } // else nada!
+ }
+ updateMnemonics();
+ }
+
+ private void setHtmlView(View v, boolean inserted, int index) {
+ if (inserted || index >= htmlViews.size()) {
+ htmlViews.insertElementAt(v, index);
+ } else {
+ htmlViews.setElementAt(v, index);
+ }
+ }
+
+ //
+ // ChangeListener
+ //
+ public void stateChanged(ChangeEvent e) {
+ JTabbedPane tabPane = (JTabbedPane)e.getSource();
+ tabPane.revalidate();
+ tabPane.repaint();
+
+ setFocusIndex(tabPane.getSelectedIndex(), false);
+
+ if (scrollableTabLayoutEnabled()) {
+ ensureCurrentLayout();
+ int index = tabPane.getSelectedIndex();
+ if (index < rects.length && index != -1) {
+ tabScroller.tabPanel.scrollRectToVisible(
+ (Rectangle)rects[index].clone());
+ }
+ }
+ }
+
+ //
+ // MouseListener
+ //
+ public void mouseClicked(MouseEvent e) {
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ }
+
+ public void mouseEntered(MouseEvent e) {
+ setRolloverTab(e.getX(), e.getY());
+ }
+
+ public void mouseExited(MouseEvent e) {
+ setRolloverTab(-1);
+ }
+
+ public void mousePressed(MouseEvent e) {
+ if (!tabPane.isEnabled()) {
+ return;
+ }
+ int tabIndex = tabForCoordinate(tabPane, e.getX(), e.getY());
+ if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) {
+ if (tabIndex != tabPane.getSelectedIndex()) {
+ // Clicking on unselected tab, change selection, do NOT
+ // request focus.
+ // This will trigger the focusIndex to change by way
+ // of stateChanged.
+ tabPane.setSelectedIndex(tabIndex);
+ }
+ else if (tabPane.isRequestFocusEnabled()) {
+ // Clicking on selected tab, try and give the tabbedpane
+ // focus. Repaint will occur in focusGained.
+ tabPane.requestFocus();
+ }
+ }
+ }
+
+ //
+ // MouseMotionListener
+ //
+ public void mouseDragged(MouseEvent e) {
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ setRolloverTab(e.getX(), e.getY());
+ }
+
+ //
+ // FocusListener
+ //
+ public void focusGained(FocusEvent e) {
+ setFocusIndex(tabPane.getSelectedIndex(), true);
+ }
+ public void focusLost(FocusEvent e) {
+ repaintTab(focusIndex);
+ }
+
+
+ //
+ // ContainerListener
+ //
+ /* GES 2/3/99:
+ The container listener code was added to support HTML
+ rendering of tab titles.
+
+ Ideally, we would be able to listen for property changes
+ when a tab is added or its text modified. At the moment
+ there are no such events because the Beans spec doesn't
+ allow 'indexed' property changes (i.e. tab 2's text changed
+ from A to B).
+
+ In order to get around this, we listen for tabs to be added
+ or removed by listening for the container events. we then
+ queue up a runnable (so the component has a chance to complete
+ the add) which checks the tab title of the new component to see
+ if it requires HTML rendering.
+
+ The Views (one per tab title requiring HTML rendering) are
+ stored in the htmlViews Vector, which is only allocated after
+ the first time we run into an HTML tab. Note that this vector
+ is kept in step with the number of pages, and nulls are added
+ for those pages whose tab title do not require HTML rendering.
+
+ This makes it easy for the paint and layout code to tell
+ whether to invoke the HTML engine without having to check
+ the string during time-sensitive operations.
+
+ When we have added a way to listen for tab additions and
+ changes to tab text, this code should be removed and
+ replaced by something which uses that. */
+
+ public void componentAdded(ContainerEvent e) {
+ JTabbedPane tp = (JTabbedPane)e.getContainer();
+ Component child = e.getChild();
+ if (child instanceof UIResource) {
+ return;
+ }
+ isRunsDirty = true;
+ updateHtmlViews(tp.indexOfComponent(child), true);
+ }
+ public void componentRemoved(ContainerEvent e) {
+ JTabbedPane tp = (JTabbedPane)e.getContainer();
+ Component child = e.getChild();
+ if (child instanceof UIResource) {
+ return;
+ }
+
+ // NOTE 4/15/2002 (joutwate):
+ // This fix is implemented using client properties since there is
+ // currently no IndexPropertyChangeEvent. Once
+ // IndexPropertyChangeEvents have been added this code should be
+ // modified to use it.
+ Integer indexObj =
+ (Integer)tp.getClientProperty("__index_to_remove__");
+ if (indexObj != null) {
+ int index = indexObj.intValue();
+ if (htmlViews != null && htmlViews.size() > index) {
+ htmlViews.removeElementAt(index);
+ }
+ tp.putClientProperty("__index_to_remove__", null);
+ }
+ isRunsDirty = true;
+ updateMnemonics();
+
+ validateFocusIndex();
+ }
+ }
+
+ /**
+ * This class should be treated as a "protected" inner class.
+ * Instantiate it only within subclasses of BasicTabbedPaneUI.
+ */
+ public class PropertyChangeHandler implements PropertyChangeListener {
+ // NOTE: This class exists only for backward compatibility. All
+ // its functionality has been moved into Handler. If you need to add
+ // new functionality add it to the Handler, but make sure this
+ // class calls into the Handler.
+ public void propertyChange(PropertyChangeEvent e) {
+ getHandler().propertyChange(e);
+ }
+ }
+
+ /**
+ * This class should be treated as a "protected" inner class.
+ * Instantiate it only within subclasses of BasicTabbedPaneUI.
+ */
+ public class TabSelectionHandler implements ChangeListener {
+ // NOTE: This class exists only for backward compatibility. All
+ // its functionality has been moved into Handler. If you need to add
+ // new functionality add it to the Handler, but make sure this
+ // class calls into the Handler.
+ public void stateChanged(ChangeEvent e) {
+ getHandler().stateChanged(e);
+ }
+ }
+
+ /**
+ * This class should be treated as a "protected" inner class.
+ * Instantiate it only within subclasses of BasicTabbedPaneUI.
+ */
+ public class MouseHandler extends MouseAdapter {
+ // NOTE: This class exists only for backward compatibility. All
+ // its functionality has been moved into Handler. If you need to add
+ // new functionality add it to the Handler, but make sure this
+ // class calls into the Handler.
+ public void mousePressed(MouseEvent e) {
+ getHandler().mousePressed(e);
+ }
+ }
+
+ /**
+ * This class should be treated as a "protected" inner class.
+ * Instantiate it only within subclasses of BasicTabbedPaneUI.
+ */
+ public class FocusHandler extends FocusAdapter {
+ // NOTE: This class exists only for backward compatibility. All
+ // its functionality has been moved into Handler. If you need to add
+ // new functionality add it to the Handler, but make sure this
+ // class calls into the Handler.
+ public void focusGained(FocusEvent e) {
+ getHandler().focusGained(e);
+ }
+ public void focusLost(FocusEvent e) {
+ getHandler().focusLost(e);
+ }
+ }
+
+ private Vector<View> createHTMLVector() {
+ Vector<View> htmlViews = new Vector<View>();
+ int count = tabPane.getTabCount();
+ if (count>0) {
+ for (int i=0 ; i<count; i++) {
+ String title = tabPane.getTitleAt(i);
+ if (BasicHTML.isHTMLString(title)) {
+ htmlViews.addElement(BasicHTML.createHTMLView(tabPane, title));
+ } else {
+ htmlViews.addElement(null);
+ }
+ }
+ }
+ return htmlViews;
+ }
+
+ @SuppressWarnings("serial") // Superclass is not serializable across versions
+ private class TabContainer extends JPanel implements UIResource {
+ private boolean notifyTabbedPane = true;
+
+ public TabContainer() {
+ super(null);
+ setOpaque(false);
+ }
+
+ public void remove(Component comp) {
+ int index = tabPane.indexOfTabComponent(comp);
+ super.remove(comp);
+ if (notifyTabbedPane && index != -1) {
+ tabPane.setTabComponentAt(index, null);
+ }
+ }
+
+ private void removeUnusedTabComponents() {
+ for (Component c : getComponents()) {
+ if (!(c instanceof UIResource)) {
+ int index = tabPane.indexOfTabComponent(c);
+ if (index == -1) {
+ super.remove(c);
+ }
+ }
+ }
+ }
+
+ public boolean isOptimizedDrawingEnabled() {
+ return tabScroller != null && !tabScroller.croppedEdge.isParamsSet();
+ }
+
+ public void doLayout() {
+ // We layout tabComponents in JTabbedPane's layout manager
+ // and use this method as a hook for repainting tabs
+ // to update tabs area e.g. when the size of tabComponent was changed
+ if (scrollableTabLayoutEnabled()) {
+ tabScroller.tabPanel.repaint();
+ tabScroller.updateView();
+ } else {
+ tabPane.repaint(getBounds());
+ }
+ }
+ }
+
+ @SuppressWarnings("serial") // Superclass is not serializable across versions
+ private class CroppedEdge extends JPanel implements UIResource {
+ private Shape shape;
+ private int tabIndex;
+ private int cropline;
+ private int cropx, cropy;
+
+ public CroppedEdge() {
+ setOpaque(false);
+ }
+
+ public void setParams(int tabIndex, int cropline, int cropx, int cropy) {
+ this.tabIndex = tabIndex;
+ this.cropline = cropline;
+ this.cropx = cropx;
+ this.cropy = cropy;
+ Rectangle tabRect = rects[tabIndex];
+ setBounds(tabRect);
+ shape = createCroppedTabShape(tabPane.getTabPlacement(), tabRect, cropline);
+ if (getParent() == null && tabContainer != null) {
+ tabContainer.add(this, 0);
+ }
+ }
+
+ public void resetParams() {
+ shape = null;
+ if (getParent() == tabContainer && tabContainer != null) {
+ tabContainer.remove(this);
+ }
+ }
+
+ public boolean isParamsSet() {
+ return shape != null;
+ }
+
+ public int getTabIndex() {
+ return tabIndex;
+ }
+
+ public int getCropline() {
+ return cropline;
+ }
+
+ public int getCroppedSideWidth() {
+ return 3;
+ }
+
+ private Color getBgColor() {
+ Component parent = tabPane.getParent();
+ if (parent != null) {
+ Color bg = parent.getBackground();
+ if (bg != null) {
+ return bg;
+ }
+ }
+ return UIManager.getColor("control");
+ }
+
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ if (isParamsSet() && g instanceof Graphics2D) {
+ Graphics2D g2 = (Graphics2D) g;
+ g2.clipRect(0, 0, getWidth(), getHeight());
+ g2.setColor(getBgColor());
+ g2.translate(cropx, cropy);
+ g2.fill(shape);
+ paintCroppedTabEdge(g);
+ g2.translate(-cropx, -cropy);
+ }
+ }
+ }
+}