Package bibliothek.gui.dock.common.perspective

Source Code of bibliothek.gui.dock.common.perspective.CGridPerspective

/*
* 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) 2010 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.perspective;

import bibliothek.gui.Dockable;
import bibliothek.gui.dock.SplitDockStation.Orientation;
import bibliothek.gui.dock.common.CControl;
import bibliothek.gui.dock.common.CGridArea;
import bibliothek.gui.dock.common.CStation;
import bibliothek.gui.dock.common.intern.CPlaceholderStrategy;
import bibliothek.gui.dock.common.intern.station.CommonDockStationFactory;
import bibliothek.gui.dock.common.mode.ExtendedMode;
import bibliothek.gui.dock.common.perspective.mode.CMaximizedModeAreaPerspective;
import bibliothek.gui.dock.common.perspective.mode.CMaximizedModePerspective;
import bibliothek.gui.dock.common.perspective.mode.CModeAreaPerspective;
import bibliothek.gui.dock.common.perspective.mode.CNormalModePerspective;
import bibliothek.gui.dock.common.perspective.mode.LocationModeManagerPerspective;
import bibliothek.gui.dock.facile.mode.Location;
import bibliothek.gui.dock.layout.DockableProperty;
import bibliothek.gui.dock.perspective.PerspectiveDockable;
import bibliothek.gui.dock.station.split.PerspectiveSplitDockGrid;
import bibliothek.gui.dock.station.split.PerspectiveSplitDockTree;
import bibliothek.gui.dock.station.split.SplitDockFullScreenProperty;
import bibliothek.gui.dock.station.split.SplitDockPathProperty;
import bibliothek.gui.dock.station.split.SplitDockPerspective;
import bibliothek.gui.dock.station.split.SplitDockPlaceholderProperty;
import bibliothek.gui.dock.station.split.SplitDockProperty;
import bibliothek.gui.dock.station.split.SplitDockPerspective.Entry;
import bibliothek.gui.dock.station.split.SplitDockPerspective.Node;
import bibliothek.gui.dock.station.split.SplitDockPerspective.Root;
import bibliothek.gui.dock.station.support.PlaceholderMap;
import bibliothek.util.Path;

/**
* A representation of a {@link CGridArea}. If this perspective is loaded with content, then all the coordinates
* are in a range between 0 and 100.
* @author Benjamin Sigg
*/
public class CGridPerspective extends SingleCDockablePerspective implements CStationPerspective {
  /** the intern representation of this perspective */
  private CommonSplitDockPerspective delegate;

  /** helper class to build up this perspective */
  private PerspectiveSplitDockGrid grid;

  /** whether {@link #gridDeploy()} is called automatically */
  private boolean autoDeploy = true;

  /** whether there are changes on {@link #grid} */
  private boolean gridChanges = false;

  /** whether {@link #gridDeploy()} currently is executed */
  private boolean onDeploy = false;

  /** the owner of this object */
  private CPerspective perspective;

  /** the mode the currently maximized element had before maximization, can be <code>null</code> */
  private Path unmaximizeMode;

  /** the location the currently maximized element had before maximization, can be <code>null</code> */
  private Location unmaximizeLocation;
 
  /** whether this perspective acts as working area */
  private boolean workingArea;
 
  /** The type of this perspective */
  private Path typeId;

  /** Whether this is a root station */
  private boolean root = true;
 
  /** identifiers children that are in normal mode */
  private CModeAreaPerspective normalMode = new CModeAreaPerspective(){
    public String getUniqueId(){
      return CGridPerspective.this.getUniqueId();
    }

    public boolean isChild( PerspectiveDockable dockable ){
      if( dockable.getParent() == intern() ) {
        return delegate.getFullscreen() != dockable;
      }
      return false;
    }
   
    public boolean isChildLocation( DockableProperty location ){
      return location instanceof SplitDockProperty
          || location instanceof SplitDockPathProperty
          || location instanceof SplitDockPlaceholderProperty;
    }
  };

