View Javadoc

1   package org.catacomb.druid.swing;
2   
3   import javax.swing.*;
4   import javax.swing.event.ChangeListener;
5   import javax.swing.plaf.ActionMapUIResource;
6   import java.awt.event.*;
7   
8   
9   /**
10   * Maintenance tip - There were some tricks to getting this code working:
11   *
12   * 1. You have to overwite addMouseListener() to do nothing 2. You have to add a
13   * mouse event on mousePressed by calling super.addMouseListener() 3. You have
14   * to replace the UIActionMap for the keyboard event "pressed" with your own
15   * one. 4. You have to remove the UIActionMap for the keyboard event "released".
16   * 5. You have to grab focus when the next state is entered, otherwise clicking
17   * on the component won't get the focus. 6. You have to make a TristateDecorator
18   * as a button model that wraps the original button model and does state
19   * management.
20   */
21  
22  public class TristateCheckBox extends JCheckBox {
23      private static final long serialVersionUID = 1L;
24  
25      /** This is a type-safe enumerated type */
26      public static class State {
27  
28          State() {
29          }
30      }
31  
32      public static final State NOT_SELECTED = new State();
33      public static final State SELECTED = new State();
34      public static final State DONT_CARE = new State();
35  
36  
37  
38      final TristateDecorator model;
39  
40  
41      public TristateCheckBox(String text, Icon icon, State initial) {
42          super(text, icon);
43          // Add a listener for when the mouse is pressed
44          super.addMouseListener(new MouseAdapter() {
45  
46              public void mousePressed(MouseEvent e) {
47                  grabFocus();
48                  model.nextState();
49              }
50          });
51          // Reset the keyboard action map
52          ActionMap map = new ActionMapUIResource();
53          map.put("pressed", new AbstractAction() {
54              private static final long serialVersionUID = 1L;
55  
56              public void actionPerformed(ActionEvent e) {
57                  grabFocus();
58                  model.nextState();
59              }
60          });
61          map.put("released", null);
62          SwingUtilities.replaceUIActionMap(this, map);
63          // set the model to the adapted model
64          model = new TristateDecorator(getModel());
65          setModel(model);
66          setState(initial);
67      }
68  
69  
70      public TristateCheckBox(String text, State initial) {
71          this(text, null, initial);
72      }
73  
74  
75      public TristateCheckBox(String text) {
76          this(text, DONT_CARE);
77      }
78  
79  
80      public TristateCheckBox() {
81          this(null);
82      }
83  
84  
85      /** No one may add mouse listeners, not even Swing! */
86      public void addMouseListener(MouseListener l) {
87      }
88  
89  
90      /**
91       * Set the new state to either SELECTED, NOT_SELECTED or DONT_CARE. If state ==
92       * null, it is treated as DONT_CARE.
93       */
94  
95  
96  
97      public void setBooleanState(Boolean b) {
98          if (b == null) {
99              setState(DONT_CARE);
100         } else if (b.booleanValue()) {
101             setState(SELECTED);
102         } else {
103             setState(NOT_SELECTED);
104         }
105     }
106 
107     public void setState(State state) {
108         model.setState(state);
109     }
110 
111 
112     /**
113      * Return the current state, which is determined by the selection status of
114      * the model.
115      */
116     public State getState() {
117         return model.getState();
118     }
119 
120 
121     public void setSelected(boolean b) {
122         if (b) {
123             setState(SELECTED);
124         } else {
125             setState(NOT_SELECTED);
126         }
127     }
128 
129     /**
130      * Exactly which Design Pattern is this? Is it an Adapter, a Proxy or a
131      * Decorator? In this case, my vote lies with the Decorator, because we are
132      * extending functionality and "decorating" the original model with a more
133      * powerful model.
134      */
135     private class TristateDecorator implements ButtonModel {
136 
137         private final ButtonModel other;
138 
139 
140         TristateDecorator(ButtonModel other) {
141             this.other = other;
142         }
143 
144 
145         void setState(State state) {
146             if (state == NOT_SELECTED) {
147                 other.setArmed(false);
148                 setPressed(false);
149                 setSelected(false);
150             } else if (state == SELECTED) {
151                 other.setArmed(false);
152                 setPressed(false);
153                 setSelected(true);
154             } else { // either "null" or DONT_CARE
155                 other.setArmed(true);
156                 setPressed(true);
157                 setSelected(true);
158             }
159         }
160 
161 
162         /**
163          * The current state is embedded in the selection / armed state of the
164          * model.
165          *
166          * We return the SELECTED state when the checkbox is selected but not
167          * armed, DONT_CARE state when the checkbox is selected and armed (grey)
168          * and NOT_SELECTED when the checkbox is deselected.
169          */
170         State getState() {
171             if (isSelected() && !isArmed()) {
172                 // normal black tick
173                 return SELECTED;
174             } else if (isSelected() && isArmed()) {
175                 // don't care grey tick
176                 return DONT_CARE;
177             } else {
178                 // normal deselected
179                 return NOT_SELECTED;
180             }
181         }
182 
183 
184         /** We rotate between NOT_SELECTED, SELECTED and DONT_CARE. */
185         void nextState() {
186             State current = getState();
187             if (current == NOT_SELECTED) {
188                 setState(SELECTED);
189             } else if (current == SELECTED) {
190                 setState(DONT_CARE);
191             } else if (current == DONT_CARE) {
192                 setState(NOT_SELECTED);
193             }
194         }
195 
196 
197         /** Filter: No one may change the armed status except us. */
198         public void setArmed(boolean b) {
199         }
200 
201 
202         /**
203          * We disable focusing on the component when it is not enabled.
204          */
205         public void setEnabled(boolean b) {
206             setFocusable(b);
207             other.setEnabled(b);
208         }
209 
210 
211         /**
212          * All these methods simply delegate to the "other" model that is being
213          * decorated.
214          */
215         public boolean isArmed() {
216             return other.isArmed();
217         }
218 
219 
220         public boolean isSelected() {
221             return other.isSelected();
222         }
223 
224 
225         public boolean isEnabled() {
226             return other.isEnabled();
227         }
228 
229 
230         public boolean isPressed() {
231             return other.isPressed();
232         }
233 
234 
235         public boolean isRollover() {
236             return other.isRollover();
237         }
238 
239 
240         public void setSelected(boolean b) {
241             other.setSelected(b);
242         }
243 
244 
245         public void setPressed(boolean b) {
246             other.setPressed(b);
247         }
248 
249 
250         public void setRollover(boolean b) {
251             other.setRollover(b);
252         }
253 
254 
255         public void setMnemonic(int key) {
256             other.setMnemonic(key);
257         }
258 
259 
260         public int getMnemonic() {
261             return other.getMnemonic();
262         }
263 
264 
265         public void setActionCommand(String s) {
266             other.setActionCommand(s);
267         }
268 
269 
270         public String getActionCommand() {
271             return other.getActionCommand();
272         }
273 
274 
275         public void setGroup(ButtonGroup group) {
276             other.setGroup(group);
277         }
278 
279 
280         public void addActionListener(ActionListener l) {
281             other.addActionListener(l);
282         }
283 
284 
285         public void removeActionListener(ActionListener l) {
286             other.removeActionListener(l);
287         }
288 
289 
290         public void addItemListener(ItemListener l) {
291             other.addItemListener(l);
292         }
293 
294 
295         public void removeItemListener(ItemListener l) {
296             other.removeItemListener(l);
297         }
298 
299 
300         public void addChangeListener(ChangeListener l) {
301             other.addChangeListener(l);
302         }
303 
304 
305         public void removeChangeListener(ChangeListener l) {
306             other.removeChangeListener(l);
307         }
308 
309 
310         public Object[] getSelectedObjects() {
311             return other.getSelectedObjects();
312         }
313     }
314 }