blob: 5a94abc2cb4e42846ee0cb8886c528cf6420084a [file] [log] [blame]
/*
* Copyright 2002-2007 Sun Microsystems, Inc. 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. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package org.jdesktop.swingx.designer.paint;
import java.awt.Color;
import java.awt.MultipleGradientPaint.CycleMethod;
import java.awt.Paint;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/** @author rbair */
public abstract class AbstractGradient extends PaintModel {
private final Comparator<GradientStop> sorter = new Comparator<GradientStop>() {
public int compare(GradientStop s1, GradientStop s2) {
//since a float value may be -.001 or .001, and since casting
//this to an int will round off to 0, I have to do a more direct
//comparison
float v = s1.getPosition() - s2.getPosition();
if (v < 0) return -1;
else if (v == 0) return 0;
else return 1;
}
};
private PropertyChangeListener stopListener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals("position")) {
if (stops.contains((GradientStop) evt.getSource())) {
resortModel(true);
} else {
System.err.println("[WARNING] The position of an orphaned stop was changed.");
}
} else {
firePropertyChange("paint", null, getPaint());
}
}
};
private List<GradientStop> stops = new ArrayList<GradientStop>();
private List<GradientStop> unmodifiable;
private CycleMethod cycleMethod;
protected AbstractGradient() {
unmodifiable = Collections.unmodifiableList(stops);
cycleMethod = CycleMethod.NO_CYCLE;
setStops(new GradientStop(0, new Matte(Color.BLUE, null)),
new GradientStop(1, new Matte(Color.WHITE, null)));
}
/**
* Copy stops and cycleMethod from src to dst
*
* @param dst The gradient to update to same stops and cycle method as this gradient
*/
protected void copyTo(AbstractGradient dst) {
dst.stops.clear();
List<GradientStop> stops = new ArrayList<GradientStop>();
for (GradientStop stop : this.stops) {
stops.add(stop.clone());
}
dst.setStops(stops);
dst.cycleMethod = this.cycleMethod;
}
public PaintControlType getPaintControlType() {
return PaintControlType.control_line;
}
public void setCycleMethod(CycleMethod method) {
CycleMethod old = cycleMethod;
Paint oldp = getPaint();
cycleMethod = method == null ? CycleMethod.NO_CYCLE : method;
firePropertyChange("cycleMethod", old, cycleMethod);
firePropertyChange("paint", oldp, getPaint());
}
public final CycleMethod getCycleMethod() {
return cycleMethod;
}
public void setStops(GradientStop... stops) {
if (stops == null || stops.length < 1) {
throw new IllegalArgumentException("Must have more than one stop");
}
List<GradientStop> old = new ArrayList<GradientStop>(this.stops);
for (GradientStop stop : old) {
stop.removePropertyChangeListener(stopListener);
}
Paint oldp = getPaint();
this.stops.clear();
Collections.addAll(this.stops, stops);
for (GradientStop stop : this.stops) {
stop.addPropertyChangeListener(stopListener);
}
resortModel(false);
firePropertyChange("stops", old, getStops());
firePropertyChange("paint", oldp, getPaint());
}
public final void setStops(List<GradientStop> stops) {
setStops(stops == null ? null : stops.toArray(new GradientStop[0]));
}
public final List<GradientStop> getStops() {
return unmodifiable;
}
private void resortModel(boolean fireEvent) {
Collections.sort(this.stops, sorter);
if (fireEvent) {
Paint oldp = getPaint();
firePropertyChange("stops", null, getStops());
firePropertyChange("paint", oldp, getPaint());
}
}
//adds a new stop, and interoplates the proper color to use based on
//its position
public GradientStop addStop(float position) {
GradientStop prevStop = null;
GradientStop nextStop = null;
for (GradientStop stop : stops) {
if (stop.getPosition() <= position) {
prevStop = stop;
} else if (stop.getPosition() >= position) {
nextStop = stop;
}
}
Matte c = null;
if (prevStop != null && nextStop != null) {
//interpolate the value of c
c = interpolate(prevStop.getColor(), nextStop.getColor(),
position / (nextStop.getPosition() - prevStop.getPosition()));
} else if (prevStop != null) {
c = prevStop.getColor().clone();
} else if (nextStop != null) {
c = nextStop.getColor().clone();
}
return addStop(position, c);
}
public GradientStop addStop(float position, Matte color) {
GradientStop s = new GradientStop(position, color);
s.addPropertyChangeListener(stopListener);
List<GradientStop> old = new ArrayList<GradientStop>(stops);
Paint oldp = getPaint();
stops.add(s);
resortModel(false);
firePropertyChange("stops", old, getStops());
firePropertyChange("paint", oldp, getPaint());
return s;
}
public GradientStop removeStop(GradientStop s) {
List<GradientStop> old = new ArrayList<GradientStop>(stops);
Paint oldp = getPaint();
stops.remove(s);
s.removePropertyChangeListener(stopListener);
resortModel(false);
firePropertyChange("stops", old, getStops());
firePropertyChange("paint", oldp, getPaint());
return s;
}
@Override public Paint getPaint() {
if (stops.size() == 0) {
return null;
}
//there are stops.size() number of main stops. Between each is
//a fractional stop. Thus, there are:
//stops.size() + stops.size() - 1
//number of fractions and colors.
float[] fractions = new float[stops.size() + stops.size() - 1];
Matte[] colors = new Matte[fractions.length];
//for each stop, create the stop and it's associated fraction
int index = 0; // the index into fractions and colors
for (int i = 0; i < stops.size(); i++) {
GradientStop s = stops.get(i);
//copy over the stop's data
colors[index] = s.getColor();
fractions[index] = s.getPosition();
//If this isn't the last stop, then add in the fraction
if (index < fractions.length - 1) {
float f1 = s.getPosition();
float f2 = stops.get(i + 1).getPosition();
index++;
fractions[index] = f1 + (f2 - f1) * s.getMidpoint();
colors[index] = interpolate(colors[index - 1], stops.get(i + 1).getColor(), .5f);
}
index++;
}
for (int i = 1; i < fractions.length; i++) {
//to avoid an error with LinearGradientPaint where two fractions
//are identical, bump up the fraction value by a miniscule amount
//if it is identical to the previous one
//NOTE: The <= is critical because the previous value may already
//have been bumped up
if (fractions[i] <= fractions[i - 1]) {
fractions[i] = fractions[i - 1] + .000001f;
}
}
//another boundary condition where multiple stops are all at the end. The
//previous loop bumped all but one of these past 1.0, which is bad.
//so remove any fractions (and their colors!) that are beyond 1.0
int outOfBoundsIndex = -1;
for (int i = 0; i < fractions.length; i++) {
if (fractions[i] > 1) {
outOfBoundsIndex = i;
break;
}
}
if (outOfBoundsIndex >= 0) {
float[] f = fractions;
Matte[] c = colors;
fractions = new float[outOfBoundsIndex];
colors = new Matte[outOfBoundsIndex];
System.arraycopy(f, 0, fractions, 0, outOfBoundsIndex);
System.arraycopy(c, 0, colors, 0, outOfBoundsIndex);
}
return createPaint(fractions, colors, cycleMethod);
}
protected abstract Paint createPaint(float[] fractions, Matte[] colors, CycleMethod method);
protected static Matte interpolate(Matte v0, Matte v1, float fraction) {
return new Matte(interpolate(v0.getColor(), v1.getColor(), fraction), v0.getUiDefaults());
}
protected static Color interpolate(Color v0, Color v1, float fraction) {
int r = v0.getRed() +
(int) ((v1.getRed() - v0.getRed()) * fraction + 0.5f);
int g = v0.getGreen() +
(int) ((v1.getGreen() - v0.getGreen()) * fraction + 0.5f);
int b = v0.getBlue() +
(int) ((v1.getBlue() - v0.getBlue()) * fraction + 0.5f);
int a = v0.getAlpha() +
(int) ((v1.getAlpha() - v0.getAlpha()) * fraction + 0.5f);
return new Color(r, g, b, a);
}
}