/* * Copyright (c) 2011, 2018, 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 javax.swing.SwingConstants; class AquaTabbedPaneTabState { static final int FIXED_SCROLL_TAB_LENGTH = 27; protected final Rectangle leftScrollTabRect = new Rectangle(); protected final Rectangle rightScrollTabRect = new Rectangle(); protected int numberOfVisibleTabs = 0; protected int[] visibleTabList = new int[10]; protected int lastLeftmostTab; protected int lastReturnAt; private boolean needsScrollers; private boolean hasMoreLeftTabs; private boolean hasMoreRightTabs; private final AquaTabbedPaneUI pane; protected AquaTabbedPaneTabState(final AquaTabbedPaneUI pane) { this.pane = pane; } protected int getIndex(final int i) { if (i >= visibleTabList.length) return Integer.MIN_VALUE; return visibleTabList[i]; } protected void init(final int tabCount) { if (tabCount < 1) needsScrollers = false; if (tabCount == visibleTabList.length) return; final int[] tempVisibleTabs = new int[tabCount]; System.arraycopy(visibleTabList, 0, tempVisibleTabs, 0, Math.min(visibleTabList.length, tabCount)); visibleTabList = tempVisibleTabs; } int getTotal() { return numberOfVisibleTabs; } boolean needsScrollTabs() { return needsScrollers; } void setNeedsScrollers(final boolean needsScrollers) { this.needsScrollers = needsScrollers; } boolean needsLeftScrollTab() { return hasMoreLeftTabs; } boolean needsRightScrollTab() { return hasMoreRightTabs; } Rectangle getLeftScrollTabRect() { return leftScrollTabRect; } Rectangle getRightScrollTabRect() { return rightScrollTabRect; } boolean isBefore(final int i) { if (numberOfVisibleTabs == 0) return true; if (i < visibleTabList[0]) return true; return false; } boolean isAfter(final int i) { if (i > visibleTabList[numberOfVisibleTabs - 1]) return true; return false; } private void addToEnd(final int idToAdd, final int length) { visibleTabList[length] = idToAdd; } private void addToBeginning(final int idToAdd, final int length) { System.arraycopy(visibleTabList, 0, visibleTabList, 1, length); visibleTabList[0] = idToAdd; } void relayoutForScrolling(final Rectangle[] rects, final int startX, final int startY, final int returnAt, final int selectedIndex, final boolean verticalTabRuns, final int tabCount, final boolean isLeftToRight) { if (!needsScrollers) { hasMoreLeftTabs = false; hasMoreRightTabs = false; return; } // we don't fit, so we need to figure the space based on the size of the popup // tab, then add the tabs, centering the selected tab as much as possible. // Tabs on TOP or BOTTOM or LEFT or RIGHT // if top or bottom, width is hardocoded // if left or right height should be hardcoded. if (verticalTabRuns) { rightScrollTabRect.height = FIXED_SCROLL_TAB_LENGTH; leftScrollTabRect.height = FIXED_SCROLL_TAB_LENGTH; } else { rightScrollTabRect.width = FIXED_SCROLL_TAB_LENGTH; leftScrollTabRect.width = FIXED_SCROLL_TAB_LENGTH; } // we have all the tab rects, we just need to adjust the x coordinates // and populate the visible list // sja fix what do we do if remaining width is <0?? // we could try to center it based on width of tabs, but for now // we try to center based on number of tabs on each side, putting the extra // on the left (since the first right is the selected tab). // if we have 0 selected we will just go right, and if we have // the logic here is start with the selected tab, and then fit // in as many tabs as possible on each side until we don't fit any more. // but if all we did was change selection then we need to try to keep the same // tabs on screen so we don't get a jarring tab moving out from under the mouse // effect. final boolean sizeChanged = returnAt != lastReturnAt; // so if we stay the same, make right the first tab and say left done = true if (pane.popupSelectionChanged || sizeChanged) { pane.popupSelectionChanged = false; lastLeftmostTab = -1; } int right = selectedIndex; int left = selectedIndex - 1; // if we had a good last leftmost tab then we set left to unused and // start at that tab. if (lastLeftmostTab >= 0) { right = lastLeftmostTab; left = -1; } else if (selectedIndex < 0) { // this is if there is none selected see radar 3138137 right = 0; left = -1; } int remainingSpace = returnAt - pane.tabAreaInsets.right - pane.tabAreaInsets.left - FIXED_SCROLL_TAB_LENGTH * 2; int visibleCount = 0; final Rectangle firstRect = rects[right]; if ((verticalTabRuns ? firstRect.height : firstRect.width) > remainingSpace) { // always show at least the selected one! addToEnd(right, visibleCount); if (verticalTabRuns) { firstRect.height = remainingSpace; // force it to fit! } else { firstRect.width = remainingSpace; // force it to fit! } visibleCount++; } else { boolean rightDone = false; boolean leftDone = false; // at least one if not more will fit while ((visibleCount < tabCount) && !(rightDone && leftDone)) { if (!rightDone && right >= 0 && right < tabCount) { final Rectangle rightRect = rects[right]; if ((verticalTabRuns ? rightRect.height : rightRect.width) > remainingSpace) { rightDone = true; } else { addToEnd(right, visibleCount); visibleCount++; remainingSpace -= (verticalTabRuns ? rightRect.height : rightRect.width); right++; continue; // this gives a bias to "paging forward", and "inching backward" } } else { rightDone = true; } if (!leftDone && left >= 0 && left < tabCount) { final Rectangle leftRect = rects[left]; if ((verticalTabRuns ? leftRect.height : leftRect.width) > remainingSpace) { leftDone = true; } else { addToBeginning(left, visibleCount); visibleCount++; remainingSpace -= (verticalTabRuns ? leftRect.height : leftRect.width); left--; } } else { leftDone = true; } } } if (visibleCount > visibleTabList.length) visibleCount = visibleTabList.length; hasMoreLeftTabs = visibleTabList[0] > 0; hasMoreRightTabs = visibleTabList[visibleCount - 1] < visibleTabList.length - 1; numberOfVisibleTabs = visibleCount; // add the scroll tab at the end; lastLeftmostTab = getIndex(0); lastReturnAt = returnAt; final int firstTabIndex = getIndex(0); final int lastTabIndex = getIndex(visibleCount - 1); // move all "invisible" tabs beyond the edge of known space... for (int i = 0; i < tabCount; i++) { if (i < firstTabIndex || i > lastTabIndex) { final Rectangle rect = rects[i]; rect.x = Short.MAX_VALUE; rect.y = Short.MAX_VALUE; } } } protected void alignRectsRunFor(final Rectangle[] rects, final Dimension tabPaneSize, final int tabPlacement, final boolean isRightToLeft) { final boolean isVertical = tabPlacement == SwingConstants.LEFT || tabPlacement == SwingConstants.RIGHT; if (isVertical) { if (needsScrollers) { stretchScrollingVerticalRun(rects, tabPaneSize); } else { centerVerticalRun(rects, tabPaneSize); } } else { if (needsScrollers) { stretchScrollingHorizontalRun(rects, tabPaneSize, isRightToLeft); } else { centerHorizontalRun(rects, tabPaneSize, isRightToLeft); } } } private void centerHorizontalRun(final Rectangle[] rects, final Dimension size, final boolean isRightToLeft) { int totalLength = 0; for (final Rectangle element : rects) { totalLength += element.width; } int x = size.width / 2 - totalLength / 2; if (isRightToLeft) { for (final Rectangle rect : rects) { rect.x = x; x += rect.width; } } else { for (int i = rects.length - 1; i >= 0; i--) { final Rectangle rect = rects[i]; rect.x = x; x += rect.width; } } } private void centerVerticalRun(final Rectangle[] rects, final Dimension size) { int totalLength = 0; for (final Rectangle element : rects) { totalLength += element.height; } int y = size.height / 2 - totalLength / 2; if (true) { for (final Rectangle rect : rects) { rect.y = y; y += rect.height; } } else { for (int i = rects.length - 1; i >= 0; i--) { final Rectangle rect = rects[i]; rect.y = y; y += rect.height; } } } private void stretchScrollingHorizontalRun(final Rectangle[] rects, final Dimension size, final boolean isRightToLeft) { final int totalTabs = getTotal(); final int firstTabIndex = getIndex(0); final int lastTabIndex = getIndex(totalTabs - 1); int totalRunLength = 0; for (int i = firstTabIndex; i <= lastTabIndex; i++) { totalRunLength += rects[i].width; } int slack = size.width - totalRunLength - pane.tabAreaInsets.left - pane.tabAreaInsets.right; if (needsLeftScrollTab()) { slack -= FIXED_SCROLL_TAB_LENGTH; } if (needsRightScrollTab()) { slack -= FIXED_SCROLL_TAB_LENGTH; } final int minSlack = (int)((float)(slack) / (float)(totalTabs)); int extraSlack = slack - (minSlack * totalTabs); int runningLength = 0; final int xOffset = pane.tabAreaInsets.left + (needsLeftScrollTab() ? FIXED_SCROLL_TAB_LENGTH : 0); if (isRightToLeft) { for (int i = firstTabIndex; i <= lastTabIndex; i++) { final Rectangle rect = rects[i]; int slackToAdd = minSlack; if (extraSlack > 0) { slackToAdd++; extraSlack--; } rect.x = runningLength + xOffset; rect.width += slackToAdd; runningLength += rect.width; } } else { for (int i = lastTabIndex; i >= firstTabIndex; i--) { final Rectangle rect = rects[i]; int slackToAdd = minSlack; if (extraSlack > 0) { slackToAdd++; extraSlack--; } rect.x = runningLength + xOffset; rect.width += slackToAdd; runningLength += rect.width; } } if (isRightToLeft) { leftScrollTabRect.x = pane.tabAreaInsets.left; leftScrollTabRect.y = rects[firstTabIndex].y; leftScrollTabRect.height = rects[firstTabIndex].height; rightScrollTabRect.x = size.width - pane.tabAreaInsets.right - rightScrollTabRect.width; rightScrollTabRect.y = rects[lastTabIndex].y; rightScrollTabRect.height = rects[lastTabIndex].height; } else { rightScrollTabRect.x = pane.tabAreaInsets.left; rightScrollTabRect.y = rects[firstTabIndex].y; rightScrollTabRect.height = rects[firstTabIndex].height; leftScrollTabRect.x = size.width - pane.tabAreaInsets.right - rightScrollTabRect.width; leftScrollTabRect.y = rects[lastTabIndex].y; leftScrollTabRect.height = rects[lastTabIndex].height; if (needsLeftScrollTab()) { for (int i = lastTabIndex; i >= firstTabIndex; i--) { final Rectangle rect = rects[i]; rect.x -= FIXED_SCROLL_TAB_LENGTH; } } if (needsRightScrollTab()) { for (int i = lastTabIndex; i >= firstTabIndex; i--) { final Rectangle rect = rects[i]; rect.x += FIXED_SCROLL_TAB_LENGTH; } } } } private void stretchScrollingVerticalRun(final Rectangle[] rects, final Dimension size) { final int totalTabs = getTotal(); final int firstTabIndex = getIndex(0); final int lastTabIndex = getIndex(totalTabs - 1); int totalRunLength = 0; for (int i = firstTabIndex; i <= lastTabIndex; i++) { totalRunLength += rects[i].height; } int slack = size.height - totalRunLength - pane.tabAreaInsets.top - pane.tabAreaInsets.bottom; if (needsLeftScrollTab()) { slack -= FIXED_SCROLL_TAB_LENGTH; } if (needsRightScrollTab()) { slack -= FIXED_SCROLL_TAB_LENGTH; } final int minSlack = (int)((float)(slack) / (float)(totalTabs)); int extraSlack = slack - (minSlack * totalTabs); int runningLength = 0; final int yOffset = pane.tabAreaInsets.top + (needsLeftScrollTab() ? FIXED_SCROLL_TAB_LENGTH : 0); for (int i = firstTabIndex; i <= lastTabIndex; i++) { final Rectangle rect = rects[i]; int slackToAdd = minSlack; if (extraSlack > 0) { slackToAdd++; extraSlack--; } rect.y = runningLength + yOffset; rect.height += slackToAdd; runningLength += rect.height; } leftScrollTabRect.x = rects[firstTabIndex].x; leftScrollTabRect.y = pane.tabAreaInsets.top; leftScrollTabRect.width = rects[firstTabIndex].width; rightScrollTabRect.x = rects[lastTabIndex].x; rightScrollTabRect.y = size.height - pane.tabAreaInsets.bottom - rightScrollTabRect.height; rightScrollTabRect.width = rects[lastTabIndex].width; } }