/** * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.airavata.xbaya.ui.graph; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Composite; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.Shape; import java.awt.event.MouseEvent; import java.awt.geom.AffineTransform; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; import java.util.Collection; import java.util.LinkedList; import java.util.List; import org.apache.airavata.workflow.model.graph.DataPort; import org.apache.airavata.workflow.model.graph.Node; import org.apache.airavata.workflow.model.graph.Node.NodeObserver; import org.apache.airavata.workflow.model.graph.Node.NodeUpdateType; import org.apache.airavata.workflow.model.graph.Port; import org.apache.airavata.xbaya.XBayaEngine; import org.apache.airavata.xbaya.graph.controller.NodeController; import org.apache.airavata.xbaya.ui.monitor.MonitorEventHandler.NodeState; import org.apache.airavata.xbaya.ui.utils.DrawUtils; public abstract class NodeGUI implements GraphPieceGUI, NodeObserver { /** * BREAK_POINT_BORDER_COLOR */ protected static final Color BREAK_POINT_BORDER_COLOR = new Color(53, 103, 157); /** * The minimum width of the node. */ protected static final int MINIMUM_WIDTH = 100; /** * The minimum height of the node */ protected static final int MINIMUM_HEIGHT = 37; protected static final int TEXT_GAP_X = 5; protected static final int TEXT_GAP_Y = 2; protected static final Color TEXT_COLOR = Color.black; protected static final int PORT_GAP = 13; protected static final int PORT_INITIAL_GAP = 10; protected static final Color EDGE_COLOR = Color.GRAY; protected static final Color DEFAULT_HEAD_COLOR = Color.white; protected static final Color SELECTED_HEAD_COLOR = Color.pink; /** * The default body color. */ public static final Color DEFAULT_BODY_COLOR = new Color(250, 220, 100); protected static final Color DRAGGED_BODY_COLOR = Color.lightGray; protected static final Color BREAK_POINT_COLOR = new Color(174, 197, 221); protected Node node; protected Dimension dimension; protected int headHeight; protected boolean selected = false; protected boolean dragged = false; protected Color headColor; protected Color bodyColor; protected List<Paintable> paintables; /** * @param node */ public NodeGUI(Node node) { this.node = node; this.bodyColor = DEFAULT_BODY_COLOR; this.headColor = DEFAULT_HEAD_COLOR; // The followings are just to make sure that it has some size. this.dimension = new Dimension(MINIMUM_WIDTH, MINIMUM_HEIGHT); this.paintables = new LinkedList<Paintable>(); node.registerObserver(this); } /** * Sets the color of the body. * * @param color * The color */ public void setBodyColor(Color color) { this.bodyColor = color; } /** * @return The color of the body. */ public Color getBodyColor() { return this.bodyColor; } /** * Sets the color of the head. * * @param color * The color to set */ public void setHeadColor(Color color) { this.headColor = color; } /** * @see org.apache.airavata.xbaya.ui.graph.GraphPieceGUI#mouseClicked(java.awt.event.MouseEvent, * org.apache.airavata.xbaya.XBayaEngine) */ public void mouseClicked(MouseEvent event, XBayaEngine engine) { // Nothing by default } /** * @param paintable */ public void addPaintable(Paintable paintable) { this.paintables.add(paintable); } /** * @param paintable */ public void removePaintable(Paintable paintable) { this.paintables.remove(paintable); } /** * @param flag */ protected void setSelectedFlag(boolean flag) { this.selected = flag; if (this.selected) { this.headColor = SELECTED_HEAD_COLOR; } else { this.headColor = DEFAULT_HEAD_COLOR; } } /** * @param flag */ protected void setDraggedFlag(boolean flag) { this.dragged = flag; } /** * Returns the position of the node. * * @return the position of the node */ protected Point getPosition() { return getNode().getPosition(); } /** * Gets the bounding Rectangle of this Node. * * @return A rectangle indicating this component's bounds */ protected Rectangle getBounds() { return new Rectangle(getNode().getPosition(), this.dimension); } /** * Checks if a user's click is to select the node. * * @param point * The location. * @return true if the user's click is to select the node; false otherwise */ protected boolean isIn(Point point) { Rectangle bounds = getBounds(); return bounds.contains(point); } /** * Checks if a user's click is to select the configuration * * @param point * @return true if the user's click is to select the node, false otherwise */ @SuppressWarnings("unused") protected boolean isInConfig(Point point) { return false; } /** * Calculates the width of the node and x-coordinate of the ports. This method has to be called before painting any * parts of the graph. * * @param g */ protected void calculatePositions(Graphics g) { Font oldFont = g.getFont(); g.setFont(new Font(oldFont.getFontName(),Font.BOLD,oldFont.getSize())); FontMetrics fm = g.getFontMetrics(); this.headHeight = fm.getHeight() + TEXT_GAP_Y * 2; int maxNumPort = Math.max(getNode().getOutputPorts().size(), getNode().getInputPorts().size()); this.dimension.height = Math.max(this.headHeight + PORT_INITIAL_GAP + PORT_GAP * maxNumPort, MINIMUM_HEIGHT); this.dimension.width = Math.max(MINIMUM_WIDTH, fm.stringWidth(getNode().getID()) + TEXT_GAP_X * 5); /* Calculates the position of ports */ setPortPositions(); g.setFont(oldFont); } /** * @param g */ protected void paint(Graphics2D g) { Shape componentShape = getComponentShape(); // Draws the body. drawBody(g, componentShape, getComponentBodyColor()); // Draws the head. drawHeader(g, getComponentHeaderShape(), getComponentHeaderText(), getComponentHeaderColor()); // Draw a small circle to indicate the break drawBreaks(g, getNode().getPosition()); // Edge drawEdge(g, componentShape, getComponentEdgeColor()); // Paint all ports drawPorts(g, getAllPorts()); // Paint extras drawExtras(g); } /** Following functions need to be overridden for if the component shape/text/color is different **/ protected final Collection<? extends Port> getAllPorts() { return getNode().getAllPorts(); } protected Node getNode() { return this.node; } protected final Color getComponentEdgeColor() { return EDGE_COLOR; } protected Color getComponentHeaderColor() { return this.headColor; } protected Shape getComponentHeaderShape() { Point position = getNode().getPosition(); RoundRectangle2D headerBoundaryRect = new RoundRectangle2D.Double(position.x, position.y, this.dimension.width, this.headHeight,DrawUtils.ARC_SIZE, DrawUtils.ARC_SIZE); return headerBoundaryRect; } protected final Color getComponentBodyColor() { Color paintBodyColor; if (this.dragged) { paintBodyColor=DRAGGED_BODY_COLOR; } else { paintBodyColor=this.bodyColor; } return paintBodyColor; } protected Shape getComponentShape() { Point position = getNode().getPosition(); RoundRectangle2D completeComponentBoundaryRect = new RoundRectangle2D.Float(position.x, position.y, this.dimension.width, this.dimension.height, DrawUtils.ARC_SIZE, DrawUtils.ARC_SIZE); return completeComponentBoundaryRect; } protected String getComponentHeaderText() { // XXX it's debatable if we should show the ID or the name. // String headerText = this.node.getName(); String headerText = getNode().getID(); return headerText; } /**---------------------------------------------------------------------------------**/ protected void drawBody(Graphics2D g, Shape shape, Color paintBodyColor) { DrawUtils.initializeGraphics2D(g); AffineTransform affineTransform = new AffineTransform(); affineTransform.translate(5,5); Shape shadow = affineTransform.createTransformedShape(shape); Composite oldComposite = g.getComposite(); g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.65F)); g.setColor(Color.GRAY); g.fill(shadow); g.setComposite(oldComposite); DrawUtils.gradientFillShape(g, getEndColor(paintBodyColor), paintBodyColor, shape); } protected void drawBreaks(Graphics2D g, Point position) { if (getNode().isBreak()) { DrawUtils.initializeGraphics2D(g); g.setColor(BREAK_POINT_COLOR); int r = this.headHeight / 4; g.fillOval(position.x + this.dimension.width - 3 * r, position.y + r, 2 * r, 2 * r); g.setColor(BREAK_POINT_BORDER_COLOR); g.drawOval(position.x + this.dimension.width - 3 * r, position.y + r, 2 * r, 2 * r); } } protected void drawExtras(Graphics2D g) { DrawUtils.initializeGraphics2D(g); for (Paintable paintable : this.paintables) { paintable.paint(g, getNode().getPosition()); } } protected void drawPorts(Graphics2D g, Collection<? extends Port> ports) { DrawUtils.initializeGraphics2D(g); for (Port port : ports) { NodeController.getGUI(port).paint(g); } } protected void drawPorts(Graphics2D g, Node node) { drawPorts(g, node.getAllPorts()); } protected void drawEdge(Graphics2D g, Shape completeComponentBoundaryShape, Color edgeColor) { DrawUtils.initializeGraphics2D(g); g.setColor(edgeColor); //uncomment the commented lines to enable a double line edge // g.setStroke(new BasicStroke(4.0f)); g.draw(completeComponentBoundaryShape); // g.setColor(Color.white); // g.setStroke(new BasicStroke(3.0f)); // g.draw(completeComponentBoundaryShape); } protected void drawHeader(Graphics2D g, Shape shape, String headerText, Color headColor, boolean lowerBorderflat) { drawHeader(g, shape, headerText, headColor, shape, lowerBorderflat); } protected void drawHeader(Graphics2D g, Shape shape, String headerText, Color headColor) { drawHeader(g, shape, headerText, headColor, true); } protected void drawHeader(Graphics2D g, Shape shape, String headerText, Color headColor, Shape headerDrawBoundaryShape) { drawHeader(g, shape, headerText, headColor, headerDrawBoundaryShape, true); } protected void drawHeader(Graphics2D g, Shape shape, String headerText, Color headColor, Shape headerDrawBoundaryShape, boolean lowerBorderflat) { DrawUtils.initializeGraphics2D(g); if (lowerBorderflat) { g.setColor(getEndColor(headColor)); Rectangle rect=new Rectangle((int) shape.getBounds().getX()+1, (int) (shape.getBounds() .getY() + shape.getBounds().getHeight() - DrawUtils.ARC_SIZE), (int) shape.getBounds().getWidth(), DrawUtils.ARC_SIZE); DrawUtils.gradientFillShape(g, getEndColor(headColor), headColor, rect); } DrawUtils.gradientFillShape(g, getEndColor(headColor), headColor, headerDrawBoundaryShape); // Text g.setColor(TEXT_COLOR); Font oldFont = g.getFont(); g.setFont(new Font(oldFont.getFontName(),Font.BOLD,oldFont.getSize())); Rectangle2D bounds = g.getFontMetrics().getStringBounds(headerText, g); g.drawString(headerText, (int)(shape.getBounds().getX() + (shape.getBounds().getWidth()-bounds.getWidth())/2), (int)(shape.getBounds().getY() + (shape.getBounds().getHeight()+bounds.getHeight())/2)); g.setFont(oldFont); } /** * Sets up the position of ports */ protected void setPortPositions() { // inputs List<? extends Port> inputPorts = getNode().getInputPorts(); for (int i = 0; i < inputPorts.size(); i++) { Port port = inputPorts.get(i); Point offset = new Point(PortGUI.DATA_PORT_SIZE / 2, this.headHeight + PORT_INITIAL_GAP + PORT_GAP * i); NodeController.getGUI(port).setOffset(offset); } // outputs List<? extends Port> outputPorts = getNode().getOutputPorts(); for (int i = 0; i < outputPorts.size(); i++) { Port port = outputPorts.get(i); // Use getBounds() instead of this.dimension because subclass might // overwrite getBounds() to have different shape. Point offset = new Point(this.getBounds().width - PortGUI.DATA_PORT_SIZE / 2, this.headHeight + PORT_INITIAL_GAP + PORT_GAP * i); NodeController.getGUI(port).setOffset(offset); } // control-in Port controlInPort = getNode().getControlInPort(); if (controlInPort != null) { NodeController.getGUI(controlInPort).setOffset(new Point(0, 0)); } // control-outs for (Port controlOutPort : getNode().getControlOutPorts()) { // By default, all ports will be drawn at the same place. Subclass // should rearrange them if there are more than one control-out // ports. NodeController.getGUI(controlOutPort).setOffset(new Point(getBounds().width, getBounds().height)); } } /** * @param workflowName * @param state */ public void setToken(String workflowName, NodeState state) { List<DataPort> inputPorts = getNode().getInputPorts(); switch (state) { case EXECUTING: for (DataPort dataPort : inputPorts) { NodeController.getGUI(((DataPort) dataPort.getFromPort())).removeToken(workflowName); NodeController.getGUI(dataPort).addToken(workflowName); } break; case FINISHED: for (DataPort dataPort : inputPorts) { NodeController.getGUI(dataPort).removeToken(workflowName); } List<DataPort> outputPorts = getNode().getOutputPorts(); for (DataPort dataPort : outputPorts) { NodeController.getGUI(dataPort).addToken(workflowName); } break; case FAILED: break; } } /** * */ public void resetTokens() { List<DataPort> inputPorts = getNode().getInputPorts(); for (DataPort dataPort : inputPorts) { NodeController.getGUI(dataPort).reset(); } List<DataPort> outputPorts = getNode().getOutputPorts(); for (DataPort dataPort : outputPorts) { NodeController.getGUI(dataPort).reset(); } } protected Color getEndColor(Color bodyColor){ return Color.white; } @Override public void nodeUpdated(NodeUpdateType type) { switch(type){ case STATE_CHANGED: updateNodeColor(); break; default: break; } } private void updateNodeColor() { switch(node.getState()){ case WAITING: setBodyColor(NodeState.DEFAULT.color); break; case EXECUTING: setBodyColor(NodeState.EXECUTING.color); break; case FAILED: setBodyColor(NodeState.FAILED.color); break; case FINISHED: setBodyColor(NodeState.FINISHED.color); break; } } }