KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > de > schlichtherle > swing > AbstractComboBoxBrowser


1 /*
2  * AbstractComboBoxBrowser.java
3  *
4  * Created on 31. Juli 2006, 10:32
5  */

6 /*
7  * Copyright 2006 Schlichtherle IT Services
8  *
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  * http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  */

21
22 package de.schlichtherle.swing;
23
24 import java.awt.Component JavaDoc;
25 import java.awt.event.ActionListener JavaDoc;
26 import java.beans.PropertyChangeEvent JavaDoc;
27 import java.beans.PropertyChangeListener JavaDoc;
28 import java.io.Serializable JavaDoc;
29 import java.util.logging.Level JavaDoc;
30 import java.util.logging.Logger JavaDoc;
31
32 import javax.swing.ComboBoxEditor JavaDoc;
33 import javax.swing.ComboBoxModel JavaDoc;
34 import javax.swing.JComboBox JavaDoc;
35 import javax.swing.MutableComboBoxModel JavaDoc;
36 import javax.swing.event.DocumentEvent JavaDoc;
37 import javax.swing.event.DocumentListener JavaDoc;
38 import javax.swing.plaf.basic.BasicComboBoxEditor JavaDoc;
39 import javax.swing.text.BadLocationException JavaDoc;
40 import javax.swing.text.Caret JavaDoc;
41 import javax.swing.text.Document JavaDoc;
42 import javax.swing.text.JTextComponent JavaDoc;
43
44 /**
45  * An observer for a {@link JComboBox} which provides auto completion
46  * for the editable text in the drop down list in order to provide quick
47  * browsing capabilities for the user.
48  * Subclasses need to implement the {@link #update} method in order to update
49  * the combo box model with the actual auto completion data.
50  * <p>
51  * This class is designed to be minimal intrusive: It works with any subclass
52  * of <code>JComboBox</code> and doesn't require a special
53  * {@link ComboBoxModel}, although its specific behaviour will only show
54  * if the <code>JComboBox</code> is <code>editable</code> and uses an
55  * instance of a {@link MutableComboBoxModel} (which, apart from the
56  * <code>editable</code> property being set to <code>true</code>, is the
57  * default for a plain <code>JComboBox</code>).
58  *
59  * @author Christian Schlichtherle
60  * @since TrueZIP 6.2
61  * @version @version@
62  */

