Package org.eclipse.zest.layouts.algorithms

Source Code of org.eclipse.zest.layouts.algorithms.AbstractLayoutAlgorithm$InternalComparator

/*******************************************************************************
* Copyright 2005, CHISEL Group, University of Victoria, Victoria, BC, Canada.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*     The Chisel Group, University of Victoria
*******************************************************************************/

package org.eclipse.zest.layouts.algorithms;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import org.eclipse.zest.layouts.Filter;
import org.eclipse.zest.layouts.InvalidLayoutConfiguration;
import org.eclipse.zest.layouts.LayoutAlgorithm;
import org.eclipse.zest.layouts.LayoutEntity;
import org.eclipse.zest.layouts.LayoutRelationship;
import org.eclipse.zest.layouts.LayoutStyles;
import org.eclipse.zest.layouts.Stoppable;
import org.eclipse.zest.layouts.constraints.BasicEntityConstraint;
import org.eclipse.zest.layouts.dataStructures.BendPoint;
import org.eclipse.zest.layouts.dataStructures.DisplayIndependentDimension;
import org.eclipse.zest.layouts.dataStructures.DisplayIndependentPoint;
import org.eclipse.zest.layouts.dataStructures.DisplayIndependentRectangle;
import org.eclipse.zest.layouts.dataStructures.InternalNode;
import org.eclipse.zest.layouts.dataStructures.InternalRelationship;
import org.eclipse.zest.layouts.progress.ProgressEvent;
import org.eclipse.zest.layouts.progress.ProgressListener;

/**
* Handles common elements in all layout algorithms
* [irbull] Refactored into a template pattern.  ApplyLayout now delegates the
* task to ApplyLayoutInternal
*
* [irbull] Included asynchronous layouts
*
* @version 1.0
* @author Casey Best
* @author Ian Bull
* @author Chris Callendar
* @author Rob Lintern
* @author Chris Bennett
*/
public abstract class AbstractLayoutAlgorithm implements LayoutAlgorithm, Stoppable {

  public void removeRelationships(Collection collection) {

  }

  public final static int MIN_ENTITY_SIZE = 5;
  private final static int MIN_TIME_DELAY_BETWEEN_PROGRESS_EVENTS = 1;

  private Thread creationThread = null;
  protected Comparator comparator;
  protected Filter filter;
  private List progressListeners;
  private Calendar lastProgressEventFired;
  private double widthToHeightRatio;

  class InternalComparator implements Comparator {
    Comparator externalComparator = null;

    public InternalComparator(Comparator externalComparator) {
      this.externalComparator = externalComparator;
    }

    public int compare(Object o1, Object o2) {
      InternalNode internalNode1 = (InternalNode) o1;
      InternalNode internalNode2 = (InternalNode) o2;

      return this.externalComparator.compare(internalNode1.getLayoutEntity(), internalNode2.getLayoutEntity());
    }

  }

  /*
   * Internal Nodes.  
   */
  private InternalNode[] internalNodes;
  private InternalRelationship[] internalRelationships;
  private double internalX;
  private double internalY;
  private double internalWidth;
  private double internalHeight;
  protected boolean internalContinuous;
  protected boolean internalAsynchronous;

  /*
   * A queue of entities and relationships to add or remove.  Each layout
   * algorithm should check these and update their internal lists.
   */

  /** A list of LayoutEntity objects to be removed from the layout. */
  private List entitiesToRemove;
  /** A list of LayoutRelationship objects to be removed. */
  private List relationshipsToRemove;
  /** A list of LayoutEntity objects to be added to the layout. */
  private List entitiesToAdd;
  /** A list of LayoutRelationship objects to be added. */
  private List relationshipsToAdd;

  //protected boolean cancelled = false;

  protected boolean layoutStopped = true;

  protected int layout_styles = 0;

  // Child classes can set to false to retain node shapes and sizes
  protected boolean resizeEntitiesAfterLayout = true;

  /**
   * Initializes the abstract layout algorithm.
   * @see LayoutStyles
   */
  public AbstractLayoutAlgorithm(int styles) {
    this.creationThread = Thread.currentThread();
    this.progressListeners = new ArrayList();
    this.lastProgressEventFired = Calendar.getInstance();
    this.widthToHeightRatio = 1.0;

    this.entitiesToRemove = new ArrayList();
    this.relationshipsToRemove = new ArrayList();
    this.entitiesToAdd = new ArrayList();
    this.relationshipsToAdd = new ArrayList();
    this.layout_styles = styles;
  }

  /**
   * Queues up the given entity (if it isn't in the list) to be added to the algorithm.
   * @param entity
   */
  public void addEntity(LayoutEntity entity) {
    if ((entity != null) && !entitiesToAdd.contains(entity)) {
      entitiesToAdd.add(entity);
    }
  }

  /**
   * Queues up the given relationshp (if it isn't in the list) to be added to the algorithm.
   * @param relationship
   */
  public void addRelationship(LayoutRelationship relationship) {
    if ((relationship != null) && !relationshipsToAdd.contains(relationship)) {
      relationshipsToAdd.add(relationship);
    }
  }

