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

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

fix #15294 - Java 9: CCE: QuadStateCheckBox$QuadStateDecorator cannot be cast to JToggleButton$ToggleButtonModel

  • Property svn:eol-style set to native
File size: 10.5 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 * It previously only implemented (and still could) the {@code ButtonModel} interface.
134 * But because of JDK-8182577 (Java 9 regression) it now extends {@code ToggleButtonModel} as a workaround.
135 * The previous implementation can be restored after Java 10 migration.
136 * See also https://bugs.openjdk.java.net/browse/JDK-8182695
137 */
138 private final class QuadStateDecorator extends ToggleButtonModel {
139 private final ButtonModel other;
140 private String propertyText;
141
142 private QuadStateDecorator(ButtonModel other) {
143 this.other = other;
144 }
145
146 private void setState(State state) {
147 if (state == State.NOT_SELECTED) {
148 other.setArmed(false);
149 other.setPressed(false);
150 other.setSelected(false);
151 setToolTipText(propertyText == null
152 ? tr("false: the property is explicitly switched off")
153 : tr("false: the property ''{0}'' is explicitly switched off", propertyText));
154 } else if (state == State.SELECTED) {
155 other.setArmed(false);
156 other.setPressed(false);
157 other.setSelected(true);
158 setToolTipText(propertyText == null
159 ? tr("true: the property is explicitly switched on")
160 : tr("true: the property ''{0}'' is explicitly switched on", propertyText));
161 } else if (state == State.PARTIAL) {
162 other.setArmed(true);
163 other.setPressed(true);
164 other.setSelected(true);
165 setToolTipText(propertyText == null
166 ? tr("partial: different selected objects have different values, do not change")
167 : tr("partial: different selected objects have different values for ''{0}'', do not change", propertyText));
168 } else {
169 other.setArmed(true);
170 other.setPressed(true);
171 other.setSelected(false);
172 setToolTipText(propertyText == null
173 ? tr("unset: do not set this property on the selected objects")
174 : tr("unset: do not set the property ''{0}'' on the selected objects", propertyText));
175 }
176 }
177
178 private void setPropertyText(String propertyText) {
179 this.propertyText = propertyText;
180 }
181
182 /**
183 * The current state is embedded in the selection / armed
184 * state of the model.
185 *
186 * We return the SELECTED state when the checkbox is selected
187 * but not armed, PARTIAL state when the checkbox is
188 * selected and armed (grey) and NOT_SELECTED when the
189 * checkbox is deselected.
190 * @return current state
191 */
192 private State getState() {
193 if (isSelected() && !isArmed()) {
194 // normal black tick
195 return State.SELECTED;
196 } else if (isSelected() && isArmed()) {
197 // don't care grey tick
198 return State.PARTIAL;
199 } else if (!isSelected() && !isArmed()) {
200 return State.NOT_SELECTED;
201 } else {
202 return State.UNSET;
203 }
204 }
205
206 /** Rotate to the next allowed state.*/
207 private void nextState() {
208 State current = getState();
209 for (int i = 0; i < allowed.length; i++) {
210 if (allowed[i] == current) {
211 setState((i == allowed.length-1) ? allowed[0] : allowed[i+1]);
212 break;
213 }
214 }
215 }
216
217 // ----------------------------------------------------------------------
218 // Filter: No one may change the armed/selected/pressed status except us.
219 // ----------------------------------------------------------------------
220
221 @Override
222 public void setArmed(boolean b) {
223 // Do nothing
224 }
225
226 @Override
227 public void setSelected(boolean b) {
228 // Do nothing
229 }
230
231 @Override
232 public void setPressed(boolean b) {
233 // Do nothing
234 }
235
236 /** We disable focusing on the component when it is not enabled. */
237 @Override
238 public void setEnabled(boolean b) {
239 setFocusable(b);
240 other.setEnabled(b);
241 }
242
243 // -------------------------------------------------------------------------------
244 // All these methods simply delegate to the "other" model that is being decorated.
245 // -------------------------------------------------------------------------------
246
247 @Override
248 public boolean isArmed() {
249 return other.isArmed();
250 }
251
252 @Override
253 public boolean isSelected() {
254 return other.isSelected();
255 }
256
257 @Override
258 public boolean isEnabled() {
259 return other.isEnabled();
260 }
261
262 @Override
263 public boolean isPressed() {
264 return other.isPressed();
265 }
266
267 @Override
268 public boolean isRollover() {
269 return other.isRollover();
270 }
271
272 @Override
273 public void setRollover(boolean b) {
274 other.setRollover(b);
275 }
276
277 @Override
278 public void setMnemonic(int key) {
279 other.setMnemonic(key);
280 }
281
282 @Override
283 public int getMnemonic() {
284 return other.getMnemonic();
285 }
286
287 @Override
288 public void setActionCommand(String s) {
289 other.setActionCommand(s);
290 }
291
292 @Override public String getActionCommand() {
293 return other.getActionCommand();
294 }
295
296 @Override public void setGroup(ButtonGroup group) {
297 other.setGroup(group);
298 }
299
300 @Override public void addActionListener(ActionListener l) {
301 other.addActionListener(l);
302 }
303
304 @Override public void removeActionListener(ActionListener l) {
305 other.removeActionListener(l);
306 }
307
308 @Override public void addItemListener(ItemListener l) {
309 other.addItemListener(l);
310 }
311
312 @Override public void removeItemListener(ItemListener l) {
313 other.removeItemListener(l);
314 }
315
316 @Override public void addChangeListener(ChangeListener l) {
317 other.addChangeListener(l);
318 }
319
320 @Override public void removeChangeListener(ChangeListener l) {
321 other.removeChangeListener(l);
322 }
323
324 @Override public Object[] getSelectedObjects() {
325 return other.getSelectedObjects();
326 }
327 }
328}
Note: See TracBrowser for help on using the repository browser.