/******************************************************************************* * <copyright> * * Copyright (c) 2005, 2012 SAP AG. * 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: * SAP AG - initial API, implementation and documentation * mgorning - Bug 363186 - Allow modification of selection and hover state also for anchors * cbrand - Bug 370440 - Over scaling of connections and lines after canvas zoom * mgorning - Bug 391523 - Revise getSelectionInfo...() in IToolBehaviorProvider * * </copyright> * *******************************************************************************/ package org.eclipse.graphiti.ui.internal.figures; import java.util.Iterator; import java.util.List; import org.eclipse.draw2d.Graphics; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.Shape; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.emf.common.util.EList; import org.eclipse.gef.handles.HandleBounds; import org.eclipse.graphiti.internal.pref.GFPreferences; import org.eclipse.graphiti.internal.services.GraphitiInternal; import org.eclipse.graphiti.mm.algorithms.GraphicsAlgorithm; import org.eclipse.graphiti.mm.algorithms.styles.AdaptedGradientColoredAreas; import org.eclipse.graphiti.mm.algorithms.styles.GradientColoredArea; import org.eclipse.graphiti.mm.algorithms.styles.GradientColoredAreas; import org.eclipse.graphiti.mm.algorithms.styles.RenderingStyle; import org.eclipse.graphiti.mm.pictograms.Anchor; import org.eclipse.graphiti.mm.pictograms.PictogramElement; import org.eclipse.graphiti.platform.ga.IVisualState; import org.eclipse.graphiti.platform.ga.IVisualStateChangeListener; import org.eclipse.graphiti.platform.ga.IVisualStateHolder; import org.eclipse.graphiti.platform.ga.VisualStateChangedEvent; import org.eclipse.graphiti.services.Graphiti; import org.eclipse.graphiti.tb.ISelectionInfo; import org.eclipse.graphiti.tb.IShapeSelectionInfo; import org.eclipse.graphiti.tb.IToolBehaviorProvider; import org.eclipse.graphiti.ui.internal.config.IConfigurationProviderInternal; import org.eclipse.graphiti.ui.internal.parts.IPictogramElementDelegate; import org.eclipse.graphiti.ui.internal.util.DataTypeTransformation; import org.eclipse.graphiti.util.IColorConstant; import org.eclipse.graphiti.util.IGradientType; import org.eclipse.graphiti.util.IPredefinedRenderingStyle; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Path; import org.eclipse.swt.widgets.Display; /** * This class is an abstract super-class for all Shapes in Graphiti. The main * idea is, that the outline and fill-area of a Shape is defined by a Path. * Sub-classes usually only have to implement the abstract method * {@link #createPath(Rectangle, Graphics, boolean)} * * @noinstantiate This class is not intended to be instantiated by clients. * @noextend This class is not intended to be subclassed by clients. */ public abstract class GFAbstractShape extends Shape implements HandleBounds, IVisualStateHolder, IVisualStateChangeListener { /** * The {@link IPictogramElementDelegate} given in the constructor. */ private final IPictogramElementDelegate pictogramElementDelegate; /** * The {@link GraphicsAlgorithm} given in the constructor. */ private final GraphicsAlgorithm graphicsAlgorithm; /** * The selection GraphicsAlgorithm. See {@link #getSelectionBorder()}. */ private GraphicsAlgorithm selectionBorder; /** * The selection-area GraphicsAlgorithms. See {@link #getClickArea()}. */ private GraphicsAlgorithm clickArea[]; // ============================ constructors ============================== /** * Creates a new GFAbstractShape. * * @param pictogramElementDelegate * The PictogramElementDelegate which provides the * GraphicsAlgorithm. * @param graphicsAlgorithm * The GraphicsAlgorithm which provides the values to paint this * Shape. It is either the immediate GraphicsAlgorithm of the * PictogramElementDelegate or a child of that immediate * GraphicsAlgorithm. It must not be null. */ public GFAbstractShape(IPictogramElementDelegate pictogramElementDelegate, GraphicsAlgorithm graphicsAlgorithm) { this.pictogramElementDelegate = pictogramElementDelegate; this.graphicsAlgorithm = graphicsAlgorithm; getVisualState().addChangeListener(this); } // ======================== new abstract methods ========================== /** * Returns the Path which shall be painted in * {@link #paintShape(Graphics, boolean)}. * * @param outerBounds * The outer bounds which shall contain the Path. They are * calculated from the bounds of this figure by * {@link GFFigureUtil#getAdjustedRectangle(Rectangle, double, int)} * . Note, that those outline-bounds are just a suggestion which * works fine for many cases. * @param graphics * The Graphics on which the outline Path shall be painted. It * can be used to react on Graphics specific values, like the * zoom-level of the Graphics. * @param isFill * if true, the Path is used for filling the Shape, otherwise for * outlining the Shape. * @return The Path which shall be painted in * {@link #paintShape(Graphics, boolean)}. */ abstract protected Path createPath(Rectangle outerBounds, Graphics graphics, boolean isFill); // ========================= new public methods =========================== /** * Returns the PictogramElementDelegate, which was given in the constructor. * * @return The PictogramElementDelegate, which was given in the constructor. */ protected IPictogramElementDelegate getPictogramElementDelegate() { return pictogramElementDelegate; } /** * Returns the GraphicsAlgorithm, which was given in the constructor. * * @return The GraphicsAlgorithm, which was given in the constructor. */ protected GraphicsAlgorithm getGraphicsAlgorithm() { return graphicsAlgorithm; } /** * Returns the IConfigurationProviderInternal. This is just a convenience * for <code>getPictogramElementDelegate().getConfigurationProvider()</code> * . * * @return The IConfigurationProviderInternal. */ protected IConfigurationProviderInternal getConfigurationProvider() { return getPictogramElementDelegate().getConfigurationProvider(); } /** * Returns the zoom-level of the given Graphics. * * @param graphics * The Graphics for which to return the zoom-level. * @return The zoom-level of the given Graphics. */ protected double getZoomLevel(Graphics graphics) { return graphics.getAbsoluteScale(); } /** * Returns the line-width of this figure adjusted according to the given * Graphics. This means especially, that the line-width is multiplied with * the zoom-level of the given Graphics. * * @param graphics * The Graphics used to adjust the line-width. * @return The line-width of this figure adjusted according to the given * Graphics. */ protected int getLineWidth(Graphics graphics) { return getLineWidth(); } /** * Changes the given outline-bounds which should be calculated by * {@link #getSingletonOutlineBounds(Graphics)}) to the fill-bounds. In this * default implementation the fill-bounds are calculated from the * outline-bounds by * <ul> * <li>Shrinking by the half line-width, so that the fill-bounds fit exactly * inside the outline painted using the line-width</li> * </ul> * * @param outlineBounds * The outline-bounds to transform. They should be calculated by * {@link #getSingletonOutlineBounds(Graphics)}. * @param graphics * The Graphics used to calculate the bounds. */ protected void transformToFillBounds(Rectangle outlineBounds, Graphics graphics) { // shrink the bounds by half line-width because there the outline is // painted int lineWidth = getLineWidth(graphics); outlineBounds.x += (lineWidth + 1) / 2; outlineBounds.y += (lineWidth + 1) / 2; outlineBounds.height -= lineWidth - 1; outlineBounds.width -= lineWidth - 1; } /** * Returns the selection-area GraphicsAlgorithms of this Shape. By default * the bounds of a Shape are used as selection-area to calculate if a Point * is inside the Shape (see {@link #containsPoint(int, int)}). With this * method the selection area can be changed, so that the selection-area are * all bounds of the returned selection-area GraphicsAlgorithm. Can be null, * if no special selection-area GraphicsAlgorithms exists. * <p> * By default this method returns * {@link IToolBehaviorProvider#getClickArea(PictogramElement)} * * @see #getSelectionBorder() * @return The selection-area GraphicsAlgorithms of this Shape. Can be null. */ protected GraphicsAlgorithm[] getClickArea() { return clickArea; } /** * Returns the selection GraphicsAlgorithm of this Shape. By default the * bounds of a Shape are used to give selection feedback, especially the * selection handles. With this method the selection feedback can be * changed, so that the handle-bounds are the bounds of the returned * selection GraphicsAlgorithm. Can be null, if no special selection * GraphicsAlgorithm exists. * <p> * By default this method returns * {@link IToolBehaviorProvider#getSelectionBorder(PictogramElement)} * * @see #getClickArea() * @return The selection GraphicsAlgorithm of this Shape. Can be null. */ protected GraphicsAlgorithm getSelectionBorder() { return selectionBorder; } /** * Returns true, if the given point is contained inside one of the * selection-area GraphicsAlgorithms defined in {@link #getClickArea()}. * * @param x * The x-coordinate of the point to check. * @param y * The y-coordinate of the point to check. * @see #containsPoint(int, int) * @return true, if the given point is contained inside one of the * selection-area GraphicsAlgorithms. */ protected Boolean containsPointInArea(int x, int y) { @SuppressWarnings("unchecked") List<IFigure> children2 = getChildren(); for (IFigure figure : children2) { if (figure instanceof DecoratorImageFigure) { if (figure.containsPoint(x, y)) { return Boolean.TRUE; } } } GraphicsAlgorithm[] gas = getClickArea(); if (gas != null) { for (int i = 0; i < gas.length; i++) { IFigure figure = getPictogramElementDelegate().getFigureForGraphicsAlgorithm(gas[i]); if (figure != null && !this.equals(figure)) { // don't check the // figure if (figure.containsPoint(x, y)) { return Boolean.TRUE; } } else { return null; } } return Boolean.FALSE; } return null; } /** * Returns true, if the given point is contained inside this Shape. This * implementation just forwards to <code>super.contains(x, y)</code>. * * @param x * The x-coordinate of the point to check. * @param y * The y-coordinate of the point to check. * @see #containsPoint(int, int) * @return true, if the given point is contained inside this Shape. */ protected boolean containsPointInFigure(int x, int y) { return super.containsPoint(x, y); } /** * A helper method, which fills the Path according to the fill-colors of * this figures GraphicsAlgorithm. This can be a single-color filling or a * gradient filling. * * @param graphics * The graphics on which to fill the Path. * @param path * The Path which to fill. */ protected void fillPath(Graphics graphics, Path path) { RenderingStyle renderingStyle = Graphiti.getGaService().getRenderingStyle(graphicsAlgorithm, true); if (adaptBackgroundToHover(graphics)) { // fill area graphics.fillPath(path); } else { if (renderingStyle != null && !isHighContrastMode()) { graphics.pushState(); try { // do not use getZoomLevel(), which returns wrong scales Rectangle pathBounds = GFFigureUtil.getPathBounds(path); graphics.clipPath(path); int styleAdaptation = getStyleAdaptation(); AdaptedGradientColoredAreas adaptedGradientColoredAreas = renderingStyle .getAdaptedGradientColoredAreas(); EList<GradientColoredAreas> gradientColoredAreas = adaptedGradientColoredAreas .getAdaptedGradientColoredAreas(); EList<GradientColoredArea> gradienColoredAreaList = null; // get the style adaption or use the default style if (gradientColoredAreas != null && gradientColoredAreas.size() > 0 && gradientColoredAreas.size() - 1 >= styleAdaptation) { gradienColoredAreaList = gradientColoredAreas.get(styleAdaptation).getGradientColor(); } else { gradienColoredAreaList = gradientColoredAreas.get( IPredefinedRenderingStyle.STYLE_ADAPTATION_DEFAULT).getGradientColor(); } boolean isVertical = true; if (adaptedGradientColoredAreas.getGradientType() != null) { if (adaptedGradientColoredAreas.getGradientType().equals(IGradientType.HORIZONTAL)) { isVertical = false; } } for (Iterator<GradientColoredArea> iterator = gradienColoredAreaList.iterator(); iterator.hasNext();) { GradientColoredArea gradientColoredArea = iterator.next(); GFFigureUtil.paintColorFlow(getConfigurationProvider(), pathBounds, graphics, gradientColoredArea, getZoomLevel(graphics), isVertical); } } finally { graphics.popState(); } } else { setBackgroundWithoutStyle(graphics, path); } } } private void setBackgroundWithoutStyle(Graphics graphics, Path path) { Color oldBackground = graphics.getBackgroundColor(); int selectionFeedback = getVisualState().getSelectionFeedback(); if (selectionFeedback == IVisualState.SELECTION_PRIMARY || selectionFeedback == IVisualState.SELECTION_SECONDARY) { // fill area IToolBehaviorProvider tbp = getConfigurationProvider().getDiagramTypeProvider() .getCurrentToolBehaviorProvider(); PictogramElement pe = getPictogramElementDelegate().getPictogramElement(); IShapeSelectionInfo selectionInfo = null; if (pe instanceof org.eclipse.graphiti.mm.pictograms.Shape) { selectionInfo = tbp.getSelectionInfoForShape((org.eclipse.graphiti.mm.pictograms.Shape) pe); } else if (pe instanceof Anchor) { selectionInfo = tbp.getSelectionInfoForAnchor((Anchor) pe); } if (selectionInfo == null) { return; } if (selectionFeedback == IVisualState.SELECTION_PRIMARY) { IColorConstant primarySelectionBackGroundColor = selectionInfo.getPrimarySelectionBackgroundColor(); if (primarySelectionBackGroundColor != null) { graphics.setBackgroundColor(DataTypeTransformation.toSwtColor(getConfigurationProvider() .getResourceRegistry(), primarySelectionBackGroundColor)); } } else if (selectionFeedback == IVisualState.SELECTION_SECONDARY) { IColorConstant secondarySelectionBackGroundColor = selectionInfo.getSecondarySelectionBackgroundColor(); if (secondarySelectionBackGroundColor != null) { graphics.setBackgroundColor(DataTypeTransformation.toSwtColor(getConfigurationProvider() .getResourceRegistry(), secondarySelectionBackGroundColor)); } } } graphics.fillPath(path); // revert to old background color graphics.setBackgroundColor(oldBackground); } private int getStyleAdaptation() { int styleAdaptation = IPredefinedRenderingStyle.STYLE_ADAPTATION_DEFAULT; int selectionFeedback = getVisualState().getSelectionFeedback(); if (selectionFeedback == IVisualState.SELECTION_PRIMARY) styleAdaptation = IPredefinedRenderingStyle.STYLE_ADAPTATION_PRIMARY_SELECTED; else if (selectionFeedback == IVisualState.SELECTION_SECONDARY) styleAdaptation = IPredefinedRenderingStyle.STYLE_ADAPTATION_SECONDARY_SELECTED; int actionTargetFeedback = getVisualState().getActionTargetFeedback(); // if (actionTargetFeedback == IVisualState.ACTION_TARGET_ALLOWED) // styleAdaptation = // IPredefinedRenderingStyle.STYLE_ADAPTATION_ACTION_ALLOWED; // else if (actionTargetFeedback == // IVisualState.ACTION_TARGET_FORBIDDEN) // styleAdaptation = // IPredefinedRenderingStyle.STYLE_ADAPTATION_ACTION_FORBIDDEN; // TODO why is every actionTargetFeedback folded to // STYLE_ADAPTATION_SECONDARY_SELECTED? // if (actionTargetFeedback == IVisualState.ACTION_TARGET_ALLOWED) styleAdaptation = IPredefinedRenderingStyle.STYLE_ADAPTATION_SECONDARY_SELECTED; return styleAdaptation; } /** * Compute hover state and adapt background color accordingly We distinguish * the state where we hover over a figure without the parent being selected * and with the parent being selected. * * @param graphics * @return */ private boolean adaptBackgroundToHover(Graphics graphics) { if (getVisualState().getHoverFeedback() == IVisualState.HOVER_ON) { IToolBehaviorProvider tbp = getConfigurationProvider().getDiagramTypeProvider() .getCurrentToolBehaviorProvider(); IFigure parent = getParent(); boolean parentSelected = false; if (parent instanceof GFAbstractShape) { GFAbstractShape gfa = (GFAbstractShape) parent; IVisualState visualState = gfa.getVisualState(); parentSelected = visualState.getSelectionFeedback() == IVisualState.SELECTION_PRIMARY; } PictogramElement pe = getPictogramElementDelegate().getPictogramElement(); if (!(pe instanceof org.eclipse.graphiti.mm.pictograms.Shape)) return false; org.eclipse.graphiti.mm.pictograms.Shape s = (org.eclipse.graphiti.mm.pictograms.Shape) pe; ISelectionInfo selectionInfo = tbp.getSelectionInfoForShape(s); IColorConstant hoverColor = null; hoverColor = selectionInfo.getHoverColor(); if (parentSelected) hoverColor = selectionInfo.getHoverColorParentSelected(); if (hoverColor != null) { Color hoverColorSwt = DataTypeTransformation.toSwtColor(getConfigurationProvider() .getResourceRegistry(), hoverColor); graphics.setBackgroundColor(hoverColorSwt); return true; } } return false; } /** * Outlines or fills this Shape on the given Graphics. First the outline * Path is be determined by calling * {@link #createPath(Rectangle, Graphics, boolean)}. Afterwards this Path * is either outlined on the Graphics using the correct line-width or filled * using a single color or color-gradients. * * @param graphics * The Graphics on which to outline or fill this Shape. * @param isFill * if true, fills this Shape, otherwise outlines this Shape. */ protected void paintShape(Graphics graphics, boolean isFill) { // initialize Graphics int oldLineWidth = graphics.getLineWidth(); graphics.setLineWidth(getLineWidth(graphics)); // get Path double zoom = getZoomLevel(graphics); int lw = getLineWidth(graphics); Rectangle pathbounds = GFFigureUtil.getAdjustedRectangle(getBounds(), zoom, lw); if (isFill) { transformToFillBounds(pathbounds, graphics); } Path path = createPath(pathbounds, graphics, isFill); // outline or fill Path if (isFill) { fillPath(graphics, path); } else { graphics.drawPath(path); } // reset Graphics path.dispose(); graphics.setLineWidth(oldLineWidth); } // ======================== overwritten methods =========================== /** * First initializes the given Graphics with settings like alpha-value, * antialias-value, ... Afterwards calls * <code>super.paintFigure(graphics)</code> to continue with the default * painting mechanisms. * * @param graphics * The Graphics on which to paint. */ @Override public void paintFigure(Graphics graphics) { if (GraphitiInternal.getEmfService().isObjectAlive(graphicsAlgorithm)) { double transparency = Graphiti.getGaService().getTransparency(graphicsAlgorithm, true); int alpha = (int) ((1.0 - transparency) * 255.0); graphics.setAlpha(alpha); graphics.setAntialias(SWT.ON); super.paintFigure(graphics); } } /** * Fills this Shape on the given Graphics. This implementation just forwards * to {@link #paintShape(Graphics, boolean)}. * * @param graphics * The Graphics on which to fill this Shape. */ @Override protected void fillShape(Graphics graphics) { paintShape(graphics, true); } /** * Outlines this Shape on the given Graphics. This implementation just * forwards to {@link #paintShape(Graphics, boolean)}. * * @param graphics * The Graphics on which to outline this Shape. */ @Override protected void outlineShape(Graphics graphics) { paintShape(graphics, false); } /** * Returns true, if the given point is contained inside this Shape. It first * calls {@link #containsPointInArea(int, int)} to check if there is a * special selection-area defined for this Shape. If not, it returns * {@link #containsPointInFigure(int, int)}. * <p> * This method is final. Override {@link #containsPointInFigure(int, int)} * if needed. * * @param x * The x-coordinate of the point to check. * @param y * The y-coordinate of the point to check. * @return true, if the given point is contained inside this Shape. */ @Override public final boolean containsPoint(int x, int y) { Boolean ret = containsPointInArea(x, y); if (ret != null) { // If a selection area is available, but the mouse is not inside // this selection area // and mouse is inside this figure (e.g. the ghost figure) then // check the main figures // of all child edit parts. // It could be possible that one of these child figures is rendered // outside and the mouse is inside this // child figure. In this case return true, otherwise the child edit // part will never be selectable (the contains // method of child edit part figure will never be called). if (ret.booleanValue() == false && getClickArea() != null && containsPointInFigure(x, y)) { List<IFigure> fList = getPictogramElementDelegate().getMainFiguresFromChildEditparts(); for (IFigure figure : fList) { if (figure.containsPoint(x, y)) { return true; } } } return ret.booleanValue(); } return containsPointInFigure(x, y); } // ========================== interface IHandleInsets ===================== /** * Returns the selection handle bounds of this Shape. First it checks, if a * special selection GraphicsAlgorithm is defined for this Shape (see * {@link #getSelectionBorder()}. Otherwise it just returns the bounds of * this Shape. * * @return The selection handle bounds of this Shape. */ public Rectangle getHandleBounds() { Rectangle ret = null; final GraphicsAlgorithm selectionGa = getSelectionBorder(); if (selectionGa != null) { IFigure selectionFigure = getPictogramElementDelegate().getFigureForGraphicsAlgorithm(selectionGa); if (selectionFigure != null) { ret = selectionFigure.getBounds(); } } if (ret == null) { ret = getBounds(); } return ret; } // ====================== interface IVisualStateHolder ==================== /** * Returns the visual state of this shape. * * @return The visual state of this shape. */ public IVisualState getVisualState() { return getPictogramElementDelegate().getVisualState(); } /** * Is called after the visual state changed. */ public void visualStateChanged(VisualStateChangedEvent e) { // The colors might have changed, so force a repaint() repaint(); } // ===================== support of selection-behavior ==================== /** * @param selectionBorder * the selectionBorder to set */ public void setSelectionBorder(GraphicsAlgorithm selectionBorder) { this.selectionBorder = selectionBorder; } /** * @param clickArea * the clickArea to set */ public void setClickArea(GraphicsAlgorithm[] clickArea) { this.clickArea = clickArea; } protected GFPreferences getPreferences() { return GFPreferences.getInstance(); } private boolean isHighContrastMode() { boolean ret = false; Display display = Display.getCurrent(); if (display == null) { display = Display.getDefault(); } if (display != null) { ret = display.getHighContrast(); } return ret; } }