/* * NullableCheckBox.java 7 nov. 2008 * * Sweet Home 3D, Copyright (c) 2008 Emmanuel PUYBARET / eTeks <info@eteks.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.eteks.sweethome3d.swing; import java.awt.Dimension; import java.awt.Graphics; import java.awt.GridLayout; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.util.ArrayList; import java.util.List; import javax.swing.JCheckBox; import javax.swing.JComponent; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; /** * A check box that accepts <code>null</code> values. Thus this check box is able to * display 3 states : <code>null</code>, <code>false</code> and <code>true</code>. * @author Emmanuel Puybaret */ public class NullableCheckBox extends JComponent { /** * Identifies a change in the check box text. */ public static final String TEXT_CHANGED_PROPERTY = "text"; /** * Identifies a change in the check box mnemonic. */ public static final String MNEMONIC_CHANGED_PROPERTY = "mnemonic"; private JCheckBox checkBox; private Boolean value = Boolean.FALSE; private boolean nullable; private ItemListener checkBoxListener; private List<ChangeListener> changeListeners = new ArrayList<ChangeListener>(1); /** * Creates a nullable check box. */ public NullableCheckBox(String text) { // Measure check box size alone without its text final Dimension checkBoxSize = new JCheckBox().getPreferredSize(); // Create a check box that displays a dash upon default check box for a null value this.checkBox = new JCheckBox(text) { @Override protected void paintComponent(Graphics g) { super.paintComponent(g); if (value == null) { g.drawRect(checkBoxSize.width / 2 - 3, checkBoxSize.height / 2, 6, 1); } } }; // Add an item listener to change default checking logic this.checkBoxListener = new ItemListener() { public void itemStateChanged(ItemEvent ev) { // If this check box is nullable if (nullable) { // Checking sequence will be null, true, false if (getValue() == Boolean.FALSE) { setValue(null); } else if (getValue() == null) { setValue(Boolean.TRUE); } else { setValue(Boolean.FALSE); } } else { setValue(checkBox.isSelected()); } } }; this.checkBox.addItemListener(this.checkBoxListener); // Add the check box and its label to this component setLayout(new GridLayout()); add(this.checkBox); } /** * Returns <code>null</code>, <code>Boolean.TRUE</code> or <code>Boolean.FALSE</code>. */ public Boolean getValue() { return this.value; } /** * Sets displayed value in check box. * @param value <code>null</code>, <code>Boolean.TRUE</code> or <code>Boolean.FALSE</code> */ public void setValue(Boolean value) { this.value = value; this.checkBox.removeItemListener(this.checkBoxListener); try { if (value != null) { this.checkBox.setSelected(value); } else if (isNullable()) { // Unselect check box to display a dash in its middle this.checkBox.setSelected(false); this.checkBox.repaint(); } else { throw new IllegalArgumentException("Check box isn't nullable"); } fireStateChanged(); } finally { this.checkBox.addItemListener(this.checkBoxListener); } } /** * Returns <code>true</code> if this check box is nullable. */ public boolean isNullable() { return this.nullable; } /** * Sets whether this check box is nullable. */ public void setNullable(boolean nullable) { this.nullable = nullable; if (!nullable && getValue() == null) { setValue(Boolean.FALSE); } } /** * Sets the mnemonic of this component. * @param mnemonic a <code>VK_...</code> code defined in <code>java.awt.event.KeyEvent</code>. */ public void setMnemonic(int mnemonic) { int oldMnemonic = this.checkBox.getMnemonic(); if (oldMnemonic != mnemonic) { this.checkBox.setMnemonic(mnemonic); firePropertyChange(MNEMONIC_CHANGED_PROPERTY, oldMnemonic, mnemonic); } } /** * Returns the mnemonic of this component. */ public int getMnemonic() { return this.checkBox.getMnemonic(); } /** * Sets the text of this component. * @param text a <code>VK_...</code> code defined in <code>java.awt.event.KeyEvent</code>. */ public void setText(String text) { String oldText = this.checkBox.getText(); if (oldText != text) { this.checkBox.setText(text); firePropertyChange(TEXT_CHANGED_PROPERTY, oldText, text); } } /** * Returns the text of this component. */ public String getText() { return this.checkBox.getText(); } /** * Sets the tool tip text displayed by this check box. */ public void setToolTipText(String text) { this.checkBox.setToolTipText(text); } @Override public void setEnabled(boolean enabled) { if (this.checkBox.isEnabled() != enabled) { this.checkBox.setEnabled(enabled); firePropertyChange("enabled", !enabled, enabled); } } @Override public boolean isEnabled() { return this.checkBox.isEnabled(); } /** * Adds a listener to this component. */ public void addChangeListener(final ChangeListener l) { this.changeListeners.add(l); } /** * Removes a listener from this component. */ public void removeChangeListener(final ChangeListener l) { this.changeListeners.remove(l); } /** * Fires a state changed event to listeners. */ private void fireStateChanged() { if (!this.changeListeners.isEmpty()) { ChangeEvent changeEvent = new ChangeEvent(this); // Work on a copy of changeListeners to ensure a listener // can modify safely listeners list ChangeListener [] listeners = this.changeListeners. toArray(new ChangeListener [this.changeListeners.size()]); for (ChangeListener listener : listeners) { listener.stateChanged(changeEvent); } } } }