/*
* Bibliothek - DockingFrames
* Library built on Java/Swing, allows the user to "drag and drop"
* panels containing any Swing-Component the developer likes to add.
*
* Copyright (C) 2011 Benjamin Sigg
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Benjamin Sigg
* benjamin_sigg@gmx.ch
* CH - Switzerland
*/
package bibliothek.gui.dock.station.layer;
import java.awt.Component;
import java.awt.Window;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.swing.SwingUtilities;
import bibliothek.gui.DockStation;
import bibliothek.gui.DockUI;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.util.DockUtilities;
/**
* The {@link OrderedLayerCollection} is a helper class that allows order a set
* {@link DockStation}s according to the rules defined in {@link DockStationDropLayer}.
* @author Benjamin Sigg
*/
public class OrderedLayerCollection {
/** all the currently known stations */
private Set<DockStation> stations = new HashSet<DockStation>();
/** factory applied to all {@link DockStation}s to find their {@link DockStationDropLayer}s */
private DockStationDropLayerFactory factory;
/**
* Creates a new collection
* @param factory applied to all {@link DockStation}s in order to find their {@link DockStationDropLayer}s.
*/
public OrderedLayerCollection( DockStationDropLayerFactory factory ){
this.factory = factory;
}
/**
* Adds an additional station which is to be ordered.
* @param station the additional station
*/
public void add( DockStation station ){
stations.add( station );
}
/**
* Sorts the current set of {@link DockStation}s currently known to this
* collection.
* @param x the x-coordinate of the mouse on the screen
* @param y the y-coordinate of the mouse on the screen
* @return the ordered stations, where the first station is the station with the highest priority
*/
public List<DockStation> sort( int x, int y ){
Map<DockStation, Node> nodes = new HashMap<DockStation, OrderedLayerCollection.Node>();
for( DockStation station : stations ){
nodes.put( station, new Node( station ) );
}
List<Node> roots = new ArrayList<Node>();
for( Node node : nodes.values() ){
if( !node.register( nodes )){
roots.add( node );
}
}
for( Node root : roots ){
root.modify();
}
List<DockStationDropLayer> layers = new ArrayList<DockStationDropLayer>();
for( Node root : roots ){
root.collect( layers, x, y );
}
layers = sort( layers );
List<DockStation> result = new ArrayList<DockStation>();
for( DockStationDropLayer layer : layers ){
if( nodes.remove( layer.getStation() ) != null ){
result.add( layer.getStation() );
}
}
return result;
}
/**
* Creates a new ordered list containing all items of <code>layer</code>, the new list
* is built by an algorithm that is resistent against inconstant ordering.
* @param layers the layers to order
* @return the ordered layers
*/
protected List<DockStationDropLayer> sort( List<DockStationDropLayer> layers ){
List<DockStationDropLayer> result = new LinkedList<DockStationDropLayer>();
for( DockStationDropLayer layer : layers ){
int index = 0;
// insertion sort
for( DockStationDropLayer resultLayer : result ){
int compare = compare( resultLayer, layer );
if( compare > 0 )
break;
else
index++;
}
result.add( index, layer );
}
return result;
}
/**
* Works like {@link Comparator#compare(Object, Object)}, compares <code>a</code> to <code>b</code>.
* @param a the first object to compare
* @param b the second object to compare
* @return a value less/equal/greater to 0 depending on whether <code>a</code> is less/equal/greater than <code>b</code>.
*/
protected int compare( DockStationDropLayer a, DockStationDropLayer b ){
if( a == b ){
return 0;
}
// direct comparison
boolean compareA = a.canCompare( b );
boolean compareB = b.canCompare( a );
if( compareA && compareB ){
int resultA = -a.compare( b );
int resultB = b.compare( a );
if( resultA == 0 ){
return resultB;
}
if( resultB == 0 ){
return resultA;
}
if( resultA < 0 == resultB < 0 ){
return resultA;
}
// contradiction
}
// Priority
LayerPriority pa = a.getPriority();
LayerPriority pb = b.getPriority();
int priority = pa.compareTo( pb );
if( priority != 0 ){
return priority;
}
// same priority
int reverse;
if( pa.isReverse() ){
reverse = -1;
}
else{
reverse = 1;
}
// by relationship
DockStation sa = a.getStation();
DockStation sb = b.getStation();
if( DockUtilities.isAncestor( sa, sb )){
return 1 * reverse;
}
if( DockUtilities.isAncestor( sb, sa )){
return -1 * reverse;
}
// by components
Component compA = a.getComponent();
Component compB = b.getComponent();
if( compA != null && compB != null ){
Window windowA = SwingUtilities.getWindowAncestor( compA );
Window windowB = SwingUtilities.getWindowAncestor( compB );
if( windowA != null && windowB != null ){
if( windowA == windowB ){
if( DockUI.isOverlapping( compA, compB )){
return -1 * reverse;
}
if( DockUI.isOverlapping( compB, compA )){
return 1 * reverse;
}
}
else{
if( isParent( windowA, windowB ))
return 1 * reverse;
if( isParent( windowB, windowA ))
return -1 * reverse;
boolean mouseOverA = windowA.getMousePosition() != null;
boolean mouseOverB = windowB.getMousePosition() != null;
if( mouseOverA && !mouseOverB ){
return -1 * reverse;
}
if( !mouseOverA && mouseOverB ){
return 1 * reverse;
}
}
}
}
return 0;
}
/**
* Tells whether <code>parent</code> is really a parent of <code>child</code>
* or not.
* @param parent a window which may be an ancestor of <code>child</code>
* @param child a window which may be child of <code>parent</code>
* @return <code>true</code> if <code>parent</code> is an
* ancestor of <code>child</code>
*/
private boolean isParent( Window parent, Window child ){
Window temp = child.getOwner();
while( temp != null ){
if( temp == parent )
return true;
temp = temp.getOwner();
}
return false;
}
/**
* Describes one {@link DockStation} and its {@link DockStationDropLayer}s.
* @author Benjamin Sigg
*/
private class Node{
private Node parent;
private List<Node> children;
private DockStation station;
private DockStationDropLayer[] layers;
/**
* Creates a new node and stores the {@link DockStationDropLayer}s
* @param station the station represented by this node.
*/
public Node( DockStation station ){
this.station = station;
if( station.getDockableCount() > 0 ){
children = new ArrayList<Node>( station.getDockableCount() );
}
layers = factory.getLayers( station );
}
/**
* Searches the parent of <code>this</code> in <code>nodes</code> and
* registers itself as child.
* @param nodes all the known stations
* @return <code>true</code> if the parent was found, <code>false</code>
* if not (in which case this is a root node).
*/
public boolean register( Map<DockStation, Node> nodes ){
Dockable child = station.asDockable();
while( child != null ){
DockStation station = child.getDockParent();
if( station == null ){
return false;
}
Node node = nodes.get( station );
if( node != null ){
node.children.add( this );
this.parent = node;
return true;
}
child = station.asDockable();
}
return false;
}
/**
* Gets all the layers of this node.
* @return all the layers
*/
public DockStationDropLayer[] getLayers(){
return layers;
}
/**
* Calls {@link #modify()} on all children nodes, then
* calls {@link DockStationDropLayer#modify(DockStationDropLayer)} on all layers of
* all parent nodes.
*/
public void modify(){
if( children != null ){
for( Node child : children ){
child.modify();
}
}
Node parent = this.parent;
while( parent != null ){
for( int i = 0; i < layers.length; i++ ){
for( DockStationDropLayer layer : parent.getLayers() ){
layers[i] = layer.modify( layers[i] );
}
}
parent = parent.parent;
}
}
/**
* Collects all layers of this node and of its children. Layers which do not contain
* the point <code>x/y</code> are ignored.
* @param layers the list to fill
* @param x the x-coordinate of the mouse on the screen
* @param y the y-coordinate of the mouse on the screen
*/
public void collect( List<DockStationDropLayer> layers, int x, int y ){
if( children != null ){
for( Node child : children ){
child.collect( layers, x, y );
}
}
for( DockStationDropLayer layer : this.layers ){
if( layer.contains( x, y )){
layers.add( layer );
}
}
}
}
}