View Javadoc

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