/*
* @(#)GradientSlider.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.swing;
import com.bric.plaf.MultiThumbSliderUI;
import com.jhlabs.image.ImageMath;
import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.plaf.ComponentUI;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Window;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.lang.reflect.Constructor;
/** This component lets the user manipulate the colors in a gradient.
* A <code>GradientSlider</code> can contain any number of thumbs. The
* slider itself represents a range of values from zero to one, so the thumbs
* must always be within this range. Each thumb maps to a specific <code>Color</code>.
* <P>There are some specific properties you can set to customize the look-and-feel
* of this slider in the default {@link com.bric.plaf.GradientSliderUI} class.
* <P>The UI for each slider is loaded from the UIManager property: "GradientSliderUI".
* By default this is "com.bric.plaf.GradientSliderUI".
* <p>The {@link GradientSliderDemoHelper} creates this sample graphic:
* <br><img src="https://javagraphics.java.net/blurbs/GradientSlider.png" alt="Screenshot of the GradientSlider">
*
* @see com.bric.swing.GradientSliderDemo
*
*/
public class GradientSlider extends MultiThumbSlider {
private static final long serialVersionUID = 1L;
static {
if(UIManager.getString("GradientSliderUI")==null)
UIManager.put("GradientSliderUI", "com.bric.plaf.GradientSliderUI");
}
/** Create a horizontal <code>GradientSlider</code> that
* represents a gradient from white to black.
*/
public GradientSlider() {
this(HORIZONTAL);
}
/** Create a <code>GradientSlider</code> that represents a
* gradient form white to black.
* @param orientation HORIZONTAL or VERTICAL
*/
public GradientSlider(int orientation) {
this(orientation, new float[] {0f,1f},new Color[] {Color.white, Color.black});
}
/** Create a new <code>GradientSlider</code>.
*
* @param orientation HORIZONTAL or VERTICAL
* @param thumbPositions the initial positions of each thumb
* @param values the initial colors at each position
* @throws IllegalArgumentException if the number of elements in
* <code>thumbPositions</code> does not equal the number of elements
* in <code>values</code>.
*
*/
public GradientSlider(int orientation,float[] thumbPositions,Color[] values) {
super(orientation, thumbPositions, values);
}
/** Returns the Color at the specified position.
*/
@Override
public Object getValue(float pos) {
for(int a = 0; a<thumbPositions.length-1; a++) {
if(thumbPositions[a]<=pos && pos<=thumbPositions[a+1]) {
float v = (pos-thumbPositions[a])/(thumbPositions[a+1]-thumbPositions[a]);
return tween((Color)values[a],(Color)values[a+1],v);
}
}
if(pos<thumbPositions[0]) {
return values[0];
}
if(pos>thumbPositions[thumbPositions.length-1]) {
return values[values.length-1];
}
return null;
}
/** This is identical to <code>getValues()</code>,
* except the return value is an array of <code>Colors</code>.
*/
public Color[] getColors() {
Color[] c = new Color[values.length];
for(int a = 0; a<c.length; a++) {
c[a] = (Color)values[a];
}
return c;
}
private static Color tween( Color c1, Color c2, float p) {
if(p==0)
return c1;
if(p==1)
return c2;
int rgb1 = c1.getRGB();
int rgb2 = c2.getRGB();
int rgb = ImageMath.mixColors(p, rgb1, rgb2);
return new Color(rgb, true);
// return new Color(
// (int)(c1.getRed()*(1-p)+c2.getRed()*(p)),
// (int)(c1.getGreen()*(1-p)+c2.getGreen()*(p)),
// (int)(c1.getBlue()*(1-p)+c2.getBlue()*(p)),
// (int)(c1.getAlpha()*(1-p)+c2.getAlpha()*(p))
// );
}
/** This invokes a <code>ColorPicker</code> dialog to edit
* the thumb at the selected index.
*
*/
@Override
public boolean doDoubleClick(int x,int y) {
int i = getSelectedThumb();
if(i!=-1) {
showColorPicker();
//showJColorChooser();
SwingUtilities.invokeLater(new SelectThumbRunnable(i));
return true;
} else {
return false;
}
}
class SelectThumbRunnable implements Runnable {
int index;
public SelectThumbRunnable(int i) {
index = i;
}
public void run() {
setSelectedThumb(index);
}
}
/** The popup for contextual menus. */
JPopupMenu popup;
private JPopupMenu createPopup() {
return new ColorPickerPopup();
}
abstract class AbstractPopup extends JPopupMenu {
private static final long serialVersionUID = 1L;
int lastSelectedThumb;
PopupMenuListener popupMenuListener = new PopupMenuListener() {
public void popupMenuCanceled(PopupMenuEvent e) {
setValueIsAdjusting(false);
SwingUtilities.invokeLater(new SelectThumbRunnable(lastSelectedThumb));
}
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
setValueIsAdjusting(false);
SwingUtilities.invokeLater(new SelectThumbRunnable(lastSelectedThumb));
}
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
setValueIsAdjusting(true);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
getFocusableComponent().requestFocus();
}
});
}
};
public AbstractPopup() {
addPopupMenuListener(popupMenuListener);
}
public abstract Component getFocusableComponent();
@Override
public void show(Component c,int x,int y) {
Color[] colors = getColors();
lastSelectedThumb = getSelectedThumb();
if(lastSelectedThumb!=-1) {
setColor(colors[lastSelectedThumb]);
super.show(c,x,y);
}
}
public abstract void setColor(Color c);
}
class ColorPickerPopup extends AbstractPopup {
private static final long serialVersionUID = 1L;
ColorPicker mini;
KeyListener commitListener = new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()==KeyEvent.VK_SPACE || e.getKeyCode()==KeyEvent.VK_ENTER) {
ColorPickerPopup.this.setVisible(false);
}
}
};
public ColorPickerPopup() {
super();
boolean includeOpacity = MultiThumbSliderUI.getProperty(GradientSlider.this,"GradientSlider.includeOpacity","true").equals("true");
mini = new ColorPicker(false,includeOpacity);
mini.setMode(ColorPicker.HUE);
mini.setPreferredSize(new Dimension(220,200));
PropertyChangeListener p = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
ColorPicker p = (ColorPicker)evt.getSource();
Color[] colors = getColors();
colors[lastSelectedThumb] = p.getColor();
setValues(getThumbPositions(),colors);
}
};
mini.addPropertyChangeListener(ColorPicker.SELECTED_COLOR_PROPERTY, p);
mini.addPropertyChangeListener(ColorPicker.OPACITY_PROPERTY, p);
for(int a = 0; a<mini.getComponentCount(); a++) {
Component c = mini.getComponent(a);
c.addKeyListener(commitListener);
}
add(mini);
}
@Override
public Component getFocusableComponent() {
return mini.getColorPanel();
}
@Override
public void setColor(Color c) {
mini.setRGB(c.getRed(), c.getGreen(), c.getBlue());
mini.setOpacity(c.getAlpha());
}
}
/** This shows a mini ColorPicker panel to let the user
* change the selected color.
*/
@Override
public boolean doPopup(int x,int y) {
if(popup==null) {
popup = createPopup();
}
popup.show(this, x, y);
return true;
}
private Frame getFrame() {
Window w = SwingUtilities.getWindowAncestor(this);
if(w instanceof Frame) return ((Frame)w);
return null;
}
private boolean showColorPicker() {
Color[] colors = getColors();
int i = getSelectedThumb();
Frame frame = getFrame();
boolean includeOpacity = MultiThumbSliderUI.getProperty(this,"GradientSlider.colorPickerIncludesOpacity","true").equals("true");
colors[i] = ColorPicker.showDialog(frame, colors[i], includeOpacity);
if(colors[i]!=null)
setValues(getThumbPositions(), colors);
return true;
}
/** TODO: If developers don't want to bundle the ColorPicker with their programs,
* they can use this method instead of <code>showColorPicker()</code>.
*/
@SuppressWarnings("unused")
private void showJColorChooser() {
Color[] colors = getColors();
int i = getSelectedThumb();
if(i>=0 && i<colors.length) {
colors[i] = JColorChooser.showDialog(this, "Choose a Color", colors[i]);
if(colors[i]!=null)
setValues(getThumbPositions(), colors);
}
}
@Override
public void updateUI() {
String name = UIManager.getString("GradientSliderUI");
if(name==null) name = "com.bric.plaf.GradientSliderUI";
try {
Class<?> c = Class.forName(name);
Constructor<?>[] constructors = c.getConstructors();
for(int a = 0; a<constructors.length; a++) {
Class<?>[] types = constructors[a].getParameterTypes();
if(types.length==1 && types[0].equals(GradientSlider.class)) {
ComponentUI ui = (ComponentUI)constructors[a].newInstance(new Object[] {this});
setUI(ui);
return;
}
}
} catch(ClassNotFoundException e) {
throw new RuntimeException("The class \""+name+"\" could not be found.");
} catch(Throwable t) {
RuntimeException e = new RuntimeException("The class \""+name+"\" could not be constructed.");
e.initCause(t);
throw e;
}
}
}