source: josm/trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingComboBox.java@ 8840

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

sonar - squid:S3052 - Fields should not be initialized to default values

  • Property svn:eol-style set to native
File size: 14.3 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.tagging.ac;
3
4import java.awt.Component;
5import java.awt.Toolkit;
6import java.awt.datatransfer.Clipboard;
7import java.awt.datatransfer.Transferable;
8import java.awt.event.FocusEvent;
9import java.awt.event.FocusListener;
10import java.awt.im.InputContext;
11import java.util.Collection;
12import java.util.Locale;
13
14import javax.swing.ComboBoxEditor;
15import javax.swing.ComboBoxModel;
16import javax.swing.DefaultComboBoxModel;
17import javax.swing.JLabel;
18import javax.swing.JList;
19import javax.swing.ListCellRenderer;
20import javax.swing.text.AttributeSet;
21import javax.swing.text.BadLocationException;
22import javax.swing.text.JTextComponent;
23import javax.swing.text.PlainDocument;
24import javax.swing.text.StyleConstants;
25
26import org.openstreetmap.josm.Main;
27import org.openstreetmap.josm.gui.widgets.JosmComboBox;
28import org.openstreetmap.josm.tools.Utils;
29
30/**
31 * Auto-completing ComboBox.
32 * @author guilhem.bonnefille@gmail.com
33 * @since 272
34 */
35public class AutoCompletingComboBox extends JosmComboBox<AutoCompletionListItem> {
36
37 private boolean autocompleteEnabled = true;
38
39 private int maxTextLength = -1;
40 private boolean useFixedLocale;
41
42 /**
43 * Auto-complete a JosmComboBox.
44 * <br>
45 * Inspired by <a href="http://www.orbital-computer.de/JComboBox">Thomas Bierhance example</a>.
46 */
47 class AutoCompletingComboBoxDocument extends PlainDocument {
48 private final JosmComboBox<AutoCompletionListItem> comboBox;
49 private boolean selecting;
50
51 /**
52 * Constructs a new {@code AutoCompletingComboBoxDocument}.
53 * @param comboBox the combobox
54 */
55 AutoCompletingComboBoxDocument(final JosmComboBox<AutoCompletionListItem> comboBox) {
56 this.comboBox = comboBox;
57 }
58
59 @Override
60 public void remove(int offs, int len) throws BadLocationException {
61 if (selecting)
62 return;
63 super.remove(offs, len);
64 }
65
66 @Override
67 public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
68 // TODO get rid of code duplication w.r.t. AutoCompletingTextField.AutoCompletionDocument.insertString
69
70 if (selecting || (offs == 0 && str.equals(getText(0, getLength()))))
71 return;
72 if (maxTextLength > -1 && str.length()+getLength() > maxTextLength)
73 return;
74 boolean initial = offs == 0 && getLength() == 0 && str.length() > 1;
75 super.insertString(offs, str, a);
76
77 // return immediately when selecting an item
78 // Note: this is done after calling super method because we need
79 // ActionListener informed
80 if (selecting)
81 return;
82 if (!autocompleteEnabled)
83 return;
84 // input method for non-latin characters (e.g. scim)
85 if (a != null && a.isDefined(StyleConstants.ComposedTextAttribute))
86 return;
87
88 // if the current offset isn't at the end of the document we don't autocomplete.
89 // If a highlighted autocompleted suffix was present and we get here Swing has
90 // already removed it from the document. getLength() therefore doesn't include the autocompleted suffix.
91 if (offs + str.length() < getLength()) {
92 return;
93 }
94
95 int size = getLength();
96 int start = offs+str.length();
97 int end = start;
98 String curText = getText(0, size);
99
100 // item for lookup and selection
101 Object item = null;
102 // if the text is a number we don't autocomplete
103 if (Main.pref.getBoolean("autocomplete.dont_complete_numbers", true)) {
104 try {
105 Long.parseLong(str);
106 if (!curText.isEmpty())
107 Long.parseLong(curText);
108 item = lookupItem(curText, true);
109 } catch (NumberFormatException e) {
110 // either the new text or the current text isn't a number. We continue with autocompletion
111 item = lookupItem(curText, false);
112 }
113 } else {
114 item = lookupItem(curText, false);
115 }
116
117 setSelectedItem(item);
118 if (initial) {
119 start = 0;
120 }
121 if (item != null) {
122 String newText = ((AutoCompletionListItem) item).getValue();
123 if (!newText.equals(curText)) {
124 selecting = true;
125 super.remove(0, size);
126 super.insertString(0, newText, a);
127 selecting = false;
128 start = size;
129 end = getLength();
130 }
131 }
132 JTextComponent editorComponent = (JTextComponent) comboBox.getEditor().getEditorComponent();
133 // save unix system selection (middle mouse paste)
134 Clipboard sysSel = Toolkit.getDefaultToolkit().getSystemSelection();
135 if (sysSel != null) {
136 Transferable old = Utils.getTransferableContent(sysSel);
137 editorComponent.select(start, end);
138 sysSel.setContents(old, null);
139 } else {
140 editorComponent.select(start, end);
141 }
142 }
143
144 private void setSelectedItem(Object item) {
145 selecting = true;
146 comboBox.setSelectedItem(item);
147 selecting = false;
148 }
149
150 private Object lookupItem(String pattern, boolean match) {
151 ComboBoxModel<AutoCompletionListItem> model = comboBox.getModel();
152 AutoCompletionListItem bestItem = null;
153 for (int i = 0, n = model.getSize(); i < n; i++) {
154 AutoCompletionListItem currentItem = model.getElementAt(i);
155 if (currentItem.getValue().equals(pattern))
156 return currentItem;
157 if (!match && currentItem.getValue().startsWith(pattern)
158 && (bestItem == null || currentItem.getPriority().compareTo(bestItem.getPriority()) > 0)) {
159 bestItem = currentItem;
160 }
161 }
162 return bestItem; // may be null
163 }
164 }
165
166 /**
167 * Creates a <code>AutoCompletingComboBox</code> with a default prototype display value.
168 */
169 public AutoCompletingComboBox() {
170 this("Foo");
171 }
172
173 /**
174 * Creates a <code>AutoCompletingComboBox</code> with the specified prototype display value.
175 * @param prototype the <code>Object</code> used to compute the maximum number of elements to be displayed at once
176 * before displaying a scroll bar. It also affects the initial width of the combo box.
177 * @since 5520
178 */
179 public AutoCompletingComboBox(String prototype) {
180 super(new AutoCompletionListItem(prototype));
181 setRenderer(new AutoCompleteListCellRenderer());
182 final JTextComponent editorComponent = (JTextComponent) this.getEditor().getEditorComponent();
183 editorComponent.setDocument(new AutoCompletingComboBoxDocument(this));
184 editorComponent.addFocusListener(
185 new FocusListener() {
186 @Override
187 public void focusLost(FocusEvent e) {
188 if (Main.map != null) {
189 Main.map.keyDetector.setEnabled(true);
190 }
191 }
192
193 @Override
194 public void focusGained(FocusEvent e) {
195 if (Main.map != null) {
196 Main.map.keyDetector.setEnabled(false);
197 }
198 // save unix system selection (middle mouse paste)
199 Clipboard sysSel = Toolkit.getDefaultToolkit().getSystemSelection();
200 if (sysSel != null) {
201 Transferable old = Utils.getTransferableContent(sysSel);
202 editorComponent.selectAll();
203 sysSel.setContents(old, null);
204 } else {
205 editorComponent.selectAll();
206 }
207 }
208 }
209 );
210 }
211
212 /**
213 * Sets the maximum text length.
214 * @param length the maximum text length in number of characters
215 */
216 public void setMaxTextLength(int length) {
217 this.maxTextLength = length;
218 }
219
220 /**
221 * Convert the selected item into a String that can be edited in the editor component.
222 *
223 * @param cbEditor the editor
224 * @param item excepts AutoCompletionListItem, String and null
225 */
226 @Override
227 public void configureEditor(ComboBoxEditor cbEditor, Object item) {
228 if (item == null) {
229 cbEditor.setItem(null);
230 } else if (item instanceof String) {
231 cbEditor.setItem(item);
232 } else if (item instanceof AutoCompletionListItem) {
233 cbEditor.setItem(((AutoCompletionListItem) item).getValue());
234 } else
235 throw new IllegalArgumentException("Unsupported item: "+item);
236 }
237
238 /**
239 * Selects a given item in the ComboBox model
240 * @param item excepts AutoCompletionListItem, String and null
241 */
242 @Override
243 public void setSelectedItem(Object item) {
244 if (item == null) {
245 super.setSelectedItem(null);
246 } else if (item instanceof AutoCompletionListItem) {
247 super.setSelectedItem(item);
248 } else if (item instanceof String) {
249 String s = (String) item;
250 // find the string in the model or create a new item
251 for (int i = 0; i < getModel().getSize(); i++) {
252 AutoCompletionListItem acItem = getModel().getElementAt(i);
253 if (s.equals(acItem.getValue())) {
254 super.setSelectedItem(acItem);
255 return;
256 }
257 }
258 super.setSelectedItem(new AutoCompletionListItem(s, AutoCompletionItemPriority.UNKNOWN));
259 } else {
260 throw new IllegalArgumentException("Unsupported item: "+item);
261 }
262 }
263
264 /**
265 * Sets the items of the combobox to the given {@code String}s.
266 * @param elems String items
267 */
268 public void setPossibleItems(Collection<String> elems) {
269 DefaultComboBoxModel<AutoCompletionListItem> model = (DefaultComboBoxModel<AutoCompletionListItem>) this.getModel();
270 Object oldValue = this.getEditor().getItem(); // Do not use getSelectedItem(); (fix #8013)
271 model.removeAllElements();
272 for (String elem : elems) {
273 model.addElement(new AutoCompletionListItem(elem, AutoCompletionItemPriority.UNKNOWN));
274 }
275 // disable autocomplete to prevent unnecessary actions in AutoCompletingComboBoxDocument#insertString
276 autocompleteEnabled = false;
277 this.getEditor().setItem(oldValue); // Do not use setSelectedItem(oldValue); (fix #8013)
278 autocompleteEnabled = true;
279 }
280
281 /**
282 * Sets the items of the combobox to the given {@code AutoCompletionListItem}s.
283 * @param elems AutoCompletionListItem items
284 */
285 public void setPossibleACItems(Collection<AutoCompletionListItem> elems) {
286 DefaultComboBoxModel<AutoCompletionListItem> model = (DefaultComboBoxModel<AutoCompletionListItem>) this.getModel();
287 Object oldValue = getSelectedItem();
288 Object editorOldValue = this.getEditor().getItem();
289 model.removeAllElements();
290 for (AutoCompletionListItem elem : elems) {
291 model.addElement(elem);
292 }
293 setSelectedItem(oldValue);
294 this.getEditor().setItem(editorOldValue);
295 }
296
297 /**
298 * Determines if autocompletion is enabled.
299 * @return {@code true} if autocompletion is enabled, {@code false} otherwise.
300 */
301 public final boolean isAutocompleteEnabled() {
302 return autocompleteEnabled;
303 }
304
305 protected void setAutocompleteEnabled(boolean autocompleteEnabled) {
306 this.autocompleteEnabled = autocompleteEnabled;
307 }
308
309 /**
310 * If the locale is fixed, English keyboard layout will be used by default for this combobox
311 * all other components can still have different keyboard layout selected
312 * @param f fixed locale
313 */
314 public void setFixedLocale(boolean f) {
315 useFixedLocale = f;
316 if (useFixedLocale) {
317 Locale oldLocale = privateInputContext.getLocale();
318 Main.info("Using English input method");
319 if (!privateInputContext.selectInputMethod(new Locale("en", "US"))) {
320 // Unable to use English keyboard layout, disable the feature
321 Main.warn("Unable to use English input method");
322 useFixedLocale = false;
323 if (oldLocale != null) {
324 Main.info("Restoring input method to " + oldLocale);
325 if (!privateInputContext.selectInputMethod(oldLocale)) {
326 Main.warn("Unable to restore input method to " + oldLocale);
327 }
328 }
329 }
330 }
331 }
332
333 private final InputContext privateInputContext = InputContext.getInstance();
334
335 @Override
336 public InputContext getInputContext() {
337 if (useFixedLocale) {
338 return privateInputContext;
339 }
340 return super.getInputContext();
341 }
342
343 /**
344 * ListCellRenderer for AutoCompletingComboBox
345 * renders an AutoCompletionListItem by showing only the string value part
346 */
347 public static class AutoCompleteListCellRenderer extends JLabel implements ListCellRenderer<AutoCompletionListItem> {
348
349 /**
350 * Constructs a new {@code AutoCompleteListCellRenderer}.
351 */
352 public AutoCompleteListCellRenderer() {
353 setOpaque(true);
354 }
355
356 @Override
357 public Component getListCellRendererComponent(
358 JList<? extends AutoCompletionListItem> list,
359 AutoCompletionListItem item,
360 int index,
361 boolean isSelected,
362 boolean cellHasFocus) {
363 if (isSelected) {
364 setBackground(list.getSelectionBackground());
365 setForeground(list.getSelectionForeground());
366 } else {
367 setBackground(list.getBackground());
368 setForeground(list.getForeground());
369 }
370
371 setText(item.getValue());
372 return this;
373 }
374 }
375}
Note: See TracBrowser for help on using the repository browser.