/************************************************************************* * @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(); } } }