  /**
   * Queues up the given entity to be removed from the algorithm next time it runs.
   * @param entity The entity to remove
   */
  public void removeEntity(LayoutEntity entity) {
    if ((entity != null) && !entitiesToRemove.contains(entity)) {
      entitiesToRemove.add(entity);
    }
  }

  /**
   * Queues up the given relationship to be removed from the algorithm next time it runs.
   * @param relationship  The relationship to remove.
   */
  public void removeRelationship(LayoutRelationship relationship) {
    if ((relationship != null) && !relationshipsToRemove.contains(relationship)) {
      relationshipsToRemove.add(relationship);
    }
  }

  /**
   * Queues up all the relationships in the list to be removed.
   * @param relationships
   */
  public void removeRelationships(List relationships) {
    // note we don't check if the relationshipsToRemove contains
    // any of the objects in relationships.
    relationshipsToRemove.addAll(relationships);
  }

  /**
   * Sets the current layout style.  This overwrites all other layout styles.
   * Use getStyle to get the current style.
   * @param style
   */
  public void setStyle(int style) {
    this.layout_styles = style;
  }

  /**
   * Gets the current layout style
   * @return
   */
  public int getStyle() {
    return this.layout_styles;
  }

  public abstract void setLayoutArea(double x, double y, double width, double height);

  /**
   * Determines if the configuration is valid for this layout
   * @param asynchronous
   * @param continuous
   */
  protected abstract boolean isValidConfiguration(boolean asynchronous, boolean continuous);

  /**
   * Apply the layout to the given entities.  The entities will be moved and resized based
   * on the algorithm.
   *
   * @param entitiesToLayout Apply the algorithm to these entities
   * @param relationshipsToConsider Only consider these relationships when applying the algorithm.
   * @param x The left side of the bounds in which the layout can place the entities.
   * @param y The top side of the bounds in which the layout can place the entities.
   * @param width The width of the bounds in which the layout can place the entities.
   * @param height The height of the bounds in which the layout can place the entities.
   */
  abstract protected void applyLayoutInternal(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider, double boundsX, double boundsY, double boundsWidth, double boundsHeight);

  /**
   * Updates the given array of entities checking if any need to be removed or added.
   * @param entities the current entities
   * @return the updated entities array
   */
  protected InternalNode[] updateEntities(InternalNode[] entities) {
    if ((entitiesToRemove.size() > 0) || (entitiesToAdd.size() > 0)) {
      List internalNodesList = new ArrayList(Arrays.asList(entities));

      // remove nodes
      for (Iterator iter = entitiesToRemove.iterator(); iter.hasNext();) {
        LayoutEntity entity = (LayoutEntity) iter.next();
        if (entity.getLayoutInformation() != null) {
          internalNodesList.remove(entity.getLayoutInformation());
        }
      }

      // Also remove from _internalNodes
      ArrayList updatedEntities = new ArrayList(internalNodes.length - entitiesToRemove.size() + entitiesToAdd.size());
      for (int i = 0; i < internalNodes.length; i++) {
        InternalNode node = internalNodes[i];
        if (entitiesToRemove.contains(node.getLayoutEntity())) {
          entitiesToRemove.remove(node.getLayoutEntity());
        } else {
          updatedEntities.add(node);
        }
      }
      entitiesToRemove.clear();

      // Add any new nodes
      LayoutEntity[] entitiesArray = new LayoutEntity[entitiesToAdd.size()];
      entitiesArray = (LayoutEntity[]) entitiesToAdd.toArray(entitiesArray);
      InternalNode[] newNodes = createInternalNodes(entitiesArray);
      for (int i = 0; i < newNodes.length; i++) {
        internalNodesList.add(newNodes[i]);
        updatedEntities.add(newNodes[i]);
      }
      entitiesToAdd.clear();

      entities = new InternalNode[internalNodesList.size()];
      entities = (InternalNode[]) internalNodesList.toArray(entities);

      internalNodes = new InternalNode[updatedEntities.size()];
      internalNodes = (InternalNode[]) updatedEntities.toArray(internalNodes);
    }

    return entities;
  }

