/** * * Copyright * 2009-2015 Jayway Products AB * 2016-2017 Föreningen Sambruk * * Licensed under AGPL, Version 3.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.gnu.org/licenses/agpl.txt * * 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 se.streamsource.streamflow.client.util; import java.awt.event.ActionEvent; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import javax.swing.AbstractAction; import javax.swing.BoxLayout; import javax.swing.DefaultListModel; import javax.swing.InputVerifier; import javax.swing.JComponent; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.KeyStroke; import javax.swing.event.PopupMenuEvent; import javax.swing.event.PopupMenuListener; import se.streamsource.streamflow.client.util.StateBinder.Binding; public class SuggestTextField<T> extends JPanel { private static final long serialVersionUID = -2427927984739983590L; private int MAX_LIST_LENGTH = 10; private int MIN_LENGTH_FOR_SEARCH = 3; private JList list; private JPopupMenu popup = new JPopupMenu(); private DefaultListModel listModel; private JTextField textField; private SuggestModel<T> model; public SuggestTextField(SuggestModel<T> model) { this.model = model; initComponents(); } public SuggestTextField(SuggestModel<T> model, int maxListLength, int minLengthForSearch) { this( model ); MAX_LIST_LENGTH = maxListLength; MIN_LENGTH_FOR_SEARCH = minLengthForSearch; } public JTextField getTextField() { return textField; } private void initComponents() { setLayout( new BoxLayout( this, BoxLayout.PAGE_AXIS ) ); textField = new JTextField(); add( textField ); listModel = new DefaultListModel(); list = new JList(listModel); JScrollPane scroll = new JScrollPane( list ); list.setFocusable( false ); scroll.getVerticalScrollBar().setFocusable( false ); scroll.getHorizontalScrollBar().setFocusable( false ); popup.add( scroll ); textField.registerKeyboardAction( new ShowPopupAction(), KeyStroke.getKeyStroke( KeyEvent.VK_DOWN, 0 ), JComponent.WHEN_FOCUSED ); textField.registerKeyboardAction( new UpAction(), KeyStroke.getKeyStroke( KeyEvent.VK_UP, 0 ), JComponent.WHEN_FOCUSED ); textField.registerKeyboardAction( new HidePopupAction(), KeyStroke.getKeyStroke( KeyEvent.VK_ESCAPE, 0 ), JComponent.WHEN_FOCUSED ); textField.addKeyListener( new KeyListener() { public void keyTyped(KeyEvent e){} public void keyPressed(KeyEvent e) {} public void keyReleased(KeyEvent e) { if (e.getKeyCode() != KeyEvent.VK_DOWN && e.getKeyCode() != KeyEvent.VK_UP && e.getKeyCode() != KeyEvent.VK_ESCAPE && e.getKeyCode() != KeyEvent.VK_ENTER) { if (textField.getText().length() >= MIN_LENGTH_FOR_SEARCH) showPopup(); else popup.setVisible( false ); } } }); textField.addFocusListener( new FocusListener() { public void focusLost(FocusEvent e) { if (!e.isTemporary()) { popup.setVisible( false ); handleSaveAction(textField.getText()); } } public void focusGained(FocusEvent e) { textField.setSelectionStart( textField.getCaretPosition() ); } }); popup.addPopupMenuListener( new PopupMenuListener() { public void popupMenuWillBecomeVisible(PopupMenuEvent e){} public void popupMenuWillBecomeInvisible(PopupMenuEvent e) { textField.unregisterKeyboardAction( KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 ) ); } public void popupMenuCanceled(PopupMenuEvent e){} } ); list.setRequestFocusEnabled( false ); } public void setBinding( final Binding binding ) { textField.setInputVerifier( new InputVerifier() { @Override public boolean verify(JComponent input) { if (!popup.isVisible()) { binding.updateProperty( textField.getText() ); return true; } return false; } }); } @SuppressWarnings("serial") private class ShowPopupAction extends AbstractAction { public void actionPerformed(ActionEvent e) { if (textField.isEnabled()) { if (popup.isVisible()) selectNextValue(); else showPopup(); } } } @SuppressWarnings("serial") private class AcceptAction extends AbstractAction { public void actionPerformed(ActionEvent e) { handleAcceptAction( model.valueAt( list.getSelectedIndex() ) ); popup.setVisible( false ); } } @SuppressWarnings("serial") private class HidePopupAction extends AbstractAction { public void actionPerformed(ActionEvent e) { popup.setVisible( false ); } } @SuppressWarnings("serial") private class UpAction extends AbstractAction { public void actionPerformed(ActionEvent e) { if (textField.isEnabled()) { if (popup.isVisible()) selectPreviousValue(); } } }; private void showPopup() { listModel.clear(); for (T item : model.options( textField.getText() )) { listModel.addElement( model.displayValue( item ) ); } if (textField.isEnabled() && list.getModel().getSize() != 0) { textField.registerKeyboardAction( new AcceptAction(), KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0 ), JComponent.WHEN_FOCUSED ); int size = list.getModel().getSize(); list.setVisibleRowCount( size < MAX_LIST_LENGTH ? size : MAX_LIST_LENGTH ); popup.pack(); if (!popup.isVisible()) { popup.show( textField, 2, textField.getHeight() ); } } else { popup.setVisible( false ); } textField.requestFocusInWindow(); } public void handleAcceptAction(T selectedItem) { textField.setText( model.displayValue( selectedItem )); } public void handleSaveAction(String text){ }; private void selectNextValue() { int index = list.getSelectedIndex(); if (index < list.getModel().getSize() - 1) { list.setSelectedIndex( index + 1 ); list.ensureIndexIsVisible( index + 1 ); } } private void selectPreviousValue() { int index = list.getSelectedIndex(); if (index > 0) { list.setSelectedIndex( index - 1 ); list.ensureIndexIsVisible( index - 1 ); } } }