/*
 * Copyright (c) 1998, 2004, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package javax.swing.colorchooser;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.event.*;
import javax.swing.border.*;
import java.awt.image.*;

Implements the default HSB Color chooser @author Tom Santos @author Steve Wilson @author Mark Davidson @author Shannon Hickey
/** * Implements the default HSB Color chooser * * @author Tom Santos * @author Steve Wilson * @author Mark Davidson * @author Shannon Hickey */
class DefaultHSBChooserPanel extends AbstractColorChooserPanel implements ChangeListener, HierarchyListener { private transient HSBImage palette; private transient HSBImage sliderPalette; private transient Image paletteImage; private transient Image sliderPaletteImage; private JSlider slider; private JSpinner hField; private JSpinner sField; private JSpinner bField; private JTextField redField; private JTextField greenField; private JTextField blueField; private boolean isAdjusting = false; // Flag which indicates that values are set internally private Point paletteSelection = new Point(); private JLabel paletteLabel; private JLabel sliderPaletteLabel; private JRadioButton hRadio; private JRadioButton sRadio; private JRadioButton bRadio; private static final int PALETTE_DIMENSION = 200; private static final int MAX_HUE_VALUE = 359; private static final int MAX_SATURATION_VALUE = 100; private static final int MAX_BRIGHTNESS_VALUE = 100; private int currentMode = HUE_MODE; private static final int HUE_MODE = 0; private static final int SATURATION_MODE = 1; private static final int BRIGHTNESS_MODE = 2; public DefaultHSBChooserPanel() { } private void addPaletteListeners() { paletteLabel.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e ) { float[] hsb = new float[3]; palette.getHSBForLocation( e.getX(), e.getY(), hsb ); updateHSB( hsb[0], hsb[1], hsb[2] ); } }); paletteLabel.addMouseMotionListener(new MouseMotionAdapter() { public void mouseDragged( MouseEvent e ){ int labelWidth = paletteLabel.getWidth(); int labelHeight = paletteLabel.getHeight(); int x = e.getX(); int y = e.getY(); if ( x >= labelWidth ) { x = labelWidth - 1; } if ( y >= labelHeight ) { y = labelHeight - 1; } if ( x < 0 ) { x = 0; } if ( y < 0 ) { y = 0; } float[] hsb = new float[3]; palette.getHSBForLocation( x, y, hsb ); updateHSB( hsb[0], hsb[1], hsb[2] ); } }); } private void updatePalette( float h, float s, float b ) { int x = 0; int y = 0; switch ( currentMode ) { case HUE_MODE: if ( h != palette.getHue() ) { palette.setHue( h ); palette.nextFrame(); } x = PALETTE_DIMENSION - (int)(s * PALETTE_DIMENSION); y = PALETTE_DIMENSION - (int)(b * PALETTE_DIMENSION); break; case SATURATION_MODE: if ( s != palette.getSaturation() ) { palette.setSaturation( s ); palette.nextFrame(); } x = (int)(h * PALETTE_DIMENSION); y = PALETTE_DIMENSION - (int)(b * PALETTE_DIMENSION); break; case BRIGHTNESS_MODE: if ( b != palette.getBrightness() ) { palette.setBrightness( b ); palette.nextFrame(); } x = (int)(h * PALETTE_DIMENSION); y = PALETTE_DIMENSION - (int)(s * PALETTE_DIMENSION); break; } paletteSelection.setLocation( x, y ); paletteLabel.repaint(); } private void updateSlider( float h, float s, float b ) { // Update the slider palette if necessary. // When the slider is the hue slider or the hue hasn't changed, // the hue of the palette will not need to be updated. if (currentMode != HUE_MODE && h != sliderPalette.getHue() ) { sliderPalette.setHue( h ); sliderPalette.nextFrame(); } float value = 0f; switch ( currentMode ) { case HUE_MODE: value = h; break; case SATURATION_MODE: value = s; break; case BRIGHTNESS_MODE: value = b; break; } slider.setValue( Math.round(value * (slider.getMaximum())) ); } private void updateHSBTextFields( float hue, float saturation, float brightness ) { int h = Math.round(hue * 359); int s = Math.round(saturation * 100); int b = Math.round(brightness * 100); if (((Integer)hField.getValue()).intValue() != h) { hField.setValue(new Integer(h)); } if (((Integer)sField.getValue()).intValue() != s) { sField.setValue(new Integer(s)); } if (((Integer)bField.getValue()).intValue() != b) { bField.setValue(new Integer(b)); } }
Updates the values of the RGB fields to reflect the new color change
/** * Updates the values of the RGB fields to reflect the new color change */
private void updateRGBTextFields( Color color ) { redField.setText(String.valueOf(color.getRed())); greenField.setText(String.valueOf(color.getGreen())); blueField.setText(String.valueOf(color.getBlue())); }
Main internal method of updating the ui controls and the color model.
/** * Main internal method of updating the ui controls and the color model. */
private void updateHSB( float h, float s, float b ) { if ( !isAdjusting ) { isAdjusting = true; updatePalette( h, s, b ); updateSlider( h, s, b ); updateHSBTextFields( h, s, b ); Color color = Color.getHSBColor(h, s, b); updateRGBTextFields( color ); getColorSelectionModel().setSelectedColor( color ); isAdjusting = false; } }
Invoked automatically when the model's state changes. It is also called by installChooserPanel to allow you to set up the initial state of your chooser. Override this method to update your ChooserPanel.
/** * Invoked automatically when the model's state changes. * It is also called by <code>installChooserPanel</code> to allow * you to set up the initial state of your chooser. * Override this method to update your <code>ChooserPanel</code>. */
public void updateChooser() { if ( !isAdjusting ) { float[] hsb = getHSBColorFromModel(); updateHSB( hsb[0], hsb[1], hsb[2] ); } } public void installChooserPanel(JColorChooser enclosingChooser) { super.installChooserPanel(enclosingChooser); setInheritsPopupMenu(true); addHierarchyListener(this); }
Invoked when the panel is removed from the chooser.
/** * Invoked when the panel is removed from the chooser. */
public void uninstallChooserPanel(JColorChooser enclosingChooser) { super.uninstallChooserPanel(enclosingChooser); cleanupPalettesIfNecessary(); removeAll(); removeHierarchyListener(this); }
Returns an float array containing the HSB values of the selected color from the ColorSelectionModel
/** * Returns an float array containing the HSB values of the selected color from * the ColorSelectionModel */
private float[] getHSBColorFromModel() { Color color = getColorFromModel(); float[] hsb = new float[3]; Color.RGBtoHSB( color.getRed(), color.getGreen(), color.getBlue(), hsb ); return hsb; }
Builds a new chooser panel.
/** * Builds a new chooser panel. */
protected void buildChooser() { setLayout(new BorderLayout()); JComponent spp = buildSliderPalettePanel(); spp.setInheritsPopupMenu(true); add(spp, BorderLayout.BEFORE_LINE_BEGINS); JPanel controlHolder = new JPanel(new SmartGridLayout(1,3)); JComponent hsbControls = buildHSBControls(); hsbControls.setInheritsPopupMenu(true); controlHolder.add(hsbControls); controlHolder.add(new JLabel(" ")); // spacer JComponent rgbControls = buildRGBControls(); rgbControls.setInheritsPopupMenu(true); controlHolder.add(rgbControls); controlHolder.setInheritsPopupMenu(true); controlHolder.setBorder(new EmptyBorder( 10, 5, 10, 5)); add( controlHolder, BorderLayout.CENTER); }
Creates the panel with the uneditable RGB field
/** * Creates the panel with the uneditable RGB field */
private JComponent buildRGBControls() { JPanel panel = new JPanel(new SmartGridLayout(2,3)); panel.setInheritsPopupMenu(true); Color color = getColorFromModel(); redField = new JTextField( String.valueOf(color.getRed()), 3 ); redField.setEditable(false); redField.setHorizontalAlignment( JTextField.RIGHT ); redField.setInheritsPopupMenu(true); greenField = new JTextField(String.valueOf(color.getGreen()), 3 ); greenField.setEditable(false); greenField.setHorizontalAlignment( JTextField.RIGHT ); greenField.setInheritsPopupMenu(true); blueField = new JTextField( String.valueOf(color.getBlue()), 3 ); blueField.setEditable(false); blueField.setHorizontalAlignment( JTextField.RIGHT ); blueField.setInheritsPopupMenu(true); String redString = UIManager.getString("ColorChooser.hsbRedText"); String greenString = UIManager.getString("ColorChooser.hsbGreenText"); String blueString = UIManager.getString("ColorChooser.hsbBlueText"); panel.add( new JLabel(redString) ); panel.add( redField ); panel.add( new JLabel(greenString) ); panel.add( greenField ); panel.add( new JLabel(blueString) ); panel.add( blueField ); return panel; }
Creates the panel with the editable HSB fields and the radio buttons.
/** * Creates the panel with the editable HSB fields and the radio buttons. */
private JComponent buildHSBControls() { String hueString = UIManager.getString("ColorChooser.hsbHueText"); String saturationString = UIManager.getString("ColorChooser.hsbSaturationText"); String brightnessString = UIManager.getString("ColorChooser.hsbBrightnessText"); RadioButtonHandler handler = new RadioButtonHandler(); hRadio = new JRadioButton(hueString); hRadio.addActionListener(handler); hRadio.setSelected(true); hRadio.setInheritsPopupMenu(true); sRadio = new JRadioButton(saturationString); sRadio.addActionListener(handler); sRadio.setInheritsPopupMenu(true); bRadio = new JRadioButton(brightnessString); bRadio.addActionListener(handler); bRadio.setInheritsPopupMenu(true); ButtonGroup group = new ButtonGroup(); group.add(hRadio); group.add(sRadio); group.add(bRadio); float[] hsb = getHSBColorFromModel(); hField = new JSpinner(new SpinnerNumberModel((int)(hsb[0] * 359), 0, 359, 1)); sField = new JSpinner(new SpinnerNumberModel((int)(hsb[1] * 100), 0, 100, 1)); bField = new JSpinner(new SpinnerNumberModel((int)(hsb[2] * 100), 0, 100, 1)); hField.addChangeListener(this); sField.addChangeListener(this); bField.addChangeListener(this); hField.setInheritsPopupMenu(true); sField.setInheritsPopupMenu(true); bField.setInheritsPopupMenu(true); JPanel panel = new JPanel( new SmartGridLayout(2, 3) ); panel.add(hRadio); panel.add(hField); panel.add(sRadio); panel.add(sField); panel.add(bRadio); panel.add(bField); panel.setInheritsPopupMenu(true); return panel; }
Handler for the radio button classes.
/** * Handler for the radio button classes. */
private class RadioButtonHandler implements ActionListener { public void actionPerformed(ActionEvent evt) { Object obj = evt.getSource(); if (obj instanceof JRadioButton) { JRadioButton button = (JRadioButton)obj; if (button == hRadio) { setMode(HUE_MODE); } else if (button == sRadio) { setMode(SATURATION_MODE); } else if (button == bRadio) { setMode(BRIGHTNESS_MODE); } } } } private void setMode(int mode) { if (currentMode == mode) { return; } isAdjusting = true; // Ensure no events propagate from changing slider value. currentMode = mode; float[] hsb = getHSBColorFromModel(); switch (currentMode) { case HUE_MODE: slider.setInverted(true); slider.setMaximum(MAX_HUE_VALUE); palette.setValues(HSBImage.HSQUARE, hsb[0], 1.0f, 1.0f); sliderPalette.setValues(HSBImage.HSLIDER, 0f, 1.0f, 1.0f); break; case SATURATION_MODE: slider.setInverted(false); slider.setMaximum(MAX_SATURATION_VALUE); palette.setValues(HSBImage.SSQUARE, hsb[0], hsb[1], 1.0f); sliderPalette.setValues(HSBImage.SSLIDER, hsb[0], 1.0f, 1.0f); break; case BRIGHTNESS_MODE: slider.setInverted(false); slider.setMaximum(MAX_BRIGHTNESS_VALUE); palette.setValues(HSBImage.BSQUARE, hsb[0], 1.0f, hsb[2]); sliderPalette.setValues(HSBImage.BSLIDER, hsb[0], 1.0f, 1.0f); break; } isAdjusting = false; palette.nextFrame(); sliderPalette.nextFrame(); updateChooser(); } protected JComponent buildSliderPalettePanel() { // This slider has to have a minimum of 0. A lot of math in this file is simplified due to this. slider = new JSlider(JSlider.VERTICAL, 0, MAX_HUE_VALUE, 0); slider.setInverted(true); slider.setPaintTrack(false); slider.setPreferredSize(new Dimension(slider.getPreferredSize().width, PALETTE_DIMENSION + 15)); slider.addChangeListener(this); slider.setInheritsPopupMenu(true); // We're not painting ticks, but need to ask UI classes to // paint arrow shape anyway, if possible. slider.putClientProperty("Slider.paintThumbArrowShape", Boolean.TRUE); paletteLabel = createPaletteLabel(); addPaletteListeners(); sliderPaletteLabel = new JLabel(); JPanel panel = new JPanel(); panel.add( paletteLabel ); panel.add( slider ); panel.add( sliderPaletteLabel ); initializePalettesIfNecessary(); return panel; } private void initializePalettesIfNecessary() { if (palette != null) { return; } float[] hsb = getHSBColorFromModel(); switch(currentMode){ case HUE_MODE: palette = new HSBImage(HSBImage.HSQUARE, PALETTE_DIMENSION, PALETTE_DIMENSION, hsb[0], 1.0f, 1.0f); sliderPalette = new HSBImage(HSBImage.HSLIDER, 16, PALETTE_DIMENSION, 0f, 1.0f, 1.0f); break; case SATURATION_MODE: palette = new HSBImage(HSBImage.SSQUARE, PALETTE_DIMENSION, PALETTE_DIMENSION, 1.0f, hsb[1], 1.0f); sliderPalette = new HSBImage(HSBImage.SSLIDER, 16, PALETTE_DIMENSION, 1.0f, 0f, 1.0f); break; case BRIGHTNESS_MODE: palette = new HSBImage(HSBImage.BSQUARE, PALETTE_DIMENSION, PALETTE_DIMENSION, 1.0f, 1.0f, hsb[2]); sliderPalette = new HSBImage(HSBImage.BSLIDER, 16, PALETTE_DIMENSION, 1.0f, 1.0f, 0f); break; } paletteImage = Toolkit.getDefaultToolkit().createImage(palette); sliderPaletteImage = Toolkit.getDefaultToolkit().createImage(sliderPalette); paletteLabel.setIcon(new ImageIcon(paletteImage)); sliderPaletteLabel.setIcon(new ImageIcon(sliderPaletteImage)); } private void cleanupPalettesIfNecessary() { if (palette == null) { return; } palette.aborted = true; sliderPalette.aborted = true; palette.nextFrame(); sliderPalette.nextFrame(); palette = null; sliderPalette = null; paletteImage = null; sliderPaletteImage = null; paletteLabel.setIcon(null); sliderPaletteLabel.setIcon(null); } protected JLabel createPaletteLabel() { return new JLabel() { protected void paintComponent( Graphics g ) { super.paintComponent( g ); g.setColor( Color.white ); g.drawOval( paletteSelection.x - 4, paletteSelection.y - 4, 8, 8 ); } }; } public String getDisplayName() { return UIManager.getString("ColorChooser.hsbNameText"); }
Provides a hint to the look and feel as to the KeyEvent.VK constant that can be used as a mnemonic to access the panel. A return value <= 0 indicates there is no mnemonic.

The return value here is a hint, it is ultimately up to the look and feel to honor the return value in some meaningful way.

This implementation looks up the value from the default ColorChooser.hsbMnemonic, or if it isn't available (or not an Integer) returns -1. The lookup for the default is done through the UIManager: UIManager.get("ColorChooser.rgbMnemonic");.

See Also:
Returns:KeyEvent.VK constant identifying the mnemonic; <= 0 for no mnemonic
Since:1.4
/** * Provides a hint to the look and feel as to the * <code>KeyEvent.VK</code> constant that can be used as a mnemonic to * access the panel. A return value <= 0 indicates there is no mnemonic. * <p> * The return value here is a hint, it is ultimately up to the look * and feel to honor the return value in some meaningful way. * <p> * This implementation looks up the value from the default * <code>ColorChooser.hsbMnemonic</code>, or if it * isn't available (or not an <code>Integer</code>) returns -1. * The lookup for the default is done through the <code>UIManager</code>: * <code>UIManager.get("ColorChooser.rgbMnemonic");</code>. * * @return KeyEvent.VK constant identifying the mnemonic; <= 0 for no * mnemonic * @see #getDisplayedMnemonicIndex * @since 1.4 */
public int getMnemonic() { return getInt("ColorChooser.hsbMnemonic", -1); }
Provides a hint to the look and feel as to the index of the character in getDisplayName that should be visually identified as the mnemonic. The look and feel should only use this if getMnemonic returns a value > 0.

The return value here is a hint, it is ultimately up to the look and feel to honor the return value in some meaningful way. For example, a look and feel may wish to render each AbstractColorChooserPanel in a JTabbedPane, and further use this return value to underline a character in the getDisplayName.

This implementation looks up the value from the default ColorChooser.rgbDisplayedMnemonicIndex, or if it isn't available (or not an Integer) returns -1. The lookup for the default is done through the UIManager: UIManager.get("ColorChooser.hsbDisplayedMnemonicIndex");.

See Also:
Returns:Character index to render mnemonic for; -1 to provide no visual identifier for this panel.
Since:1.4
/** * Provides a hint to the look and feel as to the index of the character in * <code>getDisplayName</code> that should be visually identified as the * mnemonic. The look and feel should only use this if * <code>getMnemonic</code> returns a value > 0. * <p> * The return value here is a hint, it is ultimately up to the look * and feel to honor the return value in some meaningful way. For example, * a look and feel may wish to render each * <code>AbstractColorChooserPanel</code> in a <code>JTabbedPane</code>, * and further use this return value to underline a character in * the <code>getDisplayName</code>. * <p> * This implementation looks up the value from the default * <code>ColorChooser.rgbDisplayedMnemonicIndex</code>, or if it * isn't available (or not an <code>Integer</code>) returns -1. * The lookup for the default is done through the <code>UIManager</code>: * <code>UIManager.get("ColorChooser.hsbDisplayedMnemonicIndex");</code>. * * @return Character index to render mnemonic for; -1 to provide no * visual identifier for this panel. * @see #getMnemonic * @since 1.4 */
public int getDisplayedMnemonicIndex() { return getInt("ColorChooser.hsbDisplayedMnemonicIndex", -1); } public Icon getSmallDisplayIcon() { return null; } public Icon getLargeDisplayIcon() { return null; }
Class for the slider and palette images.
/** * Class for the slider and palette images. */
class HSBImage extends SyntheticImage { protected float h = .0f; protected float s = .0f; protected float b = .0f; protected float[] hsb = new float[3]; protected boolean isDirty = true; protected int cachedY; protected int cachedColor; protected int type; private static final int HSQUARE = 0; private static final int SSQUARE = 1; private static final int BSQUARE = 2; private static final int HSLIDER = 3; private static final int SSLIDER = 4; private static final int BSLIDER = 5; protected HSBImage(int type, int width, int height, float h, float s, float b) { super(width, height); setValues(type, h, s, b); } public void setValues(int type, float h, float s, float b) { this.type = type; cachedY = -1; cachedColor = 0; setHue( h ); setSaturation( s ); setBrightness( b ); } public final void setHue( float hue ) { h = hue; } public final void setSaturation( float saturation ) { s = saturation; } public final void setBrightness( float brightness ) { b = brightness; } public final float getHue() { return h; } public final float getSaturation() { return s; } public final float getBrightness() { return b; } protected boolean isStatic() { return false; } public synchronized void nextFrame() { isDirty = true; notifyAll(); } public synchronized void addConsumer(ImageConsumer ic) { isDirty = true; super.addConsumer(ic); } private int getRGBForLocation( int x, int y ) { if (type >= HSLIDER && y == cachedY) { return cachedColor; } getHSBForLocation( x, y, hsb ); cachedY = y; cachedColor = Color.HSBtoRGB( hsb[0], hsb[1], hsb[2] ); return cachedColor; } public void getHSBForLocation( int x, int y, float[] hsbArray ) { switch (type) { case HSQUARE: { float saturationStep = ((float)x) / width; float brightnessStep = ((float)y) / height; hsbArray[0] = h; hsbArray[1] = s - saturationStep; hsbArray[2] = b - brightnessStep; break; } case SSQUARE: { float brightnessStep = ((float)y) / height; float step = 1.0f / ((float)width); hsbArray[0] = x * step; hsbArray[1] = s; hsbArray[2] = 1.0f - brightnessStep; break; } case BSQUARE: { float saturationStep = ((float)y) / height; float step = 1.0f / ((float)width); hsbArray[0] = x * step; hsbArray[1] = 1.0f - saturationStep; hsbArray[2] = b; break; } case HSLIDER: { float step = 1.0f / ((float)height); hsbArray[0] = y * step; hsbArray[1] = s; hsbArray[2] = b; break; } case SSLIDER: { float saturationStep = ((float)y) / height; hsbArray[0] = h; hsbArray[1] = s - saturationStep; hsbArray[2] = b; break; } case BSLIDER: { float brightnessStep = ((float)y) / height; hsbArray[0] = h; hsbArray[1] = s; hsbArray[2] = b - brightnessStep; break; } } }
Overriden method from SyntheticImage
/** * Overriden method from SyntheticImage */
protected void computeRow( int y, int[] row ) { if ( y == 0 ) { synchronized ( this ) { try { while ( !isDirty ) { wait(); } } catch (InterruptedException ie) { } isDirty = false; } } if (aborted) { return; } for ( int i = 0; i < row.length; ++i ) { row[i] = getRGBForLocation( i, y ); } } } public void stateChanged(ChangeEvent e) { if (e.getSource() == slider) { boolean modelIsAdjusting = slider.getModel().getValueIsAdjusting(); if (!modelIsAdjusting && !isAdjusting) { int sliderValue = slider.getValue(); int sliderRange = slider.getMaximum(); float value = (float)sliderValue / (float)sliderRange; float[] hsb = getHSBColorFromModel(); switch ( currentMode ){ case HUE_MODE: updateHSB(value, hsb[1], hsb[2]); break; case SATURATION_MODE: updateHSB(hsb[0], value, hsb[2]); break; case BRIGHTNESS_MODE: updateHSB(hsb[0], hsb[1], value); break; } } } else if (e.getSource() instanceof JSpinner) { float hue = ((Integer)hField.getValue()).floatValue() / 359f; float saturation = ((Integer)sField.getValue()).floatValue() / 100f; float brightness = ((Integer)bField.getValue()).floatValue() / 100f; updateHSB(hue, saturation, brightness); } } public void hierarchyChanged(HierarchyEvent he) { if ((he.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0) { if (isDisplayable()) { initializePalettesIfNecessary(); } else { cleanupPalettesIfNecessary(); } } } }