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<OsmPrimitive> 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<String>(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<String> newValues = new Vector<String>(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();
