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

Last change on this file since 10755 was 10604, checked in by Don-vip, 8 years ago

fix #12478, fix #12565, fix #11114 - Use ​Swing Copy/Paste instead of CopyAction/PasteAction with custom buffer (patch by michael2402, modified) - gsoc-core

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