package com.baselet.element.old.custom; import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Composite; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Polygon; import java.awt.Rectangle; import java.awt.Shape; import java.awt.geom.Arc2D; import java.awt.geom.CubicCurve2D; import java.awt.geom.Ellipse2D; import java.awt.geom.Line2D; import java.awt.geom.QuadCurve2D; import java.awt.geom.RoundRectangle2D; import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.Vector; import com.baselet.control.HandlerElementMap; import com.baselet.control.basics.Converter; import com.baselet.control.constants.FacetConstants; import com.baselet.control.enums.AlignHorizontal; import com.baselet.control.enums.Direction; import com.baselet.control.enums.LineType; import com.baselet.control.util.Utils; import com.baselet.custom.CustomFunction; import com.baselet.diagram.DiagramHandler; import com.baselet.diagram.draw.helper.ColorOwn; import com.baselet.diagram.draw.helper.ColorOwn.Transparency; import com.baselet.element.interfaces.GridElement; import com.baselet.element.old.OldGridElement; @SuppressWarnings("serial") public abstract class CustomElement extends OldGridElement { private static class Text { private final String text; private final int x, y; private final AlignHorizontal align; private Integer fixedSize; private Text(String text, int x, int y, AlignHorizontal align) { this.text = text; this.x = x; this.y = y; this.align = align; } private Text(String text, int x, int y, AlignHorizontal align, Integer fixedSize) { this.text = text; this.x = x; this.y = y; this.align = align; this.fixedSize = fixedSize; // some texts should not be zoomed } } protected float zoom; // We need a seperate program behaviour for 10% and 20% zoom. Otherwise manual resized entities would have the following bugs: // 10%: entity grow without end, 20%: relations don't stick on the right or bottom end of entity private boolean bugfix; protected Graphics2D g2; protected float temp; protected int width, height; protected Composite[] composites; private String code; private final Vector<StyleShape> shapes = new Vector<StyleShape>(); private final Vector<Text> texts = new Vector<Text>(); // The temp-variables are needed to store styles with setLineType etc. methods temporarily so that draw-Methods know the actual set style private LineType tmpLineType; private int tmpLineThickness; private Color tmpFgColor; private Color tmpBgColor; private float tmpAlpha; private boolean specialLine, specialFgColor, specialBgColor; private boolean wordWrap = false; private boolean allowResize = true; public abstract void paint(); public final void setCode(String code) { this.code = code; } public final String getCode() { return code; } private void drawShapes() { g2.setColor(bgColor); g2.setComposite(composites[1]); for (StyleShape s : shapes) { specialBgColor = !s.getBgColor().equals(bgColor); if (specialBgColor) { g2.setColor(s.getBgColor()); g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, s.getAlpha())); } g2.fill(s.getShape()); if (specialBgColor) { g2.setColor(bgColor); g2.setComposite(composites[1]); } } g2.setComposite(composites[0]); g2.setColor(fgColor); for (StyleShape s : shapes) { specialLine = s.getLineType() != LineType.SOLID || s.getLineThickness() != FacetConstants.LINE_WIDTH_DEFAULT; specialFgColor = !s.getFgColor().equals(Converter.convert(ColorOwn.DEFAULT_FOREGROUND)); if (specialLine) { g2.setStroke(Utils.getStroke(s.getLineType(), s.getLineThickness())); } if (specialFgColor) { if (HandlerElementMap.getHandlerForElement(this).getDrawPanel().getSelector().isSelected(this)) { g2.setColor(Converter.convert(ColorOwn.SELECTION_FG)); } else { g2.setColor(s.getFgColor()); } } g2.draw(s.getShape()); if (specialLine) { g2.setStroke(Utils.getStroke(LineType.SOLID, (float) FacetConstants.LINE_WIDTH_DEFAULT)); } if (specialFgColor) { g2.setColor(fgColor); } } for (Text t : texts) { boolean applyZoom = true; if (t.fixedSize != null) { HandlerElementMap.getHandlerForElement(this).getFontHandler().setFontSize((double) t.fixedSize); applyZoom = false; } HandlerElementMap.getHandlerForElement(this).getFontHandler().writeText(g2, t.text, t.x, t.y, t.align, applyZoom); if (t.fixedSize != null) { HandlerElementMap.getHandlerForElement(this).getFontHandler().resetFontSize(); } } texts.clear(); shapes.clear(); } @Override public final void paintEntity(Graphics g) { g2 = (Graphics2D) g; composites = colorize(g2); g2.setFont(HandlerElementMap.getHandlerForElement(this).getFontHandler().getFont()); g2.setColor(fgColor); zoom = HandlerElementMap.getHandlerForElement(this).getZoomFactor(); if (zoom < 0.25) { bugfix = true; } else { bugfix = false; } // width and height must be zoomed back to 100% before any custom code is applied temp = getRectangle().width; width = Math.round(temp / zoom); // use Math.round cause (int) would round down from 239.99998 to 240 temp = getRectangle().height; height = Math.round(temp / zoom); // Set width and height on grid (used for manually resized custom elements mainly width = onGrid(width); height = onGrid(height); // secure this thread before executing the code! // String key = "R" + Math.random(); // CustomElementSecurityManager.addThread(Thread.currentThread(), key); resetAll(); // Reset all tempstyle variables before painting paint(); // calls the paint method of the specific custom element // CustomElementSecurityManager.remThread(Thread.currentThread(), key); width = onGrid(width); height = onGrid(height); // After the custom code we zoom the width and height back width *= zoom; height *= zoom; drawShapes(); // Resize elements if manual resize is not set // if (!this.allowResize || (this.autoResizeandManualResizeEnabled() && !this.isManualResized())) { // CHANGED: Resize every custom object by +1px to get consistent height and width if (!bugfix) { this.setSize(width + 1, height + 1); // } } } @Override public GridElement cloneFromMe() { CustomElement e = (CustomElement) super.cloneFromMe(); e.code = code; return e; } @CustomFunction(param_defaults = "text,x,y") protected final int print(String text, int x, int inY) { int y = inY; List<String> list = wordWrap ? splitString(text, width, HandlerElementMap.getHandlerForElement(this)) : Arrays.asList(new String[] { text }); for (String s : list) { texts.add(new Text(s, (int) (x * zoom), (int) (y * zoom), AlignHorizontal.LEFT)); y += textHeight(); } return y - inY; } @CustomFunction(param_defaults = "text,y") protected final int printLeft(String text, int inY) { int y = inY; List<String> list = wordWrap ? splitString(text, width, HandlerElementMap.getHandlerForElement(this)) : Arrays.asList(new String[] { text }); for (String s : list) { texts.add(new Text(s, (int) HandlerElementMap.getHandlerForElement(this).getFontHandler().getDistanceBetweenTexts(), (int) (y * zoom), AlignHorizontal.LEFT)); y += textHeight(); } return y - inY; } @CustomFunction(param_defaults = "text,y") protected final int printRight(String text, int inY) { int y = inY; List<String> list = wordWrap ? splitString(text, width, HandlerElementMap.getHandlerForElement(this)) : Arrays.asList(new String[] { text }); for (String s : list) { texts.add(new Text(s, (int) (width * zoom - textWidth(s, true)), (int) (y * zoom), AlignHorizontal.LEFT)); y += textHeight(); } return y - inY; } @CustomFunction(param_defaults = "text,y") protected final int printCenter(String text, int inY) { int y = inY; List<String> list = wordWrap ? splitString(text, width, HandlerElementMap.getHandlerForElement(this)) : Arrays.asList(new String[] { text }); for (String s : list) { texts.add(new Text(s, (int) ((onGrid(width) * zoom - textWidth(s, true)) / 2), (int) (y * zoom), AlignHorizontal.LEFT)); y += textHeight(); } return y - inY; } @CustomFunction(param_defaults = "text,x,y,fixedFontSize") protected final int printFixedSize(String text, int x, int inY, int fixedFontSize) { int y = inY; List<String> list = wordWrap ? splitString(text, width, HandlerElementMap.getHandlerForElement(this)) : Arrays.asList(new String[] { text }); for (String s : list) { texts.add(new Text(s, x, y, AlignHorizontal.LEFT, fixedFontSize)); y += textHeight(); } return y - inY; } @CustomFunction(param_defaults = "value") protected final int onGrid(double value) { return onGrid(value, false); } @CustomFunction(param_defaults = "value, roundUp") protected final int onGrid(double value, boolean roundUp) { if (value % 10 != 0) { value -= value % 10; if (roundUp) { value += 10; } } // BUGFIX for 10% and 20% zoom: Otherwise a manual resized entity border wouldn't be visible because of the exclusion of line 146 if (bugfix) { value--; } return (int) value; } @CustomFunction(param_defaults = "value1,value2") protected final int min(int value1, int value2) { return Math.min(value1, value2); } @CustomFunction(param_defaults = "value1,value2") protected final int max(int value1, int value2) { return Math.max(value1, value2); } @CustomFunction(param_defaults = "minWidth, minHeight, horizontalSpacing") protected final void setAutoresize(int minWidth, int minHeight, int horizontalSpacing) { if (!isManualResized()) { height = minHeight; // minimal height width = minWidth; // minimal width // calculates the width and height of the component for (String textline : Utils.decomposeStrings(getPanelAttributes())) { height = height + textHeight(); width = Math.max(textWidth(textline, false) + 10 + horizontalSpacing, width); } if (height < minHeight) { height = minHeight; } if (width < minWidth) { width = minWidth; } } } @Override @CustomFunction(param_defaults = "") public final boolean isManualResized() { return super.isManualResized(); } @CustomFunction(param_defaults = "wordWrap") public final void setWordWrap(boolean wordWrap) { this.wordWrap = wordWrap; } @CustomFunction(param_defaults = "") public final boolean isWordWrap() { return wordWrap; } @CustomFunction(param_defaults = "x, y, width, height, start, extent") protected final void drawArcOpen(float x, float y, float width, float height, float start, float extent) { shapes.add(new StyleShape(new Arc2D.Float(x * zoom, y * zoom, width * zoom, height * zoom, start, extent, Arc2D.OPEN), tmpLineType, tmpLineThickness, tmpFgColor, tmpBgColor, tmpAlpha)); } @CustomFunction(param_defaults = "x, y, width, height, start, extent") protected final void drawArcChord(float x, float y, float width, float height, float start, float extent) { shapes.add(new StyleShape(new Arc2D.Float(x * zoom, y * zoom, width * zoom, height * zoom, start, extent, Arc2D.CHORD), tmpLineType, tmpLineThickness, tmpFgColor, tmpBgColor, tmpAlpha)); } @CustomFunction(param_defaults = "x, y, width, height, start, extent") protected final void drawArcPie(float x, float y, float width, float height, float start, float extent) { shapes.add(new StyleShape(new Arc2D.Float(x * zoom, y * zoom, width * zoom, height * zoom, start, extent, Arc2D.PIE), tmpLineType, tmpLineThickness, tmpFgColor, tmpBgColor, tmpAlpha)); } @CustomFunction(param_defaults = "x, y, radius") protected final void drawCircle(int x, int y, int radius) { shapes.add(new StyleShape(new Ellipse2D.Float((int) ((x - radius) * zoom), (int) ((y - radius) * zoom), (int) (radius * 2 * zoom), (int) (radius * 2 * zoom)), tmpLineType, tmpLineThickness, tmpFgColor, tmpBgColor, tmpAlpha)); } @CustomFunction(param_defaults = "x1, y1, ctrlx1, ctrly1, ctrlx2, ctrly2, x2, y2") protected final void drawCurveCubic(float x1, float y1, float ctrlx1, float ctrly1, float ctrlx2, float ctrly2, float x2, float y2) { shapes.add(new StyleShape(new CubicCurve2D.Float(x1 * zoom, y1 * zoom, ctrlx1 * zoom, ctrly1 * zoom, ctrlx2 * zoom, ctrly2 * zoom, x2 * zoom, y2 * zoom), tmpLineType, tmpLineThickness, tmpFgColor, tmpBgColor, tmpAlpha)); } @CustomFunction(param_defaults = "x1, y1, ctrlx, ctrly, x2, y2") protected final void drawCurveQuad(float x1, float y1, float ctrlx, float ctrly, float x2, float y2) { shapes.add(new StyleShape(new QuadCurve2D.Float(x1 * zoom, y1 * zoom, ctrlx * zoom, ctrly * zoom, x2 * zoom, y2 * zoom), tmpLineType, tmpLineThickness, tmpFgColor, tmpBgColor, tmpAlpha)); } @CustomFunction(param_defaults = "x, y, radiusX, radiusYs") protected final void drawEllipse(int x, int y, int radiusX, int radiusY) { shapes.add(new StyleShape(new Ellipse2D.Float((int) ((x - radiusX) * zoom), (int) ((y - radiusY) * zoom), (int) (radiusX * 2 * zoom), (int) (radiusY * 2 * zoom)), tmpLineType, tmpLineThickness, tmpFgColor, tmpBgColor, tmpAlpha)); } @CustomFunction(param_defaults = "x1, y1, x2, y2") protected final void drawLine(int x1, int y1, int x2, int y2) { shapes.add(new StyleShape(new Line2D.Float((int) (x1 * zoom), (int) (y1 * zoom), (int) (x2 * zoom), (int) (y2 * zoom)), tmpLineType, tmpLineThickness, tmpFgColor, tmpBgColor, tmpAlpha)); } @CustomFunction(param_defaults = "y") protected final void drawLineHorizontal(int y) { shapes.add(new StyleShape(new Line2D.Float((int) (0 * zoom), (int) (y * zoom), HandlerElementMap.getHandlerForElement(this).realignToGrid(false, (int) (width * zoom), true), (int) (y * zoom)), tmpLineType, tmpLineThickness, tmpFgColor, tmpBgColor, tmpAlpha)); } @CustomFunction(param_defaults = "x") protected final void drawLineVertical(int x) { shapes.add(new StyleShape(new Line2D.Float((int) (x * zoom), (int) (0 * zoom), (int) (x * zoom), HandlerElementMap.getHandlerForElement(this).realignToGrid(false, (int) (height * zoom), true)), tmpLineType, tmpLineThickness, tmpFgColor, tmpBgColor, tmpAlpha)); } @CustomFunction(param_defaults = "polygon") protected final void drawPolygon(Polygon polygon) { for (int i = 0; i < polygon.xpoints.length; i++) { polygon.xpoints[i] *= zoom; } for (int i = 0; i < polygon.ypoints.length; i++) { polygon.ypoints[i] *= zoom; } shapes.add(new StyleShape(polygon, tmpLineType, tmpLineThickness, tmpFgColor, tmpBgColor, tmpAlpha)); } @CustomFunction(param_defaults = "x, y, width, height") protected final void drawRectangle(int x, int y, int width, int height) { shapes.add(new StyleShape(new Rectangle((int) (x * zoom), (int) (y * zoom), (int) (width * zoom), (int) (height * zoom)), tmpLineType, tmpLineThickness, tmpFgColor, tmpBgColor, tmpAlpha)); } @CustomFunction(param_defaults = "x, y, width, height, arcw, arch") protected final void drawRectangleRound(int x, int y, int width, int height, float arcw, float arch) { shapes.add(new StyleShape(new RoundRectangle2D.Float((int) (x * zoom), (int) (y * zoom), (int) (width * zoom), (int) (height * zoom), arcw * zoom, arch * zoom), tmpLineType, tmpLineThickness, tmpFgColor, tmpBgColor, tmpAlpha)); } // EXAMPLE: drawShape(new java.awt.geom.RoundRectangle2D.Float(10,10, 50, 40, 15,15)); // WARNING: Shapes aren't zoomed automatically @CustomFunction(param_defaults = "shape") protected final void drawShape(Shape shape) { shapes.add(new StyleShape(shape, tmpLineType, tmpLineThickness, tmpFgColor, tmpBgColor, tmpAlpha)); } /* STYLING METHODS */ @CustomFunction(param_defaults = "lineType") protected final void setLineType(int lineType) { if (lineType == 0) { tmpLineType = LineType.SOLID; } else if (lineType == 1) { tmpLineType = LineType.DASHED; } else if (lineType == 2) { tmpLineType = LineType.DOTTED; } else if (lineType == 3) { tmpLineType = LineType.DOUBLE; } else if (lineType == 4) { tmpLineType = LineType.DOUBLE_DASHED; } else if (lineType == 5) { tmpLineType = LineType.DOUBLE_DOTTED; } else { tmpLineType = LineType.SOLID; } } @CustomFunction(param_defaults = "lineThickness") protected final void setLineThickness(int lineThickness) { tmpLineThickness = lineThickness; } @CustomFunction(param_defaults = "foregroundColor") protected final void setForegroundColor(String fgColorString) { tmpFgColor = Converter.convert(ColorOwn.forStringOrNull(fgColorString, Transparency.FOREGROUND)); if (tmpFgColor == null) { tmpFgColor = fgColor; // unknown colors resolve to default color } } @CustomFunction(param_defaults = "backgroundColor") protected final void setBackgroundColor(String bgColorString) { // OldGridElements apply transparency for background explicitly, therefore don't apply it here tmpBgColor = Converter.convert(ColorOwn.forStringOrNull(bgColorString, Transparency.FOREGROUND)); if (tmpBgColor == null) { tmpBgColor = bgColor; // unknown colors resolve to default color } // Transparency is 0% if none or 50% if anything else if (bgColorString.equals("none")) { tmpAlpha = OldGridElement.ALPHA_FULL_TRANSPARENCY; } else { tmpAlpha = OldGridElement.ALPHA_MIDDLE_TRANSPARENCY; } } @CustomFunction(param_defaults = "") protected final void resetAll() { tmpLineThickness = (int) FacetConstants.LINE_WIDTH_DEFAULT; tmpLineType = LineType.SOLID; tmpFgColor = fgColor; tmpBgColor = bgColor; tmpAlpha = alphaFactor; } protected final int textHeight() { return (int) (HandlerElementMap.getHandlerForElement(this).getFontHandler().getFontSize(false) + HandlerElementMap.getHandlerForElement(this).getFontHandler().getDistanceBetweenTexts(false)); } protected final int textWidth(String text, boolean applyZoom) { return (int) (HandlerElementMap.getHandlerForElement(this).getFontHandler().getTextSize(text, applyZoom).getWidth() + (int) HandlerElementMap.getHandlerForElement(this).getFontHandler().getDistanceBetweenTexts(applyZoom)); } protected final int textWidth(String text) { return textWidth(text, false); } protected final void allowResize(boolean allow) { allowResize = allow; } @Override public Set<Direction> getResizeArea(int x, int y) { if (allowResize) { return super.getResizeArea(x, y); } else { return new HashSet<Direction>(); } } private static List<String> splitString(String text, float width, DiagramHandler handler) { StringBuilder stringBuilder = new StringBuilder(text); int lastEmptyChar = -1; // is -1 if there was no ' ' in this line int firstCharInLine = 0; for (int i = 0; i < text.length(); i++) { if (stringBuilder.charAt(i) == ' ') { lastEmptyChar = i; } else if (stringBuilder.charAt(i) == '\n') { lastEmptyChar = -1; firstCharInLine = i + 1; } if (handler.getFontHandler().getTextWidth(text.substring(firstCharInLine, i), false) + 15 > width) { if (lastEmptyChar != -1) { stringBuilder.setCharAt(lastEmptyChar, '\n'); firstCharInLine = lastEmptyChar + 1; lastEmptyChar = -1; } else { stringBuilder.insert(i, '\n'); firstCharInLine = i + 1; } } } return Arrays.asList(stringBuilder.toString().split("\\n")); } @Override public boolean isDeprecated() { return false; } }