/*
* @(#)MultiThumbSliderUI.java
*
* $Date: 2014-06-06 20:04:49 +0200 (P, 06 jún. 2014) $
*
* Copyright (c) 2011 by Jeremy Wood.
* All rights reserved.
*
* The copyright of this software is owned by Jeremy Wood.
* You may not use, copy or modify this software, except in
* accordance with the license agreement you entered into with
* Jeremy Wood. For details see accompanying license terms.
*
* This software is probably, but not necessarily, discussed here:
* https://javagraphics.java.net/
*
* That site should also contain the most recent official version
* of this software. (See the SVN repository for more details.)
*/
package com.bric.plaf;
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.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JComponent;
import javax.swing.SwingConstants;
import javax.swing.UIManager;
import javax.swing.plaf.ComponentUI;
import com.bric.swing.MultiThumbSlider;
/** This is the abstract UI for <code>MultiThumbSliders</code>
*
*
*/
public abstract class MultiThumbSliderUI extends ComponentUI implements MouseListener, MouseMotionListener {
protected MultiThumbSlider slider;
/** The maximum width returned by <code>getMaximumSize()</code>.
* (or if the slider is vertical, this is the maximum height.)
*/
int MAX_LENGTH = 300;
/** The minimum width returned by <code>getMinimumSize()</code>.
* (or if the slider is vertical, this is the minimum height.)
*/
int MIN_LENGTH = 50;
/** The maximum width returned by <code>getPreferredSize()</code>.
* (or if the slider is vertical, this is the preferred height.)
*/
int PREF_LENGTH = 140;
/** The height of a horizontal slider -- or width of a vertical slider.
*/
int DEPTH = 15;
/** The pixel position of the thumbs. This may be x or y coordinates, depending on
* whether this slider is horizontal or vertical
*/
int[] thumbPositions = new int[0];
/** A float from zero to one, indicating whether that thumb should be highlighted
* or not.
*/
protected float[] thumbIndications = new float[0];
/** This is used by the animating thread. The field indication is updated until it equals this value. */
private float indicationGoal = 0;
/** The overall indication of the thumbs. At one they should be opaque,
* at zero they should be transparent.
*/
float indication = 0;
/** The rectangle the track should be painted in. */
protected Rectangle trackRect = new Rectangle(0,0,0,0);
public MultiThumbSliderUI(MultiThumbSlider slider) {
this.slider = slider;
}
@Override
public Dimension getMaximumSize(JComponent s) {
MultiThumbSlider mySlider = (MultiThumbSlider)s;
if(mySlider.getOrientation()==MultiThumbSlider.HORIZONTAL) {
return new Dimension(MAX_LENGTH, DEPTH);
}
return new Dimension(DEPTH, MAX_LENGTH);
}
@Override
public Dimension getMinimumSize(JComponent s) {
MultiThumbSlider mySlider = (MultiThumbSlider)s;
if(mySlider.getOrientation()==MultiThumbSlider.HORIZONTAL) {
return new Dimension(MIN_LENGTH, DEPTH);
}
return new Dimension(DEPTH, MIN_LENGTH);
}
@Override
public Dimension getPreferredSize(JComponent s) {
MultiThumbSlider mySlider = (MultiThumbSlider)s;
if(mySlider.getOrientation()==MultiThumbSlider.HORIZONTAL) {
return new Dimension(PREF_LENGTH, DEPTH);
}
return new Dimension(DEPTH, PREF_LENGTH);
}
/** This records the positions/values of each thumb.
* This is used when the mouse is pressed, so as the mouse
* is dragged values can get replaced and rearranged freely.
* (Including removing and adding thumbs)
*
*/
class State {
Object[] values;
float[] positions;
int selectedThumb;
public State() {
values = slider.getValues();
positions = slider.getThumbPositions();
selectedThumb = slider.getSelectedThumb(false);
}
public State(State s) {
selectedThumb = s.selectedThumb;
positions = new float[s.positions.length];
values = new Object[s.values.length];
System.arraycopy(s.positions,0,positions,0,positions.length);
System.arraycopy(s.values,0,values,0,values.length);
}
/** Strip values outside of [0,1] */
private void polish() {
while(positions[0]<0) {
float[] f2 = new float[positions.length-1];
System.arraycopy(positions,1,f2,0,positions.length-1);
Object[] c2 = new Object[values.length-1];
System.arraycopy(values,1,c2,0,positions.length-1);
positions = f2;
values = c2;
selectedThumb++;
}
while(positions[positions.length-1]>1) {
float[] f2 = new float[positions.length-1];
System.arraycopy(positions,0,f2,0,positions.length-1);
Object[] c2 = new Object[values.length-1];
System.arraycopy(values,0,c2,0,positions.length-1);
positions = f2;
values = c2;
selectedThumb--;
}
if(selectedThumb>=positions.length)
selectedThumb = -1;
}
/** Make the slider reflect this object */
public void install() {
polish();
slider.setValues(positions, values);
slider.setSelectedThumb(selectedThumb);
}
public void removeThumb(int index) {
float[] f = new float[positions.length-1];
Object[] c = new Object[values.length-1];
System.arraycopy(positions, 0, f, 0, index);
System.arraycopy(values, 0, c, 0, index);
System.arraycopy(positions, index+1, f, index, f.length-index);
System.arraycopy(values, index+1, c, index, f.length-index);
positions = f;
values = c;
selectedThumb = -1;
}
}
Thread animatingThread = null;
Runnable animatingRunnable = new Runnable() {
public void run() {
boolean finished = false;
while(!finished) {
synchronized(MultiThumbSliderUI.this) {
finished = true;
for(int a = 0; a<thumbIndications.length; a++) {
if(a!=slider.getSelectedThumb()) {
if(a==currentIndicatedThumb) {
if(thumbIndications[a]<1) {
thumbIndications[a] = Math.min(1,thumbIndications[a]+.025f);
finished = false;
}
} else {
if(thumbIndications[a]>0) {
thumbIndications[a] = Math.max(0,thumbIndications[a]-.025f);
finished = false;
}
}
} else {
//the selected thumb is painted as selected,
//so there's no indication to animate.
//just set the indication to whatever it should
//be and move on. No repainting.
if(a==currentIndicatedThumb) {
thumbIndications[a] = 1;
} else {
thumbIndications[a] = 0;
}
}
}
if(indicationGoal>indication+.01f) {
if(indication<.99f) {
indication = Math.min(1,indication+.1f);
finished = false;
}
} else if(indicationGoal<indication-.01f){
if(indication>.01f) {
indication = Math.max(0,indication-.1f);
finished = false;
}
}
}
if(!finished)
slider.repaint();
//rest a little bit
long t = System.currentTimeMillis();
while(System.currentTimeMillis()-t<20) {
try {
Thread.sleep(10);
} catch(Exception e) {
Thread.yield();
}
}
}
}
};
private int currentIndicatedThumb = -1;
private boolean mouseInside = false;
private boolean mouseIsDown = false;
private State pressedState;
private int dx, dy;
public void mousePressed(MouseEvent e) {
dx = 0;
dy = 0;
if(slider.isEnabled()==false) return;
if(e.getClickCount()>=2) {
if(slider.doDoubleClick(e.getX(),e.getY())) {
e.consume();
return;
}
} else if(e.isPopupTrigger()) {
int x = e.getX();
int y = e.getY();
if(slider.getOrientation()==MultiThumbSlider.HORIZONTAL) {
if(x<trackRect.x || x>trackRect.x+trackRect.width)
return;
y = trackRect.y+trackRect.height;
} else {
if(y<trackRect.y || y>trackRect.y+trackRect.height)
return;
x = trackRect.x+trackRect.width;
}
if(slider.doPopup(x,y)) {
e.consume();
return;
}
}
mouseIsDown = true;
mouseMoved(e);
if(e.getSource()!=slider) {
throw new RuntimeException("only install this UI on the GradientSlider it was constructed with");
}
slider.requestFocus();
int index = getIndex(e);
if(index!=-1) {
if(slider.getOrientation()==SwingConstants.HORIZONTAL) {
dx = -e.getX()+thumbPositions[index];
} else {
dy = -e.getY()+thumbPositions[index];
}
}
if(index!=-1) {
slider.setSelectedThumb(index);
e.consume();
} else {
if(slider.isAutoAdding()) {
float k;
int v;
if(slider.getOrientation()==MultiThumbSlider.HORIZONTAL) {
v = e.getX();
} else {
v = e.getY();
}
if(slider.getOrientation()==MultiThumbSlider.HORIZONTAL) {
k = ((float)(v-trackRect.x))/((float)trackRect.width);
if(slider.isInverted())
k = 1-k;
} else {
k = ((float)(v-trackRect.y))/((float)trackRect.height);
if(slider.isInverted()==false)
k= 1-k;
}
if(k>0 && k<1) {
int added = slider.addThumb(k);
slider.setSelectedThumb(added);
}
e.consume();
} else {
if(slider.getSelectedThumb()!=-1) {
slider.setSelectedThumb(-1);
e.consume();
}
}
}
pressedState = new State();
}
private int getIndex(MouseEvent e) {
int v;
if(slider.getOrientation()==MultiThumbSlider.HORIZONTAL) {
v = e.getX();
if(v<trackRect.x-getClickLocationTolerance()+1 || v>trackRect.x+trackRect.width+getClickLocationTolerance()-1) {
return -1; // didn't click in the track;
}
} else {
v = e.getY();
if(v<trackRect.y-getClickLocationTolerance()+1 || v>trackRect.y+trackRect.height+getClickLocationTolerance()-1) {
return -1;
}
}
int min = Math.abs(v-thumbPositions[0]);
int minIndex = 0;
for(int a = 1; a<thumbPositions.length; a++) {
int distance = Math.abs(v-thumbPositions[a]);
if(distance<min) {
min = distance;
minIndex = a;
}
}
if(min<getClickLocationTolerance()) {
return minIndex;
}
return -1;
}
public void mouseEntered(MouseEvent e) {
mouseMoved(e);
}
public void mouseExited(MouseEvent e) {
setCurrentIndicatedThumb(-1);
setMouseInside(false);
}
public void mouseClicked(MouseEvent e) {}
public void mouseMoved(MouseEvent e) {
if(slider.isEnabled()==false) return;
int i = getIndex(e);
setCurrentIndicatedThumb(i);
boolean b = (e.getX()>=0 && e.getX()<slider.getWidth() && e.getY()>=0 && e.getY()<slider.getHeight());
if(mouseIsDown) b = true;
setMouseInside(b);
}
private void setCurrentIndicatedThumb(int i) {
if(getProperty(slider,"MultiThumbSlider.indicateThumb","true").equals("false")) {
//never activate a specific thumb
i = -1;
}
currentIndicatedThumb = i;
boolean finished = true;
for(int a = 0; a<thumbIndications.length; a++) {
if(a==currentIndicatedThumb) {
if(thumbIndications[a]!=1) {
finished = false;
}
} else {
if(thumbIndications[a]!=0) {
finished = false;
}
}
}
if(!finished) {
synchronized(MultiThumbSliderUI.this) {
if(animatingThread==null || animatingThread.isAlive()==false) {
animatingThread = new Thread(animatingRunnable);
animatingThread.start();
}
}
}
}
private void setMouseInside(boolean b) {
mouseInside = b;
updateIndication();
}
public void mouseDragged(MouseEvent e) {
if(slider.isEnabled()==false) return;
e.translatePoint(dx, dy);
mouseMoved(e);
if(pressedState!=null && pressedState.selectedThumb!=-1) {
slider.setValueIsAdjusting(true);
State newState = new State(pressedState);
float v;
boolean outside;
if(slider.getOrientation()==MultiThumbSlider.HORIZONTAL) {
v = ((float)(e.getX()-trackRect.x))/((float)trackRect.width);
if(slider.isInverted())
v = 1-v;
outside = (e.getY()<trackRect.y-10) || (e.getY()>trackRect.y+trackRect.height+10);
//don't whack the thumb off the slider if you happen to be *near* the edge:
if(e.getX()>trackRect.x-10 && e.getX()<trackRect.x+trackRect.width+10) {
if(v<0) v = 0;
if(v>1) v = 1;
}
} else {
v = ((float)(e.getY()-trackRect.y))/((float)trackRect.height);
if(slider.isInverted()==false)
v = 1-v;
outside = (e.getX()<trackRect.x-10) || (e.getX()>trackRect.x+trackRect.width+10);
if(e.getY()>trackRect.y-10 && e.getY()<trackRect.y+trackRect.height+10) {
if(v<0) v = 0;
if(v>1) v = 1;
}
}
if(newState.positions.length<=2) {
outside = false; //I don't care if you are outside: no removing!
}
newState.positions[newState.selectedThumb] = v;
//because we delegate mouseReleased() to this method:
if(outside) {
newState.removeThumb(newState.selectedThumb);
}
if(validatePositions(newState)) {
newState.install();
}
e.consume();
}
}
public void mouseReleased(MouseEvent e) {
if(slider.isEnabled()==false) return;
mouseIsDown = false;
if(pressedState!=null && slider.getThumbCount()<=pressedState.positions.length) {
mouseDragged(e); //go ahead and commit this final location
}
if(slider.isValueAdjusting()) {
slider.setValueIsAdjusting(false);
}
if(e.isPopupTrigger() && slider.doPopup(e.getX(),e.getY())) {
//on windows popuptriggers happen on mouseRelease
e.consume();
return;
}
}
/** This retrieves a property.
* If the component has this property manually set (by calling
* <code>component.putClientProperty()</code>), then that value will be returned.
* Otherwise this method refers to <code>UIManager.get()</code>. If that
* value is missing, this returns <code>defaultValue</code>
*
* @param jc
* @param propertyName the property name
* @param defaultValue if no other value is found, this is returned
* @return the property value
*/
public static String getProperty(JComponent jc,String propertyName,String defaultValue) {
Object jcValue = jc.getClientProperty(propertyName);
if(jcValue!=null)
return jcValue.toString();
Object uiValue = UIManager.get(propertyName);
if(uiValue!=null)
return uiValue.toString();
return defaultValue;
}
/** How many pixels can you deviate from a thumb and and still "click" it.*/
public abstract int getClickLocationTolerance();
/** Makes sure the thumbs are in the right order.
*
* @param state
* @return true if the thumbs are valid. False if there are two
* thumbs with the same value (this is not allowed)
*/
protected static boolean validatePositions(State state) {
float[] p = state.positions;
Object[] c = state.values;
/** Don't let the user position a thumb outside of
* [0,1] if there are only 2 colors:
* colors outside [0,1] are deleted, and we can't delete
* colors so we get less than 2.
*/
if(p.length<=2) {
/** Since the user can only manipulate 1 thumb at a time,
* only 1 thumb should be outside the domain of [0,1].
* So we *don't* have to reorganize c when we change p
*/
for(int a = 0; a<p.length; a++) {
if(p[a]<0) {
p[a] = 0;
} else if(p[a]>1) {
p[a] = 1;
}
}
}
//validate the new positions:
boolean checkAgain = true;
while(checkAgain) {
checkAgain = false;
for(int a = 0; a<p.length-1; a++) {
if(p[a]==p[a+1])
return false; //we can't make two equal
if(p[a]>p[a+1]) {
checkAgain = true;
float swap1 = p[a];
p[a] = p[a+1];
p[a+1] = swap1;
Object swap2 = c[a];
c[a] = c[a+1];
c[a+1] = swap2;
if(a==state.selectedThumb) {
state.selectedThumb = a+1;
} else if(a+1==state.selectedThumb) {
state.selectedThumb = a;
}
}
}
}
return true;
}
FocusListener focusListener = new FocusListener() {
public void focusLost(FocusEvent e) {
Component c = (Component)e.getSource();
if( getProperty(slider,"MultiThumbSlider.indicateComponent","true").toString().equals("true") ) {
slider.setSelectedThumb(-1);
}
updateIndication();
c.repaint();
}
public void focusGained(FocusEvent e) {
Component c = (Component)e.getSource();
int i = slider.getSelectedThumb(false);
if(i==-1) {
int direction = 1;
if(slider.getOrientation()==MultiThumbSlider.VERTICAL)
direction *= -1;
if(slider.isInverted())
direction *= -1;
slider.setSelectedThumb( (direction==1) ? 0 : slider.getThumbCount()-1 );
}
updateIndication();
c.repaint();
}
};
/** This will try to add a thumb between index1 and index2.
* <P>This method will not add a thumb if there is already a very
* small distance between these two endpoints
*
* @param index1
* @param index2
* @return true if a new thumb was added
*/
protected boolean addThumb(int index1,int index2) {
float pos1 = 0;
float pos2 = 1;
int min;
int max;
if(index1<index2) {
min = index1;
max = index2;
} else {
min = index2;
max = index1;
}
float[] positions = slider.getThumbPositions();
if(min>=0)
pos1 = positions[min];
if(max<positions.length)
pos2 = positions[max];
if(pos2-pos1<.05)
return false;
float newPosition = (pos1+pos2)/2f;
slider.setSelectedThumb(slider.addThumb(newPosition));
return true;
}
KeyListener keyListener = new KeyListener() {
public void keyPressed(KeyEvent e) {
if(slider.isEnabled()==false) return;
if(e.getSource()!=slider)
throw new RuntimeException("only install this UI on the GradientSlider it was constructed with");
int i = slider.getSelectedThumb();
int code = e.getKeyCode();
int orientation = slider.getOrientation();
if( i!=-1 &&
(code==KeyEvent.VK_RIGHT || code==KeyEvent.VK_LEFT) &&
orientation==MultiThumbSlider.HORIZONTAL &&
e.getModifiers()==Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()) {
//insert a new thumb
int i2;
if( (code==KeyEvent.VK_RIGHT && slider.isInverted()==false) ||
(code==KeyEvent.VK_LEFT && slider.isInverted()==true)) {
i2 = i+1;
} else {
i2 = i-1;
}
addThumb(i,i2);
e.consume();
return;
} else if( i!=-1 &&
(code==KeyEvent.VK_UP || code==KeyEvent.VK_DOWN) &&
orientation==MultiThumbSlider.VERTICAL &&
e.getModifiers()==Toolkit.getDefaultToolkit().getMenuShortcutKeyMask()) {
//insert a new thumb
int i2;
if( (code==KeyEvent.VK_UP && slider.isInverted()==false) ||
(code==KeyEvent.VK_DOWN && slider.isInverted()==true)) {
i2 = i+1;
} else {
i2 = i-1;
}
addThumb(i,i2);
e.consume();
return;
} else if(code==KeyEvent.VK_DOWN &&
orientation==MultiThumbSlider.HORIZONTAL &&
i!=-1) {
//popup up!
int x = slider.isInverted() ?
(int)(trackRect.x+trackRect.width*(1-slider.getThumbPositions()[i])) :
(int)(trackRect.x+trackRect.width*slider.getThumbPositions()[i]);
int y = trackRect.y+trackRect.height;
if(slider.doPopup(x, y)) {
e.consume();
return;
}
} else if(code==KeyEvent.VK_RIGHT &&
orientation==MultiThumbSlider.VERTICAL &&
i!=-1) {
//popup up!
int y = slider.isInverted() ?
(int)(trackRect.y+trackRect.height*slider.getThumbPositions()[i]) :
(int)(trackRect.y+trackRect.height*(1-slider.getThumbPositions()[i]));
int x = trackRect.x+trackRect.width;
if(slider.doPopup(x, y)) {
e.consume();
return;
}
}
if(i!=-1) {
//move the selected thumb
if(code==KeyEvent.VK_RIGHT || code==KeyEvent.VK_DOWN) {
nudge(i,1);
e.consume();
} else if(code==KeyEvent.VK_LEFT || code==KeyEvent.VK_UP) {
nudge(i,-1);
e.consume();
} else if(code==KeyEvent.VK_DELETE || code==KeyEvent.VK_BACK_SPACE) {
if(slider.getThumbCount()>2) {
slider.removeThumb(i);
e.consume();
}
} else if(code==KeyEvent.VK_SPACE || code==KeyEvent.VK_ENTER) {
slider.doDoubleClick(-1, -1);
}
}
}
public void keyReleased(KeyEvent e) {}
public void keyTyped(KeyEvent e) {}
};
PropertyChangeListener propertyListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
String name = e.getPropertyName();
if(name.equals(MultiThumbSlider.VALUES_PROPERTY) ||
name.equals(MultiThumbSlider.ORIENTATION_PROPERTY) ||
name.equals(MultiThumbSlider.INVERTED_PROPERTY)) {
calculateGeometry();
slider.repaint();
} else if(name.equals(MultiThumbSlider.SELECTED_THUMB_PROPERTY) ||
name.equals(MultiThumbSlider.PAINT_TICKS_PROPERTY)) {
slider.repaint();
} else if(name.equals("MultiThumbSlider.indicateComponent")) {
setMouseInside(mouseInside);
slider.repaint();
}
}
};
ComponentListener compListener = new ComponentListener() {
public void componentHidden(ComponentEvent e) {}
public void componentMoved(ComponentEvent e) {}
public void componentResized(ComponentEvent e) {
calculateGeometry();
Component c = (Component)e.getSource();
c.repaint();
}
public void componentShown(ComponentEvent e) {}
};
protected void updateIndication() {
synchronized(MultiThumbSliderUI.this) {
if(slider.isEnabled() && (slider.hasFocus() || mouseInside)) {
indicationGoal = 1;
} else {
indicationGoal = 0;
}
if(getProperty(slider,"MultiThumbSlider.indicateComponent","true").equals("false")) {
//always turn on the "indication", so controls are always visible
indicationGoal = 1;
if(slider.isVisible()==false) { //when the component isn't yet initialized
indication = 1; //initialize it to fully indicated
}
}
if(indication!=indicationGoal) {
if(animatingThread==null || animatingThread.isAlive()==false) {
animatingThread = new Thread(animatingRunnable);
animatingThread.start();
}
}
}
}
protected synchronized void calculateGeometry() {
trackRect = calculateTrackRect();
float[] pos = slider.getThumbPositions();
if(thumbPositions.length!=pos.length) {
thumbPositions = new int[pos.length];
thumbIndications = new float[pos.length];
}
if(slider.getOrientation()==MultiThumbSlider.HORIZONTAL) {
for(int a = 0; a<thumbPositions.length; a++) {
if(slider.isInverted()==false) {
thumbPositions[a] = trackRect.x+(int)(trackRect.width*pos[a]);
} else {
thumbPositions[a] = trackRect.x+(int)(trackRect.width*(1-pos[a]));
}
thumbIndications[a] = 0;
}
} else {
for(int a = 0; a<thumbPositions.length; a++) {
if(slider.isInverted()) {
thumbPositions[a] = trackRect.y+(int)(trackRect.height*pos[a]);
} else {
thumbPositions[a] = trackRect.y+(int)(trackRect.height*(1-pos[a]));
}
thumbIndications[a] = 0;
}
}
}
protected Rectangle calculateTrackRect() {
Insets i = new Insets(5,5,5,5);
int w, h;
if(slider.getOrientation()==MultiThumbSlider.HORIZONTAL) {
w = slider.getWidth()-i.left-i.right;
h = Math.min(DEPTH, slider.getHeight()-i.top-i.bottom);
} else {
h = slider.getHeight()-i.top-i.bottom;
w = Math.min(DEPTH, slider.getWidth()-i.left-i.right);
}
return new Rectangle(slider.getWidth()/2-w/2,slider.getHeight()/2-h/2, w, h);
}
private void nudge(int thumbIndex,int direction) {
float pixelFraction;
if(slider.getOrientation()==MultiThumbSlider.HORIZONTAL) {
pixelFraction = 1f/(trackRect.width);
} else {
pixelFraction = 1f/(trackRect.height);
}
if(direction<0)
pixelFraction *= -1;
if(slider.isInverted())
pixelFraction *= -1;
if(slider.getOrientation()==MultiThumbSlider.VERTICAL)
pixelFraction *= -1;
//repeat a couple of times: it's possible we'll nudge two values
//so they're exactly equal, which will make validate() fail.
//in that case: move the value ANOTHER nudge to the left/right
//to really make a change. But make sure we still respect the [0,1] limits.
State state = new State();
while(state.positions[thumbIndex]>=0 && state.positions[thumbIndex]<=1) {
state.positions[thumbIndex]+=pixelFraction;
if(validatePositions(state)) {
state.install();
return;
}
}
}
@Override
public void installUI(JComponent slider) {
slider.addMouseListener(this);
slider.addMouseMotionListener(this);
slider.addFocusListener(focusListener);
slider.addKeyListener(keyListener);
slider.addComponentListener(compListener);
slider.addPropertyChangeListener(propertyListener);
}
@Override
public void paint(Graphics g, JComponent slider2) {
if(slider2!=slider)
throw new RuntimeException("only use this UI on the GradientSlider it was constructed with");
Graphics2D g2 = (Graphics2D)g;
int w = slider.getWidth();
int h = slider.getHeight();
if(slider.isOpaque()) {
g.setColor(slider.getBackground());
g.fillRect(0,0,w,h);
}
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_OFF);
paintTrack(g2);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
paintFocus(g2);
paintThumbs(g2);
}
protected abstract void paintTrack(Graphics2D g);
protected abstract void paintFocus(Graphics2D g);
protected abstract void paintThumbs(Graphics2D g);
@Override
public void uninstallUI(JComponent slider) {
slider.removeMouseListener(this);
slider.removeMouseMotionListener(this);
slider.removeFocusListener(focusListener);
slider.removeKeyListener(keyListener);
slider.removeComponentListener(compListener);
slider.removePropertyChangeListener(propertyListener);
super.uninstallUI(slider);
}
}