  /** identifies children that are in maximized mode */
  private CMaximizedModeAreaPerspective maximalMode = new CMaximizedModeAreaPerspective(){
    public String getUniqueId(){
      return CGridPerspective.this.getUniqueId();
    }

    public boolean isChild( PerspectiveDockable dockable ){
      if( dockable.getParent() == intern() ) {
        return delegate.getFullscreen() == dockable;
      }
      return false;
    }

    public void setUnmaximize( Path mode, Location location ){
      unmaximizeLocation = location;
      unmaximizeMode = mode;
    }

    public Location getUnmaximizeLocation(){
      return unmaximizeLocation;
    }

    public Path getUnmaximizeMode(){
      return unmaximizeMode;
    }
   
    public boolean isChildLocation( DockableProperty location ){
      return location instanceof SplitDockFullScreenProperty;
    }
  };
 
  /**
   * Creates a new, empty perspective.
   * @param id the unique identifier of this perspective
   */
  public CGridPerspective( String id ){
    this( id, null );
  }
 
  /**
   * Creates a new, empty perspective.
   * @param id the unique identifier of this perspective
   * @param typeId the type of this station, can be <code>null</code>
   */
  public CGridPerspective( String id, Path typeId ){
    this( id, typeId, false );
  }

  /**
   * Creates a new, empty perspective.
   * @param id the unique identifier of this perspective
   * @param typeId the type of this station, can be <code>null</code>
   * @param workingArea whether this station should be treated as {@link CStation#isWorkingArea() working area} or not.
   */
  public CGridPerspective( String id, Path typeId, boolean workingArea ){
    super( id );
    delegate = new CommonSplitDockPerspective();
    delegate.setHasFullscreenAction( false );
    setWorkingArea( workingArea );
    gridClear();
    if( typeId == null ){
      typeId = CGridArea.TYPE_ID;
    }
    this.typeId = typeId;
  }
 
  public boolean isWorkingArea(){
    return workingArea;
  }
 
  public Path getTypeId(){
    return typeId;
  }
 
  public boolean isRoot(){
    return root;
  }
 
  public void setRoot( boolean root ){
    this.root = root;
  }
 
  /**
   * Sets whether this station should be regarded as a {@link CStation#isWorkingArea() working area} or not. This
   * setting is not stored, it is the clients responsibility to make sure that the matching {@link CStation} is
   * or is not a working area.
   * @param workingArea whether this station is to be treated like a working area or not
   */
  public void setWorkingArea( boolean workingArea ){
    this.workingArea = workingArea;
  }

  @Override
  protected CommonSplitDockPerspective create(){
    return delegate;
  }

  @Override
  public CommonSplitDockPerspective intern(){
    return (CommonSplitDockPerspective) super.intern();
  }

  public void setPerspective( CPerspective perspective ){
    if( this.perspective != null ) {
      ((CNormalModePerspective) this.perspective.getLocationManager().getMode( ExtendedMode.NORMALIZED )).remove( normalMode );
      ((CMaximizedModePerspective) this.perspective.getLocationManager().getMode( ExtendedMode.MAXIMIZED )).remove( maximalMode );
    }
    this.perspective = perspective;
    if( this.perspective != null ) {
      ((CNormalModePerspective) this.perspective.getLocationManager().getMode( ExtendedMode.NORMALIZED )).add( normalMode );
      ((CMaximizedModePerspective) this.perspective.getLocationManager().getMode( ExtendedMode.MAXIMIZED )).add( maximalMode );
    }
  }
 
  public CPerspective getPerspective(){
    return perspective;
  }

  /**
   * Calls {@link #gridDeploy()}, but only if {@link #isAutoDeploy()} returns <code>true</code> and
   * if {@link #grid() the grid} was accessed.
   */
  protected void maybeDeploy(){
    if( isAutoDeploy() && gridChanges ) {
      gridDeploy();
    }
  }

  private PerspectiveDockable[] convert( CDockablePerspective[] dockables ){
    PerspectiveDockable[] result = new PerspectiveDockable[dockables.length];
    for( int i = 0; i < result.length; i++ ) {
      result[i] = dockables[i].intern().asDockable();
    }
    return result;
  }

  /**
   * Unpacks the stations (e.g. a stack) that is stored at <code>x,y,width,height</code>. The result
   * is like removing all children and add them again with {@link #gridAdd(double, double, double, double, CDockablePerspective...)}.
   * @param x the x-coordinate of a set of {@link CDockablePerspective}, can be any number
   * @param y the y-coordinate of a set of {@link CDockablePerspective}, can be any number
   * @param width the width of a set of {@link CDockablePerspective}, can be any number greater than 0
   * @param height the height of a set of {@link CDockablePerspective}, can be any number greater than 0
   */
  public void unpack( double x, double y, double width, double height ){
    gridChanges = true;
    grid.unpack( x, y, width, height );
  }
 