63 public abstract class AbstractComboBoxBrowser implements Serializable JavaDoc {
64
65     private final Listener JavaDoc listener = new Listener JavaDoc();
66
67     private JComboBox JavaDoc comboBox;
68
69     /**
70      * Used to inhibit mutual recursive event firing.
71      */

72     private transient boolean recursion;
73
74     /**
75      * Creates a new combo box auto completion browser.
76      * {@link #setComboBox} must be called in order to use this object.
77      */

78     public AbstractComboBoxBrowser() {
79     }
80
81     /**
82      * Creates a new combo box auto completion browser.
83      * Note that this constructor does <em>not</em> call {@link #update}
84      * and hence the drop down list of the combo box is left unchanged.
85      *
86      * @param comboBox The combo box to enable browsing for auto completions.
87      * May be <code>null</code>.
88      */

89     public AbstractComboBoxBrowser(final JComboBox JavaDoc comboBox) {
90         changeComboBox(null, comboBox, false);
91     }
92
93     /**
94      * Returns the combo box which this object is auto completing.
95      * The default is <code>null</code>.
96      */

97     public JComboBox JavaDoc getComboBox() {
98         return comboBox;
99     }
100
101     /**
102      * Sets the combo box which this object is auto completing and updates
103      * the drop down list with the auto completion for the currently selected
104      * item.
105      *
106      * @param comboBox The combo box to enable browsing for auto completions.
107      * May be <code>null</code>.
108      */

109     public void setComboBox(final JComboBox JavaDoc comboBox) {
110         changeComboBox(getComboBox(), comboBox, true);
111     }
112
113     private void changeComboBox(
114             final JComboBox JavaDoc oldCB,
115             final JComboBox JavaDoc newCB,
116             final boolean update) {
117         if (newCB == oldCB)
118             return;
119
120         ComboBoxEditor JavaDoc oldCBE = null;
121         if (oldCB != null) {
122             oldCB.removePropertyChangeListener("editor", listener);
123             oldCBE = oldCB.getEditor();
124             oldCB.setEditor(((ComboBoxEditorProxy) oldCBE).getEditor());
125         }
126
127         this.comboBox = newCB;
128         
129         ComboBoxEditor JavaDoc newCBE = null;
130         if (newCB != null) {
131             newCB.updateUI(); // ensure comboBoxEditor is initialized
132
newCBE = new ComboBoxEditorProxy(newCB.getEditor());
133             newCB.setEditor(newCBE);
134             newCB.addPropertyChangeListener("editor", listener);
135         }
136
137         changeEditor(oldCBE, newCBE, update);
138     }
139
140     private void changeEditor(
141             final ComboBoxEditor JavaDoc oldCBE,
142             final ComboBoxEditor JavaDoc newCBE,
143             final boolean update) {
144         if (newCBE == oldCBE)
145             return;
146
147         JTextComponent JavaDoc oldText = null;
148         if (oldCBE != null) {
149             final Component JavaDoc component = oldCBE.getEditorComponent();
150             if (component instanceof JTextComponent JavaDoc)
151                 oldText = (JTextComponent JavaDoc) component;
152         }
153
154         JTextComponent JavaDoc newText = null;
155         if (newCBE != null) {
156             final Component JavaDoc component = newCBE.getEditorComponent();
157             if (component instanceof JTextComponent JavaDoc)
158                 newText = (JTextComponent JavaDoc) component;
159         }
160
161         changeText(oldText, newText, update);
162     }
163
164     private void changeText(
165             final JTextComponent JavaDoc oldTC,
166             final JTextComponent JavaDoc newTC,
167             final boolean update) {
168         if (newTC == oldTC)
169             return;
170
171         Document JavaDoc oldDocument = null;
172         if (oldTC != null) {
173             oldTC.removePropertyChangeListener("document", listener);
174             oldDocument = oldTC.getDocument();
175         }
176
177         Document JavaDoc newDocument = null;
178         if (newTC != null) {
179             newDocument = newTC.getDocument();
180             newTC.addPropertyChangeListener("document", listener);
181         }
182
183         changeDocument(oldDocument, newDocument, update);
184     }
185
186     private void changeDocument(
187             final Document JavaDoc oldDoc,
188             final Document JavaDoc newDoc,
189             final boolean update) {
190         if (newDoc == oldDoc)
191             return;
192
193         if (oldDoc != null)
194             oldDoc.removeDocumentListener(listener);
195
196         if (newDoc != null) {
197             if (update) {
198                 String JavaDoc txt;
199                 try {
200                     txt = newDoc.getText(0, newDoc.getLength());
201                 } catch (BadLocationException JavaDoc e) {
202                     txt = null;
203                 }
204                 update(txt);
205             }
206             newDoc.addDocumentListener(listener);
207         }
208     }
209
210     private void documentUpdated() {
211         if (lock())
212             return;
213         try {
214             final JComboBox JavaDoc cb = getComboBox();
215             final ComboBoxEditor JavaDoc cbe = cb.getEditor();
216             final JTextComponent JavaDoc tc = (JTextComponent JavaDoc) cbe.getEditorComponent();
217             assert cb.isShowing() || !tc.isFocusOwner();
218             if (!tc.isFocusOwner() /*|| !cb.isShowing()*/)
219                 return;
220
221             //cb.setPopupVisible(update(tc.getText())); // doesn't work: adjusts popup size!
222
cb.setPopupVisible(false);
223             if (update(tc.getText()))
224                 cb.setPopupVisible(true);
225         } finally {
226             unlock();
227         }
228     }
229
230     private void updateEditor(final ComboBoxEditor JavaDoc cbe, final Object JavaDoc item) {
231         if (lock())
232             return;
233         try {
234             cbe.setItem(item);
235             if (!(item instanceof String JavaDoc))
236                 return;
237
238             final JComboBox JavaDoc cb = getComboBox();
239             final JTextComponent JavaDoc tc = (JTextComponent JavaDoc) cbe.getEditorComponent();
240             assert cb.isShowing() || !tc.isFocusOwner();
241             if (!tc.isFocusOwner() /*|| !cb.isShowing()*/)
242                 return;
243
244             // Compensate for an issue with some look and feels
245
// which select the entire tc if an item is changed.
246
// This is inconvenient for auto completion because the
247
// next typed character would replace the entire tc...
248
final Caret JavaDoc caret = tc.getCaret();
249             caret.setDot(((String JavaDoc) item).length());
250         } finally {
251             unlock();
252         }
253     }
254
255     /**
256      * Subclasses are expected to update the auto completion elements in the
257      * model of this combo box based on the specified <code>initials</code>.
258      * They should not do any other work within this method.
259      * In particular, they should not update the visual appearance of this
260      * component.
261      * <p>
262      * {@link #getComboBox} is guaranteed to return non-<code>null</code> if
263      * this method is called from this abstract base class.
264      *
265      * @param initials The text to auto complete. May be <code>null</code>.
266      * @return Whether or not the combo box should pop up to show the updated
267      * contents of its model.
268      */

269     protected abstract boolean update(String JavaDoc initials);
270
271     /**
272      * Locks out mutual recursive event notification.
273      * <b>Warning:</b> This method works in a synchronized or single threaded
274      * environment only!
275      *
276      * @return Whether or not updating the combo box model was already locked.
277      */

278     private final boolean lock() {
279         if (recursion)
280             return true;
281         recursion = true;
282         return false;
283     }
284
285     /**
286      * Unlocks mutual recursive event notification.
287      * <b>Warning:</b> This method works in a synchronized or single threaded
288      * environment only!
289      */

290     private final void unlock() {
291         recursion = false;
292     }
293
294     private final class Listener
295             implements DocumentListener JavaDoc, PropertyChangeListener JavaDoc {
296         public void insertUpdate(DocumentEvent JavaDoc e) {
297             documentUpdated();
298         }
299
300         public void removeUpdate(DocumentEvent JavaDoc e) {
301             documentUpdated();
302         }
303
304         public void changedUpdate(DocumentEvent JavaDoc e) {
305             documentUpdated();
306         }
307
308         public void propertyChange(final PropertyChangeEvent JavaDoc e) {
309             final String JavaDoc property = e.getPropertyName();
310             if ("editor".equals(property))
311                 changeEditor( (ComboBoxEditor JavaDoc) e.getOldValue(),
312                                 (ComboBoxEditor JavaDoc) e.getNewValue(),
313                                 true);
314             else if ("document".equals(property))
315                 changeDocument( (Document JavaDoc) e.getOldValue(),
316                                 (Document JavaDoc) e.getNewValue(),
317                                 true);
318             else
319                 throw new AssertionError JavaDoc(
320                         "Received change event for unknown property: "
321                         + property);
322         }
323     }
324
325     /**
326      * This proxy controls access to the real <code>ComboBoxEditor</code>
327      * installed by the client application or the pluggable look and feel.
328      * It is used to lock out mutual recursion caused by modifications to
329      * the list model in the <code>JComboBox</code>.
330      * <p>
331      * Note that there is a slight chance that the introduction of this proxy
332      * breaks the look and feel if it does <code>instanceof</code> tests for
333      * a particular class, but I'm not aware of any look and feel which is
334      * actually affected.
335      * In order to reduce this risk, this class is extended from
336      * {@link BasicComboBoxEditor}, although it overrides all methods which
337      * are defined in the {@link ComboBoxEditor} interface.
338      */

339     private final class ComboBoxEditorProxy
340             extends BasicComboBoxEditor JavaDoc
341             implements ComboBoxEditor JavaDoc {
342         private final ComboBoxEditor JavaDoc comboBoxEditor;
343
344         public ComboBoxEditorProxy(ComboBoxEditor JavaDoc comboBoxEditor) {
345             this.comboBoxEditor = comboBoxEditor;
346         }
347
348         public ComboBoxEditor JavaDoc getEditor() {
349             return comboBoxEditor;
350         }
351         
352         public Component JavaDoc getEditorComponent() {
353             return comboBoxEditor.getEditorComponent();
354         }
355
356         public void setItem(final Object JavaDoc item) {
357             updateEditor(comboBoxEditor, item);
358         }
359
360         public Object JavaDoc getItem() {
361             return comboBoxEditor.getItem();
362         }
363
364         public void selectAll() {
365             comboBoxEditor.selectAll();
366         }
367
368         public void addActionListener(ActionListener JavaDoc actionListener) {
369             comboBoxEditor.addActionListener(actionListener);
370         }
371
372         public void removeActionListener(ActionListener JavaDoc actionListener) {
373             comboBoxEditor.removeActionListener(actionListener);
374         }
375     }
376 }
377
Popular Tags