/* * Copyright (C) 2014 by Array Systems Computing Inc. http://www.array.ca * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by the Free * Software Foundation; either version 3 of the License, or (at your option) * any later version. * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, see http://www.gnu.org/licenses/ */ package org.esa.snap.graphbuilder.rcp.dialogs.support; import com.bc.ceres.binding.ConversionException; import com.bc.ceres.binding.Converter; import com.bc.ceres.binding.Property; import com.bc.ceres.binding.PropertyContainer; import com.bc.ceres.binding.PropertyDescriptor; import com.bc.ceres.binding.dom.DomConverter; import com.bc.ceres.binding.dom.DomElement; import com.bc.ceres.binding.dom.XppDomElement; import com.thoughtworks.xstream.io.xml.xppdom.XppDom; import org.apache.commons.math3.util.FastMath; import org.esa.snap.core.datamodel.Product; import org.esa.snap.core.gpf.GPF; import org.esa.snap.core.gpf.OperatorSpi; import org.esa.snap.core.gpf.annotations.ParameterDescriptorFactory; import org.esa.snap.core.gpf.graph.GraphException; import org.esa.snap.core.gpf.graph.Node; import org.esa.snap.core.gpf.graph.NodeSource; import org.esa.snap.core.util.SystemUtils; import org.esa.snap.graphbuilder.gpf.ui.OperatorUI; import org.esa.snap.graphbuilder.gpf.ui.UIValidation; import java.awt.*; import java.awt.geom.Rectangle2D; import java.util.HashMap; import java.util.Map; /** * Represents a node of the graph for the GraphBuilder * Stores, saves and loads the display position for the node * User: lveci * Date: Jan 17, 2008 */ public class GraphNode { private final Node node; private final Map<String, Object> parameterMap = new HashMap<>(10); private OperatorUI operatorUI = null; private int nodeWidth = 60; private int nodeHeight = 25; private int halfNodeHeight = 0; private int halfNodeWidth = 0; private static final int hotSpotSize = 10; private static final int halfHotSpotSize = hotSpotSize / 2; private int hotSpotOffset = 0; private Point displayPosition = new Point(0, 0); private XppDom displayParameters; private static Color shadowColor = new Color(0, 0, 0, 64); public GraphNode(final Node n) throws IllegalArgumentException { node = n; displayParameters = new XppDom("node"); displayParameters.setAttribute("id", node.getId()); initParameters(); } public void setOperatorUI(final OperatorUI ui) { operatorUI = ui; } public OperatorUI GetOperatorUI() { return operatorUI; } private void initParameters() throws IllegalArgumentException { final OperatorSpi operatorSpi = GPF.getDefaultInstance().getOperatorSpiRegistry().getOperatorSpi(node.getOperatorName()); if (operatorSpi == null) return; final ParameterDescriptorFactory parameterDescriptorFactory = new ParameterDescriptorFactory(); final PropertyContainer valueContainer = PropertyContainer.createMapBacked(parameterMap, operatorSpi.getOperatorClass(), parameterDescriptorFactory); final DomElement config = node.getConfiguration(); final int count = config.getChildCount(); for (int i = 0; i < count; ++i) { final DomElement child = config.getChild(i); final String name = child.getName(); final String value = child.getValue(); if (name == null || value == null) continue; try { if (child.getChildCount() == 0) { final Converter converter = getConverter(valueContainer, name); if (converter == null) { final String msg = "Graph parameter " + name + " not found for Operator " + operatorSpi.getOperatorAlias(); //throw new IllegalArgumentException(msg); SystemUtils.LOG.warning(msg); } else { parameterMap.put(name, converter.parse(value)); } } else { final DomConverter domConverter = getDomConverter(valueContainer, name); if(domConverter != null) { try { final Object obj = domConverter.convertDomToValue(child, null); parameterMap.put(name, obj); } catch (Exception e) { SystemUtils.LOG.warning(e.getMessage()); } } else { final Converter converter = getConverter(valueContainer, name); final Object[] objArray = new Object[child.getChildCount()]; int c = 0; for (DomElement ch : child.getChildren()) { final String v = ch.getValue(); if (converter != null) { objArray[c++] = converter.parse(v); } else { objArray[c++] = v; } } parameterMap.put(name, objArray); } } } catch (ConversionException e) { throw new IllegalArgumentException(name); } } } private static Converter getConverter(final PropertyContainer valueContainer, final String name) { final Property[] properties = valueContainer.getProperties(); for (Property p : properties) { final PropertyDescriptor descriptor = p.getDescriptor(); if (descriptor != null && (descriptor.getName().equals(name) || (descriptor.getAlias() != null && descriptor.getAlias().equals(name)))) { return descriptor.getConverter(); } } return null; } private static DomConverter getDomConverter(final PropertyContainer valueContainer, final String name) { final Property[] properties = valueContainer.getProperties(); for (Property p : properties) { final PropertyDescriptor descriptor = p.getDescriptor(); if (descriptor != null && (descriptor.getName().equals(name) || (descriptor.getAlias() != null && descriptor.getAlias().equals(name)))) { return descriptor.getDomConverter(); } } return null; } void setDisplayParameters(final XppDom presentationXML) { for (XppDom params : presentationXML.getChildren()) { final String id = params.getAttribute("id"); if (id != null && id.equals(node.getId())) { displayParameters = params; final XppDom dpElem = displayParameters.getChild("displayPosition"); if (dpElem != null) { displayPosition.x = (int) Float.parseFloat(dpElem.getAttribute("x")); displayPosition.y = (int) Float.parseFloat(dpElem.getAttribute("y")); } return; } } } void updateParameters() throws GraphException { if (operatorUI != null) { final XppDomElement config = new XppDomElement("parameters"); updateParameterMap(config); node.setConfiguration(config); } } void AssignParameters(final XppDom presentationXML) throws GraphException { updateParameters(); AssignDisplayParameters(presentationXML); } void AssignDisplayParameters(final XppDom presentationXML) { XppDom nodeElem = null; for (XppDom elem : presentationXML.getChildren()) { final String id = elem.getAttribute("id"); if (id != null && id.equals(node.getId())) { nodeElem = elem; break; } } if (nodeElem == null) { presentationXML.addChild(displayParameters); } XppDom dpElem = displayParameters.getChild("displayPosition"); if (dpElem == null) { dpElem = new XppDom("displayPosition"); displayParameters.addChild(dpElem); } dpElem.setAttribute("y", String.valueOf(displayPosition.getY())); dpElem.setAttribute("x", String.valueOf(displayPosition.getX())); } /** * Gets the display position of a node * * @return Point The position of the node */ public Point getPos() { return displayPosition; } /** * Sets the display position of a node and writes it to the xml * * @param p The position of the node */ public void setPos(Point p) { displayPosition = p; } public Node getNode() { return node; } public int getWidth() { return nodeWidth; } public int getHeight() { return nodeHeight; } public static int getHotSpotSize() { return hotSpotSize; } int getHalfNodeWidth() { return halfNodeWidth; } public int getHalfNodeHeight() { return halfNodeHeight; } private void setSize(final int width, final int height) { nodeWidth = width; nodeHeight = height; halfNodeHeight = nodeHeight / 2; halfNodeWidth = nodeWidth / 2; hotSpotOffset = halfNodeHeight - halfHotSpotSize; } public int getHotSpotOffset() { return hotSpotOffset; } /** * Gets the uniqe node identifier. * * @return the identifier */ public String getID() { return node.getId(); } /** * Gets the name of the operator. * * @return the name of the operator. */ public String getOperatorName() { return node.getOperatorName(); } public Map<String, Object> getParameterMap() { return parameterMap; } public void connectOperatorSource(final String id) { // check if already a source for this node disconnectOperatorSources(id); //check if connected sources exists String cntStr = ""; if(node.getSources().length > 0) { cntStr = "."+node.getSources().length; } final NodeSource ns = new NodeSource("sourceProduct"+cntStr, id); node.addSource(ns); } public void disconnectOperatorSources(final String id) { for (NodeSource ns : node.getSources()) { if (ns.getSourceNodeId().equals(id)) { node.removeSource(ns); } } } public void disconnectAllSources() { final NodeSource[] sources = node.getSources(); for (NodeSource source : sources) { node.removeSource(source); } } public boolean isNodeSource(final GraphNode source) { for (NodeSource ns : node.getSources()) { if (ns.getSourceNodeId().equals(source.getID())) { return true; } } return false; } boolean HasSources() { return node.getSources().length > 0; } public UIValidation validateParameterMap() { if (operatorUI != null) return operatorUI.validateParameters(); return new UIValidation(UIValidation.State.OK, ""); } void setSourceProducts(final Product[] products) { if (operatorUI != null) { operatorUI.setSourceProducts(products); } } void updateParameterMap(final XppDomElement parentElement) throws GraphException { //if(operatorUI.hasSourceProducts()) operatorUI.updateParameters(); operatorUI.convertToDOM(parentElement); } /** * Draw a GraphNode as a rectangle with a name * * @param g The Java2D Graphics * @param col The color to draw */ public void drawNode(final Graphics2D g, final Color col) { final int x = displayPosition.x; final int y = displayPosition.y; g.setFont(g.getFont().deriveFont(Font.BOLD, 11)); final FontMetrics metrics = g.getFontMetrics(); final String name = node.getId(); final Rectangle2D rect = metrics.getStringBounds(name, g); final int stringWidth = (int) rect.getWidth(); setSize(Math.max(stringWidth, 50) + 10, 25); int step = 4; int alpha = 96; for (int i = 0; i < step; ++i) { g.setColor(new Color(0, 0, 0, alpha - (32 * i))); g.drawLine(x + i + 1, y + nodeHeight + i, x + nodeWidth + i - 1, y + nodeHeight + i); g.drawLine(x + nodeWidth + i, y + i, x + nodeWidth + i, y + nodeHeight + i); } Shape clipShape = new Rectangle(x, y, nodeWidth, nodeHeight); g.setComposite(AlphaComposite.SrcAtop); g.setPaint(new GradientPaint(x, y, col, x + nodeWidth, y + nodeHeight, col.darker())); g.fill(clipShape); g.setColor(Color.blue); g.draw3DRect(x, y, nodeWidth - 1, nodeHeight - 1, true); g.setColor(Color.BLACK); g.drawString(name, x + (nodeWidth - stringWidth) / 2, y + 15); } /** * Draws the hotspot where the user can join the node to a source node * * @param g The Java2D Graphics * @param col The color to draw */ public void drawHeadHotspot(final Graphics g, final Color col) { final Point p = displayPosition; g.setColor(col); g.drawOval(p.x - halfHotSpotSize, p.y + hotSpotOffset, hotSpotSize, hotSpotSize); } /** * Draws the hotspot where the user can join the node to a source node * * @param g The Java2D Graphics * @param col The color to draw */ public void drawTailHotspot(final Graphics g, final Color col) { final Point p = displayPosition; g.setColor(col); final int x = p.x + nodeWidth; final int y = p.y + halfNodeHeight; final int[] xpoints = {x, x + hotSpotOffset, x, x}; final int[] ypoints = {y - halfHotSpotSize, y, y + halfHotSpotSize, y - halfHotSpotSize}; g.fillPolygon(xpoints, ypoints, xpoints.length); } /** * Draw a line between source and target nodes * * @param g The Java2D Graphics * @param src the source GraphNode */ public void drawConnectionLine(final Graphics2D g, final GraphNode src) { final Point nodePos = displayPosition; final Point srcPos = src.displayPosition; final int nodeEndX = nodePos.x + nodeWidth; final int nodeMidY = nodePos.y + halfNodeHeight; final int srcEndX = srcPos.x + src.getWidth(); final int srcMidY = srcPos.y + src.getHalfNodeHeight(); if (srcEndX <= nodePos.x) { if (srcPos.y > nodePos.y + nodeHeight) { // to UR drawArrow(g, nodePos.x + halfNodeWidth, nodePos.y + nodeHeight, srcEndX, srcMidY); } else if (srcPos.y + src.getHeight() < nodePos.y) { // to DR drawArrow(g, nodePos.x + halfNodeWidth, nodePos.y, srcEndX, srcMidY); } else { // to R drawArrow(g, nodePos.x, nodeMidY, srcEndX, srcMidY); } } else if (srcPos.x >= nodeEndX) { if (srcPos.y > nodePos.y + nodeHeight) { // to UL drawArrow(g, nodePos.x + halfNodeWidth, nodePos.y + nodeHeight, srcPos.x, srcPos.y + halfNodeHeight); } else if (srcPos.y + src.getHeight() < nodePos.y) { // to DL drawArrow(g, nodePos.x + halfNodeWidth, nodePos.y, srcPos.x, srcPos.y + halfNodeHeight); } else { // to L drawArrow(g, nodeEndX, nodeMidY, srcPos.x, srcPos.y + halfNodeHeight); } } else { if (srcPos.y > nodePos.y + nodeHeight) { // U drawArrow(g, nodePos.x + halfNodeWidth, nodePos.y + nodeHeight, srcPos.x + src.getHalfNodeWidth(), srcPos.y); } else { // D drawArrow(g, nodePos.x + halfNodeWidth, nodePos.y, srcPos.x + src.getHalfNodeWidth(), srcPos.y + src.getHeight()); } } } /** * Draws an arrow head at the correct angle * * @param g The Java2D Graphics * @param tailX position X on target node * @param tailY position Y on target node * @param headX position X on source node * @param headY position Y on source node */ private static void drawArrow(final Graphics2D g, final int tailX, final int tailY, final int headX, final int headY) { final double t1 = Math.abs(headY - tailY); final double t2 = Math.abs(headX - tailX); double theta = Math.atan(t1 / t2); if (headX >= tailX) { if (headY > tailY) theta = Math.PI + theta; else theta = -(Math.PI + theta); } else if (headX < tailX && headY > tailY) theta = 2 * Math.PI - theta; final double cosTheta = FastMath.cos(theta); final double sinTheta = FastMath.sin(theta); final Point p2 = new Point(-8, -3); final Point p3 = new Point(-8, +3); int x = (int) Math.round((cosTheta * p2.x) - (sinTheta * p2.y)); p2.y = (int) Math.round((sinTheta * p2.x) + (cosTheta * p2.y)); p2.x = x; x = (int) Math.round((cosTheta * p3.x) - (sinTheta * p3.y)); p3.y = (int) Math.round((sinTheta * p3.x) + (cosTheta * p3.y)); p3.x = x; p2.translate(tailX, tailY); p3.translate(tailX, tailY); Stroke oldStroke = g.getStroke(); g.setStroke(new BasicStroke(2)); g.drawLine(tailX, tailY, headX, headY); g.drawLine(tailX, tailY, p2.x, p2.y); g.drawLine(p3.x, p3.y, tailX, tailY); g.setStroke(oldStroke); } }