  /**
   * Adds <code>dockables</code> at location <code>x/y</code> with size <code>width/height</code> to an internal
   * list of pending commands to execute. This method does not change the layout of this area, but a call
   * to {@link #gridDeploy()} will.<br>
   * Calling this method several times with the same location and size has the same effect as calling it once,
   * but with a bigger array that contains all the dockables that would otherwise be added through many calls.
   * @param x the x-coordinate of <code>dockables</code>, can be any number
   * @param y the y-coordinate of <code>dockables</code>, can be any number
   * @param width the width of <code>dockables</code>, can be any number greater than 0
   * @param height the height of <code>dockables</code>, can be any number greater than 0
   * @param dockables the elements to add, should contain at least one item
   * @see #gridClear()
   * @see #gridDeploy()
   */
  public void gridAdd( double x, double y, double width, double height, CDockablePerspective... dockables ){
    gridChanges = true;
    grid.addDockable( x, y, width, height, convert( dockables ) );
  }
 
  /**
   * Adds <code>dockables</code> as placeholder at location <code>x/y</code> with size <code>width/height</code> to
   * an internal list of pending commands to execute. This method does not change the layout of this area, but a call
   * to {@link #gridDeploy()} will.<br>
   * Calling this method several times with the same location and size has the same effect as calling it once,
   * but with a bigger array that contains all the dockables that would otherwise be added through many calls.
   * @param x the x-coordinate of <code>dockables</code>, can be any number
   * @param y the y-coordinate of <code>dockables</code>, can be any number
   * @param width the width of <code>dockables</code>, can be any number greater than 0
   * @param height the height of <code>dockables</code>, can be any number greater than 0
   * @param dockables the elements whose placeholders to add, should contain at least one item
   * @see #gridClear()
   * @see #gridDeploy()
   * @throws IllegalArgumentException if not all dockables have a placeholder
   */
  public void gridPlaceholder( double x, double y, double width, double height, CDockablePerspective... dockables ){
    gridChanges = true;
    Path[] placeholders = new Path[ dockables.length ];
    for( int i = 0; i < dockables.length; i++ ){
      placeholders[i] = dockables[i].intern().asDockable().getPlaceholder();
      if( placeholders[i] == null ){
        throw new IllegalArgumentException( "dockable '" + i + "' does not have a placeholder: " + dockables[i] );
      }
    }
    grid.addPlaceholders( x, y, width, height, placeholders );
  }

  /**
   * Using location <code>x/y</code> and size <code>width/height</code> as key, this method set the selection
   * in a group of dockables. This method does not change the layout directly, but a call to {@link #gridDeploy()} will.
   * @param x the x-coordinate of <code>dockables</code>, can be any number
   * @param y the y-coordinate of <code>dockables</code>, can be any number
   * @param width the width of <code>dockables</code>, can be any number greater than 0
   * @param height the height of <code>dockables</code>, can be any number greater than 0
   * @param selection the element that should be selected, must already be in the group
   * @see #gridClear()
   * @see #gridDeploy()
   */
  public void gridSelect( double x, double y, double width, double height, CDockablePerspective selection ){
    gridChanges = true;
    grid.setSelected( x, y, width, height, selection == null ? null : selection.intern().asDockable() );
  }

  /**
   * Adds a constraint to the algorithm that is executed by {@link #gridDeploy()}, the constraint tells that
   * there should be a horizontal divider from <code>x1/y</code> to <code>x2/y</code>.
   * @param x1 the beginning of the divider
   * @param x2 the end of the divider
   * @param y the vertical position of the divider
   */
  public void gridHorizontal( double x1, double x2, double y ){
    gridChanges = true;
    grid.addHorizontalDivider( x1, x2, y );
  }

  /**
   * Adds a constraint to the algorithm that is executed by {@link #gridDeploy()}, the constraint tells that
   * there should be a vertical divider from <code>x/y1</code> to <code>x/y2</code>.
   * @param x the horizontal position of the divider
   * @param y1 the beginning of the divider
   * @param y2 the end of the divider
   */
  public void gridVertical( double x, double y1, double y2 ){
    gridChanges = true;
    grid.addVerticalDivider( x, y1, y2 );
  }