  /**
   * Updates the given array of relationships checking if any need to be removed or added.
   * Also updates the original array of relationships.
   * @param relationships the current relationships
   * @return the update relationships array
   */
  protected InternalRelationship[] updateRelationships(InternalRelationship[] relationships) {
    if ((relationshipsToRemove.size() > 0) || (relationshipsToAdd.size() > 0)) {
      List internalRelsList = new ArrayList(Arrays.asList(relationships));

      // remove relationships
      if (relationshipsToRemove.size() > 0) {
        for (Iterator iter = relationshipsToRemove.iterator(); iter.hasNext();) {
          LayoutRelationship relation = (LayoutRelationship) iter.next();
          if (relation.getLayoutInformation() != null) {
            internalRelsList.remove(relation.getLayoutInformation());
          }
        }
      }

      // Also remove from _internalRelationships
      ArrayList updatedRelationships = new ArrayList(internalRelationships.length - relationshipsToRemove.size() + relationshipsToAdd.size());
      for (int i = 0; i < internalRelationships.length; i++) {
        InternalRelationship relation = internalRelationships[i];
        if (relationshipsToRemove.contains(relation.getLayoutRelationship())) {
          relationshipsToRemove.remove(relation.getLayoutRelationship());
        } else {
          updatedRelationships.add(relation);
        }
      }
      relationshipsToRemove.clear();

      // add relationships
      if (relationshipsToAdd.size() > 0) {
        LayoutRelationship[] relsArray = new LayoutRelationship[relationshipsToAdd.size()];
        relsArray = (LayoutRelationship[]) relationshipsToAdd.toArray(relsArray);
        InternalRelationship[] newRelationships = createInternalRelationships(relsArray);
        for (int i = 0; i < newRelationships.length; i++) {
          internalRelsList.add(newRelationships[i]);
          updatedRelationships.add(newRelationships[i]);
        }
      }
      relationshipsToAdd.clear();

      relationships = new InternalRelationship[internalRelsList.size()];
      relationships = (InternalRelationship[]) internalRelsList.toArray(relationships);

      internalRelationships = new InternalRelationship[updatedRelationships.size()];
      internalRelationships = (InternalRelationship[]) updatedRelationships.toArray(internalRelationships);
    }

    return relationships;
  }

  /**
   * Moves all the entities by the given amount. 
   * @param dx  the amount to shift the entities in the x-direction
   * @param dy  the amount to shift the entities in the y-direction
   */
  /*
   public void moveAllEntities(double dx, double dy) {
   if ((dx != 0) || (dy != 0)) {
   synchronized (_internalNodes) {
   for (int i = 0; i < _internalNodes.length; i++) {
   InternalNode node = _internalNodes[i];
   node.setInternalLocation(node.getInternalX()+dx, node.getInternalY()+dy);
   node.setLocation(node.getX()+dx, node.getY()+dy);
   }
   }
   }
   }
   */

  /**
   * Returns true if the layout algorithm is running
   * @return boolean if the layout algorithm is running
   */
  public synchronized boolean isRunning() {
    return !layoutStopped;
  }

  /**
   * Stops the current layout from running.
   * All layout algorithms should constantly check isLayoutRunning
   */
  public synchronized void stop() {
    layoutStopped = true;
    postLayoutAlgorithm(internalNodes, internalRelationships);
    fireProgressEnded(getTotalNumberOfLayoutSteps());
  }

  //  /**
  //   * Sleeps while the algorithm is paused.
  //   */
  //  protected void sleepWhilePaused() {
  //    // do nothing while the algorithm is paused
  //    boolean wasPaused = false;
  //    while (isPaused()) {
  //      try {
  //        Thread.sleep(200);
  //      } catch (InterruptedException e) {
  //      }
  //      wasPaused = true;
  //    }
  //    // update the node positions (they might have been moved while paused)
  //    if (wasPaused) {
  //      for (int i = 0; i < internalNodes.length; i++) {
  //        InternalNode node = internalNodes[i];
  //        node.setInternalLocation(node.getPreferredX(), node.getPreferredY());
  //      }
  //    }
  //  }

  private void setupLayout(LayoutEntity[] entitiesToLayout, LayoutRelationship[] relationshipsToConsider, double x, double y, double width, double height) {
    internalX = x;
    internalY = y;
    internalHeight = height;
    internalWidth = width;
    // Filter all the unwanted entities and relationships
    entitiesToLayout = (LayoutEntity[]) filterUnwantedObjects(entitiesToLayout);
    relationshipsToConsider = (LayoutRelationship[]) filterUnwantedObjects(relationshipsToConsider);

    // Check that the input is valid
    if (!verifyInput(entitiesToLayout, relationshipsToConsider)) {
      layoutStopped = true;
      throw new RuntimeException("The relationships in relationshipsToConsider don't contain the entities in entitiesToLayout");
    }

    // Create the internal nodes and relationship
    internalNodes = createInternalNodes(entitiesToLayout);
    internalRelationships = createInternalRelationships(relationshipsToConsider);
  }

  //  public synchronized Stoppable getLayoutThread(LayoutEntity[] entitiesToLayout, LayoutRelationship[] relationshipsToConsider, double x, double y, double width, double height, boolean continuous) {
  //    //setupLayout( entitiesToLayout, relationshipsToConsider, x, y, width, height );
  //    this.layoutStopped = false;
  //    this.runContinuously = continuous;
  //    setupLayout(entitiesToLayout, relationshipsToConsider, x, y, width, height);
  //    preLayoutAlgorithm(internalNodes, internalRelationships, internalX, internalY, internalWidth, internalHeight);
  //    fireProgressStarted(getTotalNumberOfLayoutSteps());
  //    return this;
  //  }

  /**
   * Code called before the layout algorithm starts
   */
  protected abstract void preLayoutAlgorithm(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider, double x, double y, double width, double height);

