| /* | |
| * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| package com.apple.laf; | |
| import java.awt.*; | |
| import java.awt.event.*; | |
| import java.awt.geom.AffineTransform; | |
| import java.beans.*; | |
| import javax.swing.*; | |
| import javax.swing.event.*; | |
| import javax.swing.plaf.*; | |
| import javax.swing.text.View; | |
| import sun.swing.SwingUtilities2; | |
| import apple.laf.*; | |
| import apple.laf.JRSUIConstants.*; | |
| public class AquaTabbedPaneUI extends AquaTabbedPaneCopyFromBasicUI { | |
| private static final int kSmallTabHeight = 20; // height of a small tab | |
| private static final int kLargeTabHeight = 23; // height of a large tab | |
| private static final int kMaxIconSize = kLargeTabHeight - 7; | |
| private static final double kNinetyDegrees = (Math.PI / 2.0); // used for rotation | |
| protected final Insets currentContentDrawingInsets = new Insets(0, 0, 0, 0); | |
| protected final Insets currentContentBorderInsets = new Insets(0, 0, 0, 0); | |
| protected final Insets contentDrawingInsets = new Insets(0, 0, 0, 0); | |
| protected int pressedTab = -3; // -2 is right scroller, -1 is left scroller | |
| protected boolean popupSelectionChanged; | |
| protected Boolean isDefaultFocusReceiver = null; | |
| protected boolean hasAvoidedFirstFocus = false; | |
| // Create PLAF | |
| public static ComponentUI createUI(final JComponent c) { | |
| return new AquaTabbedPaneUI(); | |
| } | |
| protected final AquaTabbedPaneTabState visibleTabState = new AquaTabbedPaneTabState(this); | |
| protected final AquaPainter<JRSUIState> painter = AquaPainter.create(JRSUIStateFactory.getTab()); | |
| public AquaTabbedPaneUI() { } | |
| protected void installListeners() { | |
| super.installListeners(); | |
| // We're not just a mouseListener, we're a mouseMotionListener | |
| if (mouseListener != null) { | |
| tabPane.addMouseMotionListener((MouseMotionListener)mouseListener); | |
| } | |
| } | |
| protected void installDefaults() { | |
| super.installDefaults(); | |
| if (tabPane.getFont() instanceof UIResource) { | |
| final Boolean b = (Boolean)UIManager.get("TabbedPane.useSmallLayout"); | |
| if (b != null && b == Boolean.TRUE) { | |
| tabPane.setFont(UIManager.getFont("TabbedPane.smallFont")); | |
| painter.state.set(Size.SMALL); | |
| } | |
| } | |
| contentDrawingInsets.set(0, 11, 13, 10); | |
| tabPane.setOpaque(false); | |
| } | |
| protected void assureRectsCreated(final int tabCount) { | |
| visibleTabState.init(tabCount); | |
| super.assureRectsCreated(tabCount); | |
| } | |
| @Override | |
| protected void uninstallListeners() { | |
| // We're not just a mouseListener, we're a mouseMotionListener | |
| if (mouseListener instanceof MouseHandler) { | |
| final MouseHandler mh = (MouseHandler) mouseListener; | |
| mh.dispose(); | |
| tabPane.removeMouseMotionListener(mh); | |
| } | |
| super.uninstallListeners(); | |
| } | |
| protected void uninstallDefaults() { | |
| contentDrawingInsets.set(0, 0, 0, 0); | |
| } | |
| protected MouseListener createMouseListener() { | |
| return new MouseHandler(); | |
| } | |
| protected FocusListener createFocusListener() { | |
| return new FocusHandler(); | |
| } | |
| protected PropertyChangeListener createPropertyChangeListener() { | |
| return new TabbedPanePropertyChangeHandler(); | |
| } | |
| protected LayoutManager createLayoutManager() { | |
| return new AquaTruncatingTabbedPaneLayout(); | |
| } | |
| protected boolean shouldRepaintSelectedTabOnMouseDown() { | |
| return false; | |
| } | |
| // Paint Methods | |
| // Cache for performance | |
| final Rectangle fContentRect = new Rectangle(); | |
| final Rectangle fIconRect = new Rectangle(); | |
| final Rectangle fTextRect = new Rectangle(); | |
| // UI Rendering | |
| public void paint(final Graphics g, final JComponent c) { | |
| painter.state.set(getDirection()); | |
| final int tabPlacement = tabPane.getTabPlacement(); | |
| final int selectedIndex = tabPane.getSelectedIndex(); | |
| paintContentBorder(g, tabPlacement, selectedIndex); | |
| // we want to call ensureCurrentLayout, but it's private | |
| ensureCurrentLayout(); | |
| final Rectangle clipRect = g.getClipBounds(); | |
| final boolean active = tabPane.isEnabled(); | |
| final boolean frameActive = AquaFocusHandler.isActive(tabPane); | |
| final boolean isLeftToRight = tabPane.getComponentOrientation().isLeftToRight() || tabPlacement == LEFT || tabPlacement == RIGHT; | |
| // Paint tabRuns of tabs from back to front | |
| if (visibleTabState.needsScrollTabs()) { | |
| paintScrollingTabs(g, clipRect, tabPlacement, selectedIndex, active, frameActive, isLeftToRight); | |
| return; | |
| } | |
| // old way | |
| paintAllTabs(g, clipRect, tabPlacement, selectedIndex, active, frameActive, isLeftToRight); | |
| } | |
| protected void paintAllTabs(final Graphics g, final Rectangle clipRect, final int tabPlacement, final int selectedIndex, final boolean active, final boolean frameActive, final boolean isLeftToRight) { | |
| boolean drawSelectedLast = false; | |
| for (int i = 0; i < rects.length; i++) { | |
| if (i == selectedIndex) { | |
| drawSelectedLast = true; | |
| } else { | |
| if (rects[i].intersects(clipRect)) { | |
| paintTabNormal(g, tabPlacement, i, active, frameActive, isLeftToRight); | |
| } | |
| } | |
| } | |
| // paint the selected tab last. | |
| if (drawSelectedLast && rects[selectedIndex].intersects(clipRect)) { | |
| paintTabNormal(g, tabPlacement, selectedIndex, active, frameActive, isLeftToRight); | |
| } | |
| } | |
| protected void paintScrollingTabs(final Graphics g, final Rectangle clipRect, final int tabPlacement, final int selectedIndex, final boolean active, final boolean frameActive, final boolean isLeftToRight) { | |
| // final Graphics g2 = g.create(); | |
| // g2.setColor(Color.cyan); | |
| // Rectangle r = new Rectangle(); | |
| // for (int i = 0; i < visibleTabState.getTotal(); i++) { | |
| // r.add(rects[visibleTabState.getIndex(i)]); | |
| // } | |
| // g2.fillRect(r.x, r.y, r.width, r.height); | |
| // g2.dispose(); | |
| // System.out.println(r); | |
| // for each visible tab, except the selected one | |
| for (int i = 0; i < visibleTabState.getTotal(); i++) { | |
| final int realIndex = visibleTabState.getIndex(i); | |
| if (realIndex != selectedIndex) { | |
| if (rects[realIndex].intersects(clipRect)) { | |
| paintTabNormal(g, tabPlacement, realIndex, active, frameActive, isLeftToRight); | |
| } | |
| } | |
| } | |
| final Rectangle leftScrollTabRect = visibleTabState.getLeftScrollTabRect(); | |
| if (visibleTabState.needsLeftScrollTab() && leftScrollTabRect.intersects(clipRect)) { | |
| paintTabNormalFromRect(g, tabPlacement, leftScrollTabRect, -2, fIconRect, fTextRect, visibleTabState.needsLeftScrollTab(), frameActive, isLeftToRight); | |
| } | |
| final Rectangle rightScrollTabRect = visibleTabState.getRightScrollTabRect(); | |
| if (visibleTabState.needsRightScrollTab() && rightScrollTabRect.intersects(clipRect)) { | |
| paintTabNormalFromRect(g, tabPlacement, rightScrollTabRect, -1, fIconRect, fTextRect, visibleTabState.needsRightScrollTab(), frameActive, isLeftToRight); | |
| } | |
| if (selectedIndex >= 0) { // && rects[selectedIndex].intersects(clipRect)) { | |
| paintTabNormal(g, tabPlacement, selectedIndex, active, frameActive, isLeftToRight); | |
| } | |
| } | |
| private static boolean isScrollTabIndex(final int index) { | |
| return index == -1 || index == -2; | |
| } | |
| protected static void transposeRect(final Rectangle r) { | |
| int temp = r.y; | |
| r.y = r.x; | |
| r.x = temp; | |
| temp = r.width; | |
| r.width = r.height; | |
| r.height = temp; | |
| } | |
| protected int getTabLabelShiftX(final int tabPlacement, final int tabIndex, final boolean isSelected) { | |
| final Rectangle tabRect = (tabIndex >= 0 ? rects[tabIndex] : visibleTabState.getRightScrollTabRect()); | |
| int nudge = 0; | |
| switch (tabPlacement) { | |
| case LEFT: | |
| case RIGHT: | |
| nudge = tabRect.height % 2; | |
| break; | |
| case BOTTOM: | |
| case TOP: | |
| default: | |
| nudge = tabRect.width % 2; | |
| } | |
| return nudge; | |
| } | |
| protected int getTabLabelShiftY(final int tabPlacement, final int tabIndex, final boolean isSelected) { | |
| switch (tabPlacement) { | |
| case RIGHT: | |
| case LEFT: | |
| case BOTTOM: | |
| return -1; | |
| case TOP: | |
| default: | |
| return 0; | |
| } | |
| } | |
| protected Icon getIconForScrollTab(final int tabPlacement, final int tabIndex, final boolean enabled) { | |
| boolean shouldFlip = !AquaUtils.isLeftToRight(tabPane); | |
| if (tabPlacement == RIGHT) shouldFlip = false; | |
| if (tabPlacement == LEFT) shouldFlip = true; | |
| int direction = tabIndex == -1 ? EAST : WEST; | |
| if (shouldFlip) { | |
| if (direction == EAST) { | |
| direction = WEST; | |
| } else if (direction == WEST) { | |
| direction = EAST; | |
| } | |
| } | |
| if (enabled) return AquaImageFactory.getArrowIconForDirection(direction); | |
| final Image icon = AquaImageFactory.getArrowImageForDirection(direction); | |
| return new ImageIcon(AquaUtils.generateDisabledImage(icon)); | |
| } | |
| protected void paintContents(final Graphics g, final int tabPlacement, final int tabIndex, final Rectangle tabRect, final Rectangle iconRect, final Rectangle textRect, final boolean isSelected) { | |
| final Shape temp = g.getClip(); | |
| g.clipRect(fContentRect.x, fContentRect.y, fContentRect.width, fContentRect.height); | |
| final Component component; | |
| final String title; | |
| final Icon icon; | |
| if (isScrollTabIndex(tabIndex)) { | |
| component = null; | |
| title = null; | |
| icon = getIconForScrollTab(tabPlacement, tabIndex, true); | |
| } else { | |
| component = getTabComponentAt(tabIndex); | |
| if (component == null) { | |
| title = tabPane.getTitleAt(tabIndex); | |
| icon = getIconForTab(tabIndex); | |
| } else { | |
| title = null; | |
| icon = null; | |
| } | |
| } | |
| final boolean isVertical = tabPlacement == RIGHT || tabPlacement == LEFT; | |
| if (isVertical) { | |
| transposeRect(fContentRect); | |
| } | |
| final Font font = tabPane.getFont(); | |
| final FontMetrics metrics = g.getFontMetrics(font); | |
| // our scrolling tabs | |
| layoutLabel(tabPlacement, metrics, tabIndex < 0 ? 0 : tabIndex, title, icon, fContentRect, iconRect, textRect, false); // Never give it "isSelected" - ApprMgr handles this | |
| if (isVertical) { | |
| transposeRect(fContentRect); | |
| transposeRect(iconRect); | |
| transposeRect(textRect); | |
| } | |
| // from super.paintText - its normal text painting is totally wrong for the Mac | |
| if (!(g instanceof Graphics2D)) { | |
| g.setClip(temp); | |
| return; | |
| } | |
| final Graphics2D g2d = (Graphics2D) g; | |
| AffineTransform savedAT = null; | |
| if (isVertical) { | |
| savedAT = g2d.getTransform(); | |
| rotateGraphics(g2d, tabRect, textRect, iconRect, tabPlacement); | |
| } | |
| // not for the scrolling tabs | |
| if (component == null && tabIndex >= 0) { | |
| String clippedTitle = SwingUtilities2.clipStringIfNecessary(tabPane, metrics, | |
| title, textRect.width); | |
| paintTitle(g2d, font, metrics, textRect, tabIndex, clippedTitle); | |
| } | |
| if (icon != null) { | |
| paintIcon(g, tabPlacement, tabIndex, icon, iconRect, isSelected); | |
| } | |
| if (savedAT != null) { | |
| g2d.setTransform(savedAT); | |
| } | |
| g.setClip(temp); | |
| } | |
| protected void paintTitle(final Graphics2D g2d, final Font font, final FontMetrics metrics, final Rectangle textRect, final int tabIndex, final String title) { | |
| final View v = getTextViewForTab(tabIndex); | |
| if (v != null) { | |
| v.paint(g2d, textRect); | |
| return; | |
| } | |
| if (title == null) return; | |
| final Color color = tabPane.getForegroundAt(tabIndex); | |
| if (color instanceof UIResource) { | |
| // sja fix getTheme().setThemeTextColor(g, isSelected, isPressed && tracking, tabPane.isEnabledAt(tabIndex)); | |
| if (tabPane.isEnabledAt(tabIndex)) { | |
| g2d.setColor(Color.black); | |
| } else { | |
| g2d.setColor(Color.gray); | |
| } | |
| } else { | |
| g2d.setColor(color); | |
| } | |
| g2d.setFont(font); | |
| SwingUtilities2.drawString(tabPane, g2d, title, textRect.x, textRect.y + metrics.getAscent()); | |
| } | |
| protected void rotateGraphics(final Graphics2D g2d, final Rectangle tabRect, final Rectangle textRect, final Rectangle iconRect, final int tabPlacement) { | |
| int yDiff = 0; // textRect.y - tabRect.y; | |
| int xDiff = 0; // (tabRect.x+tabRect.width) - (textRect.x+textRect.width); | |
| int yIconDiff = 0; // iconRect.y - tabRect.y; | |
| int xIconDiff = 0; // (tabRect.x+tabRect.width) - (iconRect.x + iconRect.width); | |
| final double rotateAmount = (tabPlacement == LEFT ? -kNinetyDegrees : kNinetyDegrees); | |
| g2d.transform(AffineTransform.getRotateInstance(rotateAmount, tabRect.x, tabRect.y)); | |
| // x and y diffs are named weirdly. | |
| // I will rename them, but what they mean now is | |
| // original x offset which will be used to adjust the y coordinate for the | |
| // rotated context | |
| if (tabPlacement == LEFT) { | |
| g2d.translate(-tabRect.height - 1, 1); | |
| xDiff = textRect.x - tabRect.x; | |
| yDiff = tabRect.height + tabRect.y - (textRect.y + textRect.height); | |
| xIconDiff = iconRect.x - tabRect.x; | |
| yIconDiff = tabRect.height + tabRect.y - (iconRect.y + iconRect.height); | |
| } else { | |
| g2d.translate(0, -tabRect.width - 1); | |
| yDiff = textRect.y - tabRect.y; | |
| xDiff = (tabRect.x + tabRect.width) - (textRect.x + textRect.width); | |
| yIconDiff = iconRect.y - tabRect.y; | |
| xIconDiff = (tabRect.x + tabRect.width) - (iconRect.x + iconRect.width); | |
| } | |
| // rotation changes needed for the rendering | |
| // we are rotating so we can't just use the rects wholesale. | |
| textRect.x = tabRect.x + yDiff; | |
| textRect.y = tabRect.y + xDiff; | |
| int tempVal = textRect.height; | |
| textRect.height = textRect.width; | |
| textRect.width = tempVal; | |
| // g.setColor(Color.red); | |
| // g.drawLine(textRect.x, textRect.y, textRect.x+textRect.height, textRect.y+textRect.width); | |
| // g.drawLine(textRect.x+textRect.height, textRect.y, textRect.x, textRect.y+textRect.width); | |
| iconRect.x = tabRect.x + yIconDiff; | |
| iconRect.y = tabRect.y + xIconDiff; | |
| tempVal = iconRect.height; | |
| iconRect.height = iconRect.width; | |
| iconRect.width = tempVal; | |
| } | |
| protected void paintTabNormal(final Graphics g, final int tabPlacement, final int tabIndex, final boolean active, final boolean frameActive, final boolean isLeftToRight) { | |
| paintTabNormalFromRect(g, tabPlacement, rects[tabIndex], tabIndex, fIconRect, fTextRect, active, frameActive, isLeftToRight); | |
| } | |
| protected void paintTabNormalFromRect(final Graphics g, | |
| final int tabPlacement, | |
| final Rectangle tabRect, | |
| final int nonRectIndex, | |
| final Rectangle iconRect, | |
| final Rectangle textRect, | |
| final boolean active, | |
| final boolean frameActive, | |
| final boolean isLeftToRight) { | |
| final int selectedIndex = tabPane.getSelectedIndex(); | |
| final boolean isSelected = selectedIndex == nonRectIndex; | |
| paintCUITab(g, tabPlacement, tabRect, isSelected, frameActive, isLeftToRight, nonRectIndex); | |
| textRect.setBounds(tabRect); | |
| fContentRect.setBounds(tabRect); | |
| paintContents(g, tabPlacement, nonRectIndex, tabRect, iconRect, textRect, isSelected); | |
| } | |
| protected void paintCUITab(final Graphics g, final int tabPlacement, | |
| final Rectangle tabRect, | |
| final boolean isSelected, | |
| final boolean frameActive, | |
| final boolean isLeftToRight, | |
| final int nonRectIndex) { | |
| final int tabCount = tabPane.getTabCount(); | |
| final boolean needsLeftScrollTab = visibleTabState.needsLeftScrollTab(); | |
| final boolean needsRightScrollTab = visibleTabState.needsRightScrollTab(); | |
| // first or last | |
| boolean first = nonRectIndex == 0; | |
| boolean last = nonRectIndex == tabCount - 1; | |
| if (needsLeftScrollTab || needsRightScrollTab) { | |
| if (nonRectIndex == -1) { | |
| first = false; | |
| last = true; | |
| } else if (nonRectIndex == -2) { | |
| first = true; | |
| last = false; | |
| } else { | |
| if (needsLeftScrollTab) first = false; | |
| if (needsRightScrollTab) last = false; | |
| } | |
| } | |
| if (tabPlacement == LEFT || tabPlacement == RIGHT) { | |
| final boolean tempSwap = last; | |
| last = first; | |
| first = tempSwap; | |
| } | |
| final State state = getState(nonRectIndex, frameActive, isSelected); | |
| painter.state.set(state); | |
| painter.state.set(isSelected || (state == State.INACTIVE && frameActive) ? BooleanValue.YES : BooleanValue.NO); | |
| painter.state.set(getSegmentPosition(first, last, isLeftToRight)); | |
| final int selectedIndex = tabPane.getSelectedIndex(); | |
| painter.state.set(getSegmentTrailingSeparator(nonRectIndex, selectedIndex, isLeftToRight)); | |
| painter.state.set(getSegmentLeadingSeparator(nonRectIndex, selectedIndex, isLeftToRight)); | |
| painter.state.set(tabPane.hasFocus() && isSelected ? Focused.YES : Focused.NO); | |
| painter.paint(g, tabPane, tabRect.x, tabRect.y, tabRect.width, tabRect.height); | |
| if (isScrollTabIndex(nonRectIndex)) return; | |
| final Color color = tabPane.getBackgroundAt(nonRectIndex); | |
| if (color == null || (color instanceof UIResource)) return; | |
| if (!isLeftToRight && (tabPlacement == TOP || tabPlacement == BOTTOM)) { | |
| final boolean tempSwap = last; | |
| last = first; | |
| first = tempSwap; | |
| } | |
| fillTabWithBackground(g, tabRect, tabPlacement, first, last, color); | |
| } | |
| protected Direction getDirection() { | |
| switch (tabPane.getTabPlacement()) { | |
| case SwingConstants.BOTTOM: return Direction.SOUTH; | |
| case SwingConstants.LEFT: return Direction.WEST; | |
| case SwingConstants.RIGHT: return Direction.EAST; | |
| } | |
| return Direction.NORTH; | |
| } | |
| protected static SegmentPosition getSegmentPosition(final boolean first, final boolean last, final boolean isLeftToRight) { | |
| if (first && last) return SegmentPosition.ONLY; | |
| if (first) return isLeftToRight ? SegmentPosition.FIRST : SegmentPosition.LAST; | |
| if (last) return isLeftToRight ? SegmentPosition.LAST : SegmentPosition.FIRST; | |
| return SegmentPosition.MIDDLE; | |
| } | |
| protected SegmentTrailingSeparator getSegmentTrailingSeparator(final int index, final int selectedIndex, final boolean isLeftToRight) { | |
| return SegmentTrailingSeparator.YES; | |
| } | |
| protected SegmentLeadingSeparator getSegmentLeadingSeparator(final int index, final int selectedIndex, final boolean isLeftToRight) { | |
| return SegmentLeadingSeparator.NO; | |
| } | |
| protected boolean isTabBeforeSelectedTab(final int index, final int selectedIndex, final boolean isLeftToRight) { | |
| if (index == -2 && visibleTabState.getIndex(0) == selectedIndex) return true; | |
| int indexBeforeSelectedIndex = isLeftToRight ? selectedIndex - 1 : selectedIndex + 1; | |
| return index == indexBeforeSelectedIndex ? true : false; | |
| } | |
| protected State getState(final int index, final boolean frameActive, final boolean isSelected) { | |
| if (!frameActive) return State.INACTIVE; | |
| if (!tabPane.isEnabled()) return State.DISABLED; | |
| if (JRSUIUtils.TabbedPane.useLegacyTabs()) { | |
| if (isSelected) return State.PRESSED; | |
| if (pressedTab == index) return State.INACTIVE; | |
| } else { | |
| if (isSelected) return State.ACTIVE; | |
| if (pressedTab == index) return State.PRESSED; | |
| } | |
| return State.ACTIVE; | |
| } | |
| /** | |
| * This routine adjusts the background fill rect so it just fits inside a tab, allowing for | |
| * whether we're talking about a first tab or last tab. NOTE that this code is very much | |
| * Aqua 2 dependent! | |
| */ | |
| static class AlterRects { | |
| Rectangle standard, first, last; | |
| AlterRects(final int x, final int y, final int w, final int h) { standard = new Rectangle(x, y, w, h); } | |
| AlterRects start(final int x, final int y, final int w, final int h) { first = new Rectangle(x, y, w, h); return this; } | |
| AlterRects end(final int x, final int y, final int w, final int h) { last = new Rectangle(x, y, w, h); return this; } | |
| static Rectangle alter(final Rectangle r, final Rectangle o) { | |
| // r = new Rectangle(r); | |
| r.x += o.x; | |
| r.y += o.y; | |
| r.width += o.width; | |
| r.height += o.height; | |
| return r; | |
| } | |
| } | |
| static AlterRects[] alterRects = new AlterRects[5]; | |
| protected static AlterRects getAlterationFor(final int tabPlacement) { | |
| if (alterRects[tabPlacement] != null) return alterRects[tabPlacement]; | |
| switch (tabPlacement) { | |
| case LEFT: return alterRects[LEFT] = new AlterRects(2, 0, -4, 1).start(0, 0, 0, -4).end(0, 3, 0, -3); | |
| case RIGHT: return alterRects[RIGHT] = new AlterRects(1, 0, -4, 1).start(0, 0, 0, -4).end(0, 3, 0, -3); | |
| case BOTTOM: return alterRects[BOTTOM] = new AlterRects(0, 1, 0, -4).start(3, 0, -3, 0).end(0, 0, -3, 0); | |
| case TOP: | |
| default: return alterRects[TOP] = new AlterRects(0, 2, 0, -4).start(3, 0, -3, 0).end(0, 0, -3, 0); | |
| } | |
| } | |
| protected void fillTabWithBackground(final Graphics g, final Rectangle rect, final int tabPlacement, final boolean first, final boolean last, final Color color) { | |
| final Rectangle fillRect = new Rectangle(rect); | |
| final AlterRects alteration = getAlterationFor(tabPlacement); | |
| AlterRects.alter(fillRect, alteration.standard); | |
| if (first) AlterRects.alter(fillRect, alteration.first); | |
| if (last) AlterRects.alter(fillRect, alteration.last); | |
| g.setColor(new Color(color.getRed(), color.getGreen(), color.getBlue(), (int)(color.getAlpha() * 0.25))); | |
| g.fillRoundRect(fillRect.x, fillRect.y, fillRect.width, fillRect.height, 3, 1); | |
| } | |
| protected Insets getContentBorderInsets(final int tabPlacement) { | |
| final Insets draw = getContentDrawingInsets(tabPlacement); // will be rotated | |
| rotateInsets(contentBorderInsets, currentContentBorderInsets, tabPlacement); | |
| currentContentBorderInsets.left += draw.left; | |
| currentContentBorderInsets.right += draw.right; | |
| currentContentBorderInsets.top += draw.top; | |
| currentContentBorderInsets.bottom += draw.bottom; | |
| return currentContentBorderInsets; | |
| } | |
| protected static void rotateInsets(final Insets topInsets, final Insets targetInsets, final int targetPlacement) { | |
| switch (targetPlacement) { | |
| case LEFT: | |
| targetInsets.top = topInsets.left; | |
| targetInsets.left = topInsets.top; | |
| targetInsets.bottom = topInsets.right; | |
| targetInsets.right = topInsets.bottom; | |
| break; | |
| case BOTTOM: | |
| targetInsets.top = topInsets.bottom; | |
| targetInsets.left = topInsets.left; | |
| targetInsets.bottom = topInsets.top; | |
| targetInsets.right = topInsets.right; | |
| break; | |
| case RIGHT: | |
| targetInsets.top = topInsets.right; | |
| targetInsets.left = topInsets.bottom; | |
| targetInsets.bottom = topInsets.left; | |
| targetInsets.right = topInsets.top; | |
| break; | |
| case TOP: | |
| default: | |
| targetInsets.top = topInsets.top; | |
| targetInsets.left = topInsets.left; | |
| targetInsets.bottom = topInsets.bottom; | |
| targetInsets.right = topInsets.right; | |
| } | |
| } | |
| protected Insets getContentDrawingInsets(final int tabPlacement) { | |
| rotateInsets(contentDrawingInsets, currentContentDrawingInsets, tabPlacement); | |
| return currentContentDrawingInsets; | |
| } | |
| protected Icon getIconForTab(final int tabIndex) { | |
| final Icon mainIcon = super.getIconForTab(tabIndex); | |
| if (mainIcon == null) return null; | |
| final int iconHeight = mainIcon.getIconHeight(); | |
| if (iconHeight <= kMaxIconSize) return mainIcon; | |
| final float ratio = (float)kMaxIconSize / (float)iconHeight; | |
| final int iconWidth = mainIcon.getIconWidth(); | |
| return new AquaIcon.CachingScalingIcon((int)(iconWidth * ratio), kMaxIconSize) { | |
| Image createImage() { | |
| return AquaIcon.getImageForIcon(mainIcon); | |
| } | |
| }; | |
| } | |
| private static final int TAB_BORDER_INSET = 9; | |
| protected void paintContentBorder(final Graphics g, final int tabPlacement, final int selectedIndex) { | |
| final int width = tabPane.getWidth(); | |
| final int height = tabPane.getHeight(); | |
| final Insets insets = tabPane.getInsets(); | |
| int x = insets.left; | |
| int y = insets.top; | |
| int w = width - insets.right - insets.left; | |
| int h = height - insets.top - insets.bottom; | |
| switch (tabPlacement) { | |
| case TOP: | |
| y += TAB_BORDER_INSET; | |
| h -= TAB_BORDER_INSET; | |
| break; | |
| case BOTTOM: | |
| h -= TAB_BORDER_INSET;// - 2; | |
| break; | |
| case LEFT: | |
| x += TAB_BORDER_INSET;// - 5; | |
| w -= TAB_BORDER_INSET;// + 1; | |
| break; | |
| case RIGHT: | |
| w -= TAB_BORDER_INSET;// + 1; | |
| break; | |
| } | |
| if (tabPane.isOpaque()) { | |
| g.setColor(tabPane.getBackground()); | |
| g.fillRect(0, 0, width, height); | |
| } | |
| AquaGroupBorder.getTabbedPaneGroupBorder().paintBorder(tabPane, g, x, y, w, h); | |
| } | |
| // see paintContentBorder | |
| protected void repaintContentBorderEdge() { | |
| final int width = tabPane.getWidth(); | |
| final int height = tabPane.getHeight(); | |
| final Insets insets = tabPane.getInsets(); | |
| final int tabPlacement = tabPane.getTabPlacement(); | |
| final Insets localContentBorderInsets = getContentBorderInsets(tabPlacement); | |
| int x = insets.left; | |
| int y = insets.top; | |
| int w = width - insets.right - insets.left; | |
| int h = height - insets.top - insets.bottom; | |
| switch (tabPlacement) { | |
| case LEFT: | |
| x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth); | |
| w = localContentBorderInsets.left; | |
| break; | |
| case RIGHT: | |
| w = localContentBorderInsets.right; | |
| break; | |
| case BOTTOM: | |
| h = localContentBorderInsets.bottom; | |
| break; | |
| case TOP: | |
| default: | |
| y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight); | |
| h = localContentBorderInsets.top; | |
| } | |
| tabPane.repaint(x, y, w, h); | |
| } | |
| public boolean isTabVisible(final int index) { | |
| if (index == -1 || index == -2) return true; | |
| for (int i = 0; i < visibleTabState.getTotal(); i++) { | |
| if (visibleTabState.getIndex(i) == index) return true; | |
| } | |
| return false; | |
| } | |
| /** | |
| * Returns the bounds of the specified tab index. The bounds are | |
| * with respect to the JTabbedPane's coordinate space. If the tab at this | |
| * index is not currently visible in the UI, then returns null. | |
| */ | |
| @Override | |
| public Rectangle getTabBounds(final JTabbedPane pane, final int i) { | |
| if (visibleTabState.needsScrollTabs() | |
| && (visibleTabState.isBefore(i) || visibleTabState.isAfter(i))) { | |
| return null; | |
| } | |
| return super.getTabBounds(pane, i); | |
| } | |
| /** | |
| * Returns the tab index which intersects the specified point | |
| * in the JTabbedPane's coordinate space. | |
| */ | |
| public int tabForCoordinate(final JTabbedPane pane, final int x, final int y) { | |
| ensureCurrentLayout(); | |
| final Point p = new Point(x, y); | |
| if (visibleTabState.needsScrollTabs()) { | |
| for (int i = 0; i < visibleTabState.getTotal(); i++) { | |
| final int realOffset = visibleTabState.getIndex(i); | |
| if (rects[realOffset].contains(p.x, p.y)) return realOffset; | |
| } | |
| if (visibleTabState.getRightScrollTabRect().contains(p.x, p.y)) return -1; //tabPane.getTabCount(); | |
| } else { | |
| //old way | |
| final int tabCount = tabPane.getTabCount(); | |
| for (int i = 0; i < tabCount; i++) { | |
| if (rects[i].contains(p.x, p.y)) return i; | |
| } | |
| } | |
| return -1; | |
| } | |
| protected Insets getTabInsets(final int tabPlacement, final int tabIndex) { | |
| switch (tabPlacement) { | |
| case LEFT: return UIManager.getInsets("TabbedPane.leftTabInsets"); | |
| case RIGHT: return UIManager.getInsets("TabbedPane.rightTabInsets"); | |
| } | |
| return tabInsets; | |
| } | |
| // This is the preferred size - the layout manager will ignore if it has to | |
| protected int calculateTabHeight(final int tabPlacement, final int tabIndex, final int fontHeight) { | |
| // Constrain to what the Mac allows | |
| final int result = super.calculateTabHeight(tabPlacement, tabIndex, fontHeight); | |
| // force tabs to have a max height for aqua | |
| if (result <= kSmallTabHeight) return kSmallTabHeight; | |
| return kLargeTabHeight; | |
| } | |
| // JBuilder requested this - it's against HI, but then so are multiple rows | |
| protected boolean shouldRotateTabRuns(final int tabPlacement) { | |
| return false; | |
| } | |
| protected class TabbedPanePropertyChangeHandler extends PropertyChangeHandler { | |
| public void propertyChange(final PropertyChangeEvent e) { | |
| final String prop = e.getPropertyName(); | |
| if (!AquaFocusHandler.FRAME_ACTIVE_PROPERTY.equals(prop)) { | |
| super.propertyChange(e); | |
| return; | |
| } | |
| final JTabbedPane comp = (JTabbedPane)e.getSource(); | |
| comp.repaint(); | |
| // Repaint the "front" tab and the border | |
| final int selected = tabPane.getSelectedIndex(); | |
| final Rectangle[] theRects = rects; | |
| if (selected >= 0 && selected < theRects.length) comp.repaint(theRects[selected]); | |
| repaintContentBorderEdge(); | |
| } | |
| } | |
| protected ChangeListener createChangeListener() { | |
| return new ChangeListener() { | |
| public void stateChanged(final ChangeEvent e) { | |
| JTabbedPane tabPane = (JTabbedPane)e.getSource(); | |
| if (!isTabVisible(tabPane.getSelectedIndex())) popupSelectionChanged = true; | |
| tabPane.revalidate(); | |
| tabPane.repaint(); | |
| } | |
| }; | |
| } | |
| protected class FocusHandler extends FocusAdapter { | |
| Rectangle sWorkingRect = new Rectangle(); | |
| public void focusGained(final FocusEvent e) { | |
| if (isDefaultFocusReceiver(tabPane) && !hasAvoidedFirstFocus) { | |
| KeyboardFocusManager.getCurrentKeyboardFocusManager().focusNextComponent(); | |
| hasAvoidedFirstFocus = true; | |
| } | |
| adjustPaintingRectForFocusRing(e); | |
| } | |
| public void focusLost(final FocusEvent e) { | |
| adjustPaintingRectForFocusRing(e); | |
| } | |
| void adjustPaintingRectForFocusRing(final FocusEvent e) { | |
| final JTabbedPane pane = (JTabbedPane)e.getSource(); | |
| final int tabCount = pane.getTabCount(); | |
| final int selectedIndex = pane.getSelectedIndex(); | |
| if (selectedIndex != -1 && tabCount > 0 && tabCount == rects.length) { | |
| sWorkingRect.setBounds(rects[selectedIndex]); | |
| sWorkingRect.grow(4, 4); | |
| pane.repaint(sWorkingRect); | |
| } | |
| } | |
| boolean isDefaultFocusReceiver(final JComponent component) { | |
| if (isDefaultFocusReceiver == null) { | |
| Component defaultFocusReceiver = KeyboardFocusManager.getCurrentKeyboardFocusManager().getDefaultFocusTraversalPolicy().getDefaultComponent(getTopLevelFocusCycleRootAncestor(component)); | |
| isDefaultFocusReceiver = defaultFocusReceiver != null && defaultFocusReceiver.equals(component); | |
| } | |
| return isDefaultFocusReceiver.booleanValue(); | |
| } | |
| Container getTopLevelFocusCycleRootAncestor(Container container) { | |
| Container ancestor; | |
| while ((ancestor = container.getFocusCycleRootAncestor()) != null) { | |
| container = ancestor; | |
| } | |
| return container; | |
| } | |
| } | |
| class MouseHandler extends MouseInputAdapter implements ActionListener { | |
| int trackingTab = -3; | |
| private final Timer popupTimer = new Timer(500, this); | |
| MouseHandler() { | |
| popupTimer.setRepeats(false); | |
| } | |
| void dispose (){ | |
| popupTimer.removeActionListener(this); | |
| popupTimer.stop(); | |
| } | |
| public void mousePressed(final MouseEvent e) { | |
| final JTabbedPane pane = (JTabbedPane)e.getSource(); | |
| if (!pane.isEnabled()) { | |
| trackingTab = -3; | |
| return; | |
| } | |
| final Point p = e.getPoint(); | |
| trackingTab = getCurrentTab(pane, p); | |
| if (trackingTab == -3 || (!shouldRepaintSelectedTabOnMouseDown() && trackingTab == pane.getSelectedIndex())) { | |
| trackingTab = -3; | |
| return; | |
| } | |
| if (trackingTab < 0 && trackingTab > -3) { | |
| popupTimer.start(); | |
| } | |
| pressedTab = trackingTab; | |
| repaint(pane, pressedTab); | |
| } | |
| public void mouseDragged(final MouseEvent e) { | |
| if (trackingTab < -2) return; | |
| final JTabbedPane pane = (JTabbedPane)e.getSource(); | |
| final int currentTab = getCurrentTab(pane, e.getPoint()); | |
| if (currentTab != trackingTab) { | |
| pressedTab = -3; | |
| } else { | |
| pressedTab = trackingTab; | |
| } | |
| if (trackingTab < 0 && trackingTab > -3) { | |
| popupTimer.start(); | |
| } | |
| repaint(pane, trackingTab); | |
| } | |
| public void mouseReleased(final MouseEvent e) { | |
| if (trackingTab < -2) return; | |
| popupTimer.stop(); | |
| final JTabbedPane pane = (JTabbedPane)e.getSource(); | |
| final Point p = e.getPoint(); | |
| final int currentTab = getCurrentTab(pane, p); | |
| if (trackingTab == -1 && currentTab == -1) { | |
| pane.setSelectedIndex(pane.getSelectedIndex() + 1); | |
| } | |
| if (trackingTab == -2 && currentTab == -2) { | |
| pane.setSelectedIndex(pane.getSelectedIndex() - 1); | |
| } | |
| if (trackingTab >= 0 && currentTab == trackingTab) { | |
| pane.setSelectedIndex(trackingTab); | |
| } | |
| repaint(pane, trackingTab); | |
| pressedTab = -3; | |
| trackingTab = -3; | |
| } | |
| public void actionPerformed(final ActionEvent e) { | |
| if (trackingTab != pressedTab) { | |
| return; | |
| } | |
| if (trackingTab == -1) { | |
| showFullPopup(false); | |
| trackingTab = -3; | |
| } | |
| if (trackingTab == -2) { | |
| showFullPopup(true); | |
| trackingTab = -3; | |
| } | |
| } | |
| int getCurrentTab(final JTabbedPane pane, final Point p) { | |
| final int tabIndex = tabForCoordinate(pane, p.x, p.y); | |
| if (tabIndex >= 0 && pane.isEnabledAt(tabIndex)) return tabIndex; | |
| if (visibleTabState.needsLeftScrollTab() && visibleTabState.getLeftScrollTabRect().contains(p)) return -2; | |
| if (visibleTabState.needsRightScrollTab() && visibleTabState.getRightScrollTabRect().contains(p)) return -1; | |
| return -3; | |
| } | |
| void repaint(final JTabbedPane pane, final int tab) { | |
| switch (tab) { | |
| case -1: | |
| pane.repaint(visibleTabState.getRightScrollTabRect()); | |
| return; | |
| case -2: | |
| pane.repaint(visibleTabState.getLeftScrollTabRect()); | |
| return; | |
| default: | |
| if (trackingTab >= 0) pane.repaint(rects[trackingTab]); | |
| return; | |
| } | |
| } | |
| void showFullPopup(final boolean firstTab) { | |
| final JPopupMenu popup = new JPopupMenu(); | |
| for (int i = 0; i < tabPane.getTabCount(); i++) { | |
| if (firstTab ? visibleTabState.isBefore(i) : visibleTabState.isAfter(i)) { | |
| popup.add(createMenuItem(i)); | |
| } | |
| } | |
| if (firstTab) { | |
| final Rectangle leftScrollTabRect = visibleTabState.getLeftScrollTabRect(); | |
| final Dimension popupRect = popup.getPreferredSize(); | |
| popup.show(tabPane, leftScrollTabRect.x - popupRect.width, leftScrollTabRect.y + 7); | |
| } else { | |
| final Rectangle rightScrollTabRect = visibleTabState.getRightScrollTabRect(); | |
| popup.show(tabPane, rightScrollTabRect.x + rightScrollTabRect.width, rightScrollTabRect.y + 7); | |
| } | |
| popup.addPopupMenuListener(new PopupMenuListener() { | |
| public void popupMenuCanceled(final PopupMenuEvent e) { } | |
| public void popupMenuWillBecomeVisible(final PopupMenuEvent e) { } | |
| public void popupMenuWillBecomeInvisible(final PopupMenuEvent e) { | |
| pressedTab = -3; | |
| tabPane.repaint(visibleTabState.getLeftScrollTabRect()); | |
| tabPane.repaint(visibleTabState.getRightScrollTabRect()); | |
| } | |
| }); | |
| } | |
| JMenuItem createMenuItem(final int i) { | |
| final Component component = getTabComponentAt(i); | |
| final JMenuItem menuItem; | |
| if (component == null) { | |
| menuItem = new JMenuItem(tabPane.getTitleAt(i), tabPane.getIconAt(i)); | |
| } else { | |
| JMenuItem tmp = new JMenuItem() { | |
| public void paintComponent(final Graphics g) { | |
| super.paintComponent(g); | |
| final Dimension size = component.getSize(); | |
| component.setSize(getSize()); | |
| component.validate(); | |
| component.paint(g); | |
| component.setSize(size); | |
| } | |
| public Dimension getPreferredSize() { | |
| return component.getPreferredSize(); | |
| } | |
| }; | |
| menuItem = tmp; | |
| } | |
| final Color background = tabPane.getBackgroundAt(i); | |
| if (!(background instanceof UIResource)) { | |
| menuItem.setBackground(background); | |
| } | |
| menuItem.setForeground(tabPane.getForegroundAt(i)); | |
| // for <rdar://problem/3520267> make sure to disable items that are disabled in the tab. | |
| if (!tabPane.isEnabledAt(i)) menuItem.setEnabled(false); | |
| final int fOffset = i; | |
| menuItem.addActionListener(new ActionListener() { | |
| public void actionPerformed(final ActionEvent ae) { | |
| boolean visible = isTabVisible(fOffset); | |
| tabPane.setSelectedIndex(fOffset); | |
| if (!visible) { | |
| popupSelectionChanged = true; | |
| tabPane.invalidate(); | |
| tabPane.repaint(); | |
| } | |
| } | |
| }); | |
| return menuItem; | |
| } | |
| } | |
| protected class AquaTruncatingTabbedPaneLayout extends AquaTabbedPaneCopyFromBasicUI.TabbedPaneLayout { | |
| // fix for Radar #3346131 | |
| protected int preferredTabAreaWidth(final int tabPlacement, final int height) { | |
| // Our superclass wants to stack tabs, but we rotate them, | |
| // so when tabs are on the left or right we know that | |
| // our width is actually the "height" of a tab which is then | |
| // rotated. | |
| if (tabPlacement == SwingConstants.LEFT || tabPlacement == SwingConstants.RIGHT) { | |
| return super.preferredTabAreaHeight(tabPlacement, height); | |
| } | |
| return super.preferredTabAreaWidth(tabPlacement, height); | |
| } | |
| protected int preferredTabAreaHeight(final int tabPlacement, final int width) { | |
| if (tabPlacement == SwingConstants.LEFT || tabPlacement == SwingConstants.RIGHT) { | |
| return super.preferredTabAreaWidth(tabPlacement, width); | |
| } | |
| return super.preferredTabAreaHeight(tabPlacement, width); | |
| } | |
| protected void calculateTabRects(final int tabPlacement, final int tabCount) { | |
| if (tabCount <= 0) return; | |
| superCalculateTabRects(tabPlacement, tabCount); // does most of the hard work | |
| // If they haven't been padded (which they only do when there are multiple rows) we should center them | |
| if (rects.length <= 0) return; | |
| visibleTabState.alignRectsRunFor(rects, tabPane.getSize(), tabPlacement, AquaUtils.isLeftToRight(tabPane)); | |
| } | |
| protected void padTabRun(final int tabPlacement, final int start, final int end, final int max) { | |
| if (tabPlacement == SwingConstants.TOP || tabPlacement == SwingConstants.BOTTOM) { | |
| super.padTabRun(tabPlacement, start, end, max); | |
| return; | |
| } | |
| final Rectangle lastRect = rects[end]; | |
| final int runHeight = (lastRect.y + lastRect.height) - rects[start].y; | |
| final int deltaHeight = max - (lastRect.y + lastRect.height); | |
| final float factor = (float)deltaHeight / (float)runHeight; | |
| for (int i = start; i <= end; i++) { | |
| final Rectangle pastRect = rects[i]; | |
| if (i > start) { | |
| pastRect.y = rects[i - 1].y + rects[i - 1].height; | |
| } | |
| pastRect.height += Math.round(pastRect.height * factor); | |
| } | |
| lastRect.height = max - lastRect.y; | |
| } | |
| /** | |
| * This is a massive routine and I left it like this because the bulk of the code comes | |
| * from the BasicTabbedPaneUI class. Here is what it does: | |
| * 1. Calculate rects for the tabs - we have to play tricks here because our right and left tabs | |
| * should get widths calculated the same way as top and bottom, but they will be rotated so the | |
| * calculated width is stored as the rect height. | |
| * 2. Decide if we can fit all the tabs. | |
| * 3. When we cannot fit all the tabs we create a tab popup, and then layout the new tabs until | |
| * we can't fit them anymore. Laying them out is a matter of adding them into the visible list | |
| * and shifting them horizontally to the correct location. | |
| */ | |
| protected synchronized void superCalculateTabRects(final int tabPlacement, final int tabCount) { | |
| final Dimension size = tabPane.getSize(); | |
| final Insets insets = tabPane.getInsets(); | |
| final Insets localTabAreaInsets = getTabAreaInsets(tabPlacement); | |
| // Calculate bounds within which a tab run must fit | |
| final int returnAt; | |
| final int x, y; | |
| switch (tabPlacement) { | |
| case SwingConstants.LEFT: | |
| maxTabWidth = calculateMaxTabHeight(tabPlacement); | |
| x = insets.left + localTabAreaInsets.left; | |
| y = insets.top + localTabAreaInsets.top; | |
| returnAt = size.height - (insets.bottom + localTabAreaInsets.bottom); | |
| break; | |
| case SwingConstants.RIGHT: | |
| maxTabWidth = calculateMaxTabHeight(tabPlacement); | |
| x = size.width - insets.right - localTabAreaInsets.right - maxTabWidth - 1; | |
| y = insets.top + localTabAreaInsets.top; | |
| returnAt = size.height - (insets.bottom + localTabAreaInsets.bottom); | |
| break; | |
| case SwingConstants.BOTTOM: | |
| maxTabHeight = calculateMaxTabHeight(tabPlacement); | |
| x = insets.left + localTabAreaInsets.left; | |
| y = size.height - insets.bottom - localTabAreaInsets.bottom - maxTabHeight; | |
| returnAt = size.width - (insets.right + localTabAreaInsets.right); | |
| break; | |
| case SwingConstants.TOP: | |
| default: | |
| maxTabHeight = calculateMaxTabHeight(tabPlacement); | |
| x = insets.left + localTabAreaInsets.left; | |
| y = insets.top + localTabAreaInsets.top; | |
| returnAt = size.width - (insets.right + localTabAreaInsets.right); | |
| break; | |
| } | |
| tabRunOverlay = getTabRunOverlay(tabPlacement); | |
| runCount = 0; | |
| selectedRun = 0; | |
| if (tabCount == 0) return; | |
| final FontMetrics metrics = getFontMetrics(); | |
| final boolean verticalTabRuns = (tabPlacement == SwingConstants.LEFT || tabPlacement == SwingConstants.RIGHT); | |
| final int selectedIndex = tabPane.getSelectedIndex(); | |
| // calculate all the widths | |
| // if they all fit we are done, if not | |
| // we have to do the dance of figuring out which ones to show. | |
| visibleTabState.setNeedsScrollers(false); | |
| for (int i = 0; i < tabCount; i++) { | |
| final Rectangle rect = rects[i]; | |
| if (verticalTabRuns) { | |
| calculateVerticalTabRunRect(rect, metrics, tabPlacement, returnAt, i, x, y); | |
| // test if we need to scroll! | |
| if (rect.y + rect.height > returnAt) { | |
| visibleTabState.setNeedsScrollers(true); | |
| } | |
| } else { | |
| calculateHorizontalTabRunRect(rect, metrics, tabPlacement, returnAt, i, x, y); | |
| // test if we need to scroll! | |
| if (rect.x + rect.width > returnAt) { | |
| visibleTabState.setNeedsScrollers(true); | |
| } | |
| } | |
| } | |
| visibleTabState.relayoutForScrolling(rects, x, y, returnAt, selectedIndex, verticalTabRuns, tabCount, AquaUtils.isLeftToRight(tabPane)); | |
| // Pad the selected tab so that it appears raised in front | |
| // if right to left and tab placement on the top or | |
| // the bottom, flip x positions and adjust by widths | |
| if (!AquaUtils.isLeftToRight(tabPane) && !verticalTabRuns) { | |
| final int rightMargin = size.width - (insets.right + localTabAreaInsets.right); | |
| for (int i = 0; i < tabCount; i++) { | |
| rects[i].x = rightMargin - rects[i].x - rects[i].width; | |
| } | |
| } | |
| } | |
| private void calculateHorizontalTabRunRect(final Rectangle rect, final FontMetrics metrics, final int tabPlacement, final int returnAt, final int i, final int x, final int y) { | |
| // Tabs on TOP or BOTTOM.... | |
| if (i > 0) { | |
| rect.x = rects[i - 1].x + rects[i - 1].width; | |
| } else { | |
| tabRuns[0] = 0; | |
| runCount = 1; | |
| maxTabWidth = 0; | |
| rect.x = x; | |
| } | |
| rect.width = calculateTabWidth(tabPlacement, i, metrics); | |
| maxTabWidth = Math.max(maxTabWidth, rect.width); | |
| rect.y = y; | |
| rect.height = maxTabHeight; | |
| } | |
| private void calculateVerticalTabRunRect(final Rectangle rect, final FontMetrics metrics, final int tabPlacement, final int returnAt, final int i, final int x, final int y) { | |
| // Tabs on LEFT or RIGHT... | |
| if (i > 0) { | |
| rect.y = rects[i - 1].y + rects[i - 1].height; | |
| } else { | |
| tabRuns[0] = 0; | |
| runCount = 1; | |
| maxTabHeight = 0; | |
| rect.y = y; | |
| } | |
| rect.height = calculateTabWidth(tabPlacement, i, metrics); | |
| maxTabHeight = Math.max(maxTabHeight, rect.height); | |
| rect.x = x; | |
| rect.width = maxTabWidth; | |
| } | |
| protected void layoutTabComponents() { | |
| final Container tabContainer = getTabContainer(); | |
| if (tabContainer == null) return; | |
| final int placement = tabPane.getTabPlacement(); | |
| final Rectangle rect = new Rectangle(); | |
| final Point delta = new Point(-tabContainer.getX(), -tabContainer.getY()); | |
| for (int i = 0; i < tabPane.getTabCount(); i++) { | |
| final Component c = getTabComponentAt(i); | |
| if (c == null) continue; | |
| getTabBounds(i, rect); | |
| final Insets insets = getTabInsets(tabPane.getTabPlacement(), i); | |
| final boolean isSeleceted = i == tabPane.getSelectedIndex(); | |
| if (placement == SwingConstants.TOP || placement == SwingConstants.BOTTOM) { | |
| rect.x += insets.left + delta.x + getTabLabelShiftX(placement, i, isSeleceted); | |
| rect.y += insets.top + delta.y + getTabLabelShiftY(placement, i, isSeleceted) + 1; | |
| rect.width -= insets.left + insets.right; | |
| rect.height -= insets.top + insets.bottom - 1; | |
| } else { | |
| rect.x += insets.top + delta.x + getTabLabelShiftY(placement, i, isSeleceted) + (placement == SwingConstants.LEFT ? 2 : 1); | |
| rect.y += insets.left + delta.y + getTabLabelShiftX(placement, i, isSeleceted); | |
| rect.width -= insets.top + insets.bottom - 1; | |
| rect.height -= insets.left + insets.right; | |
| } | |
| c.setBounds(rect); | |
| } | |
| } | |
| } | |
| } |