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