/*
* 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) 2015 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.common.grouping;
import bibliothek.gui.DockStation;
import bibliothek.gui.Dockable;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.mode.CLocationMode;
import bibliothek.gui.dock.common.mode.ExtendedMode;
import bibliothek.gui.dock.common.perspective.CControlPerspective;
import bibliothek.gui.dock.common.perspective.CGridPerspective;
import bibliothek.gui.dock.facile.mode.Location;
import bibliothek.gui.dock.layout.DockableProperty;
import bibliothek.gui.dock.station.PlaceholderMapping;
import bibliothek.gui.dock.station.stack.StackDockProperty;
import bibliothek.gui.dock.util.DockUtilities;
import bibliothek.util.ClientOnly;
import bibliothek.util.Path;
/**
* When a {@link Dockable} is about to be moved to a new position, then this {@link DockableGrouping} searches for
* a unique identifier, called a "placeholder", which indicates where the {@link Dockable} should be placed.<br>
* The {@link #getPlaceholder() placeholder} is placed at the location of the {@link Dockable}, for which
* {@link #hierarchyChanged(Dockable)} was called last. Basically the placeholder always follows the last {@link Dockable}
* that was moved around.<br>
* This object tries to ensure that for each {@link ExtendedMode} there is no more than one placeholder set. Meaning
* there is only one location marked for each mode.<br>
* Clients may use the {@link CControlPerspective}, and methods like
* {@link CGridPerspective#gridPlaceholder(double, double, double, double, bibliothek.gui.dock.common.perspective.CDockablePerspective...) CGridPerspective.gridPlaceholder},
* to set the initial location of the placeholder.
* @author Benjamin Sigg
*/
@ClientOnly
// note: classname is written in the tutorial, if renaming this class, also update tutorial!
public class PlaceholderGrouping implements DockableGrouping{
private CControl control;
private Path placeholder;
/**
* Creates a new grouping.
* @param control The realm in which this object acts, used to access things like the root {@link DockStation}s
* @param placeholder a unique identifier with which the location of a group of {@link Dockable}s is marked
*/
public PlaceholderGrouping( CControl control, Path placeholder ){
if( control == null ){
throw new IllegalArgumentException( "control must not be null" );
}
if( placeholder == null ){
throw new IllegalArgumentException( "placeholder must not be null" );
}
this.control = control;
this.placeholder = placeholder;
}
/**
* Gets the placeholder that is searched by this {@link PlaceholderGrouping}, and that is placed wherever
* a {@link Dockable} is moved when {@link #hierarchyChanged(Dockable)} was called.
* @return the placeholder, not <code>null</code>
*/
public Path getPlaceholder() {
return placeholder;
}
/**
* Placeholder that marks the last position where this {@link PlaceholderGrouping} did store something. The default implementation
* always returns {@link #getPlaceholder() placeholder + "last"}, but subclasses may change the behavior.
* @return the last location where something interesting happened
*/
protected Path getLastPlaceholder(){
return placeholder.append( "last" );
}
public Location getStoredLocation( Dockable dockable, CLocationMode mode, Location history ) {
return history;
}
public Location getValidatedLocation( Dockable dockable, CLocationMode mode, Location validatedHistory ) {
if( validatedHistory != null ){
Location result = findLocationFor( dockable, mode, validatedHistory );
if( result != validatedHistory ){
return result;
}
}
Location result = findLocationFor( dockable, mode );
if( result == null ){
result = validatedHistory;
}
return result;
}
public ExtendedMode getInitialMode( Dockable dockable ) {
ExtendedMode mode = getInitialMode( dockable, true );
if( mode == null ){
mode = getInitialMode( dockable, false );
}
return mode;
}
private ExtendedMode getInitialMode( Dockable dockable, boolean validateMode ){
Path lastPlaceholder = getLastPlaceholder();
for( CLocationMode mode : control.getLocationManager().modes() ){
for( String id : mode.getRepresentationIds() ){
DockStation station = mode.getRepresentation( id );
if( station.getPlaceholderMapping().hasPlaceholder( lastPlaceholder )){
boolean valid = true;
if( validateMode ){
Dockable child = station.getPlaceholderMapping().getDockableAt( lastPlaceholder );
if( child != null ){
valid = mode.isCurrentMode( child );
}
}
if( valid ){
return mode.getExtendedMode();
}
}
}
}
return null;
}
/**
* Searches a location for <code>dockable</code>.
* @param dockable the element whose new location is searched
* @param mode the target mode of <code>dockable</code>
* @return the new location, or <code>null</code> if no location can be calculated
*/
protected Location findLocationFor( Dockable dockable, CLocationMode mode ) {
for( String root : mode.getRepresentationIds() ){
DockStation station = mode.getRepresentation( root );
if( station.getPlaceholderMapping().hasPlaceholder( placeholder )){
DockableProperty location = getLocation( station );
if( location != null ){
return new Location( mode.getUniqueIdentifier(), root, location, false );
}
}
}
return null;
}
/**
* Called by {@link #getValidatedLocation(Dockable, CLocationMode, Location)}, this method tries to find a location on the
* root station designated by <code>validatedHistory</code>.
* @param dockable the element whose new location is searched
* @param mode the target mode of <code>dockable</code>
* @param validatedHistory the location where the unmodified algorithm would place <code>dockable</code>
* @return either a replacement for <code>validatedHistory</code>, or <code>validatedHistory</code>. Should not return <code>null</code>.
*/
protected Location findLocationFor( Dockable dockable, CLocationMode mode, Location validatedHistory ) {
DockStation root = mode.getRepresentation( validatedHistory.getRoot() );
if( root != null && root.getPlaceholderMapping().hasPlaceholder( placeholder )){
DockableProperty location = getLocation( root );
if( location != null ){
return new Location( mode.getUniqueIdentifier(), validatedHistory.getRoot(), location, validatedHistory.isApplicationDefined() );
}
}
return validatedHistory;
}
/**
* Searches the ideal location on <code>root</code> that matches {@link #getPlaceholder() the placeholder}.
* @param root the station on which to search the placeholder
* @return the location of {@link #getPlaceholder() the placeholder}, or <code>null</code> if the placeholder was not found
*/
protected DockableProperty getLocation( DockStation root ) {
DockableProperty result = root.getPlaceholderMapping().getLocationAt( placeholder );
DockableProperty last = result;
DockableProperty previous = null;
DockStation parent = root;
while( last != previous ){
previous = last;
Dockable child = parent.getPlaceholderMapping().getDockableAt( placeholder );
if( child != null ){
parent = child.asDockStation();
}
else{
parent = null;
}
if( parent != null ){
PlaceholderMapping mapping = parent.getPlaceholderMapping();
if( mapping.hasPlaceholder( placeholder )){
DockableProperty next = mapping.getLocationAt( placeholder );
if( next != null ){
while( last.getSuccessor() != null ){
last = last.getSuccessor();
}
last.setSuccessor( next );
last = next;
}
}
}
}
while( last != null ){
DockableProperty successor = last.getSuccessor();
if( successor == null ){
last.setSuccessor( new StackDockProperty( Integer.MAX_VALUE, placeholder ) );
}
last = successor;
}
return result;
}
public void hierarchyChanged( Dockable dockable ) {
markLocation( dockable );
}
public void focusGained( Dockable dockable ) {
markLocation( dockable );
}
/**
* Makes sure that the placeholder marks the current location of <code>dockable</code>.
* @param dockable defines the location to mark
*/
protected void markLocation( Dockable dockable ){
removePlaceholderInMode( dockable );
removePlaceholderEverywhere();
Path lastPlaceholder = getLastPlaceholder();
DockStation parent = dockable.getDockParent();
while( parent != null && dockable != null ){
PlaceholderMapping mapping = parent.getPlaceholderMapping();
mapping.addPlaceholder( dockable, placeholder );
mapping.addPlaceholder( dockable, lastPlaceholder );
dockable = parent.asDockable();
if( dockable != null ){
parent = dockable.getDockParent();
}
}
}
/**
* Removes the {@link #placeholder} from any {@link DockStation} that is not an ancestor of <code>dockable</code>,
* but is associated with the current {@link ExtendedMode} of <code>dockable</code>.
* @param dockable defines the mode to check, and defines that its parent stations are not to be touched
*/
private void removePlaceholderInMode( Dockable dockable ){
CLocationMode mode = control.getLocationManager().getCurrentMode( dockable );
if( mode == null ){
return;
}
for( String id : mode.getRepresentationIds() ){
DockStation station = mode.getRepresentation( id );
if( !DockUtilities.isAncestor( station, dockable )){
station.getPlaceholderMapping().removePlaceholder( placeholder );
}
}
}
/**
* Removes the {@link #getLastPlaceholder() last placeholder} everywhere.
*/
public void removePlaceholderEverywhere(){
Path lastPlaceholder = getLastPlaceholder();
for( CLocationMode mode : control.getLocationManager().modes() ){
for( String id : mode.getRepresentationIds() ){
DockStation station = mode.getRepresentation( id );
station.getPlaceholderMapping().removePlaceholder( lastPlaceholder );
}
}
}
}