/******************************************************************************* * 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); } }