/**
* 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;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import javax.swing.Box;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import net.java.dev.spellcast.utilities.JComponentUtilities;
import net.java.dev.spellcast.utilities.SortedListModel;
import net.sourceforge.kolmafia.KoLCharacter;
import net.sourceforge.kolmafia.KoLConstants;
import net.sourceforge.kolmafia.KoLConstants.MafiaState;
import net.sourceforge.kolmafia.KoLmafia;
import net.sourceforge.kolmafia.KoLmafiaGUI;
import net.sourceforge.kolmafia.RequestThread;
import net.sourceforge.kolmafia.SpecialOutfit;
import net.sourceforge.kolmafia.preferences.Preferences;
import net.sourceforge.kolmafia.request.MallSearchRequest;
import net.sourceforge.kolmafia.request.PurchaseRequest;
import net.sourceforge.kolmafia.swingui.listener.DefaultComponentFocusTraversalPolicy;
import net.sourceforge.kolmafia.swingui.panel.GenericPanel;
import net.sourceforge.kolmafia.swingui.widget.AutoFilterComboBox;
import net.sourceforge.kolmafia.swingui.widget.AutoHighlightTextField;
import net.sourceforge.kolmafia.swingui.widget.GenericScrollPane;
import net.sourceforge.kolmafia.swingui.widget.ShowDescriptionList;
import net.sourceforge.kolmafia.utilities.InputFieldUtilities;
public class MallSearchFrame
extends GenericPanelFrame
{
public static SortedListModel<PurchaseRequest> results;
private static MallSearchFrame INSTANCE = null;
private static final SortedListModel<PurchaseRequest> pastSearches = new SortedListModel<PurchaseRequest>();
private boolean currentlySearching;
private boolean currentlyBuying;
private ShowDescriptionList resultsList;
private final MallSearchPanel mallSearch;
public MallSearchFrame()
{
super( "Purchases" );
this.mallSearch = new MallSearchPanel();
this.setContentPanel( this.mallSearch );
MallSearchFrame.INSTANCE = this;
}
@Override
public JTabbedPane getTabbedPane()
{
return null;
}
public static void updateMeat()
{
if ( MallSearchFrame.INSTANCE != null )
{
MallSearchFrame.INSTANCE.mallSearch.setBalance();
}
}
/**
* An internal class which represents the panel used for mall searches in the <code>AdventureFrame</code>.
*/
private class MallSearchPanel
extends GenericPanel
implements FocusListener
{
private final JComponent searchField;
private final AutoHighlightTextField countField;
private final JCheckBox forceSortingCheckBox;
private final JCheckBox limitPurchasesCheckBox;
private final JLabel inventoryBalanceLabel;
private final JLabel storageBalanceLabel;
public MallSearchPanel()
{
super( "search", "purchase", "cancel", new Dimension( 100, 20 ), new Dimension( 250, 20 ) );
this.searchField =
Preferences.getBoolean( "cacheMallSearches" ) ?
(JComponent) new AutoFilterComboBox( MallSearchFrame.pastSearches, true ) :
(JComponent) new AutoHighlightTextField();
this.countField = new AutoHighlightTextField();
this.forceSortingCheckBox = new JCheckBox();
this.limitPurchasesCheckBox = new JCheckBox();
MallSearchFrame.results = new SortedListModel<PurchaseRequest>();
JPanel checkBoxPanels = new JPanel();
checkBoxPanels.add( Box.createHorizontalStrut( 20 ) );
checkBoxPanels.add( new JLabel( "Force Sort: " ), "" );
checkBoxPanels.add( this.forceSortingCheckBox );
checkBoxPanels.add( Box.createHorizontalStrut( 20 ) );
checkBoxPanels.add( new JLabel( "Limit Purchases: " ), "" );
checkBoxPanels.add( this.limitPurchasesCheckBox );
checkBoxPanels.add( Box.createHorizontalStrut( 20 ) );
this.limitPurchasesCheckBox.setSelected( true );
JPanel balancePanel = new JPanel( new GridLayout( 1, 2 ) );
this.inventoryBalanceLabel = new JLabel( "", JLabel.CENTER );
this.inventoryBalanceLabel.setForeground( Color.BLACK );
this.storageBalanceLabel = new JLabel( "", JLabel.CENTER );
this.storageBalanceLabel.setForeground( Color.BLUE );
balancePanel.add( this.inventoryBalanceLabel );
balancePanel.add( this.storageBalanceLabel );
this.setBalance();
VerifiableElement[] elements = new VerifiableElement[ 4 ];
elements[ 0 ] = new VerifiableElement( "Item to Find: ", this.searchField );
elements[ 1 ] = new VerifiableElement( "Search Limit: ", this.countField );
elements[ 2 ] = new VerifiableElement( " ", checkBoxPanels, false );
elements[ 3 ] = new VerifiableElement( "", balancePanel, false );
int searchCount = Preferences.getInteger( "defaultLimit" );
this.countField.setText( searchCount <= 0 ? "5" : String.valueOf( searchCount ) );
this.setContent( elements );
this.add( new SearchResultsPanel(), BorderLayout.CENTER );
MallSearchFrame.this.currentlySearching = false;
MallSearchFrame.this.currentlyBuying = false;
this.setFocusCycleRoot( true );
this.setFocusTraversalPolicy( new DefaultComponentFocusTraversalPolicy( this.searchField ) );
this.addFocusListener( this );
}
public void setBalance()
{
if ( KoLCharacter.canInteract() )
{
this.inventoryBalanceLabel.setText( "" );
this.storageBalanceLabel.setText( "" );
return;
}
StringBuilder buffer = new StringBuilder();
buffer.append( "Meat in inventory: " );
buffer.append( KoLConstants.COMMA_FORMAT.format( KoLCharacter.getAvailableMeat() ) );
this.inventoryBalanceLabel.setText( buffer.toString() );
buffer.setLength( 0 );
buffer.append( "Meat in storage: " );
buffer.append( KoLConstants.COMMA_FORMAT.format( KoLCharacter.getStorageMeat() ) );
this.storageBalanceLabel.setText( buffer.toString() );
}
public void focusGained( FocusEvent e )
{
this.searchField.requestFocus();
}
public void focusLost( FocusEvent e )
{
}
@Override
public void actionConfirmed()
{
int searchCount = InputFieldUtilities.getValue( this.countField, 0 );
if ( searchCount > 0 )
{
Preferences.setInteger( "defaultLimit", searchCount );
}
PurchaseRequest.setUsePriceComparison( this.forceSortingCheckBox.isSelected() );
String searchText = null;
if ( this.searchField instanceof AutoHighlightTextField )
{
searchText = ( (AutoHighlightTextField) this.searchField ).getText();
}
else
{
( (AutoFilterComboBox) this.searchField ).forceAddition();
searchText = (String) ( (AutoFilterComboBox) this.searchField ).getSelectedItem();
}
MallSearchFrame.this.currentlySearching = true;
MallSearchFrame.searchMall( new MallSearchRequest( searchText, searchCount, MallSearchFrame.results, false ) );
MallSearchFrame.this.currentlySearching = false;
this.searchField.requestFocus();
}
@Override
public void actionCancelled()
{
if ( MallSearchFrame.this.currentlySearching )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "Search stopped." );
return;
}
if ( MallSearchFrame.this.currentlyBuying )
{
KoLmafia.updateDisplay( MafiaState.ERROR, "Purchases stopped." );
return;
}
PurchaseRequest[] purchases = MallSearchFrame.this.resultsList.getSelectedPurchases();
if ( purchases == null || purchases.length == 0 )
{
this.setStatusMessage( "Please select a store from which to purchase." );
return;
}
int defaultPurchases = 0;
for ( int i = 0; i < purchases.length; ++i )
{
defaultPurchases +=
purchases[ i ].getQuantity() == PurchaseRequest.MAX_QUANTITY ? PurchaseRequest.MAX_QUANTITY : purchases[ i ].getLimit();
}
int count = defaultPurchases;
if ( this.limitPurchasesCheckBox.isSelected() || defaultPurchases >= 1000 )
{
Integer value = InputFieldUtilities.getQuantity( "Maximum number of items to purchase?", defaultPurchases, 1 );
count = ( value == null ) ? 0 : value.intValue();
}
if ( count == 0 )
{
return;
}
MallSearchFrame.this.currentlyBuying = true;
SpecialOutfit.createImplicitCheckpoint();
KoLmafia.makePurchases( MallSearchFrame.results, purchases, count, false, 0 );
SpecialOutfit.restoreImplicitCheckpoint();
MallSearchFrame.this.currentlyBuying = false;
}
}
public static final void searchMall( final MallSearchRequest request )
{
if ( MallSearchFrame.INSTANCE == null )
{
KoLmafiaGUI.constructFrame( "MallSearchFrame" );
}
MallSearchFrame.results.clear();
request.setResults( MallSearchFrame.results );
RequestThread.postRequest( request );
}
private String getPurchaseSummary( final Object[] purchases )
{
if ( purchases == null || purchases.length == 0 )
{
return "";
}
long totalPrice = 0;
int totalPurchases = 0;
PurchaseRequest currentPurchase = null;
for ( int i = 0; i < purchases.length; ++i )
{
currentPurchase = (PurchaseRequest) purchases[ i ];
totalPurchases += currentPurchase.getLimit();
totalPrice += (long) currentPurchase.getLimit() * (long) currentPurchase.getPrice();
}
return KoLConstants.COMMA_FORMAT.format( totalPurchases ) + " " +
currentPurchase.getItem().getPluralName( totalPurchases ) + " for " +
KoLConstants.COMMA_FORMAT.format( totalPrice ) + " " +
currentPurchase.getCurrency( totalPrice );
}
/**
* An internal class which represents the panel used for tallying the results of the mall search request. Note that
* all of the tallying functionality is handled by the <code>LockableListModel</code> provided, so this functions
* as a container for that list model.
*/
private class SearchResultsPanel
extends JPanel
{
public SearchResultsPanel()
{
super( new BorderLayout() );
JPanel resultsPanel = new JPanel( new BorderLayout() );
resultsPanel.add( JComponentUtilities.createLabel(
"Search Results", SwingConstants.CENTER, Color.black, Color.white ), BorderLayout.NORTH );
MallSearchFrame.this.resultsList = new ShowDescriptionList( MallSearchFrame.results );
MallSearchFrame.this.resultsList.setSelectionMode( ListSelectionModel.MULTIPLE_INTERVAL_SELECTION );
MallSearchFrame.this.resultsList.setPrototypeCellValue( "ABCDEFGHIJKLMNOPQRSTUVWXYZ" );
MallSearchFrame.this.resultsList.setVisibleRowCount( 11 );
MallSearchFrame.this.resultsList.addListSelectionListener( new PurchaseSelectListener() );
this.add( new GenericScrollPane( MallSearchFrame.this.resultsList ), BorderLayout.CENTER );
}
/**
* An internal listener class which detects which values are selected in the search results panel.
*/
private class PurchaseSelectListener
implements ListSelectionListener
{
public void valueChanged( final ListSelectionEvent e )
{
if ( e.getValueIsAdjusting() )
{
return;
}
// Reset the status message on this panel to
// show what the current state of the selections
// is at this time.
if ( !MallSearchFrame.this.currentlyBuying )
{
MallSearchFrame.this.mallSearch.setStatusMessage( MallSearchFrame.this.getPurchaseSummary( MallSearchFrame.this.resultsList.getSelectedValues() ) );
}
}
}
}
}