blob: be764d091290f3063f613607fc3e6b69fa8a32fa [file] [log] [blame]
/*
* 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;
}
}
}