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