/* Copyright 2008-2010 Gephi Authors : Mathieu Bastian <mathieu.bastian@gephi.org> Website : http://www.gephi.org This file is part of Gephi. Gephi is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Gephi 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 Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with Gephi. If not, see <http://www.gnu.org/licenses/>. */ package org.gephi.ui.components.gradientslider; import com.bric.swing.ColorPicker; 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; import javax.swing.JColorChooser; import javax.swing.JPopupMenu; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import javax.swing.plaf.ComponentUI; /** 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 org.gephi.ui.components.gradientslider.GradientSliderUI} class. * <P>The UI for each slider is loaded from the UIManager property: "GradientSliderUI". * By default this is "org.gephi.ui.components.gradientslider.GradientSliderUI". */ //Author Jeremy Wood public class GradientSlider extends MultiThumbSlider { private static final long serialVersionUID = 1L; static { if (UIManager.getString("GradientSliderUI") == null) { UIManager.put("GradientSliderUI", "org.gephi.ui.components.gradientslider.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. */ public Object getValue(float pos) { for (int a = 0; a < thumbPositions.length - 1; a++) { if (thumbPositions[a] == pos) { return values[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 (Color) values[0]; } if (pos > thumbPositions[thumbPositions.length - 1]) { return (Color) 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) { 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. * */ 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(); 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() { 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(0); 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); } public Component getFocusableComponent() { return mini.getColorPanel(); } public void setColor(Color c) { mini.setRGB(c.getRed(), c.getGreen(), c.getBlue()); } } /** This shows a mini ColorPicker panel to let the user * change the selected color. */ 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.includeOpacity", "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>. */ 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); } } } public void updateUI() { String name = UIManager.getString("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; } } }