blob: cc1ed7360093caa42fe07a714830aea4b5023106 [file] [log] [blame]
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.ui;
import com.intellij.icons.AllIcons;
import com.intellij.ide.ui.LafManager;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.wm.WindowManager;
import com.intellij.util.Alarm;
import com.intellij.util.Consumer;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.plaf.basic.BasicButtonUI;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ImageObserver;
import java.awt.image.MemoryImageSource;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
/**
* @author pegov
* @author Konstantin Bulenkov
*/
public class ColorPicker extends JPanel implements ColorListener, DocumentListener {
private static final String COLOR_CHOOSER_COLORS_KEY = "ColorChooser.RecentColors";
private static final String HSB_PROPERTY = "color.picker.is.hsb";
private Color myColor;
private ColorPreviewComponent myPreviewComponent;
private final ColorWheelPanel myColorWheelPanel;
private final JTextField myRed;
private final JTextField myGreen;
private final JTextField myBlue;
private final JTextField myHex;
private final Alarm myUpdateQueue;
private final ColorPickerListener[] myExternalListeners;
private final boolean myOpacityInPercent;
private RecentColorsComponent myRecentColorsComponent;
private final ColorPipette myPicker;
private final JLabel myR = new JLabel("R:");
private final JLabel myG = new JLabel("G:");
private final JLabel myB = new JLabel("B:");
private final JLabel myR_after = new JLabel("");
private final JLabel myG_after = new JLabel("");
private final JLabel myB_after = new JLabel("");
private final JComboBox myFormat = new JComboBox(new String[]{"RGB", "HSB"}) {
@Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
UIManager.LookAndFeelInfo info = LafManager.getInstance().getCurrentLookAndFeel();
if (info != null && info.getName().contains("Windows"))
size.width += 10;
return size;
}
};
public ColorPicker(@NotNull Disposable parent, @Nullable Color color, boolean enableOpacity) {
this(parent, color, true, enableOpacity, new ColorPickerListener[0], false);
}
private ColorPicker(Disposable parent,
@Nullable Color color,
boolean restoreColors, boolean enableOpacity,
ColorPickerListener[] listeners, boolean opacityInPercent) {
myUpdateQueue = new Alarm(Alarm.ThreadToUse.SWING_THREAD, parent);
myRed = createColorField(false);
myGreen = createColorField(false);
myBlue = createColorField(false);
myHex = createColorField(true);
myOpacityInPercent = opacityInPercent;
setLayout(new BorderLayout());
setBorder(BorderFactory.createEmptyBorder(5, 5, 0, 5));
myColorWheelPanel = new ColorWheelPanel(this, enableOpacity, myOpacityInPercent);
myExternalListeners = listeners;
myFormat.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
PropertiesComponent.getInstance().setValue(HSB_PROPERTY, String.valueOf(!isRGBMode()));
myR.setText(isRGBMode() ? "R:" : "H:");
myG.setText(isRGBMode() ? "G:" : "S:");
myR_after.setText(isRGBMode() ? "" : "\u00B0");
myG.setText(isRGBMode() ? "G:" : "S:");
myG_after.setText(isRGBMode() ? "" : "%");
myB_after.setText(isRGBMode() ? "" : "%");
applyColor(myColor);
}
});
myPicker = new ColorPipette(this, getColor());
myPicker.setListener(new ColorListener() {
@Override
public void colorChanged(Color color, Object source) {
setColor(color, source);
}
});
try {
add(buildTopPanel(true), BorderLayout.NORTH);
add(myColorWheelPanel, BorderLayout.CENTER);
myRecentColorsComponent = new RecentColorsComponent(new ColorListener() {
@Override
public void colorChanged(Color color, Object source) {
setColor(color, source);
}
}, restoreColors);
add(myRecentColorsComponent, BorderLayout.SOUTH);
}
catch (ParseException ignore) {
}
Color c = color == null ? myRecentColorsComponent.getMostRecentColor() : color;
if (c == null) {
c = Color.WHITE;
}
setColor(c, this);
setSize(300, 350);
final boolean hsb = PropertiesComponent.getInstance().getBoolean(HSB_PROPERTY, false);
if (hsb) {
myFormat.setSelectedIndex(1);
}
}
private boolean isRGBMode() {
return myFormat.getSelectedIndex() == 0;
}
private JTextField createColorField(boolean hex) {
final NumberDocument doc = new NumberDocument(hex);
int lafFix = UIUtil.isUnderWindowsLookAndFeel() || UIUtil.isUnderDarcula() ? 1 : 0;
UIManager.LookAndFeelInfo info = LafManager.getInstance().getCurrentLookAndFeel();
if (info != null && (info.getName().startsWith("IDEA") || info.getName().equals("Windows Classic")))
lafFix = 1;
final JTextField field = new JTextField(doc, "", (hex ? 5:2) + lafFix);
field.setSize(50, -1);
doc.setSource(field);
field.getDocument().addDocumentListener(this);
field.addFocusListener(new FocusAdapter() {
@Override
public void focusGained(final FocusEvent e) {
field.selectAll();
}
});
return field;
}
public JComponent getPreferredFocusedComponent() {
return myHex;
}
private void setColor(Color color, Object src) {
colorChanged(color, src);
myColorWheelPanel.setColor(color, src);
}
public void appendRecentColor() {
myRecentColorsComponent.appendColor(myColor);
}
public void saveRecentColors() {
myRecentColorsComponent.saveColors();
}
public Color getColor() {
return myColor;
}
@Override
public void insertUpdate(DocumentEvent e) {
update(((NumberDocument)e.getDocument()).mySrc);
}
private void update(final JTextField src) {
myUpdateQueue.cancelAllRequests();
myUpdateQueue.addRequest(new Runnable() {
@Override
public void run() {
validateAndUpdatePreview(src);
}
}, 300);
}
@Override
public void removeUpdate(DocumentEvent e) {
update(((NumberDocument)e.getDocument()).mySrc);
}
@Override
public void changedUpdate(DocumentEvent e) {
// ignore
}
private void validateAndUpdatePreview(JTextField src) {
Color color;
if (myHex.hasFocus()) {
Color c = ColorUtil.fromHex(myHex.getText(), null);
color = c != null ? ColorUtil.toAlpha(c, myColorWheelPanel.myColorWheel.myOpacity) : null;
} else {
color = gatherRGB();
}
if (color != null) {
if (myColorWheelPanel.myOpacityComponent != null) {
color = ColorUtil.toAlpha(color, myColorWheelPanel.myOpacityComponent.getValue());
}
updatePreview(color, src == myHex);
}
}
private void updatePreview(Color color, boolean fromHex) {
if (color != null && !color.equals(myColor)) {
myColor = color;
myPreviewComponent.setColor(color);
myColorWheelPanel.setColor(color, fromHex ? myHex : null);
if (fromHex) {
applyColor(color);
} else {
applyColorToHEX(color);
}
fireColorChanged(color);
}
}
@Override
public void colorChanged(Color color, Object source) {
if (color != null && !color.equals(myColor)) {
myColor = color;
applyColor(color);
if (source != myHex) {
applyColorToHEX(color);
}
myPreviewComponent.setColor(color);
fireColorChanged(color);
}
}
private void fireColorChanged(Color color) {
if (myExternalListeners == null) return;
for (ColorPickerListener listener : myExternalListeners) {
listener.colorChanged(color);
}
}
private void fireClosed(@Nullable Color color) {
if (myExternalListeners == null) return;
for (ColorPickerListener listener : myExternalListeners) {
listener.closed(color);
}
}
@Nullable
private Color gatherRGB() {
try {
final int r = Integer.parseInt(myRed.getText());
final int g = Integer.parseInt(myGreen.getText());
final int b = Integer.parseInt(myBlue.getText());
return isRGBMode() ? new Color(r, g, b) : new Color(Color.HSBtoRGB(((float)r) / 360f, ((float)g) / 100f, ((float)b) / 100f));
} catch (Exception ignore) {
}
return null;
}
private void applyColorToHEX(final Color c) {
myHex.setText(String.format("%06X", (0xFFFFFF & c.getRGB())));
}
private void applyColorToRGB(final Color color) {
myRed.setText(String.valueOf(color.getRed()));
myGreen.setText(String.valueOf(color.getGreen()));
myBlue.setText(String.valueOf(color.getBlue()));
}
private void applyColorToHSB(final Color c) {
final float[] hbs = Color.RGBtoHSB(c.getRed(), c.getGreen(), c.getBlue(), null);
myRed.setText(String.valueOf(((int)(360f * hbs[0]))));
myGreen.setText(String.valueOf(((int)(100f * hbs[1]))));
myBlue.setText(String.valueOf(((int)(100f * hbs[2]))));
}
private void applyColor(final Color color) {
if (isRGBMode()) {
applyColorToRGB(color);
} else {
applyColorToHSB(color);
}
}
@Nullable
public static Color showDialog(Component parent,
String caption,
Color preselectedColor,
boolean enableOpacity,
ColorPickerListener[] listeners,
boolean opacityInPercent) {
final ColorPickerDialog dialog = new ColorPickerDialog(parent, caption, preselectedColor, enableOpacity, listeners, opacityInPercent);
dialog.show();
if (dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
return dialog.getColor();
}
return null;
}
private JComponent buildTopPanel(boolean enablePipette) throws ParseException {
final JPanel result = new JPanel(new BorderLayout());
final JPanel previewPanel = new JPanel(new BorderLayout());
if (enablePipette && ColorPipette.isAvailable()) {
final JButton pipette = new JButton();
pipette.setUI(new BasicButtonUI());
pipette.setRolloverEnabled(true);
pipette.setIcon(AllIcons.Ide.Pipette);
pipette.setBorder(IdeBorderFactory.createEmptyBorder());
pipette.setRolloverIcon(AllIcons.Ide.Pipette_rollover);
pipette.setFocusable(false);
pipette.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
myPicker.myOldColor = getColor();
myPicker.pick();
//JBPopupFactory.getInstance().createBalloonBuilder(new JLabel("Press ESC button to close pipette"))
// .setAnimationCycle(2000)
// .setSmallVariant(true)
// .createBalloon().show(new RelativePoint(pipette, new Point(pipette.getWidth() / 2, 0)), Balloon.Position.above);
}
});
previewPanel.add(pipette, BorderLayout.WEST);
}
myPreviewComponent = new ColorPreviewComponent();
previewPanel.add(myPreviewComponent, BorderLayout.CENTER);
result.add(previewPanel, BorderLayout.NORTH);
final JPanel rgbPanel = new JPanel();
rgbPanel.setLayout(new BoxLayout(rgbPanel, BoxLayout.X_AXIS));
if (!UIUtil.isUnderAquaLookAndFeel()) {
myR_after.setPreferredSize(new Dimension(14, -1));
myG_after.setPreferredSize(new Dimension(14, -1));
myB_after.setPreferredSize(new Dimension(14, -1));
}
rgbPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
rgbPanel.add(myR);
rgbPanel.add(myRed);
if (!UIUtil.isUnderAquaLookAndFeel()) rgbPanel.add(myR_after);
rgbPanel.add(Box.createHorizontalStrut(2));
rgbPanel.add(myG);
rgbPanel.add(myGreen);
if (!UIUtil.isUnderAquaLookAndFeel()) rgbPanel.add(myG_after);
rgbPanel.add(Box.createHorizontalStrut(2));
rgbPanel.add(myB);
rgbPanel.add(myBlue);
if (!UIUtil.isUnderAquaLookAndFeel()) rgbPanel.add(myB_after);
rgbPanel.add(Box.createHorizontalStrut(2));
rgbPanel.add(myFormat);
result.add(rgbPanel, BorderLayout.WEST);
final JPanel hexPanel = new JPanel();
hexPanel.setLayout(new BoxLayout(hexPanel, BoxLayout.X_AXIS));
hexPanel.setBorder(BorderFactory.createEmptyBorder(10, 0, 0, 0));
hexPanel.add(new JLabel("#"));
hexPanel.add(myHex);
result.add(hexPanel, BorderLayout.EAST);
return result;
}
private static class ColorWheelPanel extends JPanel {
private ColorWheel myColorWheel;
private SlideComponent myBrightnessComponent;
private SlideComponent myOpacityComponent = null;
private ColorWheelPanel(ColorListener listener, boolean enableOpacity, boolean opacityInPercent) {
setLayout(new BorderLayout());
setBorder(BorderFactory.createEmptyBorder(5, 0, 5, 0));
myColorWheel = new ColorWheel();
add(myColorWheel, BorderLayout.CENTER);
myColorWheel.addListener(listener);
myBrightnessComponent = new SlideComponent("Brightness", true);
myBrightnessComponent.setToolTipText("Brightness");
myBrightnessComponent.addListener(new Consumer<Integer>() {
@Override
public void consume(Integer value) {
myColorWheel.setBrightness(1f - (value / 255f));
myColorWheel.repaint();
}
});
add(myBrightnessComponent, BorderLayout.EAST);
if (enableOpacity) {
myOpacityComponent = new SlideComponent("Opacity", false);
myOpacityComponent.setUnits(opacityInPercent ? SlideComponent.Unit.PERCENT : SlideComponent.Unit.LEVEL);
myOpacityComponent.setToolTipText("Opacity");
myOpacityComponent.addListener(new Consumer<Integer>() {
@Override
public void consume(Integer integer) {
myColorWheel.setOpacity(integer.intValue());
myColorWheel.repaint();
}
});
add(myOpacityComponent, BorderLayout.SOUTH);
}
}
public void setColor(Color color, Object source) {
float[] hsb = new float[3];
Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), hsb);
myBrightnessComponent.setValue(255 - (int)(hsb[2] * 255));
myBrightnessComponent.repaint();
myColorWheel.dropImage();
if (myOpacityComponent != null && source instanceof ColorPicker) {
myOpacityComponent.setValue(color.getAlpha());
myOpacityComponent.repaint();
}
myColorWheel.setColor(color, source);
}
}
private static class ColorWheel extends JComponent {
private static final int BORDER_SIZE = 5;
private float myBrightness = 1f;
private float myHue = 1f;
private float mySaturation = 0f;
private Image myImage;
private Rectangle myWheel;
private boolean myShouldInvalidate = true;
private Color myColor;
private final List<ColorListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
private int myOpacity;
private ColorWheel() {
setOpaque(true);
addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
myShouldInvalidate = true;
}
});
addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
final int x = e.getX();
final int y = e.getY();
int mx = myWheel.x + myWheel.width / 2;
int my = myWheel.y + myWheel.height / 2;
double s;
double h;
s = Math.sqrt((double)((x - mx) * (x - mx) + (y - my) * (y - my))) / (myWheel.height / 2);
h = -Math.atan2((double)(y - my), (double)(x - mx)) / (2 * Math.PI);
if (h < 0) h += 1.0;
if (s > 1) s = 1.0;
setHSBValue((float)h, (float)s, myBrightness, myOpacity);
}
});
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
final int x = e.getX();
final int y = e.getY();
int mx = myWheel.x + myWheel.width / 2;
int my = myWheel.y + myWheel.height / 2;
double s;
double h;
s = Math.sqrt((double)((x - mx) * (x - mx) + (y - my) * (y - my))) / (myWheel.height / 2);
h = -Math.atan2((double)(y - my), (double)(x - mx)) / (2 * Math.PI);
if (h < 0) h += 1.0;
if (s <= 1) {
setHSBValue((float)h, (float)s, myBrightness, myOpacity);
}
}
});
}
private void setHSBValue(float h, float s, float b, int opacity) {
Color rgb = new Color(Color.HSBtoRGB(h, s, b));
setColor(ColorUtil.toAlpha(rgb, opacity), this);
}
private void setColor(Color color, Object source) {
float[] hsb = new float[3];
Color.RGBtoHSB(color.getRed(), color.getGreen(), color.getBlue(), hsb);
myColor = color;
myHue = hsb[0];
mySaturation = hsb[1];
myBrightness = hsb[2];
myOpacity = color.getAlpha();
fireColorChanged(source);
repaint();
}
public void addListener(ColorListener listener) {
myListeners.add(listener);
}
private void fireColorChanged(Object source) {
for (ColorListener listener : myListeners) {
listener.colorChanged(myColor, source);
}
}
public void setBrightness(float brightness) {
if (brightness != myBrightness) {
myImage = null;
setHSBValue(myHue, mySaturation, brightness, myOpacity);
}
}
public void setOpacity(int opacity) {
if (opacity != myOpacity) {
setHSBValue(myHue, mySaturation, myBrightness, opacity);
}
}
@Override
public Dimension getPreferredSize() {
return getMinimumSize();
}
@Override
public Dimension getMinimumSize() {
return new Dimension(300, 300);
}
@Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
final Dimension size = getSize();
int _size = Math.min(size.width, size.height);
_size = Math.min(_size, 600);
if (myImage != null && myShouldInvalidate) {
if (myImage.getWidth(null) != _size) {
myImage = null;
}
}
myShouldInvalidate = false;
if (myImage == null) {
myImage = createImage(new ColorWheelImageProducer(_size - BORDER_SIZE * 2, _size - BORDER_SIZE * 2, myBrightness));
myWheel = new Rectangle(BORDER_SIZE, BORDER_SIZE, _size - BORDER_SIZE * 2, _size - BORDER_SIZE * 2);
}
g.setColor(UIManager.getColor("Panel.background"));
g.fillRect(0, 0, getWidth(), getHeight());
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, ((float)myOpacity) / 255f));
g.drawImage(myImage, myWheel.x, myWheel.y, null);
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC, 1.0f));
int mx = myWheel.x + myWheel.width / 2;
int my = myWheel.y + myWheel.height / 2;
g.setColor(Color.white);
int arcw = (int)(myWheel.width * mySaturation / 2);
int arch = (int)(myWheel.height * mySaturation / 2);
double th = myHue * 2 * Math.PI;
final int x = (int)(mx + arcw * Math.cos(th));
final int y = (int)(my - arch * Math.sin(th));
g.fillRect(x - 2, y - 2, 4, 4);
g.setColor(Color.BLACK);
g.drawRect(x - 2, y - 2, 4, 4);
}
public void dropImage() {
myImage = null;
}
}
private static class ColorPreviewComponent extends JComponent {
private Color myColor;
private ColorPreviewComponent() {
setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 2));
}
@Override
public Dimension getPreferredSize() {
return new Dimension(100, 32);
}
public void setColor(Color c) {
myColor = c;
repaint();
}
@Override
protected void paintComponent(Graphics g) {
final Insets i = getInsets();
final Rectangle r = getBounds();
final int width = r.width - i.left - i.right;
final int height = r.height - i.top - i.bottom;
g.setColor(Color.WHITE);
g.fillRect(i.left, i.top, width, height);
g.setColor(myColor);
g.fillRect(i.left, i.top, width, height);
g.setColor(Color.BLACK);
g.drawRect(i.left, i.top, width - 1, height - 1);
g.setColor(Color.WHITE);
g.drawRect(i.left + 1, i.top + 1, width - 3, height - 3);
}
}
public class NumberDocument extends PlainDocument {
private final boolean myHex;
private JTextField mySrc;
public NumberDocument(boolean hex) {
myHex = hex;
}
void setSource(JTextField field) {
mySrc = field;
}
public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
final boolean rgb = isRGBMode();
char[] source = str.toCharArray();
if (mySrc != null) {
final int selected = mySrc.getSelectionEnd() - mySrc.getSelectionStart();
int newLen = mySrc.getText().length() - selected + str.length();
if (newLen > (myHex ? 6 : 3)) {
Toolkit.getDefaultToolkit().beep();
return;
}
}
char[] result = new char[source.length];
int j = 0;
for (int i = 0; i < result.length; i++) {
if (myHex ? "0123456789abcdefABCDEF".indexOf(source[i]) >= 0 : Character.isDigit(source[i])) {
result[j++] = source[i];
}
else {
Toolkit.getDefaultToolkit().beep();
}
}
final String toInsert = StringUtil.toUpperCase(new String(result, 0, j));
final String res = new StringBuilder(mySrc.getText()).insert(offs, toInsert).toString();
try {
if (!myHex) {
final int num = Integer.parseInt(res);
if (rgb) {
if (num > 255) {
Toolkit.getDefaultToolkit().beep();
return;
}
} else {
if ((mySrc == myRed && num > 359)
|| ((mySrc == myGreen || mySrc == myBlue) && num > 100)) {
Toolkit.getDefaultToolkit().beep();
return;
}
}
}
}
catch (NumberFormatException ignore) {
}
super.insertString(offs, toInsert, a);
}
}
private class RecentColorsComponent extends JComponent {
private static final int WIDTH = 10 * 30 + 13;
private static final int HEIGHT = 62 + 3;
private List<Color> myRecentColors = new ArrayList<Color>();
private RecentColorsComponent(final ColorListener listener, boolean restoreColors) {
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
Color color = getColor(e);
if (color != null) {
listener.colorChanged(color, RecentColorsComponent.this);
}
}
});
if (restoreColors) {
restoreColors();
}
}
@Nullable
public Color getMostRecentColor() {
return myRecentColors.isEmpty() ? null : myRecentColors.get(myRecentColors.size() - 1);
}
private void restoreColors() {
final String value = PropertiesComponent.getInstance().getValue(COLOR_CHOOSER_COLORS_KEY);
if (value != null) {
final List<String> colors = StringUtil.split(value, ",,,");
for (String color : colors) {
if (color.contains("-")) {
List<String> components = StringUtil.split(color, "-");
if (components.size() == 4) {
myRecentColors.add(new Color(Integer.parseInt(components.get(0)),
Integer.parseInt(components.get(1)),
Integer.parseInt(components.get(2)),
Integer.parseInt(components.get(3))));
}
}
else {
myRecentColors.add(new Color(Integer.parseInt(color)));
}
}
}
}
@Override
public String getToolTipText(MouseEvent event) {
Color color = getColor(event);
if (color != null) {
return String.format("R: %d G: %d B: %d A: %s", color.getRed(), color.getGreen(), color.getBlue(),
String.format("%.2f", (float)(color.getAlpha() / 255.0)));
}
return super.getToolTipText(event);
}
@Nullable
private Color getColor(MouseEvent event) {
Pair<Integer, Integer> pair = pointToCellCoords(event.getPoint());
if (pair != null) {
int ndx = pair.second + pair.first * 10;
if (myRecentColors.size() > ndx) {
return myRecentColors.get(ndx);
}
}
return null;
}
public void saveColors() {
final List<String> values = new ArrayList<String>();
for (Color recentColor : myRecentColors) {
if (recentColor == null) break;
values
.add(String.format("%d-%d-%d-%d", recentColor.getRed(), recentColor.getGreen(), recentColor.getBlue(), recentColor.getAlpha()));
}
PropertiesComponent.getInstance().setValue(COLOR_CHOOSER_COLORS_KEY, StringUtil.join(values, ",,,"));
}
public void appendColor(Color c) {
if (!myRecentColors.contains(c)) {
myRecentColors.add(c);
}
if (myRecentColors.size() > 20) {
myRecentColors = new ArrayList<Color>(myRecentColors.subList(myRecentColors.size() - 20, myRecentColors.size()));
}
}
@Nullable
private Pair<Integer, Integer> pointToCellCoords(Point p) {
int x = p.x;
int y = p.y;
final Insets i = getInsets();
final Dimension d = getSize();
final int left = i.left + (d.width - i.left - i.right - WIDTH) / 2;
final int top = i.top + (d.height - i.top - i.bottom - HEIGHT) / 2;
int col = (x - left - 2) / 31;
col = col > 9 ? 9 : col;
int row = (y - top - 2) / 31;
row = row > 1 ? 1 : row;
return row >= 0 && col >= 0 ? Pair.create(row, col) : null;
}
@Override
public Dimension getPreferredSize() {
return getMinimumSize();
}
@Override
public Dimension getMinimumSize() {
return new Dimension(WIDTH, HEIGHT);
}
@Override
protected void paintComponent(Graphics g) {
final Insets i = getInsets();
final Dimension d = getSize();
final int left = i.left + (d.width - i.left - i.right - WIDTH) / 2;
final int top = i.top + (d.height - i.top - i.bottom - HEIGHT) / 2;
g.setColor(Color.WHITE);
g.fillRect(left, top, WIDTH, HEIGHT);
g.setColor(Color.GRAY);
g.drawLine(left + 1, i.top + HEIGHT / 2, left + WIDTH - 3, i.top + HEIGHT / 2);
g.drawRect(left + 1, top + 1, WIDTH - 3, HEIGHT - 3);
for (int k = 1; k < 10; k++) {
g.drawLine(left + 1 + k * 31, top + 1, left + 1 + k * 31, top + HEIGHT - 3);
}
for (int r = 0; r < myRecentColors.size(); r++) {
int row = r / 10;
int col = r % 10;
Color color = myRecentColors.get(r);
g.setColor(color);
g.fillRect(left + 2 + col * 30 + col + 1, top + 2 + row * 30 + row + 1, 28, 28);
}
}
}
static class ColorPickerDialog extends DialogWrapper {
private final Color myPreselectedColor;
private final ColorPickerListener[] myListeners;
private ColorPicker myColorPicker;
private final boolean myEnableOpacity;
private ColorPipette myPicker;
private final boolean myOpacityInPercent;
public ColorPickerDialog(Component parent,
String caption,
@Nullable Color preselectedColor,
boolean enableOpacity,
ColorPickerListener[] listeners,
boolean opacityInPercent) {
super(parent, true);
myListeners = listeners;
setTitle(caption);
myPreselectedColor = preselectedColor;
myEnableOpacity = enableOpacity;
myOpacityInPercent = opacityInPercent;
setResizable(false);
setOKButtonText("Choose");
init();
addMouseListener((MouseMotionListener)new MouseAdapter() {
@Override
public void mouseEntered(MouseEvent e) {
myPicker.cancelPipette();
}
@Override
public void mouseExited(MouseEvent e) {
myPicker.pick();
}
});
}
@Override
protected JComponent createCenterPanel() {
if (myColorPicker == null) {
myColorPicker = new ColorPicker(myDisposable, myPreselectedColor, true, myEnableOpacity, myListeners, myOpacityInPercent);
}
return myColorPicker;
}
public Color getColor() {
return myColorPicker.getColor();
}
@Override
public JComponent getPreferredFocusedComponent() {
return myColorPicker.getPreferredFocusedComponent();
}
@Override
protected void doOKAction() {
myColorPicker.appendRecentColor();
myColorPicker.saveRecentColors();
super.doOKAction();
}
@Override
public void show() {
super.show();
myColorPicker.fireClosed(getExitCode() == DialogWrapper.OK_EXIT_CODE ? getColor() : null);
}
}
public static class ColorWheelImageProducer extends MemoryImageSource {
private int[] myPixels;
private int myWidth;
private int myHeight;
private float myBrightness = 1f;
private float[] myHues;
private float[] mySat;
private int[] myAlphas;
public ColorWheelImageProducer(int w, int h, float brightness) {
super(w, h, null, 0, w);
myPixels = new int[w * h];
myWidth = w;
myHeight = h;
myBrightness = brightness;
generateLookupTables();
newPixels(myPixels, ColorModel.getRGBdefault(), 0, w);
setAnimated(true);
generateColorWheel();
}
public int getRadius() {
return Math.min(myWidth, myHeight) / 2 - 2;
}
private void generateLookupTables() {
mySat = new float[myWidth * myHeight];
myHues = new float[myWidth * myHeight];
myAlphas = new int[myWidth * myHeight];
float radius = getRadius();
// blend is used to create a linear alpha gradient of two extra pixels
float blend = (radius + 2f) / radius - 1f;
// Center of the color wheel circle
int cx = myWidth / 2;
int cy = myHeight / 2;
for (int x = 0; x < myWidth; x++) {
int kx = x - cx; // Kartesian coordinates of x
int squarekx = kx * kx; // Square of kartesian x
for (int y = 0; y < myHeight; y++) {
int ky = cy - y; // Kartesian coordinates of y
int index = x + y * myWidth;
mySat[index] = (float)Math.sqrt(squarekx + ky
* ky)
/ radius;
if (mySat[index] <= 1f) {
myAlphas[index] = 0xff000000;
}
else {
myAlphas[index] = (int)((blend - Math.min(blend,
mySat[index] - 1f)) * 255 / blend) << 24;
mySat[index] = 1f;
}
if (myAlphas[index] != 0) {
myHues[index] = (float)(Math.atan2(ky, kx) / Math.PI / 2d);
}
}
}
}
public void generateColorWheel() {
for (int index = 0; index < myPixels.length; index++) {
if (myAlphas[index] != 0) {
myPixels[index] = myAlphas[index]
| 0xffffff
& Color.HSBtoRGB(myHues[index],
mySat[index], myBrightness);
}
}
newPixels();
}
}
private static class ColorPipette implements ImageObserver {
private Dialog myPickerFrame;
private final JComponent myParent;
private Color myOldColor;
private Timer myTimer;
private Point myPoint = new Point();
private Point myPickOffset;
private Robot myRobot = null;
private Color myPreviousColor;
private Point myPreviousLocation;
private Rectangle myCaptureRect;
private Graphics2D myGraphics;
private BufferedImage myImage;
private Point myHotspot;
private Point myCaptureOffset;
private BufferedImage myMagnifierImage;
private Color myTransparentColor = new Color(0, true);
private Rectangle myZoomRect;
private Rectangle myGlassRect;
private ColorListener myColorListener;
private BufferedImage myMaskImage;
private Alarm myColorListenersNotifier = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
private ColorPipette(JComponent parent, Color oldColor) {
myParent = parent;
myOldColor = oldColor;
try {
myRobot = new Robot();
}
catch (AWTException e) {
// should not happen
}
}
public void setListener(ColorListener colorListener) {
myColorListener = colorListener;
}
public void pick() {
Dialog picker = getPicker();
picker.setVisible(true);
myTimer.start();
// it seems like it's the lowest value for opacity for mouse events to be processed correctly
WindowManager.getInstance().setAlphaModeRatio(picker, SystemInfo.isMac ? 0.95f : 0.99f);
}
@Override
public boolean imageUpdate(Image img, int flags, int x, int y, int width, int height) {
return false;
}
private Dialog getPicker() {
if (myPickerFrame == null) {
Window owner = SwingUtilities.getWindowAncestor(myParent);
if (owner instanceof Dialog) {
myPickerFrame = new JDialog((Dialog)owner);
}
else if (owner instanceof Frame) {
myPickerFrame = new JDialog((Frame)owner);
}
else {
myPickerFrame = new JDialog(new JFrame());
}
myPickerFrame.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
e.consume();
pickDone();
}
@Override
public void mouseClicked(MouseEvent e) {
e.consume();
}
@Override
public void mouseExited(MouseEvent e) {
updatePipette();
}
});
myPickerFrame.addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
updatePipette();
}
});
myPickerFrame.addFocusListener(new FocusAdapter() {
@Override
public void focusLost(FocusEvent e) {
cancelPipette();
}
});
myPickerFrame.setSize(50, 50);
myPickerFrame.setUndecorated(true);
myPickerFrame.setAlwaysOnTop(true);
JRootPane rootPane = ((JDialog)myPickerFrame).getRootPane();
rootPane.putClientProperty("Window.shadow", Boolean.FALSE);
myGlassRect = new Rectangle(0, 0, 32, 32);
myPickOffset = new Point(0, 0);
myCaptureRect = new Rectangle(-4, -4, 8, 8);
myCaptureOffset = new Point(myCaptureRect.x, myCaptureRect.y);
myHotspot = new Point(14, 16);
myZoomRect = new Rectangle(0, 0, 32, 32);
myMaskImage = UIUtil.createImage(32, 32, BufferedImage.TYPE_INT_ARGB);
Graphics2D maskG = myMaskImage.createGraphics();
maskG.setColor(Color.BLUE);
maskG.fillRect(0, 0, 32, 32);
maskG.setColor(Color.RED);
maskG.setComposite(AlphaComposite.SrcOut);
maskG.fillRect(0, 0, 32, 32);
maskG.dispose();
myMagnifierImage = UIUtil.createImage(32, 32, BufferedImage.TYPE_INT_ARGB);
Graphics2D graphics = myMagnifierImage.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
graphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
graphics.setColor(Color.BLACK);
//graphics.drawOval(1, 1, 30, 30);
//graphics.drawOval(2, 2, 28, 28);
//
//graphics.drawLine(2, 16, 12, 16);
//graphics.drawLine(20, 16, 30, 16);
//
//graphics.drawLine(16, 2, 16, 12);
//graphics.drawLine(16, 20, 16, 30);
AllIcons.Ide.Pipette.paintIcon(null, graphics, 14, 0);
graphics.dispose();
myImage = myParent.getGraphicsConfiguration().createCompatibleImage(myMagnifierImage.getWidth(), myMagnifierImage.getHeight(),
Transparency.TRANSLUCENT);
myGraphics = (Graphics2D)myImage.getGraphics();
myGraphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
myPickerFrame.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
switch (e.getKeyCode()) {
case KeyEvent.VK_ESCAPE:
cancelPipette();
break;
case KeyEvent.VK_ENTER:
pickDone();
break;
}
}
});
myTimer = new Timer(5, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
updatePipette();
}
});
}
return myPickerFrame;
}
private void cancelPipette() {
myTimer.stop();
myPickerFrame.setVisible(false);
if (myColorListener != null && myOldColor != null) {
myColorListener.colorChanged(myOldColor, this);
}
}
public void pickDone() {
PointerInfo pointerInfo = MouseInfo.getPointerInfo();
Point location = pointerInfo.getLocation();
Color pixelColor = myRobot.getPixelColor(location.x + myPickOffset.x, location.y + myPickOffset.y);
cancelPipette();
if (myColorListener != null) {
myColorListener.colorChanged(pixelColor, this);
myOldColor = pixelColor;
}
}
private void updatePipette() {
if (myPickerFrame != null && myPickerFrame.isShowing()) {
PointerInfo pointerInfo = MouseInfo.getPointerInfo();
Point mouseLoc = pointerInfo.getLocation();
myPickerFrame.setLocation(mouseLoc.x - myPickerFrame.getWidth() / 2, mouseLoc.y - myPickerFrame.getHeight() / 2);
myPoint.x = mouseLoc.x + myPickOffset.x;
myPoint.y = mouseLoc.y + myPickOffset.y;
final Color c = myRobot.getPixelColor(myPoint.x, myPoint.y);
if (!c.equals(myPreviousColor) || !mouseLoc.equals(myPreviousLocation)) {
myPreviousColor = c;
myPreviousLocation = mouseLoc;
myCaptureRect.setLocation(mouseLoc.x - 2/*+ myCaptureOffset.x*/, mouseLoc.y - 2/*+ myCaptureOffset.y*/);
myCaptureRect.setBounds(mouseLoc.x -2, mouseLoc.y -2, 5, 5);
BufferedImage capture = myRobot.createScreenCapture(myCaptureRect);
// Clear the cursor graphics
myGraphics.setComposite(AlphaComposite.Src);
myGraphics.setColor(myTransparentColor);
myGraphics.fillRect(0, 0, myImage.getWidth(), myImage.getHeight());
myGraphics.drawImage(capture, myZoomRect.x, myZoomRect.y, myZoomRect.width, myZoomRect.height, this);
// cropping round image
myGraphics.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_OUT));
myGraphics.drawImage(myMaskImage, myZoomRect.x, myZoomRect.y, myZoomRect.width, myZoomRect.height, this);
// paint magnifier
myGraphics.setComposite(AlphaComposite.SrcOver);
myGraphics.drawImage(myMagnifierImage, 0, 0, this);
// We need to create a new subImage. This forces that
// the color picker uses the new imagery.
//BufferedImage subImage = myImage.getSubimage(0, 0, myImage.getWidth(), myImage.getHeight());
myPickerFrame.setCursor(myParent.getToolkit().createCustomCursor(myImage, myHotspot, "ColorPicker"));
if (myColorListener != null) {
myColorListenersNotifier.cancelAllRequests();
myColorListenersNotifier.addRequest(new Runnable() {
@Override
public void run() {
myColorListener.colorChanged(c, ColorPipette.this);
}
}, 300);
}
}
}
}
//public static void pickColor(ColorListener listener, JComponent c) {
// new ColorPipette(c, new ColorListener() {
// @Override
// public void colorChanged(Color color, Object source) {
// ColorPicker.this.setColor(color, my);
// }
// }).pick(listener);
//}
public static boolean isAvailable() {
try {
Robot robot = new Robot();
robot.createScreenCapture(new Rectangle(0, 0, 1, 1));
return WindowManager.getInstance().isAlphaModeSupported();
}
catch (AWTException e) {
return false;
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
showDialog(null, "", null, true, null, false);
}
});
}
}
interface ColorListener {
void colorChanged(Color color, Object source);
}