/**
* 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 java.util.stream.Stream;
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.PolygonBounds;
import org.expeditee.encryption.items.surrogates.EncryptionDetail;
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.io.DefaultFrameWriter;
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 _width = -Integer.MAX_VALUE;
private Integer _minWidth = -Integer.MAX_VALUE;
private boolean _singleLine = false;
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;
private int _tabIndex = -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
protected StringBuffer _text = new StringBuffer();
// place holder text to use if _text is empty.
protected StringBuffer _placeholder = new StringBuffer();
private List _textLayouts = new LinkedList();
private List _maskTextLayouts = new LinkedList();
private List _placeholderTextLayouts = new LinkedList();
// The font to display this text in
private Font _font;
// The optional mask character to us in place of the text's content.
private Integer _mask = null;
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);
}
}
}
}
}
/**
* 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);
}
/**
* @param width
* The maximum width of this item when justification is applied to
* it.
*/
@Override
public void setWidth(Integer width) {
System.err.println("Text::setWidth::Text content=" + getText() + ", new width=" + width);
invalidateAll();
if (width == null) {
setJustification(Justification.left);
setRightMargin(DisplayController.getFramePaintAreaWidth(), false);
return;
}
if (width == 0) {
System.err.println("Width of Zero: " + getText());
}
_width = width;
rebuild(true);
invalidateAll();
if (isSurrogate()) {
surrogatePropertyInheritance.put(DefaultFrameWriter.WIDTH_TO_SAVE_STR, false);
Item primary = getPrimary();
if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.WIDTH_TO_SAVE_STR)) {
EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
primary.primaryPropertyEncryption.put(DefaultFrameWriter.WIDTH_TO_SAVE_STR, inheritanceCheckOnSave);
}
}
}
@Override
public void setMinWidth(final Integer width) {
invalidateAll();
if (width == null) {
setJustification(Justification.left);
setRightMargin(DisplayController.getFramePaintAreaWidth(), false);
return;
}
_minWidth = width;
rebuild(true);
invalidateAll();
if (isSurrogate()) {
surrogatePropertyInheritance.put(DefaultFrameWriter.MIN_WIDTH_TO_SAVE_STR, false);
Item primary = getPrimary();
if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.MIN_WIDTH_TO_SAVE_STR)) {
EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
primary.primaryPropertyEncryption.put(DefaultFrameWriter.MIN_WIDTH_TO_SAVE_STR, inheritanceCheckOnSave);
}
}
}
/**
* <<<<<<< .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 (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.WIDTH_TO_SAVE_STR)) {
return this.getPrimary().getWidth();
} else {
if (_width == null || _width <= 0)
return null;
return _width;
}
}
public Integer getAbsoluteWidth() {
if (_width == null || _width == Integer.MIN_VALUE) {
// When absoluting Integer.MIN_VALUE, the java API is defined to give you back Integer.MIN_VALUE!!?!
// This is because of the asymmetry of two's complement integer representation.
// We would prefer to use Integer.MAX_VALUE in this circumstance.
return Integer.MAX_VALUE;
}
return Math.abs(_width);
}
public Integer getMinWidth() {
if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.MIN_WIDTH_TO_SAVE_STR)) {
return this.getPrimary().getMinWidth();
} else {
if (_minWidth == null || _minWidth <= 0)
return null;
return _minWidth;
}
}
public Integer getAbsoluteMinWidth() {
if (_minWidth == null) {
return Integer.MAX_VALUE;
}
return Math.abs(_minWidth);
}
@Override
public Colour getHighlightColor() {
if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.HIGHLIGHT_STR)) {
return this.getPrimary().getHighlightColor();
} else {
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();
if (isSurrogate()) {
surrogatePropertyInheritance.put(DefaultFrameWriter.JUSTIFICATION_STR, false);
Item primary = getPrimary();
if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.JUSTIFICATION_STR)) {
EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
primary.primaryPropertyEncryption.put(DefaultFrameWriter.JUSTIFICATION_STR, inheritanceCheckOnSave);
}
}
}
/**
* 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 (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.JUSTIFICATION_STR)) {
return ((Text) this.getPrimary()).getJustification();
} else {
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);
if (isSurrogate()) {
surrogatePropertyInheritance.put(DefaultFrameWriter.TEXT_STR, false);
Item primary = getPrimary();
if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.TEXT_STR)) {
EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
primary.primaryPropertyEncryption.put(DefaultFrameWriter.TEXT_STR, inheritanceCheckOnSave);
}
}
}
/**
* 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') {
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(getTextLayouts().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 > getTextLayouts().size() - 1) {
return new Point(getX(), getY());
}
TextLayout last = getTextLayouts().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.getX(), p.getY());
}
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() + getTextLayouts().get(line).getStartCharIndex();
// Clear the last selected
updateLastSelected();
invalidateAll();
}
public void setSelectionEnd(Point p) {
setSelectionEnd(p.getX(), p.getY());
}
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() + getTextLayouts().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(final String text, final float mouseX, final float mouseY, int insertPos) {
TextHitInfo hit;
TextLayout currentLayout = null;
int lineIndex;
invalidateAll();
// if it is a empty string then do not move the mouse
if (text == null || text.length() == 0) {
return new Point((int) mouseX, (int) mouseY);
}
// if there is no text yet then simply append parameter and rebuild before moving on.
// rebuild re-initialises the TextLayouts
// calculate were we are in the content (hit, lineIndex, currentLayout)
if (_text == null || _text.length() == 0) {
_text = new StringBuffer().append(text);
rebuild(true);
assert (getTextLayouts().size() == 1);
currentLayout = getTextLayouts().get(0);
hit = currentLayout.getNextRightHit(0);
lineIndex = 0;
}
// otherwise we are inserting text and calculating the index into the content that we are at
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() + getTextLayouts().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 < getTextLayouts().size(); i++) {
if (getTextLayouts().get(i).getEndCharIndex() + 1 >= insertionIndex) {
newLine = i;
break;
}
}
currentLayout = getTextLayouts().get(newLine);
insertionIndex -= currentLayout.getStartCharIndex();
if (newLine == lineIndex) {
if (insertionIndex > 0) {
hit = currentLayout.getNextRightHit(insertionIndex - 1);
} else {
hit = currentLayout.getNextLeftHit(1);
}
} else if (newLine < lineIndex) {
hit = currentLayout.getNextRightHit(insertionIndex - 1);
} else {
hit = currentLayout.getNextRightHit(insertionIndex - 1);
}
lineIndex = newLine;
}
// If we have no mask then....
// move the cursor to the new location
float[] caret = currentLayout.getCaretInfo(hit);
float y = getLineDrop(currentLayout) * lineIndex;
y = getY() + y + caret[1];
float x = getX() + caret[0] + getJustOffset(currentLayout);
x = Math.min(x, (getX() - Item.MARGIN_RIGHT - (2 * getGravity()) + getBoundsWidth()));
invalidateAll();
final Point newCursor = new Point(Math.round(x), Math.round(y));
return newCursor;
}
/**
*
*/
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 = getTextLayouts().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, getTextLayouts().size() - 1);
}
hit = getCharPosition(line, mouseX);
if (direction == LEFT) {
if (hit.getInsertionIndex() > 0) {
char prevChar = ' ';
do {
hit = getTextLayouts().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 + getTextLayouts().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() + getTextLayouts().get(line).getStartCharIndex());
// This takes care of hard line break in
if (line > 0 && nextChar == '\n') {
line--;
hit = getTextLayouts().get(line)
.getNextRightHit(getTextLayouts().get(line).getCharacterCount() - 1);
}
// This takes care of soft line breaks.
} else if (line > 0) {
line--;
hit = getTextLayouts().get(line).getNextRightHit(getTextLayouts().get(line).getCharacterCount() - 1);
// Skip the spaces at the end of a line with soft linebreak
while (hit.getCharIndex() > 0 && _text
.charAt(getTextLayouts().get(line).getStartCharIndex() + hit.getCharIndex() - 1) == ' ') {
hit = getTextLayouts().get(line).getNextLeftHit(hit);
}
}
} else if (direction == RIGHT) {
if (hit.getInsertionIndex() < getTextLayouts().get(line).getCharacterCount()) {
hit = getTextLayouts().get(line).getNextRightHit(hit);
// Skip whole word if needs be
while (wholeWord && hit.getCharIndex() > 0
&& hit.getCharIndex() < getTextLayouts().get(line).getCharacterCount()
&& Character.isLetterOrDigit(_text
.charAt(getTextLayouts().get(line).getStartCharIndex() + hit.getCharIndex() - 1))) {
hit = getTextLayouts().get(line).getNextRightHit(hit);
}
} else if (line < getTextLayouts().size() - 1) {
line++;
hit = getTextLayouts().get(line).getNextLeftHit(1);
}
}
current = getTextLayouts().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.getX(), resultPos.getY());
}
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(final int line, float mouseX) {
if (line < 0 || line >= getTextLayouts().size()) {
return null;
}
final TextLayout layout = getTextLayouts().get(line);
mouseX += getOffset().getX();
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().getY();
float y = getY();
for (TextLayout text : getTextLayouts()) {
// 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 getTextLayouts().indexOf(text);
}
// check if the cursor is between lines
if (mouseY - y < bounds.getMinY()) {
return Math.max(0, getTextLayouts().indexOf(text) - 1);
}
y += getLineDrop(text);
}
return getTextLayouts().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();
_font = font;
rebuild(false);
invalidateAll();
if (isSurrogate()) {
surrogatePropertyInheritance.put(DefaultFrameWriter.FONT_STR, false);
Item primary = getPrimary();
if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.FONT_STR)) {
EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
primary.primaryPropertyEncryption.put(DefaultFrameWriter.FONT_STR, inheritanceCheckOnSave);
}
}
}
/**
* Gets the font of this text item.
*
* @return The Font assigned to this text item, or null if none is assigned.
*/
public Font getFont() {
if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.FONT_STR)) {
return ((Text) this.getPrimary()).getFont();
} else {
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() {
Font f = getFont();
if (f == null) {
_font = EcosystemManager.getFontManager().getDefaultFont().clone();
f = _font;
}
return f;
}
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 (getTextLayouts().size() == 0) {
rebuild(false);
}
for (TextLayout layout : getTextLayouts()) {
String text = layout.getLine().replaceAll("\n", "");
if (!text.equals("")) {
list.add(text);
}
}
return list;
} catch (Exception e) {
System.out.println("Exception in Text::getTextList::message is: " + e.getMessage());
return null;
}
}
@Override
public String getText() {
if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.TEXT_STR)) {
return this.getPrimary().getText();
} else {
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();
if (isSurrogate()) {
surrogatePropertyInheritance.put(DefaultFrameWriter.SPACING_STR, false);
Item primary = getPrimary();
if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.SPACING_STR)) {
EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
primary.primaryPropertyEncryption.put(DefaultFrameWriter.SPACING_STR, inheritanceCheckOnSave);
}
}
}
/**
* Returns the inter-line spacing (in pixels) of this Text.
*
* @return The spacing (inter-line) in pixels of this Text.
*/
public float getSpacing() {
if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.SPACING_STR)) {
return ((Text) this.getPrimary()).getSpacing();
} else {
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.
*/
protected 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;
if (isSurrogate()) {
surrogatePropertyInheritance.put(DefaultFrameWriter.WORD_SPACING_STR, false);
Item primary = getPrimary();
if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.WORD_SPACING_STR)) {
EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
primary.primaryPropertyEncryption.put(DefaultFrameWriter.WORD_SPACING_STR, inheritanceCheckOnSave);
}
}
}
public int getWordSpacing() {
if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.WORD_SPACING_STR)) {
return ((Text) this.getPrimary()).getWordSpacing();
} else {
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);
if (isSurrogate()) {
surrogatePropertyInheritance.put(DefaultFrameWriter.LETTER_SPACING_STR, false);
Item primary = getPrimary();
if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.LETTER_SPACING_STR)) {
EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
primary.primaryPropertyEncryption.put(DefaultFrameWriter.LETTER_SPACING_STR, inheritanceCheckOnSave);
}
}
}
/**
* @return The spacing (proportional to the font size) between letters. See
* {@link java.awt.font.TextAttribute#TRACKING}
*/
public float getLetterSpacing() {
if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.LETTER_SPACING_STR)) {
return ((Text) this.getPrimary()).getLetterSpacing();
} else {
return _letter_spacing;
}
}
public void setInitialSpacing(float spacing) {
_initial_spacing = spacing;
if (isSurrogate()) {
surrogatePropertyInheritance.put(DefaultFrameWriter.INITIAL_SPACING_STR, false);
Item primary = getPrimary();
if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.INITIAL_SPACING_STR)) {
EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
primary.primaryPropertyEncryption.put(DefaultFrameWriter.INITIAL_SPACING_STR, inheritanceCheckOnSave);
}
}
}
public float getInitialSpacing() {
if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.INITIAL_SPACING_STR)) {
return ((Text) this.getPrimary()).getInitialSpacing();
} else {
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 : getTextLayouts()) {
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.getX(), mousePosition.getY(), 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().getX();
mouseY += getOffset().getY();
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;
}
if (this.getMinWidth() != null && outline.contains(mouseX, mouseY)) {
return true;
}
for (TextLayout text : getTextLayouts()) {
// 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.
*/
@Override
public AxisAlignedBoxBounds updateBounds() {
boolean isFakeLayout = false;
// if there is no text, there is nothing to do
if (_text == null) {
return null;
}
// if there is no text layouts and the text has no min width, do nothing
if (getTextLayouts() == null || (getTextLayouts().size() < 1 && this.getMinWidth() == null)) {
return null;
}
if (this.getMinWidth() != null && getTextLayouts().size() == 0 && this.getFont() != null) {
// could use any text, 'p' is used simply to get some text in correct font.
getTextLayouts().add(TextLayout.getManager().layoutStringSimple("p", this.getFont()));
isFakeLayout = true;
}
int preChangeWidth = 0;
if (getOldBounds() != null) {
preChangeWidth = AxisAlignedBoxBounds.getEnclosing(getOldBounds()).getWidth();
}
int minX = Integer.MAX_VALUE;
int maxX = Integer.MIN_VALUE + 1; // +1 makes it safe to do math.abs on
int minY = Integer.MAX_VALUE;
int maxY = Integer.MIN_VALUE + 1; // +1 makes it safe to do math.abs on
float y = -1;
// Fix concurrency error in ScaleFrameset
List tmpTextLayouts;
synchronized (getTextLayouts()) {
tmpTextLayouts = new LinkedList(getTextLayouts());
}
for (int index = 0; index < tmpTextLayouts.size(); index++) {
final TextLayout layout = tmpTextLayouts.get(index);
AxisAlignedBoxBounds bounds = layout.getLogicalHighlightShape(0, layout.getCharacterCount());
if (y < 0) {
y = 0;
} else {
y += getLineDrop(layout);
}
int minWidth = getAbsoluteMinWidth();
minX = Math.min(minX, bounds.getMinX());
maxX = minWidth < Integer.MAX_VALUE ? Math.max(minX + minWidth, bounds.getMaxX())
: Math.max(maxX, bounds.getMaxX());
minY = Math.min(minY, (int) (bounds.getMinY() + y));
maxY = Math.max(maxY, (int) (bounds.getMaxY() + 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();
}
final int xPos = getX() + minX - getGravity();
final int yPos = getY() + minY - getGravity();
final int width = 2 * getGravity() + maxX - minX;
final int height = 2 * getGravity() + maxY - minY;
AxisAlignedBoxBounds ret = new AxisAlignedBoxBounds(xPos, yPos, width, height);
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);
}
}
if (isFakeLayout) {
getTextLayouts().remove(0).release();
}
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 ) && getPlaceholder() == null) {
// Frame parent = getParent();
// if(parent != null)
// parent.removeItem(this);
return;
}
EcosystemManager.getTextLayoutManager().releaseLayouts(getTextLayouts());
if (getTextLayouts() != null) {
getTextLayouts().clear();
}
EcosystemManager.getTextLayoutManager().releaseLayouts(_maskTextLayouts);
if (_maskTextLayouts != null) {
_maskTextLayouts.clear();
}
EcosystemManager.getTextLayoutManager().releaseLayouts(_placeholderTextLayouts);
if (_placeholderTextLayouts != null) {
_placeholderTextLayouts.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().getSortedItems()) {
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) Integer.MAX_VALUE;
if (limitWidth) {
if (_width == null) {
width = DisplayController.getFramePaintAreaWidth() - getX();
} else {
width = getAbsoluteWidth();
}
}
Font paintFont = getPaintFont();
if (this._text != null && this._text.length() > 0) {
List proposedTextLayout =
EcosystemManager.getTextLayoutManager().layoutString(
_text.toString(),
paintFont,
new Point(getX(), getY()), lines != null ? lines.toArray(new org.expeditee.core.Line[1]) : null,
(int) width,
(int) getSpacing(),
true,
getJustification() == Justification.full
);
if (proposedTextLayout.size() > 1 && isSingleLineOnly()) {
paintFont = paintFont.clone();
while(proposedTextLayout.size() > 1) {
paintFont.setSize(Math.max(paintFont.getSize() - 1, MINIMUM_FONT_SIZE));
proposedTextLayout =
EcosystemManager.getTextLayoutManager().layoutString(
_text.toString(),
paintFont,
new Point(getX(), getY()), lines != null ? lines.toArray(new org.expeditee.core.Line[1]) : null,
paintFont.getSize() > MINIMUM_FONT_SIZE ? (int) width : Integer.MAX_VALUE,
(int) getSpacing(),
true,
getJustification() == Justification.full
);
}
}
this._textLayouts = proposedTextLayout;
if (this.getMask() != null) {
final Stream maskStream = _text.toString().chars().mapToObj(c -> (char) this.getMask().intValue());
final StringBuilder sb = new StringBuilder();
maskStream.forEach(c -> sb.append(c));
this._maskTextLayouts =
EcosystemManager.getTextLayoutManager().layoutString(
sb.toString(),
paintFont,
new Point(getX(), getY()), lines != null ? lines.toArray(new org.expeditee.core.Line[1]) : null,
(int) width,
(int) getSpacing(),
true,
getJustification() == Justification.full
);
}
}
if (this.getPlaceholder() != null) {
this._placeholderTextLayouts =
EcosystemManager.getTextLayoutManager().layoutString(
getPlaceholder(),
paintFont,
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 (_width != null);
if (_width == null) {
justify(false);
}
return _width > 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 (getTextLayouts().get(line).getStartCharIndex() > selectionRight) {
return null;
}
// if the selection is before this line, return null
if (getTextLayouts().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 - getTextLayouts().get(line).getStartCharIndex());
// int end = Math.min(_lineOffsets.get(line) +
// _textLayouts.get(line).getCharacterCount(), _selectionEnd);
int end = Math.min(selectionRight - getTextLayouts().get(line).getStartCharIndex(),
getTextLayouts().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;
}
boolean hasContent = !(_text == null || (_text.length() == 0 && getMinWidth() == null));
boolean hasPlaceholderContent = getPlaceholder() != null;
// if the text to paint is empty string and there is no min width, do nothing.
if (!hasContent && !hasPlaceholderContent) {
return;
}
if (_autoWrap || ExperimentalFeatures.AutoWrap.get()) {
invalidateAll();
rebuild(true);
} else if (getTextLayouts().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(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));
}
if (getTextLayouts() == _placeholderTextLayouts) {
paintColour = paintColour.clone();
paintColour.setAlpha(Colour.FromComponent255(60));
}
Colour selectionColour = getSelectionColour();
// width -= getX();
// int line = 0;
// boolean tab = false;
synchronized (getTextLayouts()) {
for (int i = 0; i < getTextLayouts().size(); i++) {
TextLayout layout = getTextLayouts().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);
}
if (layout.getCharacterCount() == 0) {
continue;
}
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().clone());
copy.setMinWidth(getMinWidthToSave());
copy.setMask(_mask);
if (hasFormula()) {
copy.calculate(getFormula());
} else {
copy.setText(_text.toString());
}
copy.setHidden(!isVisible());
return copy;
}
@Override
public float getSize() {
if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.FONT_STR)) {
return ((Text) this.getPrimary()).getSize();
} else {
return getPaintFont().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();
if (isSurrogate()) {
surrogatePropertyInheritance.put(DefaultFrameWriter.FONT_STR, false);
Item primary = getPrimary();
if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.FONT_STR)) {
EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
primary.primaryPropertyEncryption.put(DefaultFrameWriter.FONT_STR, inheritanceCheckOnSave);
}
}
}
@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.getX(), newPoint.getY(), 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.getX(), newPoint.getY(), 0));
} else {
newPoint.set(insertText("\b", mouseX, mouseY, 1));
}
}
FrameUtils.setLastEdited(this);
rebuild(true);
DisplayController.setCursorPosition(newPoint.getX(), newPoint.getY(), 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.getMinWidth() != null) {
// final TextLayout base = _textLayouts.get(0);
// _textLayouts.set(0, TextLayout.get(" ", base.getFont(), 0, 1));
getTextLayouts().clear();
}
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() {
final int boundsHeight = getBoundsHeight();
setPosition(MARGIN_LEFT, MARGIN_LEFT + boundsHeight);
Frame modelFrame = getParentOrCurrentFrame();
if (modelFrame != null) {
int model_frame_name_x = modelFrame.getNameItem().getX();
if (model_frame_name_x < DisplayController.MINIMUM_FRAME_WIDTH) {
System.err.println("**** Text::resetTitlePostion(): value to be used as right margin from position of frameName < 512");
System.err.println(" Overriding to ensure reasonable width for title");
model_frame_name_x = DisplayController.MINIMUM_FRAME_WIDTH;
}
setRightMargin(model_frame_name_x - 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;
}
@Override
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 : getTextLayouts()) {
// 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.getFramePaintAreaWidth();
/*
* 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); // +1 makes it safe to do math.abs on
}
} 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.getFramePaintAreaWidth();
// 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.getSizeEnforceMinimum();
Dimension maxSize = DisplayController.getFramePaintAreaSize();
if (maxSize != null) {
// setMaxWidth(maxSize.width);
setPosition(maxSize.width - getBoundsWidth(), getBoundsHeight());
}
}
@Override
protected int getLinkYOffset() {
if (getTextLayouts().size() == 0) {
return 0;
}
return Math.round(-(getTextLayouts().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';
}
@Override
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 super.dontSave() || (text.trim().length() == 0 && this.getMinWidth() == null);
}
@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
*/
@Override
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(getTextLayouts().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();
if (isSurrogate()) {
surrogatePropertyInheritance.put(DefaultFrameWriter.ANCHOR_LEFT_STR, false);
Item primary = getPrimary();
if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.ANCHOR_LEFT_STR)) {
EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
primary.primaryPropertyEncryption.put(DefaultFrameWriter.ANCHOR_LEFT_STR, inheritanceCheckOnSave);
}
}
}
@Override
public void setAnchorRight(Integer anchor) {
if (!isLineEnd()) {
super.setAnchorRight(anchor);
// Subtract off the link width
if (anchor != null) {
setX(DisplayController.getFramePaintAreaWidth() - anchor - getBoundsWidth() + getLeftMargin());
//System.err.println("Text::setAnchorRight::boundsWidth=" + getBoundsWidth());
}
return;
}
invalidateFill();
invalidateCommonTrait(ItemAppearence.PreMoved);
this._anchoring.setRightAnchor(anchor);
int oldX = getX();
if (anchor != null) {
float deltaX = DisplayController.getFramePaintAreaWidth() - anchor - getBoundsWidth()
+ getLeftMargin() - oldX;
anchorConnected(AnchorEdgeType.Right, deltaX);
}
invalidateCommonTrait(ItemAppearence.PostMoved);
invalidateFill();
if (isSurrogate()) {
surrogatePropertyInheritance.put(DefaultFrameWriter.ANCHOR_RIGHT_STR, false);
Item primary = getPrimary();
if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.ANCHOR_RIGHT_STR)) {
EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
primary.primaryPropertyEncryption.put(DefaultFrameWriter.ANCHOR_RIGHT_STR, inheritanceCheckOnSave);
}
}
}
@Override
public void setAnchorTop(Integer anchor) {
if (!isLineEnd()) {
super.setAnchorTop(anchor);
if (anchor != null) {
if (!getTextLayouts().isEmpty()) {
final float ascent = getTextLayouts().get(0).getAscent();
setY(anchor + ascent);
} else if (this.getFont() != null) {
// p could be any character
final TextLayout fakeLayout = TextLayout.getManager().layoutStringSimple("p", this.getFont());
final float ascent = fakeLayout.getAscent();
EcosystemManager.getTextLayoutManager().releaseLayout(fakeLayout);
setY(anchor + ascent);
}
}
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();
if (isSurrogate()) {
surrogatePropertyInheritance.put(DefaultFrameWriter.ANCHOR_TOP_STR, false);
Item primary = getPrimary();
if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.ANCHOR_TOP_STR)) {
EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
primary.primaryPropertyEncryption.put(DefaultFrameWriter.ANCHOR_TOP_STR, inheritanceCheckOnSave);
}
}
}
@Override
public void setAnchorBottom(Integer anchor) {
if (!isLineEnd()) {
super.setAnchorBottom(anchor);
if (anchor != null) {
if (!getTextLayouts().isEmpty()) {
final float ascent = getTextLayouts().get(0).getAscent();
final float descent = getTextLayouts().get(0).getDescent();
setY(DisplayController.getFramePaintAreaHeight()
- (anchor + this.getBoundsHeight() - ascent - descent));
} else if (this.getFont() != null) {
// p could be any character
final TextLayout fakeLayout = TextLayout.getManager().layoutStringSimple("p", this.getFont());
final float ascent = fakeLayout.getAscent();
final float descent = fakeLayout.getDescent();
setY(DisplayController.getFramePaintAreaHeight()
- (anchor + this.getBoundsHeight() - ascent - descent));
}
}
return;
}
invalidateFill();
invalidateCommonTrait(ItemAppearence.PreMoved);
this._anchoring.setBottomAnchor(anchor);
int oldY = getY();
if (anchor != null) {
float deltaY = DisplayController.getFramePaintAreaHeight() - anchor - oldY;
anchorConnected(AnchorEdgeType.Bottom, deltaY);
}
invalidateCommonTrait(ItemAppearence.PostMoved);
invalidateFill();
if (isSurrogate()) {
surrogatePropertyInheritance.put(DefaultFrameWriter.ANCHOR_BOTTOM_STR, false);
Item primary = getPrimary();
if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.ANCHOR_BOTTOM_STR)) {
EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
primary.primaryPropertyEncryption.put(DefaultFrameWriter.ANCHOR_BOTTOM_STR, inheritanceCheckOnSave);
}
}
}
@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(getTextLayouts().get(0));
int cumulativeHeight = rect.getSize().height;
int maxWidth = rect.getSize().width;
if (getTextLayouts().size() > 1) {
for (int i = 1; i < getTextLayouts().size(); i++) {
final AxisAlignedBoxBounds r = getPixelBounds(getTextLayouts().get(i));
cumulativeHeight += getTextLayouts().get(i).getDescent() + getTextLayouts().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(getTextLayouts().get(0));
if (getTextLayouts().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() + getTextLayouts().get(0).getDescent()));
int y = (int) (rect.getMaxY() + getTextLayouts().get(0).getDescent());
for (int i = 1; i < getTextLayouts().size(); i++) {
final AxisAlignedBoxBounds r = getPixelBounds(getTextLayouts().get(i));
poly.addPoint(r.getMaxX(), y);
poly.addPoint(r.getMaxX(), Math.round(y + r.getHeight() + getTextLayouts().get(i).getDescent()));
y = Math.round(y + r.getHeight() + getTextLayouts().get(i).getDescent());
}
poly.addPoint(rect.getMinX() + getPixelBounds(getTextLayouts().get(getTextLayouts().size() - 1)).getWidth(),
Math.round(y + getTextLayouts().get(getTextLayouts().size() - 1).getDescent()));
poly.addPoint(rect.getMinX(), Math.round(y + getTextLayouts().get(getTextLayouts().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 + 1; // +1 makes it safe to do math.abs on
//
// int min_yt = Integer.MAX_VALUE;
// int max_yb = Integer.MIN_VALUE + 1; // +1 makes it safe to do math.abs on
//
//
// 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() {
if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.AUTO_WRAP_TO_SAVE_STR)) {
return ((Text) this.getPrimary()).getAutoWrap();
} else {
return _autoWrap;
}
}
// workaround since true is the default value and would not be displayed
// normally
public String getAutoWrapToSave() {
if (!getAutoWrap()) {
return null;
}
return "true";
}
public void setAutoWrap(boolean autoWrap) {
_autoWrap = autoWrap;
if (isSurrogate()) {
surrogatePropertyInheritance.put(DefaultFrameWriter.AUTO_WRAP_TO_SAVE_STR, false);
Item primary = getPrimary();
if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.AUTO_WRAP_TO_SAVE_STR)) {
EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
primary.primaryPropertyEncryption.put(DefaultFrameWriter.AUTO_WRAP_TO_SAVE_STR, inheritanceCheckOnSave);
}
}
}
/**
* 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.getX(), newMouse.getY(), 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());
}
}
public Integer getMask() {
if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.MASK_STR)) {
return ((Text) this.getPrimary()).getMask();
} else {
return _mask;
}
}
public void setMask(final Integer c) {
_mask = c;
if (isSurrogate()) {
surrogatePropertyInheritance.put(DefaultFrameWriter.MASK_STR, false);
Item primary = getPrimary();
if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.MASK_STR)) {
EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
primary.primaryPropertyEncryption.put(DefaultFrameWriter.MASK_STR, inheritanceCheckOnSave);
}
}
}
public String getPlaceholder() {
if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.PLACEHOLDER_STR)) {
return ((Text) this.getPrimary()).getPlaceholder();
} else {
if (_placeholder == null || _placeholder.length() == 0) {
return null;
}
return _placeholder.toString();
}
}
public void setPlaceholder(String _placeholder) {
this._placeholder = new StringBuffer(_placeholder);
if (isSurrogate()) {
surrogatePropertyInheritance.put(DefaultFrameWriter.PLACEHOLDER_STR, false);
Item primary = getPrimary();
if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.PLACEHOLDER_STR)) {
EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
primary.primaryPropertyEncryption.put(DefaultFrameWriter.PLACEHOLDER_STR, inheritanceCheckOnSave);
}
}
}
protected List getTextLayouts() {
if (this.getPlaceholder() != null && (this._text == null || this._text.length() == 0)) {
return _placeholderTextLayouts;
} else if (this.getMask() != null) {
return _maskTextLayouts;
} else {
return _textLayouts;
}
}
public boolean isSingleLineOnly() {
if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.SINGLE_LINE_ONLY_STR)) {
return ((Text) this.getPrimary()).isSingleLineOnly();
} else {
return _singleLine;
}
}
public void setSingleLineOnly(boolean _singleLine) {
this._singleLine = _singleLine;
if (isSurrogate()) {
surrogatePropertyInheritance.put(DefaultFrameWriter.SINGLE_LINE_ONLY_STR, false);
Item primary = getPrimary();
if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.SINGLE_LINE_ONLY_STR)) {
EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
primary.primaryPropertyEncryption.put(DefaultFrameWriter.SINGLE_LINE_ONLY_STR, inheritanceCheckOnSave);
}
}
}
public int getTabIndex() {
if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.TAB_INDEX_STR)) {
return ((Text) this.getPrimary()).getTabIndex();
} else {
return this._tabIndex;
}
}
public void setTabIndex(int index) {
this._tabIndex = index;
if (isSurrogate()) {
surrogatePropertyInheritance.put(DefaultFrameWriter.TAB_INDEX_STR, false);
Item primary = getPrimary();
if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.TAB_INDEX_STR)) {
EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
primary.primaryPropertyEncryption.put(DefaultFrameWriter.TAB_INDEX_STR, inheritanceCheckOnSave);
}
}
}
public Text getTabNext() {
if (this._tabIndex >= 0) {
Collection textItems = this.getParent().getTextItems();
Text ret = null;
for (Text t: textItems) {
if (t._tabIndex > this._tabIndex) {
if (ret == null) {
ret = t;
} else if (t._tabIndex < ret._tabIndex) {
ret = t;
}
}
}
return ret;
} else {
return null;
}
}
public Text getTabPrevious() {
if (this._tabIndex >= 0) {
Collection textItems = this.getParent().getTextItems();
Text ret = null;
for (Text t: textItems) {
if (t._tabIndex < this._tabIndex) {
if (ret == null) {
ret = t;
} else if (t._tabIndex > ret._tabIndex) {
ret = t;
}
}
}
return ret;
} else {
return null;
}
}
}