/* * Licensed Materials - Property of Perforce Software, Inc. * © Copyright Perforce Software, Inc. 2014, 2021 * © Copyright IBM Corp. 2009, 2014 * © Copyright ILOG 1996, 2009 * All Rights Reserved. * * Note to U.S. Government Users Restricted Rights: * The Software and Documentation were developed at private expense and * are "Commercial Items" as that term is defined at 48 CFR 2.101, * consisting of "Commercial Computer Software" and * "Commercial Computer Software Documentation", as such terms are * used in 48 CFR 12.212 or 48 CFR 227.7202-1 through 227.7202-4, * as applicable. */ import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Paint; import java.awt.Shape; import java.io.IOException; import ilog.views.IlvGraphic; import ilog.views.IlvPoint; import ilog.views.IlvRect; import ilog.views.IlvSelection; import ilog.views.IlvTextInterface; import ilog.views.IlvTransformer; import ilog.views.graphic.IlvText; import ilog.views.graphic.IlvTextSelection; import ilog.views.internal.impl.IlvUtility2D; import ilog.views.io.IlvInputStream; import ilog.views.io.IlvOutputStream; import ilog.views.io.IlvReadFileException; /** * A text object that uses less memory than the usual <code>IlvText</code>. * However it is also slower drawn when be at large magnification. The trick * is that usually only very few of these objects are visible in large * magnification, hence the slow down does not matter. If we draw with * demagnification, we draw only the rectangle without the text, which would * anyway be unreadable. So, at large demagnification, it is faster than * <code>IlvText</code>. */ public class LightText extends IlvGraphic implements IlvTextInterface { IlvPoint anchor; String label; byte flags; double[] margins; Color foreground; Paint saveFillPaint; Paint fillPaint; IlvRect bounds; IlvText updatingDelegate; int updatingCounter; private static final short FILL_FLAG = 1; private static final short STROKE_FLAG = 2; private static final short ALIAS_FLAG = 4; /** * Creates a new <code>LightText</code>. */ public LightText() { this(new IlvPoint(), ""); } /** * Creates a new <code>LightText</code>. */ public LightText(IlvPoint anchor, String text) { super(); this.margins = new double[4]; this.label = text; this.anchor = new IlvPoint(anchor); updateBounds(null); } /** * Creates a new <code>LightText</code> by copying an existing one. */ public LightText(LightText source) { super(source); anchor = new IlvPoint(source.anchor); label = source.label; flags = source.flags; margins = new double[4]; System.arraycopy(source.margins, 0, margins, 0, 4); foreground = source.foreground; saveFillPaint = source.saveFillPaint; bounds = new IlvRect(source.bounds); updatePaints(); } /** * Copies the object. * @return a copy of the object. * @see #isOwner */ Override public IlvGraphic copy() { return new LightText(this); } /** * Returns <code>true</code> if the object is zoomable. * Since <code>IlvText</code> is zoomable, this object is also zoomable. */ Override public boolean zoomable() { return true; } /** * Returns <code>true</code> if the label can be a multi-line label. * Since <code>IlvText</code> supports multi-line labels, this object is also * supports multi-line labels. */ Override public boolean supportMultiline() { return true; } /** * Returns the area where the label is displayed. * It is equivalent to the {@link #boundingBox(IlvTransformer)} without * the margins. * @param t the transformer used to draw the graphic object. */ Override public IlvRect getLabelBBox(IlvTransformer t) { IlvRect bbox = new IlvRect(bounds); bbox.x += getLeftMargin(); bbox.width -= (getLeftMargin() + getRightMargin()); bbox.y += getTopMargin(); bbox.height -= (getTopMargin() + getBottomMargin()); if (t != null && !t.isIdentity()) t.apply(bbox); if (bbox.height < 0.1) bbox.height = 0.1; if (bbox.width < 0.1) bbox.width = 0.1; return bbox; } /** * Reads the object from an <code>IlvInputStream</code>. * @param stream the input stream. * @exception IlvReadFileException if the format is not correct. */ public LightText(IlvInputStream stream) throws IlvReadFileException { super(stream); label = stream.readString("label"); flags = (byte)stream.readShort("flags"); anchor = stream.readPoint("anchorPoint"); margins = stream.readDoubleOrFloatArray("margins"); foreground = stream.readColor("foreground"); saveFillPaint = IlvUtility2D.readPaint(stream); bounds = stream.readRect("bounds"); updatePaints(); } /** * Draws the object. * Calls <code>draw</code> on the referenced object. * @param dst the destination Graphics. * @param t the transformation used to draw the object. */ Override public void draw(Graphics dst, IlvTransformer t) { IlvRect bbox = boundingBox(t); if (bbox.width <= 2 || bbox.height <= 2) { // draw only the border dst.setColor(getForeground()); int w = Math.max(1, (int)bbox.width); int h = Math.max(1, (int)bbox.height); dst.fillRect((int)bbox.x, (int)bbox.y, w, h); } else if (bbox.width <= 4 || bbox.height <= 4) { // draw border and inner, but no text int w = Math.max(1, (int)bbox.width); int h = Math.max(1, (int)bbox.height); if (isFillOn()) { Graphics2D dst2 = (Graphics2D)dst; Paint old = dst2.getPaint(); dst2.setPaint(fillPaint); dst.fillRect((int)bbox.x, (int)bbox.y, w, h); dst2.setPaint(old); } dst.setColor(getForeground()); if (isStrokeOn()) { dst.drawRect((int)bbox.x, (int)bbox.y, w, h); } else { int x = (int)bbox.x; int middle = (int)(bbox.y + 0.5 * bbox.height); dst.drawLine(x, middle, x + w, middle); } } else { // draw the text via delegate getDelegate(true).draw(dst, t); } } /** * Returns the bounding rectangle of the object. * Calls <code>boundingBox</code> on the referenced object. * @param t the transformer used to draw the object. */ Override public IlvRect boundingBox(IlvTransformer t) { IlvRect bbox = new IlvRect(bounds); if (t != null && !t.isIdentity()) t.apply(bbox); if (bbox.height < 0.1) bbox.height = 0.1; if (bbox.width < 0.1) bbox.width = 0.1; return bbox; } /** * Applies a transformation to the shape of the object. * <p> * Note that the method must never be called with a <code>null</code> * argument. * @param t The transformer to be applied. */ Override public void applyTransform(IlvTransformer t) { if (t == null || t.isIdentity()) return; t.apply(bounds); if (bounds.width < 0.1) bounds.width = 0.1; if (bounds.height < 0.1) bounds.height = 0.1; } /** * Writes the object to an <code>IlvOutputStream</code>. * @param stream the output stream. */ Override public void write(IlvOutputStream stream) throws IOException { super.write(stream); stream.write("label", getLabel()); stream.write("flags", flags); stream.write("anchorPoint", anchor); stream.write("margins", margins); stream.write("foreground", getForeground()); IlvUtility2D.writePaint(stream, getFillPaint()); stream.write("bounds", bounds); } /** * Sets the label that will be displayed by the <code>IlvText</code>. If the * label contains line breaking characters (<code>'\n'</code>) it will be * displayed on several lines. * <p> * Note that this may change its bounding rectangle. For this reason, * if the object is contained inside a manager, you should modify this flag * using the <code>applyToObject</code> method of the manager. * <p> * @param label the new label. */ Override public void setLabel(String label) { if (label == null) label = ""; if (!label.equals(this.label)) { IlvRect textbbox = getDelegate(false).boundingBox(); IlvTransformer t = IlvTransformer.computeTransformer(textbbox, bounds, null); this.label = label; updateBounds(t); } } /** * Returns the label that will be displayed by the <code>IlvText</code>. * @see #setLabel(String) */ Override public String getLabel() { return label; } /** * Sets the anchor point for the text. This specifies to which point in * manager coordinate (before applying {@link #getTransformer()}) the text * will be attached to. * @param anchor the anchor point. */ public final void setAnchorPoint(IlvPoint anchor) { if (anchor == null) anchor = new IlvPoint(); if (!anchor.equals(this.anchor)) { IlvRect textbbox = getDelegate(false).boundingBox(); IlvTransformer t = IlvTransformer.computeTransformer(textbbox, bounds, null); this.anchor.setLocation(anchor); updateBounds(t); } } /** * Returns the anchor point for the text. This is the point in * manager coordinate (before applying {@link #getTransformer()}) the text * is attached to. * @see #getAnchorPosition() * @beaninfo * description: The anchor point for the text. * editor: ilog.views.beans.editor.IlvPointEditor.class */ public final IlvPoint getAnchorPoint() { return new IlvPoint(anchor); } /** * Changes the visibility of the background rectangle * of the text. * @param value <code>true</code> if fill is on. */ Override public final void setFillOn(boolean value) { if (value) { flags |= FILL_FLAG; updatePaints(); } else flags &= ~FILL_FLAG; } /** * Returns <code>true</code> if the background rectangle * of the text is drawn. * @see #setFillPaint * @see #setFillOn */ public final boolean isFillOn() { return (flags & FILL_FLAG) != 0; } /** * Changes the visibility of the stroke of the background rectangle. * @param value <code>true</code> if stroke is on. */ Override public final void setStrokeOn(boolean value) { if (value) flags |= STROKE_FLAG; else flags &= ~STROKE_FLAG; } /** * Returns <code>true</code> if the frame of the background rectangle * of the text is drawn. */ public final boolean isStrokeOn() { return (flags & STROKE_FLAG) != 0; } /** * Returns <code>true</code> if the anti-aliasing mode * of the label is on. The default value is <code>false</code>. */ public final boolean isAntialiasing() { return (flags & ALIAS_FLAG) != 0; } /** * Changes the anti-aliasing mode of the text. * The default value is <code>false</code>. * Note that this may change its bounding rectangle. For this reason, * if the object is contained inside a manager, you should modify this flag * using the <code>applyToObject</code> method of the manager. */ public final void setAntialiasing(boolean set) { if (set != isAntialiasing()) { IlvRect textbbox = getDelegate(false).boundingBox(); IlvTransformer t = IlvTransformer.computeTransformer(textbbox, bounds, null); if (set) flags |= ALIAS_FLAG; else flags &= ~ALIAS_FLAG; updateBounds(t); } } /** * Sets the left margin of the box around the text. * Note that this may change its bounding rectangle. * For this reason, if the object is contained inside a manager * you should modify the parameter using the <code>applyToObject</code> * method of the manager. * The default value is <code>0</code>. */ public final void setLeftMargin(double margin) { if (margin < 0) throw new IllegalArgumentException("Illegal margin " + margin); if (margins[0] != margin) { IlvRect textbbox = getDelegate(false).boundingBox(); IlvTransformer t = IlvTransformer.computeTransformer(textbbox, bounds, null); margins[0] = margin; updateBounds(t); } } /** * Returns the left margin of the box around the text. * The default value is <code>0</code>. * @see #setLeftMargin */ public final double getLeftMargin() { return margins[0]; } /** * Sets the right margin of the box around the text. * Note that this may change its bounding rectangle. * For this reason, if the object is contained inside a manager * you should modify the parameter using the <code>applyToObject</code> * method of the manager. * The default value is <code>0</code>. */ public final void setRightMargin(double margin) { if (margin < 0) throw new IllegalArgumentException("Illegal margin " + margin); if (margins[1] != margin) { IlvRect textbbox = getDelegate(false).boundingBox(); IlvTransformer t = IlvTransformer.computeTransformer(textbbox, bounds, null); margins[1] = margin; updateBounds(t); } } /** * Returns the right margin of the box around the text. * The default value is <code>0</code>. * @see #setRightMargin */ public final double getRightMargin() { return margins[1]; } /** * Sets the top margin of the box around the text. * Note that this may change its bounding rectangle. * For this reason, if the object is contained inside a manager * you should modify the parameter using the <code>applyToObject</code> * method of the manager. * The default value is <code>0</code>. */ public final void setTopMargin(double margin) { if (margin < 0) throw new IllegalArgumentException("Illegal margin " + margin); if (margins[2] != margin) { IlvRect textbbox = getDelegate(false).boundingBox(); IlvTransformer t = IlvTransformer.computeTransformer(textbbox, bounds, null); margins[2] = margin; updateBounds(t); } } /** * Returns the top margin of the box around the text. * The default value is <code>0</code>. * @see #setTopMargin */ public final double getTopMargin() { return margins[2]; } /** * Sets the bottom margin of the box around the text. * Note that this may change its bounding rectangle. * For this reason, if the object is contained inside a manager * you should modify the parameter using the <code>applyToObject</code> * method of the manager. * The default value is <code>0</code>. */ public final void setBottomMargin(double margin) { if (margin < 0) throw new IllegalArgumentException("Illegal margin " + margin); if (margins[3] != margin) { IlvRect textbbox = getDelegate(false).boundingBox(); IlvTransformer t = IlvTransformer.computeTransformer(textbbox, bounds, null); margins[3] = margin; updateBounds(t); } } /** * Returns the bottom margin of the box around the text. * The default value is <code>0</code>. * @see #setBottomMargin */ public final double getBottomMargin() { return margins[3]; } /** * Changes the foreground color of the object, i.e the color used * to draw the text itself. * @param c The new color. */ Override public void setForeground(Color c) { foreground = c; } /** * Returns the foreground color of object, i.e the color used * to draw the text itself. The default value is <code>Color.BLACK</code>. */ public Color getForeground() { return (foreground!=null)?foreground:Color.black; } /** * Changes the background <code>Paint</code> of the object, i.e the * paint server used to fill the background rectangle. * @param paint the new <code>Paint</code> object. */ public void setFillPaint(Paint paint) { saveFillPaint = paint; updatePaints(); } /** * Returns the <code>Paint</code> object used to fill the background * rectangle. The default value is <code>Color.GRAY</code>. * @see #setFillPaint */ public Paint getFillPaint() { if (saveFillPaint == null) return Color.gray; return saveFillPaint; } private void updateBounds(IlvTransformer t) { bounds = getDelegate(false).boundingBox(); if (t != null) t.apply(bounds); updatePaints(); } private void updatePaints() { // this means we are here always in paint-relative mode if (isFillOn()) fillPaint = IlvUtility2D.AdaptPaint(boundingBox(), getFillPaint()); } /** * This method returns a <code>Shape</code> object enveloping the * characters specified by <code>range</code>. * This is part of the <code>IlvTextInterface</code>. * @param range A <code>Range</code> indicating the number and position of * selected characters. * @param t The transformer used while drawing the graphic object being * edited. * @return A <code>Shape</code> used to highlight the selected characters. * If <code>null</code> is returned, the text is not editable and the caret * shape should not be displayed. */ Override public Shape getCaretShape(IlvTextSelection.Range range, IlvTransformer t) { return getDelegate(true).getCaretShape(range, t); } /** * Returns the index of the character clicked by the user. * This is part of the <code>IlvTextInterface</code>. * @param click The position clicked on with the mouse. The coordinates * have already been transformed by the <code>IlvTransformer</code>, * <code>t</code>. * @param t The transformer used to draw the label. * @return The index of the character clicked by the mouse. When no * character has been selected, -1 is returned. */ Override public int pickCharacter(IlvPoint click, IlvTransformer t) { return getDelegate(true).pickCharacter(click, t); } /** * Returns the line count for multi-line text. * This is part of the <code>IlvTextInterface</code>. * @return The line count for multi-line text. For a single line * text, the value <code>1</code> is returned. */ Override public int lineCount() { return getDelegate(true).lineCount(); } /** * Returns the line offset for a given line. * This is part of the <code>IlvTextInterface</code>. * @param index The line index. The first line in the label has index 0. * @return The line offset for the selected character in a label. * Returns 0 for single line label or -1 if the specified index is out of * range. */ Override public int lineOffset(int index) { return getDelegate(true).lineOffset(index); } /** * Returns the delegate object that actually does the drawing. * Note we don't cache the delegate, therefore the LightText uses less * memory. */ private IlvText getDelegate(boolean includeBounds) { IlvText text = (updatingDelegate != null ? updatingDelegate : new IlvText(anchor, label)); text.setAntialiasing(isAntialiasing()); text.setLeftMargin(getLeftMargin()); text.setRightMargin(getRightMargin()); text.setTopMargin(getTopMargin()); text.setBottomMargin(getBottomMargin()); text.setFillOn(isFillOn()); text.setStrokeOn(isStrokeOn()); text.setForeground(getForeground()); text.setFillPaint(getFillPaint()); if (includeBounds) text.moveResize(bounds); else if (bounds != null) { text.move(bounds.x, bounds.y); } return text; } /** * Starts or stops an updating session. * This can be used to improve performance when many properties * are changed at once. */ public void setUpdating(boolean updating) { if (updating) { if (updatingCounter == 0) updatingDelegate = new IlvText(anchor, label); updatingCounter++; } else { updatingCounter--; if (updatingCounter == 0) updatingDelegate = null; } } /** * Creates a selection object for this graphic object. */ Override public IlvSelection makeSelection() { return new IlvTextSelection(this, true); } }