  /**
   * Deletes all pending commands that were collected by the <code>grid*</code> methods. A call to this
   * method does not change the current layout of this area, but a call to {@link #gridDeploy()} will.
   * @see #gridDeploy()
   */
  public void gridClear(){
    grid = new PerspectiveSplitDockGrid();
  }

  /**
   * Removes all children of this area, then executes pending commands that add dockables at specified locations.<br>
   * In particular this method analyzes all the commands that were generated by calls to the <code>grid*</code> methods
   * and merges them into a layout that fits the locations and sizes the client specified as good as possible.<br>
   * If {@link #isAutoDeploy()} returns <code>true</code>, then this method is called automatically before storing
   * the layout of this area.<br>
   * This method will silently return if the list of pending commands was never accessed directly or indirectly
   * by the client.
   * @see #isAutoDeploy()
   * @see #gridAdd(double, double, double, double, CDockablePerspective...)
   * @see #gridSelect(double, double, double, double, CDockablePerspective)
   * @see #gridHorizontal(double, double, double)
   * @see #gridVertical(double, double, double)
   * @see #gridClear() 
   */
  public void gridDeploy(){
    if( gridChanges ) {
      gridChanges = false;
      try {
        onDeploy = true;
        delegate.read( grid.toTree(), null );
      }
      finally {
        onDeploy = false;
      }
    }
  }

  /**
   * Reads the contents of the {@link #getRoot() root} and resets the {@link #grid() grid} to reflect that
   * root. This method is called once during construction of this perspective, it can later be called
   * to reset the perspective.
   */
  public void gridPrepare(){
    gridChanges = false;
    gridClear();
    handle( delegate.getRoot().getChild(), 0, 0, 100, 100 );
  }

  private void handle( Entry entry, double x, double y, double width, double height ){
    if( entry != null ){
      if( entry.asLeaf() != null ) {
        PerspectiveDockable dockable = entry.asLeaf().getDockable();
        if( dockable != null ) {
          grid.addDockable( x, y, width, height, dockable );
        }
      }
      else{
        Node node = entry.asNode();
        double divider = node.getDivider();
        if( node.getOrientation() == Orientation.HORIZONTAL ){
          handle( node.getChildA(), x, y, width*divider, height );
          handle( node.getChildB(), x+width*divider, y, width*(1-divider), height );
        }
        else{
          handle( node.getChildA(), x, y, width, height*divider );
          handle( node.getChildB(), x, y+height*divider, width, height*(1-divider) );
        }
      }
    }
  }

  /**
   * Allows access to the internal representation of this area as grid. Changes to the returned object will stored
   * but not change the layout of this area directly, a call to {@link #gridDeploy()} will change the layout however.
   * @return the internal grid
   * @see #gridDeploy()
   */
  public PerspectiveSplitDockGrid grid(){
    gridChanges = true;
    return grid;
  }

  /**
   * Sets whether {@link #gridDeploy()} is called automatically by this area before accessing the tree
   * of {@link Dockable}s. The default value for this property is <code>true</code>.<br>
   * Clients have to call {@link #gridDeploy()} if this property is <code>false</code> in order to execute commands
   * that were collected with the <code>grid*</code> methods.
   * @param autoDeploy whether {@link #gridDeploy()} is called automatically
   */
  public void setAutoDeploy( boolean autoDeploy ){
    this.autoDeploy = autoDeploy;
  }

  /**
   * Tells whether {@link #gridDeploy()} will be called automatically before accessing the tree of {@link Dockable}s.
   * @return whether automatic deployment is active
   * @see #setAutoDeploy(boolean)
   */
  public boolean isAutoDeploy(){
    return autoDeploy;
  }

  /**
   * Gets access to the intern tree that represents the layout of this area. Clients may alter this tree in any
   * way they like. Please note that if {@link #isAutoDeploy() automatic deployment} is active, {@link #gridDeploy()}
   * can be triggered by invoking this method.
   * @return the root of the intern tree of dockables, <code>null</code> if this area does not have any children
   */
  public SplitDockPerspective.Root getRoot(){
    return delegate.getRoot();
  }

