/* Copyright 2003-2012 Dmitry Barashev, GanttProject Team This file is part of GanttProject, an opensource project management tool. GanttProject 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. GanttProject 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 GanttProject. If not, see <http://www.gnu.org/licenses/>. */ package biz.ganttproject.core.chart.canvas; import java.awt.Color; import java.awt.Font; import java.awt.Paint; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.WeakHashMap; /** * Stores the available primitives and their information (used for painting) and * provides methods to retrieve them * * @author bard */ public class Canvas { private ArrayList<Rectangle> myRectangles = new ArrayList<Rectangle>(); private ArrayList<Line> myLines = new ArrayList<Line>(); private ArrayList<Text> myTexts = new ArrayList<Text>(); private Map<Object, Shape> myModelObject2primitive = new WeakHashMap<Object, Shape>(); private List<Canvas> myLayers = new ArrayList<Canvas>(); private int myDeltaX; private int myDeltaY; private List<TextGroup> myTextGroups = new ArrayList<TextGroup>(); private final DummySpatialIndex<Text> myTextIndex = new DummySpatialIndex<Text>(); private final DummySpatialIndex<Polygon> myPolygonIndex = new DummySpatialIndex<Canvas.Polygon>(); /** Horizontal alignments for texts */ public enum HAlignment { CENTER, LEFT, RIGHT }; /** Vertical alignments for texts */ public enum VAlignment { CENTER, TOP, BOTTOM }; public static class Shape { private Color myBackgroundColor; private Color myForegroundColor; private String myStyleName; private Object myModelObject; private boolean isVisible = true; private LinkedHashSet<String> myStyles; private Float myOpacity = null; private LinkedHashSet<String> getStyles() { if (myStyles == null) { myStyles = new LinkedHashSet<String>(); } return myStyles; } public void addStyle(String style) { getStyles().add(style); } public boolean hasStyle(String style) { return getStyles().contains(style); } public void setStyle(String styleName) { myStyleName = styleName; } public String getStyle() { return myStyleName; } public Color getBackgroundColor() { return myBackgroundColor; } public void setBackgroundColor(Color myBackgroundColor) { this.myBackgroundColor = myBackgroundColor; } public Color getForegroundColor() { return myForegroundColor; } public void setForegroundColor(Color myForegroundColor) { this.myForegroundColor = myForegroundColor; } public Object getModelObject() { return myModelObject; } public void setModelObject(Object modelObject) { myModelObject = modelObject; } public boolean isVisible() { return isVisible; } public void setVisible(boolean visible) { isVisible = visible; } public Float getOpacity() { return myOpacity; } public void setOpacity(float opacity) { myOpacity = opacity; } } public static class Polygon extends Shape { private final int myLeftX; private final int myRightX; private final int myTopY; private final int myBottomY; private final int[] myPointsX; private final int[] myPointsY; private Polygon(int... points) { myPointsX = new int[points.length / 2]; myPointsY = new int[points.length / 2]; int leftX = Integer.MAX_VALUE; int topY = Integer.MAX_VALUE; int rightX = Integer.MIN_VALUE; int bottomY = Integer.MIN_VALUE; for (int i = 0; i < points.length / 2; i++) { leftX = Math.min(leftX, points[i * 2]); rightX = Math.max(rightX, points[i * 2]); topY = Math.min(topY, points[i * 2 + 1]); bottomY = Math.max(bottomY, points[i * 2 + 1]); myPointsX[i] = points[i * 2]; myPointsY[i] = points[i * 2 + 1]; } myLeftX = leftX; myRightX = rightX; myTopY = topY; myBottomY = bottomY; } public int[] getPointsX() { return myPointsX; } public int[] getPointsY() { return myPointsY; } public int getPointCount() { return myPointsX.length; } public int getLeftX() { return myLeftX; } public int getTopY() { return myTopY; } public int getRightX() { return myRightX; } public int getBottomY() { return myBottomY; } public int getHeight() { return myBottomY - myTopY; } public int getWidth() { return myRightX - myLeftX; } public int getMiddleY() { return myTopY + getHeight() / 2; } public int getMiddleX() { return myLeftX + getWidth() / 2; } @Override public String toString() { return "Polygon[" + myLeftX + "," + myTopY + ",w=" + getWidth() + ",h=" + getHeight() + ",style=" + getStyle() + "]"; } } public static class Rectangle extends Polygon { public Paint myPaint; private Rectangle(int leftx, int topy, int width, int height) { super(leftx, topy, leftx + width, topy + height); } public Paint getBackgroundPaint() { return myPaint; } public void setBackgroundPaint(Paint paint) { myPaint = paint; } } public static class Arrow extends Shape { public static Arrow NONE = new Arrow(0, 0); public static Arrow FINISH = new Arrow(7, 3); private final int myLength; private final int myWidth; public Arrow(int length, int width) { myLength = length; myWidth = width; } public int getLength() { return myLength; } public int getWidth() { return myWidth; } } public static class Line extends Shape { private final int myStartX; private final int myStartY; private final int myFinishX; private final int myFinishY; private Arrow myArrow = Arrow.NONE; private Line(int startx, int starty, int finishx, int finishy) { myStartX = startx; myStartY = starty; myFinishX = finishx; myFinishY = finishy; } public int getStartX() { return myStartX; } public int getStartY() { return myStartY; } public int getFinishX() { return myFinishX; } public int getFinishY() { return myFinishY; } public void setArrow(Arrow arrow) { myArrow = arrow; } public Arrow getArrow() { return myArrow; } } public static class Label { public final String text; public final int lengthPx; public final int heightPx; private final Text myOwner; public Label(Text owner, String text, int lengthPx) { this(owner, text, lengthPx, Integer.MIN_VALUE); } public Label(Text owner, String text, int lengthPx, int heightPx) { this.myOwner = owner; this.text = text; this.lengthPx = lengthPx; this.heightPx = heightPx; } public void setVisible(boolean isVisible) { if (isVisible && myOwner != null) { myOwner.index(this); } } } public static class Text extends Shape { private final int myLeftX; private final int myBottomY; private Font myFont; private int myMaxLength; private HAlignment myHAlignment = HAlignment.LEFT; private VAlignment myVAlignment = VAlignment.BOTTOM; private TextSelector mySelector; private final SpatialIndex<Text> myIndex; private Text(int leftX, int bottomY, final String text, SpatialIndex<Text> index) { this(leftX, bottomY, (TextSelector)null, index); mySelector = new TextSelector() { @Override public Label[] getLabels(TextMetrics textLengthCalculator) { return new Label[] {createLabel(text, textLengthCalculator.getTextLength(text))}; } }; } void index(Label label) { assert label.myOwner == this; if (myIndex != null && label.heightPx != Integer.MIN_VALUE) { myIndex.put(this, myLeftX, myBottomY, label.lengthPx, label.heightPx); } } private Text(int leftX, int bottomY, TextSelector delegateSelector) { this(leftX, bottomY, delegateSelector, null); } private Text(int leftX, int bottomY, TextSelector delegateSelector, SpatialIndex<Text> index) { myLeftX = leftX; myBottomY = bottomY; mySelector = delegateSelector; myMaxLength = -1; myIndex = index; } public void setFont(Font font) { myFont = font; } public void setMaxLength(int maxLength) { myMaxLength = maxLength; } public int getMaxLength() { return myMaxLength; } public Font getFont() { return myFont; } public Label[] getLabels(TextMetrics textLengthCalculator) { return mySelector.getLabels(textLengthCalculator); // String text = mySelector.getText(textLengthCalculator); // int length = textLengthCalculator.getTextLength(text); // return new Label[] { new Label(text, length) }; } public int getLeftX() { return myLeftX; } public int getBottomY() { return myBottomY; } public void setAlignment(HAlignment halignment, VAlignment valignment) { myHAlignment = halignment; myVAlignment = valignment; } public HAlignment getHAlignment() { return myHAlignment; } public VAlignment getVAlignment() { return myVAlignment; } public TextSelector getTextSelector() { return mySelector; } public Label createLabel(String text, int lengthPx) { return createLabel(text, lengthPx, Integer.MIN_VALUE); } public Label createLabel(String text, int lengthPx, int heightPx) { return new Label(this, text, lengthPx, heightPx); } @Override public String toString() { return String.format("TBox [%d, %d]", myLeftX, myBottomY); } public void setSelector(TextSelector selector) { mySelector = selector; } } public static class TextGroup { private List<String> myLineStyles; private int myHeight; private FontChooser myFontChooser; private List<Font> myFonts; private List<List<Text>> myLines = new ArrayList<List<Text>>(); private int myBottomY; private int myLeftX; private List<Integer> myBaselines = new ArrayList<Integer>(); public TextGroup(int leftX, int bottomY, int height, String... lineStyles) { myLeftX = leftX; myBottomY = bottomY; myHeight = height; myLineStyles = new ArrayList<String>(Arrays.asList(lineStyles)); for (int i = 0; i < myLineStyles.size(); i++) { myLines.add(new ArrayList<Text>()); } myFonts = new ArrayList<Font>(); } public void setFonts(FontChooser fontChooser) { for (int totalHeight = getTotalHeight(fontChooser, myBaselines); totalHeight > myHeight; totalHeight = getTotalHeight(fontChooser, myBaselines)) { int baseSize = fontChooser.decreaseBaseFontSize(); if (baseSize < 8) { break; } } for (String style : myLineStyles) { myFonts.add(fontChooser.getFont(style)); } myFontChooser = fontChooser; } private int getTotalHeight(FontChooser fontChooser, List<Integer> lineBaselines) { lineBaselines.clear(); int totalHeight = 0; for (String style : myLineStyles) { totalHeight += fontChooser.getMarginTop(style); totalHeight += fontChooser.getTextHeight(style); totalHeight += fontChooser.getMarginBottom(style); lineBaselines.add(totalHeight); } return totalHeight; } public Text addText(int x, int y, TextSelector textSelector) { Text result = new Text(x, y, textSelector); myLines.get(y).add(result); return result; } public int getLineCount() { return myLines.size(); } public List<Text> getLine(int i) { return myLines.get(i); } public Font getFont(int i) { return myFonts.get(i); } public Color getColor(int i) { return myFontChooser.getColor(myLineStyles.get(i)); } public int getLeftX() { return myLeftX; } public int getBottomY() { return myBottomY; } public int getBottomY(int line) { return myBottomY + myBaselines.get(line); } } public Canvas() { this(0, 0); } public Canvas(int deltax, int deltay) { myDeltaX = deltax; myDeltaY = deltay; } public void setOffset(int deltax, int deltay) { myDeltaX = deltax; myDeltaY = deltay; // for (GraphicPrimitiveContainer layer : myLayers) { // layer.setOffset(deltax, deltay); // } } public Polygon createPolygon(int... points) { assert points.length % 2 == 0 : "The number of points must be even"; Polygon result = new Polygon(points); myPolygonIndex.put(result, result.getLeftX(), result.getBottomY(), result.getWidth(), result.getHeight()); return result; } public Rectangle createRectangle(int leftx, int topy, int width, int height) { Rectangle result = createDetachedRectangle(leftx, topy, width, height); myRectangles.add(result); return result; } public Rectangle createDetachedRectangle(int leftx, int topy, int width, int height) { if (width < 0) { width = -width; leftx = leftx - width; } return new Rectangle(leftx + myDeltaX, topy + myDeltaY, width, height); } public Line createLine(int startx, int starty, int finishx, int finishy) { Line result = new Line(startx + myDeltaX, starty + myDeltaY, finishx + myDeltaX, finishy + myDeltaY); myLines.add(result); return result; } public Text createText(int leftx, int bottomy, String text) { Text result = new Text(leftx + myDeltaX, bottomy + myDeltaY, text, myTextIndex); myTexts.add(result); return result; } public Text createText(int leftx, int bottomy, TextSelector textSelector) { Text result = new Text(leftx + myDeltaX, bottomy + myDeltaY, textSelector, myTextIndex); myTexts.add(result); return result; } public TextGroup createTextGroup(int leftX, int bottomY, int height, String... styles) { TextGroup result = new TextGroup(leftX, bottomY, height, styles); myTextGroups.add(result); return result; } public void paint(Painter painter) { painter.prePaint(); for (int i = 0; i < myRectangles.size(); i++) { Rectangle next = myRectangles.get(i); if (next.isVisible()) { painter.paint(next); } } for (Polygon p : myPolygonIndex.values()) { if (p.isVisible()) { painter.paint(p); } } for (int i = 0; i < myLines.size(); i++) { Line next = myLines.get(i); if (next.isVisible()) { painter.paint(next); } } for (int i = 0; i < myTexts.size(); i++) { Text next = myTexts.get(i); if (next.isVisible()) { painter.paint(next); } } for (TextGroup textGroup : myTextGroups) { painter.paint(textGroup); } } public void clear() { myTextIndex.clear(); myPolygonIndex.clear(); myRectangles.clear(); myLines.clear(); myTexts.clear(); myTextGroups.clear(); myModelObject2primitive.clear(); for (Canvas layer : getLayers()) { layer.clear(); } } public void bind(Shape primitive, Object modelObject) { myModelObject2primitive.put(modelObject, primitive); primitive.setModelObject(modelObject); } public Shape getPrimitive(Object modelObject) { return myModelObject2primitive.get(modelObject); } public Shape getPrimitive(int x, int y) { return getPrimitive(x, 0, y, 0); } public Shape getPrimitive(int x, int xThreshold, int y, int yThreshold) { Shape result = null; for (int i = 0; i < myRectangles.size(); i++) { Rectangle next = myRectangles.get(i); // System.err.println(" next rectangle="+next); if (next.getLeftX() <= x + xThreshold && next.getRightX() >= x - xThreshold && next.getTopY() <= y + yThreshold && next.getBottomY() >= y - yThreshold) { result = next; break; } } if (result != null) { return result; } result = myPolygonIndex.get(x, xThreshold, y, yThreshold); if (result != null) { return result; } return myTextIndex.get(x + myDeltaX, y + myDeltaY); } public List<Canvas> getLayers() { return Collections.unmodifiableList(myLayers); } public Canvas getLayer(int layer) { if (layer < 0 || layer >= myLayers.size()) { throw new IllegalArgumentException(); } return myLayers.get(layer); } public Canvas newLayer() { Canvas result = new Canvas(); myLayers.add(result); return result; } public void createLayers(int count) { for (int i = 0; i < count; i++) { newLayer(); } } }