Add auto-completion to key/value comboboxes From: <> --- .../josm/gui/dialogs/AutoCompleteComboBox.java | 96 +++++++++++++++++++++++ .../josm/gui/dialogs/PropertiesDialog.java | 41 +++++----- 2 files changed, 118 insertions(+), 19 deletions(-) diff --git a/src/org/openstreetmap/josm/gui/dialogs/AutoCompleteComboBox.java b/src/org/openstreetmap/josm/gui/dialogs/AutoCompleteComboBox.java new file mode 100644 index 0000000..aec3d5f --- /dev/null +++ b/src/org/openstreetmap/josm/gui/dialogs/AutoCompleteComboBox.java @@ -0,0 +1,96 @@ +package org.openstreetmap.josm.gui.dialogs; + +import java.util.Collection; + +import javax.swing.ComboBoxModel; +import javax.swing.DefaultComboBoxModel; +import javax.swing.JComboBox; +import javax.swing.text.AttributeSet; +import javax.swing.text.BadLocationException; +import javax.swing.text.JTextComponent; +import javax.swing.text.PlainDocument; + +public class AutoCompleteComboBox extends JComboBox { + + /** + * Auto-complete a JComboBox. + * + * Inspired by http://www.orbital-computer.de/JComboBox/ + */ + protected class AutoCompleteComboBoxDocument extends PlainDocument { + private JComboBox comboBox; + + private boolean selecting = false; + + public AutoCompleteComboBoxDocument(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; + } + } + + AutoCompleteComboBox() { + // get the combo box' editor component + JTextComponent editor = (JTextComponent) this.getEditor().getEditorComponent(); + // change the editor's document + editor.setDocument(new AutoCompleteComboBoxDocument(this)); + } + + public void setPossibleItems(Collection elems) { + Object oldValue = this.getSelectedItem(); + DefaultComboBoxModel model = (DefaultComboBoxModel)this.getModel(); + model.removeAllElements(); + for (String elem : elems) model.addElement(elem); + this.setSelectedItem(oldValue); + this.getEditor().selectAll(); + } +} diff --git a/src/org/openstreetmap/josm/gui/dialogs/PropertiesDialog.java b/src/org/openstreetmap/josm/gui/dialogs/PropertiesDialog.java index da639d9..a8e1349 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; @@ -36,6 +38,7 @@ import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.table.DefaultTableCellRenderer; import javax.swing.table.DefaultTableModel; +import javax.swing.text.JTextComponent; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.command.ChangePropertyCommand; @@ -164,8 +167,8 @@ 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). + * 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(); @@ -192,33 +195,33 @@ public class PropertiesDialog extends ToggleDialog implements SelectionChangedLi } for (int i = 0; i < data.getRowCount(); ++i) allData.remove(data.getValueAt(i, 0)); - final JComboBox keys = new JComboBox(new Vector(allData.keySet())); + final AutoCompleteComboBox keys = new AutoCompleteComboBox(); + keys.setPossibleItems(allData.keySet()); keys.setEditable(true); + p.add(keys, BorderLayout.CENTER); JPanel p2 = new JPanel(new BorderLayout()); p.add(p2, BorderLayout.SOUTH); p2.add(new JLabel(tr("Please select a value")), BorderLayout.NORTH); - final JComboBox values = new JComboBox(); + final AutoCompleteComboBox values = new AutoCompleteComboBox(); values.setEditable(true); 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)); - values.setSelectedItem(oldValue); - values.getEditor().selectAll(); + + // get the combo box' editor component + JTextComponent editor = (JTextComponent) values.getEditor().getEditorComponent(); + // Refresh the values model when focus is gained + editor.addFocusListener(new FocusAdapter() { + public void focusGained(FocusEvent e) { + String key = keys.getEditor().getItem().toString(); + if (allData.containsKey(key)) { + values.setPossibleItems(allData.get(key)); + } else { + values.removeAllItems(); } } - - }; - keys.addActionListener(link); - + }); + JOptionPane pane = new JOptionPane(p, JOptionPane.PLAIN_MESSAGE, JOptionPane.OK_CANCEL_OPTION){ @Override public void selectInitialValue() { keys.requestFocusInWindow();