  /**
   * Maximized <code>dockable</code> on this station. Please read about the side effects in {@link #maximize(PerspectiveDockable)}.
   * @param dockable the element to maximize, not <code>null</code>
   */
  public void maximize( CDockablePerspective dockable ){
    maximize( dockable.intern().asDockable() );
  }

  /**
   * Maximized <code>dockable</code> on this station. Note that maximized elements will be de-maximized by a
   * {@link CControl} unless {@link CControl#setRevertToBasicModes(boolean)} was switched to <code>false</code>.
   * A call to this method has several side effects that must be cared for:
   * <ul>
   *   <li>If necessary and if auto-deploy is set, {@link #gridDeploy()} is called.</li>
   <li>If the parent of <code>dockable</code> is not this station, then <code>dockable</code>
   *  is removed from the parent and added to this. The location <code>dockable</code> has on its
   *  parent is stored and can be used during un-maximization.</li>
   <li>If <code>dockable</code> has no parent, then it is added to this station. No location
   *  information is stored. This is no problem for {@link CDockablePerspective} as they usually have a
   *  history of legal locations associated, but for any other dockable the missing location can lead to
   *  strange behavior when un-maximizing.</li>
   * </ul>
   * @param dockable the element to maximize
   */
  public void maximize( PerspectiveDockable dockable ){
    maybeDeploy();

    // find current location
    LocationModeManagerPerspective manager = perspective.getLocationManager();
    Location location = manager.getLocation( dockable );
    Path mode = null;
    if( location == null ) {
      ExtendedMode eMode = manager.getMode( dockable );
      if( eMode != null ) {
        mode = eMode.getModeIdentifier();
      }
    }
    else {
      mode = location.getMode();
    }

    // reparent if necessary
    if( dockable.getParent() != intern() ) {
      if( dockable.getParent() != null ) {
        dockable.getParent().remove( dockable );
      }

      Root root = getRoot();
      SplitDockPerspective.Leaf leaf = new SplitDockPerspective.Leaf( dockable, null, null, -1 );

      if( root.getChild() == null ) {
        root.setChild( leaf );
      }
      else {
        root.setChild( new SplitDockPerspective.Node( Orientation.HORIZONTAL, 0.5, leaf, root.getChild(), null, null, -1 ) );
      }
    }

    // store
    delegate.setFullscreen( dockable );

    unmaximizeLocation = location;
    unmaximizeMode = mode;
  }

  /**
   * Gets the element that is maximized.
   * @return the maximized child or <code>null</code>
   */
  public PerspectiveDockable getMaximized(){
    return delegate.getFullscreen();
  }

  @Override
  public CStationPerspective asStation(){
    return this;
  }

  public String getFactoryID(){
    return delegate.getFactoryID();
  }

  public PlaceholderMap getPlaceholders(){
    return delegate.getPlaceholders();
  }

  public void setPlaceholders( PlaceholderMap placeholders ){
    delegate.setPlaceholders( placeholders );
  }

  /**
   * The type of object that is used by a {@link CGridPerspective} as intern representation.
   * @author Benjamin Sigg
   */
  public class CommonSplitDockPerspective extends SplitDockPerspective implements CommonDockStationPerspective {
    public CElementPerspective getElement(){
      return CGridPerspective.this;
    }
   
    @Override
    public String getFactoryID(){
      return CommonDockStationFactory.FACTORY_ID;
    }
   
    public String getConverterID(){
      return super.getFactoryID();
    }
   
    @Override
    public void read( PerspectiveSplitDockTree tree, PerspectiveDockable fullscreen ){
      super.read( tree, fullscreen );
      if( !onDeploy ) {
        gridPrepare();
      }
    }

    @Override
    protected PerspectiveDockable combine( PerspectiveDockable[] dockables, PerspectiveDockable selection ){
      return new CStackPerspective( dockables, selection );
    }

    @Override
    public Path getPlaceholder(){
      return CPlaceholderStrategy.getSingleDockablePlaceholder( getUniqueId() );
    }

    @Override
    public Root getRoot(){
      maybeDeploy();
      return super.getRoot();
    }

    @Override
    public int getDockableCount(){
      maybeDeploy();
      return super.getDockableCount();
    }

    @Override
    public PerspectiveDockable getDockable( int index ){
      maybeDeploy();
      return super.getDockable( index );
    }
  }
}
TOP

Related Classes of bibliothek.gui.dock.common.perspective.CGridPerspective

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.