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