source: josm/trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingTextField.java@ 13170

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

see #13747 - fix focus/selection issues in presets dialogs

  • Property svn:eol-style set to native
File size: 10.0 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.event.FocusAdapter;
6import java.awt.event.FocusEvent;
7import java.awt.event.KeyAdapter;
8import java.awt.event.KeyEvent;
9import java.util.EventObject;
10import java.util.Objects;
11
12import javax.swing.ComboBoxEditor;
13import javax.swing.JTable;
14import javax.swing.event.CellEditorListener;
15import javax.swing.table.TableCellEditor;
16import javax.swing.text.AttributeSet;
17import javax.swing.text.BadLocationException;
18import javax.swing.text.Document;
19import javax.swing.text.PlainDocument;
20import javax.swing.text.StyleConstants;
21
22import org.openstreetmap.josm.gui.util.CellEditorSupport;
23import org.openstreetmap.josm.gui.widgets.JosmTextField;
24import org.openstreetmap.josm.spi.preferences.Config;
25import org.openstreetmap.josm.tools.Logging;
26
27/**
28 * AutoCompletingTextField is a text field with autocompletion behaviour. It
29 * can be used as table cell editor in {@link JTable}s.
30 *
31 * Autocompletion is controlled by a list of {@link AutoCompletionListItem}s
32 * managed in a {@link AutoCompletionList}.
33 *
34 * @since 1762
35 */
36public class AutoCompletingTextField extends JosmTextField implements ComboBoxEditor, TableCellEditor {
37
38 private Integer maxChars;
39
40 /**
41 * The document model for the editor
42 */
43 class AutoCompletionDocument extends PlainDocument {
44
45 @Override
46 public void insertString(int offs, String str, AttributeSet a) throws BadLocationException {
47
48 // If a maximum number of characters is specified, avoid to exceed it
49 if (maxChars != null && str != null && getLength() + str.length() > maxChars) {
50 int allowedLength = maxChars-getLength();
51 if (allowedLength > 0) {
52 str = str.substring(0, allowedLength);
53 } else {
54 return;
55 }
56 }
57
58 if (autoCompletionList == null) {
59 super.insertString(offs, str, a);
60 return;
61 }
62
63 // input method for non-latin characters (e.g. scim)
64 if (a != null && a.isDefined(StyleConstants.ComposedTextAttribute)) {
65 super.insertString(offs, str, a);
66 return;
67 }
68
69 // if the current offset isn't at the end of the document we don't autocomplete.
70 // If a highlighted autocompleted suffix was present and we get here Swing has
71 // already removed it from the document. getLength() therefore doesn't include the
72 // autocompleted suffix.
73 //
74 if (offs < getLength()) {
75 super.insertString(offs, str, a);
76 return;
77 }
78
79 String currentText = getText(0, getLength());
80 // if the text starts with a number we don't autocomplete
81 if (Config.getPref().getBoolean("autocomplete.dont_complete_numbers", true)) {
82 try {
83 Long.parseLong(str);
84 if (currentText.isEmpty()) {
85 // we don't autocomplete on numbers
86 super.insertString(offs, str, a);
87 return;
88 }
89 Long.parseLong(currentText);
90 super.insertString(offs, str, a);
91 return;
92 } catch (NumberFormatException e) {
93 // either the new text or the current text isn't a number. We continue with autocompletion
94 Logging.trace(e);
95 }
96 }
97 String prefix = currentText.substring(0, offs);
98 autoCompletionList.applyFilter(prefix+str);
99 if (autoCompletionList.getFilteredSize() > 0 && !Objects.equals(str, noAutoCompletionString)) {
100 // there are matches. Insert the new text and highlight the auto completed suffix
101 String matchingString = autoCompletionList.getFilteredItemAt(0).getValue();
102 remove(0, getLength());
103 super.insertString(0, matchingString, a);
104
105 // highlight from insert position to end position to put the caret at the end
106 setCaretPosition(offs + str.length());
107 moveCaretPosition(getLength());
108 } else {
109 // there are no matches. Insert the new text, do not highlight
110 //
111 String newText = prefix + str;
112 remove(0, getLength());
113 super.insertString(0, newText, a);
114 setCaretPosition(getLength());
115 }
116 }
117 }
118
119 /** the auto completion list user input is matched against */
120 protected AutoCompletionList autoCompletionList;
121 /** a string which should not be auto completed */
122 protected String noAutoCompletionString;
123
124 @Override
125 protected Document createDefaultModel() {
126 return new AutoCompletionDocument();
127 }
128
129 protected final void init() {
130 addFocusListener(
131 new FocusAdapter() {
132 @Override
133 public void focusGained(FocusEvent e) {
134 if (e != null && e.getOppositeComponent() != null) {
135 // Select all characters when the change of focus occurs inside JOSM only.
136 // When switching from another application, it is annoying, see #13747
137 selectAll();
138 }
139 applyFilter(getText());
140 }
141 }
142 );
143
144 addKeyListener(
145 new KeyAdapter() {
146 @Override
147 public void keyReleased(KeyEvent e) {
148 if (getText().isEmpty()) {
149 applyFilter("");
150 }
151 }
152 }
153 );
154 tableCellEditorSupport = new CellEditorSupport(this);
155 }
156
157 /**
158 * Constructs a new {@code AutoCompletingTextField}.
159 */
160 public AutoCompletingTextField() {
161 this(0);
162 }
163
164 /**
165 * Constructs a new {@code AutoCompletingTextField}.
166 * @param columns the number of columns to use to calculate the preferred width;
167 * if columns is set to zero, the preferred width will be whatever naturally results from the component implementation
168 */
169 public AutoCompletingTextField(int columns) {
170 this(columns, true);
171 }
172
173 /**
174 * Constructs a new {@code AutoCompletingTextField}.
175 * @param columns the number of columns to use to calculate the preferred width;
176 * if columns is set to zero, the preferred width will be whatever naturally results from the component implementation
177 * @param undoRedo Enables or not Undo/Redo feature. Not recommended for table cell editors, unless each cell provides its own editor
178 */
179 public AutoCompletingTextField(int columns, boolean undoRedo) {
180 super(null, null, columns, undoRedo);
181 init();
182 }
183
184 protected void applyFilter(String filter) {
185 if (autoCompletionList != null) {
186 autoCompletionList.applyFilter(filter);
187 }
188 }
189
190 /**
191 * Returns the auto completion list.
192 * @return the auto completion list; may be null, if no auto completion list is set
193 */
194 public AutoCompletionList getAutoCompletionList() {
195 return autoCompletionList;
196 }
197
198 /**
199 * Sets the auto completion list.
200 * @param autoCompletionList the auto completion list; if null, auto completion is
201 * disabled
202 */
203 public void setAutoCompletionList(AutoCompletionList autoCompletionList) {
204 this.autoCompletionList = autoCompletionList;
205 }
206
207 @Override
208 public Component getEditorComponent() {
209 return this;
210 }
211
212 @Override
213 public Object getItem() {
214 return getText();
215 }
216
217 @Override
218 public void setItem(Object anObject) {
219 if (anObject == null) {
220 setText("");
221 } else {
222 setText(anObject.toString());
223 }
224 }
225
226 @Override
227 public void setText(String t) {
228 // disallow auto completion for this explicitly set string
229 this.noAutoCompletionString = t;
230 super.setText(t);
231 }
232
233 /**
234 * Sets the maximum number of characters allowed.
235 * @param max maximum number of characters allowed
236 * @since 5579
237 */
238 public void setMaxChars(Integer max) {
239 maxChars = max;
240 }
241
242 /* ------------------------------------------------------------------------------------ */
243 /* TableCellEditor interface */
244 /* ------------------------------------------------------------------------------------ */
245
246 private transient CellEditorSupport tableCellEditorSupport;
247 private String originalValue;
248
249 @Override
250 public void addCellEditorListener(CellEditorListener l) {
251 tableCellEditorSupport.addCellEditorListener(l);
252 }
253
254 protected void rememberOriginalValue(String value) {
255 this.originalValue = value;
256 }
257
258 protected void restoreOriginalValue() {
259 setText(originalValue);
260 }
261
262 @Override
263 public void removeCellEditorListener(CellEditorListener l) {
264 tableCellEditorSupport.removeCellEditorListener(l);
265 }
266
267 @Override
268 public void cancelCellEditing() {
269 restoreOriginalValue();
270 tableCellEditorSupport.fireEditingCanceled();
271 }
272
273 @Override
274 public Object getCellEditorValue() {
275 return getText();
276 }
277
278 @Override
279 public boolean isCellEditable(EventObject anEvent) {
280 return true;
281 }
282
283 @Override
284 public boolean shouldSelectCell(EventObject anEvent) {
285 return true;
286 }
287
288 @Override
289 public boolean stopCellEditing() {
290 tableCellEditorSupport.fireEditingStopped();
291 return true;
292 }
293
294 @Override
295 public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
296 setText(value == null ? "" : value.toString());
297 rememberOriginalValue(getText());
298 return this;
299 }
300}
Note: See TracBrowser for help on using the repository browser.