org.netbeans.modules.visual.layout.ConnectionWidgetLayout Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.visual.layout;
import org.netbeans.api.visual.layout.LayoutFactory.ConnectionWidgetLayoutAlignment;
import org.netbeans.modules.visual.util.GeomUtil;
import org.netbeans.api.visual.widget.ConnectionWidget;
import org.netbeans.api.visual.widget.Widget;
import org.netbeans.api.visual.layout.Layout;
import org.netbeans.api.visual.layout.LayoutFactory;
import java.awt.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* @author David Kaspar
*/
public final class ConnectionWidgetLayout implements Layout {
private final boolean useStacking = true; // if false, then it is backward compatible; if true, then child widgets with the same placement are stacked vertically
private final HashMap placements;
private final HashMap> reverse;
public ConnectionWidgetLayout () {
placements = new HashMap();
reverse = new HashMap> ();
}
public void setConstraint (Widget childWidget, ConnectionWidgetLayoutAlignment alignment, float placementInPercentage) {
assert childWidget != null;
assert alignment != null;
setConstraint (childWidget, new Placement (alignment, placementInPercentage));
}
public void setConstraint (Widget childWidget, ConnectionWidgetLayoutAlignment alignment, int placementAtDistance) {
assert childWidget != null;
assert alignment != null;
setConstraint (childWidget, new Placement (alignment, placementAtDistance));
}
public void removeConstraint (Widget childWidget) {
assert childWidget != null;
setConstraint(childWidget, null);
}
private void setConstraint (final Widget childWidget, final Placement newPlacement) {
final Placement oldPlacement;
if (newPlacement != null)
oldPlacement = placements.put (childWidget, newPlacement);
else
oldPlacement = placements.remove (childWidget);
if (oldPlacement != null && ! oldPlacement.equals (newPlacement))
reverse.get(oldPlacement).remove (childWidget);
if (newPlacement != null) {
ArrayList list = reverse.get(newPlacement);
if (list == null) {
list = new ArrayList ();
reverse.put (newPlacement, list);
}
list.add (childWidget);
}
}
public void layout (Widget widget) {
ConnectionWidget connectionWidget = (ConnectionWidget) widget;
connectionWidget.calculateRouting ();
java.util.List controlPoints = connectionWidget.getControlPoints ();
boolean empty = controlPoints == null || controlPoints.size () <= 1;
double totalDistance = 0.0;
double[] distances = new double[empty ? 0 : controlPoints.size () - 1];
for (int i = 0; i < distances.length; i ++)
distances[i] = totalDistance += GeomUtil.distanceSq (controlPoints.get (i), controlPoints.get (i + 1));
ArrayList childrenToResolve = new ArrayList (widget.getChildren());
for (Map.Entry> entry : reverse.entrySet()) {
Placement placement = entry.getKey ();
ArrayList currentlyResolving = null;
for (Widget childWidget : entry.getValue()) {
if (childWidget.getParentWidget() == widget && childWidget.isVisible()) {
if (currentlyResolving == null)
currentlyResolving = new ArrayList();
currentlyResolving.add(childWidget);
}
}
if (currentlyResolving == null)
continue;
Point point;
if (empty) {
point = new Point();
} else if (placement.isPercentage) {
float percentage = placement.placementInPercentage;
if (percentage <= 0.0)
point = connectionWidget.getFirstControlPoint ();
else if (percentage >= 1.0)
point = connectionWidget.getLastControlPoint ();
else
point = getLinePointAtPercentage (distances, (int) (percentage * totalDistance), controlPoints);
} else {
int distance = placement.placementAtDistance;
if (distance < 0)
point = getLinePointAtPercentage (distances, distance + (int) totalDistance, controlPoints);
else
point = getLinePointAtPercentage (distances, distance, controlPoints);
}
layoutChildrenAt (point, placement.alignment, connectionWidget, currentlyResolving);
childrenToResolve.removeAll (currentlyResolving);
}
if (! childrenToResolve.isEmpty())
layoutChildrenAt (new Point (), null, connectionWidget, childrenToResolve);
}
public boolean requiresJustification (Widget widget) {
return false;
}
public void justify (Widget widget) {
}
private Point getLinePointAtPercentage (double[] distances, int lineDistance, java.util.List controlPoints) {
int index = distances.length - 1;
for (int i = 0; i < distances.length; i ++) {
if (lineDistance < distances[i]) {
index = i;
break;
}
}
double segmentStartDistance = index > 0 ? distances[index - 1] : 0;
double segmentLength = distances[index] - segmentStartDistance;
double segmentDistance = lineDistance - segmentStartDistance;
if (segmentLength == 0.0) {
return controlPoints.get (index);
}
Point p1 = controlPoints.get (index);
Point p2 = controlPoints.get (index + 1);
double segmentFactor = segmentDistance / segmentLength;
return new Point ((int) (p1.x + (p2.x - p1.x) * segmentFactor), (int) (p1.y + (p2.y - p1.y) * segmentFactor));
}
private void layoutChildrenAt (Point linePoint, ConnectionWidgetLayoutAlignment alignment, ConnectionWidget connectionWidget, ArrayList children) {
if (alignment == null)
alignment = ConnectionWidgetLayoutAlignment.NONE;
ConnectionWidgetLayoutAlignment adjustedAlignment = getAdjustedAlignment(alignment, connectionWidget);
if (useStacking) {
layoutStackedChildrenAt (linePoint, adjustedAlignment, connectionWidget, children);
} else {
for (Widget childWidget : children) {
layoutSingleChildAt (linePoint, adjustedAlignment, connectionWidget, childWidget);
}
}
}
private void layoutStackedChildrenAt (Point linePoint, ConnectionWidgetLayoutAlignment adjustedAlignment, ConnectionWidget connectionWidget, ArrayList children) {
int areaWidth = 0, areaHeight = 0;
if (adjustedAlignment != ConnectionWidgetLayoutAlignment.NONE) {
for (Widget childWidget : children) {
Rectangle bounds = childWidget.getPreferredBounds ();
areaWidth = Math.max (areaWidth, bounds.width);
areaHeight += bounds.height;
}
}
Point referencePoint = getReferencePointForAdjustedAlignment(adjustedAlignment, new Rectangle (areaWidth, areaHeight));
int areaX = linePoint.x - referencePoint.x;
int areaY = linePoint.y - referencePoint.y;
int yCursor = 0;
for (Widget childWidget : children) {
Rectangle preferredBounds = childWidget.getPreferredBounds ();
Point location = childWidget.getPreferredLocation ();
int x = areaX - preferredBounds.x;
int y = areaY + yCursor - preferredBounds.y;
if (location != null) {
x += location.x;
y += location.y;
}
switch (adjustedAlignment) {
case CENTER_LEFT:
break;
case CENTER_RIGHT:
x += areaWidth - preferredBounds.width;
break;
case CENTER:
x += (areaWidth - preferredBounds.width) / 2;
break;
}
yCursor += preferredBounds.height;
childWidget.resolveBounds (new Point (x, y), preferredBounds);
}
}
private void layoutSingleChildAt (Point linePoint, ConnectionWidgetLayoutAlignment alignment, ConnectionWidget connectionWidget, Widget childWidget) {
Rectangle preferredBounds = childWidget.getPreferredBounds ();
Point referencePoint = getReferencePointForAdjustedAlignment (alignment, preferredBounds);
Point location = childWidget.getPreferredLocation ();
if (location != null)
referencePoint.translate (-location.x, -location.y);
childWidget.resolveBounds (new Point (linePoint.x - referencePoint.x, linePoint.y - referencePoint.y), preferredBounds);
}
private ConnectionWidgetLayoutAlignment getAdjustedHorizontalAlignment (ConnectionWidgetLayoutAlignment adjustedAlignment) {
switch (adjustedAlignment) {
case TOP_LEFT:
case CENTER_LEFT:
case BOTTOM_LEFT:
return ConnectionWidgetLayoutAlignment.CENTER_LEFT;
case TOP_RIGHT:
case CENTER_RIGHT:
case BOTTOM_RIGHT:
return ConnectionWidgetLayoutAlignment.CENTER_RIGHT;
case TOP_CENTER:
case CENTER:
case BOTTOM_CENTER:
return ConnectionWidgetLayoutAlignment.CENTER;
default:
return ConnectionWidgetLayoutAlignment.NONE;
}
}
private Point getReferencePointForAdjustedAlignment (ConnectionWidgetLayoutAlignment adjustedAlignment, Rectangle rectangle) {
switch (adjustedAlignment) {
case BOTTOM_CENTER:
return new Point (GeomUtil.centerX (rectangle), rectangle.y - 1);
case BOTTOM_LEFT:
return new Point (rectangle.x + rectangle.width, rectangle.y - 1);
case BOTTOM_RIGHT:
return new Point (rectangle.x - 1, rectangle.y - 1);
case CENTER:
return GeomUtil.center (rectangle);
case CENTER_LEFT:
return new Point (rectangle.x + rectangle.width, GeomUtil.centerY (rectangle));
case CENTER_RIGHT:
return new Point (rectangle.x - 1, GeomUtil.centerY (rectangle));
case NONE:
return new Point ();
case TOP_CENTER:
return new Point (GeomUtil.centerX (rectangle), rectangle.y + rectangle.height);
case TOP_LEFT:
return new Point (rectangle.x + rectangle.width, rectangle.y + rectangle.height);
case TOP_RIGHT:
return new Point (rectangle.x - 1, rectangle.y + rectangle.height);
default:
return new Point ();
}
}
private ConnectionWidgetLayoutAlignment getAdjustedAlignment(ConnectionWidgetLayoutAlignment alignment, ConnectionWidget connectionWidget) {
LayoutFactory.ConnectionWidgetLayoutAlignment retVal = alignment;
if(alignment == ConnectionWidgetLayoutAlignment.CENTER_SOURCE) {
Point sourcePt = connectionWidget.getFirstControlPoint();
Rectangle sourceBounds = getSourceBounds(connectionWidget);
retVal = calculateBestCenterAlignment(sourcePt, sourceBounds);
} else if(alignment == ConnectionWidgetLayoutAlignment.CENTER_TARGET) {
Point targetPt = connectionWidget.getLastControlPoint();
Rectangle targetBounds = getTargetBounds(connectionWidget);
retVal = calculateBestCenterAlignment(targetPt, targetBounds);
} else if(alignment == ConnectionWidgetLayoutAlignment.BOTTOM_SOURCE) {
Point sourcePt = connectionWidget.getFirstControlPoint();
Rectangle sourceBounds = getSourceBounds(connectionWidget);
retVal = calculateBestBottomAlignment(sourcePt, sourceBounds);
} else if(alignment == ConnectionWidgetLayoutAlignment.BOTTOM_TARGET) {
Point targetPt = connectionWidget.getLastControlPoint();
Rectangle targetBounds = getTargetBounds(connectionWidget);
retVal = calculateBestBottomAlignment(targetPt, targetBounds);
} else if(alignment == ConnectionWidgetLayoutAlignment.TOP_SOURCE) {
Point sourcePt = connectionWidget.getFirstControlPoint();
Rectangle sourceBounds = getSourceBounds(connectionWidget);
retVal = calculateBestTopAlignment(sourcePt, sourceBounds);
} else if(alignment == ConnectionWidgetLayoutAlignment.TOP_TARGET) {
Point targetPt = connectionWidget.getLastControlPoint();
Rectangle targetBounds = getTargetBounds(connectionWidget);
retVal = calculateBestTopAlignment(targetPt, targetBounds);
}
return retVal;
}
private LayoutFactory.ConnectionWidgetLayoutAlignment calculateBestCenterAlignment(Point point, Rectangle bounds) {
LayoutFactory.ConnectionWidgetLayoutAlignment retVal = LayoutFactory.ConnectionWidgetLayoutAlignment.NONE;
if(bounds != null) {
if(point.x <= bounds.x) {
retVal = LayoutFactory.ConnectionWidgetLayoutAlignment.CENTER_LEFT;
} else if(point.x >= (bounds.x + bounds.width)) {
retVal = LayoutFactory.ConnectionWidgetLayoutAlignment.CENTER_RIGHT;
} else if(point.y <= bounds.y) {
retVal = LayoutFactory.ConnectionWidgetLayoutAlignment.TOP_CENTER;
} else if(point.y >= (bounds.y + bounds.height)) {
retVal = LayoutFactory.ConnectionWidgetLayoutAlignment.BOTTOM_CENTER;
}
}
return retVal;
}
private LayoutFactory.ConnectionWidgetLayoutAlignment calculateBestBottomAlignment(Point point, Rectangle bounds) {
LayoutFactory.ConnectionWidgetLayoutAlignment retVal = LayoutFactory.ConnectionWidgetLayoutAlignment.NONE;
if(point.x <= bounds.x) {
retVal = LayoutFactory.ConnectionWidgetLayoutAlignment.BOTTOM_LEFT;
} else if(point.x >= (bounds.x + bounds.width)) {
retVal = LayoutFactory.ConnectionWidgetLayoutAlignment.BOTTOM_RIGHT;
} else if(point.y <= bounds.y) {
retVal = LayoutFactory.ConnectionWidgetLayoutAlignment.TOP_RIGHT;
} else if(point.y >= (bounds.y + bounds.height)) {
retVal = LayoutFactory.ConnectionWidgetLayoutAlignment.BOTTOM_LEFT;
}
return retVal;
}
private LayoutFactory.ConnectionWidgetLayoutAlignment calculateBestTopAlignment(Point point, Rectangle bounds) {
LayoutFactory.ConnectionWidgetLayoutAlignment retVal = LayoutFactory.ConnectionWidgetLayoutAlignment.NONE;
if(point.x <= bounds.x) {
retVal = LayoutFactory.ConnectionWidgetLayoutAlignment.TOP_LEFT;
} else if(point.x >= (bounds.x + bounds.width)) {
retVal = LayoutFactory.ConnectionWidgetLayoutAlignment.TOP_RIGHT;
} else if(point.y <= bounds.y) {
retVal = LayoutFactory.ConnectionWidgetLayoutAlignment.TOP_LEFT;
} else if(point.y >= (bounds.y + bounds.height)) {
retVal = LayoutFactory.ConnectionWidgetLayoutAlignment.BOTTOM_RIGHT;
}
return retVal;
}
private Rectangle getSourceBounds(ConnectionWidget connectionWidget) {
Widget source = connectionWidget.getSourceAnchor().getRelatedWidget();
if (source == null)
return null;
Point sourceLocation = source.getLocation();
Rectangle clientArea = source.getClientArea();
return new Rectangle(sourceLocation, clientArea.getSize());
}
private Rectangle getTargetBounds(ConnectionWidget connectionWidget) {
Widget target = connectionWidget.getTargetAnchor().getRelatedWidget();
if (target == null)
return null;
Point targetLocation = target.getLocation();
Rectangle targetArea = target.getClientArea();
return new Rectangle(targetLocation, targetArea.getSize());
}
private static class Placement {
private final ConnectionWidgetLayoutAlignment alignment;
private final boolean isPercentage;
private final float placementInPercentage;
private final int placementAtDistance;
public Placement(ConnectionWidgetLayoutAlignment alignment, float placementInPercentage) {
this.alignment = alignment;
this.isPercentage = true;
this.placementInPercentage = placementInPercentage;
this.placementAtDistance = 0;
}
public Placement(ConnectionWidgetLayoutAlignment alignment, int placementAtDistance) {
this.alignment = alignment;
this.isPercentage = false;
this.placementInPercentage = 0.0f;
this.placementAtDistance = placementAtDistance;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Placement other = (Placement) obj;
if (this.alignment != other.alignment) {
return false;
}
if (this.isPercentage != other.isPercentage) {
return false;
}
if (this.placementInPercentage != other.placementInPercentage) {
return false;
}
if (this.placementAtDistance != other.placementAtDistance) {
return false;
}
return true;
}
@Override
public int hashCode() {
int hash = 3;
hash = 17 * hash + (this.alignment != null ? this.alignment.hashCode() : 0);
hash = 17 * hash + (this.isPercentage ? 1 : 0);
hash = 17 * hash + Float.floatToIntBits(this.placementInPercentage);
hash = 17 * hash + this.placementAtDistance;
return hash;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy