package nodebox.ui; import javax.swing.*; import javax.swing.border.EmptyBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import javax.swing.event.EventListenerList; import java.awt.*; import java.awt.Color; import java.awt.event.*; import java.text.NumberFormat; import java.util.Locale; import java.util.prefs.Preferences; public class ColorDialog extends JDialog implements ChangeListener { public static final String COLOR_RANGE = "NBColorRange"; public enum ColorRange { PERCENTAGE, ABSOLUTE } public enum ColorComponent { RED, GREEN, BLUE, ALPHA, HUE, SATURATION, BRIGHTNESS } private OKAction okAction = new OKAction(); private CancelAction cancelAction = new CancelAction(); private Preferences preferences; private ColorRangeMenu rangeBox; private Color color; private Color newColor; private ColorField colorField; private JTextField hexField; private ColorPanel[] panels; private ColorRange colorRange; private DraggableNumber hueDraggable, saturationDraggable, brightnessDraggable; private DraggableNumber redDraggable, blueDraggable, greenDraggable; private DraggableNumber alphaDraggable; private float hue, saturation, brightness, red, green, blue, alpha; /** * Only one <code>ChangeEvent</code> is needed per slider instance since the * event's only (read-only) state is the source property. The source * of events generated here is always "this". The event is lazily * created the first time that an event notification is fired. * * @see #fireStateChanged */ protected transient ChangeEvent changeEvent = null; /** * A list of event listeners for this component. */ protected EventListenerList listenerList = new EventListenerList(); public ColorDialog(Frame owner) { super(owner, "Choose Color"); getRootPane().putClientProperty("Window.style", "small"); colorField = new ColorField(); Dimension d = new Dimension(Integer.MAX_VALUE, 80); JPanel topPanel = new JPanel(); topPanel.setLayout(new BorderLayout()); topPanel.setMinimumSize(d); topPanel.setPreferredSize(d); topPanel.setMaximumSize(d); topPanel.setSize(d); topPanel.add(colorField, BorderLayout.WEST); JPanel hexPanel = new JPanel(null); hexPanel.setMinimumSize(d); hexPanel.setPreferredSize(d); hexPanel.setMaximumSize(d); hexPanel.setSize(d); hexField = new JTextField(); hexField.setFont(Theme.SMALL_BOLD_FONT); hexField.setForeground(Theme.TEXT_NORMAL_COLOR); hexField.setHorizontalAlignment(JTextField.CENTER); hexField.setBackground(null); hexPanel.add(hexField); hexField.setBounds(0, 10, 110, 22); hexField.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { String s = hexField.getText().toLowerCase(Locale.US); if (! s.startsWith("#")) s = "#" + s; if (s.length() == 7) s = s + "ff"; try { nodebox.graphics.Color c = new nodebox.graphics.Color(s); red = (float) c.getRed(); green = (float) c.getGreen(); blue = (float) c.getBlue(); alpha = (float) c.getAlpha(); } catch (IllegalArgumentException ex) { JOptionPane.showMessageDialog(ColorDialog.this, ex.getMessage()); } updateHSB(); updateColor(); } }); topPanel.add(hexPanel, BorderLayout.CENTER); topPanel.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 7)); ColorPanel red = new ColorPanel("red"); ColorPanel green = new ColorPanel("green"); ColorPanel blue = new ColorPanel("blue"); ColorPanel alpha = new ColorPanel("alpha"); ColorPanel hue = new ColorPanel("hue"); ColorPanel saturation = new ColorPanel("saturation"); ColorPanel brightness = new ColorPanel("brightness"); panels = new ColorPanel[]{red, green, blue, alpha, hue, saturation, brightness}; d = new Dimension(120, Integer.MAX_VALUE); rangeBox = new ColorRangeMenu(); rangeBox.setMinimumSize(d); rangeBox.setPreferredSize(d); rangeBox.setSize(d); JButton cancelButton = new JButton(cancelAction); JButton okButton = new JButton(okAction); d = new Dimension(Integer.MAX_VALUE, 30); JPanel bottomPanel = new JPanel(); bottomPanel.setLayout(new BoxLayout(bottomPanel, BoxLayout.X_AXIS)); bottomPanel.setMinimumSize(d); bottomPanel.setPreferredSize(d); bottomPanel.setMaximumSize(d); bottomPanel.setSize(d); bottomPanel.add(Box.createHorizontalStrut(77)); bottomPanel.add(rangeBox); bottomPanel.add(Box.createHorizontalGlue()); bottomPanel.add(cancelButton); bottomPanel.add(okButton); bottomPanel.add(Box.createHorizontalStrut(5)); getRootPane().setDefaultButton(okButton); Container contents = getContentPane(); contents.setLayout(new BoxLayout(contents, BoxLayout.Y_AXIS)); contents.add(topPanel); contents.add(hue); contents.add(saturation); contents.add(brightness); contents.add(Box.createVerticalStrut(12)); contents.add(red); contents.add(green); contents.add(blue); contents.add(Box.createVerticalStrut(12)); contents.add(alpha); contents.add(bottomPanel); contents.add(Box.createVerticalStrut(5)); pack(); setColor(Color.WHITE); this.preferences = Preferences.userNodeForPackage(this.getClass()); setColorRange(ColorRange.valueOf(getPreferences().get(COLOR_RANGE, "ABSOLUTE"))); KeyStroke escapeStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); getRootPane().registerKeyboardAction(new ActionListener() { public void actionPerformed(ActionEvent e) { setColor(color); dispose(); } }, escapeStroke, JComponent.WHEN_IN_FOCUSED_WINDOW); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { setColor(newColor); } }); } private float clamp(float v) { return Math.max(0F, Math.min(1F, v)); } public void setColor(Color color) { this.color = color; this.newColor = color; float[] components = color.getComponents(null); assert components.length == 4; setRGBA(components[0], components[1], components[2], components[3]); updatePanels(); fireStateChanged(); } public Color getColor() { return newColor; } public void setRGB(float r, float g, float b) { setRGBA(r, g, b, 1.0F); } public void setRGBA(float r, float g, float b, float a) { if (red == r && green == g && blue == b && alpha == a) return; red = clamp(r); green = clamp(g); blue = clamp(b); alpha = clamp(a); updateHSB(); } private void updateRGB() { if (saturation == 0) red = green = blue = brightness; else { float h = hue; if (hue == 1.0) h = 0.999998f; float s = saturation; float v = brightness; float r, g, b, f, p, q, t; h = h / (float) (60.0 / 360); int i = (int) Math.floor(h); f = h - i; p = v * (1 - s); q = v * (1 - s * f); t = v * (1 - s * (1 - f)); float rgb[]; if (i == 0) rgb = new float[]{v, t, p}; else if (i == 1) rgb = new float[]{q, v, p}; else if (i == 2) rgb = new float[]{p, v, t}; else if (i == 3) rgb = new float[]{p, q, v}; else if (i == 4) rgb = new float[]{t, p, v}; else rgb = new float[]{v, p, q}; red = rgb[0]; green = rgb[1]; blue = rgb[2]; } } public void setHSBA(float h, float s, float b, float a) { if (hue == h && saturation == s && brightness == b && alpha == a) return; hue = clamp(h); saturation = clamp(s); brightness = clamp(b); alpha = clamp(a); updateRGB(); } private void updateHSB() { float h = 0; float s = 0; float v = Math.max(Math.max(red, green), blue); float d = v - Math.min(Math.min(red, green), blue); if (v != 0) s = d / v; if (s != 0) { if (red == v) h = 0 + (green - blue) / d; else if (green == v) h = 2 + (blue - red) / d; else h = 4 + (red - green) / d; } h = h * (float) (60.0 / 360); if (h < 0) h = h + 1; hue = h; saturation = s; brightness = v; } public float getRed() { return red; } public void setRed(float r) { red = clamp(r); updateHSB(); updateColor(); } public float getGreen() { return green; } public void setGreen(float g) { green = clamp(g); updateHSB(); updateColor(); } public float getBlue() { return blue; } public void setBlue(float b) { blue = clamp(b); updateHSB(); updateColor(); } public float getAlpha() { return alpha; } public void setAlpha(float a) { alpha = clamp(a); updateColor(); } public float getHue() { return hue; } public void setHue(float h) { hue = clamp(h); updateRGB(); updateColor(); } public float getSaturation() { return saturation; } public void setSaturation(float s) { saturation = clamp(s); updateRGB(); updateColor(); } public float getBrightness() { return brightness; } public void setBrightness(float b) { brightness = clamp(b); updateRGB(); updateColor(); } private void updateColor() { newColor = new Color(red, green, blue, alpha); updatePanels(); fireStateChanged(); } private void updatePanels() { colorField.repaint(); for (ColorPanel panel : panels) { panel.updateDraggableNumber(); panel.repaint(); } hexField.setText(new nodebox.graphics.Color(newColor).toString()); } public ColorRange getColorRange() { return colorRange; } public void setColorRange(ColorRange colorRange) { this.colorRange = colorRange; getPreferences().put(COLOR_RANGE, colorRange.toString()); for (ColorPanel panel : panels) panel.setColorRange(colorRange); } public Preferences getPreferences() { return preferences; } /** * Adds a ChangeListener to the slider. * * @param l the ChangeListener to add * @see #fireStateChanged * @see #removeChangeListener */ public void addChangeListener(ChangeListener l) { listenerList.add(ChangeListener.class, l); } /** * Removes a ChangeListener from the slider. * * @param l the ChangeListener to remove * @see #fireStateChanged * @see #addChangeListener */ public void removeChangeListener(ChangeListener l) { listenerList.remove(ChangeListener.class, l); } /** * Send a ChangeEvent, whose source is this Slider, to * each listener. This method method is called each time * a ChangeEvent is received from the model. * * @see #addChangeListener * @see javax.swing.event.EventListenerList */ protected void fireStateChanged() { Object[] listeners = listenerList.getListenerList(); for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == ChangeListener.class) { if (changeEvent == null) { changeEvent = new ChangeEvent(this); } ((ChangeListener) listeners[i + 1]).stateChanged(changeEvent); } } } public void stateChanged(ChangeEvent e) { } public static void main(String[] args) { ColorDialog cd = new ColorDialog(null); cd.setSize(500, 340); cd.setLocationByPlatform(true); cd.setAlwaysOnTop(true); cd.setVisible(true); } public class ColorField extends JButton { public ColorField() { Dimension d = new Dimension(75, 75); setMinimumSize(d); setPreferredSize(d); addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { if (e.getX() >= 2 && e.getX() <= 70 && e.getY() >= 38 && e.getY() <= 70) setColor(color); } }); } @Override protected void paintComponent(Graphics g) { g.setColor(newColor); g.fillRect(2, 2, 68, 35); g.setColor(color); g.fillRect(2, 37, 68, 34); g.setColor(Color.LIGHT_GRAY); g.drawRect(2, 2, 67, 67); g.setColor(new Color(0.6F, 0.6F, 0.6F)); g.drawLine(3, 2, 68, 2); } } public class ColorPanel extends JPanel implements ChangeListener { private ColorComponent colorComponent; private ColorSlider slider; private DraggableNumber draggableNumber; public ColorPanel(String colorComponent) { this(ColorComponent.valueOf(colorComponent.toUpperCase(Locale.US))); } public ColorPanel(ColorComponent colorComponent) { this.colorComponent = colorComponent; setLayout(new BoxLayout(this, BoxLayout.X_AXIS)); Dimension d = new Dimension(Integer.MAX_VALUE, 30); setMinimumSize(d); setPreferredSize(d); setMaximumSize(d); JLabel label = new JLabel(nodebox.util.StringUtils.humanizeName(colorComponent.toString().toLowerCase(Locale.US)), JLabel.RIGHT); Dimension size = label.getSize(); label.setFont(Theme.SMALL_BOLD_FONT); label.setForeground(new Color(66, 66, 66)); label.setPreferredSize(new Dimension(75, size.height)); label.setAlignmentY(JLabel.CENTER); label.setBorder(new EmptyBorder(3, 0, 0, 0)); add(label); add(Box.createHorizontalStrut(2)); draggableNumber = new DraggableNumber(); draggableNumber.addChangeListener(this); d = new Dimension(20, Integer.MAX_VALUE); draggableNumber.setPreferredSize(d); draggableNumber.setMaximumSize(d); draggableNumber.setSize(d); add(draggableNumber); slider = new ColorSlider(this); add(slider); add(Box.createHorizontalStrut(7)); } public void setColorRange(ColorRange colorRange) { if (colorRange == ColorRange.ABSOLUTE) { NumberFormat intFormat = NumberFormat.getNumberInstance(Locale.US); intFormat.setMinimumFractionDigits(0); intFormat.setMaximumFractionDigits(0); draggableNumber.setNumberFormat(intFormat); draggableNumber.setMinimumValue(0.0); draggableNumber.setMaximumValue(255.0); } else if (colorRange == ColorRange.PERCENTAGE) { NumberFormat floatFormat = NumberFormat.getNumberInstance(Locale.US); floatFormat.setMinimumFractionDigits(2); floatFormat.setMaximumFractionDigits(2); draggableNumber.setNumberFormat(floatFormat); draggableNumber.setMinimumValue(0.0); draggableNumber.setMaximumValue(100.0); } updateDraggableNumber(); } public void setValue(float value) { switch (colorComponent) { case RED: setRed(value); break; case GREEN: setGreen(value); break; case BLUE: setBlue(value); break; case ALPHA: setAlpha(value); break; case HUE: setHue(value); break; case SATURATION: setSaturation(value); break; case BRIGHTNESS: setBrightness(value); break; default: break; } } public void updateDraggableNumber() { float range = 0.0F; if (colorRange == ColorRange.ABSOLUTE) range = 255.0F; else if (colorRange == ColorRange.PERCENTAGE) range = 100.0F; switch (colorComponent) { case RED: draggableNumber.setValue(red * range); break; case GREEN: draggableNumber.setValue(green * range); break; case BLUE: draggableNumber.setValue(blue * range); break; case ALPHA: draggableNumber.setValue(alpha * range); break; case HUE: draggableNumber.setValue(hue * range); break; case SATURATION: draggableNumber.setValue(saturation * range); break; case BRIGHTNESS: draggableNumber.setValue(brightness * range); break; default: break; } } public ColorComponent getColorComponent() { return colorComponent; } public void stateChanged(ChangeEvent e) { if (e.getSource() == draggableNumber) { float range = 0.0F; if (colorRange == ColorRange.ABSOLUTE) range = 255.0F; else if (colorRange == ColorRange.PERCENTAGE) range = 100.0F; float v = (float) (draggableNumber.getValue() / range); setValue(v); } } } public class ColorSlider extends JComponent { private ColorPanel panel; private final int WIDTH_OFFSET = 8; private final int HALF_WIDTH_OFFSET = WIDTH_OFFSET / 2; private final int HEIGHT_OFFSET = 10; private final int ARROW_HEIGHT = 6; public ColorSlider(ColorPanel panel) { this.panel = panel; enableEvents(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK); } @Override protected void paintComponent(Graphics g) { Graphics2D g2 = (Graphics2D) g; g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Rectangle r = g.getClipBounds(); float value = 0F; switch (panel.getColorComponent()) { case RED: drawRed(g); value = red; break; case GREEN: drawGreen(g); value = green; break; case BLUE: drawBlue(g); value = blue; break; case ALPHA: drawAlpha(g); value = alpha; break; case HUE: drawHue(g); value = hue; break; case SATURATION: drawSaturation(g); value = saturation; break; case BRIGHTNESS: drawBrightness(g); value = brightness; break; default: break; } g.setColor(new Color(0.85F, 0.85F, 0.85F)); g.drawLine(HALF_WIDTH_OFFSET, r.height - HEIGHT_OFFSET - 1, r.width - HALF_WIDTH_OFFSET - 1, r.height - HEIGHT_OFFSET - 1); g.drawLine(r.width - HALF_WIDTH_OFFSET - 1, 0, r.width - HALF_WIDTH_OFFSET - 1, r.height - HEIGHT_OFFSET - 1); g.drawLine(HALF_WIDTH_OFFSET, 0, HALF_WIDTH_OFFSET, r.height - HEIGHT_OFFSET - 1); g.setColor(new Color(0.65F, 0.65F, 0.65F)); g.drawLine(HALF_WIDTH_OFFSET + 1, 0, r.width - HALF_WIDTH_OFFSET - 1, 0); int i = (int) Math.round(value * (r.width - WIDTH_OFFSET)); g.setColor(Color.BLACK); Polygon p = new Polygon(); p.addPoint(i + HALF_WIDTH_OFFSET, r.height - HEIGHT_OFFSET); p.addPoint(i + WIDTH_OFFSET, r.height - ARROW_HEIGHT); p.addPoint(i, r.height - ARROW_HEIGHT); g.fillPolygon(p); } private void drawRed(Graphics g) { Rectangle r = g.getClipBounds(); int width = r.width - WIDTH_OFFSET; for (int i = 0; i < width; i++) { Color c = new Color((float) i / width, green, blue); g.setColor(c); g.fillRect(i + HALF_WIDTH_OFFSET, 0, 1, r.height - HEIGHT_OFFSET); } } private void drawGreen(Graphics g) { Rectangle r = g.getClipBounds(); int width = r.width - WIDTH_OFFSET; for (int i = 0; i < width; i++) { Color c = new Color(red, (float) i / width, blue); g.setColor(c); g.fillRect(i + HALF_WIDTH_OFFSET, 0, 1, r.height - HEIGHT_OFFSET); } } private void drawBlue(Graphics g) { Rectangle r = g.getClipBounds(); int width = r.width - WIDTH_OFFSET; for (int i = 0; i < width; i++) { Color c = new Color(red, green, (float) i / width); g.setColor(c); g.fillRect(i + HALF_WIDTH_OFFSET, 0, 1, r.height - HEIGHT_OFFSET); } } private void drawAlpha(Graphics g) { Rectangle r = g.getClipBounds(); int width = r.width - WIDTH_OFFSET; for (int i = 0; i < width; i++) { Color c = new Color(red, green, blue, (float) i / width); g.setColor(c); g.fillRect(i + HALF_WIDTH_OFFSET, 0, 1, r.height - HEIGHT_OFFSET); } } private void drawHue(Graphics g) { Rectangle r = g.getClipBounds(); int width = r.width - WIDTH_OFFSET; for (int i = 0; i < width; i++) { Color hsb = Color.getHSBColor((float) i / width, saturation, brightness); Color c = new Color(hsb.getRed(), hsb.getGreen(), hsb.getBlue()); g.setColor(c); g.fillRect(i + HALF_WIDTH_OFFSET, 0, 1, r.height - HEIGHT_OFFSET); } } private void drawSaturation(Graphics g) { Rectangle r = g.getClipBounds(); int width = r.width - WIDTH_OFFSET; for (int i = 0; i < width; i++) { Color hsb = Color.getHSBColor(hue, (float) i / width, brightness); Color c = new Color(hsb.getRed(), hsb.getGreen(), hsb.getBlue()); g.setColor(c); g.fillRect(i + HALF_WIDTH_OFFSET, 0, 1, r.height - HEIGHT_OFFSET); } } private void drawBrightness(Graphics g) { Rectangle r = g.getClipBounds(); int width = r.width - WIDTH_OFFSET; for (int i = 0; i < width; i++) { Color hsb = Color.getHSBColor(hue, saturation, (float) i / width); Color c = new Color(hsb.getRed(), hsb.getGreen(), hsb.getBlue()); g.setColor(c); g.fillRect(i + HALF_WIDTH_OFFSET, 0, 1, r.height - HEIGHT_OFFSET); } } @Override protected void processMouseEvent(MouseEvent e) { if (e.getID() == MouseEvent.MOUSE_PRESSED) { int width = getWidth() - WIDTH_OFFSET; int x = e.getX(); if (x < HALF_WIDTH_OFFSET) panel.setValue(0); else if (x > width + WIDTH_OFFSET) panel.setValue(1); else panel.setValue((float) (x - HALF_WIDTH_OFFSET) / width); } } @Override protected void processMouseMotionEvent(MouseEvent e) { if (e.getID() == MouseEvent.MOUSE_DRAGGED) { int width = getWidth() - WIDTH_OFFSET; int x = e.getX(); if (x < HALF_WIDTH_OFFSET) panel.setValue(0); else if (x > width + WIDTH_OFFSET) panel.setValue(1); else panel.setValue((float) (x - HALF_WIDTH_OFFSET) / width); } } } private class ColorRangeMenu extends PaneMenu { private ColorRangePopup colorRangePopup; public ColorRangeMenu() { colorRangePopup = new ColorRangePopup(); } @Override public void mousePressed(MouseEvent e) { colorRangePopup.show(this, 0, 20); } @Override public String getMenuName() { switch (colorRange) { case PERCENTAGE: return "0-100"; case ABSOLUTE: return "0-255"; default: return "0-255"; } } private class ColorRangePopup extends JPopupMenu { public ColorRangePopup() { add(new ChangeColorRangeAction("0-100", ColorRange.PERCENTAGE)); add(new ChangeColorRangeAction("0-255", ColorRange.ABSOLUTE)); } } private class ChangeColorRangeAction extends AbstractAction { private ColorRange colorRange; private ChangeColorRangeAction(String name, ColorRange colorRange) { super(name); this.colorRange = colorRange; } public void actionPerformed(ActionEvent e) { setColorRange(colorRange); ColorRangeMenu.this.repaint(); } } } public class OKAction extends AbstractAction { public OKAction() { putValue(NAME, "Ok"); putValue(ACCELERATOR_KEY, Platform.getKeyStroke(KeyEvent.VK_ENTER)); } public void actionPerformed(ActionEvent e) { setColor(newColor); ColorDialog.this.setVisible(false); } } public class CancelAction extends AbstractAction { public CancelAction() { putValue(NAME, "Cancel"); putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0)); } public void actionPerformed(ActionEvent e) { setColor(color); ColorDialog.this.setVisible(false); } } }