/** * Copyright (c) 2011-2014, OpenIoT * * This file is part of OpenIoT. * * OpenIoT is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, version 3 of the License. * * OpenIoT 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with OpenIoT. If not, see <http://www.gnu.org/licenses/>. * * Contact: OpenIoT mailto: info@openiot.eu */ package org.openiot.ui.request.definition.web.jsf.components.graph; import java.io.IOException; import java.util.ResourceBundle; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.context.ResponseWriter; import org.openiot.ui.request.commons.interfaces.GraphModel; import org.openiot.ui.request.commons.models.GraphNodePosition; import org.openiot.ui.request.commons.nodes.enums.EndpointType; import org.openiot.ui.request.commons.nodes.interfaces.GraphNode; import org.openiot.ui.request.commons.nodes.interfaces.GraphNodeConnection; import org.openiot.ui.request.commons.nodes.interfaces.GraphNodeEndpoint; import org.openiot.ui.request.definition.web.util.FaceletLocalization; import org.primefaces.renderkit.CoreRenderer; /** * @author Achilleas Anagnostopoulos (aanag) email: aanag@sensap.eu */ public class NodeGraphRenderer extends CoreRenderer { public static final int MIN_NODE_WIDTH = 150; public static final int MIN_NODE_HEIGHT = 60; public static final int PIXELS_PER_HORIZONTAL_ENDPOINT = 20; public static final int PIXELS_PER_VERTICAL_ENDPOINT = 40; @Override public void decode(FacesContext context, UIComponent component) { decodeBehaviors(context, component); } @Override public void encodeEnd(final FacesContext fc, final UIComponent component) throws IOException { encodeMarkup(fc, component); encodeScript(fc, component); } protected void encodeMarkup(final FacesContext fc, final UIComponent component) throws IOException { ResponseWriter writer = fc.getResponseWriter(); NodeGraph nodeGraph = (NodeGraph) component; String clientId = nodeGraph.getClientId(fc); writer.startElement("div", null); writer.writeAttribute("id", clientId, null); writer.writeAttribute("class", "graph-node-wrapper " + (nodeGraph.getStyleClass() != null ? nodeGraph.getStyleClass() : ""), null); { // Render model markup encodeGraphNodeMarkup(fc, component); } writer.endElement("div"); } protected void encodeScript(final FacesContext fc, final UIComponent component) throws IOException { encodeGraphNodeScript(fc, component); } @Override public boolean getRendersChildren() { return true; } @Override public void encodeChildren(final FacesContext fc, final UIComponent component) throws IOException { // nothing to do } //-------------------------------------------------------------------------- // Implementation //-------------------------------------------------------------------------- private void encodeGraphNodeMarkup(final FacesContext fc, final UIComponent component) throws IOException { NodeGraph nodeGraph = (NodeGraph) component; GraphModel model = nodeGraph.getModel(); if (nodeGraph.getModel() == null) { return; } ResponseWriter writer = fc.getResponseWriter(); ResourceBundle messages = nodeGraph.getTranslations(); // Render a node for each graphNode for (GraphNode node : model.getNodes()) { GraphNodePosition position = model.lookupGraphNodePosition(node.getUID()); // Count endpoints on each side int topEndpoints = 0; int rightEndpoints = 0; int bottomEndpoints = 0; int leftEndpoints = 0; for (GraphNodeEndpoint endpoint : node.getEndpointDefinitions()) { if( !endpoint.isVisible()){ continue; } switch (endpoint.getAnchor()) { case Top: topEndpoints++; break; case Right: rightEndpoints++; break; case Bottom: bottomEndpoints++; break; case Left: leftEndpoints++; break; } } // Calculate node width and height and a starting position for each node type so they are aligned centered int nodeWidth = Math.max(MIN_NODE_WIDTH, Math.max(topEndpoints, bottomEndpoints) * PIXELS_PER_HORIZONTAL_ENDPOINT); int nodeHeight = Math.max(MIN_NODE_HEIGHT, Math.max(leftEndpoints, rightEndpoints) * PIXELS_PER_VERTICAL_ENDPOINT); writer.startElement("div", null); writer.writeAttribute("id", node.getUID(), null); writer.writeAttribute("style", "position:absolute;left:" + position.getX() + "px;top:" + position.getY() + "px;width:" + nodeWidth + "px;height:" + nodeHeight + "px;", null); writer.writeAttribute("class", "graph-node " + node.getType() + (model.getSelectedNode() != null && model.getSelectedNode().getUID().equals(node.getUID()) ? " graph-node-selected" : ""), null); { // Render label String label = node.getLabel(); if (messages != null) { label = FaceletLocalization.lookupLabelTranslation(messages, node.getLabel(), "UI_NODE_" + node.getLabel()); } writer.startElement("div", null); writer.writeAttribute("class", "graph-node-label " + node.getType(), null); writer.write(label == null ? "-" : label); writer.endElement("div"); } writer.endElement("div"); } } private void encodeGraphNodeScript(final FacesContext fc, final UIComponent component) throws IOException { NodeGraph nodeGraph = (NodeGraph) component; GraphModel model = nodeGraph.getModel(); if (nodeGraph.getModel() == null) { return; } String clientId = nodeGraph.getClientId(fc); ResponseWriter writer = fc.getResponseWriter(); ResourceBundle messages = nodeGraph.getTranslations(); startScript(writer, clientId); writer.write("$(function() {"); { writer.write("Sensap.cw('NodeGraph','" + nodeGraph.resolveWidgetVar() + "',{"); writer.write("id:'" + clientId + "',"); if (model.getSelectedNode() != null) { writer.write("selectedNodeId: '" + model.getSelectedNode().getUID() + "',"); } // Encode all endpoints writer.write("endpoints:["); for (GraphNode node : model.getNodes()) { // Count endpoints on each side int topEndpoints = 0; int rightEndpoints = 0; int bottomEndpoints = 0; int leftEndpoints = 0; for (GraphNodeEndpoint endpoint : node.getEndpointDefinitions()) { if( !endpoint.isVisible()){ continue; } switch (endpoint.getAnchor()) { case Top: topEndpoints++; break; case Right: rightEndpoints++; break; case Bottom: bottomEndpoints++; break; case Left: leftEndpoints++; break; } } // Calculate node width and height and a starting position for each node type so they are aligned centered int nodeWidth = Math.max(MIN_NODE_WIDTH, Math.max(topEndpoints, bottomEndpoints) * PIXELS_PER_HORIZONTAL_ENDPOINT); int nodeHeight = Math.max(MIN_NODE_HEIGHT, Math.max(leftEndpoints, rightEndpoints) * PIXELS_PER_VERTICAL_ENDPOINT); double stepX = (double) PIXELS_PER_HORIZONTAL_ENDPOINT / nodeWidth; double stepY = (double) PIXELS_PER_VERTICAL_ENDPOINT / nodeHeight; double topPosition = 1.0 / (2.0 * (double) topEndpoints); double rightPosition = 1.0 / (2.0 * (double) rightEndpoints); double bottomPosition = 1.0 / (2.0 * (double) bottomEndpoints); double leftPosition = 1.0 / (2.0 * (double) leftEndpoints); for (GraphNodeEndpoint endpoint : node.getEndpointDefinitions()) { // Ignore invisible endpoints if( !endpoint.isVisible()){ continue; } double labelX = 0; double labelY = 0; double anchorXPos = 0; double anchorYPos = 0; double anchorDX = 0; double anchorDY = 0; String label = endpoint.getLabel(); if (messages != null) { label = FaceletLocalization.lookupLabelTranslation(messages, endpoint.getLabel(), "UI_NODE_ENDPOINT_" + node.getClass().getSimpleName() + "_" + endpoint.getLabel(), "UI_NODE_ENDPOINT_" + endpoint.getLabel()); } switch (endpoint.getAnchor()) { case Bottom: anchorXPos = bottomPosition; anchorYPos = 1.0; bottomPosition += stepX; anchorDX = 0; anchorDY = 1; labelX = 0.5; labelY = -0.5; break; case Left: anchorXPos = 0; anchorYPos = leftPosition; leftPosition += stepY; anchorDX = -1; anchorDY = 0; //labelX = -0.18 * (double) label.length(); labelX = 0; labelY = -0.5; break; case Right: anchorXPos = 1.0; anchorYPos = rightPosition; rightPosition += stepY; anchorDX = 1; anchorDY = 0; //labelX = 0.21 * (double) label.length(); labelX = 1.0; labelY = -0.5; break; case Top: anchorXPos = topPosition; anchorYPos = 0; topPosition += stepX; anchorDX = 0; anchorDY = -1; labelX = 0.5; labelY = 1.2; break; } writer.write("{"); { writer.write("nodeId : '" + node.getUID() + "',"); writer.write("id : '" + endpoint.getUID() + "',"); writer.write("endpoint : '" + endpoint.getConnectorType().toString() + "',"); writer.write("connector:[ 'Flowchart', { stub:[40, 60], gap:10, cornerRadius:5 } ],"); writer.write("anchor : [" + anchorXPos + ", " + anchorYPos + ", " + anchorDX + ", " + anchorDY + "],"); writer.write("scope : '" + endpoint.getScope() + "',"); writer.write("isSource : " + (EndpointType.Input.equals(endpoint.getType()) ? "false" : "true") + ","); writer.write("isTarget : " + (EndpointType.Output.equals(endpoint.getType()) ? "false" : "true") + ","); if (EndpointType.Input.equals(endpoint.getType())) { writer.write("dropOptions:{ hoverClass:'hover', activeClass:'active' },"); } writer.write("maxConnections : " + endpoint.getMaxConnections() + ","); writer.write("overlays: ["); { writer.write("[ 'Label', { location:[" + labelX + ", " + labelY + "], cssClass : 'graph-endpoint-label', label : '" + label + "' } ]"); } writer.write("]"); } writer.write("},"); } } writer.write("],"); // Encode all connections writer.write("connections : ["); for (GraphNodeConnection connection : model.getConnections()) { writer.write("{"); writer.write("source : '" + connection.getSourceEndpoint().getUID() + "',"); writer.write("target : '" + connection.getDestinationEndpoint().getUID() + "',"); writer.write("parameters : {connectionId : '" + connection.getUID() + "'}"); writer.write("},"); } writer.write("]"); // Encode client behaviors encodeClientBehaviors(fc, nodeGraph); writer.write("},true);"); } writer.write("});"); endScript(writer); } }