/******************************************************************************* * Copyright (c) 2000, 2005 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.gef.editpolicies; import java.util.List; import org.eclipse.draw2d.FlowLayout; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.Polyline; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.draw2d.geometry.Transposer; import org.eclipse.gef.EditPart; import org.eclipse.gef.GraphicalEditPart; import org.eclipse.gef.Request; import org.eclipse.gef.RequestConstants; import org.eclipse.gef.requests.DropRequest; /** * An EditPolicy for use with {@link org.eclipse.draw2d.FlowLayout}. This EditPolicy knows * how to map an <x,y> coordinate on the layout container to the appropriate index for the * operation being performed. It also shows target feedback consisting of an insertion * line at the appropriate location. * @since 2.0 */ public abstract class FlowLayoutEditPolicy extends OrderedLayoutEditPolicy { private Polyline insertionLine; /** * @see LayoutEditPolicy#eraseLayoutTargetFeedback(Request) */ protected void eraseLayoutTargetFeedback(Request request) { if (insertionLine != null) { removeFeedback(insertionLine); insertionLine = null; } } private Rectangle getAbsoluteBounds(GraphicalEditPart ep) { Rectangle bounds = ep.getFigure().getBounds().getCopy(); ep.getFigure().translateToAbsolute(bounds); return bounds; } /** * @param request the Request * @return the index for the insertion reference */ protected int getFeedbackIndexFor(Request request) { List children = getHost().getChildren(); if (children.isEmpty()) return -1; Transposer transposer = new Transposer(); transposer.setEnabled (!isHorizontal()); Point p = transposer.t(getLocationFromRequest(request)); // Current row bottom, initialize to above the top. int rowBottom = Integer.MIN_VALUE; int candidate = -1; for (int i = 0; i < children.size(); i++) { EditPart child = (EditPart) children.get(i); Rectangle rect = transposer.t(getAbsoluteBounds(((GraphicalEditPart)child))); if (rect.y > rowBottom) { /* * We are in a new row, so if we don't have a candidate but yet are within the * previous row, then the current entry becomes the candidate. This is because * we know we must be to the right of center of the last Figure in the * previous row, so this Figure (which is at the start of a new row) is the * candidate. */ if (p.y <= rowBottom) { if (candidate == -1) candidate = i; break; } else candidate = -1; // Mouse's Y is outside the row, so reset the candidate } rowBottom = Math.max(rowBottom, rect.bottom()); if (candidate == -1) { /* * See if we have a possible candidate. It is a candidate if the cursor is * left of the center of this candidate. */ if (p.x <= rect.x + (rect.width / 2)) candidate = i; } if (candidate != -1) { // We have a candidate, see if the rowBottom has grown to include the mouse Y. if (p.y <= rowBottom) { /* * Now we have determined that the cursor.Y is above the bottom of the * current row of figures. Stop now, to prevent the next row from being * searched */ break; } } } return candidate; } /** * @see OrderedLayoutEditPolicy#getInsertionReference(Request) */ protected EditPart getInsertionReference(Request request) { List children = getHost().getChildren(); if (request.getType().equals(RequestConstants.REQ_CREATE)) { int i = getFeedbackIndexFor(request); if (i == -1) return null; return (EditPart)children.get(i); } int index = getFeedbackIndexFor(request); if (index != -1) { List selection = getHost().getViewer().getSelectedEditParts(); do { EditPart editpart = (EditPart) children.get(index); if (!selection.contains(editpart)) return editpart; } while (++index < children.size()); } return null; //Not found, add at the end. } /** * Lazily creates and returns a <code>Polyline</code> Figure for use as feedback. * @return a Polyline figure */ protected Polyline getLineFeedback() { if (insertionLine == null) { insertionLine = new Polyline(); insertionLine.setLineWidth(2); insertionLine.addPoint(new Point(0, 0)); insertionLine.addPoint(new Point(10, 10)); addFeedback(insertionLine); } return insertionLine; } private Point getLocationFromRequest(Request request) { return ((DropRequest)request).getLocation(); } /** * @return <code>true</code> if the host's LayoutManager is in a horizontal orientation */ protected boolean isHorizontal() { IFigure figure = ((GraphicalEditPart)getHost()).getContentPane(); return ((FlowLayout)figure.getLayoutManager()).isHorizontal(); } /** * Shows an insertion line if there is one or more current children. * @see LayoutEditPolicy#showLayoutTargetFeedback(Request) */ protected void showLayoutTargetFeedback(Request request) { if (getHost().getChildren().size() == 0) return; Polyline fb = getLineFeedback(); Transposer transposer = new Transposer(); transposer.setEnabled(!isHorizontal()); boolean before = true; int epIndex = getFeedbackIndexFor(request); Rectangle r = null; if (epIndex == -1) { before = false; epIndex = getHost().getChildren().size() - 1; EditPart editPart = (EditPart) getHost().getChildren().get(epIndex); r = transposer.t(getAbsoluteBounds((GraphicalEditPart)editPart)); } else { EditPart editPart = (EditPart) getHost().getChildren().get(epIndex); r = transposer.t(getAbsoluteBounds((GraphicalEditPart)editPart)); Point p = transposer.t(getLocationFromRequest(request)); if (p.x <= r.x + (r.width / 2)) before = true; else { /* * We are not to the left of this Figure, so the emphasis line needs to be to * the right of the previous Figure, which must be on the previous row. */ before = false; epIndex--; editPart = (EditPart) getHost().getChildren().get(epIndex); r = transposer.t(getAbsoluteBounds((GraphicalEditPart)editPart)); } } int x = Integer.MIN_VALUE; if (before) { /* * Want the line to be halfway between the end of the previous and the beginning * of this one. If at the begining of a line, then start halfway between the left * edge of the parent and the beginning of the box, but no more than 5 pixels (it * would be too far and be confusing otherwise). */ if (epIndex > 0) { // Need to determine if a line break. Rectangle boxPrev = transposer.t( getAbsoluteBounds( (GraphicalEditPart) getHost().getChildren().get(epIndex - 1))); int prevRight = boxPrev.right(); if (prevRight < r.x) { // Not a line break x = prevRight + (r.x - prevRight) / 2; } else if (prevRight == r.x) { x = prevRight + 1; } } if (x == Integer.MIN_VALUE) { // It is a line break. Rectangle parentBox = transposer.t( getAbsoluteBounds((GraphicalEditPart)getHost())); x = r.x - 5; if (x < parentBox.x) x = parentBox.x + (r.x - parentBox.x) / 2; } } else { /* * We only have before==false if we are at the end of a line, so go halfway * between the right edge and the right edge of the parent, but no more than 5 * pixels. */ Rectangle parentBox = transposer.t( getAbsoluteBounds((GraphicalEditPart)getHost())); int rRight = r.x + r.width; int pRight = parentBox.x + parentBox.width; x = rRight + 5; if (x > pRight) x = rRight + (pRight - rRight) / 2; } Point p1 = new Point(x, r.y - 4); p1 = transposer.t(p1); fb.translateToRelative(p1); Point p2 = new Point(x, r.y + r.height + 4); p2 = transposer.t(p2); fb.translateToRelative(p2); fb.setPoint(p1, 0); fb.setPoint(p2, 1); } }