/*
* Licensed Materials - Property of Rogue Wave Software, Inc.
* © Copyright Rogue Wave Software, Inc. 2014, 2017
* © 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);
}
}