/* * Open Source Physics software is free software as described near the bottom of this code file. * * For additional information and documentation on Open Source Physics please see: * <http://www.opensourcephysics.org/> */ package org.opensourcephysics.tools; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import javax.swing.AbstractAction; import javax.swing.ActionMap; import javax.swing.ButtonGroup; import javax.swing.ButtonModel; import javax.swing.Icon; import javax.swing.JCheckBox; import javax.swing.SwingUtilities; import javax.swing.event.ChangeListener; import javax.swing.plaf.ActionMapUIResource; /** * A checkbox with checked, unchecked and part-checked states * * @author Dr. Heinz M. Kabutz * see http://www.javaspecialists.co.za/archive/Issue082.html */ public class TristateCheckBox extends JCheckBox { /** This is a type-safe enumerated type */ public static class State { private State() { /** empty block */ } } public static final State NOT_SELECTED = new State(); public static final State SELECTED = new State(); public static final State PART_SELECTED = new State(); private final TristateDecorator model; /** * Constructor TristateCheckBox * @param text * @param icon * @param initial */ public TristateCheckBox(String text, Icon icon, State initial) { super(text, icon); // Add a listener for when the mouse is pressed super.addMouseListener(new MouseAdapter() { public void mousePressed(MouseEvent e) { grabFocus(); model.nextState(); } }); // Reset the keyboard action map ActionMap map = new ActionMapUIResource(); map.put("pressed", new AbstractAction() { //$NON-NLS-1$ public void actionPerformed(ActionEvent e) { grabFocus(); model.nextState(); } }); map.put("released", null); //$NON-NLS-1$ SwingUtilities.replaceUIActionMap(this, map); // set the model to the adapted model model = new TristateDecorator(getModel()); setModel(model); setState(initial); } /** * Constructor TristateCheckBox * @param text * @param initial */ public TristateCheckBox(String text, State initial) { this(text, null, initial); } /** * Constructor TristateCheckBox * @param text */ public TristateCheckBox(String text) { this(text, PART_SELECTED); } /** * Constructor TristateCheckBox */ public TristateCheckBox() { this(null); } /** No one may add mouse listeners, not even Swing! */ public void addMouseListener(MouseListener l) { /** empty block */ } /** * Set the new state to either SELECTED, NOT_SELECTED or * PART_SELECTED. If state == null, it is treated as PART_SELECTED. */ public void setState(State state) { model.setState(state); } /** Return the current state, which is determined by the * selection status of the model. */ public State getState() { return model.getState(); } public void setSelected(boolean b) { if(b) { setState(SELECTED); } else { setState(NOT_SELECTED); } } /** * Exactly which Design Pattern is this? Is it an Adapter, * a Proxy or a Decorator? In this case, my vote lies with the * Decorator, because we are extending functionality and * "decorating" the original model with a more powerful model. */ private class TristateDecorator implements ButtonModel { private final ButtonModel other; private TristateDecorator(ButtonModel other) { this.other = other; } private void setState(State state) { if(state==NOT_SELECTED) { other.setArmed(false); setPressed(false); setSelected(false); } else if(state==SELECTED) { other.setArmed(false); setPressed(false); setSelected(true); } else { // either "null" or DONT_CARE other.setArmed(true); setPressed(true); setSelected(true); } } /** * The current state is embedded in the selection / armed * state of the model. * * We return the SELECTED state when the checkbox is selected * but not armed, PART_SELECTED state when the checkbox is * selected and armed (grey) and NOT_SELECTED when the * checkbox is deselected. */ private State getState() { if(isSelected()&&!isArmed()) { // normal black tick return SELECTED; } else if(isSelected()&&isArmed()) { // don't care grey tick return PART_SELECTED; } else { // normal deselected return NOT_SELECTED; } } /** We rotate between NOT_SELECTED, SELECTED and PART_SELECTED.*/ private void nextState() { State current = getState(); if(current==NOT_SELECTED) { setState(SELECTED); } else if(current==SELECTED) { setState(PART_SELECTED); } else if(current==PART_SELECTED) { setState(NOT_SELECTED); } } /** Filter: No one may change the armed status except us. */ public void setArmed(boolean b) { /** empty block */ } /** We disable focusing on the component when it is not * enabled. */ public void setEnabled(boolean b) { setFocusable(b); other.setEnabled(b); } /** All these methods simply delegate to the "other" model * that is being decorated. */ public boolean isArmed() { return other.isArmed(); } public boolean isSelected() { return other.isSelected(); } public boolean isEnabled() { return other.isEnabled(); } public boolean isPressed() { return other.isPressed(); } public boolean isRollover() { return other.isRollover(); } public void setSelected(boolean b) { other.setSelected(b); } public void setPressed(boolean b) { other.setPressed(b); } public void setRollover(boolean b) { other.setRollover(b); } public void setMnemonic(int key) { other.setMnemonic(key); } public int getMnemonic() { return other.getMnemonic(); } public void setActionCommand(String s) { other.setActionCommand(s); } public String getActionCommand() { return other.getActionCommand(); } public void setGroup(ButtonGroup group) { other.setGroup(group); } public void addActionListener(ActionListener l) { other.addActionListener(l); } public void removeActionListener(ActionListener l) { other.removeActionListener(l); } public void addItemListener(ItemListener l) { other.addItemListener(l); } public void removeItemListener(ItemListener l) { other.removeItemListener(l); } public void addChangeListener(ChangeListener l) { other.addChangeListener(l); } public void removeChangeListener(ChangeListener l) { other.removeChangeListener(l); } public Object[] getSelectedObjects() { return other.getSelectedObjects(); } } } /* * Open Source Physics software is free software; you can redistribute * it and/or modify it under the terms of the GNU General Public License (GPL) as * published by the Free Software Foundation; either version 2 of the License, * or(at your option) any later version. * Code that uses any portion of the code in the org.opensourcephysics package * or any subpackage (subdirectory) of this package must must also be be released * under the GNU GPL license. * * This software 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; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston MA 02111-1307 USA * or view the license online at http://www.gnu.org/copyleft/gpl.html * * Copyright (c) 2007 The Open Source Physics project * http://www.opensourcephysics.org */