Link to code: AutocompleteGUI.java

/*************************************************************************
 * @author Matthew Drabick, adapted by Austin Lu for COMPSCI 201 Autocomplete,
 * 
 *         Interactive GUI used to demonstrate the Autocomplete data type.
 *
 *         * Reads a list of terms and weights from a file, specified as a command-line argument.
 *
 *         * As the user types in a text box, display the top-k terms that start with the text that
 *         the user types.
 *
 *         * Displays the result in a browser if the user selects a term (by pressing enter or
 *         clicking a selection).
 *
 *         BUG: Selections don't autoupdate if user enter character into text box without typing it
 *         (e.g., by selecting it from Mac OS X Character Viewer). BUG: Completion list disappears
 *         when user clicks to browse.
 *         
 *         * (06/01/2015) - GUI is no longer case-sensitive, but results are
 *         still displayed in their original case.
 *     
 *
 *************************************************************************/

import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.net.URISyntaxException;

import javax.swing.*;

import java.util.*;

@SuppressWarnings("serial")
public class AutocompleteGUI extends JFrame {
	private static int DEF_WIDTH = 600;
	private static int DEF_HEIGHT = 400;
	private static String searchURL = "https://www.google.com/search?q=";
	private HashMap<String, String> casingMap;

	public static final String CHARSET = "UTF-8";
	public static final Locale LOCALE = Locale.US;

	// display top k results
	private final int k;
	private final String autocompletorClassName;