  /**
   * Code called after the layout algorithm ends
   */
  protected abstract void postLayoutAlgorithm(InternalNode[] entitiesToLayout, InternalRelationship[] relationshipsToConsider);

  /**
   *  Gets the total number of steps in this layout
   */
  protected abstract int getTotalNumberOfLayoutSteps();

  /**
   * Gets the current layout step
   * @return
   */
  protected abstract int getCurrentLayoutStep();

  /**
   * This actually applies the layout
   */
  public synchronized void applyLayout(final LayoutEntity[] entitiesToLayout, final LayoutRelationship[] relationshipsToConsider, final double x, final double y, final double width, final double height, boolean asynchronous, boolean continuous) throws InvalidLayoutConfiguration {
    checkThread();
    this.internalAsynchronous = asynchronous;
    this.internalContinuous = continuous;

    if (!isValidConfiguration(asynchronous, continuous)) {
      throw new InvalidLayoutConfiguration();
    }

    clearBendPoints(relationshipsToConsider);

    this.layoutStopped = false;

    // when an algorithm starts, reset the progress event
    lastProgressEventFired = Calendar.getInstance();
    if (asynchronous) {

      Thread thread = new Thread(new Runnable() {

        public void run() {
          setupLayout(entitiesToLayout, relationshipsToConsider, x, y, width, height);
          preLayoutAlgorithm(internalNodes, internalRelationships, internalX, internalY, internalWidth, internalHeight);
          fireProgressStarted(getTotalNumberOfLayoutSteps());

          applyLayoutInternal(internalNodes, internalRelationships, internalX, internalY, internalWidth, internalHeight);
          stop();
        }

      });
      thread.setPriority(Thread.MIN_PRIORITY);
      thread.start();
    } else {

      // If we are running synchronously then we have to stop this at some
      // point? right?
      setupLayout(entitiesToLayout, relationshipsToConsider, x, y, width, height);
      preLayoutAlgorithm(internalNodes, internalRelationships, internalX, internalY, internalWidth, internalHeight);
      fireProgressStarted(getTotalNumberOfLayoutSteps());

      applyLayoutInternal(internalNodes, internalRelationships, internalX, internalY, internalWidth, internalHeight);
      stop();
    }

  }

  /**
   * Clear out all old bend points before doing a layout
   */
  private void clearBendPoints(LayoutRelationship[] relationships) {
    for (int i = 0; i < relationships.length; i++) {
      LayoutRelationship rel = relationships[i];
      rel.clearBendPoints();
    }
  }

  /**
   * Update external bend points from the internal bendpoints list. Save the
   * source and destination points for later use in scaling and translating
   * @param relationshipsToConsider
   */
  protected void updateBendPoints(InternalRelationship[] relationshipsToConsider) {
    for (int i = 0; i < relationshipsToConsider.length; i++) {
      InternalRelationship relationship = relationshipsToConsider[i];
      List bendPoints = relationship.getBendPoints();
      if (bendPoints.size() > 0) {
        // We will assume that source/dest coordinates are for center of node
        BendPoint[] externalBendPoints = new BendPoint[bendPoints.size() + 2];
        InternalNode sourceNode = relationship.getSource();
        externalBendPoints[0] = new BendPoint(sourceNode.getInternalX(), sourceNode.getInternalY());
        InternalNode destNode = relationship.getDestination();
        externalBendPoints[externalBendPoints.length - 1] = new BendPoint(destNode.getInternalX(), destNode.getInternalY());

        for (int j = 0; j < bendPoints.size(); j++) {
          BendPoint bp = (BendPoint) bendPoints.get(j);
          externalBendPoints[j + 1] = new BendPoint(bp.x, bp.y, bp.getIsControlPoint());
        }
        relationship.getLayoutRelationship().setBendPoints(externalBendPoints);
      }
    }
  }

  //  public void run() {
  //
  //    if (started == true) {
  //      throw new RuntimeException("Layout has already run!");
  //    }
  //    started = true;
  //    //layoutStopped = false;
  //    isLayoutPaused = false;
  //    applyLayoutInternal(internalNodes, internalRelationships, internalX, internalY, internalWidth, internalHeight);
  //    stop();
  //    layoutStopped = true;
  //    isLayoutPaused = false;
  //  }

  /**
   * Creates a list of InternalNode objects from the list of LayoutEntity objects the user
   * wants layed out. Sets the internal nodes' positions and sizes from the
   * external entities.
   */
  private InternalNode[] createInternalNodes(LayoutEntity[] nodes) {
    InternalNode[] internalNodes = new InternalNode[nodes.length];
    BasicEntityConstraint basicEntityConstraint = new BasicEntityConstraint();
    for (int i = 0; i < nodes.length; i++) {
      basicEntityConstraint.clear();
      LayoutEntity externalNode = nodes[i];
      InternalNode internalNode = new InternalNode(externalNode);
      externalNode.populateLayoutConstraint(basicEntityConstraint);
      internalNode.setInternalLocation(externalNode.getXInLayout(), externalNode.getYInLayout());
      internalNodes[i] = internalNode;
    } // end of for
    return internalNodes;
  }

