X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.g2d%2Fsrc%2Forg%2Fsimantics%2Fg2d%2Felement%2FElementUtils.java;h=465db5e934d7efe01fe5e5bae3778c9187a8f7c3;hp=0f2a8c70905523e6348cce0129dc9be6041edb4d;hb=27f08248fa2471dab6bce315387b9617fcfeb1ea;hpb=969bd23cab98a79ca9101af33334000879fb60c5 diff --git a/bundles/org.simantics.g2d/src/org/simantics/g2d/element/ElementUtils.java b/bundles/org.simantics.g2d/src/org/simantics/g2d/element/ElementUtils.java index 0f2a8c709..465db5e93 100644 --- a/bundles/org.simantics.g2d/src/org/simantics/g2d/element/ElementUtils.java +++ b/bundles/org.simantics.g2d/src/org/simantics/g2d/element/ElementUtils.java @@ -1,1236 +1,1251 @@ -/******************************************************************************* - * Copyright (c) 2007, 2010 Association for Decentralized Information Management - * in Industry THTH ry. - * 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: - * VTT Technical Research Centre of Finland - initial API and implementation - *******************************************************************************/ -package org.simantics.g2d.element; - -import java.awt.Color; -import java.awt.Font; -import java.awt.Shape; -import java.awt.Stroke; -import java.awt.geom.AffineTransform; -import java.awt.geom.Area; -import java.awt.geom.NoninvertibleTransformException; -import java.awt.geom.Point2D; -import java.awt.geom.Rectangle2D; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; - -import org.simantics.g2d.canvas.ICanvasContext; -import org.simantics.g2d.diagram.IDiagram; -import org.simantics.g2d.diagram.handler.DataElementMap; -import org.simantics.g2d.diagram.handler.PickRequest; -import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy; -import org.simantics.g2d.diagram.handler.Topology.Terminal; -import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil; -import org.simantics.g2d.element.handler.Adapter; -import org.simantics.g2d.element.handler.AdditionalColor; -import org.simantics.g2d.element.handler.BendsHandler; -import org.simantics.g2d.element.handler.BendsHandler.Bend; -import org.simantics.g2d.element.handler.BorderColor; -import org.simantics.g2d.element.handler.Clickable; -import org.simantics.g2d.element.handler.Clickable.ClickListener; -import org.simantics.g2d.element.handler.Clickable.PressStatus; -import org.simantics.g2d.element.handler.EdgeVisuals; -import org.simantics.g2d.element.handler.ElementAdapter; -import org.simantics.g2d.element.handler.FillColor; -import org.simantics.g2d.element.handler.Hover; -import org.simantics.g2d.element.handler.InternalSize; -import org.simantics.g2d.element.handler.Move; -import org.simantics.g2d.element.handler.Outline; -import org.simantics.g2d.element.handler.Parent; -import org.simantics.g2d.element.handler.Pick; -import org.simantics.g2d.element.handler.Resize; -import org.simantics.g2d.element.handler.Scale; -import org.simantics.g2d.element.handler.Stateful; -import org.simantics.g2d.element.handler.TerminalLayout; -import org.simantics.g2d.element.handler.TerminalTopology; -import org.simantics.g2d.element.handler.Text; -import org.simantics.g2d.element.handler.TextColor; -import org.simantics.g2d.element.handler.TextEditor; -import org.simantics.g2d.element.handler.TextFont; -import org.simantics.g2d.element.handler.Transform; -import org.simantics.g2d.participant.TransformUtil; -import org.simantics.g2d.utils.GeometryUtils; -import org.simantics.g2d.utils.geom.DirectionSet; -import org.simantics.scenegraph.INode; -import org.simantics.scenegraph.ParentNode; -import org.simantics.utils.ObjectUtils; -import org.simantics.utils.datastructures.hints.IHintContext; -import org.simantics.utils.datastructures.hints.IHintContext.Key; - -/** - * Utils for element users. - * - * @See {@link TerminalUtil} - * @See {@link ElementHandlerUtils} Utils for element handler (coders) - * @author Toni Kalajainen - */ -public class ElementUtils { - - @SuppressWarnings("unchecked") - public static T getObject(IElement e) { - return (T) e.getHint(ElementHints.KEY_OBJECT); - } - - public static void disable(IElement e) - { - Stateful enabled = e.getElementClass().getSingleItem(Stateful.class); - enabled.setEnabled(e, false); - } - - public static void enable(IElement e) - { - Stateful enabled = e.getElementClass().getSingleItem(Stateful.class); - enabled.setEnabled(e, true); - } - - /** - * @param e - * @return - */ - public static boolean isHidden(IElement e) { - return e.containsHint(ElementHints.KEY_HIDDEN); - } - - /** - * @param e - * @param state null to remove hidden state - * @return - */ - public static void setHidden(IElement e, boolean hidden) { - if (hidden) - e.setHint(ElementHints.KEY_HIDDEN, HideState.COMPLETELY_HIDDEN); - else - e.removeHint(ElementHints.KEY_HIDDEN); - } - - public static void setText(IElement e, String text) - { - Text t = e.getElementClass().getSingleItem(Text.class); - t.setText(e, text); - } - - public static String getText(IElement e) - { - Text t = e.getElementClass().getSingleItem(Text.class); - return t.getText(e); - } - - /** - * Resizes or scales an element to fit it into a rectangle. - * - * @param e element - * @param rect rectangle on diagram - */ - public static void fitToRectangle(IElement e, Rectangle2D rect) - { - ElementClass ec = e.getElementClass(); - Move m = ec.getSingleItem(Move.class); - InternalSize b = ec.getSingleItem(InternalSize.class); - Rectangle2D internalSize = b.getBounds(e, null); - if (internalSize == null) - return; - - Resize rs = ec.getAtMostOneItemOfClass(Resize.class); - - Scale s = ec.getAtMostOneItemOfClass(Scale.class); - Point2D scale = s==null?new Point2D.Double(1.0,1.0):s.getScale(e); - double width = rect.getWidth(); - double height = rect.getHeight(); - double aspectRatio = width/height; - - Double requiredAspectRatio = rs==null?null:rs.getFixedAspectRatio(e); - if (requiredAspectRatio!=null) - { - if (aspectRatio>requiredAspectRatio) - width = height*requiredAspectRatio; - else - height = width / requiredAspectRatio; - } - - // Resize it - if (rs!=null) { - m.moveTo(e, rect.getX(), rect.getY()); - if (scale!=null) { - width /= scale.getX(); - height /= scale.getY(); - } - Rectangle2D r = new Rectangle2D.Double(0, 0, width, height); - rs.resize(e, r); - } else - // Scale it - if (s!=null) { - double sx = rect.getWidth() / internalSize.getWidth(); - double sy = rect.getHeight() / internalSize.getHeight(); - double px = rect.getX() - internalSize.getX()*sx; - double py = rect.getY() - internalSize.getY()*sy; - m.moveTo(e, px, py); - scale.setLocation(sx, sy); - s.setScale(e, scale); - } - } - - public static void addClickListener(IElement e, ICanvasContext ctx, ClickListener listener) - { - Clickable clickable = e.getElementClass().getAtMostOneItemOfClass(Clickable.class); - clickable.addListener(e, ctx, ctx.getThreadAccess(), listener); - } - - public static Point2D getPos(IElement e) - { - Move m = e.getElementClass().getSingleItem(Move.class); - return m.getPosition(e); - } - - public static Point2D getPos(IElement e, Point2D result) - { - Move m = e.getElementClass().getSingleItem(Move.class); - if (result == null) - result = new Point2D.Double(); - Point2D p = m.getPosition(e); - result.setLocation(p); - return result; - } - - public static Point2D getAbsolutePos(IElement e) - { - Transform tr = e.getElementClass().getSingleItem(Transform.class); - AffineTransform at = tr.getTransform(e); - return new Point2D.Double(at.getTranslateX(), at.getTranslateY()); - } - - public static Point2D getAbsolutePos(IElement e, Point2D result) - { - Transform tr = e.getElementClass().getSingleItem(Transform.class); - AffineTransform at = tr.getTransform(e); - if (result == null) - result = new Point2D.Double(); - result.setLocation(at.getTranslateX(), at.getTranslateY()); - return result; - } - - public static void setPos(IElement e, Point2D newPosition) - { - Move m = e.getElementClass().getSingleItem(Move.class); - m.moveTo(e, newPosition.getX(), newPosition.getY()); - } - - public static void setPos(IElement e, double x, double y) - { - Move m = e.getElementClass().getSingleItem(Move.class); - m.moveTo(e,x, y); - } - - public static IElement getByData(IDiagram d, Object data) - { - DataElementMap map = d.getDiagramClass().getSingleItem(DataElementMap.class); - return map.getElement(d, data); - } - - public static Object getData(IDiagram d, IElement element) - { - DataElementMap map = d.getDiagramClass().getSingleItem(DataElementMap.class); - return map.getData(d, element); - } - - /** - * Get all terminals of an element. - * - * @param e element - * @param result a store for the terminals - * @param clearResult true to clear the result collection - * before filling it - * @return the specified result collection - */ - public static Collection getTerminals(IElement e, Collection result, boolean clearResult) { - if (clearResult) - result.clear(); - TerminalTopology tt = e.getElementClass().getSingleItem(TerminalTopology.class); - tt.getTerminals(e, result); - return result; - } - - /** - * Get a terminal of an element assuming there is only a single terminal. - * - * @param e element - * @param t terminal - * @return the only terminal of element e - * @throws IllegalArgumentException if there are zero or multiple terminals - */ - public static Terminal getSingleTerminal(IElement e) { - ArrayList ts = new ArrayList(4); - TerminalTopology tt = e.getElementClass().getSingleItem(TerminalTopology.class); - tt.getTerminals(e, ts); - if (ts.size() != 1) - throw new IllegalArgumentException("expected 1 terminal, element e has " + ts.size() + " terminals: " + ts); - return ts.get(0); - } - - /** - * Get a terminal of an element assuming there is only a single terminal. - * If there are no or multiple terminals, null is returned. - * - * @param e element - * @param t terminal - * @return - */ - public static Terminal peekSingleTerminal(IElement e) { - ArrayList ts = new ArrayList(4); - TerminalTopology tt = e.getElementClass().getSingleItem(TerminalTopology.class); - tt.getTerminals(e, ts); - if (ts.size() != 1) - return null; - return ts.get(0); - } - - /** - * Get allowed outward directions of a terminal - * @param e element - * @param t terminal - * @return - */ - public static DirectionSet getTerminalDirection(IElement e, Terminal t) - { - List tls = e.getElementClass().getItemsByClass(TerminalLayout.class); - DirectionSet result = new DirectionSet(); - for (TerminalLayout tl : tls) { - tl.getTerminalDirection(e, t, result); - } - return result; - } - - public static AffineTransform getTransform(IElement e) - { - return e.getElementClass().getSingleItem(Transform.class).getTransform(e); - } - - public static AffineTransform getTransform(IElement e, AffineTransform result) - { - if (e == null) - return result; - AffineTransform tr = e.getElementClass().getSingleItem(Transform.class).getTransform(e); - result.setTransform(tr); - return result; - } - - /** - * @param e the element to get the local transform from - * @param result the transform to set to the local transform value or - * null to allocate a new transform if the element - * doesn't provide one. By providing a result transform one can make - * sure that no internal state of the element is returned. - * @return the provided result transform or a new transform instance - * depending on the arguments - */ - public static AffineTransform getLocalTransform(IElement e, AffineTransform result) - { - AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM); - if (result == null) - result = new AffineTransform(); - if (at != null) - result.setTransform(at); - return result; - } - - public static void setTransform(IElement e, AffineTransform at) - { - e.getElementClass().getSingleItem(Transform.class).setTransform(e, at); - } - - public static AffineTransform getInvTransform(IElement e) - { - try { - return e.getElementClass().getSingleItem(Transform.class).getTransform(e).createInverse(); - } catch (NoninvertibleTransformException e1) { - throw new RuntimeException(e1); - } - } - - - /** - * Element to canvas coordinates - * @param e - * @param elementPoint - * @param canvasPoint - * @return - */ - public static Point2D elementToCanvasCoordinate(IElement e, Point2D elementPoint, Point2D canvasPoint) - { - Transform t = e.getElementClass().getSingleItem(Transform.class); - AffineTransform at = t.getTransform(e); - return at.transform(elementPoint, canvasPoint); - } - - /** - * Element to control coordinates - * @param e - * @param ctx - * @param elementPoint - * @param controlPoint - * @return - */ - public static Point2D elementToControlCoordinate(IElement e, ICanvasContext ctx, Point2D elementPoint, Point2D controlPoint) - { - Transform t = e.getElementClass().getSingleItem(Transform.class); - TransformUtil util = ctx.getSingleItem(TransformUtil.class); - Point2D canvasPoint = t.getTransform(e).transform(elementPoint, null); - return util.getTransform().transform(elementPoint, canvasPoint); - } - - public static Point2D controlToElementCoordinate(IElement e, ICanvasContext ctx, Point2D controlPoint, Point2D elementPoint) - { - Transform t = e.getElementClass().getSingleItem(Transform.class); - AffineTransform at = t.getTransform(e); - TransformUtil util = ctx.getSingleItem(TransformUtil.class); - Point2D canvasPoint = util.controlToCanvas(controlPoint, new Point2D.Double()); - if (elementPoint==null) elementPoint = new Point2D.Double(); - try { - at.inverseTransform(canvasPoint, elementPoint); - return elementPoint; - } catch (NoninvertibleTransformException e1) { - throw new RuntimeException(e1); - } - } - - public static Point2D controlToCanvasCoordinate(ICanvasContext ctx, Point2D controlPoint, Point2D canvasPoint) - { - TransformUtil tu = ctx.getSingleItem(TransformUtil.class); - return tu.controlToCanvas(controlPoint, canvasPoint); - } - - - public static PressStatus getPressStatus(IElement e, ICanvasContext ctx) - { - Clickable c = e.getElementClass().getAtMostOneItemOfClass(Clickable.class); - if (c==null) return null; - return c.getPressStatus(e, ctx); - } - - public static Color getBorderColor(IElement e) - { - return getBorderColor(e, null); - } - - public static Color getFillColor(IElement e) - { - return getFillColor(e, null); - } - - public static Color getAdditionalColor(IElement e) - { - return getAdditionalColor(e, null); - } - - public static Color getTextColor(IElement e) - { - return getTextColor(e, null); - } - - /** - * Get border color of element of return defaultValue if border color is not - * available. - * - * @param e - * @param defaultValue - * @return - */ - public static Color getBorderColor(IElement e, Color defaultValue) - { - BorderColor bc = e.getElementClass().getAtMostOneItemOfClass(BorderColor.class); - if (bc==null) return defaultValue; - Color c = bc.getBorderColor(e); - return c != null ? c : defaultValue; - } - - /** - * Get fill color of element of return defaultValue if fill color is not - * available. - * - * @param e - * @param defaultValue - * @return - */ - public static Color getFillColor(IElement e, Color defaultValue) - { - FillColor fc = e.getElementClass().getAtMostOneItemOfClass(FillColor.class); - if (fc==null) return defaultValue; - Color c = fc.getFillColor(e); - return c != null ? c : defaultValue; - } - - /** - * Get additional color of element of return defaultValue if additional - * color is not available. - * - * @param e - * @param defaultValue - * @return - */ - public static Color getAdditionalColor(IElement e, Color defaultValue) - { - AdditionalColor ac = e.getElementClass().getAtMostOneItemOfClass(AdditionalColor.class); - if (ac==null) return null; - Color c = ac.getAdditionalColor(e); - return c != null ? c : defaultValue; - } - - /** - * Get text color of element of return defaultValue if text color is not - * available. - * - * @param e - * @param defaultValue - * @return - */ - public static Color getTextColor(IElement e, Color defaultValue) - { - TextColor tc = e.getElementClass().getAtMostOneItemOfClass(TextColor.class); - if (tc==null) return defaultValue; - Color c = tc.getTextColor(e); - return c != null ? c : defaultValue; - } - - public static TextEditor getTextEditor(IElement e) - { - TextEditor ed = e.getElementClass().getAtMostOneItemOfClass(TextEditor.class); - return ed; - } - - public static void setBorderColor(IElement e, Color color) - { - BorderColor bc = e.getElementClass().getAtMostOneItemOfClass(BorderColor.class); - if (bc==null) return; - bc.setBorderColor(e, color); - } - - public static void setFillColor(IElement e, Color color) - { - FillColor bc = e.getElementClass().getAtMostOneItemOfClass(FillColor.class); - if (bc==null) return; - bc.setFillColor(e, color); - } - - public static void setAdditionalColor(IElement e, Color color) - { - AdditionalColor bc = e.getElementClass().getAtMostOneItemOfClass(AdditionalColor.class); - if (bc==null) return; - bc.setAdditionalColor(e, color); - } - - public static void setTextColor(IElement e, Color color) - { - TextColor bc = e.getElementClass().getAtMostOneItemOfClass(TextColor.class); - if (bc==null) return; - bc.setTextColor(e, color); - } - - public static void setEdgeStroke(IElement e, Stroke s) - { - EdgeVisuals ev = e.getElementClass().getSingleItem(EdgeVisuals.class); - ev.setStroke(e, s); - } - - /** - * Fill given map with element bounds (the bounds on diagram) - * - * @param elements - * @param rects structure to be filled or null (instantates new) - * @return rects or newly instantiated structure - */ - public static Map getElementBoundsOnDiagram(Collection elements, Map rects) - { - if (rects == null) rects = new HashMap(); - for (IElement e : elements) { - Shape shape = getElementBoundsOnDiagram(e); - rects.put(e, shape.getBounds2D()); - } - return rects; - } - - /** - * get element bounds - * @param e element - * @return element bounds in element coordinates - */ - public static Rectangle2D getElementBounds(IElement e) - { - InternalSize b = e.getElementClass().getSingleItem(InternalSize.class); - return b.getBounds(e, new Rectangle2D.Double()); - } - - /** - * get element bounds - * @param e element - * @param result a rectangle for storing the result - * @return the specified result rectangle - */ - public static Rectangle2D getElementBounds(IElement e, Rectangle2D result) - { - InternalSize b = e.getElementClass().getSingleItem(InternalSize.class); - return b.getBounds(e, result); - } - - /** - * Get rough estimation of outer bounds of an element - * @param e element - * @return bounds on a diagram - */ - public static Shape getElementBoundsOnDiagram(IElement e) - { - Rectangle2D elementBounds = getElementBounds(e); - Transform t = e.getElementClass().getSingleItem(Transform.class); - AffineTransform canvasToElement = t.getTransform(e); - return GeometryUtils.transformShape(elementBounds, canvasToElement); - } - - /** - * Get rough estimation of outer bounds of an element - * @param e element - * @param result a rectangle for storing the result - * @return bounds on a diagram - */ - public static Rectangle2D getElementBoundsOnDiagram(IElement e, Rectangle2D result) - { - result = getElementBounds(e, result); - Transform t = e.getElementClass().getSingleItem(Transform.class); - AffineTransform canvasToElement = t.getTransform(e); - Shape shp = GeometryUtils.transformShape(result, canvasToElement); - result.setFrame(shp.getBounds2D()); - return result; - } - - /** - * Get union of outer bounds of a set of elements - * @param elements - * @return Union of element bounds (on diagram) or null - */ - public static Shape getElementBoundsOnDiagram(Collection elements) - { - if (elements.size()==0) return null; - if (elements.size()==1) return getElementBoundsOnDiagram(elements.iterator().next()); - Area a = new Area(); - for (IElement e : elements) { - Shape bounds = getElementBoundsOnDiagram(e); - Area ae = bounds instanceof Area ? (Area) bounds : new Area(bounds); - a.add(ae); - } - return a; - } - - /** - * Get union of outer bounds of a set of elements - * @param elements - * @return Union of element bounds (on diagram) or null - */ - public static Rectangle2D getSurroundingElementBoundsOnDiagram(Collection elements) - { - if (elements.size()==0) return null; - if (elements.size()==1) return getElementBoundsOnDiagram(elements.iterator().next()).getBounds2D(); - double minX = Double.MAX_VALUE, minY = Double.MAX_VALUE, maxX = -Double.MAX_VALUE, maxY = -Double.MAX_VALUE; - for (IElement e : elements) { - Rectangle2D bounds = getElementBoundsOnDiagram(e).getBounds2D(); - if (bounds.getMinX() < minX) minX = bounds.getMinX(); - if (bounds.getMinY() < minY) minY = bounds.getMinY(); - if (bounds.getMaxX() > maxX) maxX = bounds.getMaxX(); - if (bounds.getMaxY() > maxY) maxY = bounds.getMaxY(); - } - return new Rectangle2D.Double(minX, minY, maxX-minX, maxY-minY); - } - - /** - * Get as accurate shape if available - * - * @param e - * @return accurate shape of an element or null if shape is not available - */ - public static Shape getElementShape(IElement e) - { - List shapeProviders = e.getElementClass().getItemsByClass(Outline.class); - if (shapeProviders.isEmpty()) return null; - if (shapeProviders.size()==1) return shapeProviders.iterator().next().getElementShape(e); - Area a = new Area(); - for (Outline es : shapeProviders) - { - Shape shape = es.getElementShape(e); - Area ae = shape instanceof Area ? (Area) shape : new Area(shape); - a.add(ae); - } - return a; - } - - public static Shape getElementShapeOnDiagram(IElement e) - { - Shape shape = getElementShape(e); - if (shape == null) - return null; - Transform t = e.getElementClass().getSingleItem(Transform.class); - AffineTransform canvasToElement = t.getTransform(e); - return GeometryUtils.transformShape(shape, canvasToElement); - } - - /** - * Get element shape is one exists otherwise its bounds - * @param e - * @return shape or bounds - */ - public static Shape getElementShapeOrBounds(IElement e) - { - Shape shape = getElementShape(e); - if (shape!=null) return shape; - return getElementBounds(e); - } - - /** - * Get element shape is one exists otherwise its bounds - * @param e - * @return shape or bounds - */ - public static Shape getElementShapeOrBoundsOnDiagram(IElement e) - { - Shape shape = getElementShapeOnDiagram(e); - if (shape!=null) return shape; - return getElementBoundsOnDiagram(e); - } - - public static Shape getElementShapesOnDiagram(Collection elements) - { - if (elements.isEmpty()) return null; - if (elements.size()==1) { - //ITask task = ThreadLogger.getInstance().begin("single element shape: " + elements.iterator().next() + ")"); - Shape shp = getElementShapeOrBoundsOnDiagram(elements.iterator().next()); - //task.finish(); - return shp; - } - Area a = new Area(); - //ITask task = ThreadLogger.getInstance().begin("union of " + elements.size() + " element shapes"); - for (IElement e : elements) { - //ITask task2 = ThreadLogger.getInstance().begin("calculate area of " + e); - Shape shape = getElementShapeOrBoundsOnDiagram(e); - //task2.finish(); - //task2 = ThreadLogger.getInstance().begin("construct area from " + shape); - Area aa = null; - if (shape instanceof Area) - aa = (Area)shape; - else - aa = new Area(shape); - //task2.finish(); - //task2 = ThreadLogger.getInstance().begin("union area " + aa); - a.add(aa); - //task2.finish(); - } - //task.finish(); - return a; - } - - public static Shape mergeShapes(Collection shapes) - { - if (shapes.isEmpty()) return null; - if (shapes.size()==1) return shapes.iterator().next(); - Area a = new Area(); - for (Shape s : shapes) - a.add(new Area(s)); - return a; - } - - public static boolean pickInElement(IElement e, ICanvasContext ctx, PickRequest req) - { - Rectangle2D elementBounds = getElementBounds(e); - - // Pick with pick handler(s) - List pickHandlers = e.getElementClass().getItemsByClass(Pick.class); - if (!pickHandlers.isEmpty()) - { - // Rough filtering with bounds - if (!GeometryUtils.intersects(req.pickArea, elementBounds)) return false; - - // Convert pick shape to element coordinates - for (Pick p : pickHandlers) - { - if (p.pickTest(e, req.pickArea, req.pickPolicy)) - return true; - } - return false; - } - - // Pick with shape handler(s) - List shapeHandlers = e.getElementClass().getItemsByClass(Outline.class); - if (!shapeHandlers.isEmpty()) - { - // Rough filtering with bounds - if (!GeometryUtils.intersects(req.pickArea, elementBounds)) return false; - - // Intersection with one shape is enough - if (req.pickPolicy == PickPolicy.PICK_INTERSECTING_OBJECTS) - { - for (Outline es : shapeHandlers) - { - Shape elementShape = es.getElementShape(e); - if (GeometryUtils.intersects(req.pickArea, elementShape)) - return true; - } - return false; - } - - // Contains of all shapes is required - if (req.pickPolicy == PickPolicy.PICK_CONTAINED_OBJECTS) - { - for (Outline es : shapeHandlers) - { - Shape elementShape = es.getElementShape(e); - if (!GeometryUtils.contains(req.pickArea, elementShape)) - return false; - } - return true; - } - return false; - } - - // Pick by rectangle - if (req.pickPolicy == PickPolicy.PICK_INTERSECTING_OBJECTS) - { - if (GeometryUtils.intersects(req.pickArea, elementBounds)) - return true; - } - - else - - if (req.pickPolicy == PickPolicy.PICK_CONTAINED_OBJECTS) - { - if (GeometryUtils.contains(req.pickArea, elementBounds)) - return true; - } - return false; - } - - /** - * Get bends of an edge - * - * @param e edge - * @param bends the handles of each bend point - * @param points collection to be filled with the bends points in the same - * order as the bend handle objects - */ - public static void getBends(IElement e, List bends, List points) - { - BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class); - bh.getBends(e, bends); - for (Bend b : bends) - { - Point2D pos = new Point2D.Double(); - bh.getBendPosition(e, b, pos); - points.add(pos); - } - } - - /** - * Get bends of an edge - * @param e edge - * @param points collection to be filled with the bends points - */ - public static void getBends(IElement e, List points) - { - BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class); - int bendCount = bh.getBendsCount(e); - ArrayList bends = new ArrayList(bendCount); - getBends(e, bends, points); - } - - - public static void resizeElement(IElement e, double x, double y, double w, double h) { - Move m = e.getElementClass().getSingleItem(Move.class); - m.moveTo(e, x, y); - Resize s = e.getElementClass().getSingleItem(Resize.class); - s.resize(e, new Rectangle2D.Double(0,0,w,h)); - } - - public static T getHintOrDefault(IHintContext e, Key key, T defaultValue) { - T t = e.getHint(key); - assert key.isValueAccepted(defaultValue); - return t == null ? defaultValue : t; - } - - public static void setOrRemoveHint(IHintContext e, Key key, Object value) { - if (value == null) { - e.removeHint(key); - } else { - assert key.isValueAccepted(value); - e.setHint(key, value); - } - } - - public static boolean elementEquals(IElement e1, IElement e2) { - Object o1 = getObject(e1); - Object o2 = getObject(e2); - if (o1 == null && o2 == null) - return ObjectUtils.objectEquals(e1, e2); - return ObjectUtils.objectEquals(o1, o2); - } - - public static IElement getDiagramMappedElement(IElement e) { - IDiagram d = e.peekDiagram(); - if (d == null) - return e; - DataElementMap map = d.getDiagramClass().getAtMostOneItemOfClass(DataElementMap.class); - if (map == null) - return e; - Object o = map.getData(d, e); - if (o == null) - return e; - IElement mapped = map.getElement(d, o); - return mapped != null ? mapped : e; - } - - /** - * Calculates the center of the bounding box containing all the specified - * elements. - * - * @param the elements for which to calculate the center of a containing - * bounding box - * @param pivotPoint a Point2D for writing the result of the calculation or - * null to allocate a new Point2D if necessary - * @return the center of the containing bounding box or null if - * there are no elements - */ - public static Point2D getElementBoundsCenter(Collection elements, Point2D result) { - Shape b = getElementBoundsOnDiagram(elements); - if (b == null) - return null; - Rectangle2D bounds = b.getBounds2D(); - if (result == null) - result = new Point2D.Double(bounds.getCenterX(), bounds.getCenterY()); - else - result.setLocation(bounds.getCenterX(), bounds.getCenterY()); - return result; - } - - /** - * A utility for retrieving the containg diagram of an element. The element - * does not have to be directly in a diagram, the utility will also look for - * through the element parents for a diagram too. - * - * @param e - * @return - */ - public static IDiagram getDiagram(IElement e) { - if (e == null) - throw new IllegalArgumentException("null element"); - IDiagram d = peekDiagram(e); - if (d == null) - throw new IllegalStateException("element " + e + " is not part of a diagram"); - return d; - } - - /** - * A utility for retrieving the containg diagram of an element. The element - * does not have to be directly in a diagram, the utility will also look for - * through the element parents for a diagram too. - * - * @param e - * @return null if the element is not on a diagram nor is the - * element transitively a child of any element that is on a diagram - */ - public static IDiagram peekDiagram(IElement e) { - while (e != null) { - IDiagram d = e.peekDiagram(); - if (d != null) - return d; - e = getParent(e); - } - return null; - } - - /** - * Retrieves a possible parent element of an element. - * - * @param e the element to get a parent for - * @return the parent element or null if the element does not - * have a parent element - */ - public static IElement getParent(IElement e) { - Parent p = e.getElementClass().getAtMostOneItemOfClass(Parent.class); - if (p == null) - return null; - return p.getParent(e); - } - - /** - * Retrieves a possible parent element of an element. - * - * @param e the element to get a parent for - * @return the parent element or null if the element does not - * have a parent element - */ - public static Collection getParents(IElement e) { - List result = new ArrayList(3); - return getParents(e, result); - } - - /** - * Retrieves a possible parent element of an element. - * - * @param e the element to get a parent for - * @param result a collection wherein to store the possible parent elements - * @return the specified result collection - */ - public static Collection getParents(IElement e, Collection result) { - IElement p = e; - while (true) { - Parent ph = p.getElementClass().getAtMostOneItemOfClass(Parent.class); - if (ph == null) - return result; - p = ph.getParent(p); - if (p == null) - return result; - result.add(p); - } - } - - /** - * @param e - * @return - */ - public static String generateNodeId(IElement e) { - - String prefix = ""; - String sgName = e.getHint(ElementHints.KEY_SG_NAME); - if (sgName != null) prefix = sgName + " "; - - Object object = e.getHint(ElementHints.KEY_OBJECT); - if (object != null) { - return prefix + object.toString(); - } - // Warning: this can be hazardous for scene graph consistency! - // Duplicate nodes may be introduced when elements are updated through - // the Image interface. - return String.valueOf(e.hashCode()); - } - - /** - * - * @param the adaption target class - * @param e the element to adapt - * @param toClass the object class to adapt the element to - * @return adapter result or null if adaptation failed - */ - public static T adaptElement(IElement e, Class toClass) { - for (ElementAdapter adapter : e.getElementClass().getItemsByClass(ElementAdapter.class)){ - T t = adapter.adapt(e, toClass); - if (t != null) - return t; - } - return null; - } - - /** - * Tries to adapt an {@link ElementClass} into a requested class through - * {@link Adapter} handlers. Implements the IElement adaptation logic - * described in {@link Adapter}. - * - * @param - * the adaption target class - * @param e - * the element to adapt - * @param toClass - * the object class to adapt the element to - * @return adapter result or null if adaptation failed - */ - public static T adapt(ElementClass ec, Class toClass) { - if (ec == null) - throw new IllegalArgumentException("null element class"); - if (toClass == null) - throw new IllegalArgumentException("null target class"); - - for (Adapter adapter : ec.getItemsByClass(Adapter.class)){ - T t = adapter.adapt(toClass); - if (t != null) - return t; - } - return null; - } - - /** - * Otherwise the same as {@link #adapt(ElementClass, Class)} but will throw - * {@link UnsupportedOperationException} if the adaption fails. - * - * @param - * the adaption target class - * @param e - * the element to adapt - * @param toClass - * the object class to adapt the element to - * @return adapter result or null if adaptation failed - * @throws UnsupportedOperationException - */ - public static T checkedAdapt(ElementClass ec, Class toClass) { - T t = adapt(ec, toClass); - if (t != null) - return t; - throw new UnsupportedOperationException("cannot adapt " + ec + " to " + toClass); - } - - /** - * Looks for a scene graph node from the specified element with the - * specified node key. If the node does not exist, a new node is created - * using the specified node class. - * - * If a previous node exists, its class is verified to match the requested - * node class and returned as such upon success. If the classes do not - * match, an exception is raised since this is most likely a bug that needs - * to be fixed elsewhere. - * - * @param - * @param forElement - * @param withParentNode - * @param withNodeKey - * @param nodeClass - * @return - */ - public static T getOrCreateNode(IElement forElement, ParentNode withParentNode, Key withNodeKey, Class nodeClass) { - return getOrCreateNode(forElement, withParentNode, withNodeKey, null, nodeClass); - } - - /** - * Looks for a scene graph node from the specified element with the - * specified node key. If the node does not exist, a new node is created - * using the specified node class. - * - * If a previous node exists, its class is verified to match the requested - * node class and returned as such upon success. If the classes do not - * match, an exception is raised since this is most likely a bug that needs - * to be fixed elsewhere. - * - * @param - * @param forElement - * @param withParentNode - * @param withNodeKey - * @param nodeId - * @param nodeClass - * @return - */ - public static T getOrCreateNode(IElement forElement, ParentNode withParentNode, Key withNodeKey, String nodeId, Class nodeClass) { - return getOrCreateNode(forElement, withParentNode, withNodeKey, nodeId, nodeClass, null); - } - - /** - * Looks for a scene graph node from the specified element with the - * specified node key. If the node does not exist, a new node is created - * using the specified node class. - * - * If a previous node exists, its class is verified to match the requested - * node class and returned as such upon success. If the classes do not - * match, an exception is raised since this is most likely a bug that needs - * to be fixed elsewhere. - * - * @param - * @param forElement - * @param withParentNode - * @param withNodeKey - * @param nodeId - * @param nodeClass - * @param nodeCreationCallback a callback that is invoked with the node - * instance if a new node was created by this method - * @return - */ - public static T getOrCreateNode(IElement forElement, ParentNode withParentNode, Key withNodeKey, String nodeId, Class nodeClass, Consumer nodeCreationCallback) { - if (!(withNodeKey instanceof SceneGraphNodeKey)) - System.out.println("ElementUtils.getOrCreateNode: WARNING: removing scene graph node with that does not extend SceneGraphNodeKey: " + withNodeKey); - - @SuppressWarnings("unchecked") - T node = (T) forElement.getHint(withNodeKey); - if (node == null) { - node = nodeId != null ? withParentNode.getOrCreateNode(nodeId, nodeClass) : withParentNode.addNode(nodeClass); - forElement.setHint(withNodeKey, node); - if (nodeCreationCallback != null) - nodeCreationCallback.accept(node); - } else { - if (!nodeClass.isAssignableFrom(node.getClass())) { - throw new ElementSceneGraphException("ElementUtils.getOrCreateNode: WARNING: existing node class (" + node.getClass() + ") does not match requested node class (" + nodeClass + ") for element " + forElement + " with parent node " + withParentNode + " and node key " + withNodeKey); - } - // If the previously available node is not a parent of the specified - // node create a new node under the specified parent and set that - // as the node of the specified element. - if (!node.getParent().equals(withParentNode)) { - node = nodeId != null ? withParentNode.getOrCreateNode(nodeId, nodeClass) : withParentNode.addNode(nodeClass); - forElement.setHint(withNodeKey, node); - if (nodeCreationCallback != null) - nodeCreationCallback.accept(node); - } - } - return node; - } - - /** - * @param element - * @param nodeKey - * @return - */ - public static INode removePossibleNode(IElement element, Key nodeKey) { - if (!(nodeKey instanceof SceneGraphNodeKey)) - System.out.println("ElementUtils.removePossibleNode: WARNING: removing scene graph node with that does not extend SceneGraphNodeKey: " + nodeKey); - - INode node = element.getHint(nodeKey); - if (node != null) - node.remove(); - return node; - } - - /** - * - * @param element - * @return - */ - public static Font getTextFont(IElement element) { - TextFont tf = element.getElementClass().getSingleItem(TextFont.class); - return tf.getFont(element); - } - - public static void setTextFont(IElement element, Font font) { - TextFont tf = element.getElementClass().getSingleItem(TextFont.class); - tf.setFont(element, font); - } - - public static void addToCollectionHint(IElement element, Key key, T item) { - Collection collection = element.getHint(key); - if (collection == null) { - collection = new ArrayList(); - element.setHint(key, collection); - } - collection.add(item); - } - - public static void removeFromCollectionHint(IElement element, Key key, T item) { - Collection collection = element.getHint(key); - if (collection != null) { - collection = new ArrayList(); - collection.remove(item); - if (collection.isEmpty()) - element.removeHint(key); - } - } - - public static void setHover(IElement e, boolean hover) - { - Hover h = e.getElementClass().getSingleItem(Hover.class); - h.setHover(e, hover); - } - - public static boolean isHovering(IElement e) - { - Hover h = e.getElementClass().getSingleItem(Hover.class); - return h.isHovering(e); - } - +/******************************************************************************* + * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * in Industry THTH ry. + * 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: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.g2d.element; + +import java.awt.Color; +import java.awt.Font; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.AffineTransform; +import java.awt.geom.Area; +import java.awt.geom.NoninvertibleTransformException; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +import org.simantics.g2d.canvas.ICanvasContext; +import org.simantics.g2d.diagram.IDiagram; +import org.simantics.g2d.diagram.handler.DataElementMap; +import org.simantics.g2d.diagram.handler.PickRequest; +import org.simantics.g2d.diagram.handler.PickRequest.PickPolicy; +import org.simantics.g2d.diagram.handler.Topology.Terminal; +import org.simantics.g2d.diagram.participant.pointertool.TerminalUtil; +import org.simantics.g2d.element.handler.Adapter; +import org.simantics.g2d.element.handler.AdditionalColor; +import org.simantics.g2d.element.handler.BendsHandler; +import org.simantics.g2d.element.handler.BendsHandler.Bend; +import org.simantics.g2d.element.handler.BorderColor; +import org.simantics.g2d.element.handler.Clickable; +import org.simantics.g2d.element.handler.Clickable.ClickListener; +import org.simantics.g2d.element.handler.Clickable.PressStatus; +import org.simantics.g2d.element.handler.EdgeVisuals; +import org.simantics.g2d.element.handler.ElementAdapter; +import org.simantics.g2d.element.handler.FillColor; +import org.simantics.g2d.element.handler.Hover; +import org.simantics.g2d.element.handler.InternalSize; +import org.simantics.g2d.element.handler.Move; +import org.simantics.g2d.element.handler.Outline; +import org.simantics.g2d.element.handler.Parameters; +import org.simantics.g2d.element.handler.Parent; +import org.simantics.g2d.element.handler.Pick; +import org.simantics.g2d.element.handler.Resize; +import org.simantics.g2d.element.handler.Scale; +import org.simantics.g2d.element.handler.Stateful; +import org.simantics.g2d.element.handler.TerminalLayout; +import org.simantics.g2d.element.handler.TerminalTopology; +import org.simantics.g2d.element.handler.Text; +import org.simantics.g2d.element.handler.TextColor; +import org.simantics.g2d.element.handler.TextEditor; +import org.simantics.g2d.element.handler.TextFont; +import org.simantics.g2d.element.handler.Transform; +import org.simantics.g2d.participant.TransformUtil; +import org.simantics.g2d.utils.GeometryUtils; +import org.simantics.g2d.utils.geom.DirectionSet; +import org.simantics.scenegraph.INode; +import org.simantics.scenegraph.ParentNode; +import org.simantics.utils.ObjectUtils; +import org.simantics.utils.datastructures.hints.IHintContext; +import org.simantics.utils.datastructures.hints.IHintContext.Key; + +/** + * Utils for element users. + * + * @See {@link TerminalUtil} + * @See {@link ElementHandlerUtils} Utils for element handler (coders) + * @author Toni Kalajainen + */ +public class ElementUtils { + + @SuppressWarnings("unchecked") + public static T getObject(IElement e) { + return (T) e.getHint(ElementHints.KEY_OBJECT); + } + + public static void disable(IElement e) + { + Stateful enabled = e.getElementClass().getSingleItem(Stateful.class); + enabled.setEnabled(e, false); + } + + public static void enable(IElement e) + { + Stateful enabled = e.getElementClass().getSingleItem(Stateful.class); + enabled.setEnabled(e, true); + } + + /** + * @param e + * @return + */ + public static boolean isHidden(IElement e) { + return e.containsHint(ElementHints.KEY_HIDDEN); + } + + /** + * @param e + * @param state null to remove hidden state + * @return + */ + public static void setHidden(IElement e, boolean hidden) { + if (hidden) + e.setHint(ElementHints.KEY_HIDDEN, HideState.COMPLETELY_HIDDEN); + else + e.removeHint(ElementHints.KEY_HIDDEN); + } + + public static void setText(IElement e, String text) + { + Text t = e.getElementClass().getSingleItem(Text.class); + t.setText(e, text); + } + + public static String getText(IElement e) + { + Text t = e.getElementClass().getSingleItem(Text.class); + return t.getText(e); + } + + /** + * Resizes or scales an element to fit it into a rectangle. + * + * @param e element + * @param rect rectangle on diagram + */ + public static void fitToRectangle(IElement e, Rectangle2D rect) + { + ElementClass ec = e.getElementClass(); + Move m = ec.getSingleItem(Move.class); + InternalSize b = ec.getSingleItem(InternalSize.class); + Rectangle2D internalSize = b.getBounds(e, null); + if (internalSize == null) + return; + + Resize rs = ec.getAtMostOneItemOfClass(Resize.class); + + Scale s = ec.getAtMostOneItemOfClass(Scale.class); + Point2D scale = s==null?new Point2D.Double(1.0,1.0):s.getScale(e); + double width = rect.getWidth(); + double height = rect.getHeight(); + double aspectRatio = width/height; + + Double requiredAspectRatio = rs==null?null:rs.getFixedAspectRatio(e); + if (requiredAspectRatio!=null) + { + if (aspectRatio>requiredAspectRatio) + width = height*requiredAspectRatio; + else + height = width / requiredAspectRatio; + } + + // Resize it + if (rs!=null) { + m.moveTo(e, rect.getX(), rect.getY()); + if (scale!=null) { + width /= scale.getX(); + height /= scale.getY(); + } + Rectangle2D r = new Rectangle2D.Double(0, 0, width, height); + rs.resize(e, r); + } else + // Scale it + if (s!=null) { + double sx = rect.getWidth() / internalSize.getWidth(); + double sy = rect.getHeight() / internalSize.getHeight(); + double px = rect.getX() - internalSize.getX()*sx; + double py = rect.getY() - internalSize.getY()*sy; + m.moveTo(e, px, py); + scale.setLocation(sx, sy); + s.setScale(e, scale); + } + } + + public static void addClickListener(IElement e, ICanvasContext ctx, ClickListener listener) + { + Clickable clickable = e.getElementClass().getAtMostOneItemOfClass(Clickable.class); + clickable.addListener(e, ctx, ctx.getThreadAccess(), listener); + } + + public static Point2D getPos(IElement e) + { + Move m = e.getElementClass().getSingleItem(Move.class); + return m.getPosition(e); + } + + public static Point2D getPos(IElement e, Point2D result) + { + Move m = e.getElementClass().getSingleItem(Move.class); + if (result == null) + result = new Point2D.Double(); + Point2D p = m.getPosition(e); + result.setLocation(p); + return result; + } + + public static Point2D getAbsolutePos(IElement e) + { + Transform tr = e.getElementClass().getSingleItem(Transform.class); + AffineTransform at = tr.getTransform(e); + return new Point2D.Double(at.getTranslateX(), at.getTranslateY()); + } + + public static Point2D getAbsolutePos(IElement e, Point2D result) + { + Transform tr = e.getElementClass().getSingleItem(Transform.class); + AffineTransform at = tr.getTransform(e); + if (result == null) + result = new Point2D.Double(); + result.setLocation(at.getTranslateX(), at.getTranslateY()); + return result; + } + + public static void setPos(IElement e, Point2D newPosition) + { + Move m = e.getElementClass().getSingleItem(Move.class); + m.moveTo(e, newPosition.getX(), newPosition.getY()); + } + + public static void setPos(IElement e, double x, double y) + { + Move m = e.getElementClass().getSingleItem(Move.class); + m.moveTo(e,x, y); + } + + public static IElement getByData(IDiagram d, Object data) + { + DataElementMap map = d.getDiagramClass().getSingleItem(DataElementMap.class); + return map.getElement(d, data); + } + + public static Object getData(IDiagram d, IElement element) + { + DataElementMap map = d.getDiagramClass().getSingleItem(DataElementMap.class); + return map.getData(d, element); + } + + /** + * Get all terminals of an element. + * + * @param e element + * @param result a store for the terminals + * @param clearResult true to clear the result collection + * before filling it + * @return the specified result collection + */ + public static Collection getTerminals(IElement e, Collection result, boolean clearResult) { + if (clearResult) + result.clear(); + TerminalTopology tt = e.getElementClass().getAtMostOneItemOfClass(TerminalTopology.class); + if (tt != null) { + tt.getTerminals(e, result); + } + return result; + } + + /** + * Get a terminal of an element assuming there is only a single terminal. + * + * @param e element + * @param t terminal + * @return the only terminal of element e + * @throws IllegalArgumentException if there are zero or multiple terminals + */ + public static Terminal getSingleTerminal(IElement e) { + ArrayList ts = new ArrayList(4); + TerminalTopology tt = e.getElementClass().getSingleItem(TerminalTopology.class); + tt.getTerminals(e, ts); + if (ts.size() != 1) + throw new IllegalArgumentException("expected 1 terminal, element e has " + ts.size() + " terminals: " + ts); + return ts.get(0); + } + + /** + * Get a terminal of an element assuming there is only a single terminal. + * If there are no or multiple terminals, null is returned. + * + * @param e element + * @param t terminal + * @return + */ + public static Terminal peekSingleTerminal(IElement e) { + ArrayList ts = new ArrayList(4); + TerminalTopology tt = e.getElementClass().getSingleItem(TerminalTopology.class); + tt.getTerminals(e, ts); + if (ts.size() != 1) + return null; + return ts.get(0); + } + + /** + * Get allowed outward directions of a terminal + * @param e element + * @param t terminal + * @return + */ + public static DirectionSet getTerminalDirection(IElement e, Terminal t) + { + List tls = e.getElementClass().getItemsByClass(TerminalLayout.class); + DirectionSet result = new DirectionSet(); + for (TerminalLayout tl : tls) { + tl.getTerminalDirection(e, t, result); + } + return result; + } + + public static AffineTransform getTransform(IElement e) + { + return e.getElementClass().getSingleItem(Transform.class).getTransform(e); + } + + public static AffineTransform getTransform(IElement e, AffineTransform result) + { + if (e == null) + return result; + AffineTransform tr = e.getElementClass().getSingleItem(Transform.class).getTransform(e); + result.setTransform(tr); + return result; + } + + /** + * @param e the element to get the local transform from + * @param result the transform to set to the local transform value or + * null to allocate a new transform if the element + * doesn't provide one. By providing a result transform one can make + * sure that no internal state of the element is returned. + * @return the provided result transform or a new transform instance + * depending on the arguments + */ + public static AffineTransform getLocalTransform(IElement e, AffineTransform result) + { + AffineTransform at = e.getHint(ElementHints.KEY_TRANSFORM); + if (result == null) + result = new AffineTransform(); + if (at != null) + result.setTransform(at); + return result; + } + + public static void setTransform(IElement e, AffineTransform at) + { + e.getElementClass().getSingleItem(Transform.class).setTransform(e, at); + } + + public static void setParameters(IElement e, Map parameters) + { + Parameters ps = e.getElementClass().getSingleItem(Parameters.class); + if(ps != null) ps.setParameters(e, parameters); + } + + public static Map getParameters(IElement e) + { + Parameters ps = e.getElementClass().getAtMostOneItemOfClass(Parameters.class); + return ps != null ? ps.getParameters(e) : null; + } + + public static AffineTransform getInvTransform(IElement e) + { + try { + return e.getElementClass().getSingleItem(Transform.class).getTransform(e).createInverse(); + } catch (NoninvertibleTransformException e1) { + throw new RuntimeException(e1); + } + } + + + /** + * Element to canvas coordinates + * @param e + * @param elementPoint + * @param canvasPoint + * @return + */ + public static Point2D elementToCanvasCoordinate(IElement e, Point2D elementPoint, Point2D canvasPoint) + { + Transform t = e.getElementClass().getSingleItem(Transform.class); + AffineTransform at = t.getTransform(e); + return at.transform(elementPoint, canvasPoint); + } + + /** + * Element to control coordinates + * @param e + * @param ctx + * @param elementPoint + * @param controlPoint + * @return + */ + public static Point2D elementToControlCoordinate(IElement e, ICanvasContext ctx, Point2D elementPoint, Point2D controlPoint) + { + Transform t = e.getElementClass().getSingleItem(Transform.class); + TransformUtil util = ctx.getSingleItem(TransformUtil.class); + Point2D canvasPoint = t.getTransform(e).transform(elementPoint, null); + return util.getTransform().transform(elementPoint, canvasPoint); + } + + public static Point2D controlToElementCoordinate(IElement e, ICanvasContext ctx, Point2D controlPoint, Point2D elementPoint) + { + Transform t = e.getElementClass().getSingleItem(Transform.class); + AffineTransform at = t.getTransform(e); + TransformUtil util = ctx.getSingleItem(TransformUtil.class); + Point2D canvasPoint = util.controlToCanvas(controlPoint, new Point2D.Double()); + if (elementPoint==null) elementPoint = new Point2D.Double(); + try { + at.inverseTransform(canvasPoint, elementPoint); + return elementPoint; + } catch (NoninvertibleTransformException e1) { + throw new RuntimeException(e1); + } + } + + public static Point2D controlToCanvasCoordinate(ICanvasContext ctx, Point2D controlPoint, Point2D canvasPoint) + { + TransformUtil tu = ctx.getSingleItem(TransformUtil.class); + return tu.controlToCanvas(controlPoint, canvasPoint); + } + + + public static PressStatus getPressStatus(IElement e, ICanvasContext ctx) + { + Clickable c = e.getElementClass().getAtMostOneItemOfClass(Clickable.class); + if (c==null) return null; + return c.getPressStatus(e, ctx); + } + + public static Color getBorderColor(IElement e) + { + return getBorderColor(e, null); + } + + public static Color getFillColor(IElement e) + { + return getFillColor(e, null); + } + + public static Color getAdditionalColor(IElement e) + { + return getAdditionalColor(e, null); + } + + public static Color getTextColor(IElement e) + { + return getTextColor(e, null); + } + + /** + * Get border color of element of return defaultValue if border color is not + * available. + * + * @param e + * @param defaultValue + * @return + */ + public static Color getBorderColor(IElement e, Color defaultValue) + { + BorderColor bc = e.getElementClass().getAtMostOneItemOfClass(BorderColor.class); + if (bc==null) return defaultValue; + Color c = bc.getBorderColor(e); + return c != null ? c : defaultValue; + } + + /** + * Get fill color of element of return defaultValue if fill color is not + * available. + * + * @param e + * @param defaultValue + * @return + */ + public static Color getFillColor(IElement e, Color defaultValue) + { + FillColor fc = e.getElementClass().getAtMostOneItemOfClass(FillColor.class); + if (fc==null) return defaultValue; + Color c = fc.getFillColor(e); + return c != null ? c : defaultValue; + } + + /** + * Get additional color of element of return defaultValue if additional + * color is not available. + * + * @param e + * @param defaultValue + * @return + */ + public static Color getAdditionalColor(IElement e, Color defaultValue) + { + AdditionalColor ac = e.getElementClass().getAtMostOneItemOfClass(AdditionalColor.class); + if (ac==null) return null; + Color c = ac.getAdditionalColor(e); + return c != null ? c : defaultValue; + } + + /** + * Get text color of element of return defaultValue if text color is not + * available. + * + * @param e + * @param defaultValue + * @return + */ + public static Color getTextColor(IElement e, Color defaultValue) + { + TextColor tc = e.getElementClass().getAtMostOneItemOfClass(TextColor.class); + if (tc==null) return defaultValue; + Color c = tc.getTextColor(e); + return c != null ? c : defaultValue; + } + + public static TextEditor getTextEditor(IElement e) + { + TextEditor ed = e.getElementClass().getAtMostOneItemOfClass(TextEditor.class); + return ed; + } + + public static void setBorderColor(IElement e, Color color) + { + BorderColor bc = e.getElementClass().getAtMostOneItemOfClass(BorderColor.class); + if (bc==null) return; + bc.setBorderColor(e, color); + } + + public static void setFillColor(IElement e, Color color) + { + FillColor bc = e.getElementClass().getAtMostOneItemOfClass(FillColor.class); + if (bc==null) return; + bc.setFillColor(e, color); + } + + public static void setAdditionalColor(IElement e, Color color) + { + AdditionalColor bc = e.getElementClass().getAtMostOneItemOfClass(AdditionalColor.class); + if (bc==null) return; + bc.setAdditionalColor(e, color); + } + + public static void setTextColor(IElement e, Color color) + { + TextColor bc = e.getElementClass().getAtMostOneItemOfClass(TextColor.class); + if (bc==null) return; + bc.setTextColor(e, color); + } + + public static void setEdgeStroke(IElement e, Stroke s) + { + EdgeVisuals ev = e.getElementClass().getSingleItem(EdgeVisuals.class); + ev.setStroke(e, s); + } + + /** + * Fill given map with element bounds (the bounds on diagram) + * + * @param elements + * @param rects structure to be filled or null (instantates new) + * @return rects or newly instantiated structure + */ + public static Map getElementBoundsOnDiagram(Collection elements, Map rects) + { + if (rects == null) rects = new HashMap(); + for (IElement e : elements) { + Shape shape = getElementBoundsOnDiagram(e); + rects.put(e, shape.getBounds2D()); + } + return rects; + } + + /** + * get element bounds + * @param e element + * @return element bounds in element coordinates + */ + public static Rectangle2D getElementBounds(IElement e) + { + InternalSize b = e.getElementClass().getSingleItem(InternalSize.class); + return b.getBounds(e, new Rectangle2D.Double()); + } + + /** + * get element bounds + * @param e element + * @param result a rectangle for storing the result + * @return the specified result rectangle + */ + public static Rectangle2D getElementBounds(IElement e, Rectangle2D result) + { + InternalSize b = e.getElementClass().getSingleItem(InternalSize.class); + return b.getBounds(e, result); + } + + /** + * Get rough estimation of outer bounds of an element + * @param e element + * @return bounds on a diagram + */ + public static Shape getElementBoundsOnDiagram(IElement e) + { + Rectangle2D elementBounds = getElementBounds(e); + Transform t = e.getElementClass().getSingleItem(Transform.class); + AffineTransform canvasToElement = t.getTransform(e); + return GeometryUtils.transformShape(elementBounds, canvasToElement); + } + + /** + * Get rough estimation of outer bounds of an element + * @param e element + * @param result a rectangle for storing the result + * @return bounds on a diagram + */ + public static Rectangle2D getElementBoundsOnDiagram(IElement e, Rectangle2D result) + { + result = getElementBounds(e, result); + Transform t = e.getElementClass().getSingleItem(Transform.class); + AffineTransform canvasToElement = t.getTransform(e); + Shape shp = GeometryUtils.transformShape(result, canvasToElement); + result.setFrame(shp.getBounds2D()); + return result; + } + + /** + * Get union of outer bounds of a set of elements + * @param elements + * @return Union of element bounds (on diagram) or null + */ + public static Shape getElementBoundsOnDiagram(Collection elements) + { + if (elements.size()==0) return null; + if (elements.size()==1) return getElementBoundsOnDiagram(elements.iterator().next()); + Area a = new Area(); + for (IElement e : elements) { + Shape bounds = getElementBoundsOnDiagram(e); + Area ae = bounds instanceof Area ? (Area) bounds : new Area(bounds); + a.add(ae); + } + return a; + } + + /** + * Get union of outer bounds of a set of elements + * @param elements + * @return Union of element bounds (on diagram) or null + */ + public static Rectangle2D getSurroundingElementBoundsOnDiagram(Collection elements) + { + if (elements.size()==0) return null; + if (elements.size()==1) return getElementBoundsOnDiagram(elements.iterator().next()).getBounds2D(); + double minX = Double.MAX_VALUE, minY = Double.MAX_VALUE, maxX = -Double.MAX_VALUE, maxY = -Double.MAX_VALUE; + for (IElement e : elements) { + Rectangle2D bounds = getElementBoundsOnDiagram(e).getBounds2D(); + if (bounds.getMinX() < minX) minX = bounds.getMinX(); + if (bounds.getMinY() < minY) minY = bounds.getMinY(); + if (bounds.getMaxX() > maxX) maxX = bounds.getMaxX(); + if (bounds.getMaxY() > maxY) maxY = bounds.getMaxY(); + } + return new Rectangle2D.Double(minX, minY, maxX-minX, maxY-minY); + } + + /** + * Get as accurate shape if available + * + * @param e + * @return accurate shape of an element or null if shape is not available + */ + public static Shape getElementShape(IElement e) + { + List shapeProviders = e.getElementClass().getItemsByClass(Outline.class); + if (shapeProviders.isEmpty()) return null; + if (shapeProviders.size()==1) return shapeProviders.iterator().next().getElementShape(e); + Area a = new Area(); + for (Outline es : shapeProviders) + { + Shape shape = es.getElementShape(e); + Area ae = shape instanceof Area ? (Area) shape : new Area(shape); + a.add(ae); + } + return a; + } + + public static Shape getElementShapeOnDiagram(IElement e) + { + Shape shape = getElementShape(e); + if (shape == null) + return null; + Transform t = e.getElementClass().getSingleItem(Transform.class); + AffineTransform canvasToElement = t.getTransform(e); + return GeometryUtils.transformShape(shape, canvasToElement); + } + + /** + * Get element shape is one exists otherwise its bounds + * @param e + * @return shape or bounds + */ + public static Shape getElementShapeOrBounds(IElement e) + { + Shape shape = getElementShape(e); + if (shape!=null) return shape; + return getElementBounds(e); + } + + /** + * Get element shape is one exists otherwise its bounds + * @param e + * @return shape or bounds + */ + public static Shape getElementShapeOrBoundsOnDiagram(IElement e) + { + Shape shape = getElementShapeOnDiagram(e); + if (shape!=null) return shape; + return getElementBoundsOnDiagram(e); + } + + public static Shape getElementShapesOnDiagram(Collection elements) + { + if (elements.isEmpty()) return null; + if (elements.size()==1) { + //ITask task = ThreadLogger.getInstance().begin("single element shape: " + elements.iterator().next() + ")"); + Shape shp = getElementShapeOrBoundsOnDiagram(elements.iterator().next()); + //task.finish(); + return shp; + } + Area a = new Area(); + //ITask task = ThreadLogger.getInstance().begin("union of " + elements.size() + " element shapes"); + for (IElement e : elements) { + //ITask task2 = ThreadLogger.getInstance().begin("calculate area of " + e); + Shape shape = getElementShapeOrBoundsOnDiagram(e); + //task2.finish(); + //task2 = ThreadLogger.getInstance().begin("construct area from " + shape); + Area aa = null; + if (shape instanceof Area) + aa = (Area)shape; + else + aa = new Area(shape); + //task2.finish(); + //task2 = ThreadLogger.getInstance().begin("union area " + aa); + a.add(aa); + //task2.finish(); + } + //task.finish(); + return a; + } + + public static Shape mergeShapes(Collection shapes) + { + if (shapes.isEmpty()) return null; + if (shapes.size()==1) return shapes.iterator().next(); + Area a = new Area(); + for (Shape s : shapes) + a.add(new Area(s)); + return a; + } + + public static boolean pickInElement(IElement e, ICanvasContext ctx, PickRequest req) + { + Rectangle2D elementBounds = getElementBounds(e); + + // Pick with pick handler(s) + List pickHandlers = e.getElementClass().getItemsByClass(Pick.class); + if (!pickHandlers.isEmpty()) + { + // Rough filtering with bounds + if (!GeometryUtils.intersects(req.pickArea, elementBounds)) return false; + + // Convert pick shape to element coordinates + for (Pick p : pickHandlers) + { + if (p.pickTest(e, req.pickArea, req.pickPolicy)) + return true; + } + return false; + } + + // Pick with shape handler(s) + List shapeHandlers = e.getElementClass().getItemsByClass(Outline.class); + if (!shapeHandlers.isEmpty()) + { + // Rough filtering with bounds + if (!GeometryUtils.intersects(req.pickArea, elementBounds)) return false; + + // Intersection with one shape is enough + if (req.pickPolicy == PickPolicy.PICK_INTERSECTING_OBJECTS) + { + for (Outline es : shapeHandlers) + { + Shape elementShape = es.getElementShape(e); + if (GeometryUtils.intersects(req.pickArea, elementShape)) + return true; + } + return false; + } + + // Contains of all shapes is required + if (req.pickPolicy == PickPolicy.PICK_CONTAINED_OBJECTS) + { + for (Outline es : shapeHandlers) + { + Shape elementShape = es.getElementShape(e); + if (!GeometryUtils.contains(req.pickArea, elementShape)) + return false; + } + return true; + } + return false; + } + + // Pick by rectangle + if (req.pickPolicy == PickPolicy.PICK_INTERSECTING_OBJECTS) + { + if (GeometryUtils.intersects(req.pickArea, elementBounds)) + return true; + } + + else + + if (req.pickPolicy == PickPolicy.PICK_CONTAINED_OBJECTS) + { + if (GeometryUtils.contains(req.pickArea, elementBounds)) + return true; + } + return false; + } + + /** + * Get bends of an edge + * + * @param e edge + * @param bends the handles of each bend point + * @param points collection to be filled with the bends points in the same + * order as the bend handle objects + */ + public static void getBends(IElement e, List bends, List points) + { + BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class); + bh.getBends(e, bends); + for (Bend b : bends) + { + Point2D pos = new Point2D.Double(); + bh.getBendPosition(e, b, pos); + points.add(pos); + } + } + + /** + * Get bends of an edge + * @param e edge + * @param points collection to be filled with the bends points + */ + public static void getBends(IElement e, List points) + { + BendsHandler bh = e.getElementClass().getSingleItem(BendsHandler.class); + int bendCount = bh.getBendsCount(e); + ArrayList bends = new ArrayList(bendCount); + getBends(e, bends, points); + } + + + public static void resizeElement(IElement e, double x, double y, double w, double h) { + Move m = e.getElementClass().getSingleItem(Move.class); + m.moveTo(e, x, y); + Resize s = e.getElementClass().getSingleItem(Resize.class); + s.resize(e, new Rectangle2D.Double(0,0,w,h)); + } + + public static T getHintOrDefault(IHintContext e, Key key, T defaultValue) { + T t = e.getHint(key); + assert key.isValueAccepted(defaultValue); + return t == null ? defaultValue : t; + } + + public static void setOrRemoveHint(IHintContext e, Key key, Object value) { + if (value == null) { + e.removeHint(key); + } else { + assert key.isValueAccepted(value); + e.setHint(key, value); + } + } + + public static boolean elementEquals(IElement e1, IElement e2) { + Object o1 = getObject(e1); + Object o2 = getObject(e2); + if (o1 == null && o2 == null) + return ObjectUtils.objectEquals(e1, e2); + return ObjectUtils.objectEquals(o1, o2); + } + + public static IElement getDiagramMappedElement(IElement e) { + IDiagram d = e.peekDiagram(); + if (d == null) + return e; + DataElementMap map = d.getDiagramClass().getAtMostOneItemOfClass(DataElementMap.class); + if (map == null) + return e; + Object o = map.getData(d, e); + if (o == null) + return e; + IElement mapped = map.getElement(d, o); + return mapped != null ? mapped : e; + } + + /** + * Calculates the center of the bounding box containing all the specified + * elements. + * + * @param the elements for which to calculate the center of a containing + * bounding box + * @param pivotPoint a Point2D for writing the result of the calculation or + * null to allocate a new Point2D if necessary + * @return the center of the containing bounding box or null if + * there are no elements + */ + public static Point2D getElementBoundsCenter(Collection elements, Point2D result) { + Shape b = getElementBoundsOnDiagram(elements); + if (b == null) + return null; + Rectangle2D bounds = b.getBounds2D(); + if (result == null) + result = new Point2D.Double(bounds.getCenterX(), bounds.getCenterY()); + else + result.setLocation(bounds.getCenterX(), bounds.getCenterY()); + return result; + } + + /** + * A utility for retrieving the containg diagram of an element. The element + * does not have to be directly in a diagram, the utility will also look for + * through the element parents for a diagram too. + * + * @param e + * @return + */ + public static IDiagram getDiagram(IElement e) { + if (e == null) + throw new IllegalArgumentException("null element"); + IDiagram d = peekDiagram(e); + if (d == null) + throw new IllegalStateException("element " + e + " is not part of a diagram"); + return d; + } + + /** + * A utility for retrieving the containg diagram of an element. The element + * does not have to be directly in a diagram, the utility will also look for + * through the element parents for a diagram too. + * + * @param e + * @return null if the element is not on a diagram nor is the + * element transitively a child of any element that is on a diagram + */ + public static IDiagram peekDiagram(IElement e) { + while (e != null) { + IDiagram d = e.peekDiagram(); + if (d != null) + return d; + e = getParent(e); + } + return null; + } + + /** + * Retrieves a possible parent element of an element. + * + * @param e the element to get a parent for + * @return the parent element or null if the element does not + * have a parent element + */ + public static IElement getParent(IElement e) { + Parent p = e.getElementClass().getAtMostOneItemOfClass(Parent.class); + if (p == null) + return null; + return p.getParent(e); + } + + /** + * Retrieves a possible parent element of an element. + * + * @param e the element to get a parent for + * @return the parent element or null if the element does not + * have a parent element + */ + public static Collection getParents(IElement e) { + List result = new ArrayList(3); + return getParents(e, result); + } + + /** + * Retrieves a possible parent element of an element. + * + * @param e the element to get a parent for + * @param result a collection wherein to store the possible parent elements + * @return the specified result collection + */ + public static Collection getParents(IElement e, Collection result) { + IElement p = e; + while (true) { + Parent ph = p.getElementClass().getAtMostOneItemOfClass(Parent.class); + if (ph == null) + return result; + p = ph.getParent(p); + if (p == null) + return result; + result.add(p); + } + } + + /** + * @param e + * @return + */ + public static String generateNodeId(IElement e) { + + String prefix = ""; + String sgName = e.getHint(ElementHints.KEY_SG_NAME); + if (sgName != null) prefix = sgName + " "; + + Object object = e.getHint(ElementHints.KEY_OBJECT); + if (object != null) { + return prefix + object.toString(); + } + // Warning: this can be hazardous for scene graph consistency! + // Duplicate nodes may be introduced when elements are updated through + // the Image interface. + return String.valueOf(e.hashCode()); + } + + /** + * + * @param the adaption target class + * @param e the element to adapt + * @param toClass the object class to adapt the element to + * @return adapter result or null if adaptation failed + */ + public static T adaptElement(IElement e, Class toClass) { + for (ElementAdapter adapter : e.getElementClass().getItemsByClass(ElementAdapter.class)){ + T t = adapter.adapt(e, toClass); + if (t != null) + return t; + } + return null; + } + + /** + * Tries to adapt an {@link ElementClass} into a requested class through + * {@link Adapter} handlers. Implements the IElement adaptation logic + * described in {@link Adapter}. + * + * @param + * the adaption target class + * @param e + * the element to adapt + * @param toClass + * the object class to adapt the element to + * @return adapter result or null if adaptation failed + */ + public static T adapt(ElementClass ec, Class toClass) { + if (ec == null) + throw new IllegalArgumentException("null element class"); + if (toClass == null) + throw new IllegalArgumentException("null target class"); + + for (Adapter adapter : ec.getItemsByClass(Adapter.class)){ + T t = adapter.adapt(toClass); + if (t != null) + return t; + } + return null; + } + + /** + * Otherwise the same as {@link #adapt(ElementClass, Class)} but will throw + * {@link UnsupportedOperationException} if the adaption fails. + * + * @param + * the adaption target class + * @param e + * the element to adapt + * @param toClass + * the object class to adapt the element to + * @return adapter result or null if adaptation failed + * @throws UnsupportedOperationException + */ + public static T checkedAdapt(ElementClass ec, Class toClass) { + T t = adapt(ec, toClass); + if (t != null) + return t; + throw new UnsupportedOperationException("cannot adapt " + ec + " to " + toClass); + } + + /** + * Looks for a scene graph node from the specified element with the + * specified node key. If the node does not exist, a new node is created + * using the specified node class. + * + * If a previous node exists, its class is verified to match the requested + * node class and returned as such upon success. If the classes do not + * match, an exception is raised since this is most likely a bug that needs + * to be fixed elsewhere. + * + * @param + * @param forElement + * @param withParentNode + * @param withNodeKey + * @param nodeClass + * @return + */ + public static T getOrCreateNode(IElement forElement, ParentNode withParentNode, Key withNodeKey, Class nodeClass) { + return getOrCreateNode(forElement, withParentNode, withNodeKey, null, nodeClass); + } + + /** + * Looks for a scene graph node from the specified element with the + * specified node key. If the node does not exist, a new node is created + * using the specified node class. + * + * If a previous node exists, its class is verified to match the requested + * node class and returned as such upon success. If the classes do not + * match, an exception is raised since this is most likely a bug that needs + * to be fixed elsewhere. + * + * @param + * @param forElement + * @param withParentNode + * @param withNodeKey + * @param nodeId + * @param nodeClass + * @return + */ + public static T getOrCreateNode(IElement forElement, ParentNode withParentNode, Key withNodeKey, String nodeId, Class nodeClass) { + return getOrCreateNode(forElement, withParentNode, withNodeKey, nodeId, nodeClass, null); + } + + /** + * Looks for a scene graph node from the specified element with the + * specified node key. If the node does not exist, a new node is created + * using the specified node class. + * + * If a previous node exists, its class is verified to match the requested + * node class and returned as such upon success. If the classes do not + * match, an exception is raised since this is most likely a bug that needs + * to be fixed elsewhere. + * + * @param + * @param forElement + * @param withParentNode + * @param withNodeKey + * @param nodeId + * @param nodeClass + * @param nodeCreationCallback a callback that is invoked with the node + * instance if a new node was created by this method + * @return + */ + public static T getOrCreateNode(IElement forElement, ParentNode withParentNode, Key withNodeKey, String nodeId, Class nodeClass, Consumer nodeCreationCallback) { + if (!(withNodeKey instanceof SceneGraphNodeKey)) + System.out.println("ElementUtils.getOrCreateNode: WARNING: removing scene graph node with that does not extend SceneGraphNodeKey: " + withNodeKey); + + @SuppressWarnings("unchecked") + T node = (T) forElement.getHint(withNodeKey); + if (node == null) { + node = nodeId != null ? withParentNode.getOrCreateNode(nodeId, nodeClass) : withParentNode.addNode(nodeClass); + forElement.setHint(withNodeKey, node); + if (nodeCreationCallback != null) + nodeCreationCallback.accept(node); + } else { + if (!nodeClass.isAssignableFrom(node.getClass())) { + throw new ElementSceneGraphException("ElementUtils.getOrCreateNode: WARNING: existing node class (" + node.getClass() + ") does not match requested node class (" + nodeClass + ") for element " + forElement + " with parent node " + withParentNode + " and node key " + withNodeKey); + } + // If the previously available node is not a parent of the specified + // node create a new node under the specified parent and set that + // as the node of the specified element. + if (!withParentNode.equals(node.getParent())) { + node = nodeId != null ? withParentNode.getOrCreateNode(nodeId, nodeClass) : withParentNode.addNode(nodeClass); + forElement.setHint(withNodeKey, node); + if (nodeCreationCallback != null) + nodeCreationCallback.accept(node); + } + } + return node; + } + + /** + * @param element + * @param nodeKey + * @return + */ + public static INode removePossibleNode(IElement element, Key nodeKey) { + if (!(nodeKey instanceof SceneGraphNodeKey)) + System.out.println("ElementUtils.removePossibleNode: WARNING: removing scene graph node with that does not extend SceneGraphNodeKey: " + nodeKey); + + INode node = element.getHint(nodeKey); + if (node != null) + node.remove(); + return node; + } + + /** + * + * @param element + * @return + */ + public static Font getTextFont(IElement element) { + TextFont tf = element.getElementClass().getSingleItem(TextFont.class); + return tf.getFont(element); + } + + public static void setTextFont(IElement element, Font font) { + TextFont tf = element.getElementClass().getSingleItem(TextFont.class); + tf.setFont(element, font); + } + + public static void addToCollectionHint(IElement element, Key key, T item) { + Collection collection = element.getHint(key); + if (collection == null) { + collection = new ArrayList(); + element.setHint(key, collection); + } + collection.add(item); + } + + public static void removeFromCollectionHint(IElement element, Key key, T item) { + Collection collection = element.getHint(key); + if (collection != null) { + collection = new ArrayList(); + collection.remove(item); + if (collection.isEmpty()) + element.removeHint(key); + } + } + + public static void setHover(IElement e, boolean hover) + { + Hover h = e.getElementClass().getSingleItem(Hover.class); + h.setHover(e, hover); + } + + public static boolean isHovering(IElement e) + { + Hover h = e.getElementClass().getSingleItem(Hover.class); + return h.isHovering(e); + } + } \ No newline at end of file