/* ======================================================================== * PlantUML : a free UML diagram generator * ======================================================================== * * (C) Copyright 2009-2017, Arnaud Roques * * Project Info: http://plantuml.com * * If you like this project or if you find it useful, you can support us at: * * http://plantuml.com/patreon (only 1$ per month!) * http://plantuml.com/paypal * * This file is part of PlantUML. * * PlantUML 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. * * PlantUML 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 library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, * USA. * * * Original Author: Arnaud Roques * */ package net.sourceforge.plantuml.timingdiagram; import java.awt.geom.Dimension2D; import java.awt.geom.Rectangle2D; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import net.sourceforge.plantuml.AnnotatedWorker; import net.sourceforge.plantuml.Dimension2DDouble; import net.sourceforge.plantuml.FileFormatOption; import net.sourceforge.plantuml.ISkinParam; import net.sourceforge.plantuml.UmlDiagram; import net.sourceforge.plantuml.UmlDiagramType; import net.sourceforge.plantuml.core.DiagramDescription; import net.sourceforge.plantuml.core.ImageData; import net.sourceforge.plantuml.graphic.HtmlColor; import net.sourceforge.plantuml.graphic.HtmlColorUtils; import net.sourceforge.plantuml.graphic.InnerStrategy; import net.sourceforge.plantuml.graphic.StringBounder; import net.sourceforge.plantuml.graphic.TextBlock; import net.sourceforge.plantuml.graphic.TextBlockCompressed; import net.sourceforge.plantuml.graphic.TextBlockRecentred; import net.sourceforge.plantuml.graphic.UDrawable; import net.sourceforge.plantuml.svek.TextBlockBackcolored; import net.sourceforge.plantuml.ugraphic.ImageBuilder; import net.sourceforge.plantuml.ugraphic.MinMax; import net.sourceforge.plantuml.ugraphic.UChangeColor; import net.sourceforge.plantuml.ugraphic.UGraphic; import net.sourceforge.plantuml.ugraphic.ULine; import net.sourceforge.plantuml.ugraphic.UStroke; import net.sourceforge.plantuml.ugraphic.UTranslate; public class TimingDiagram extends UmlDiagram implements Clock { private final Map players = new LinkedHashMap(); private final List messages = new ArrayList(); private final TimingRuler ruler = new TimingRuler(getSkinParam()); private TimeTick now; private Player lastPlayer; public DiagramDescription getDescription() { return new DiagramDescription("(Timing Diagram)"); } @Override public UmlDiagramType getUmlDiagramType() { return UmlDiagramType.TIMING; } @Override protected ImageData exportDiagramInternal(OutputStream os, int index, FileFormatOption fileFormatOption) throws IOException { final double dpiFactor = 1; final double margin = 10; final ImageBuilder imageBuilder = new ImageBuilder(getSkinParam(), dpiFactor, fileFormatOption.isWithMetadata() ? getMetadata() : null, getWarningOrError(), margin, margin, getAnimation()); TextBlock result = getTextBlock(); final ISkinParam skinParam = getSkinParam(); result = new AnnotatedWorker(this, skinParam).addAdd(result); imageBuilder.setUDrawable(result); return imageBuilder.writeImageTOBEMOVED(fileFormatOption, seed(), os); } private TextBlockBackcolored getTextBlock() { return new TextBlockBackcolored() { public void drawU(UGraphic ug) { drawInternal(ug); } public Rectangle2D getInnerPosition(String member, StringBounder stringBounder, InnerStrategy strategy) { return null; } public Dimension2D calculateDimension(StringBounder stringBounder) { final UTranslate lastTranslate = getUTranslateForPlayer(null, stringBounder); final double withBeforeRuler = getWithBeforeRuler(stringBounder); final double totalWith = withBeforeRuler + ruler.getWidth() + marginX1 + marginX2; return new Dimension2DDouble(totalWith, lastTranslate.getDy() + ruler.getHeight(stringBounder)); } public MinMax getMinMax(StringBounder stringBounder) { throw new UnsupportedOperationException(); } public HtmlColor getBackcolor() { return null; } }; } public static final double marginX1 = 5; private final double marginX2 = 5; private void drawInternal(UGraphic ug) { final StringBounder stringBounder = ug.getStringBounder(); final UTranslate lastTranslate = getUTranslateForPlayer(null, stringBounder); final double withBeforeRuler = getWithBeforeRuler(stringBounder); final double totalWith = withBeforeRuler + ruler.getWidth() + marginX1 + marginX2; final ULine border = new ULine(0, lastTranslate.getDy()); final UStroke borderStroke = new UStroke(1.7); ug.apply(new UChangeColor(HtmlColorUtils.BLACK)).apply(borderStroke).draw(border); ug.apply(new UChangeColor(HtmlColorUtils.BLACK)).apply(borderStroke).apply(new UTranslate(totalWith, 0)) .draw(border); ug = ug.apply(new UTranslate(marginX1, 0)); for (Player player : players.values()) { final UGraphic playerUg = ug.apply(getUTranslateForPlayer(player, stringBounder)); player.drawU(playerUg); player.drawContent(playerUg.apply(new UTranslate(withBeforeRuler, 0))); player.drawWidthHeader(playerUg); playerUg.apply(new UChangeColor(HtmlColorUtils.BLACK)).apply(borderStroke) .apply(new UTranslate(-marginX1, 0)).draw(new ULine(totalWith, 0)); } ug = ug.apply(new UTranslate(withBeforeRuler, 0)); ruler.draw(ug.apply(lastTranslate)); for (TimeMessage timeMessage : messages) { drawMessages(ug, timeMessage); } } private double getWithBeforeRuler(StringBounder stringBounder) { double width = 0; for (Player player : players.values()) { width = Math.max(width, player.getGetWidthHeader(stringBounder)); } return width; } private void drawMessages(UGraphic ug, TimeMessage message) { final Player player1 = message.getPlayer1(); final Player player2 = message.getPlayer2(); final UTranslate translate1 = getUTranslateForPlayer(player1, ug.getStringBounder()); final UTranslate translate2 = getUTranslateForPlayer(player2, ug.getStringBounder()); final IntricatedPoint pt1 = player1.getTimeProjection(ug.getStringBounder(), message.getTick1()); final IntricatedPoint pt2 = player2.getTimeProjection(ug.getStringBounder(), message.getTick2()); if (pt1 == null || pt2 == null) { return; } final TimeArrow timeArrow = TimeArrow.create(pt1.translated(translate1), pt2.translated(translate2), message.getLabel(), getSkinParam()); timeArrow.drawU(ug); } public UTranslate getUTranslateForPlayer(Player candidat, StringBounder stringBounder) { double y = 0; for (Player player : players.values()) { final Dimension2D dim = player.calculateDimension(stringBounder); if (candidat == player) { return new UTranslate(0, y); } y += dim.getHeight(); } if (candidat == null) { return new UTranslate(0, y); } throw new IllegalArgumentException(); } public void createLifeLine(String code, String full, TimingStyle type) { final Player player = new Player(code, full, type, getSkinParam(), ruler); players.put(code, player); lastPlayer = player; } public void createTimeMessage(Player player1, TimeTick time1, Player player2, TimeTick time2, String label) { final TimeMessage message = new TimeMessage(new TickInPlayer(player1, time1), new TickInPlayer(player2, time2), label); messages.add(message); } public void addTime(TimeTick time) { this.now = time; ruler.addTime(time); } public void updateNow(TimeTick time) { this.now = time; } public Player getPlayer(String code) { return players.get(code); } public TimeTick getNow() { // if (now == null) { // throw new IllegalStateException(); // } return now; } public void setLastPlayer(Player player) { this.lastPlayer = player; } public Player getLastPlayer() { return lastPlayer; } public void scaleInPixels(long tick, long pixel) { ruler.scaleInPixels(tick, pixel); } }