package org.geogebra.desktop.gui.inputfield; import static java.awt.event.KeyEvent.VK_DOWN; import static java.awt.event.KeyEvent.VK_ENTER; import static java.awt.event.KeyEvent.VK_ESCAPE; import static java.awt.event.KeyEvent.VK_UP; import java.awt.Component; import java.awt.Dimension; import java.awt.Font; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionAdapter; import java.util.ArrayList; import javax.swing.BorderFactory; import javax.swing.DefaultListCellRenderer; import javax.swing.DefaultListModel; import javax.swing.JList; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.border.Border; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import org.geogebra.common.main.GeoGebraColorConstants; import org.geogebra.desktop.awt.GColorD; /** * Prepares and shows a JPopupMenu containing the history list for an * AutoCompleteTextField. Adapted from OptionsPopup. * * @author G. Sturr * */ public class HistoryPopupD implements ListSelectionListener { private AutoCompleteTextFieldD textField; private JPopupMenu popup; private JList historyList; private boolean isDownPopup = false; private KeyListener keyListener; private KeyListener[] textFieldKeyListeners; private DefaultListModel model; public HistoryPopupD(AutoCompleteTextFieldD autoCompleteField) { this.textField = autoCompleteField; historyList = new JList(); historyList.setCellRenderer(new HistoryListCellRenderer()); historyList.setBorder(BorderFactory.createEmptyBorder()); historyList.addListSelectionListener(this); historyList.setFocusable(false); popup = new JPopupMenu(); JScrollPane scroller = new JScrollPane(historyList); scroller.setBorder(BorderFactory.createEmptyBorder()); popup.add(scroller); popup.setFocusable(false); registerListeners(); } /** * Set the font to display the history commands * * @param font * the new font */ public void setFont(Font font) { historyList.setFont(font); } private class PopupListener implements PopupMenuListener { private KeyListener[] listListeners; @Override public void popupMenuCanceled(PopupMenuEvent e) { // nothing to do } @Override public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { textField.removeKeyListener(keyListener); for (KeyListener listener : textFieldKeyListeners) { textField.addKeyListener(listener); } } @Override public void popupMenuWillBecomeVisible(PopupMenuEvent e) { // Remove key listeners and replace with own; textFieldKeyListeners = textField.getKeyListeners(); for (KeyListener listener : textFieldKeyListeners) { textField.removeKeyListener(listener); } textField.addKeyListener(keyListener); listListeners = historyList.getKeyListeners(); for (KeyListener listener : listListeners) { historyList.removeKeyListener(listener); } historyList.addKeyListener(keyListener); } } private void registerListeners() { // add mouse motion listener to repaint the list for rollover effect historyList.addMouseMotionListener(new MouseMotionAdapter() { @Override public void mouseMoved(MouseEvent e) { historyList.repaint(); } }); // create key listener (will be added by PopupListener) keyListener = new KeyAdapter() { @Override public void keyPressed(KeyEvent e) { handleSpecialKeys(e); } }; // add mouse listener historyList.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { handleMouseClick(e); } }); popup.addPopupMenuListener(new PopupListener()); } public void showPopup() { // get the current history list and load it into the JList ArrayList<String> list = textField.getHistory(); if (list.isEmpty()) { return; } model = new DefaultListModel(); if (isDownPopup) { for (int i = 0; i < list.size(); i++) { model.addElement(list.get(list.size() - i - 1)); } } else { for (int i = 0; i < list.size(); i++) { model.addElement(list.get(i)); } } historyList.setModel(model); // set the visual features of the list int rowCount = Math.min(list.size(), 10); historyList.setVisibleRowCount(rowCount); if (isDownPopup) { historyList.setSelectedIndex(0); historyList.ensureIndexIsVisible(0); } else { historyList.setSelectedIndex(list.size() - 1); historyList.ensureIndexIsVisible(list.size() - 1); } popup.setPreferredSize(null); // set the width of the popup equal to the textfield width Dimension d = popup.getPreferredSize(); d.width = textField.getWidth(); popup.setPopupSize(d); // popup.pack(); // position the popup above/below the text field // with small vertical offset to compensate for the shadow in upwards // popups if (isDownPopup) { popup.show(textField, 0, textField.getPreferredSize().height); } else { popup.show(textField, 0, -popup.getPreferredSize().height - 4); } } public boolean isDownPopup() { return isDownPopup; } public void setDownPopup(boolean isDownPopup) { this.isDownPopup = isDownPopup; } private boolean isPopupVisible() { return popup.isVisible(); } private void hidePopup() { if (!isPopupVisible()) { return; } popup.setVisible(false); } /** * handles selection in the history popup; pastes the selected string into * the input field and hides the popup */ @Override public void valueChanged(ListSelectionEvent evt) { if (!evt.getValueIsAdjusting()) { if (evt.getSource() == historyList) { textField.setText((String) historyList.getSelectedValue()); // this.setVisible(false); } } } private void undoPopupChange() { DefaultListModel model1 = (DefaultListModel) historyList.getModel(); textField.setText((String) model1.getElementAt(model1.size() - 1)); } public void handleMouseClick(MouseEvent e) { // selection listener has handled text changes, so just exit after a // click hidePopup(); } public void handleSpecialKeys(KeyEvent keyEvent) { if (!isPopupVisible()) { return; } switch (keyEvent.getKeyCode()) { case VK_ESCAPE: // [ESC] cancel the popup and undo any changes undoPopupChange(); hidePopup(); keyEvent.consume(); break; case VK_ENTER: hidePopup(); keyEvent.consume(); break; case VK_DOWN: if (!isDownPopup && historyList .getSelectedIndex() == historyList.getModel().getSize() - 1) { hidePopup(); } else { navigateRelative(+1); } break; case VK_UP: if (isDownPopup && historyList.getSelectedIndex() == 0) { hidePopup(); } else { navigateRelative(-1); } break; default: hidePopup(); } } private void navigateRelative(int offset) { boolean up = offset < 0; int end = model.getSize() - 1; int index = historyList.getSelectedIndex(); // Wrap around if (-1 == index) { index = up ? end : 0; } else if (0 == index && up || end == index && !up) { index = -1; } else { index += offset; index = Math.max(0, Math.min(end, index)); } if (-1 == index) { historyList.clearSelection(); } else { historyList.setSelectedIndex(index); historyList.ensureIndexIsVisible(index); } } /** * custom cell renderer for the history list, draws grid lines * */ private static class HistoryListCellRenderer extends DefaultListCellRenderer { private static final long serialVersionUID = 1L; /* * private Color bgColor; private Color listSelectionBackground = * MyTable.SELECTED_BACKGROUND_COLOR; private Color listBackground = * Color.white; private Color rolloverBackground = Color.lightGray; */ // create grid lines with this border private Border gridBorder = BorderFactory.createCompoundBorder( BorderFactory.createMatteBorder(0, 0, 1, 0, GColorD.getAwtColor( GeoGebraColorConstants.TABLE_GRID_COLOR)), BorderFactory.createEmptyBorder(2, 5, 2, 5)); @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); setText((String) value); setBorder(gridBorder); /* * setForeground(Color.black); * * // paint roll-over row Point point = list.getMousePosition(); int * mouseOver = point==null ? -1 : list.locationToIndex(point); if * (index == mouseOver) bgColor = rolloverBackground; else bgColor = * listBackground; setBackground(bgColor); */ return this; } } }