X-Git-Url: https://gerrit.simantics.org/r/gitweb?a=blobdiff_plain;ds=sidebyside;f=bundles%2Forg.simantics.diagram%2Fsrc%2Forg%2Fsimantics%2Fdiagram%2Felements%2FTextNode.java;h=4554decee2c01a41b032be9dab5c77ecec2af2f7;hb=b141b4ad0251d4a0a86a2b677a5b0eb8f5a6107e;hp=b407796e7b814736614dc620e0274fc4987b3aa1;hpb=d1860919acc0768cf723960406d2bea078bcb621;p=simantics%2Fplatform.git
diff --git a/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/TextNode.java b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/TextNode.java
index b407796e7..4554decee 100644
--- a/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/TextNode.java
+++ b/bundles/org.simantics.diagram/src/org/simantics/diagram/elements/TextNode.java
@@ -1,2032 +1,1968 @@
-/*******************************************************************************
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management
- * in Industry THTH ry.
- * 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:
- * VTT Technical Research Centre of Finland - initial API and implementation
- *******************************************************************************/
-package org.simantics.diagram.elements;
-
-import java.awt.AlphaComposite;
-import java.awt.BasicStroke;
-import java.awt.Color;
-import java.awt.Composite;
-import java.awt.Font;
-import java.awt.FontMetrics;
-import java.awt.Graphics2D;
-import java.awt.RenderingHints;
-import java.awt.Shape;
-import java.awt.Toolkit;
-import java.awt.datatransfer.Clipboard;
-import java.awt.datatransfer.DataFlavor;
-import java.awt.datatransfer.StringSelection;
-import java.awt.datatransfer.Transferable;
-import java.awt.event.KeyEvent;
-import java.awt.font.FontRenderContext;
-import java.awt.font.LineBreakMeasurer;
-import java.awt.font.TextAttribute;
-import java.awt.font.TextHitInfo;
-import java.awt.font.TextLayout;
-import java.awt.geom.AffineTransform;
-import java.awt.geom.Point2D;
-import java.awt.geom.Rectangle2D;
-import java.io.IOException;
-import java.text.AttributedCharacterIterator;
-import java.text.AttributedString;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Hashtable;
-import java.util.List;
-
-import org.simantics.datatypes.literal.RGB;
-import org.simantics.db.layer0.variable.RVI;
-import org.simantics.diagram.elements.Line.BoundsProcedure;
-import org.simantics.g2d.canvas.ICanvasContext;
-import org.simantics.g2d.element.IElement;
-import org.simantics.scenegraph.IDynamicSelectionPainterNode;
-import org.simantics.scenegraph.LoaderNode;
-import org.simantics.scenegraph.ScenegraphUtils;
-import org.simantics.scenegraph.g2d.G2DNode;
-import org.simantics.scenegraph.g2d.G2DPDFRenderingHints;
-import org.simantics.scenegraph.g2d.events.Event;
-import org.simantics.scenegraph.g2d.events.EventTypes;
-import org.simantics.scenegraph.g2d.events.KeyEvent.KeyPressedEvent;
-import org.simantics.scenegraph.g2d.events.MouseEvent;
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseButtonPressedEvent;
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseClickEvent;
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDoubleClickedEvent;
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseDragBegin;
-import org.simantics.scenegraph.g2d.events.MouseEvent.MouseMovedEvent;
-import org.simantics.scenegraph.g2d.events.NodeEventHandler;
-import org.simantics.scenegraph.g2d.events.command.CommandEvent;
-import org.simantics.scenegraph.g2d.events.command.Commands;
-import org.simantics.scenegraph.utils.GeometryUtils;
-import org.simantics.scenegraph.utils.NodeUtil;
-import org.simantics.scl.runtime.function.Function1;
-import org.simantics.scl.runtime.function.Function2;
-import org.simantics.ui.colors.Colors;
-import org.simantics.ui.dnd.LocalObjectTransferable;
-import org.simantics.ui.dnd.MultiTransferable;
-import org.simantics.ui.dnd.PlaintextTransfer;
-import org.simantics.ui.fonts.Fonts;
-import org.simantics.utils.threads.AWTThread;
-
-import com.lowagie.text.DocumentException;
-import com.lowagie.text.Element;
-import com.lowagie.text.Rectangle;
-import com.lowagie.text.pdf.FontMapper;
-import com.lowagie.text.pdf.PdfFormField;
-import com.lowagie.text.pdf.PdfWriter;
-import com.lowagie.text.pdf.TextField;
-
-import gnu.trove.list.array.TIntArrayList;
-
-
-/**
- * TextNode which supports in-line editing.
- *
- * By default TextNode
is in editable = false state. Use
- * {@link #setEditable(boolean)} to make it editable.
- *
- * @author Hannu Niemistö
- * @author Marko Luukkainen
- * @author Tuukka Lehtonen
- *
- * TODO:
- * o proper support for defining clipping bounds for the text (needed for page templates) (currently through fixedWidth)
- * o fix editing xOffset to work with fixed width and multi-line text
- * o
- *
- * @see Line
- * @see TextLayout
- */
-public class TextNode extends G2DNode implements IDynamicSelectionPainterNode, LoaderNode {
-
- private static final long serialVersionUID = 654692698101485672L;
-
- /**
- * TODO: justify existence for this
- */
- private static final BasicStroke RESET_STROKE = new BasicStroke(1);
-
- /**
- * Src-over alpha composite instance with 50% opacity.
- */
- private static final AlphaComposite SrcOver_50 = AlphaComposite.SrcOver.derive(0.5f);
-
- /**
- * For (inexact) measurement of rendered text bounds.
- */
- protected static final FontRenderContext FRC = new FontRenderContext(new AffineTransform(), true, true);
-
- private static final Font FONT = Font.decode("Arial 6");
- private static final Color SELECTION_BACKGROUND_COLOR = new Color(0x316ac5);
-// private static final double MAX_CARET_POSITION = 1.0;
-
- /**
- * The complete text visualized by this node.
- */
- protected String text = null;
-
- /**
- * The font used to render the {@link #text}.
- */
- protected Font font = FONT;
-
- /**
- * The color of the rendered text. Default value is {@value Color#black}.
- */
- protected Color color = Color.BLACK;
-
- /**
- * The background color used for filling the background of the bounding box
- * of the rendered text. null
means no fill.
- * Default value is null
.
- */
- protected Color backgroundColor = null;
-
- /**
- * The color used for drawing the expanded bounding box border for the
- * rendered text. null
means no border is rendered. Default
- * value is null
.
- */
- protected Color borderColor = null;
-
- protected double scale = 1.0;
- protected transient double scaleRecip = 1.0;
-
- /**
- *
- */
- protected float borderWidth = 0.f;
-
- protected double paddingX = 2.0;
- protected double paddingY = 2.0;
-
- /**
- * Horizontal text box alignment with respect to its origin. Default value is
- * 0 (leading).
- */
- protected byte horizontalAlignment = 0;
- /**
- * Vertical text box alignment with respect to its origin. Default value is
- * 3 (baseline).
- */
- protected byte verticalAlignment = 3;
-
- /**
- * Tells if this node is still pending for real results or not.
- */
- protected static final int STATE_PENDING = (1 << 0);
- protected static final int STATE_HOVER = (1 << 1);
- protected static final int STATE_EDITABLE = (1 << 2);
- protected static final int STATE_SHOW_SELECTION = (1 << 3);
- protected static final int STATE_WRAP_TEXT = (1 << 4);
- protected transient static final int STATE_EDITING = (1 << 5);
- protected transient static final int STATE_VALID = (1 << 6);
- protected transient static final int STATE_X_OFFSET_IS_DIRTY = (1 << 7);
- protected static final int STATE_ALWAYS_ADD_LISTENERS = (1 << 8);
- protected static final int STATE_LISTENERS_ADDED = (1 << 9);
-
- /**
- * A combination of all the STATE_ constants defined in this class,
- * e.g. {@link #STATE_PENDING}.
- */
- protected int state = STATE_SHOW_SELECTION | STATE_WRAP_TEXT | STATE_VALID | STATE_X_OFFSET_IS_DIRTY;
-
- protected RVI dataRVI = null;
-
- int caret = 0;
- int selectionTail = 0;
- float xOffset = 0;
-
- float fixedWidth = 0f;
-
- private Rectangle2D targetBounds;
-
- Function1 validator;
- ITextListener textListener;
- ITextContentFilter editContentFilter;
-
- /**
- * The renderable line structures parsed from {@link #text} by
- * {@link #parseLines(String)}, laid out by
- * {@link #layoutLines(Line[], FontRenderContext)} and aligned by
- * {@link #alignLines(Line[], Rectangle2D, byte, byte)}
- */
- protected transient Line[] lines = null;
- protected transient FontMetrics fontMetrics = null;
-
- /**
- * Stores the value of {@link #text} before edit mode was last entered. Used
- * for restoring the original value if editing is cancelled.
- */
- private transient String textBeforeEdit = null;
- protected transient TextEditActivation editActivation;
-
- /**
- * Stores the last scaled bounds.
- */
- private transient Rectangle2D lastBounds = new Rectangle2D.Double();
-
- /**
- * This must be nullified if anything that affects the result of
- * {@link #getTightAlignedBoundsInLocal(Rectangle2D, FontRenderContext)}
- * changes. It will cause the cached value to be recalculated on the next
- * request.
- */
- private transient Rectangle2D tightBoundsCache = null;
-
- @Override
- public void init() {
- super.init();
- // Mark this node as pending
- NodeUtil.increasePending(this);
- }
-
- @Override
- public void cleanup() {
- removeListeners();
- super.cleanup();
- }
-
- protected boolean hasState(int flags) {
- return (state & flags) == flags;
- }
-
- protected void setState(int flags) {
- this.state |= flags;
- }
-
- protected void setState(int flags, boolean set) {
- if (set)
- this.state |= flags;
- else
- this.state &= ~flags;
- }
-
- protected void clearState(int flags) {
- this.state &= ~flags;
- }
-
- protected void setListeners(boolean add) {
- if (add)
- addListeners();
- else
- removeListeners();
- }
-
- protected void addListeners() {
- if (!hasState(STATE_LISTENERS_ADDED)) {
- addEventHandler(this);
- setState(STATE_LISTENERS_ADDED);
- }
- }
-
- protected void removeListeners() {
- if (hasState(STATE_LISTENERS_ADDED)) {
- removeEventHandler(this);
- clearState(STATE_LISTENERS_ADDED);
- }
- }
-
- /**
- * Set to true to always enable event listening in this TextNode to allow the text node to keep track of hovering, etc. and to allow DnD even when
- * @param force
- */
- public void setForceEventListening(boolean force) {
- setState(STATE_ALWAYS_ADD_LISTENERS, force);
- if (force && !hasState(STATE_EDITABLE)) {
- setListeners(force);
- }
- }
-
- /**
- * Enables or disables edit mode. It also sets
- * the caret at the end of text all selects the
- * whole text (this is the usual convention when
- * beginning to edit one line texts).
- * @param edit
- * @return null if no change to edit state was made
- */
- public Boolean setEditMode(boolean edit) {
- return setEditMode(edit, true);
- }
-
- /**
- * Enables or disables edit mode. It also sets
- * the caret at the end of text all selects the
- * whole text (this is the usual convention when
- * beginning to edit one line texts).
- * @param edit
- * @return null if no change to edit state was made
- */
- protected Boolean setEditMode(boolean edit, boolean notify) {
- if (edit && !hasState(STATE_EDITABLE))
- return null;
- if (hasState(STATE_EDITING) == edit)
- return null;
- setState(STATE_EDITING, edit);
- if (edit) {
- caret = text != null ? text.length() : 0;
- selectionTail = 0;
- textBeforeEdit = text;
- if (notify)
- fireTextEditingStarted();
- return Boolean.TRUE;
- } else {
- if (notify)
- fireTextEditingEnded();
- return Boolean.FALSE;
- }
- }
-
- @SyncField({"editable"})
- public void setEditable(boolean editable) {
- boolean changed = hasState(STATE_EDITABLE) != editable;
- setState(STATE_EDITABLE, editable);
- if (hasState(STATE_EDITING) && !editable)
- setEditMode(false);
- if (changed && !hasState(STATE_ALWAYS_ADD_LISTENERS)) {
- setListeners(editable);
- }
- }
-
- public boolean isEditable() {
- return hasState(STATE_EDITABLE);
- }
-
- public boolean isEditMode() {
- return hasState(STATE_EDITING);
- }
-
- @SyncField({"wrapText"})
- public void setWrapText(boolean wrapText) {
- setState(STATE_WRAP_TEXT, wrapText);
- }
-
- /**
- * @return Does the text box wrap text if
- * the width of the box is fixed
- */
- public boolean isWrapText() {
- return hasState(STATE_WRAP_TEXT);
- }
-
- @SyncField({"showSelection"})
- public void setShowSelection(boolean showSelection) {
- setState(STATE_SHOW_SELECTION, showSelection);
- }
-
- public boolean showsSelection() {
- return hasState(STATE_SHOW_SELECTION);
- }
-
- /**
- * @param text
- * @param font
- * @param color
- * @param x not supported anymore, use {@link #setTransform(AffineTransform)} instead
- * @param y not supported anymore, use {@link #setTransform(AffineTransform)} instead
- * @param scale
- */
- @SyncField({"text", "font", "color", "x", "y", "scale"})
- public void init(String text, Font font, Color color, double x, double y, double scale) {
- // no value => value
- if(this.text == null && text != null) NodeUtil.decreasePending(this);
-
- if (hasState(STATE_EDITING))
- return;
-
- this.text = new String(text != null ? text : "");
- this.font = font;
- this.color = color;
- this.scale = scale;
- this.scaleRecip = 1.0 / scale;
- this.caret = 0;
- this.selectionTail = 0;
-
- resetCaches();
- }
-
- @SyncField({"paddingX", "paddingY"})
- public void setPadding(double x, double y) {
- this.paddingX = x;
- this.paddingY = y;
- }
-
- @SyncField({"color"})
- public void setColor(Color color) {
- this.color = color;
- }
-
- @SyncField({"backgroundColor"})
- public void setBackgroundColor(Color color) {
- this.backgroundColor = color;
- }
-
- @SyncField({"borderColor"})
- public void setBorderColor(Color color) {
- this.borderColor = color;
- }
-
- public String getText() {
- return text;
- }
-
- public String getTextBeforeEdit() {
- return textBeforeEdit;
- }
-
- @SyncField({"text","caret","selectionTail"})
- public void setText(String text) {
- //System.out.println("TextNode.setText('" + text + "', " + editing + ")");
- if (hasState(STATE_EDITING))
- return;
-
- // value => no value
- if(this.text != null && text == null) NodeUtil.increasePending(this);
- // no value => value
- if(this.text == null && text != null) NodeUtil.decreasePending(this);
-
- this.text = text != null ? text : "";
- caret = Math.min(caret, this.text.length());
- selectionTail = caret;
-
- resetCaches();
- }
-
- @SyncField({"pending"})
- public void setPending(boolean pending) {
- boolean p = hasState(STATE_PENDING);
- if(!p && pending) NodeUtil.increasePending(this);
- if(p && !pending) NodeUtil.decreasePending(this);
- if(p != pending)
- setState(STATE_PENDING, pending);
- }
-
- @SyncField({"fixedWidth"})
- public void setFixedWidth(float fixedWidth) {
- if (fixedWidth < 0f)
- throw new IllegalArgumentException("negative fixed width");
- this.fixedWidth = fixedWidth;
- invalidateXOffset();
- }
-
- /**
- * Bounds where the text box will be drawn
- * @param bounds
- */
- public void setTargetBounds(Rectangle2D bounds) {
- this.targetBounds = bounds;
- }
-
- final public void synchronizeWidth(float width) {
- if (width >= 0.0f)
- setFixedWidth(width);
- }
-
- final public void synchronizeBorderWidth(float width) {
- if (width >= 0.0f)
- setBorderWidth(width);
- }
-
- public final void synchronizeWrapText(boolean wrap) {
- setState(STATE_WRAP_TEXT, wrap);
- }
-
- public boolean isHovering() {
- return hasState(STATE_HOVER);
- }
-
- @SyncField({"hover"})
- public void setHover(boolean hover) {
- setState(STATE_HOVER, hover);
- repaint();
- }
-
- public Font getFont() {
- return font;
- }
-
- @SyncField({"font"})
- public void setFont(Font font) {
- this.font = font;
- resetCaches();
- }
-
- public double getBorderWidth() {
- return borderWidth;
- }
-
- @SyncField({"borderWidth"})
- public void setBorderWidth(float width) {
- this.borderWidth = width;
- }
-
- public void setBorderWidth(double width) {
- setBorderWidth((float)width);
- }
-
- @SyncField({"horizontalAlignment"})
- public void setHorizontalAlignment(byte horizontalAlignment) {
- if (horizontalAlignment < 0 && horizontalAlignment > 2)
- throw new IllegalArgumentException("Invalid horizontal alignment: " + horizontalAlignment + ", must be between 0 and 2");
- this.horizontalAlignment = horizontalAlignment;
- resetCaches();
- }
-
- final public void synchronizeHorizontalAlignment(byte horizontalAlignment) {
- if (horizontalAlignment >= 0 && horizontalAlignment <= 2)
- setHorizontalAlignment(horizontalAlignment);
- }
-
- public byte getHorizontalAlignment() {
- return horizontalAlignment;
- }
-
- @SyncField({"verticalAlignment"})
- public void setVerticalAlignment(byte verticalAlignment) {
- if (verticalAlignment < 0 && verticalAlignment > 3)
- throw new IllegalArgumentException("Invalid vertical alignment: " + verticalAlignment + ", must be between 0 and 3");
- this.verticalAlignment = verticalAlignment;
- resetCaches();
- }
-
- final public void synchronizeVerticalAlignment(byte verticalAlignment) {
- if (verticalAlignment >= 0 && verticalAlignment <= 3)
- setVerticalAlignment(verticalAlignment);
- }
-
- public byte getVerticalAlignment() {
- return verticalAlignment;
- }
-
- /**
- * Rendering is single-threaded so we can use a static rectangle for
- * calculating the expanded bounds for the node.
- */
- private static transient ThreadLocal tempBounds = new ThreadLocal() {
- @Override
- protected Rectangle2D initialValue() {
- return new Rectangle2D.Double();
- }
- };
-
- /**
- * Rendering is single-threaded so we can use a static AffineTransform to
- * prevent continuous memory allocation during text rendering.
- */
- private static transient ThreadLocal tempAffineTransform = new ThreadLocal() {
- @Override
- protected AffineTransform initialValue() {
- return new AffineTransform();
- }
- };
-
- @Override
- public void render(Graphics2D g) {
- AffineTransform ot = g.getTransform();
- render(g, true);
- g.setTransform(ot);
- }
-
- /**
- * Note: does not return transformation, stroke, color, etc. to their
- * original states
- *
- * @param g
- * @param applyTransform
- */
- public void render(Graphics2D g, boolean applyTransform) {
- if (text == null || font == null || color == null)
- return;
-
- // Cache font metrics if necessary
- if (fontMetrics == null)
- fontMetrics = g.getFontMetrics(font);
-
- Color color = this.color;
- boolean isSelected = NodeUtil.isSelected(this, 1);
- boolean hover = hasState(STATE_HOVER);
- boolean editing = hasState(STATE_EDITING);
-
- if (!isSelected && hover) {
- color = add(color, 120, 120, 120);
- }
-
- if (applyTransform)
- g.transform(transform);
- // Apply separate legacy scale
- if (scale != 1.0)
- g.scale(scale, scale);
-
- // Safety for not rendering when the scale of this text is too small.
- // When the scale is too small it will cause internal exceptions while
- // stroking fonts.
- double currentScale = GeometryUtils.getScale(g.getTransform());
- //System.out.println("currentScale: " + currentScale);
- if (currentScale < 1e-6)
- return;
-
- g.setFont(font);
- //g.translate(x, y);
-
- // Calculate text clip rectangle.
- // This updates textLayout if necessary.
- Rectangle2D r = getTightAlignedBoundsInLocal(tempBounds.get(), fontMetrics.getFontRenderContext());
-
- computeEditingXOffset();
-
- if (fixedWidth > 0f)
- r.setFrame(r.getMinX(), r.getMinY(), fixedWidth, r.getHeight());
- if(targetBounds != null) {
- double w = (targetBounds.getWidth() - paddingX * 2) * scaleRecip;
- double h = (targetBounds.getHeight() - paddingY * 2) * scaleRecip;
- double x = (targetBounds.getMinX() + paddingX) * scaleRecip;
- double y = (targetBounds.getMinY() + paddingY) * scaleRecip;
- r.setRect(x, y, w, h);
- }
-
- Rectangle2D textClip = r.getBounds2D();
-
- expandBoundsUnscaled(r);
-
- // Speed rendering optimization: don't draw text that is too small to
- // read when not editing
- boolean renderText = true;
- if (!editing) {
- Object renderingHint = g.getRenderingHint(RenderingHints.KEY_RENDERING);
- if (renderingHint != RenderingHints.VALUE_RENDER_QUALITY) {
- float textSizeMM = (float) currentScale * GeometryUtils.pointToMillimeter(font.getSize2D());
- if (textSizeMM < 1.5f)
- renderText = false;
- }
- }
-
- Shape clipSave = g.getClip();
- g.setClip(textClip);
-
- // PDF
- PdfWriter writer = (PdfWriter) g.getRenderingHint(G2DPDFRenderingHints.KEY_PDF_WRITER);
- boolean isRenderingPdf = writer != null;
- boolean isPdfField = false;
- String fieldName = null;
- if (writer != null) {
- // TODO: replace this hack with proper text field name field
- fieldName = NodeUtil.getNodeName(this);
- isPdfField = ( fieldName.equals("approved_by") ||
- fieldName.equals("checked_by") ||
- fieldName.equals("designer name") ||
- fieldName.equals("created_by") );
- }
-
- Color backgroundColor = hasState(STATE_VALID) ? this.backgroundColor : Color.red;
-
- // RENDER
- if ( !isPdfField ) {
-
- // Fill background if necessary
- if (backgroundColor != null) {
- g.setColor(backgroundColor);
- g.fill(r);
- }
-
- if (editing) {
-
- int selectionMin = Math.min(caret, selectionTail);
- int selectionMax = Math.max(caret, selectionTail);
-
- // Base text
- g.setColor(color);
- renderText(g, xOffset, isRenderingPdf);
-
- Shape clip = g.getClip();
-
- // Selection background & text
- for (Line line : lines) {
- if (line.intersectsRange(selectionMin, selectionMax)) {
- Shape selShape = line.getLogicalHighlightShape(selectionMin, selectionMax);
- line.translate(g, xOffset, 0);
- g.setClip(selShape);
- g.setColor(SELECTION_BACKGROUND_COLOR);
- g.fill(selShape);
- g.setColor(Color.WHITE);
- // #6459: render as text in PDF and paths on screen
- if (isRenderingPdf)
- g.drawString(line.getText(), 0, 0);
- else
- line.layout.draw(g, 0, 0);
- line.translateInv(g, xOffset, 0);
- }
- }
-
- g.setClip(clip);
-
- // Caret
-
- renderCaret(g);
-
-
- } else {
-
- if (renderText) {
- g.setColor(color);
- renderText(g, 0, isRenderingPdf);
- }
-
- }
- } else {
- // PDF
- // TODO: multiline support
-// try {
- AffineTransform at = g.getTransform();
- float height = writer.getPageSize().getHeight();
- Rectangle2D rr = textClip;
- // Point2D pt1 = new Point2D.Double(rr.getX(), rr.getY()+rr.getHeight());
- // Point2D pt2 = new Point2D.Double(rr.getX()+rr.getWidth(), rr.getY());
- Point2D pt1 = new Point2D.Double(0, 0);
- Point2D pt2 = new Point2D.Double(47.f/*+rr.getWidth()*/, -rr.getHeight());
- pt1 = at.transform(pt1, pt1);
- pt2 = at.transform(pt2, pt2);
- Rectangle rectangle = new Rectangle(
- (float) pt1.getX(),
- height-(float) pt1.getY(),
- (float) pt2.getX(),
- height-(float) pt2.getY());
-
- FontMapper mapper = (FontMapper) g.getRenderingHint(G2DPDFRenderingHints.KEY_PDF_FONTMAPPER);
-// FontMetrics fm = g.getFontMetrics(font);
-
- // TODO Oikea leveys
- // TODO Uniikki nimi
- /*
- PdfFormField field = PdfFormField.createTextField(writer, false, false, 20);
- field.setFieldName(this.getId().toString());
- field.setWidget(rectangle, PdfAnnotation.HIGHLIGHT_NONE);
- field.setQuadding(PdfFormField.Q_RIGHT);
- field.setFieldFlags(PdfFormField.FF_READ_ONLY);
- field.setRotate(90);
- writer.addAnnotation(field);
- */
-
-
- // Signature Field
- /*
- if (text==null) {
- PdfFormField field = PdfFormField.createSignature(writer);
- field.setWidget(rectangle, PdfAnnotation.HIGHLIGHT_NONE);
- field.setFieldName(fieldName);
- field.setQuadding(PdfFormField.Q_LEFT);
- field.setFlags(PdfAnnotation.FLAGS_PRINT);
- //field.setFieldFlags(PdfFormField.FF_READ_ONLY)
- field.setFieldFlags(PdfFormField.FF_EDIT);
- field.setPage();
- field.setMKBackgroundColor( backgroundColor!=null?Color.WHITE:backgroundColor );
- PdfAppearance tp = PdfAppearance.createAppearance(writer, 72, 48);
- tp.rectangle(rectangle);
- tp.stroke();
- field.setAppearance(PdfAnnotation.APPEARANCE_NORMAL, tp);
- writer.addAnnotation(field);
- } else */
- {
- // Text Field
- try {
- TextField textField = new TextField(writer, rectangle, fieldName);
- textField.setFieldName(fieldName);
- textField.setFont(mapper.awtToPdf(font));
- textField.setBorderStyle(0);
- //textField.setAlignment(Element.ALIGN_LEFT);
- textField.setAlignment(Element.ALIGN_BOTTOM);
- textField.setRotation(90);
- textField.setOptions(TextField.EDIT|TextField.DO_NOT_SPELL_CHECK);
- if ( text!=null ) {
- textField.setText(text);
- }
- if ( color!=null ) {
- textField.setTextColor(color);
- }
- textField.setBackgroundColor( backgroundColor!=null?Color.WHITE:backgroundColor );
- PdfFormField field = textField.getTextField();
- writer.addAnnotation(field);
- } catch (IOException e) {
- e.printStackTrace();
- } catch (DocumentException e) {
- e.printStackTrace();
- }
- }
-
-// } catch (IOException e) {
-// // TODO Auto-generated catch block
-// e.printStackTrace();
-// } catch (DocumentException e) {
-// // TODO Auto-generated catch block
-// e.printStackTrace();
-// }
- }
- /// PDF
-
- g.setClip(clipSave);
-
- if (borderWidth > 0f && borderColor != null) {
- g.setColor(borderColor);
- g.setStroke(new BasicStroke((float) (scale*borderWidth)));
- g.draw(r);
- }
-
- //System.out.println("bw: " + borderWidth);
- if (isSelected && showsSelection()) {
- Composite oc = g.getComposite();
- g.setComposite(SrcOver_50);
- g.setColor(Color.RED);
- float bw = borderWidth;
- double s = currentScale;
- if (bw <= 0f) {
- bw = (float) (1f / s);
- } else {
- bw *= 5f * scale;
- }
- g.setStroke(new BasicStroke(bw));
- g.draw(r);
- //g.draw(GeometryUtils.expandRectangle(r, 1.0));
-
- g.setComposite(oc);
- }
-
- g.scale(scaleRecip, scaleRecip);
- g.setStroke(RESET_STROKE);
-
- lastBounds = getScaledOffsetBounds(r, lastBounds, scale, 0, 0);
-// g.setColor(Color.MAGENTA); // DEBUG
-// g.draw(lastBounds); // DEBUG
-// g.setColor(Color.ORANGE); // DEBUG
-// g.draw(getBoundsInLocal()); // DEBUG
-
- renderSelectedHover(g, isSelected, hover);
- }
-
- private void renderCaret(Graphics2D g) {
- g.setColor(Color.BLACK);
- for (int i = 0; i < lines.length; i++) {
- Line line = lines[i];
- // prevent rendering caret twice on line changes
- if (line.containsOffset(caret) && // line contains caret
- (caret != line.endOffset || //caret is not in the end of the line
- i == lines.length-1 || //caret is end of the last line
- lines[i+1].startOffset != line.endOffset)) { // beginning of the next line does not start withe the same index as current line
- Shape[] caretShape = line.getCaretShapes(caret);
- line.translate(g, xOffset, 0);
- g.draw(caretShape[0]);
- if (caretShape[1] != null)
- g.draw(caretShape[1]);
- line.translateInv(g, xOffset, 0);
- }
- }
- }
- private void renderText(Graphics2D g, float xOffset, boolean isRenderingPdf) {
- //g.draw(tightBoundsCache); // DEBUG
- for (Line line : lines) {
- // #6459: render as text in PDF and paths on screen
- if (isRenderingPdf)
- g.drawString(line.getText(), line.alignedPosX + xOffset, line.alignedPosY);
- else
- line.layout.draw(g, line.alignedPosX + xOffset, line.alignedPosY);
- //g.draw(line.abbox); // DEBUG
- }
- }
-
- protected Rectangle2D getScaledOffsetBounds(Rectangle2D originalBounds, Rectangle2D dst, double scale, double offsetX, double offsetY) {
- AffineTransform btr = tempAffineTransform.get();
- btr.setToTranslation(offsetX*scale, offsetY*scale);
- btr.scale(scale, scale);
- if (btr.isIdentity()) {
- dst.setFrame(originalBounds);
- } else {
- dst.setFrame(btr.createTransformedShape(originalBounds).getBounds2D());
- }
- return dst;
- }
-
- /**
- * Invoked when TextNode is selected and a mouse is hovering on top of it. Can be overridden to add custom rendering.
- *
- * @param g
- */
- protected void renderSelectedHover(Graphics2D g, boolean isSelected, boolean isHovering) {
- }
-
- /**
- * Replaces the current selection with the content or inserts
- * the content at caret. After the insertion the caret
- * will be at the end of inserted text and selection will
- * be empty.
- * @param content
- */
- @SyncField({"text","caret","selectionTail"})
- protected void insert(String content) {
- content = editContentFilter != null ? editContentFilter.filter(this, content) : content;
-
- int selectionMin = Math.min(caret, selectionTail);
- int selectionMax = Math.max(caret, selectionTail);
-
- String begin = text.substring(0, selectionMin);
- String end = text.substring(selectionMax);
- text = begin + content + end;
- caret = selectionMin + content.length();
- selectionTail = caret;
-
- assert (caret <= text.length());
- //System.out.println(text + " " + caret );
-
- if(validator != null) {
- String error = validator.apply(text);
- setState(STATE_VALID, (error == null));
- }
-
- resetCaches();
- }
-
- @ServerSide
- protected void fireTextChanged() {
- if(textListener != null)
- textListener.textChanged();
- repaint();
- }
-
- @ServerSide
- protected void fireTextEditingStarted() {
- if(textListener != null)
- textListener.textEditingStarted();
- }
-
- @ServerSide
- protected void fireTextEditingCancelled() {
- setState(STATE_VALID);
-
- if (deactivateEdit()) {
- if (textListener != null)
- textListener.textEditingCancelled();
-
- setEditMode(false, false);
-
- if (textBeforeEdit != null)
- setText(textBeforeEdit);
-
- repaint();
- }
- }
-
- @ServerSide
- public void fireTextEditingEnded() {
- if (!hasState(STATE_VALID)) {
- fireTextEditingCancelled();
- setState(STATE_VALID);
- return;
- }
-
- if (deactivateEdit()) {
- if (textListener != null)
- textListener.textEditingEnded();
-
- setEditMode(false, false);
- repaint();
- }
- }
-
- public void setTextListener(ITextListener listener) {
- this.textListener = listener;
- }
-
- public void setValidator(Function1 validator) {
- this.validator = validator;
- }
-
- public void setContentFilter(ITextContentFilter filter) {
- this.editContentFilter = filter;
- }
-
- public void setRVI(RVI rvi) {
- this.dataRVI = rvi;
- }
-
- private void invalidateXOffset() {
- setState(STATE_X_OFFSET_IS_DIRTY);
- }
-
- private void computeEditingXOffset() {
-
- if(lines == null) return;
- if(!hasState(STATE_X_OFFSET_IS_DIRTY)) return;
- if(fixedWidth > 0f) {
-
- // TODO: implement
-// float[] coords = textLayout.getCaretInfo(TextHitInfo.afterOffset(caret));
-// if(coords != null) {
-// if(coords[0] > (fixedWidth*MAX_CARET_POSITION)) xOffset = (float)((fixedWidth*MAX_CARET_POSITION)-coords[0]);
-// else xOffset = 0;
-// }
-
- } else {
-
- xOffset = 0;
-
- }
-
- clearState(STATE_X_OFFSET_IS_DIRTY);
-
- }
-
- @SyncField({"caret","selectionTail"})
- protected void moveCaret(int move, boolean select) {
- // prevent setting caret into line separator.
- if (move > 0) {
- while (text.length()> caret+move && getLineSeparator().indexOf(text.charAt(caret+move)) > 0)
- move++;
- } else if (move < 0) {
- while (caret+move >= 0 && text.length()> caret+move && getLineSeparator().indexOf(text.charAt(caret+move)) > 0)
- move--;
- }
- caret += move;
- if(caret < 0)
- caret = 0;
- if (caret > text.length())
- caret = text.length();
- if(!select)
- selectionTail = caret;
- }
-
- private Line findCaretLine() {
- // Find the line where caret is. Starting from first line.
- for(int i = 0; i < lines.length; i++) {
- Line line = lines[i];
- if(caret <= line.endOffset) {
- return line;
- }
- }
- return null;
- }
-
- /**
- * Moves caret to next not letter or digit
- * @param shiftDown
- */
- private void moveCaretCtrlLeft(boolean shiftDown) {
- Line line = findCaretLine();
- if(line != null) {
- int i;
- for(i = caret-1; i > line.startOffset; i--) {
- char c = line.document.charAt(i);
- if(!Character.isLetterOrDigit(c)) {
- break;
- }
- }
- moveCaret(i - caret, shiftDown);
- }
- }
-
- /**
- * Moves caret to previous non letter or digit
- * @param shiftDown
- */
- private void moveCaretCtrlRight(boolean shiftDown) {
- Line line = findCaretLine();
- if(line != null) {
- int i;
- for(i = caret + 1; i < line.endOffset; i++) {
- char c = line.document.charAt(i);
- if(!Character.isLetterOrDigit(c)) {
- break;
- }
- }
- moveCaret(i - caret, shiftDown);
- }
- }
-
- /**
- * Moves caret to line end
- * @param shiftDown
- */
- private void moveCaretEnd(boolean shiftDown) {
- Line line = findCaretLine();
- if(line != null)
- // Move caret to the end of the line
- moveCaret(line.endOffset - caret, shiftDown);
- }
-
- /**
- * Moves caret to beginning of a line
- * @param shiftDown
- */
- private void moveCaretHome(boolean shiftDown) {
- Line line = findCaretLine();
- if(line != null)
- // Move caret to the beginning of the line
- moveCaret(line.startOffset - caret, shiftDown);
- }
-
- /**
- * Moves caret one row up and tries to maintain the location
- * @param shiftDown
- */
- private void moveCaretRowUp(boolean shiftDown) {
- // Find the line where caret is. Starting from first line.
- for(int i = 0; i < lines.length; i++) {
- Line line = lines[i];
- if(caret <= line.endOffset) {
- // caret is in this line
- if(i == 0) {
- // Already on top line
- // Select the beginning of the line
- moveCaret(-caret, shiftDown);
- } else {
- Line prevLine = lines[i-1];
- int prevLength = prevLine.endOffset - prevLine.startOffset;
- int posInCurRow = caret - line.startOffset;
- if(prevLength < posInCurRow)
- posInCurRow = prevLength;
-
- int newPos = prevLine.startOffset + posInCurRow;
- moveCaret(newPos - caret, shiftDown);
- }
- break;
- }
- }
- }
-
- /**
- * Moves caret one row down and tries to maintain the location
- * @param shiftDown
- */
- private void moveCaretRowDown(boolean shiftDown) {
- // Find the line where caret is. Starting from last line.
- for(int i = lines.length - 1; i >= 0; i--) {
- Line line = lines[i];
- if(caret >= line.startOffset) {
- // caret is in this line
- if(i == lines.length - 1) {
- // Already on bottom line, cannot go below
- // Select to the end of the line
- moveCaret(line.endOffset - caret, shiftDown);
- } else {
- Line prevLine = lines[i+1]; // Previous line
-
- // Find new caret position.
- // Either it is in the same index as before, or if the row
- // is not long enough, select the end of the row.
- int prevLength = prevLine.endOffset - prevLine.startOffset;
- int posInCurRow = caret - line.startOffset;
- if(prevLength < posInCurRow)
- posInCurRow = prevLength;
- int newPos = prevLine.startOffset + posInCurRow;
- moveCaret(newPos - caret, shiftDown);
- }
- break;
- }
- }
- }
-
- @SyncField({"caret","selectionTail"})
- protected void setCaret(int pos, boolean select) {
- caret = pos;
- if (caret < 0)
- caret = 0;
- if (caret > text.length())
- caret = text.length();
- if (!select)
- selectionTail = caret;
- }
-
- protected void setCaret(Point2D point) {
- setCaret(point, false);
- }
-
- @SyncField({"caret","selectionTail"})
- protected void setCaret(Point2D point, boolean select) {
- double lineY = 0;
- for(int i = 0; i < lines.length; i++) {
- Line line = lines[i];
- Rectangle2D bounds = line.abbox;
- // Add heights of bboxes for determining the correct line
- if(i == 0)
- lineY = bounds.getY();
- else
- lineY += lines[i-1].abbox.getHeight();
-
- double lineHeight = bounds.getHeight();
- double hitY = point.getY() / scale;
- if(hitY >= lineY && hitY <= lineY + lineHeight) {
- // Hit is in this line
- float x = (float)(point.getX() / scale) - (float)line.abbox.getX();
- float y = (float)(point.getY() / scale - lineHeight * i) ;
- TextHitInfo info = line.layout.hitTestChar(x, y);
- caret = line.startOffset + info.getInsertionIndex();
- if (caret > line.endOffset)
- caret = line.endOffset;
- if (!select)
- selectionTail = caret;
- repaint();
- break;
- }
- }
- invalidateXOffset();
- assert (caret <= text.length());
- }
-
- @Override
- public Rectangle2D getBoundsInLocal() {
- if(targetBounds != null)
- return targetBounds;
- else
- return expandBounds( getTightAlignedBoundsInLocal(null) );
- }
-
- protected Rectangle2D expandBounds(Rectangle2D r) {
- r.setRect(r.getX() * scale - paddingX, r.getY() * scale -paddingY, r.getWidth()*scale + paddingX + paddingX, r.getHeight()*scale + paddingY + paddingY);
- //System.out.println(" => " + r);
- return r;
- }
-
- protected Rectangle2D expandBoundsUnscaled(Rectangle2D r) {
- r.setRect(r.getX() - scaleRecip*paddingX, r.getY() -scaleRecip*paddingY, r.getWidth() + scaleRecip*paddingX + scaleRecip*paddingX, r.getHeight() + scaleRecip*paddingY + scaleRecip*paddingY);
- //System.out.println(" => " + r);
- return r;
- }
-
- protected Rectangle2D expandBounds(Rectangle2D r, double amount) {
- r.setRect(r.getX() - amount, r.getY() - amount, r.getWidth() + 2*amount, r.getHeight() + 2*amount);
- return r;
- }
-
- protected Rectangle2D expandBounds(Rectangle2D r, double left, double top, double right, double bottom) {
- r.setRect(r.getX() - left, r.getY() - top, r.getWidth() + left + right, r.getHeight() + top + bottom);
- return r;
- }
-
- private void resetCaches() {
- this.tightBoundsCache = null;
- this.lines = null;
- this.fontMetrics = null;
- }
-
- /**
- * Returns the tight bounds around the current text using the current font
- * in the specified rectangle. If the specified rectangle is
- * null
a new Rectangle2D.Double instance will be created.
- *
- * @param r
- * @return
- */
- protected Rectangle2D getTightAlignedBoundsInLocal(Rectangle2D r) {
- return getTightAlignedBoundsInLocal(r, FRC);
- }
-
- /**
- * Returns the tight bounds around the current text using the current font
- * in the specified rectangle. If the specified rectangle is
- * null
a new Rectangle2D.Double instance will be created.
- *
- * @param r
- * the rectangle where the result of the method is placed or
- * null
to allocate new rectangle
- * @param frc current font render context
- * @return r or new Rectangle2D.Double instance containing the requested
- * text bounds
- */
- protected Rectangle2D getTightAlignedBoundsInLocal(Rectangle2D r, FontRenderContext frc) {
- if (r == null)
- r = new Rectangle2D.Double();
-
- if (tightBoundsCache != null) {
- r.setFrame(tightBoundsCache);
- return r;
- }
-
- String txt = text;
- if (font == null || txt == null) {
- r.setFrame(0, 0, 2, 1);
- return r;
- }
-
- //System.out.println("TextNode.getTightAlignedBoundsInLocal('" + txt + "')");
-
- // Parse & layout (unaligned)
- Line[] lines = null;
-
- if(hasState(STATE_WRAP_TEXT)) {
- float width = fixedWidth;
- if(width <= 0 && targetBounds != null)
- width = (float) (((targetBounds.getWidth() - 2*paddingX)) * scaleRecip);
- if(width > 0)
- lines = wrapLines(txt, font, width, frc);
- }
-
- if(lines == null)
- lines = parseLines(txt);
- this.lines = layoutLines(lines, frc);
-
- // Calculate tight bounds based on unaligned layout
- //System.out.println("Unaligned");
- tightBoundsCache = calculateBounds(lines, Line.BBOX, null);
- //System.out.println(" => " + tightBoundsCache);
-
- this.lines = layoutLinesX(lines, tightBoundsCache);
- // Align each line to the calculated tight bounds
- this.lines = alignLines(this.lines, tightBoundsCache, horizontalAlignment, verticalAlignment);
-
- // Calculate aligned bounds
- //System.out.println("Aligned");
- calculateBounds(lines, Line.ABBOX, tightBoundsCache);
-
- r.setFrame(tightBoundsCache);
- //System.out.println(" => " + tightBoundsCache);
-
- return r;
- }
-
- /**
- * @param lines
- * @param bbox
- * the bounding box of all the whole laid out text (only bbox
- * size is used)
- * @return
- */
- private Line[] layoutLinesX(Line[] lines, Rectangle2D bbox) {
- int lineCount = lines.length;
- for (int l = 0; l < lineCount; ++l) {
- Line line = lines[l];
- // Compute pen x position. If the paragraph is right-to-left we
- // will align the TextLayouts to the right edge of the panel.
- // Note: drawPosX is always where the LEFT of the text is placed.
- // NOTE: This changes based on horizontal alignment
- line.drawPosX = (float) (line.layout.isLeftToRight() ? 0f
- : tightBoundsCache.getWidth() - line.layout.getAdvance());
- }
- return lines;
- }
-
- /**
- * @param lines
- * @param boundsProvider
- * @param result
- * @return
- */
- private static Rectangle2D calculateBounds(Line[] lines, BoundsProcedure boundsProvider, Rectangle2D result) {
- if (result == null)
- result = new Rectangle2D.Double();
- else
- result.setFrame(0, 0, 0, 0);
-
- for (Line line : lines) {
- //System.out.println("line: " + line);
- Rectangle2D bbox = boundsProvider.getBounds(line);
- if (result.isEmpty())
- result.setFrame(bbox);
- else
- Rectangle2D.union(result, bbox, result);
- //System.out.println("bounds: " + result);
- }
- //System.out.println("final bounds: " + result);
-
- return result;
- }
-
- /**
- * @param lines
- * @param bbox
- * @param hAlign
- * @param vAlign
- * @return aligned lines
- */
- private Line[] alignLines(Line[] lines, Rectangle2D bbox, byte hAlign, byte vAlign) {
-// System.out.println("horizontal align: " + Alignment.values()[hAlign]);
-// System.out.println("vertical align : " + Alignment.values()[vAlign]);
-// System.out.println("bbox: " + bbox);
- double xbase = 0;
-// double ybase = 0;
- if(targetBounds != null) {
- /* In normal cases the bounding box moves when
- * typing. If target bounds are set, the text
- * is fitted into the box.
- */
- switch (hAlign) {
- case 1: // Trailing
- xbase = (targetBounds.getMaxX()-paddingX) * scaleRecip;
- break;
- case 2: // Center
- xbase = targetBounds.getCenterX() * scaleRecip;
- break;
- default: // Leading / Baseline
- // Do nothing
- break;
- }
- }
-
-
- for (Line line : lines) {
- double xoffset = 0;
- double yoffset = 0;
-
- switch (hAlign) {
- case 1: // Trailing
- xoffset = xbase - line.bbox.getWidth();
- break;
- case 2: // Center
- xoffset = xbase - line.bbox.getWidth() / 2;
- break;
- default: // Leading / Baseline
- // Do nothing
- break;
- }
-
- switch (vAlign) {
- case 0:
- yoffset = line.layout.getAscent();
- break;
- case 1:
- yoffset = -bbox.getHeight() + line.layout.getAscent();
- break;
- case 2:
- yoffset = -bbox.getHeight() / 2 + line.layout.getAscent();
- break;
- }
-
- line.alignOffset(xoffset, yoffset);
- }
- return lines;
- }
-
- /**
- * @param lines
- * @param frc
- * @return
- */
- private Line[] layoutLines(Line[] lines, FontRenderContext frc) {
- TextLayout emptyRowLayout = null;
- int lineCount = lines.length;
- float y = 0;
- for (int l = 0; l < lineCount; ++l) {
- Line line = lines[l];
- String lineText = line.getText();
- // " " because TextLayout requires non-empty text and
- // We don't want zero size for the text.
- if (lineText.isEmpty()) {
- lineText = " ";
- if (emptyRowLayout == null)
- emptyRowLayout = new TextLayout(lineText, font, frc);
- line.layout = emptyRowLayout;
- } else {
- line.layout = new TextLayout(lineText, font, frc);
- }
-
- //y += line.layout.getAscent();
- line.drawPosY = y;
- y += line.layout.getDescent() + line.layout.getLeading() + line.layout.getAscent();
-
- Rectangle2D bbox = line.layout.getLogicalHighlightShape(0, lineText.length()).getBounds2D();
- bbox.setFrame(bbox.getX(), bbox.getY() + line.drawPosY, bbox.getWidth(), bbox.getHeight());
- line.bbox = bbox;
- }
-
- return lines;
- }
-
- /**
- * Splits the specified string into {@link Line} structures, one for each
- * line in the input text. The returned lines are only partially defined,
- * waiting to be laid out (see
- * {@link #layoutLines(Line[], FontRenderContext)})
- *
- * @param txt
- * input text
- * @return parsed text lines as {@link Line} structures
- * @see #layoutLines(Line[], FontRenderContext)
- */
- private static Line[] parseLines(String txt) {
- int len = txt.length();
- if (len == 0)
- return new Line[] { new Line("", 0, 0) };
-
- TIntArrayList lfpos = new TIntArrayList();
- int pos = 0;
- int lineCount = 1;
- for (;pos < len; ++lineCount) {
- int nextlf = txt.indexOf('\n', pos);
- lfpos.add(nextlf != -1 ? nextlf : len);
- if (nextlf == -1)
- break;
- pos = nextlf + 1;
- }
- Line[] lines = new Line[lineCount];
- pos = 0;
- for (int i = 0; i < lineCount-1; ++i) {
- int lf = lfpos.getQuick(i);
- int cr = (lf > 0) && (txt.charAt(lf - 1) == '\r') ? lf - 1 : lf;
- lines[i] = new Line(txt, pos, cr);
- pos = lf + 1;
- }
- lines[lineCount - 1] = new Line(txt, pos, len);
-
- return lines;
- }
-
-
- private static Line[] wrapLines(String txt, Font font, float fixedWidth, FontRenderContext frc) {
- if(txt == null || txt.isEmpty())
- txt = " ";
-
- ArrayList lines =
- new ArrayList();
-
- Hashtable map = new Hashtable();
- map.put(TextAttribute.FONT, font);
- AttributedString attributedText = new AttributedString(txt.isEmpty() ? "__ERROR__" : txt, map);
-
- AttributedCharacterIterator paragraph = attributedText.getIterator();
- int paragraphStart = paragraph.getBeginIndex();
- int paragraphEnd = paragraph.getEndIndex();
- LineBreakMeasurer lineMeasurer = new LineBreakMeasurer(paragraph, frc);
-
- float breakWidth = fixedWidth;
-
- // Force text to be vertical, by setting break width to 1, if the text area is narrower than "GGGG"
-
- // Set position to the index of the first character in the paragraph.
- lineMeasurer.setPosition(paragraphStart);
-
- // Get lines until the entire paragraph has been displayed.
- int next, limit, charat, position = 0;
-
- while ((position = lineMeasurer.getPosition()) < paragraphEnd) {
-
- // Find possible line break and set it as a limit to the next layout
- next = lineMeasurer.nextOffset(breakWidth);
- limit = next;
- charat = txt.indexOf(System.getProperty("line.separator"),position+1);
- if(charat < next && charat != -1){
- limit = charat;
- }
-
- lineMeasurer.nextLayout(breakWidth, limit, false);
- // Add Line
- lines.add(new Line(txt, position, limit));
- }
-
- return lines.toArray(new Line[lines.size()]);
- }
-
-
- public String getClipboardContent() {
- Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
- Transferable clipData = clipboard.getContents(this);
- try {
- return (String) (clipData.getTransferData(DataFlavor.stringFlavor));
- } catch (Exception ee) {
- return null;
- }
- }
-
- public void setClipboardContent(String content) {
- Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
- StringSelection data = new StringSelection(content);
- clipboard.setContents(data, data);
- }
-
- @Override
- public String toString() {
- return super.toString() + " [text=" + text + ", font=" + font + ", color=" + color + "]";
- }
-
- @Override
- protected boolean handleCommand(CommandEvent e) {
- if (!hasState(STATE_EDITING))
- return false;
-
- if (Commands.SELECT_ALL.equals(e.command)) {
- selectAll();
- return true;
- }
- return false;
- }
-
- @Override
- protected boolean keyPressed(KeyPressedEvent event) {
- if (!hasState(STATE_EDITING))
- return false;
-
- char c = event.character;
- boolean ctrl = event.isControlDown();
- boolean alt = event.isAltDown();
-
-// System.out.println("Key pressed '" + (event.character == 0 ? "\\0" : "" + c) + "' " + event.keyCode + " - " + Integer.toBinaryString(event.stateMask));
-// System.out.println("ctrl: " + ctrl);
-// System.out.println("alt: " + alt);
- if (ctrl && !alt) {
- switch (event.keyCode) {
- case KeyEvent.VK_C:
- if (caret != selectionTail) {
- int selectionMin = Math.min(caret, selectionTail);
- int selectionMax = Math.max(caret, selectionTail);
- setClipboardContent(text.substring(selectionMin, selectionMax));
- }
- break;
-
- case KeyEvent.VK_X:
- if (caret != selectionTail) {
- int selectionMin = Math.min(caret, selectionTail);
- int selectionMax = Math.max(caret, selectionTail);
- setClipboardContent(text.substring(selectionMin, selectionMax));
- insert("");
- }
- break;
-
- case KeyEvent.VK_RIGHT:
- if (c == '\0') {
- // '\'' has the same keycode as VK_RIGHT but when right
- // arrow is pressed, event character is \0.
- moveCaretCtrlRight(event.isShiftDown());
- }
- break;
-
- case KeyEvent.VK_LEFT:
- moveCaretCtrlLeft(event.isShiftDown());
- break;
-
- case KeyEvent.VK_V:
- {
- String content = getClipboardContent();
- if(content != null)
- insert(content);
- break;
- }
-
- // Replaced by #handleCommand
-// case KeyEvent.VK_A:
-// {
-// selectAll();
-// return true;
-// }
-
- case KeyEvent.VK_ENTER:
- {
- insert(getLineSeparator());
- }
-
- break;
-
- default:
- return false;
- }
- } else if (!ctrl && alt) {
- return false;
- } else {
- switch (event.keyCode) {
- case KeyEvent.VK_LEFT:
- moveCaret(-1, event.isShiftDown());
- break;
- case KeyEvent.VK_RIGHT:
- if (c == '\0') {
- // '\'' has the same keycode as VK_RIGHT but when right
- // arrow is pressed, event character is \0.
- moveCaret(1, event.isShiftDown());
- break;
- }
- // Intentional fallthrough to default case
- case KeyEvent.VK_UP:
- moveCaretRowUp(event.isShiftDown());
- break;
- case KeyEvent.VK_DOWN:
- moveCaretRowDown(event.isShiftDown());
- break;
- case KeyEvent.VK_HOME:
- moveCaretHome(event.isShiftDown());
- break;
- case KeyEvent.VK_END:
- moveCaretEnd(event.isShiftDown());
- break;
-
- case KeyEvent.VK_ENTER:
- fireTextEditingEnded();
- return true;
-
- case KeyEvent.VK_ESCAPE:
- text = textBeforeEdit;
- resetCaches();
- clearState(STATE_EDITING);
- fireTextEditingCancelled();
- return true;
-
- case KeyEvent.VK_BACK_SPACE:
- if(caret == selectionTail && caret > 0) {
- // line separator may use multiple characters, we want to remove that with one command
- String lineSep = getLineSeparator();
- int index = lineSep.indexOf(text.charAt(caret-1));
- if (index == -1)
- --caret;
- else {
- caret-= (index+1);
- selectionTail+= (lineSep.length()-index-1);
- }
- }
- insert("");
- break;
-
- case KeyEvent.VK_DELETE:
- if(caret == selectionTail && caret < text.length()) {
- String lineSep = getLineSeparator();
- int index = lineSep.indexOf(text.charAt(caret));
- if (index==-1)
- ++caret;
- else {
- selectionTail-= index;
- caret+= (lineSep.length()-index);
- }
- }
- insert("");
- break;
-
-
-
- default:
- if (c == 65535 || Character.getType(c) == Character.CONTROL) {
- return false;
- }
- //System.out.println("Char " + c + " " + Character.getType(c) + " " + text);
- insert(new String(new char[] {c}));
- }
- }
-
- // FIXME This is called even if just caret was moved.
- // This is currently necessary for repaints.
- fireTextChanged();
- invalidateXOffset();
- return true;
- }
-
- protected String getLineSeparator() {
- return System.getProperty("line.separator");
- }
-
- protected void selectAll() {
- setCaret(0, false);
- setCaret(text.length(), true);
- }
-
- protected transient int hoverClick = 0;
-
- @Override
- protected boolean mouseClicked(MouseClickEvent event) {
- if (event.button != MouseClickEvent.LEFT_BUTTON)
- return false;
-
- if (hasState(STATE_HOVER)) {
- hoverClick++;
- if (hoverClick < 2)
- return false;
- ICanvasContext ctx = DiagramNodeUtil.getCanvasContext(this);
- // FIXME: needed only because eventdelegator registrations are done before adding node to scene graph.
- if (ctx == null)
- return false;
- IElement e = DiagramNodeUtil.getElement(ctx, this);
- if (!hasState(STATE_EDITING)) {
- if (Boolean.TRUE.equals(setEditMode(true))) {
- editActivation = activateEdit(0, e, ctx);
- repaint();
- }
- }
- } else {
- hoverClick = 0;
- if (hasState(STATE_EDITING)) {
- fireTextEditingEnded();
- }
- }
- return false;
- }
-
- protected boolean mouseDoubleClicked(MouseDoubleClickedEvent event) {
- if (event.button != MouseClickEvent.LEFT_BUTTON)
- return false;
-
- if (hitTest(event, 0)) {
- ICanvasContext ctx = DiagramNodeUtil.getCanvasContext(this);
- // FIXME: needed only because eventdelegator registrations are done before adding node to scene graph.
- if (ctx == null)
- return false;
-
- if (text != null) {
- // Select the whole text.
- setCaret(0, false);
- setCaret(text.length(), true);
- repaint();
- }
- }
- return false;
- }
-
-
- @Override
- protected boolean mouseButtonPressed(MouseButtonPressedEvent event) {
- if (!hasState(STATE_EDITING))
- return false;
-
- Point2D local = controlToLocal( event.controlPosition );
- // FIXME: once the event coordinate systems are cleared up, remove this workaround
- local = parentToLocal(local);
- if (hasState(STATE_HOVER) && this.containsLocal(local)) {
- setCaret(local, event.isShiftDown());
- }
- return false;
- }
-
- @Override
- protected boolean mouseMoved(MouseMovedEvent event) {
- boolean hit = hitTest(event, 3.0);
- if (hit != hasState(STATE_HOVER)) {
- setState(STATE_HOVER, hit);
- repaint();
- }
- return false;
- }
-
- private boolean isControlDown(MouseEvent e) {
- return e.isControlDown() || lastMouseEvent!=null?lastMouseEvent.isControlDown():false;
- }
-
- protected boolean isShiftDown(MouseEvent e) {
- return e.isShiftDown() || lastMouseEvent!=null?lastMouseEvent.isShiftDown():false;
- }
-
-// private boolean isAltDown(MouseEvent e) {
-// return e.isAltDown() || lastMouseEvent!=null?lastMouseEvent.isAltDown():false;
-// }
-
- @Override
- protected boolean mouseDragged(MouseDragBegin e) {
- if (isHovering()
- && (isControlDown(e) || isShiftDown(e))
- && e.context instanceof NodeEventHandler
- && (dataRVI != null || text != null))
- {
- List trs = new ArrayList<>(2);
- if (dataRVI != null) {
- trs.add(new LocalObjectTransferable(dataRVI));
- trs.add(new PlaintextTransfer(dataRVI.toString()));
- } else if (text != null && !text.isEmpty()) {
- trs.add(new PlaintextTransfer(text));
- }
- if (!trs.isEmpty()) {
- e.transferable = new MultiTransferable(trs);
- return true;
- }
- }
- return false;
- }
-
- protected boolean hitTest(MouseEvent event, double tolerance) {
- Rectangle2D bounds = getBoundsInternal();
- if (bounds == null)
- return false;
- Point2D localPos = NodeUtil.worldToLocal(this, event.controlPosition, new Point2D.Double());
- double x = localPos.getX();
- double y = localPos.getY();
- boolean hit = bounds.contains(x, y);
- return hit;
- }
-
- public Rectangle2D getBoundsInternal() {
- Rectangle2D local = lastBounds;
- if (local == null)
- return null;
- // TODO: potential spot for CPU/memory allocation optimization
- // by using more specialized implementations
- if (transform.isIdentity())
- return local;
- return transform.createTransformedShape(local).getBounds2D();
- }
-
- protected Color add(Color c, int r, int g, int b) {
- int nr = Math.min(255, c.getRed() + r);
- int ng = Math.min(255, c.getGreen() + g);
- int nb = Math.min(255, c.getBlue() + b);
- return new Color(nr, ng, nb);
- }
-
- public TextEditActivation activateEdit(int mouseId, IElement e, ICanvasContext ctx) {
- EditDataNode data = EditDataNode.getNode(this);
- deactivateEdit(data, null);
- TextEditActivation result = new TextEditActivation(mouseId, e, ctx);
- data.setTextEditActivation(result);
- return result;
- }
-
- /**
- * @return true
if this node is or was previously in editing
- * state
- */
- protected boolean deactivateEdit() {
- boolean result = deactivateEdit( editActivation );
- result |= editActivation != null;
- editActivation = null;
- return result;
- }
-
- protected boolean deactivateEdit(TextEditActivation activation) {
- return deactivateEdit( EditDataNode.getNode(this), activation );
- }
-
- protected boolean deactivateEdit(EditDataNode data, TextEditActivation activation) {
- TextEditActivation previous = data.getTextEditActivation();
- if (previous != null && (previous == activation || activation == null)) {
- previous.release();
- data.setTextEditActivation(null);
- return true;
- }
- return false;
- }
-
- @Override
- public int getEventMask() {
- return EventTypes.KeyPressedMask | EventTypes.MouseMovedMask | EventTypes.MouseButtonPressedMask
- | EventTypes.MouseClickMask | EventTypes.MouseDragBeginMask | EventTypes.CommandMask;
- }
-
- private MouseEvent lastMouseEvent = null;
-
- @Override
- public boolean handleEvent(Event e) {
- if(e instanceof MouseEvent && !(e instanceof MouseDragBegin)) lastMouseEvent = (MouseEvent)e;
- return super.handleEvent(e);
- }
-
- @Override
- public Function1