/*
* Copyright (c) 2011, 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;
}
}