/** * Copyright (c) 2005-2017, KoLmafia development team * http://kolmafia.sourceforge.net/ * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * [1] Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * [2] Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * [3] Neither the name "KoLmafia" nor the names of its contributors may * be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package net.sourceforge.kolmafia.swingui.widget; import java.awt.event.FocusEvent; import java.awt.event.FocusListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import javax.swing.JComboBox; import javax.swing.text.JTextComponent; import net.java.dev.spellcast.utilities.LockableListModel; import net.java.dev.spellcast.utilities.LockableListModel.ListElementFilter; import net.sourceforge.kolmafia.utilities.StringUtilities; import net.sourceforge.kolmafia.swingui.widget.DisabledItemsComboBox; public class AutoFilterComboBox extends DisabledItemsComboBox implements ListElementFilter { private int currentIndex = -1; private boolean isRecentFocus = false; private String currentName; private String matchString; public Object currentMatch; private LockableListModel model; private boolean allowAdditions; private boolean active, strict; private final JTextComponent editor; public AutoFilterComboBox( final LockableListModel model, final boolean allowAdditions ) { this.setModel( model ); this.setEditable( true ); this.allowAdditions = allowAdditions; NameInputListener listener = new NameInputListener(); this.addItemListener( listener ); this.editor = (JTextComponent) this.getEditor().getEditorComponent(); this.editor.addFocusListener( listener ); this.editor.addKeyListener( listener ); } public String getText() { return (String) ( this.getSelectedItem() != null ? this.getSelectedItem() : this.currentMatch ); } public void setText( final String text ) { if ( this.model.indexOf( text ) != -1 ) { this.setSelectedItem( text ); } else { this.setSelectedItem( null ); this.currentMatch = text; this.editor.setText( text ); } } public void setModel( final LockableListModel model ) { super.setModel( model ); this.model = model; this.model.setFilter( this ); } public void forceAddition() { if ( this.currentName == null || this.currentName.length() == 0 ) { return; } if ( this.currentMatch == null && this.allowAdditions && !this.model.contains( this.currentName ) ) { this.model.add( this.currentName ); } this.setSelectedItem( this.currentName ); } private void update() { if ( this.currentName == null ) { return; } this.isRecentFocus = false; this.currentIndex = -1; this.model.setSelectedItem( null ); this.active = true; this.matchString = this.currentName.toLowerCase(); this.strict = true; this.model.updateFilter( false ); if ( this.model.getSize() > 0 ) { return; } this.strict = false; this.model.updateFilter( false ); } public synchronized void findMatch( final int keyCode ) { this.currentName = this.getEditor().getItem().toString(); int caretPosition = this.editor.getCaretPosition(); if ( !this.allowAdditions && this.model.contains( this.currentName ) ) { this.setSelectedItem( this.currentName ); return; } this.currentMatch = null; this.update(); if ( this.allowAdditions ) { if ( this.model.getSize() != 1 || keyCode == KeyEvent.VK_BACK_SPACE || keyCode == KeyEvent.VK_DELETE ) { this.editor.setText( this.currentName ); this.editor.setCaretPosition( caretPosition ); return; } this.currentMatch = this.model.getElementAt( 0 ); this.matchString = this.currentMatch.toString().toLowerCase(); this.editor.setText( this.currentMatch.toString() ); this.editor.moveCaretPosition( caretPosition ); return; } this.editor.setText( this.currentName ); if ( !this.isPopupVisible() ) { this.showPopup(); } } public boolean isVisible( final Object element ) { if ( !this.active ) { return true; } // If it's not a result, then check to see if you need to // filter based on its string form. if ( this.matchString == null || this.matchString.length() == 0 ) { return true; } String elementName = element.toString().toLowerCase(); return this.allowAdditions ? elementName.startsWith( this.matchString ) : this.strict ? elementName.indexOf( this.matchString ) != -1 : StringUtilities.fuzzyMatches( elementName, this.matchString ); } private class NameInputListener extends KeyAdapter implements FocusListener, ItemListener { @Override public void keyReleased( final KeyEvent e ) { if ( e.getKeyCode() == KeyEvent.VK_DOWN ) { if ( !AutoFilterComboBox.this.isRecentFocus && AutoFilterComboBox.this.currentIndex + 1 < AutoFilterComboBox.this.model.getSize() ) { AutoFilterComboBox.this.currentMatch = AutoFilterComboBox.this.model.getElementAt( ++AutoFilterComboBox.this.currentIndex ); } AutoFilterComboBox.this.isRecentFocus = false; } else if ( e.getKeyCode() == KeyEvent.VK_UP ) { if ( !AutoFilterComboBox.this.isRecentFocus && AutoFilterComboBox.this.model.getSize() > 0 && AutoFilterComboBox.this.currentIndex > 0 ) { AutoFilterComboBox.this.currentMatch = AutoFilterComboBox.this.model.getElementAt( --AutoFilterComboBox.this.currentIndex ); } AutoFilterComboBox.this.isRecentFocus = false; } else if ( e.getKeyCode() == KeyEvent.VK_ENTER || e.getKeyCode() == KeyEvent.VK_TAB ) { this.focusLost( null ); } else if ( e.getKeyChar() != KeyEvent.CHAR_UNDEFINED ) { AutoFilterComboBox.this.findMatch( e.getKeyCode() ); } } public final void itemStateChanged( final ItemEvent e ) { AutoFilterComboBox.this.currentMatch = AutoFilterComboBox.this.getSelectedItem(); if ( AutoFilterComboBox.this.currentMatch == null ) { return; } AutoFilterComboBox.this.currentName = AutoFilterComboBox.this.currentMatch.toString(); if ( !AutoFilterComboBox.this.isPopupVisible() ) { AutoFilterComboBox.this.active = false; AutoFilterComboBox.this.model.updateFilter( false ); } } public final void focusGained( final FocusEvent e ) { AutoFilterComboBox.this.getEditor().selectAll(); AutoFilterComboBox.this.isRecentFocus = true; AutoFilterComboBox.this.currentIndex = AutoFilterComboBox.this.model.getSelectedIndex(); } public final void focusLost( final FocusEvent e ) { if ( AutoFilterComboBox.this.currentMatch != null ) { AutoFilterComboBox.this.setSelectedItem( AutoFilterComboBox.this.currentMatch ); } else if ( AutoFilterComboBox.this.currentName != null && AutoFilterComboBox.this.currentName.trim().length() != 0 ) { AutoFilterComboBox.this.forceAddition(); } } } }