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