0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041: package org.netbeans.api.visual.widget;
0042:
0043: import org.netbeans.api.visual.anchor.Anchor;
0044: import org.netbeans.api.visual.anchor.AnchorShape;
0045: import org.netbeans.api.visual.anchor.PointShape;
0046: import org.netbeans.api.visual.layout.LayoutFactory;
0047: import org.netbeans.api.visual.model.ObjectState;
0048: import org.netbeans.api.visual.router.Router;
0049: import org.netbeans.api.visual.router.RouterFactory;
0050: import org.netbeans.modules.visual.layout.ConnectionWidgetLayout;
0051:
0052: import java.awt.*;
0053: import java.awt.geom.AffineTransform;
0054: import java.awt.geom.GeneralPath;
0055: import java.awt.geom.Line2D;
0056: import java.awt.geom.Point2D;
0057: import java.util.ArrayList;
0058: import java.util.Collection;
0059: import java.util.Collections;
0060: import java.util.List;
0061:
0062: /**
0063: * This class represents a connection between two location. The locations are resolved by Anchors.
0064: * The path of the connection is specified by control points which are resolved by Routers (DirectRouter is used by default).
0065: * <p>
0066: * The connection is rendered using the foreground color and a specified stroke. It also renders control points,
0067: * end points (first and last control points) and anchors. The shape of points are defined by assigned AnchorShape and PointShape.
0068: * <p>
0069: * For speed optimalization, the connection widget are usually placed in the a separate layer widget that is rendered after
0070: * the main layer with widgets (that used anchors depends on) is rendered.
0071: * <p>
0072: * Line color is defined by foregroundColor property. Note: If you are changing a state of the ConnectionWidget
0073: * (e.g. using it as a representation of an object in ObjectScene, GraphScene or GraphPinScene classes,
0074: * then the ConnectionWidget.notifyStateChanged method is automatically called.
0075: * The built-in implementation of this method overrides foregroundColor based on a new state of the widget
0076: * (the particular color is resolved by the LookFeel of the scene).
0077: *
0078: * @author David Kaspar
0079: */
0080: // TODO - control points can be modified by accessing: getControlPoints ().get (0).x or y
0081: public class ConnectionWidget extends Widget {
0082:
0083: private static final double HIT_DISTANCE_SQUARE = 16.0;
0084: private static final Stroke STROKE_DEFAULT = new BasicStroke(1.0f);
0085:
0086: /**
0087: * This enum represents a policy which is used for re-routing control points of a ConnectionWidget.
0088: * @since 2.9
0089: */
0090: public enum RoutingPolicy {
0091:
0092: /**
0093: * All control points are rerouted when it is necessary. This is the default policy.
0094: * @since 2.9
0095: */
0096: ALWAYS_ROUTE,
0097:
0098: /**
0099: * All control points (except end points) are kept at the same location.
0100: * End points are updated to locations resolved by source and target anchors.
0101: * Note: This is used when an user customizes/adds/removes control points
0102: * and the change has to be kept until it is reset by the user (setting policy to ALWAYS_ROUTE.
0103: * @since 2.9
0104: */
0105: UPDATE_END_POINTS_ONLY,
0106:
0107: /**
0108: * Temporarily disables routing until any end point changes its location. Locations are the first
0109: * and the last point of control points. When an end point location is changed,
0110: * then the policy is automatically changed to <code>ALWAYS_ROUTE</code>.
0111: * Note: This is used by GraphLayouts which routes the path (control points)
0112: * by themselves and would like to keep the path until user moves with source or target widget/anchor.
0113: * @since 2.9
0114: */
0115: DISABLE_ROUTING_UNTIL_END_POINT_IS_MOVED,
0116:
0117: /**
0118: * Disable routing completely, so control points are kept at their previous location.
0119: * Note: This is not often used unless you have to freeze a ConnectionWidget
0120: * @since 2.9
0121: */
0122: DISABLE_ROUTING
0123:
0124: }
0125:
0126: private Anchor sourceAnchor;
0127: private Anchor targetAnchor;
0128: private AnchorShape sourceAnchorShape;
0129: private AnchorShape targetAnchorShape;
0130: private PointShape controlPointShape;
0131: private PointShape endPointShape;
0132: private Router router;
0133: private boolean routingRequired;
0134: private List<Point> controlPoints = Collections.emptyList();
0135: private List<Point> controlPointsUm = Collections
0136: .unmodifiableList(controlPoints);
0137: private ConnectionWidgetLayout connectionWidgetLayout;
0138: private Stroke stroke;
0139: private boolean paintControlPoints;
0140: private Color lineColor;
0141: private Cursor controlPointsCursor;
0142: private int controlPointCutDistance;
0143:
0144: private Anchor.Entry sourceEntry;
0145: private Anchor.Entry targetEntry;
0146:
0147: private RoutingPolicy routingPolicy;
0148:
0149: /**
0150: * Creates a connection widget.
0151: * @param scene the scene
0152: */
0153: public ConnectionWidget(Scene scene) {
0154: super (scene);
0155: sourceAnchorShape = AnchorShape.NONE;
0156: targetAnchorShape = AnchorShape.NONE;
0157: controlPointShape = PointShape.NONE;
0158: endPointShape = PointShape.NONE;
0159: router = RouterFactory.createDirectRouter();
0160: routingRequired = true;
0161: connectionWidgetLayout = new ConnectionWidgetLayout(this );
0162: setLayout(connectionWidgetLayout);
0163: stroke = STROKE_DEFAULT;
0164: paintControlPoints = false;
0165: controlPointCutDistance = 0;
0166: sourceEntry = new ConnectionEntry(true);
0167: targetEntry = new ConnectionEntry(false);
0168:
0169: routingPolicy = RoutingPolicy.ALWAYS_ROUTE;
0170: }
0171:
0172: /**
0173: * Implements the widget-state specific look of the widget.
0174: * @param previousState the previous state
0175: * @param state the new state
0176: */
0177: public void notifyStateChanged(ObjectState previousState,
0178: ObjectState state) {
0179: setForeground(lineColor != null ? lineColor : getScene()
0180: .getLookFeel().getLineColor(state));
0181: setPaintControlPoints(state.isSelected());
0182: }
0183:
0184: /**
0185: * Returns a stroke of the connection widget.
0186: * @return the stroke
0187: */
0188: public final Stroke getStroke() {
0189: return stroke;
0190: }
0191:
0192: /**
0193: * Sets a stroke.
0194: * @param stroke the stroke
0195: */
0196: public final void setStroke(Stroke stroke) {
0197: assert stroke != null;
0198: this .stroke = stroke;
0199: repaint(); // TODO - check when to revalidate and when to repaint only
0200: }
0201:
0202: /**
0203: * Returns line color of the widget.
0204: * @return the line color; null if no line color is specified
0205: */
0206: public final Color getLineColor() {
0207: return lineColor;
0208: }
0209:
0210: /**
0211: * Sets a line color of the widget.
0212: * @param lineColor the line color; if null, then the line color will be resolved from LookFeel of the scene.
0213: */
0214: public final void setLineColor(Color lineColor) {
0215: this .lineColor = lineColor;
0216: ObjectState state = getState();
0217: notifyStateChanged(state, state);
0218: }
0219:
0220: /**
0221: * Returns whether the control (and end) points are painted
0222: * @return true, if the control points (and end points) are painted
0223: */
0224: public final boolean isPaintControlPoints() {
0225: return paintControlPoints;
0226: }
0227:
0228: /**
0229: * Sets whether the control (and end) points are painted
0230: * @param paintControlPoints if true, then control points are painted
0231: */
0232: public final void setPaintControlPoints(boolean paintControlPoints) {
0233: this .paintControlPoints = paintControlPoints;
0234: repaint();
0235: }
0236:
0237: /**
0238: * Returns the cursor for control point.
0239: * @return the cursor
0240: * @since 2.3
0241: */
0242: public final Cursor getControlPointsCursor() {
0243: return controlPointsCursor;
0244: }
0245:
0246: /**
0247: * Sets a control points cursor. The cursor is used only when mouse is over a visible control point
0248: * @param controlPointsCursor the control points cursor
0249: * @since 2.3
0250: */
0251: public final void setControlPointsCursor(Cursor controlPointsCursor) {
0252: this .controlPointsCursor = controlPointsCursor;
0253: }
0254:
0255: /**
0256: * Returns the cut distance at control points.
0257: * @return the cut distance
0258: * @since 2.5
0259: */
0260: public int getControlPointCutDistance() {
0261: return controlPointCutDistance;
0262: }
0263:
0264: /**
0265: * Sets the cut distance at control points.
0266: * @param controlPointCutDistance if positive number, then the path is cut to render smooth corners;
0267: * otherwise the path is rendered using control points only
0268: * @since 2.5
0269: */
0270: public void setControlPointCutDistance(int controlPointCutDistance) {
0271: this .controlPointCutDistance = controlPointCutDistance;
0272: repaint();
0273: }
0274:
0275: /**
0276: * Returns a source anchor of the connection widget.
0277: * @return the source anchor
0278: */
0279: public final Anchor getSourceAnchor() {
0280: return sourceAnchor;
0281: }
0282:
0283: /**
0284: * Sets a source anchor of the connection widget.
0285: * @param sourceAnchor the source anchor
0286: */
0287: public final void setSourceAnchor(Anchor sourceAnchor) {
0288: if (this .sourceAnchor != null)
0289: this .sourceAnchor.removeEntry(sourceEntry);
0290: this .sourceAnchor = sourceAnchor;
0291: if (this .sourceAnchor != null)
0292: sourceAnchor.addEntry(sourceEntry);
0293: reroute();
0294: }
0295:
0296: /**
0297: * Returns a target anchor of the connection widget.
0298: * @return the target anchor
0299: */
0300: public final Anchor getTargetAnchor() {
0301: return targetAnchor;
0302: }
0303:
0304: /**
0305: * Sets a target anchor of the connection widget.
0306: * @param targetAnchor the target anchor
0307: */
0308: public final void setTargetAnchor(Anchor targetAnchor) {
0309: if (this .targetAnchor != null)
0310: this .targetAnchor.removeEntry(targetEntry);
0311: this .targetAnchor = targetAnchor;
0312: if (targetAnchor != null)
0313: targetAnchor.addEntry(targetEntry);
0314: reroute();
0315: }
0316:
0317: /**
0318: * Returns an anchor entry representing the source of the connection widget.
0319: * @return the anchor entry representing the source of the connection widget
0320: */
0321: public Anchor.Entry getSourceAnchorEntry() {
0322: return sourceEntry;
0323: }
0324:
0325: /**
0326: * Returns an anchor entry representing the target of the connection widget.
0327: * @return the anchor entry representing the target of the connection widget
0328: */
0329: public Anchor.Entry getTargetAnchorEntry() {
0330: return targetEntry;
0331: }
0332:
0333: /**
0334: * Returns an anchor shape of the source of the connection widget.
0335: * @return the source anchor shape
0336: */
0337: public AnchorShape getSourceAnchorShape() {
0338: return sourceAnchorShape;
0339: }
0340:
0341: /**
0342: * Sets the anchor shape of the source of the connection widget.
0343: * @param sourceAnchorShape the source anchor shape
0344: */
0345: public void setSourceAnchorShape(AnchorShape sourceAnchorShape) {
0346: assert sourceAnchorShape != null;
0347: boolean repaintOnly = this .sourceAnchorShape.getRadius() == sourceAnchorShape
0348: .getRadius();
0349: this .sourceAnchorShape = sourceAnchorShape;
0350: revalidate(repaintOnly);
0351: }
0352:
0353: /**
0354: * Returns an anchor shape of the target of the connection widget.
0355: * @return the target anchor shape
0356: */
0357: public AnchorShape getTargetAnchorShape() {
0358: return targetAnchorShape;
0359: }
0360:
0361: /**
0362: * Sets the anchor shape of the target of the connection widget.
0363: * @param targetAnchorShape the target anchor shape
0364: */
0365: public void setTargetAnchorShape(AnchorShape targetAnchorShape) {
0366: assert targetAnchorShape != null;
0367: boolean repaintOnly = this .targetAnchorShape.getRadius() == targetAnchorShape
0368: .getRadius();
0369: this .targetAnchorShape = targetAnchorShape;
0370: revalidate(repaintOnly);
0371: }
0372:
0373: /**
0374: * Returns a point shape of control points of the connection widget.
0375: * @return the control points shape
0376: */
0377: public PointShape getControlPointShape() {
0378: return controlPointShape;
0379: }
0380:
0381: /**
0382: * Sets a point shape of control points of the connection widget.
0383: * @param controlPointShape the control points shape
0384: */
0385: public void setControlPointShape(PointShape controlPointShape) {
0386: assert controlPointShape != null;
0387: boolean repaintOnly = this .controlPointShape.getRadius() == controlPointShape
0388: .getRadius();
0389: this .controlPointShape = controlPointShape;
0390: revalidate(repaintOnly);
0391: }
0392:
0393: /**
0394: * Returns a point shape of end points of the connection widget.
0395: * @return the end points shape
0396: */
0397: public PointShape getEndPointShape() {
0398: return endPointShape;
0399: }
0400:
0401: /**
0402: * Sets a point shape of end points of the connection widget.
0403: * @param endPointShape the end points shape
0404: */
0405: public void setEndPointShape(PointShape endPointShape) {
0406: assert endPointShape != null;
0407: boolean repaintOnly = this .endPointShape.getRadius() == endPointShape
0408: .getRadius();
0409: this .endPointShape = endPointShape;
0410: revalidate(repaintOnly);
0411: }
0412:
0413: /**
0414: * Returns a routing policy.
0415: * @return the routing policy
0416: * @since 2.9
0417: */
0418: public final RoutingPolicy getRoutingPolicy() {
0419: return routingPolicy;
0420: }
0421:
0422: /**
0423: * Sets a routing policy. It invokes re-routing in case of routing policy change unless its is changed to DISABLE_ROUTING.
0424: * @param routingPolicy the new routing policy
0425: * @since 2.9
0426: */
0427: public final void setRoutingPolicy(RoutingPolicy routingPolicy) {
0428: assert routingPolicy != null;
0429: if (this .routingPolicy == routingPolicy)
0430: return;
0431: boolean changed = routingPolicy != RoutingPolicy.DISABLE_ROUTING;
0432: this .routingPolicy = routingPolicy;
0433: if (changed)
0434: reroute();
0435: }
0436:
0437: /**
0438: * Returns the control-points-based path router of the connection widget.
0439: * @return the path router
0440: */
0441: public final Router getRouter() {
0442: return router;
0443: }
0444:
0445: /**
0446: * Sets a control-points-based path router of the connection widget.
0447: * @param router the path router
0448: */
0449: public final void setRouter(Router router) {
0450: assert router != null;
0451: this .router = router;
0452: reroute();
0453: }
0454:
0455: /**
0456: * Returns a list of control points.
0457: * @return the list of control points
0458: */
0459: public List<Point> getControlPoints() {
0460: return controlPointsUm;
0461: }
0462:
0463: /**
0464: * Returns a location of control point at the specified index in the list of control points.
0465: *
0466: * @param index index of the control point to return
0467: * @return the point; null if the control point does not exist
0468: */
0469: public Point getControlPoint(int index) {
0470: if (index < 0 || index >= controlPoints.size())
0471: return null;
0472: return new Point(controlPoints.get(index));
0473: }
0474:
0475: /**
0476: * Sets control points.
0477: * @param controlPoints the list of control points
0478: * @param sceneLocations if true, then controlPoints argyment is taken as a list of scene locations;
0479: * if false, then controlPoints argument is taken as a list of local locations
0480: */
0481: public void setControlPoints(Collection<Point> controlPoints,
0482: boolean sceneLocations) {
0483: if (sceneLocations) {
0484: Point translation = this .convertLocalToScene(new Point());
0485: ArrayList<Point> list = new ArrayList<Point>();
0486: for (Point point : controlPoints)
0487: list.add(new Point(point.x - translation.x, point.y
0488: - translation.y));
0489: this .controlPoints = list;
0490: } else
0491: this .controlPoints = new ArrayList<Point>(controlPoints);
0492: this .controlPointsUm = Collections
0493: .unmodifiableList(this .controlPoints);
0494: routingRequired = false;
0495: revalidate();
0496: }
0497:
0498: /**
0499: * Sets a constraint for a child widget when ConnectionWidgetLayout (by default) is used.
0500: * @param childWidget the child widget for which the constraint is set
0501: * @param alignment the alignment specified relatively to the origin point
0502: * @param placementInPercentage the placement on a path in percentage of the path length
0503: */
0504: public void setConstraint(Widget childWidget,
0505: LayoutFactory.ConnectionWidgetLayoutAlignment alignment,
0506: float placementInPercentage) {
0507: connectionWidgetLayout.setConstraint(childWidget, alignment,
0508: placementInPercentage);
0509: }
0510:
0511: /**
0512: * Sets a constraint for a child widget when ConnectionWidgetLayout (by default) is used.
0513: * @param childWidget the child widget for which the constraint is set
0514: * @param alignment the alignment specified relatively to the origin point
0515: * @param placementAtDistance the placement on a path in pixels as a distance from the source anchor
0516: */
0517: public void setConstraint(Widget childWidget,
0518: LayoutFactory.ConnectionWidgetLayoutAlignment alignment,
0519: int placementAtDistance) {
0520: connectionWidgetLayout.setConstraint(childWidget, alignment,
0521: placementAtDistance);
0522: }
0523:
0524: /**
0525: * Removes a constraint for a child widget.
0526: * @param childWidget the child widget
0527: */
0528: public void removeConstraint(Widget childWidget) {
0529: connectionWidgetLayout.removeConstraint(childWidget);
0530: }
0531:
0532: /**
0533: * Forces path routing.
0534: */
0535: public final void calculateRouting() {
0536: if (routingRequired) {
0537: switch (routingPolicy) {
0538: case ALWAYS_ROUTE:
0539: setControlPoints(router.routeConnection(this ), true);
0540: break;
0541: case UPDATE_END_POINTS_ONLY: {
0542: Point sourcePoint = sourceAnchor != null ? sourceAnchor
0543: .compute(sourceEntry).getAnchorSceneLocation()
0544: : null;
0545: Point targetPoint = targetAnchor != null ? targetAnchor
0546: .compute(targetEntry).getAnchorSceneLocation()
0547: : null;
0548: if (sourcePoint == null || targetPoint == null) {
0549: controlPoints.clear();
0550: break;
0551: }
0552: sourcePoint = convertSceneToLocal(sourcePoint);
0553: targetPoint = convertSceneToLocal(targetPoint);
0554: if (controlPoints.size() < 1)
0555: controlPoints.add(sourcePoint);
0556: else
0557: controlPoints.set(0, sourcePoint);
0558: if (controlPoints.size() < 2)
0559: controlPoints.add(targetPoint);
0560: else
0561: controlPoints.set(controlPoints.size() - 1,
0562: targetPoint);
0563: }
0564: break;
0565: case DISABLE_ROUTING_UNTIL_END_POINT_IS_MOVED: {
0566: Point sourcePoint = sourceAnchor != null ? sourceAnchor
0567: .compute(sourceEntry).getAnchorSceneLocation()
0568: : null;
0569: Point firstPoint = getFirstControlPoint();
0570: if (firstPoint != null)
0571: firstPoint = convertLocalToScene(firstPoint);
0572: if (sourcePoint == null ? firstPoint == null
0573: : sourcePoint.equals(firstPoint)) {
0574: Point targetPoint = targetAnchor != null ? targetAnchor
0575: .compute(targetEntry)
0576: .getAnchorSceneLocation()
0577: : null;
0578: Point lastPoint = getLastControlPoint();
0579: if (lastPoint != null)
0580: lastPoint = convertLocalToScene(lastPoint);
0581: if (targetPoint == null ? lastPoint == null
0582: : targetPoint.equals(lastPoint))
0583: break;
0584: }
0585: routingPolicy = RoutingPolicy.ALWAYS_ROUTE;
0586: setControlPoints(router.routeConnection(this ), true);
0587: }
0588: break;
0589: case DISABLE_ROUTING:
0590: break;
0591: default:
0592: throw new IllegalStateException(
0593: "Unexpected routing policy: " + routingPolicy); // NOI18N
0594: }
0595: }
0596: }
0597:
0598: /**
0599: * Calculates a client area of the connection widget.
0600: * @return the calculated client area
0601: */
0602: protected Rectangle calculateClientArea() {
0603: calculateRouting();
0604: int controlPointShapeRadius = controlPointShape.getRadius();
0605: int controlPointShapeRadius2 = controlPointShapeRadius
0606: + controlPointShapeRadius;
0607: int endPointShapeRadius = endPointShape.getRadius();
0608:
0609: Rectangle rect = null;
0610: for (Point point : controlPoints) {
0611: Rectangle addRect = new Rectangle(point.x
0612: - controlPointShapeRadius, point.y
0613: - controlPointShapeRadius,
0614: controlPointShapeRadius2, controlPointShapeRadius2);
0615: if (rect == null)
0616: rect = addRect;
0617: else
0618: rect.add(addRect);
0619: }
0620:
0621: Point firstPoint = getFirstControlPoint();
0622: if (firstPoint != null) {
0623: int radius = Math.max(sourceAnchorShape.getRadius(),
0624: endPointShapeRadius);
0625: int radius2 = radius + radius;
0626: if (rect == null)
0627: rect = new Rectangle(firstPoint.x - radius,
0628: firstPoint.y - radius, radius2, radius2);
0629: else
0630: rect.add(new Rectangle(firstPoint.x - radius,
0631: firstPoint.y - radius, radius2, radius2));
0632: }
0633:
0634: Point lastPoint = getLastControlPoint();
0635: if (lastPoint != null) {
0636: int radius = Math.max(targetAnchorShape.getRadius(),
0637: endPointShapeRadius);
0638: int radius2 = radius + radius;
0639: if (rect == null)
0640: rect = new Rectangle(lastPoint.x - radius, lastPoint.y
0641: - radius, radius2, radius2);
0642: else
0643: rect.add(new Rectangle(lastPoint.x - radius,
0644: lastPoint.y - radius, radius2, radius2));
0645: }
0646:
0647: if (rect != null)
0648: rect.grow(2, 2); // TODO - improve line width calculation
0649:
0650: return rect != null ? rect : new Rectangle();
0651: }
0652:
0653: /**
0654: * Returns whether the connection widget is validated and routed.
0655: * @return true, if the connection widget is validated and routed
0656: */
0657: public boolean isValidated() {
0658: return super .isValidated() && isRouted();
0659: }
0660:
0661: /**
0662: * Returns whether the connection widget is routed.
0663: * @return true if the connection widget is routed
0664: */
0665: public final boolean isRouted() {
0666: return !routingRequired;
0667: }
0668:
0669: /**
0670: * Schedules the connection widget for re-routing its path.
0671: */
0672: public final void reroute() {
0673: routingRequired = true;
0674: revalidate();
0675: }
0676:
0677: /**
0678: * Returns the first control point.
0679: * @return the first control point; null, if list of control points is empty
0680: */
0681: public final Point getFirstControlPoint() {
0682: if (controlPoints.size() <= 0)
0683: return null;
0684: return new Point(controlPoints.get(0));
0685: }
0686:
0687: /**
0688: * Returns the last control point.
0689: * @return the last control point; null, if list of control points is empty
0690: */
0691: public final Point getLastControlPoint() {
0692: int size = controlPoints.size();
0693: if (size <= 0)
0694: return null;
0695: return new Point(controlPoints.get(size - 1));
0696: }
0697:
0698: /**
0699: * Returns the rotation of the source anchor shape.
0700: * @return the source anchor shape rotation
0701: */
0702: private double getSourceAnchorShapeRotation() {
0703: if (controlPoints.size() <= 1)
0704: return 0.0;
0705: Point point1 = controlPoints.get(0);
0706: Point point2 = controlPoints.get(1);
0707: return Math.atan2(point2.y - point1.y, point2.x - point1.x);
0708: }
0709:
0710: /**
0711: * Returns the rotation of the target anchor shape.
0712: * @return the target anchor shape rotation
0713: */
0714: public double getTargetAnchorShapeRotation() {
0715: int size = controlPoints.size();
0716: if (size <= 1)
0717: return 0.0;
0718: Point point1 = controlPoints.get(size - 1);
0719: Point point2 = controlPoints.get(size - 2);
0720: return Math.atan2(point2.y - point1.y, point2.x - point1.x);
0721: }
0722:
0723: /**
0724: * Returns whether a specified local location is a part of the connection widget. It checks whether the location is
0725: * close to the control-points-based path (up to 4px from the line),
0726: * close to the anchors (defined by AnchorShape) or
0727: * close to the control points (PointShape).
0728: * @param localLocation the local locaytion
0729: * @return true, if the location is a part of the connection widget
0730: */
0731: public boolean isHitAt(Point localLocation) {
0732: if (!super .isHitAt(localLocation))
0733: return false;
0734:
0735: List<Point> controlPoints = getControlPoints();
0736: for (int i = 0; i < controlPoints.size() - 1; i++) {
0737: Point point1 = controlPoints.get(i);
0738: Point point2 = controlPoints.get(i + 1);
0739: double dist = Line2D.ptSegDistSq(point1.x, point1.y,
0740: point2.x, point2.y, localLocation.x,
0741: localLocation.y);
0742: if (dist < HIT_DISTANCE_SQUARE)
0743: return true;
0744: }
0745:
0746: return getControlPointHitAt(localLocation) >= 0;
0747: }
0748:
0749: /**
0750: * Returns whether the local location hits the first control point (also meant to be the source anchor).
0751: * @param localLocation the local location
0752: * @return true if it hits the first control point
0753: */
0754: public final boolean isFirstControlPointHitAt(Point localLocation) {
0755: int endRadius = endPointShape.getRadius();
0756: endRadius *= endRadius;
0757: Point firstPoint = getFirstControlPoint();
0758: if (firstPoint != null)
0759: if (Point2D.distanceSq(firstPoint.x, firstPoint.y,
0760: localLocation.x, localLocation.y) <= endRadius)
0761: return true;
0762: return false;
0763: }
0764:
0765: /**
0766: * Returns whether the local location hits the last control point (also meant to be the target anchor).
0767: * @param localLocation the local location
0768: * @return true if it hits the last control point
0769: */
0770: public final boolean isLastControlPointHitAt(Point localLocation) {
0771: int endRadius = endPointShape.getRadius();
0772: endRadius *= endRadius;
0773: Point lastPoint = getLastControlPoint();
0774: if (lastPoint != null)
0775: if (Point2D.distanceSq(lastPoint.x, lastPoint.y,
0776: localLocation.x, localLocation.y) <= endRadius)
0777: return true;
0778: return false;
0779: }
0780:
0781: /**
0782: * Returns an index of a control point that is hit by the local location
0783: * @param localLocation the local location
0784: * @return the index; -1 if no control point was hit
0785: */
0786: public final int getControlPointHitAt(Point localLocation) {
0787: int controlRadius = controlPointShape.getRadius();
0788: controlRadius *= controlRadius;
0789:
0790: if (isFirstControlPointHitAt(localLocation))
0791: return 0;
0792:
0793: if (isLastControlPointHitAt(localLocation))
0794: return controlPoints.size() - 1;
0795:
0796: for (int i = 0; i < controlPoints.size(); i++) {
0797: Point point = controlPoints.get(i);
0798: if (Point2D.distanceSq(point.x, point.y, localLocation.x,
0799: localLocation.y) <= controlRadius)
0800: return i;
0801: }
0802:
0803: return -1;
0804: }
0805:
0806: /**
0807: * Returns a cursor for a specified local location in the widget.
0808: * If paintControlPoints is true and controlPointsCursor is non-null and local location is over a control point, then it return controlPointsCursor.
0809: * Otherwise it return value from super.getCursorAt method.
0810: * @param localLocation the local location
0811: * @return the cursor
0812: * @since 2.3
0813: */
0814: protected Cursor getCursorAt(Point localLocation) {
0815: if (paintControlPoints) {
0816: Cursor pointsCursor = getControlPointsCursor();
0817: if (pointsCursor != null
0818: && getControlPointHitAt(localLocation) >= 0)
0819: return pointsCursor;
0820: }
0821: return super .getCursorAt(localLocation);
0822: }
0823:
0824: /**
0825: * Paints the connection widget (the path, the anchor shapes, the control points, the end points).
0826: */
0827: protected void paintWidget() {
0828: Graphics2D gr = getGraphics();
0829: gr.setColor(getForeground());
0830: GeneralPath path = null;
0831:
0832: Point firstControlPoint = getFirstControlPoint();
0833: Point lastControlPoint = getLastControlPoint();
0834: boolean isSourceCutDistance = Math.abs(sourceAnchorShape
0835: .getCutDistance()) != 0.0;
0836: boolean isTargetCutDistance = Math.abs(targetAnchorShape
0837: .getCutDistance()) != 0.0;
0838: double firstControlPointRotation = firstControlPoint != null
0839: && (sourceAnchorShape.isLineOriented() || isSourceCutDistance) ? getSourceAnchorShapeRotation()
0840: : 0.0;
0841: double lastControlPointRotation = lastControlPoint != null
0842: && (targetAnchorShape.isLineOriented() || isTargetCutDistance) ? getTargetAnchorShapeRotation()
0843: : 0.0;
0844:
0845: List<Point> points;
0846: if ((isSourceCutDistance || isTargetCutDistance)
0847: && controlPoints.size() >= 2) {
0848: points = new ArrayList<Point>(controlPoints);
0849: points
0850: .set(
0851: 0,
0852: new Point(
0853: firstControlPoint.x
0854: + (int) (sourceAnchorShape
0855: .getCutDistance() * Math
0856: .cos(firstControlPointRotation)),
0857: firstControlPoint.y
0858: + (int) (sourceAnchorShape
0859: .getCutDistance() * Math
0860: .sin(firstControlPointRotation))));
0861: points
0862: .set(
0863: controlPoints.size() - 1,
0864: new Point(
0865: lastControlPoint.x
0866: + (int) (targetAnchorShape
0867: .getCutDistance() * Math
0868: .cos(lastControlPointRotation)),
0869: lastControlPoint.y
0870: + (int) (targetAnchorShape
0871: .getCutDistance() * Math
0872: .sin(lastControlPointRotation))));
0873: } else {
0874: points = controlPoints;
0875: }
0876:
0877: if (controlPointCutDistance > 0) {
0878: for (int a = 0; a < points.size() - 1; a++) {
0879: Point p1 = points.get(a);
0880: Point p2 = points.get(a + 1);
0881: double len = p1.distance(p2);
0882:
0883: if (a > 0) {
0884: Point p0 = points.get(a - 1);
0885: double ll = p0.distance(p1);
0886: if (len < ll)
0887: ll = len;
0888: ll /= 2;
0889: double cll = controlPointCutDistance;
0890: if (cll > ll)
0891: cll = ll;
0892: double direction = Math.atan2(p2.y - p1.y, p2.x
0893: - p1.x);
0894: if (!Double.isNaN(direction)) {
0895: path = addToPath(path, p1.x
0896: + (int) (cll * Math.cos(direction)),
0897: p1.y
0898: + (int) (cll * Math
0899: .sin(direction)));
0900: }
0901: } else {
0902: path = addToPath(path, p1.x, p1.y);
0903: }
0904:
0905: if (a < points.size() - 2) {
0906: Point p3 = points.get(a + 2);
0907: double ll = p2.distance(p3);
0908: if (len < ll)
0909: ll = len;
0910: ll /= 2;
0911: double cll = controlPointCutDistance;
0912: if (cll > ll)
0913: cll = ll;
0914: double direction = Math.atan2(p2.y - p1.y, p2.x
0915: - p1.x);
0916: if (!Double.isNaN(direction)) {
0917: path = addToPath(path, p2.x
0918: - (int) (cll * Math.cos(direction)),
0919: p2.y
0920: - (int) (cll * Math
0921: .sin(direction)));
0922: }
0923: } else {
0924: path = addToPath(path, p2.x, p2.y);
0925: }
0926: }
0927: } else {
0928: for (Point point : points)
0929: path = addToPath(path, point.x, point.y);
0930: }
0931: if (path != null) {
0932: Stroke previousStroke = gr.getStroke();
0933: gr.setPaint(getForeground());
0934: gr.setStroke(getStroke());
0935: gr.draw(path);
0936: gr.setStroke(previousStroke);
0937: }
0938:
0939: AffineTransform previousTransform;
0940:
0941: if (firstControlPoint != null) {
0942: previousTransform = gr.getTransform();
0943: gr.translate(firstControlPoint.x, firstControlPoint.y);
0944: if (sourceAnchorShape.isLineOriented())
0945: gr.rotate(firstControlPointRotation);
0946: sourceAnchorShape.paint(gr, true);
0947: gr.setTransform(previousTransform);
0948: }
0949:
0950: if (lastControlPoint != null) {
0951: previousTransform = gr.getTransform();
0952: gr.translate(lastControlPoint.x, lastControlPoint.y);
0953: if (targetAnchorShape.isLineOriented())
0954: gr.rotate(lastControlPointRotation);
0955: targetAnchorShape.paint(gr, false);
0956: gr.setTransform(previousTransform);
0957: }
0958:
0959: if (paintControlPoints) {
0960: int last = controlPoints.size() - 1;
0961: for (int index = 0; index <= last; index++) {
0962: Point point = controlPoints.get(index);
0963: previousTransform = gr.getTransform();
0964: gr.translate(point.x, point.y);
0965: if (index == 0 || index == last)
0966: endPointShape.paint(gr);
0967: else
0968: controlPointShape.paint(gr);
0969: gr.setTransform(previousTransform);
0970: }
0971: }
0972: }
0973:
0974: private GeneralPath addToPath(GeneralPath path, int x, int y) {
0975: if (path == null) {
0976: path = new GeneralPath();
0977: path.moveTo(x, y);
0978: } else {
0979: path.lineTo(x, y);
0980: }
0981: return path;
0982: }
0983:
0984: private class ConnectionEntry implements Anchor.Entry {
0985:
0986: private boolean source;
0987:
0988: public ConnectionEntry(boolean source) {
0989: this .source = source;
0990: }
0991:
0992: public void revalidateEntry() {
0993: ConnectionWidget.this .reroute();
0994: }
0995:
0996: public ConnectionWidget getAttachedConnectionWidget() {
0997: return ConnectionWidget.this ;
0998: }
0999:
1000: public boolean isAttachedToConnectionSource() {
1001: return source;
1002: }
1003:
1004: public Anchor getAttachedAnchor() {
1005: return source ? ConnectionWidget.this .getSourceAnchor()
1006: : ConnectionWidget.this .getTargetAnchor();
1007: }
1008:
1009: public Anchor getOppositeAnchor() {
1010: return source ? ConnectionWidget.this.getTargetAnchor()
1011: : ConnectionWidget.this.getSourceAnchor();
1012: }
1013:
1014: }
1015:
1016: }
|