/* * @(#)ColorPicker.java * * $Date: 2012-07-03 01:10:05 -0500 (Tue, 03 Jul 2012) $ * * 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 ale.util.colors.bric.swing; import java.awt.Color; import java.awt.Dimension; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; 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 ale.util.colors.bric.plaf.ColorPickerSliderUI; import ale.view.gui.util.GUIStrings; /** * 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 = GUIStrings.getCurrentLocale(); /** * 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(Color originalColor) { return showDialog(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(Color originalColor, boolean includeOpacity) { return showDialog(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(String title, Color originalColor, boolean includeOpacity) { ColorPickerDialog d; d = new ColorPickerDialog(originalColor, includeOpacity); d.setTitle(title == null ? strings.getString("ColorPickerDialogTitle") : 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() { @Override public void stateChanged(ChangeEvent e) { Object src = e.getSource(); if (ColorPicker.this.hue.contains(src) || ColorPicker.this.sat.contains(src) || ColorPicker.this.bri.contains(src)) { if (ColorPicker.this.adjustingSpinners > 0) { return; } setHSB(ColorPicker.this.hue.getFloatValue() / 360f, ColorPicker.this.sat.getFloatValue() / 100f, ColorPicker.this.bri.getFloatValue() / 100f); } else if (ColorPicker.this.red.contains(src) || ColorPicker.this.green.contains(src) || ColorPicker.this.blue.contains(src)) { if (ColorPicker.this.adjustingSpinners > 0) { return; } setRGB(ColorPicker.this.red.getIntValue(), ColorPicker.this.green.getIntValue(), ColorPicker.this.blue.getIntValue()); } else if (src == ColorPicker.this.colorPanel) { if (ColorPicker.this.adjustingColorPanel > 0) { return; } int mode = getMode(); if ((mode == HUE) || (mode == BRI) || (mode == SAT)) { float[] hsb = ColorPicker.this.colorPanel.getHSB(); setHSB(hsb[0], hsb[1], hsb[2]); } else { int[] rgb = ColorPicker.this.colorPanel.getRGB(); setRGB(rgb[0], rgb[1], rgb[2]); } } else if (src == ColorPicker.this.slider) { if (ColorPicker.this.adjustingSlider > 0) { return; } int v = ColorPicker.this.slider.getValue(); Option option = getSelectedOption(); option.setValue(v); } else if (ColorPicker.this.alpha.contains(src)) { if (ColorPicker.this.adjustingOpacity > 0) { return; } int v = ColorPicker.this.alpha.getIntValue(); setOpacity(v); } else if (src == ColorPicker.this.opacitySlider) { if (ColorPicker.this.adjustingOpacity > 0) { return; } setOpacity(ColorPicker.this.opacitySlider.getValue()); } } }; ActionListener actionListener = new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Object src = e.getSource(); if (src == ColorPicker.this.hue.radioButton) { setMode(HUE); } else if (src == ColorPicker.this.bri.radioButton) { setMode(BRI); } else if (src == ColorPicker.this.sat.radioButton) { setMode(SAT); } else if (src == ColorPicker.this.red.radioButton) { setMode(RED); } else if (src == ColorPicker.this.green.radioButton) { setMode(GREEN); } else if (src == ColorPicker.this.blue.radioButton) { setMode(BLUE); } } }; /** * @return the currently selected <code>Option</code> */ private Option getSelectedOption() { int mode = getMode(); if (mode == HUE) { return this.hue; } else if (mode == SAT) { return this.sat; } else if (mode == BRI) { return this.bri; } else if (mode == RED) { return this.red; } else if (mode == GREEN) { return this.green; } else { return this.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) { this.myStamp = stamp; this.text = s; } @Override public void run() { if (SwingUtilities.isEventDispatchThread() == false) { long WAIT = 1500; while ((System.currentTimeMillis() - this.myStamp) < WAIT) { try { long delay = WAIT - (System.currentTimeMillis() - this.myStamp); if (delay < 1) { delay = 1; } Thread.sleep(delay); } catch (Exception e) { Thread.yield(); } } SwingUtilities.invokeLater(this); return; } if (this.myStamp != ColorPicker.this.hexDocListener.lastTimeStamp) { // another event has come along and trumped this one return; } if (this.text.length() > 6) { this.text = this.text.substring(0, 6); } while (this.text.length() < 6) { this.text = this.text + "0"; } if (ColorPicker.this.hexField.getText().equals(this.text)) { return; } int pos = ColorPicker.this.hexField.getCaretPosition(); ColorPicker.this.hexField.setText(this.text); ColorPicker.this.hexField.setCaretPosition(pos); } } HexDocumentListener hexDocListener = new HexDocumentListener(); class HexDocumentListener implements DocumentListener { long lastTimeStamp; @Override public void changedUpdate(DocumentEvent e) { this.lastTimeStamp = System.currentTimeMillis(); if (ColorPicker.this.adjustingHexField > 0) { return; } String s = ColorPicker.this.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(this.lastTimeStamp, s); thread.start(); while ((System.currentTimeMillis() - this.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(strings.getString("alphaLabel"), 255); private Option hue = new Option(strings.getString("hueLabel"), 360); private Option sat = new Option(strings.getString("saturationLabel"), 100); private Option bri = new Option(strings.getString("brightnessLabel"), 100); private Option red = new Option(strings.getString("redLabel"), 255); private Option green = new Option(strings.getString("greenLabel"), 255); private Option blue = new Option(strings.getString("blueLabel"), 255); private ColorSwatch preview = new ColorSwatch(50); private JLabel hexLabel = new JLabel(strings.getString("hexLabel")); 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.getString("opacityLabel")); /** 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[] { this.hue, this.sat, this.bri, this.red, this.green, this.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(this.hexLabel, c); c.gridx++; c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.HORIZONTAL; options.add(this.hexField, c); c.gridy++; c.gridx = 0; c.anchor = GridBagConstraints.EAST; c.fill = GridBagConstraints.NONE; options.add(this.alpha.label, c); c.gridx++; c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.HORIZONTAL; options.add(this.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(this.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(this.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(this.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(this.opacityLabel, c); c.gridx++; c.gridwidth = 2; c.weightx = 1; c.fill = GridBagConstraints.HORIZONTAL; add(this.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); this.expertControls.add(this.preview, c); c.gridy++; c.weighty = 0; c.anchor = GridBagConstraints.CENTER; c.insets = new Insets(normalInsets.top, normalInsets.left, 0, normalInsets.right); this.expertControls.add(options, c); this.preview.setOpaque(true); this.colorPanel.setPreferredSize(new Dimension(this.expertControls.getPreferredSize().height, this.expertControls.getPreferredSize().height)); this.slider.addChangeListener(this.changeListener); this.colorPanel.addChangeListener(this.changeListener); this.slider.setUI(new ColorPickerSliderUI(this.slider, this)); this.hexField.getDocument().addDocumentListener(this.hexDocListener); setMode(BRI); setExpertControlsVisible(showExpertControls); setOpacityVisible(includeOpacity); this.opacitySlider.addChangeListener(this.changeListener); setOpacity(255); setOpaque(this, false); this.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) { this.hexLabel.setVisible(b); this.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) { this.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) { this.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[] { this.hue.getFloatValue() / 360f, this.sat.getFloatValue() / 100f, this.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[] { this.currentRed, this.currentGreen, this.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 (this.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."); } this.adjustingOpacity++; try { this.opacitySlider.setValue(v); this.alpha.spinner.setValue(new Integer(v)); if (this.lastOpacity != v) { firePropertyChange(OPACITY_PROPERTY, new Integer(this.lastOpacity), new Integer(v)); Color c = this.preview.getForeground(); this.preview.setForeground(new Color(c.getRed(), c.getGreen(), c.getBlue(), v)); } this.lastOpacity = v; } finally { this.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)); this.hue.radioButton.setSelected(mode == HUE); this.sat.radioButton.setSelected(mode == SAT); this.bri.radioButton.setSelected(mode == BRI); this.red.radioButton.setSelected(mode == RED); this.green.radioButton.setSelected(mode == GREEN); this.blue.radioButton.setSelected(mode == BLUE); this.colorPanel.setMode(mode); this.adjustingSlider++; try { this.slider.setValue(0); Option option = getSelectedOption(); this.slider.setInverted(mode == HUE); int max = option.getMaximum(); this.slider.setMaximum(max); this.slider.setValue(option.getIntValue()); this.slider.repaint(); if ((mode == HUE) || (mode == SAT) || (mode == BRI)) { setHSB(this.hue.getFloatValue() / 360f, this.sat.getFloatValue() / 100f, this.bri.getFloatValue() / 100f); } else { setRGB(this.red.getIntValue(), this.green.getIntValue(), this.blue.getIntValue()); } } finally { this.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) { this.hue.radioButton.setVisible(b && this.hue.isVisible()); this.sat.radioButton.setVisible(b && this.sat.isVisible()); this.bri.radioButton.setVisible(b && this.bri.isVisible()); this.red.radioButton.setVisible(b && this.red.isVisible()); this.green.radioButton.setVisible(b && this.green.isVisible()); this.blue.radioButton.setVisible(b && this.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 = this.adjustingSpinners == 0; this.adjustingSpinners++; this.adjustingColorPanel++; int alpha = this.alpha.getIntValue(); try { if (updateRGBSpinners) { this.red.setValue(r); this.green.setValue(g); this.blue.setValue(b); } this.preview.setForeground(new Color(r, g, b, alpha)); float[] hsb = new float[3]; Color.RGBtoHSB(r, g, b, hsb); this.hue.setValue((int) ((hsb[0] * 360f) + .49f)); this.sat.setValue((int) ((hsb[1] * 100f) + .49f)); this.bri.setValue((int) ((hsb[2] * 100f) + .49f)); this.colorPanel.setRGB(r, g, b); updateHexField(); updateSlider(); } finally { this.adjustingSpinners--; this.adjustingColorPanel--; } this.currentRed = r; this.currentGreen = g; this.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], this.opacitySlider.getValue()); } private void updateSlider() { this.adjustingSlider++; try { int mode = getMode(); if (mode == HUE) { this.slider.setValue(this.hue.getIntValue()); } else if (mode == SAT) { this.slider.setValue(this.sat.getIntValue()); } else if (mode == BRI) { this.slider.setValue(this.bri.getIntValue()); } else if (mode == RED) { this.slider.setValue(this.red.getIntValue()); } else if (mode == GREEN) { this.slider.setValue(this.green.getIntValue()); } else if (mode == BLUE) { this.slider.setValue(this.blue.getIntValue()); } } finally { this.adjustingSlider--; } this.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 this.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) { this.red.setVisible(b); this.green.setVisible(b); this.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) { this.hue.setVisible(b); this.sat.setVisible(b); this.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) { this.opacityLabel.setVisible(b); this.opacitySlider.setVisible(b); this.alpha.label.setVisible(b); this.alpha.spinner.setVisible(b); } /** @return the <code>ColorPickerPanel</code> this <code>ColorPicker</code> displays. */ public ColorPickerPanel getColorPanel() { return this.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 = this.adjustingSpinners == 0; this.adjustingSpinners++; this.adjustingColorPanel++; try { if (updateHSBSpinners) { this.hue.setValue((int) ((h * 360f) + .49f)); this.sat.setValue((int) ((s * 100f) + .49f)); this.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); this.preview.setForeground(c); this.currentRed = c.getRed(); this.currentGreen = c.getGreen(); this.currentBlue = c.getBlue(); this.red.setValue(this.currentRed); this.green.setValue(this.currentGreen); this.blue.setValue(this.currentBlue); this.colorPanel.setHSB(h, s, b); updateHexField(); updateSlider(); this.slider.repaint(); } finally { this.adjustingSpinners--; this.adjustingColorPanel--; } Color newColor = getColor(); if (lastColor.equals(newColor) == false) { firePropertyChange(SELECTED_COLOR_PROPERTY, lastColor, newColor); } } private void updateHexField() { this.adjustingHexField++; try { int r = this.red.getIntValue(); int g = this.green.getIntValue(); int b = this.blue.getIntValue(); int i = (r << 16) + (g << 8) + b; String s = Integer.toHexString(i).toUpperCase(); while (s.length() < 6) { s = "0" + s; } if (this.hexField.getText().equalsIgnoreCase(s) == false) { this.hexField.setText(s); } } finally { this.adjustingHexField--; } } class Option { JRadioButton radioButton = new JRadioButton(); JSpinner spinner; JSlider slider; JLabel label; public Option(String text, int max) { this.spinner = new JSpinner(new SpinnerNumberModel(0, 0, max, 5)); this.spinner.addChangeListener(ColorPicker.this.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()); this.label = new JLabel(text); this.radioButton.addActionListener(ColorPicker.this.actionListener); } public void setValue(int i) { if (this.slider != null) { this.slider.setValue(i); } if (this.spinner != null) { this.spinner.setValue(new Integer(i)); } } public int getMaximum() { if (this.slider != null) { return this.slider.getMaximum(); } return ((Number) ((SpinnerNumberModel) this.spinner.getModel()).getMaximum()).intValue(); } public boolean contains(Object src) { return ((src == this.slider) || (src == this.spinner) || (src == this.radioButton) || (src == this.label)); } public float getFloatValue() { return getIntValue(); } public int getIntValue() { if (this.slider != null) { return this.slider.getValue(); } return ((Number) this.spinner.getValue()).intValue(); } public boolean isVisible() { return this.label.isVisible(); } public void setVisible(boolean b) { boolean radioButtonsAllowed = true; Boolean z = (Boolean) getClientProperty(MODE_CONTROLS_VISIBLE_PROPERTY); if (z != null) { radioButtonsAllowed = z.booleanValue(); } this.radioButton.setVisible(b && radioButtonsAllowed); if (this.slider != null) { this.slider.setVisible(b); } if (this.spinner != null) { this.spinner.setVisible(b); } this.label.setVisible(b); } } }