/*
* Get Organized - Organize your schedule, course assignments, and grades
* Copyright © 2012 Alex Laird
* getorganized@alexlaird.com
* alexlaird.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* @(#)ColorPicker.java 1.0 2008-03-01
*
* Copyright (c) 2008 Jeremy Wood
* E-mail: mickleness@gmail.com
* 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.
*/
package adl.go.gui.colorpicker;
import adl.go.gui.ViewPanel;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dialog;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Frame;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ButtonGroup;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
/**
* This is a panel that offers a robust set of controls to pick a color. <P>This
* was originally intended to replace the
* <code>JColorChooser</code>. To use this class to create a color choosing
* dialog, simply call:
* <BR><code>ColorPicker.showDialog(frame, originalColor);</code> <P>However
* this panel is also resizable, and it can exist in other contexts. For
* example, you might try the following panel:
* <BR><code>ColorPicker picker = new ColorPicker(false, false);</code>
* <BR><code>picker.setPreferredSize(new Dimension(200,160));</code>
* <BR><code>picker.setMode(ColorPicker.HUE);</code> <P>This will create a
* miniature color picker that still lets the user choose from every available
* color, but it does not include all the buttons and numeric controls on the
* right side of the panel. This might be ideal if you are working with limited
* space, or non-power-users who don't need the RGB values of a color. The
* <code>main()</code> method of this class demonstrates possible ways you can
* customize a
* <code>ColorPicker</code> component. <P>To listen to color changes to this
* panel, you can add a
* <code>PropertyChangeListener</code> listening for changes to the
* <code>SELECTED_COLOR_PROPERTY</code>. This will be triggered only when the
* RGB value of the selected color changes. <P>To listen to opacity changes to
* this panel, use a
* <code>PropertyChangeListener</code> listening for changes to the
* <code>OPACITY_PROPERTY</code>.
*
* @version 1.2
* @author Jeremy Wood
*/
public final class ColorPicker extends JPanel
{
private static final long serialVersionUID = 3L;
public static Color showDialog(Container owner, Color originalColor, ViewPanel viewPanel)
{
if (owner instanceof Window)
{
return showDialog ((Window) owner, originalColor, viewPanel);
}
else
{
Logger.getLogger (ColorPicker.class.getName ()).log (Level.SEVERE, "Not a Window subclass: {0}", owner);
Toolkit.getDefaultToolkit ().beep ();
}
return null;
}
/**
* This creates a modal dialog prompting the user to select a color. <P>This
* uses a generic dialog title: "Choose a Color", and does not include
* opacity.
*
* @param owner the dialog this new dialog belongs to. This must be a Frame
* or a Dialog. Java 1.6 supports Windows here, but this package is
* designed/compiled to work in Java 1.4, so
* an <code>IllegalArgumentException</code> will be thrown if this component
* is a <code>Window</code>.
* @param originalColor the color the <code>ColorPicker</code> initially
* points to.
* @return the <code>Color</code> the user chooses, or <code>null</code> if
* the user cancels the dialog.
*/
public static Color showDialog(Window owner, Color originalColor, ViewPanel viewPanel)
{
return showDialog (owner, null, originalColor, false, viewPanel);
}
/**
* This creates a modal dialog prompting the user to select a color. <P>This
* uses a generic dialog title: "Choose a Color".
*
* @param owner the dialog this new dialog belongs to. This must be a Frame
* or a Dialog. Java 1.6 supports Windows here, but this package is
* designed/compiled to work in Java 1.4, so
* an <code>IllegalArgumentException</code> will be thrown if this component
* is a <code>Window</code>.
* @param originalColor the color the <code>ColorPicker</code> initially
* points to.
* @param includeOpacity whether to add a control for the opacity of the
* color.
* @return the <code>Color</code> the user chooses, or <code>null</code> if
* the user cancels the dialog.
*/
public static Color showDialog(Window owner, Color originalColor, boolean includeOpacity, ViewPanel viewPanel)
{
return showDialog (owner, null, originalColor, includeOpacity, viewPanel);
}
/**
* This creates a modal dialog prompting the user to select a color.
*
* @param owner the dialog this new dialog belongs to. This must be a Frame
* or a Dialog. Java 1.6 supports Windows here, but this package is
* designed/compiled to work in Java 1.4, so
* an <code>IllegalArgumentException</code> will be thrown if this component
* is a <code>Window</code>.
* @param title the title for the dialog.
* @param originalColor the color the <code>ColorPicker</code> initially
* points to.
* @param includeOpacity whether to add a control for the opacity of the
* color.
* @return the <code>Color</code> the user chooses, or <code>null</code> if
* the user cancels the dialog.
*/
public static Color showDialog(Window owner, String title, Color originalColor, boolean includeOpacity, ViewPanel viewPanel)
{
ColorPickerDialog d;
if (owner instanceof Frame || owner == null)
{
d = new ColorPickerDialog ((Frame) owner, originalColor, includeOpacity, viewPanel);
}
else if (owner instanceof Dialog)
{
d = new ColorPickerDialog ((Dialog) owner, originalColor, includeOpacity, viewPanel);
}
else
{
throw new IllegalArgumentException ("the owner (" + owner.getClass ().getName () + ") must be a java.awt.Frame or a java.awt.Dialog");
}
hexLabel.setFont (new Font ("Verdana", Font.BOLD, 11));
d.setTitle (title == null
? "Choose a Color"
: title);
d.pack ();
d.setVisible (true);
return d.getColor ();
}
/**
* <
* code>PropertyChangeEvents</code> will be triggered for this property when
* the selected color changes. <P>(Events are only created when then RGB
* values of the color change. This means, for example, that the change from
* HSB(0,0,0) to HSB(.4,0,0) will <i>not</i> generate events, because when
* the brightness stays zero the RGB color remains (0,0,0). So although the
* hue moved around, the color is still black, so no events are created.)
*
*/
public static final String SELECTED_COLOR_PROPERTY = "selected color";
/**
* <
* code>PropertyChangeEvents</code> will be triggered for this property when
* <code>setModeControlsVisible()</code> is called.
*/
public static final String MODE_CONTROLS_VISIBLE_PROPERTY = "mode controls visible";
/**
* <
* code>PropertyChangeEvents</code> will be triggered when the opacity value
* is adjusted.
*/
public static final String OPACITY_PROPERTY = "opacity";
/**
* <
* code>PropertyChangeEvents</code> will be triggered when the mode changes.
* (That is, when the wheel switches from HUE, SAT, BRI, RED, GREEN, or BLUE
* modes.)
*/
public static final String MODE_PROPERTY = "mode";
/**
* Used to indicate when we're in "hue mode".
*/
protected static final int HUE = 0;
/**
* Used to indicate when we're in "brightness mode".
*/
protected static final int BRI = 1;
/**
* Used to indicate when we're in "saturation mode".
*/
protected static final int SAT = 2;
/**
* Used to indicate when we're in "red mode".
*/
protected static final int RED = 3;
/**
* Used to indicate when we're in "green mode".
*/
protected static final int GREEN = 4;
/**
* Used to indicate when we're in "blue mode".
*/
protected static final int BLUE = 5;
/**
* The vertical slider
*/
private JSlider slider = new JSlider (JSlider.VERTICAL, 0, 100, 0);
ChangeListener changeListener = new ChangeListener ()
{
@Override
public void stateChanged(ChangeEvent e)
{
Object src = e.getSource ();
if (hue.contains (src) || sat.contains (src) || bri.contains (src))
{
if (adjustingSpinners > 0)
{
return;
}
setHSB (hue.getFloatValue () / 360f,
sat.getFloatValue () / 100f,
bri.getFloatValue () / 100f);
}
else if (red.contains (src) || green.contains (src) || blue.contains (src))
{
if (adjustingSpinners > 0)
{
return;
}
setRGB (red.getIntValue (),
green.getIntValue (),
blue.getIntValue ());
}
else if (src == colorPanel)
{
if (adjustingColorPanel > 0)
{
return;
}
int mode = getMode ();
if (mode == HUE || mode == BRI || mode == SAT)
{
float[] hsb = colorPanel.getHSB ();
setHSB (hsb[0], hsb[1], hsb[2]);
}
else
{
int[] rgb = colorPanel.getRGB ();
setRGB (rgb[0], rgb[1], rgb[2]);
}
}
else if (src == slider)
{
if (adjustingSlider > 0)
{
return;
}
int v = slider.getValue ();
Option option = getSelectedOption ();
option.setValue (v);
}
else if (alpha.contains (src))
{
if (adjustingOpacity > 0)
{
return;
}
int v = alpha.getIntValue ();
setOpacity (((float) v) / 255f);
}
else if (src == opacitySlider)
{
if (adjustingOpacity > 0)
{
return;
}
float newValue = (((float) opacitySlider.getValue ()) / 255f);
setOpacity (newValue);
}
}
};
ActionListener actionListener = new ActionListener ()
{
@Override
public void actionPerformed(ActionEvent e)
{
Object src = e.getSource ();
if (src == hue.radioButton)
{
setMode (HUE);
}
else if (src == bri.radioButton)
{
setMode (BRI);
}
else if (src == sat.radioButton)
{
setMode (SAT);
}
else if (src == red.radioButton)
{
setMode (RED);
}
else if (src == green.radioButton)
{
setMode (GREEN);
}
else if (src == blue.radioButton)
{
setMode (BLUE);
}
}
};
/**
* @return the currently selected <code>Option</code>
*/
private Option getSelectedOption()
{
int mode = getMode ();
if (mode == HUE)
{
return hue;
}
else if (mode == SAT)
{
return sat;
}
else if (mode == BRI)
{
return bri;
}
else if (mode == RED)
{
return red;
}
else if (mode == GREEN)
{
return green;
}
else
{
return blue;
}
}
/**
* This thread will wait a second or two before committing the text in the
* hex TextField. This gives the user a chance to finish typing... but if
* the user is just waiting for something to happen, this makes sure after a
* second or two something happens.
*/
class HexUpdateThread extends Thread
{
long myStamp;
String text;
public HexUpdateThread(long stamp, String s)
{
myStamp = stamp;
text = s;
}
@Override
public void run()
{
if (SwingUtilities.isEventDispatchThread () == false)
{
long WAIT = 1500;
while (System.currentTimeMillis () - myStamp < WAIT)
{
try
{
long delay = WAIT - (System.currentTimeMillis () - myStamp);
if (delay < 1)
{
delay = 1;
}
Thread.sleep (delay);
}
catch (Exception e)
{
Thread.yield ();
}
}
SwingUtilities.invokeLater (this);
return;
}
if (myStamp != hexDocListener.lastTimeStamp)
{
//another event has come along and trumped this one
return;
}
if (text.length () > 6)
{
text = text.substring (0, 6);
}
while (text.length () < 6)
{
text = text + "0";
}
if (hexField.getText ().equals (text))
{
return;
}
int pos = hexField.getCaretPosition ();
hexField.setText (text);
hexField.setCaretPosition (pos);
}
}
HexDocumentListener hexDocListener = new HexDocumentListener ();
class HexDocumentListener implements DocumentListener
{
long lastTimeStamp;
@Override
public void changedUpdate(DocumentEvent e)
{
lastTimeStamp = System.currentTimeMillis ();
if (adjustingHexField > 0)
{
return;
}
String s = hexField.getText ();
s = stripToHex (s);
if (s.length () == 6)
{
//the user typed 6 digits: we can work with this:
try
{
int i = Integer.parseInt (s, 16);
setRGB (((i >> 16) & 0xff), ((i >> 8) & 0xff), ((i) & 0xff));
return;
}
catch (NumberFormatException e2)
{
}
}
Thread thread = new HexUpdateThread (lastTimeStamp, s);
thread.start ();
while (System.currentTimeMillis () - lastTimeStamp == 0)
{
Thread.yield ();
}
}
/**
* Strips a string down to only uppercase hex-supported characters.
*/
private String stripToHex(String s)
{
s = s.toUpperCase ();
String s2 = "";
for (int a = 0; a < s.length (); a++)
{
char c = s.charAt (a);
if (c == '0' || c == '1' || c == '2' || c == '3' || c == '4' || c == '5'
|| c == '6' || c == '7' || c == '8' || c == '9' || c == '0'
|| c == 'A' || c == 'B' || c == 'C' || c == 'D' || c == 'E' || c == 'F')
{
s2 = s2 + c;
}
}
return s2;
}
@Override
public void insertUpdate(DocumentEvent e)
{
changedUpdate (e);
}
@Override
public void removeUpdate(DocumentEvent e)
{
changedUpdate (e);
}
};
private Option alpha = new Option ("Alpha:", 255);
private Option hue = new Option ("Hue:", 360);
private Option sat = new Option ("Sat:", 100);
private Option bri = new Option ("Bri:", 100);
private Option red = new Option ("Red:", 255);
private Option green = new Option ("Green:", 255);
private Option blue = new Option ("Blue:", 255);
private ColorSwatch preview = new ColorSwatch (50);
private static JLabel hexLabel = new JLabel ("Hex:");
private JTextField hexField = new JTextField ("000000");
/**
* Used to indicate when we're internally adjusting the value of the
* spinners. If this equals zero, then incoming events are triggered by the
* user and must be processed. If this is not equal to zero, then incoming
* events are triggered by another method that's already responding to the
* user's actions.
*/
private int adjustingSpinners = 0;
/**
* Used to indicate when we're internally adjusting the value of the slider.
* If this equals zero, then incoming events are triggered by the user and
* must be processed. If this is not equal to zero, then incoming events are
* triggered by another method that's already responding to the user's
* actions.
*/
private int adjustingSlider = 0;
/**
* Used to indicate when we're internally adjusting the selected color of
* the ColorPanel. If this equals zero, then incoming events are triggered
* by the user and must be processed. If this is not equal to zero, then
* incoming events are triggered by another method that's already responding
* to the user's actions.
*/
private int adjustingColorPanel = 0;
/**
* Used to indicate when we're internally adjusting the value of the hex
* field. If this equals zero, then incoming events are triggered by the
* user and must be processed. If this is not equal to zero, then incoming
* events are triggered by another method that's already responding to the
* user's actions.
*/
private int adjustingHexField = 0;
/**
* Used to indicate when we're internally adjusting the value of the
* opacity. If this equals zero, then incoming events are triggered by the
* user and must be processed. If this is not equal to zero, then incoming
* events are triggered by another method that's already responding to the
* user's actions.
*/
private int adjustingOpacity = 0;
/**
* The "expert" controls are the controls on the right side of this panel:
* the labels/spinners/radio buttons.
*/
private JPanel expertControls = new JPanel (new GridBagLayout ());
private ColorPickerPanel colorPanel = new ColorPickerPanel ();
private JSlider opacitySlider = new JSlider (0, 255, 255);
private JLabel opacityLabel = new JLabel ("Opacity:");
/**
* Create a new
* <code>ColorPicker</code> with all controls visible except opacity.
*/
public ColorPicker()
{
this (true, false);
}
/**
* Create a new
* <code>ColorPicker</code>.
*
* @param showExpertControls the labels/spinners/buttons on the right side
* of a <code>ColorPicker</code> are optional. This boolean will control
* whether they are shown or not. <P>It may be that your users will never
* need or want numeric control when they choose their colors, so hiding
* this may simplify your interface.
* @param includeOpacity whether the opacity controls will be shown
*/
public ColorPicker(boolean showExpertControls, boolean includeOpacity)
{
super (new GridBagLayout ());
GridBagConstraints c = new GridBagConstraints ();
Insets normalInsets = new Insets (3, 3, 3, 3);
JPanel options = new JPanel (new GridBagLayout ());
c.gridx = 0;
c.gridy = 0;
c.weightx = 1;
c.weighty = 1;
c.insets = normalInsets;
ButtonGroup bg = new ButtonGroup ();
//put them in order
Option[] optionsArray = new Option[]
{
hue, sat, bri, red, green, blue
};
for (int a = 0; a < optionsArray.length; a++)
{
if (a == 3 || a == 6)
{
c.insets = new Insets (normalInsets.top + 10, normalInsets.left, normalInsets.bottom, normalInsets.right);
}
else
{
c.insets = normalInsets;
}
c.anchor = GridBagConstraints.EAST;
c.fill = GridBagConstraints.NONE;
options.add (optionsArray[a].label, c);
c.gridx++;
c.anchor = GridBagConstraints.WEST;
c.fill = GridBagConstraints.HORIZONTAL;
if (optionsArray[a].spinner != null)
{
options.add (optionsArray[a].spinner, c);
}
else
{
options.add (optionsArray[a].slider, c);
}
c.gridx++;
c.fill = GridBagConstraints.NONE;
options.add (optionsArray[a].radioButton, c);
c.gridy++;
c.gridx = 0;
bg.add (optionsArray[a].radioButton);
}
c.insets = new Insets (normalInsets.top + 10, normalInsets.left, normalInsets.bottom, normalInsets.right);
c.anchor = GridBagConstraints.EAST;
c.fill = GridBagConstraints.NONE;
options.add (hexLabel, c);
c.gridx++;
c.anchor = GridBagConstraints.WEST;
c.fill = GridBagConstraints.HORIZONTAL;
options.add (hexField, c);
c.gridy++;
c.gridx = 0;
c.anchor = GridBagConstraints.EAST;
c.fill = GridBagConstraints.NONE;
options.add (alpha.label, c);
c.gridx++;
c.anchor = GridBagConstraints.WEST;
c.fill = GridBagConstraints.HORIZONTAL;
options.add (alpha.spinner, c);
c.gridx = 0;
c.gridy = 0;
c.weightx = 1;
c.weighty = 1;
c.fill = GridBagConstraints.BOTH;
c.anchor = GridBagConstraints.CENTER;
c.insets = normalInsets;
c.gridwidth = 2;
add (colorPanel, c);
c.gridwidth = 1;
c.insets = normalInsets;
c.gridx += 2;
c.weighty = 1;
c.gridwidth = 1;
c.fill = GridBagConstraints.VERTICAL;
c.weightx = 0;
add (slider, c);
c.gridx++;
c.fill = GridBagConstraints.VERTICAL;
c.gridheight = GridBagConstraints.REMAINDER;
c.anchor = GridBagConstraints.CENTER;
c.insets = new Insets (0, 0, 0, 0);
add (expertControls, c);
c.gridx = 0;
c.gridheight = 1;
c.gridy = 1;
c.weightx = 0;
c.weighty = 0;
c.insets = normalInsets;
c.anchor = GridBagConstraints.CENTER;
add (opacityLabel, c);
c.gridx++;
c.gridwidth = 2;
c.weightx = 1;
c.fill = GridBagConstraints.HORIZONTAL;
add (opacitySlider, c);
c.gridx = 0;
c.gridy = 0;
c.gridheight = 1;
c.gridwidth = 1;
c.fill = GridBagConstraints.BOTH;
c.weighty = 1;
c.anchor = GridBagConstraints.CENTER;
c.weightx = 1;
c.insets = new Insets (normalInsets.top, normalInsets.left + 8, normalInsets.bottom + 10, normalInsets.right + 8);
expertControls.add (preview, c);
c.gridy++;
c.weighty = 0;
c.anchor = GridBagConstraints.CENTER;
c.insets = new Insets (normalInsets.top, normalInsets.left, 0, normalInsets.right);
expertControls.add (options, c);
preview.setOpaque (true);
colorPanel.setPreferredSize (new Dimension (expertControls.getPreferredSize ().height,
expertControls.getPreferredSize ().height));
slider.addChangeListener (changeListener);
colorPanel.addChangeListener (changeListener);
slider.setUI (new ColorPickerSliderUI (slider, this));
hexField.getDocument ().addDocumentListener (hexDocListener);
setMode (BRI);
setExpertControlsVisible (showExpertControls);
setOpacityVisible (includeOpacity);
opacitySlider.addChangeListener (changeListener);
setOpacity (1);
}
/**
* This controls whether the hex field (and label) are visible or not.
* <P>Note this lives inside the "expert controls", so if
* <code>setExpertControlsVisible(false)</code> has been called, then
* calling this method makes no difference: the hex controls will be hidden.
*/
public void setHexControlsVisible(boolean b)
{
hexLabel.setVisible (b);
hexField.setVisible (b);
}
/**
* This controls whether the preview swatch visible or not. <P>Note this
* lives inside the "expert controls", so if
* <code>setExpertControlsVisible(false)</code> has been called, then
* calling this method makes no difference: the swatch will be hidden.
*/
public void setPreviewSwatchVisible(boolean b)
{
preview.setVisible (b);
}
/**
* The labels/spinners/buttons on the right side of a
* <code>ColorPicker</code> are optional. This method will control whether
* they are shown or not. <P>It may be that your users will never need or
* want numeric control when they choose their colors, so hiding this may
* simplify your interface.
*
* @param b whether to show or hide the expert controls.
*/
public void setExpertControlsVisible(boolean b)
{
expertControls.setVisible (b);
}
/**
* @return the current HSB coordinates of this <code>ColorPicker</code>.
* Each value is between [0,1].
*
*/
public float[] getHSB()
{
return new float[]
{
hue.getFloatValue () / 360f,
sat.getFloatValue () / 100f,
bri.getFloatValue () / 100f
};
}
/**
* @return the current RGB coordinates of this <code>ColorPicker</code>.
* Each value is between [0,255].
*
*/
public int[] getRGB()
{
return new int[]
{
red.getIntValue (),
green.getIntValue (),
blue.getIntValue ()
};
}
/**
* Returns the currently selected opacity (a float between 0 and 1).
*
* @return the currently selected opacity (a float between 0 and 1).
*/
public float getOpacity()
{
return ((float) opacitySlider.getValue ()) / 255f;
}
private float lastOpacity = 1;
/**
* Sets the currently selected opacity.
*
* @param v a float between 0 and 1.
*/
public void setOpacity(float v)
{
if (v < 0 || v > 1)
{
throw new IllegalArgumentException ("The opacity (" + v + ") must be between 0 and 1.");
}
adjustingOpacity++;
try
{
int i = (int) (255 * v);
opacitySlider.setValue (i);
alpha.spinner.setValue (new Integer (i));
if (lastOpacity != v)
{
firePropertyChange (OPACITY_PROPERTY, new Float (lastOpacity), new Float (i));
Color c = preview.getForeground ();
preview.setForeground (new Color (c.getRed (), c.getGreen (), c.getBlue (), i));
}
lastOpacity = v;
}
finally
{
adjustingOpacity--;
}
}
/**
* Sets the mode of this
* <code>ColorPicker</code>. This is especially useful if this picker is in
* non-expert mode, so the radio buttons are not visible for the user to
* directly select.
*
* @param mode must be HUE, SAT, BRI, RED, GREEN or BLUE.
*/
public void setMode(int mode)
{
if (!(mode == HUE || mode == SAT || mode == BRI || mode == RED || mode == GREEN || mode == BLUE))
{
throw new IllegalArgumentException ("mode must be HUE, SAT, BRI, REd, GREEN, or BLUE");
}
putClientProperty (MODE_PROPERTY, new Integer (mode));
hue.radioButton.setSelected (mode == HUE);
sat.radioButton.setSelected (mode == SAT);
bri.radioButton.setSelected (mode == BRI);
red.radioButton.setSelected (mode == RED);
green.radioButton.setSelected (mode == GREEN);
blue.radioButton.setSelected (mode == BLUE);
colorPanel.setMode (mode);
adjustingSlider++;
try
{
slider.setValue (0);
Option option = getSelectedOption ();
slider.setInverted (mode == HUE);
int max = option.getMaximum ();
slider.setMaximum (max);
slider.setValue (option.getIntValue ());
slider.repaint ();
if (mode == HUE || mode == SAT || mode == BRI)
{
setHSB (hue.getFloatValue () / 360f,
sat.getFloatValue () / 100f,
bri.getFloatValue () / 100f);
}
else
{
setRGB (red.getIntValue (),
green.getIntValue (),
blue.getIntValue ());
}
}
finally
{
adjustingSlider--;
}
}
/**
* This controls whether the radio buttons that adjust the mode are visible.
* <P>(These buttons appear next to the spinners in the expert controls.)
* <P>Note these live inside the "expert controls", so if
* <code>setExpertControlsVisible(false)</code> has been called, then these
* will never be visible.
*
* @param b
*/
public void setModeControlsVisible(boolean b)
{
hue.radioButton.setVisible (b && hue.isVisible ());
sat.radioButton.setVisible (b && sat.isVisible ());
bri.radioButton.setVisible (b && bri.isVisible ());
red.radioButton.setVisible (b && red.isVisible ());
green.radioButton.setVisible (b && green.isVisible ());
blue.radioButton.setVisible (b && blue.isVisible ());
putClientProperty (MODE_CONTROLS_VISIBLE_PROPERTY, b);
}
/**
* @return the current mode of this <code>ColorPicker</code>. <BR>This will
* return <code>HUE</code>, <code>SAT</code>, <code>BRI</code>,
* <code>RED</code>, <code>GREEN</code>, or <code>BLUE</code>. <P>The
* default mode is <code>BRI</code>, because that provides the most
* aesthetic/recognizable color wheel.
*/
public int getMode()
{
Integer i = (Integer) getClientProperty (MODE_PROPERTY);
if (i == null)
{
return -1;
}
return i.intValue ();
}
/**
* Sets the current color of this
* <code>ColorPicker</code>. This method simply calls
* <code>setRGB()</code> and
* <code>setOpacity()</code>.
*
* @param c the new color to use.
*/
public void setColor(Color c)
{
setRGB (c.getRed (), c.getGreen (), c.getBlue ());
float opacity = ((float) c.getAlpha ()) / 255f;
setOpacity (opacity);
}
/**
* Sets the current color of this
* <code>ColorPicker</code>
*
* @param r the red value. Must be between [0,255].
* @param g the green value. Must be between [0,255].
* @param b the blue value. Must be between [0,255].
*/
public void setRGB(int r, int g, int b)
{
if (r < 0 || r > 255)
{
throw new IllegalArgumentException ("The red value (" + r + ") must be between [0,255].");
}
if (g < 0 || g > 255)
{
throw new IllegalArgumentException ("The green value (" + g + ") must be between [0,255].");
}
if (b < 0 || b > 255)
{
throw new IllegalArgumentException ("The blue value (" + b + ") must be between [0,255].");
}
Color lastColor = getColor ();
boolean updateRGBSpinners = adjustingSpinners == 0;
adjustingSpinners++;
adjustingColorPanel++;
int myAlpha = this.alpha.getIntValue ();
try
{
if (updateRGBSpinners)
{
red.setValue (r);
green.setValue (g);
blue.setValue (b);
}
preview.setForeground (new Color (r, g, b, myAlpha));
float[] hsb = new float[3];
Color.RGBtoHSB (r, g, b, hsb);
hue.setValue ((int) (hsb[0] * 360f + .49f));
sat.setValue ((int) (hsb[1] * 100f + .49f));
bri.setValue ((int) (hsb[2] * 100f + .49f));
colorPanel.setRGB (r, g, b);
updateHexField ();
updateSlider ();
}
finally
{
adjustingSpinners--;
adjustingColorPanel--;
}
Color newColor = getColor ();
if (lastColor.equals (newColor) == false)
{
firePropertyChange (SELECTED_COLOR_PROPERTY, lastColor, newColor);
}
}
/**
* @return the current <code>Color</code> this <code>ColorPicker</code> has
* selected. <P>This is equivalent to: <BR><code>int[] i = getRGB();</code>
* <BR><code>return new Color(i[0], i[1], i[2], opacitySlider.getValue());</code>
*/
public Color getColor()
{
int[] i = getRGB ();
return new Color (i[0], i[1], i[2], opacitySlider.getValue ());
}
private void updateSlider()
{
adjustingSlider++;
try
{
int mode = getMode ();
if (mode == HUE)
{
slider.setValue (hue.getIntValue ());
}
else if (mode == SAT)
{
slider.setValue (sat.getIntValue ());
}
else if (mode == BRI)
{
slider.setValue (bri.getIntValue ());
}
else if (mode == RED)
{
slider.setValue (red.getIntValue ());
}
else if (mode == GREEN)
{
slider.setValue (green.getIntValue ());
}
else if (mode == BLUE)
{
slider.setValue (blue.getIntValue ());
}
}
finally
{
adjustingSlider--;
}
slider.repaint ();
}
/**
* This returns the panel with several rows of spinner controls. <P>Note you
* can also call methods such as
* <code>setRGBControlsVisible()</code> to adjust which controls are
* showing. <P>(This returns the panel this
* <code>ColorPicker</code> uses, so if you put it in another container, it
* will be removed from this
* <code>ColorPicker</code>.)
*
* @return the panel with several rows of spinner controls.
*/
public JPanel getExpertControls()
{
return expertControls;
}
/**
* This shows or hides the RGB spinner controls. <P>Note these live inside
* the "expert controls", so if
* <code>setExpertControlsVisible(false)</code> has been called, then
* calling this method makes no difference: the RGB controls will be hidden.
*
* @param b whether the controls should be visible or not.
*/
public void setRGBControlsVisible(boolean b)
{
red.setVisible (b);
green.setVisible (b);
blue.setVisible (b);
}
/**
* This shows or hides the HSB spinner controls. <P>Note these live inside
* the "expert controls", so if
* <code>setExpertControlsVisible(false)</code> has been called, then
* calling this method makes no difference: the HSB controls will be hidden.
*
* @param b whether the controls should be visible or not.
*/
public void setHSBControlsVisible(boolean b)
{
hue.setVisible (b);
sat.setVisible (b);
bri.setVisible (b);
}
/**
* This shows or hides the alpha controls. <P>Note the alpha spinner live
* inside the "expert controls", so if
* <code>setExpertControlsVisible(false)</code> has been called, then this
* method does not affect that spinner. However, the opacity slider is
* <i>not</i> affected by the visibility of the export controls.
*
* @param b
*/
public void setOpacityVisible(boolean b)
{
opacityLabel.setVisible (b);
opacitySlider.setVisible (b);
alpha.label.setVisible (b);
alpha.spinner.setVisible (b);
}
/**
* @return the <code>ColorPickerPanel</code> this <code>ColorPicker</code>
* displays.
*/
public ColorPickerPanel getColorPanel()
{
return colorPanel;
}
/**
* Sets the current color of this
* <code>ColorPicker</code>
*
* @param h the hue value.
* @param s the saturation value. Must be between [0,1].
* @param b the blue value. Must be between [0,1].
*/
public void setHSB(float h, float s, float b)
{
if (Float.isInfinite (h) || Float.isNaN (h))
{
throw new IllegalArgumentException ("The hue value (" + h + ") is not a valid number.");
}
//hue is cyclic, so it can be any value:
while (h < 0)
{
h++;
}
while (h > 1)
{
h--;
}
if (s < 0 || s > 1)
{
throw new IllegalArgumentException ("The saturation value (" + s + ") must be between [0,1]");
}
if (b < 0 || b > 1)
{
throw new IllegalArgumentException ("The brightness value (" + b + ") must be between [0,1]");
}
Color lastColor = getColor ();
boolean updateHSBSpinners = adjustingSpinners == 0;
adjustingSpinners++;
adjustingColorPanel++;
try
{
if (updateHSBSpinners)
{
hue.setValue ((int) (h * 360f + .49f));
sat.setValue ((int) (s * 100f + .49f));
bri.setValue ((int) (b * 100f + .49f));
}
Color c = new Color (Color.HSBtoRGB (h, s, b));
int myAlpha = this.alpha.getIntValue ();
c = new Color (c.getRed (), c.getGreen (), c.getBlue (), myAlpha);
preview.setForeground (c);
red.setValue (c.getRed ());
green.setValue (c.getGreen ());
blue.setValue (c.getBlue ());
colorPanel.setHSB (h, s, b);
updateHexField ();
updateSlider ();
slider.repaint ();
}
finally
{
adjustingSpinners--;
adjustingColorPanel--;
}
Color newColor = getColor ();
if (lastColor.equals (newColor) == false)
{
firePropertyChange (SELECTED_COLOR_PROPERTY, lastColor, newColor);
}
}
private void updateHexField()
{
adjustingHexField++;
try
{
int r = red.getIntValue ();
int g = green.getIntValue ();
int b = blue.getIntValue ();
int i = (r << 16) + (g << 8) + b;
String s = Integer.toHexString (i).toUpperCase ();
while (s.length () < 6)
{
s = "0" + s;
}
if (hexField.getText ().equalsIgnoreCase (s) == false)
{
hexField.setText (s);
}
}
finally
{
adjustingHexField--;
}
}
class Option
{
JRadioButton radioButton = new JRadioButton ();
JSpinner spinner;
JSlider slider;
JLabel label;
public Option(String text, int max)
{
spinner = new JSpinner (new SpinnerNumberModel (0, 0, max, 5));
spinner.addChangeListener (changeListener);
/*this tries out Tim Boudreaux's new slider UI.
* It's a good UI, but I think for the ColorPicker
* the numeric controls are more useful.
* That is: users who want click-and-drag control to choose
* their colors don't need any of these Option objects
* at all; only power users who may have specific RGB
* values in mind will use these controls: and when they do
* limiting them to a slider is unnecessary.
* That's my current position... of course it may
* not be true in the real world... :)
*/
//slider = new JSlider(0,max);
//slider.addChangeListener(changeListener);
//slider.setUI(new org.netbeans.paint.api.components.PopupSliderUI());
label = new JLabel (text);
label.setFont (new Font ("Verdana", Font.BOLD, 11));
radioButton.addActionListener (actionListener);
}
public void setValue(int i)
{
if (slider != null)
{
slider.setValue (i);
}
if (spinner != null)
{
spinner.setValue (new Integer (i));
}
}
public int getMaximum()
{
if (slider != null)
{
return slider.getMaximum ();
}
return ((Number) ((SpinnerNumberModel) spinner.getModel ()).getMaximum ()).intValue ();
}
public boolean contains(Object src)
{
return (src == slider || src == spinner || src == radioButton || src == label);
}
public float getFloatValue()
{
return getIntValue ();
}
public int getIntValue()
{
if (slider != null)
{
return slider.getValue ();
}
return ((Number) spinner.getValue ()).intValue ();
}
public boolean isVisible()
{
return label.isVisible ();
}
public void setVisible(boolean b)
{
boolean radioButtonsAllowed = true;
Boolean z = (Boolean) getClientProperty (MODE_CONTROLS_VISIBLE_PROPERTY);
if (z != null)
{
radioButtonsAllowed = z.booleanValue ();
}
radioButton.setVisible (b && radioButtonsAllowed);
if (slider != null)
{
slider.setVisible (b);
}
if (spinner != null)
{
spinner.setVisible (b);
}
label.setVisible (b);
}
}
}