Java Tutorial/Intermediate GUIs/Textfield with text completion

From Wikiversity
Jump to navigation Jump to search

The CompletionTextField combines use of Listeners, Threads, TreeSets, Comparators and Events to create a subclass of JComboBox that allows text completion. When the JComboBox shows its popup list every key press reduces the list of available choices to the list that starts with the given prefix.

CompletionTextField contains the following inner classes:

  • StringItem — an item that can be shown in the completion list
  • CompletionComboBoxModel — the ComoboBoxModel for the completion list
    • ItemComparator — a comparator that can be used for a TreeSet
    • Selection — a range of selected indices
  • CompletionComboBoxEditor — an editor for text editing in the ComboBox
    • CompletionKeyAdapter — an adapter class for receiving key events

CompletionTextField source[edit | edit source]

package org.wikiversity.java_tutorial;

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Arrays;
import java.util.Comparator;
import java.util.TreeSet;

import javax.swing.AbstractListModel;
import javax.swing.ComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.SwingUtilities;
import javax.swing.plaf.basic.BasicComboBoxEditor;

public class CompletionTextField extends JComboBox
{
	public interface Item {
		public String getName ();
	}
	
	public class StringItem implements Item {
		private String name;
		public StringItem (String str) { name = str; }
		public String getName () { return name; }
		public String toString () { return name; }
	}

	public static class CompletionComboBoxModel extends AbstractListModel implements ComboBoxModel
	{
		public static class ItemComparator implements Comparator<Item> {
			public int compare (Item i1, Item i2)
			{
				return i1.getName ().compareTo (i2.getName ());
			}
		}
		public static class Selection {
			public int from;
			public int length;
		}
		private Item[] itemList;
		private Object selection;
		private static ItemComparator itemComparator = new ItemComparator ();
		private Selection range = new Selection ();

		public CompletionComboBoxModel (Item[] itemList)
		{
			TreeSet<Item> treeSet = new TreeSet<Item> (itemComparator);
			treeSet.addAll (Arrays.asList (itemList));
			itemList = treeSet.toArray (itemList);
			for (int i=itemList.length-1; i >= 0; i--)
			{
				if (itemList[i] == null)
					throw new Error ("Sorting eliminated enum items with the same name");
			}
			this.itemList = itemList;
			range.length  = itemList.length;
		}

		public Object getElementAt (int index)
		{
			try {
				return itemList[index + range.from];
			}
			catch (ArrayIndexOutOfBoundsException ex) {
				return null;
			}
		}

		public int getSize ()
		{
			return range.length; // range.length may include the full list
		}

		public Object getSelectedItem () {
			return selection;
		}

		public void setSelectedItem (Object anItem)
		{
			if ((anItem != null && !anItem.equals (selection)) ||
				(anItem == null && selection != null))
			{
				selection = anItem;
				fireContentsChanged (this, -1, -1);
			}
		}

		public int relativeIndexOf (String name)
		{
			int len = range.from + range.length;
			for (int i=range.from; i < len; i++)
			{
				if (itemList[i].getName ().equalsIgnoreCase (name))
					return i + range.from;
			}
			return -1;
		}

		public Selection getMatchingItems (Selection selection, String prefix)
		{
			if (prefix.length () == 0)
			{
				selection.from   = 0;
				selection.length = itemList.length;
				return selection;
			}
			selection.from   = -1;
			selection.length = 0;
			for (int i=0; i < itemList.length; i++)
			{
				if (selection.from == -1)
				{
					if (startsWithIgnoreCase (itemList[i].getName (), prefix))
					{
						selection.from   = i;
						selection.length = 1;
					}
				}
				else
				{
					if (startsWithIgnoreCase (itemList[i].getName (), prefix))
						selection.length ++;
					else
						return selection;
				}
			}
			if (selection.from == -1)
				selection.from = 0;
			return selection;
		}

		private boolean startsWithIgnoreCase (String s1, String s2)
		{
			int len = Math.min (s1.length (), s2.length ());
			for (int i=0; i < len; i++)
			{
				if (Character.toUpperCase (s1.charAt (i)) != Character.toUpperCase (s2.charAt (i)))
					return false;
			}
			return true;
		}
		
		public void narrowListToPrefix (String prefix)
		{
			int size, previousSize = range.length;
			
			getMatchingItems (range, prefix);
			size = range.from + range.length;
			if (previousSize > range.length)
				fireIntervalRemoved (this, size, previousSize);
			else
				fireIntervalAdded (this, previousSize, size);
			fireContentsChanged (this, 0, size);
		}
	}

	public class CompletionComboBoxEditor extends BasicComboBoxEditor
	{
		class CompletionKeyAdapter extends KeyAdapter implements Runnable
		{	
			public void keyTyped (KeyEvent e)
			{
				SwingUtilities.invokeLater (this);
			}

			public void run ()
			{
				inhibited = true;
				try {
					ctf_GetCompletionComboBoxModel ().narrowListToPrefix (editor.getText ());
					if (isPopupVisible ())
					{
						hidePopup (); // cause refresh of the popup including the number of items
						showPopup ();
					}
				}
				finally {
					inhibited = false;
				}
			}
		}
		private CompletionKeyAdapter completionKeyAdapter = new CompletionKeyAdapter ();
		
		public CompletionComboBoxEditor ()
		{
			super ();
			editor.addKeyListener (completionKeyAdapter);
		}

		boolean inhibited = false;
		
		public void setItem (Object anObject)
		{
			if (!inhibited) // refuse to set an item as a result of the call to narrowListToPrefix () above
				super.setItem (anObject);
		}
	}

	public CompletionTextField ()
	{
		StringItem[] items = { new StringItem ("aaa"),  new StringItem ("abb"), new StringItem ("abc"), };
		CompletionComboBoxModel model = new CompletionComboBoxModel (items);
		setEditor (new CompletionComboBoxEditor ());
		setModel (model);
		setEditable (true);
	}
	
	public CompletionTextField (Item[] itemList)
	{
		CompletionComboBoxModel model = new CompletionComboBoxModel (itemList);
		setEditor (new CompletionComboBoxEditor ());
		setModel (model);
		setEditable (true);
	}

	public CompletionComboBoxModel ctf_GetCompletionComboBoxModel ()
	{
		return (CompletionComboBoxModel) getModel ();
	}
}

(source code from vsrs.svn.sourceforge.net)