  /**
   * Creates a list of InternalRelationship objects from the given list of LayoutRelationship objects.
   * @param rels
   * @return List of internal relationships
   */
  private InternalRelationship[] createInternalRelationships(LayoutRelationship[] rels) {
    ArrayList listOfInternalRelationships = new ArrayList(rels.length);
    for (int i = 0; i < rels.length; i++) {
      LayoutRelationship relation = rels[i];
      InternalNode src = (InternalNode) relation.getSourceInLayout().getLayoutInformation();
      InternalNode dest = (InternalNode) relation.getDestinationInLayout().getLayoutInformation();
      if ((src != null) && (dest != null)) {
        InternalRelationship internalRelationship = new InternalRelationship(relation, src, dest);
        listOfInternalRelationships.add(internalRelationship);
      } else {
        throw new RuntimeException("Error creating internal relationship, one of the nodes is null: src=" + src + ", dest=" + dest);
      }
    }
    InternalRelationship[] internalRelationships = new InternalRelationship[listOfInternalRelationships.size()];
    listOfInternalRelationships.toArray(internalRelationships);
    return internalRelationships;
  }

  /**
   * Removes any objects that are currently filtered
   */
  private Object[] filterUnwantedObjects(Object[] objects) {
    // first remove any entities or relationships that are filtered.
    List unfilteredObjsList = new ArrayList();
    if (filter != null) {
      for (int i = 0; i < objects.length; i++) {
        Object object = objects[i];
        if (!filter.isObjectFiltered(object)) {
          unfilteredObjsList.add(object);
        }
      }
      //@tag bug.156266-ClassCast.fix : use reflection to create the array.
      Object[] unfilteredObjs = (Object[]) java.lang.reflect.Array.newInstance(objects.getClass().getComponentType(), unfilteredObjsList.size());
      unfilteredObjsList.toArray(unfilteredObjs);
      return unfilteredObjs;
    }
    return objects;
  }

  /**
   * Filters the entities and relationships to apply the layout on
   */
  public void setFilter(Filter filter) {
    this.filter = filter;
  }

  /**
   * Determines the order in which the objects should be displayed.
   * Note: Some algorithms force a specific order.
   */
  public void setComparator(Comparator comparator) {
    this.comparator = new InternalComparator(comparator);
  }

  /**
   * Verifies the endpoints of the relationships are entities in the entitiesToLayout list.
   * Allows other classes in this package to use this method to verify the input
   */
  public static boolean verifyInput(LayoutEntity[] entitiesToLayout, LayoutRelationship[] relationshipsToConsider) {
    boolean stillValid = true;
    for (int i = 0; i < relationshipsToConsider.length; i++) {
      LayoutRelationship relationship = relationshipsToConsider[i];
      LayoutEntity source = relationship.getSourceInLayout();
      LayoutEntity destination = relationship.getDestinationInLayout();
      boolean containsSrc = false;
      boolean containsDest = false;
      int j = 0;
      while (j < entitiesToLayout.length && !(containsSrc && containsDest)) {
        if (entitiesToLayout[j].equals(source)) {
          containsSrc = true;
        }
        if (entitiesToLayout[j].equals(destination)) {
          containsDest = true;
        }
        j++;
      }
      stillValid = containsSrc && containsDest;
    }
    return stillValid;
  }

  /**
   * Gets the location in the layout bounds for this node
   * @param x
   * @param y
   * @return
   */
  protected DisplayIndependentPoint getLocalLocation(InternalNode[] entitiesToLayout, double x, double y, DisplayIndependentRectangle realBounds) {

    double screenWidth = realBounds.width;
    double screenHeight = realBounds.height;
    DisplayIndependentRectangle layoutBounds = getLayoutBounds(entitiesToLayout, false);
    double localX = (x / screenWidth) * layoutBounds.width + layoutBounds.x;
    double localY = (y / screenHeight) * layoutBounds.height + layoutBounds.y;
    return new DisplayIndependentPoint(localX, localY);
  }

  /**
   * Find an appropriate size for the given nodes, then fit them into the given bounds.
   * The relative locations of the nodes to each other must be preserved.
   * Child classes should set flag reresizeEntitiesAfterLayout to false if they
   * want to preserve node sizes.
   */
  protected void defaultFitWithinBounds(InternalNode[] entitiesToLayout, DisplayIndependentRectangle realBounds) {
    defaultFitWithinBounds(entitiesToLayout, new InternalRelationship[0], realBounds);
  }

