import java.applet.*; import java.awt.*; import java.awt.event.*; import java.util.*; public class JumpToWidget extends Applet implements KeyListener,FocusListener { private String namesString; private String valuesString; private Vector namesVector; private Vector lowercaseNamesVector; private Vector valuesVector; private Vector optionsValuesVector; private Vector optionsNamesVector; private java.awt.List optionsList; private java.awt.TextField tf; private int isCaseSensitive; /** * Initializes the user interface and private variables. */ public void init() { int numOptions; // create user interface using the grid bag layout GridBagLayout layout = new GridBagLayout(); this.setLayout(layout); GridBagConstraints constraints = new GridBagConstraints(); // prepare grid bag constraints for text field constraints.weightx = 100; constraints.weighty = 0; constraints.gridx = 0; constraints.gridy = 0; constraints.gridwidth = 1; constraints.gridheight = 1; constraints.fill = GridBagConstraints.HORIZONTAL; constraints.anchor = GridBagConstraints.NORTHEAST; // the text field tf = new TextField("",25); tf.addKeyListener(this); layout.setConstraints(tf, constraints); this.add(tf); optionsList = new java.awt.List(30, false); // prepare grid bag constraints for list constraints.gridy = 1; constraints.weighty = 100; constraints.fill = GridBagConstraints.BOTH; constraints.anchor = GridBagConstraints.CENTER; layout.setConstraints(optionsList, constraints); // add the list and calculate the number of rows that // are actually visible this.add(optionsList); this.validate(); Dimension dim = new Dimension(); dim = optionsList.getSize(); Dimension dim4 = new Dimension(); Dimension dim5 = new Dimension(); dim4 = optionsList.getPreferredSize(4); dim5 = optionsList.getPreferredSize(5); numOptions = (dim.height/(dim5.height - dim4.height)) - 1; if(numOptions <= 0) { numOptions = 1; } // remove and re-add the list so that it's number of rows // is the same as the number that are visible this.remove(optionsList); this.validate(); optionsList = new java.awt.List(numOptions, false); optionsList.addFocusListener(this); optionsList.setFont(new Font("SansSerif",java.awt.Font.PLAIN,11)); layout.setConstraints(optionsList, constraints); this.add(optionsList); // initialize private variables namesVector = new Vector(); lowercaseNamesVector = new Vector(); valuesVector = new Vector(); optionsValuesVector = new Vector(); optionsNamesVector = new Vector(); // set the case sensitivity setCaseSensitivity(getParameter("casesensitivity")); // read the parameters from the html page loadNamesAndValues(getParameter("names"), getParameter("values")); } /** * Returns the value of the selected option. * @return null if no option is selected, otherwise * the value of the currently selected option */ public String getSelectedValue() { if(optionsList.getSelectedIndex() != -1) { return (String)optionsValuesVector.elementAt(optionsList.getSelectedIndex()); } else { return null; } } /** * Returns the name of the selected option. * @return null if no option is selected, otherwise * the visible name of the currently selected option */ public String getSelectedName() { if(optionsList.getSelectedItem() != null) { return (String)optionsList.getSelectedItem(); } else { return null; } } public void setCaseSensitivity(String strCaseSensitivity) { strCaseSensitivity = strCaseSensitivity.toLowerCase(); if(strCaseSensitivity.compareTo("yes") == 0 || strCaseSensitivity.compareTo("y") == 0 || strCaseSensitivity.compareTo("1") == 0 || strCaseSensitivity.compareTo("true") == 0 ) { isCaseSensitive = 1; } else { isCaseSensitive = 0; } } /** * Reloads the name and value strings into the internal vectors * and refreshes the options list. This must be called after * the names and values fields are changed. If the names and values * don't agree in number, then an error is printed in the text field. * @see JumpToWidget#namesString * @see JumpToWidget#valuesString */ public void loadNamesAndValues(String strNames, String strValues) { this.namesString = new String(strNames); this.valuesString = new String(strValues); int i; int countNames; int countValues; StringTokenizer st; String tempString; // clear the data vectors and options list namesVector.removeAllElements(); valuesVector.removeAllElements(); optionsNamesVector.removeAllElements(); optionsValuesVector.removeAllElements(); lowercaseNamesVector.removeAllElements(); optionsList.removeAll(); // get the names into an array st = new StringTokenizer(this.namesString, "|"); countNames = 0; while (st.hasMoreTokens()) { tempString = new String(st.nextToken()); namesVector.addElement(tempString); optionsNamesVector.addElement(tempString); if(isCaseSensitive == 0) { lowercaseNamesVector.addElement(tempString.toLowerCase()); } else { lowercaseNamesVector.addElement(tempString); } countNames++; } // get the values into an array st = new StringTokenizer(valuesString, "|"); countValues = 0; while (st.hasMoreTokens()) { tempString = new String(st.nextToken()); valuesVector.addElement(tempString); optionsValuesVector.addElement(tempString); countValues++; } // if the number of names doesn't match the number of values // add an error to the option box. if(countNames != countValues) { // if there are names, but no values, then if(countValues == 0 && countNames != 0) { // load the names as values for(i = 0; i < namesVector.size(); i++) { valuesVector.addElement(namesVector.elementAt(i)); optionsValuesVector.addElement(namesVector.elementAt(i)); countValues++; } } else { // otherwise, print error tf.setText("ERROR: number of values doesn't match number of names"); } } else { // reset the text field tf.setText(""); } optionsList.setVisible(false); // only add the visible options because adding options is really slow for(i = 0; i < optionsList.getRows() && i < optionsNamesVector.size(); i++) { optionsList.add( (String)optionsNamesVector.elementAt(i) ); } // select the first item if(optionsList.getItemCount() > 0) { optionsList.select(0); } optionsList.setVisible(true); } /** * When the list gains focus, then we must populate * the list with the remaining options which match * the substring. */ public void focusGained(java.awt.event.FocusEvent fe) { int i; int selectedIndex; // only reload if the number of options displayed is // less than the actual number of options if(optionsList.getItemCount() < optionsNamesVector.size()) { // rememember the index of the selected item selectedIndex = optionsList.getSelectedIndex(); // remove the options optionsList.removeAll(); // show loading message optionsList.add("Loading..."); // set temporarily invisible optionsList.setVisible(false); // set the display to the new names for(i = 0; i < optionsNamesVector.size(); i++) { optionsList.add( (String)optionsNamesVector.elementAt(i) ); } // show the options list optionsList.setVisible(true); // remove the loading message optionsList.remove(0); // reselect the selected item optionsList.select(selectedIndex); // make sure focus stays on the list box optionsList.requestFocus(); } } /** * Does nothing */ public void focusLost(java.awt.event.FocusEvent fe) { // do nothing } /** * When key is released on the text field, then * the internal options vectors are reloaded and the options * list is updated to show only the options that contain * the substrings in the text field. To improve performance, * only first few visible options are added back to the options list. */ public void keyReleased(KeyEvent e) { int i, j, bAddOption; String compareString, tempString; Vector compareVector; StringTokenizer st; if(e.getKeyCode() == e.VK_TAB || e.getKeyCode() == e.VK_DOWN) { // if tab is pressed, transfer control to the list box optionsList.requestFocus(); } else { // remove all the options optionsList.removeAll(); optionsNamesVector.removeAllElements(); optionsValuesVector.removeAllElements(); // disable redrawing temporarily optionsList.setVisible(false); // set the compare string if(isCaseSensitive == 0) { compareString = new String(tf.getText().toLowerCase()); } else { compareString = new String(tf.getText()); } // create the compare vector compareVector = new Vector(); st = new StringTokenizer(compareString, " "); i = 0; while (st.hasMoreTokens()) { tempString = new String(st.nextToken()); compareVector.addElement(tempString); i++; } // if not tokens found, then the add only the // full compare string if(i == 0) { compareVector.addElement(compareString); } // add back those that contain the new substring for(i = 0; i < namesVector.size(); i++) { bAddOption = 1; for(j = 0; j < compareVector.size(); j++) { if(((String)lowercaseNamesVector.elementAt(i)).indexOf((String)compareVector.elementAt(j)) == -1) { bAddOption = 0; } } // only add if all substrings were in the name if(bAddOption == 1) { // add the option to the options list //optionsList.add( (String)namesVector.elementAt(i) ); optionsNamesVector.addElement( (String)namesVector.elementAt(i) ); // add the corresponding value optionsValuesVector.addElement( (String)valuesVector.elementAt(i) ); } } // only add the visible options because adding options is really slow for(i = 0; i < optionsList.getRows() && i < optionsNamesVector.size(); i++) { optionsList.add( (String)optionsNamesVector.elementAt(i) ); } // select the first item if(optionsList.getItemCount() > 0) { optionsList.select(0); } // reenable redrawing optionsList.setVisible(true); } // end of else statment } /** * Do nothing. */ public void keyTyped(KeyEvent e) { } /** * Do nothing. */ public void keyPressed(KeyEvent e) {} /* // test timing function public void testOptionsSpeed() { long startTime, endTime; Long testLong = new Long(0); int i = 0, countOptions; Vector testVector = new Vector(); countOptions = optionsList.getItemCount(); startTime = System.currentTimeMillis(); optionsList.removeAll(); endTime = System.currentTimeMillis(); System.out.println("removed "+countOptions+" options in "+((endTime-startTime))+" milliseconds."); countOptions = 10000; startTime = System.currentTimeMillis(); optionsList.setVisible(false); for(i = 0; i < countOptions; i++) { optionsList.add( "test string: "+i); } optionsList.setVisible(true); endTime = System.currentTimeMillis(); System.out.println("added "+countOptions+" options in "+((endTime-startTime))+" milliseconds."); } */ }