/*
* Copyright (c) 2005-2016 Substance Kirill Grouchnikov. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* o Neither the name of Substance Kirill Grouchnikov nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.pushingpixels.substance.internal.ui;
import java.awt.Adjustable;
import java.awt.AlphaComposite;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.MouseEvent;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Map;
import java.util.Set;
import javax.swing.BoundedRangeModel;
import javax.swing.ButtonModel;
import javax.swing.DefaultButtonModel;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;
import javax.swing.plaf.ComponentUI;
import javax.swing.plaf.basic.BasicScrollBarUI;
import org.pushingpixels.lafwidget.LafWidget;
import org.pushingpixels.lafwidget.LafWidgetRepository;
import org.pushingpixels.lafwidget.LafWidgetUtilities;
import org.pushingpixels.lafwidget.animation.effects.GhostPaintingUtils;
import org.pushingpixels.lafwidget.contrib.intellij.UIUtil;
import org.pushingpixels.substance.api.ColorSchemeAssociationKind;
import org.pushingpixels.substance.api.ComponentState;
import org.pushingpixels.substance.api.SubstanceColorScheme;
import org.pushingpixels.substance.api.SubstanceLookAndFeel;
import org.pushingpixels.substance.api.painter.border.SubstanceBorderPainter;
import org.pushingpixels.substance.api.painter.fill.SubstanceFillPainter;
import org.pushingpixels.substance.api.shaper.SubstanceButtonShaper;
import org.pushingpixels.substance.internal.animation.StateTransitionTracker;
import org.pushingpixels.substance.internal.animation.TransitionAwareUI;
import org.pushingpixels.substance.internal.painter.BackgroundPaintingUtils;
import org.pushingpixels.substance.internal.utils.HashMapKey;
import org.pushingpixels.substance.internal.utils.LazyResettableHashMap;
import org.pushingpixels.substance.internal.utils.RolloverControlListener;
import org.pushingpixels.substance.internal.utils.SubstanceColorSchemeUtilities;
import org.pushingpixels.substance.internal.utils.SubstanceColorUtilities;
import org.pushingpixels.substance.internal.utils.SubstanceCoreUtilities;
import org.pushingpixels.substance.internal.utils.SubstanceImageCreator;
import org.pushingpixels.substance.internal.utils.SubstanceOutlineUtilities;
import org.pushingpixels.substance.internal.utils.SubstanceSizeUtils;
/**
* UI for scroll bars in <b>Substance </b> look and feel.
*
* @author Kirill Grouchnikov
*/
public class SubstanceScrollBarUI extends BasicScrollBarUI implements TransitionAwareUI {
/**
* Surrogate button model for tracking the thumb transitions.
*/
private ButtonModel thumbModel;
/**
* Stores computed images for vertical thumbs.
*/
private static LazyResettableHashMap<BufferedImage> thumbVerticalMap = new LazyResettableHashMap<BufferedImage>(
"SubstanceScrollBarUI.thumbVertical");
/**
* Stores computed images for horizontal thumbs.
*/
private static LazyResettableHashMap<BufferedImage> thumbHorizontalMap = new LazyResettableHashMap<BufferedImage>(
"SubstanceScrollBarUI.thumbHorizontal");
/**
* Listener for thumb transition animations.
*/
private RolloverControlListener substanceThumbRolloverListener;
protected StateTransitionTracker compositeStateTransitionTracker;
/**
* Property change listener.
*
*/
private PropertyChangeListener substancePropertyListener;
/**
* Scroll bar width.
*/
protected int scrollBarWidth;
/**
* Listener on adjustments made to the scrollbar model - this is for the
* overlay mode (see {@link SubstanceLookAndFeel#OVERLAY_PROPERTY} and
* repaiting both scrollbars with the viewport.
*
* @since version 3.2
*/
protected AdjustmentListener substanceAdjustmentListener;
private Set<LafWidget> lafWidgets;
private static int THUMB_DELTA = 2;
/*
* (non-Javadoc)
*
* @see javax.swing.plaf.ComponentUI#createUI(javax.swing.JComponent)
*/
public static ComponentUI createUI(JComponent comp) {
SubstanceCoreUtilities.testComponentCreationThreadingViolation(comp);
return new SubstanceScrollBarUI(comp);
}
/**
* Simple constructor.
*
* @param b
* Associated component.
*/
protected SubstanceScrollBarUI(JComponent b) {
super();
this.thumbModel = new DefaultButtonModel();
this.thumbModel.setArmed(false);
this.thumbModel.setSelected(false);
this.thumbModel.setPressed(false);
this.thumbModel.setRollover(false);
b.setOpaque(false);
}
@Override
public void installUI(JComponent c) {
this.lafWidgets = LafWidgetRepository.getRepository().getMatchingWidgets(c);
super.installUI(c);
for (LafWidget lafWidget : this.lafWidgets) {
lafWidget.installUI();
}
}
@Override
public void uninstallUI(JComponent c) {
for (LafWidget lafWidget : this.lafWidgets) {
lafWidget.uninstallUI();
}
super.uninstallUI(c);
}
/*
* (non-Javadoc)
*
* @see javax.swing.plaf.basic.BasicScrollBarUI#createDecreaseButton(int)
*/
@Override
protected JButton createDecreaseButton(int orientation) {
return null;
}
/*
* (non-Javadoc)
*
* @see javax.swing.plaf.basic.BasicScrollBarUI#createIncreaseButton(int)
*/
@Override
protected JButton createIncreaseButton(int orientation) {
return null;
}
/**
* Retrieves image for vertical thumb.
*
* @param thumbBounds
* Thumb bounding rectangle.
* @return Image for vertical thumb.
*/
private BufferedImage getThumbVertical(Rectangle thumbBounds) {
int width = Math.max(1, thumbBounds.width);
int delta = Math.max(0, (int) (0.4 * width));
if (delta % 2 == 1) {
delta--;
}
width -= delta;
int height = Math.max(1, thumbBounds.height);
StateTransitionTracker.ModelStateInfo modelStateInfo = this.compositeStateTransitionTracker
.getModelStateInfo();
ComponentState currState = modelStateInfo.getCurrModelState();
// enabled scroll bar is always painted as active
SubstanceColorScheme baseFillScheme = (currState != ComponentState.ENABLED)
? SubstanceColorSchemeUtilities.getColorScheme(this.scrollbar, currState)
: SubstanceColorSchemeUtilities.getActiveColorScheme(this.scrollbar, currState);
SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
.getColorScheme(this.scrollbar, ColorSchemeAssociationKind.BORDER, currState);
BufferedImage baseLayer = getThumbVertical(this.scrollbar, width, height, baseFillScheme,
baseBorderScheme);
Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
.getStateContributionMap();
if (currState.isDisabled() || (activeStates.size() == 1)) {
return baseLayer;
}
int scaleFactor = UIUtil.getScaleFactor();
BufferedImage result = SubstanceCoreUtilities.getBlankImage(
baseLayer.getWidth() / scaleFactor, baseLayer.getHeight() / scaleFactor);
Graphics2D g2d = result.createGraphics();
g2d.drawImage(baseLayer, 0, 0, baseLayer.getWidth() / scaleFactor,
baseLayer.getHeight() / scaleFactor, null);
for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
.entrySet()) {
ComponentState activeState = activeEntry.getKey();
if (activeState == modelStateInfo.getCurrModelState())
continue;
float contribution = activeEntry.getValue().getContribution();
if (contribution == 0.0f)
continue;
g2d.setComposite(AlphaComposite.SrcOver.derive(contribution));
SubstanceColorScheme fillScheme = (activeState != ComponentState.ENABLED)
? SubstanceColorSchemeUtilities.getColorScheme(this.scrollbar, activeState)
: SubstanceColorSchemeUtilities.getActiveColorScheme(this.scrollbar,
activeState);
SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
.getColorScheme(this.scrollbar, ColorSchemeAssociationKind.BORDER, activeState);
BufferedImage layer = getThumbVertical(this.scrollbar, width, height, fillScheme,
borderScheme);
g2d.drawImage(layer, 0, 0, layer.getWidth() / scaleFactor,
layer.getHeight() / scaleFactor, null);
}
g2d.dispose();
return result;
}
/**
* Retrieves image for vertical thumb.
*
* @param scrollBar
* Scroll bar.
* @param width
* Thumb width.
* @param height
* Thumb height.
* @param kind
* Color scheme kind.
* @param cyclePos
* Cycle position.
* @param scheme
* The first color scheme.
* @param scheme2
* The second color scheme.
* @param borderScheme
* The first border color scheme.
* @param borderScheme2
* The second border color scheme.
* @return Image for vertical thumb.
*/
private static BufferedImage getThumbVertical(JScrollBar scrollBar, int width, int height,
SubstanceColorScheme scheme, SubstanceColorScheme borderScheme) {
SubstanceFillPainter painter = SubstanceCoreUtilities.getFillPainter(scrollBar);
SubstanceButtonShaper shaper = SubstanceCoreUtilities.getButtonShaper(scrollBar);
SubstanceBorderPainter borderPainter = SubstanceCoreUtilities.getBorderPainter(scrollBar);
HashMapKey key = SubstanceCoreUtilities.getHashKey(width, height, scheme.getDisplayName(),
borderScheme.getDisplayName(), painter.getDisplayName(), shaper.getDisplayName(),
borderPainter.getDisplayName());
BufferedImage result = SubstanceScrollBarUI.thumbVerticalMap.get(key);
if (result == null) {
// System.out.println("Cache miss - computing");
// System.out.println("New image for vertical thumb");
float radius = width / 2;
float borderDelta = SubstanceSizeUtils.getBorderStrokeWidth() / 2.0f;
GeneralPath contour = SubstanceOutlineUtilities.getBaseOutline(height, width, radius,
null, borderDelta);
result = SubstanceCoreUtilities.getBlankImage(height, width);
painter.paintContourBackground(result.createGraphics(), scrollBar, height, width,
contour, false, scheme, true);
borderPainter.paintBorder(result.getGraphics(), scrollBar, height, width, contour, null,
borderScheme);
result = SubstanceImageCreator.getRotated(result, 3, false);
// System.out.println(key);
SubstanceScrollBarUI.thumbVerticalMap.put(key, result);
}
return result;
}
/**
* Retrieves image for horizontal thumb.
*
* @param thumbBounds
* Thumb bounding rectangle.
* @return Image for horizontal thumb.
*/
private BufferedImage getThumbHorizontal(Rectangle thumbBounds) {
int width = Math.max(1, thumbBounds.width);
int height = Math.max(1, thumbBounds.height);
int delta = Math.max(0, (int) (0.4 * height));
if (delta % 2 == 1) {
delta--;
}
height -= delta;
StateTransitionTracker.ModelStateInfo modelStateInfo = this.compositeStateTransitionTracker
.getModelStateInfo();
ComponentState currState = modelStateInfo.getCurrModelState();
SubstanceColorScheme baseFillScheme = (currState != ComponentState.ENABLED)
? SubstanceColorSchemeUtilities.getColorScheme(this.scrollbar, currState)
: SubstanceColorSchemeUtilities.getActiveColorScheme(this.scrollbar, currState);
SubstanceColorScheme baseBorderScheme = SubstanceColorSchemeUtilities
.getColorScheme(this.scrollbar, ColorSchemeAssociationKind.BORDER, currState);
BufferedImage baseLayer = getThumbHorizontal(this.scrollbar, width, height, baseFillScheme,
baseBorderScheme);
Map<ComponentState, StateTransitionTracker.StateContributionInfo> activeStates = modelStateInfo
.getStateContributionMap();
if (currState.isDisabled() || (activeStates.size() == 1)) {
return baseLayer;
}
int scaleFactor = UIUtil.getScaleFactor();
BufferedImage result = SubstanceCoreUtilities.getBlankImage(
baseLayer.getWidth() / scaleFactor, baseLayer.getHeight() / scaleFactor);
Graphics2D g2d = result.createGraphics();
g2d.drawImage(baseLayer, 0, 0, baseLayer.getWidth() / scaleFactor,
baseLayer.getHeight() / scaleFactor, null);
for (Map.Entry<ComponentState, StateTransitionTracker.StateContributionInfo> activeEntry : activeStates
.entrySet()) {
ComponentState activeState = activeEntry.getKey();
if (activeState == modelStateInfo.getCurrModelState())
continue;
// if (activeState == ComponentState.ENABLED)
// activeState = ComponentState.SELECTED;
float contribution = activeEntry.getValue().getContribution();
if (contribution == 0.0f)
continue;
g2d.setComposite(AlphaComposite.SrcOver.derive(contribution));
SubstanceColorScheme fillScheme = (activeState != ComponentState.ENABLED)
? SubstanceColorSchemeUtilities.getColorScheme(this.scrollbar, activeState)
: SubstanceColorSchemeUtilities.getActiveColorScheme(this.scrollbar,
activeState);
SubstanceColorScheme borderScheme = SubstanceColorSchemeUtilities
.getColorScheme(this.scrollbar, ColorSchemeAssociationKind.BORDER, activeState);
BufferedImage layer = getThumbHorizontal(this.scrollbar, width, height, fillScheme,
borderScheme);
g2d.drawImage(layer, 0, 0, layer.getWidth() / scaleFactor,
layer.getHeight() / scaleFactor, null);
}
g2d.dispose();
return result;
}
/**
* Retrieves image for horizontal thumb.
*
* @param scrollBar
* Scroll bar.
* @param width
* Thumb width.
* @param height
* Thumb height.
* @param kind
* Color scheme kind.
* @param cyclePos
* Cycle position.
* @param scheme
* The first color scheme.
* @param scheme2
* The second color scheme.
* @param borderScheme
* The first border color scheme.
* @param borderScheme2
* The second border color scheme.
* @return Image for horizontal thumb.
*/
private static BufferedImage getThumbHorizontal(JScrollBar scrollBar, int width, int height,
SubstanceColorScheme scheme, SubstanceColorScheme borderScheme) {
SubstanceFillPainter painter = SubstanceCoreUtilities.getFillPainter(scrollBar);
SubstanceButtonShaper shaper = SubstanceCoreUtilities.getButtonShaper(scrollBar);
SubstanceBorderPainter borderPainter = SubstanceCoreUtilities.getBorderPainter(scrollBar);
HashMapKey key = SubstanceCoreUtilities.getHashKey(width, height, scheme.getDisplayName(),
borderScheme.getDisplayName(), painter.getDisplayName(), shaper.getDisplayName(),
borderPainter.getDisplayName());
float radius = height / 2;
float borderDelta = SubstanceSizeUtils.getBorderStrokeWidth() / 2.0f;
GeneralPath contour = SubstanceOutlineUtilities.getBaseOutline(width, height, radius, null,
borderDelta);
BufferedImage opaque = SubstanceScrollBarUI.thumbHorizontalMap.get(key);
if (opaque == null) {
// System.out.println("New image for horizontal thumb");
opaque = SubstanceCoreUtilities.getBlankImage(width, height);
painter.paintContourBackground(opaque.createGraphics(), scrollBar, width, height,
contour, false, scheme, true);
borderPainter.paintBorder(opaque.getGraphics(), scrollBar, width, height, contour, null,
borderScheme);
SubstanceScrollBarUI.thumbHorizontalMap.put(key, opaque);
}
return opaque;
}
/**
* Returns the scroll button state.
*
* @param scrollButton
* Scroll button.
* @return Scroll button state.
*/
protected ComponentState getState(JButton scrollButton) {
if (scrollButton == null)
return null;
ComponentState result = ((TransitionAwareUI) scrollButton.getUI()).getTransitionTracker()
.getModelStateInfo().getCurrModelState();
if ((result == ComponentState.ENABLED)
&& SubstanceCoreUtilities.hasFlatAppearance(this.scrollbar, false)) {
result = null;
}
if (SubstanceCoreUtilities.isButtonNeverPainted(scrollButton)) {
result = null;
}
return result;
}
/*
* (non-Javadoc)
*
* @see
* javax.swing.plaf.basic.BasicScrollBarUI#paintTrack(java.awt.Graphics,
* javax.swing.JComponent, java.awt.Rectangle)
*/
@Override
protected void paintTrack(Graphics g, JComponent c, Rectangle trackBounds) {
Graphics2D graphics = (Graphics2D) g.create();
if (this.scrollbar.getOrientation() == JScrollBar.VERTICAL) {
graphics.translate(trackBounds.x, trackBounds.y - THUMB_DELTA);
} else {
graphics.translate(trackBounds.x - THUMB_DELTA, trackBounds.y);
}
graphics.setColor(SubstanceColorUtilities.getBackgroundFillColorScrollBar(this.scrollbar));
// graphics.setColor(Color.red);
graphics.fillRect(0, 0, this.scrollbar.getWidth(), this.scrollbar.getHeight());
GhostPaintingUtils.paintGhostImages(this.scrollbar, g);
graphics.dispose();
}
/*
* (non-Javadoc)
*
* @see
* javax.swing.plaf.basic.BasicScrollBarUI#paintThumb(java.awt.Graphics,
* javax.swing.JComponent, java.awt.Rectangle)
*/
@Override
protected void paintThumb(Graphics g, JComponent c, Rectangle thumbBounds) {
// System.out.println("Thumb");
Graphics2D graphics = (Graphics2D) g.create();
this.thumbModel.setSelected(this.thumbModel.isSelected() || this.isDragging);
this.thumbModel.setEnabled(c.isEnabled());
int scaleFactor = UIUtil.getScaleFactor();
boolean isVertical = (this.scrollbar.getOrientation() == Adjustable.VERTICAL);
if (isVertical) {
Rectangle adjustedBounds = new Rectangle(thumbBounds.x, thumbBounds.y,
thumbBounds.width, thumbBounds.height);
BufferedImage thumbImage = this.getThumbVertical(adjustedBounds);
int xdelta = (thumbBounds.width - thumbImage.getWidth() / scaleFactor) / 2;
graphics.drawImage(thumbImage, adjustedBounds.x + xdelta, adjustedBounds.y,
thumbImage.getWidth() / scaleFactor, thumbImage.getHeight() / scaleFactor,
null);
} else {
Rectangle adjustedBounds = new Rectangle(thumbBounds.x, thumbBounds.y,
thumbBounds.width, thumbBounds.height);
BufferedImage thumbImage = this.getThumbHorizontal(adjustedBounds);
int ydelta = (thumbBounds.height - thumbImage.getHeight() / scaleFactor) / 2;
graphics.drawImage(thumbImage, adjustedBounds.x, adjustedBounds.y + ydelta,
thumbImage.getWidth() / scaleFactor, thumbImage.getHeight() / scaleFactor,
null);
}
graphics.dispose();
}
@Override
public void paint(Graphics g, JComponent c) {
Graphics2D graphics = (Graphics2D) g.create();
BackgroundPaintingUtils.update(graphics, c, false);
float alpha = SubstanceColorSchemeUtilities.getAlpha(this.scrollbar,
ComponentState.getState(this.thumbModel, this.scrollbar));
graphics.setComposite(LafWidgetUtilities.getAlphaComposite(c, alpha, g));
super.paint(graphics, c);
graphics.dispose();
}
/*
* (non-Javadoc)
*
* @see javax.swing.plaf.basic.BasicScrollBarUI#installDefaults()
*/
@Override
protected void installDefaults() {
super.installDefaults();
this.scrollBarWidth = SubstanceSizeUtils
.getScrollBarWidth(SubstanceSizeUtils.getComponentFontSize(this.scrollbar));
for (LafWidget lafWidget : this.lafWidgets) {
lafWidget.installDefaults();
}
}
@Override
protected void uninstallDefaults() {
for (LafWidget lafWidget : this.lafWidgets) {
lafWidget.uninstallDefaults();
}
super.uninstallDefaults();
}
/*
* (non-Javadoc)
*
* @see javax.swing.plaf.basic.BasicScrollBarUI#installComponents()
*/
@Override
protected void installComponents() {
this.compositeStateTransitionTracker = new StateTransitionTracker(this.scrollbar,
this.thumbModel);
this.compositeStateTransitionTracker.registerModelListeners();
for (LafWidget lafWidget : this.lafWidgets) {
lafWidget.installComponents();
}
}
/*
* (non-Javadoc)
*
* @see javax.swing.plaf.basic.BasicScrollBarUI#uninstallComponents()
*/
@Override
protected void uninstallComponents() {
this.compositeStateTransitionTracker.unregisterModelListeners();
for (LafWidget lafWidget : this.lafWidgets) {
lafWidget.uninstallComponents();
}
}
/*
* (non-Javadoc)
*
* @see javax.swing.plaf.basic.BasicScrollBarUI#installListeners()
*/
@Override
protected void installListeners() {
super.installListeners();
this.substanceThumbRolloverListener = new RolloverControlListener(this, this.thumbModel);
this.scrollbar.addMouseListener(this.substanceThumbRolloverListener);
this.scrollbar.addMouseMotionListener(this.substanceThumbRolloverListener);
this.substancePropertyListener = (PropertyChangeEvent evt) -> {
if ("font".equals(evt.getPropertyName())) {
SwingUtilities.invokeLater(() -> scrollbar.updateUI());
}
};
this.scrollbar.addPropertyChangeListener(this.substancePropertyListener);
this.substanceAdjustmentListener = (AdjustmentEvent e) -> {
SubstanceCoreUtilities.testComponentStateChangeThreadingViolation(scrollbar);
Component parent = SubstanceScrollBarUI.this.scrollbar.getParent();
if (parent instanceof JScrollPane) {
JScrollPane jsp = (JScrollPane) parent;
JScrollBar hor = jsp.getHorizontalScrollBar();
JScrollBar ver = jsp.getVerticalScrollBar();
JScrollBar other = null;
if (SubstanceScrollBarUI.this.scrollbar == hor) {
other = ver;
}
if (SubstanceScrollBarUI.this.scrollbar == ver) {
other = hor;
}
if ((other != null) && other.isVisible())
other.repaint();
SubstanceScrollBarUI.this.scrollbar.repaint();
}
};
this.scrollbar.addAdjustmentListener(this.substanceAdjustmentListener);
for (LafWidget lafWidget : this.lafWidgets) {
lafWidget.installListeners();
}
}
/*
* (non-Javadoc)
*
* @see javax.swing.plaf.basic.BasicScrollBarUI#uninstallListeners()
*/
@Override
protected void uninstallListeners() {
this.scrollbar.removeMouseListener(this.substanceThumbRolloverListener);
this.scrollbar.removeMouseMotionListener(this.substanceThumbRolloverListener);
this.substanceThumbRolloverListener = null;
this.scrollbar.removePropertyChangeListener(this.substancePropertyListener);
this.substancePropertyListener = null;
this.scrollbar.removeAdjustmentListener(this.substanceAdjustmentListener);
this.substanceAdjustmentListener = null;
for (LafWidget lafWidget : this.lafWidgets) {
lafWidget.uninstallListeners();
}
super.uninstallListeners();
}
public boolean isInside(MouseEvent me) {
Rectangle trackB = this.getTrackBounds();
if (trackB == null)
return false;
return trackB.contains(me.getX(), me.getY());
}
@Override
public StateTransitionTracker getTransitionTracker() {
return this.compositeStateTransitionTracker;
}
/*
* (non-Javadoc)
*
* @see javax.swing.plaf.basic.BasicScrollBarUI#scrollByBlock(int)
*/
@Override
public void scrollByBlock(int direction) {
// This method is called from SubstanceScrollPaneUI to implement wheel
// scrolling.
int oldValue = this.scrollbar.getValue();
int blockIncrement = this.scrollbar.getBlockIncrement(direction);
int delta = blockIncrement * ((direction > 0) ? +1 : -1);
int newValue = oldValue + delta;
// Check for overflow.
if ((delta > 0) && (newValue < oldValue)) {
newValue = this.scrollbar.getMaximum();
} else if ((delta < 0) && (newValue > oldValue)) {
newValue = this.scrollbar.getMinimum();
}
this.scrollbar.setValue(newValue);
}
/**
* Scrolls the associated scroll bar.
*
* @param direction
* Direction.
* @param units
* Scroll units.
*/
public void scrollByUnits(int direction, int units) {
// This method is called from SubstanceScrollPaneUI to implement wheel
// scrolling.
int delta;
for (int i = 0; i < units; i++) {
if (direction > 0) {
delta = this.scrollbar.getUnitIncrement(direction);
} else {
delta = -this.scrollbar.getUnitIncrement(direction);
}
int oldValue = this.scrollbar.getValue();
int newValue = oldValue + delta;
// Check for overflow.
if ((delta > 0) && (newValue < oldValue)) {
newValue = this.scrollbar.getMaximum();
} else if ((delta < 0) && (newValue > oldValue)) {
newValue = this.scrollbar.getMinimum();
}
if (oldValue == newValue) {
break;
}
this.scrollbar.setValue(newValue);
}
}
/*
* (non-Javadoc)
*
* @see
* javax.swing.plaf.basic.BasicScrollBarUI#layoutVScrollbar(javax.swing.
* JScrollBar)
*/
@Override
protected void layoutVScrollbar(JScrollBar sb) {
this.layoutVScrollbarNone(sb);
}
/*
* (non-Javadoc)
*
* @see
* javax.swing.plaf.basic.BasicScrollBarUI#layoutHScrollbar(javax.swing.
* JScrollBar)
*/
@Override
protected void layoutHScrollbar(JScrollBar sb) {
this.layoutHScrollbarNone(sb);
}
protected void layoutVScrollbarNone(JScrollBar sb) {
Dimension sbSize = sb.getSize();
Insets sbInsets = sb.getInsets();
/*
* Width and left edge of the buttons and thumb.
*/
int itemW = sbSize.width - (sbInsets.left + sbInsets.right);
int itemX = sbInsets.left;
/*
* Nominal locations of the buttons, assuming their preferred size will
* fit.
*/
int incrButtonY = sbSize.height - sbInsets.bottom - THUMB_DELTA;
/*
* The thumb must fit within the height left over after we subtract the
* preferredSize of the buttons and the insets.
*/
int sbInsetsH = sbInsets.top + sbInsets.bottom;
float trackH = sbSize.height - sbInsetsH - THUMB_DELTA;
/*
* Compute the height and origin of the thumb. The case where the thumb
* is at the bottom edge is handled specially to avoid numerical
* problems in computing thumbY. Enforce the thumbs min/max dimensions.
* If the thumb doesn't fit in the track (trackH) we'll hide it later.
*/
float min = sb.getMinimum();
float extent = sb.getVisibleAmount();
float range = sb.getMaximum() - min;
float value = sb.getValue();
int thumbH = (range <= 0) ? this.getMaximumThumbSize().height
: (int) (trackH * (extent / range));
thumbH = Math.max(thumbH, this.getMinimumThumbSize().height);
thumbH = Math.min(thumbH, this.getMaximumThumbSize().height);
thumbH -= THUMB_DELTA;
int thumbY = incrButtonY - thumbH + THUMB_DELTA;
if (value < (sb.getMaximum() - sb.getVisibleAmount())) {
float thumbRange = trackH - thumbH;
thumbY = THUMB_DELTA + (int) (0.5f + (thumbRange * ((value - min) / (range - extent))));
}
/*
* Update the trackRect field.
*/
int itrackY = THUMB_DELTA;
int itrackH = incrButtonY - itrackY - 2 * THUMB_DELTA;
this.trackRect.setBounds(itemX, itrackY, itemW, itrackH);
/*
* If the thumb isn't going to fit, zero it's bounds. Otherwise make
* sure it fits between the buttons. Note that setting the thumbs bounds
* will cause a repaint.
*/
if (thumbH >= (int) trackH) {
this.setThumbBounds(0, 0, 0, 0);
} else {
if ((thumbY + thumbH) > incrButtonY) {
thumbY = incrButtonY - thumbH;
}
if (thumbY < 0) {
thumbY = 0;
}
this.setThumbBounds(itemX, thumbY, itemW, thumbH);
}
}
protected void layoutHScrollbarNone(JScrollBar sb) {
Dimension sbSize = sb.getSize();
Insets sbInsets = sb.getInsets();
/*
* Height and top edge of the buttons and thumb.
*/
int itemH = sbSize.height - (sbInsets.top + sbInsets.bottom);
int itemY = sbInsets.top;
boolean ltr = sb.getComponentOrientation().isLeftToRight();
/*
* Nominal locations of the buttons, assuming their preferred size will
* fit.
*/
int incrButtonX = ltr ? sbSize.width - sbInsets.right - THUMB_DELTA
: sbInsets.left + THUMB_DELTA;
/*
* The thumb must fit within the width left over after we subtract the
* preferredSize of the buttons and the insets.
*/
int sbInsetsW = sbInsets.left + sbInsets.right;
float trackW = sbSize.width - sbInsetsW - THUMB_DELTA;
/*
* Compute the width and origin of the thumb. Enforce the thumbs min/max
* dimensions. The case where the thumb is at the right edge is handled
* specially to avoid numerical problems in computing thumbX. If the
* thumb doesn't fit in the track (trackH) we'll hide it later.
*/
float min = sb.getMinimum();
float max = sb.getMaximum();
float extent = sb.getVisibleAmount();
float range = max - min;
float value = sb.getValue();
int thumbW = (range <= 0) ? this.getMaximumThumbSize().width
: (int) (trackW * (extent / range));
thumbW = Math.max(thumbW, this.getMinimumThumbSize().width);
thumbW = Math.min(thumbW, this.getMaximumThumbSize().width);
thumbW -= THUMB_DELTA;
int thumbX = ltr ? incrButtonX - thumbW + THUMB_DELTA : sbInsets.left + THUMB_DELTA;
if (value < (max - sb.getVisibleAmount())) {
float thumbRange = trackW - thumbW;
if (ltr) {
thumbX = THUMB_DELTA
+ (int) (0.5f + (thumbRange * ((value - min) / (range - extent))));
} else {
thumbX = THUMB_DELTA
+ (int) (0.5f + (thumbRange * ((max - extent - value) / (range - extent))));
// thumbX += decrButton2X + decrButton2W;
}
}
/*
* Update the trackRect field.
*/
if (ltr) {
int itrackX = sbInsets.left + THUMB_DELTA;
int itrackW = incrButtonX - itrackX - 2 * THUMB_DELTA;
this.trackRect.setBounds(itrackX, itemY, itrackW, itemH);
} else {
int itrackX = incrButtonX + THUMB_DELTA;
int itrackW = sbSize.width - itrackX - 2 * THUMB_DELTA;
this.trackRect.setBounds(itrackX, itemY, itrackW, itemH);
}
/*
* Make sure the thumb fits between the buttons. Note that setting the
* thumbs bounds causes a repaint.
*/
if (thumbW >= (int) trackW) {
this.setThumbBounds(0, 0, 0, 0);
} else {
if (ltr) {
if (thumbX + thumbW > incrButtonX) {
thumbX = incrButtonX - thumbW;
}
if (thumbX < 0) {
thumbX = 1;
}
} else {
if (thumbX + thumbW > (sbSize.width - sbInsets.left)) {
thumbX = sbSize.width - sbInsets.left - thumbW;
}
// if (thumbX < (incrButtonX + decrButton2W)) {
// thumbX = incrButtonX + decrButton2W + 1;
// }
}
this.setThumbBounds(thumbX, itemY, thumbW, itemH);
}
}
/**
* Returns the memory usage string.
*
* @return The memory usage string.
*/
public static String getMemoryUsage() {
StringBuffer sb = new StringBuffer();
sb.append("SubstanceScrollBarUI: \n");
sb.append("\t" + thumbHorizontalMap.size() + " thumb horizontal, " + thumbVerticalMap.size()
+ " thumb vertical");
return sb.toString();
}
/*
* (non-Javadoc)
*
* @see javax.swing.plaf.basic.BasicScrollBarUI#createTrackListener()
*/
@Override
protected TrackListener createTrackListener() {
return new SubstanceTrackListener();
}
/**
* Track mouse drags. Had to take this one from BasicScrollBarUI since the
* setValueForm method is private.
*/
protected class SubstanceTrackListener extends TrackListener {
/**
* Current scroll direction.
*/
private transient int direction = +1;
/*
* (non-Javadoc)
*
* @see
* javax.swing.plaf.basic.BasicScrollBarUI$TrackListener#mouseReleased
* (java.awt.event.MouseEvent)
*/
@Override
public void mouseReleased(MouseEvent e) {
if (SubstanceScrollBarUI.this.isDragging) {
SubstanceScrollBarUI.this.updateThumbState(e.getX(), e.getY());
}
if (SwingUtilities.isRightMouseButton(e)
|| (!SubstanceScrollBarUI.this.getSupportsAbsolutePositioning()
&& SwingUtilities.isMiddleMouseButton(e)))
return;
if (!SubstanceScrollBarUI.this.scrollbar.isEnabled())
return;
Rectangle r = SubstanceScrollBarUI.this.getTrackBounds();
SubstanceScrollBarUI.this.scrollbar.repaint(r.x, r.y, r.width, r.height);
SubstanceScrollBarUI.this.trackHighlight = NO_HIGHLIGHT;
SubstanceScrollBarUI.this.isDragging = false;
this.offset = 0;
SubstanceScrollBarUI.this.scrollTimer.stop();
SubstanceScrollBarUI.this.scrollbar.setValueIsAdjusting(false);
}
/*
* (non-Javadoc)
*
* @see
* javax.swing.plaf.basic.BasicScrollBarUI$TrackListener#mousePressed
* (java.awt.event.MouseEvent)
*/
@Override
public void mousePressed(MouseEvent e) {
// If the mouse is pressed above the "thumb" component then reduce
// the scrollbars value by one page ("page up"), otherwise increase
// it by one page. If there is no thumb then page up if the mouse is
// in the upper half of the track.
if (SwingUtilities.isRightMouseButton(e)
|| (!SubstanceScrollBarUI.this.getSupportsAbsolutePositioning()
&& SwingUtilities.isMiddleMouseButton(e)))
return;
if (!SubstanceScrollBarUI.this.scrollbar.isEnabled())
return;
if (!SubstanceScrollBarUI.this.scrollbar.hasFocus()
&& SubstanceScrollBarUI.this.scrollbar.isRequestFocusEnabled()) {
SubstanceScrollBarUI.this.scrollbar.requestFocus();
}
SubstanceScrollBarUI.this.scrollbar.setValueIsAdjusting(true);
this.currentMouseX = e.getX();
this.currentMouseY = e.getY();
// Clicked in the Thumb area?
if (SubstanceScrollBarUI.this.getThumbBounds().contains(this.currentMouseX,
this.currentMouseY)) {
switch (SubstanceScrollBarUI.this.scrollbar.getOrientation()) {
case JScrollBar.VERTICAL:
this.offset = this.currentMouseY - SubstanceScrollBarUI.this.getThumbBounds().y;
break;
case JScrollBar.HORIZONTAL:
this.offset = this.currentMouseX - SubstanceScrollBarUI.this.getThumbBounds().x;
break;
}
SubstanceScrollBarUI.this.isDragging = true;
return;
} else if (SubstanceScrollBarUI.this.getSupportsAbsolutePositioning()
&& SwingUtilities.isMiddleMouseButton(e)) {
switch (SubstanceScrollBarUI.this.scrollbar.getOrientation()) {
case JScrollBar.VERTICAL:
this.offset = SubstanceScrollBarUI.this.getThumbBounds().height / 2;
break;
case JScrollBar.HORIZONTAL:
this.offset = SubstanceScrollBarUI.this.getThumbBounds().width / 2;
break;
}
SubstanceScrollBarUI.this.isDragging = true;
this.setValueFrom(e);
return;
}
SubstanceScrollBarUI.this.isDragging = false;
Dimension sbSize = SubstanceScrollBarUI.this.scrollbar.getSize();
this.direction = +1;
switch (SubstanceScrollBarUI.this.scrollbar.getOrientation()) {
case JScrollBar.VERTICAL:
if (SubstanceScrollBarUI.this.getThumbBounds().isEmpty()) {
int scrollbarCenter = sbSize.height / 2;
this.direction = (this.currentMouseY < scrollbarCenter) ? -1 : +1;
} else {
int thumbY = SubstanceScrollBarUI.this.getThumbBounds().y;
this.direction = (this.currentMouseY < thumbY) ? -1 : +1;
}
break;
case JScrollBar.HORIZONTAL:
if (SubstanceScrollBarUI.this.getThumbBounds().isEmpty()) {
int scrollbarCenter = sbSize.width / 2;
this.direction = (this.currentMouseX < scrollbarCenter) ? -1 : +1;
} else {
int thumbX = SubstanceScrollBarUI.this.getThumbBounds().x;
this.direction = (this.currentMouseX < thumbX) ? -1 : +1;
}
if (!SubstanceScrollBarUI.this.scrollbar.getComponentOrientation()
.isLeftToRight()) {
this.direction = -this.direction;
}
break;
}
SubstanceScrollBarUI.this.scrollByBlock(this.direction);
SubstanceScrollBarUI.this.scrollTimer.stop();
SubstanceScrollBarUI.this.scrollListener.setDirection(this.direction);
SubstanceScrollBarUI.this.scrollListener.setScrollByBlock(true);
this.startScrollTimerIfNecessary();
}
/*
* (non-Javadoc)
*
* @see
* javax.swing.plaf.basic.BasicScrollBarUI$TrackListener#mouseDragged
* (java.awt.event.MouseEvent)
*/
@Override
public void mouseDragged(MouseEvent e) {
// Set the models value to the position of the thumb's top of
// Vertical scrollbar, or the left/right of Horizontal scrollbar in
// LTR / RTL scrollbar relative to the origin of
// the track.
if (SwingUtilities.isRightMouseButton(e)
|| (!SubstanceScrollBarUI.this.getSupportsAbsolutePositioning()
&& SwingUtilities.isMiddleMouseButton(e)))
return;
if (!SubstanceScrollBarUI.this.scrollbar.isEnabled()
|| SubstanceScrollBarUI.this.getThumbBounds().isEmpty()) {
return;
}
if (SubstanceScrollBarUI.this.isDragging) {
this.setValueFrom(e);
} else {
this.currentMouseX = e.getX();
this.currentMouseY = e.getY();
SubstanceScrollBarUI.this.updateThumbState(this.currentMouseX, this.currentMouseY);
this.startScrollTimerIfNecessary();
}
}
/**
* Sets the scrollbar value based on the specified mouse event.
*
* @param e
* Mouse event.
*/
private void setValueFrom(MouseEvent e) {
boolean active = SubstanceScrollBarUI.this.isThumbRollover();
BoundedRangeModel model = SubstanceScrollBarUI.this.scrollbar.getModel();
Rectangle thumbR = SubstanceScrollBarUI.this.getThumbBounds();
int thumbMin = 0, thumbMax = 0, thumbPos;
if (SubstanceScrollBarUI.this.scrollbar.getOrientation() == JScrollBar.VERTICAL) {
thumbMin = THUMB_DELTA;
thumbMax = SubstanceScrollBarUI.this.scrollbar.getSize().height
- SubstanceScrollBarUI.this.scrollbar.getInsets().bottom - thumbR.height
- THUMB_DELTA;
thumbPos = Math.min(thumbMax, Math.max(thumbMin, (e.getY() - this.offset)));
SubstanceScrollBarUI.this.setThumbBounds(thumbR.x, thumbPos, thumbR.width,
thumbR.height);
} else {
thumbMin = THUMB_DELTA;
thumbMax = SubstanceScrollBarUI.this.scrollbar.getSize().width
- SubstanceScrollBarUI.this.scrollbar.getInsets().right - thumbR.width
- THUMB_DELTA;
// System.out.println(thumbMin + " : " + thumbMax + " : "
// + (e.getX() - offset));
thumbPos = Math.min(thumbMax, Math.max(thumbMin, (e.getX() - this.offset)));
SubstanceScrollBarUI.this.setThumbBounds(thumbPos, thumbR.y, thumbR.width,
thumbR.height);
}
/*
* Set the scrollbars value. If the thumb has reached the end of the
* scrollbar, then just set the value to its maximum. Otherwise
* compute the value as accurately as possible.
*/
if (thumbPos == thumbMax) {
if (SubstanceScrollBarUI.this.scrollbar.getOrientation() == JScrollBar.VERTICAL
|| SubstanceScrollBarUI.this.scrollbar.getComponentOrientation()
.isLeftToRight()) {
SubstanceScrollBarUI.this.scrollbar
.setValue(model.getMaximum() - model.getExtent());
} else {
SubstanceScrollBarUI.this.scrollbar.setValue(model.getMinimum());
}
} else {
float valueMax = model.getMaximum() - model.getExtent();
float valueRange = valueMax - model.getMinimum();
float thumbValue = thumbPos - thumbMin;
float thumbRange = thumbMax - thumbMin;
int value;
if (SubstanceScrollBarUI.this.scrollbar.getOrientation() == JScrollBar.VERTICAL
|| SubstanceScrollBarUI.this.scrollbar.getComponentOrientation()
.isLeftToRight()) {
value = (int) (0.5 + ((thumbValue / thumbRange) * valueRange));
} else {
value = (int) (0.5 + (((thumbMax - thumbPos) / thumbRange) * valueRange));
}
SubstanceScrollBarUI.this.scrollbar.setValue(value + model.getMinimum());
}
SubstanceScrollBarUI.this.setThumbRollover(active);
}
/**
* If necessary, starts the scroll timer.
*/
private void startScrollTimerIfNecessary() {
if (SubstanceScrollBarUI.this.scrollTimer.isRunning()) {
return;
}
switch (SubstanceScrollBarUI.this.scrollbar.getOrientation()) {
case JScrollBar.VERTICAL:
if (this.direction > 0) {
if (SubstanceScrollBarUI.this.getThumbBounds().y + SubstanceScrollBarUI.this
.getThumbBounds().height < ((SubstanceTrackListener) SubstanceScrollBarUI.this.trackListener).currentMouseY) {
SubstanceScrollBarUI.this.scrollTimer.start();
}
} else if (SubstanceScrollBarUI.this
.getThumbBounds().y > ((SubstanceTrackListener) SubstanceScrollBarUI.this.trackListener).currentMouseY) {
SubstanceScrollBarUI.this.scrollTimer.start();
}
break;
case JScrollBar.HORIZONTAL:
if (this.direction > 0) {
if (SubstanceScrollBarUI.this.getThumbBounds().x + SubstanceScrollBarUI.this
.getThumbBounds().width < ((SubstanceTrackListener) SubstanceScrollBarUI.this.trackListener).currentMouseX) {
SubstanceScrollBarUI.this.scrollTimer.start();
}
} else if (SubstanceScrollBarUI.this
.getThumbBounds().x > ((SubstanceTrackListener) SubstanceScrollBarUI.this.trackListener).currentMouseX) {
SubstanceScrollBarUI.this.scrollTimer.start();
}
break;
}
}
/*
* (non-Javadoc)
*
* @see
* javax.swing.plaf.basic.BasicScrollBarUI$TrackListener#mouseMoved(
* java.awt.event.MouseEvent)
*/
@Override
public void mouseMoved(MouseEvent e) {
if (!SubstanceScrollBarUI.this.isDragging) {
SubstanceScrollBarUI.this.updateThumbState(e.getX(), e.getY());
}
}
/*
* (non-Javadoc)
*
* @see
* javax.swing.plaf.basic.BasicScrollBarUI$TrackListener#mouseExited
* (java.awt.event.MouseEvent)
*/
@Override
public void mouseExited(MouseEvent e) {
if (!SubstanceScrollBarUI.this.isDragging) {
SubstanceScrollBarUI.this.setThumbRollover(false);
}
}
}
/*
* (non-Javadoc)
*
* @see javax.swing.plaf.basic.BasicScrollBarUI#createArrowButtonListener()
*/
@Override
protected ArrowButtonListener createArrowButtonListener() {
return null;
}
/**
* Updates the thumb state based on the coordinates.
*
* @param x
* X coordinate.
* @param y
* Y coordinate.
*/
private void updateThumbState(int x, int y) {
Rectangle rect = this.getThumbBounds();
this.setThumbRollover(rect.contains(x, y));
}
/*
* (non-Javadoc)
*
* @see
* javax.swing.plaf.basic.BasicScrollBarUI#getPreferredSize(javax.swing.
* JComponent)
*/
@Override
public Dimension getPreferredSize(JComponent c) {
if (scrollbar.getOrientation() == JScrollBar.VERTICAL) {
return new Dimension(scrollBarWidth, Math.max(48, 5 * scrollBarWidth));
} else {
return new Dimension(Math.max(48, 5 * scrollBarWidth), scrollBarWidth);
}
}
@Override
public void update(Graphics g, JComponent c) {
super.update(g, c);
GhostPaintingUtils.paintGhostImages(c, g);
}
}