/** * 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.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.swing.JList; import net.java.dev.spellcast.utilities.LockableListModel; import net.java.dev.spellcast.utilities.LockableListModel.ListElementFilter; import net.sourceforge.kolmafia.AdventureResult; import net.sourceforge.kolmafia.KoLAdventure; import net.sourceforge.kolmafia.objectpool.Concoction; import net.sourceforge.kolmafia.persistence.ConcoctionDatabase.QueuedConcoction; import net.sourceforge.kolmafia.persistence.FaxBotDatabase.Monster; import net.sourceforge.kolmafia.persistence.ItemDatabase; import net.sourceforge.kolmafia.persistence.Script; import net.sourceforge.kolmafia.request.CreateItemRequest; import net.sourceforge.kolmafia.session.StoreManager.SoldItem; import net.sourceforge.kolmafia.session.StoreManager.StoreLogEntry; import net.sourceforge.kolmafia.utilities.LowerCaseEntry; import net.sourceforge.kolmafia.utilities.StringUtilities; public class AutoFilterTextField extends AutoHighlightTextField implements ActionListener, ListElementFilter { protected JList list; protected String text; protected LockableListModel model; protected boolean strict; protected int quantity; protected int price; protected boolean qtyChecked; protected boolean qtyEQ, qtyLT, qtyGT; protected boolean asChecked; protected boolean asEQ, asLT, asGT; protected boolean notChecked; private static final Pattern QTYSEARCH_PATTERN = Pattern.compile( "\\s*#\\s*([<=>]+)\\s*([\\d,]+)\\s*" ); private static final Pattern ASSEARCH_PATTERN = Pattern.compile( "\\s*\\p{Sc}\\s*([<=>]+)\\s*([\\d,]+)\\s*" ); private static final Pattern NOTSEARCH_PATTERN = Pattern.compile( "\\s*!\\s*=\\s*(.+)\\s*" ); public AutoFilterTextField( final JList list ) { this.setList( list ); this.addKeyListener( new FilterListener() ); // Make this look like a normal search field on OS X. // Note that the field MUST NOT be forced to a height other than its // preferred height; that produces some ugly visual glitches. this.putClientProperty( "JTextField.variant", "search" ); } public AutoFilterTextField( final JList list, Object initial ) { this.setList( list ); this.addKeyListener( new FilterListener() ); // Make this look like a normal search field on OS X. // Note that the field MUST NOT be forced to a height other than its // preferred height; that produces some ugly visual glitches. this.putClientProperty( "JTextField.variant", "search" ); if ( initial != null ) { this.setText( initial.toString() ); } } public AutoFilterTextField( LockableListModel displayModel ) { this.addKeyListener( new FilterListener() ); this.model = displayModel; this.model.setFilter( this ); // Make this look like a normal search field on OS X. // Note that the field MUST NOT be forced to a height other than its // preferred height; that produces some ugly visual glitches. this.putClientProperty( "JTextField.variant", "search" ); } public void setList( final JList list ) { this.list = list; this.model = (LockableListModel) list.getModel(); this.model.setFilter( this ); this.list.clearSelection(); } public void setModel( final LockableListModel model ) { this.model = model; this.model.setFilter( this ); } public void actionPerformed( final ActionEvent e ) { this.update(); } @Override public void setText( final String text ) { super.setText( text ); this.update(); } public boolean isVisible( final Object element ) { if ( this.qtyChecked ) { int qty = AutoFilterTextField.getResultQuantity( element ); if ( ( qty == this.quantity && !this.qtyEQ ) || ( qty < this.quantity && !this.qtyLT ) || ( qty > this.quantity && !this.qtyGT ) ) { return false; } } if ( this.asChecked ) { int as = AutoFilterTextField.getResultPrice( element ); if ( ( as == this.price && !this.asEQ ) || ( as < this.price && !this.asLT ) || ( as > this.price && !this.asGT ) ) { return false; } } if ( this.text == null || this.text.length() == 0 ) { return true; } // If it's not a result, then check to see if you need to // filter based on its string form. String elementName = AutoFilterTextField.getResultName( element ); if ( this.notChecked ) { return elementName.indexOf( this.text ) == -1; } return this.strict ? elementName.indexOf( this.text ) != -1 : StringUtilities.fuzzyMatches( elementName, this.text ); } public static final String getResultName( final Object element ) { if ( element == null ) { return ""; } if ( element instanceof AdventureResult ) { return ( (AdventureResult) element ).getName().toLowerCase(); } if ( element instanceof CreateItemRequest ) { return ( (CreateItemRequest) element ).getName().toLowerCase(); } if ( element instanceof Concoction ) { return ( (Concoction) element ).getName().toLowerCase(); } if ( element instanceof QueuedConcoction ) { return ( (QueuedConcoction) element ).getName().toLowerCase(); } if ( element instanceof SoldItem ) { return ( (SoldItem) element ).getItemName().toLowerCase(); } if ( element instanceof StoreLogEntry ) { return ( (StoreLogEntry) element ).toString().toLowerCase(); } if ( element instanceof LowerCaseEntry ) { return ( (LowerCaseEntry) element ).getLowerCase(); } if ( element instanceof KoLAdventure ) { return ( (KoLAdventure) element ).toLowerCaseString(); } if ( element instanceof Monster ) { return ( (Monster) element ).toLowerCaseString(); } if ( element instanceof Script ) { return ( (Script) element ).getScriptName().toLowerCase(); } return element.toString(); } public static final int getResultPrice( final Object element ) { if ( element == null ) { return -1; } if ( element instanceof AdventureResult ) { return ItemDatabase.getPriceById( ( (AdventureResult) element ).getItemId() ); } return -1; } public static final int getResultQuantity( final Object element ) { if ( element == null ) { return -1; } if ( element instanceof AdventureResult ) { return ( (AdventureResult) element ).getCount(); } if ( element instanceof CreateItemRequest ) { return ( (CreateItemRequest) element ).getQuantityPossible(); } if ( element instanceof Concoction ) { return ( (Concoction) element ).getAvailable(); } if ( element instanceof SoldItem ) { return ( (SoldItem) element ).getQuantity(); } if ( element instanceof LowerCaseEntry ) { // no meaningful integer fields return -1; } if ( element instanceof KoLAdventure ) { return StringUtilities.parseInt( ( (KoLAdventure) element ).getAdventureId() ); } return -1; } public void update() { try { AutoFilterTextField.this.qtyChecked = false; AutoFilterTextField.this.asChecked = false; AutoFilterTextField.this.notChecked = false; AutoFilterTextField.this.text = AutoFilterTextField.this.getText().toLowerCase(); Matcher mqty = AutoFilterTextField.QTYSEARCH_PATTERN.matcher( AutoFilterTextField.this.text ); if ( mqty.find() ) { AutoFilterTextField.this.qtyChecked = true; AutoFilterTextField.this.quantity = StringUtilities.parseInt( mqty.group( 2 ) ); String op = mqty.group( 1 ); AutoFilterTextField.this.qtyEQ = op.indexOf( "=" ) != -1; AutoFilterTextField.this.qtyLT = op.indexOf( "<" ) != -1; AutoFilterTextField.this.qtyGT = op.indexOf( ">" ) != -1; AutoFilterTextField.this.text = mqty.replaceFirst( "" ); } Matcher mas = AutoFilterTextField.ASSEARCH_PATTERN.matcher( AutoFilterTextField.this.text ); if ( mas.find() ) { AutoFilterTextField.this.asChecked = true; AutoFilterTextField.this.price = StringUtilities.parseInt( mas.group( 2 ) ); String op = mas.group( 1 ); AutoFilterTextField.this.asEQ = op.indexOf( "=" ) != -1; AutoFilterTextField.this.asLT = op.indexOf( "<" ) != -1; AutoFilterTextField.this.asGT = op.indexOf( ">" ) != -1; AutoFilterTextField.this.text = mas.replaceFirst( "" ); } Matcher mnot = AutoFilterTextField.NOTSEARCH_PATTERN.matcher( AutoFilterTextField.this.text ); if ( mnot.find() ) { AutoFilterTextField.this.notChecked = true; AutoFilterTextField.this.text = mnot.group( 1 ); } AutoFilterTextField.this.strict = true; AutoFilterTextField.this.model.updateFilter( false ); if ( AutoFilterTextField.this.model.getSize() == 0 ) { AutoFilterTextField.this.strict = false; AutoFilterTextField.this.model.updateFilter( false ); } if ( AutoFilterTextField.this.list != null ) { JList list = AutoFilterTextField.this.list; if ( AutoFilterTextField.this.model.getSize() == 1 ) { list.setSelectedIndex( 0 ); } else if ( list.getSelectedIndices().length == 1 ) { list.ensureIndexIsVisible( list.getSelectedIndex() ); } else { list.clearSelection(); } } } finally { if ( AutoFilterTextField.this.model.size() > 0 ) { AutoFilterTextField.this.model.fireContentsChanged( AutoFilterTextField.this.model, 0, AutoFilterTextField.this.model.size() - 1 ); } } } private class FilterListener extends KeyAdapter { @Override public void keyReleased( final KeyEvent e ) { AutoFilterTextField.this.update(); } } }