Add auto-completion to key/value comboboxes From: <> --- .../josm/gui/dialogs/PropertiesDialog.java | 110 ++++++++++++++++++++--- 1 files changed, 95 insertions(+), 15 deletions(-) diff --git a/src/org/openstreetmap/josm/gui/dialogs/PropertiesDialog.java b/src/org/openstreetmap/josm/gui/dialogs/PropertiesDialog.java index da639d9..c529a4a 100644 --- a/src/org/openstreetmap/josm/gui/dialogs/PropertiesDialog.java +++ b/src/org/openstreetmap/josm/gui/dialogs/PropertiesDialog.java @@ -11,6 +11,8 @@ import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; @@ -23,6 +25,7 @@ import java.util.Vector; import java.util.Map.Entry; import javax.swing.Box; +import javax.swing.ComboBoxModel; import javax.swing.DefaultComboBoxModel; import javax.swing.JButton; import javax.swing.JComboBox; @@ -36,6 +39,10 @@ import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableModel; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.JTextComponent; +import javax.swing.text.PlainDocument; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.command.ChangePropertyCommand; @@ -164,8 +171,74 @@ public class PropertiesDialog extends ToggleDialog implements SelectionChangedLi } /** - * Open the add selection dialog and add a new key/value to the table (and to the - * dataset, of course). + * Auto-complete a JComboBox. + * + * Inspired by http://www.orbital-computer.de/JComboBox/ + */ + class AutoCompleteComboBox extends PlainDocument { + private JComboBox comboBox; + + private boolean selecting = false; + + public AutoCompleteComboBox(final JComboBox comboBox) { + this.comboBox = comboBox; + } + + public void remove(int offs, int len) throws BadLocationException { + // return immediately when selecting an item + if (selecting) + return; + super.remove(offs, len); + } + + public void insertString(int offs, String str, AttributeSet a) + throws BadLocationException { + // insert the string into the document + super.insertString(offs, str, a); + // return immediately when selecting an item + // Nota: this is done after calling super method because we need + // ActionListener informed + if (selecting) + return; + // lookup and select a matching item + Object item = lookupItem(getText(0, getLength())); + if (item != null) { + // remove all text and insert the completed string + super.remove(0, getLength()); + super.insertString(0, item.toString(), a); + // select the completed part + JTextComponent editor = (JTextComponent)comboBox.getEditor() + .getEditorComponent(); + editor.setSelectionStart(offs + str.length()); + editor.setSelectionEnd(getLength()); + } + setSelectedItem(item); + } + + private void setSelectedItem(Object item) { + selecting = true; + comboBox.setSelectedItem(item); + selecting = false; + } + + private Object lookupItem(String pattern) { + // iterate over all items + ComboBoxModel model = comboBox.getModel(); + for (int i = 0, n = model.getSize(); i < n; i++) { + Object currentItem = model.getElementAt(i); + // current item starts with the pattern? + if (currentItem.toString().startsWith(pattern)) { + return currentItem; + } + } + // no item starts with the pattern => return null + return null; + } + } + + /** + * Open the add selection dialog and add a new key/value to the table (and + * to the dataset, of course). */ void add() { Collection sel = Main.ds.getSelected(); @@ -194,6 +267,10 @@ public class PropertiesDialog extends ToggleDialog implements SelectionChangedLi allData.remove(data.getValueAt(i, 0)); final JComboBox keys = new JComboBox(new Vector(allData.keySet())); keys.setEditable(true); + // get the combo box' editor component + JTextComponent editor = (JTextComponent) keys.getEditor().getEditorComponent(); + // change the editor's document + editor.setDocument(new AutoCompleteComboBox(keys)); p.add(keys, BorderLayout.CENTER); JPanel p2 = new JPanel(new BorderLayout()); @@ -201,24 +278,27 @@ public class PropertiesDialog extends ToggleDialog implements SelectionChangedLi p2.add(new JLabel(tr("Please select a value")), BorderLayout.NORTH); final JComboBox values = new JComboBox(); values.setEditable(true); + // get the combo box' editor component + editor = (JTextComponent) values.getEditor().getEditorComponent(); + // change the editor's document + editor.setDocument(new AutoCompleteComboBox(values)); p2.add(values, BorderLayout.CENTER); - - ActionListener link = new ActionListener() { - - public void actionPerformed(ActionEvent e) { - String key = keys.getEditor().getItem().toString(); - if (allData.containsKey(key)) { - Vector newValues = new Vector(allData.get(key)); - Object oldValue = values.getSelectedItem(); - values.setModel(new DefaultComboBoxModel(newValues)); + + // Refresh the values model when focus is gained + editor.addFocusListener(new FocusAdapter() { + public void focusGained(FocusEvent e) { + Object oldValue = values.getSelectedItem(); + DefaultComboBoxModel model = (DefaultComboBoxModel)values.getModel(); + model.removeAllElements(); + String key = keys.getEditor().getItem().toString(); + if (allData.containsKey(key)) { + for (String elem : allData.get(key)) model.addElement(elem); values.setSelectedItem(oldValue); values.getEditor().selectAll(); } } - - }; - keys.addActionListener(link); - + }); + JOptionPane pane = new JOptionPane(p, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION){ @Override public void selectInitialValue() { keys.requestFocusInWindow();