You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ColourPickerPanel.java 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. /*
  2. * Copyright (c) 2006-2007 Chris Smith, Shane Mc Cormack, Gregory Holmes
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.ui.components;
  23. import java.awt.Color;
  24. import java.awt.Dimension;
  25. import java.awt.Graphics;
  26. import java.awt.Polygon;
  27. import java.awt.Rectangle;
  28. import java.awt.event.ActionEvent;
  29. import java.awt.event.ActionListener;
  30. import java.awt.event.MouseEvent;
  31. import java.awt.event.MouseListener;
  32. import java.awt.event.MouseMotionListener;
  33. import java.util.ArrayList;
  34. import java.util.List;
  35. import javax.swing.JPanel;
  36. import com.dmdirc.ui.messages.ColourManager;
  37. /**
  38. * The ColourPickerPanel allows users to pick either an IRC colour or a hex
  39. * colour visually.
  40. * @author chris
  41. */
  42. public final class ColourPickerPanel extends JPanel implements MouseListener, MouseMotionListener {
  43. /** ActionEvent ID for when a hex colour is selected. */
  44. public static final int ACTION_HEX = 10001;
  45. /** ActionEvent ID for when an irc colour is selected. */
  46. public static final int ACTION_IRC = 10002;
  47. /**
  48. * A version number for this class. It should be changed whenever the class
  49. * structure is changed (or anything else that would prevent serialized
  50. * objects being unserialized with the new class).
  51. */
  52. private static final long serialVersionUID = 1;
  53. /** The width of each IRC colour patch. */
  54. private static final int IRC_WIDTH = 9;
  55. /** The height of each IRC colour patch. */
  56. private static final int IRC_HEIGHT = 16;
  57. /** The width of the hex colour patch. */
  58. private static final int HEX_WIDTH = 125;
  59. /** The height of the hex colour patch. */
  60. private static final int HEX_HEIGHT = 125;
  61. /** The size of borders to use. */
  62. private static final int BORDER_SIZE = 7;
  63. /** The size of slider to use. */
  64. private static final int SLIDER_WIDTH = 10;
  65. /** The height of the preview area. */
  66. private static final int PREVIEW_HEIGHT = 20;
  67. /** Whether to show IRC colours. */
  68. private final boolean showIrc;
  69. /** Whether to show hex colours. */
  70. private final boolean showHex;
  71. /** The y-coord of the start of the IRC colours block. */
  72. private int ircOffset;
  73. /** The y-coord of the start of the hex colours block. */
  74. private int hexOffset;
  75. /** The y-coord of the start of the preview block. */
  76. private int previewOffset;
  77. /** The saturation to use. */
  78. private float saturation = (float) 1.0;
  79. /** The colour to show in the preview window. */
  80. private Color preview;
  81. /** Rectangle we use to indicate that only the preview should be drawn. */
  82. private Rectangle previewRect;
  83. /** A list of registered actionlisteners. */
  84. private final List<ActionListener> listeners = new ArrayList<ActionListener>();
  85. /**
  86. * Creates a new instance of ColourPickerPanel.
  87. * @param newShowIrc Whether to show IRC colours or not
  88. * @param newShowHex Whether to show hex colours or not
  89. */
  90. public ColourPickerPanel(final boolean newShowIrc, final boolean newShowHex) {
  91. super();
  92. this.showIrc = newShowIrc;
  93. this.showHex = newShowHex;
  94. final int height = 65 + (showIrc ? 30 : 0) + (showHex ? 145 : 0) + (showHex & showIrc ? 10 : 0);
  95. setPreferredSize(new Dimension(160, height));
  96. addMouseListener(this);
  97. addMouseMotionListener(this);
  98. }
  99. /**
  100. * Creates a new instance of ColourPickerPanel, showing both IRC and Hex
  101. * colours.
  102. */
  103. public ColourPickerPanel() {
  104. this(true, true);
  105. }
  106. /** {@inheritDoc} */
  107. public void paint(final Graphics g) {
  108. int offset = 20;
  109. if (previewRect == null || !previewRect.equals(g.getClipBounds())) {
  110. g.setColor(getBackground());
  111. g.fillRect(0, 0, getWidth(), getHeight());
  112. g.setColor(Color.BLACK);
  113. if (showIrc) {
  114. g.drawString("IRC Colours", BORDER_SIZE, offset);
  115. offset += BORDER_SIZE;
  116. ircOffset = offset;
  117. for (int i = 0; i < 16; i++) {
  118. g.setColor(ColourManager.getColour(i));
  119. g.fillRect(i * IRC_WIDTH + BORDER_SIZE, offset, IRC_WIDTH, IRC_HEIGHT);
  120. g.setColor(Color.BLACK);
  121. g.drawRect(i * IRC_WIDTH + BORDER_SIZE, offset, IRC_WIDTH, IRC_HEIGHT);
  122. }
  123. offset += IRC_HEIGHT + 20;
  124. }
  125. if (showHex) {
  126. g.drawString("Hex Colours", BORDER_SIZE, offset);
  127. offset += BORDER_SIZE;
  128. hexOffset = offset;
  129. for (int i = HEX_WIDTH; i > 0; i--) {
  130. for (int j = HEX_HEIGHT; j > 0; j--) {
  131. g.setColor(new Color(Color.HSBtoRGB((float) i / HEX_WIDTH, saturation, (float) j / HEX_HEIGHT)));
  132. g.drawLine(BORDER_SIZE + i, offset + HEX_HEIGHT - j, BORDER_SIZE + i, offset + HEX_HEIGHT - j);
  133. }
  134. }
  135. g.setColor(Color.BLACK);
  136. g.drawRect(BORDER_SIZE, offset, HEX_HEIGHT, HEX_WIDTH);
  137. g.drawRect(BORDER_SIZE * 2 + HEX_WIDTH, offset, 10, HEX_HEIGHT);
  138. for (int i = 1; i < HEX_HEIGHT; i++) {
  139. g.setColor(new Color(Color.HSBtoRGB(0, (float) i / HEX_HEIGHT, 1)));
  140. g.drawLine(BORDER_SIZE * 2 + HEX_WIDTH + 1, offset + i,
  141. BORDER_SIZE * 2 + HEX_WIDTH + SLIDER_WIDTH - 1, offset + i);
  142. }
  143. final Polygon arrow = new Polygon();
  144. arrow.addPoint(HEX_WIDTH + BORDER_SIZE * 2 + 4, offset + Math.round(saturation * HEX_HEIGHT));
  145. arrow.addPoint(HEX_WIDTH + BORDER_SIZE * 2 + 13, offset + Math.round(saturation * HEX_HEIGHT) + 5);
  146. arrow.addPoint(HEX_WIDTH + BORDER_SIZE * 2 + 13, offset + Math.round(saturation * HEX_HEIGHT) - 5);
  147. g.setColor(Color.BLACK);
  148. g.fillPolygon(arrow);
  149. offset += HEX_HEIGHT + 20;
  150. }
  151. g.drawString("Preview", BORDER_SIZE, offset);
  152. offset += BORDER_SIZE;
  153. previewOffset = offset;
  154. if (previewRect == null) {
  155. previewRect = new Rectangle(0, previewOffset, getWidth(), PREVIEW_HEIGHT);
  156. }
  157. } else {
  158. offset = previewOffset;
  159. }
  160. g.drawRect(BORDER_SIZE, offset, getWidth() - BORDER_SIZE * 2, PREVIEW_HEIGHT);
  161. if (preview == null) {
  162. g.setColor(getBackground());
  163. g.fillRect(BORDER_SIZE + 1, offset + 1, getWidth() - BORDER_SIZE * 2 - 1, PREVIEW_HEIGHT - 1);
  164. g.setColor(Color.BLACK);
  165. g.drawLine(BORDER_SIZE, offset, getWidth() - BORDER_SIZE, offset + PREVIEW_HEIGHT);
  166. } else {
  167. g.setColor(preview);
  168. g.fillRect(BORDER_SIZE + 1, offset + 1, getWidth() - BORDER_SIZE * 2 - 1, PREVIEW_HEIGHT - 1);
  169. }
  170. }
  171. /**
  172. * Retrieves the hex colour beneath the mouse. It is assumed that this
  173. * method is only called if the mouse is within the hex area.
  174. * @param e The mouse event that triggered this call
  175. * @return A colour object representing the colour beneat the mouse
  176. */
  177. private Color getHexColour(final MouseEvent e) {
  178. final int i = e.getX() - BORDER_SIZE;
  179. final int j = HEX_HEIGHT - (e.getY() - hexOffset);
  180. return new Color(Color.HSBtoRGB((float) i / HEX_WIDTH, saturation, (float) j / HEX_HEIGHT));
  181. }
  182. /**
  183. * Retrieves the irc colour beneath the mouse. It is assumed that this
  184. * method is only called if the mouse is within the irc colour area.
  185. * @param e The mouse event that triggered this call
  186. * @return A colour object representing the colour beneat the mouse
  187. */
  188. private Color getIrcColour(final MouseEvent e) {
  189. final int i = (e.getX() - BORDER_SIZE) / IRC_WIDTH;
  190. return ColourManager.getColour(i);
  191. }
  192. /**
  193. * Adds an action listener to this object. Action events are generated (and
  194. * passed to all action listeners) when the user selects a colour. The two
  195. * IDs used by this object are ACTION_HEX and ACTION_IRC, to indicate a
  196. * hex colour or an irc colour was selected, respectively.
  197. * @param listener The action listener to register
  198. */
  199. public void addActionListener(final ActionListener listener) {
  200. listeners.add(listener);
  201. }
  202. /**
  203. * Removes an action listener from this object.
  204. * @param listener The listener to be removed
  205. */
  206. public void removeActionListener(final ActionListener listener) {
  207. listeners.remove(listener);
  208. }
  209. /**
  210. * Throws a new action event to all listeners.
  211. * @param id The id of the action
  212. * @param message The 'message' to use for the event
  213. */
  214. private void throwAction(final int id, final String message) {
  215. final ActionEvent event = new ActionEvent(this, id, message);
  216. for (ActionListener listener : listeners) {
  217. listener.actionPerformed(event);
  218. }
  219. }
  220. /**
  221. * Converts the specified integer (in the range 0-255) into a hex string.
  222. * @param value The integer to convert
  223. * @return A char digit hex string representing the specified integer
  224. */
  225. private String toHex(final int value) {
  226. final char[] chars = {
  227. '0', '1', '2', '3', '4', '5', '6', '7',
  228. '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
  229. };
  230. return ("" + chars[value / 16]) + chars[value % 16];
  231. }
  232. /** {@inheritDoc} */
  233. public void mouseClicked(final MouseEvent e) {
  234. if (showIrc && e.getY() > ircOffset
  235. && e.getY() < ircOffset + IRC_HEIGHT && e.getX() > BORDER_SIZE
  236. && e.getX() < BORDER_SIZE + 16 * IRC_WIDTH) {
  237. final int i = (e.getX() - BORDER_SIZE) / IRC_WIDTH;
  238. throwAction(ACTION_IRC, "" + i);
  239. } else if (showHex
  240. && e.getY() > hexOffset && e.getY() < hexOffset + HEX_HEIGHT) {
  241. if (e.getX() > BORDER_SIZE && e.getX() < BORDER_SIZE + HEX_WIDTH) {
  242. final Color color = getHexColour(e);
  243. throwAction(ACTION_HEX, toHex(color.getRed()) + toHex(color.getGreen()) + toHex(color.getBlue()));
  244. } else if (e.getX() > BORDER_SIZE * 2 + HEX_WIDTH
  245. && e.getX() < BORDER_SIZE * 3 + HEX_WIDTH + SLIDER_WIDTH) {
  246. saturation = (float) (e.getY() - hexOffset) / 125;
  247. repaint();
  248. }
  249. }
  250. }
  251. /** {@inheritDoc} */
  252. public void mousePressed(final MouseEvent e) {
  253. // Do nothing
  254. }
  255. /** {@inheritDoc} */
  256. public void mouseReleased(final MouseEvent e) {
  257. // Do nothing
  258. }
  259. /** {@inheritDoc} */
  260. public void mouseEntered(final MouseEvent e) {
  261. // Do nothing
  262. }
  263. /** {@inheritDoc} */
  264. public void mouseExited(final MouseEvent e) {
  265. // Do nothing
  266. }
  267. /** {@inheritDoc} */
  268. public void mouseDragged(final MouseEvent e) {
  269. // Do nothing
  270. }
  271. /** {@inheritDoc} */
  272. public void mouseMoved(final MouseEvent e) {
  273. if (showIrc && e.getY() > ircOffset
  274. && e.getY() < ircOffset + IRC_HEIGHT && e.getX() > BORDER_SIZE
  275. && e.getX() < BORDER_SIZE + 16 * IRC_WIDTH) {
  276. preview = getIrcColour(e);
  277. } else if (showHex && e.getY() > hexOffset
  278. && e.getY() < hexOffset + HEX_HEIGHT && e.getX() > BORDER_SIZE
  279. && e.getX() < BORDER_SIZE + HEX_WIDTH) {
  280. preview = getHexColour(e);
  281. } else {
  282. preview = null;
  283. }
  284. repaint(previewRect);
  285. }
  286. }