	public AutocompleteGUI(String fileName, int k, String className) {
		this.k = k;
		this.autocompletorClassName = className;
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		setTitle("Autocomplete");
		setLocationRelativeTo(null);
		Container content = getContentPane();
		GroupLayout layout = new GroupLayout(content);
		content.setLayout(layout);
		layout.setAutoCreateGaps(true);
		layout.setAutoCreateContainerGaps(true);
		final AutocompletePanel ap = new AutocompletePanel(fileName);
		JButton searchButton = new JButton("Search Google");
		// searchButton.setBorder(BorderFactory.createEmptyBorder(2, 0, 0, 0));
		searchButton.addMouseListener(new MouseListener() {
			// Need a bunch of unimplemented reports
			public void mousePressed(MouseEvent e) {
			}

			public void mouseReleased(MouseEvent e) {
			}

			public void mouseEntered(MouseEvent e) {
			}

			public void mouseExited(MouseEvent e) {
			}

			@Override
			public void mouseClicked(MouseEvent e) {
				searchOnline(ap.getSelectedText());
			}
		});
		JLabel textLabel = new JLabel("Type text:");
		textLabel.setBorder(BorderFactory.createEmptyBorder(1, 4, 0, 0));
		layout.setHorizontalGroup(layout.createSequentialGroup()
				.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
				.addComponent(textLabel, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE,
						GroupLayout.PREFERRED_SIZE)
				.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE,
						GroupLayout.DEFAULT_SIZE)
				.addComponent(ap, 0, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
				.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE,
						GroupLayout.DEFAULT_SIZE)
				.addComponent(searchButton, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE,
						GroupLayout.PREFERRED_SIZE)
				.addPreferredGap(LayoutStyle.ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE));
		layout.setVerticalGroup(
				layout.createSequentialGroup().addGroup(layout.createParallelGroup(GroupLayout.Alignment.BASELINE)
						.addComponent(textLabel).addComponent(ap).addComponent(searchButton)));
		setPreferredSize(new Dimension(DEF_WIDTH, DEF_HEIGHT));
		pack();
	}

	private class AutocompletePanel extends JPanel {
		private final JTextField searchText;
		private Autocompletor auto;
		private String[] results = new String[k];
		private JList<String> suggestions;

		// keep these two values in sync! - used to keep the listbox the same
		// width as the textfield
		private final int DEF_COLUMNS = 30;
		private final String suggListLen = "<b>Harry Potter and the Deathly Hallows: Part 1 (2010)</b>";

		public AutocompletePanel(String filename) {
			super();

			// read in the data
			Scanner in;
			try {
				in = new Scanner(new File(filename), CHARSET);
				in.useLocale(LOCALE);
				int N = Integer.parseInt(in.nextLine());
				String[] terms = new String[N];
				double[] weights = new double[N];
				casingMap = new HashMap<String, String>();
				for (int i = 0; i < N; i++) {
					String line = in.nextLine();
					int tab = line.indexOf('\t');
					weights[i] = Double.parseDouble(line.substring(0, tab).trim());
					casingMap.put(line.substring(tab + 1).toLowerCase(), line.substring(tab + 1));
					terms[i] = line.substring(tab + 1).toLowerCase();
					// create the autocomplete object
				}
				auto = (Autocompletor) Class.forName(autocompletorClassName)
						.getDeclaredConstructor(String[].class, double[].class).newInstance(terms, weights);

			} catch (InstantiationException | IllegalAccessException | ClassNotFoundException | IllegalArgumentException
					| InvocationTargetException | NoSuchMethodException | SecurityException e1) {
				e1.printStackTrace();
				System.exit(1);
			} catch (FileNotFoundException e2) {
				System.out.println("Cannot read file " + filename);
				System.exit(1);

			}

			GroupLayout layout = new GroupLayout(this);
			this.setLayout(layout);
			searchText = new JTextField(DEF_COLUMNS);
			searchText.setMaximumSize(
					new Dimension(searchText.getMaximumSize().width, searchText.getPreferredSize().height));
			searchText.getInputMap().put(KeyStroke.getKeyStroke("UP"), "none");
			searchText.getInputMap().put(KeyStroke.getKeyStroke("DOWN"), "none");
			searchText.addFocusListener(new FocusListener() {
				@Override
				public void focusGained(FocusEvent e) {
					int pos = searchText.getText().length();
					searchText.setCaretPosition(pos);
				}

				public void focusLost(FocusEvent e) {
				}
			});
			JPanel searchTextPanel = new JPanel();
			searchTextPanel.add(searchText);
			searchTextPanel.setBorder(BorderFactory.createEmptyBorder(2, 0, 0, 0));
			searchTextPanel.setLayout(new GridLayout(1, 1));
			suggestions = new JList<String>(results);
			suggestions.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1));
			suggestions.setVisible(false);
			suggestions.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
			suggestions.setMaximumSize(
					new Dimension(searchText.getMaximumSize().width, suggestions.getPreferredSize().height));
			suggestions.setPrototypeCellValue(suggListLen); // set to make equal
															// to the width of
															// the
															// textfield
			suggestions.setFont(suggestions.getFont().deriveFont(Font.PLAIN, 13));
			Action makeSelection = new AbstractAction() {
				public void actionPerformed(ActionEvent e) {
					if (!suggestions.isSelectionEmpty()) {
						String selection = (String) suggestions.getSelectedValue();
						selection = selection.replaceAll("\\<.*?>", "");
						searchText.setText(selection);
						getSuggestions(selection);
					}
					searchOnline(searchText.getText());
				}
			};
			Action moveSelectionUp = new AbstractAction() {
				@Override
				public void actionPerformed(ActionEvent e) {
					if (suggestions.getSelectedIndex() >= 0) {
						suggestions.requestFocusInWindow();
						suggestions.setSelectedIndex(suggestions.getSelectedIndex() - 1);
					}
				}
			};
			Action moveSelectionDown = new AbstractAction() {
				public void actionPerformed(ActionEvent e) {
					if (suggestions.getSelectedIndex() != results.length) {
						suggestions.requestFocusInWindow();
						suggestions.setSelectedIndex(suggestions.getSelectedIndex() + 1);
					}
				}
			};
			Action moveSelectionUpFocused = new AbstractAction() {
				public void actionPerformed(ActionEvent e) {
					if (suggestions.getSelectedIndex() == 0) {
						suggestions.clearSelection();
						searchText.requestFocusInWindow();
						// int pos = searchText.getText().length();
						// searchText.select(pos, pos);
						searchText.setSelectionEnd(0);

					} else if (suggestions.getSelectedIndex() >= 0) {
						suggestions.setSelectedIndex(suggestions.getSelectedIndex() - 1);
					}
				}
			};
			suggestions.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("UP"),
					"moveSelectionUp");
			suggestions.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("DOWN"),
					"moveSelectionDown");
			suggestions.getActionMap().put("moveSelectionUp", moveSelectionUp);
			suggestions.getActionMap().put("moveSelectionDown", moveSelectionDown);
			suggestions.getInputMap(JComponent.WHEN_FOCUSED).put(KeyStroke.getKeyStroke("ENTER"), "makeSelection");
			suggestions.getInputMap().put(KeyStroke.getKeyStroke("UP"), "moveSelectionUpFocused");
			suggestions.getActionMap().put("moveSelectionUpFocused", moveSelectionUpFocused);
			suggestions.getActionMap().put("makeSelection", makeSelection);
			JPanel suggestionsPanel = new JPanel();
			suggestionsPanel.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
			suggestionsPanel.add(suggestions);
			suggestionsPanel.setLayout(new GridLayout(1, 1));
			this.setMaximumSize(new Dimension(searchText.getMaximumSize().width, this.getPreferredSize().height));
			suggestions.addMouseListener(new MouseAdapter() {
				@Override
				public void mouseClicked(MouseEvent mouseEvent) {
					JList<?> theList = (JList<?>) mouseEvent.getSource();
					if (mouseEvent.getClickCount() >= 1) {
						int index = theList.locationToIndex(mouseEvent.getPoint());
						if (index >= 0) {
							String selection = getSelectedText();
							searchText.setText(selection);
							String text = searchText.getText();
							getSuggestions(text);
							searchOnline(searchText.getText());
						}
					}
				}
			});
			searchText.addKeyListener(new KeyListener() {
				@Override
				public void keyPressed(KeyEvent e) {
				}

				@Override
				public void keyReleased(KeyEvent e) {
					JTextField txtSrc = (JTextField) e.getSource();
					String text = txtSrc.getText();
					getSuggestions(text);
				}

				@Override
				public void keyTyped(KeyEvent e) {
				}
			});
			searchText.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					String selection = getSelectedText();
					searchText.setText(selection);
					getSuggestions(selection);
					searchOnline(searchText.getText());
				}

			});
			layout.setHorizontalGroup(layout.createSequentialGroup()
					.addGroup(layout.createParallelGroup(GroupLayout.Alignment.LEADING)
							.addComponent(searchTextPanel, 0, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
							.addComponent(suggestionsPanel, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE,
									GroupLayout.PREFERRED_SIZE))

			);
			layout.setVerticalGroup(
					layout.createSequentialGroup().addComponent(searchTextPanel).addComponent(suggestionsPanel));
		}

		public void paintComponent(Graphics g) {
			super.paintComponent(g);
		}

		/**
		 * Makes a call to the implementation of Autocomplete to get suggestions
		 * for the currently entered text.
		 * 
		 * @param text
		 *            string to search for
		 */
		public void getSuggestions(String text) {
			// text = text.trim();
			if (text.equals("")) {
				suggestions.clearSelection();
				suggestions.setVisible(false);
			} else {
				int textLen = text.length();
				Queue<String> resultQ = new LinkedList<String>();
				for (String term : auto.topKMatches(text.toLowerCase(), k)) {
					resultQ.add(casingMap.get(term));
				}
				if (!resultQ.isEmpty()) {
					results = new String[resultQ.size()];
					for (int i = 0; i < results.length; i++) {
						results[i] = resultQ.remove();
						results[i] = "<html>" + results[i].substring(0, textLen) + "<b>" + results[i].substring(textLen)
								+ "</b></html>";
					}
					suggestions.setListData(results);
					suggestions.setVisible(true);
					// suggestions.setSelectedIndex(0); // Pressing enter
					// automatically selects the first one
					// if nothing has been
				} else {
					// No suggestions
					suggestions.setVisible(false);
					suggestions.clearSelection();
				}
			}
		}

		public String getSelectedText() {
			if (!suggestions.isSelectionEmpty()) {
				String selection = (String) suggestions.getSelectedValue();
				selection = selection.replaceAll("\\<.*?>", "");
				return selection;
			} else
				return getSearchText();
		}

		public String getSearchText() {
			return searchText.getText();
		}
	}

	/**
	 * Creates a URI from the user-defined string and searches the web with the
	 * selected search engine Opens the default web browser (or a new tab if it
	 * is already open)
	 * 
	 * @param s
	 *            string to search online for
	 */
	private void searchOnline(String s) {
		URI searchAddress = null;
		try {
			URI tempAddress = new URI(searchURL + s.trim().replace(' ', '+'));
			searchAddress = new URI(tempAddress.toASCIIString()); // hack to
																	// handle
																	// Unicode
		} catch (URISyntaxException e2) {
			e2.printStackTrace();
			return;
		}
		try {
			Desktop.getDesktop().browse(searchAddress);
		} catch (IOException e1) {
			e1.printStackTrace();
		}
	}
}