package bibliothek.help.view.dock;
import java.awt.Component;
import java.awt.Point;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.Icon;
import javax.swing.SwingUtilities;
import bibliothek.gui.DockController;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.FlapDockStation;
import bibliothek.gui.dock.action.ActionGuard;
import bibliothek.gui.dock.action.DefaultDockActionSource;
import bibliothek.gui.dock.action.DockAction;
import bibliothek.gui.dock.action.DockActionIcon;
import bibliothek.gui.dock.action.DockActionSource;
import bibliothek.gui.dock.action.LocationHint;
import bibliothek.gui.dock.action.actions.SimpleButtonAction;
import bibliothek.gui.dock.control.DockRegister;
import bibliothek.gui.dock.event.DockRegisterAdapter;
import bibliothek.gui.dock.layout.DockableProperty;
import bibliothek.gui.dock.util.DockUtilities;
import bibliothek.help.Core;
import bibliothek.help.util.ResourceSet;
import bibliothek.util.container.Tuple;
/**
* The minimizer adds two new {@link DockAction actions} to the DockingFrames.
* These actions can be used to <i>minimize</i> and <i>normalize</i> any
* {@link Dockable}.<br>
* Minimizing a <code>Dockable</code> means to store the location of the
* <code>Dockable</code> and then move the <code>Dockable</code> into a
* {@link FlapDockStation} that sits at the edge of the applications main frame.<br>
* Normalizing a <code>Dockable</code> means to move a minimized <code>Dockable</code>
* from a <code>FlapDockStation</code> back to its original position.<br>
* Clients can define which {@link DockStation}s are used for the "normal"
* and the "minimized" <code>Dockable</code>s by registering them through
* {@link #addAreaMinimized(FlapDockStation, DockableProperty) addAreaMinimized}
* and {@link #addAreaNormalized(DockStation) addAreaNormalized}.
* @author Benjamin Sigg
*
*/
public class Minimizer {
/** the areas which contain the normalized {@link Dockable}s */
private List<DockStation> areaNormalized = new ArrayList<DockStation>();
/** the areas which contain the minimized {@link Dockable}s */
private List<FlapDockStation> areaMinimized = new ArrayList<FlapDockStation>();
/** the station used to normalize a {@link Dockable} when its original position is unknown */
private DockStation defaultStation;
/** the preferred location where to add a minimized {@link Dockable} to a station from {@link #areaMinimized} */
private Map<FlapDockStation, DockableProperty> defaultDrops = new HashMap<FlapDockStation, DockableProperty>();
/** the preferred locations where a {@link Dockable} should be normalized again */
private Map<Dockable, Tuple<DockStation, DockableProperty>> locations =
new HashMap<Dockable, Tuple<DockStation,DockableProperty>>();
/** the core of this application */
private Core core;
/** the controller for which this {@link Minimizer} works */
private DockController controller;
/**
* Creates a new <code>Minimizer</code>, adds all listeners and actions
* to <code>controller</code>.
* @param core the center of this application
* @param controller the controller to which the actions and listeners are
* added.
*/
public Minimizer( Core core, DockController controller ){
this.core = core;
this.controller = controller;
controller.addActionGuard( new Minimize() );
controller.addActionGuard( new Normalize() );
controller.getRegister().addDockRegisterListener( new Listener() );
}
/**
* Sets the station to which {@link Dockable}s are "normalized" when
* their old location is not known or invalid.
* @param defaultStation the backup
*/
public void setDefaultStation( DockStation defaultStation ) {
this.defaultStation = defaultStation;
}
/**
* Stores a new station whose children will have the "minimize"-action.
* @param station a station whose children can be minimized
*/
public void addAreaNormalized( DockStation station ){
areaNormalized.add( station );
}
/**
* Stores a new station whose children are minimized {@link Dockable}s.
* @param station the new station
* @param defaultDrop the location where normally children will be inserted
*/
public void addAreaMinimized( FlapDockStation station, DockableProperty defaultDrop ){
areaMinimized.add( station );
defaultDrops.put( station, defaultDrop );
}
/**
* Ensures that <code>dockable</code> is no longer minimized.
* @param dockable the <code>Dockable</code> that will be shown on
* one of the {@link #addAreaNormalized(DockStation) normalized stations}.
*/
public void normalize( Dockable dockable ){
Tuple<DockStation, DockableProperty> location = locations.remove( dockable );
if( location == null ){
// add it somewhere...
DockStation parent = stationOf( dockable );
dockable.getDockParent().drag( dockable );
if( parent != null )
defaultStation.drop( dockable, defaultDrops.get( parent ) );
else
defaultStation.drop( dockable );
}
else{
DockStation parent = stationOf( dockable );
dockable.getDockParent().drag( dockable );
boolean done = location.getA().drop( dockable, location.getB() );
if( !done ){
if( parent != null )
defaultStation.drop( dockable, defaultDrops.get( parent ) );
else
defaultStation.drop( dockable );
}
}
}
/**
* Searches the first parent of <code>dockable</code> that was
* registered through {@link #addAreaMinimized(FlapDockStation, DockableProperty)}.
* @param dockable the element whose parent is searched
* @return one of the stations for minimized {@link Dockable}s or <code>null</code>
*/
private DockStation stationOf( Dockable dockable ){
DockStation parent = dockable.getDockParent();
while( parent != null ){
if( areaMinimized.contains( parent ))
return parent;
Dockable parentDockable = parent.asDockable();
if( parentDockable == null )
return null;
parent = parentDockable.getDockParent();
}
return null;
}
/**
* Ensures that <code>dockable</code> is no longer "normalized". The
* old location of <code>dockable</code> is stored, so it can be
* {@link #normalize(Dockable) normalized} again.
* @param dockable the element to minimize
*/
public void minimize( Dockable dockable ){
Component component = dockable.getComponent();
Point center = new Point( component.getWidth()/2, component.getHeight()/2);
SwingUtilities.convertPointToScreen( center, component );
FlapDockStation bestStation = null;
double bestDistance = Double.MAX_VALUE;
for( FlapDockStation station : areaMinimized ){
Component stationComponent = station.getComponent();
Point stationCenter = new Point( stationComponent.getWidth()/2, stationComponent.getHeight()/2 );
SwingUtilities.convertPointToScreen( stationCenter, stationComponent );
double dist = Math.pow( center.x - stationCenter.x, 2 ) + Math.pow( center.y - stationCenter.y, 2 );
if( dist < bestDistance ){
bestDistance = dist;
bestStation = station;
}
}
if( bestStation != null ){
DockStation root = DockUtilities.getRoot( dockable );
DockableProperty location = DockUtilities.getPropertyChain( root, dockable );
dockable.getDockParent().drag( dockable );
bestStation.add( dockable );
locations.put( dockable, new Tuple<DockStation, DockableProperty>( root, location ) );
}
}
/**
* A listener added to the {@link DockRegister}, this listener is responsible
* to remove data about {@link Dockable}s that are no longer registered.
* @author Benjamin Sigg
*
*/
private class Listener extends DockRegisterAdapter{
@Override
public void dockableUnregistered( DockController controller, Dockable dockable ) {
if( !core.isOnThemeUpdate() )
locations.remove( dockable );
}
}
/**
* An action and action-guard that allows the user to minimize a {@link Dockable}.
* The action is only added to children of the "normalized stations".
* @author Benjamin Sigg
*
*/
private class Minimize extends SimpleButtonAction implements ActionGuard {
/** the result of {@link #getSource(Dockable)} */
private DefaultDockActionSource source;
/**
* Creates a new action and action-guard
*/
public Minimize(){
source = new DefaultDockActionSource( new LocationHint( LocationHint.ACTION_GUARD, LocationHint.RIGHT ), this );
setText( "Minimize" );
setIcon( ResourceSet.ICONS.get( "minimize" ) );
}
@Override
public void action( Dockable dockable ) {
minimize( dockable );
}
public boolean react( Dockable dockable ) {
if( dockable.asDockStation() != null )
return false;
DockStation parent = dockable.getDockParent();
while( parent != null ){
if( areaNormalized.contains( parent ))
return true;
Dockable parentDockable = parent.asDockable();
if( parentDockable == null )
return false;
parent = parentDockable.getDockParent();
}
return false;
}
public DockActionSource getSource( Dockable dockable ) {
return source;
}
}
/**
* An action that allows the user to normalize a {@link Dockable}.
* The action is only added to the children of the "minimized stations".
* @author Benjamin Sigg
*
*/
private class Normalize extends SimpleButtonAction implements ActionGuard {
/** the result of {@link #getSource(Dockable)} */
private DefaultDockActionSource source;
/** the icon of this action */
private DockActionIcon icon;
/**
* Creates a new action
*/
public Normalize(){
source = new DefaultDockActionSource( new LocationHint( LocationHint.ACTION_GUARD, LocationHint.RIGHT ), this );
setText( "Normalize" );
icon = new DockActionIcon( "split.normalize", this ){
protected void changed( Icon oldValue, Icon newValue ){
setIcon( newValue );
}
};
icon.setManager( controller.getIcons() );
}
@Override
public void action( Dockable dockable ) {
normalize( dockable );
}
public boolean react( Dockable dockable ) {
if( dockable.asDockStation() != null )
return false;
DockStation parent = dockable.getDockParent();
while( parent != null ){
if( areaMinimized.contains( parent ))
return true;
Dockable parentDockable = parent.asDockable();
if( parentDockable == null )
return false;
parent = parentDockable.getDockParent();
}
return false;
}
public DockActionSource getSource( Dockable dockable ) {
return source;
}
}
}