1 /*
2  * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.apple.laf;
27 
28 import java.awt.*;
29 
30 import javax.swing.SwingConstants;
31 
32 class AquaTabbedPaneTabState {
33     static final int FIXED_SCROLL_TAB_LENGTH = 27;
34 
35     protected final Rectangle leftScrollTabRect = new Rectangle();
36     protected final Rectangle rightScrollTabRect = new Rectangle();
37 
38     protected int numberOfVisibleTabs = 0;
39     protected int visibleTabList[] = new int[10];
40     protected int lastLeftmostTab;
41     protected int lastReturnAt;
42 
43     private boolean needsScrollers;
44     private boolean hasMoreLeftTabs;
45     private boolean hasMoreRightTabs;
46 
47     private final AquaTabbedPaneUI pane;
48 
AquaTabbedPaneTabState(final AquaTabbedPaneUI pane)49     protected AquaTabbedPaneTabState(final AquaTabbedPaneUI pane) {
50         this.pane = pane;
51     }
52 
getIndex(final int i)53     protected int getIndex(final int i) {
54         if (i >= visibleTabList.length) return Integer.MIN_VALUE;
55         return visibleTabList[i];
56     }
57 
init(final int tabCount)58     protected void init(final int tabCount) {
59         if (tabCount < 1) needsScrollers = false;
60         if (tabCount == visibleTabList.length) return;
61         final int[] tempVisibleTabs = new int[tabCount];
62         System.arraycopy(visibleTabList, 0, tempVisibleTabs, 0, Math.min(visibleTabList.length, tabCount));
63         visibleTabList = tempVisibleTabs;
64     }
65 
getTotal()66     int getTotal() {
67         return numberOfVisibleTabs;
68     }
69 
needsScrollTabs()70     boolean needsScrollTabs() {
71         return needsScrollers;
72     }
73 
setNeedsScrollers(final boolean needsScrollers)74     void setNeedsScrollers(final boolean needsScrollers) {
75         this.needsScrollers = needsScrollers;
76     }
77 
needsLeftScrollTab()78     boolean needsLeftScrollTab() {
79         return hasMoreLeftTabs;
80     }
81 
needsRightScrollTab()82     boolean needsRightScrollTab() {
83         return hasMoreRightTabs;
84     }
85 
getLeftScrollTabRect()86     Rectangle getLeftScrollTabRect() {
87         return leftScrollTabRect;
88     }
89 
getRightScrollTabRect()90     Rectangle getRightScrollTabRect() {
91         return rightScrollTabRect;
92     }
93 
isBefore(final int i)94     boolean isBefore(final int i) {
95         if (numberOfVisibleTabs == 0) return true;
96         if (i < visibleTabList[0]) return true;
97         return false;
98     }
99 
isAfter(final int i)100     boolean isAfter(final int i) {
101         if (i > visibleTabList[numberOfVisibleTabs - 1]) return true;
102         return false;
103     }
104 
addToEnd(final int idToAdd, final int length)105     private void addToEnd(final int idToAdd, final int length) {
106         visibleTabList[length] = idToAdd;
107     }
108 
addToBeginning(final int idToAdd, final int length)109     private void addToBeginning(final int idToAdd, final int length) {
110         System.arraycopy(visibleTabList, 0, visibleTabList, 1, length);
111         visibleTabList[0] = idToAdd;
112     }
113 
114 
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)115     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) {
116         if (!needsScrollers) {
117             hasMoreLeftTabs = false;
118             hasMoreRightTabs = false;
119             return;
120         }
121 
122         // we don't fit, so we need to figure the space based on the size of the popup
123         // tab, then add the tabs, centering the selected tab as much as possible.
124 
125         // Tabs on TOP or BOTTOM or LEFT or RIGHT
126         // if top or bottom, width is hardocoded
127         // if left or right height should be hardcoded.
128         if (verticalTabRuns) {
129             rightScrollTabRect.height = FIXED_SCROLL_TAB_LENGTH;
130             leftScrollTabRect.height = FIXED_SCROLL_TAB_LENGTH;
131         } else {
132             rightScrollTabRect.width = FIXED_SCROLL_TAB_LENGTH;
133             leftScrollTabRect.width = FIXED_SCROLL_TAB_LENGTH;
134         }
135 
136         // we have all the tab rects, we just need to adjust the x coordinates
137         // and populate the visible list
138 
139         // sja fix what do we do if remaining width is <0??
140 
141         // we could try to center it based on width of tabs, but for now
142         // we try to center based on number of tabs on each side, putting the extra
143         // on the left (since the first right is the selected tab).
144         // if we have 0 selected we will just go right, and if we have
145 
146         // the logic here is start with the selected tab, and then fit
147         // in as many tabs as possible on each side until we don't fit any more.
148         // but if all we did was change selection then we need to try to keep the same
149         // tabs on screen so we don't get a jarring tab moving out from under the mouse
150         // effect.
151 
152         final boolean sizeChanged = returnAt != lastReturnAt;
153         // so if we stay the same, make right the first tab and say left done = true
154         if (pane.popupSelectionChanged || sizeChanged) {
155             pane.popupSelectionChanged = false;
156             lastLeftmostTab = -1;
157         }
158 
159         int right = selectedIndex;
160         int left = selectedIndex - 1;
161 
162         // if we had a good last leftmost tab then we set left to unused and
163         // start at that tab.
164         if (lastLeftmostTab >= 0) {
165             right = lastLeftmostTab;
166             left = -1;
167         } else if (selectedIndex < 0) {
168             // this is if there is none selected see radar 3138137
169             right = 0;
170             left = -1;
171         }
172 
173         int remainingSpace = returnAt - pane.tabAreaInsets.right - pane.tabAreaInsets.left - FIXED_SCROLL_TAB_LENGTH * 2;
174         int visibleCount = 0;
175 
176         final Rectangle firstRect = rects[right];
177         if ((verticalTabRuns ? firstRect.height : firstRect.width) > remainingSpace) {
178             // always show at least the selected one!
179             addToEnd(right, visibleCount);
180             if (verticalTabRuns) {
181                 firstRect.height = remainingSpace; // force it to fit!
182             } else {
183                 firstRect.width = remainingSpace; // force it to fit!
184             }
185             visibleCount++;
186         } else {
187             boolean rightDone = false;
188             boolean leftDone = false;
189 
190             // at least one if not more will fit
191             while ((visibleCount < tabCount) && !(rightDone && leftDone)) {
192                 if (!rightDone && right >= 0 && right < tabCount) {
193                     final Rectangle rightRect = rects[right];
194                     if ((verticalTabRuns ? rightRect.height : rightRect.width) > remainingSpace) {
195                         rightDone = true;
196                     } else {
197                         addToEnd(right, visibleCount);
198                         visibleCount++;
199                         remainingSpace -= (verticalTabRuns ? rightRect.height : rightRect.width);
200                         right++;
201                         continue; // this gives a bias to "paging forward", and "inching backward"
202                     }
203                 } else {
204                     rightDone = true;
205                 }
206 
207                 if (!leftDone && left >= 0 && left < tabCount) {
208                     final Rectangle leftRect = rects[left];
209                     if ((verticalTabRuns ? leftRect.height : leftRect.width) > remainingSpace) {
210                         leftDone = true;
211                     } else {
212                         addToBeginning(left, visibleCount);
213                         visibleCount++;
214                         remainingSpace -= (verticalTabRuns ? leftRect.height : leftRect.width);
215                         left--;
216                     }
217                 } else {
218                     leftDone = true;
219                 }
220             }
221         }
222 
223         if (visibleCount > visibleTabList.length) visibleCount = visibleTabList.length;
224 
225         hasMoreLeftTabs = visibleTabList[0] > 0;
226         hasMoreRightTabs = visibleTabList[visibleCount - 1] < visibleTabList.length - 1;
227 
228         numberOfVisibleTabs = visibleCount;
229         // add the scroll tab at the end;
230         lastLeftmostTab = getIndex(0);
231         lastReturnAt = returnAt;
232 
233         final int firstTabIndex = getIndex(0);
234         final int lastTabIndex = getIndex(visibleCount - 1);
235 
236         // move all "invisible" tabs beyond the edge of known space...
237         for (int i = 0; i < tabCount; i++) {
238             if (i < firstTabIndex || i > lastTabIndex) {
239                 final Rectangle rect = rects[i];
240                 rect.x = Short.MAX_VALUE;
241                 rect.y = Short.MAX_VALUE;
242             }
243         }
244     }
245 
246     protected void alignRectsRunFor(final Rectangle[] rects, final Dimension tabPaneSize, final int tabPlacement, final boolean isRightToLeft) {
247         final boolean isVertical = tabPlacement == SwingConstants.LEFT || tabPlacement == SwingConstants.RIGHT;
248 
249         if (isVertical) {
250             if (needsScrollers) {
251                 stretchScrollingVerticalRun(rects, tabPaneSize);
252             } else {
253                 centerVerticalRun(rects, tabPaneSize);
254             }
255         } else {
256             if (needsScrollers) {
257                 stretchScrollingHorizontalRun(rects, tabPaneSize, isRightToLeft);
258             } else {
259                 centerHorizontalRun(rects, tabPaneSize, isRightToLeft);
260             }
261         }
262     }
263 
264     private void centerHorizontalRun(final Rectangle[] rects, final Dimension size, final boolean isRightToLeft) {
265         int totalLength = 0;
266         for (final Rectangle element : rects) {
267             totalLength += element.width;
268         }
269 
270         int x = size.width / 2 - totalLength / 2;
271 
272         if (isRightToLeft) {
273             for (final Rectangle rect : rects) {
274                 rect.x = x;
275                 x += rect.width;
276             }
277         } else {
278             for (int i = rects.length - 1; i >= 0; i--) {
279                 final Rectangle rect = rects[i];
280                 rect.x = x;
281                 x += rect.width;
282             }
283         }
284     }
285 
286     private void centerVerticalRun(final Rectangle[] rects, final Dimension size) {
287         int totalLength = 0;
288         for (final Rectangle element : rects) {
289             totalLength += element.height;
290         }
291 
292         int y = size.height / 2 - totalLength / 2;
293 
294         if (true) {
295             for (final Rectangle rect : rects) {
296                 rect.y = y;
297                 y += rect.height;
298             }
299         } else {
300             for (int i = rects.length - 1; i >= 0; i--) {
301                 final Rectangle rect = rects[i];
302                 rect.y = y;
303                 y += rect.height;
304             }
305         }
306     }
307 
stretchScrollingHorizontalRun(final Rectangle[] rects, final Dimension size, final boolean isRightToLeft)308     private void stretchScrollingHorizontalRun(final Rectangle[] rects, final Dimension size, final boolean isRightToLeft) {
309         final int totalTabs = getTotal();
310         final int firstTabIndex = getIndex(0);
311         final int lastTabIndex = getIndex(totalTabs - 1);
312 
313         int totalRunLength = 0;
314         for (int i = firstTabIndex; i <= lastTabIndex; i++) {
315             totalRunLength += rects[i].width;
316         }
317 
318         int slack = size.width - totalRunLength - pane.tabAreaInsets.left - pane.tabAreaInsets.right;
319         if (needsLeftScrollTab()) {
320             slack -= FIXED_SCROLL_TAB_LENGTH;
321         }
322         if (needsRightScrollTab()) {
323             slack -= FIXED_SCROLL_TAB_LENGTH;
324         }
325 
326         final int minSlack = (int)((float)(slack) / (float)(totalTabs));
327         int extraSlack = slack - (minSlack * totalTabs);
328         int runningLength = 0;
329         final int xOffset = pane.tabAreaInsets.left + (needsLeftScrollTab() ? FIXED_SCROLL_TAB_LENGTH : 0);
330 
331         if (isRightToLeft) {
332             for (int i = firstTabIndex; i <= lastTabIndex; i++) {
333                 final Rectangle rect = rects[i];
334                 int slackToAdd = minSlack;
335                 if (extraSlack > 0) {
336                     slackToAdd++;
337                     extraSlack--;
338                 }
339                 rect.x = runningLength + xOffset;
340                 rect.width += slackToAdd;
341                 runningLength += rect.width;
342             }
343         } else {
344             for (int i = lastTabIndex; i >= firstTabIndex; i--) {
345                 final Rectangle rect = rects[i];
346                 int slackToAdd = minSlack;
347                 if (extraSlack > 0) {
348                     slackToAdd++;
349                     extraSlack--;
350                 }
351                 rect.x = runningLength + xOffset;
352                 rect.width += slackToAdd;
353                 runningLength += rect.width;
354             }
355         }
356 
357         if (isRightToLeft) {
358             leftScrollTabRect.x = pane.tabAreaInsets.left;
359             leftScrollTabRect.y = rects[firstTabIndex].y;
360             leftScrollTabRect.height = rects[firstTabIndex].height;
361 
362             rightScrollTabRect.x = size.width - pane.tabAreaInsets.right - rightScrollTabRect.width;
363             rightScrollTabRect.y = rects[lastTabIndex].y;
364             rightScrollTabRect.height = rects[lastTabIndex].height;
365         } else {
366             rightScrollTabRect.x = pane.tabAreaInsets.left;
367             rightScrollTabRect.y = rects[firstTabIndex].y;
368             rightScrollTabRect.height = rects[firstTabIndex].height;
369 
370             leftScrollTabRect.x = size.width - pane.tabAreaInsets.right - rightScrollTabRect.width;
371             leftScrollTabRect.y = rects[lastTabIndex].y;
372             leftScrollTabRect.height = rects[lastTabIndex].height;
373 
374             if (needsLeftScrollTab()) {
375                 for (int i = lastTabIndex; i >= firstTabIndex; i--) {
376                     final Rectangle rect = rects[i];
377                     rect.x -= FIXED_SCROLL_TAB_LENGTH;
378                 }
379             }
380 
381             if (needsRightScrollTab()) {
382                 for (int i = lastTabIndex; i >= firstTabIndex; i--) {
383                     final Rectangle rect = rects[i];
384                     rect.x += FIXED_SCROLL_TAB_LENGTH;
385                 }
386             }
387         }
388     }
389 
stretchScrollingVerticalRun(final Rectangle[] rects, final Dimension size)390     private void stretchScrollingVerticalRun(final Rectangle[] rects, final Dimension size) {
391         final int totalTabs = getTotal();
392         final int firstTabIndex = getIndex(0);
393         final int lastTabIndex = getIndex(totalTabs - 1);
394 
395         int totalRunLength = 0;
396         for (int i = firstTabIndex; i <= lastTabIndex; i++) {
397             totalRunLength += rects[i].height;
398         }
399 
400         int slack = size.height - totalRunLength - pane.tabAreaInsets.top - pane.tabAreaInsets.bottom;
401         if (needsLeftScrollTab()) {
402             slack -= FIXED_SCROLL_TAB_LENGTH;
403         }
404         if (needsRightScrollTab()) {
405             slack -= FIXED_SCROLL_TAB_LENGTH;
406         }
407 
408         final int minSlack = (int)((float)(slack) / (float)(totalTabs));
409         int extraSlack = slack - (minSlack * totalTabs);
410         int runningLength = 0;
411         final int yOffset = pane.tabAreaInsets.top + (needsLeftScrollTab() ? FIXED_SCROLL_TAB_LENGTH : 0);
412 
413         for (int i = firstTabIndex; i <= lastTabIndex; i++) {
414             final Rectangle rect = rects[i];
415             int slackToAdd = minSlack;
416             if (extraSlack > 0) {
417                 slackToAdd++;
418                 extraSlack--;
419             }
420             rect.y = runningLength + yOffset;
421             rect.height += slackToAdd;
422             runningLength += rect.height;
423         }
424 
425         leftScrollTabRect.x = rects[firstTabIndex].x;
426         leftScrollTabRect.y = pane.tabAreaInsets.top;
427         leftScrollTabRect.width = rects[firstTabIndex].width;
428 
429         rightScrollTabRect.x = rects[lastTabIndex].x;
430         rightScrollTabRect.y = size.height - pane.tabAreaInsets.bottom - rightScrollTabRect.height;
431         rightScrollTabRect.width = rects[lastTabIndex].width;
432     }
433 }
434