  /**
   * Find an appropriate size for the given nodes, then fit them into the given bounds.
   * The relative locations of the nodes to each other must be preserved.
   * Child classes should set flag reresizeEntitiesAfterLayout to false if they
   * want to preserve node sizes.
   */
  protected void defaultFitWithinBounds(InternalNode[] entitiesToLayout, InternalRelationship[] relationships, DisplayIndependentRectangle realBounds) {

    DisplayIndependentRectangle layoutBounds;

    if (resizeEntitiesAfterLayout) {
      layoutBounds = getLayoutBounds(entitiesToLayout, false);

      // Convert node x,y to be in percent rather than absolute coords
      convertPositionsToPercentage(entitiesToLayout, relationships, layoutBounds, false /*do not update size*/);

      // Resize and shift nodes
      resizeAndShiftNodes(entitiesToLayout);
    }

    // Recalculate layout, allowing for the node width, which we now know
    layoutBounds = getLayoutBounds(entitiesToLayout, true);

    // adjust node positions again, to the new coordinate system (still as a percentage)
    convertPositionsToPercentage(entitiesToLayout, relationships, layoutBounds, true /*update node size*/);

    DisplayIndependentRectangle screenBounds = calcScreenBounds(realBounds, layoutBounds);

    // Now convert to real screen coordinates
    convertPositionsToCoords(entitiesToLayout, relationships, screenBounds);
  }

  /**
   * Calculate the screen bounds, maintaining the 
   * @param realBounds
   * @return
   */
  private DisplayIndependentRectangle calcScreenBounds(DisplayIndependentRectangle realBounds, DisplayIndependentRectangle layoutBounds) {
    if (resizeEntitiesAfterLayout) { // OK to alter aspect ratio
      double borderWidth = Math.min(realBounds.width, realBounds.height) / 10.0; // use 10% for the border - 5% on each side
      return new DisplayIndependentRectangle(realBounds.x + borderWidth / 2.0, realBounds.y + borderWidth / 2.0, realBounds.width - borderWidth, realBounds.height - borderWidth);
    } else { // retain layout aspect ratio
      double heightAdjustment = realBounds.height / layoutBounds.height;
      double widthAdjustment = realBounds.width / layoutBounds.width;
      double ratio = Math.min(heightAdjustment, widthAdjustment);
      double adjustedHeight = layoutBounds.height * ratio;
      double adjustedWidth = layoutBounds.width * ratio;
      double adjustedX = realBounds.x + (realBounds.width - adjustedWidth) / 2.0;
      double adjustedY = realBounds.y + (realBounds.height - adjustedHeight) / 2.0;
      double borderWidth = Math.min(adjustedWidth, adjustedHeight) / 10.0; // use 10% for the border - 5% on each side
      return new DisplayIndependentRectangle(adjustedX + borderWidth / 2.0, adjustedY + borderWidth / 2.0, adjustedWidth - borderWidth, adjustedHeight - borderWidth);
    }
  }

  /**
   * Find and set the node size - shift the nodes to the right and down to make
   * room for the width and height.
   * @param entitiesToLayout
   * @param relationships
   */
  private void resizeAndShiftNodes(InternalNode[] entitiesToLayout) {
    // get maximum node size as percent of screen dimmensions
    double nodeSize = getNodeSize(entitiesToLayout);
    double halfNodeSize = nodeSize / 2;

    // Resize and shift nodes
    for (int i = 0; i < entitiesToLayout.length; i++) {
      InternalNode node = entitiesToLayout[i];
      node.setInternalSize(nodeSize, nodeSize);
      node.setInternalLocation(node.getInternalX() + halfNodeSize, node.getInternalY() + halfNodeSize);
    }
  }

  /**
   * Convert all node positions into a percentage of the screen. If includeNodeSize
   * is true then this also updates the node's internal size.
   * @param entitiesToLayout
   */
  private void convertPositionsToPercentage(InternalNode[] entitiesToLayout, InternalRelationship[] relationships, DisplayIndependentRectangle layoutBounds, boolean includeNodeSize) {

    // Adjust node positions and sizes
    for (int i = 0; i < entitiesToLayout.length; i++) {
      InternalNode node = entitiesToLayout[i];
      DisplayIndependentPoint location = node.getInternalLocation().convertToPercent(layoutBounds);
      node.setInternalLocation(location.x, location.y);
      if (includeNodeSize) { // adjust node sizes
        double width = node.getInternalWidth() / layoutBounds.width;
        double height = node.getInternalHeight() / layoutBounds.height;
        node.setInternalSize(width, height);
      }
    }

    // Adjust bendpoint positions
    for (int i = 0; i < relationships.length; i++) {
      InternalRelationship rel = relationships[i];
      for (int j = 0; j < rel.getBendPoints().size(); j++) {
        BendPoint bp = (BendPoint) rel.getBendPoints().get(j);
        DisplayIndependentPoint toPercent = bp.convertToPercent(layoutBounds);
        bp.setX(toPercent.x);
        bp.setY(toPercent.y);
      }
    }
  }

