Changeset 18125 in josm


Ignore:
Timestamp:
2021-08-05T13:43:42+02:00 (3 years ago)
Author:
Don-vip
Message:

fix #21198 - fix use of Enter key in AutoCompletingComboBox dropdown (patch by marcello, regression of r18098)

Location:
trunk/src/org/openstreetmap/josm
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/tagging/ac/AutoCompletionItem.java

    r16488 r18125  
    7575    }
    7676
     77    /**
     78     * Here we return the value instead of a representation of the inner object state because both
     79     * {@link javax.swing.plaf.basic.BasicComboBoxEditor#setItem(Object)} and
     80     * {@link javax.swing.DefaultListCellRenderer#getListCellRendererComponent}
     81     * expect it, thus making derived Editor and CellRenderer classes superfluous.
     82     */
    7783    @Override
    7884    public String toString() {
    79         StringBuilder sb = new StringBuilder();
    80         sb.append("<val='")
    81           .append(value)
    82           .append("',")
    83           .append(priority)
    84           .append('>');
    85         return sb.toString();
     85        return value;
    8686    }
    8787
  • trunk/src/org/openstreetmap/josm/gui/tagging/ac/AutoCompletingComboBox.java

    r18098 r18125  
    22package org.openstreetmap.josm.gui.tagging.ac;
    33
    4 import java.awt.Component;
    54import java.awt.datatransfer.Clipboard;
    65import java.awt.datatransfer.Transferable;
     
    1211import java.util.LinkedList;
    1312import java.util.Locale;
    14 import java.util.stream.IntStream;
    15 
    16 import javax.swing.ComboBoxEditor;
     13
    1714import javax.swing.ComboBoxModel;
    1815import javax.swing.DefaultComboBoxModel;
    19 import javax.swing.JLabel;
    20 import javax.swing.JList;
    21 import javax.swing.ListCellRenderer;
    2216import javax.swing.text.AttributeSet;
    2317import javax.swing.text.BadLocationException;
     
    4438
    4539    private boolean autocompleteEnabled = true;
     40    private boolean locked = false;
    4641
    4742    private int maxTextLength = -1;
     
    9388     */
    9489    class AutoCompletingComboBoxDocument extends PlainDocument {
    95         private boolean selecting;
    9690
    9791        @Override
    9892        public void remove(int offs, int len) throws BadLocationException {
    99             if (selecting)
    100                 return;
    10193            try {
    10294                super.remove(offs, len);
     
    111103            // TODO get rid of code duplication w.r.t. AutoCompletingTextField.AutoCompletionDocument.insertString
    112104
    113             if (selecting || (offs == 0 && str.equals(getText(0, getLength()))))
    114                 return;
    115             if (maxTextLength > -1 && str.length()+getLength() > maxTextLength)
    116                 return;
    117             boolean initial = offs == 0 && getLength() == 0 && str.length() > 1;
     105            if (maxTextLength > -1 && str.length() + getLength() > maxTextLength)
     106                return;
     107
    118108            super.insertString(offs, str, a);
    119109
    120             // return immediately when selecting an item
    121             // Note: this is done after calling super method because we need
    122             // ActionListener informed
    123             if (selecting)
    124                 return;
     110            if (locked)
     111                return; // don't get in a loop
     112
    125113            if (!autocompleteEnabled)
    126114                return;
     115
    127116            // input method for non-latin characters (e.g. scim)
    128117            if (a != null && a.isDefined(StyleConstants.ComposedTextAttribute))
    129118                return;
    130119
    131             // if the current offset isn't at the end of the document we don't autocomplete.
     120            // if the cursor isn't at the end of the text we don't autocomplete.
    132121            // If a highlighted autocompleted suffix was present and we get here Swing has
    133122            // already removed it from the document. getLength() therefore doesn't include the autocompleted suffix.
     
    136125            }
    137126
    138             int size = getLength();
    139             int start = offs+str.length();
    140             int end = start;
    141             String curText = getText(0, size);
    142 
    143             // item for lookup and selection
    144             Object item;
    145             // if the text is a number we don't autocomplete
    146             if (Config.getPref().getBoolean("autocomplete.dont_complete_numbers", true)) {
    147                 try {
    148                     Long.parseLong(str);
    149                     if (!curText.isEmpty())
    150                         Long.parseLong(curText);
    151                     item = lookupItem(curText, true);
    152                 } catch (NumberFormatException e) {
    153                     // either the new text or the current text isn't a number. We continue with autocompletion
    154                     item = lookupItem(curText, false);
    155                 }
    156             } else {
    157                 item = lookupItem(curText, false);
    158             }
    159 
    160             if (initial) {
    161                 start = 0;
    162             }
    163             if (item != null) {
    164                 String newText = ((AutoCompletionItem) item).getValue();
    165                 if (!newText.equals(curText)) {
    166                     selecting = true;
    167                     super.remove(0, size);
    168                     super.insertString(0, newText, a);
    169                     AutoCompletingComboBox.this.setSelectedItem(item);
    170                     selecting = false;
    171                     start = size;
    172                     end = getLength();
    173                 }
    174             }
    175             final JTextComponent editorComponent = getEditorComponent();
     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
    176135            // save unix system selection (middle mouse paste)
    177136            Clipboard sysSel = ClipboardUtils.getSystemSelection();
    178137            if (sysSel != null) {
    179138                Transferable old = ClipboardUtils.getClipboardContent(sysSel);
    180                 editorComponent.select(start, end);
    181139                if (old != null) {
    182140                    sysSel.setContents(old, null);
    183141                }
    184             } else {
    185                 editorComponent.select(start, end);
    186             }
    187         }
    188 
    189         private Object lookupItem(String pattern, boolean match) {
    190             ComboBoxModel<AutoCompletionItem> model = getModel();
    191             AutoCompletionItem bestItem = null;
    192             for (int i = 0, n = model.getSize(); i < n; i++) {
    193                 AutoCompletionItem currentItem = model.getElementAt(i);
    194                 if (currentItem.getValue().equals(pattern))
    195                     return currentItem;
    196                 if (!match && currentItem.getValue().startsWith(pattern)
    197                 && (bestItem == null || currentItem.getPriority().compareTo(bestItem.getPriority()) > 0)) {
    198                     bestItem = currentItem;
    199                 }
    200             }
    201             return bestItem; // may be null
     142            }
    202143        }
    203144    }
     
    218159    public AutoCompletingComboBox(String prototype) {
    219160        super(new AutoCompletionItem(prototype));
    220         setRenderer(new AutoCompleteListCellRenderer());
    221161        final JTextComponent editorComponent = this.getEditorComponent();
    222162        editorComponent.setDocument(new AutoCompletingComboBoxDocument());
     
    225165
    226166    /**
     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    /**
    227213     * Sets the maximum text length.
    228214     * @param length the maximum text length in number of characters
     
    230216    public void setMaxTextLength(int length) {
    231217        this.maxTextLength = length;
    232     }
    233 
    234     /**
    235      * Convert the selected item into a String that can be edited in the editor component.
    236      *
    237      * @param cbEditor    the editor
    238      * @param item      excepts AutoCompletionListItem, String and null
    239      */
    240     @Override
    241     public void configureEditor(ComboBoxEditor cbEditor, Object item) {
    242         if (item == null) {
    243             cbEditor.setItem(null);
    244         } else if (item instanceof String) {
    245             cbEditor.setItem(item);
    246         } else if (item instanceof AutoCompletionItem) {
    247             cbEditor.setItem(((AutoCompletionItem) item).getValue());
    248         } else
    249             throw new IllegalArgumentException("Unsupported item: "+item);
    250     }
    251 
    252     /**
    253      * Selects a given item in the ComboBox model
    254      * @param item the item of type AutoCompletionItem, String or null
    255      */
    256     @Override
    257     public void setSelectedItem(Object item) {
    258         setSelectedItem(item, false);
    259218    }
    260219
     
    271230            setAutocompleteEnabled(false);
    272231        }
    273         if (item == null) {
    274             super.setSelectedItem(null);
    275         } else if (item instanceof AutoCompletionItem) {
    276             super.setSelectedItem(item);
    277         } else if (item instanceof String) {
    278             String s = (String) item;
    279             // find the string in the model or create a new item
    280             AutoCompletionItem acItem = IntStream.range(0, getModel().getSize())
    281                     .mapToObj(i -> getModel().getElementAt(i))
    282                     .filter(i -> s.equals(i.getValue()))
    283                     .findFirst()
    284                     .orElseGet(() -> new AutoCompletionItem(s, AutoCompletionPriority.UNKNOWN));
    285             super.setSelectedItem(acItem);
    286         } else {
    287             setAutocompleteEnabled(previousState);
    288             throw new IllegalArgumentException("Unsupported item: "+item);
    289         }
     232        setSelectedItem(item);
    290233        setAutocompleteEnabled(previousState);
    291234    }
     
    410353        }
    411354    }
    412 
    413     /**
    414      * ListCellRenderer for AutoCompletingComboBox
    415      * renders an AutoCompletionListItem by showing only the string value part
    416      */
    417     public static class AutoCompleteListCellRenderer extends JLabel implements ListCellRenderer<AutoCompletionItem> {
    418 
    419         /**
    420          * Constructs a new {@code AutoCompleteListCellRenderer}.
    421          */
    422         public AutoCompleteListCellRenderer() {
    423             setOpaque(true);
    424         }
    425 
    426         @Override
    427         public Component getListCellRendererComponent(
    428                 JList<? extends AutoCompletionItem> list,
    429                 AutoCompletionItem item,
    430                 int index,
    431                 boolean isSelected,
    432                 boolean cellHasFocus) {
    433             if (isSelected) {
    434                 setBackground(list.getSelectionBackground());
    435                 setForeground(list.getSelectionForeground());
    436             } else {
    437                 setBackground(list.getBackground());
    438                 setForeground(list.getForeground());
    439             }
    440 
    441             setText(item.getValue());
    442             return this;
    443         }
    444     }
    445355}
Note: See TracChangeset for help on using the changeset viewer.