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

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

see #15182 - deprecate Main.map and Main.isDisplayingMapView(). Replacements: gui.MainApplication.getMap() / gui.MainApplication.isDisplayingMapView()

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