/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.mvnsearch.snippet.plugin.util;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.JTextComponent;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
/**
* auto completer
*/
public abstract class AutoCompleter {
protected JList list = new JList();
protected JPopupMenu popup = new JPopupMenu();
protected JTextComponent textComp;
private static final String AUTOCOMPLETER = "AUTOCOMPLETER"; //NOI18N
/**
* construct completion on text component
*
* @param comp text component
*/
public AutoCompleter(JTextComponent comp) {
textComp = comp;
textComp.putClientProperty(AUTOCOMPLETER, this);
JScrollPane scroll = new JScrollPane(list);
scroll.setBorder(null);
list.setFocusable(false);
scroll.getVerticalScrollBar().setFocusable(false);
scroll.getHorizontalScrollBar().setFocusable(false);
popup.setBorder(BorderFactory.createLineBorder(Color.black));
popup.add(scroll);
if (textComp instanceof JTextField) {
textComp.registerKeyboardAction(showAction, KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0), JComponent.WHEN_FOCUSED);
textComp.getDocument().addDocumentListener(documentListener);
} else {
textComp.registerKeyboardAction(showAction, KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, KeyEvent.CTRL_MASK), JComponent.WHEN_FOCUSED);
}
textComp.registerKeyboardAction(upAction, KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0), JComponent.WHEN_FOCUSED);
textComp.registerKeyboardAction(hidePopupAction, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_FOCUSED);
popup.addPopupMenuListener(new PopupMenuListener() {
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
}
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
textComp.unregisterKeyboardAction(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0));
}
public void popupMenuCanceled(PopupMenuEvent e) {
}
});
list.setRequestFocusEnabled(false);
}
static Action acceptAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
JComponent tf = (JComponent) e.getSource();
AutoCompleter completer = (AutoCompleter) tf.getClientProperty(AUTOCOMPLETER);
completer.popup.setVisible(false);
completer.acceptedListItem((String) completer.list.getSelectedValue());
}
};
DocumentListener documentListener = new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
showPopup();
}
public void removeUpdate(DocumentEvent e) {
showPopup();
}
public void changedUpdate(DocumentEvent e) {
}
};
private void showPopup() {
if (!textComp.isShowing()) return;
popup.setVisible(false);
if (textComp.isEnabled() && updateListData() && list.getModel().getSize() != 0) {
if (!(textComp instanceof JTextField))
textComp.getDocument().addDocumentListener(documentListener);
textComp.registerKeyboardAction(acceptAction, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), JComponent.WHEN_FOCUSED);
int size = list.getModel().getSize();
list.setVisibleRowCount(size < 10 ? size : 10);
int x = 0;
try {
int pos = Math.min(textComp.getCaret().getDot(), textComp.getCaret().getMark());
x = textComp.getUI().modelToView(textComp, pos).x;
} catch (BadLocationException e) {
e.printStackTrace();
}
popup.show(textComp, x, textComp.getHeight());
} else
popup.setVisible(false);
textComp.requestFocus();
}
static Action showAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
JComponent tf = (JComponent) e.getSource();
AutoCompleter completer = (AutoCompleter) tf.getClientProperty(AUTOCOMPLETER);
if (tf.isEnabled()) {
if (completer.popup.isVisible())
completer.selectNextPossibleValue();
else
completer.showPopup();
}
}
};
static Action upAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
JComponent tf = (JComponent) e.getSource();
AutoCompleter completer = (AutoCompleter) tf.getClientProperty(AUTOCOMPLETER);
if (tf.isEnabled()) {
if (completer.popup.isVisible())
completer.selectPreviousPossibleValue();
}
}
};
static Action hidePopupAction = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
JComponent tf = (JComponent) e.getSource();
AutoCompleter completer = (AutoCompleter) tf.getClientProperty(AUTOCOMPLETER);
if (tf.isEnabled())
completer.popup.setVisible(false);
}
};
/**
* Selects the next item in the list. It won't change the selection if the
* currently selected item is already the last item.
*/
protected void selectNextPossibleValue() {
int si = list.getSelectedIndex();
if (si < list.getModel().getSize() - 1) {
list.setSelectedIndex(si + 1);
list.ensureIndexIsVisible(si + 1);
}
}
/**
* Selects the previous item in the list. It won't change the selection if the
* currently selected item is already the first item.
*/
protected void selectPreviousPossibleValue() {
int si = list.getSelectedIndex();
if (si > 0) {
list.setSelectedIndex(si - 1);
list.ensureIndexIsVisible(si - 1);
}
}
/**
* update list model depending on the data in textfield
*
* @return data updated or not
*/
protected abstract boolean updateListData();
/**
* user has selected some item in the list. update textfield accordingly
*
* @param selected item
*/
protected abstract void acceptedListItem(String selected);
}