  /**
   * Convert the positions from a percentage of bounds area to fixed
   * coordinates. NOTE: ALL OF THE POSITIONS OF NODES UNTIL NOW WERE FOR THE
   * CENTER OF THE NODE - Convert it to the left top corner.
   *
   * @param entitiesToLayout
   * @param relationships
   * @param realBounds
   */
  private void convertPositionsToCoords(InternalNode[] entitiesToLayout, InternalRelationship[] relationships, DisplayIndependentRectangle screenBounds) {

    // Adjust node positions and sizes
    for (int i = 0; i < entitiesToLayout.length; i++) {
      InternalNode node = entitiesToLayout[i];
      double width = node.getInternalWidth() * screenBounds.width;
      double height = node.getInternalHeight() * screenBounds.height;
      DisplayIndependentPoint location = node.getInternalLocation().convertFromPercent(screenBounds);
      node.setInternalLocation(location.x - width / 2, location.y - height / 2);
      if (resizeEntitiesAfterLayout) {
        adjustNodeSizeAndPos(node, height, width);
      } else {
        node.setInternalSize(width, height);
      }
    }

    // Adjust bendpoint positions and shift based on source node size
    for (int i = 0; i < relationships.length; i++) {
      InternalRelationship rel = relationships[i];
      for (int j = 0; j < rel.getBendPoints().size(); j++) {
        BendPoint bp = (BendPoint) rel.getBendPoints().get(j);
        DisplayIndependentPoint fromPercent = bp.convertFromPercent(screenBounds);
        bp.setX(fromPercent.x);
        bp.setY(fromPercent.y);
      }
    }
  }

  /**
   * Adjust node size to take advantage of space. Reset position to top left corner of node.
   * @param node
   * @param height
   * @param width
   */
  private void adjustNodeSizeAndPos(InternalNode node, double height, double width) {
    double widthUsingHeight = height * widthToHeightRatio;
    if (widthToHeightRatio <= 1.0 && widthUsingHeight <= width) {
      double widthToUse = height * widthToHeightRatio;
      double leftOut = width - widthToUse;
      node.setInternalSize(Math.max(height * widthToHeightRatio, MIN_ENTITY_SIZE), Math.max(height, MIN_ENTITY_SIZE));
      node.setInternalLocation(node.getInternalX() + leftOut / 2, node.getInternalY());

    } else {
      double heightToUse = height / widthToHeightRatio;
      double leftOut = height - heightToUse;

      node.setInternalSize(Math.max(width, MIN_ENTITY_SIZE), Math.max(width / widthToHeightRatio, MIN_ENTITY_SIZE));
      node.setInternalLocation(node.getInternalX(), node.getInternalY() + leftOut / 2);
    }

  }

  /**
   * Returns the maximum possible node size as a percentage of the width or height in current coord system.
   */
  private double getNodeSize(InternalNode[] entitiesToLayout) {
    double width, height;
    if (entitiesToLayout.length == 1) {
      width = 0.8;
      height = 0.8;
    } else {
      DisplayIndependentDimension minimumDistance = getMinimumDistance(entitiesToLayout);
      width = 0.8 * minimumDistance.width;
      height = 0.8 * minimumDistance.height;
    }
    return Math.max(width, height);
  }

  /**
   * Find the bounds in which the nodes are located.  Using the bounds against the real bounds
   * of the screen, the nodes can proportionally be placed within the real bounds.
   * The bounds can be determined either including the size of the nodes or not.  If the size
   * is not included, the bounds will only be guaranteed to include the center of each node.
   */
  protected DisplayIndependentRectangle getLayoutBounds(InternalNode[] entitiesToLayout, boolean includeNodeSize) {
    double rightSide = Double.MIN_VALUE;
    double bottomSide = Double.MIN_VALUE;
    double leftSide = Double.MAX_VALUE;
    double topSide = Double.MAX_VALUE;
    for (int i = 0; i < entitiesToLayout.length; i++) {
      InternalNode entity = entitiesToLayout[i];
      if (entity.hasPreferredLocation()) {
        continue;
      }

      if (includeNodeSize) {
        leftSide = Math.min(entity.getInternalX() - entity.getInternalWidth() / 2, leftSide);
        topSide = Math.min(entity.getInternalY() - entity.getInternalHeight() / 2, topSide);
        rightSide = Math.max(entity.getInternalX() + entity.getInternalWidth() / 2, rightSide);
        bottomSide = Math.max(entity.getInternalY() + entity.getInternalHeight() / 2, bottomSide);
      } else {
        leftSide = Math.min(entity.getInternalX(), leftSide);
        topSide = Math.min(entity.getInternalY(), topSide);
        rightSide = Math.max(entity.getInternalX(), rightSide);
        bottomSide = Math.max(entity.getInternalY(), bottomSide);
      }
    }
    return new DisplayIndependentRectangle(leftSide, topSide, rightSide - leftSide, bottomSide - topSide);
  }

