/* * Rework from original HSBChooserPanel from Sun * (Copyright 2006 Sun Microsystems, Inc.) * see @(#)DefaultHSBChooserPanel.java 1.27 05/11/17 * */ package com.mandelsoft.swing.colorchooser; import com.mandelsoft.swing.GBC; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.GridBagLayout; import java.awt.Image; import java.awt.Point; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.HierarchyEvent; import java.awt.event.HierarchyListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.awt.image.ImageConsumer; import javax.swing.ButtonGroup; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.JColorChooser; 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.UIManager; import javax.swing.border.EmptyBorder; import javax.swing.colorchooser.AbstractColorChooserPanel; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; /** * Implements the default HSB Color chooser * * @version based on 1.27 11/17/05 * @author Tom Santos * @author Steve Wilson * @author Mark Davidson * @author Shannon Hickey */ public class HSBChooserPanel 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 HSBChooserPanel() { } 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 */ 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. */ 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 <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. */ 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 */ 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. */ protected void buildChooser() { setLayout(new BorderLayout()); JComponent spp=buildSliderPalettePanel(); spp.setInheritsPopupMenu(true); add(spp, BorderLayout.BEFORE_LINE_BEGINS); JPanel controlHolder=new JPanel(new GridBagLayout()); JComponent hsbControls=buildHSBControls(); hsbControls.setInheritsPopupMenu(true); controlHolder.add(hsbControls, new GBC(0,0)); controlHolder.add(new JLabel(" "),new GBC(1,0)); // spacer JComponent rgbControls=buildRGBControls(); rgbControls.setInheritsPopupMenu(true); controlHolder.add(rgbControls, new GBC(0,1)); controlHolder.setInheritsPopupMenu(true); controlHolder.setBorder(new EmptyBorder(10, 5, 10, 5)); add(controlHolder, BorderLayout.CENTER); } /** * Creates the panel with the uneditable RGB field */ private JComponent buildRGBControls() { JPanel panel=new JPanel(new GridBagLayout()); 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),new GBC(0,0)); panel.add(redField, new GBC(1,0)); panel.add(new JLabel(greenString),new GBC(0,1)); panel.add(greenField,new GBC(1,1)); panel.add(new JLabel(blueString), new GBC(0,2)); panel.add(blueField, new GBC(1,2)); return panel; } /** * 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 GridBagLayout()); panel.add(hRadio,new GBC(0,0)); panel.add(hField,new GBC(1,0)); panel.add(sRadio,new GBC(0,1)); panel.add(sField,new GBC(1,1)); panel.add(bRadio,new GBC(0,2)); panel.add(bField,new GBC(1,2)); panel.setInheritsPopupMenu(true); return panel; } /** * 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 * <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 -1; } /** * 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 -1; } public Icon getSmallDisplayIcon() { return null; } public Icon getLargeDisplayIcon() { return null; } /** * 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 */ 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 (true || !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(); } } } }