/******************************************************************************* * Copyright (c) 2016 Weasis Team and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Nicolas Roduit - initial API and implementation *******************************************************************************/ package org.weasis.core.ui.model.graphic.imp; import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.Rectangle; import java.awt.event.KeyEvent; import java.awt.font.FontRenderContext; import java.awt.font.TextLayout; import java.awt.geom.AffineTransform; import java.awt.geom.Area; import java.awt.geom.Line2D; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.List; import java.util.Optional; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementWrapper; import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlType; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import org.weasis.core.api.gui.util.GeomUtil; import org.weasis.core.api.util.StringUtil; import org.weasis.core.ui.Messages; import org.weasis.core.ui.editor.image.ViewCanvas; import org.weasis.core.ui.model.graphic.AbstractDragGraphic; import org.weasis.core.ui.model.graphic.Graphic; import org.weasis.core.ui.model.utils.bean.AdvancedShape; import org.weasis.core.ui.model.utils.bean.AdvancedShape.BasicShape; import org.weasis.core.ui.model.utils.bean.AdvancedShape.ScaleInvariantShape; import org.weasis.core.ui.model.utils.exceptions.InvalidShapeException; import org.weasis.core.ui.model.utils.imp.DefaultGraphicLabel; import org.weasis.core.ui.serialize.RectangleAdapter; import org.weasis.core.ui.util.MouseEventDouble; @XmlType(name = "annotation") @XmlRootElement(name = "annotation") @XmlAccessorType(XmlAccessType.NONE) public class AnnotationGraphic extends AbstractDragGraphic { private static final long serialVersionUID = -6993299250389257151L; public static final Integer POINTS_NUMBER = 2; public static final Icon ICON = new ImageIcon(AnnotationGraphic.class.getResource("/icon/22x22/draw-text.png")); //$NON-NLS-1$ protected Point2D ptBox; protected Point2D ptAnchor; // Let AB be a simple a line segment protected String[] labels; protected Boolean lineABvalid; // estimate if line segment is valid or not protected Rectangle2D labelBounds; protected Double labelWidth; protected Double labelHeight; public AnnotationGraphic() { super(POINTS_NUMBER); } public AnnotationGraphic(AnnotationGraphic annotationGraphic) { super(annotationGraphic); } @Override protected void initCopy(Graphic graphic) { super.initCopy(graphic); if (graphic instanceof AnnotationGraphic) { AnnotationGraphic annotationGraphic = (AnnotationGraphic) graphic; labels = Optional.ofNullable(annotationGraphic.labels).map(l -> l.clone()).orElse(null); labelBounds = Optional.ofNullable(annotationGraphic.labelBounds).map(lb -> lb.getBounds2D()).orElse(null); labelWidth = annotationGraphic.labelWidth; labelHeight = annotationGraphic.labelHeight; } } @Override public AnnotationGraphic copy() { return new AnnotationGraphic(this); } @Override protected void prepareShape() throws InvalidShapeException { if (!isShapeValid()) { throw new InvalidShapeException("This shape cannot be drawn"); //$NON-NLS-1$ } // Do not build shape as labelBounds can be initialize only by the method setLabel() } protected void setHandlePointList(Point2D.Double ptAnchor, Point2D.Double ptBox) { Point2D.Double pt2 = (ptBox == null && ptAnchor != null) ? ptAnchor : ptBox; Point2D.Double pt1 = (pt2 != null && pt2.equals(ptAnchor)) ? null : ptAnchor; setHandlePoint(0, pt1 == null ? null : (Point2D.Double) pt1.clone()); setHandlePoint(1, pt2 == null ? null : (Point2D.Double) pt2.clone()); buildShape(null); } @Override public Icon getIcon() { return ICON; } @Override public String getUIName() { return Messages.getString("Tools.Anno"); //$NON-NLS-1$ } @XmlElementWrapper(name = "labels") @XmlElement(name = "label") public String[] getLabels() { return labels; } public void setLabels(String[] labels) { this.labels = labels; } @XmlElement(name = "labelBounds") @XmlJavaTypeAdapter(RectangleAdapter.Rectangle2DAdapter.class) public Rectangle2D getLabelBounds() { return labelBounds; } public void setLabelBounds(Rectangle2D labelBounds) { this.labelBounds = labelBounds; } @XmlAttribute(name = "labelWidth") public Double getLabelWidth() { return labelWidth; } public void setLabelWidth(Double labelWidth) { this.labelWidth = labelWidth; } @XmlAttribute(name = "labelHeight") public Double getLabelHeight() { return labelHeight; } public void setLabelHeight(Double labelHeight) { this.labelHeight = labelHeight; } @Override public void updateLabel(ViewCanvas<?> view2d, Point2D pos, boolean releasedEvent) { setLabel(labels, view2d, pos); } @Override public void updateLabel(Object source, ViewCanvas<?> view2d) { setLabel(labels, view2d); } @Override public void buildShape(MouseEventDouble mouseEvent) { updateTool(); AdvancedShape newShape = null; if (ptBox != null) { ViewCanvas<?> view = getDefaultView2d(mouseEvent); if (labels == null) { if (view != null) { setLabel(new String[] { getInitialText(view) }, view, ptBox); // call buildShape return; } if (labelHeight == 0 || labelWidth == 0) { // This graphic cannot be displayed, remove it. fireRemoveAction(); return; } } newShape = new AdvancedShape(this, 2); Line2D line = null; if (lineABvalid) { line = new Line2D.Double(ptBox, ptAnchor); } labelBounds = new Rectangle.Double(); labelBounds.setFrameFromCenter(ptBox.getX(), ptBox.getY(), ptBox.getX() + labelWidth / 2.0 + DefaultGraphicLabel.GROWING_BOUND, ptBox.getY() + labelHeight * (labels == null ? 1 : labels.length) / 2.0 + DefaultGraphicLabel.GROWING_BOUND); GeomUtil.growRectangle(labelBounds, DefaultGraphicLabel.GROWING_BOUND); if (line != null) { newShape.addLinkSegmentToInvariantShape(line, ptBox, labelBounds, getDashStroke(lineThickness), false); ScaleInvariantShape arrow = newShape.addScaleInvShape(GeomUtil.getArrowShape(ptAnchor, ptBox, 15, 8), ptAnchor, getStroke(lineThickness), false); arrow.setFilled(true); } newShape.addAllInvShape(labelBounds, ptBox, getStroke(lineThickness), false); } setShape(newShape, mouseEvent); } @Override public int getKeyCode() { return KeyEvent.VK_B; } @Override public int getModifier() { return 0; } protected void updateTool() { ptAnchor = getHandlePoint(0); ptBox = getHandlePoint(1); lineABvalid = ptAnchor != null && !ptAnchor.equals(ptBox); } protected String getInitialText(ViewCanvas<?> view) { return Messages.getString("AnnotationGraphic.text_box"); //$NON-NLS-1$ } @Override public void paintLabel(Graphics2D g2d, AffineTransform transform) { if (labelVisible && labels != null && labelBounds != null) { Paint oldPaint = g2d.getPaint(); Rectangle2D rect = labelBounds; Point2D pt = new Point2D.Double(rect.getCenterX(), rect.getCenterY()); if (transform != null) { transform.transform(pt, pt); } float px = (float) (pt.getX() - rect.getWidth() / 2 + DefaultGraphicLabel.GROWING_BOUND); float py = (float) (pt.getY() - rect.getHeight() / 2 + DefaultGraphicLabel.GROWING_BOUND); for (String label : labels) { if (StringUtil.hasText(label)) { py += labelHeight; DefaultGraphicLabel.paintColorFontOutline(g2d, label, px, py, Color.WHITE); } } g2d.setPaint(oldPaint); } } @Override public Area getArea(AffineTransform transform) { if (shape == null) { return new Area(); } if (shape instanceof AdvancedShape) { AdvancedShape s = (AdvancedShape) shape; Area area = s.getArea(transform); List<BasicShape> list = s.getShapeList(); if (!list.isEmpty()) { BasicShape b = list.get(list.size() - 1); // Allow to move inside the box, not only around stroke. area.add(new Area(b.getRealShape())); } return area; } else { return super.getArea(transform); } } public Point2D getAnchorPoint() { updateTool(); return ptAnchor == null ? null : (Point2D) ptAnchor.clone(); } public Point2D getBoxPoint() { updateTool(); return ptBox == null ? null : (Point2D) ptBox.clone(); } protected void reset() { labels = null; labelBounds = null; labelHeight = labelWidth = 0d; } @Override public void setLabel(String[] labels, ViewCanvas<?> view2d) { Point2D pt = getBoxPoint(); if (pt == null) { pt = getAnchorPoint(); } if (pt != null) { this.setLabel(labels, view2d, pt); } } @Override public void setLabel(String[] labels, ViewCanvas<?> view2d, Point2D pos) { if (view2d == null || labels == null || labels.length == 0 || pos == null) { reset(); } else { Graphics2D g2d = (Graphics2D) view2d.getJComponent().getGraphics(); if (g2d == null) { return; } this.labels = labels; Font defaultFont = g2d.getFont(); FontRenderContext fontRenderContext = ((Graphics2D) view2d.getJComponent().getGraphics()).getFontRenderContext(); updateBoundsSize(defaultFont, fontRenderContext); labelBounds = new Rectangle.Double(); labelBounds.setFrameFromCenter(pos.getX(), pos.getY(), ptBox.getX() + labelWidth / 2.0 + DefaultGraphicLabel.GROWING_BOUND, ptBox.getY() + labelHeight * this.labels.length / 2.0 + DefaultGraphicLabel.GROWING_BOUND); GeomUtil.growRectangle(labelBounds, DefaultGraphicLabel.GROWING_BOUND); } buildShape(null); } protected void updateBoundsSize(Font defaultFont, FontRenderContext fontRenderContext) { Optional.ofNullable(defaultFont).orElseThrow(() -> new RuntimeException("Font should not be null")); //$NON-NLS-1$ Optional.ofNullable(fontRenderContext) .orElseThrow(() -> new RuntimeException("FontRenderContext should not be null")); //$NON-NLS-1$ if (labels == null || labels.length == 0) { reset(); } else { double maxWidth = 0; for (String label : labels) { if (StringUtil.hasText(label)) { TextLayout layout = new TextLayout(label, defaultFont, fontRenderContext); maxWidth = Math.max(layout.getBounds().getWidth(), maxWidth); } } labelHeight = new TextLayout("Tg", defaultFont, fontRenderContext).getBounds().getHeight() + 2; //$NON-NLS-1$ labelWidth = maxWidth; } } }