1 /* 2 * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package com.sun.java.swing.plaf.gtk; 26 27 import java.awt.*; 28 import java.awt.event.*; 29 import java.awt.image.*; 30 import javax.swing.*; 31 import javax.swing.colorchooser.*; 32 import javax.swing.event.*; 33 import javax.swing.plaf.*; 34 35 /** 36 * A color chooser panel mimicking that of GTK's: a color wheel showing 37 * hue and a triangle that varies saturation and brightness. 38 * 39 * @author Scott Violet 40 */ 41 class GTKColorChooserPanel extends AbstractColorChooserPanel implements 42 ChangeListener { 43 private static final float PI_3 = (float)(Math.PI / 3); 44 45 private ColorTriangle triangle; 46 private JLabel lastLabel; 47 private JLabel label; 48 49 private JSpinner hueSpinner; 50 private JSpinner saturationSpinner; 51 private JSpinner valueSpinner; 52 53 private JSpinner redSpinner; 54 private JSpinner greenSpinner; 55 private JSpinner blueSpinner; 56 57 private JTextField colorNameTF; 58 59 private boolean settingColor; 60 61 // The colors are mirrored to avoid creep in adjusting an individual 62 // value. 63 private float hue; 64 private float saturation; 65 private float brightness; 66 67 68 69 /** 70 * Convenience method to transfer focus to the next child of component. 71 */ 72 // PENDING: remove this when a variant of this is added to awt. compositeRequestFocus(Component component, boolean direction)73 static void compositeRequestFocus(Component component, boolean direction) { 74 if (component instanceof Container) { 75 Container container = (Container)component; 76 if (container.isFocusCycleRoot()) { 77 FocusTraversalPolicy policy = container. 78 getFocusTraversalPolicy(); 79 Component comp = policy.getDefaultComponent(container); 80 if (comp!=null) { 81 comp.requestFocus(); 82 return; 83 } 84 } 85 Container rootAncestor = container.getFocusCycleRootAncestor(); 86 if (rootAncestor!=null) { 87 FocusTraversalPolicy policy = rootAncestor. 88 getFocusTraversalPolicy(); 89 Component comp; 90 91 if (direction) { 92 comp = policy.getComponentAfter(rootAncestor, container); 93 } 94 else { 95 comp = policy.getComponentBefore(rootAncestor, container); 96 } 97 if (comp != null) { 98 comp.requestFocus(); 99 return; 100 } 101 } 102 } 103 component.requestFocus(); 104 } 105 106 107 /** 108 * Returns a user presentable description of this GTKColorChooserPane. 109 */ getDisplayName()110 public String getDisplayName() { 111 return (String)UIManager.get("GTKColorChooserPanel.nameText"); 112 } 113 114 /** 115 * Returns the mnemonic to use with <code>getDisplayName</code>. 116 */ getMnemonic()117 public int getMnemonic() { 118 String m = (String)UIManager.get("GTKColorChooserPanel.mnemonic"); 119 120 if (m != null) { 121 try { 122 int value = Integer.parseInt(m); 123 124 return value; 125 } catch (NumberFormatException nfe) {} 126 } 127 return -1; 128 } 129 130 /** 131 * Character to underline that represents the mnemonic. 132 */ getDisplayedMnemonicIndex()133 public int getDisplayedMnemonicIndex() { 134 String m = (String)UIManager.get( 135 "GTKColorChooserPanel.displayedMnemonicIndex"); 136 137 if (m != null) { 138 try { 139 int value = Integer.parseInt(m); 140 141 return value; 142 } catch (NumberFormatException nfe) {} 143 } 144 return -1; 145 } 146 getSmallDisplayIcon()147 public Icon getSmallDisplayIcon() { 148 return null; 149 } 150 getLargeDisplayIcon()151 public Icon getLargeDisplayIcon() { 152 return null; 153 } 154 uninstallChooserPanel(JColorChooser enclosingChooser)155 public void uninstallChooserPanel(JColorChooser enclosingChooser) { 156 super.uninstallChooserPanel(enclosingChooser); 157 removeAll(); 158 } 159 160 /** 161 * Builds and configures the widgets for the GTKColorChooserPanel. 162 */ buildChooser()163 protected void buildChooser() { 164 triangle = new ColorTriangle(); 165 triangle.setName("GTKColorChooserPanel.triangle"); 166 167 // PENDING: when we straighten out user setting opacity, this should 168 // be changed. 169 label = new OpaqueLabel(); 170 label.setName("GTKColorChooserPanel.colorWell"); 171 label.setOpaque(true); 172 label.setMinimumSize(new Dimension(67, 32)); 173 label.setPreferredSize(new Dimension(67, 32)); 174 label.setMaximumSize(new Dimension(67, 32)); 175 176 // PENDING: when we straighten out user setting opacity, this should 177 // be changed. 178 lastLabel = new OpaqueLabel(); 179 lastLabel.setName("GTKColorChooserPanel.lastColorWell"); 180 lastLabel.setOpaque(true); 181 lastLabel.setMinimumSize(new Dimension(67, 32)); 182 lastLabel.setPreferredSize(new Dimension(67, 32)); 183 lastLabel.setMaximumSize(new Dimension(67, 32)); 184 185 hueSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 360, 1)); 186 configureSpinner(hueSpinner, "GTKColorChooserPanel.hueSpinner"); 187 saturationSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 255, 1)); 188 configureSpinner(saturationSpinner, 189 "GTKColorChooserPanel.saturationSpinner"); 190 valueSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 255, 1)); 191 configureSpinner(valueSpinner, "GTKColorChooserPanel.valueSpinner"); 192 redSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 255, 1)); 193 configureSpinner(redSpinner, "GTKColorChooserPanel.redSpinner"); 194 greenSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 255, 1)); 195 configureSpinner(greenSpinner, "GTKColorChooserPanel.greenSpinner"); 196 blueSpinner = new JSpinner(new SpinnerNumberModel(0, 0, 255, 1)); 197 configureSpinner(blueSpinner, "GTKColorChooserPanel.blueSpinner"); 198 199 colorNameTF = new JTextField(8); 200 201 setLayout(new GridBagLayout()); 202 203 add(this, "GTKColorChooserPanel.hue", hueSpinner, -1, -1); 204 add(this, "GTKColorChooserPanel.red", redSpinner, -1, -1); 205 add(this, "GTKColorChooserPanel.saturation", saturationSpinner, -1,-1); 206 add(this, "GTKColorChooserPanel.green", greenSpinner, -1, -1); 207 add(this, "GTKColorChooserPanel.value", valueSpinner, -1, -1); 208 add(this, "GTKColorChooserPanel.blue", blueSpinner, -1, -1); 209 210 add(new JSeparator(SwingConstants.HORIZONTAL), new 211 GridBagConstraints(1, 3, 4, 1, 1, 0, 212 GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, 213 new Insets(14, 0, 0, 0), 0, 0)); 214 215 add(this, "GTKColorChooserPanel.colorName", colorNameTF, 0, 4); 216 217 add(triangle, new GridBagConstraints(0, 0, 1, 5, 0, 0, 218 GridBagConstraints.LINE_START, GridBagConstraints.NONE, 219 new Insets(14, 20, 2, 9), 0, 0)); 220 221 Box hBox = Box.createHorizontalBox(); 222 hBox.add(lastLabel); 223 hBox.add(label); 224 add(hBox, new GridBagConstraints(0, 5, 1, 1, 0, 0, 225 GridBagConstraints.CENTER, GridBagConstraints.NONE, 226 new Insets(0, 0, 0, 0), 0, 0)); 227 228 add(new JSeparator(SwingConstants.HORIZONTAL), new 229 GridBagConstraints(0, 6, 5, 1, 1, 0, 230 GridBagConstraints.LINE_START, GridBagConstraints.HORIZONTAL, 231 new Insets(12, 0, 0, 0), 0, 0)); 232 } 233 234 /** 235 * Configures the spinner. 236 */ configureSpinner(JSpinner spinner, String name)237 private void configureSpinner(JSpinner spinner, String name) { 238 spinner.addChangeListener(this); 239 spinner.setName(name); 240 JComponent editor = spinner.getEditor(); 241 if (editor instanceof JSpinner.DefaultEditor) { 242 JFormattedTextField ftf = ((JSpinner.DefaultEditor)editor). 243 getTextField(); 244 245 ftf.setFocusLostBehavior(JFormattedTextField.COMMIT_OR_REVERT); 246 } 247 } 248 249 /** 250 * Adds the widget creating a JLabel with the specified name. 251 */ add(Container parent, String key, JComponent widget, int x, int y)252 private void add(Container parent, String key, JComponent widget, 253 int x, int y) { 254 JLabel label = new JLabel(UIManager.getString(key + "Text", 255 getLocale())); 256 String mnemonic = (String)UIManager.get(key + "Mnemonic", getLocale()); 257 258 if (mnemonic != null) { 259 try { 260 label.setDisplayedMnemonic(Integer.parseInt(mnemonic)); 261 } catch (NumberFormatException nfe) { 262 } 263 String mnemonicIndex = (String)UIManager.get(key + "MnemonicIndex", 264 getLocale()); 265 266 if (mnemonicIndex != null) { 267 try { 268 label.setDisplayedMnemonicIndex(Integer.parseInt( 269 mnemonicIndex)); 270 } catch (NumberFormatException nfe) { 271 } 272 } 273 } 274 label.setLabelFor(widget); 275 if (x < 0) { 276 x = parent.getComponentCount() % 4; 277 } 278 if (y < 0) { 279 y = parent.getComponentCount() / 4; 280 } 281 GridBagConstraints con = new GridBagConstraints(x + 1, y, 1, 1, 0, 0, 282 GridBagConstraints.FIRST_LINE_END, GridBagConstraints.NONE, 283 new Insets(4, 0, 0, 4), 0, 0); 284 if (y == 0) { 285 con.insets.top = 14; 286 } 287 parent.add(label, con); 288 con.gridx++; 289 parent.add(widget, con); 290 } 291 292 /** 293 * Refreshes the display from the model. 294 */ updateChooser()295 public void updateChooser() { 296 if (!settingColor) { 297 lastLabel.setBackground(getColorFromModel()); 298 setColor(getColorFromModel(), true, true, false); 299 } 300 } 301 302 /** 303 * Resets the red component of the selected color. 304 */ setRed(int red)305 private void setRed(int red) { 306 setRGB(red << 16 | getColor().getGreen() << 8 | getColor().getBlue()); 307 } 308 309 /** 310 * Resets the green component of the selected color. 311 */ setGreen(int green)312 private void setGreen(int green) { 313 setRGB(getColor().getRed() << 16 | green << 8 | getColor().getBlue()); 314 } 315 316 /** 317 * Resets the blue component of the selected color. 318 */ setBlue(int blue)319 private void setBlue(int blue) { 320 setRGB(getColor().getRed() << 16 | getColor().getGreen() << 8 | blue); 321 } 322 323 /** 324 * Sets the hue of the selected color and updates the display if 325 * necessary. 326 */ setHue(float hue, boolean update)327 private void setHue(float hue, boolean update) { 328 setHSB(hue, saturation, brightness); 329 if (update) { 330 settingColor = true; 331 hueSpinner.setValue(Integer.valueOf((int)(hue * 360))); 332 settingColor = false; 333 } 334 } 335 336 /** 337 * Returns the current amount of hue. 338 */ getHue()339 private float getHue() { 340 return hue; 341 } 342 343 /** 344 * Resets the saturation. 345 */ setSaturation(float saturation)346 private void setSaturation(float saturation) { 347 setHSB(hue, saturation, brightness); 348 } 349 350 /** 351 * Returns the saturation. 352 */ getSaturation()353 private float getSaturation() { 354 return saturation; 355 } 356 357 /** 358 * Sets the brightness. 359 */ setBrightness(float brightness)360 private void setBrightness(float brightness) { 361 setHSB(hue, saturation, brightness); 362 } 363 364 /** 365 * Returns the brightness. 366 */ getBrightness()367 private float getBrightness() { 368 return brightness; 369 } 370 371 /** 372 * Sets the saturation and brightness and updates the display if 373 * necessary. 374 */ setSaturationAndBrightness(float s, float b, boolean update)375 private void setSaturationAndBrightness(float s, float b, boolean update) { 376 setHSB(hue, s, b); 377 if (update) { 378 settingColor = true; 379 saturationSpinner.setValue(Integer.valueOf((int)(s * 255))); 380 valueSpinner.setValue(Integer.valueOf((int)(b * 255))); 381 settingColor = false; 382 } 383 } 384 385 /** 386 * Resets the rgb values. 387 */ setRGB(int rgb)388 private void setRGB(int rgb) { 389 Color color = new Color(rgb); 390 391 setColor(color, false, true, true); 392 393 settingColor = true; 394 hueSpinner.setValue(Integer.valueOf((int)(hue * 360))); 395 saturationSpinner.setValue(Integer.valueOf((int)(saturation * 255))); 396 valueSpinner.setValue(Integer.valueOf((int)(brightness * 255))); 397 settingColor = false; 398 } 399 400 /** 401 * Resets the hsb values. 402 */ setHSB(float h, float s, float b)403 private void setHSB(float h, float s, float b) { 404 Color color = Color.getHSBColor(h, s, b); 405 406 this.hue = h; 407 this.saturation = s; 408 this.brightness = b; 409 setColor(color, false, false, true); 410 411 settingColor = true; 412 redSpinner.setValue(Integer.valueOf(color.getRed())); 413 greenSpinner.setValue(Integer.valueOf(color.getGreen())); 414 blueSpinner.setValue(Integer.valueOf(color.getBlue())); 415 settingColor = false; 416 } 417 418 419 /** 420 * Rests the color. 421 * 422 * @param color new Color 423 * @param updateSpinners whether or not to update the spinners. 424 * @param updateHSB if true, the hsb fields are updated based on the 425 * new color 426 * @param updateModel if true, the model is set. 427 */ setColor(Color color, boolean updateSpinners, boolean updateHSB, boolean updateModel)428 private void setColor(Color color, boolean updateSpinners, 429 boolean updateHSB, boolean updateModel) { 430 if (color == null) { 431 color = Color.BLACK; 432 } 433 434 settingColor = true; 435 436 if (updateHSB) { 437 float[] hsb = Color.RGBtoHSB(color.getRed(), color.getGreen(), 438 color.getBlue(), null); 439 hue = hsb[0]; 440 saturation = hsb[1]; 441 brightness = hsb[2]; 442 } 443 444 if (updateModel) { 445 ColorSelectionModel model = getColorSelectionModel(); 446 if (model != null) { 447 model.setSelectedColor(color); 448 } 449 } 450 451 triangle.setColor(hue, saturation, brightness); 452 label.setBackground(color); 453 // Force Integer to pad the string with 0's by adding 0x1000000 and 454 // then removing the first character. 455 String hexString = Integer.toHexString( 456 (color.getRGB() & 0xFFFFFF) | 0x1000000); 457 colorNameTF.setText("#" + hexString.substring(1)); 458 459 if (updateSpinners) { 460 redSpinner.setValue(Integer.valueOf(color.getRed())); 461 greenSpinner.setValue(Integer.valueOf(color.getGreen())); 462 blueSpinner.setValue(Integer.valueOf(color.getBlue())); 463 464 hueSpinner.setValue(Integer.valueOf((int)(hue * 360))); 465 saturationSpinner.setValue(Integer.valueOf((int)(saturation * 255))); 466 valueSpinner.setValue(Integer.valueOf((int)(brightness * 255))); 467 } 468 settingColor = false; 469 } 470 getColor()471 public Color getColor() { 472 return label.getBackground(); 473 } 474 475 /** 476 * ChangeListener method, updates the necessary display widgets. 477 */ stateChanged(ChangeEvent e)478 public void stateChanged(ChangeEvent e) { 479 if (settingColor) { 480 return; 481 } 482 Color color = getColor(); 483 484 if (e.getSource() == hueSpinner) { 485 setHue(((Number)hueSpinner.getValue()).floatValue() / 360, false); 486 } 487 else if (e.getSource() == saturationSpinner) { 488 setSaturation(((Number)saturationSpinner.getValue()). 489 floatValue() / 255); 490 } 491 else if (e.getSource() == valueSpinner) { 492 setBrightness(((Number)valueSpinner.getValue()). 493 floatValue() / 255); 494 } 495 else if (e.getSource() == redSpinner) { 496 setRed(((Number)redSpinner.getValue()).intValue()); 497 } 498 else if (e.getSource() == greenSpinner) { 499 setGreen(((Number)greenSpinner.getValue()).intValue()); 500 } 501 else if (e.getSource() == blueSpinner) { 502 setBlue(((Number)blueSpinner.getValue()).intValue()); 503 } 504 } 505 506 507 508 /** 509 * Flag indicating the angle, or hue, has changed and the triangle 510 * needs to be recreated. 511 */ 512 private static final int FLAGS_CHANGED_ANGLE = 1 << 0; 513 /** 514 * Indicates the wheel is being dragged. 515 */ 516 private static final int FLAGS_DRAGGING = 1 << 1; 517 /** 518 * Indicates the triangle is being dragged. 519 */ 520 private static final int FLAGS_DRAGGING_TRIANGLE = 1 << 2; 521 /** 522 * Indicates a color is being set and we should ignore setColor 523 */ 524 private static final int FLAGS_SETTING_COLOR = 1 << 3; 525 /** 526 * Indicates the wheel has focus. 527 */ 528 private static final int FLAGS_FOCUSED_WHEEL = 1 << 4; 529 /** 530 * Indicates the triangle has focus. 531 */ 532 private static final int FLAGS_FOCUSED_TRIANGLE = 1 << 5; 533 534 535 /** 536 * Class responsible for rendering a color wheel and color triangle. 537 */ 538 private class ColorTriangle extends JPanel { 539 /** 540 * Cached image of the wheel. 541 */ 542 private Image wheelImage; 543 544 /** 545 * Cached image of the triangle. 546 */ 547 private Image triangleImage; 548 549 /** 550 * Angle triangle is rotated by. 551 */ 552 private double angle; 553 554 /** 555 * Boolean bitmask. 556 */ 557 private int flags; 558 559 /** 560 * X location of selected color indicator. 561 */ 562 private int circleX; 563 /** 564 * Y location of selected color indicator. 565 */ 566 private int circleY; 567 568 ColorTriangle()569 public ColorTriangle() { 570 enableEvents(AWTEvent.FOCUS_EVENT_MASK); 571 enableEvents(AWTEvent.MOUSE_EVENT_MASK); 572 enableEvents(AWTEvent.MOUSE_MOTION_EVENT_MASK); 573 574 setMinimumSize(new Dimension(getWheelRadius() * 2 + 2, 575 getWheelRadius() * 2 + 2)); 576 setPreferredSize(new Dimension(getWheelRadius() * 2 + 2, 577 getWheelRadius() * 2 + 2)); 578 579 // We want to handle tab ourself. 580 setFocusTraversalKeysEnabled(false); 581 582 // PENDING: this should come from the style. 583 getInputMap().put(KeyStroke.getKeyStroke("UP"), "up"); 584 getInputMap().put(KeyStroke.getKeyStroke("DOWN"), "down"); 585 getInputMap().put(KeyStroke.getKeyStroke("LEFT"), "left"); 586 getInputMap().put(KeyStroke.getKeyStroke("RIGHT"), "right"); 587 588 getInputMap().put(KeyStroke.getKeyStroke("KP_UP"), "up"); 589 getInputMap().put(KeyStroke.getKeyStroke("KP_DOWN"), "down"); 590 getInputMap().put(KeyStroke.getKeyStroke("KP_LEFT"), "left"); 591 getInputMap().put(KeyStroke.getKeyStroke("KP_RIGHT"), "right"); 592 593 getInputMap().put(KeyStroke.getKeyStroke("TAB"), "focusNext"); 594 getInputMap().put(KeyStroke.getKeyStroke("shift TAB"),"focusLast"); 595 596 ActionMap map = (ActionMap)UIManager.get( 597 "GTKColorChooserPanel.actionMap"); 598 599 if (map == null) { 600 map = new ActionMapUIResource(); 601 map.put("left", new ColorAction("left", 2)); 602 map.put("right", new ColorAction("right", 3)); 603 map.put("up", new ColorAction("up", 0)); 604 map.put("down", new ColorAction("down", 1)); 605 map.put("focusNext", new ColorAction("focusNext", 4)); 606 map.put("focusLast", new ColorAction("focusLast", 5)); 607 UIManager.getLookAndFeelDefaults().put( 608 "GTKColorChooserPanel.actionMap", map); 609 } 610 SwingUtilities.replaceUIActionMap(this, map); 611 } 612 613 /** 614 * Returns the GTKColorChooserPanel. 615 */ getGTKColorChooserPanel()616 GTKColorChooserPanel getGTKColorChooserPanel() { 617 return GTKColorChooserPanel.this; 618 } 619 620 /** 621 * Gives focus to the wheel. 622 */ focusWheel()623 void focusWheel() { 624 setFocusType(1); 625 } 626 627 /** 628 * Gives focus to the triangle. 629 */ focusTriangle()630 void focusTriangle() { 631 setFocusType(2); 632 } 633 634 /** 635 * Returns true if the wheel currently has focus. 636 */ isWheelFocused()637 boolean isWheelFocused() { 638 return isSet(FLAGS_FOCUSED_WHEEL); 639 } 640 641 /** 642 * Resets the selected color. 643 */ setColor(float h, float s, float b)644 public void setColor(float h, float s, float b) { 645 if (isSet(FLAGS_SETTING_COLOR)) { 646 return; 647 } 648 649 setAngleFromHue(h); 650 setSaturationAndBrightness(s, b); 651 } 652 653 /** 654 * Returns the selected color. 655 */ getColor()656 public Color getColor() { 657 return GTKColorChooserPanel.this.getColor(); 658 } 659 660 /** 661 * Returns the x location of the selected color indicator. 662 */ getColorX()663 int getColorX() { 664 return circleX + getIndicatorSize() / 2 - getWheelXOrigin(); 665 } 666 667 /** 668 * Returns the y location of the selected color indicator. 669 */ getColorY()670 int getColorY() { 671 return circleY + getIndicatorSize() / 2 - getWheelYOrigin(); 672 } 673 processEvent(AWTEvent e)674 protected void processEvent(AWTEvent e) { 675 if (e.getID() == MouseEvent.MOUSE_PRESSED || 676 ((isSet(FLAGS_DRAGGING) ||isSet(FLAGS_DRAGGING_TRIANGLE)) && 677 e.getID() == MouseEvent.MOUSE_DRAGGED)) { 678 // Assign focus to either the wheel or triangle and attempt 679 // to drag either the wheel or triangle. 680 int size = getWheelRadius(); 681 int x = ((MouseEvent)e).getX() - size; 682 int y = ((MouseEvent)e).getY() - size; 683 684 if (!hasFocus()) { 685 requestFocus(); 686 } 687 if (!isSet(FLAGS_DRAGGING_TRIANGLE) && 688 adjustHue(x, y, e.getID() == MouseEvent.MOUSE_PRESSED)) { 689 setFlag(FLAGS_DRAGGING, true); 690 setFocusType(1); 691 } 692 else if (adjustSB(x, y, e.getID() == 693 MouseEvent.MOUSE_PRESSED)) { 694 setFlag(FLAGS_DRAGGING_TRIANGLE, true); 695 setFocusType(2); 696 } 697 else { 698 setFocusType(2); 699 } 700 } 701 else if (e.getID() == MouseEvent.MOUSE_RELEASED) { 702 // Stopped dragging 703 setFlag(FLAGS_DRAGGING_TRIANGLE, false); 704 setFlag(FLAGS_DRAGGING, false); 705 } 706 else if (e.getID() == FocusEvent.FOCUS_LOST) { 707 // Reset the flags to indicate no one has focus 708 setFocusType(0); 709 } 710 else if (e.getID() == FocusEvent.FOCUS_GAINED) { 711 // Gained focus, reassign focus to the wheel if no one 712 // currently has focus. 713 if (!isSet(FLAGS_FOCUSED_TRIANGLE) && 714 !isSet(FLAGS_FOCUSED_WHEEL)) { 715 setFlag(FLAGS_FOCUSED_WHEEL, true); 716 setFocusType(1); 717 } 718 repaint(); 719 } 720 super.processEvent(e); 721 } 722 paintComponent(Graphics g)723 public void paintComponent(Graphics g) { 724 super.paintComponent(g); 725 726 // Draw the wheel and triangle 727 int size = getWheelRadius(); 728 int width = getWheelWidth(); 729 Image image = getImage(size); 730 g.drawImage(image, getWheelXOrigin() - size, 731 getWheelYOrigin() - size, null); 732 733 // Draw the focus indicator for the wheel 734 if (hasFocus() && isSet(FLAGS_FOCUSED_WHEEL)) { 735 g.setColor(Color.BLACK); 736 g.drawOval(getWheelXOrigin() - size, getWheelYOrigin() - size, 737 2 * size, 2 * size); 738 g.drawOval(getWheelXOrigin() - size + width, getWheelYOrigin()- 739 size + width, 2 * (size - width), 2 * 740 (size - width)); 741 } 742 743 // Draw a line on the wheel indicating the selected hue. 744 if (Math.toDegrees(Math.PI * 2 - angle) <= 20 || 745 Math.toDegrees(Math.PI * 2 - angle) >= 201) { 746 g.setColor(Color.WHITE); 747 } 748 else { 749 g.setColor(Color.BLACK); 750 } 751 int lineX0 = (int)(Math.cos(angle) * size); 752 int lineY0 = (int)(Math.sin(angle) * size); 753 int lineX1 = (int)(Math.cos(angle) * (size - width)); 754 int lineY1 = (int)(Math.sin(angle) * (size - width)); 755 g.drawLine(lineX0 + size, lineY0 + size, lineX1 + size, 756 lineY1 + size); 757 758 // Draw the focus indicator on the triangle 759 if (hasFocus() && isSet(FLAGS_FOCUSED_TRIANGLE)) { 760 Graphics g2 = g.create(); 761 int innerR = getTriangleCircumscribedRadius(); 762 int a = (int)(3 * innerR / Math.sqrt(3)); 763 g2.translate(getWheelXOrigin(), getWheelYOrigin()); 764 ((Graphics2D)g2).rotate(angle + Math.PI / 2); 765 g2.setColor(Color.BLACK); 766 g2.drawLine(0, -innerR, a / 2, innerR / 2); 767 g2.drawLine(a / 2, innerR / 2, -a / 2, innerR / 2); 768 g2.drawLine(-a / 2, innerR / 2, 0, -innerR); 769 g2.dispose(); 770 } 771 772 // Draw the selected color indicator. 773 g.setColor(Color.BLACK); 774 g.drawOval(circleX, circleY, getIndicatorSize() - 1, 775 getIndicatorSize() - 1); 776 g.setColor(Color.WHITE); 777 g.drawOval(circleX + 1, circleY + 1, getIndicatorSize() - 3, 778 getIndicatorSize() - 3); 779 } 780 781 /** 782 * Returns an image representing the triangle and wheel. 783 */ getImage(int size)784 private Image getImage(int size) { 785 if (!isSet(FLAGS_CHANGED_ANGLE) && wheelImage != null && 786 wheelImage.getWidth(null) == size * 2) { 787 return wheelImage; 788 } 789 if (wheelImage == null || wheelImage.getWidth(null) != size) { 790 wheelImage = getWheelImage(size); 791 } 792 int innerR = getTriangleCircumscribedRadius(); 793 int triangleSize = (int)(innerR * 3.0 / 2.0); 794 int a = (int)(2 * triangleSize / Math.sqrt(3)); 795 if (triangleImage == null || triangleImage.getWidth(null) != a) { 796 triangleImage = new BufferedImage(a, a, 797 BufferedImage.TYPE_INT_ARGB); 798 } 799 Graphics g = triangleImage.getGraphics(); 800 g.setColor(new Color(0, 0, 0, 0)); 801 g.fillRect(0, 0, a, a); 802 g.translate(a / 2, 0); 803 paintTriangle(g, triangleSize, getColor()); 804 g.translate(-a / 2, 0); 805 g.dispose(); 806 807 g = wheelImage.getGraphics(); 808 g.setColor(new Color(0, 0, 0, 0)); 809 g.fillOval(getWheelWidth(), getWheelWidth(), 810 2 * (size - getWheelWidth()), 811 2 * (size - getWheelWidth())); 812 813 double rotate = Math.toRadians(-30.0) + angle; 814 g.translate(size, size); 815 ((Graphics2D)g).rotate(rotate); 816 g.drawImage(triangleImage, -a / 2, 817 getWheelWidth() - size, null); 818 ((Graphics2D)g).rotate(-rotate); 819 g.translate(a / 2, size - getWheelWidth()); 820 821 setFlag(FLAGS_CHANGED_ANGLE, false); 822 823 return wheelImage; 824 } 825 paintTriangle(Graphics g, int size, Color color)826 private void paintTriangle(Graphics g, int size, Color color) { 827 float[] colors = Color.RGBtoHSB(color.getRed(), 828 color.getGreen(), 829 color.getBlue(), null); 830 float hue = colors[0]; 831 double dSize = (double)size; 832 for (int y = 0; y < size; y++) { 833 int maxX = (int)(y * Math.tan(Math.toRadians(30.0))); 834 float factor = maxX * 2; 835 if (maxX > 0) { 836 float value = (float)(y / dSize); 837 for (int x = -maxX; x <= maxX; x++) { 838 float saturation = (float)x / factor + .5f; 839 g.setColor(Color.getHSBColor(hue, saturation, value)); 840 g.fillRect(x, y, 1, 1); 841 } 842 } 843 else { 844 g.setColor(color); 845 g.fillRect(0, y, 1, 1); 846 } 847 } 848 } 849 850 /** 851 * Returns a color wheel image for the specified size. 852 * 853 * @param size Integer giving size of color wheel. 854 * @return Color wheel image 855 */ getWheelImage(int size)856 private Image getWheelImage(int size) { 857 int minSize = size - getWheelWidth(); 858 int doubleSize = size * 2; 859 BufferedImage image = new BufferedImage(doubleSize, doubleSize, 860 BufferedImage.TYPE_INT_ARGB); 861 862 for (int y = -size; y < size; y++) { 863 int ySquared = y * y; 864 for (int x = -size; x < size; x++) { 865 double rad = Math.sqrt(ySquared + x * x); 866 867 if (rad < size && rad > minSize) { 868 int rgb = colorWheelLocationToRGB(x, y, rad) | 869 0xFF000000; 870 image.setRGB(x + size, y + size, rgb); 871 } 872 } 873 } 874 wheelImage = image; 875 return wheelImage; 876 } 877 878 /** 879 * Adjusts the saturation and brightness. <code>x</code> and 880 * <code>y</code> give the location to adjust to and are relative 881 * to the origin of the wheel/triangle. 882 * 883 * @param x X coordinate on the triangle to adjust to 884 * @param y Y coordinate on the triangle to adjust to 885 * @param checkLoc if true the location is checked to make sure 886 * it is contained in the triangle, if false the location is 887 * constrained to fit in the triangle. 888 * @return true if the location is valid 889 */ adjustSB(int x, int y, boolean checkLoc)890 boolean adjustSB(int x, int y, boolean checkLoc) { 891 int innerR = getWheelRadius() - getWheelWidth(); 892 boolean resetXY = false; 893 // Invert the axis. 894 y = -y; 895 if (checkLoc && (x < -innerR || x > innerR || y < -innerR || 896 y > innerR)) { 897 return false; 898 } 899 // Rotate to origin and and verify x is valid. 900 int triangleSize = innerR * 3 / 2; 901 double x1 = Math.cos(angle) * x - Math.sin(angle) * y; 902 double y1 = Math.sin(angle) * x + Math.cos(angle) * y; 903 if (x1 < -(innerR / 2)) { 904 if (checkLoc) { 905 return false; 906 } 907 x1 = -innerR / 2; 908 resetXY = true; 909 } 910 else if ((int)x1 > innerR) { 911 if (checkLoc) { 912 return false; 913 } 914 x1 = innerR; 915 resetXY = true; 916 } 917 // Verify y location is valid. 918 int maxY = (int)((triangleSize - x1 - innerR / 2.0) * 919 Math.tan(Math.toRadians(30.0))); 920 if (y1 <= -maxY) { 921 if (checkLoc) { 922 return false; 923 } 924 y1 = -maxY; 925 resetXY = true; 926 } 927 else if (y1 > maxY) { 928 if (checkLoc) { 929 return false; 930 } 931 y1 = maxY; 932 resetXY = true; 933 } 934 // Rotate again to determine value and scale 935 double x2 = Math.cos(Math.toRadians(-30.0)) * x1 - 936 Math.sin(Math.toRadians(-30.0)) * y1; 937 double y2 = Math.sin(Math.toRadians(-30.0)) * x1 + 938 Math.cos(Math.toRadians(-30.0)) * y1; 939 float value = Math.min(1.0f, (float)((innerR - y2) / 940 (double)triangleSize)); 941 float maxX = (float)(Math.tan(Math.toRadians(30)) * (innerR - y2)); 942 float saturation = Math.min(1.0f, (float)(x2 / maxX / 2 + .5)); 943 944 setFlag(FLAGS_SETTING_COLOR, true); 945 if (resetXY) { 946 setSaturationAndBrightness(saturation, value); 947 } 948 else { 949 setSaturationAndBrightness(saturation, value, x + 950 getWheelXOrigin(),getWheelYOrigin() - y); 951 } 952 GTKColorChooserPanel.this.setSaturationAndBrightness(saturation, 953 value, true); 954 setFlag(FLAGS_SETTING_COLOR, false); 955 return true; 956 } 957 958 /** 959 * Sets the saturation and brightness. 960 */ setSaturationAndBrightness(float s, float b)961 private void setSaturationAndBrightness(float s, float b) { 962 int innerR = getTriangleCircumscribedRadius(); 963 int triangleSize = innerR * 3 / 2; 964 double x = b * triangleSize; 965 double maxY = x * Math.tan(Math.toRadians(30.0)); 966 double y = 2 * maxY * s - maxY; 967 x = x - innerR; 968 double x1 = Math.cos(Math.toRadians(-60.0) - angle) * 969 x - Math.sin(Math.toRadians(-60.0) - angle) * y; 970 double y1 = Math.sin(Math.toRadians(-60.0) - angle) * x + 971 Math.cos(Math.toRadians(-60.0) - angle) * y; 972 int newCircleX = (int)x1 + getWheelXOrigin(); 973 int newCircleY = getWheelYOrigin() - (int)y1; 974 975 setSaturationAndBrightness(s, b, newCircleX, newCircleY); 976 } 977 978 979 /** 980 * Sets the saturation and brightness. 981 */ setSaturationAndBrightness(float s, float b, int newCircleX, int newCircleY)982 private void setSaturationAndBrightness(float s, float b, 983 int newCircleX, int newCircleY) { 984 newCircleX -= getIndicatorSize() / 2; 985 newCircleY -= getIndicatorSize() / 2; 986 987 int minX = Math.min(newCircleX, circleX); 988 int minY = Math.min(newCircleY, circleY); 989 990 repaint(minX, minY, Math.max(circleX, newCircleX) - minX + 991 getIndicatorSize() + 1, Math.max(circleY, newCircleY) - 992 minY + getIndicatorSize() + 1); 993 circleX = newCircleX; 994 circleY = newCircleY; 995 } 996 997 /** 998 * Adjusts the hue based on the passed in location. 999 * 1000 * @param x X location to adjust to, relative to the origin of the 1001 * wheel 1002 * @param y Y location to adjust to, relative to the origin of the 1003 * wheel 1004 * @param check if true the location is checked to make sure 1005 * it is contained in the wheel, if false the location is 1006 * constrained to fit in the wheel 1007 * @return true if the location is valid. 1008 */ adjustHue(int x, int y, boolean check)1009 private boolean adjustHue(int x, int y, boolean check) { 1010 double rad = Math.sqrt(x * x + y * y); 1011 int size = getWheelRadius(); 1012 1013 if (!check || (rad >= size - getWheelWidth() && rad < size)) { 1014 // Map the location to an angle and reset hue 1015 double angle; 1016 if (x == 0) { 1017 if (y > 0) { 1018 angle = Math.PI / 2.0; 1019 } 1020 else { 1021 angle = Math.PI + Math.PI / 2.0; 1022 } 1023 } 1024 else { 1025 angle = Math.atan((double)y / (double)x); 1026 if (x < 0) { 1027 angle += Math.PI; 1028 } 1029 else if (angle < 0) { 1030 angle += 2 * Math.PI; 1031 } 1032 } 1033 setFlag(FLAGS_SETTING_COLOR, true); 1034 setHue((float)(1.0 - angle / Math.PI / 2), true); 1035 setFlag(FLAGS_SETTING_COLOR, false); 1036 setHueAngle(angle); 1037 setSaturationAndBrightness(getSaturation(), getBrightness()); 1038 return true; 1039 } 1040 return false; 1041 } 1042 1043 /** 1044 * Rotates the triangle to accommodate the passed in hue. 1045 */ setAngleFromHue(float hue)1046 private void setAngleFromHue(float hue) { 1047 setHueAngle((1.0 - hue) * Math.PI * 2); 1048 } 1049 1050 /** 1051 * Sets the angle representing the hue. 1052 */ setHueAngle(double angle)1053 private void setHueAngle(double angle) { 1054 double oldAngle = this.angle; 1055 1056 this.angle = angle; 1057 if (angle != oldAngle) { 1058 setFlag(FLAGS_CHANGED_ANGLE, true); 1059 repaint(); 1060 } 1061 } 1062 1063 /** 1064 * Returns the size of the color indicator. 1065 */ getIndicatorSize()1066 private int getIndicatorSize() { 1067 return 8; 1068 } 1069 1070 /** 1071 * Returns the circumscribed radius of the triangle. 1072 */ getTriangleCircumscribedRadius()1073 private int getTriangleCircumscribedRadius() { 1074 return 72; 1075 } 1076 1077 /** 1078 * Returns the x origin of the wheel and triangle. 1079 */ getWheelXOrigin()1080 private int getWheelXOrigin() { 1081 return 85; 1082 } 1083 1084 /** 1085 * Returns y origin of the wheel and triangle. 1086 */ getWheelYOrigin()1087 private int getWheelYOrigin() { 1088 return 85; 1089 } 1090 1091 /** 1092 * Returns the width of the wheel. 1093 */ getWheelWidth()1094 private int getWheelWidth() { 1095 return 13; 1096 } 1097 1098 /** 1099 * Sets the focus to one of: 0 no one, 1 the wheel or 2 the triangle. 1100 */ setFocusType(int type)1101 private void setFocusType(int type) { 1102 if (type == 0) { 1103 setFlag(FLAGS_FOCUSED_WHEEL, false); 1104 setFlag(FLAGS_FOCUSED_TRIANGLE, false); 1105 repaint(); 1106 } 1107 else { 1108 int toSet = FLAGS_FOCUSED_WHEEL; 1109 int toUnset = FLAGS_FOCUSED_TRIANGLE; 1110 1111 if (type == 2) { 1112 toSet = FLAGS_FOCUSED_TRIANGLE; 1113 toUnset = FLAGS_FOCUSED_WHEEL; 1114 } 1115 if (!isSet(toSet)) { 1116 setFlag(toSet, true); 1117 repaint(); 1118 setFlag(toUnset, false); 1119 } 1120 } 1121 } 1122 1123 /** 1124 * Returns the radius of the wheel. 1125 */ getWheelRadius()1126 private int getWheelRadius() { 1127 // As far as I can tell, GTK doesn't allow stretching this 1128 // widget 1129 return 85; 1130 } 1131 1132 /** 1133 * Updates the flags bitmask. 1134 */ setFlag(int flag, boolean value)1135 private void setFlag(int flag, boolean value) { 1136 if (value) { 1137 flags |= flag; 1138 } 1139 else { 1140 flags &= ~flag; 1141 } 1142 } 1143 1144 /** 1145 * Returns true if a particular flag has been set. 1146 */ isSet(int flag)1147 private boolean isSet(int flag) { 1148 return ((flags & flag) == flag); 1149 } 1150 1151 /** 1152 * Returns the RGB color to use for the specified location. The 1153 * passed in point must be on the color wheel and be relative to the 1154 * origin of the color wheel. 1155 * 1156 * @param x X location to get color for 1157 * @param y Y location to get color for 1158 * @param rad Radius from center of color wheel 1159 * @return integer with red, green and blue components 1160 */ colorWheelLocationToRGB(int x, int y, double rad)1161 private int colorWheelLocationToRGB(int x, int y, double rad) { 1162 double angle = Math.acos((double)x / rad); 1163 int rgb; 1164 1165 if (angle < PI_3) { 1166 if (y < 0) { 1167 // FFFF00 - FF0000 1168 rgb = 0xFF0000 | Math.min(255, 1169 (int)(255 * angle / PI_3)) << 8; 1170 } 1171 else { 1172 // FF0000 - FF00FF 1173 rgb = 0xFF0000 | Math.min(255, 1174 (int)(255 * angle / PI_3)); 1175 } 1176 } 1177 else if (angle < 2 * PI_3) { 1178 angle -= PI_3; 1179 if (y < 0) { 1180 // 00FF00 - FFFF00 1181 rgb = 0x00FF00 | Math.max(0, 255 - 1182 (int)(255 * angle / PI_3)) << 16; 1183 } 1184 else { 1185 // FF00FF - 0000FF 1186 rgb = 0x0000FF | Math.max(0, 255 - 1187 (int)(255 * angle / PI_3)) << 16; 1188 } 1189 } 1190 else { 1191 angle -= 2 * PI_3; 1192 if (y < 0) { 1193 // 00FFFF - 00FF00 1194 rgb = 0x00FF00 | Math.min(255, 1195 (int)(255 * angle / PI_3)); 1196 } 1197 else { 1198 // 0000FF - 00FFFF 1199 rgb = 0x0000FF | Math.min(255, 1200 (int)(255 * angle / PI_3)) << 8; 1201 } 1202 } 1203 return rgb; 1204 } 1205 1206 /** 1207 * Increments the hue. 1208 */ incrementHue(boolean positive)1209 void incrementHue(boolean positive) { 1210 float hue = triangle.getGTKColorChooserPanel().getHue(); 1211 1212 if (positive) { 1213 hue += 1.0f / 360.0f; 1214 } 1215 else { 1216 hue -= 1.0f / 360.0f; 1217 } 1218 if (hue > 1) { 1219 hue -= 1; 1220 } 1221 else if (hue < 0) { 1222 hue += 1; 1223 } 1224 getGTKColorChooserPanel().setHue(hue, true); 1225 } 1226 } 1227 1228 1229 /** 1230 * Action class used for colors. 1231 */ 1232 private static class ColorAction extends AbstractAction { 1233 private int type; 1234 ColorAction(String name, int type)1235 ColorAction(String name, int type) { 1236 super(name); 1237 this.type = type; 1238 } 1239 actionPerformed(ActionEvent e)1240 public void actionPerformed(ActionEvent e) { 1241 ColorTriangle triangle = (ColorTriangle)e.getSource(); 1242 1243 if (triangle.isWheelFocused()) { 1244 float hue = triangle.getGTKColorChooserPanel().getHue(); 1245 1246 switch (type) { 1247 case 0: 1248 case 2: 1249 triangle.incrementHue(true); 1250 break; 1251 case 1: 1252 case 3: 1253 triangle.incrementHue(false); 1254 break; 1255 case 4: 1256 triangle.focusTriangle(); 1257 break; 1258 case 5: 1259 compositeRequestFocus(triangle, false); 1260 break; 1261 } 1262 } 1263 else { 1264 int xDelta = 0; 1265 int yDelta = 0; 1266 1267 switch (type) { 1268 case 0: 1269 // up 1270 yDelta--; 1271 break; 1272 case 1: 1273 // down 1274 yDelta++; 1275 break; 1276 case 2: 1277 // left 1278 xDelta--; 1279 break; 1280 case 3: 1281 // right 1282 xDelta++; 1283 break; 1284 case 4: 1285 compositeRequestFocus(triangle, true); 1286 return; 1287 case 5: 1288 triangle.focusWheel(); 1289 return; 1290 } 1291 triangle.adjustSB(triangle.getColorX() + xDelta, 1292 triangle.getColorY() + yDelta, true); 1293 } 1294 } 1295 } 1296 1297 1298 private class OpaqueLabel extends JLabel { isOpaque()1299 public boolean isOpaque() { 1300 return true; 1301 } 1302 } 1303 } 1304