diff --git a/diylc/diylc-core/src/org/diylc/presenter/update.xml b/diylc/diylc-core/src/org/diylc/presenter/update.xml index b7c38f46..8d3f6c55 100644 --- a/diylc/diylc-core/src/org/diylc/presenter/update.xml +++ b/diylc/diylc-core/src/org/diylc/presenter/update.xml @@ -2706,6 +2706,10 @@ 2019-03-24 00:00:00.000 CET + + NEW_FEATURE + Added "Tape Measure" component for measuring the distance between any two points on the drawing + NEW_FEATURE New app bundle for Mac OS using appbundler instead of javabundler diff --git a/diylc/diylc-library/lib/diylc-core.jar b/diylc/diylc-library/lib/diylc-core.jar index 6f2c6063..cc12a635 100644 Binary files a/diylc/diylc-library/lib/diylc-core.jar and b/diylc/diylc-library/lib/diylc-core.jar differ diff --git a/diylc/diylc-library/src/org/diylc/components/shapes/TapeMeasure.java b/diylc/diylc-library/src/org/diylc/components/shapes/TapeMeasure.java new file mode 100644 index 00000000..d36062f1 --- /dev/null +++ b/diylc/diylc-library/src/org/diylc/components/shapes/TapeMeasure.java @@ -0,0 +1,357 @@ +/* + + DIY Layout Creator (DIYLC). + Copyright (c) 2009-2018 held jointly by the individual authors. + + This file is part of DIYLC. + + DIYLC 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. + + DIYLC 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 DIYLC. If not, see . + +*/ +package org.diylc.components.shapes; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Font; +import java.awt.FontMetrics; +import java.awt.Graphics2D; +import java.awt.Point; +import java.awt.Polygon; +import java.awt.RenderingHints; +import java.awt.Shape; +import java.awt.Stroke; +import java.awt.geom.AffineTransform; +import java.awt.geom.Rectangle2D; +import java.text.DecimalFormat; + +import org.diylc.common.Display; +import org.diylc.common.LineStyle; +import org.diylc.common.ObjectCache; +import org.diylc.common.SimpleComponentTransformer; +import org.diylc.components.AbstractLeadedComponent; +import org.diylc.core.ComponentState; +import org.diylc.core.CreationMethod; +import org.diylc.core.IDIYComponent; +import org.diylc.core.IDrawingObserver; +import org.diylc.core.Project; +import org.diylc.core.annotations.BomPolicy; +import org.diylc.core.annotations.ComponentDescriptor; +import org.diylc.core.annotations.EditableProperty; +import org.diylc.core.measures.Size; +import org.diylc.core.measures.SizeUnit; + +@ComponentDescriptor(name = "Tape Measure", author = "Branislav Stojkovic", category = "Shapes", + creationMethod = CreationMethod.POINT_BY_POINT, instanceNamePrefix = "TM", description = "Measures distance between the two points", + zOrder = IDIYComponent.COMPONENT, bomPolicy = BomPolicy.NEVER_SHOW, autoEdit = false, + transformer = SimpleComponentTransformer.class) +public class TapeMeasure extends AbstractLeadedComponent { + + private static final long serialVersionUID = 1L; + + public static Color COLOR = Color.black; + + private Color color = COLOR; + protected LineStyle style = LineStyle.SOLID; + private Size thickness = new Size(1d, SizeUnit.px); + private Size arrowSize = new Size(5d, SizeUnit.px); + private Polygon arrow = null; + private AffineTransform arrowTx = new AffineTransform(); + private boolean arrowStart = true; + private boolean arrowEnd = true; + private SizeUnit unit = SizeUnit.mm; + private int decimals = 2; + + @Override + public void drawIcon(Graphics2D g2d, int width, int height) { + g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1)); + g2d.setColor(COLOR); + g2d.drawLine(1, height - 2, width / 4, height * 3 / 4 - 1); + g2d.drawLine(width * 3 / 4, height / 4 - 1, width - 2, 1); + g2d.setBackground(Color.black); + g2d.fillPolygon(new int[] {1, 1, 4}, new int[] {height - 4, height - 1, height - 1}, 3); + g2d.fillPolygon(new int[] {width - 4, width - 1, width - 1}, new int[] {1, 1, 4}, 3); + g2d.rotate(-Math.PI / 4, width / 2, height / 2); + String text = "8mm"; + g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + Font font = Project.DEFAULT_FONT.deriveFont(8f); + FontMetrics metrics = g2d.getFontMetrics(font); + int x = (width - metrics.stringWidth(text)) / 2; + int y = ((height - metrics.getHeight()) / 2) + metrics.getAscent() - 1; + g2d.setFont(font); + g2d.drawString(text, x, y); + } + + @Override + public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode, Project project, + IDrawingObserver drawingObserver) { + float thickness = (float) getThickness().convertToPixels(); + Stroke stroke = null; + switch (getStyle()) { + case SOLID: + stroke = ObjectCache.getInstance().fetchZoomableStroke(thickness); + break; + case DASHED: + stroke = ObjectCache.getInstance().fetchStroke(thickness, new float[] {thickness * 2, thickness * 4}, thickness * 4, BasicStroke.CAP_SQUARE); + break; + case DOTTED: + stroke = ObjectCache.getInstance().fetchStroke(thickness, new float[] {thickness, thickness * 5}, 0, BasicStroke.CAP_ROUND); + break; + } + g2d.setStroke(stroke); + g2d.setColor(componentState == ComponentState.SELECTED ? SELECTION_COLOR : color); + + Point startPoint = new Point(getControlPoint(0)); + Point endPoint = new Point(getControlPoint(1)); + + if (arrowStart) { + arrowTx.setToIdentity(); + double angle = Math.atan2(getControlPoint(1).y - getControlPoint(0).y, getControlPoint(1).x - getControlPoint(0).x); + arrowTx.translate(getControlPoint(0).x, getControlPoint(0).y); + arrowTx.rotate((angle + Math.PI / 2d)); + AffineTransform oldTx = g2d.getTransform(); + g2d.transform(arrowTx); + g2d.fill(getArrow()); + g2d.setTransform(oldTx); + + // make the line slightly shorter so line end doesn't overlap with the arrow + double distance = distance(startPoint, endPoint); + interpolate(startPoint, endPoint, getArrowSize().convertToPixels() * 0.9 / distance, startPoint); + } + if (arrowEnd) { + arrowTx.setToIdentity(); + double angle = Math.atan2(getControlPoint(1).y - getControlPoint(0).y, getControlPoint(1).x - getControlPoint(0).x); + arrowTx.translate(getControlPoint(1).x, getControlPoint(1).y); + arrowTx.rotate((angle - Math.PI / 2d)); + AffineTransform oldTx = g2d.getTransform(); + g2d.transform(arrowTx); + g2d.fill(getArrow()); + g2d.setTransform(oldTx); + + // make the line slightly shorter so line end doesn't overlap with the arrow + double distance = distance(startPoint, endPoint); + interpolate(endPoint, startPoint, getArrowSize().convertToPixels() * 0.9 / distance, endPoint); + } + + double theta = Math.atan2(getPoints()[1].y - getPoints()[0].y, getPoints()[1].x - getPoints()[0].x); + + String text = getValue(); + Font font = Project.DEFAULT_FONT; + FontMetrics metrics = g2d.getFontMetrics(font); + int strWidth = metrics.stringWidth(text); + int x = -strWidth / 2; + int y = -metrics.getHeight() / 2 + metrics.getAscent(); + + int legLength = (int) (getPoints()[0].distance(getPoints()[1]) - strWidth) / 2 - 6; + + System.out.println(strWidth + " " + legLength); + + // draw legs around the enter text + g2d.drawLine(startPoint.x, startPoint.y, + (int)Math.round(startPoint.x + Math.cos(theta) * legLength), + (int)Math.round(startPoint.y + Math.sin(theta) * legLength)); + + g2d.drawLine(endPoint.x, endPoint.y, + (int)Math.round(endPoint.x - Math.cos(theta) * legLength), + (int)Math.round(endPoint.y - Math.sin(theta) * legLength)); + + // Adjust label angle if needed to make sure that it's readable. + if ((theta >= Math.PI / 2 && theta <= Math.PI) || (theta < -Math.PI / 2 && theta > -Math.PI)) { + theta += Math.PI; + } + + g2d.translate((getPoints()[0].x + getPoints()[1].x) / 2, (getPoints()[0].y + getPoints()[1].y) / 2); + g2d.rotate(theta); + + g2d.setFont(font); + g2d.drawString(text, x, y); + } + + private void interpolate(Point p1, Point p2, double t, Point p) { + p.setLocation((int)Math.round(p1.x * (1-t) + p2.x * t), (int)Math.round(p1.y * (1-t) + p2.y * t)); + } + + private double distance(Point p1, Point p2) { + return Math.sqrt((p1.x - p2.x) * (p1.x - p2.x) + (p1.y - p2.y) * (p1.y - p2.y)); + } + + @Override + public Color getLeadColorForPainting(ComponentState componentState) { + return componentState == ComponentState.SELECTED || componentState == ComponentState.DRAGGING ? SELECTION_COLOR + : color; + } + + public Polygon getArrow() { + if (arrow == null) { + arrow = new Polygon(); + int t = (int) getArrowSize().convertToPixels(); + arrow.addPoint(0, 0); + arrow.addPoint(-t, -t * 2); + arrow.addPoint(t, -t * 2); + } + return arrow; + } + + @EditableProperty + public Size getThickness() { + if (thickness == null) + thickness = new Size(1d, SizeUnit.px); + return thickness; + } + + public void setThickness(Size thickness) { + this.thickness = thickness; + } + + @EditableProperty + public Size getArrowSize() { + if (arrowSize == null) + arrowSize = thickness = new Size(1d, SizeUnit.px); + return arrowSize; + } + + public void setArrowSize(Size arrowSize) { + this.arrowSize = arrowSize; + arrow = null; + } + + @Override + public Color getLeadColor() { + return super.getLeadColor(); + } + + @EditableProperty + public Color getColor() { + return color; + } + + public void setColor(Color color) { + this.color = color; + } + + @EditableProperty(name = "Style") + public LineStyle getStyle() { + if (style == null) + style = LineStyle.SOLID; + return style; + } + + public void setStyle(LineStyle style) { + this.style = style; + } + + @EditableProperty + public SizeUnit getUnit() { + return unit; + } + + public void setUnit(SizeUnit unit) { + this.unit = unit; + } + + @EditableProperty + public int getDecimals() { + return decimals; + } + + public void setDecimals(int decimals) { + this.decimals = decimals; + } + + public Color getBodyColor() { + return super.getBodyColor(); + } + + @Override + public Color getBorderColor() { + return super.getBorderColor(); + } + + @Override + public Byte getAlpha() { + return super.getAlpha(); + } + + @Override + public Size getLength() { + return super.getLength(); + } + + @Override + public Size getWidth() { + return super.getWidth(); + } + + @Override + public String getValue() { + Point a = getControlPoint(0); + Point b = getControlPoint(1); + double d = a.distance(b); + double convertedD = SizeUnit.px.getFactor() / getUnit().getFactor() * d; + String format = "#"; + if (getDecimals() > 0) { + format += "."; + for (int i = 0; i < getDecimals(); i++) + format += "#"; + } + DecimalFormat decimalFormat = new DecimalFormat(format); + return decimalFormat.format(convertedD) + getUnit().toString(); + } + + @Override + public void setValue(String value) {} + + @Override + protected Shape getBodyShape() { + return new Rectangle2D.Double(0, 0, 50, 20); + } + + @Override + protected Size getDefaultWidth() { + return null; + } + + @Override + protected Size getDefaultLength() { + return null; + } + + @Override + public String getControlPointNodeName(int index) { + return null; + } + + @Deprecated + @Override + public String getName() { + return super.getName(); + } + + @Deprecated + @Override + public Display getDisplay() { + return Display.VALUE; + } + + @Deprecated + @Override + public org.diylc.components.AbstractLeadedComponent.LabelOriantation getLabelOriantation() { + return org.diylc.components.AbstractLeadedComponent.LabelOriantation.Directional; + } + + @Deprecated + @Override + public boolean getMoveLabel() { + return false; + } +} diff --git a/diylc/diylc-swing/lib/diylc-core.jar b/diylc/diylc-swing/lib/diylc-core.jar index 6f2c6063..cc12a635 100644 Binary files a/diylc/diylc-swing/lib/diylc-core.jar and b/diylc/diylc-swing/lib/diylc-core.jar differ diff --git a/diylc/diylc-swing/library/main.jar b/diylc/diylc-swing/library/main.jar index 08a0179a..c92195c0 100644 Binary files a/diylc/diylc-swing/library/main.jar and b/diylc/diylc-swing/library/main.jar differ