source: josm/trunk/src/org/openstreetmap/josm/gui/widgets/QuadStateCheckBox.java@ 15287

Last change on this file since 15287 was 15287, checked in by Don-vip, 5 years ago

see #15294, see #15484 - code cleanup, revert r12829 / r13036 now that Java 9 is EOL

  • Property svn:eol-style set to native
File size: 10.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.widgets;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.event.ActionEvent;
7import java.awt.event.ActionListener;
8import java.awt.event.ItemListener;
9import java.awt.event.MouseAdapter;
10import java.awt.event.MouseEvent;
11import java.awt.event.MouseListener;
12
13import javax.swing.AbstractAction;
14import javax.swing.ActionMap;
15import javax.swing.ButtonGroup;
16import javax.swing.ButtonModel;
17import javax.swing.Icon;
18import javax.swing.JCheckBox;
19import javax.swing.SwingUtilities;
20import javax.swing.event.ChangeListener;
21import javax.swing.plaf.ActionMapUIResource;
22
23import org.openstreetmap.josm.tools.Utils;
24
25/**
26 * A four-state checkbox. The states are enumerated in {@link State}.
27 * @since 591
28 */
29public class QuadStateCheckBox extends JCheckBox {
30
31 /**
32 * The 4 possible states of this checkbox.
33 */
34 public enum State {
35 /** Not selected: the property is explicitly switched off */
36 NOT_SELECTED,
37 /** Selected: the property is explicitly switched on */
38 SELECTED,
39 /** Unset: do not set this property on the selected objects */
40 UNSET,
41 /** Partial: different selected objects have different values, do not change */
42 PARTIAL
43 }
44
45 private final transient QuadStateDecorator cbModel;
46 private State[] allowed;
47
48 /**
49 * Constructs a new {@code QuadStateCheckBox}.
50 * @param text the text of the check box
51 * @param icon the Icon image to display
52 * @param initial The initial state
53 * @param allowed The allowed states
54 */
55 public QuadStateCheckBox(String text, Icon icon, State initial, State... allowed) {
56 super(text, icon);
57 this.allowed = Utils.copyArray(allowed);
58 // Add a listener for when the mouse is pressed
59 super.addMouseListener(new MouseAdapter() {
60 @Override public void mousePressed(MouseEvent e) {
61 grabFocus();
62 cbModel.nextState();
63 }
64 });
65 // Reset the keyboard action map
66 ActionMap map = new ActionMapUIResource();
67 map.put("pressed", new AbstractAction() {
68 @Override
69 public void actionPerformed(ActionEvent e) {
70 grabFocus();
71 cbModel.nextState();
72 }
73 });
74 map.put("released", null);
75 SwingUtilities.replaceUIActionMap(this, map);
76 // set the model to the adapted model
77 cbModel = new QuadStateDecorator(getModel());
78 setModel(cbModel);
79 setState(initial);
80 }
81
82 /**
83 * Constructs a new {@code QuadStateCheckBox}.
84 * @param text the text of the check box
85 * @param initial The initial state
86 * @param allowed The allowed states
87 */
88 public QuadStateCheckBox(String text, State initial, State... allowed) {
89 this(text, null, initial, allowed);
90 }
91
92 /** Do not let anyone add mouse listeners */
93 @Override
94 public synchronized void addMouseListener(MouseListener l) {
95 // Do nothing
96 }
97
98 /**
99 * Sets a text describing this property in the tooltip text
100 * @param propertyText a description for the modelled property
101 */
102 public final void setPropertyText(final String propertyText) {
103 cbModel.setPropertyText(propertyText);
104 }
105
106 /**
107 * Set the new state.
108 * @param state The new state
109 */
110 public final void setState(State state) {
111 cbModel.setState(state);
112 }
113
114 /**
115 * Return the current state, which is determined by the selection status of the model.
116 * @return The current state
117 */
118 public State getState() {
119 return cbModel.getState();
120 }
121
122 @Override
123 public void setSelected(boolean b) {
124 if (b) {
125 setState(State.SELECTED);
126 } else {
127 setState(State.NOT_SELECTED);
128 }
129 }
130
131 /**
132 * Button model for the {@code QuadStateCheckBox}.
133 */
134 private final class QuadStateDecorator implements ButtonModel {
135 private final ButtonModel other;
136 private String propertyText;
137
138 private QuadStateDecorator(ButtonModel other) {
139 this.other = other;
140 }
141
142 private void setState(State state) {
143 if (state == State.NOT_SELECTED) {
144 other.setArmed(false);
145 other.setPressed(false);
146 other.setSelected(false);
147 setToolTipText(propertyText == null
148 ? tr("false: the property is explicitly switched off")
149 : tr("false: the property ''{0}'' is explicitly switched off", propertyText));
150 } else if (state == State.SELECTED) {
151 other.setArmed(false);
152 other.setPressed(false);
153 other.setSelected(true);
154 setToolTipText(propertyText == null
155 ? tr("true: the property is explicitly switched on")
156 : tr("true: the property ''{0}'' is explicitly switched on", propertyText));
157 } else if (state == State.PARTIAL) {
158 other.setArmed(true);
159 other.setPressed(true);
160 other.setSelected(true);
161 setToolTipText(propertyText == null
162 ? tr("partial: different selected objects have different values, do not change")
163 : tr("partial: different selected objects have different values for ''{0}'', do not change", propertyText));
164 } else {
165 other.setArmed(true);
166 other.setPressed(true);
167 other.setSelected(false);
168 setToolTipText(propertyText == null
169 ? tr("unset: do not set this property on the selected objects")
170 : tr("unset: do not set the property ''{0}'' on the selected objects", propertyText));
171 }
172 }
173
174 private void setPropertyText(String propertyText) {
175 this.propertyText = propertyText;
176 }
177
178 /**
179 * The current state is embedded in the selection / armed
180 * state of the model.
181 *
182 * We return the SELECTED state when the checkbox is selected
183 * but not armed, PARTIAL state when the checkbox is
184 * selected and armed (grey) and NOT_SELECTED when the
185 * checkbox is deselected.
186 * @return current state
187 */
188 private State getState() {
189 if (isSelected() && !isArmed()) {
190 // normal black tick
191 return State.SELECTED;
192 } else if (isSelected() && isArmed()) {
193 // don't care grey tick
194 return State.PARTIAL;
195 } else if (!isSelected() && !isArmed()) {
196 return State.NOT_SELECTED;
197 } else {
198 return State.UNSET;
199 }
200 }
201
202 /** Rotate to the next allowed state.*/
203 private void nextState() {
204 State current = getState();
205 for (int i = 0; i < allowed.length; i++) {
206 if (allowed[i] == current) {
207 setState((i == allowed.length-1) ? allowed[0] : allowed[i+1]);
208 break;
209 }
210 }
211 }
212
213 // ----------------------------------------------------------------------
214 // Filter: No one may change the armed/selected/pressed status except us.
215 // ----------------------------------------------------------------------
216
217 @Override
218 public void setArmed(boolean b) {
219 // Do nothing
220 }
221
222 @Override
223 public void setSelected(boolean b) {
224 // Do nothing
225 }
226
227 @Override
228 public void setPressed(boolean b) {
229 // Do nothing
230 }
231
232 /** We disable focusing on the component when it is not enabled. */
233 @Override
234 public void setEnabled(boolean b) {
235 setFocusable(b);
236 if (other != null) {
237 other.setEnabled(b);
238 }
239 }
240
241 // -------------------------------------------------------------------------------
242 // All these methods simply delegate to the "other" model that is being decorated.
243 // -------------------------------------------------------------------------------
244
245 @Override
246 public boolean isArmed() {
247 return other.isArmed();
248 }
249
250 @Override
251 public boolean isSelected() {
252 return other.isSelected();
253 }
254
255 @Override
256 public boolean isEnabled() {
257 return other.isEnabled();
258 }
259
260 @Override
261 public boolean isPressed() {
262 return other.isPressed();
263 }
264
265 @Override
266 public boolean isRollover() {
267 return other.isRollover();
268 }
269
270 @Override
271 public void setRollover(boolean b) {
272 other.setRollover(b);
273 }
274
275 @Override
276 public void setMnemonic(int key) {
277 other.setMnemonic(key);
278 }
279
280 @Override
281 public int getMnemonic() {
282 return other.getMnemonic();
283 }
284
285 @Override
286 public void setActionCommand(String s) {
287 other.setActionCommand(s);
288 }
289
290 @Override public String getActionCommand() {
291 return other.getActionCommand();
292 }
293
294 @Override public void setGroup(ButtonGroup group) {
295 other.setGroup(group);
296 }
297
298 @Override public void addActionListener(ActionListener l) {
299 other.addActionListener(l);
300 }
301
302 @Override public void removeActionListener(ActionListener l) {
303 other.removeActionListener(l);
304 }
305
306 @Override public void addItemListener(ItemListener l) {
307 other.addItemListener(l);
308 }
309
310 @Override public void removeItemListener(ItemListener l) {
311 other.removeItemListener(l);
312 }
313
314 @Override public void addChangeListener(ChangeListener l) {
315 other.addChangeListener(l);
316 }
317
318 @Override public void removeChangeListener(ChangeListener l) {
319 other.removeChangeListener(l);
320 }
321
322 @Override public Object[] getSelectedObjects() {
323 return other.getSelectedObjects();
324 }
325 }
326}
Note: See TracBrowser for help on using the repository browser.