View Javadoc

1   /*
2    *  XNap - A P2P framework and client.
3    *
4    *  See the file AUTHORS for copyright information.
5    *
6    *  This program is free software; you can redistribute it and/or modify
7    *  it under the terms of the GNU General Public License as published by
8    *  the Free Software Foundation.
9    *
10   *  This program is distributed in the hope that it will be useful,
11   *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12   *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   *  GNU General Public License for more details.
14   *
15   *  You should have received a copy of the GNU General Public License
16   *  along with this program; if not, write to the Free Software
17   *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18   */
19  package org.xnap.gui.component;
20  
21  import java.awt.Dimension;
22  import java.awt.event.ActionEvent;
23  import java.awt.event.FocusAdapter;
24  import java.awt.event.FocusEvent;
25  import java.util.Vector;
26  import javax.help.CSH;
27  import javax.swing.AbstractAction;
28  import javax.swing.Action;
29  import javax.swing.ComboBoxModel;
30  import javax.swing.JComboBox;
31  import javax.swing.JTextField;
32  import javax.swing.event.ListDataEvent;
33  import javax.swing.text.JTextComponent;
34  import org.apache.log4j.Logger;
35  import org.xnap.XNap;
36  import org.xnap.gui.action.ClearHistoriesAction;
37  import org.xnap.gui.action.Clearable;
38  import org.xnap.gui.event.PopupListener;
39  import org.xnap.gui.menu.TextFieldMenu;
40  import org.xnap.gui.util.IconHelper;
41  import org.xnap.util.Preferences;
42  import org.xnap.util.PreferencesProvider;
43  
44  
45  /***
46   * Provides a <code>JComboBox</code> with auto completion and a history.
47   */
48  public class HistoryComboBox extends JComboBox implements Clearable, Completable
49  {
50      //--- Constant(s) ---
51  	
52      //--- Data field(s) ---
53  	
54      private static Logger logger = Logger.getLogger(HistoryComboBox.class);
55      
56      private boolean itemsOnly;
57  	
58      private JTextField jtf;
59      
60      private TextFieldMenu menu;
61      
62  	/***
63  	 * Holds the current completion mode.
64  	 */
65  	private CompletionMode mode = null;
66  
67      //--- Constructor(s) ---
68      /***
69       * Constructs a new HistoryComboBox with a history and specified width.
70       */
71      public HistoryComboBox(Vector history, int columns)
72      {
73          super(history);
74          initialize(columns);
75      }
76  
77      /***
78       * Constructs a new HistoryComboBox that takes it's items from an existing
79       * ComboBoxModel.
80       */
81      public HistoryComboBox(ComboBoxModel model, int columns)
82      {
83          super(model);
84          initialize(columns);
85      }
86  
87  
88      /***
89       * Constructs a new HistoryComboBox with specified width.
90       */
91      public HistoryComboBox(int columns)
92      {
93          initialize(columns);
94      }
95  
96      public Object[] getHistoryItems()
97      {
98          int count = getItemCount();
99          Object[] array = new Object[count];
100 
101         for (int i = 0; (i < array.length) && (i < getItemCount()); i++) {
102             array[i] = getItemAt(i);
103         }
104         return array;
105     }
106 
107     public boolean isItemSelected()
108     {
109         return isItem(jtf.getText());
110     }
111 
112     public void setItemsOnly(boolean newValue)
113     {
114         this.itemsOnly = newValue;
115     }
116 
117     public boolean getItemsOnly()
118     {
119         return itemsOnly;
120     }
121 
122     public Dimension getMaximumSize()
123     {
124         return jtf.getMaximumSize();
125     }
126 
127     public Dimension getMinimumSize()
128     {
129         return jtf.getMinimumSize();
130     }
131 
132     public Dimension getPreferredSize()
133     {
134         return jtf.getPreferredSize();
135     }
136 
137     /***
138      * Returns the selected object if autoComplete is true or editable is
139      * false.
140      * Otherwise you only get a string.
141      * @return the selected object (if autoComplete is true or editable is
142      * false).
143      */
144     public Object getSelectedItem()
145     {
146         ComboBoxModel boxModel = getModel();
147         Object item = boxModel.getSelectedItem();
148 
149         if (item != null) {
150             if (item instanceof String) {
151                 Object o = getItem((String)item);
152 
153                 if (o != null) {
154                     return o;
155                 }
156                 else {
157                     return jtf.getText();
158                 }
159             }
160             else {
161                 return item;
162             }
163         }
164         else {
165             return jtf.getText();
166         }
167     }
168 
169     public void setText(String newValue)
170     {
171         jtf.setText(newValue);
172     }
173 
174     public String getText()
175     {
176         return jtf.getText();
177     }
178 
179     public JTextComponent getTextComponent()
180     {
181         return jtf;
182     }
183 
184 	public JTextField getTextField()
185 	{
186 		return jtf;
187 	}
188 
189 	public void setCompletionModel(CompletionModel model)
190 	{
191 		mode.setModel(model);
192 	}
193 
194 	public CompletionModel getCompletionModel()
195 	{
196 		return mode.getModel();
197 	}
198 
199 	public CompletionMode getCompletionMode()
200 	{
201 		return mode;
202 	}
203 	
204 	public void setPreferences(String prefsKey, PreferencesProvider prefs)
205 	{
206 		menu.setPreferences(prefsKey, prefs);
207 	}
208 	
209 	public void setPreferences(String prefsKey)
210 	{
211 		setPreferences(prefsKey, Preferences.getInstance());
212 	}
213 
214 	/***
215 	 * Overwritten to insert the new elements in the history into the
216 	 * completion model.  
217 	 */
218 	public void intervalAdded(ListDataEvent event)
219 	{
220 		super.intervalAdded(event);
221 		for (int i = event.getIndex0(); i <= event.getIndex1(); i++) {
222 			mode.getModel().insert(getModel().getElementAt(i));
223 		}
224 	}
225 
226 	private void setCompletionMode(CompletionMode newMode)
227 	{
228 		if (mode != null) {
229 			if (mode.isEnabled()) {
230 				mode.setEnabled(false);
231 				newMode.setModel(mode.getModel());
232 				mode = newMode;
233 				mode.setEnabled(true);
234 			}
235 			else {
236 				newMode.setModel(mode.getModel());
237 				mode = newMode;
238 			}
239 		}
240 		else {
241 			mode = newMode;
242 			mode.setEnabled(true);
243 		}
244 	}
245 
246 	public void setCompletionMode(String mode)
247 	{
248 		CompletionMode m = CompletionModeFactory.createCompletionMode(mode,
249 																	  jtf, true);
250 		setCompletionMode(m);
251 	}
252 
253     /***
254      * Adds item if not already in list.
255      */
256     public void addDistinctItemAtTop(Object item)
257     {
258         for (int i = getItemCount() - 1; i >= 0; i--) {
259             if (item.toString().equals(getItemAt(i).toString())) {
260                 removeItemAt(i);
261             }
262         }
263         insertItemAt(item, 0);
264         setSelectedIndex(0);
265     }
266 
267     /***
268      * Implements {@link Clearable} interface.
269      */
270     public void clear()
271     {
272         removeAllItems();
273     }
274 
275     /***
276      * Returns the first object which corresponds to name.
277      * @param name The name of the object to return.
278      * @return the object whose name is name.
279      */
280     private Object getItem(String name)
281     {
282         ComboBoxModel boxModel = getModel();
283 
284         for (int i = 0; i < boxModel.getSize(); i++) {
285             // shouldn't it be toString().equals(name) ??
286             if (boxModel.getElementAt(i).equals(name)) {
287                 return boxModel.getElementAt(i);
288             }
289         }
290 
291         return null;
292     }
293 
294     private boolean isItem(String name)
295     {
296         return getItem(name) != null;
297     }
298 
299     //--- Method(s) ---
300 
301     private void initialize(int columns)
302     {
303         jtf = (JTextField)getEditor().getEditorComponent();
304 
305         jtf.setColumns(columns);
306         jtf.addFocusListener(new FocusHandler());
307         setEditable(true);
308 
309         // register standard help text for component
310         CSH.setHelpIDString(this, "historycombobox");
311 
312         // popup menu
313         menu = new TextFieldMenu(this, null);
314 		menu.addSeparator();
315         menu.add(new XNapMenuItem(new ClearHistoryAction()));
316         jtf.addMouseListener(new PopupListener(menu));
317 
318         // clear all histories action
319         ClearHistoriesAction.add(this);
320 
321         for (int i = 0; i < getModel().getSize(); i++) {
322             mode.getModel().insert(getModel().getElementAt(i));
323         }
324     }
325 
326     private boolean selectFirstMatchingItem(String string)
327     {
328         ComboBoxModel boxModel = getModel();
329         int matchingItemIndex = -1;
330         string = string.toLowerCase();
331 
332         for (int i = 0; i < boxModel.getSize(); i++) {
333             if (boxModel.getElementAt(i).toString().toLowerCase().
334 				startsWith(string)) {
335                 matchingItemIndex = i;
336                 break;
337             }
338         }
339 
340         //logger.debug(string + ":" + 1);
341         if (matchingItemIndex != -1) {
342             /* Each selection is being cached, therefore the string in the
343             editor won't be updated when selecting the same item twice.  */
344             setSelectedItem(null);
345             setSelectedIndex(matchingItemIndex);
346 
347             return true;
348         }
349         else {
350             return false;
351         }
352     }
353 
354     /***
355      * Make sure a valid item is selected when focus is lost.
356      */
357     public class FocusHandler extends FocusAdapter
358     {
359         public void focusGained(FocusEvent e)
360         {
361             //  	    jtf.setCaretPosition(jtf.getText().length());
362             //  	    jtf.moveCaretPosition(0);
363         }
364 
365         public void focusLost(FocusEvent e)
366         {
367             int caretPos = jtf.getCaretPosition();
368 
369             if (getItemsOnly() && !isItemSelected()) {
370                 HistoryComboBox.this.requestFocus();
371                 selectFirstMatchingItem(jtf.getText());
372                 jtf.setCaretPosition(jtf.getText().length());
373                 jtf.moveCaretPosition(caretPos);
374             }
375         }
376     }
377 
378     private class ClearHistoryAction extends AbstractAction
379     {
380         public ClearHistoryAction()
381         {
382             putValue(Action.NAME, XNap.tr("Clear History"));
383             putValue(IconHelper.XNAP_ICON, "eraser.png");
384         }
385 
386         public void actionPerformed(ActionEvent event)
387         {
388             clear();
389         }
390     }
391 }