/*
* Copyright (c) 2010, skobbler GmbH
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. 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.
* 3. Neither the name of the project 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.
*
* Created on Apr 7, 2011 by Bea
* Modified on $DateTime$ by $Author$
*/
package org.openstreetmap.josm.plugins.mapdust.gui.component.slider;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JSlider;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeListener;
import javax.swing.plaf.basic.BasicSliderUI;
import org.openstreetmap.josm.tools.ImageProvider;
/**
* This class defines a customized <code>BasicSliderUI</code> for the
* <code>RelevanceSlider</code> object.
*
* @author Bea
* @version $Revision$
*/
public class RelevanceSliderUI extends BasicSliderUI {
/** The rectangle of the upper thumb */
private Rectangle upperRect;
/** Flag indicating if the upper thumb is selected or not */
private boolean isUpperSelected;
/** Flag indicating if the lower thumb is dragging or not */
private transient boolean isLowerDragging;
/** Flag indicating if the upper thumb is dragging or not */
private transient boolean isUpperDragging;
/** The icon used for representing the upper and lower thumbs */
private final Icon sliderIcon;
/**
* Builds a new <code>RelevanceSliderUI</code> object based on the given
* arguments.
*
* @param relevanceSlider The <code>RelevanceSlider</code> object
*/
public RelevanceSliderUI(RelevanceSlider relevanceSlider) {
super(relevanceSlider);
this.sliderIcon = ImageProvider.get("slider/thumb.png");
}
/**
* Installs the UI on the given component.
*
* @param component A <code>JComponent</code> object
*/
@Override
public void installUI(JComponent component) {
upperRect = new Rectangle();
super.installUI(component);
}
/**
* Changes the slider current state shown on the GUI with a new state.
* Basically moves the upper and lower thumbs on the slider, based on a
* previous user action.
*/
public void changeSliderState() {
if (!isLowerDragging && !isUpperDragging) {
calculateThumbLocation();
slider.repaint();
} else {
if (isUpperSelected) {
calculateThumbLocation();
slider.repaint();
}
}
}
/**
* Creates a new custom <code>TrackListener</code> object for the slider.
*
* @param slider The <code>JSlider</code> object
* @return A new <code>RangeTrackListener</code> object
*/
@Override
protected TrackListener createTrackListener(JSlider slider) {
return new RangeTrackListener();
}
/**
* Creates a new custom <code>ChangeListener</code> object for the slider.
*
* @param slider The <code>JSlider</code> object
* @return A new <code>RelevanceChangeListener</code> object
*/
@Override
protected ChangeListener createChangeListener(JSlider slider) {
return new RelevanceChangeListener(this);
}
/**
* Calculates the slider thumb sizes. Also sets the size for the upper and
* lower thumb.
*
*/
@Override
protected void calculateThumbSize() {
super.calculateThumbSize();
upperRect.setSize(sliderIcon.getIconWidth(), sliderIcon.getIconHeight());
}
/**
* Calculates the locations of the upper and lower thumbs.
*/
@Override
protected void calculateThumbLocation() {
super.calculateThumbLocation();
if (getSlider().getSnapToTicks()) {
int upperValue = getSlider().getUpperValue();
int snappedValue = upperValue;
int majorTickSpacing = getSlider().getMajorTickSpacing();
int minorTickSpacing = getSlider().getMinorTickSpacing();
int tickSpacing = 0;
if (minorTickSpacing > 0) {
tickSpacing = minorTickSpacing;
} else if (majorTickSpacing > 0) {
tickSpacing = majorTickSpacing;
}
if (tickSpacing != 0) {
int min = getSlider().getMinimum();
if ((upperValue - min) % tickSpacing != 0) {
float temp = upperValue - min;
temp /= tickSpacing;
int whichTick = Math.round(temp);
snappedValue = min + (whichTick * tickSpacing);
}
if (snappedValue != upperValue) {
int extent = snappedValue - getSlider().getLowerValue();
getSlider().setExtent(extent);
}
}
}
int value = getSlider().getLowerValue() + getSlider().getExtent();
int upperPosition = xPositionForValue(value);
upperRect.x = upperPosition - (upperRect.width / 2);
upperRect.y = trackRect.y;
}
/**
* Returns the size of the thumbs. The dimension is computed based on the
* thumb icon dimension.
*
* @return A <code>Dimension</code> object.
*/
@Override
protected Dimension getThumbSize() {
return new Dimension(sliderIcon.getIconWidth(),
sliderIcon.getIconHeight());
}
/**
* Paints the given components.
*
* @param g The <code>Graphics</code> object
* @param c The <code>JComponent</code> object
*/
@Override
public void paint(Graphics g, JComponent c) {
super.paint(g, c);
Rectangle clipRect = g.getClipBounds();
if (isUpperSelected) {
if (clipRect.intersects(thumbRect)) {
sliderIcon.paintIcon(getSlider(), g, thumbRect.x, thumbRect.y);
}
if (clipRect.intersects(upperRect)) {
sliderIcon.paintIcon(getSlider(), g, upperRect.x, upperRect.y);
}
} else {
if (clipRect.intersects(upperRect)) {
sliderIcon.paintIcon(getSlider(), g, upperRect.x, upperRect.y);
}
if (clipRect.intersects(thumbRect)) {
sliderIcon.paintIcon(getSlider(), g, thumbRect.x, thumbRect.y);
}
}
}
@Override
public void paintThumb(Graphics g) {}
/**
* Paints the tack of the slider. The selected part of the slider will be
* painted with orange.
*
* @param g The <code>Graphics</code> object
*/
@Override
public void paintTrack(Graphics g) {
super.paintTrack(g);
Rectangle trackBounds = trackRect;
int lowerX = thumbRect.x + (thumbRect.width / 2);
int upperX = upperRect.x + (upperRect.width / 2);
int cy = (trackBounds.height / 2) - 2;
Color oldColor = g.getColor();
g.translate(trackBounds.x, trackBounds.y + cy);
g.setColor(Color.orange);
for (int y = 0; y <= 3; y++) {
g.drawLine(lowerX - trackBounds.x, y, upperX - trackBounds.x, y);
}
g.translate(-trackBounds.x, -(trackBounds.y + cy));
g.setColor(oldColor);
}
/**
* Moves the selected thumb in the specified direction by a block increment.
*
* @param direction The direction
*/
@Override
public void scrollByBlock(int direction) {
synchronized (getSlider()) {
int min = getSlider().getMinimum();
int max = getSlider().getMaximum();
int incr = (max - min) / 10;
if (incr <= 0 && max > min) {
incr = 1;
}
int delta = incr * ((direction > 0) ? POSITIVE_SCROLL
: NEGATIVE_SCROLL);
if (isUpperSelected) {
int oldValue = getSlider().getUpperValue();
getSlider().setUpperValue(oldValue + delta);
} else {
int oldValue = getSlider().getLowerValue();
getSlider().setLowerValue(oldValue + delta);
}
}
}
/**
* Moves the selected thumb in the specified direction by a unit increment.
*
* @param direction The direction
*/
@Override
public void scrollByUnit(int direction) {
synchronized (getSlider()) {
int delta = 1 * ((direction > 0) ? POSITIVE_SCROLL : NEGATIVE_SCROLL);
if (isUpperSelected) {
int oldValue = getSlider().getUpperValue();
getSlider().setUpperValue(oldValue + delta);
} else {
int oldValue = getSlider().getLowerValue();
getSlider().setLowerValue(oldValue + delta);
}
}
}
/**
* Returns the <code>RelevanceSlider</code> object
*
* @return relevance slider
*/
protected RelevanceSlider getSlider() {
return (RelevanceSlider) slider;
}
/**
* Returns the thumb rectangle. This method returns the parent class
* thumbRect field.
*
* @return A <code>Rectangle</code> object
*/
protected Rectangle getThumbRect() {
return super.thumbRect;
}
/**
* Returns the track rectangle. This method returns the parent class
* trackRect field.
*
* @return A <code>Rectangle</code> object
*/
protected Rectangle getTrackRect() {
return super.trackRect;
}
/**
* Returns the upper thumb rectangle.
*
* @return A <code>Rectangle</code> object
*/
public Rectangle getUpperRect() {
return upperRect;
}
/**
* This method it is used for accessing the base class 'drawInverted'
* method.
*
* @return boolean
*/
@Override
protected boolean drawInverted() {
return super.drawInverted();
}
/**
* This method it is used for accessing the base class 'xPositionForValue'
* method.
*
* @param value The integer value
* @return the x position for the given value
*/
@Override
protected int xPositionForValue(int value) {
return super.xPositionForValue(value);
}
/**
* Returns the isUpperSelected field value.
*
* @return the isUpperSelected
*/
public boolean getIsUpperSelected() {
return isUpperSelected;
}
/**
* Sets the isUpperSelected field to a new value
*
* @param isUpperSelected the isUpperSelected to set
*/
public void setIsUpperSelected(boolean isUpperSelected) {
this.isUpperSelected = isUpperSelected;
}
/**
* Returns the isLowerDragging field value.
*
* @return the isLowerDragging
*/
public boolean getIsLowerDragging() {
return isLowerDragging;
}
/**
* Sets the isLowerDragging field value
*
* @param isLowerDragging the isLowerDragging to set
*/
public void setIsLowerDragging(boolean isLowerDragging) {
this.isLowerDragging = isLowerDragging;
}
/**
* Returns the isUpperDragging field value
*
* @return the isUpperDragging
*/
public boolean getIsUpperDragging() {
return isUpperDragging;
}
/**
* Sets the isUpperDragging field value
*
* @param isUpperDragging the isUpperDragging to set
*/
public void setIsUpperDragging(boolean isUpperDragging) {
this.isUpperDragging = isUpperDragging;
}
/**
* Custom <code>TrackListener</code> inner class for the
* <code>RelevanceSliderUI</code> object.
*
*/
class RangeTrackListener extends TrackListener {
/**
* Listens and handles the mouse pressed event.
*
* @param event The <code>MouseEvent</code> object
*/
@Override
public void mousePressed(MouseEvent event) {
if (!getSlider().isEnabled()) {
return;
}
currentMouseX = event.getX();
currentMouseY = event.getY();
if (getSlider().isRequestFocusEnabled()) {
getSlider().requestFocus();
}
boolean lowerPressed = false;
boolean upperPressed = false;
if (getIsUpperSelected()) {
if (getUpperRect().contains(currentMouseX, currentMouseY)) {
upperPressed = true;
} else {
if (getThumbRect().contains(currentMouseX, currentMouseY)) {
lowerPressed = true;
}
}
} else {
if (getThumbRect().contains(currentMouseX, currentMouseY)) {
lowerPressed = true;
} else {
if (getUpperRect().contains(currentMouseX, currentMouseY)) {
upperPressed = true;
}
}
}
/* lower thumb was pressed */
if (lowerPressed) {
offset = currentMouseX - getThumbRect().x;
setIsUpperSelected(false);
setIsLowerDragging(true);
return;
}
setIsLowerDragging(false);
/* upper thumb was pressed */
if (upperPressed) {
offset = currentMouseX - getUpperRect().x;
setIsUpperSelected(true);
setIsUpperDragging(true);
return;
}
setIsUpperDragging(false);
}
/**
* Listens and handles the mouse released event.
*
* @param event The <code>MouseEvent</code> object
*/
@Override
public void mouseReleased(MouseEvent event) {
setIsLowerDragging(false);
setIsUpperDragging(false);
getSlider().setValueIsAdjusting(false);
super.mouseReleased(event);
}
/**
* Listens and handles the mouse dragged event.
*
* @param event The <code>MouseEvent</code> object
*/
@Override
public void mouseDragged(MouseEvent event) {
if (!getSlider().isEnabled()) {
return;
}
currentMouseX = event.getX();
currentMouseY = event.getY();
if (getIsLowerDragging()) {
getSlider().setValueIsAdjusting(true);
moveLowerThumb(event);
} else if (getIsUpperDragging()) {
getSlider().setValueIsAdjusting(true);
moveUpperThumb(event);
}
}
@Override
public boolean shouldScroll(int direction) {
return false;
}
/**
* Moves the location of the lower thumb, and sets its corresponding
* value in the slider.
*
* @param event The <code>MouseEvent</code> object
*/
private void moveLowerThumb(MouseEvent event) {
int halfThumbWidth = getThumbRect().width / 2;
int thumbLeft = currentMouseX - offset;
int trackLeft = getTrackRect().x;
int trackRight = getTrackRect().x + (getTrackRect().width - 1);
int hMax = xPositionForValue(getSlider().getLowerValue()
+ getSlider().getExtent());
if (drawInverted()) {
trackLeft = hMax;
} else {
trackRight = hMax;
}
thumbLeft = Math.max(thumbLeft, trackLeft - halfThumbWidth);
thumbLeft = Math.min(thumbLeft, trackRight - halfThumbWidth);
/* set location & repaint */
setThumbLocation(thumbLeft, getThumbRect().y);
/* set lower value */
getSlider().setLowerValue(getSnappedValue(event));
}
/**
* Moves the location of the upper thumb, and sets its corresponding
* value in the slider.
*
* @param event The <code>MouseEvent</code> object
*/
private void moveUpperThumb(MouseEvent event) {
int halfThumbWidth = getThumbRect().width / 2;
int thumbLeft = currentMouseX - offset;
int trackLeft = getTrackRect().x;
int trackRight = getTrackRect().x + (getTrackRect().width - 1);
int hMin = xPositionForValue((getSlider()).getLowerValue());
if (drawInverted()) {
trackRight = hMin;
} else {
trackLeft = hMin;
}
thumbLeft = Math.max(thumbLeft, trackLeft - halfThumbWidth);
thumbLeft = Math.min(thumbLeft, trackRight - halfThumbWidth);
/* set location & repaint */
Rectangle upperUnionRect = new Rectangle();
upperUnionRect.setBounds(getUpperRect());
getUpperRect().setLocation(thumbLeft, getThumbRect().y);
SwingUtilities.computeUnion(getUpperRect().x, getUpperRect().y,
getUpperRect().width, getUpperRect().height,
upperUnionRect);
getSlider().repaint(upperUnionRect.x, upperUnionRect.y,
upperUnionRect.width, upperUnionRect.height);
/* set the new upper value */
getSlider().setUpperValue(getSnappedValue(event));
}
/**
* Computes the snapped values for the upper/lower relevance slider
* value. If the value of the slider is not on a tick, than its value
* will be adjusted according to the nearest left or right tick.
*
* @param evt The <code>MouseEvent</code>
* @return The snapped value
*/
private int getSnappedValue(MouseEvent evt) {
int pozX = valueForXPosition(evt.getX());
int pozY = valueForYPosition(evt.getY());
int value = getSlider().getOrientation() ==
SwingConstants.HORIZONTAL ? pozX : pozY;
int snappedValue = value;
int tickSpacing = 0;
int majorTickSpacing = getSlider().getMajorTickSpacing();
int minorTickSpacing = getSlider().getMinorTickSpacing();
if (minorTickSpacing > 0)
tickSpacing = minorTickSpacing;
else if (majorTickSpacing > 0)
tickSpacing = majorTickSpacing;
/* If it's not on a tick, change the value */
if (tickSpacing != 0) {
if ((value - getSlider().getMinimum()) % tickSpacing != 0) {
float temp = (float) (value - getSlider().getMinimum())
/ (float) tickSpacing;
snappedValue = getSlider().getMinimum()+ (Math.round(temp)
* tickSpacing);
}
}
return snappedValue;
}
}
}