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 import java.awt.event.*; 30 import java.beans.*; 31 import java.util.*; 32 33 import javax.swing.*; 34 import javax.swing.Timer; 35 import javax.swing.event.*; 36 import javax.swing.plaf.*; 37 38 import apple.laf.*; 39 import apple.laf.JRSUIConstants.*; 40 import apple.laf.JRSUIState.ScrollBarState; 41 42 import com.apple.laf.AquaUtils.RecyclableSingleton; 43 44 public class AquaScrollBarUI extends ScrollBarUI { 45 private static final int kInitialDelay = 300; 46 private static final int kNormalDelay = 100; 47 48 // when we make small and mini scrollbars, this will no longer be a constant 49 static final int MIN_ARROW_COLLAPSE_SIZE = 64; 50 51 // tracking state 52 protected boolean fIsDragging; 53 protected Timer fScrollTimer; 54 protected ScrollListener fScrollListener; 55 protected TrackListener fTrackListener; 56 protected Hit fTrackHighlight = Hit.NONE; 57 protected Hit fMousePart = Hit.NONE; // Which arrow (if any) we moused pressed down in (used by arrow drag tracking) 58 59 protected JScrollBar fScrollBar; 60 protected ModelListener fModelListener; 61 protected PropertyChangeListener fPropertyChangeListener; 62 63 protected final AquaPainter<ScrollBarState> painter = AquaPainter.create(JRSUIStateFactory.getScrollBar()); 64 65 // Create PLAF createUI(final JComponent c)66 public static ComponentUI createUI(final JComponent c) { 67 return new AquaScrollBarUI(); 68 } 69 AquaScrollBarUI()70 public AquaScrollBarUI() { } 71 installUI(final JComponent c)72 public void installUI(final JComponent c) { 73 fScrollBar = (JScrollBar)c; 74 installListeners(); 75 configureScrollBarColors(); 76 } 77 uninstallUI(final JComponent c)78 public void uninstallUI(final JComponent c) { 79 uninstallListeners(); 80 fScrollBar = null; 81 } 82 configureScrollBarColors()83 protected void configureScrollBarColors() { 84 LookAndFeel.installColors(fScrollBar, "ScrollBar.background", "ScrollBar.foreground"); 85 } 86 createTrackListener()87 protected TrackListener createTrackListener() { 88 return new TrackListener(); 89 } 90 createScrollListener()91 protected ScrollListener createScrollListener() { 92 return new ScrollListener(); 93 } 94 installListeners()95 protected void installListeners() { 96 fTrackListener = createTrackListener(); 97 fModelListener = createModelListener(); 98 fPropertyChangeListener = createPropertyChangeListener(); 99 fScrollBar.addMouseListener(fTrackListener); 100 fScrollBar.addMouseMotionListener(fTrackListener); 101 fScrollBar.getModel().addChangeListener(fModelListener); 102 fScrollBar.addPropertyChangeListener(fPropertyChangeListener); 103 fScrollListener = createScrollListener(); 104 fScrollTimer = new Timer(kNormalDelay, fScrollListener); 105 fScrollTimer.setInitialDelay(kInitialDelay); // default InitialDelay? 106 } 107 uninstallListeners()108 protected void uninstallListeners() { 109 fScrollTimer.stop(); 110 fScrollTimer = null; 111 fScrollBar.getModel().removeChangeListener(fModelListener); 112 fScrollBar.removeMouseListener(fTrackListener); 113 fScrollBar.removeMouseMotionListener(fTrackListener); 114 fScrollBar.removePropertyChangeListener(fPropertyChangeListener); 115 } 116 createPropertyChangeListener()117 protected PropertyChangeListener createPropertyChangeListener() { 118 return new PropertyChangeHandler(); 119 } 120 createModelListener()121 protected ModelListener createModelListener() { 122 return new ModelListener(); 123 } 124 syncState(final JComponent c)125 protected void syncState(final JComponent c) { 126 final ScrollBarState scrollBarState = painter.state; 127 scrollBarState.set(isHorizontal() ? Orientation.HORIZONTAL : Orientation.VERTICAL); 128 129 final float trackExtent = fScrollBar.getMaximum() - fScrollBar.getMinimum() - fScrollBar.getModel().getExtent(); 130 if (trackExtent <= 0.0f) { 131 scrollBarState.set(NothingToScroll.YES); 132 return; 133 } 134 135 final ScrollBarPart pressedPart = getPressedPart(); 136 scrollBarState.set(pressedPart); 137 scrollBarState.set(getState(c, pressedPart)); 138 scrollBarState.set(NothingToScroll.NO); 139 scrollBarState.setValue((fScrollBar.getValue() - fScrollBar.getMinimum()) / trackExtent); 140 scrollBarState.setThumbStart(getThumbStart()); 141 scrollBarState.setThumbPercent(getThumbPercent()); 142 scrollBarState.set(shouldShowArrows() ? ShowArrows.YES : ShowArrows.NO); 143 } 144 paint(final Graphics g, final JComponent c)145 public void paint(final Graphics g, final JComponent c) { 146 syncState(c); 147 painter.paint(g, c, 0, 0, fScrollBar.getWidth(), fScrollBar.getHeight()); 148 } 149 getState(final JComponent c, final ScrollBarPart pressedPart)150 protected State getState(final JComponent c, final ScrollBarPart pressedPart) { 151 if (!AquaFocusHandler.isActive(c)) return State.INACTIVE; 152 if (!c.isEnabled()) return State.INACTIVE; 153 if (pressedPart != ScrollBarPart.NONE) return State.PRESSED; 154 return State.ACTIVE; 155 } 156 157 private static final RecyclableSingleton<Map<Hit, ScrollBarPart>> hitToPressedPartMap = new RecyclableSingleton<Map<Hit,ScrollBarPart>>(){ 158 @Override 159 protected Map<Hit, ScrollBarPart> getInstance() { 160 final Map<Hit, ScrollBarPart> map = new HashMap<Hit, ScrollBarPart>(7); 161 map.put(ScrollBarHit.ARROW_MAX, ScrollBarPart.ARROW_MAX); 162 map.put(ScrollBarHit.ARROW_MIN, ScrollBarPart.ARROW_MIN); 163 map.put(ScrollBarHit.ARROW_MAX_INSIDE, ScrollBarPart.ARROW_MAX_INSIDE); 164 map.put(ScrollBarHit.ARROW_MIN_INSIDE, ScrollBarPart.ARROW_MIN_INSIDE); 165 map.put(ScrollBarHit.TRACK_MAX, ScrollBarPart.TRACK_MAX); 166 map.put(ScrollBarHit.TRACK_MIN, ScrollBarPart.TRACK_MIN); 167 map.put(ScrollBarHit.THUMB, ScrollBarPart.THUMB); 168 return map; 169 } 170 }; getPressedPart()171 protected ScrollBarPart getPressedPart() { 172 if (!fTrackListener.fInArrows || !fTrackListener.fStillInArrow) return ScrollBarPart.NONE; 173 final ScrollBarPart pressedPart = hitToPressedPartMap.get().get(fMousePart); 174 if (pressedPart == null) return ScrollBarPart.NONE; 175 return pressedPart; 176 } 177 shouldShowArrows()178 protected boolean shouldShowArrows() { 179 return MIN_ARROW_COLLAPSE_SIZE < (isHorizontal() ? fScrollBar.getWidth() : fScrollBar.getHeight()); 180 } 181 182 // Layout Methods 183 // Layout is controlled by the user in the Appearance Control Panel 184 // Theme will redraw correctly for the current layout layoutContainer(final Container fScrollBarContainer)185 public void layoutContainer(final Container fScrollBarContainer) { 186 fScrollBar.repaint(); 187 fScrollBar.revalidate(); 188 } 189 getTrackBounds()190 protected Rectangle getTrackBounds() { 191 return new Rectangle(0, 0, fScrollBar.getWidth(), fScrollBar.getHeight()); 192 } 193 getDragBounds()194 protected Rectangle getDragBounds() { 195 return new Rectangle(0, 0, fScrollBar.getWidth(), fScrollBar.getHeight()); 196 } 197 startTimer(final boolean initial)198 protected void startTimer(final boolean initial) { 199 fScrollTimer.setInitialDelay(initial ? kInitialDelay : kNormalDelay); // default InitialDelay? 200 fScrollTimer.start(); 201 } 202 scrollByBlock(final int direction)203 protected void scrollByBlock(final int direction) { 204 synchronized(fScrollBar) { 205 final int oldValue = fScrollBar.getValue(); 206 final int blockIncrement = fScrollBar.getBlockIncrement(direction); 207 final int delta = blockIncrement * ((direction > 0) ? +1 : -1); 208 209 fScrollBar.setValue(oldValue + delta); 210 fTrackHighlight = direction > 0 ? ScrollBarHit.TRACK_MAX : ScrollBarHit.TRACK_MIN; 211 fScrollBar.repaint(); 212 fScrollListener.setDirection(direction); 213 fScrollListener.setScrollByBlock(true); 214 } 215 } 216 scrollByUnit(final int direction)217 protected void scrollByUnit(final int direction) { 218 synchronized(fScrollBar) { 219 int delta = fScrollBar.getUnitIncrement(direction); 220 if (direction <= 0) delta = -delta; 221 222 fScrollBar.setValue(delta + fScrollBar.getValue()); 223 fScrollBar.repaint(); 224 fScrollListener.setDirection(direction); 225 fScrollListener.setScrollByBlock(false); 226 } 227 } 228 getPartHit(final int x, final int y)229 protected Hit getPartHit(final int x, final int y) { 230 syncState(fScrollBar); 231 return JRSUIUtils.HitDetection.getHitForPoint(painter.getControl(), 0, 0, fScrollBar.getWidth(), fScrollBar.getHeight(), x, y); 232 } 233 234 protected class PropertyChangeHandler implements PropertyChangeListener { propertyChange(final PropertyChangeEvent e)235 public void propertyChange(final PropertyChangeEvent e) { 236 final String propertyName = e.getPropertyName(); 237 238 if ("model".equals(propertyName)) { 239 final BoundedRangeModel oldModel = (BoundedRangeModel)e.getOldValue(); 240 final BoundedRangeModel newModel = (BoundedRangeModel)e.getNewValue(); 241 oldModel.removeChangeListener(fModelListener); 242 newModel.addChangeListener(fModelListener); 243 fScrollBar.repaint(); 244 fScrollBar.revalidate(); 245 } else if (AquaFocusHandler.FRAME_ACTIVE_PROPERTY.equals(propertyName)) { 246 fScrollBar.repaint(); 247 } 248 } 249 } 250 251 protected class ModelListener implements ChangeListener { stateChanged(final ChangeEvent e)252 public void stateChanged(final ChangeEvent e) { 253 layoutContainer(fScrollBar); 254 } 255 } 256 257 // Track mouse drags. 258 protected class TrackListener extends MouseAdapter implements MouseMotionListener { 259 protected transient int fCurrentMouseX, fCurrentMouseY; 260 protected transient boolean fInArrows; // are we currently tracking arrows? 261 protected transient boolean fStillInArrow = false; // Whether mouse is in an arrow during arrow tracking 262 protected transient boolean fStillInTrack = false; // Whether mouse is in the track during pageup/down tracking 263 protected transient int fFirstMouseX, fFirstMouseY, fFirstValue; // Values for getValueFromOffset 264 mouseReleased(final MouseEvent e)265 public void mouseReleased(final MouseEvent e) { 266 if (!fScrollBar.isEnabled()) return; 267 if (fInArrows) { 268 mouseReleasedInArrows(e); 269 } else { 270 mouseReleasedInTrack(e); 271 } 272 273 fInArrows = false; 274 fStillInArrow = false; 275 fStillInTrack = false; 276 277 fScrollBar.repaint(); 278 fScrollBar.revalidate(); 279 } 280 mousePressed(final MouseEvent e)281 public void mousePressed(final MouseEvent e) { 282 if (!fScrollBar.isEnabled()) return; 283 284 final Hit part = getPartHit(e.getX(), e.getY()); 285 fInArrows = HitUtil.isArrow(part); 286 if (fInArrows) { 287 mousePressedInArrows(e, part); 288 } else { 289 if (part == Hit.NONE) { 290 fTrackHighlight = Hit.NONE; 291 } else { 292 mousePressedInTrack(e, part); 293 } 294 } 295 } 296 mouseDragged(final MouseEvent e)297 public void mouseDragged(final MouseEvent e) { 298 if (!fScrollBar.isEnabled()) return; 299 300 if (fInArrows) { 301 mouseDraggedInArrows(e); 302 } else if (fIsDragging) { 303 mouseDraggedInTrack(e); 304 } else { 305 // In pageup/down zones 306 307 // check that thumb has not been scrolled under the mouse cursor 308 final Hit previousPart = getPartHit(fCurrentMouseX, fCurrentMouseY); 309 if (!HitUtil.isTrack(previousPart)) { 310 fStillInTrack = false; 311 } 312 313 fCurrentMouseX = e.getX(); 314 fCurrentMouseY = e.getY(); 315 316 final Hit part = getPartHit(e.getX(), e.getY()); 317 final boolean temp = HitUtil.isTrack(part); 318 if (temp == fStillInTrack) return; 319 320 fStillInTrack = temp; 321 if (!fStillInTrack) { 322 fScrollTimer.stop(); 323 } else { 324 fScrollListener.actionPerformed(new ActionEvent(fScrollTimer, 0, "")); 325 startTimer(false); 326 } 327 } 328 } 329 getValueFromOffset(final int xOffset, final int yOffset, final int firstValue)330 int getValueFromOffset(final int xOffset, final int yOffset, final int firstValue) { 331 final boolean isHoriz = isHorizontal(); 332 333 // find the amount of pixels we've moved x & y (we only care about one) 334 final int offsetWeCareAbout = isHoriz ? xOffset : yOffset; 335 336 // now based on that floating point percentage compute the real scroller value. 337 final int visibleAmt = fScrollBar.getVisibleAmount(); 338 final int max = fScrollBar.getMaximum(); 339 final int min = fScrollBar.getMinimum(); 340 final int extent = max - min; 341 342 // ask native to tell us what the new float that is a ratio of how much scrollable area 343 // we have moved (not the thumb area, just the scrollable). If the 344 // scroller goes 0-100 with a visible area of 20 we are getting a ratio of the 345 // remaining 80. 346 syncState(fScrollBar); 347 final double offsetChange = JRSUIUtils.ScrollBar.getNativeOffsetChange(painter.getControl(), 0, 0, fScrollBar.getWidth(), fScrollBar.getHeight(), offsetWeCareAbout, visibleAmt, extent); 348 349 // the scrollable area is the extent - visible amount; 350 final int scrollableArea = extent - visibleAmt; 351 352 final int changeByValue = (int)(offsetChange * scrollableArea); 353 int newValue = firstValue + changeByValue; 354 newValue = Math.max(min, newValue); 355 newValue = Math.min((max - visibleAmt), newValue); 356 return newValue; 357 } 358 359 /** 360 * Arrow Listeners 361 */ 362 // Because we are handling both mousePressed and Actions 363 // we need to make sure we don't fire under both conditions. 364 // (keyfocus on scrollbars causes action without mousePress mousePressedInArrows(final MouseEvent e, final Hit part)365 void mousePressedInArrows(final MouseEvent e, final Hit part) { 366 final int direction = HitUtil.isIncrement(part) ? 1 : -1; 367 368 fStillInArrow = true; 369 scrollByUnit(direction); 370 fScrollTimer.stop(); 371 fScrollListener.setDirection(direction); 372 fScrollListener.setScrollByBlock(false); 373 374 fMousePart = part; 375 startTimer(true); 376 } 377 mouseReleasedInArrows(final MouseEvent e)378 void mouseReleasedInArrows(final MouseEvent e) { 379 fScrollTimer.stop(); 380 fMousePart = Hit.NONE; 381 fScrollBar.setValueIsAdjusting(false); 382 } 383 mouseDraggedInArrows(final MouseEvent e)384 void mouseDraggedInArrows(final MouseEvent e) { 385 final Hit whichPart = getPartHit(e.getX(), e.getY()); 386 387 if ((fMousePart == whichPart) && fStillInArrow) return; // Nothing has changed, so return 388 389 if (fMousePart != whichPart && !HitUtil.isArrow(whichPart)) { 390 // The mouse is not over the arrow we mouse pressed in, so stop the timer and mark as 391 // not being in the arrow 392 fScrollTimer.stop(); 393 fStillInArrow = false; 394 fScrollBar.repaint(); 395 } else { 396 // We are in the arrow we mouse pressed down in originally, but the timer was stopped so we need 397 // to start it up again. 398 fMousePart = whichPart; 399 fScrollListener.setDirection(HitUtil.isIncrement(whichPart) ? 1 : -1); 400 fStillInArrow = true; 401 fScrollListener.actionPerformed(new ActionEvent(fScrollTimer, 0, "")); 402 startTimer(false); 403 } 404 405 fScrollBar.repaint(); 406 } 407 mouseReleasedInTrack(final MouseEvent e)408 void mouseReleasedInTrack(final MouseEvent e) { 409 if (fTrackHighlight != Hit.NONE) { 410 fScrollBar.repaint(); 411 } 412 413 fTrackHighlight = Hit.NONE; 414 fIsDragging = false; 415 fScrollTimer.stop(); 416 fScrollBar.setValueIsAdjusting(false); 417 } 418 419 /** 420 * Adjust the fScrollBars value based on the result of hitTestTrack 421 */ mousePressedInTrack(final MouseEvent e, final Hit part)422 void mousePressedInTrack(final MouseEvent e, final Hit part) { 423 fScrollBar.setValueIsAdjusting(true); 424 425 // If option-click, toggle scroll-to-here 426 boolean shouldScrollToHere = (part != ScrollBarHit.THUMB) && JRSUIUtils.ScrollBar.useScrollToClick(); 427 if (e.isAltDown()) shouldScrollToHere = !shouldScrollToHere; 428 429 // pretend the mouse was dragged from a point in the current thumb to the current mouse point in one big jump 430 if (shouldScrollToHere) { 431 final Point p = getScrollToHereStartPoint(e.getX(), e.getY()); 432 fFirstMouseX = p.x; 433 fFirstMouseY = p.y; 434 fFirstValue = fScrollBar.getValue(); 435 moveToMouse(e); 436 437 // OK, now we're in the thumb - any subsequent dragging should move it 438 fTrackHighlight = ScrollBarHit.THUMB; 439 fIsDragging = true; 440 return; 441 } 442 443 fCurrentMouseX = e.getX(); 444 fCurrentMouseY = e.getY(); 445 446 int direction = 0; 447 if (part == ScrollBarHit.TRACK_MIN) { 448 fTrackHighlight = ScrollBarHit.TRACK_MIN; 449 direction = -1; 450 } else if (part == ScrollBarHit.TRACK_MAX) { 451 fTrackHighlight = ScrollBarHit.TRACK_MAX; 452 direction = 1; 453 } else { 454 fFirstValue = fScrollBar.getValue(); 455 fFirstMouseX = fCurrentMouseX; 456 fFirstMouseY = fCurrentMouseY; 457 fTrackHighlight = ScrollBarHit.THUMB; 458 fIsDragging = true; 459 return; 460 } 461 462 fIsDragging = false; 463 fStillInTrack = true; 464 465 scrollByBlock(direction); 466 // Check the new location of the thumb 467 // stop scrolling if the thumb is under the mouse?? 468 469 final Hit newPart = getPartHit(fCurrentMouseX, fCurrentMouseY); 470 if (newPart == ScrollBarHit.TRACK_MIN || newPart == ScrollBarHit.TRACK_MAX) { 471 fScrollTimer.stop(); 472 fScrollListener.setDirection(((newPart == ScrollBarHit.TRACK_MAX) ? 1 : -1)); 473 fScrollListener.setScrollByBlock(true); 474 startTimer(true); 475 } 476 } 477 478 /** 479 * Set the models value to the position of the top/left 480 * of the thumb relative to the origin of the track. 481 */ mouseDraggedInTrack(final MouseEvent e)482 void mouseDraggedInTrack(final MouseEvent e) { 483 moveToMouse(e); 484 } 485 486 // For normal mouse dragging or click-to-here 487 // fCurrentMouseX, fCurrentMouseY, and fFirstValue must be set moveToMouse(final MouseEvent e)488 void moveToMouse(final MouseEvent e) { 489 fCurrentMouseX = e.getX(); 490 fCurrentMouseY = e.getY(); 491 492 final int oldValue = fScrollBar.getValue(); 493 final int newValue = getValueFromOffset(fCurrentMouseX - fFirstMouseX, fCurrentMouseY - fFirstMouseY, fFirstValue); 494 if (newValue == oldValue) return; 495 496 fScrollBar.setValue(newValue); 497 final Rectangle dirtyRect = getTrackBounds(); 498 fScrollBar.repaint(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height); 499 } 500 } 501 502 /** 503 * Listener for scrolling events initiated in the ScrollPane. 504 */ 505 protected class ScrollListener implements ActionListener { 506 boolean fUseBlockIncrement; 507 int fDirection = 1; 508 setDirection(final int direction)509 void setDirection(final int direction) { 510 this.fDirection = direction; 511 } 512 setScrollByBlock(final boolean block)513 void setScrollByBlock(final boolean block) { 514 this.fUseBlockIncrement = block; 515 } 516 actionPerformed(final ActionEvent e)517 public void actionPerformed(final ActionEvent e) { 518 if (fUseBlockIncrement) { 519 Hit newPart = getPartHit(fTrackListener.fCurrentMouseX, fTrackListener.fCurrentMouseY); 520 521 if (newPart == ScrollBarHit.TRACK_MIN || newPart == ScrollBarHit.TRACK_MAX) { 522 final int newDirection = (newPart == ScrollBarHit.TRACK_MAX ? 1 : -1); 523 if (fDirection != newDirection) { 524 fDirection = newDirection; 525 } 526 } 527 528 scrollByBlock(fDirection); 529 newPart = getPartHit(fTrackListener.fCurrentMouseX, fTrackListener.fCurrentMouseY); 530 531 if (newPart == ScrollBarHit.THUMB) { 532 ((Timer)e.getSource()).stop(); 533 } 534 } else { 535 scrollByUnit(fDirection); 536 } 537 538 if (fDirection > 0 && fScrollBar.getValue() + fScrollBar.getVisibleAmount() >= fScrollBar.getMaximum()) { 539 ((Timer)e.getSource()).stop(); 540 } else if (fDirection < 0 && fScrollBar.getValue() <= fScrollBar.getMinimum()) { 541 ((Timer)e.getSource()).stop(); 542 } 543 } 544 } 545 getThumbStart()546 float getThumbStart() { 547 final int max = fScrollBar.getMaximum(); 548 final int min = fScrollBar.getMinimum(); 549 final int extent = max - min; 550 if (extent <= 0) return 0f; 551 552 return (float)(fScrollBar.getValue() - fScrollBar.getMinimum()) / (float)extent; 553 } 554 getThumbPercent()555 float getThumbPercent() { 556 final int visible = fScrollBar.getVisibleAmount(); 557 final int max = fScrollBar.getMaximum(); 558 final int min = fScrollBar.getMinimum(); 559 final int extent = max - min; 560 if (extent <= 0) return 0f; 561 562 return (float)visible / (float)extent; 563 } 564 565 /** 566 * A scrollbar's preferred width is 16 by a reasonable size to hold 567 * the arrows 568 * 569 * @param c The JScrollBar that's delegating this method to us. 570 * @return The preferred size of a Basic JScrollBar. 571 * @see #getMaximumSize 572 * @see #getMinimumSize 573 */ getPreferredSize(final JComponent c)574 public Dimension getPreferredSize(final JComponent c) { 575 return isHorizontal() ? new Dimension(96, 15) : new Dimension(15, 96); 576 } 577 getMinimumSize(final JComponent c)578 public Dimension getMinimumSize(final JComponent c) { 579 return isHorizontal() ? new Dimension(54, 15) : new Dimension(15, 54); 580 } 581 getMaximumSize(final JComponent c)582 public Dimension getMaximumSize(final JComponent c) { 583 return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); 584 } 585 isHorizontal()586 boolean isHorizontal() { 587 return fScrollBar.getOrientation() == Adjustable.HORIZONTAL; 588 } 589 590 // only do scroll-to-here for page up and page down regions, when the option key is pressed 591 // This gets the point where the mouse would have been clicked in the current thumb 592 // so we can pretend the mouse was dragged to the current mouse point in one big jump getScrollToHereStartPoint(final int clickPosX, final int clickPosY)593 Point getScrollToHereStartPoint(final int clickPosX, final int clickPosY) { 594 // prepare the track rectangle and limit rectangle so we can do our calculations 595 final Rectangle limitRect = getDragBounds(); // GetThemeTrackDragRect 596 597 // determine the bounding rectangle for our thumb region 598 syncState(fScrollBar); 599 double[] rect = new double[4]; 600 JRSUIUtils.ScrollBar.getPartBounds(rect, painter.getControl(), 0, 0, fScrollBar.getWidth(), fScrollBar.getHeight(), ScrollBarPart.THUMB); 601 final Rectangle r = new Rectangle((int)rect[0], (int)rect[1], (int)rect[2], (int)rect[3]); 602 603 // figure out the scroll-to-here start location based on our orientation, the 604 // click position, and where it must be in the thumb to travel to the endpoints 605 // properly. 606 final Point startPoint = new Point(clickPosX, clickPosY); 607 608 if (isHorizontal()) { 609 final int halfWidth = r.width / 2; 610 final int limitRectRight = limitRect.x + limitRect.width; 611 612 if (clickPosX + halfWidth > limitRectRight) { 613 // Up against right edge 614 startPoint.x = r.x + r.width - limitRectRight - clickPosX - 1; 615 } else if (clickPosX - halfWidth < limitRect.x) { 616 // Up against left edge 617 startPoint.x = r.x + clickPosX - limitRect.x; 618 } else { 619 // Center the thumb 620 startPoint.x = r.x + halfWidth; 621 } 622 623 // Pretend clicked in middle of indicator vertically 624 startPoint.y = (r.y + r.height) / 2; 625 return startPoint; 626 } 627 628 final int halfHeight = r.height / 2; 629 final int limitRectBottom = limitRect.y + limitRect.height; 630 631 if (clickPosY + halfHeight > limitRectBottom) { 632 // Up against bottom edge 633 startPoint.y = r.y + r.height - limitRectBottom - clickPosY - 1; 634 } else if (clickPosY - halfHeight < limitRect.y) { 635 // Up against top edge 636 startPoint.y = r.y + clickPosY - limitRect.y; 637 } else { 638 // Center the thumb 639 startPoint.y = r.y + halfHeight; 640 } 641 642 // Pretend clicked in middle of indicator horizontally 643 startPoint.x = (r.x + r.width) / 2; 644 645 return startPoint; 646 } 647 648 static class HitUtil { isIncrement(final Hit hit)649 static boolean isIncrement(final Hit hit) { 650 return (hit == ScrollBarHit.ARROW_MAX) || (hit == ScrollBarHit.ARROW_MAX_INSIDE); 651 } 652 isDecrement(final Hit hit)653 static boolean isDecrement(final Hit hit) { 654 return (hit == ScrollBarHit.ARROW_MIN) || (hit == ScrollBarHit.ARROW_MIN_INSIDE); 655 } 656 isArrow(final Hit hit)657 static boolean isArrow(final Hit hit) { 658 return isIncrement(hit) || isDecrement(hit); 659 } 660 isTrack(final Hit hit)661 static boolean isTrack(final Hit hit) { 662 return (hit == ScrollBarHit.TRACK_MAX) || (hit == ScrollBarHit.TRACK_MIN); 663 } 664 } 665 } 666