/**
* Text.java
* Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package org.expeditee.items;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.StringTokenizer;
import org.expeditee.core.Colour;
import org.expeditee.core.Dimension;
import org.expeditee.core.Fill;
import org.expeditee.core.Font;
import org.expeditee.core.GradientFill;
import org.expeditee.core.Point;
import org.expeditee.core.Range;
import org.expeditee.core.Stroke;
import org.expeditee.core.TextHitInfo;
import org.expeditee.core.TextLayout;
import org.expeditee.core.bounds.AxisAlignedBoxBounds;
import org.expeditee.core.bounds.CombinationBoxBounds;
import org.expeditee.core.bounds.PolygonBounds;
import org.expeditee.gio.EcosystemManager;
import org.expeditee.gio.GraphicsManager;
import org.expeditee.gio.gesture.StandardGestureActions;
import org.expeditee.gui.AttributeValuePair;
import org.expeditee.gui.DisplayController;
import org.expeditee.gui.Frame;
import org.expeditee.gui.FrameIO;
import org.expeditee.gui.FrameUtils;
import org.expeditee.gui.FreeItems;
import org.expeditee.gui.MessageBay;
import org.expeditee.items.MagneticConstraint.MagneticConstraints;
import org.expeditee.math.ExpediteeJEP;
import org.expeditee.settings.experimental.ExperimentalFeatures;
import org.expeditee.stats.Formatter;
import org.nfunk.jep.Node;
/**
* Represents text displayed on the screen, which may include multiple lines.
* All standard text properties can be set (font, style, size).
*
* @author jdm18
*
*/
public class Text extends Item {
private static final int ADJUST_WIDTH_THRESHOLD = 200;
public static final char DELETE_CHARACTER = 0x7F;
public static final char BACKSPACE_CHARACTER = '\b';
public static final char TAB_CHARACTER = '\t';
public static final char ESC_CHARACTER = 0x1B;
public static String LINE_SEPARATOR = System.getProperty("line.separator");
// public static char[] BULLETS = { '\u2219', '\u2218', '\u2217' };
public static char[] BULLETS = { '\u25AA', '\u25AB', '\u2217' };
private static char DEFAULT_BULLET = BULLETS[2];
private static String DEFAULT_BULLET_STRING = DEFAULT_BULLET + " ";
private String[] _processedText = null;
public String[] getProcessedText() {
return _processedText;
}
public void setProcessedText(String[] tokens) {
_processedText = tokens;
}
public static final String FRAME_NAME_SEPARATOR = " on frame ";
/** The default font used to display text items if no font is specified. */
public static final String DEFAULT_FONT = "Serif-Plain-18";
public static final Colour DEFAULT_COLOR = Colour.BLACK;
public static final int MINIMUM_RANGED_CHARS = 2;
public static final int NONE = 0;
public static final int UP = 1;
public static final int DOWN = 2;
public static final int LEFT = 3;
public static final int RIGHT = 4;
public static final int HOME = 5;
public static final int LINE_HOME = 9;
public static final int LINE_END = 10;
public static final int END = 6;
public static final int PAGE_DOWN = 7;
public static final int PAGE_UP = 8;
/**
* Set the width to be IMPLICIT, but as wide as possible, a negative width value
* is one that is implicitly set by the system... a positive value is one
* explicitly set by the user.
*/
/**
* The maximum allowable width of the Text item. Actual width may be less than this
* value, subject to text wrapping. Negative values indicate the width was implicitly
* set by the system, positive values indicate explicit setting by the user. Initially
* set to be as wide as possible.
*/
private Integer _maxWidth = -Integer.MAX_VALUE;
private Justification _justification = Justification.left;
private float _spacing = -1;
private int _word_spacing = -1;
private float _initial_spacing = 0;
private float _letter_spacing = 0;
// used during ranging out
private int _selectionStart = -1;
private int _selectionEnd = -1;
/** Keeps track of the last Text item selected. */
private static Text _lastSelected = null;
// Range selection colours
/** Colour of selected range when for selecting text. */
public static final Colour RANGE_SELECT_COLOUR = Colour.FromRGB255(255, 160, 160);
/** Colour of selected range when for cutting text. */
public static final Colour RANGE_CUT_COLOUR = Colour.FromRGB255(160, 255, 160);
/** Colour of selected range when for copying text. */
public static final Colour RANGE_COPY_COLOUR = Colour.FromRGB255(160, 160, 255);
/** Colour of selected range when for deleting text. */
public static final Colour RANGE_DELETE_COLOUR = Colour.FromRGB255(235, 235, 140);
/** The colour to draw range selections in. */
private Colour _selectionColour = RANGE_SELECT_COLOUR;
// whether autowrap is on/off for this item
protected boolean _autoWrap = false;
// text is broken up into lines
private StringBuffer _text = new StringBuffer();
private List _textLayouts = new LinkedList();
// The font to display this text in
private Font _font;
protected static void InitFontFamily(File fontFamilyDir)
{
File[] fontFiles = fontFamilyDir.listFiles();
for (File fontFile : fontFiles) {
String ext = "";
String fileName = fontFile.getName().toLowerCase();
int i = fileName.lastIndexOf('.');
int p = Math.max(fileName.lastIndexOf('/'), fileName.lastIndexOf('\\'));
if (i > p) {
ext = fileName.substring(i + 1);
}
if (ext.equals("ttf")) {
try {
Font font = EcosystemManager.getFontManager().registerFontFile(fontFile);
if (font != null) {
String font_family = font.getFamilyName();
if (!FONT_WHEEL_ADDITIONAL_LOOKUP.containsKey(font_family)) {
if (FONT_WHEEL_ADDITIONAL_LOOKUP.size() > 0) {
System.out.print(", ");
}
System.out.print("'" + font_family + "'");
FONT_WHEEL_ADDITIONAL_LOOKUP.put(font_family, font);
/*
int cdut = font.canDisplayUpTo("09AZaz");
if (cdut >= 0) {
// Some problem has occured (should return -1 to show all chars possible)
System.out.println(" [Non-ASCII font]");
}
*/
System.out.flush();
}
System.out.print("'" + font_family + "'");
} else {
System.err.println("Error: Failed to add custom True-Type Font file: " + fontFile);
}
} catch (Exception e) {
System.err.println("Failed to load custon font file: " + fontFile);
}
}
}
}
public static void InitFonts() {
File fontDirectory = new File(FrameIO.FONT_PATH);
if (fontDirectory != null) {
File[] fontFamilyDirs = fontDirectory.listFiles();
if (fontFamilyDirs != null) {
if (fontFamilyDirs.length > 0) {
System.out.println("Loading custom fonts:");
}
for (File fontFamilyDir : fontFamilyDirs) {
if (fontFamilyDir.isDirectory()) {
InitFontFamily(fontFamilyDir);
}
}
System.out.println();
}
}
}
/**
* Similar to parseArgs() in InteractiveWidget. Code based on routine used in
* Apache Ant: org.apache.tools.ant.types.Commandline::translateCommandline()
*
* @param toProcess
* the command line to process.
* @return the command line broken into strings. An empty or null toProcess
* parameter results in a zero sized array.
*/
public static String[] parseArgsApache(String toProcess) {
if (toProcess == null || toProcess.length() == 0) {
// no command? no string
return new String[0];
}
// parse with a simple finite state machine
final int normal = 0;
final int inQuote = 1;
final int inDoubleQuote = 2;
int state = normal;
final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", true);
final ArrayList result = new ArrayList();
final StringBuilder current = new StringBuilder();
boolean lastTokenHasBeenQuoted = false;
while (tok.hasMoreTokens()) {
String nextTok = tok.nextToken();
switch (state) {
case inQuote:
if ("\'".equals(nextTok)) {
lastTokenHasBeenQuoted = true;
state = normal;
} else {
current.append(nextTok);
}
break;
case inDoubleQuote:
if ("\"".equals(nextTok)) {
lastTokenHasBeenQuoted = true;
state = normal;
} else {
current.append(nextTok);
}
break;
default:
if ("\'".equals(nextTok)) {
state = inQuote;
} else if ("\"".equals(nextTok)) {
state = inDoubleQuote;
} else if (" ".equals(nextTok)) {
if (lastTokenHasBeenQuoted || current.length() != 0) {
result.add(current.toString());
current.setLength(0);
}
} else {
current.append(nextTok);
}
lastTokenHasBeenQuoted = false;
break;
}
}
if (lastTokenHasBeenQuoted || current.length() != 0) {
result.add(current.toString());
}
if (state == inQuote || state == inDoubleQuote) {
System.err.println("Error: Unbalanced quotes -- failed to parse '" + toProcess + "'");
return null;
}
return result.toArray(new String[result.size()]);
}
/**
* Creates a new Text Item with the given ID and text.
*
* @param id
* The id of this item
* @param text
* The text to use in this item
*/
public Text(int id, String text) {
super();
_text.append(text);
rebuild(false);
setID(id);
}
/**
* Creates a text item which is not added to the frame.
*
* @param text
*/
public Text(String text) {
super();
_text.append(text);
rebuild(false);
setID(-1);
}
/**
* Creates a new Text Item with the given ID
*
* @param id
* The ID to of this item
*/
public Text(int id) {
super();
setID(id);
}
public Text(int i, String string, Colour foreground, Colour background) {
this(i, string);
this.setColor(foreground);
this.setBackgroundColor(background);
}
/**
<<<<<<< .mine
* Sets the maximum width of this Text item when justification is used.
* passing in 0 or -1 means there is no maximum width
||||||| .r1094
* Sets the maximum width of this Text item when justifcation is used.
* passing in 0 or -1 means there is no maximum width
=======
* Sets the maximum width of this Text item when justifcation is used. passing
* in 0 or -1 means there is no maximum width
>>>>>>> .r1100
*
* @param width
* The maximum width of this item when justification is applied to
* it.
*/
@Override
public void setWidth(Integer width) {
invalidateAll();
if (width == null) {
setJustification(Justification.left);
setRightMargin(DisplayController.getFramePaintArea().getWidth(), false);
return;
}
_maxWidth = width;
rebuild(true);
invalidateAll();
}
/**
<<<<<<< .mine
* Returns the maximum width of this Text item when justification is used. If
* the width is negative, it means no explicit width has been set
||||||| .r1094
* Returns the maximum width of this Text item when justifcation is used. If
* the width is negative, it means no explicit width has been set
=======
* Returns the maximum width of this Text item when justifcation is used. If the
* width is negative, it means no explicit width has been set
>>>>>>> .r1100
*
* @return The maximum width of this Text item when justification is used
*/
@Override
public Integer getWidth()
{
if (_maxWidth == null || _maxWidth <= 0) return null;
return _maxWidth;
}
public Integer getAbsoluteWidth() {
if (_maxWidth == null) {
return Integer.MAX_VALUE;
}
return Math.abs(_maxWidth);
}
@Override
public Colour getHighlightColor() {
if (_highlightColour.equals(getPaintBackgroundColor()))
return ALTERNATE_HIGHLIGHT;
return _highlightColour;
}
/**
* Sets the justification of this Text item. The given integer should correspond
* to one of the JUSTIFICATION constants defined in Item
*
* @param just
* The justification to apply to this Text item
*/
public void setJustification(Justification just) {
invalidateAll();
// Only justification left works with 0 width
// if (just != null && just != Justification.left && !hasWidth()) {
// // TODO Tighten this up so it subtracts the margin widths
// setWidth(getBoundsWidth());
// }
_justification = just;
rebuild(true);
invalidateAll();
}
/**
* Returns the current justification of this Text item. The default value left
* justification.
*
* TODO: Why return null when justification is set to left? cts16
*
* @return The justification of this Text item
*/
public Justification getJustification()
{
if (_justification == null || _justification.equals(Justification.left)) return null;
return _justification;
}
/**
* Gets the distance from the left of the bounding box that the given layout
* should be shifted to justify it.
*
* @param layout
* The line of text to calculate the justification offset for.
*
* @return
* The distance to shift the line of text by.
*/
private int getJustOffset(TextLayout layout) {
if (getJustification() == Justification.center)
return (int) ((getAbsoluteWidth() - layout.getAdvance()) / 2);
else if (getJustification() == Justification.right)
return (int) (getAbsoluteWidth() - layout.getAdvance());
return 0;
}
/**
* Sets the text displayed on the screen to the given String. It does not reset
* the formula, attributeValuePair or other cached values.
*
* @param text
* The String to display on the screen when drawing this Item.
*/
@Override
public void setText(String text) {
setText(text, false);
}
public void setText(String text, Boolean clearCache) {
// if (_text != null && text.length() < _text.length())
invalidateAll();
_text = new StringBuffer(text);
/*
* Always clearingCach remove formulas when moving in and out of XRay mode
*/
if (clearCache) {
clearCache();
}
rebuild(true);
invalidateAll();
}
public void setTextList(List text) {
if (text == null || text.size() <= 0)
return;
invalidateAll();
StringBuffer sb = new StringBuffer();
for (int i = 0; i < text.size(); i++) {
sb.append(text.get(i)).append('\n');
}
if (sb.length() > 0)
sb.deleteCharAt(sb.length() - 1);
setText(sb.toString());
rebuild(true);
invalidateAll();
}
public void setAttributeValue(String value) {
AttributeValuePair avp = new AttributeValuePair(getText(), false);
avp.setValue(value);
setText(avp.toString());
}
/**
* Inserts the given String at the start of the first line of this Text Item.
*
* @param text
* The String to insert.
*/
public void prependText(String text)
{
invalidateAll();
_text.insert(0, text);
rebuild(false);
invalidateAll();
}
/**
* If the first line of text starts with the given String, then it is removed
* otherwise no action is taken.
*
* @param text
* The String to remove from the first line of Text
*/
public void removeText(String text) {
if (_text.length() > 0 && _text.indexOf(text) == 0) {
invalidateAll();
_text.delete(0, text.length());
invalidateAll();
}
}
public void removeEndText(String textToRemove) {
int length = _text.length();
if (length > 0) {
int pos = _text.indexOf(textToRemove);
int textToRemoveLength = textToRemove.length();
if (pos + textToRemoveLength == length) {
// Need the invalidate all for dateStamp toggling
invalidateAll();
_text.delete(pos, length);
invalidateAll();
}
}
}
/**
* Appends the given String to any text already present in this Item
*
* @param text
* The String to append.
*/
public void appendText(String text) {
invalidateAll();
_text.append(text);
rebuild(false);
invalidateAll();
}
/**
* Used by the frame reader to construct multi-line text items. It must run
* quickly, so that the system still responds well for long text items.
*
* @param text
*/
public void appendLine(String text) {
if (text == null)
text = "";
if (_text.length() > 0)
_text.append('\n');
_text.append(text);
rebuild(true);
}
/**
* Tests if the first line of this Text starts with the given String.
*
* @param text
* The prefix to check for
* @return True if the first line starts with the given String, False otherwise.
*/
public boolean startsWith(String text) {
return startsWith(text, true);
}
public boolean startsWith(String text, boolean ignoreCase) {
if (text == null || _text == null || _text.length() < 1)
return false;
if (ignoreCase)
return _text.toString().toLowerCase().startsWith(text.toLowerCase());
else
return _text.indexOf(text) == 0;
}
/**
* Inserts a character into the Text of this Item.
*
* @param ch
* The character insert.
* @param mouseX
* The X position to insert the Strings at.
* @param mouseY
* The Y position to insert the Strings at.
*/
public Point insertChar(char ch, float mouseX, float mouseY) {
if (ch != '\t') /* && ch != '\n' */
return insertText("" + ch, mouseX, mouseY);
return insertText(" " + ch, mouseX, mouseY);
}
/**
* @param index
* @return
*/
private char getNextBullet(char bullet) {
for (int i = 0; i < BULLETS.length - 1; i++) {
if (BULLETS[i] == bullet)
return BULLETS[i + 1];
}
return BULLETS[0];
}
private char getPreviousBullet(char bullet) {
for (int i = 1; i < BULLETS.length; i++) {
if (BULLETS[i] == bullet)
return BULLETS[i - 1];
}
return BULLETS[BULLETS.length - 1];
}
public Point getLineEndPosition(float mouseY) {
return getEdgePosition(getLinePosition(mouseY), false);
}
public Point getLineStartPosition(float mouseY) {
return getEdgePosition(getLinePosition(mouseY), true);
}
public Point getParagraphEndPosition() {
return getEdgePosition(_textLayouts.size() - 1, false);
}
public Point getParagraphStartPosition() {
return getEdgePosition(0, true);
}
private Point getEdgePosition(int line, boolean start) {
// if there is no text yet, or the line is invalid
if (_text == null || _text.length() == 0 || line < 0 || line > _textLayouts.size() - 1)
return new Point(getX(), getY());
TextLayout last = _textLayouts.get(line);
TextHitInfo hit;
if (start)
hit = last.getNextLeftHit(1);
else
hit = last.getNextRightHit(last.getCharacterCount() - 1);
// move the cursor to the new location
float[] caret = last.getCaretInfo(hit);
float y = getLineDrop(last) * line;
float x = getX() + caret[0] + getJustOffset(last);
x = Math.min(
x,
(getX() - Item.MARGIN_RIGHT - (2 * getGravity()) + getBoundsWidth())
);
return new Point((int) x, (int) (getY() + y + caret[1]));
}
public void setSelectionStart(Point p)
{
setSelectionStart(p.x, p.y);
}
public void setSelectionStart(float mouseX, float mouseY) {
// determine what line is being pointed to
int line = getLinePosition(mouseY);
// get the character being pointed to
TextHitInfo hit = getCharPosition(line, mouseX);
_selectionStart = hit.getInsertionIndex() + _textLayouts.get(line).getStartCharIndex();
// Clear the last selected
updateLastSelected();
invalidateAll();
}
public void setSelectionEnd(Point p)
{
setSelectionEnd(p.x, p.y);
}
public void setSelectionEnd(float mouseX, float mouseY) {
// determine what line is being pointed to
int line = getLinePosition(mouseY);
// get the character being pointed to
TextHitInfo hit = getCharPosition(line, mouseX);
_selectionEnd = hit.getInsertionIndex() + _textLayouts.get(line).getStartCharIndex();
// Clear the last selected
updateLastSelected();
invalidateAll();
}
public void clearSelection() {
_selectionStart = -1;
_selectionEnd = -1;
invalidateAll();
}
public void clearSelectionEnd() {
_selectionEnd = -1;
invalidateAll();
}
/** Makes sure only one text has a selection at a time. */
public void updateLastSelected()
{
if (_lastSelected != this) {
if (_lastSelected != null) _lastSelected.clearSelection();
_lastSelected = this;
}
}
public String copySelectedText() {
if (_selectionStart < 0 || _selectionEnd < 0)
return null;
else if (_selectionEnd > _text.length())
_selectionEnd = _text.length();
return _text.substring(Math.min(_selectionStart, _selectionEnd), Math.max(_selectionStart, _selectionEnd));
}
public String cutSelectedText() {
return replaceSelectedText("");
}
public String replaceSelectedText(String newText) {
if (_selectionStart < 0 || _selectionEnd < 0)
return null;
invalidateAll();
if (_selectionEnd > _text.length())
_selectionEnd = _text.length();
int left = Math.min(_selectionStart, _selectionEnd);
int right = Math.max(_selectionStart, _selectionEnd);
// Trim the text to remove new lines on the beginning and end of the
// string
if (_text.charAt(left) == '\n') {
// if the entire line is being removed then remove one of the new
// lines, the first case checks if the last line is being removed
if (right >= _text.length() || _text.charAt(right) == '\n') {
_text.deleteCharAt(left);
right--;
} else {
left++;
}
}
// New lines are always at the start of the line for now...
// if(_text.charAt(right - 1) == '\n' && left < right){
// right--;
// }
String s = _text.substring(left, right);
_text.delete(left, right);
_text.insert(left, newText);
rebuild(true);
clearCache();
invalidateAll();
return s;
}
public int getSelectionSize() {
if (_selectionEnd < 0 || _selectionStart < 0)
return 0;
// System.out.println(_selectionStart + ":" + _selectionEnd);
return Math.abs(_selectionEnd - _selectionStart);
}
/**
* Inserts the given String into the Text at the position given by the mouseX
* and mouseY coordinates
*
* @param text
* The String to insert into this Text.
* @param mouseX
* The X position to insert the String
* @param mouseY
* The Y position to insert the String
* @return The new location that the mouse cursor should be moved to
*/
public Point insertText(String text, float mouseX, float mouseY)
{
final Point newPos = insertText(text, mouseX, mouseY, -1);
return newPos;
}
public Point insertText(String text, float mouseX, float mouseY, int insertPos)
{
TextHitInfo hit;
TextLayout current = null;
int lineIndex;
invalidateAll();
// check for empty string
if (text == null || text.length() == 0)
return new Point((int) mouseX, (int) mouseY);
// if there is no text yet
if (_text == null || _text.length() == 0) {
_text = new StringBuffer().append(text);
// create the linebreaker and layouts
rebuild(true);
assert (_textLayouts.size() == 1);
current = _textLayouts.get(0);
hit = current.getNextRightHit(0);
lineIndex = 0;
// otherwise, we are inserting text
} else {
clearCache();
// determine what line is being pointed to
lineIndex = getLinePosition(mouseY);
// get the character being pointed to
hit = getCharPosition(lineIndex, mouseX);
int insertionIndex = hit.getInsertionIndex() + _textLayouts.get(lineIndex).getStartCharIndex();
if (lineIndex > 0 && hit.getInsertionIndex() == 0) {
// Only move forward a char if the line begins with a hard line
// break... not a soft line break
if (_text.charAt(insertionIndex) == '\n') {
insertionIndex++;
}
}
if (insertPos < 0)
insertPos = insertionIndex;
// if this is a backspace key
if (text.charAt(0) == '\b') {
if (hasSelection()) {
insertionIndex = deleteSelection(insertionIndex);
} else if (insertPos > 0) {
deleteChar(insertPos - 1);
if (insertionIndex > 0)
insertionIndex--;
}
// if this is a delete key
} else if (text.charAt(0) == (char) 0x7F) {
if (hasSelection()) {
insertionIndex = deleteSelection(insertionIndex);
} else if (insertPos < _text.length()) {
deleteChar(insertPos);
}
// this is a tab
} else if (text.charAt(0) == '\t') {
// Text length greater than 1 signals a backwards tab
if (text.length() > 1) {
// Find the first non space char to see if its a bullet
int index = 0;
for (index = 0; index < _text.length(); index++) {
if (!Character.isSpaceChar(_text.charAt(index)))
break;
}
// Check if there is a space after the bullet
if (index < _text.length() - 1 && _text.charAt(index + 1) == ' ') {
// Change the bullet
_text.setCharAt(index, getPreviousBullet(_text.charAt(index)));
}
// Remove the spacing at the start
for (int i = 0; i < TAB_STRING.length(); i++) {
if (_text.length() > 0 && Character.isSpaceChar(_text.charAt(0))) {
deleteChar(0);
insertionIndex--;
} else
break;
}
} else {
// / Find the first non space char to see if its a bullet
int index = 0;
for (index = 0; index < _text.length(); index++) {
if (!Character.isSpaceChar(_text.charAt(index)))
break;
}
// Check if there is a space after the bullet
if (index < _text.length() - 1 && _text.charAt(index + 1) == ' ') {
char nextBullet = getNextBullet(_text.charAt(index));
// Change the bullet
_text.setCharAt(index, nextBullet);
}
// Insert the spacing at the start
insertString(TAB_STRING, 0);
insertionIndex += TAB_STRING.length();
}
// this is a normal insert
} else {
insertString(text, insertPos);
insertionIndex += text.length();
}
if (_text.length() == 0) {
rebuild(false);
return new Point((int) this._x, (int) this._y);
}
int newLine = lineIndex;
// if a rebuild is required
rebuild(true, false);
// determine the new position the cursor should have
for (int i = 0; i < _textLayouts.size(); i++) {
if (_textLayouts.get(i).getEndCharIndex() + 1 >= insertionIndex) {
newLine = i;
break;
}
}
current = _textLayouts.get(newLine);
insertionIndex -= current.getStartCharIndex();
if (newLine == lineIndex) {
if (insertionIndex > 0)
hit = current.getNextRightHit(insertionIndex - 1);
else
hit = current.getNextLeftHit(1);
} else if (newLine < lineIndex) {
hit = current.getNextRightHit(insertionIndex - 1);
} else {
hit = current.getNextRightHit(insertionIndex - 1);
}
lineIndex = newLine;
}
// move the cursor to the new location
float[] caret = current.getCaretInfo(hit);
float y = getLineDrop(current) * lineIndex;
float x = getX() + caret[0] + getJustOffset(current);
x = Math.min(
x,
(getX() - Item.MARGIN_RIGHT - (2 * getGravity()) + getBoundsWidth())
);
invalidateAll();
return new Point(Math.round(x), Math.round(getY() + y + caret[1]));
}
/**
*
*/
private void clearCache() {
_attributeValuePair = null;
setProcessedText(null);
setFormula(null);
}
/**
* @param pos
* @return
*/
private int deleteSelection(int pos) {
int selectionLength = getSelectionSize();
cutSelectedText();
clearSelection();
pos -= selectionLength;
return pos;
}
public Point moveCursor(int direction, float mouseX, float mouseY, boolean setSelection, boolean wholeWord)
{
if (setSelection) {
if (!hasSelection()) {
setSelectionStart(mouseX, mouseY);
}
} else {
// clearSelection();
}
Point resultPos = null;
// check for home or end keys
switch (direction) {
case HOME:
resultPos = getParagraphStartPosition();
break;
case END:
resultPos = getParagraphEndPosition();
break;
case LINE_HOME:
resultPos = getLineStartPosition(mouseY);
break;
case LINE_END:
resultPos = getLineEndPosition(mouseY);
break;
default:
TextHitInfo hit;
TextLayout current;
int line;
// if there is no text yet
if (_text == null || _text.length() == 0) {
return new Point((int) mouseX, (int) mouseY);
// otherwise, move the cursor
} else {
// determine the line of text to check
line = getLinePosition(mouseY);
if (line < 0)
line = _textLayouts.size() - 1;
// if the cursor is moving up or down, change the line
if (direction == UP)
line = Math.max(line - 1, 0);
else if (direction == DOWN)
line = Math.min(line + 1, _textLayouts.size() - 1);
hit = getCharPosition(line, mouseX);
if (direction == LEFT) {
if (hit.getInsertionIndex() > 0) {
char prevChar = ' ';
do {
hit = _textLayouts.get(line).getNextLeftHit(hit);
// Stop if at the start of the line
if (hit.getInsertionIndex() == 0)
break;
// Keep going if the char to the left is a
// letterOrDigit
prevChar = _text.charAt(hit.getInsertionIndex() - 1 + _textLayouts.get(line).getStartCharIndex());
} while (wholeWord && Character.isLetterOrDigit(prevChar));
// TODO Go to the start of the word instead of before the word
char nextChar = _text.charAt(hit.getInsertionIndex() + _textLayouts.get(line).getStartCharIndex());
// This takes care of hard line break in
if (line > 0 && nextChar == '\n') {
line--;
hit = _textLayouts.get(line).getNextRightHit(_textLayouts.get(line).getCharacterCount() - 1);
}
// This takes care of soft line breaks.
} else if (line > 0) {
line--;
hit = _textLayouts.get(line).getNextRightHit(_textLayouts.get(line).getCharacterCount() - 1);
// Skip the spaces at the end of a line with soft linebreak
while (hit.getCharIndex() > 0 && _text.charAt(_textLayouts.get(line).getStartCharIndex() + hit.getCharIndex() - 1) == ' ') {
hit = _textLayouts.get(line).getNextLeftHit(hit);
}
}
} else if (direction == RIGHT) {
if (hit.getInsertionIndex() < _textLayouts.get(line).getCharacterCount()) {
hit = _textLayouts.get(line).getNextRightHit(hit);
// Skip whole word if needs be
while (wholeWord
&& hit.getCharIndex() > 0
&& hit.getCharIndex() < _textLayouts.get(line).getCharacterCount()
&& Character.isLetterOrDigit(_text.charAt(_textLayouts.get(line).getStartCharIndex() + hit.getCharIndex() - 1)))
{
hit = _textLayouts.get(line).getNextRightHit(hit);
}
} else if (line < _textLayouts.size() - 1) {
line++;
hit = _textLayouts.get(line).getNextLeftHit(1);
}
}
current = _textLayouts.get(line);
}
// move the cursor to the new location
float[] caret = current.getCaretInfo(hit);
float y = getLineDrop(current) * line;
resultPos = new Point((int) (getX() + caret[0] + getJustOffset(current)), (int) (getY() + y + caret[1]));
break;
}
if (setSelection) setSelectionEnd(resultPos.x, resultPos.y);
return resultPos;
}
/**
* Iterates through the given line string and returns the position of the
* character being pointed at by the mouse.
*
* @param line
* The index of the _text array of the String to be searched.
* @param mouseX
* The X coordinate of the mouse
* @return The position in the string of the character being pointed at.
*/
public TextHitInfo getCharPosition(int line, float mouseX) {
if (line < 0 || line >= _textLayouts.size())
return null;
TextLayout layout = _textLayouts.get(line);
mouseX += getOffset().x;
mouseX -= getJustOffset(layout);
return layout.hitTestChar(mouseX - getX(), 0);
}
/**
* Gets the index into the _textLayout
list which corresponds to the line
* covered by the given mouseY
position.
*
* @param mouseY
* The y-coordinate to test for line coverage.
*
* @return
* The line which occupies the given y-coordinate, or the last line if none do.
*/
public int getLinePosition(float mouseY) {
mouseY += getOffset().y;
float y = getY();
for (TextLayout text : _textLayouts) {
// calculate X to ensure it is in the shape
AxisAlignedBoxBounds bounds = text.getLogicalHighlightShape(0, text.getCharacterCount());
if (bounds.getWidth() < 1) bounds.getSize().width = 10;
double x = bounds.getCentreX();
if (bounds.contains((int) x, (int) (mouseY - y)))
return _textLayouts.indexOf(text);
// check if the cursor is between lines
if (mouseY - y < bounds.getMinY())
return Math.max(0, _textLayouts.indexOf(text) - 1);
y += getLineDrop(text);
}
return _textLayouts.size() - 1;
}
/**
* Sets the Font that this text will be displayed with on the screen.
*
* @param font
* The Font to display the Text of this Item in.
*/
public void setFont(Font font)
{
invalidateAll();
// all decoding occurs in the Utils class
_font = font;
// rejustify();
rebuild(false);
invalidateAll();
}
/**
* Gets the font of this text item.
*
* @return The Font assigned to this text item, or null if none is assigned.
*/
public Font getFont() {
return _font;
}
/**
* Gets the font that should be used to paint this text item during drawing.
*
* @return The font to paint the text item with.
*/
public Font getPaintFont()
{
if (getFont() == null) return EcosystemManager.getFontManager().getDefaultFont();
return getFont();
}
public String getFamily() {
return getPaintFont().getFamilyName();
}
public void setFamily(String newFamily)
{
setFont(new Font(newFamily, getFontStyle(), Math.round(getSize())));
setLetterSpacing(this._letter_spacing);
}
public Font.Style getFontStyle() {
Font f = getPaintFont();
return f.getStyle();
}
public static final String MONOSPACED_FONT = "monospaced";
public static final String[] FONT_WHEEL = { "sansserif", "monospaced", "serif", "dialog", "dialoginput" };
public static final char[] FONT_CHARS = { 's', 'm', 't', 'd', 'i' };
// A hashtable to store the font family names that are loaded in through the TTF
// files
// provided in the 'assets' folder
public static HashMap FONT_WHEEL_ADDITIONAL_LOOKUP = new HashMap<>();
private static final int NEARBY_GRAVITY = 2;
public static final int MINIMUM_FONT_SIZE = 6;
public void toggleFontFamily() {
String fontFamily = getFamily().toLowerCase();
// set it to the first font by default
setFamily(FONT_WHEEL[0]);
for (int i = 0; i < FONT_WHEEL.length - 3; i++) {
if (fontFamily.equals(FONT_WHEEL[i])) {
setFamily(FONT_WHEEL[i + 1]);
break;
}
}
}
public void toggleFontStyle() {
invalidateAll();
Font currentFont = getPaintFont();
Font.Style currentStyle = currentFont.getStyle();
Font.Style newStyle = Font.Style.PLAIN;
switch (currentStyle) {
case PLAIN:
newStyle = Font.Style.BOLD;
break;
case BOLD:
newStyle = Font.Style.ITALIC;
break;
case ITALIC:
newStyle = Font.Style.BOLD_ITALIC;
break;
default:
newStyle = Font.Style.PLAIN;
break;
}
setFont(new Font(currentFont.getFamilyName(), newStyle, currentFont.getSize()));
rebuild(true);
invalidateAll();
}
public void toggleBold() {
invalidateAll();
Font currentFont = getPaintFont();
currentFont.toggleBold();
setFont(currentFont);
rebuild(true);
invalidateAll();
}
public void toggleItalics() {
invalidateAll();
Font currentFont = getPaintFont();
currentFont.toggleItalic();
setFont(currentFont);
rebuild(true);
invalidateAll();
}
public void setFontStyle(String newFace)
{
Font currentFont = getPaintFont();
if (newFace == null || newFace.trim().length() == 0) {
currentFont.setStyle(Font.Style.PLAIN);
setFont(currentFont);
return;
}
newFace = newFace.toLowerCase().trim();
if (newFace.equals("plain") || newFace.equals("p")) {
currentFont.setStyle(Font.Style.PLAIN);
} else if (newFace.equals("bold") || newFace.equals("b")) {
currentFont.setStyle(Font.Style.BOLD);
} else if (newFace.equals("italic") || newFace.equals("i")) {
currentFont.setStyle(Font.Style.ITALIC);
} else if (newFace.equals("bolditalic") || newFace.equals("italicbold") || newFace.equals("bi") || newFace.equals("ib")) {
currentFont.setStyle(Font.Style.BOLD_ITALIC);
}
setFont(currentFont);
}
/**
* Returns a String array of this Text object's text, split up into separate
* lines.
*
* @return The String array with one element per line of text in this Item.
*/
public List getTextList() {
if (_text == null)
return null;
try {
List list = new LinkedList();
// Rebuilding prevents errors when displaying frame bitmaps
if (_textLayouts.size() == 0) {
rebuild(false);
}
for (TextLayout layout : _textLayouts) {
String text = layout.getLine().replaceAll("\n", "");
if (!text.equals("")) list.add(text);
}
return list;
} catch (Exception e) {
System.out.println(e.getMessage());
return null;
}
}
public String getText() {
return _text.toString();
}
/**
* Returns the first line of text in this Text Item
*
* @return The first line of Text
*/
public String getFirstLine() {
if (_text == null || _text.length() == 0)
return null;
// start at the first non-newLine char
int index = 0;
while (_text.charAt(index) == '\n') {
index++;
}
int nextNewLine = _text.indexOf("\n", index);
/* If there are no more newLines return the remaining text */
if (nextNewLine < 0)
return _text.substring(index);
return _text.substring(index, nextNewLine);
}
/**
* Sets the inter-line spacing (in pixels) of this text.
*
* @param spacing
* The number of pixels to allow between each line
*/
public void setSpacing(float spacing) {
_spacing = spacing;
invalidateBounds();
}
/**
* Returns the inter-line spacing (in pixels) of this Text.
*
* @return The spacing (inter-line) in pixels of this Text.
*/
public float getSpacing() {
return _spacing;
}
/**
* Gets the y-distance that should be advanced between this layout and the next.
*
* @param layout
* The TextLayout to calculate line-drop for.
*
* @return
* The distance to advance in the y-direction before the next line.
*/
private float getLineDrop(TextLayout layout)
{
if (getSpacing() < 0) {
return layout.getAscent() + layout.getDescent() + layout.getLeading();
}
return layout.getAscent() + layout.getDescent() + getSpacing();
}
public void setWordSpacing(int spacing) {
_word_spacing = spacing;
}
public int getWordSpacing() {
return _word_spacing;
}
/**
* Sets the spacing (proportional to the font size) between letters
*
* @param spacing
* Additional spacing to add between letters. See
* {@link java.awt.font.TextAttribute#TRACKING}
*/
public void setLetterSpacing(float spacing) {
_letter_spacing = spacing;
Font currentFont = getPaintFont();
currentFont.setSpacing(spacing);
setFont(currentFont);
}
/**
* @return The spacing (proportional to the font size) between letters. See
* {@link java.awt.font.TextAttribute#TRACKING}
*/
public float getLetterSpacing() {
return _letter_spacing;
}
public void setInitialSpacing(float spacing) {
_initial_spacing = spacing;
}
public float getInitialSpacing() {
return _initial_spacing;
}
// @Override
/* public boolean intersectsOLD(Polygon p) {
if (super.intersects(p)) {
float textY = getY();
for (TextLayout text : _textLayouts) {
// check left and right of each box
Rectangle2D textOutline = text.getLogicalHighlightShape(0, text.getCharacterCount()).getBounds2D();
textOutline.setRect(textOutline.getX() + getX() - 1, textOutline.getY() + textY - 1,
textOutline.getWidth() + 2, textOutline.getHeight() + 2);
if (p.intersects(textOutline))
return true;
textY += getLineDrop(text);
}
}
return false;
}*/
// The following version of intersect uses a tighter definition for the text,
// based on
@Override
public boolean intersects(PolygonBounds p) {
if (super.intersects(p)) {
// float textY = getY();
for (TextLayout text : _textLayouts) {
AxisAlignedBoxBounds text_pixel_bounds_rect = getPixelBounds(text);
if (p.intersects(text_pixel_bounds_rect)) {
return true;
}
}
}
return false;
}
@Override
public boolean contains(Point mousePosition) {
return contains(mousePosition.x, mousePosition.y, getGravity() * NEARBY_GRAVITY);
}
public boolean contains(int mouseX, int mouseY)
{
return contains(new Point(mouseX, mouseY));
}
public boolean contains(int mouseX, int mouseY, int gravity)
{
mouseX += getOffset().x;
mouseY += getOffset().y;
float textY = getY();
float textX = getX();
AxisAlignedBoxBounds outline = getBoundingBox();
if (outline == null) return false;
// Check if its outside the top and left and bottom bounds
if (outline.getMinX() - mouseX > gravity
|| outline.getMinY() - mouseY > gravity
|| mouseY - (outline.getMinY() + outline.getHeight()) > gravity
|| mouseX - (outline.getMinX() + outline.getWidth()) > gravity) {
return false;
}
for (TextLayout text : _textLayouts) {
// check left and right of each box
AxisAlignedBoxBounds textOutline = text.getLogicalHighlightShape(0, text.getCharacterCount());
// check if the cursor is within the top, bottom and within the
// gravity of right
int justOffset = getJustOffset(text);
if (mouseY - textY > textOutline.getMinY() &&
mouseY - textY < textOutline.getMinY() + textOutline.getHeight() &&
mouseX - textX - justOffset < textOutline.getWidth() + gravity + Item.MARGIN_RIGHT)
{
return true;
}
textY += getLineDrop(text);
}
return false;
}
/**
* Updates the Polygon (rectangle) that surrounds this Text on the screen.
*/
public AxisAlignedBoxBounds updateBounds()
{
// if there is no text, there is nothing to do
if (_text == null) return null;
if (_textLayouts == null || _textLayouts.size() < 1) return null;
int preChangeWidth = 0;
if (getOldBounds() != null) {
preChangeWidth = AxisAlignedBoxBounds.getEnclosing(getOldBounds()).getWidth();
}
int minX = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE;
int minY = Integer.MAX_VALUE;
int maxY = Integer.MIN_VALUE;
float y = -1;
// Fix concurrency error in ScaleFrameset
List tmpTextLayouts;
synchronized (_textLayouts) {
tmpTextLayouts = new LinkedList(_textLayouts);
}
for (TextLayout layout : tmpTextLayouts) {
AxisAlignedBoxBounds bounds = layout.getLogicalHighlightShape(0, layout.getCharacterCount());
if (y < 0)
y = 0;
else
y += getLineDrop(layout);
maxX = Math.max(maxX, (int) bounds.getMaxX());
minX = Math.min(minX, (int) bounds.getMinX());
maxY = Math.max(maxY, (int) (bounds.getMaxY() + y));
minY = Math.min(minY, (int) (bounds.getMinY() + y));
}
minX -= getLeftMargin();
maxX += Item.MARGIN_RIGHT;
// If its justification right or center then DONT limit the width
if (getJustification() != null) {
maxX = Item.MARGIN_RIGHT + getAbsoluteWidth();
}
AxisAlignedBoxBounds ret = new AxisAlignedBoxBounds(getX() + minX - getGravity(),
getY() + minY - getGravity(),
2 * getGravity() + maxX - minX,
2 * getGravity() + maxY - minY);
Dimension polySize = ret.getSize();
if(preChangeWidth != 0 && preChangeWidth != polySize.width) {
if (polySize.width > preChangeWidth) {
MagneticConstraints.getInstance().textGrown(this, polySize.width - preChangeWidth);
} else {
MagneticConstraints.getInstance().textShrunk(this, preChangeWidth - polySize.width);
}
}
return ret;
}
// TODO it seems like this method has some exponential processing which
// makes items copy really slowly when there are lots of lines of text!
// This needs to be fixed!!
public void rebuild(boolean limitWidth) {
rebuild(limitWidth, true);
}
/**
*
* @param limitWidth
* @param newLinebreakerAlways
* true if a new line breaker should always be created.
*/
private void rebuild(boolean limitWidth, boolean newLinebreakerAlways) {
// TODO make this more efficient so it only clears annotation list when it really has to
if (isAnnotation()) {
Frame parent = getParent();
// parent can be null when running tests
if (parent != null) {
parent.clearAnnotations();
}
}
// if there is no text, there is nothing to do
if (_text == null || _text.length() == 0) {
// Frame parent = getParent();
// if(parent != null)
// parent.removeItem(this);
return;
}
EcosystemManager.getTextLayoutManager().releaseLayouts(_textLayouts);
if (_textLayouts != null) _textLayouts.clear();
// Calculate the maximum allowable width of this line of text
List lines = null;
if(_autoWrap || ExperimentalFeatures.AutoWrap.get()) {
lines = new LinkedList();
if(DisplayController.getCurrentFrame() == null) {
return;
}
for(Item item : DisplayController.getCurrentFrame().getItems()) {
if(item instanceof Line) {
lines.add(new org.expeditee.core.Line (((Line) item).getStartItem().getPosition(), ((Line) item).getEndItem().getPosition()));
}
if(item instanceof Picture) {
lines.add(new org.expeditee.core.Line(item.getPosition(), new Point(item.getX(), item.getY() + item.getHeight())));
}
}
for(Item item : FreeItems.getInstance()) {
if(item instanceof Line) {
lines.add(new org.expeditee.core.Line(((Line) item).getStartItem().getPosition(), ((Line) item).getEndItem().getPosition()));
}
if(item instanceof Picture) {
lines.add(new org.expeditee.core.Line(item.getPosition(), new Point(item.getX(), item.getY() + item.getHeight())));
}
}
}
float width = Float.MAX_VALUE;
if (limitWidth) {
if(_maxWidth == null) {
width = DisplayController.getFramePaintArea().getWidth() - getX();
} else {
width = getAbsoluteWidth();
}
}
_textLayouts = EcosystemManager.getTextLayoutManager().layoutString(_text.toString(),
getPaintFont(),
new Point(getX(), getY()),
lines != null ? lines.toArray(new org.expeditee.core.Line[1]) : null,
(int) width,
(int) getSpacing(),
true,
getJustification() == Justification.full);
invalidateBounds();
}
/**
* Calculates the maximum possible distance a line can extend to the right from a given (x,y) point
* without crossing any of the lines in the given list.
*
* @param x
* The x-coordinate of the beginning point.
*
* @param y
* The y-coordinate of the beginning point.
*
* @param lines
* A list of pairs of points describing the lines that should stop the extension.
*
* @return
* The length of the extended line.
*/
/* private float getLineWidth(int x, float y, List lines) {
float width = FrameGraphics.getMaxFrameSize().width;
for (Point[] l : lines) {
// check for lines that cross over our y
if ((l[0].y >= y && l[1].y <= y) || (l[0].y <= y && l[1].y >= y)) {
float dX = l[0].x - l[1].x;
float dY = l[0].y - l[1].y;
float newWidth;
if (dX == 0) {
newWidth = l[0].x;
} else if (dY == 0) {
newWidth = Math.min(l[0].x, l[1].x);
} else {
// System.out.print("gradient: " + (dY / dX));
newWidth = l[0].x + (y - l[0].y) * dX / dY;
}
// System.out.println("dY:" + dY + " dX:" + dX + " width:" + newWidth);
if (newWidth < x) {
continue;
}
if (newWidth < width) {
width = newWidth;
}
}
}
return width - x;
}*/
private boolean hasFixedWidth() {
assert (_maxWidth != null);
if (_maxWidth == null) {
justify(false);
}
return _maxWidth > 0;
}
private int _alpha = -1;
public void setAlpha(int alpha) {
_alpha = alpha;
}
private Range getSelectedRange(int line) {
if (_selectionEnd >= _text.length()) {
_selectionEnd = _text.length();
}
if (_selectionStart < 0)
_selectionStart = 0;
if (_selectionStart < 0 || _selectionEnd < 0)
return null;
int selectionLeft = Math.min(_selectionStart, _selectionEnd);
int selectionRight = Math.max(_selectionStart, _selectionEnd);
// if the selection is after this line, return null
if (_textLayouts.get(line).getStartCharIndex() > selectionRight) return null;
// if the selection is before this line, return null
if (_textLayouts.get(line).getEndCharIndex() < selectionLeft) return null;
// Dont highlight a single char
// if (selectionRight - selectionLeft <= MINIMUM_RANGED_CHARS)
// return null;
// the selection occurs on this line, determine where it lies on the
// line
int start = Math.max(0, selectionLeft - _textLayouts.get(line).getStartCharIndex());
// int end = Math.min(_lineOffsets.get(line) +
// _textLayouts.get(line).getCharacterCount(), _selectionEnd);
int end = Math.min(selectionRight - _textLayouts.get(line).getStartCharIndex(), _textLayouts.get(line).getCharacterCount());
// System.out.println(line + ": " + start + "x" + end + " (" +
// _selectionStart + "x" + _selectionEnd + ")");
return new Range(start, end, true, true);
}
/** Sets the colour that should be used to render the selected range. */
public void setSelectionColour(Colour colour)
{
if (colour == null) colour = RANGE_SELECT_COLOUR;
_selectionColour = colour;
}
/** Gets the colour that should be used to render the selected range. */
public Colour getSelectionColour()
{
return _selectionColour;
}
@Override
public void paint()
{
if (!isVisible()) return;
// if there is no text to paint, do nothing.
if (_text == null || _text.length() == 0) return;
if (_autoWrap || ExperimentalFeatures.AutoWrap.get()) {
invalidateAll();
rebuild(true);
} else if (_textLayouts.size() < 1) {
clipFrameMargin();
rebuild(true);
// return;
}
// check if its a vector item and paint all the vector stuff too if this
// item is a free item
// This will allow for dragging vectors around the place!
if (hasVector() && isFloating()) {
DisplayController.requestRefresh(false);
// TODO make this use a more efficient paint method...
// Have the text item return a bigger repaint area if it has an
// associated vector
}
GraphicsManager g = EcosystemManager.getGraphicsManager();
AxisAlignedBoxBounds bounds = (AxisAlignedBoxBounds) getBounds();
// the background is only cleared if required
if (getBackgroundColor() != null) {
Colour bgc = getBackgroundColor();
if (_alpha > 0) {
bgc = new Colour(bgc.getRed(), bgc.getGreen(), bgc.getBlue(),
Colour.FromComponent255(_alpha));
}
Colour gradientColor = getGradientColor();
Fill fill;
if (gradientColor != null && bounds != null) {
fill = new GradientFill(bgc, new Point((int) (bounds.getMinX() + bounds.getWidth() * 0.3), bounds.getMinY()), gradientColor, new Point((int) (bounds.getMinX() + bounds.getWidth() * 1.3), bounds.getMinY()));
} else {
fill = new Fill(bgc);
}
g.drawRectangle(bounds, 0.0, fill, null, null, null);
}
if (hasVisibleBorder()) {
Stroke borderStroke = new Stroke(getThickness(), DEFAULT_CAP, DEFAULT_JOIN);
g.drawRectangle(bounds, 0.0, null, getPaintBorderColor(), borderStroke, null);
}
if (hasFormula()) {
Stroke highlightStroke = new Stroke(1F, DEFAULT_CAP, DEFAULT_JOIN);
Point start = getEdgePosition(0, true);
Point end = getEdgePosition(0, false);
g.drawLine(start, end, getPaintHighlightColor(), highlightStroke);
}
if (isHighlighted()) {
Stroke highlightStroke = new Stroke((float) getHighlightThickness(), DEFAULT_CAP, DEFAULT_JOIN);
Fill fill;
if (HighlightMode.Enclosed.equals(getHighlightMode())) {
fill = new Fill(getPaintHighlightColor());
} else {
fill = null;
}
g.drawRectangle(bounds, 0.0, fill, getPaintHighlightColor(), highlightStroke, null);
}
float y = getY();
Colour paintColour = getPaintColor();
if (_alpha > 0) {
paintColour = new Colour(paintColour);
paintColour.setAlpha(Colour.FromComponent255(_alpha));
}
Colour selectionColour = getSelectionColour();
// width -= getX();
// int line = 0;
// boolean tab = false;
synchronized (_textLayouts) {
for (int i = 0; i < _textLayouts.size(); i++) {
TextLayout layout = _textLayouts.get(i);
Range selectedRange = getSelectedRange(i);
if (selectedRange != null) {
AxisAlignedBoxBounds highlight = layout.getLogicalHighlightShape(selectedRange.lowerBound, selectedRange.upperBound);
highlight.getTopLeft().add(getX() + getJustOffset(layout), (int) y);
g.drawRectangle(highlight,
0.0,
new Fill(selectionColour),
null, null, null);
}
int ldx = 1 + getX() + getJustOffset(layout); // Layout draw x
g.drawTextLayout(layout, new Point(ldx, (int) y), paintColour);
y += getLineDrop(layout);
}
}
paintLink();
}
// TODO: Revise
@Override
protected AxisAlignedBoxBounds getLinkDrawArea()
{
return getDrawingArea();
}
/**
* Determines if this text has any text in it.
*
* @return True if this Item has no text in it, false otherwise.
*/
public boolean isEmpty()
{
return (_text == null || _text.length() == 0);
}
@Override
public Text copy() {
Text copy = new Text(getID());
// copy standard item values
Item.DuplicateItem(this, copy);
// copy values specific to text items
copy.setSpacing(getSpacing());
copy.setInitialSpacing(getInitialSpacing());
copy.setWidth(getWidthToSave());
copy.setJustification(getJustification());
copy.setLetterSpacing(getLetterSpacing());
copy.setWordSpacing(getWordSpacing());
copy.setWidth(getWidthToSave());
copy.setFont(getFont());
if (hasFormula()) {
copy.calculate(getFormula());
} else {
copy.setText(_text.toString());
}
copy.setHidden(!isVisible());
return copy;
}
@Override
public float getSize() {
return getPaintFont().getSize();
}
/**
* Returns the number of characters in this Text, excluding new lines.
*
* @return The sum of the length of each line of text
*/
public int getLength() {
return _text.length();
}
@Override
public void setSize(float size) {
invalidateAll();
// size *= UserSettings.ScaleFactor;
// Dont want to have size set when duplicating a point which has size 0
if (size < 0)
return;
if (size < MINIMUM_FONT_SIZE)
size = MINIMUM_FONT_SIZE;
Font currentFont = getPaintFont();
currentFont.setSize((int) size);
setFont(currentFont);
rebuild(true);
invalidateAll();
}
@Override
public void setAnnotation(boolean val) {
float mouseX = DisplayController.getFloatMouseX();
float mouseY = DisplayController.getFloatMouseY();
Point newPoint = new Point();
if (val) {
// if this is already an annotation, do nothing
if (isAnnotation())
return;
if (!isLineEnd() && _text.length() > 0 && _text.charAt(0) == DEFAULT_BULLET) {
newPoint.set(insertText("\b", mouseX, mouseY, 1));
if (_text.length() > 0 && _text.charAt(0) == ' ')
newPoint.set(insertText("\b", newPoint.x, newPoint.y, 1));
} else {
newPoint.set(insertText("@", mouseX, mouseY, 0));
}
} else {
// if this is not an annotation, do nothing
if (!isAnnotation())
return;
if (!isLineEnd() && _text.charAt(0) == '@') {
newPoint.set(insertText("\b", mouseX, mouseY, 1));
newPoint.set(insertText(DEFAULT_BULLET_STRING, newPoint.x, newPoint.y, 0));
} else {
newPoint.set(insertText("\b", mouseX, mouseY, 1));
}
}
FrameUtils.setLastEdited(this);
rebuild(true);
DisplayController.setCursorPosition(newPoint.x, newPoint.y, false);
}
/**
*
*/
private void insertString(String toInsert, int pos) {
assert (toInsert.length() > 0);
invalidateAll();
_text.insert(pos, toInsert);
rebuild(false);
invalidateAll();
}
private void deleteChar(int pos) {
_text.deleteCharAt(pos);
if (_text.length() == 0) {
if (this.isLineEnd()) {
// Remove and replace with a dot
Item.replaceText(this);
DisplayController.setCursorPosition(this._x, this._y);
}
return;
}
}
@Override
public boolean isAnnotation() {
if (_text != null && _text.length() > 0 && _text.charAt(0) == '@')
return true;
return false;
}
public boolean isSpecialAnnotation() {
assert _text != null;
String s = _text.toString().toLowerCase();
if (s.length() > 0 && s.indexOf("@") == 0) {
if (s.equals("@old") || s.equals("@ao") || s.equals("@itemtemplate") || s.equals("@parent")
|| s.equals("@next") || s.equals("@previous") || s.equals("@first") || s.equals("@i")
|| s.equals("@iw") || s.equals("@f"))
return true;
}
return false;
}
@Override
public Item merge(Item merger, int mouseX, int mouseY) {
if (merger.isLineEnd()) {
// Merging line ends onto non line end text is a no-op
if (!isLineEnd())
return null;
if (merger instanceof Text)
insertText(((Text) merger).getText(), mouseX, mouseY);
// Set the position by moving the cursor before calling this
// method!!
List lines = new LinkedList();
lines.addAll(merger.getLines());
for (Line line : lines) {
line.replaceLineEnd(merger, this);
}
merger.delete();
this.setOffset(0, 0);
return null;
}
if (!(merger instanceof Text))
return merger;
Text merge = (Text) merger;
// insertText(merge.getText(), mouseX, mouseY);
// if the item being merged has a link
if (merge.getLink() != null) {
// if this item has a link, keep it on the cursor
if (getLink() != null) {
merge.setText(merge.getLink());
merge.setLink(null);
// return merge;
// TODO get this to return the merged item and attach it to the
// cursor only when the user presses the middle button.
} else
setLink(merge.getLink());
}
return null;
}
/**
* Resets the position of the item to the default position for a title item.
*
*/
public void resetTitlePosition() {
setPosition(MARGIN_LEFT, MARGIN_LEFT + getBoundsHeight());
Frame modelFrame = getParentOrCurrentFrame();
if (modelFrame != null) {
setRightMargin(modelFrame.getNameItem().getX() - MARGIN_LEFT, true);
} else {
System.out.print("Error: text.resetTitlePosition, getParent or currentFrame returned null");
setRightMargin(MARGIN_LEFT, true);
}
}
/**
* Removes the set of characters up to the first space in this text item.
*
* @return the string that was removed.
*/
public String stripFirstWord() {
int firstSpace = _text.toString().indexOf(' ');
// if there is only one word just make it blank
if (firstSpace < 0 || firstSpace + 1 >= _text.length()) {
String text = _text.toString();
setText("");
return text;
}
String firstWord = _text.toString().substring(0, firstSpace);
setText(_text.toString().substring(firstSpace).trim());
return firstWord;
}
public String toString() {
String message = "[" + getFirstLine() + "]" + FRAME_NAME_SEPARATOR;
if (getParent() != null)
return message + getParent().getName();
return message + getDateCreated();
}
public Text getTemplateForm() {
Text template = this.copy();
template.setID(-1);
// reset width of global templates so the widths of the items on the settings
// frames don't cause issues
// this is in response to the fact that FrameCreator.addItem() sets rightMargin
// when it adds items
template.setWidth(null);
/*
* The template must have text otherwise the bounds height will be zero!! This
* will stop escape drop down from working if there is no item template
*/
template.setText("@");
return template;
}
/**
* Checks if the given point is 'near' any line of the text item.
*/
@Override
public boolean isNear(int x, int y) {
if (super.isNear(x, y)) {
// TODO check that it is actually near one of the lines of space
// return contains(x, y, getGravity() * 2 + NEAR_DISTANCE);
// at the moment contains ignores gravity when checking the top and
// bottom of text lines... so the cursor must be between two text
// lines
float textY = getY();
float textX = getX();
for (TextLayout text : _textLayouts) {
// check left and right of each box
AxisAlignedBoxBounds textOutline = text.getLogicalHighlightShape(0, text.getCharacterCount());
// check if the cursor is within the top, bottom and within the
// gravity of right
if (y - textY > textOutline.getMinY() - NEAR_DISTANCE &&
y - textY < textOutline.getMinY() + textOutline.getHeight() + NEAR_DISTANCE &&
x - textX < textOutline.getWidth() + NEAR_DISTANCE)
{
return true;
}
textY += getLineDrop(text);
}
}
return false;
}
@Override
public void anchor() {
super.anchor();
// ensure all text items have their selection cleared
clearSelection();
setAlpha(0);
if (isLineEnd())
DisplayController.setCursor(Item.DEFAULT_CURSOR);
String text = _text.toString().trim();
clipFrameMargin();
// Show the overlay stuff immediately if this is an overlay item
if (hasLink() && (text.startsWith("@ao") || text.startsWith("@o"))) {
StandardGestureActions.Refresh();
}
}
private void clipFrameMargin() {
if (!hasFixedWidth()) {
int frameWidth = DisplayController.getFramePaintArea().getWidth();
/*
* Only change width if it is more than 150 pixels from the right of the screen
*/
if (!_text.toString().contains(" ")) {
Integer width = getWidth();
if (width == null || width < 0)
setWidth(Integer.MIN_VALUE + 1);
} else if (frameWidth - getX() > ADJUST_WIDTH_THRESHOLD) {
justify(false);
// setRightMargin(frameWidth, false);
}
}
}
public void justify(boolean fixWidth, PolygonBounds enclosure) {
// if autowrap is on, wrapping is done every time we draw
if(ExperimentalFeatures.AutoWrap.get()) return;
Integer width = DisplayController.getFramePaintArea().getWidth();
// Check if that text item is inside an enclosing rectangle...
// Set its max width accordingly
if (enclosure != null) {
AxisAlignedBoxBounds bounds = AxisAlignedBoxBounds.getEnclosing(enclosure);
if (bounds.getWidth() > 200 && getX() < bounds.getWidth() / 3 + bounds.getMinX()) {
width = bounds.getMinX() + bounds.getWidth();
}
}
if (getWidth() == null) setRightMargin(width, fixWidth);
// Check for the annotation that restricts the width of text items on the frame
String widthString;
if ((widthString = getParentOrCurrentFrame().getAnnotationValue("maxwidth")) != null) {
try {
int oldWidth = getWidth();
int maxWidth = Integer.parseInt(widthString);
if (maxWidth < oldWidth)
setWidth(maxWidth);
} catch (NumberFormatException nfe) {
}
}
}
public void justify(boolean fixWidth) {
// if autowrap is on, wrapping is done every time we draw
if(ExperimentalFeatures.AutoWrap.get()) return;
this.justify(fixWidth, FrameUtils.getEnlosingPolygon());
}
public void resetFrameNamePosition() {
Dimension maxSize = DisplayController.getFramePaintArea().getSize();
if (maxSize != null) {
// setMaxWidth(maxSize.width);
setPosition(maxSize.width - getBoundsWidth(), getBoundsHeight());
}
}
@Override
protected int getLinkYOffset() {
if (_textLayouts.size() == 0)
return 0;
return Math.round(-(_textLayouts.get(0).getAscent() / 2));
}
@Override
public String getName() {
return getFirstLine();
}
public static final String TAB_STRING = " ";
public Point insertTab(char ch, float mouseX, float mouseY) {
return insertText("" + ch, mouseX, mouseY);
}
public Point removeTab(char ch, float mouseX, float mouseY) {
// Insert a space as a flag that it is a backwards tab
return insertText(ch + " ", mouseX, mouseY);
}
public static boolean isBulletChar(char c) {
for (int i = 0; i < BULLETS.length; i++) {
if (BULLETS[i] == c)
return true;
}
return c == '*' || c == '+' || c == '>' || c == '-' || c == 'o';
}
public boolean hasOverlay() {
if (!isAnnotation() || getLink() == null)
return false;
String text = getText().toLowerCase();
// TODO make it so can just check the _overlay variable
// Mike can't remember the reason _overlay var can't be use! oops
if (!text.startsWith("@"))
return false;
return text.startsWith("@o") || text.startsWith("@ao") || text.startsWith("@v") || text.startsWith("@av");
}
public boolean hasSelection() {
return getSelectionSize() > 0;
}
/**
* Dont save text items that are all white space.
*/
@Override
public boolean dontSave() {
String text = getText();
assert (text != null);
return text.trim().length() == 0 || super.dontSave();
}
@Override
public boolean calculate(String formula) {
if (DisplayController.isXRayMode())
return false;
super.calculate(formula);
if (isFloating() || formula == null || formula.length() == 0) {
return false;
}
formula = formula.replace(':', '=');
String lowercaseFormula = formula.toLowerCase();
ExpediteeJEP myParser = new ExpediteeJEP();
int nextVarNo = 1;
// Add variables from the containing rectangle if the item being
// calculated is inside the enclosing rectangle
Collection- enclosed = getItemsInSameEnclosure();
for (Item i : enclosed) {
if (i == this)
continue;
if (i instanceof Text && !i.isAnnotation()) {
AttributeValuePair pair = i.getAttributeValuePair();
if (pair.hasPair()) {
try {
double value = pair.getDoubleValue();
myParser.addVariable(pair.getAttribute(), value);
// myParser.addVariable("$" + nextVarNo++, value);
} catch (NumberFormatException nfe) {
continue;
} catch (Exception e) {
e.printStackTrace();
}
} // else {
// Add anonomous vars
try {
double value = pair.getDoubleValue();
if (value != Double.NaN)
myParser.addVariable("$" + nextVarNo++, value);
} catch (NumberFormatException nfe) {
continue;
} catch (Exception e) {
e.printStackTrace();
}
// }
}
}
// Add the variables from this frame
myParser.addVariables(this.getParentOrCurrentFrame());
String linkedFrame = getAbsoluteLink();
// Add the relative frame variable if the item is linked
if (linkedFrame != null) {
Frame frame = FrameIO.LoadFrame(linkedFrame);
myParser.addVariables(frame);
// If the frame is linked add vector variable for the frame
if (lowercaseFormula.contains("$frame")) {
myParser.addVectorVariable(frame.getNonAnnotationItems(true), "$frame");
}
}
// Add the relative box variable if this item is a line end
if (this.isLineEnd()) {
// if its a line end add the enclosed stuff as an @variable
if (lowercaseFormula.contains("$box")) {
myParser.addVectorVariable(getEnclosedItems(), "$box");
}
}
myParser.resetObserver();
try {
Node node = myParser.parse(formula);
String result = myParser.evaluate(node);
if (result != null) {
this.setText(result);
this.setFormula(formula);
if (!this.hasAction()) {
setActionMark(false);
setAction("extract formula");
}
}
} catch (Throwable e) {
// e.printStackTrace();
String formula2 = getFormula();
this.setText(formula2);
this.setFormula(formula2);
return false;
}
_attributeValuePair = null;
return true;
}
/**
* Gets items which are in the same enclosure as this item. In the event more
* than one enclosure meets this criteria, then the one returned is the one with
* the smallest area. TODO: Improve the efficiency of this method
*
* @return
*/
public Collection
- getItemsInSameEnclosure() {
Collection
- sameEnclosure = null;
Collection
- seen = new HashSet
- ();
Frame parent = getParentOrCurrentFrame();
double enclosureArea = Double.MAX_VALUE;
for (Item i : parent.getVisibleItems()) {
/*
* Go through all the enclosures looking for one that includes this item
*/
if (!seen.contains(i) && i.isEnclosed()) {
seen.addAll(i.getEnclosingDots());
Collection
- enclosed = i.getEnclosedItems();
// Check if we have found an enclosure containing this item
// Check it is smaller than any other enclosure found containing
// this item
if (enclosed.contains(this) && i.getEnclosedArea() < enclosureArea) {
sameEnclosure = enclosed;
}
}
}
if (sameEnclosure == null)
return new LinkedList
- ();
return sameEnclosure;
}
/**
* Returns true if items of the parent frame should be recalculated when this
* item is modified
*/
public boolean recalculateWhenChanged() {
if (/*
* !isAnnotation() &&
*/(hasFormula() || isLineEnd()))
return true;
try {
AttributeValuePair avp = getAttributeValuePair();
if (!avp.getDoubleValue().equals(Double.NaN))
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
public float getLineHeight() {
return getLineDrop(_textLayouts.get(0));
}
@Override
public void setAnchorLeft(Integer anchor) {
if (!isLineEnd()) {
super.setAnchorLeft(anchor);
// Subtract off the link width
if (anchor != null) {
setX(anchor + getLeftMargin());
}
return;
}
invalidateFill();
invalidateCommonTrait(ItemAppearence.PreMoved);
this._anchoring.setLeftAnchor(anchor);
int oldX = getX();
if (anchor != null) {
float deltaX = anchor + getLeftMargin() - oldX;
anchorConnected(AnchorEdgeType.Left, deltaX);
}
invalidateCommonTrait(ItemAppearence.PostMoved);
invalidateFill();
}
@Override
public void setAnchorRight(Integer anchor) {
if (!isLineEnd()) {
super.setAnchorRight(anchor);
// Subtract off the link width
if (anchor != null) {
setX(DisplayController.getFramePaintArea().getWidth() - anchor
- getBoundsWidth() + getLeftMargin());
}
return;
}
invalidateFill();
invalidateCommonTrait(ItemAppearence.PreMoved);
this._anchoring.setRightAnchor(anchor);
int oldX = getX();
if (anchor != null) {
float deltaX = DisplayController.getFramePaintArea().getWidth() - anchor
- getBoundsWidth() + getLeftMargin() - oldX;
anchorConnected(AnchorEdgeType.Right, deltaX);
}
invalidateCommonTrait(ItemAppearence.PostMoved);
invalidateFill();
}
@Override
public void setAnchorTop(Integer anchor) {
if (!isLineEnd()) {
super.setAnchorTop(anchor);
if (anchor != null) {
setY(anchor + _textLayouts.get(0).getAscent());
}
return;
}
invalidateFill();
invalidateCommonTrait(ItemAppearence.PreMoved);
this._anchoring.setTopAnchor(anchor);
int oldY = getY();
if (anchor != null) {
float deltaY = anchor - oldY;
anchorConnected(AnchorEdgeType.Top, deltaY);
}
invalidateCommonTrait(ItemAppearence.PostMoved);
invalidateFill();
}
@Override
public void setAnchorBottom(Integer anchor) {
if (!isLineEnd()) {
super.setAnchorBottom(anchor);
if (anchor != null) {
setY(DisplayController.getFramePaintArea().getHeight() - (anchor + this.getBoundsHeight() - _textLayouts.get(0).getAscent() - _textLayouts.get(0).getDescent()));
}
return;
}
invalidateFill();
invalidateCommonTrait(ItemAppearence.PreMoved);
this._anchoring.setBottomAnchor(anchor);
int oldY = getY();
if (anchor != null) {
float deltaY = DisplayController.getFramePaintArea().getHeight() - anchor - oldY;
anchorConnected(AnchorEdgeType.Bottom, deltaY);
}
invalidateCommonTrait(ItemAppearence.PostMoved);
invalidateFill();
}
@Override
public void scale(Float scale, int originX, int originY) {
setSize(getSize() * scale);
Integer width = getWidth();
if (width != null) {
setWidth(Math.round(width * scale));
}
super.scale(scale, originX, originY);
rebuild(true);
}
protected AxisAlignedBoxBounds getPixelBounds(TextLayout layout)
{
// Does 'layout' need to be synchronized (similar to _textLayouts below)??
int x = getX();
int y = getY();
int ldx = 1 + x + getJustOffset(layout); // Layout draw x
AxisAlignedBoxBounds layout_rect = layout.getPixelBounds(ldx, y);
return layout_rect;
}
/**
* Creates the smallest possible rectangle object to enclose the Text Item
* completely. Width of the rectangle is determined by the line in the Text Item
* that protrudes to the right the most. Height of the rectangle is determined
* by the number of lines in the Text Item.
*
* @return A rectangle enclosing the Text Item, without gravity represented.
* @see #getPixelBoundsUnionTight()
*/
public AxisAlignedBoxBounds getPixelBoundsUnion()
{
final AxisAlignedBoxBounds rect = getPixelBounds(_textLayouts.get(0));
int cumulativeHeight = rect.getSize().height;
int maxWidth = rect.getSize().width;
if (_textLayouts.size() > 1) {
for (int i = 1; i < _textLayouts.size(); i++) {
final AxisAlignedBoxBounds r = getPixelBounds(_textLayouts.get(i));
cumulativeHeight += _textLayouts.get(i).getDescent() + _textLayouts.get(i).getAscent();
if (r.getSize().width > maxWidth)
maxWidth = r.getSize().width;
}
}
rect.getSize().width = maxWidth;
rect.getSize().height = cumulativeHeight;
return rect;
}
/**
* Creates the smallest possible polygon to enclose the Text Item completely.
* The shape of the polygon is determined by the length of each line, tightly
* fitting the shape so that no white space is inside the resulting polygon.
*
* @return A polygon enclosing the Text Item, without gravity represented.
* @see #getPixelBoundsUnion()
*/
public PolygonBounds getPixelBoundsUnionTight()
{
final AxisAlignedBoxBounds rect = getPixelBounds(_textLayouts.get(0));
if (_textLayouts.size() == 1) {
return PolygonBounds.fromBox(rect);
} else {
final PolygonBounds poly = new PolygonBounds();
poly.addPoint(rect.getMinX(), rect.getMinY());
poly.addPoint(rect.getMaxX(), rect.getMinY());
poly.addPoint(rect.getMaxX(), Math.round(rect.getMaxY() + _textLayouts.get(0).getDescent()));
int y = (int) (rect.getMaxY() + _textLayouts.get(0).getDescent());
for (int i = 1; i < _textLayouts.size(); i++) {
final AxisAlignedBoxBounds r = getPixelBounds(_textLayouts.get(i));
poly.addPoint(r.getMaxX(), y);
poly.addPoint(r.getMaxX(), Math.round(y + r.getHeight() + _textLayouts.get(i).getDescent()));
y = Math.round(y + r.getHeight() + _textLayouts.get(i).getDescent());
}
poly.addPoint(rect.getMinX() + getPixelBounds(_textLayouts.get(_textLayouts.size() - 1)).getWidth(), Math.round(y + _textLayouts.get(_textLayouts.size() - 1).getDescent()));
poly.addPoint(rect.getMinX(), Math.round(y + _textLayouts.get(_textLayouts.size() - 1).getDescent()));
return poly;
}
}
/*
public AxisAlignedBoxBounds getPixelBoundsUnion()
{
synchronized (_textLayouts) {
CombinationBoxBounds c = null;
for (TextLayout layout: _textLayouts) {
if (c == null) {
c = new CombinationBoxBounds(getPixelBounds(layout));
} else {
c.add(getPixelBounds(layout));
}
}
return AxisAlignedBoxBounds.getEnclosing(c);
}
}
*/
// public Rectangle getPixelBoundsUnion()
// {
// synchronized (_textLayouts) {
//
// int x = getX();
// int y = getY();
//
// int min_xl = Integer.MAX_VALUE;
// int max_xr = Integer.MIN_VALUE;
//
// int min_yt = Integer.MAX_VALUE;
// int max_yb = Integer.MIN_VALUE;
//
//
// for (int i = 0; i < _textLayouts.size(); i++) {
// TextLayout layout = _textLayouts.get(i);
//
// int ldx = 1+x+getJustOffset(layout); // Layout draw x
// Rectangle layout_rect = layout.getPixelBounds(null, ldx, y);
//
// int xl = layout_rect.x;
// int xr = xl + layout_rect.width -1;
//
// int yt = layout_rect.y;
// int yb = yt + layout_rect.height -1;
//
// min_xl = Math.min(min_xl,xl);
// max_xr = Math.max(max_xr,xr);
//
// min_yt = Math.min(min_yt,yt);
// max_yb = Math.max(max_yb,yb);
// }
//
// if ((min_xl >= max_xr) || (min_yt >= max_yb)) {
// // No valid rectangle are found
// return null;
// }
//
// return new Rectangle(min_xl,min_yt,max_xr-min_xl+1,max_yb-min_yt+1);
//
// }
//
// }
/*
* Returns the SIMPLE statement contained by this text item.
*
*/
public String getStatement() {
return getText().split("\\s+")[0];
}
public boolean getAutoWrap() {
return _autoWrap;
}
// workaround since true is the default value and would not be displayed
// normally
public String getAutoWrapToSave() {
if (!_autoWrap) {
return null;
}
return "true";
}
public void setAutoWrap(boolean autoWrap) {
_autoWrap = autoWrap;
}
/**
* Creates a new Text Item whose text contains the given character. This
* method also moves the mouse cursor to be pointing at the newly created
* Text Item ready to insert the next character.
*
* @param start
* The character to use as the initial text of this Item.
* @return The newly created Text Item
*/
public static Text createText(char start) {
Text t = DisplayController.getCurrentFrame().createBlankText(
"" + start);
Point newMouse = t.insertChar(start, DisplayController.getMouseX(), DisplayController.getMouseY());
DisplayController.setCursorPosition(newMouse.x, newMouse.y, false);
return t;
}
/**
* Creates a new Text Item with no text. The newly created Item is a copy of
* any ItemTemplate if one is present, and inherits all the attributes of
* the Template
*
* @return The newly created Text Item
*/
public static Text createText() {
return DisplayController.getCurrentFrame().createNewText();
}
/**
* If the given Item is null, then a new Text item is created with the
* current date. If the given Item is not null, then the current date is
* prepended to the Item's text
*
* @param toAdd
* The Item to prepend the date to, or null
*/
public static void AddDate(Item toAdd) {
String date1 = Formatter.getDateTime();
String date2 = Formatter.getDate();
final String leftSeparator = " :";
final String rightSeparator = ": ";
String dateToAdd = date1 + rightSeparator;
boolean prepend = false;
boolean append = false;
// if the user is pointing at an item, add the date where ever the
// cursor is pointing
if (toAdd != null && toAdd instanceof Text) {
// permission check
if (!toAdd.hasPermission(UserAppliedPermission.full)) {
MessageBay.displayMessage("Insufficicent permission to add the date to that item");
return;
}
Text textItem = (Text) toAdd;
String text = textItem.getText();
// check if the default date has already been put on this item
if (text.startsWith(date1 + rightSeparator)) {
textItem.removeText(date1 + rightSeparator);
dateToAdd = date2 + rightSeparator;
prepend = true;
} else if (text.startsWith(date2 + rightSeparator)) {
textItem.removeText(date2 + rightSeparator);
dateToAdd = leftSeparator + date2;
append = true;
} else if (text.endsWith(leftSeparator + date2)) {
textItem.removeEndText(leftSeparator + date2);
append = true;
dateToAdd = leftSeparator + date1;
} else if (text.endsWith(leftSeparator + date1)) {
textItem.removeEndText(leftSeparator + date1);
if (textItem.getLength() > 0) {
dateToAdd = "";
prepend = true;
} else {
// use the default date format
prepend = true;
}
}
if (prepend) {
// add the date to the text item
textItem.prependText(dateToAdd);
if (dateToAdd.length() == textItem.getLength())
DisplayController.setCursorPosition(textItem.getParagraphEndPosition());
} else if (append) {
textItem.appendText(dateToAdd);
if (dateToAdd.length() == textItem.getLength())
DisplayController.setCursorPosition(textItem.getPosition());
} else {
for (int i = 0; i < date1.length(); i++) {
StandardGestureActions.processChar(date1.charAt(i), false);
}
}
textItem.getParent().setChanged(true);
DisplayController.requestRefresh(true);
// } else {
// MessageBay
// .displayMessage("Only text items can have the date prepended to
// them");
// }
// otherwise, create a new text item
} else {
Text newText = createText();
newText.setText(dateToAdd);
DisplayController.getCurrentFrame().addItem(newText);
DisplayController.getCurrentFrame().setChanged(true);
DisplayController.requestRefresh(true);
DisplayController.setCursorPosition(newText.getParagraphEndPosition());
}
}
}