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

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

see #15182 - deprecate all Main logging methods and introduce suitable replacements in Logging for most of them

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