/**
* 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.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.LayoutManager;
import java.awt.Point;
import java.awt.datatransfer.StringSelection;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.image.AreaAveragingScaleFilter;
import java.awt.image.FilteredImageSource;
import java.awt.image.PixelGrabber;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import net.java.dev.spellcast.utilities.JComponentUtilities;
import net.sourceforge.kolmafia.KoLmafia;
import net.sourceforge.kolmafia.RequestThread;
import net.sourceforge.kolmafia.objectpool.IntegerPool;
import net.sourceforge.kolmafia.request.TrophyRequest;
import net.sourceforge.kolmafia.request.TrophyRequest.Trophy;
import net.sourceforge.kolmafia.swingui.button.InvocationButton;
import net.sourceforge.kolmafia.utilities.FileUtilities;
public class TrophyFrame
extends GenericFrame
{
public TrophyFrame()
{
super( "Trophy Arranger" );
this.setCenterComponent( new TrophyArrangePanel() );
}
public static class TrophyArrangePanel
extends JPanel
{
private TrophyPanel shownList, hiddenList;
public TrophyArrangePanel()
{
this.setLayout( new BorderLayout() );
JPanel eastPanel = new JPanel( new BorderLayout() );
this.add( eastPanel, BorderLayout.EAST );
JPanel buttonPanel = new JPanel( new GridLayout( 0, 1, 5, 5 ) );
eastPanel.add( buttonPanel, BorderLayout.NORTH );
buttonPanel.add( new InvocationButton( "refresh", this, "doRefresh" ) );
buttonPanel.add( new InvocationButton( "save", this, "doSave" ) );
buttonPanel.add( new InvocationButton( "show all", this, "doShowAll" ) );
buttonPanel.add( new InvocationButton( "hide all", this, "doHideAll" ) );
buttonPanel.add( new InvocationButton( "autosort", this, "doAutoSort" ) );
eastPanel.add( new JLabel( "<html><center>top list:<br>visible<hr>bottom:<br>hidden<br><br>drag to<br>rearrange<br><br>trophies<br>exchange<br>positions<br>if dropped<br>directly<br>on top of<br>another,<br>otherwise<br>moved.</html>" ), BorderLayout.SOUTH );
shownList = new TrophyPanel( true );
hiddenList = new TrophyPanel( false );
JSplitPane split = new JSplitPane( JSplitPane.VERTICAL_SPLIT, true,
new TrophyScrollPane( shownList ),
new TrophyScrollPane( hiddenList ) );
split.setOneTouchExpandable( true );
split.setResizeWeight( 0.8 );
this.add( split, BorderLayout.CENTER );
this.doRefresh();
}
public void doRefresh()
{
this.shownList.removeAll();
this.hiddenList.removeAll();
TrophyRequest req = new TrophyRequest();
RequestThread.postRequest( req );
ArrayList trophies = req.getTrophies();
if ( req == null )
{
return;
}
Iterator i = trophies.iterator();
while ( i.hasNext() )
{
Trophy t = (Trophy) i.next();
FileUtilities.downloadImage( KoLmafia.imageServerPath() + t.filename );
(t.visible ? this.shownList : this.hiddenList).add(
new DraggableTrophy( t ) );
}
this.shownList.revalidate();
this.shownList.repaint();
this.hiddenList.revalidate();
this.hiddenList.repaint();
}
public void doSave()
{
ArrayList trophies = new ArrayList();
this.shownList.addChildrenToList( trophies );
this.hiddenList.addChildrenToList( trophies );
RequestThread.postRequest( new TrophyRequest( trophies ) );
}
public void doShowAll()
{
while ( this.hiddenList.getComponentCount() > 0 )
{
this.shownList.add( this.hiddenList.getComponent( 0 ) );
}
this.shownList.revalidate();
this.shownList.repaint();
this.hiddenList.revalidate();
this.hiddenList.repaint();
}
public void doHideAll()
{
while ( this.shownList.getComponentCount() > 0 )
{
this.hiddenList.add( this.shownList.getComponent( 0 ) );
}
this.shownList.revalidate();
this.shownList.repaint();
this.hiddenList.revalidate();
this.hiddenList.repaint();
}
public void doAutoSort()
{
this.shownList.doAutoSort();
this.hiddenList.doAutoSort();
}
}
private static class TrophyScrollPane
extends JScrollPane
{
public TrophyScrollPane( JComponent component )
{
super( component, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER );
this.getVerticalScrollBar().setUnitIncrement( 25 );
}
}
private static class TrophyPanel
extends JPanel
implements LayoutManager, DropTargetListener
{
private boolean shown;
private static TrophyPanel sourceList = null;
private static DraggableTrophy source = null;
private int destIndex = -1;
private boolean isExchange = false;
public TrophyPanel( boolean shown )
{
super( null );
this.shown = shown;
this.setLayout( this );
new DropTarget( this, DnDConstants.ACTION_MOVE | DnDConstants.ACTION_LINK,
(DropTargetListener) this );
}
public void addChildrenToList( ArrayList list )
{
int nc = this.getComponentCount();
for ( int i = 0; i < nc; ++i )
{
DraggableTrophy t = (DraggableTrophy) this.getComponent( i );
t.trophy.visible = this.shown;
list.add( t.trophy );
}
}
public void doAutoSort()
{
int nc = this.getComponentCount();
for ( int i = 0; i < nc; ++i )
{
((DraggableTrophy) this.getComponent( i )).score = Integer.MAX_VALUE;
}
for ( int i = 0; i < nc - 2; ++i )
{
DraggableTrophy one = (DraggableTrophy) this.getComponent( i );
DraggableTrophy best = null;
int bestScore = Integer.MAX_VALUE;
for ( int j = i + 1; j < nc; ++j )
{
DraggableTrophy two = (DraggableTrophy) this.getComponent( j );
int score = Math.min( two.score, one.getSimilarity( two ) );
two.score = score;
if ( score < bestScore )
{
bestScore = score;
best = two;
}
}
this.add( best, i + 1 );
this.revalidate();
this.repaint();
}
}
/* Required methods for DropTargetListener */
public void dragEnter( DropTargetDragEvent dtde )
{
}
public void dragOver( DropTargetDragEvent dtde )
{
Point xy = dtde.getLocation();
dtde.acceptDrag( this.findDrop( xy.x, xy.y ) );
}
public void dropActionChanged( DropTargetDragEvent dtde )
{
Point xy = dtde.getLocation();
dtde.acceptDrag( this.findDrop( xy.x, xy.y ) );
}
public void dragExit( DropTargetEvent dte )
{
}
public void drop( DropTargetDropEvent dtde )
{
Point xy = dtde.getLocation();
dtde.acceptDrop( this.findDrop( xy.x, xy.y ) );
if ( TrophyPanel.source == null )
{ // something dropped from elsewhere, ignore it.
}
else
{
int sourceIndex = TrophyPanel.source.getIndex();
int destIndex = this.destIndex;
if ( this.isExchange )
{
DraggableTrophy dest =
(DraggableTrophy) this.getComponent( destIndex );
//System.out.println( this.source.getSimilarity( dest ) + " " +
// this.source.trophy.name + "/" + dest.trophy.name );
if ( TrophyPanel.sourceList == this )
{
if ( sourceIndex < destIndex )
{
this.add( dest, sourceIndex );
this.add( TrophyPanel.source, destIndex );
}
else
{
this.add( TrophyPanel.source, destIndex );
this.add( dest, sourceIndex );
}
}
else
{
this.add( TrophyPanel.source, destIndex );
TrophyPanel.sourceList.add( dest, sourceIndex );
}
}
else // move, instead of exchange
{
if ( TrophyPanel.sourceList == this && destIndex >= sourceIndex )
{
--destIndex;
}
this.add( TrophyPanel.source, destIndex );
}
this.revalidate();
this.repaint();
if ( TrophyPanel.sourceList != this )
{
TrophyPanel.sourceList.revalidate();
TrophyPanel.sourceList.repaint();
}
TrophyPanel.source = null;
}
dtde.dropComplete( true );
}
/* Required methods for LayoutManager */
public void addLayoutComponent( String name, Component comp )
{
}
public void removeLayoutComponent( Component comp )
{
}
public Dimension minimumLayoutSize( Container parent )
{
return this.preferredLayoutSize( parent );
}
public Dimension preferredLayoutSize( Container parent )
{
int nc = parent.getComponentCount();
int height = ( nc / 11 ) * 2;
nc %= 11;
if ( nc > 5 )
{
height += 2;
}
else if ( nc > 0 || height == 0 )
{
++height;
}
Insets ins = parent.getInsets();
return new Dimension( 600 + ins.left + ins.right,
100 * height + ins.top + ins.bottom );
}
public void layoutContainer( Container parent )
{
Insets ins = parent.getInsets();
int nc = parent.getComponentCount();
int line = 0;
int index = 0;
while ( index < nc )
{
int nl = Math.min( nc - index, 5 + (line & 1) );
int y = 100 * line + ins.top;
int x = 50 * (6 - nl) + ins.left;
while ( nl-- > 0 )
{
Component c = parent.getComponent( index++ );
c.setBounds( x, y, 100, 100 );
x += 100;
}
++line;
}
}
private int findDrop( int x, int y )
{
Insets ins = this.getInsets();
int nc = this.getComponentCount();
y -= ins.top;
y = Math.max( 0, y / 100 );
int sol = (y / 2) * 11 + 5 * (y & 1);
int nl = Math.min( Math.max( 0, nc - sol ), 5 + (y & 1) );
x -= 50 * (6 - nl) + ins.left - 25;
x = x >= 0 ? x / 50 : -99;
boolean isExchange = (x & 1) != 0;
x /= 2;
if ( x < 0 )
{ // before start of this line
x = 0;
isExchange = false;
}
else if ( x >= nl )
{ // after end of this line
x = nl;
isExchange = false;
}
this.destIndex = Math.min( x + sol, nc );
this.isExchange = isExchange;
return isExchange ? DnDConstants.ACTION_LINK : DnDConstants.ACTION_MOVE;
}
}
private static class DraggableTrophy
extends JLabel
implements DragGestureListener, DragSourceListener
{
public Trophy trophy;
private final DragSource dragSource = DragSource.getDefaultDragSource();
private final HashMap similarities = new HashMap();
private int[] cache;
public int score;
public DraggableTrophy( Trophy trophy )
{
super( JComponentUtilities.getImage( trophy.filename ) );
this.trophy = trophy;
this.setToolTipText( "<html>" + trophy.name + "<br>" +
trophy.filename.substring( trophy.filename.lastIndexOf( "/" ) + 1 )
+ "</html>" );
this.dragSource.createDefaultDragGestureRecognizer( this,
DnDConstants.ACTION_MOVE | DnDConstants.ACTION_LINK,
(DragGestureListener) this );
}
public int getIndex()
{
Container parent = this.getParent();
int nc = parent.getComponentCount();
for ( int i = 0; i < nc; ++i )
{
if ( parent.getComponent( i ) == this )
{
return i;
}
}
return -1; // wut?
}
public int getSimilarity( DraggableTrophy other )
{
Integer key, rv;
int id1 = this.trophy.id;
int id2 = other.trophy.id;
key = IntegerPool.get( id1 < id2 ? (id1 << 16 ) | id2 :
(id2 << 16) | id1 );
rv = (Integer) this.similarities.get( key );
if ( rv != null ) return rv.intValue();
int[] img1 = this.grab();
int[] img2 = other.grab();
int score = 0;
for ( int i = Math.min( img1.length, img2.length ) - 1; i >= 0; --i )
{
score += Math.abs( (img1[ i ] & 0xFF) - (img2[ i ] & 0xFF) );
}
this.similarities.put( key, IntegerPool.get( score ) );
return score;
}
private int[] grab()
{
if ( this.cache != null ) return this.cache;
PixelGrabber g = new PixelGrabber(
this.createImage(
new FilteredImageSource(
((ImageIcon) this.getIcon()).getImage().getSource(),
new AreaAveragingScaleFilter( 25, 25 ) ) ),
0, 0, 25, 25, true );
try
{
g.grabPixels();
}
catch ( InterruptedException e )
{
return new int[ 0 ];
}
Object rv = g.getPixels();
if ( rv instanceof int[] )
{
this.cache = (int[]) rv;
return this.cache;
}
return new int[ 0 ]; // don't know how to handle any other format
}
/* Methods required by DragGestureListener */
public void dragGestureRecognized( DragGestureEvent dge )
{
TrophyPanel.source = this;
TrophyPanel.sourceList = (TrophyPanel) this.getParent();
dge.startDrag( null, new StringSelection( this.trophy.name ),
(DragSourceListener) this );
//dge.startDrag( null, ((ImageIcon) this.getIcon()).getImage(),
// new Point( -50, -50 ), new StringSelection( this.trophy.name ),
// (DragSourceListener) this);
}
/* Methods required by DragSourceListener */
public void dragEnter( DragSourceDragEvent dsde )
{
}
public void dragOver( DragSourceDragEvent dsde )
{
}
public void dropActionChanged( DragSourceDragEvent dsde )
{
}
public void dragExit( DragSourceEvent dse )
{
}
public void dragDropEnd( DragSourceDropEvent dsde )
{
}
}
}