Changeset 18173 in josm for trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingComboBox.java
- Timestamp:
- 2021-08-24T02:43:50+02:00 (3 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingComboBox.java
r18141 r18173 2 2 package org.openstreetmap.josm.gui.tagging.ac; 3 3 4 import java.awt.datatransfer.Clipboard;5 import java.awt.datatransfer.Transferable;6 import java.awt.event.FocusEvent;7 import java.awt.event.FocusListener;8 import java.awt.im.InputContext;9 import java.util.Collection;10 import java.util.Collections;11 import java.util.LinkedList;12 import java.util.Locale;13 14 import javax.swing.ComboBoxModel;15 import javax.swing.DefaultComboBoxModel;16 import javax.swing.text.AttributeSet;17 import javax.swing.text.BadLocationException;18 import javax.swing.text.JTextComponent;19 import javax.swing.text.PlainDocument;20 import javax.swing.text.StyleConstants;21 22 4 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionItem; 23 import org.openstreetmap.josm.data.tagging.ac.AutoCompletionPriority;24 import org.openstreetmap.josm.gui.MainApplication;25 import org.openstreetmap.josm.gui.MapFrame;26 import org.openstreetmap.josm.gui.datatransfer.ClipboardUtils;27 import org.openstreetmap.josm.gui.widgets.JosmComboBox;28 import org.openstreetmap.josm.spi.preferences.Config;29 import org.openstreetmap.josm.tools.Logging;30 import org.openstreetmap.josm.tools.Utils;31 5 32 6 /** 33 * Auto-completing ComboBox. 7 * An auto-completing ComboBox. 8 * 34 9 * @author guilhem.bonnefille@gmail.com 35 10 * @since 272 11 * @deprecated Use the generic type {@link AutoCompComboBox} instead. Eg. 12 * {@code AutoCompComboBox<AutoCompletionItem>} or {@code AutoCompComboBox<String>}. 36 13 */ 37 public class AutoCompletingComboBox extends JosmComboBox<AutoCompletionItem> { 38 39 private boolean autocompleteEnabled = true; 40 private boolean locked; 41 42 private int maxTextLength = -1; 43 private boolean useFixedLocale; 44 45 private final transient InputContext privateInputContext = InputContext.getInstance(); 46 47 static final class InnerFocusListener implements FocusListener { 48 private final JTextComponent editorComponent; 49 50 InnerFocusListener(JTextComponent editorComponent) { 51 this.editorComponent = editorComponent; 52 } 53 54 @Override 55 public void focusLost(FocusEvent e) { 56 MapFrame map = MainApplication.getMap(); 57 if (map != null) { 58 map.keyDetector.setEnabled(true); 59 } 60 } 61 62 @Override 63 public void focusGained(FocusEvent e) { 64 MapFrame map = MainApplication.getMap(); 65 if (map != null) { 66 map.keyDetector.setEnabled(false); 67 } 68 // save unix system selection (middle mouse paste) 69 Clipboard sysSel = ClipboardUtils.getSystemSelection(); 70 if (sysSel != null) { 71 Transferable old = ClipboardUtils.getClipboardContent(sysSel); 72 editorComponent.selectAll(); 73 if (old != null) { 74 sysSel.setContents(old, null); 75 } 76 } else if (e != null && e.getOppositeComponent() != null) { 77 // Select all characters when the change of focus occurs inside JOSM only. 78 // When switching from another application, it is annoying, see #13747 79 editorComponent.selectAll(); 80 } 81 } 82 } 83 84 /** 85 * Auto-complete a JosmComboBox. 86 * <br> 87 * Inspired by <a href="http://www.orbital-computer.de/JComboBox">Thomas Bierhance example</a>. 88 */ 89 class AutoCompletingComboBoxDocument extends PlainDocument { 90 91 @Override 92 public void remove(int offs, int len) throws BadLocationException { 93 try { 94 super.remove(offs, len); 95 } catch (IllegalArgumentException e) { 96 // IAE can happen with Devanagari script, see #15825 97 Logging.error(e); 98 } 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 (maxTextLength > -1 && str.length() + getLength() > maxTextLength) 106 return; 107 108 super.insertString(offs, str, a); 109 110 if (locked) 111 return; // don't get in a loop 112 113 if (!autocompleteEnabled) 114 return; 115 116 // input method for non-latin characters (e.g. scim) 117 if (a != null && a.isDefined(StyleConstants.ComposedTextAttribute)) 118 return; 119 120 // if the cursor isn't at the end of the text we don't autocomplete. 121 // If a highlighted autocompleted suffix was present and we get here Swing has 122 // already removed it from the document. getLength() therefore doesn't include the autocompleted suffix. 123 if (offs + str.length() < getLength()) { 124 return; 125 } 126 127 String prefix = getText(0, getLength()); // the whole text after insertion 128 129 if (Config.getPref().getBoolean("autocomplete.dont_complete_numbers", true) 130 && prefix.matches("^\\d+$")) 131 return; 132 133 autocomplete(prefix); 134 135 // save unix system selection (middle mouse paste) 136 Clipboard sysSel = ClipboardUtils.getSystemSelection(); 137 if (sysSel != null) { 138 Transferable old = ClipboardUtils.getClipboardContent(sysSel); 139 if (old != null) { 140 sysSel.setContents(old, null); 141 } 142 } 143 } 144 } 145 146 /** 147 * Creates a <code>AutoCompletingComboBox</code> with a default prototype display value. 148 */ 149 public AutoCompletingComboBox() { 150 this("Foo"); 151 } 152 153 /** 154 * Creates a <code>AutoCompletingComboBox</code> with the specified prototype display value. 155 * @param prototype the <code>Object</code> used to compute the maximum number of elements to be displayed at once 156 * before displaying a scroll bar. It also affects the initial width of the combo box. 157 * @since 5520 158 */ 159 public AutoCompletingComboBox(String prototype) { 160 super(new AutoCompletionItem(prototype)); 161 final JTextComponent editorComponent = this.getEditorComponent(); 162 editorComponent.setDocument(new AutoCompletingComboBoxDocument()); 163 editorComponent.addFocusListener(new InnerFocusListener(editorComponent)); 164 } 165 166 /** 167 * Autocomplete a string. 168 * <p> 169 * Look in the model for an item whose true prefix matches the string. If 170 * found, set the editor to the item and select the item in the model too. 171 * 172 * @param prefix The prefix to autocomplete. 173 */ 174 private void autocomplete(String prefix) { 175 // candidate item for autocomplete 176 AutoCompletionItem item = findBestCandidate(prefix); 177 if (item != null) { 178 try { 179 locked = true; 180 setSelectedItem(item); 181 getEditor().setItem(item); 182 // select the autocompleted suffix in the editor 183 getEditorComponent().select(prefix.length(), item.getValue().length()); 184 } finally { 185 locked = false; 186 } 187 } 188 } 189 190 /** 191 * Find the best candidate for autocompletion. 192 * @param prefix The true prefix to match. 193 * @return The best candidate (may be null) 194 */ 195 private AutoCompletionItem findBestCandidate(String prefix) { 196 ComboBoxModel<AutoCompletionItem> model = getModel(); 197 AutoCompletionItem bestCandidate = null; 198 for (int i = 0, n = model.getSize(); i < n; i++) { 199 AutoCompletionItem currentItem = model.getElementAt(i); 200 // the "same" string is always the best candidate, but it is of 201 // no use for autocompletion 202 if (currentItem.getValue().equals(prefix)) 203 return null; 204 if (currentItem.getValue().startsWith(prefix) 205 && (bestCandidate == null || currentItem.getPriority().compareTo(bestCandidate.getPriority()) > 0)) { 206 bestCandidate = currentItem; 207 } 208 } 209 return bestCandidate; 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 * Selects a given item in the ComboBox model 222 * @param item the item of type AutoCompletionItem, String or null 223 * @param disableAutoComplete if true, autocomplete {@linkplain #setAutocompleteEnabled is disabled} during the operation 224 * @since 15885 225 */ 226 public void setSelectedItem(Object item, final boolean disableAutoComplete) { 227 final boolean previousState = isAutocompleteEnabled(); 228 if (disableAutoComplete) { 229 // disable autocomplete to prevent unnecessary actions in AutoCompletingComboBoxDocument#insertString 230 setAutocompleteEnabled(false); 231 } 232 setSelectedItem(item); 233 setAutocompleteEnabled(previousState); 234 } 235 236 /** 237 * Sets the items of the combobox to the given {@code String}s in reversed order (last element first). 238 * @param elems String items 239 */ 240 public void setPossibleItems(Collection<String> elems) { 241 DefaultComboBoxModel<AutoCompletionItem> model = (DefaultComboBoxModel<AutoCompletionItem>) this.getModel(); 242 Object oldValue = this.getEditor().getItem(); // Do not use getSelectedItem(); (fix #8013) 243 model.removeAllElements(); 244 for (String elem : elems) { 245 model.addElement(new AutoCompletionItem(elem, AutoCompletionPriority.UNKNOWN)); 246 } 247 this.setSelectedItem(null); 248 this.setSelectedItem(oldValue, true); 249 } 250 251 /** 252 * Sets the items of the combobox to the given {@code String}s in top down order. 253 * @param elems Collection of String items (is not changed) 254 * @since 15011 255 */ 256 public void setPossibleItemsTopDown(Collection<String> elems) { 257 // We have to reverse the history, because ComboBoxHistory will reverse it again in addElement() 258 LinkedList<String> reversed = new LinkedList<>(elems); 259 Collections.reverse(reversed); 260 setPossibleItems(reversed); 261 } 262 263 /** 264 * Sets the items of the combobox to the given {@code AutoCompletionItem}s. 265 * @param elems AutoCompletionItem items 266 * @since 12859 267 */ 268 public void setPossibleAcItems(Collection<AutoCompletionItem> elems) { 269 DefaultComboBoxModel<AutoCompletionItem> model = (DefaultComboBoxModel<AutoCompletionItem>) this.getModel(); 270 Object oldValue = getSelectedItem(); 271 Object editorOldValue = this.getEditor().getItem(); 272 model.removeAllElements(); 273 for (AutoCompletionItem elem : elems) { 274 model.addElement(elem); 275 } 276 setSelectedItem(oldValue); 277 this.getEditor().setItem(editorOldValue); 278 } 279 280 /** 281 * Determines if autocompletion is enabled. 282 * @return {@code true} if autocompletion is enabled, {@code false} otherwise. 283 */ 284 public final boolean isAutocompleteEnabled() { 285 return autocompleteEnabled; 286 } 287 288 /** 289 * Sets whether the autocompletion is enabled 290 * @param autocompleteEnabled {@code true} to enable autocompletion 291 * @since 15567 (visibility) 292 */ 293 public void setAutocompleteEnabled(boolean autocompleteEnabled) { 294 this.autocompleteEnabled = autocompleteEnabled; 295 } 296 297 /** 298 * If the locale is fixed, English keyboard layout will be used by default for this combobox 299 * all other components can still have different keyboard layout selected 300 * @param f fixed locale 301 */ 302 public void setFixedLocale(boolean f) { 303 useFixedLocale = f; 304 if (useFixedLocale) { 305 Locale oldLocale = privateInputContext.getLocale(); 306 Logging.info("Using English input method"); 307 if (!privateInputContext.selectInputMethod(new Locale("en", "US"))) { 308 // Unable to use English keyboard layout, disable the feature 309 Logging.warn("Unable to use English input method"); 310 useFixedLocale = false; 311 if (oldLocale != null) { 312 Logging.info("Restoring input method to " + oldLocale); 313 if (!privateInputContext.selectInputMethod(oldLocale)) { 314 Logging.warn("Unable to restore input method to " + oldLocale); 315 } 316 } 317 } 318 } 319 } 320 321 @Override 322 public InputContext getInputContext() { 323 if (useFixedLocale) { 324 return privateInputContext; 325 } 326 return super.getInputContext(); 327 } 328 329 /** 330 * Returns the edited item with whitespaces removed 331 * @return the edited item with whitespaces removed 332 * @since 15835 333 */ 334 public String getEditItem() { 335 return Utils.removeWhiteSpaces(getEditor().getItem().toString()); 336 } 337 338 /** 339 * Returns the selected item or the edited item as string 340 * @return the selected item or the edited item as string 341 * @see #getSelectedItem() 342 * @see #getEditItem() 343 * @since 15835 344 */ 345 public String getSelectedOrEditItem() { 346 final Object selectedItem = getSelectedItem(); 347 if (selectedItem instanceof AutoCompletionItem) { 348 return ((AutoCompletionItem) selectedItem).getValue(); 349 } else if (selectedItem instanceof String) { 350 return (String) selectedItem; 351 } else { 352 return getEditItem(); 353 } 354 } 14 @Deprecated 15 public class AutoCompletingComboBox extends AutoCompComboBox<AutoCompletionItem> { 355 16 }
Note:
See TracChangeset
for help on using the changeset viewer.