0001: /*
0002: * CloseTabbedPaneUI.java
0003: *
0004: * Copyright (C) 2002, 2003, 2004, 2005, 2006 Takis Diakoumis
0005: *
0006: * This program is free software; you can redistribute it and/or
0007: * modify it under the terms of the GNU General Public License
0008: * as published by the Free Software Foundation; either version 2
0009: * of the License, or any later version.
0010: *
0011: * This program is distributed in the hope that it will be useful,
0012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0014: * GNU General Public License for more details.
0015: *
0016: * You should have received a copy of the GNU General Public License
0017: * along with this program; if not, write to the Free Software
0018: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
0019: *
0020: */
0021:
0022: package org.underworldlabs.swing.plaf;
0023:
0024: import java.awt.Color;
0025: import java.awt.Component;
0026: import java.awt.Container;
0027: import java.awt.Dimension;
0028: import java.awt.Event;
0029: import java.awt.Font;
0030: import java.awt.FontMetrics;
0031: import java.awt.Graphics;
0032: import java.awt.Graphics2D;
0033: import java.awt.Insets;
0034: import java.awt.LayoutManager;
0035: import java.awt.Point;
0036: import java.awt.Polygon;
0037: import java.awt.Rectangle;
0038: import java.awt.Shape; //import java.awt.Toolkit;
0039:
0040: import java.awt.event.ActionEvent;
0041: import java.awt.event.ActionListener;
0042: import java.awt.event.ContainerEvent;
0043: import java.awt.event.ContainerListener;
0044: import java.awt.event.FocusAdapter;
0045: import java.awt.event.FocusEvent;
0046: import java.awt.event.FocusListener;
0047: import java.awt.event.MouseEvent;
0048: import java.awt.event.MouseListener;
0049: import java.awt.event.MouseMotionListener;
0050:
0051: import java.beans.PropertyChangeEvent;
0052: import java.beans.PropertyChangeListener;
0053:
0054: import java.util.Hashtable;
0055: import java.util.Vector;
0056:
0057: import javax.swing.AbstractAction;
0058: import javax.swing.ActionMap;
0059: import javax.swing.Icon;
0060: import javax.swing.InputMap;
0061: import javax.swing.JComponent;
0062: import javax.swing.JPanel;
0063: import javax.swing.JTabbedPane;
0064: import javax.swing.JViewport;
0065: import javax.swing.KeyStroke;
0066: import javax.swing.LookAndFeel;
0067: import javax.swing.SwingConstants;
0068: import javax.swing.SwingUtilities;
0069: import javax.swing.UIManager;
0070:
0071: import javax.swing.event.ChangeEvent;
0072: import javax.swing.event.ChangeListener;
0073: import javax.swing.plaf.ActionMapUIResource;
0074: import javax.swing.plaf.ComponentUI;
0075: import javax.swing.plaf.InputMapUIResource;
0076: import javax.swing.plaf.UIResource;
0077:
0078: import javax.swing.plaf.basic.BasicArrowButton;
0079: import javax.swing.plaf.basic.BasicGraphicsUtils;
0080: import javax.swing.plaf.basic.BasicHTML;
0081: import javax.swing.plaf.basic.BasicTabbedPaneUI;
0082:
0083: import javax.swing.text.View;
0084: import org.underworldlabs.swing.SimpleCloseTabbedPane;
0085: import org.underworldlabs.swing.CloseTabbedPane;
0086:
0087: //import org.executequery.util.IconUtilities;
0088:
0089: /*
0090: * @(#)CloseTabbedPaneUI.java 1.126 03/01/23
0091: *
0092: * Copyright 2003 Sun Microsystems, Inc. All rights reserved.
0093: * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
0094: */
0095:
0096: /* ----------------------------------------------------------
0097: * CVS NOTE: Changes to the CVS repository prior to the
0098: * release of version 3.0.0beta1 has meant a
0099: * resetting of CVS revision numbers.
0100: * ----------------------------------------------------------
0101: */
0102:
0103: /** This is a slight modification to the original BasicTabbedPaneUI.
0104: * It removes the heavy border and makes the selected tab bg white.
0105: * This is the beginning of a larger modification - at the moment it
0106: * really is purpose built for the nav panel and its white content panels
0107: * and gray lines.
0108: */
0109: /**
0110: *
0111: * @author Takis Diakoumis
0112: * @version $Revision: 1.5 $
0113: * @date $Date: 2006/07/15 10:32:09 $
0114: */
0115: public class CloseTabbedPaneUI extends BasicTabbedPaneUI implements
0116: SwingConstants {
0117:
0118: // Instance variables initialized at installation
0119:
0120: protected JTabbedPane tabPane;
0121:
0122: protected Color highlight;
0123: protected Color lightHighlight;
0124: protected Color shadow;
0125: protected Color darkShadow;
0126: protected Color focus;
0127:
0128: private Color activeColor;
0129: private Color unselectedBackground;
0130: private Color controlShadow;
0131: //private Color unselectedBackground;
0132:
0133: protected int textIconGap;
0134:
0135: protected int tabRunOverlay;
0136:
0137: protected Insets tabInsets;
0138: protected Insets selectedTabPadInsets;
0139: protected Insets tabAreaInsets;
0140: protected Insets contentBorderInsets;
0141:
0142: // Transient variables (recalculated each time TabbedPane is layed out)
0143:
0144: protected int tabRuns[] = new int[10];
0145: protected int runCount = 0;
0146: protected int selectedRun = -1;
0147: protected Rectangle rects[] = new Rectangle[0];
0148: protected int maxTabHeight;
0149: protected int maxTabWidth;
0150:
0151: // Listeners
0152:
0153: /*
0154: protected ChangeListener tabChangeListener;
0155: protected PropertyChangeListener propertyChangeListener;
0156: protected MouseListener mouseListener;
0157: protected FocusListener focusListener;
0158: */
0159:
0160: // PENDING(api): See comment for ContainerHandler
0161: private ContainerListener containerListener;
0162:
0163: // Private instance data
0164:
0165: private Insets currentPadInsets = new Insets(0, 0, 0, 0);
0166: private Insets currentTabAreaInsets = new Insets(0, 0, 0, 0);
0167:
0168: private Component visibleComponent;
0169: // PENDING(api): See comment for ContainerHandler
0170: private Vector htmlViews;
0171:
0172: private Hashtable mnemonicToIndexMap;
0173:
0174: /**
0175: * InputMap used for mnemonics. Only non-null if the JTabbedPane has
0176: * mnemonics associated with it. Lazily created in initMnemonics.
0177: */
0178: private InputMap mnemonicInputMap;
0179:
0180: // For use when tabLayoutPolicy = SCROLL_TAB_LAYOUT
0181: private ScrollableTabSupport tabScroller;
0182:
0183: /**
0184: * A rectangle used for general layout calculations in order
0185: * to avoid constructing many new Rectangles on the fly.
0186: */
0187: protected transient Rectangle calcRect = new Rectangle(0, 0, 0, 0);
0188:
0189: /**
0190: * Number of tabs. When the count differs, the mnemonics are updated.
0191: */
0192: // PENDING: This wouldn't be necessary if JTabbedPane had a better
0193: // way of notifying listeners when the count changed.
0194: private int tabCount;
0195:
0196: // UI creation
0197:
0198: public static ComponentUI createUI(JComponent c) {
0199: return new CloseTabbedPaneUI();
0200: }
0201:
0202: // UI Installation/De-installation
0203:
0204: public void installUI(JComponent c) {
0205: this .tabPane = (JTabbedPane) c;
0206:
0207: c.setLayout(createLayoutManager());
0208: installComponents();
0209: installDefaults();
0210: installListeners();
0211: installKeyboardActions();
0212: }
0213:
0214: public void uninstallUI(JComponent c) {
0215: //System.out.println("XX");
0216: uninstallKeyboardActions();
0217: uninstallListeners();
0218: uninstallDefaults();
0219: uninstallComponents();
0220: c.setLayout(null);
0221:
0222: this .tabPane = null;
0223: }
0224:
0225: /**
0226: * Invoked by <code>installUI</code> to create
0227: * a layout manager object to manage
0228: * the <code>JTabbedPane</code>.
0229: *
0230: * @return a layout manager object
0231: *
0232: * @see TabbedPaneLayout
0233: * @see javax.swing.JTabbedPane#getTabLayoutPolicy
0234: */
0235: protected LayoutManager createLayoutManager() {
0236: if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
0237: return new TabbedPaneScrollLayout();
0238: } else { /* WRAP_TAB_LAYOUT */
0239: return new TabbedPaneLayout();
0240: }
0241: }
0242:
0243: /* In an attempt to preserve backward compatibility for programs
0244: * which have extended CloseTabbedPaneUI to do their own layout, the
0245: * UI uses the installed layoutManager (and not tabLayoutPolicy) to
0246: * determine if scrollTabLayout is enabled.
0247: */
0248: private boolean scrollableTabLayoutEnabled() {
0249: return true;
0250: //return (tabPane.getLayout() instanceof TabbedPaneScrollLayout);
0251: }
0252:
0253: /**
0254: * Creates and installs any required subcomponents for the JTabbedPane.
0255: * Invoked by installUI.
0256: *
0257: * @since 1.4
0258: */
0259: protected void installComponents() {
0260: if (scrollableTabLayoutEnabled()) {
0261: if (tabScroller == null) {
0262: tabScroller = new ScrollableTabSupport(tabPane
0263: .getTabPlacement());
0264: tabPane.add(tabScroller.viewport);
0265: tabPane.add(tabScroller.scrollForwardButton);
0266: tabPane.add(tabScroller.scrollBackwardButton);
0267: }
0268: }
0269: }
0270:
0271: /**
0272: * Removes any installed subcomponents from the JTabbedPane.
0273: * Invoked by uninstallUI.
0274: *
0275: * @since 1.4
0276: */
0277: protected void uninstallComponents() {
0278: if (scrollableTabLayoutEnabled()) {
0279: tabPane.remove(tabScroller.viewport);
0280: tabPane.remove(tabScroller.scrollForwardButton);
0281: tabPane.remove(tabScroller.scrollBackwardButton);
0282: tabScroller = null;
0283: }
0284: }
0285:
0286: protected void installDefaults() {
0287: LookAndFeel.installColorsAndFont(tabPane,
0288: "TabbedPane.background", "TabbedPane.foreground",
0289: "TabbedPane.font");
0290:
0291: highlight = UIManager.getColor("TabbedPane.light");
0292: lightHighlight = UIManager.getColor("TabbedPane.highlight");
0293: shadow = UIManager.getColor("TabbedPane.shadow");
0294: darkShadow = UIManager.getColor("TabbedPane.darkShadow");
0295: focus = UIManager.getColor("TabbedPane.focus");
0296: controlShadow = UIManager.getColor("controlShadow");
0297:
0298: activeColor = UIManager.getColor("TabbedPane.selected");
0299: if (activeColor == null) {
0300: activeColor = UIManager.getColor("TabbedPane.background");
0301:
0302: if (activeColor == null) { // if still null (some l&f)
0303: activeColor = UIManager.getColor("control");
0304: }
0305:
0306: }
0307:
0308: unselectedBackground = UIManager
0309: .getColor("TabbedPane.unselectedBackground");
0310: if (unselectedBackground == null) {
0311: unselectedBackground = UIManager
0312: .getColor("TabbedPane.unselectedTabBackground");
0313:
0314: if (unselectedBackground == null) { // if still null (some l&f)
0315: unselectedBackground = UIManager
0316: .getColor("controlLightShadow");
0317: if (unselectedBackground == null) { // if still null - give up
0318: unselectedBackground = tabPane.getBackground()
0319: .darker();
0320: }
0321: }
0322:
0323: }
0324:
0325: // make the selected colour a little darker
0326: //unselectedBackground = unselectedBackground.darker();
0327:
0328: textIconGap = 2;
0329: //textIconGap = UIManager.getInt("TabbedPane.textIconGap");
0330:
0331: tabInsets = UIManager.getInsets("TabbedPane.tabInsets");
0332:
0333: selectedTabPadInsets = UIManager
0334: .getInsets("TabbedPane.selectedTabPadInsets");
0335:
0336: tabAreaInsets = new Insets(2, 0, 0, 6);
0337: //tabAreaInsets = UIManager.getInsets("TabbedPane.tabAreaInsets");
0338:
0339: contentBorderInsets = new Insets(1, 1, 1, 1);
0340: //contentBorderInsets = UIManager.getInsets("TabbedPane.contentBorderInsets");
0341:
0342: tabRunOverlay = UIManager.getInt("TabbedPane.tabRunOverlay");
0343:
0344: }
0345:
0346: protected void uninstallDefaults() {
0347: highlight = null;
0348: lightHighlight = null;
0349: shadow = null;
0350: darkShadow = null;
0351: focus = null;
0352: tabInsets = null;
0353: selectedTabPadInsets = null;
0354: tabAreaInsets = null;
0355: contentBorderInsets = null;
0356: }
0357:
0358: private Handler handler = new Handler();
0359:
0360: protected void installListeners() {
0361: if ((propertyChangeListener = createPropertyChangeListener()) != null) {
0362: tabPane.addPropertyChangeListener(propertyChangeListener);
0363: }
0364: if ((tabChangeListener = createChangeListener()) != null) {
0365: tabPane.addChangeListener(tabChangeListener);
0366: }
0367: if ((mouseListener = createMouseListener()) != null) {
0368: if (scrollableTabLayoutEnabled()) {
0369: tabScroller.tabPanel.addMouseListener(mouseListener);
0370: tabScroller.tabPanel
0371: .addMouseMotionListener((MouseMotionListener) mouseListener);
0372: } else { // WRAP_TAB_LAYOUT
0373: tabPane.addMouseListener(mouseListener);
0374: }
0375: }
0376: if ((focusListener = createFocusListener()) != null) {
0377: tabPane.addFocusListener(focusListener);
0378: }
0379: // PENDING(api) : See comment for ContainerHandler
0380: if ((containerListener = new ContainerHandler()) != null) {
0381: tabPane.addContainerListener(containerListener);
0382: if (tabPane.getTabCount() > 0) {
0383: htmlViews = createHTMLVector();
0384: }
0385: }
0386: }
0387:
0388: protected void uninstallListeners() {
0389: if (mouseListener != null) {
0390: if (scrollableTabLayoutEnabled()) { // SCROLL_TAB_LAYOUT
0391: tabScroller.tabPanel.removeMouseListener(mouseListener);
0392:
0393: } else { // WRAP_TAB_LAYOUT
0394: tabPane.removeMouseListener(mouseListener);
0395: }
0396: mouseListener = null;
0397: }
0398: if (focusListener != null) {
0399: tabPane.removeFocusListener(focusListener);
0400: focusListener = null;
0401: }
0402:
0403: // PENDING(api): See comment for ContainerHandler
0404: if (containerListener != null) {
0405: tabPane.removeContainerListener(containerListener);
0406: containerListener = null;
0407: if (htmlViews != null) {
0408: htmlViews.removeAllElements();
0409: htmlViews = null;
0410: }
0411: }
0412: if (tabChangeListener != null) {
0413: tabPane.removeChangeListener(tabChangeListener);
0414: tabChangeListener = null;
0415: }
0416: if (propertyChangeListener != null) {
0417: tabPane
0418: .removePropertyChangeListener(propertyChangeListener);
0419: propertyChangeListener = null;
0420: }
0421: }
0422:
0423: protected MouseListener createMouseListener() {
0424: return handler;//new MouseHandler();
0425: }
0426:
0427: protected FocusListener createFocusListener() {
0428: return handler;//new FocusHandler();
0429: }
0430:
0431: protected ChangeListener createChangeListener() {
0432: return handler;//new TabSelectionHandler();
0433: }
0434:
0435: protected PropertyChangeListener createPropertyChangeListener() {
0436: return handler;//new PropertyChangeHandler();
0437: }
0438:
0439: protected void installKeyboardActions() {
0440: InputMap km = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
0441:
0442: SwingUtilities.replaceUIInputMap(tabPane,
0443: JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, km);
0444: km = getInputMap(JComponent.WHEN_FOCUSED);
0445: SwingUtilities.replaceUIInputMap(tabPane,
0446: JComponent.WHEN_FOCUSED, km);
0447: ActionMap am = getActionMap();
0448:
0449: SwingUtilities.replaceUIActionMap(tabPane, am);
0450:
0451: /*
0452: if (scrollableTabLayoutEnabled()) {
0453: tabScroller.scrollForwardButton.setAction(am.get("scrollTabsForwardAction"));
0454: tabScroller.scrollBackwardButton.setAction(am.get("scrollTabsBackwardAction"));
0455: }
0456: */
0457: }
0458:
0459: InputMap getInputMap(int condition) {
0460: if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
0461: return (InputMap) UIManager
0462: .get("TabbedPane.ancestorInputMap");
0463: } else if (condition == JComponent.WHEN_FOCUSED) {
0464: return (InputMap) UIManager.get("TabbedPane.focusInputMap");
0465: }
0466: return null;
0467: }
0468:
0469: ActionMap getActionMap() {
0470: ActionMap map = (ActionMap) UIManager
0471: .get("TabbedPane.actionMap");
0472:
0473: if (map == null) {
0474: map = createActionMap();
0475: if (map != null) {
0476: UIManager.getLookAndFeelDefaults().put(
0477: "TabbedPane.actionMap", map);
0478: }
0479: }
0480: return map;
0481: }
0482:
0483: ActionMap createActionMap() {
0484: ActionMap map = new ActionMapUIResource();
0485: map.put("navigateNext", new NextAction());
0486: map.put("navigatePrevious", new PreviousAction());
0487: map.put("navigateRight", new RightAction());
0488: map.put("navigateLeft", new LeftAction());
0489: map.put("navigateUp", new UpAction());
0490: map.put("navigateDown", new DownAction());
0491: map.put("navigatePageUp", new PageUpAction());
0492: map.put("navigatePageDown", new PageDownAction());
0493: map.put("requestFocus", new RequestFocusAction());
0494: map.put("requestFocusForVisibleComponent",
0495: new RequestFocusForVisibleAction());
0496: map.put("setSelectedIndex", new SetSelectedIndexAction());
0497: // map.put("scrollTabsForwardAction", new ScrollTabsForwardAction());
0498: // map.put("scrollTabsBackwardAction",new ScrollTabsBackwardAction());
0499: return map;
0500: }
0501:
0502: /*
0503: protected void uninstallKeyboardActions() {
0504: SwingUtilities.replaceUIActionMap(tabPane, null);
0505: SwingUtilities.replaceUIInputMap(tabPane,
0506: JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
0507: null);
0508: SwingUtilities.replaceUIInputMap(tabPane,
0509: JComponent.WHEN_FOCUSED,
0510: null);
0511: }
0512: */
0513:
0514: /**
0515: * Reloads the mnemonics. This should be invoked when a memonic changes,
0516: * when the title of a mnemonic changes, or when tabs are added/removed.
0517: */
0518: private void updateMnemonics() {
0519: resetMnemonics();
0520: for (int counter = tabPane.getTabCount() - 1; counter >= 0; counter--) {
0521: int mnemonic = tabPane.getMnemonicAt(counter);
0522:
0523: if (mnemonic > 0) {
0524: addMnemonic(counter, mnemonic);
0525: }
0526: }
0527: }
0528:
0529: /**
0530: * Resets the mnemonics bindings to an empty state.
0531: */
0532: private void resetMnemonics() {
0533: if (mnemonicToIndexMap != null) {
0534: mnemonicToIndexMap.clear();
0535: mnemonicInputMap.clear();
0536: }
0537: }
0538:
0539: /**
0540: * Adds the specified mnemonic at the specified index.
0541: */
0542: private void addMnemonic(int index, int mnemonic) {
0543: if (mnemonicToIndexMap == null) {
0544: initMnemonics();
0545: }
0546: mnemonicInputMap.put(KeyStroke.getKeyStroke(mnemonic,
0547: Event.ALT_MASK), "setSelectedIndex");
0548: mnemonicToIndexMap.put(new Integer(mnemonic),
0549: new Integer(index));
0550: }
0551:
0552: /**
0553: * Installs the state needed for mnemonics.
0554: */
0555: private void initMnemonics() {
0556: mnemonicToIndexMap = new Hashtable();
0557: mnemonicInputMap = new InputMapUIResource();
0558: mnemonicInputMap
0559: .setParent(SwingUtilities.getUIInputMap(tabPane,
0560: JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT));
0561: SwingUtilities.replaceUIInputMap(tabPane,
0562: JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT,
0563: mnemonicInputMap);
0564: }
0565:
0566: // Geometry
0567:
0568: public Dimension getPreferredSize(JComponent c) {
0569: // Default to LayoutManager's preferredLayoutSize
0570: return null;
0571: }
0572:
0573: public Dimension getMinimumSize(JComponent c) {
0574: // Default to LayoutManager's minimumLayoutSize
0575: return null;
0576: }
0577:
0578: public Dimension getMaximumSize(JComponent c) {
0579: // Default to LayoutManager's maximumLayoutSize
0580: return null;
0581: }
0582:
0583: // UI Rendering
0584:
0585: public void paint(Graphics g, JComponent c) {
0586: int tc = tabPane.getTabCount();
0587:
0588: if (tabCount != tc) {
0589: tabCount = tc;
0590: //updateMnemonics();
0591: }
0592:
0593: int selectedIndex = tabPane.getSelectedIndex();
0594: int tabPlacement = tabPane.getTabPlacement();
0595:
0596: ensureCurrentLayout();
0597:
0598: // Paint tab area
0599: // If scrollable tabs are enabled, the tab area will be
0600: // painted by the scrollable tab panel instead.
0601: //
0602: if (!scrollableTabLayoutEnabled()) { // WRAP_TAB_LAYOUT
0603: paintTabArea(g, tabPlacement, selectedIndex);
0604: }
0605:
0606: // Paint content border
0607: paintContentBorder(g, tabPlacement, selectedIndex);
0608: }
0609:
0610: /**
0611: * Paints the tabs in the tab area.
0612: * Invoked by paint().
0613: * The graphics parameter must be a valid <code>Graphics</code>
0614: * object. Tab placement may be either:
0615: * <code>JTabbedPane.TOP</code>, <code>JTabbedPane.BOTTOM</code>,
0616: * <code>JTabbedPane.LEFT</code>, or <code>JTabbedPane.RIGHT</code>.
0617: * The selected index must be a valid tabbed pane tab index (0 to
0618: * tab count - 1, inclusive) or -1 if no tab is currently selected.
0619: * The handling of invalid parameters is unspecified.
0620: *
0621: * @param g the graphics object to use for rendering
0622: * @param tabPlacement the placement for the tabs within the JTabbedPane
0623: * @param selectedIndex the tab index of the selected component
0624: *
0625: * @since 1.4
0626: */
0627: protected void paintTabArea(Graphics g, int tabPlacement,
0628: int selectedIndex) {
0629: int tabCount = tabPane.getTabCount();
0630:
0631: Rectangle iconRect = new Rectangle(), textRect = new Rectangle();
0632: Rectangle clipRect = g.getClipBounds();
0633:
0634: // Paint tabRuns of tabs from back to front
0635: for (int i = runCount - 1; i >= 0; i--) {
0636: int start = tabRuns[i];
0637: int next = tabRuns[(i == runCount - 1) ? 0 : i + 1];
0638: int end = (next != 0 ? next - 1 : tabCount - 1);
0639:
0640: for (int j = start; j <= end; j++) {
0641:
0642: // stupid hack - remove all causes index exception
0643: try {
0644: if (rects[j].intersects(clipRect)) {
0645: paintTab(g, tabPlacement, rects, j, iconRect,
0646: textRect);
0647: }
0648: } catch (ArrayIndexOutOfBoundsException e) {
0649: break;
0650: }
0651:
0652: }
0653:
0654: }
0655:
0656: // Paint selected tab if its in the front run
0657: // since it may overlap other tabs
0658: if (selectedIndex >= 0
0659: && getRunForTab(tabCount, selectedIndex) == 0) {
0660: if (rects[selectedIndex].intersects(clipRect)) {
0661: paintTab(g, tabPlacement, rects, selectedIndex,
0662: iconRect, textRect);
0663: }
0664: }
0665: }
0666:
0667: protected void paintTab(Graphics g, int tabPlacement,
0668: Rectangle[] rects, int tabIndex, Rectangle iconRect,
0669: Rectangle textRect) {
0670:
0671: Rectangle tabRect = rects[tabIndex];
0672: int selectedIndex = tabPane.getSelectedIndex();
0673: boolean isSelected = selectedIndex == tabIndex;
0674: Graphics2D g2 = null;
0675: Polygon cropShape = null;
0676: Shape save = null;
0677: int cropx = 0;
0678: int cropy = 0;
0679:
0680: if (scrollableTabLayoutEnabled()) {
0681: if (g instanceof Graphics2D) {
0682: g2 = (Graphics2D) g;
0683:
0684: // Render visual for cropped tab edge...
0685: Rectangle viewRect = tabScroller.viewport.getViewRect();
0686: int cropline;
0687:
0688: switch (tabPlacement) {
0689: case LEFT:
0690: case RIGHT:
0691: cropline = viewRect.y + viewRect.height;
0692: if ((tabRect.y < cropline)
0693: && (tabRect.y + tabRect.height > cropline)) {
0694: cropShape = createCroppedTabClip(tabPlacement,
0695: tabRect, cropline);
0696: cropx = tabRect.x;
0697: cropy = cropline - 1;
0698: }
0699: break;
0700: case TOP:
0701: case BOTTOM:
0702: default:
0703: cropline = viewRect.x + viewRect.width;
0704:
0705: if ((tabRect.x < cropline)
0706: && (tabRect.x + tabRect.width > cropline)) {
0707: cropShape = createCroppedTabClip(tabPlacement,
0708: tabRect, cropline);
0709: cropx = cropline - 1;
0710: cropy = tabRect.y;
0711: }
0712:
0713: }
0714:
0715: if (cropShape != null) {
0716: save = g2.getClip();
0717: g2.clip(cropShape);
0718: }
0719:
0720: }
0721:
0722: }
0723:
0724: paintTabBackground(g, tabPlacement, tabIndex, tabRect.x,
0725: tabRect.y, tabRect.width, tabRect.height + 1,
0726: isSelected);
0727:
0728: paintTabBorder(g, tabPlacement, tabIndex, tabRect.x, tabRect.y,
0729: tabRect.width, tabRect.height + 1, isSelected);
0730:
0731: String title = tabPane.getTitleAt(tabIndex);
0732: Font font = tabPane.getFont();
0733: FontMetrics metrics = g.getFontMetrics(font);
0734: Icon icon = getIconForTab(tabIndex);
0735:
0736: layoutLabel(tabPlacement, metrics, tabIndex, title, icon,
0737: tabRect, iconRect, textRect, isSelected);
0738:
0739: paintText(g, tabPlacement, font, metrics, tabIndex, title,
0740: textRect, isSelected);
0741:
0742: paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected);
0743:
0744: if (cropShape != null) {
0745: paintCroppedTabEdge(g, tabPlacement, tabIndex, isSelected,
0746: cropx, cropy);
0747: g2.setClip(save);
0748: }
0749:
0750: Rectangle closeIconRect = getCloseIconRectangle(tabRect);
0751: closeIcon.paintIcon(tabPane, g, closeIconRect.x,
0752: closeIconRect.y);
0753:
0754: if (tabIndex == currentCloseRolloverIndex) {
0755: g.setColor(Color.GRAY);
0756: g.drawRect(closeIconRect.x - 2, closeIconRect.y - 2,
0757: closeIconRect.width + 3, closeIconRect.height + 3);
0758: }
0759:
0760: }
0761:
0762: private Icon closeIcon = new TabCloseIcon();
0763:
0764: private Rectangle getCloseIconRectangle(Rectangle tabRect) {
0765: int iconWidth = closeIcon.getIconWidth();
0766: int iconHeight = closeIcon.getIconHeight();
0767:
0768: int y = tabRect.y + ((tabRect.height - iconHeight) / 2);
0769: int x = tabRect.x + ((int) (tabRect.width - iconWidth - 6));
0770:
0771: return new Rectangle(x, y, iconWidth, iconHeight);
0772: }
0773:
0774: /* This method will create and return a polygon shape for the given tab rectangle
0775: * which has been cropped at the specified cropline with a torn edge visual.
0776: * e.g. A "File" tab which has cropped been cropped just after the "i":
0777: * -------------
0778: * | ..... |
0779: * | . |
0780: * | ... . |
0781: * | . . |
0782: * | . . |
0783: * | . . |
0784: * --------------
0785: *
0786: * The x, y arrays below define the pattern used to create a "torn" edge
0787: * segment which is repeated to fill the edge of the tab.
0788: * For tabs placed on TOP and BOTTOM, this righthand torn edge is created by
0789: * line segments which are defined by coordinates obtained by
0790: * subtracting xCropLen[i] from (tab.x + tab.width) and adding yCroplen[i]
0791: * to (tab.y).
0792: * For tabs placed on LEFT or RIGHT, the bottom torn edge is created by
0793: * subtracting xCropLen[i] from (tab.y + tab.height) and adding yCropLen[i]
0794: * to (tab.x).
0795: */
0796: private int xCropLen[] = { 1, 1, 0, 0, 1, 1, 2, 2 };
0797: private int yCropLen[] = { 0, 3, 3, 6, 6, 9, 9, 12 };
0798: private static final int CROP_SEGMENT = 12;
0799:
0800: private Polygon createCroppedTabClip(int tabPlacement,
0801: Rectangle tabRect, int cropline) {
0802: int rlen = 0;
0803: int start = 0;
0804: int end = 0;
0805: int ostart = 0;
0806:
0807: switch (tabPlacement) {
0808: case LEFT:
0809: case RIGHT:
0810: rlen = tabRect.width;
0811: start = tabRect.x;
0812: end = tabRect.x + tabRect.width;
0813: ostart = tabRect.y;
0814: break;
0815: case TOP:
0816: case BOTTOM:
0817: default:
0818: rlen = tabRect.height;
0819: start = tabRect.y;
0820: end = tabRect.y + tabRect.height + 1;
0821: ostart = tabRect.x;
0822: }
0823:
0824: int rcnt = rlen / CROP_SEGMENT;
0825:
0826: if (rlen % CROP_SEGMENT > 0) {
0827: rcnt++;
0828: }
0829:
0830: int npts = 2 + (rcnt * 8);
0831: int xp[] = new int[npts];
0832: int yp[] = new int[npts];
0833: int pcnt = 0;
0834:
0835: xp[pcnt] = ostart;
0836: yp[pcnt++] = end;
0837: xp[pcnt] = ostart;
0838: yp[pcnt++] = start;
0839: for (int i = 0; i < rcnt; i++) {
0840: for (int j = 0; j < xCropLen.length; j++) {
0841: xp[pcnt] = cropline - xCropLen[j];
0842: yp[pcnt] = start + (i * CROP_SEGMENT) + yCropLen[j];
0843: if (yp[pcnt] >= end) {
0844: yp[pcnt] = end;
0845: pcnt++;
0846: break;
0847: }
0848: pcnt++;
0849: }
0850: }
0851: if (tabPlacement == JTabbedPane.TOP
0852: || tabPlacement == JTabbedPane.BOTTOM) {
0853: return new Polygon(xp, yp, pcnt);
0854:
0855: } else { // LEFT or RIGHT
0856: return new Polygon(yp, xp, pcnt);
0857: }
0858: }
0859:
0860: /* If tabLayoutPolicy == SCROLL_TAB_LAYOUT, this method will paint an edge
0861: * indicating the tab is cropped in the viewport display
0862: */
0863: private void paintCroppedTabEdge(Graphics g, int tabPlacement,
0864: int tabIndex, boolean isSelected, int x, int y) {
0865:
0866: switch (tabPlacement) {
0867:
0868: case LEFT:
0869: case RIGHT:
0870: int xx = x;
0871: g.setColor(shadow);
0872:
0873: while (xx <= x + rects[tabIndex].width) {
0874:
0875: for (int i = 0; i < xCropLen.length; i += 2) {
0876: g.drawLine(xx + yCropLen[i], y - xCropLen[i], xx
0877: + yCropLen[i + 1] - 1, y - xCropLen[i + 1]);
0878: }
0879:
0880: xx += CROP_SEGMENT;
0881:
0882: }
0883:
0884: break;
0885:
0886: case TOP:
0887: case BOTTOM:
0888: default:
0889: int yy = y;
0890: g.setColor(controlShadow);
0891:
0892: while (yy <= y + rects[tabIndex].height) {
0893:
0894: for (int i = 0; i < xCropLen.length; i += 2) {
0895: g
0896: .drawLine(x - xCropLen[i],
0897: yy + yCropLen[i], x
0898: - xCropLen[i + 1], yy
0899: + yCropLen[i + 1] - 1);
0900: }
0901:
0902: yy += CROP_SEGMENT;
0903:
0904: }
0905:
0906: }
0907:
0908: }
0909:
0910: protected void layoutLabel(int tabPlacement, FontMetrics metrics,
0911: int tabIndex, String title, Icon icon, Rectangle tabRect,
0912: Rectangle iconRect, Rectangle textRect, boolean isSelected) {
0913:
0914: textRect.x = textRect.y = iconRect.x = iconRect.y = 0;
0915:
0916: View v = getTextViewForTab(tabIndex);
0917:
0918: if (v != null) {
0919: tabPane.putClientProperty("html", v);
0920: }
0921:
0922: SwingUtilities.layoutCompoundLabel((JComponent) tabPane,
0923: metrics, title, icon, SwingUtilities.CENTER,
0924: SwingUtilities.LEFT, SwingUtilities.CENTER,
0925: SwingUtilities.TRAILING, tabRect, iconRect, textRect,
0926: textIconGap);
0927:
0928: tabPane.putClientProperty("html", null);
0929:
0930: int xNudge = getTabLabelShiftX(tabPlacement, tabIndex,
0931: isSelected);
0932: int yNudge = getTabLabelShiftY(tabPlacement, tabIndex,
0933: isSelected);
0934: iconRect.x += xNudge + 5;
0935: iconRect.y += yNudge;
0936: textRect.x += xNudge + 6;
0937: textRect.y += yNudge;
0938:
0939: }
0940:
0941: protected void paintIcon(Graphics g, int tabPlacement,
0942: int tabIndex, Icon icon, Rectangle iconRect,
0943: boolean isSelected) {
0944: if (icon != null) {
0945:
0946: int y = iconRect.y - 1;
0947:
0948: if (!isSelected)
0949: y += 2;
0950:
0951: icon.paintIcon(tabPane, g, iconRect.x, y);
0952:
0953: }
0954:
0955: }
0956:
0957: protected void paintText(Graphics g, int tabPlacement, Font font,
0958: FontMetrics metrics, int tabIndex, String title,
0959: Rectangle textRect, boolean isSelected) {
0960:
0961: g.setFont(font);
0962:
0963: View v = getTextViewForTab(tabIndex);
0964:
0965: if (v != null) {
0966: v.paint(g, textRect); // html
0967: }
0968:
0969: else { // plain text
0970: int mnemIndex = tabPane
0971: .getDisplayedMnemonicIndexAt(tabIndex);
0972:
0973: if (tabPane.isEnabled() && tabPane.isEnabledAt(tabIndex)) {
0974: g.setColor(tabPane.getForegroundAt(tabIndex));
0975: int y = textRect.y + metrics.getAscent() - 1;
0976:
0977: if (!isSelected)
0978: y += 2;
0979:
0980: BasicGraphicsUtils.drawStringUnderlineCharAt(g, title,
0981: mnemIndex, textRect.x, y);
0982:
0983: }
0984:
0985: else { // tab disabled
0986: g
0987: .setColor(tabPane.getBackgroundAt(tabIndex)
0988: .brighter());
0989: BasicGraphicsUtils.drawStringUnderlineCharAt(g, title,
0990: mnemIndex, textRect.x, textRect.y
0991: + metrics.getAscent() - 1);
0992: g.setColor(tabPane.getBackgroundAt(tabIndex).darker());
0993: BasicGraphicsUtils.drawStringUnderlineCharAt(g, title,
0994: mnemIndex, textRect.x - 1, textRect.y
0995: + metrics.getAscent() - 1);
0996: }
0997:
0998: }
0999:
1000: }
1001:
1002: protected int getTabLabelShiftX(int tabPlacement, int tabIndex,
1003: boolean isSelected) {
1004: Rectangle tabRect = rects[tabIndex];
1005: int nudge = 0;
1006: switch (tabPlacement) {
1007: case LEFT:
1008: nudge = isSelected ? -1 : 1;
1009: break;
1010: case RIGHT:
1011: nudge = isSelected ? 1 : -1;
1012: break;
1013: case BOTTOM:
1014: case TOP:
1015: default:
1016: nudge = tabRect.width % 2;
1017: }
1018: return nudge;
1019: }
1020:
1021: protected int getTabLabelShiftY(int tabPlacement, int tabIndex,
1022: boolean isSelected) {
1023: Rectangle tabRect = rects[tabIndex];
1024: int nudge = 0;
1025: switch (tabPlacement) {
1026: case TOP:
1027: case BOTTOM:
1028: nudge = isSelected ? 1 : -1;
1029: break;
1030: case LEFT:
1031: case RIGHT:
1032: nudge = tabRect.height % 2;
1033: break;
1034: default:
1035: nudge = isSelected ? -1 : 1;
1036: }
1037: return nudge;
1038: }
1039:
1040: protected void paintFocusIndicator(Graphics g, int tabPlacement,
1041: Rectangle[] rects, int tabIndex, Rectangle iconRect,
1042: Rectangle textRect, boolean isSelected) {
1043:
1044: Rectangle tabRect = rects[tabIndex];
1045: if (tabPane.hasFocus() && isSelected) {
1046: int x, y, w, h;
1047: g.setColor(focus);
1048: switch (tabPlacement) {
1049: case LEFT:
1050: x = tabRect.x + 3;
1051: y = tabRect.y + 3;
1052: w = tabRect.width - 5;
1053: h = tabRect.height - 6;
1054: break;
1055: case RIGHT:
1056: x = tabRect.x + 2;
1057: y = tabRect.y + 3;
1058: w = tabRect.width - 5;
1059: h = tabRect.height - 6;
1060: break;
1061: case BOTTOM:
1062: x = tabRect.x + 3;
1063: y = tabRect.y + 2;
1064: w = tabRect.width - 6;
1065: h = tabRect.height - 5;
1066: break;
1067: case TOP:
1068: default:
1069: x = tabRect.x + 3;
1070: y = tabRect.y + 3;
1071: w = tabRect.width - 6;
1072: h = tabRect.height - 5;
1073: }
1074: BasicGraphicsUtils.drawDashedRect(g, x, y, w, h);
1075: }
1076: }
1077:
1078: /**
1079: * this function draws the border around each tab
1080: * note that this function does now draw the background of the tab.
1081: * that is done elsewhere
1082: */
1083: protected void paintTabBorder(Graphics g, int tabPlacement,
1084: int tabIndex, int x, int y, int w, int h, boolean isSelected) {
1085:
1086: g.setColor(lightHighlight);
1087:
1088: switch (tabPlacement) {
1089: case LEFT:
1090: g.drawLine(x + 1, y + h - 2, x + 1, y + h - 2); // bottom-left highlight
1091: g.drawLine(x, y + 2, x, y + h - 3); // left highlight
1092: g.drawLine(x + 1, y + 1, x + 1, y + 1); // top-left highlight
1093: g.drawLine(x + 2, y, x + w - 1, y); // top highlight
1094:
1095: g.setColor(shadow);
1096: g.drawLine(x + 2, y + h - 2, x + w - 1, y + h - 2); // bottom shadow
1097:
1098: g.setColor(darkShadow);
1099: g.drawLine(x + 2, y + h - 1, x + w - 1, y + h - 1); // bottom dark shadow
1100: break;
1101: case RIGHT:
1102: g.drawLine(x, y, x + w - 3, y); // top highlight
1103:
1104: g.setColor(shadow);
1105: g.drawLine(x, y + h - 2, x + w - 3, y + h - 2); // bottom shadow
1106: g.drawLine(x + w - 2, y + 2, x + w - 2, y + h - 3); // right shadow
1107:
1108: g.setColor(darkShadow);
1109: g.drawLine(x + w - 2, y + 1, x + w - 2, y + 1); // top-right dark shadow
1110: g.drawLine(x + w - 2, y + h - 2, x + w - 2, y + h - 2); // bottom-right dark shadow
1111: g.drawLine(x + w - 1, y + 2, x + w - 1, y + h - 3); // right dark shadow
1112: g.drawLine(x, y + h - 1, x + w - 3, y + h - 1); // bottom dark shadow
1113: break;
1114: case BOTTOM:
1115: g.setColor(darkShadow);
1116: g.drawLine(x, y, x, y + h - 3); // left highlight
1117: g.drawLine(x + 1, y + h - 2, x + 1, y + h - 2); // bottom-left highlight
1118:
1119: g.drawLine(x + 2, y + h - 1, x + w - 3, y + h - 1); // bottom dark shadow
1120: g.drawLine(x + w - 2, y + h - 2, x + w - 2, y + h - 2); // bottom-right dark shadow
1121: g.drawLine(x + w - 1, y, x + w - 1, y + h - 3); // right dark shadow
1122: break;
1123: case TOP:
1124: default:
1125: g.setColor(darkShadow);
1126: g.drawLine(x, y + 2, x, y + h - 2); // left side
1127: g.drawLine(x + 1, y + 1, x + 1, y + 1); // top-left side
1128:
1129: g.drawLine(x + 2, y, x + w - 3, y); // top side
1130:
1131: g.drawLine(x + w - 2, y + 1, x + w - 2, y + 1); // top-right side
1132: g.drawLine(x + w - 1, y + 2, x + w - 1, y + h + 3); // right side
1133: }
1134:
1135: }
1136:
1137: protected void paintTabBackground(Graphics g, int tabPlacement,
1138: int tabIndex, int x, int y, int w, int h, boolean isSelected) {
1139:
1140: if (isSelected) {
1141: g.setColor(activeColor);
1142: } else {
1143: g.setColor(unselectedBackground);
1144: }
1145:
1146: switch (tabPlacement) {
1147:
1148: case LEFT:
1149: g.fillRect(x + 1, y + 1, w - 2, h - 3);
1150: break;
1151: case RIGHT:
1152: g.fillRect(x, y + 1, w - 2, h - 3);
1153: break;
1154: case BOTTOM:
1155: g.fillRect(x + 1, y, w - 3, h - 1);
1156: g.fillRect(x + w - 3, y, 3, h - 2);
1157: break;
1158: case TOP:
1159: default:
1160: g.fillRect(x + 1, y + 1, w - 3, h - 1);
1161: g.fillRect(x + w - 3, y + 1, 2, h - 2);
1162:
1163: }
1164:
1165: }
1166:
1167: protected void paintContentBorder(Graphics g, int tabPlacement,
1168: int selectedIndex) {
1169: int width = tabPane.getWidth();
1170: int height = tabPane.getHeight();
1171: Insets insets = tabPane.getInsets();
1172:
1173: int x = insets.left;
1174: int y = insets.top;
1175: int w = width - insets.right - insets.left;
1176: int h = height - insets.top - insets.bottom;
1177:
1178: switch (tabPlacement) {
1179: case LEFT:
1180: x += calculateTabAreaWidth(tabPlacement, runCount,
1181: maxTabWidth);
1182: w -= (x - insets.left);
1183: break;
1184: case RIGHT:
1185: w -= calculateTabAreaWidth(tabPlacement, runCount,
1186: maxTabWidth);
1187: break;
1188: case BOTTOM:
1189: h -= calculateTabAreaHeight(tabPlacement, runCount,
1190: maxTabHeight);
1191: break;
1192: case TOP:
1193: default:
1194: y += calculateTabAreaHeight(tabPlacement, runCount,
1195: maxTabHeight);
1196: h -= (y - insets.top);
1197: }
1198: // Fill region behind content area
1199: if (unselectedBackground == null) {
1200: g.setColor(tabPane.getBackground());
1201: } else {
1202: g.setColor(unselectedBackground);
1203: }
1204:
1205: g.fillRect(x, y, w, h);
1206:
1207: paintContentBorderTopEdge(g, tabPlacement, selectedIndex, x, y,
1208: w, h);
1209: paintContentBorderLeftEdge(g, tabPlacement, selectedIndex, x,
1210: y, w, h);
1211: paintContentBorderBottomEdge(g, tabPlacement, selectedIndex, x,
1212: y, w, h);
1213: paintContentBorderRightEdge(g, tabPlacement, selectedIndex, x,
1214: y, w, h);
1215:
1216: }
1217:
1218: protected void paintContentBorderTopEdge(Graphics g,
1219: int tabPlacement, int selectedIndex, int x, int y, int w,
1220: int h) {
1221:
1222: // paint the far edges
1223: g.setColor(darkShadow);
1224: g.drawLine(x, y, x + w - 2, y);
1225:
1226: if (selectedIndex == -1) {
1227: return;
1228: }
1229:
1230: if (tabPlacement == TOP) {
1231: Rectangle rect = rects[selectedIndex];
1232: g.setColor(activeColor);
1233: g.drawLine(rect.x + 1, y, rect.x + rect.width - 2, y);
1234: }
1235:
1236: }
1237:
1238: protected void paintContentBorderBottomEdge(Graphics g,
1239: int tabPlacement, int selectedIndex, int x, int y, int w,
1240: int h) {
1241:
1242: // TODO: CHANGED
1243:
1244: // paint the far edges
1245: g.setColor(darkShadow);
1246: g.drawLine(x, y + h - 1, x + w, y + h - 1);
1247:
1248: if (selectedIndex == -1) {
1249: return;
1250: }
1251:
1252: if (tabPlacement == BOTTOM) {
1253: Rectangle rect = rects[selectedIndex];
1254: g.setColor(activeColor);
1255: g.drawLine(rect.x + 1, y + h - 1, rect.x + rect.width - 2,
1256: y + h - 1);
1257: }
1258:
1259: }
1260:
1261: protected void paintContentBorderLeftEdge(Graphics g,
1262: int tabPlacement, int selectedIndex, int x, int y, int w,
1263: int h) {
1264:
1265: g.setColor(controlShadow);
1266: g.drawLine(x, y, x, y + h - 2);
1267: }
1268:
1269: protected void paintContentBorderRightEdge(Graphics g,
1270: int tabPlacement, int selectedIndex, int x, int y, int w,
1271: int h) {
1272:
1273: g.setColor(controlShadow);
1274: g.drawLine(x + w - 1, y, x + w - 1, y + h - 1);
1275:
1276: }
1277:
1278: private boolean isLeftToRight(Component c) {
1279: return c.getComponentOrientation().isLeftToRight();
1280: }
1281:
1282: private void ensureCurrentLayout() {
1283: if (!tabPane.isValid()) {
1284: tabPane.validate();
1285: }
1286: /* If tabPane doesn't have a peer yet, the validate() call will
1287: * silently fail. We handle that by forcing a layout if tabPane
1288: * is still invalid. See bug 4237677.
1289: */
1290: if (!tabPane.isValid()) {
1291: TabbedPaneLayout layout = (TabbedPaneLayout) tabPane
1292: .getLayout();
1293: layout.calculateLayoutInfo();
1294: }
1295: }
1296:
1297: // TabbedPaneUI methods
1298:
1299: /**
1300: * Returns the bounds of the specified tab index. The bounds are
1301: * with respect to the JTabbedPane's coordinate space.
1302: */
1303: public Rectangle getTabBounds(JTabbedPane pane, int i) {
1304: ensureCurrentLayout();
1305: Rectangle tabRect = new Rectangle();
1306: return getTabBounds(i, tabRect);
1307: }
1308:
1309: public int getTabRunCount(JTabbedPane pane) {
1310: ensureCurrentLayout();
1311: return runCount;
1312: }
1313:
1314: /**
1315: * Returns the tab index which intersects the specified point
1316: * in the JTabbedPane's coordinate space.
1317: */
1318: public int tabForCoordinate(JTabbedPane pane, int x, int y) {
1319: ensureCurrentLayout();
1320: Point p = new Point(x, y);
1321:
1322: if (scrollableTabLayoutEnabled()) {
1323: translatePointToTabPanel(x, y, p);
1324: }
1325: int tabCount = tabPane.getTabCount();
1326: for (int i = 0; i < tabCount; i++) {
1327: if (rects[i].contains(p.x, p.y)) {
1328: return i;
1329: }
1330: }
1331: return -1;
1332: }
1333:
1334: /**
1335: * Returns the bounds of the specified tab in the coordinate space
1336: * of the JTabbedPane component. This is required because the tab rects
1337: * are by default defined in the coordinate space of the component where
1338: * they are rendered, which could be the JTabbedPane
1339: * (for WRAP_TAB_LAYOUT) or a ScrollableTabPanel (SCROLL_TAB_LAYOUT).
1340: * This method should be used whenever the tab rectangle must be relative
1341: * to the JTabbedPane itself and the result should be placed in a
1342: * designated Rectangle object (rather than instantiating and returning
1343: * a new Rectangle each time). The tab index parameter must be a valid
1344: * tabbed pane tab index (0 to tab count - 1, inclusive). The destination
1345: * rectangle parameter must be a valid <code>Rectangle</code> instance.
1346: * The handling of invalid parameters is unspecified.
1347: *
1348: * @param tabIndex the index of the tab
1349: * @param dest the rectangle where the result should be placed
1350: * @return the resulting rectangle
1351: *
1352: * @since 1.4
1353: */
1354: protected Rectangle getTabBounds(int tabIndex, Rectangle dest) {
1355: dest.width = rects[tabIndex].width;
1356: dest.height = rects[tabIndex].height;
1357:
1358: if (scrollableTabLayoutEnabled()) { // SCROLL_TAB_LAYOUT
1359: // Need to translate coordinates based on viewport location &
1360: // view position
1361: Point vpp = tabScroller.viewport.getLocation();
1362: Point viewp = tabScroller.viewport.getViewPosition();
1363: dest.x = rects[tabIndex].x + vpp.x - viewp.x;
1364: dest.y = rects[tabIndex].y + vpp.y - viewp.y;
1365:
1366: } else { // WRAP_TAB_LAYOUT
1367: dest.x = rects[tabIndex].x;
1368: dest.y = rects[tabIndex].y;
1369: }
1370: return dest;
1371: }
1372:
1373: /**
1374: * Returns the tab index which intersects the specified point
1375: * in the coordinate space of the component where the
1376: * tabs are actually rendered, which could be the JTabbedPane
1377: * (for WRAP_TAB_LAYOUT) or a ScrollableTabPanel (SCROLL_TAB_LAYOUT).
1378: */
1379: private int getTabAtLocation(int x, int y) {
1380: ensureCurrentLayout();
1381:
1382: int tabCount = tabPane.getTabCount();
1383: for (int i = 0; i < tabCount; i++) {
1384: if (rects[i].contains(x, y)) {
1385: return i;
1386: }
1387: }
1388: return -1;
1389: }
1390:
1391: /**
1392: * Returns the index of the tab closest to the passed in location, note
1393: * that the returned tab may not contain the location x,y.
1394: */
1395: private int getClosestTab(int x, int y) {
1396: int min = 0;
1397: int tabCount = Math.min(rects.length, tabPane.getTabCount());
1398: int max = tabCount;
1399: int tabPlacement = tabPane.getTabPlacement();
1400: boolean useX = (tabPlacement == TOP || tabPlacement == BOTTOM);
1401: int want = (useX) ? x : y;
1402:
1403: while (min != max) {
1404: int current = (max + min) / 2;
1405: int minLoc;
1406: int maxLoc;
1407:
1408: if (useX) {
1409: minLoc = rects[current].x;
1410: maxLoc = minLoc + rects[current].width;
1411: } else {
1412: minLoc = rects[current].y;
1413: maxLoc = minLoc + rects[current].height;
1414: }
1415: if (want < minLoc) {
1416: max = current;
1417: if (min == max) {
1418: return Math.max(0, current - 1);
1419: }
1420: } else if (want >= maxLoc) {
1421: min = current;
1422: if (max - min <= 1) {
1423: return Math.max(current + 1, tabCount - 1);
1424: }
1425: } else {
1426: return current;
1427: }
1428: }
1429: return min;
1430: }
1431:
1432: /**
1433: * Returns a point which is translated from the specified point in the
1434: * JTabbedPane's coordinate space to the coordinate space of the
1435: * ScrollableTabPanel. This is used for SCROLL_TAB_LAYOUT ONLY.
1436: */
1437: private Point translatePointToTabPanel(int srcx, int srcy,
1438: Point dest) {
1439: Point vpp = tabScroller.viewport.getLocation();
1440: Point viewp = tabScroller.viewport.getViewPosition();
1441: dest.x = srcx + vpp.x + viewp.x;
1442: dest.y = srcy + vpp.y + viewp.y;
1443: return dest;
1444: }
1445:
1446: // CloseTabbedPaneUI methods
1447:
1448: protected Component getVisibleComponent() {
1449: return visibleComponent;
1450: }
1451:
1452: protected void setVisibleComponent(Component component) {
1453: if (visibleComponent != null && visibleComponent != component
1454: && visibleComponent.getParent() == tabPane) {
1455: visibleComponent.setVisible(false);
1456: }
1457: if (component != null && !component.isVisible()) {
1458: component.setVisible(true);
1459: }
1460: visibleComponent = component;
1461: }
1462:
1463: protected void assureRectsCreated(int tabCount) {
1464: int rectArrayLen = rects.length;
1465: if (tabCount != rectArrayLen) {
1466: Rectangle[] tempRectArray = new Rectangle[tabCount];
1467: System.arraycopy(rects, 0, tempRectArray, 0, Math.min(
1468: rectArrayLen, tabCount));
1469: rects = tempRectArray;
1470: for (int rectIndex = rectArrayLen; rectIndex < tabCount; rectIndex++) {
1471: rects[rectIndex] = new Rectangle();
1472: }
1473: }
1474:
1475: }
1476:
1477: protected void expandTabRunsArray() {
1478: int rectLen = tabRuns.length;
1479: int[] newArray = new int[rectLen + 10];
1480: System.arraycopy(tabRuns, 0, newArray, 0, runCount);
1481: tabRuns = newArray;
1482: }
1483:
1484: protected int getRunForTab(int tabCount, int tabIndex) {
1485: for (int i = 0; i < runCount; i++) {
1486: int first = tabRuns[i];
1487: int last = lastTabInRun(tabCount, i);
1488: if (tabIndex >= first && tabIndex <= last) {
1489: return i;
1490: }
1491: }
1492: return 0;
1493: }
1494:
1495: protected int lastTabInRun(int tabCount, int run) {
1496: if (runCount == 1) {
1497: return tabCount - 1;
1498: }
1499: int nextRun = (run == runCount - 1 ? 0 : run + 1);
1500: if (tabRuns[nextRun] == 0) {
1501: return tabCount - 1;
1502: }
1503: return tabRuns[nextRun] - 1;
1504: }
1505:
1506: protected int getTabRunOverlay(int tabPlacement) {
1507: return tabRunOverlay;
1508: }
1509:
1510: protected int getTabRunIndent(int tabPlacement, int run) {
1511: return 0;
1512: }
1513:
1514: protected boolean shouldPadTabRun(int tabPlacement, int run) {
1515: return runCount > 1;
1516: }
1517:
1518: protected boolean shouldRotateTabRuns(int tabPlacement) {
1519: return true;
1520: }
1521:
1522: protected Icon getIconForTab(int tabIndex) {
1523: return (!tabPane.isEnabled() || !tabPane.isEnabledAt(tabIndex)) ? tabPane
1524: .getDisabledIconAt(tabIndex)
1525: : tabPane.getIconAt(tabIndex);
1526: }
1527:
1528: /**
1529: * Returns the text View object required to render stylized text (HTML) for
1530: * the specified tab or null if no specialized text rendering is needed
1531: * for this tab. This is provided to support html rendering inside tabs.
1532: *
1533: * @param tabIndex the index of the tab
1534: * @return the text view to render the tab's text or null if no
1535: * specialized rendering is required
1536: *
1537: * @since 1.4
1538: */
1539: protected View getTextViewForTab(int tabIndex) {
1540: if (htmlViews != null) {
1541: return (View) htmlViews.elementAt(tabIndex);
1542: }
1543: return null;
1544: }
1545:
1546: protected int calculateTabHeight(int tabPlacement, int tabIndex,
1547: int fontHeight) {
1548: int height = 0;
1549: View v = getTextViewForTab(tabIndex);
1550: if (v != null) {
1551: // html
1552: height += (int) v.getPreferredSpan(View.Y_AXIS);
1553: } else {
1554: // plain text
1555: height += fontHeight;
1556: }
1557: Icon icon = getIconForTab(tabIndex);
1558: Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
1559:
1560: if (icon != null) {
1561: height = Math.max(height, icon.getIconHeight());
1562: }
1563: height += tabInsets.top + tabInsets.bottom + 2;
1564:
1565: // TODO: CHANGED
1566: return height;
1567: }
1568:
1569: protected int calculateMaxTabHeight(int tabPlacement) {
1570: FontMetrics metrics = getFontMetrics();
1571: int tabCount = tabPane.getTabCount();
1572: int result = 0;
1573: int fontHeight = metrics.getHeight();
1574: for (int i = 0; i < tabCount; i++) {
1575: result = Math.max(calculateTabHeight(tabPlacement, i,
1576: fontHeight), result);
1577: }
1578: return result + 2;
1579: }
1580:
1581: protected int calculateTabWidth(int tabPlacement, int tabIndex,
1582: FontMetrics metrics) {
1583: Icon icon = getIconForTab(tabIndex);
1584: Insets tabInsets = getTabInsets(tabPlacement, tabIndex);
1585: int width = tabInsets.left + tabInsets.right;// + 3;
1586:
1587: if (icon != null) {
1588: width += icon.getIconWidth() + textIconGap;
1589: }
1590: View v = getTextViewForTab(tabIndex);
1591: if (v != null) {
1592: // html
1593: width += (int) v.getPreferredSpan(View.X_AXIS);
1594: } else {
1595: // plain text
1596: String title = tabPane.getTitleAt(tabIndex);
1597: width += SwingUtilities.computeStringWidth(metrics, title);
1598: }
1599:
1600: // add the close button width
1601: //width += (closeIcon.getIconWidth() + 2);
1602: // TODO: CHANGED
1603: return width + 15;
1604: }
1605:
1606: protected int calculateMaxTabWidth(int tabPlacement) {
1607: FontMetrics metrics = getFontMetrics();
1608: int tabCount = tabPane.getTabCount();
1609: int result = 0;
1610: for (int i = 0; i < tabCount; i++) {
1611: result = Math.max(calculateTabWidth(tabPlacement, i,
1612: metrics), result);
1613: }
1614: return result;
1615: }
1616:
1617: protected int calculateTabAreaHeight(int tabPlacement,
1618: int horizRunCount, int maxTabHeight) {
1619: Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
1620: int tabRunOverlay = getTabRunOverlay(tabPlacement);
1621: return (horizRunCount > 0 ? horizRunCount
1622: * (maxTabHeight - tabRunOverlay) + tabRunOverlay
1623: + tabAreaInsets.top + tabAreaInsets.bottom : 0);
1624: }
1625:
1626: protected int calculateTabAreaWidth(int tabPlacement,
1627: int vertRunCount, int maxTabWidth) {
1628: Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
1629: int tabRunOverlay = getTabRunOverlay(tabPlacement);
1630: return (vertRunCount > 0 ? vertRunCount
1631: * (maxTabWidth - tabRunOverlay) + tabRunOverlay
1632: + tabAreaInsets.left + tabAreaInsets.right : 0);
1633: }
1634:
1635: protected Insets getTabInsets(int tabPlacement, int tabIndex) {
1636: return tabInsets;
1637: }
1638:
1639: protected Insets getSelectedTabPadInsets(int tabPlacement) {
1640: rotateInsets(selectedTabPadInsets, currentPadInsets,
1641: tabPlacement);
1642: return currentPadInsets;
1643: }
1644:
1645: protected Insets getTabAreaInsets(int tabPlacement) {
1646: rotateInsets(tabAreaInsets, currentTabAreaInsets, tabPlacement);
1647: return currentTabAreaInsets;
1648: }
1649:
1650: protected Insets getContentBorderInsets(int tabPlacement) {
1651: return contentBorderInsets;
1652: }
1653:
1654: protected FontMetrics getFontMetrics() {
1655: Font font = tabPane.getFont();
1656: return tabPane.getFontMetrics(font);// Toolkit.getDefaultToolkit().getFontMetrics(font);
1657: }
1658:
1659: // Tab Navigation methods
1660:
1661: protected void navigateSelectedTab(int direction) {
1662: int tabPlacement = tabPane.getTabPlacement();
1663: int current = tabPane.getSelectedIndex();
1664: int tabCount = tabPane.getTabCount();
1665: boolean leftToRight = isLeftToRight(tabPane);
1666:
1667: // If we have no tabs then don't navigate.
1668: if (tabCount <= 0) {
1669: return;
1670: }
1671:
1672: int offset;
1673: switch (tabPlacement) {
1674: case NEXT:
1675: selectNextTab(current);
1676: break;
1677: case PREVIOUS:
1678: selectPreviousTab(current);
1679: break;
1680: case LEFT:
1681: case RIGHT:
1682: switch (direction) {
1683: case NORTH:
1684: selectPreviousTabInRun(current);
1685: break;
1686: case SOUTH:
1687: selectNextTabInRun(current);
1688: break;
1689: case WEST:
1690: offset = getTabRunOffset(tabPlacement, tabCount,
1691: current, false);
1692: selectAdjacentRunTab(tabPlacement, current, offset);
1693: break;
1694: case EAST:
1695: offset = getTabRunOffset(tabPlacement, tabCount,
1696: current, true);
1697: selectAdjacentRunTab(tabPlacement, current, offset);
1698: break;
1699: default:
1700: }
1701: break;
1702: case BOTTOM:
1703: case TOP:
1704: default:
1705: switch (direction) {
1706: case NORTH:
1707: offset = getTabRunOffset(tabPlacement, tabCount,
1708: current, false);
1709: selectAdjacentRunTab(tabPlacement, current, offset);
1710: break;
1711: case SOUTH:
1712: offset = getTabRunOffset(tabPlacement, tabCount,
1713: current, true);
1714: selectAdjacentRunTab(tabPlacement, current, offset);
1715: break;
1716: case EAST:
1717: if (leftToRight) {
1718: selectNextTabInRun(current);
1719: } else {
1720: selectPreviousTabInRun(current);
1721: }
1722: break;
1723: case WEST:
1724: if (leftToRight) {
1725: selectPreviousTabInRun(current);
1726: } else {
1727: selectNextTabInRun(current);
1728: }
1729: break;
1730: default:
1731: }
1732: }
1733: }
1734:
1735: protected void selectNextTabInRun(int current) {
1736: int tabCount = tabPane.getTabCount();
1737: int tabIndex = getNextTabIndexInRun(tabCount, current);
1738:
1739: while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
1740: tabIndex = getNextTabIndexInRun(tabCount, tabIndex);
1741: }
1742: tabPane.setSelectedIndex(tabIndex);
1743: }
1744:
1745: protected void selectPreviousTabInRun(int current) {
1746: int tabCount = tabPane.getTabCount();
1747: int tabIndex = getPreviousTabIndexInRun(tabCount, current);
1748:
1749: while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
1750: tabIndex = getPreviousTabIndexInRun(tabCount, tabIndex);
1751: }
1752: tabPane.setSelectedIndex(tabIndex);
1753: }
1754:
1755: protected void selectNextTab(int current) {
1756: int tabIndex = getNextTabIndex(current);
1757:
1758: while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
1759: tabIndex = getNextTabIndex(tabIndex);
1760: }
1761: tabPane.setSelectedIndex(tabIndex);
1762: }
1763:
1764: protected void selectPreviousTab(int current) {
1765: int tabIndex = getPreviousTabIndex(current);
1766:
1767: while (tabIndex != current && !tabPane.isEnabledAt(tabIndex)) {
1768: tabIndex = getPreviousTabIndex(tabIndex);
1769: }
1770: tabPane.setSelectedIndex(tabIndex);
1771: }
1772:
1773: protected void selectAdjacentRunTab(int tabPlacement, int tabIndex,
1774: int offset) {
1775: if (runCount < 2) {
1776: return;
1777: }
1778: int newIndex;
1779: Rectangle r = rects[tabIndex];
1780: switch (tabPlacement) {
1781: case LEFT:
1782: case RIGHT:
1783: newIndex = getTabAtLocation(r.x + r.width / 2 + offset, r.y
1784: + r.height / 2);
1785: break;
1786: case BOTTOM:
1787: case TOP:
1788: default:
1789: newIndex = getTabAtLocation(r.x + r.width / 2, r.y
1790: + r.height / 2 + offset);
1791: }
1792: if (newIndex != -1) {
1793: while (!tabPane.isEnabledAt(newIndex)
1794: && newIndex != tabIndex) {
1795: newIndex = getNextTabIndex(newIndex);
1796: }
1797: tabPane.setSelectedIndex(newIndex);
1798: }
1799: }
1800:
1801: protected int getTabRunOffset(int tabPlacement, int tabCount,
1802: int tabIndex, boolean forward) {
1803: int run = getRunForTab(tabCount, tabIndex);
1804: int offset;
1805: switch (tabPlacement) {
1806: case LEFT: {
1807: if (run == 0) {
1808: offset = (forward ? -(calculateTabAreaWidth(
1809: tabPlacement, runCount, maxTabWidth) - maxTabWidth)
1810: : -maxTabWidth);
1811:
1812: } else if (run == runCount - 1) {
1813: offset = (forward ? maxTabWidth
1814: : calculateTabAreaWidth(tabPlacement, runCount,
1815: maxTabWidth)
1816: - maxTabWidth);
1817: } else {
1818: offset = (forward ? maxTabWidth : -maxTabWidth);
1819: }
1820: break;
1821: }
1822: case RIGHT: {
1823: if (run == 0) {
1824: offset = (forward ? maxTabWidth
1825: : calculateTabAreaWidth(tabPlacement, runCount,
1826: maxTabWidth)
1827: - maxTabWidth);
1828: } else if (run == runCount - 1) {
1829: offset = (forward ? -(calculateTabAreaWidth(
1830: tabPlacement, runCount, maxTabWidth) - maxTabWidth)
1831: : -maxTabWidth);
1832: } else {
1833: offset = (forward ? maxTabWidth : -maxTabWidth);
1834: }
1835: break;
1836: }
1837: case BOTTOM: {
1838: if (run == 0) {
1839: offset = (forward ? maxTabHeight
1840: : calculateTabAreaHeight(tabPlacement,
1841: runCount, maxTabHeight)
1842: - maxTabHeight);
1843: } else if (run == runCount - 1) {
1844: offset = (forward ? -(calculateTabAreaHeight(
1845: tabPlacement, runCount, maxTabHeight) - maxTabHeight)
1846: : -maxTabHeight);
1847: } else {
1848: offset = (forward ? maxTabHeight : -maxTabHeight);
1849: }
1850: break;
1851: }
1852: case TOP:
1853: default: {
1854: if (run == 0) {
1855: offset = (forward ? -(calculateTabAreaHeight(
1856: tabPlacement, runCount, maxTabHeight) - maxTabHeight)
1857: : -maxTabHeight);
1858: } else if (run == runCount - 1) {
1859: offset = (forward ? maxTabHeight
1860: : calculateTabAreaHeight(tabPlacement,
1861: runCount, maxTabHeight)
1862: - maxTabHeight);
1863: } else {
1864: offset = (forward ? maxTabHeight : -maxTabHeight);
1865: }
1866: }
1867: }
1868: return offset;
1869: }
1870:
1871: protected int getPreviousTabIndex(int base) {
1872: int tabIndex = (base - 1 >= 0 ? base - 1 : tabPane
1873: .getTabCount() - 1);
1874: return (tabIndex >= 0 ? tabIndex : 0);
1875: }
1876:
1877: protected int getNextTabIndex(int base) {
1878: return (base + 1) % tabPane.getTabCount();
1879: }
1880:
1881: protected int getNextTabIndexInRun(int tabCount, int base) {
1882: if (runCount < 2) {
1883: return getNextTabIndex(base);
1884: }
1885: int currentRun = getRunForTab(tabCount, base);
1886: int next = getNextTabIndex(base);
1887: if (next == tabRuns[getNextTabRun(currentRun)]) {
1888: return tabRuns[currentRun];
1889: }
1890: return next;
1891: }
1892:
1893: protected int getPreviousTabIndexInRun(int tabCount, int base) {
1894: if (runCount < 2) {
1895: return getPreviousTabIndex(base);
1896: }
1897: int currentRun = getRunForTab(tabCount, base);
1898: if (base == tabRuns[currentRun]) {
1899: int previous = tabRuns[getNextTabRun(currentRun)] - 1;
1900: return (previous != -1 ? previous : tabCount - 1);
1901: }
1902: return getPreviousTabIndex(base);
1903: }
1904:
1905: protected int getPreviousTabRun(int baseRun) {
1906: int runIndex = (baseRun - 1 >= 0 ? baseRun - 1 : runCount - 1);
1907: return (runIndex >= 0 ? runIndex : 0);
1908: }
1909:
1910: protected int getNextTabRun(int baseRun) {
1911: return (baseRun + 1) % runCount;
1912: }
1913:
1914: protected static void rotateInsets(Insets topInsets,
1915: Insets targetInsets, int targetPlacement) {
1916:
1917: switch (targetPlacement) {
1918: case LEFT:
1919: targetInsets.top = topInsets.left;
1920: targetInsets.left = topInsets.top;
1921: targetInsets.bottom = topInsets.right;
1922: targetInsets.right = topInsets.bottom;
1923: break;
1924: case BOTTOM:
1925: targetInsets.top = topInsets.bottom;
1926: targetInsets.left = topInsets.left;
1927: targetInsets.bottom = topInsets.top;
1928: targetInsets.right = topInsets.right;
1929: break;
1930: case RIGHT:
1931: targetInsets.top = topInsets.left;
1932: targetInsets.left = topInsets.bottom;
1933: targetInsets.bottom = topInsets.right;
1934: targetInsets.right = topInsets.top;
1935: break;
1936: case TOP:
1937: default:
1938: targetInsets.top = topInsets.top;
1939: targetInsets.left = topInsets.left;
1940: targetInsets.bottom = topInsets.bottom;
1941: targetInsets.right = topInsets.right;
1942: }
1943: }
1944:
1945: // REMIND(aim,7/29/98): This method should be made
1946: // protected in the next release where
1947: // API changes are allowed
1948: //
1949: boolean requestFocusForVisibleComponent() {
1950: Component visibleComponent = getVisibleComponent();
1951:
1952: if (visibleComponent.isFocusable()) {
1953: visibleComponent.requestFocus();
1954: return true;
1955: }
1956:
1957: else if (visibleComponent instanceof JComponent) {
1958: JComponent jComponent = (JComponent) visibleComponent;
1959:
1960: //************* CHECK THIS **********************************
1961: if (jComponent.getFocusTraversalPolicy()
1962: .getDefaultComponent(jComponent)
1963: .requestFocusInWindow()) {
1964: return true;
1965: }
1966:
1967: // if (((JComponent)visibleComponent).requestDefaultFocus()) {
1968: // return true;
1969: // }
1970: }
1971:
1972: return false;
1973:
1974: }
1975:
1976: private static class RightAction extends AbstractAction {
1977: public void actionPerformed(ActionEvent e) {
1978: JTabbedPane pane = (JTabbedPane) e.getSource();
1979: CloseTabbedPaneUI ui = (CloseTabbedPaneUI) pane.getUI();
1980: ui.navigateSelectedTab(EAST);
1981: }
1982: };
1983:
1984: private static class LeftAction extends AbstractAction {
1985: public void actionPerformed(ActionEvent e) {
1986: JTabbedPane pane = (JTabbedPane) e.getSource();
1987: CloseTabbedPaneUI ui = (CloseTabbedPaneUI) pane.getUI();
1988: ui.navigateSelectedTab(WEST);
1989: }
1990: };
1991:
1992: private static class UpAction extends AbstractAction {
1993: public void actionPerformed(ActionEvent e) {
1994: JTabbedPane pane = (JTabbedPane) e.getSource();
1995: CloseTabbedPaneUI ui = (CloseTabbedPaneUI) pane.getUI();
1996: ui.navigateSelectedTab(NORTH);
1997: }
1998: };
1999:
2000: private static class DownAction extends AbstractAction {
2001: public void actionPerformed(ActionEvent e) {
2002: JTabbedPane pane = (JTabbedPane) e.getSource();
2003: CloseTabbedPaneUI ui = (CloseTabbedPaneUI) pane.getUI();
2004: ui.navigateSelectedTab(SOUTH);
2005: }
2006: };
2007:
2008: private static class NextAction extends AbstractAction {
2009: public void actionPerformed(ActionEvent e) {
2010: JTabbedPane pane = (JTabbedPane) e.getSource();
2011: CloseTabbedPaneUI ui = (CloseTabbedPaneUI) pane.getUI();
2012: ui.navigateSelectedTab(NEXT);
2013: }
2014: };
2015:
2016: private static class PreviousAction extends AbstractAction {
2017: public void actionPerformed(ActionEvent e) {
2018: JTabbedPane pane = (JTabbedPane) e.getSource();
2019: CloseTabbedPaneUI ui = (CloseTabbedPaneUI) pane.getUI();
2020: ui.navigateSelectedTab(PREVIOUS);
2021: }
2022: };
2023:
2024: private static class PageUpAction extends AbstractAction {
2025: public void actionPerformed(ActionEvent e) {
2026: JTabbedPane pane = (JTabbedPane) e.getSource();
2027: CloseTabbedPaneUI ui = (CloseTabbedPaneUI) pane.getUI();
2028: int tabPlacement = pane.getTabPlacement();
2029: if (tabPlacement == TOP || tabPlacement == BOTTOM) {
2030: ui.navigateSelectedTab(WEST);
2031: } else {
2032: ui.navigateSelectedTab(NORTH);
2033: }
2034: }
2035: };
2036:
2037: private static class PageDownAction extends AbstractAction {
2038: public void actionPerformed(ActionEvent e) {
2039: JTabbedPane pane = (JTabbedPane) e.getSource();
2040: CloseTabbedPaneUI ui = (CloseTabbedPaneUI) pane.getUI();
2041: int tabPlacement = pane.getTabPlacement();
2042: if (tabPlacement == TOP || tabPlacement == BOTTOM) {
2043: ui.navigateSelectedTab(EAST);
2044: } else {
2045: ui.navigateSelectedTab(SOUTH);
2046: }
2047: }
2048: };
2049:
2050: private static class RequestFocusAction extends AbstractAction {
2051: public void actionPerformed(ActionEvent e) {
2052: JTabbedPane pane = (JTabbedPane) e.getSource();
2053: pane.requestFocus();
2054: }
2055: };
2056:
2057: private static class RequestFocusForVisibleAction extends
2058: AbstractAction {
2059: public void actionPerformed(ActionEvent e) {
2060: JTabbedPane pane = (JTabbedPane) e.getSource();
2061: CloseTabbedPaneUI ui = (CloseTabbedPaneUI) pane.getUI();
2062: ui.requestFocusForVisibleComponent();
2063: }
2064: };
2065:
2066: /**
2067: * Selects a tab in the JTabbedPane based on the String of the
2068: * action command. The tab selected is based on the first tab that
2069: * has a mnemonic matching the first character of the action command.
2070: */
2071: private static class SetSelectedIndexAction extends AbstractAction {
2072: public void actionPerformed(ActionEvent e) {
2073: JTabbedPane pane = (JTabbedPane) e.getSource();
2074:
2075: if (pane != null
2076: && (pane.getUI() instanceof CloseTabbedPaneUI)) {
2077: CloseTabbedPaneUI ui = (CloseTabbedPaneUI) pane.getUI();
2078: String command = e.getActionCommand();
2079:
2080: if (command != null && command.length() > 0) {
2081: int mnemonic = (int) e.getActionCommand().charAt(0);
2082: if (mnemonic >= 'a' && mnemonic <= 'z') {
2083: mnemonic -= ('a' - 'A');
2084: }
2085: Integer index = (Integer) ui.mnemonicToIndexMap
2086: .get(new Integer(mnemonic));
2087: if (index != null
2088: && pane.isEnabledAt(index.intValue())) {
2089: pane.setSelectedIndex(index.intValue());
2090: }
2091: }
2092: }
2093: }
2094: };
2095:
2096: /*
2097: private static class ScrollTabsForwardAction extends AbstractAction {
2098: public void actionPerformed(ActionEvent e) {
2099: JTabbedPane pane = null;
2100: Object src = e.getSource();
2101: if (src instanceof JTabbedPane) {
2102: pane = (JTabbedPane)src;
2103: } else if (src instanceof ScrollableTabButton) {
2104: pane = (JTabbedPane)((ScrollableTabButton)src).getParent();
2105: } else {
2106: return; // shouldn't happen
2107: }
2108:
2109: System.out.println("pane: "+pane.getClass().getName());
2110: System.out.println("ui: "+(pane.getUI()).getClass().getName());
2111:
2112: CloseTabbedPaneUI ui = (CloseTabbedPaneUI)pane.getUI();
2113:
2114: if (ui.scrollableTabLayoutEnabled()) {
2115: ui.tabScroller.scrollForward(pane.getTabPlacement());
2116: }
2117: }
2118: }
2119:
2120: private static class ScrollTabsBackwardAction extends AbstractAction {
2121: public void actionPerformed(ActionEvent e) {
2122: JTabbedPane pane = null;
2123: Object src = e.getSource();
2124: if (src instanceof JTabbedPane) {
2125: pane = (JTabbedPane)src;
2126: } else if (src instanceof ScrollableTabButton) {
2127: pane = (JTabbedPane)((ScrollableTabButton)src).getParent();
2128: } else {
2129: return; // shouldn't happen
2130: }
2131: CloseTabbedPaneUI ui = (CloseTabbedPaneUI)pane.getUI();
2132:
2133: if (ui.scrollableTabLayoutEnabled()) {
2134: ui.tabScroller.scrollBackward(pane.getTabPlacement());
2135: }
2136: }
2137: }
2138: */
2139:
2140: /**
2141: * This inner class is marked "public" due to a compiler bug.
2142: * This class should be treated as a "protected" inner class.
2143: * Instantiate it only within subclasses of CloseTabbedPaneUI.
2144: */
2145: public class TabbedPaneLayout implements LayoutManager {
2146:
2147: public void addLayoutComponent(String name, Component comp) {
2148: }
2149:
2150: public void removeLayoutComponent(Component comp) {
2151: }
2152:
2153: public Dimension preferredLayoutSize(Container parent) {
2154: return calculateSize(false);
2155: }
2156:
2157: public Dimension minimumLayoutSize(Container parent) {
2158: return calculateSize(true);
2159: }
2160:
2161: protected Dimension calculateSize(boolean minimum) {
2162: int tabPlacement = tabPane.getTabPlacement();
2163: Insets insets = tabPane.getInsets();
2164: Insets contentInsets = getContentBorderInsets(tabPlacement);
2165: Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
2166:
2167: Dimension zeroSize = new Dimension(0, 0);
2168: int height = contentInsets.top + contentInsets.bottom;
2169: int width = contentInsets.left + contentInsets.right;
2170: int cWidth = 0;
2171: int cHeight = 0;
2172:
2173: // Determine minimum size required to display largest
2174: // child in each dimension
2175: //
2176: for (int i = 0; i < tabPane.getTabCount(); i++) {
2177: Component component = tabPane.getComponentAt(i);
2178: if (component != null) {
2179: Dimension size = zeroSize;
2180: size = minimum ? component.getMinimumSize()
2181: : component.getPreferredSize();
2182:
2183: if (size != null) {
2184: cHeight = Math.max(size.height, cHeight);
2185: cWidth = Math.max(size.width, cWidth);
2186: }
2187: }
2188: }
2189: // Add content border insets to minimum size
2190: width += cWidth;
2191: height += cHeight;
2192: int tabExtent = 0;
2193:
2194: // Calculate how much space the tabs will need, based on the
2195: // minimum size required to display largest child + content border
2196: //
2197: switch (tabPlacement) {
2198: case LEFT:
2199: case RIGHT:
2200: height = Math.max(height,
2201: calculateMaxTabHeight(tabPlacement)
2202: + tabAreaInsets.top
2203: + tabAreaInsets.bottom);
2204: tabExtent = preferredTabAreaWidth(tabPlacement, height);
2205: width += tabExtent;
2206: break;
2207: case TOP:
2208: case BOTTOM:
2209: default:
2210: width = Math.max(width,
2211: calculateMaxTabWidth(tabPlacement)
2212: + tabAreaInsets.left
2213: + tabAreaInsets.right);
2214: tabExtent = preferredTabAreaHeight(tabPlacement, width);
2215: height += tabExtent;
2216: }
2217: return new Dimension(width + insets.left + insets.right,
2218: height + insets.bottom + insets.top);
2219:
2220: }
2221:
2222: protected int preferredTabAreaHeight(int tabPlacement, int width) {
2223: FontMetrics metrics = getFontMetrics();
2224: int tabCount = tabPane.getTabCount();
2225: int total = 0;
2226: if (tabCount > 0) {
2227: int rows = 1;
2228: int x = 0;
2229:
2230: int maxTabHeight = calculateMaxTabHeight(tabPlacement);
2231:
2232: for (int i = 0; i < tabCount; i++) {
2233: int tabWidth = calculateTabWidth(tabPlacement, i,
2234: metrics);
2235:
2236: if (x != 0 && x + tabWidth > width) {
2237: rows++;
2238: x = 0;
2239: }
2240: x += tabWidth;
2241: }
2242: total = calculateTabAreaHeight(tabPlacement, rows,
2243: maxTabHeight);
2244: }
2245: return total;
2246: }
2247:
2248: protected int preferredTabAreaWidth(int tabPlacement, int height) {
2249: FontMetrics metrics = getFontMetrics();
2250: int tabCount = tabPane.getTabCount();
2251: int total = 0;
2252: if (tabCount > 0) {
2253: int columns = 1;
2254: int y = 0;
2255: int fontHeight = metrics.getHeight();
2256:
2257: maxTabWidth = calculateMaxTabWidth(tabPlacement);
2258:
2259: for (int i = 0; i < tabCount; i++) {
2260: int tabHeight = calculateTabHeight(tabPlacement, i,
2261: fontHeight);
2262:
2263: if (y != 0 && y + tabHeight > height) {
2264: columns++;
2265: y = 0;
2266: }
2267: y += tabHeight;
2268: }
2269: total = calculateTabAreaWidth(tabPlacement, columns,
2270: maxTabWidth);
2271: }
2272: return total;
2273: }
2274:
2275: public void layoutContainer(Container parent) {
2276: int tabPlacement = tabPane.getTabPlacement();
2277: Insets insets = tabPane.getInsets();
2278: int selectedIndex = tabPane.getSelectedIndex();
2279: Component visibleComponent = getVisibleComponent();
2280:
2281: calculateLayoutInfo();
2282:
2283: if (selectedIndex < 0) {
2284: if (visibleComponent != null) {
2285: // The last tab was removed, so remove the component
2286: setVisibleComponent(null);
2287: }
2288: } else {
2289: int cx, cy, cw, ch;
2290: int totalTabWidth = 0;
2291: int totalTabHeight = 0;
2292: Insets contentInsets = getContentBorderInsets(tabPlacement);
2293:
2294: Component selectedComponent = tabPane
2295: .getComponentAt(selectedIndex);
2296: boolean shouldChangeFocus = false;
2297:
2298: // In order to allow programs to use a single component
2299: // as the display for multiple tabs, we will not change
2300: // the visible compnent if the currently selected tab
2301: // has a null component. This is a bit dicey, as we don't
2302: // explicitly state we support this in the spec, but since
2303: // programs are now depending on this, we're making it work.
2304: //
2305: if (selectedComponent != null) {
2306: if (selectedComponent != visibleComponent
2307: && visibleComponent != null) {
2308:
2309: if (SwingUtilities
2310: .findFocusOwner(visibleComponent) != null) {
2311: shouldChangeFocus = true;
2312: }
2313: }
2314: setVisibleComponent(selectedComponent);
2315: }
2316:
2317: Rectangle bounds = tabPane.getBounds();
2318: int numChildren = tabPane.getComponentCount();
2319:
2320: if (numChildren > 0) {
2321:
2322: switch (tabPlacement) {
2323: case LEFT:
2324: totalTabWidth = calculateTabAreaWidth(
2325: tabPlacement, runCount, maxTabWidth);
2326: cx = insets.left + totalTabWidth
2327: + contentInsets.left;
2328: cy = insets.top + contentInsets.top;
2329: break;
2330: case RIGHT:
2331: totalTabWidth = calculateTabAreaWidth(
2332: tabPlacement, runCount, maxTabWidth);
2333: cx = insets.left + contentInsets.left;
2334: cy = insets.top + contentInsets.top;
2335: break;
2336: case BOTTOM:
2337: totalTabHeight = calculateTabAreaHeight(
2338: tabPlacement, runCount, maxTabHeight);
2339: cx = insets.left + contentInsets.left;
2340: cy = insets.top + contentInsets.top;
2341: break;
2342: case TOP:
2343: default:
2344: totalTabHeight = calculateTabAreaHeight(
2345: tabPlacement, runCount, maxTabHeight);
2346: cx = insets.left + contentInsets.left;
2347: cy = insets.top + totalTabHeight
2348: + contentInsets.top;
2349: }
2350:
2351: cw = bounds.width - totalTabWidth - insets.left
2352: - insets.right - contentInsets.left
2353: - contentInsets.right;
2354: ch = bounds.height - totalTabHeight - insets.top
2355: - insets.bottom - contentInsets.top
2356: - contentInsets.bottom;
2357:
2358: for (int i = 0; i < numChildren; i++) {
2359: Component child = tabPane.getComponent(i);
2360: child.setBounds(cx, cy, cw, ch);
2361: }
2362: }
2363:
2364: if (shouldChangeFocus) {
2365: if (!requestFocusForVisibleComponent()) {
2366: tabPane.requestFocus();
2367: }
2368: }
2369: }
2370: }
2371:
2372: public void calculateLayoutInfo() {
2373: int tabCount = tabPane.getTabCount();
2374: assureRectsCreated(tabCount);
2375: calculateTabRects(tabPane.getTabPlacement(), tabCount);
2376: }
2377:
2378: protected void calculateTabRects(int tabPlacement, int tabCount) {
2379: FontMetrics metrics = getFontMetrics();
2380: Dimension size = tabPane.getSize();
2381: Insets insets = tabPane.getInsets();
2382: Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
2383: int fontHeight = metrics.getHeight();
2384: int selectedIndex = tabPane.getSelectedIndex();
2385: int tabRunOverlay;
2386: int i, j;
2387: int x, y;
2388: int returnAt;
2389: boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
2390: boolean leftToRight = isLeftToRight(tabPane);
2391:
2392: //
2393: // Calculate bounds within which a tab run must fit
2394: //
2395: switch (tabPlacement) {
2396: case LEFT:
2397: maxTabWidth = calculateMaxTabWidth(tabPlacement);
2398: x = insets.left + tabAreaInsets.left;
2399: y = insets.top + tabAreaInsets.top;
2400: returnAt = size.height
2401: - (insets.bottom + tabAreaInsets.bottom);
2402: break;
2403: case RIGHT:
2404: maxTabWidth = calculateMaxTabWidth(tabPlacement);
2405: x = size.width - insets.right - tabAreaInsets.right
2406: - maxTabWidth;
2407: y = insets.top + tabAreaInsets.top;
2408: returnAt = size.height
2409: - (insets.bottom + tabAreaInsets.bottom);
2410: break;
2411: case BOTTOM:
2412: maxTabHeight = calculateMaxTabHeight(tabPlacement);
2413: x = insets.left + tabAreaInsets.left;
2414: y = size.height - insets.bottom - tabAreaInsets.bottom
2415: - maxTabHeight;
2416: returnAt = size.width
2417: - (insets.right + tabAreaInsets.right);
2418: break;
2419: case TOP:
2420: default:
2421: maxTabHeight = calculateMaxTabHeight(tabPlacement);
2422: x = insets.left + tabAreaInsets.left;
2423: y = insets.top + tabAreaInsets.top;
2424: returnAt = size.width
2425: - (insets.right + tabAreaInsets.right);
2426: break;
2427: }
2428:
2429: tabRunOverlay = getTabRunOverlay(tabPlacement);
2430:
2431: runCount = 0;
2432: selectedRun = -1;
2433:
2434: if (tabCount == 0) {
2435: return;
2436: }
2437:
2438: // Run through tabs and partition them into runs
2439: Rectangle rect;
2440: for (i = 0; i < tabCount; i++) {
2441: rect = rects[i];
2442:
2443: if (!verticalTabRuns) {
2444: // Tabs on TOP or BOTTOM....
2445: if (i > 0) {
2446: rect.x = rects[i - 1].x + rects[i - 1].width;
2447: } else {
2448: tabRuns[0] = 0;
2449: runCount = 1;
2450: maxTabWidth = 0;
2451: rect.x = x;
2452: }
2453: rect.width = calculateTabWidth(tabPlacement, i,
2454: metrics);
2455: maxTabWidth = Math.max(maxTabWidth, rect.width);
2456:
2457: // Never move a TAB down a run if it is in the first column.
2458: // Even if there isn't enough room, moving it to a fresh
2459: // line won't help.
2460: if (rect.x != 2 + insets.left
2461: && rect.x + rect.width > returnAt) {
2462: if (runCount > tabRuns.length - 1) {
2463: expandTabRunsArray();
2464: }
2465: tabRuns[runCount] = i;
2466: runCount++;
2467: rect.x = x;
2468: }
2469: // Initialize y position in case there's just one run
2470: rect.y = y;
2471: rect.height = maxTabHeight/* - 2*/;
2472:
2473: } else {
2474: // Tabs on LEFT or RIGHT...
2475: if (i > 0) {
2476: rect.y = rects[i - 1].y + rects[i - 1].height;
2477: } else {
2478: tabRuns[0] = 0;
2479: runCount = 1;
2480: maxTabHeight = 0;
2481: rect.y = y;
2482: }
2483: rect.height = calculateTabHeight(tabPlacement, i,
2484: fontHeight);
2485: maxTabHeight = Math.max(maxTabHeight, rect.height);
2486:
2487: // Never move a TAB over a run if it is in the first run.
2488: // Even if there isn't enough room, moving it to a fresh
2489: // column won't help.
2490: if (rect.y != 2 + insets.top
2491: && rect.y + rect.height > returnAt) {
2492: if (runCount > tabRuns.length - 1) {
2493: expandTabRunsArray();
2494: }
2495: tabRuns[runCount] = i;
2496: runCount++;
2497: rect.y = y;
2498: }
2499: // Initialize x position in case there's just one column
2500: rect.x = x;
2501: rect.width = maxTabWidth/* - 2*/;
2502:
2503: }
2504: if (i == selectedIndex) {
2505: selectedRun = runCount - 1;
2506: }
2507: }
2508:
2509: if (runCount > 1) {
2510: // Re-distribute tabs in case last run has leftover space
2511: normalizeTabRuns(tabPlacement, tabCount,
2512: verticalTabRuns ? y : x, returnAt);
2513:
2514: selectedRun = getRunForTab(tabCount, selectedIndex);
2515:
2516: // Rotate run array so that selected run is first
2517: if (shouldRotateTabRuns(tabPlacement)) {
2518: rotateTabRuns(tabPlacement, selectedRun);
2519: }
2520: }
2521:
2522: // Step through runs from back to front to calculate
2523: // tab y locations and to pad runs appropriately
2524: for (i = runCount - 1; i >= 0; i--) {
2525: int start = tabRuns[i];
2526: int next = tabRuns[i == (runCount - 1) ? 0 : i + 1];
2527: int end = (next != 0 ? next - 1 : tabCount - 1);
2528: if (!verticalTabRuns) {
2529: for (j = start; j <= end; j++) {
2530: rect = rects[j];
2531: rect.y = y;
2532: rect.x += getTabRunIndent(tabPlacement, i);
2533: }
2534: if (shouldPadTabRun(tabPlacement, i)) {
2535: padTabRun(tabPlacement, start, end, returnAt);
2536: }
2537: if (tabPlacement == BOTTOM) {
2538: y -= (maxTabHeight - tabRunOverlay);
2539: } else {
2540: y += (maxTabHeight - tabRunOverlay);
2541: }
2542: } else {
2543: for (j = start; j <= end; j++) {
2544: rect = rects[j];
2545: rect.x = x;
2546: rect.y += getTabRunIndent(tabPlacement, i);
2547: }
2548: if (shouldPadTabRun(tabPlacement, i)) {
2549: padTabRun(tabPlacement, start, end, returnAt);
2550: }
2551: if (tabPlacement == RIGHT) {
2552: x -= (maxTabWidth - tabRunOverlay);
2553: } else {
2554: x += (maxTabWidth - tabRunOverlay);
2555: }
2556: }
2557: }
2558:
2559: // Pad the selected tab so that it appears raised in front
2560: padSelectedTab(tabPlacement, selectedIndex);
2561:
2562: // if right to left and tab placement on the top or
2563: // the bottom, flip x positions and adjust by widths
2564: if (!leftToRight && !verticalTabRuns) {
2565: int rightMargin = size.width
2566: - (insets.right + tabAreaInsets.right);
2567: for (i = 0; i < tabCount; i++) {
2568: rects[i].x = rightMargin - rects[i].x
2569: - rects[i].width;
2570: }
2571: }
2572: }
2573:
2574: /*
2575: * Rotates the run-index array so that the selected run is run[0]
2576: */
2577: protected void rotateTabRuns(int tabPlacement, int selectedRun) {
2578: for (int i = 0; i < selectedRun; i++) {
2579: int save = tabRuns[0];
2580: for (int j = 1; j < runCount; j++) {
2581: tabRuns[j - 1] = tabRuns[j];
2582: }
2583: tabRuns[runCount - 1] = save;
2584: }
2585: }
2586:
2587: protected void normalizeTabRuns(int tabPlacement, int tabCount,
2588: int start, int max) {
2589: boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
2590: int run = runCount - 1;
2591: boolean keepAdjusting = true;
2592: double weight = 1.25;
2593:
2594: // At this point the tab runs are packed to fit as many
2595: // tabs as possible, which can leave the last run with a lot
2596: // of extra space (resulting in very fat tabs on the last run).
2597: // So we'll attempt to distribute this extra space more evenly
2598: // across the runs in order to make the runs look more consistent.
2599: //
2600: // Starting with the last run, determine whether the last tab in
2601: // the previous run would fit (generously) in this run; if so,
2602: // move tab to current run and shift tabs accordingly. Cycle
2603: // through remaining runs using the same algorithm.
2604: //
2605: while (keepAdjusting) {
2606: int last = lastTabInRun(tabCount, run);
2607: int prevLast = lastTabInRun(tabCount, run - 1);
2608: int end;
2609: int prevLastLen;
2610:
2611: if (!verticalTabRuns) {
2612: end = rects[last].x + rects[last].width;
2613: prevLastLen = (int) (maxTabWidth * weight);
2614: } else {
2615: end = rects[last].y + rects[last].height;
2616: prevLastLen = (int) (maxTabHeight * weight * 2);
2617: }
2618:
2619: // Check if the run has enough extra space to fit the last tab
2620: // from the previous row...
2621: if (max - end > prevLastLen) {
2622:
2623: // Insert tab from previous row and shift rest over
2624: tabRuns[run] = prevLast;
2625: if (!verticalTabRuns) {
2626: rects[prevLast].x = start;
2627: } else {
2628: rects[prevLast].y = start;
2629: }
2630: for (int i = prevLast + 1; i <= last; i++) {
2631: if (!verticalTabRuns) {
2632: rects[i].x = rects[i - 1].x
2633: + rects[i - 1].width;
2634: } else {
2635: rects[i].y = rects[i - 1].y
2636: + rects[i - 1].height;
2637: }
2638: }
2639:
2640: } else if (run == runCount - 1) {
2641: // no more room left in last run, so we're done!
2642: keepAdjusting = false;
2643: }
2644: if (run - 1 > 0) {
2645: // check previous run next...
2646: run -= 1;
2647: } else {
2648: // check last run again...but require a higher ratio
2649: // of extraspace-to-tabsize because we don't want to
2650: // end up with too many tabs on the last run!
2651: run = runCount - 1;
2652: weight += .25;
2653: }
2654: }
2655: }
2656:
2657: protected void padTabRun(int tabPlacement, int start, int end,
2658: int max) {
2659: Rectangle lastRect = rects[end];
2660: if (tabPlacement == TOP || tabPlacement == BOTTOM) {
2661: int runWidth = (lastRect.x + lastRect.width)
2662: - rects[start].x;
2663: int deltaWidth = max - (lastRect.x + lastRect.width);
2664: float factor = (float) deltaWidth / (float) runWidth;
2665:
2666: for (int j = start; j <= end; j++) {
2667: Rectangle pastRect = rects[j];
2668: if (j > start) {
2669: pastRect.x = rects[j - 1].x
2670: + rects[j - 1].width;
2671: }
2672: pastRect.width += Math.round((float) pastRect.width
2673: * factor);
2674: }
2675: lastRect.width = max - lastRect.x;
2676: } else {
2677: int runHeight = (lastRect.y + lastRect.height)
2678: - rects[start].y;
2679: int deltaHeight = max - (lastRect.y + lastRect.height);
2680: float factor = (float) deltaHeight / (float) runHeight;
2681:
2682: for (int j = start; j <= end; j++) {
2683: Rectangle pastRect = rects[j];
2684: if (j > start) {
2685: pastRect.y = rects[j - 1].y
2686: + rects[j - 1].height;
2687: }
2688: pastRect.height += Math
2689: .round((float) pastRect.height * factor);
2690: }
2691: lastRect.height = max - lastRect.y;
2692: }
2693: }
2694:
2695: protected void padSelectedTab(int tabPlacement,
2696: int selectedIndex) {
2697:
2698: if (selectedIndex >= 0) {
2699: Rectangle selRect = rects[selectedIndex];
2700: Insets padInsets = getSelectedTabPadInsets(tabPlacement);
2701: selRect.x -= padInsets.left;
2702: selRect.width += (padInsets.left + padInsets.right);
2703: selRect.y -= padInsets.top;
2704: selRect.height += (padInsets.top + padInsets.bottom);
2705: }
2706: }
2707: }
2708:
2709: private class TabbedPaneScrollLayout extends TabbedPaneLayout {
2710:
2711: protected int preferredTabAreaHeight(int tabPlacement, int width) {
2712: return calculateMaxTabHeight(tabPlacement);
2713: }
2714:
2715: protected int preferredTabAreaWidth(int tabPlacement, int height) {
2716: return calculateMaxTabWidth(tabPlacement);
2717: }
2718:
2719: public void layoutContainer(Container parent) {
2720: int tabPlacement = tabPane.getTabPlacement();
2721: int tabCount = tabPane.getTabCount();
2722: Insets insets = tabPane.getInsets();
2723: int selectedIndex = tabPane.getSelectedIndex();
2724: Component visibleComponent = getVisibleComponent();
2725:
2726: calculateLayoutInfo();
2727:
2728: if (selectedIndex < 0) {
2729: if (visibleComponent != null) {
2730: // The last tab was removed, so remove the component
2731: setVisibleComponent(null);
2732: }
2733: } else {
2734: Component selectedComponent = tabPane
2735: .getComponentAt(selectedIndex);
2736: boolean shouldChangeFocus = false;
2737:
2738: // In order to allow programs to use a single component
2739: // as the display for multiple tabs, we will not change
2740: // the visible compnent if the currently selected tab
2741: // has a null component. This is a bit dicey, as we don't
2742: // explicitly state we support this in the spec, but since
2743: // programs are now depending on this, we're making it work.
2744: //
2745: if (selectedComponent != null) {
2746: if (selectedComponent != visibleComponent
2747: && visibleComponent != null) {
2748: if (SwingUtilities
2749: .findFocusOwner(visibleComponent) != null) {
2750: shouldChangeFocus = true;
2751: }
2752: }
2753: setVisibleComponent(selectedComponent);
2754: }
2755: int tx, ty, tw, th; // tab area bounds
2756: int cx, cy, cw, ch; // content area bounds
2757: Insets contentInsets = getContentBorderInsets(tabPlacement);
2758: Rectangle bounds = tabPane.getBounds();
2759: int numChildren = tabPane.getComponentCount();
2760:
2761: if (numChildren > 0) {
2762: switch (tabPlacement) {
2763: case LEFT:
2764: // calculate tab area bounds
2765: tw = calculateTabAreaWidth(tabPlacement,
2766: runCount, maxTabWidth);
2767: th = bounds.height - insets.top - insets.bottom;
2768: tx = insets.left;
2769: ty = insets.top;
2770:
2771: // calculate content area bounds
2772: cx = tx + tw + contentInsets.left;
2773: cy = ty + contentInsets.top;
2774: cw = bounds.width - insets.left - insets.right
2775: - tw - contentInsets.left
2776: - contentInsets.right;
2777: ch = bounds.height - insets.top - insets.bottom
2778: - contentInsets.top
2779: - contentInsets.bottom;
2780: break;
2781: case RIGHT:
2782: // calculate tab area bounds
2783: tw = calculateTabAreaWidth(tabPlacement,
2784: runCount, maxTabWidth);
2785: th = bounds.height - insets.top - insets.bottom;
2786: tx = bounds.width - insets.right - tw;
2787: ty = insets.top;
2788:
2789: // calculate content area bounds
2790: cx = insets.left + contentInsets.left;
2791: cy = insets.top + contentInsets.top;
2792: cw = bounds.width - insets.left - insets.right
2793: - tw - contentInsets.left
2794: - contentInsets.right;
2795: ch = bounds.height - insets.top - insets.bottom
2796: - contentInsets.top
2797: - contentInsets.bottom;
2798: break;
2799: case BOTTOM:
2800: // calculate tab area bounds
2801: tw = bounds.width - insets.left - insets.right;
2802: th = calculateTabAreaHeight(tabPlacement,
2803: runCount, maxTabHeight);
2804: tx = insets.left;
2805: ty = bounds.height - insets.bottom - th;
2806:
2807: // calculate content area bounds
2808: cx = insets.left + contentInsets.left;
2809: cy = insets.top + contentInsets.top;
2810: cw = bounds.width - insets.left - insets.right
2811: - contentInsets.left
2812: - contentInsets.right;
2813: ch = bounds.height - insets.top - insets.bottom
2814: - th - contentInsets.top
2815: - contentInsets.bottom;
2816: break;
2817: case TOP:
2818: default:
2819: // calculate tab area bounds
2820: tw = bounds.width - insets.left - insets.right;
2821: th = calculateTabAreaHeight(tabPlacement,
2822: runCount, maxTabHeight);
2823: tx = insets.left;
2824: ty = insets.top;
2825:
2826: // calculate content area bounds
2827: cx = tx + contentInsets.left;
2828: cy = ty + th + contentInsets.top;
2829: cw = bounds.width - insets.left - insets.right
2830: - contentInsets.left
2831: - contentInsets.right;
2832: ch = bounds.height - insets.top - insets.bottom
2833: - th - contentInsets.top
2834: - contentInsets.bottom;
2835: }
2836:
2837: for (int i = 0; i < numChildren; i++) {
2838: Component child = tabPane.getComponent(i);
2839:
2840: if (child instanceof ScrollableTabViewport) {
2841: JViewport viewport = (JViewport) child;
2842: Rectangle viewRect = viewport.getViewRect();
2843: int vw = tw;
2844: int vh = th;
2845: switch (tabPlacement) {
2846: case LEFT:
2847: case RIGHT:
2848: int totalTabHeight = rects[tabCount - 1].y
2849: + rects[tabCount - 1].height;
2850: if (totalTabHeight > th) {
2851: // Allow space for scrollbuttons
2852: vh = Math.max(th - 36, 36);
2853: if (totalTabHeight - viewRect.y <= vh) {
2854: // Scrolled to the end, so ensure the viewport size is
2855: // such that the scroll offset aligns with a tab
2856: vh = totalTabHeight
2857: - viewRect.y;
2858: }
2859: }
2860: break;
2861: case BOTTOM:
2862: case TOP:
2863: default:
2864: int totalTabWidth = rects[tabCount - 1].x
2865: + rects[tabCount - 1].width;
2866: if (totalTabWidth > tw) {
2867: // Need to allow space for scrollbuttons
2868: vw = Math.max(tw - 36, 36);
2869: ;
2870: if (totalTabWidth - viewRect.x <= vw) {
2871: // Scrolled to the end, so ensure the viewport size is
2872: // such that the scroll offset aligns with a tab
2873: vw = totalTabWidth - viewRect.x;
2874: }
2875: }
2876: }
2877: child.setBounds(tx, ty, vw, vh);
2878:
2879: } else if (child instanceof ScrollableTabButton) {
2880: ScrollableTabButton scrollbutton = (ScrollableTabButton) child;
2881: Dimension bsize = scrollbutton
2882: .getPreferredSize();
2883: int bx = 0;
2884: int by = 0;
2885: int bw = bsize.width;
2886: int bh = bsize.height;
2887: boolean visible = false;
2888:
2889: switch (tabPlacement) {
2890: case LEFT:
2891: case RIGHT:
2892: int totalTabHeight = rects[tabCount - 1].y
2893: + rects[tabCount - 1].height;
2894: if (totalTabHeight > th) {
2895: int dir = scrollbutton
2896: .scrollsForward() ? SOUTH
2897: : NORTH;
2898: scrollbutton.setDirection(dir);
2899: visible = true;
2900: bx = (tabPlacement == LEFT ? tx
2901: + tw - bsize.width : tx);
2902: by = dir == SOUTH ? bounds.height
2903: - insets.bottom
2904: - bsize.height
2905: : bounds.height
2906: - insets.bottom - 2
2907: * bsize.height;
2908: }
2909: break;
2910:
2911: case BOTTOM:
2912: case TOP:
2913: default:
2914: int totalTabWidth = rects[tabCount - 1].x
2915: + rects[tabCount - 1].width;
2916:
2917: if (totalTabWidth > tw) {
2918: int dir = scrollbutton
2919: .scrollsForward() ? EAST
2920: : WEST;
2921: scrollbutton.setDirection(dir);
2922: visible = true;
2923: bx = dir == EAST ? bounds.width
2924: - insets.left - bsize.width
2925: : bounds.width
2926: - insets.left - 2
2927: * bsize.width;
2928: by = (tabPlacement == TOP ? ty + th
2929: - bsize.height : ty);
2930: }
2931: }
2932:
2933: child.setVisible(visible);
2934: if (visible) {
2935: child.setBounds(bx, by, bw, bh);
2936: }
2937:
2938: } else {
2939: // All content children...
2940: child.setBounds(cx, cy, cw, ch);
2941: }
2942: }
2943: if (shouldChangeFocus) {
2944: if (!requestFocusForVisibleComponent()) {
2945: tabPane.requestFocus();
2946: }
2947: }
2948: }
2949: }
2950: }
2951:
2952: protected void calculateTabRects(int tabPlacement, int tabCount) {
2953: FontMetrics metrics = getFontMetrics();
2954: Dimension size = tabPane.getSize();
2955: Insets insets = tabPane.getInsets();
2956: Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
2957: int fontHeight = metrics.getHeight();
2958: int selectedIndex = tabPane.getSelectedIndex();
2959: int i, j;
2960: boolean verticalTabRuns = (tabPlacement == LEFT || tabPlacement == RIGHT);
2961: boolean leftToRight = isLeftToRight(tabPane);
2962: int x = tabAreaInsets.left;
2963: int y = tabAreaInsets.top;
2964: int totalWidth = 0;
2965: int totalHeight = 0;
2966:
2967: //
2968: // Calculate bounds within which a tab run must fit
2969: //
2970: switch (tabPlacement) {
2971: case LEFT:
2972: case RIGHT:
2973: maxTabWidth = calculateMaxTabWidth(tabPlacement);
2974: break;
2975: case BOTTOM:
2976: case TOP:
2977: default:
2978: maxTabHeight = calculateMaxTabHeight(tabPlacement);
2979: }
2980:
2981: runCount = 0;
2982: selectedRun = -1;
2983:
2984: if (tabCount == 0) {
2985: return;
2986: }
2987:
2988: selectedRun = 0;
2989: runCount = 1;
2990:
2991: // Run through tabs and lay them out in a single run
2992: Rectangle rect;
2993: for (i = 0; i < tabCount; i++) {
2994: rect = rects[i];
2995:
2996: if (!verticalTabRuns) {
2997: // Tabs on TOP or BOTTOM....
2998: if (i > 0) {
2999: rect.x = rects[i - 1].x + rects[i - 1].width;
3000: } else {
3001: tabRuns[0] = 0;
3002: maxTabWidth = 0;
3003: totalHeight += maxTabHeight;
3004: rect.x = x;
3005: }
3006: rect.width = calculateTabWidth(tabPlacement, i,
3007: metrics);
3008: totalWidth = rect.x + rect.width;
3009: maxTabWidth = Math.max(maxTabWidth, rect.width);
3010:
3011: rect.y = y;
3012: rect.height = maxTabHeight/* - 2*/;
3013:
3014: } else {
3015: // Tabs on LEFT or RIGHT...
3016: if (i > 0) {
3017: rect.y = rects[i - 1].y + rects[i - 1].height;
3018: } else {
3019: tabRuns[0] = 0;
3020: maxTabHeight = 0;
3021: totalWidth = maxTabWidth;
3022: rect.y = y;
3023: }
3024: rect.height = calculateTabHeight(tabPlacement, i,
3025: fontHeight);
3026: totalHeight = rect.y + rect.height;
3027: maxTabHeight = Math.max(maxTabHeight, rect.height);
3028:
3029: rect.x = x;
3030: rect.width = maxTabWidth/* - 2*/;
3031:
3032: }
3033: }
3034:
3035: // for right to left and tab placement on the top or
3036: // the bottom, flip x positions and adjust by widths
3037: if (!leftToRight && !verticalTabRuns) {
3038: int rightMargin = size.width
3039: - (insets.right + tabAreaInsets.right);
3040: for (i = 0; i < tabCount; i++) {
3041: rects[i].x = rightMargin - rects[i].x
3042: - rects[i].width;
3043: }
3044: }
3045: //tabPanel.setSize(totalWidth, totalHeight);
3046: tabScroller.tabPanel.setPreferredSize(new Dimension(
3047: totalWidth, totalHeight));
3048: }
3049: }
3050:
3051: private class ScrollableTabSupport implements ChangeListener,
3052: ActionListener {
3053: public ScrollableTabViewport viewport;
3054: public ScrollableTabPanel tabPanel;
3055: public ScrollableTabButton scrollForwardButton;
3056: public ScrollableTabButton scrollBackwardButton;
3057: public int leadingTabIndex;
3058:
3059: private Point tabViewPosition = new Point(0, 0);
3060:
3061: ScrollableTabSupport(int tabPlacement) {
3062: viewport = new ScrollableTabViewport();
3063: tabPanel = new ScrollableTabPanel();
3064: viewport.setView(tabPanel);
3065: viewport.addChangeListener(this );
3066:
3067: if (tabPlacement == TOP || tabPlacement == BOTTOM) {
3068: scrollForwardButton = new ScrollableTabButton(EAST);
3069: scrollBackwardButton = new ScrollableTabButton(WEST);
3070: } else { // tabPlacement = LEFT || RIGHT
3071: scrollForwardButton = new ScrollableTabButton(SOUTH);
3072: scrollBackwardButton = new ScrollableTabButton(NORTH);
3073: }
3074:
3075: scrollForwardButton.addActionListener(this );
3076: scrollBackwardButton.addActionListener(this );
3077: }
3078:
3079: public void actionPerformed(ActionEvent e) {
3080: Object object = e.getSource();
3081: if (object == scrollForwardButton) {
3082: scrollForward(tabPane.getTabPlacement());
3083: } else if (object == scrollBackwardButton) {
3084: scrollBackward(tabPane.getTabPlacement());
3085: }
3086: }
3087:
3088: public void scrollForward(int tabPlacement) {
3089: Dimension viewSize = viewport.getViewSize();
3090: Rectangle viewRect = viewport.getViewRect();
3091:
3092: if (tabPlacement == TOP || tabPlacement == BOTTOM) {
3093: if (viewRect.width >= viewSize.width - viewRect.x) {
3094: return; // no room left to scroll
3095: }
3096: } else { // tabPlacement == LEFT || tabPlacement == RIGHT
3097: if (viewRect.height >= viewSize.height - viewRect.y) {
3098: return;
3099: }
3100: }
3101: setLeadingTabIndex(tabPlacement, leadingTabIndex + 1);
3102: }
3103:
3104: public void scrollBackward(int tabPlacement) {
3105: if (leadingTabIndex == 0) {
3106: return; // no room left to scroll
3107: }
3108: setLeadingTabIndex(tabPlacement, leadingTabIndex - 1);
3109: }
3110:
3111: public void setLeadingTabIndex(int tabPlacement, int index) {
3112: leadingTabIndex = index;
3113: Dimension viewSize = viewport.getViewSize();
3114: Rectangle viewRect = viewport.getViewRect();
3115:
3116: switch (tabPlacement) {
3117: case TOP:
3118: case BOTTOM:
3119: tabViewPosition.x = leadingTabIndex == 0 ? 0
3120: : rects[leadingTabIndex].x;
3121:
3122: if ((viewSize.width - tabViewPosition.x) < viewRect.width) {
3123: // We've scrolled to the end, so adjust the viewport size
3124: // to ensure the view position remains aligned on a tab boundary
3125: Dimension extentSize = new Dimension(viewSize.width
3126: - tabViewPosition.x, viewRect.height);
3127: viewport.setExtentSize(extentSize);
3128: }
3129: break;
3130: case LEFT:
3131: case RIGHT:
3132: tabViewPosition.y = leadingTabIndex == 0 ? 0
3133: : rects[leadingTabIndex].y;
3134:
3135: if ((viewSize.height - tabViewPosition.y) < viewRect.height) {
3136: // We've scrolled to the end, so adjust the viewport size
3137: // to ensure the view position remains aligned on a tab boundary
3138: Dimension extentSize = new Dimension(
3139: viewRect.width, viewSize.height
3140: - tabViewPosition.y);
3141: viewport.setExtentSize(extentSize);
3142: }
3143: }
3144: viewport.setViewPosition(tabViewPosition);
3145: }
3146:
3147: public void stateChanged(ChangeEvent e) {
3148: JViewport viewport = (JViewport) e.getSource();
3149: int tabPlacement = tabPane.getTabPlacement();
3150: int tabCount = tabPane.getTabCount();
3151: Rectangle vpRect = viewport.getBounds();
3152: Dimension viewSize = viewport.getViewSize();
3153: Rectangle viewRect = viewport.getViewRect();
3154:
3155: leadingTabIndex = getClosestTab(viewRect.x, viewRect.y);
3156:
3157: // If the tab isn't right aligned, adjust it.
3158: if (leadingTabIndex + 1 < tabCount) {
3159: switch (tabPlacement) {
3160: case TOP:
3161: case BOTTOM:
3162: if (rects[leadingTabIndex].x < viewRect.x) {
3163: leadingTabIndex++;
3164: }
3165: break;
3166: case LEFT:
3167: case RIGHT:
3168: if (rects[leadingTabIndex].y < viewRect.y) {
3169: leadingTabIndex++;
3170: }
3171: break;
3172: }
3173: }
3174: Insets contentInsets = getContentBorderInsets(tabPlacement);
3175: switch (tabPlacement) {
3176: case LEFT:
3177: tabPane.repaint(vpRect.x + vpRect.width, vpRect.y,
3178: contentInsets.left, vpRect.height);
3179: scrollBackwardButton.setEnabled(viewRect.y > 0);
3180: scrollForwardButton
3181: .setEnabled(leadingTabIndex < tabCount - 1
3182: && viewSize.height - viewRect.y > viewRect.height);
3183: break;
3184: case RIGHT:
3185: tabPane.repaint(vpRect.x - contentInsets.right,
3186: vpRect.y, contentInsets.right, vpRect.height);
3187: scrollBackwardButton.setEnabled(viewRect.y > 0);
3188: scrollForwardButton
3189: .setEnabled(leadingTabIndex < tabCount - 1
3190: && viewSize.height - viewRect.y > viewRect.height);
3191: break;
3192: case BOTTOM:
3193: tabPane.repaint(vpRect.x, vpRect.y
3194: - contentInsets.bottom, vpRect.width,
3195: contentInsets.bottom);
3196: scrollBackwardButton.setEnabled(viewRect.x > 0);
3197: scrollForwardButton
3198: .setEnabled(leadingTabIndex < tabCount - 1
3199: && viewSize.width - viewRect.x > viewRect.width);
3200: break;
3201: case TOP:
3202: default:
3203: tabPane.repaint(vpRect.x, vpRect.y + vpRect.height,
3204: vpRect.width, contentInsets.top);
3205: scrollBackwardButton.setEnabled(viewRect.x > 0);
3206: scrollForwardButton
3207: .setEnabled(leadingTabIndex < tabCount - 1
3208: && viewSize.width - viewRect.x > viewRect.width);
3209: }
3210: }
3211:
3212: public String toString() {
3213: return new String("viewport.viewSize="
3214: + viewport.getViewSize() + "\n"
3215: + "viewport.viewRectangle="
3216: + viewport.getViewRect() + "\n"
3217: + "leadingTabIndex=" + leadingTabIndex + "\n"
3218: + "tabViewPosition=" + tabViewPosition);
3219: }
3220:
3221: }
3222:
3223: private class ScrollableTabViewport extends JViewport implements
3224: UIResource {
3225: public ScrollableTabViewport() {
3226: super ();
3227: setScrollMode(SIMPLE_SCROLL_MODE);
3228: }
3229: } // class ScrollableTabViewport
3230:
3231: private class ScrollableTabPanel extends JPanel implements
3232: UIResource {
3233: public ScrollableTabPanel() {
3234: setLayout(null);
3235: }
3236:
3237: public void paintComponent(Graphics g) {
3238: super .paintComponent(g);
3239: CloseTabbedPaneUI.this .paintTabArea(g, tabPane
3240: .getTabPlacement(), tabPane.getSelectedIndex());
3241:
3242: }
3243: } // class ScrollableTabPanel
3244:
3245: private class ScrollableTabButton extends BasicArrowButton
3246: implements UIResource, SwingConstants, MouseListener {
3247:
3248: private boolean mouseOver = false;
3249:
3250: public ScrollableTabButton(int direction) {
3251: super (direction);
3252: addMouseListener(this );
3253: }
3254:
3255: public boolean scrollsForward() {
3256: return direction == EAST || direction == SOUTH;
3257: }
3258:
3259: public void mouseEntered(MouseEvent e) {
3260: mouseOver = true;
3261: repaint();
3262: }
3263:
3264: public void mouseExited(MouseEvent e) {
3265: mouseOver = false;
3266: repaint();
3267: }
3268:
3269: public void mouseClicked(MouseEvent e) {
3270: }
3271:
3272: public void mousePressed(MouseEvent e) {
3273: }
3274:
3275: public void mouseReleased(MouseEvent e) {
3276: }
3277:
3278: public boolean isMouseOver() {
3279: return mouseOver;
3280: }
3281:
3282: public void setMouseOver(boolean _mouseOver) {
3283: mouseOver = _mouseOver;
3284: }
3285:
3286: public void paint(Graphics g) {
3287: Color origColor;
3288: boolean isPressed, isEnabled;
3289: int w, h, size;
3290:
3291: w = getSize().width;
3292: h = getSize().height;
3293: origColor = g.getColor();
3294: isPressed = getModel().isPressed();
3295: isEnabled = isEnabled();
3296:
3297: g.setColor(getBackground());
3298: g.fillRect(0, 0, w, h);
3299:
3300: if (mouseOver && isEnabled) {
3301: g.setColor(Color.DARK_GRAY);
3302: g.drawRect(1, 1, w - 2, h - 2);
3303: }
3304:
3305: // If there's no room to draw arrow, bail
3306: if (h < 5 || w < 5) {
3307: g.setColor(origColor);
3308: return;
3309: }
3310:
3311: g.translate(1, 1);
3312:
3313: // Draw the arrow
3314: size = Math.min((h - 4) / 3, (w - 4) / 3);
3315: size = Math.max(size, 2);
3316:
3317: paintTriangle(g, (w - size) / 2, (h - size) / 2, size,
3318: direction, isEnabled);
3319:
3320: // Reset the Graphics back to it's original settings
3321: g.translate(-1, -1);
3322: g.setColor(origColor);
3323:
3324: }
3325:
3326: public void paintTriangle(Graphics g, int x, int y, int size,
3327: int direction, boolean isEnabled) {
3328:
3329: Color oldColor = g.getColor();
3330: int mid, i, j;
3331:
3332: j = 0;
3333: size = Math.max(size, 2);
3334: mid = (size / 2) - 1;
3335:
3336: g.translate(x, y);
3337:
3338: if (isEnabled) {
3339: g.setColor(darkShadow);
3340: } else {
3341: g.setColor(shadow);
3342: }
3343:
3344: switch (direction) {
3345: case NORTH:
3346: for (i = 0; i < size; i++) {
3347: g.drawLine(mid - i, i, mid + i, i);
3348: }
3349: break;
3350: case SOUTH:
3351: j = 0;
3352: for (i = size - 1; i >= 0; i--) {
3353: g.drawLine(mid - i, j, mid + i, j);
3354: j++;
3355: }
3356: break;
3357: case WEST:
3358: for (i = 0; i < size; i++) {
3359: g.drawLine(i, mid - i, i, mid + i);
3360: }
3361: break;
3362: case EAST:
3363: j = 0;
3364: for (i = size - 1; i >= 0; i--) {
3365: g.drawLine(j, mid - i, j, mid + i);
3366: j++;
3367: }
3368: break;
3369: }
3370: g.translate(-x, -y);
3371: g.setColor(oldColor);
3372: }
3373:
3374: } // class ScrollableTabButton
3375:
3376: // Controller: event listeners
3377:
3378: protected int currentCloseRolloverIndex = -1;
3379:
3380: public class Handler implements MouseListener, MouseMotionListener,
3381: FocusListener, PropertyChangeListener, ChangeListener {
3382:
3383: // ------------------------------------------------------
3384: // FocusListener implementation
3385: // ------------------------------------------------------
3386:
3387: public void focusGained(FocusEvent e) {
3388: JTabbedPane tabPane = (JTabbedPane) e.getSource();
3389: int tabCount = tabPane.getTabCount();
3390: int selectedIndex = tabPane.getSelectedIndex();
3391: if (selectedIndex != -1 && tabCount > 0
3392: && tabCount == rects.length) {
3393: tabPane.repaint(getTabBounds(tabPane, selectedIndex));
3394: }
3395: }
3396:
3397: public void focusLost(FocusEvent e) {
3398: JTabbedPane tabPane = (JTabbedPane) e.getSource();
3399: int tabCount = tabPane.getTabCount();
3400: int selectedIndex = tabPane.getSelectedIndex();
3401: if (selectedIndex != -1 && tabCount > 0
3402: && tabCount == rects.length) {
3403: tabPane.repaint(getTabBounds(tabPane, selectedIndex));
3404: }
3405: }
3406:
3407: // ------------------------------------------------------
3408: // PropertyChangeListener implementation
3409: // ------------------------------------------------------
3410:
3411: public void propertyChange(PropertyChangeEvent e) {
3412: JTabbedPane pane = (JTabbedPane) e.getSource();
3413: String name = e.getPropertyName();
3414:
3415: if ("mnemonicAt".equals(name)) {
3416: updateMnemonics();
3417: pane.repaint();
3418: } else if ("displayedMnemonicIndexAt".equals(name)) {
3419: pane.repaint();
3420: } else if (name.equals("indexForTitle")) {
3421: int index = ((Integer) e.getNewValue()).intValue();
3422: String title = tabPane.getTitleAt(index);
3423: if (BasicHTML.isHTMLString(title)) {
3424: if (htmlViews == null) { // Initialize vector
3425: htmlViews = createHTMLVector();
3426: } else { // Vector already exists
3427: View v = BasicHTML.createHTMLView(tabPane,
3428: title);
3429: htmlViews.setElementAt(v, index);
3430: }
3431: } else {
3432: if (htmlViews != null
3433: && htmlViews.elementAt(index) != null) {
3434: htmlViews.setElementAt(null, index);
3435: }
3436: }
3437: updateMnemonics();
3438: } else if (name.equals("tabLayoutPolicy")) {
3439: CloseTabbedPaneUI.this .uninstallUI(pane);
3440: CloseTabbedPaneUI.this .installUI(pane);
3441: }
3442: }
3443:
3444: // ------------------------------------------------------
3445: // ChangeListener implementation
3446: // ------------------------------------------------------
3447:
3448: public void stateChanged(ChangeEvent e) {
3449: JTabbedPane tabPane = (JTabbedPane) e.getSource();
3450: tabPane.revalidate();
3451: tabPane.repaint();
3452:
3453: if (tabPane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT) {
3454: int index = tabPane.getSelectedIndex();
3455: if (index < rects.length && index != -1) {
3456: tabScroller.tabPanel
3457: .scrollRectToVisible(rects[index]);
3458: }
3459: }
3460: }
3461:
3462: // ------------------------------------------------------
3463: // MouseListener, MouseMotionListener implementation
3464: // ------------------------------------------------------
3465:
3466: public void mouseMoved(MouseEvent e) {
3467: if (!tabPane.isEnabled()) {
3468: return;
3469: }
3470:
3471: boolean doRepaint = false;
3472: if (currentCloseRolloverIndex != -1) {
3473: doRepaint = true;
3474: currentCloseRolloverIndex = -1;
3475: }
3476:
3477: int x = e.getX();
3478: int y = e.getY();
3479: int rollOverIndex = -1;
3480:
3481: for (int i = 0, k = tabPane.getTabCount(); i < k; i++) {
3482: Rectangle tabRect = rects[i];
3483: if (tabRect.contains(x, y)) {
3484: rollOverIndex = i;
3485: Rectangle iconRect = getCloseIconRectangle(tabRect);
3486: if (iconRect.contains(x, y)) {
3487: currentCloseRolloverIndex = i;
3488: doRepaint = true;
3489: }
3490: break;
3491: }
3492: }
3493:
3494: if (doRepaint) {
3495: tabPane.repaint();
3496: }
3497:
3498: // fire the rollover listeners
3499: if (currentCloseRolloverIndex == -1 && rollOverIndex != -1) {
3500:
3501: Point point = SwingUtilities.convertPoint(
3502: tabScroller.tabPanel, x, y, tabPane);
3503:
3504: if (tabPane instanceof SimpleCloseTabbedPane) {
3505: SimpleCloseTabbedPane pane = (SimpleCloseTabbedPane) tabPane;
3506: pane.fireTabRollOver(new TabRolloverEvent(pane,
3507: rollOverIndex, point.x, point.y));
3508: }
3509: } else { // fire roll finished
3510: if (tabPane instanceof SimpleCloseTabbedPane) {
3511: SimpleCloseTabbedPane pane = (SimpleCloseTabbedPane) tabPane;
3512: pane.fireTabRollOverFinished(new TabRolloverEvent(
3513: pane, -1));
3514: }
3515: }
3516:
3517: }
3518:
3519: public void mouseClicked(MouseEvent e) {
3520: mousePressed(e);
3521: }
3522:
3523: public void mousePressed(MouseEvent e) {
3524: if (!tabPane.isEnabled()) {
3525: return;
3526: }
3527: int tabIndex = getTabAtLocation(e.getX(), e.getY());
3528: if (tabIndex >= 0 && tabPane.isEnabledAt(tabIndex)) {
3529: if (tabIndex == tabPane.getSelectedIndex()) {
3530: if (tabPane.isRequestFocusEnabled()) {
3531: tabPane.requestFocus();
3532: tabPane
3533: .repaint(getTabBounds(tabPane, tabIndex));
3534: }
3535: } else {
3536: tabPane.setSelectedIndex(tabIndex);
3537: }
3538: }
3539: }
3540:
3541: public void mouseReleased(MouseEvent e) {
3542:
3543: int x = e.getX();
3544: int y = e.getY();
3545:
3546: // check if we have a popup menu available
3547: if (e.isPopupTrigger()) {
3548: if (tabPane instanceof CloseTabbedPane) {
3549: CloseTabbedPane closeTabPane = (CloseTabbedPane) tabPane;
3550: if (closeTabPane.isTabPopupEnabled()) {
3551:
3552: for (int i = 0, k = tabPane.getTabCount(); i < k; i++) {
3553: Rectangle tabRect = rects[i];
3554: if (tabRect.contains(x, y)) {
3555: closeTabPane.showPopup(i, x, y);
3556: break;
3557: }
3558: }
3559:
3560: }
3561: }
3562: }
3563:
3564: for (int i = 0, k = tabPane.getTabCount(); i < k; i++) {
3565: Rectangle tabRect = rects[i];
3566: if (tabRect.contains(x, y)) {
3567: Rectangle iconRect = getCloseIconRectangle(tabRect);
3568: if (iconRect.contains(x, y)) {
3569: tabPane.remove(i);
3570: }
3571: break;
3572: }
3573: }
3574: }
3575:
3576: public void mouseExited(MouseEvent e) {
3577: // fire the rollover listeners
3578: if (tabPane instanceof SimpleCloseTabbedPane) {
3579: SimpleCloseTabbedPane pane = (SimpleCloseTabbedPane) tabPane;
3580: pane.fireTabRollOverFinished(new TabRolloverEvent(pane,
3581: -1, e.getX(), e.getY()));
3582: }
3583: }
3584:
3585: public void mouseDragged(MouseEvent e) {
3586: }
3587:
3588: public void mouseEntered(MouseEvent e) {
3589: }
3590:
3591: }
3592:
3593: /* GES 2/3/99:
3594: The container listener code was added to support HTML
3595: rendering of tab titles.
3596:
3597: Ideally, we would be able to listen for property changes
3598: when a tab is added or its text modified. At the moment
3599: there are no such events because the Beans spec doesn't
3600: allow 'indexed' property changes (i.e. tab 2's text changed
3601: from A to B).
3602:
3603: In order to get around this, we listen for tabs to be added
3604: or removed by listening for the container events. we then
3605: queue up a runnable (so the component has a chance to complete
3606: the add) which checks the tab title of the new component to see
3607: if it requires HTML rendering.
3608:
3609: The Views (one per tab title requiring HTML rendering) are
3610: stored in the htmlViews Vector, which is only allocated after
3611: the first time we run into an HTML tab. Note that this vector
3612: is kept in step with the number of pages, and nulls are added
3613: for those pages whose tab title do not require HTML rendering.
3614:
3615: This makes it easy for the paint and layout code to tell
3616: whether to invoke the HTML engine without having to check
3617: the string during time-sensitive operations.
3618:
3619: When we have added a way to listen for tab additions and
3620: changes to tab text, this code should be removed and
3621: replaced by something which uses that. */
3622:
3623: private class ContainerHandler implements ContainerListener {
3624: public void componentAdded(ContainerEvent e) {
3625: JTabbedPane tp = (JTabbedPane) e.getContainer();
3626: Component child = e.getChild();
3627: if (child instanceof UIResource) {
3628: return;
3629: }
3630: int index = tp.indexOfComponent(child);
3631: String title = tp.getTitleAt(index);
3632: boolean isHTML = BasicHTML.isHTMLString(title);
3633: if (isHTML) {
3634: if (htmlViews == null) { // Initialize vector
3635: htmlViews = createHTMLVector();
3636: } else { // Vector already exists
3637: View v = BasicHTML.createHTMLView(tp, title);
3638: htmlViews.insertElementAt(v, index);
3639: }
3640: } else { // Not HTML
3641: if (htmlViews != null) { // Add placeholder
3642: htmlViews.insertElementAt(null, index);
3643: } // nada!
3644: }
3645: }
3646:
3647: public void componentRemoved(ContainerEvent e) {
3648: JTabbedPane tp = (JTabbedPane) e.getContainer();
3649: Component child = e.getChild();
3650: if (child instanceof UIResource) {
3651: return;
3652: }
3653:
3654: // NOTE 4/15/2002 (joutwate):
3655: // This fix is implemented using client properties since there is
3656: // currently no IndexPropertyChangeEvent. Once
3657: // IndexPropertyChangeEvents have been added this code should be
3658: // modified to use it.
3659: Integer indexObj = (Integer) tp
3660: .getClientProperty("__index_to_remove__");
3661: if (indexObj != null) {
3662: int index = indexObj.intValue();
3663: if (htmlViews != null && htmlViews.size() >= index) {
3664: htmlViews.removeElementAt(index);
3665: }
3666: }
3667: }
3668: }
3669:
3670: private Vector createHTMLVector() {
3671: Vector htmlViews = new Vector();
3672: int count = tabPane.getTabCount();
3673: if (count > 0) {
3674: for (int i = 0; i < count; i++) {
3675: String title = tabPane.getTitleAt(i);
3676: if (BasicHTML.isHTMLString(title)) {
3677: htmlViews.addElement(BasicHTML.createHTMLView(
3678: tabPane, title));
3679: } else {
3680: htmlViews.addElement(null);
3681: }
3682: }
3683: }
3684: return htmlViews;
3685: }
3686:
3687: }
|