001 // License: GPL. Copyright 2008 by Frederik Ramm and others
002 package org.openstreetmap.josm.gui;
003
004 import static org.openstreetmap.josm.tools.I18n.tr;
005
006 import java.awt.event.ActionEvent;
007 import java.awt.event.ActionListener;
008 import java.awt.event.ItemListener;
009 import java.awt.event.MouseAdapter;
010 import java.awt.event.MouseEvent;
011 import java.awt.event.MouseListener;
012
013 import javax.swing.AbstractAction;
014 import javax.swing.ActionMap;
015 import javax.swing.ButtonGroup;
016 import javax.swing.ButtonModel;
017 import javax.swing.Icon;
018 import javax.swing.JCheckBox;
019 import javax.swing.SwingUtilities;
020 import javax.swing.event.ChangeListener;
021 import javax.swing.plaf.ActionMapUIResource;
022
023 public class QuadStateCheckBox extends JCheckBox {
024
025 public enum State { NOT_SELECTED, SELECTED, UNSET, PARTIAL }
026
027 private final QuadStateDecorator model;
028 private State[] allowed;
029
030 public QuadStateCheckBox(String text, Icon icon, State initial, State[] allowed) {
031 super(text, icon);
032 this.allowed = allowed;
033 // Add a listener for when the mouse is pressed
034 super.addMouseListener(new MouseAdapter() {
035 @Override public void mousePressed(MouseEvent e) {
036 grabFocus();
037 model.nextState();
038 }
039 });
040 // Reset the keyboard action map
041 ActionMap map = new ActionMapUIResource();
042 map.put("pressed", new AbstractAction() {
043 public void actionPerformed(ActionEvent e) {
044 grabFocus();
045 model.nextState();
046 }
047 });
048 map.put("released", null);
049 SwingUtilities.replaceUIActionMap(this, map);
050 // set the model to the adapted model
051 model = new QuadStateDecorator(getModel());
052 setModel(model);
053 setState(initial);
054 }
055 public QuadStateCheckBox(String text, State initial, State[] allowed) {
056 this(text, null, initial, allowed);
057 }
058
059 /** Do not let anyone add mouse listeners */
060 @Override public void addMouseListener(MouseListener l) { }
061 /**
062 * Set the new state.
063 */
064 public void setState(State state) { model.setState(state); }
065 /** Return the current state, which is determined by the
066 * selection status of the model. */
067 public State getState() { return model.getState(); }
068 @Override public void setSelected(boolean b) {
069 if (b) {
070 setState(State.SELECTED);
071 } else {
072 setState(State.NOT_SELECTED);
073 }
074 }
075
076 private class QuadStateDecorator implements ButtonModel {
077 private final ButtonModel other;
078 private QuadStateDecorator(ButtonModel other) {
079 this.other = other;
080 }
081 private void setState(State state) {
082 if (state == State.NOT_SELECTED) {
083 other.setArmed(false);
084 other.setPressed(false);
085 other.setSelected(false);
086 setToolTipText(tr("false: the property is explicitly switched off"));
087 } else if (state == State.SELECTED) {
088 other.setArmed(false);
089 other.setPressed(false);
090 other.setSelected(true);
091 setToolTipText(tr("true: the property is explicitly switched on"));
092 } else if (state == State.PARTIAL) {
093 other.setArmed(true);
094 other.setPressed(true);
095 other.setSelected(true);
096 setToolTipText(tr("partial: different selected objects have different values, do not change"));
097 } else {
098 other.setArmed(true);
099 other.setPressed(true);
100 other.setSelected(false);
101 setToolTipText(tr("unset: do not set this property on the selected objects"));
102 }
103 }
104 /**
105 * The current state is embedded in the selection / armed
106 * state of the model.
107 *
108 * We return the SELECTED state when the checkbox is selected
109 * but not armed, PARTIAL state when the checkbox is
110 * selected and armed (grey) and NOT_SELECTED when the
111 * checkbox is deselected.
112 */
113 private State getState() {
114 if (isSelected() && !isArmed()) {
115 // normal black tick
116 return State.SELECTED;
117 } else if (isSelected() && isArmed()) {
118 // don't care grey tick
119 return State.PARTIAL;
120 } else if (!isSelected() && !isArmed()) {
121 return State.NOT_SELECTED;
122 } else {
123 return State.UNSET;
124 }
125 }
126 /** Rotate to the next allowed state.*/
127 private void nextState() {
128 State current = getState();
129 for (int i = 0; i < allowed.length; i++) {
130 if (allowed[i] == current) {
131 setState((i == allowed.length-1) ? allowed[0] : allowed[i+1]);
132 break;
133 }
134 }
135 }
136 /** Filter: No one may change the armed/selected/pressed status except us. */
137 public void setArmed(boolean b) { }
138 public void setSelected(boolean b) { }
139 public void setPressed(boolean b) { }
140 /** We disable focusing on the component when it is not
141 * enabled. */
142 public void setEnabled(boolean b) {
143 setFocusable(b);
144 other.setEnabled(b);
145 }
146 /** All these methods simply delegate to the "other" model
147 * that is being decorated. */
148 public boolean isArmed() { return other.isArmed(); }
149 public boolean isSelected() { return other.isSelected(); }
150 public boolean isEnabled() { return other.isEnabled(); }
151 public boolean isPressed() { return other.isPressed(); }
152 public boolean isRollover() { return other.isRollover(); }
153 public void setRollover(boolean b) { other.setRollover(b); }
154 public void setMnemonic(int key) { other.setMnemonic(key); }
155 public int getMnemonic() { return other.getMnemonic(); }
156 public void setActionCommand(String s) {
157 other.setActionCommand(s);
158 }
159 public String getActionCommand() {
160 return other.getActionCommand();
161 }
162 public void setGroup(ButtonGroup group) {
163 other.setGroup(group);
164 }
165 public void addActionListener(ActionListener l) {
166 other.addActionListener(l);
167 }
168 public void removeActionListener(ActionListener l) {
169 other.removeActionListener(l);
170 }
171 public void addItemListener(ItemListener l) {
172 other.addItemListener(l);
173 }
174 public void removeItemListener(ItemListener l) {
175 other.removeItemListener(l);
176 }
177 public void addChangeListener(ChangeListener l) {
178 other.addChangeListener(l);
179 }
180 public void removeChangeListener(ChangeListener l) {
181 other.removeChangeListener(l);
182 }
183 public Object[] getSelectedObjects() {
184 return other.getSelectedObjects();
185 }
186 }
187 }