| /* | |
| * Copyright (c) 2021, 2024, 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.Adjustable; | |
| import java.awt.Component; | |
| import java.awt.Container; | |
| import java.awt.Dimension; | |
| import java.awt.Graphics; | |
| import java.awt.Insets; | |
| import java.awt.Point; | |
| import java.awt.Rectangle; | |
| import java.awt.event.ActionEvent; | |
| import java.awt.event.ActionListener; | |
| import java.awt.event.MouseAdapter; | |
| import java.awt.event.MouseEvent; | |
| import java.awt.event.MouseMotionListener; | |
| import java.beans.PropertyChangeEvent; | |
| import java.beans.PropertyChangeListener; | |
| import java.util.HashMap; | |
| import java.util.Map; | |
| import javax.swing.BoundedRangeModel; | |
| import javax.swing.JComponent; | |
| import javax.swing.JFrame; | |
| import javax.swing.JScrollBar; | |
| import javax.swing.LookAndFeel; | |
| import javax.swing.Timer; | |
| import javax.swing.event.ChangeEvent; | |
| import javax.swing.event.ChangeListener; | |
| import javax.swing.plaf.ComponentUI; | |
| import javax.swing.plaf.ScrollBarUI; | |
| import apple.laf.JRSUIStateFactory; | |
| import apple.laf.JRSUIConstants.Hit; | |
| import apple.laf.JRSUIConstants.NothingToScroll; | |
| import apple.laf.JRSUIConstants.Orientation; | |
| import apple.laf.JRSUIConstants.ScrollBarHit; | |
| import apple.laf.JRSUIConstants.ScrollBarPart; | |
| import apple.laf.JRSUIConstants.ShowArrows; | |
| import apple.laf.JRSUIConstants.State; | |
| import apple.laf.JRSUIState.ScrollBarState; | |
| import apple.laf.JRSUIUtils; | |
| import com.apple.laf.AquaUtils.RecyclableSingleton; | |
| public class AquaScrollBarUI extends ScrollBarUI { | |
| private static final int kInitialDelay = 300; | |
| private static final int kNormalDelay = 100; | |
| // when we make small and mini scrollbars, this will no longer be a constant | |
| static final int MIN_ARROW_COLLAPSE_SIZE = 64; | |
| // tracking state | |
| protected boolean fIsDragging; | |
| protected Timer fScrollTimer; | |
| protected ScrollListener fScrollListener; | |
| protected TrackListener fTrackListener; | |
| protected Hit fTrackHighlight = Hit.NONE; | |
| protected Hit fMousePart = Hit.NONE; // Which arrow (if any) we moused pressed down in (used by arrow drag tracking) | |
| protected JScrollBar fScrollBar; | |
| protected ModelListener fModelListener; | |
| protected PropertyChangeListener fPropertyChangeListener; | |
| protected final AquaPainter<ScrollBarState> painter = AquaPainter.create(JRSUIStateFactory.getScrollBar()); | |
| // Create PLAF | |
| public static ComponentUI createUI(final JComponent c) { | |
| return new AquaScrollBarUI(); | |
| } | |
| public AquaScrollBarUI() { } | |
| public void installUI(final JComponent c) { | |
| fScrollBar = (JScrollBar)c; | |
| installListeners(); | |
| configureScrollBarColors(); | |
| } | |
| public void uninstallUI(final JComponent c) { | |
| uninstallListeners(); | |
| fScrollBar = null; | |
| } | |
| protected void configureScrollBarColors() { | |
| LookAndFeel.installColors(fScrollBar, "ScrollBar.background", "ScrollBar.foreground"); | |
| } | |
| protected TrackListener createTrackListener() { | |
| return new TrackListener(); | |
| } | |
| protected ScrollListener createScrollListener() { | |
| return new ScrollListener(); | |
| } | |
| protected void installListeners() { | |
| fTrackListener = createTrackListener(); | |
| fModelListener = createModelListener(); | |
| fPropertyChangeListener = createPropertyChangeListener(); | |
| fScrollBar.addMouseListener(fTrackListener); | |
| fScrollBar.addMouseMotionListener(fTrackListener); | |
| fScrollBar.getModel().addChangeListener(fModelListener); | |
| fScrollBar.addPropertyChangeListener(fPropertyChangeListener); | |
| fScrollListener = createScrollListener(); | |
| fScrollTimer = new Timer(kNormalDelay, fScrollListener); | |
| fScrollTimer.setInitialDelay(kInitialDelay); // default InitialDelay? | |
| } | |
| protected void uninstallListeners() { | |
| fScrollTimer.stop(); | |
| fScrollTimer = null; | |
| fScrollBar.getModel().removeChangeListener(fModelListener); | |
| fScrollBar.removeMouseListener(fTrackListener); | |
| fScrollBar.removeMouseMotionListener(fTrackListener); | |
| fScrollBar.removePropertyChangeListener(fPropertyChangeListener); | |
| } | |
| protected PropertyChangeListener createPropertyChangeListener() { | |
| return new PropertyChangeHandler(); | |
| } | |
| protected ModelListener createModelListener() { | |
| return new ModelListener(); | |
| } | |
| protected void syncState(final JComponent c) { | |
| final ScrollBarState scrollBarState = painter.state; | |
| scrollBarState.set(isHorizontal() ? Orientation.HORIZONTAL : Orientation.VERTICAL); | |
| final float trackExtent = fScrollBar.getMaximum() - fScrollBar.getMinimum() - fScrollBar.getModel().getExtent(); | |
| if (trackExtent <= 0.0f) { | |
| scrollBarState.set(NothingToScroll.YES); | |
| return; | |
| } | |
| final ScrollBarPart pressedPart = getPressedPart(); | |
| scrollBarState.set(pressedPart); | |
| scrollBarState.set(getState(c, pressedPart)); | |
| scrollBarState.set(NothingToScroll.NO); | |
| scrollBarState.setValue((fScrollBar.getValue() - fScrollBar.getMinimum()) / trackExtent); | |
| scrollBarState.setThumbStart(getThumbStart()); | |
| scrollBarState.setThumbPercent(getThumbPercent()); | |
| scrollBarState.set(shouldShowArrows() ? ShowArrows.YES : ShowArrows.NO); | |
| } | |
| public void paint(final Graphics g, final JComponent c) { | |
| syncState(c); | |
| Rectangle trackBounds = getTrackBounds(); | |
| painter.paint(g, c, trackBounds.x, trackBounds.y, trackBounds.width, trackBounds.height); | |
| } | |
| protected State getState(final JComponent c, final ScrollBarPart pressedPart) { | |
| if (!AquaFocusHandler.isActive(c)) return State.INACTIVE; | |
| if (!c.isEnabled()) return State.INACTIVE; | |
| if (pressedPart != ScrollBarPart.NONE) return State.PRESSED; | |
| return State.ACTIVE; | |
| } | |
| private static final RecyclableSingleton<Map<Hit, ScrollBarPart>> hitToPressedPartMap = new RecyclableSingleton<Map<Hit,ScrollBarPart>>(){ | |
| @Override | |
| protected Map<Hit, ScrollBarPart> getInstance() { | |
| final Map<Hit, ScrollBarPart> map = new HashMap<Hit, ScrollBarPart>(7); | |
| map.put(ScrollBarHit.ARROW_MAX, ScrollBarPart.ARROW_MAX); | |
| map.put(ScrollBarHit.ARROW_MIN, ScrollBarPart.ARROW_MIN); | |
| map.put(ScrollBarHit.ARROW_MAX_INSIDE, ScrollBarPart.ARROW_MAX_INSIDE); | |
| map.put(ScrollBarHit.ARROW_MIN_INSIDE, ScrollBarPart.ARROW_MIN_INSIDE); | |
| map.put(ScrollBarHit.TRACK_MAX, ScrollBarPart.TRACK_MAX); | |
| map.put(ScrollBarHit.TRACK_MIN, ScrollBarPart.TRACK_MIN); | |
| map.put(ScrollBarHit.THUMB, ScrollBarPart.THUMB); | |
| return map; | |
| } | |
| }; | |
| protected ScrollBarPart getPressedPart() { | |
| if (!fTrackListener.fInArrows || !fTrackListener.fStillInArrow) return ScrollBarPart.NONE; | |
| final ScrollBarPart pressedPart = hitToPressedPartMap.get().get(fMousePart); | |
| if (pressedPart == null) return ScrollBarPart.NONE; | |
| return pressedPart; | |
| } | |
| protected boolean shouldShowArrows() { | |
| return MIN_ARROW_COLLAPSE_SIZE < (isHorizontal() ? fScrollBar.getWidth() : fScrollBar.getHeight()); | |
| } | |
| // Layout Methods | |
| // Layout is controlled by the user in the Appearance Control Panel | |
| // Theme will redraw correctly for the current layout | |
| public void layoutContainer(final Container fScrollBarContainer) { | |
| fScrollBar.repaint(); | |
| fScrollBar.revalidate(); | |
| } | |
| protected Rectangle getTrackBounds() { | |
| Insets insets = fScrollBar.getInsets(); | |
| return new Rectangle(insets.left, insets.top, | |
| fScrollBar.getWidth() - (insets.left + insets.right), | |
| fScrollBar.getHeight() - (insets.top + insets.bottom)); | |
| } | |
| protected Rectangle getDragBounds() { | |
| Insets insets = fScrollBar.getInsets(); | |
| return new Rectangle(insets.left, insets.top, | |
| fScrollBar.getWidth() - (insets.left + insets.right), | |
| fScrollBar.getHeight() - (insets.top + insets.bottom)); | |
| } | |
| protected void startTimer(final boolean initial) { | |
| fScrollTimer.setInitialDelay(initial ? kInitialDelay : kNormalDelay); // default InitialDelay? | |
| fScrollTimer.start(); | |
| } | |
| protected void scrollByBlock(final int direction) { | |
| synchronized(fScrollBar) { | |
| final int oldValue = fScrollBar.getValue(); | |
| final int blockIncrement = fScrollBar.getBlockIncrement(direction); | |
| final int delta = blockIncrement * ((direction > 0) ? +1 : -1); | |
| fScrollBar.setValue(oldValue + delta); | |
| fTrackHighlight = direction > 0 ? ScrollBarHit.TRACK_MAX : ScrollBarHit.TRACK_MIN; | |
| fScrollBar.repaint(); | |
| fScrollListener.setDirection(direction); | |
| fScrollListener.setScrollByBlock(true); | |
| } | |
| } | |
| protected void scrollByUnit(final int direction) { | |
| synchronized(fScrollBar) { | |
| int delta = fScrollBar.getUnitIncrement(direction); | |
| if (direction <= 0) delta = -delta; | |
| fScrollBar.setValue(delta + fScrollBar.getValue()); | |
| fScrollBar.repaint(); | |
| fScrollListener.setDirection(direction); | |
| fScrollListener.setScrollByBlock(false); | |
| } | |
| } | |
| protected Hit getPartHit(final int x, final int y) { | |
| syncState(fScrollBar); | |
| Insets insets = fScrollBar.getInsets(); | |
| return JRSUIUtils.HitDetection.getHitForPoint(painter.getControl(), insets.left, insets.top, | |
| fScrollBar.getWidth() - (insets.left + insets.right), | |
| fScrollBar.getHeight() - (insets.top + insets.bottom), x, y); | |
| } | |
| protected class PropertyChangeHandler implements PropertyChangeListener { | |
| public void propertyChange(final PropertyChangeEvent e) { | |
| final String propertyName = e.getPropertyName(); | |
| if ("model".equals(propertyName)) { | |
| final BoundedRangeModel oldModel = (BoundedRangeModel)e.getOldValue(); | |
| final BoundedRangeModel newModel = (BoundedRangeModel)e.getNewValue(); | |
| oldModel.removeChangeListener(fModelListener); | |
| newModel.addChangeListener(fModelListener); | |
| fScrollBar.repaint(); | |
| fScrollBar.revalidate(); | |
| } else if (AquaFocusHandler.FRAME_ACTIVE_PROPERTY.equals(propertyName)) { | |
| fScrollBar.repaint(); | |
| } | |
| } | |
| } | |
| protected class ModelListener implements ChangeListener { | |
| public void stateChanged(final ChangeEvent e) { | |
| layoutContainer(fScrollBar); | |
| } | |
| } | |
| // Track mouse drags. | |
| protected class TrackListener extends MouseAdapter implements MouseMotionListener { | |
| protected transient int fCurrentMouseX, fCurrentMouseY; | |
| protected transient boolean fInArrows; // are we currently tracking arrows? | |
| protected transient boolean fStillInArrow = false; // Whether mouse is in an arrow during arrow tracking | |
| protected transient boolean fStillInTrack = false; // Whether mouse is in the track during pageup/down tracking | |
| protected transient int fFirstMouseX, fFirstMouseY, fFirstValue; // Values for getValueFromOffset | |
| public void mouseReleased(final MouseEvent e) { | |
| if (!fScrollBar.isEnabled()) return; | |
| if (fInArrows) { | |
| mouseReleasedInArrows(e); | |
| } else { | |
| mouseReleasedInTrack(e); | |
| } | |
| fInArrows = false; | |
| fStillInArrow = false; | |
| fStillInTrack = false; | |
| fScrollBar.repaint(); | |
| fScrollBar.revalidate(); | |
| } | |
| public void mousePressed(final MouseEvent e) { | |
| if (!fScrollBar.isEnabled()) return; | |
| final Hit part = getPartHit(e.getX(), e.getY()); | |
| fInArrows = HitUtil.isArrow(part); | |
| if (fInArrows) { | |
| mousePressedInArrows(e, part); | |
| } else { | |
| if (part == Hit.NONE) { | |
| fTrackHighlight = Hit.NONE; | |
| } else { | |
| mousePressedInTrack(e, part); | |
| } | |
| } | |
| } | |
| public void mouseDragged(final MouseEvent e) { | |
| if (!fScrollBar.isEnabled()) return; | |
| if (fInArrows) { | |
| mouseDraggedInArrows(e); | |
| } else if (fIsDragging) { | |
| mouseDraggedInTrack(e); | |
| } else { | |
| // In pageup/down zones | |
| // check that thumb has not been scrolled under the mouse cursor | |
| final Hit previousPart = getPartHit(fCurrentMouseX, fCurrentMouseY); | |
| if (!HitUtil.isTrack(previousPart)) { | |
| fStillInTrack = false; | |
| } | |
| fCurrentMouseX = e.getX(); | |
| fCurrentMouseY = e.getY(); | |
| final Hit part = getPartHit(e.getX(), e.getY()); | |
| final boolean temp = HitUtil.isTrack(part); | |
| if (temp == fStillInTrack) return; | |
| fStillInTrack = temp; | |
| if (!fStillInTrack) { | |
| fScrollTimer.stop(); | |
| } else { | |
| fScrollListener.actionPerformed(new ActionEvent(fScrollTimer, 0, "")); | |
| startTimer(false); | |
| } | |
| } | |
| } | |
| int getValueFromOffset(final int xOffset, final int yOffset, final int firstValue) { | |
| final boolean isHoriz = isHorizontal(); | |
| // find the amount of pixels we've moved x & y (we only care about one) | |
| final int offsetWeCareAbout = isHoriz ? xOffset : yOffset; | |
| // now based on that floating point percentage compute the real scroller value. | |
| final int visibleAmt = fScrollBar.getVisibleAmount(); | |
| final int max = fScrollBar.getMaximum(); | |
| final int min = fScrollBar.getMinimum(); | |
| final int extent = max - min; | |
| // ask native to tell us what the new float that is a ratio of how much scrollable area | |
| // we have moved (not the thumb area, just the scrollable). If the | |
| // scroller goes 0-100 with a visible area of 20 we are getting a ratio of the | |
| // remaining 80. | |
| syncState(fScrollBar); | |
| final Rectangle limitRect = getDragBounds(); // GetThemeTrackDragRect | |
| final double offsetChange = JRSUIUtils.ScrollBar.getNativeOffsetChange(painter.getControl(), | |
| limitRect.x, limitRect.y, limitRect.width, limitRect.height, offsetWeCareAbout, visibleAmt, extent); | |
| // the scrollable area is the extent - visible amount; | |
| final int scrollableArea = extent - visibleAmt; | |
| final int changeByValue = (int)(offsetChange * scrollableArea); | |
| int newValue = firstValue + changeByValue; | |
| newValue = Math.max(min, newValue); | |
| newValue = Math.min((max - visibleAmt), newValue); | |
| return newValue; | |
| } | |
| /** | |
| * Arrow Listeners | |
| */ | |
| // Because we are handling both mousePressed and Actions | |
| // we need to make sure we don't fire under both conditions. | |
| // (keyfocus on scrollbars causes action without mousePress | |
| void mousePressedInArrows(final MouseEvent e, final Hit part) { | |
| final int direction = HitUtil.isIncrement(part) ? 1 : -1; | |
| fStillInArrow = true; | |
| scrollByUnit(direction); | |
| fScrollTimer.stop(); | |
| fScrollListener.setDirection(direction); | |
| fScrollListener.setScrollByBlock(false); | |
| fMousePart = part; | |
| startTimer(true); | |
| } | |
| void mouseReleasedInArrows(final MouseEvent e) { | |
| fScrollTimer.stop(); | |
| fMousePart = Hit.NONE; | |
| fScrollBar.setValueIsAdjusting(false); | |
| } | |
| void mouseDraggedInArrows(final MouseEvent e) { | |
| final Hit whichPart = getPartHit(e.getX(), e.getY()); | |
| if ((fMousePart == whichPart) && fStillInArrow) return; // Nothing has changed, so return | |
| if (fMousePart != whichPart && !HitUtil.isArrow(whichPart)) { | |
| // The mouse is not over the arrow we mouse pressed in, so stop the timer and mark as | |
| // not being in the arrow | |
| fScrollTimer.stop(); | |
| fStillInArrow = false; | |
| fScrollBar.repaint(); | |
| } else { | |
| // We are in the arrow we mouse pressed down in originally, but the timer was stopped so we need | |
| // to start it up again. | |
| fMousePart = whichPart; | |
| fScrollListener.setDirection(HitUtil.isIncrement(whichPart) ? 1 : -1); | |
| fStillInArrow = true; | |
| fScrollListener.actionPerformed(new ActionEvent(fScrollTimer, 0, "")); | |
| startTimer(false); | |
| } | |
| fScrollBar.repaint(); | |
| } | |
| void mouseReleasedInTrack(final MouseEvent e) { | |
| if (fTrackHighlight != Hit.NONE) { | |
| fScrollBar.repaint(); | |
| } | |
| fTrackHighlight = Hit.NONE; | |
| fIsDragging = false; | |
| fScrollTimer.stop(); | |
| fScrollBar.setValueIsAdjusting(false); | |
| } | |
| /** | |
| * Adjust the fScrollBars value based on the result of hitTestTrack | |
| */ | |
| void mousePressedInTrack(final MouseEvent e, final Hit part) { | |
| fScrollBar.setValueIsAdjusting(true); | |
| // If option-click, toggle scroll-to-here | |
| boolean shouldScrollToHere = (part != ScrollBarHit.THUMB) && JRSUIUtils.ScrollBar.useScrollToClick(); | |
| if (e.isAltDown()) shouldScrollToHere = !shouldScrollToHere; | |
| // pretend the mouse was dragged from a point in the current thumb to the current mouse point in one big jump | |
| if (shouldScrollToHere) { | |
| final Point p = getScrollToHereStartPoint(e.getX(), e.getY()); | |
| fFirstMouseX = p.x; | |
| fFirstMouseY = p.y; | |
| fFirstValue = fScrollBar.getValue(); | |
| moveToMouse(e); | |
| // OK, now we're in the thumb - any subsequent dragging should move it | |
| fTrackHighlight = ScrollBarHit.THUMB; | |
| fIsDragging = true; | |
| return; | |
| } | |
| fCurrentMouseX = e.getX(); | |
| fCurrentMouseY = e.getY(); | |
| int direction = 0; | |
| if (part == ScrollBarHit.TRACK_MIN) { | |
| fTrackHighlight = ScrollBarHit.TRACK_MIN; | |
| direction = -1; | |
| } else if (part == ScrollBarHit.TRACK_MAX) { | |
| fTrackHighlight = ScrollBarHit.TRACK_MAX; | |
| direction = 1; | |
| } else { | |
| fFirstValue = fScrollBar.getValue(); | |
| fFirstMouseX = fCurrentMouseX; | |
| fFirstMouseY = fCurrentMouseY; | |
| fTrackHighlight = ScrollBarHit.THUMB; | |
| fIsDragging = true; | |
| return; | |
| } | |
| fIsDragging = false; | |
| fStillInTrack = true; | |
| scrollByBlock(direction); | |
| // Check the new location of the thumb | |
| // stop scrolling if the thumb is under the mouse?? | |
| final Hit newPart = getPartHit(fCurrentMouseX, fCurrentMouseY); | |
| if (newPart == ScrollBarHit.TRACK_MIN || newPart == ScrollBarHit.TRACK_MAX) { | |
| fScrollTimer.stop(); | |
| fScrollListener.setDirection(((newPart == ScrollBarHit.TRACK_MAX) ? 1 : -1)); | |
| fScrollListener.setScrollByBlock(true); | |
| startTimer(true); | |
| } | |
| } | |
| /** | |
| * Set the models value to the position of the top/left | |
| * of the thumb relative to the origin of the track. | |
| */ | |
| void mouseDraggedInTrack(final MouseEvent e) { | |
| moveToMouse(e); | |
| } | |
| // For normal mouse dragging or click-to-here | |
| // fCurrentMouseX, fCurrentMouseY, and fFirstValue must be set | |
| void moveToMouse(final MouseEvent e) { | |
| fCurrentMouseX = e.getX(); | |
| fCurrentMouseY = e.getY(); | |
| final int oldValue = fScrollBar.getValue(); | |
| final int newValue = getValueFromOffset(fCurrentMouseX - fFirstMouseX, fCurrentMouseY - fFirstMouseY, fFirstValue); | |
| if (newValue == oldValue) return; | |
| fScrollBar.setValue(newValue); | |
| final Rectangle dirtyRect = getTrackBounds(); | |
| fScrollBar.repaint(dirtyRect.x, dirtyRect.y, dirtyRect.width, dirtyRect.height); | |
| } | |
| } | |
| /** | |
| * Listener for scrolling events initiated in the ScrollPane. | |
| */ | |
| protected class ScrollListener implements ActionListener { | |
| boolean fUseBlockIncrement; | |
| int fDirection = 1; | |
| void setDirection(final int direction) { | |
| this.fDirection = direction; | |
| } | |
| void setScrollByBlock(final boolean block) { | |
| this.fUseBlockIncrement = block; | |
| } | |
| public void actionPerformed(final ActionEvent e) { | |
| Component parent = fScrollBar.getParent(); | |
| do { | |
| if (parent instanceof JFrame par) { | |
| if (!par.isEnabled()) { | |
| ((Timer)e.getSource()).stop(); | |
| fScrollBar.setValueIsAdjusting(false); | |
| return; | |
| } | |
| break; | |
| } else { | |
| if (parent != null) { | |
| parent = parent.getParent(); | |
| } | |
| } | |
| } while (parent != null); | |
| if (fUseBlockIncrement) { | |
| Hit newPart = getPartHit(fTrackListener.fCurrentMouseX, fTrackListener.fCurrentMouseY); | |
| if (newPart == ScrollBarHit.TRACK_MIN || newPart == ScrollBarHit.TRACK_MAX) { | |
| final int newDirection = (newPart == ScrollBarHit.TRACK_MAX ? 1 : -1); | |
| if (fDirection != newDirection) { | |
| fDirection = newDirection; | |
| } | |
| } | |
| scrollByBlock(fDirection); | |
| newPart = getPartHit(fTrackListener.fCurrentMouseX, fTrackListener.fCurrentMouseY); | |
| if (newPart == ScrollBarHit.THUMB) { | |
| ((Timer)e.getSource()).stop(); | |
| } | |
| } else { | |
| scrollByUnit(fDirection); | |
| } | |
| if (fDirection > 0 && fScrollBar.getValue() + fScrollBar.getVisibleAmount() >= fScrollBar.getMaximum()) { | |
| ((Timer)e.getSource()).stop(); | |
| } else if (fDirection < 0 && fScrollBar.getValue() <= fScrollBar.getMinimum()) { | |
| ((Timer)e.getSource()).stop(); | |
| } | |
| } | |
| } | |
| float getThumbStart() { | |
| final int max = fScrollBar.getMaximum(); | |
| final int min = fScrollBar.getMinimum(); | |
| final int extent = max - min; | |
| if (extent <= 0) return 0f; | |
| return (float)(fScrollBar.getValue() - fScrollBar.getMinimum()) / (float)extent; | |
| } | |
| float getThumbPercent() { | |
| final int visible = fScrollBar.getVisibleAmount(); | |
| final int max = fScrollBar.getMaximum(); | |
| final int min = fScrollBar.getMinimum(); | |
| final int extent = max - min; | |
| if (extent <= 0) return 0f; | |
| return (float)visible / (float)extent; | |
| } | |
| /** | |
| * A scrollbar's preferred width is 16 by a reasonable size to hold | |
| * the arrows | |
| * | |
| * @param c The JScrollBar that's delegating this method to us. | |
| * @return The preferred size of a Basic JScrollBar. | |
| * @see #getMaximumSize | |
| * @see #getMinimumSize | |
| */ | |
| public Dimension getPreferredSize(final JComponent c) { | |
| return isHorizontal() ? new Dimension(96, 15) : new Dimension(15, 96); | |
| } | |
| public Dimension getMinimumSize(final JComponent c) { | |
| return isHorizontal() ? new Dimension(54, 15) : new Dimension(15, 54); | |
| } | |
| public Dimension getMaximumSize(final JComponent c) { | |
| return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE); | |
| } | |
| boolean isHorizontal() { | |
| return fScrollBar.getOrientation() == Adjustable.HORIZONTAL; | |
| } | |
| // only do scroll-to-here for page up and page down regions, when the option key is pressed | |
| // This gets the point where the mouse would have been clicked in the current thumb | |
| // so we can pretend the mouse was dragged to the current mouse point in one big jump | |
| Point getScrollToHereStartPoint(final int clickPosX, final int clickPosY) { | |
| // prepare the track rectangle and limit rectangle so we can do our calculations | |
| final Rectangle limitRect = getDragBounds(); // GetThemeTrackDragRect | |
| // determine the bounding rectangle for our thumb region | |
| syncState(fScrollBar); | |
| double[] rect = new double[4]; | |
| JRSUIUtils.ScrollBar.getPartBounds(rect, painter.getControl(), limitRect.x, limitRect.y, limitRect.width, limitRect.height, ScrollBarPart.THUMB); | |
| final Rectangle r = new Rectangle((int)rect[0], (int)rect[1], (int)rect[2], (int)rect[3]); | |
| // figure out the scroll-to-here start location based on our orientation, the | |
| // click position, and where it must be in the thumb to travel to the endpoints | |
| // properly. | |
| final Point startPoint = new Point(clickPosX, clickPosY); | |
| if (isHorizontal()) { | |
| final int halfWidth = r.width / 2; | |
| final int limitRectRight = limitRect.x + limitRect.width; | |
| if (clickPosX + halfWidth > limitRectRight) { | |
| // Up against right edge | |
| startPoint.x = r.x + r.width - limitRectRight - clickPosX - 1; | |
| } else if (clickPosX - halfWidth < limitRect.x) { | |
| // Up against left edge | |
| startPoint.x = r.x + clickPosX - limitRect.x; | |
| } else { | |
| // Center the thumb | |
| startPoint.x = r.x + halfWidth; | |
| } | |
| // Pretend clicked in middle of indicator vertically | |
| startPoint.y = (r.y + r.height) / 2; | |
| return startPoint; | |
| } | |
| final int halfHeight = r.height / 2; | |
| final int limitRectBottom = limitRect.y + limitRect.height; | |
| if (clickPosY + halfHeight > limitRectBottom) { | |
| // Up against bottom edge | |
| startPoint.y = r.y + r.height - limitRectBottom - clickPosY - 1; | |
| } else if (clickPosY - halfHeight < limitRect.y) { | |
| // Up against top edge | |
| startPoint.y = r.y + clickPosY - limitRect.y; | |
| } else { | |
| // Center the thumb | |
| startPoint.y = r.y + halfHeight; | |
| } | |
| // Pretend clicked in middle of indicator horizontally | |
| startPoint.x = (r.x + r.width) / 2; | |
| return startPoint; | |
| } | |
| static class HitUtil { | |
| static boolean isIncrement(final Hit hit) { | |
| return (hit == ScrollBarHit.ARROW_MAX) || (hit == ScrollBarHit.ARROW_MAX_INSIDE); | |
| } | |
| static boolean isDecrement(final Hit hit) { | |
| return (hit == ScrollBarHit.ARROW_MIN) || (hit == ScrollBarHit.ARROW_MIN_INSIDE); | |
| } | |
| static boolean isArrow(final Hit hit) { | |
| return isIncrement(hit) || isDecrement(hit); | |
| } | |
| static boolean isTrack(final Hit hit) { | |
| return (hit == ScrollBarHit.TRACK_MAX) || (hit == ScrollBarHit.TRACK_MIN); | |
| } | |
| } | |
| } |