/* * @(#)ColorPicker.java * * $Date: 2011-02-24 00:42:26 -0600 (Thu, 24 Feb 2011) $ * * 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: * http://javagraphics.java.net/ * * That site should also contain the most recent official version * of this software. (See the SVN repository for more details.) */ package colorpicker.swing; import java.awt.Color; import java.awt.Dialog; import java.awt.Dimension; import java.awt.Frame; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.Window; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.ResourceBundle; import javax.swing.ButtonGroup; import javax.swing.JComponent; 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; import colorpicker.plaf.ColorPickerSliderUI; /** 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>. * */ public class ColorPicker extends JPanel { private static final long serialVersionUID = 3L; /** The localized strings used in this (and related) panel(s). */ protected static ResourceBundle strings = ResourceBundle.getBundle("colorpicker.swing.resources.ColorPicker"); /** 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) { return showDialog(owner, null, originalColor, false ); } /** 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) { return showDialog(owner, null, originalColor, includeOpacity ); } /** 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) { ColorPickerDialog d; if(owner instanceof Frame || owner==null) { d = new ColorPickerDialog( (Frame)owner, originalColor, includeOpacity); } else if(owner instanceof Dialog){ d = new ColorPickerDialog( (Dialog)owner, originalColor, includeOpacity); } else { throw new IllegalArgumentException("the owner ("+owner.getClass().getName()+") must be a java.awt.Frame or a java.awt.Dialog"); } d.setTitle(title == null ? strings.getObject("ColorPickerDialogTitle").toString() : 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". */ public static final int HUE = 0; /** Used to indicate when we're in "brightness mode". */ public static final int BRI = 1; /** Used to indicate when we're in "saturation mode". */ public static final int SAT = 2; /** Used to indicate when we're in "red mode". */ public static final int RED = 3; /** Used to indicate when we're in "green mode". */ public static final int GREEN = 4; /** Used to indicate when we're in "blue mode". */ public static final int BLUE = 5; /** The vertical slider */ private JSlider slider = new JSlider(JSlider.VERTICAL,0,100,0); private int currentRed = 0; private int currentGreen = 0; private int currentBlue = 0; ChangeListener changeListener = new ChangeListener() { 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( v ); } else if(src==opacitySlider) { if(adjustingOpacity>0) return; setOpacity( opacitySlider.getValue() ); } } }; ActionListener actionListener = new ActionListener() { 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; } 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; 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) { //this shouldn't happen, since we already stripped out non-hex characters. e2.printStackTrace(); } } 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; } public void insertUpdate(DocumentEvent e) { changedUpdate(e); } public void removeUpdate(DocumentEvent e) { changedUpdate(e); } }; private Option alpha = new Option(strings.getObject("alphaLabel").toString(), 255); private Option hue = new Option(strings.getObject("hueLabel").toString(), 360); private Option sat = new Option(strings.getObject("saturationLabel").toString(), 100); private Option bri = new Option(strings.getObject("brightnessLabel").toString(), 100); private Option red = new Option(strings.getObject("redLabel").toString(), 255); private Option green = new Option(strings.getObject("greenLabel").toString(), 255); private Option blue = new Option(strings.getObject("blueLabel").toString(), 255); private ColorSwatch preview = new ColorSwatch(50); private JLabel hexLabel = new JLabel(strings.getObject("hexLabel").toString()); 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(strings.getObject("opacityLabel").toString()); /** 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( 255 ); setOpaque(this,false); preview.setForeground( getColor() ); } private static void setOpaque(JComponent jc,boolean opaque) { if(jc instanceof JTextField) return; jc.setOpaque(false); if(jc instanceof JSpinner) return; for(int a = 0; a<jc.getComponentCount(); a++) { JComponent child = (JComponent)jc.getComponent(a); setOpaque(child,opaque); } } /** 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[] { currentRed, currentGreen, currentBlue }; } /** 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 int lastOpacity = 255; /** Sets the currently selected opacity. * * @param v an int between 0 and 255. */ public void setOpacity(int v) { if(v<0 || v>255) throw new IllegalArgumentException("The opacity ("+v+") must be between 0 and 255."); adjustingOpacity++; try { opacitySlider.setValue( v ); alpha.spinner.setValue( new Integer(v) ); if(lastOpacity!=v) { firePropertyChange(OPACITY_PROPERTY,new Integer(lastOpacity),new Integer(v)); Color c = preview.getForeground(); preview.setForeground(new Color(c.getRed(), c.getGreen(), c.getBlue(), v)); } 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,new Boolean(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()); setOpacity(c.getAlpha()); } /** 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 alpha = this.alpha.getIntValue(); try { if(updateRGBSpinners) { red.setValue(r); green.setValue(g); blue.setValue(b); } preview.setForeground(new Color(r,g,b, alpha)); 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--; } currentRed = r; currentGreen = g; currentBlue = b; 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 alpha = this.alpha.getIntValue(); c = new Color(c.getRed(), c.getGreen(), c.getBlue(), alpha); preview.setForeground(c); currentRed = c.getRed(); currentGreen = c.getGreen(); currentBlue = c.getBlue(); red.setValue(currentRed); green.setValue(currentGreen); blue.setValue(currentBlue); 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); 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); } } }