  /**
   * minDistance is the closest that any two points are together.
   * These two points become the center points for the two closest nodes,
   * which we wish to make them as big as possible without overlapping.
   * This will be the maximum of minDistanceX and minDistanceY minus a bit, lets say 20%
   *
   * We make the recommended node size a square for convenience.
   *
   *
   *    _______
   *   |       |
   *   |       |
   *   |   +   |
   *   |   |\  |
   *   |___|_\_|_____
   *       | | \     |
   *       | |  \    |
   *       +-|---+   |
   *         |       |
   *         |_______|
   *
   * 
   *
   */
  private DisplayIndependentDimension getMinimumDistance(InternalNode[] entitiesToLayout) {
    DisplayIndependentDimension horAndVertdistance = new DisplayIndependentDimension(Double.MAX_VALUE, Double.MAX_VALUE);
    double minDistance = Double.MAX_VALUE; // the minimum distance between all the nodes
    //TODO: Very Slow!
    for (int i = 0; i < entitiesToLayout.length; i++) {
      InternalNode layoutEntity1 = entitiesToLayout[i];
      double x1 = layoutEntity1.getInternalX();
      double y1 = layoutEntity1.getInternalY();
      for (int j = i + 1; j < entitiesToLayout.length; j++) {
        InternalNode layoutEntity2 = entitiesToLayout[j];
        double x2 = layoutEntity2.getInternalX();
        double y2 = layoutEntity2.getInternalY();
        double distanceX = Math.abs(x1 - x2);
        double distanceY = Math.abs(y1 - y2);
        double distance = Math.sqrt(Math.pow(distanceX, 2) + Math.pow(distanceY, 2));

        if (distance < minDistance) {
          minDistance = distance;
          horAndVertdistance.width = distanceX;
          horAndVertdistance.height = distanceY;
        }
      }
    }
    return horAndVertdistance;
  }

  /**
   * Set the width to height ratio you want the entities to use
   */
  public void setEntityAspectRatio(double ratio) {
    widthToHeightRatio = ratio;
  }

  /**
   * Returns the width to height ratio this layout will use to set the size of the entities.
   */
  public double getEntityAspectRatio() {
    return widthToHeightRatio;
  }

  /**
   * A layout algorithm could take an uncomfortable amout of time to complete.  To relieve some of
   * the mystery, the layout algorithm will notify each ProgressListener of its progress.
   */
  public void addProgressListener(ProgressListener listener) {
    if (!progressListeners.contains(listener)) {
      progressListeners.add(listener);
    }
  }

  /**
   * Removes the given progress listener, preventing it from receiving any more updates.
   */
  public void removeProgressListener(ProgressListener listener) {
    if (progressListeners.contains(listener)) {
      progressListeners.remove(listener);
    }
  }

  /**
   * Updates the layout locations so the external nodes know about the new locations
   */
  protected void updateLayoutLocations(InternalNode[] nodes) {
    for (int i = 0; i < nodes.length; i++) {
      InternalNode node = nodes[i];
      if (!node.hasPreferredLocation()) {
        node.setLocation(node.getInternalX(), node.getInternalY());

        if ((layout_styles & LayoutStyles.NO_LAYOUT_NODE_RESIZING) != 1) {
          // Only set the size if we are supposed to
          node.setSize(node.getInternalWidth(), node.getInternalHeight());
        }
      }
    }
  }

  protected void fireProgressStarted(int totalNumberOfSteps) {
    ProgressEvent event = new ProgressEvent(0, totalNumberOfSteps);
    for (int i = 0; i < progressListeners.size(); i++) {
      ProgressListener listener = (ProgressListener) progressListeners.get(i);

      listener.progressStarted(event);
    }
  }

  protected void fireProgressEnded(int totalNumberOfSteps) {
    ProgressEvent event = new ProgressEvent(totalNumberOfSteps, totalNumberOfSteps);
    for (int i = 0; i < progressListeners.size(); i++) {
      ProgressListener listener = (ProgressListener) progressListeners.get(i);
      listener.progressEnded(event);
    }

  }

  /**
   * Fires an event to notify all of the registered ProgressListeners that another step
   * has been completed in the algorithm.
   * @param currentStep The current step completed.
   * @param totalNumberOfSteps The total number of steps in the algorithm.
   */
  protected void fireProgressEvent(int currentStep, int totalNumberOfSteps) {

    // Update the layout locations to the external nodes
    Calendar now = Calendar.getInstance();
    now.add(Calendar.MILLISECOND, -MIN_TIME_DELAY_BETWEEN_PROGRESS_EVENTS);

    if (now.after(lastProgressEventFired) || currentStep == totalNumberOfSteps) {
      ProgressEvent event = new ProgressEvent(currentStep, totalNumberOfSteps);

      for (int i = 0; i < progressListeners.size(); i++) {

        ProgressListener listener = (ProgressListener) progressListeners.get(i);
        listener.progressUpdated(event);
      }
      lastProgressEventFired = Calendar.getInstance();
    }

  }

  protected int getNumberOfProgressListeners() {
    return progressListeners.size();
  }

  private void checkThread() {
    if (this.creationThread != Thread.currentThread()) {
      throw new RuntimeException("Invalid Thread Access.");
    }
  }

}
TOP

Related Classes of org.eclipse.zest.layouts.algorithms.AbstractLayoutAlgorithm$InternalComparator

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.