| /* |
| * Copyright (c) 2002, 2008, 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 com.sun.java.swing.plaf.gtk; |
| |
| import java.awt.*; |
| import java.awt.event.*; |
| import java.awt.image.*; |
| import javax.swing.*; |
| import javax.swing.colorchooser.*; |
| import javax.swing.event.*; |
| import javax.swing.plaf.*; |
| |
| /** |
| * A color chooser panel mimicking that of GTK's: a color wheel showing |
| * hue and a triangle that varies saturation and brightness. |
| * |
| * @author Scott Violet |
| */ |
| class GTKColorChooserPanel extends AbstractColorChooserPanel implements |
| ChangeListener { |
| private static final float PI_3 = (float)(Math.PI / 3); |
| |
| private ColorTriangle triangle; |
| private JLabel lastLabel; |
| private JLabel label; |
| |
| private JSpinner hueSpinner; |
| private JSpinner saturationSpinner; |
| private JSpinner valueSpinner; |
| |
| private JSpinner redSpinner; |
| private JSpinner greenSpinner; |
| private JSpinner blueSpinner; |
| |
| private JTextField colorNameTF; |
| |
| private boolean settingColor; |
| |
| // The colors are mirrored to avoid creep in adjusting an individual |
| // value. |
| private float hue; |
| private float saturation; |
| private float brightness; |
| |
| |
| |
| /** |
| * Convenience method to transfer focus to the next child of component. |
| */ |
| // PENDING: remove this when a variant of this is added to awt. |
| static void compositeRequestFocus(Component component, boolean direction) { |
| if (component instanceof Container) { |
| Container container = (Container)component; |
| if (container.isFocusCycleRoot()) { |
| FocusTraversalPolicy policy = container. |
| getFocusTraversalPolicy(); |
| Component comp = policy.getDefaultComponent(container); |
| if (comp!=null) { |
| comp.requestFocus(); |
| return; |
| } |
| } |
| Container rootAncestor = container.getFocusCycleRootAncestor(); |
| if (rootAncestor!=null) { |
| FocusTraversalPolicy policy = rootAncestor. |
| getFocusTraversalPolicy(); |
| Component comp; |
| |
| if (direction) { |
| comp = policy.getComponentAfter(rootAncestor, container); |
| } |
| else { |
| comp = policy.getComponentBefore(rootAncestor, container); |
| } |
| if (comp != null) { |
| comp.requestFocus(); |
| return; |
| } |
| } |
| } |
| component.requestFocus(); |
| } |
| |
| |
| /** |
| * Returns a user presentable description of this GTKColorChooserPane. |
| */ |
| public String getDisplayName() { |
| return (String)UIManager.get("GTKColorChooserPanel.nameText"); |
| } |
| |
| /** |
| * Returns the mnemonic to use with <code>getDisplayName</code>. |
| */ |
| public int getMnemonic() { |
| String m = (String)UIManager.get("GTKColorChooserPanel.mnemonic"); |
| |
| if (m != null) { |
| try { |
| int value = Integer.parseInt(m); |
| |
| return value; |
| } catch (NumberFormatException nfe) {} |
| } |
| return -1; |
| } |
| |
| /** |
| * Character to underline that represents the mnemonic. |
| */ |
| public int getDisplayedMnemonicIndex() { |
| String m = (String)UIManager.get( |
| "GTKColorChooserPanel.displayedMnemonicIndex"); |
| |
| if (m != null) { |
| try { |
| int value = Integer.parseInt(m); |
| |
| return value; |
| } catch (NumberFormatException nfe) {} |
| } |
| return -1; |
| } |
| |
| public Icon getSmallDisplayIcon() { |
| return null; |
| } |
| |
| public Icon getLargeDisplayIcon() { |
| return null; |
| } |
| |
| public void uninstallChooserPanel(JColorChooser enclosingChooser) { |
| super.uninstallChooserPanel(enclosingChooser); |
| removeAll(); |
| } |
| |
| /** |
| * Builds and configures the widgets for the GTKColorChooserPanel. |
| */ |
| protected void buildChooser() { |
| triangle = new ColorTriangle(); |
| triangle.setName("GTKColorChooserPanel.triangle"); |
| |
| // PENDING: when we straighten out user setting opacity, this should |
| // be changed. |
| label = new OpaqueLabel(); |
| label.setName("GTKColorChooserPanel.colorWell"); |
| label.setOpaque(true); |
| label.setMinimumSize(new Dimension(67, 32)); |
| label.setPreferredSize(new Dimension(67, 32)); |
| label.setMaximumSize(new Dimension(67, 32)); |
| |
| // PENDING: when we straighten out user setting opacity, this should |
| // be changed. |
| lastLabel = new OpaqueLabel(); |
| lastLabel.setName("GTKColorChooserPanel.lastColorWell"); |
| lastLabel.setOpaque(true); |
| lastLabel.setMinimumSize(new Dimension(67, 32)); |
| lastLabel.setPreferredSize(new Dimension(67, 32)); |
| lastLabel.setMaximumSize(new Dimension(67, 32)); |
| |
| hueSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 360, 1)); |
| configureSpinner(hueSpinner, "GTKColorChooserPanel.hueSpinner"); |
| saturationSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 255, 1)); |
| configureSpinner(saturationSpinner, |
| "GTKColorChooserPanel.saturationSpinner"); |
| valueSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 255, 1)); |
| configureSpinner(valueSpinner, "GTKColorChooserPanel.valueSpinner"); |
| redSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 255, 1)); |
| configureSpinner(redSpinner, "GTKColorChooserPanel.redSpinner"); |
| greenSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 255, 1)); |
| configureSpinner(greenSpinner, "GTKColorChooserPanel.greenSpinner"); |
| blueSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 255, 1)); |
| configureSpinner(blueSpinner, "GTKColorChooserPanel.blueSpinner"); |
| |
| colorNameTF = new JTextField(8); |
| |
| setLayout(new GridBagLayout()); |
| |
| add(this, "GTKColorChooserPanel.hue", hueSpinner, -1, -1); |
| add(this, "GTKColorChooserPanel.red", redSpinner, -1, -1); |
| add(this, "GTKColorChooserPanel.saturation", saturationSpinner, -1,-1); |
| add(this, "GTKColorChooserPanel.green", greenSpinner, -1, -1); |
| add(this, "GTKColorChooserPanel.value", valueSpinner, -1, -1); |
| add(this, "GTKColorChooserPanel.blue", blueSpinner, -1, -1); |
| |
| add(new JSeparator(SwingConstants.HORIZONTAL), new |
| GridBagConstraints(1, 3, 4, 1, 1, 0, |
| GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, |
| new Insets(14, 0, 0, 0), 0, 0)); |
| |
| add(this, "GTKColorChooserPanel.colorName", colorNameTF, 0, 4); |
| |
| add(triangle, new GridBagConstraints(0, 0, 1, 5, 0, 0, |
| GridBagConstraints.LINE_START, GridBagConstraints.NONE, |
| new Insets(14, 20, 2, 9), 0, 0)); |
| |
| Box hBox = Box.createHorizontalBox(); |
| hBox.add(lastLabel); |
| hBox.add(label); |
| add(hBox, new GridBagConstraints(0, 5, 1, 1, 0, 0, |
| GridBagConstraints.CENTER, GridBagConstraints.NONE, |
| new Insets(0, 0, 0, 0), 0, 0)); |
| |
| add(new JSeparator(SwingConstants.HORIZONTAL), new |
| GridBagConstraints(0, 6, 5, 1, 1, 0, |
| GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, |
| new Insets(12, 0, 0, 0), 0, 0)); |
| } |
| |
| /** |
| * Configures the spinner. |
| */ |
| private void configureSpinner(JSpinner spinner, String name) { |
| spinner.addChangeListener(this); |
| spinner.setName(name); |
| JComponent editor = spinner.getEditor(); |
| if (editor instanceof JSpinner.DefaultEditor) { |
| JFormattedTextField ftf = ((JSpinner.DefaultEditor)editor). |
| getTextField(); |
| |
| ftf.setFocusLostBehavior(JFormattedTextField.COMMIT_OR_REVERT); |
| } |
| } |
| |
| /** |
| * Adds the widget creating a JLabel with the specified name. |
| */ |
| private void add(Container parent, String key, JComponent widget, |
| int x, int y) { |
| JLabel label = new JLabel(UIManager.getString(key + "Text", |
| getLocale())); |
| String mnemonic = (String)UIManager.get(key + "Mnemonic", getLocale()); |
| |
| if (mnemonic != null) { |
| try { |
| label.setDisplayedMnemonic(Integer.parseInt(mnemonic)); |
| } catch (NumberFormatException nfe) { |
| } |
| String mnemonicIndex = (String)UIManager.get(key + "MnemonicIndex", |
| getLocale()); |
| |
| if (mnemonicIndex != null) { |
| try { |
| label.setDisplayedMnemonicIndex(Integer.parseInt( |
| mnemonicIndex)); |
| } catch (NumberFormatException nfe) { |
| } |
| } |
| } |
| label.setLabelFor(widget); |
| if (x < 0) { |
| x = parent.getComponentCount() % 4; |
| } |
| if (y < 0) { |
| y = parent.getComponentCount() / 4; |
| } |
| GridBagConstraints con = new GridBagConstraints(x + 1, y, 1, 1, 0, 0, |
| GridBagConstraints.FIRST_LINE_END, GridBagConstraints.NONE, |
| new Insets(4, 0, 0, 4), 0, 0); |
| if (y == 0) { |
| con.insets.top = 14; |
| } |
| parent.add(label, con); |
| con.gridx++; |
| parent.add(widget, con); |
| } |
| |
| /** |
| * Refreshes the display from the model. |
| */ |
| public void updateChooser() { |
| if (!settingColor) { |
| lastLabel.setBackground(getColorFromModel()); |
| setColor(getColorFromModel(), true, true, false); |
| } |
| } |
| |
| /** |
| * Resets the red component of the selected color. |
| */ |
| private void setRed(int red) { |
| setRGB(red << 16 | getColor().getGreen() << 8 | getColor().getBlue()); |
| } |
| |
| /** |
| * Resets the green component of the selected color. |
| */ |
| private void setGreen(int green) { |
| setRGB(getColor().getRed() << 16 | green << 8 | getColor().getBlue()); |
| } |
| |
| /** |
| * Resets the blue component of the selected color. |
| */ |
| private void setBlue(int blue) { |
| setRGB(getColor().getRed() << 16 | getColor().getGreen() << 8 | blue); |
| } |
| |
| /** |
| * Sets the hue of the selected color and updates the display if |
| * necessary. |
| */ |
| private void setHue(float hue, boolean update) { |
| setHSB(hue, saturation, brightness); |
| if (update) { |
| settingColor = true; |
| hueSpinner.setValue(Integer.valueOf((int)(hue * 360))); |
| settingColor = false; |
| } |
| } |
| |
| /** |
| * Returns the current amount of hue. |
| */ |
| private float getHue() { |
| return hue; |
| } |
| |
| /** |
| * Resets the saturation. |
| */ |
| private void setSaturation(float saturation) { |
| setHSB(hue, saturation, brightness); |
| } |
| |
| /** |
| * Returns the saturation. |
| */ |
| private float getSaturation() { |
| return saturation; |
| } |
| |
| /** |
| * Sets the brightness. |
| */ |
| private void setBrightness(float brightness) { |
| setHSB(hue, saturation, brightness); |
| } |
| |
| /** |
| * Returns the brightness. |
| */ |
| private float getBrightness() { |
| return brightness; |
| } |
| |
| /** |
| * Sets the saturation and brightness and updates the display if |
| * necessary. |
| */ |
| private void setSaturationAndBrightness(float s, float b, boolean update) { |
| setHSB(hue, s, b); |
| if (update) { |
| settingColor = true; |
| saturationSpinner.setValue(Integer.valueOf((int)(s * 255))); |
| valueSpinner.setValue(Integer.valueOf((int)(b * 255))); |
| settingColor = false; |
| } |
| } |
| |
| /** |
| * Resets the rgb values. |
| */ |
| private void setRGB(int rgb) { |
| Color color = new Color(rgb); |
| |
| setColor(color, false, true, true); |
| |
| settingColor = true; |
| hueSpinner.setValue(Integer.valueOf((int)(hue * 360))); |
| saturationSpinner.setValue(Integer.valueOf((int)(saturation * 255))); |
| valueSpinner.setValue(Integer.valueOf((int)(brightness * 255))); |
| settingColor = false; |
| } |
| |
| /** |
| * Resets the hsb values. |
| */ |
| private void setHSB(float h, float s, float b) { |
| Color color = Color.getHSBColor(h, s, b); |
| |
| this.hue = h; |
| this.saturation = s; |
| this.brightness = b; |
| setColor(color, false, false, true); |
| |
| settingColor = true; |
| redSpinner.setValue(Integer.valueOf(color.getRed())); |
| greenSpinner.setValue(Integer.valueOf(color.getGreen())); |
| blueSpinner.setValue(Integer.valueOf(color.getBlue())); |
| settingColor = false; |
| } |
| |
| |
| /** |
| * Rests the color. |
| * |
| * @param color new Color |
| * @param updateSpinners whether or not to update the spinners. |
| * @param updateHSB if true, the hsb fields are updated based on the |
| * new color |
| * @param updateModel if true, the model is set. |
| */ |
| private void setColor(Color color, boolean updateSpinners, |
| boolean updateHSB, boolean updateModel) { |
| if (color == null) { |
| color = Color.BLACK; |
| } |
| |
| settingColor = true; |
| |
| if (updateHSB) { |
| float[] hsb = Color.RGBtoHSB(color.getRed(), color.getGreen(), |
| color.getBlue(), null); |
| hue = hsb[0]; |
| saturation = hsb[1]; |
| brightness = hsb[2]; |
| } |
| |
| if (updateModel) { |
| ColorSelectionModel model = getColorSelectionModel(); |
| if (model != null) { |
| model.setSelectedColor(color); |
| } |
| } |
| |
| triangle.setColor(hue, saturation, brightness); |
| label.setBackground(color); |
| // Force Integer to pad the string with 0's by adding 0x1000000 and |
| // then removing the first character. |
| String hexString = Integer.toHexString( |
| (color.getRGB() & 0xFFFFFF) | 0x1000000); |
| colorNameTF.setText("#" + hexString.substring(1)); |
| |
| if (updateSpinners) { |
| redSpinner.setValue(Integer.valueOf(color.getRed())); |
| greenSpinner.setValue(Integer.valueOf(color.getGreen())); |
| blueSpinner.setValue(Integer.valueOf(color.getBlue())); |
| |
| hueSpinner.setValue(Integer.valueOf((int)(hue * 360))); |
| saturationSpinner.setValue(Integer.valueOf((int)(saturation * 255))); |
| valueSpinner.setValue(Integer.valueOf((int)(brightness * 255))); |
| } |
| settingColor = false; |
| } |
| |
| public Color getColor() { |
| return label.getBackground(); |
| } |
| |
| /** |
| * ChangeListener method, updates the necessary display widgets. |
| */ |
| public void stateChanged(ChangeEvent e) { |
| if (settingColor) { |
| return; |
| } |
| Color color = getColor(); |
| |
| if (e.getSource() == hueSpinner) { |
| setHue(((Number)hueSpinner.getValue()).floatValue() / 360, false); |
| } |
| else if (e.getSource() == saturationSpinner) { |
| setSaturation(((Number)saturationSpinner.getValue()). |
| floatValue() / 255); |
| } |
| else if (e.getSource() == valueSpinner) { |
| setBrightness(((Number)valueSpinner.getValue()). |
| floatValue() / 255); |
| } |
| else if (e.getSource() == redSpinner) { |
| setRed(((Number)redSpinner.getValue()).intValue()); |
| } |
| else if (e.getSource() == greenSpinner) { |
| setGreen(((Number)greenSpinner.getValue()).intValue()); |
| } |
| else if (e.getSource() == blueSpinner) { |
| setBlue(((Number)blueSpinner.getValue()).intValue()); |
| } |
| } |
| |
| |
| |
| /** |
| * Flag indicating the angle, or hue, has changed and the triangle |
| * needs to be recreated. |
| */ |
| private static final int FLAGS_CHANGED_ANGLE = 1 << 0; |
| /** |
| * Indicates the wheel is being dragged. |
| */ |
| private static final int FLAGS_DRAGGING = 1 << 1; |
| /** |
| * Indicates the triangle is being dragged. |
| */ |
| private static final int FLAGS_DRAGGING_TRIANGLE = 1 << 2; |
| /** |
| * Indicates a color is being set and we should ignore setColor |
| */ |
| private static final int FLAGS_SETTING_COLOR = 1 << 3; |
| /** |
| * Indicates the wheel has focus. |
| */ |
| private static final int FLAGS_FOCUSED_WHEEL = 1 << 4; |
| /** |
| * Indicates the triangle has focus. |
| */ |
| private static final int FLAGS_FOCUSED_TRIANGLE = 1 << 5; |
| |
| |
| /** |
| * Class responsible for rendering a color wheel and color triangle. |
| */ |
| private class ColorTriangle extends JPanel { |
| /** |
| * Cached image of the wheel. |
| */ |
| private Image wheelImage; |
| |
| /** |
| * Cached image of the triangle. |
| */ |
| private Image triangleImage; |
| |
| /** |
| * Angle triangle is rotated by. |
| */ |
| private double angle; |
| |
| /** |
| * Boolean bitmask. |
| */ |
| private int flags; |
| |
| /** |
| * X location of selected color indicator. |
| */ |
| private int circleX; |
| /** |
| * Y location of selected color indicator. |
| */ |
| private int circleY; |
| |
| |
| public ColorTriangle() { |
| enableEvents(AWTEvent.FOCUS_EVENT_MASK); |
| enableEvents(AWTEvent.MOUSE_EVENT_MASK); |
| enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK); |
| |
| setMinimumSize(new Dimension(getWheelRadius() * 2 + 2, |
| getWheelRadius() * 2 + 2)); |
| setPreferredSize(new Dimension(getWheelRadius() * 2 + 2, |
| getWheelRadius() * 2 + 2)); |
| |
| // We want to handle tab ourself. |
| setFocusTraversalKeysEnabled(false); |
| |
| // PENDING: this should come from the style. |
| getInputMap().put(KeyStroke.getKeyStroke("UP"), "up"); |
| getInputMap().put(KeyStroke.getKeyStroke("DOWN"), "down"); |
| getInputMap().put(KeyStroke.getKeyStroke("LEFT"), "left"); |
| getInputMap().put(KeyStroke.getKeyStroke("RIGHT"), "right"); |
| |
| getInputMap().put(KeyStroke.getKeyStroke("KP_UP"), "up"); |
| getInputMap().put(KeyStroke.getKeyStroke("KP_DOWN"), "down"); |
| getInputMap().put(KeyStroke.getKeyStroke("KP_LEFT"), "left"); |
| getInputMap().put(KeyStroke.getKeyStroke("KP_RIGHT"), "right"); |
| |
| getInputMap().put(KeyStroke.getKeyStroke("TAB"), "focusNext"); |
| getInputMap().put(KeyStroke.getKeyStroke("shift TAB"),"focusLast"); |
| |
| ActionMap map = (ActionMap)UIManager.get( |
| "GTKColorChooserPanel.actionMap"); |
| |
| if (map == null) { |
| map = new ActionMapUIResource(); |
| map.put("left", new ColorAction("left", 2)); |
| map.put("right", new ColorAction("right", 3)); |
| map.put("up", new ColorAction("up", 0)); |
| map.put("down", new ColorAction("down", 1)); |
| map.put("focusNext", new ColorAction("focusNext", 4)); |
| map.put("focusLast", new ColorAction("focusLast", 5)); |
| UIManager.getLookAndFeelDefaults().put( |
| "GTKColorChooserPanel.actionMap", map); |
| } |
| SwingUtilities.replaceUIActionMap(this, map); |
| } |
| |
| /** |
| * Returns the GTKColorChooserPanel. |
| */ |
| GTKColorChooserPanel getGTKColorChooserPanel() { |
| return GTKColorChooserPanel.this; |
| } |
| |
| /** |
| * Gives focus to the wheel. |
| */ |
| void focusWheel() { |
| setFocusType(1); |
| } |
| |
| /** |
| * Gives focus to the triangle. |
| */ |
| void focusTriangle() { |
| setFocusType(2); |
| } |
| |
| /** |
| * Returns true if the wheel currently has focus. |
| */ |
| boolean isWheelFocused() { |
| return isSet(FLAGS_FOCUSED_WHEEL); |
| } |
| |
| /** |
| * Resets the selected color. |
| */ |
| public void setColor(float h, float s, float b) { |
| if (isSet(FLAGS_SETTING_COLOR)) { |
| return; |
| } |
| |
| setAngleFromHue(h); |
| setSaturationAndBrightness(s, b); |
| } |
| |
| /** |
| * Returns the selected color. |
| */ |
| public Color getColor() { |
| return GTKColorChooserPanel.this.getColor(); |
| } |
| |
| /** |
| * Returns the x location of the selected color indicator. |
| */ |
| int getColorX() { |
| return circleX + getIndicatorSize() / 2 - getWheelXOrigin(); |
| } |
| |
| /** |
| * Returns the y location of the selected color indicator. |
| */ |
| int getColorY() { |
| return circleY + getIndicatorSize() / 2 - getWheelYOrigin(); |
| } |
| |
| protected void processEvent(AWTEvent e) { |
| if (e.getID() == MouseEvent.MOUSE_PRESSED || |
| ((isSet(FLAGS_DRAGGING) ||isSet(FLAGS_DRAGGING_TRIANGLE)) && |
| e.getID() == MouseEvent.MOUSE_DRAGGED)) { |
| // Assign focus to either the wheel or triangle and attempt |
| // to drag either the wheel or triangle. |
| int size = getWheelRadius(); |
| int x = ((MouseEvent)e).getX() - size; |
| int y = ((MouseEvent)e).getY() - size; |
| |
| if (!hasFocus()) { |
| requestFocus(); |
| } |
| if (!isSet(FLAGS_DRAGGING_TRIANGLE) && |
| adjustHue(x, y, e.getID() == MouseEvent.MOUSE_PRESSED)) { |
| setFlag(FLAGS_DRAGGING, true); |
| setFocusType(1); |
| } |
| else if (adjustSB(x, y, e.getID() == |
| MouseEvent.MOUSE_PRESSED)) { |
| setFlag(FLAGS_DRAGGING_TRIANGLE, true); |
| setFocusType(2); |
| } |
| else { |
| setFocusType(2); |
| } |
| } |
| else if (e.getID() == MouseEvent.MOUSE_RELEASED) { |
| // Stopped dragging |
| setFlag(FLAGS_DRAGGING_TRIANGLE, false); |
| setFlag(FLAGS_DRAGGING, false); |
| } |
| else if (e.getID() == FocusEvent.FOCUS_LOST) { |
| // Reset the flags to indicate no one has focus |
| setFocusType(0); |
| } |
| else if (e.getID() == FocusEvent.FOCUS_GAINED) { |
| // Gained focus, reassign focus to the wheel if no one |
| // currently has focus. |
| if (!isSet(FLAGS_FOCUSED_TRIANGLE) && |
| !isSet(FLAGS_FOCUSED_WHEEL)) { |
| setFlag(FLAGS_FOCUSED_WHEEL, true); |
| setFocusType(1); |
| } |
| repaint(); |
| } |
| super.processEvent(e); |
| } |
| |
| public void paintComponent(Graphics g) { |
| super.paintComponent(g); |
| |
| // Draw the wheel and triangle |
| int size = getWheelRadius(); |
| int width = getWheelWidth(); |
| Image image = getImage(size); |
| g.drawImage(image, getWheelXOrigin() - size, |
| getWheelYOrigin() - size, null); |
| |
| // Draw the focus indicator for the wheel |
| if (hasFocus() && isSet(FLAGS_FOCUSED_WHEEL)) { |
| g.setColor(Color.BLACK); |
| g.drawOval(getWheelXOrigin() - size, getWheelYOrigin() - size, |
| 2 * size, 2 * size); |
| g.drawOval(getWheelXOrigin() - size + width, getWheelYOrigin()- |
| size + width, 2 * (size - width), 2 * |
| (size - width)); |
| } |
| |
| // Draw a line on the wheel indicating the selected hue. |
| if (Math.toDegrees(Math.PI * 2 - angle) <= 20 || |
| Math.toDegrees(Math.PI * 2 - angle) >= 201) { |
| g.setColor(Color.WHITE); |
| } |
| else { |
| g.setColor(Color.BLACK); |
| } |
| int lineX0 = (int)(Math.cos(angle) * size); |
| int lineY0 = (int)(Math.sin(angle) * size); |
| int lineX1 = (int)(Math.cos(angle) * (size - width)); |
| int lineY1 = (int)(Math.sin(angle) * (size - width)); |
| g.drawLine(lineX0 + size, lineY0 + size, lineX1 + size, |
| lineY1 + size); |
| |
| // Draw the focus indicator on the triangle |
| if (hasFocus() && isSet(FLAGS_FOCUSED_TRIANGLE)) { |
| Graphics g2 = g.create(); |
| int innerR = getTriangleCircumscribedRadius(); |
| int a = (int)(3 * innerR / Math.sqrt(3)); |
| g2.translate(getWheelXOrigin(), getWheelYOrigin()); |
| ((Graphics2D)g2).rotate(angle + Math.PI / 2); |
| g2.setColor(Color.BLACK); |
| g2.drawLine(0, -innerR, a / 2, innerR / 2); |
| g2.drawLine(a / 2, innerR / 2, -a / 2, innerR / 2); |
| g2.drawLine(-a / 2, innerR / 2, 0, -innerR); |
| g2.dispose(); |
| } |
| |
| // Draw the selected color indicator. |
| g.setColor(Color.BLACK); |
| g.drawOval(circleX, circleY, getIndicatorSize() - 1, |
| getIndicatorSize() - 1); |
| g.setColor(Color.WHITE); |
| g.drawOval(circleX + 1, circleY + 1, getIndicatorSize() - 3, |
| getIndicatorSize() - 3); |
| } |
| |
| /** |
| * Returns an image representing the triangle and wheel. |
| */ |
| private Image getImage(int size) { |
| if (!isSet(FLAGS_CHANGED_ANGLE) && wheelImage != null && |
| wheelImage.getWidth(null) == size * 2) { |
| return wheelImage; |
| } |
| if (wheelImage == null || wheelImage.getWidth(null) != size) { |
| wheelImage = getWheelImage(size); |
| } |
| int innerR = getTriangleCircumscribedRadius(); |
| int triangleSize = (int)(innerR * 3.0 / 2.0); |
| int a = (int)(2 * triangleSize / Math.sqrt(3)); |
| if (triangleImage == null || triangleImage.getWidth(null) != a) { |
| triangleImage = new BufferedImage(a, a, |
| BufferedImage.TYPE_INT_ARGB); |
| } |
| Graphics g = triangleImage.getGraphics(); |
| g.setColor(new Color(0, 0, 0, 0)); |
| g.fillRect(0, 0, a, a); |
| g.translate(a / 2, 0); |
| paintTriangle(g, triangleSize, getColor()); |
| g.translate(-a / 2, 0); |
| g.dispose(); |
| |
| g = wheelImage.getGraphics(); |
| g.setColor(new Color(0, 0, 0, 0)); |
| g.fillOval(getWheelWidth(), getWheelWidth(), |
| 2 * (size - getWheelWidth()), |
| 2 * (size - getWheelWidth())); |
| |
| double rotate = Math.toRadians(-30.0) + angle; |
| g.translate(size, size); |
| ((Graphics2D)g).rotate(rotate); |
| g.drawImage(triangleImage, -a / 2, |
| getWheelWidth() - size, null); |
| ((Graphics2D)g).rotate(-rotate); |
| g.translate(a / 2, size - getWheelWidth()); |
| |
| setFlag(FLAGS_CHANGED_ANGLE, false); |
| |
| return wheelImage; |
| } |
| |
| private void paintTriangle(Graphics g, int size, Color color) { |
| float[] colors = Color.RGBtoHSB(color.getRed(), |
| color.getGreen(), |
| color.getBlue(), null); |
| float hue = colors[0]; |
| double dSize = (double)size; |
| for (int y = 0; y < size; y++) { |
| int maxX = (int)(y * Math.tan(Math.toRadians(30.0))); |
| float factor = maxX * 2; |
| if (maxX > 0) { |
| float value = (float)(y / dSize); |
| for (int x = -maxX; x <= maxX; x++) { |
| float saturation = (float)x / factor + .5f; |
| g.setColor(Color.getHSBColor(hue, saturation, value)); |
| g.fillRect(x, y, 1, 1); |
| } |
| } |
| else { |
| g.setColor(color); |
| g.fillRect(0, y, 1, 1); |
| } |
| } |
| } |
| |
| /** |
| * Returns a color wheel image for the specified size. |
| * |
| * @param size Integer giving size of color wheel. |
| * @return Color wheel image |
| */ |
| private Image getWheelImage(int size) { |
| int minSize = size - getWheelWidth(); |
| int doubleSize = size * 2; |
| BufferedImage image = new BufferedImage(doubleSize, doubleSize, |
| BufferedImage.TYPE_INT_ARGB); |
| |
| for (int y = -size; y < size; y++) { |
| int ySquared = y * y; |
| for (int x = -size; x < size; x++) { |
| double rad = Math.sqrt(ySquared + x * x); |
| |
| if (rad < size && rad > minSize) { |
| int rgb = colorWheelLocationToRGB(x, y, rad) | |
| 0xFF000000; |
| image.setRGB(x + size, y + size, rgb); |
| } |
| } |
| } |
| wheelImage = image; |
| return wheelImage; |
| } |
| |
| /** |
| * Adjusts the saturation and brightness. <code>x</code> and |
| * <code>y</code> give the location to adjust to and are relative |
| * to the origin of the wheel/triangle. |
| * |
| * @param x X coordinate on the triangle to adjust to |
| * @param y Y coordinate on the triangle to adjust to |
| * @param checkLoc if true the location is checked to make sure |
| * it is contained in the triangle, if false the location is |
| * constrained to fit in the triangle. |
| * @return true if the location is valid |
| */ |
| boolean adjustSB(int x, int y, boolean checkLoc) { |
| int innerR = getWheelRadius() - getWheelWidth(); |
| boolean resetXY = false; |
| // Invert the axis. |
| y = -y; |
| if (checkLoc && (x < -innerR || x > innerR || y < -innerR || |
| y > innerR)) { |
| return false; |
| } |
| // Rotate to origin and and verify x is valid. |
| int triangleSize = innerR * 3 / 2; |
| double x1 = Math.cos(angle) * x - Math.sin(angle) * y; |
| double y1 = Math.sin(angle) * x + Math.cos(angle) * y; |
| if (x1 < -(innerR / 2)) { |
| if (checkLoc) { |
| return false; |
| } |
| x1 = -innerR / 2; |
| resetXY = true; |
| } |
| else if ((int)x1 > innerR) { |
| if (checkLoc) { |
| return false; |
| } |
| x1 = innerR; |
| resetXY = true; |
| } |
| // Verify y location is valid. |
| int maxY = (int)((triangleSize - x1 - innerR / 2.0) * |
| Math.tan(Math.toRadians(30.0))); |
| if (y1 <= -maxY) { |
| if (checkLoc) { |
| return false; |
| } |
| y1 = -maxY; |
| resetXY = true; |
| } |
| else if (y1 > maxY) { |
| if (checkLoc) { |
| return false; |
| } |
| y1 = maxY; |
| resetXY = true; |
| } |
| // Rotate again to determine value and scale |
| double x2 = Math.cos(Math.toRadians(-30.0)) * x1 - |
| Math.sin(Math.toRadians(-30.0)) * y1; |
| double y2 = Math.sin(Math.toRadians(-30.0)) * x1 + |
| Math.cos(Math.toRadians(-30.0)) * y1; |
| float value = Math.min(1.0f, (float)((innerR - y2) / |
| (double)triangleSize)); |
| float maxX = (float)(Math.tan(Math.toRadians(30)) * (innerR - y2)); |
| float saturation = Math.min(1.0f, (float)(x2 / maxX / 2 + .5)); |
| |
| setFlag(FLAGS_SETTING_COLOR, true); |
| if (resetXY) { |
| setSaturationAndBrightness(saturation, value); |
| } |
| else { |
| setSaturationAndBrightness(saturation, value, x + |
| getWheelXOrigin(),getWheelYOrigin() - y); |
| } |
| GTKColorChooserPanel.this.setSaturationAndBrightness(saturation, |
| value, true); |
| setFlag(FLAGS_SETTING_COLOR, false); |
| return true; |
| } |
| |
| /** |
| * Sets the saturation and brightness. |
| */ |
| private void setSaturationAndBrightness(float s, float b) { |
| int innerR = getTriangleCircumscribedRadius(); |
| int triangleSize = innerR * 3 / 2; |
| double x = b * triangleSize; |
| double maxY = x * Math.tan(Math.toRadians(30.0)); |
| double y = 2 * maxY * s - maxY; |
| x = x - innerR; |
| double x1 = Math.cos(Math.toRadians(-60.0) - angle) * |
| x - Math.sin(Math.toRadians(-60.0) - angle) * y; |
| double y1 = Math.sin(Math.toRadians(-60.0) - angle) * x + |
| Math.cos(Math.toRadians(-60.0) - angle) * y; |
| int newCircleX = (int)x1 + getWheelXOrigin(); |
| int newCircleY = getWheelYOrigin() - (int)y1; |
| |
| setSaturationAndBrightness(s, b, newCircleX, newCircleY); |
| } |
| |
| |
| /** |
| * Sets the saturation and brightness. |
| */ |
| private void setSaturationAndBrightness(float s, float b, |
| int newCircleX, int newCircleY) { |
| newCircleX -= getIndicatorSize() / 2; |
| newCircleY -= getIndicatorSize() / 2; |
| |
| int minX = Math.min(newCircleX, circleX); |
| int minY = Math.min(newCircleY, circleY); |
| |
| repaint(minX, minY, Math.max(circleX, newCircleX) - minX + |
| getIndicatorSize() + 1, Math.max(circleY, newCircleY) - |
| minY + getIndicatorSize() + 1); |
| circleX = newCircleX; |
| circleY = newCircleY; |
| } |
| |
| /** |
| * Adjusts the hue based on the passed in location. |
| * |
| * @param x X location to adjust to, relative to the origin of the |
| * wheel |
| * @param y Y location to adjust to, relative to the origin of the |
| * wheel |
| * @param check if true the location is checked to make sure |
| * it is contained in the wheel, if false the location is |
| * constrained to fit in the wheel |
| * @return true if the location is valid. |
| */ |
| private boolean adjustHue(int x, int y, boolean check) { |
| double rad = Math.sqrt(x * x + y * y); |
| int size = getWheelRadius(); |
| |
| if (!check || (rad >= size - getWheelWidth() && rad < size)) { |
| // Map the location to an angle and reset hue |
| double angle; |
| if (x == 0) { |
| if (y > 0) { |
| angle = Math.PI / 2.0; |
| } |
| else { |
| angle = Math.PI + Math.PI / 2.0; |
| } |
| } |
| else { |
| angle = Math.atan((double)y / (double)x); |
| if (x < 0) { |
| angle += Math.PI; |
| } |
| else if (angle < 0) { |
| angle += 2 * Math.PI; |
| } |
| } |
| setFlag(FLAGS_SETTING_COLOR, true); |
| setHue((float)(1.0 - angle / Math.PI / 2), true); |
| setFlag(FLAGS_SETTING_COLOR, false); |
| setHueAngle(angle); |
| setSaturationAndBrightness(getSaturation(), getBrightness()); |
| return true; |
| } |
| return false; |
| } |
| |
| /** |
| * Rotates the triangle to accomodate the passed in hue. |
| */ |
| private void setAngleFromHue(float hue) { |
| setHueAngle((1.0 - hue) * Math.PI * 2); |
| } |
| |
| /** |
| * Sets the angle representing the hue. |
| */ |
| private void setHueAngle(double angle) { |
| double oldAngle = this.angle; |
| |
| this.angle = angle; |
| if (angle != oldAngle) { |
| setFlag(FLAGS_CHANGED_ANGLE, true); |
| repaint(); |
| } |
| } |
| |
| /** |
| * Returns the size of the color indicator. |
| */ |
| private int getIndicatorSize() { |
| return 8; |
| } |
| |
| /** |
| * Returns the circumscribed radius of the triangle. |
| */ |
| private int getTriangleCircumscribedRadius() { |
| return 72; |
| } |
| |
| /** |
| * Returns the x origin of the wheel and triangle. |
| */ |
| private int getWheelXOrigin() { |
| return 85; |
| } |
| |
| /** |
| * Returns y origin of the wheel and triangle. |
| */ |
| private int getWheelYOrigin() { |
| return 85; |
| } |
| |
| /** |
| * Returns the width of the wheel. |
| */ |
| private int getWheelWidth() { |
| return 13; |
| } |
| |
| /** |
| * Sets the focus to one of: 0 no one, 1 the wheel or 2 the triangle. |
| */ |
| private void setFocusType(int type) { |
| if (type == 0) { |
| setFlag(FLAGS_FOCUSED_WHEEL, false); |
| setFlag(FLAGS_FOCUSED_TRIANGLE, false); |
| repaint(); |
| } |
| else { |
| int toSet = FLAGS_FOCUSED_WHEEL; |
| int toUnset = FLAGS_FOCUSED_TRIANGLE; |
| |
| if (type == 2) { |
| toSet = FLAGS_FOCUSED_TRIANGLE; |
| toUnset = FLAGS_FOCUSED_WHEEL; |
| } |
| if (!isSet(toSet)) { |
| setFlag(toSet, true); |
| repaint(); |
| setFlag(toUnset, false); |
| } |
| } |
| } |
| |
| /** |
| * Returns the radius of the wheel. |
| */ |
| private int getWheelRadius() { |
| // As far as I can tell, GTK doesn't allow stretching this |
| // widget |
| return 85; |
| } |
| |
| /** |
| * Updates the flags bitmask. |
| */ |
| private void setFlag(int flag, boolean value) { |
| if (value) { |
| flags |= flag; |
| } |
| else { |
| flags &= ~flag; |
| } |
| } |
| |
| /** |
| * Returns true if a particular flag has been set. |
| */ |
| private boolean isSet(int flag) { |
| return ((flags & flag) == flag); |
| } |
| |
| /** |
| * Returns the RGB color to use for the specified location. The |
| * passed in point must be on the color wheel and be relative to the |
| * origin of the color wheel. |
| * |
| * @param x X location to get color for |
| * @param y Y location to get color for |
| * @param rad Radius from center of color wheel |
| * @return integer with red, green and blue components |
| */ |
| private int colorWheelLocationToRGB(int x, int y, double rad) { |
| double angle = Math.acos((double)x / rad); |
| int rgb; |
| |
| if (angle < PI_3) { |
| if (y < 0) { |
| // FFFF00 - FF0000 |
| rgb = 0xFF0000 | Math.min(255, |
| (int)(255 * angle / PI_3)) << 8; |
| } |
| else { |
| // FF0000 - FF00FF |
| rgb = 0xFF0000 | Math.min(255, |
| (int)(255 * angle / PI_3)); |
| } |
| } |
| else if (angle < 2 * PI_3) { |
| angle -= PI_3; |
| if (y < 0) { |
| // 00FF00 - FFFF00 |
| rgb = 0x00FF00 | Math.max(0, 255 - |
| (int)(255 * angle / PI_3)) << 16; |
| } |
| else { |
| // FF00FF - 0000FF |
| rgb = 0x0000FF | Math.max(0, 255 - |
| (int)(255 * angle / PI_3)) << 16; |
| } |
| } |
| else { |
| angle -= 2 * PI_3; |
| if (y < 0) { |
| // 00FFFF - 00FF00 |
| rgb = 0x00FF00 | Math.min(255, |
| (int)(255 * angle / PI_3)); |
| } |
| else { |
| // 0000FF - 00FFFF |
| rgb = 0x0000FF | Math.min(255, |
| (int)(255 * angle / PI_3)) << 8; |
| } |
| } |
| return rgb; |
| } |
| |
| /** |
| * Increments the hue. |
| */ |
| void incrementHue(boolean positive) { |
| float hue = triangle.getGTKColorChooserPanel().getHue(); |
| |
| if (positive) { |
| hue += 1.0f / 360.0f; |
| } |
| else { |
| hue -= 1.0f / 360.0f; |
| } |
| if (hue > 1) { |
| hue -= 1; |
| } |
| else if (hue < 0) { |
| hue += 1; |
| } |
| getGTKColorChooserPanel().setHue(hue, true); |
| } |
| } |
| |
| |
| /** |
| * Action class used for colors. |
| */ |
| private static class ColorAction extends AbstractAction { |
| private int type; |
| |
| ColorAction(String name, int type) { |
| super(name); |
| this.type = type; |
| } |
| |
| public void actionPerformed(ActionEvent e) { |
| ColorTriangle triangle = (ColorTriangle)e.getSource(); |
| |
| if (triangle.isWheelFocused()) { |
| float hue = triangle.getGTKColorChooserPanel().getHue(); |
| |
| switch (type) { |
| case 0: |
| case 2: |
| triangle.incrementHue(true); |
| break; |
| case 1: |
| case 3: |
| triangle.incrementHue(false); |
| break; |
| case 4: |
| triangle.focusTriangle(); |
| break; |
| case 5: |
| compositeRequestFocus(triangle, false); |
| break; |
| } |
| } |
| else { |
| int xDelta = 0; |
| int yDelta = 0; |
| |
| switch (type) { |
| case 0: |
| // up |
| yDelta--; |
| break; |
| case 1: |
| // down |
| yDelta++; |
| break; |
| case 2: |
| // left |
| xDelta--; |
| break; |
| case 3: |
| // right |
| xDelta++; |
| break; |
| case 4: |
| compositeRequestFocus(triangle, true); |
| return; |
| case 5: |
| triangle.focusWheel(); |
| return; |
| } |
| triangle.adjustSB(triangle.getColorX() + xDelta, |
| triangle.getColorY() + yDelta, true); |
| } |
| } |
| } |
| |
| |
| private class OpaqueLabel extends JLabel { |
| public boolean isOpaque() { |
| return true; |
| } |
| } |
| } |