/* * Copyright 2008 Tom Huybrechts and hudson.dev.java.net * * Licensed 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 hudson.jbpm.rendering; import hudson.jbpm.model.gpd.BendPoint; import hudson.jbpm.model.gpd.Edge; import hudson.jbpm.model.gpd.GPD; import hudson.jbpm.model.gpd.NodeState; import java.awt.BasicStroke; import java.awt.Color; import java.awt.Font; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Polygon; import java.awt.RenderingHints; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.List; import java.util.Map; import javax.swing.JComponent; import org.dom4j.DocumentException; import org.jbpm.JbpmConfiguration; import org.jbpm.graph.def.Node; import org.jbpm.graph.def.ProcessDefinition; import org.jbpm.graph.def.Transition; import org.jbpm.graph.exe.ProcessInstance; import org.jbpm.graph.exe.Token; import org.jbpm.graph.log.TokenCreateLog; import org.jbpm.graph.log.TransitionLog; import org.jbpm.logging.log.ProcessLog; import org.jbpm.taskmgmt.log.TaskCreateLog; public final class ProcessInstanceRenderer extends JComponent { private static Color LINE_COLOR = new Color(180, 180, 180); private static final Color TEXT_COLOR = Color.BLACK; private static final Font FONT = new Font("Tahoma", Font.BOLD, 12); private static final Color NODE_COLOR_1 = new Color(220, 220, 235); private static final Color NODE_COLOR_2 = Color.WHITE; private final ProcessDefinition def; // private final ProcessLayout layout; private final GPD gpd; public ProcessInstanceRenderer(ProcessInstance processInstance, GPD gpd) throws DocumentException { def = processInstance.getProcessDefinition(); this.gpd = gpd; Map<Token, List<ProcessLog>> logs = JbpmConfiguration.getInstance() .getCurrentJbpmContext().getLoggingSession() .findLogsByProcessInstance(processInstance.getId()); handleLog(logs, processInstance.getRootToken()); setBackground(Color.WHITE); setOpaque(true); setSize(gpd.getWidth(), gpd.getHeight()); } private void handleLog(Map<Token, List<ProcessLog>> logs, Token token) { List<ProcessLog> list = logs.get(token); for (ProcessLog log : list) { // System.out.println(log); if (log instanceof TransitionLog) { String source = ((TransitionLog) log).getSourceNode().getName(); String target = ((TransitionLog) log).getDestinationNode() .getName(); gpd.getNode(source).setState(NodeState.Completed); gpd.getNode(target).setState(NodeState.Entered); } else if (log instanceof TokenCreateLog) { Token subToken = ((TokenCreateLog) log).getChild(); handleLog(logs, subToken); } else if (log instanceof TaskCreateLog) { gpd.getNode( ((TaskCreateLog) log).getTaskInstance().getTask() .getTaskNode().getName()).setState( NodeState.TaskCreated); } } } @Override public void paint(Graphics g) { Graphics2D g2 = (Graphics2D) g; g2.setBackground(Color.WHITE); g2.clearRect(0, 0, getWidth(), getHeight()); Map<String, Node> tasks = def.getNodesMap(); for (Map.Entry<String, Node> entry : tasks.entrySet()) { List<Transition> transitions = entry.getValue() .getLeavingTransitions(); for (int i = 0; transitions != null && i < transitions.size(); i++) { Transition transition = transitions.get(i); String from = transition.getFrom().getName(); String to = transition.getTo().getName(); Edge edge = gpd.getNode(from).getEdge(i); paintLine(g2, gpd.getNode(from).asRectangle(), gpd.getNode(to) .asRectangle(), edge, transition.getName()); } } for (Map.Entry<String, Node> entry : tasks.entrySet()) { Node task = tasks.get(entry.getKey()); Rectangle2D.Double rect = gpd.getNode(task.getName()).asRectangle(); paintTask(g2, task, rect); } } public static void paintLine(Graphics2D g2, Rectangle2D.Double from, Rectangle2D.Double to, Edge edge, String label) { List<BendPoint> bendPoints = edge.getBendPoints(); Point2D.Double fromRectCenter = new Point2D.Double(from.getCenterX(), from.getCenterY()); Point2D.Double toRectCenter = new Point2D.Double(to.getCenterX(), to .getCenterY()); Point2D.Double startPoint = fromRectCenter; Point2D.Double endPoint; for (int i = 1; i < bendPoints.size() + 2; i++) { endPoint = getPoint(i, fromRectCenter, bendPoints, toRectCenter); Line2D.Double line = new Line2D.Double(startPoint, endPoint); Point2D.Double intersection = new Point2D.Double(); if (GraphicsUtil.getLineRectangleIntersection(from, line, intersection)) { line.x1 = intersection.x; line.y1 = intersection.y; } if (GraphicsUtil.getLineRectangleIntersection(to, line, intersection)) { line.x2 = intersection.x; line.y2 = intersection.y; } drawArrow(g2, line, 1, endPoint == toRectCenter); startPoint = endPoint; } if (label != null) { Point2D.Double labelPoint = new Point2D.Double(); int count = bendPoints.size() + 2; if (count % 2 == 0) { Point2D.Double a = getPoint(count / 2 - 1, fromRectCenter, bendPoints, toRectCenter); Point2D.Double b = getPoint(count / 2, fromRectCenter, bendPoints, toRectCenter); labelPoint.x = (a.x + b.x) / 2 + edge.getLabel().getX(); labelPoint.y = (a.y + b.y) / 2 + edge.getLabel().getY(); } else { Point2D.Double a = getPoint(count / 2, fromRectCenter, bendPoints, toRectCenter); labelPoint.x = a.x + edge.getLabel().getX(); labelPoint.y = a.y + edge.getLabel().getY(); } g2.setColor(Color.BLACK); int textHeight = g2.getFontMetrics().getAscent(); g2.drawString(label, (int) labelPoint.x, (int) labelPoint.y + textHeight); } } private static Point2D.Double getPoint(int i, Point2D.Double start, List<BendPoint> bendPoints, Point2D.Double end) { if (i == 0) { return start; } if (i == bendPoints.size() + 1) { return end; } BendPoint bp = bendPoints.get(i - 1); return new Point2D.Double(start.x + bp.getW1(), start.y + bp.getH1()); } public void paintTask(Graphics2D g2, Node node, Rectangle2D.Double rect) { g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Color nodeColor = getNodeColor(node); g2.setPaint(new GradientPaint(new Point2D.Double(rect.x, rect.y), nodeColor, new Point2D.Double(rect.x, rect.y + rect.height), NODE_COLOR_2)); g2.fill(rect); g2.setPaint(LINE_COLOR); g2.draw(rect); double imageY = rect.y + rect.height / 2 - 16 / 2; Image image = GraphicsUtil.getImage(node); if (image != null) { int w = image.getWidth(null); int h = image.getHeight(null); g2.drawImage(image, (int) rect.x + 8, (int) imageY, (int) rect.x + 8 + w, (int) imageY + h, 0, 0, w, h, this); } int textWidth = g2.getFontMetrics().stringWidth(node.getName()); int textHeight = g2.getFontMetrics().getAscent(); g2.setColor(TEXT_COLOR); g2.setFont(FONT); g2.drawString(node.getName(), (int) (rect.x + 12 + (rect.width - textWidth) / 2), (int) (rect.y + (rect.height + textHeight) / 2)); } public static void drawArrow(Graphics2D g2d, Line2D.Double line, float stroke, boolean arrow) { int xCenter = (int) line.getX1(); int yCenter = (int) line.getY1(); double x = line.getX2(); double y = line.getY2(); double aDir = Math.atan2(xCenter - x, yCenter - y); int i1 = 12 + (int) (stroke * 2); int i2 = 6 + (int) stroke; // make the arrow head the same size Line2D.Double base = new Line2D.Double(x + xCor(i1, aDir + .5), y + yCor(i1, aDir + .5), x + xCor(i1, aDir - .5), y + yCor(i1, aDir - .5)); Point2D.Double intersect = new Point2D.Double(); GraphicsUtil.getLineLineIntersection(line, base, intersect); g2d.setPaint(LINE_COLOR); if (arrow) { g2d.draw(new Line2D.Double(xCenter, yCenter, intersect.x, intersect.y)); g2d.setStroke(new BasicStroke(1f)); // make the arrow head solid // even if // dash pattern has been specified Polygon tmpPoly = new Polygon(); // regardless of the length tmpPoly.addPoint((int) x, (int) y); // arrow tip tmpPoly.addPoint((int) x + xCor(i1, aDir + .5), (int) y + yCor(i1, aDir + .5)); // tmpPoly.addPoint(x + xCor(i2, aDir), y + yCor(i2, aDir)); tmpPoly.addPoint((int) x + xCor(i1, aDir - .5), (int) y + yCor(i1, aDir - .5)); tmpPoly.addPoint((int) x, (int) y); // arrow tip g2d.drawPolygon(tmpPoly); } else { g2d.draw(new Line2D.Double(xCenter, yCenter, x, y)); } // g2d.setPaint(Color.WHITE); } private static int yCor(int len, double dir) { return (int) (len * Math.cos(dir)); } private static int xCor(int len, double dir) { return (int) (len * Math.sin(dir)); } private Color getNodeColor(Node node) { NodeState state = gpd.getNode(node.getName()).getState(); if (state == NodeState.Entered) { return new Color(150, 150, 230); } else if (state == NodeState.Started) { return new Color(150, 255, 150); } else if (state == NodeState.Completed) { return new Color(50, 235, 50); } else if (state == NodeState.TaskCreated) { return new Color(235, 150, 150); } else { return NODE_COLOR_1; } } }