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 
  49     protected AquaTabbedPaneTabState(final AquaTabbedPaneUI pane) {
  50         this.pane = pane;
  51     }
  52 
  53     protected int getIndex(final int i) {
  54         if (i >= visibleTabList.length) return Integer.MIN_VALUE;
  55         return visibleTabList[i];
  56     }
  57 
  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 
  66     int getTotal() {
  67         return numberOfVisibleTabs;
  68     }
  69 
  70     boolean needsScrollTabs() {
  71         return needsScrollers;
  72     }
  73 
  74     void setNeedsScrollers(final boolean needsScrollers) {
  75         this.needsScrollers = needsScrollers;
  76     }
  77 
  78     boolean needsLeftScrollTab() {
  79         return hasMoreLeftTabs;
  80     }
  81 
  82     boolean needsRightScrollTab() {
  83         return hasMoreRightTabs;
  84     }
  85 
  86     Rectangle getLeftScrollTabRect() {
  87         return leftScrollTabRect;
  88     }
  89 
  90     Rectangle getRightScrollTabRect() {
  91         return rightScrollTabRect;
  92     }
  93 
  94     boolean isBefore(final int i) {
  95         if (numberOfVisibleTabs == 0) return true;
  96         if (i < visibleTabList[0]) return true;
  97         return false;
  98     }
  99 
 100     boolean isAfter(final int i) {
 101         if (i > visibleTabList[numberOfVisibleTabs - 1]) return true;
 102         return false;
 103     }
 104 
 105     private void addToEnd(final int idToAdd, final int length) {
 106         visibleTabList[length] = idToAdd;
 107     }
 108 
 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 
 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 
 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 
 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 }