001 /* 002 * @(#)ColorPicker.java 1.0 2008-03-01 003 * 004 * Copyright (c) 2008 Jeremy Wood 005 * E-mail: mickleness@gmail.com 006 * All rights reserved. 007 * 008 * The copyright of this software is owned by Jeremy Wood. 009 * You may not use, copy or modify this software, except in 010 * accordance with the license agreement you entered into with 011 * Jeremy Wood. For details see accompanying license terms. 012 */ 013 014 package com.colorpicker.swing; 015 016 import com.colorpicker.swing.*; 017 import java.awt.*; 018 019 import javax.swing.*; 020 import java.awt.event.*; 021 import java.beans.PropertyChangeEvent; 022 import java.beans.PropertyChangeListener; 023 import java.util.*; 024 import javax.swing.event.*; 025 026 027 028 029 /** This is a panel that offers a robust set of controls to pick a color. 030 * <P>This was originally intended to replace the <code>JColorChooser</code>. 031 * To use this class to create a color choosing dialog, simply call: 032 * <BR><code>ColorPicker.showDialog(frame, originalColor);</code> 033 * <P>However this panel is also resizable, and it can exist in other contexts. 034 * For example, you might try the following panel: 035 * <BR><code>ColorPicker picker = new ColorPicker(false, false);</code> 036 * <BR><code>picker.setPreferredSize(new Dimension(200,160));</code> 037 * <BR><code>picker.setMode(ColorPicker.HUE);</code> 038 * <P>This will create a miniature color picker that still lets the user choose 039 * from every available color, but it does not include all the buttons and 040 * numeric controls on the right side of the panel. This might be ideal if you 041 * are working with limited space, or non-power-users who don't need the 042 * RGB values of a color. The <code>main()</code> method of this class demonstrates 043 * possible ways you can customize a <code>ColorPicker</code> component. 044 * <P>To listen to color changes to this panel, you can add a <code>PropertyChangeListener</code> 045 * listening for changes to the <code>SELECTED_COLOR_PROPERTY</code>. This will be triggered only 046 * when the RGB value of the selected color changes. 047 * <P>To listen to opacity changes to this panel, use a <code>PropertyChangeListener</code> listening 048 * for changes to the <code>OPACITY_PROPERTY</code>. 049 * 050 * @version 1.3 051 * @author Jeremy Wood (With some updates from Frederic Roudaut to fit my needs) 052 */ 053 public class ColorPicker extends JPanel { 054 private static final long serialVersionUID = 3L; 055 056 /** The localized strings used in this (and related) panel(s). */ 057 protected static ResourceBundle strings = ResourceBundle.getBundle("com.colorpicker.swing.ColorPicker"); 058 059 /** This demonstrates how to customize a small <code>ColorPicker</code> component. 060 */ 061 public static void main(String[] args) { 062 final JFrame demo = new JFrame("Demo"); 063 final JWindow palette = new JWindow(demo); 064 final ColorPicker picker = new ColorPicker(true,false); 065 066 final JComboBox comboBox = new JComboBox(); 067 final JCheckBox alphaCheckbox = new JCheckBox("Include Alpha"); 068 final JCheckBox hsbCheckbox = new JCheckBox("Include HSB Values"); 069 final JCheckBox rgbCheckbox = new JCheckBox("Include RGB Values"); 070 final JCheckBox modeCheckbox = new JCheckBox("Include Mode Controls",true); 071 final JButton button = new JButton("Show Dialog"); 072 073 demo.getContentPane().setLayout(new GridBagLayout()); 074 palette.getContentPane().setLayout(new GridBagLayout()); 075 076 GridBagConstraints c = new GridBagConstraints(); 077 c.gridx = 0; c.gridy = 0; c.weightx = 1; c.weighty = 0; 078 c.insets = new Insets(5,5,5,5); c.anchor = GridBagConstraints.WEST; 079 palette.getContentPane().add(comboBox,c); 080 c.gridy++; 081 palette.getContentPane().add(alphaCheckbox,c); 082 c.gridy++; 083 palette.getContentPane().add(hsbCheckbox,c); 084 c.gridy++; 085 palette.getContentPane().add(rgbCheckbox,c); 086 c.gridy++; 087 palette.getContentPane().add(modeCheckbox,c); 088 089 c.gridy = 0; 090 c.weighty = 1; c.fill = GridBagConstraints.BOTH; 091 picker.setPreferredSize(new Dimension(220,200)); 092 demo.getContentPane().add(picker,c); 093 c.gridy++; c.weighty = 0; 094 demo.getContentPane().add(picker.getExpertControls(),c); 095 c.gridy++; c.fill = GridBagConstraints.NONE; 096 demo.getContentPane().add(button,c); 097 098 comboBox.addItem("Hue"); 099 comboBox.addItem("Saturation"); 100 comboBox.addItem("Brightness"); 101 comboBox.addItem("Red"); 102 comboBox.addItem("Green"); 103 comboBox.addItem("Blue"); 104 105 ActionListener checkboxListener = new ActionListener() { 106 public void actionPerformed(ActionEvent e) { 107 Object src = e.getSource(); 108 if(src==alphaCheckbox) { 109 picker.setOpacityVisible(alphaCheckbox.isSelected()); 110 } else if(src==hsbCheckbox) { 111 picker.setHSBControlsVisible(hsbCheckbox.isSelected()); 112 } else if(src==rgbCheckbox) { 113 picker.setRGBControlsVisible(rgbCheckbox.isSelected()); 114 } else if(src==modeCheckbox) { 115 picker.setModeControlsVisible(modeCheckbox.isSelected()); 116 } 117 demo.pack(); 118 } 119 }; 120 picker.setOpacityVisible(false); 121 picker.setHSBControlsVisible(false); 122 picker.setRGBControlsVisible(false); 123 picker.setHexControlsVisible(false); 124 picker.setPreviewSwatchVisible(false); 125 126 picker.addPropertyChangeListener(MODE_PROPERTY, new PropertyChangeListener() { 127 public void propertyChange(PropertyChangeEvent evt) { 128 129 int m = picker.getMode(); 130 if(m==HUE) { 131 comboBox.setSelectedIndex(0); 132 } else if(m==SAT) { 133 comboBox.setSelectedIndex(1); 134 } else if(m==BRI) { 135 comboBox.setSelectedIndex(2); 136 } else if(m==RED) { 137 comboBox.setSelectedIndex(3); 138 } else if(m==GREEN) { 139 comboBox.setSelectedIndex(4); 140 } else if(m==BLUE) { 141 comboBox.setSelectedIndex(5); 142 } 143 } 144 }); 145 146 alphaCheckbox.addActionListener(checkboxListener); 147 hsbCheckbox.addActionListener(checkboxListener); 148 rgbCheckbox.addActionListener(checkboxListener); 149 modeCheckbox.addActionListener(checkboxListener); 150 button.addActionListener(new ActionListener() { 151 public void actionPerformed(ActionEvent e) { 152 Color color = picker.getColor(); 153 color = ColorPicker.showDialog(demo, color, true); 154 if(color!=null) 155 picker.setColor(color); 156 } 157 }); 158 159 comboBox.addActionListener(new ActionListener() { 160 public void actionPerformed(ActionEvent e) { 161 int i = ((JComboBox)e.getSource()).getSelectedIndex(); 162 if(i==0) { 163 picker.setMode(ColorPicker.HUE); 164 } else if(i==1) { 165 picker.setMode(ColorPicker.SAT); 166 } else if(i==2) { 167 picker.setMode(ColorPicker.BRI); 168 } else if(i==3) { 169 picker.setMode(ColorPicker.RED); 170 } else if(i==4) { 171 picker.setMode(ColorPicker.GREEN); 172 } else if(i==5) { 173 picker.setMode(ColorPicker.BLUE); 174 } 175 } 176 }); 177 comboBox.setSelectedIndex(2); 178 179 palette.pack(); 180 palette.setLocationRelativeTo(null); 181 182 demo.addComponentListener(new ComponentAdapter() { 183 public void componentMoved(ComponentEvent e) { 184 Point p = demo.getLocation(); 185 palette.setLocation(new Point(p.x-palette.getWidth()-10,p.y)); 186 } 187 }); 188 demo.pack(); 189 demo.setLocationRelativeTo(null); 190 demo.setVisible(true); 191 palette.setVisible(true); 192 193 demo.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 194 } 195 196 /** This creates a modal dialog prompting the user to select a color. 197 * <P>This uses a generic dialog title: "Choose a Color", and does not include opacity. 198 * 199 * @param owner the dialog this new dialog belongs to. This must be a Frame or a Dialog. 200 * Java 1.6 supports Windows here, but this package is designed/compiled to work in Java 1.4, 201 * so an <code>IllegalArgumentException</code> will be thrown if this component is a <code>Window</code>. 202 * @param originalColor the color the <code>ColorPicker</code> initially points to. 203 * @return the <code>Color</code> the user chooses, or <code>null</code> if the user cancels the dialog. 204 */ 205 public static Color showDialog(Window owner,Color originalColor) { 206 return showDialog(owner, null, originalColor, false ); 207 } 208 209 /** This creates a modal dialog prompting the user to select a color. 210 * <P>This uses a generic dialog title: "Choose a Color". 211 * 212 * @param owner the dialog this new dialog belongs to. This must be a Frame or a Dialog. 213 * Java 1.6 supports Windows here, but this package is designed/compiled to work in Java 1.4, 214 * so an <code>IllegalArgumentException</code> will be thrown if this component is a <code>Window</code>. 215 * @param originalColor the color the <code>ColorPicker</code> initially points to. 216 * @param includeOpacity whether to add a control for the opacity of the color. 217 * @return the <code>Color</code> the user chooses, or <code>null</code> if the user cancels the dialog. 218 */ 219 public static Color showDialog(Window owner,Color originalColor,boolean includeOpacity) { 220 return showDialog(owner, null, originalColor, includeOpacity ); 221 } 222 223 /** This creates a modal dialog prompting the user to select a color. 224 * 225 * @param owner the dialog this new dialog belongs to. This must be a Frame or a Dialog. 226 * Java 1.6 supports Windows here, but this package is designed/compiled to work in Java 1.4, 227 * so an <code>IllegalArgumentException</code> will be thrown if this component is a <code>Window</code>. 228 * @param title the title for the dialog. 229 * @param originalColor the color the <code>ColorPicker</code> initially points to. 230 * @param includeOpacity whether to add a control for the opacity of the color. 231 * @return the <code>Color</code> the user chooses, or <code>null</code> if the user cancels the dialog. 232 */ 233 public static Color showDialog(Window owner, String title,Color originalColor,boolean includeOpacity) { 234 ColorPickerDialog d; 235 if(owner instanceof Frame || owner==null) { 236 d = new ColorPickerDialog( (Frame)owner, originalColor, includeOpacity); 237 } else if(owner instanceof Dialog){ 238 d = new ColorPickerDialog( (Dialog)owner, originalColor, includeOpacity); 239 } else { 240 throw new IllegalArgumentException("the owner ("+owner.getClass().getName()+") must be a java.awt.Frame or a java.awt.Dialog"); 241 } 242 243 d.setTitle(title == null ? 244 strings.getObject("ColorPickerDialogTitle").toString() : 245 title); 246 d.pack(); 247 d.setVisible(true); 248 return d.getColor(); 249 } 250 251 /** <code>PropertyChangeEvents</code> will be triggered for this property when the selected color 252 * changes. 253 * <P>(Events are only created when then RGB values of the color change. This means, for example, 254 * that the change from HSB(0,0,0) to HSB(.4,0,0) will <i>not</i> generate events, because when the 255 * brightness stays zero the RGB color remains (0,0,0). So although the hue moved around, the color 256 * is still black, so no events are created.) 257 * 258 */ 259 public static final String SELECTED_COLOR_PROPERTY = "selected color"; 260 261 262 /** <code>PropertyChangeEvents</code> will be triggered when the Hexadecimal value changes. 263 * (Fix by frederic.roudaut@free.fr because of some problems with SELECTED_COLOR_PROPERTY) 264 */ 265 public static final String HEX_COLOR_PROPERTY = "hexadecimal value"; 266 267 268 /** <code>PropertyChangeEvents</code> will be triggered for this property when <code>setModeControlsVisible()</code> 269 * is called. 270 */ 271 public static final String MODE_CONTROLS_VISIBLE_PROPERTY = "mode controls visible"; 272 273 /** <code>PropertyChangeEvents</code> will be triggered when the opacity value is 274 * adjusted. 275 */ 276 public static final String OPACITY_PROPERTY = "opacity"; 277 278 /** <code>PropertyChangeEvents</code> will be triggered when the mode changes. 279 * (That is, when the wheel switches from HUE, SAT, BRI, RED, GREEN, or BLUE modes.) 280 */ 281 public static final String MODE_PROPERTY = "mode"; 282 283 /** Used to indicate when we're in "hue mode". */ 284 protected static final int HUE = 0; 285 /** Used to indicate when we're in "brightness mode". */ 286 protected static final int BRI = 1; 287 /** Used to indicate when we're in "saturation mode". */ 288 protected static final int SAT = 2; 289 /** Used to indicate when we're in "red mode". */ 290 protected static final int RED = 3; 291 /** Used to indicate when we're in "green mode". */ 292 protected static final int GREEN = 4; 293 /** Used to indicate when we're in "blue mode". */ 294 protected static final int BLUE = 5; 295 296 /** The vertical slider */ 297 private JSlider slider = new JSlider(JSlider.VERTICAL,0,100,0); 298 299 ChangeListener changeListener = new ChangeListener() { 300 public void stateChanged(ChangeEvent e) { 301 Object src = e.getSource(); 302 if(hue.contains(src) || sat.contains(src) || bri.contains(src)) { 303 if(adjustingSpinners>0) 304 return; 305 306 setHSB( hue.getFloatValue()/360f, 307 sat.getFloatValue()/100f, 308 bri.getFloatValue()/100f ); 309 } else if(red.contains(src) || green.contains(src) || blue.contains(src)) { 310 if(adjustingSpinners>0) 311 return; 312 313 setRGB( red.getIntValue(), 314 green.getIntValue(), 315 blue.getIntValue() ); 316 } else if(src==colorPanel) { 317 if(adjustingColorPanel>0) 318 return; 319 320 int mode = getMode(); 321 if(mode==HUE || mode==BRI || mode==SAT) { 322 float[] hsb = colorPanel.getHSB(); 323 setHSB(hsb[0],hsb[1],hsb[2]); 324 } else { 325 int[] rgb = colorPanel.getRGB(); 326 setRGB(rgb[0],rgb[1],rgb[2]); 327 } 328 } else if(src==slider) { 329 if(adjustingSlider>0) 330 return; 331 332 int v = slider.getValue(); 333 Option option = getSelectedOption(); 334 option.setValue(v); 335 } else if(alpha.contains(src)) { 336 if(adjustingOpacity>0) 337 return; 338 int v = alpha.getIntValue(); 339 setOpacity( ((float)v)/255f ); 340 } else if(src==opacitySlider) { 341 if(adjustingOpacity>0) return; 342 343 float newValue = ( ((float)opacitySlider.getValue())/255f ); 344 setOpacity(newValue); 345 } 346 } 347 }; 348 349 ActionListener actionListener = new ActionListener() { 350 public void actionPerformed(ActionEvent e) { 351 Object src = e.getSource(); 352 if(src==hue.radioButton) { 353 setMode(HUE); 354 } else if(src==bri.radioButton) { 355 setMode(BRI); 356 } else if(src==sat.radioButton) { 357 setMode(SAT); 358 } else if(src==red.radioButton) { 359 setMode(RED); 360 } else if(src==green.radioButton) { 361 setMode(GREEN); 362 } else if(src==blue.radioButton) { 363 setMode(BLUE); 364 } 365 } 366 }; 367 368 /** @return the currently selected <code>Option</code> 369 */ 370 private Option getSelectedOption() { 371 int mode = getMode(); 372 if(mode==HUE) { 373 return hue; 374 } else if(mode==SAT) { 375 return sat; 376 } else if(mode==BRI) { 377 return bri; 378 } else if(mode==RED) { 379 return red; 380 } else if(mode==GREEN) { 381 return green; 382 } else { 383 return blue; 384 } 385 } 386 387 /** This thread will wait a second or two before committing the text in 388 * the hex TextField. This gives the user a chance to finish typing... 389 * but if the user is just waiting for something to happen, this makes sure 390 * after a second or two something happens. 391 */ 392 class HexUpdateThread extends Thread { 393 long myStamp; 394 String text; 395 396 public HexUpdateThread(long stamp,String s) { 397 myStamp = stamp; 398 text = s; 399 } 400 401 public void run() { 402 if(SwingUtilities.isEventDispatchThread()==false) { 403 long WAIT = 1500; 404 405 while(System.currentTimeMillis()-myStamp<WAIT) { 406 try { 407 long delay = WAIT - (System.currentTimeMillis()-myStamp); 408 if(delay<1) delay = 1; 409 Thread.sleep( delay ); 410 } catch(Exception e) { 411 Thread.yield(); 412 } 413 } 414 SwingUtilities.invokeLater(this); 415 return; 416 } 417 418 if(myStamp!=hexDocListener.lastTimeStamp) { 419 //another event has come along and trumped this one 420 return; 421 } 422 423 if(text.length()>6) 424 text = text.substring(0,6); 425 while(text.length()<6) { 426 text = text+"0"; 427 } 428 if(hexField.getText().equals(text)) 429 return; 430 431 int pos = hexField.getCaretPosition(); 432 hexField.setText(text); 433 hexField.setCaretPosition(pos); 434 } 435 } 436 437 HexDocumentListener hexDocListener = new HexDocumentListener(); 438 439 class HexDocumentListener implements DocumentListener { 440 long lastTimeStamp; 441 442 public void changedUpdate(DocumentEvent e) { 443 lastTimeStamp = System.currentTimeMillis(); 444 445 if(adjustingHexField>0) 446 return; 447 448 String s = hexField.getText(); 449 s = stripToHex(s); 450 if(s.length()==6) { 451 //the user typed 6 digits: we can work with this: 452 try { 453 int i = Integer.parseInt(s,16); 454 setRGB( ((i >> 16) & 0xff), ((i >> 8) & 0xff), ((i) & 0xff) ); 455 return; 456 } catch(NumberFormatException e2) { 457 //this shouldn't happen, since we already stripped out non-hex characters. 458 e2.printStackTrace(); 459 } 460 } 461 Thread thread = new HexUpdateThread(lastTimeStamp,s); 462 thread.start(); 463 while(System.currentTimeMillis()-lastTimeStamp==0) { 464 Thread.yield(); 465 } 466 } 467 468 /** Strips a string down to only uppercase hex-supported characters. */ 469 private String stripToHex(String s) { 470 s = s.toUpperCase(); 471 String s2 = ""; 472 for(int a = 0; a<s.length(); a++) { 473 char c = s.charAt(a); 474 if(c=='0' || c=='1' || c=='2' || c=='3' || c=='4' || c=='5' || 475 c=='6' || c=='7' || c=='8' || c=='9' || c=='0' || 476 c=='A' || c=='B' || c=='C' || c=='D' || c=='E' || c=='F') { 477 s2 = s2+c; 478 } 479 } 480 return s2; 481 } 482 483 public void insertUpdate(DocumentEvent e) { 484 changedUpdate(e); 485 } 486 487 public void removeUpdate(DocumentEvent e) { 488 changedUpdate(e); 489 } 490 }; 491 492 private Option alpha = new Option(strings.getObject("alphaLabel").toString(), 255); 493 private Option hue = new Option(strings.getObject("hueLabel").toString(), 360); 494 private Option sat = new Option(strings.getObject("saturationLabel").toString(), 100); 495 private Option bri = new Option(strings.getObject("brightnessLabel").toString(), 100); 496 private Option red = new Option(strings.getObject("redLabel").toString(), 255); 497 private Option green = new Option(strings.getObject("greenLabel").toString(), 255); 498 private Option blue = new Option(strings.getObject("blueLabel").toString(), 255); 499 private ColorSwatch preview = new ColorSwatch(50); 500 private JLabel hexLabel = new JLabel(strings.getObject("hexLabel").toString()); 501 private JTextField hexField = new JTextField("000000"); 502 503 /** Used to indicate when we're internally adjusting the value of the spinners. 504 * If this equals zero, then incoming events are triggered by the user and must be processed. 505 * If this is not equal to zero, then incoming events are triggered by another method 506 * that's already responding to the user's actions. 507 */ 508 private int adjustingSpinners = 0; 509 510 /** Used to indicate when we're internally adjusting the value of the slider. 511 * If this equals zero, then incoming events are triggered by the user and must be processed. 512 * If this is not equal to zero, then incoming events are triggered by another method 513 * that's already responding to the user's actions. 514 */ 515 private int adjustingSlider = 0; 516 517 /** Used to indicate when we're internally adjusting the selected color of the ColorPanel. 518 * If this equals zero, then incoming events are triggered by the user and must be processed. 519 * If this is not equal to zero, then incoming events are triggered by another method 520 * that's already responding to the user's actions. 521 */ 522 private int adjustingColorPanel = 0; 523 524 /** Used to indicate when we're internally adjusting the value of the hex field. 525 * If this equals zero, then incoming events are triggered by the user and must be processed. 526 * If this is not equal to zero, then incoming events are triggered by another method 527 * that's already responding to the user's actions. 528 */ 529 private int adjustingHexField = 0; 530 531 /** Used to indicate when we're internally adjusting the value of the opacity. 532 * If this equals zero, then incoming events are triggered by the user and must be processed. 533 * If this is not equal to zero, then incoming events are triggered by another method 534 * that's already responding to the user's actions. 535 */ 536 private int adjustingOpacity = 0; 537 538 /** The "expert" controls are the controls on the right side 539 * of this panel: the labels/spinners/radio buttons. 540 */ 541 private JPanel expertControls = new JPanel(new GridBagLayout()); 542 543 private ColorPickerPanel colorPanel = new ColorPickerPanel(); 544 545 private JSlider opacitySlider = new JSlider(0,255,255); 546 private JLabel opacityLabel = new JLabel(strings.getObject("opacityLabel").toString()); 547 548 /** Create a new <code>ColorPicker</code> with all controls visible except opacity. */ 549 public ColorPicker() { 550 this(true,false); 551 } 552 553 /** Create a new <code>ColorPicker</code>. 554 * 555 * @param showExpertControls the labels/spinners/buttons on the right side of a 556 * <code>ColorPicker</code> are optional. This boolean will control whether they 557 * are shown or not. 558 * <P>It may be that your users will never need or want numeric control when 559 * they choose their colors, so hiding this may simplify your interface. 560 * @param includeOpacity whether the opacity controls will be shown 561 */ 562 public ColorPicker(boolean showExpertControls,boolean includeOpacity) { 563 super(new GridBagLayout()); 564 GridBagConstraints c = new GridBagConstraints(); 565 566 Insets normalInsets = new Insets(3,3,3,3); 567 568 JPanel options = new JPanel(new GridBagLayout()); 569 c.gridx = 0; c.gridy = 0; c.weightx = 1; c.weighty = 1; 570 c.insets = normalInsets; 571 ButtonGroup bg = new ButtonGroup(); 572 573 //put them in order 574 Option[] optionsArray = new Option[] { 575 hue, sat, bri, red, green, blue 576 }; 577 578 for(int a = 0; a<optionsArray.length; a++) { 579 if(a==3 || a==6) { 580 c.insets = new Insets(normalInsets.top+10,normalInsets.left,normalInsets.bottom,normalInsets.right); 581 } else { 582 c.insets = normalInsets; 583 } 584 c.anchor = GridBagConstraints.EAST; 585 c.fill = GridBagConstraints.NONE; 586 options.add(optionsArray[a].label,c); 587 c.gridx++; 588 c.anchor = GridBagConstraints.WEST; 589 c.fill = GridBagConstraints.HORIZONTAL; 590 if(optionsArray[a].spinner!=null) { 591 options.add(optionsArray[a].spinner,c); 592 } else { 593 options.add(optionsArray[a].slider,c); 594 } 595 c.gridx++; c.fill = GridBagConstraints.NONE; 596 options.add(optionsArray[a].radioButton,c); 597 c.gridy++; 598 c.gridx = 0; 599 bg.add(optionsArray[a].radioButton); 600 } 601 c.insets = new Insets(normalInsets.top+10,normalInsets.left,normalInsets.bottom,normalInsets.right); 602 c.anchor = GridBagConstraints.EAST; c.fill = GridBagConstraints.NONE; 603 options.add(hexLabel,c); 604 c.gridx++; 605 c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.HORIZONTAL; 606 options.add(hexField,c); 607 c.gridy++; c.gridx = 0; 608 c.anchor = GridBagConstraints.EAST; c.fill = GridBagConstraints.NONE; 609 options.add(alpha.label,c); 610 c.gridx++; 611 c.anchor = GridBagConstraints.WEST; c.fill = GridBagConstraints.HORIZONTAL; 612 options.add(alpha.spinner,c); 613 614 c.gridx = 0; c.gridy = 0; c.weightx = 1; 615 c.weighty = 1; c.fill = GridBagConstraints.BOTH; 616 c.anchor = GridBagConstraints.CENTER; c.insets = normalInsets; 617 c.gridwidth = 2; 618 add(colorPanel,c); 619 620 c.gridwidth = 1; 621 c.insets = normalInsets; 622 c.gridx+=2; c.weighty = 1; c.gridwidth = 1; 623 c.fill = GridBagConstraints.VERTICAL; c.weightx = 0; 624 add(slider,c); 625 626 c.gridx++; c.fill = GridBagConstraints.VERTICAL; c.gridheight = c.REMAINDER; 627 c.anchor = GridBagConstraints.CENTER; c.insets = new Insets(0,0,0,0); 628 add(expertControls,c); 629 630 c.gridx = 0; c.gridheight = 1; 631 c.gridy = 1; c.weightx = 0; c.weighty = 0; 632 c.insets = normalInsets; c.anchor = c.CENTER; 633 add(opacityLabel,c); 634 c.gridx++; c.gridwidth = 2; 635 c.weightx = 1; c.fill = c.HORIZONTAL; 636 add(opacitySlider,c); 637 638 c.gridx = 0; c.gridy = 0; 639 c.gridheight = 1; c.gridwidth = 1; 640 c.fill = GridBagConstraints.BOTH; 641 c.weighty = 1; c.anchor = GridBagConstraints.CENTER; 642 c.weightx = 1; 643 c.insets = new Insets(normalInsets.top,normalInsets.left+8,normalInsets.bottom+10,normalInsets.right+8); 644 expertControls.add(preview,c); 645 c.gridy++; c.weighty = 0; c.anchor = GridBagConstraints.CENTER; 646 c.insets = new Insets(normalInsets.top,normalInsets.left,0,normalInsets.right); 647 expertControls.add(options,c); 648 649 preview.setOpaque(true); 650 651 colorPanel.setPreferredSize(new Dimension(expertControls.getPreferredSize().height, 652 expertControls.getPreferredSize().height)); 653 654 slider.addChangeListener(changeListener); 655 colorPanel.addChangeListener(changeListener); 656 slider.setUI(new ColorPickerSliderUI(slider,this)); 657 hexField.getDocument().addDocumentListener(hexDocListener); 658 659 setMode(BRI); 660 661 setExpertControlsVisible(showExpertControls); 662 663 setOpacityVisible(includeOpacity); 664 665 opacitySlider.addChangeListener(changeListener); 666 667 setOpacity(1); 668 669 } 670 671 /** This controls whether the hex field (and label) are visible or not. 672 * <P>Note this lives inside the "expert controls", so if <code>setExpertControlsVisible(false)</code> 673 * has been called, then calling this method makes no difference: the hex controls will be hidden. 674 */ 675 public void setHexControlsVisible(boolean b) { 676 hexLabel.setVisible(b); 677 hexField.setVisible(b); 678 } 679 680 /** This controls whether the preview swatch visible or not. 681 * <P>Note this lives inside the "expert controls", so if <code>setExpertControlsVisible(false)</code> 682 * has been called, then calling this method makes no difference: the swatch will be hidden. 683 */ 684 public void setPreviewSwatchVisible(boolean b) { 685 preview.setVisible(b); 686 } 687 688 /** The labels/spinners/buttons on the right side of a <code>ColorPicker</code> 689 * are optional. This method will control whether they are shown or not. 690 * <P>It may be that your users will never need or want numeric control when 691 * they choose their colors, so hiding this may simplify your interface. 692 * 693 * @param b whether to show or hide the expert controls. 694 */ 695 public void setExpertControlsVisible(boolean b) { 696 expertControls.setVisible(b); 697 } 698 699 /** @return the current HSB coordinates of this <code>ColorPicker</code>. 700 * Each value is between [0,1]. 701 * 702 */ 703 public float[] getHSB() { 704 return new float[] { 705 hue.getFloatValue()/360f, 706 sat.getFloatValue()/100f, 707 bri.getFloatValue()/100f 708 }; 709 } 710 711 /** @return the current RGB coordinates of this <code>ColorPicker</code>. 712 * Each value is between [0,255]. 713 * 714 */ 715 public int[] getRGB() { 716 return new int[] { 717 red.getIntValue(), 718 green.getIntValue(), 719 blue.getIntValue() 720 }; 721 } 722 723 /** Returns the currently selected opacity (a float between 0 and 1). 724 * 725 * @return the currently selected opacity (a float between 0 and 1). 726 */ 727 public float getOpacity() { 728 return ((float)opacitySlider.getValue())/255f; 729 } 730 731 private float lastOpacity = 1; 732 733 /** Sets the currently selected opacity. 734 * 735 * @param v a float between 0 and 1. 736 */ 737 public void setOpacity(float v) { 738 if(v<0 || v>1) 739 throw new IllegalArgumentException("The opacity ("+v+") must be between 0 and 1."); 740 adjustingOpacity++; 741 try { 742 int i = (int)(255*v); 743 opacitySlider.setValue( i ); 744 alpha.spinner.setValue( new Integer(i) ); 745 if(lastOpacity!=v) { 746 firePropertyChange(OPACITY_PROPERTY,new Float(lastOpacity),new Float(i)); 747 Color c = preview.getForeground(); 748 preview.setForeground(new Color(c.getRed(), c.getGreen(), c.getBlue(), i)); 749 } 750 lastOpacity = v; 751 } finally { 752 adjustingOpacity--; 753 } 754 } 755 756 /** Sets the mode of this <code>ColorPicker</code>. 757 * This is especially useful if this picker is in non-expert mode, so 758 * the radio buttons are not visible for the user to directly select. 759 * 760 * @param mode must be HUE, SAT, BRI, RED, GREEN or BLUE. 761 */ 762 public void setMode(int mode) { 763 if(!(mode==HUE || mode==SAT || mode==BRI || mode==RED || mode==GREEN || mode==BLUE)) 764 throw new IllegalArgumentException("mode must be HUE, SAT, BRI, REd, GREEN, or BLUE"); 765 putClientProperty(MODE_PROPERTY,new Integer(mode)); 766 hue.radioButton.setSelected(mode==HUE); 767 sat.radioButton.setSelected(mode==SAT); 768 bri.radioButton.setSelected(mode==BRI); 769 red.radioButton.setSelected(mode==RED); 770 green.radioButton.setSelected(mode==GREEN); 771 blue.radioButton.setSelected(mode==BLUE); 772 773 colorPanel.setMode(mode); 774 adjustingSlider++; 775 try { 776 slider.setValue(0); 777 Option option = getSelectedOption(); 778 slider.setInverted(mode==HUE); 779 int max = option.getMaximum(); 780 slider.setMaximum(max); 781 slider.setValue( option.getIntValue() ); 782 slider.repaint(); 783 784 if(mode==HUE || mode==SAT || mode==BRI) { 785 setHSB( hue.getFloatValue()/360f, 786 sat.getFloatValue()/100f, 787 bri.getFloatValue()/100f ); 788 } else { 789 setRGB( red.getIntValue(), 790 green.getIntValue(), 791 blue.getIntValue() ); 792 793 } 794 } finally { 795 adjustingSlider--; 796 } 797 } 798 799 /** This controls whether the radio buttons that adjust the mode are visible. 800 * <P>(These buttons appear next to the spinners in the expert controls.) 801 * <P>Note these live inside the "expert controls", so if <code>setExpertControlsVisible(false)</code> 802 * has been called, then these will never be visible. 803 * 804 * @param b 805 */ 806 public void setModeControlsVisible(boolean b) { 807 hue.radioButton.setVisible(b && hue.isVisible()); 808 sat.radioButton.setVisible(b && sat.isVisible()); 809 bri.radioButton.setVisible(b && bri.isVisible()); 810 red.radioButton.setVisible(b && red.isVisible()); 811 green.radioButton.setVisible(b && green.isVisible()); 812 blue.radioButton.setVisible(b && blue.isVisible()); 813 putClientProperty(MODE_CONTROLS_VISIBLE_PROPERTY,new Boolean(b)); 814 } 815 816 /** @return the current mode of this <code>ColorPicker</code>. 817 * <BR>This will return <code>HUE</code>, <code>SAT</code>, <code>BRI</code>, 818 * <code>RED</code>, <code>GREEN</code>, or <code>BLUE</code>. 819 * <P>The default mode is <code>BRI</code>, because that provides the most 820 * aesthetic/recognizable color wheel. 821 */ 822 public int getMode() { 823 Integer i = (Integer)getClientProperty(MODE_PROPERTY); 824 if(i==null) return -1; 825 return i.intValue(); 826 } 827 828 /** Sets the current color of this <code>ColorPicker</code>. 829 * This method simply calls <code>setRGB()</code> and <code>setOpacity()</code>. 830 * @param c the new color to use. 831 */ 832 public void setColor(Color c) { 833 setRGB(c.getRed(),c.getGreen(),c.getBlue()); 834 float opacity = ((float)c.getAlpha())/255f; 835 setOpacity(opacity); 836 } 837 838 /** Sets the current color of this <code>ColorPicker</code> 839 * 840 * @param r the red value. Must be between [0,255]. 841 * @param g the green value. Must be between [0,255]. 842 * @param b the blue value. Must be between [0,255]. 843 */ 844 public void setRGB(int r,int g,int b) { 845 if(r<0 || r>255) 846 throw new IllegalArgumentException("The red value ("+r+") must be between [0,255]."); 847 if(g<0 || g>255) 848 throw new IllegalArgumentException("The green value ("+g+") must be between [0,255]."); 849 if(b<0 || b>255) 850 throw new IllegalArgumentException("The blue value ("+b+") must be between [0,255]."); 851 852 853 Color lastColor = getColor(); 854 boolean updateRGBSpinners = adjustingSpinners==0; 855 856 adjustingSpinners++; 857 adjustingColorPanel++; 858 int alpha = this.alpha.getIntValue(); 859 try { 860 if(updateRGBSpinners) { 861 red.setValue(r); 862 green.setValue(g); 863 blue.setValue(b); 864 } 865 preview.setForeground(new Color(r,g,b, alpha)); 866 float[] hsb = new float[3]; 867 Color.RGBtoHSB(r, g, b, hsb); 868 hue.setValue( (int)(hsb[0]*360f+.49f)); 869 sat.setValue( (int)(hsb[1]*100f+.49f)); 870 bri.setValue( (int)(hsb[2]*100f+.49f)); 871 colorPanel.setRGB(r, g, b); 872 updateHexField(); 873 updateSlider(); 874 } finally { 875 adjustingSpinners--; 876 adjustingColorPanel--; 877 } 878 Color newColor = getColor(); 879 if(lastColor.equals(newColor)==false) 880 firePropertyChange(SELECTED_COLOR_PROPERTY,lastColor,newColor); 881 882 } 883 884 /** @return the current <code>Color</code> this <code>ColorPicker</code> has selected. 885 * <P>This is equivalent to: 886 * <BR><code>int[] i = getRGB();</code> 887 * <BR><code>return new Color(i[0], i[1], i[2], opacitySlider.getValue());</code> 888 */ 889 public Color getColor() { 890 int[] i = getRGB(); 891 return new Color(i[0], i[1], i[2], opacitySlider.getValue()); 892 } 893 894 private void updateSlider() { 895 adjustingSlider++; 896 try { 897 int mode = getMode(); 898 if(mode==HUE) { 899 slider.setValue( hue.getIntValue() ); 900 } else if(mode==SAT) { 901 slider.setValue( sat.getIntValue() ); 902 } else if(mode==BRI) { 903 slider.setValue( bri.getIntValue() ); 904 } else if(mode==RED) { 905 slider.setValue( red.getIntValue() ); 906 } else if(mode==GREEN) { 907 slider.setValue( green.getIntValue() ); 908 } else if(mode==BLUE) { 909 slider.setValue( blue.getIntValue() ); 910 } 911 } finally { 912 adjustingSlider--; 913 } 914 slider.repaint(); 915 } 916 917 /** This returns the panel with several rows of spinner controls. 918 * <P>Note you can also call methods such as <code>setRGBControlsVisible()</code> to adjust 919 * which controls are showing. 920 * <P>(This returns the panel this <code>ColorPicker</code> uses, so if you put it in 921 * another container, it will be removed from this <code>ColorPicker</code>.) 922 * @return the panel with several rows of spinner controls. 923 */ 924 public JPanel getExpertControls() { 925 return expertControls; 926 } 927 928 /** This shows or hides the RGB spinner controls. 929 * <P>Note these live inside the "expert controls", so if <code>setExpertControlsVisible(false)</code> 930 * has been called, then calling this method makes no difference: the RGB controls will be hidden. 931 * 932 * @param b whether the controls should be visible or not. 933 */ 934 public void setRGBControlsVisible(boolean b) { 935 red.setVisible(b); 936 green.setVisible(b); 937 blue.setVisible(b); 938 } 939 940 /** This shows or hides the HSB spinner controls. 941 * <P>Note these live inside the "expert controls", so if <code>setExpertControlsVisible(false)</code> 942 * has been called, then calling this method makes no difference: the HSB controls will be hidden. 943 * 944 * @param b whether the controls should be visible or not. 945 */ 946 public void setHSBControlsVisible(boolean b) { 947 hue.setVisible(b); 948 sat.setVisible(b); 949 bri.setVisible(b); 950 } 951 952 /** This shows or hides the alpha controls. 953 * <P>Note the alpha spinner live inside the "expert controls", so if <code>setExpertControlsVisible(false)</code> 954 * has been called, then this method does not affect that spinner. 955 * However, the opacity slider is <i>not</i> affected by the visibility of the export controls. 956 * @param b 957 */ 958 public void setOpacityVisible(boolean b) { 959 opacityLabel.setVisible(b); 960 opacitySlider.setVisible(b); 961 alpha.label.setVisible(b); 962 alpha.spinner.setVisible(b); 963 } 964 965 /** @return the <code>ColorPickerPanel</code> this <code>ColorPicker</code> displays. */ 966 public ColorPickerPanel getColorPanel() { 967 return colorPanel; 968 } 969 970 /** Sets the current color of this <code>ColorPicker</code> 971 * 972 * @param h the hue value. 973 * @param s the saturation value. Must be between [0,1]. 974 * @param b the blue value. Must be between [0,1]. 975 */ 976 public void setHSB(float h, float s, float b) { 977 if(Float.isInfinite(h) || Float.isNaN(h)) 978 throw new IllegalArgumentException("The hue value ("+h+") is not a valid number."); 979 //hue is cyclic, so it can be any value: 980 while(h<0) h++; 981 while(h>1) h--; 982 983 if(s<0 || s>1) 984 throw new IllegalArgumentException("The saturation value ("+s+") must be between [0,1]"); 985 if(b<0 || b>1) 986 throw new IllegalArgumentException("The brightness value ("+b+") must be between [0,1]"); 987 988 Color lastColor = getColor(); 989 990 boolean updateHSBSpinners = adjustingSpinners==0; 991 adjustingSpinners++; 992 adjustingColorPanel++; 993 try { 994 if(updateHSBSpinners) { 995 hue.setValue( (int)(h*360f+.49f)); 996 sat.setValue( (int)(s*100f+.49f)); 997 bri.setValue( (int)(b*100f+.49f)); 998 } 999 1000 Color c = new Color(Color.HSBtoRGB(h, s, b)); 1001 int alpha = this.alpha.getIntValue(); 1002 c = new Color(c.getRed(), c.getGreen(), c.getBlue(), alpha); 1003 preview.setForeground(c); 1004 red.setValue(c.getRed()); 1005 green.setValue(c.getGreen()); 1006 blue.setValue(c.getBlue()); 1007 colorPanel.setHSB(h, s, b); 1008 updateHexField(); 1009 updateSlider(); 1010 slider.repaint(); 1011 } finally { 1012 adjustingSpinners--; 1013 adjustingColorPanel--; 1014 } 1015 Color newColor = getColor(); 1016 if(lastColor.equals(newColor)==false) 1017 firePropertyChange(SELECTED_COLOR_PROPERTY,lastColor,newColor); 1018 } 1019 1020 private void updateHexField() { 1021 adjustingHexField++; 1022 try { 1023 int r = red.getIntValue(); 1024 int g = green.getIntValue(); 1025 int b = blue.getIntValue(); 1026 1027 int i = (r << 16) + (g << 8) +b; 1028 String s = Integer.toHexString(i).toUpperCase(); 1029 while(s.length()<6) 1030 s = "0"+s; 1031 if(hexField.getText().equalsIgnoreCase(s)==false) 1032 { 1033 // Fix by frederic.roudaut@free.fr 1034 firePropertyChange(HEX_COLOR_PROPERTY,hexField.getText(),s); 1035 hexField.setText(s); 1036 } 1037 } finally { 1038 adjustingHexField--; 1039 } 1040 } 1041 1042 class Option { 1043 JRadioButton radioButton = new JRadioButton(); 1044 JSpinner spinner; 1045 JSlider slider; 1046 JLabel label; 1047 public Option(String text,int max) { 1048 spinner = new JSpinner(new SpinnerNumberModel(0,0,max,5)); 1049 spinner.addChangeListener(changeListener); 1050 1051 /*this tries out Tim Boudreaux's new slider UI. 1052 * It's a good UI, but I think for the ColorPicker 1053 * the numeric controls are more useful. 1054 * That is: users who want click-and-drag control to choose 1055 * their colors don't need any of these Option objects 1056 * at all; only power users who may have specific RGB 1057 * values in mind will use these controls: and when they do 1058 * limiting them to a slider is unnecessary. 1059 * That's my current position... of course it may 1060 * not be true in the real world... :) 1061 */ 1062 //slider = new JSlider(0,max); 1063 //slider.addChangeListener(changeListener); 1064 //slider.setUI(new org.netbeans.paint.api.components.PopupSliderUI()); 1065 1066 label = new JLabel(text); 1067 radioButton.addActionListener(actionListener); 1068 } 1069 1070 public void setValue(int i) { 1071 if(slider!=null) { 1072 slider.setValue(i); 1073 } 1074 if(spinner!=null) { 1075 spinner.setValue(new Integer(i)); 1076 } 1077 } 1078 1079 public int getMaximum() { 1080 if(slider!=null) 1081 return slider.getMaximum(); 1082 return ((Number) ((SpinnerNumberModel)spinner.getModel()).getMaximum() ).intValue(); 1083 } 1084 1085 public boolean contains(Object src) { 1086 return (src==slider || src==spinner || src==radioButton || src==label); 1087 } 1088 1089 public float getFloatValue() { 1090 return getIntValue(); 1091 } 1092 1093 public int getIntValue() { 1094 if(slider!=null) 1095 return slider.getValue(); 1096 return ((Number)spinner.getValue()).intValue(); 1097 } 1098 1099 public boolean isVisible() { 1100 return label.isVisible(); 1101 } 1102 1103 public void setVisible(boolean b) { 1104 boolean radioButtonsAllowed = true; 1105 Boolean z = (Boolean)getClientProperty(MODE_CONTROLS_VISIBLE_PROPERTY); 1106 if(z!=null) radioButtonsAllowed = z.booleanValue(); 1107 1108 radioButton.setVisible(b && radioButtonsAllowed); 1109 if(slider!=null) 1110 slider.setVisible(b); 1111 if(spinner!=null) 1112 spinner.setVisible(b); 1113 label.setVisible(b); 1114 } 1115 } 1116 }