source: trunk/src/org/expeditee/items/Text.java@ 1416

Last change on this file since 1416 was 1416, checked in by bln4, 5 years ago

Fixed font size of surrogate effecting primary.
Added comment to further document how EncryptionDetail.Type.InheritanceCheckOnSave works.

File size: 99.1 KB
RevLine 
[919]1/**
2 * Text.java
3 * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
[4]19package org.expeditee.items;
20
[777]21import java.io.File;
[1047]22import java.util.ArrayList;
[242]23import java.util.Collection;
[594]24import java.util.HashMap;
[242]25import java.util.HashSet;
[4]26import java.util.LinkedList;
27import java.util.List;
[1047]28import java.util.StringTokenizer;
[1210]29import java.util.stream.Stream;
[4]30
[1101]31import org.expeditee.core.Colour;
32import org.expeditee.core.Dimension;
33import org.expeditee.core.Fill;
34import org.expeditee.core.Font;
35import org.expeditee.core.GradientFill;
36import org.expeditee.core.Point;
37import org.expeditee.core.Range;
38import org.expeditee.core.Stroke;
39import org.expeditee.core.TextHitInfo;
40import org.expeditee.core.TextLayout;
41import org.expeditee.core.bounds.AxisAlignedBoxBounds;
42import org.expeditee.core.bounds.PolygonBounds;
[1413]43import org.expeditee.encryption.items.surrogates.EncryptionDetail;
[1101]44import org.expeditee.gio.EcosystemManager;
45import org.expeditee.gio.GraphicsManager;
46import org.expeditee.gio.gesture.StandardGestureActions;
[242]47import org.expeditee.gui.AttributeValuePair;
[1101]48import org.expeditee.gui.DisplayController;
[156]49import org.expeditee.gui.Frame;
[176]50import org.expeditee.gui.FrameIO;
[247]51import org.expeditee.gui.FrameUtils;
[613]52import org.expeditee.gui.FreeItems;
[1101]53import org.expeditee.gui.MessageBay;
[1410]54import org.expeditee.io.DefaultFrameWriter;
[906]55import org.expeditee.items.MagneticConstraint.MagneticConstraints;
[190]56import org.expeditee.math.ExpediteeJEP;
[632]57import org.expeditee.settings.experimental.ExperimentalFeatures;
[1101]58import org.expeditee.stats.Formatter;
[176]59import org.nfunk.jep.Node;
[4]60
61/**
62 * Represents text displayed on the screen, which may include multiple lines.
63 * All standard text properties can be set (font, style, size).
64 *
65 * @author jdm18
66 *
67 */
68public class Text extends Item {
[400]69 private static final int ADJUST_WIDTH_THRESHOLD = 200;
[1210]70
[1101]71 public static final char DELETE_CHARACTER = 0x7F;
[1210]72
[1101]73 public static final char BACKSPACE_CHARACTER = '\b';
[1210]74
[1101]75 public static final char TAB_CHARACTER = '\t';
[1210]76
[1101]77 public static final char ESC_CHARACTER = 0x1B;
[400]78
[115]79 public static String LINE_SEPARATOR = System.getProperty("line.separator");
[137]80
[776]81 // public static char[] BULLETS = { '\u2219', '\u2218', '\u2217' };
82 public static char[] BULLETS = { '\u25AA', '\u25AB', '\u2217' };
[137]83
[115]84 private static char DEFAULT_BULLET = BULLETS[2];
[137]85
[115]86 private static String DEFAULT_BULLET_STRING = DEFAULT_BULLET + " ";
[137]87
[88]88 private String[] _processedText = null;
[7]89
[88]90 public String[] getProcessedText() {
91 return _processedText;
[7]92 }
93
[88]94 public void setProcessedText(String[] tokens) {
95 _processedText = tokens;
[7]96 }
97
[4]98 public static final String FRAME_NAME_SEPARATOR = " on frame ";
99
[1101]100 /** The default font used to display text items if no font is specified. */
[4]101 public static final String DEFAULT_FONT = "Serif-Plain-18";
102
[1101]103 public static final Colour DEFAULT_COLOR = Colour.BLACK;
[4]104
105 public static final int MINIMUM_RANGED_CHARS = 2;
106
[105]107 public static final int NONE = 0;
[4]108
[105]109 public static final int UP = 1;
[4]110
[105]111 public static final int DOWN = 2;
[4]112
[105]113 public static final int LEFT = 3;
[4]114
[105]115 public static final int RIGHT = 4;
[4]116
[105]117 public static final int HOME = 5;
[4]118
[147]119 public static final int LINE_HOME = 9;
120
121 public static final int LINE_END = 10;
122
[105]123 public static final int END = 6;
[13]124
[105]125 public static final int PAGE_DOWN = 7;
[13]126
[105]127 public static final int PAGE_UP = 8;
128
[1101]129 /**
[1095]130 * Set the width to be IMPLICIT, but as wide as possible, a negative width value
131 * is one that is implicitly set by the system... a positive value is one
132 * explicitly set by the user.
[410]133 */
[1101]134 /**
[1210]135 * The maximum allowable width of the Text item. Actual width may be less than
136 * this value, subject to text wrapping. Negative values indicate the width was
137 * implicitly set by the system, positive values indicate explicit setting by
138 * the user. Initially set to be as wide as possible.
[1101]139 */
[1369]140 private Integer _width = -Integer.MAX_VALUE;
[1210]141
[1200]142 private Integer _minWidth = -Integer.MAX_VALUE;
[1369]143
[1379]144 private boolean _singleLine = false;
145
[147]146 private Justification _justification = Justification.left;
[4]147
[659]148 private float _spacing = -1;
[4]149
150 private int _word_spacing = -1;
151
[659]152 private float _initial_spacing = 0;
[4]153
[659]154 private float _letter_spacing = 0;
[4]155
156 // used during ranging out
157 private int _selectionStart = -1;
158
159 private int _selectionEnd = -1;
[1381]160
161 private int _tabIndex = -1;
[1095]162
[1101]163 /** Keeps track of the last Text item selected. */
164 private static Text _lastSelected = null;
[1210]165
[1101]166 // Range selection colours
167 /** Colour of selected range when for selecting text. */
168 public static final Colour RANGE_SELECT_COLOUR = Colour.FromRGB255(255, 160, 160);
169 /** Colour of selected range when for cutting text. */
170 public static final Colour RANGE_CUT_COLOUR = Colour.FromRGB255(160, 255, 160);
171 /** Colour of selected range when for copying text. */
172 public static final Colour RANGE_COPY_COLOUR = Colour.FromRGB255(160, 160, 255);
173 /** Colour of selected range when for deleting text. */
174 public static final Colour RANGE_DELETE_COLOUR = Colour.FromRGB255(235, 235, 140);
[1210]175
[1101]176 /** The colour to draw range selections in. */
177 private Colour _selectionColour = RANGE_SELECT_COLOUR;
[1210]178
[613]179 // whether autowrap is on/off for this item
180 protected boolean _autoWrap = false;
[137]181
[4]182 // text is broken up into lines
[1200]183 protected StringBuffer _text = new StringBuffer();
[1296]184
185 // place holder text to use if _text is empty.
186 protected StringBuffer _placeholder = new StringBuffer();
[4]187
[1210]188 private List<TextLayout> _textLayouts = new LinkedList<TextLayout>();
189 private List<TextLayout> _maskTextLayouts = new LinkedList<TextLayout>();
[1296]190 private List<TextLayout> _placeholderTextLayouts = new LinkedList<TextLayout>();
[4]191
192 // The font to display this text in
193 private Font _font;
[1210]194
[1200]195 // The optional mask character to us in place of the text's content.
196 private Integer _mask = null;
[1095]197
[1210]198 protected static void InitFontFamily(File fontFamilyDir) {
[836]199 File[] fontFiles = fontFamilyDir.listFiles();
[4]200
[836]201 for (File fontFile : fontFiles) {
202 String ext = "";
203 String fileName = fontFile.getName().toLowerCase();
204
205 int i = fileName.lastIndexOf('.');
206 int p = Math.max(fileName.lastIndexOf('/'), fileName.lastIndexOf('\\'));
207
208 if (i > p) {
[1095]209 ext = fileName.substring(i + 1);
[777]210 }
[836]211
212 if (ext.equals("ttf")) {
213
214 try {
[1101]215 Font font = EcosystemManager.getFontManager().registerFontFile(fontFile);
[836]216
[1101]217 if (font != null) {
[982]218
[1210]219 String font_family = font.getFamilyName();
220 if (!FONT_WHEEL_ADDITIONAL_LOOKUP.containsKey(font_family)) {
221
[1095]222 if (FONT_WHEEL_ADDITIONAL_LOOKUP.size() > 0) {
223 System.out.print(", ");
224 }
[1101]225 System.out.print("'" + font_family + "'");
[1095]226
227 FONT_WHEEL_ADDITIONAL_LOOKUP.put(font_family, font);
228
[1101]229 /*
[1210]230 * int cdut = font.canDisplayUpTo("09AZaz"); if (cdut >= 0) { // Some problem
231 * has occured (should return -1 to show all chars possible)
232 *
233 * System.out.println(" [Non-ASCII font]"); }
234 */
[1095]235 System.out.flush();
[982]236 }
237
[1244]238 //System.out.print("'" + font_family + "'");
239
[1095]240 } else {
241 System.err.println("Error: Failed to add custom True-Type Font file: " + fontFile);
[982]242 }
[1095]243
244 } catch (Exception e) {
[836]245 System.err.println("Failed to load custon font file: " + fontFile);
246 }
[777]247 }
248 }
[836]249 }
[1095]250
[836]251 public static void InitFonts() {
[1095]252
[1101]253 File fontDirectory = new File(FrameIO.FONT_PATH);
254 if (fontDirectory != null) {
255 File[] fontFamilyDirs = fontDirectory.listFiles();
256 if (fontFamilyDirs != null) {
[836]257
[1101]258 if (fontFamilyDirs.length > 0) {
259 System.out.println("Loading custom fonts:");
260 }
[836]261
[1101]262 for (File fontFamilyDir : fontFamilyDirs) {
263 if (fontFamilyDir.isDirectory()) {
264 InitFontFamily(fontFamilyDir);
[836]265 }
266 }
267 }
268 }
[777]269 }
270
[1047]271 /**
[1095]272 * Similar to parseArgs() in InteractiveWidget. Code based on routine used in
273 * Apache Ant: org.apache.tools.ant.types.Commandline::translateCommandline()
274 *
275 * @param toProcess
276 * the command line to process.
277 * @return the command line broken into strings. An empty or null toProcess
278 * parameter results in a zero sized array.
279 */
280 public static String[] parseArgsApache(String toProcess) {
281 if (toProcess == null || toProcess.length() == 0) {
282 // no command? no string
283 return new String[0];
284 }
285 // parse with a simple finite state machine
[836]286
[1095]287 final int normal = 0;
288 final int inQuote = 1;
289 final int inDoubleQuote = 2;
290 int state = normal;
291 final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", true);
292 final ArrayList<String> result = new ArrayList<String>();
293 final StringBuilder current = new StringBuilder();
294 boolean lastTokenHasBeenQuoted = false;
[1047]295
[1095]296 while (tok.hasMoreTokens()) {
297 String nextTok = tok.nextToken();
298 switch (state) {
299 case inQuote:
300 if ("\'".equals(nextTok)) {
301 lastTokenHasBeenQuoted = true;
302 state = normal;
303 } else {
304 current.append(nextTok);
305 }
306 break;
307 case inDoubleQuote:
308 if ("\"".equals(nextTok)) {
309 lastTokenHasBeenQuoted = true;
310 state = normal;
311 } else {
312 current.append(nextTok);
313 }
314 break;
315 default:
316 if ("\'".equals(nextTok)) {
317 state = inQuote;
318 } else if ("\"".equals(nextTok)) {
319 state = inDoubleQuote;
320 } else if (" ".equals(nextTok)) {
321 if (lastTokenHasBeenQuoted || current.length() != 0) {
322 result.add(current.toString());
323 current.setLength(0);
324 }
325 } else {
326 current.append(nextTok);
327 }
328 lastTokenHasBeenQuoted = false;
329 break;
330 }
331 }
332 if (lastTokenHasBeenQuoted || current.length() != 0) {
333 result.add(current.toString());
334 }
335 if (state == inQuote || state == inDoubleQuote) {
336 System.err.println("Error: Unbalanced quotes -- failed to parse '" + toProcess + "'");
337 return null;
338 }
[1047]339
[1095]340 return result.toArray(new String[result.size()]);
341 }
[1047]342
[4]343 /**
344 * Creates a new Text Item with the given ID and text.
345 *
346 * @param id
347 * The id of this item
348 * @param text
349 * The text to use in this item
350 */
351 public Text(int id, String text) {
352 super();
353 _text.append(text);
354 rebuild(false);
355
356 setID(id);
357 }
[137]358
[108]359 /**
360 * Creates a text item which is not added to the frame.
[137]361 *
[108]362 * @param text
363 */
364 public Text(String text) {
365 super();
366 _text.append(text);
367 rebuild(false);
368 setID(-1);
369 }
[4]370
371 /**
372 * Creates a new Text Item with the given ID
373 *
374 * @param id
375 * The ID to of this item
376 */
377 public Text(int id) {
378 super();
379 setID(id);
380 }
381
[1101]382 public Text(int i, String string, Colour foreground, Colour background) {
[97]383 this(i, string);
384 this.setColor(foreground);
385 this.setBackgroundColor(background);
386 }
387
[4]388 /**
389 * @param width
[1095]390 * The maximum width of this item when justification is applied to
391 * it.
[4]392 */
393 @Override
[400]394 public void setWidth(Integer width) {
[185]395 invalidateAll();
[400]396
397 if (width == null) {
[362]398 setJustification(Justification.left);
[1258]399 setRightMargin(DisplayController.getFramePaintAreaWidth(), false);
[400]400 return;
[362]401 }
[4]402
[1369]403 _width = width;
[776]404 rebuild(true);
[185]405 invalidateAll();
[1413]406
407 if (isSurrogate()) {
408 surrogatePropertyInheritance.put(DefaultFrameWriter.WIDTH_TO_SAVE_STR, false);
[1414]409 Item primary = getPrimary();
[1413]410 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.WIDTH_TO_SAVE_STR)) {
[1415]411 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
412 primary.primaryPropertyEncryption.put(DefaultFrameWriter.WIDTH_TO_SAVE_STR, inheritanceCheckOnSave);
[1413]413 }
414 }
[4]415 }
[1210]416
[1200]417 @Override
418 public void setMinWidth(final Integer width) {
419 invalidateAll();
[1210]420
[1200]421 if (width == null) {
422 setJustification(Justification.left);
[1258]423 setRightMargin(DisplayController.getFramePaintAreaWidth(), false);
[1200]424 return;
425 }
[1210]426
[1200]427 _minWidth = width;
428 rebuild(true);
429 invalidateAll();
[1413]430
431 if (isSurrogate()) {
432 surrogatePropertyInheritance.put(DefaultFrameWriter.MIN_WIDTH_TO_SAVE_STR, false);
[1414]433 Item primary = getPrimary();
[1413]434 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.MIN_WIDTH_TO_SAVE_STR)) {
[1415]435 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
436 primary.primaryPropertyEncryption.put(DefaultFrameWriter.MIN_WIDTH_TO_SAVE_STR, inheritanceCheckOnSave);
[1413]437 }
438 }
[1200]439 }
[4]440
441 /**
[1210]442 * <<<<<<< .mine Returns the maximum width of this Text item when justification
443 * is used. If the width is negative, it means no explicit width has been set
444 * ||||||| .r1094 Returns the maximum width of this Text item when justifcation
445 * is used. If the width is negative, it means no explicit width has been set
446 * ======= Returns the maximum width of this Text item when justifcation is
447 * used. If the width is negative, it means no explicit width has been set
448 * >>>>>>> .r1100
[4]449 *
450 * @return The maximum width of this Text item when justification is used
451 */
452 @Override
[1210]453 public Integer getWidth() {
[1413]454 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.WIDTH_TO_SAVE_STR)) {
[1414]455 return this.getPrimary().getWidth();
[1413]456 } else {
[1415]457 if (_width == null || _width <= 0)
458 return null;
[1413]459 return _width;
[1190]460 }
[4]461 }
462
[400]463 public Integer getAbsoluteWidth() {
[1415]464 if (_width == null || _width == Integer.MIN_VALUE) {
465 // When absoluting Integer.MIN_VALUE, the java API is defined to give you back Integer.MIN_VALUE!!?!
466 // This is because of the asymmetry of two's complement integer representation.
467 // We would prefer to use Integer.MAX_VALUE in this circumstance.
[400]468 return Integer.MAX_VALUE;
469 }
[1415]470
[1369]471 return Math.abs(_width);
[400]472 }
[1210]473
[1200]474 public Integer getMinWidth() {
[1413]475 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.MIN_WIDTH_TO_SAVE_STR)) {
[1414]476 return this.getPrimary().getMinWidth();
[1413]477 } else {
[1415]478 if (_minWidth == null || _minWidth <= 0)
479 return null;
[1413]480 return _minWidth;
[1200]481 }
482 }
[1210]483
[1200]484 public Integer getAbsoluteMinWidth() {
485 if (_minWidth == null) {
486 return Integer.MAX_VALUE;
487 }
488 return Math.abs(_minWidth);
489 }
[1369]490
[121]491 @Override
[1101]492 public Colour getHighlightColor() {
[1413]493 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.HIGHLIGHT_STR)) {
[1414]494 return this.getPrimary().getHighlightColor();
[1413]495 } else {
496 return _highlightColour;
[1190]497 }
[121]498 }
499
[4]500 /**
[1095]501 * Sets the justification of this Text item. The given integer should correspond
502 * to one of the JUSTIFICATION constants defined in Item
[4]503 *
504 * @param just
505 * The justification to apply to this Text item
506 */
[97]507 public void setJustification(Justification just) {
[362]508 invalidateAll();
509
510 // Only justification left works with 0 width
[400]511 // if (just != null && just != Justification.left && !hasWidth()) {
512 // // TODO Tighten this up so it subtracts the margin widths
513 // setWidth(getBoundsWidth());
514 // }
[362]515
[4]516 _justification = just;
[199]517 rebuild(true);
[362]518 invalidateAll();
[1413]519
520 if (isSurrogate()) {
521 surrogatePropertyInheritance.put(DefaultFrameWriter.JUSTIFICATION_STR, false);
[1414]522 Item primary = getPrimary();
[1413]523 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.JUSTIFICATION_STR)) {
[1415]524 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
525 primary.primaryPropertyEncryption.put(DefaultFrameWriter.JUSTIFICATION_STR, inheritanceCheckOnSave);
[1413]526 }
527 }
[4]528 }
529
530 /**
[1095]531 * Returns the current justification of this Text item. The default value left
532 * justification.
[4]533 *
[1101]534 * TODO: Why return null when justification is set to left? cts16
535 *
[4]536 * @return The justification of this Text item
537 */
[1210]538 public Justification getJustification() {
[1413]539 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.JUSTIFICATION_STR)) {
[1414]540 return ((Text) this.getPrimary()).getJustification();
[1410]541 } else {
542 if (_justification == null || _justification.equals(Justification.left)) {
543 return null;
544 }
545
546 return _justification;
[1190]547 }
[4]548 }
549
[1101]550 /**
551 * Gets the distance from the left of the bounding box that the given layout
552 * should be shifted to justify it.
553 *
554 * @param layout
[1210]555 * The line of text to calculate the justification offset for.
[1101]556 *
[1210]557 * @return The distance to shift the line of text by.
[1101]558 */
[4]559 private int getJustOffset(TextLayout layout) {
[1190]560 if (getJustification() == Justification.center) {
[400]561 return (int) ((getAbsoluteWidth() - layout.getAdvance()) / 2);
[1190]562 } else if (getJustification() == Justification.right) {
[400]563 return (int) (getAbsoluteWidth() - layout.getAdvance());
[1190]564 }
[4]565
566 return 0;
567 }
568
569 /**
[1095]570 * Sets the text displayed on the screen to the given String. It does not reset
571 * the formula, attributeValuePair or other cached values.
[4]572 *
573 * @param text
574 * The String to display on the screen when drawing this Item.
575 */
[176]576 @Override
[4]577 public void setText(String text) {
[390]578 setText(text, false);
579 }
[400]580
[390]581 public void setText(String text, Boolean clearCache) {
[376]582 // if (_text != null && text.length() < _text.length())
583 invalidateAll();
[4]584 _text = new StringBuffer(text);
[376]585
586 /*
[1095]587 * Always clearingCach remove formulas when moving in and out of XRay mode
[376]588 */
[400]589 if (clearCache) {
[390]590 clearCache();
[400]591 }
592
[247]593 rebuild(true);
[126]594 invalidateAll();
[4]595 }
596
597 public void setTextList(List<String> text) {
[1190]598 if (text == null || text.size() <= 0) {
[4]599 return;
[1190]600 }
[4]601
[376]602 invalidateAll();
603
604 StringBuffer sb = new StringBuffer();
605
606 for (int i = 0; i < text.size(); i++) {
607 sb.append(text.get(i)).append('\n');
[4]608 }
[376]609
[1190]610 if (sb.length() > 0) {
[376]611 sb.deleteCharAt(sb.length() - 1);
[1190]612 }
[376]613
614 setText(sb.toString());
615
616 rebuild(true);
617 invalidateAll();
[4]618 }
619
620 public void setAttributeValue(String value) {
[289]621 AttributeValuePair avp = new AttributeValuePair(getText(), false);
[286]622 avp.setValue(value);
623 setText(avp.toString());
[4]624 }
625
626 /**
[1095]627 * Inserts the given String at the start of the first line of this Text Item.
[4]628 *
629 * @param text
630 * The String to insert.
631 */
[1210]632 public void prependText(String text) {
[1101]633 invalidateAll();
[4]634 _text.insert(0, text);
635 rebuild(false);
[1101]636 invalidateAll();
[4]637 }
638
639 /**
[1095]640 * If the first line of text starts with the given String, then it is removed
641 * otherwise no action is taken.
[4]642 *
643 * @param text
644 * The String to remove from the first line of Text
645 */
646 public void removeText(String text) {
[121]647 if (_text.length() > 0 && _text.indexOf(text) == 0) {
[247]648 invalidateAll();
[4]649 _text.delete(0, text.length());
[1101]650 invalidateAll();
[121]651 }
[4]652
653 }
[7]654
[4]655 public void removeEndText(String textToRemove) {
656 int length = _text.length();
657 if (length > 0) {
658 int pos = _text.indexOf(textToRemove);
659 int textToRemoveLength = textToRemove.length();
660 if (pos + textToRemoveLength == length) {
[1101]661 // Need the invalidate all for dateStamp toggling
662 invalidateAll();
[4]663 _text.delete(pos, length);
[1101]664 invalidateAll();
[4]665 }
666 }
[137]667
[4]668 }
669
670 /**
671 * Appends the given String to any text already present in this Item
672 *
673 * @param text
674 * The String to append.
675 */
676 public void appendText(String text) {
[1101]677 invalidateAll();
[4]678 _text.append(text);
679 rebuild(false);
[1101]680 invalidateAll();
[4]681 }
[7]682
[404]683 /**
684 * Used by the frame reader to construct multi-line text items. It must run
685 * quickly, so that the system still responds well for long text items.
686 *
687 * @param text
688 */
[4]689 public void appendLine(String text) {
[1190]690 if (text == null) {
[4]691 text = "";
[1190]692 }
[7]693
[1190]694 if (_text.length() > 0) {
[4]695 _text.append('\n');
[1190]696 }
[7]697
[4]698 _text.append(text);
[1095]699
[776]700 rebuild(true);
[1413]701
702 if (isSurrogate()) {
703 surrogatePropertyInheritance.put(DefaultFrameWriter.TEXT_STR, false);
[1414]704 Item primary = getPrimary();
[1413]705 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.TEXT_STR)) {
[1415]706 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
707 primary.primaryPropertyEncryption.put(DefaultFrameWriter.TEXT_STR, inheritanceCheckOnSave);
[1413]708 }
709 }
[4]710 }
711
712 /**
713 * Tests if the first line of this Text starts with the given String.
714 *
715 * @param text
716 * The prefix to check for
[1095]717 * @return True if the first line starts with the given String, False otherwise.
[4]718 */
719 public boolean startsWith(String text) {
720 return startsWith(text, true);
721 }
722
723 public boolean startsWith(String text, boolean ignoreCase) {
[1190]724 if (text == null || _text == null || _text.length() < 1) {
[4]725 return false;
[1190]726 }
[4]727
[1190]728 if (ignoreCase) {
[1095]729 return _text.toString().toLowerCase().startsWith(text.toLowerCase());
[1190]730 } else {
[4]731 return _text.indexOf(text) == 0;
[1190]732 }
[4]733 }
734
735 /**
736 * Inserts a character into the Text of this Item.
737 *
738 * @param ch
739 * The character insert.
740 * @param mouseX
741 * The X position to insert the Strings at.
742 * @param mouseY
743 * The Y position to insert the Strings at.
744 */
[1101]745 public Point insertChar(char ch, float mouseX, float mouseY) {
[1190]746 if (ch != '\t') {
[4]747 return insertText("" + ch, mouseX, mouseY);
[1190]748 }
[80]749
750 return insertText(" " + ch, mouseX, mouseY);
[4]751 }
752
[80]753 /**
754 * @param index
755 * @return
756 */
757 private char getNextBullet(char bullet) {
[137]758 for (int i = 0; i < BULLETS.length - 1; i++) {
[1190]759 if (BULLETS[i] == bullet) {
[137]760 return BULLETS[i + 1];
[1190]761 }
[80]762 }
[115]763 return BULLETS[0];
[80]764 }
765
766 private char getPreviousBullet(char bullet) {
[137]767 for (int i = 1; i < BULLETS.length; i++) {
[1190]768 if (BULLETS[i] == bullet) {
[115]769 return BULLETS[i - 1];
[1190]770 }
[80]771 }
[115]772 return BULLETS[BULLETS.length - 1];
[80]773 }
774
[1101]775 public Point getLineEndPosition(float mouseY) {
[4]776 return getEdgePosition(getLinePosition(mouseY), false);
777 }
778
[1101]779 public Point getLineStartPosition(float mouseY) {
[4]780 return getEdgePosition(getLinePosition(mouseY), true);
781 }
782
[1101]783 public Point getParagraphEndPosition() {
[1210]784 return getEdgePosition(getTextLayouts().size() - 1, false);
[4]785 }
[98]786
[1101]787 public Point getParagraphStartPosition() {
[86]788 return getEdgePosition(0, true);
789 }
[4]790
[1101]791 private Point getEdgePosition(int line, boolean start) {
[4]792 // if there is no text yet, or the line is invalid
[1210]793 if (_text == null || _text.length() == 0 || line < 0 || line > getTextLayouts().size() - 1) {
[1101]794 return new Point(getX(), getY());
[1190]795 }
[4]796
[1210]797 TextLayout last = getTextLayouts().get(line);
[4]798 TextHitInfo hit;
[1190]799 if (start) {
[4]800 hit = last.getNextLeftHit(1);
[1190]801 } else {
[4]802 hit = last.getNextRightHit(last.getCharacterCount() - 1);
[1190]803 }
[4]804
805 // move the cursor to the new location
806 float[] caret = last.getCaretInfo(hit);
[105]807 float y = getLineDrop(last) * line;
[4]808
[107]809 float x = getX() + caret[0] + getJustOffset(last);
[1101]810
[1210]811 x = Math.min(x, (getX() - Item.MARGIN_RIGHT - (2 * getGravity()) + getBoundsWidth()));
[1101]812 return new Point((int) x, (int) (getY() + y + caret[1]));
[4]813 }
814
[1210]815 public void setSelectionStart(Point p) {
[1143]816 setSelectionStart(p.getX(), p.getY());
[1101]817 }
[1210]818
[176]819 public void setSelectionStart(float mouseX, float mouseY) {
[4]820 // determine what line is being pointed to
821 int line = getLinePosition(mouseY);
822
823 // get the character being pointed to
824 TextHitInfo hit = getCharPosition(line, mouseX);
[1210]825 _selectionStart = hit.getInsertionIndex() + getTextLayouts().get(line).getStartCharIndex();
826
[1101]827 // Clear the last selected
828 updateLastSelected();
[1210]829
[121]830 invalidateAll();
[4]831 }
832
[1210]833 public void setSelectionEnd(Point p) {
[1143]834 setSelectionEnd(p.getX(), p.getY());
[1101]835 }
836
[176]837 public void setSelectionEnd(float mouseX, float mouseY) {
[4]838 // determine what line is being pointed to
839 int line = getLinePosition(mouseY);
840
841 // get the character being pointed to
842 TextHitInfo hit = getCharPosition(line, mouseX);
[1210]843 _selectionEnd = hit.getInsertionIndex() + getTextLayouts().get(line).getStartCharIndex();
844
[1101]845 // Clear the last selected
846 updateLastSelected();
[1210]847
[121]848 invalidateAll();
[4]849 }
850
851 public void clearSelection() {
852 _selectionStart = -1;
853 _selectionEnd = -1;
[121]854 invalidateAll();
[4]855 }
856
857 public void clearSelectionEnd() {
858 _selectionEnd = -1;
[121]859 invalidateAll();
[4]860 }
[1210]861
[1101]862 /** Makes sure only one text has a selection at a time. */
[1210]863 public void updateLastSelected() {
[1101]864 if (_lastSelected != this) {
[1190]865 if (_lastSelected != null) {
866 _lastSelected.clearSelection();
867 }
[1101]868 _lastSelected = this;
869 }
870 }
[4]871
872 public String copySelectedText() {
[1190]873 if (_selectionStart < 0 || _selectionEnd < 0) {
[4]874 return null;
[1190]875 } else if (_selectionEnd > _text.length()) {
[4]876 _selectionEnd = _text.length();
[1190]877 }
[4]878
[1095]879 return _text.substring(Math.min(_selectionStart, _selectionEnd), Math.max(_selectionStart, _selectionEnd));
[4]880 }
881
882 public String cutSelectedText() {
[7]883 return replaceSelectedText("");
884 }
885
886 public String replaceSelectedText(String newText) {
[1190]887 if (_selectionStart < 0 || _selectionEnd < 0) {
[4]888 return null;
[1190]889 }
[137]890
[121]891 invalidateAll();
[137]892
[1190]893 if (_selectionEnd > _text.length()) {
[4]894 _selectionEnd = _text.length();
[1190]895 }
[4]896
897 int left = Math.min(_selectionStart, _selectionEnd);
898 int right = Math.max(_selectionStart, _selectionEnd);
899
[147]900 // Trim the text to remove new lines on the beginning and end of the
901 // string
[154]902 if (_text.charAt(left) == '\n') {
903 // if the entire line is being removed then remove one of the new
904 // lines, the first case checks if the last line is being removed
905 if (right >= _text.length() || _text.charAt(right) == '\n') {
[147]906 _text.deleteCharAt(left);
907 right--;
[154]908 } else {
[147]909 left++;
910 }
911 }
[154]912 // New lines are always at the start of the line for now...
913 // if(_text.charAt(right - 1) == '\n' && left < right){
914 // right--;
915 // }
[4]916 String s = _text.substring(left, right);
917
[7]918 _text.delete(left, right);
919 _text.insert(left, newText);
920 rebuild(true);
[421]921
[410]922 clearCache();
[137]923
[121]924 invalidateAll();
[4]925
926 return s;
927 }
928
[131]929 public int getSelectionSize() {
[1190]930 if (_selectionEnd < 0 || _selectionStart < 0) {
[4]931 return 0;
[1190]932 }
[4]933
[7]934 // System.out.println(_selectionStart + ":" + _selectionEnd);
[4]935
936 return Math.abs(_selectionEnd - _selectionStart);
937 }
938
939 /**
[1095]940 * Inserts the given String into the Text at the position given by the mouseX
941 * and mouseY coordinates
[4]942 *
943 * @param text
944 * The String to insert into this Text.
945 * @param mouseX
946 * The X position to insert the String
947 * @param mouseY
948 * The Y position to insert the String
949 * @return The new location that the mouse cursor should be moved to
950 */
[1210]951 public Point insertText(String text, float mouseX, float mouseY) {
[1101]952 final Point newPos = insertText(text, mouseX, mouseY, -1);
[906]953 return newPos;
[108]954 }
955
[1210]956 public Point insertText(final String text, final float mouseX, final float mouseY, int insertPos) {
[4]957 TextHitInfo hit;
[1210]958 TextLayout currentLayout = null;
[1101]959 int lineIndex;
[121]960 invalidateAll();
[4]961
[1210]962 // if it is a empty string then do not move the mouse
[1190]963 if (text == null || text.length() == 0) {
[1101]964 return new Point((int) mouseX, (int) mouseY);
[1190]965 }
[1210]966
967 // if there is no text yet then simply append parameter and rebuild before moving on.
968 // rebuild re-initialises the TextLayouts
969 // calculate were we are in the content (hit, lineIndex, currentLayout)
[4]970 if (_text == null || _text.length() == 0) {
971 _text = new StringBuffer().append(text);
972 rebuild(true);
[1210]973
974 assert (getTextLayouts().size() == 1);
975 currentLayout = getTextLayouts().get(0);
976 hit = currentLayout.getNextRightHit(0);
[1101]977 lineIndex = 0;
[1210]978 }
979 // otherwise we are inserting text and calculating the index into the content that we are at
980 else {
981
[376]982 clearCache();
[4]983 // determine what line is being pointed to
[1101]984 lineIndex = getLinePosition(mouseY);
[4]985
986 // get the character being pointed to
[1101]987 hit = getCharPosition(lineIndex, mouseX);
[4]988
[1210]989 int insertionIndex = hit.getInsertionIndex() + getTextLayouts().get(lineIndex).getStartCharIndex();
[4]990
[1101]991 if (lineIndex > 0 && hit.getInsertionIndex() == 0) {
[154]992 // Only move forward a char if the line begins with a hard line
993 // break... not a soft line break
[1101]994 if (_text.charAt(insertionIndex) == '\n') {
995 insertionIndex++;
[154]996 }
997 }
[4]998
[1190]999 if (insertPos < 0) {
[1101]1000 insertPos = insertionIndex;
[1190]1001 }
[108]1002
[4]1003 // if this is a backspace key
[1101]1004 if (text.charAt(0) == '\b') {
[324]1005 if (hasSelection()) {
[1101]1006 insertionIndex = deleteSelection(insertionIndex);
[324]1007 } else if (insertPos > 0) {
[108]1008 deleteChar(insertPos - 1);
[1190]1009 if (insertionIndex > 0) {
[1101]1010 insertionIndex--;
[1190]1011 }
[4]1012 }
[1101]1013 // if this is a delete key
1014 } else if (text.charAt(0) == (char) 0x7F) {
[324]1015 if (hasSelection()) {
[1101]1016 insertionIndex = deleteSelection(insertionIndex);
[324]1017 } else if (insertPos < _text.length()) {
[108]1018 deleteChar(insertPos);
[80]1019 }
[1101]1020 // this is a tab
1021 } else if (text.charAt(0) == '\t') {
[80]1022 // Text length greater than 1 signals a backwards tab
1023 if (text.length() > 1) {
[86]1024 // Find the first non space char to see if its a bullet
[80]1025 int index = 0;
1026 for (index = 0; index < _text.length(); index++) {
[1190]1027 if (!Character.isSpaceChar(_text.charAt(index))) {
[80]1028 break;
[1190]1029 }
[4]1030 }
[80]1031 // Check if there is a space after the bullet
[1095]1032 if (index < _text.length() - 1 && _text.charAt(index + 1) == ' ') {
[80]1033 // Change the bullet
[1095]1034 _text.setCharAt(index, getPreviousBullet(_text.charAt(index)));
[80]1035 }
1036 // Remove the spacing at the start
1037 for (int i = 0; i < TAB_STRING.length(); i++) {
[1095]1038 if (_text.length() > 0 && Character.isSpaceChar(_text.charAt(0))) {
[108]1039 deleteChar(0);
[1101]1040 insertionIndex--;
[1190]1041 } else {
[80]1042 break;
[1190]1043 }
[80]1044 }
1045 } else {
1046 // / Find the first non space char to see if its a bullet
1047 int index = 0;
1048 for (index = 0; index < _text.length(); index++) {
[1190]1049 if (!Character.isSpaceChar(_text.charAt(index))) {
[80]1050 break;
[1190]1051 }
[80]1052 }
1053 // Check if there is a space after the bullet
[1095]1054 if (index < _text.length() - 1 && _text.charAt(index + 1) == ' ') {
[80]1055 char nextBullet = getNextBullet(_text.charAt(index));
1056 // Change the bullet
1057 _text.setCharAt(index, nextBullet);
1058 }
1059 // Insert the spacing at the start
1060 insertString(TAB_STRING, 0);
[1101]1061 insertionIndex += TAB_STRING.length();
[4]1062 }
[1101]1063 // this is a normal insert
[4]1064 } else {
[108]1065 insertString(text, insertPos);
[1101]1066 insertionIndex += text.length();
[4]1067 }
1068
[137]1069 if (_text.length() == 0) {
[108]1070 rebuild(false);
[1101]1071 return new Point((int) this._x, (int) this._y);
[108]1072 }
1073
[1101]1074 int newLine = lineIndex;
[4]1075
1076 // if a rebuild is required
[199]1077 rebuild(true, false);
[1125]1078
[4]1079 // determine the new position the cursor should have
[1210]1080 for (int i = 0; i < getTextLayouts().size(); i++) {
1081 if (getTextLayouts().get(i).getEndCharIndex() + 1 >= insertionIndex) {
[1101]1082 newLine = i;
[4]1083 break;
1084 }
1085 }
1086
[1210]1087 currentLayout = getTextLayouts().get(newLine);
1088 insertionIndex -= currentLayout.getStartCharIndex();
[4]1089
[1101]1090 if (newLine == lineIndex) {
[1190]1091 if (insertionIndex > 0) {
[1210]1092 hit = currentLayout.getNextRightHit(insertionIndex - 1);
[1190]1093 } else {
[1210]1094 hit = currentLayout.getNextLeftHit(1);
[1190]1095 }
[1101]1096 } else if (newLine < lineIndex) {
[1210]1097 hit = currentLayout.getNextRightHit(insertionIndex - 1);
[4]1098 } else {
[1210]1099 hit = currentLayout.getNextRightHit(insertionIndex - 1);
[4]1100 }
1101
[1101]1102 lineIndex = newLine;
[4]1103 }
[1200]1104
[1210]1105 // If we have no mask then....
1106 // move the cursor to the new location
1107 float[] caret = currentLayout.getCaretInfo(hit);
1108 float y = getLineDrop(currentLayout) * lineIndex;
1109 y = getY() + y + caret[1];
[4]1110
[1210]1111 float x = getX() + caret[0] + getJustOffset(currentLayout);
1112 x = Math.min(x, (getX() - Item.MARGIN_RIGHT - (2 * getGravity()) + getBoundsWidth()));
[4]1113
[1210]1114 invalidateAll();
1115
1116 final Point newCursor = new Point(Math.round(x), Math.round(y));
1117 return newCursor;
[4]1118 }
1119
[336]1120 /**
[376]1121 *
1122 */
1123 private void clearCache() {
1124 _attributeValuePair = null;
1125 setProcessedText(null);
1126 setFormula(null);
1127 }
1128
1129 /**
[336]1130 * @param pos
1131 * @return
1132 */
1133 private int deleteSelection(int pos) {
1134 int selectionLength = getSelectionSize();
1135 cutSelectedText();
1136 clearSelection();
1137 pos -= selectionLength;
1138 return pos;
1139 }
1140
[1210]1141 public Point moveCursor(int direction, float mouseX, float mouseY, boolean setSelection, boolean wholeWord) {
[176]1142 if (setSelection) {
[376]1143 if (!hasSelection()) {
[176]1144 setSelectionStart(mouseX, mouseY);
1145 }
[376]1146 } else {
1147 // clearSelection();
[176]1148 }
1149
[1101]1150 Point resultPos = null;
[176]1151
[4]1152 // check for home or end keys
[147]1153 switch (direction) {
1154 case HOME:
[176]1155 resultPos = getParagraphStartPosition();
1156 break;
[147]1157 case END:
[176]1158 resultPos = getParagraphEndPosition();
1159 break;
[147]1160 case LINE_HOME:
[176]1161 resultPos = getLineStartPosition(mouseY);
1162 break;
[147]1163 case LINE_END:
[176]1164 resultPos = getLineEndPosition(mouseY);
1165 break;
1166 default:
1167 TextHitInfo hit;
1168 TextLayout current;
1169 int line;
[4]1170
[176]1171 // if there is no text yet
1172 if (_text == null || _text.length() == 0) {
[1101]1173 return new Point((int) mouseX, (int) mouseY);
[176]1174 // otherwise, move the cursor
1175 } else {
1176 // determine the line of text to check
1177 line = getLinePosition(mouseY);
[1190]1178 if (line < 0) {
[1210]1179 line = getTextLayouts().size() - 1;
[1190]1180 }
[4]1181
[176]1182 // if the cursor is moving up or down, change the line
[1190]1183 if (direction == UP) {
[176]1184 line = Math.max(line - 1, 0);
[1190]1185 } else if (direction == DOWN) {
[1210]1186 line = Math.min(line + 1, getTextLayouts().size() - 1);
[1190]1187 }
[4]1188
[176]1189 hit = getCharPosition(line, mouseX);
[4]1190
[176]1191 if (direction == LEFT) {
1192 if (hit.getInsertionIndex() > 0) {
[376]1193
1194 char prevChar = ' ';
1195 do {
[1210]1196 hit = getTextLayouts().get(line).getNextLeftHit(hit);
[376]1197
1198 // Stop if at the start of the line
[1190]1199 if (hit.getInsertionIndex() == 0) {
[376]1200 break;
[1190]1201 }
[376]1202 // Keep going if the char to the left is a
1203 // letterOrDigit
[1210]1204 prevChar = _text
1205 .charAt(hit.getInsertionIndex() - 1 + getTextLayouts().get(line).getStartCharIndex());
[1095]1206 } while (wholeWord && Character.isLetterOrDigit(prevChar));
[1210]1207
[1101]1208 // TODO Go to the start of the word instead of before the word
[1210]1209 char nextChar = _text
1210 .charAt(hit.getInsertionIndex() + getTextLayouts().get(line).getStartCharIndex());
1211
[1101]1212 // This takes care of hard line break in
[376]1213 if (line > 0 && nextChar == '\n') {
[176]1214 line--;
[1210]1215 hit = getTextLayouts().get(line)
1216 .getNextRightHit(getTextLayouts().get(line).getCharacterCount() - 1);
[176]1217 }
[1210]1218
1219 // This takes care of soft line breaks.
[176]1220 } else if (line > 0) {
[86]1221 line--;
[1210]1222 hit = getTextLayouts().get(line).getNextRightHit(getTextLayouts().get(line).getCharacterCount() - 1);
1223
[1101]1224 // Skip the spaces at the end of a line with soft linebreak
[1210]1225 while (hit.getCharIndex() > 0 && _text
1226 .charAt(getTextLayouts().get(line).getStartCharIndex() + hit.getCharIndex() - 1) == ' ') {
1227 hit = getTextLayouts().get(line).getNextLeftHit(hit);
[176]1228 }
[86]1229 }
[176]1230 } else if (direction == RIGHT) {
[1210]1231 if (hit.getInsertionIndex() < getTextLayouts().get(line).getCharacterCount()) {
1232 hit = getTextLayouts().get(line).getNextRightHit(hit);
[376]1233 // Skip whole word if needs be
[1210]1234 while (wholeWord && hit.getCharIndex() > 0
1235 && hit.getCharIndex() < getTextLayouts().get(line).getCharacterCount()
1236 && Character.isLetterOrDigit(_text
1237 .charAt(getTextLayouts().get(line).getStartCharIndex() + hit.getCharIndex() - 1))) {
1238 hit = getTextLayouts().get(line).getNextRightHit(hit);
[1101]1239 }
[1210]1240 } else if (line < getTextLayouts().size() - 1) {
[176]1241 line++;
[1210]1242 hit = getTextLayouts().get(line).getNextLeftHit(1);
[86]1243 }
[4]1244 }
[1210]1245 current = getTextLayouts().get(line);
[4]1246 }
1247
[176]1248 // move the cursor to the new location
1249 float[] caret = current.getCaretInfo(hit);
1250 float y = getLineDrop(current) * line;
1251
[1101]1252 resultPos = new Point((int) (getX() + caret[0] + getJustOffset(current)), (int) (getY() + y + caret[1]));
1253
[176]1254 break;
[4]1255 }
[1210]1256
[1190]1257 if (setSelection) {
1258 setSelectionEnd(resultPos.getX(), resultPos.getY());
1259 }
[1210]1260
[176]1261 return resultPos;
[4]1262 }
1263
1264 /**
1265 * Iterates through the given line string and returns the position of the
1266 * character being pointed at by the mouse.
1267 *
1268 * @param line
1269 * The index of the _text array of the String to be searched.
1270 * @param mouseX
1271 * The X coordinate of the mouse
1272 * @return The position in the string of the character being pointed at.
1273 */
[1210]1274 public TextHitInfo getCharPosition(final int line, float mouseX) {
1275 if (line < 0 || line >= getTextLayouts().size()) {
[4]1276 return null;
[1190]1277 }
[1210]1278
1279 final TextLayout layout = getTextLayouts().get(line);
[1143]1280 mouseX += getOffset().getX();
[4]1281 mouseX -= getJustOffset(layout);
1282
1283 return layout.hitTestChar(mouseX - getX(), 0);
1284 }
1285
[1101]1286 /**
[1210]1287 * Gets the index into the <code>_textLayout</code> list which corresponds to
1288 * the line covered by the given <code>mouseY</code> position.
[1101]1289 *
1290 * @param mouseY
[1210]1291 * The y-coordinate to test for line coverage.
[1101]1292 *
[1210]1293 * @return The line which occupies the given y-coordinate, or the last line if
1294 * none do.
[1101]1295 */
[108]1296 public int getLinePosition(float mouseY) {
[1143]1297 mouseY += getOffset().getY();
[4]1298
[107]1299 float y = getY();
[4]1300
[1210]1301 for (TextLayout text : getTextLayouts()) {
[4]1302 // calculate X to ensure it is in the shape
[1101]1303 AxisAlignedBoxBounds bounds = text.getLogicalHighlightShape(0, text.getCharacterCount());
[4]1304
[1190]1305 if (bounds.getWidth() < 1) {
1306 bounds.getSize().width = 10;
1307 }
[4]1308
[1101]1309 double x = bounds.getCentreX();
[4]1310
[1190]1311 if (bounds.contains((int) x, (int) (mouseY - y))) {
[1210]1312 return getTextLayouts().indexOf(text);
[1190]1313 }
[4]1314
1315 // check if the cursor is between lines
[1190]1316 if (mouseY - y < bounds.getMinY()) {
[1210]1317 return Math.max(0, getTextLayouts().indexOf(text) - 1);
[1190]1318 }
[4]1319
1320 y += getLineDrop(text);
1321 }
1322
[1210]1323 return getTextLayouts().size() - 1;
[4]1324 }
1325
1326 /**
1327 * Sets the Font that this text will be displayed with on the screen.
1328 *
1329 * @param font
1330 * The Font to display the Text of this Item in.
1331 */
[1210]1332 public void setFont(Font font) {
[130]1333 invalidateAll();
[1210]1334
[4]1335 _font = font;
[1125]1336
[4]1337 rebuild(false);
[137]1338
[130]1339 invalidateAll();
[1415]1340
[1413]1341 if (isSurrogate()) {
1342 surrogatePropertyInheritance.put(DefaultFrameWriter.FONT_STR, false);
[1414]1343 Item primary = getPrimary();
[1413]1344 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.FONT_STR)) {
[1415]1345 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
1346 primary.primaryPropertyEncryption.put(DefaultFrameWriter.FONT_STR, inheritanceCheckOnSave);
[1413]1347 }
1348 }
[4]1349 }
1350
1351 /**
[1101]1352 * Gets the font of this text item.
[4]1353 *
[1101]1354 * @return The Font assigned to this text item, or null if none is assigned.
[4]1355 */
1356 public Font getFont() {
[1413]1357 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.FONT_STR)) {
[1414]1358 return ((Text) this.getPrimary()).getFont();
[1410]1359 } else {
1360 return _font;
1361 }
[4]1362 }
1363
[1101]1364 /**
1365 * Gets the font that should be used to paint this text item during drawing.
1366 *
1367 * @return The font to paint the text item with.
1368 */
[1210]1369 public Font getPaintFont() {
[1369]1370 Font f = getFont();
[1125]1371 if (f == null) {
[1210]1372 _font = EcosystemManager.getFontManager().getDefaultFont().clone();
[1369]1373 f = _font;
[1116]1374 }
[1125]1375 return f;
[4]1376 }
1377
1378 public String getFamily() {
[1101]1379 return getPaintFont().getFamilyName();
[4]1380 }
1381
[1210]1382 public void setFamily(String newFamily) {
[1101]1383 setFont(new Font(newFamily, getFontStyle(), Math.round(getSize())));
[1095]1384
[879]1385 setLetterSpacing(this._letter_spacing);
[4]1386 }
1387
[1101]1388 public Font.Style getFontStyle() {
[4]1389 Font f = getPaintFont();
[1101]1390 return f.getStyle();
[4]1391 }
[7]1392
[72]1393 public static final String MONOSPACED_FONT = "monospaced";
1394
[982]1395 public static final String[] FONT_WHEEL = { "sansserif", "monospaced", "serif", "dialog", "dialoginput" };
[1095]1396 public static final char[] FONT_CHARS = { 's', 'm', 't', 'd', 'i' };
[7]1397
[1095]1398 // A hashtable to store the font family names that are loaded in through the TTF
1399 // files
1400 // provided in the 'assets' folder
1401 public static HashMap<String, Font> FONT_WHEEL_ADDITIONAL_LOOKUP = new HashMap<>();
1402
[7]1403 private static final int NEARBY_GRAVITY = 2;
1404
[348]1405 public static final int MINIMUM_FONT_SIZE = 6;
[72]1406
[4]1407 public void toggleFontFamily() {
1408 String fontFamily = getFamily().toLowerCase();
[7]1409 // set it to the first font by default
[4]1410 setFamily(FONT_WHEEL[0]);
[7]1411
[4]1412 for (int i = 0; i < FONT_WHEEL.length - 3; i++) {
[7]1413 if (fontFamily.equals(FONT_WHEEL[i])) {
1414 setFamily(FONT_WHEEL[i + 1]);
[4]1415 break;
1416 }
1417 }
1418 }
[7]1419
[4]1420 public void toggleFontStyle() {
[362]1421 invalidateAll();
[4]1422 Font currentFont = getPaintFont();
[1101]1423 Font.Style currentStyle = currentFont.getStyle();
1424 Font.Style newStyle = Font.Style.PLAIN;
1425 switch (currentStyle) {
[1210]1426 case PLAIN:
1427 newStyle = Font.Style.BOLD;
1428 break;
1429 case BOLD:
1430 newStyle = Font.Style.ITALIC;
1431 break;
1432 case ITALIC:
1433 newStyle = Font.Style.BOLD_ITALIC;
1434 break;
1435 default:
1436 newStyle = Font.Style.PLAIN;
1437 break;
[1101]1438 }
1439 setFont(new Font(currentFont.getFamilyName(), newStyle, currentFont.getSize()));
[362]1440 rebuild(true);
1441 invalidateAll();
[4]1442 }
1443
[362]1444 public void toggleBold() {
1445 invalidateAll();
1446 Font currentFont = getPaintFont();
[1101]1447 currentFont.toggleBold();
[1210]1448 // setFont(currentFont);
[362]1449 rebuild(true);
1450 invalidateAll();
1451 }
1452
1453 public void toggleItalics() {
1454 invalidateAll();
1455 Font currentFont = getPaintFont();
[1101]1456 currentFont.toggleItalic();
[1210]1457 // setFont(currentFont);
[362]1458 rebuild(true);
1459 invalidateAll();
1460 }
1461
[1210]1462 public void setFontStyle(String newFace) {
[1101]1463 Font currentFont = getPaintFont();
[4]1464 if (newFace == null || newFace.trim().length() == 0) {
[1101]1465 currentFont.setStyle(Font.Style.PLAIN);
[1210]1466 // setFont(currentFont);
[4]1467 return;
1468 }
1469
1470 newFace = newFace.toLowerCase().trim();
1471
[311]1472 if (newFace.equals("plain") || newFace.equals("p")) {
[1101]1473 currentFont.setStyle(Font.Style.PLAIN);
[311]1474 } else if (newFace.equals("bold") || newFace.equals("b")) {
[1101]1475 currentFont.setStyle(Font.Style.BOLD);
[311]1476 } else if (newFace.equals("italic") || newFace.equals("i")) {
[1101]1477 currentFont.setStyle(Font.Style.ITALIC);
[1210]1478 } else if (newFace.equals("bolditalic") || newFace.equals("italicbold") || newFace.equals("bi")
1479 || newFace.equals("ib")) {
[1101]1480 currentFont.setStyle(Font.Style.BOLD_ITALIC);
[4]1481 }
1482
[1210]1483 // setFont(currentFont);
1484
[4]1485 }
1486
1487 /**
1488 * Returns a String array of this Text object's text, split up into separate
1489 * lines.
1490 *
1491 * @return The String array with one element per line of text in this Item.
1492 */
[80]1493 public List<String> getTextList() {
[1190]1494 if (_text == null) {
[4]1495 return null;
[1190]1496 }
[4]1497 try {
1498 List<String> list = new LinkedList<String>();
1499
[410]1500 // Rebuilding prevents errors when displaying frame bitmaps
[1210]1501 if (getTextLayouts().size() == 0) {
[410]1502 rebuild(false);
1503 }
1504
[1210]1505 for (TextLayout layout : getTextLayouts()) {
[1101]1506 String text = layout.getLine().replaceAll("\n", "");
[1190]1507 if (!text.equals("")) {
1508 list.add(text);
1509 }
[4]1510 }
1511
1512 return list;
1513 } catch (Exception e) {
[1210]1514 System.out.println("Exception in Text::getTextList::message is: " + e.getMessage());
[4]1515 return null;
1516 }
1517 }
1518
[1190]1519 @Override
[80]1520 public String getText() {
[1413]1521 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.TEXT_STR)) {
[1414]1522 return this.getPrimary().getText();
[1413]1523 } else {
1524 return _text.toString();
1525 }
[4]1526 }
1527
1528 /**
1529 * Returns the first line of text in this Text Item
1530 *
1531 * @return The first line of Text
1532 */
1533 public String getFirstLine() {
[1190]1534 if (_text == null || _text.length() == 0) {
[4]1535 return null;
[1190]1536 }
[4]1537
[348]1538 // start at the first non-newLine char
1539 int index = 0;
1540 while (_text.charAt(index) == '\n') {
1541 index++;
1542 }
[4]1543
[348]1544 int nextNewLine = _text.indexOf("\n", index);
1545
1546 /* If there are no more newLines return the remaining text */
[1190]1547 if (nextNewLine < 0) {
[348]1548 return _text.substring(index);
[1190]1549 }
[348]1550
1551 return _text.substring(index, nextNewLine);
[4]1552 }
1553
1554 /**
1555 * Sets the inter-line spacing (in pixels) of this text.
1556 *
1557 * @param spacing
1558 * The number of pixels to allow between each line
1559 */
[659]1560 public void setSpacing(float spacing) {
[4]1561 _spacing = spacing;
[1101]1562 invalidateBounds();
[1413]1563
1564 if (isSurrogate()) {
1565 surrogatePropertyInheritance.put(DefaultFrameWriter.SPACING_STR, false);
[1414]1566 Item primary = getPrimary();
[1413]1567 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.SPACING_STR)) {
[1415]1568 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
1569 primary.primaryPropertyEncryption.put(DefaultFrameWriter.SPACING_STR, inheritanceCheckOnSave);
[1413]1570 }
1571 }
[4]1572 }
1573
1574 /**
1575 * Returns the inter-line spacing (in pixels) of this Text.
1576 *
1577 * @return The spacing (inter-line) in pixels of this Text.
1578 */
[659]1579 public float getSpacing() {
[1413]1580 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.SPACING_STR)) {
[1414]1581 return ((Text) this.getPrimary()).getSpacing();
[1410]1582 } else {
1583 return _spacing;
1584 }
[4]1585 }
1586
[1101]1587 /**
1588 * Gets the y-distance that should be advanced between this layout and the next.
1589 *
1590 * @param layout
[1210]1591 * The TextLayout to calculate line-drop for.
1592 *
1593 * @return The distance to advance in the y-direction before the next line.
[1101]1594 */
[1210]1595 protected float getLineDrop(TextLayout layout) {
[1101]1596 if (getSpacing() < 0) {
[1095]1597 return layout.getAscent() + layout.getDescent() + layout.getLeading();
[1101]1598 }
[4]1599
[105]1600 return layout.getAscent() + layout.getDescent() + getSpacing();
[4]1601 }
1602
1603 public void setWordSpacing(int spacing) {
1604 _word_spacing = spacing;
[1413]1605
1606 if (isSurrogate()) {
1607 surrogatePropertyInheritance.put(DefaultFrameWriter.WORD_SPACING_STR, false);
[1414]1608 Item primary = getPrimary();
[1413]1609 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.WORD_SPACING_STR)) {
[1415]1610 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
1611 primary.primaryPropertyEncryption.put(DefaultFrameWriter.WORD_SPACING_STR, inheritanceCheckOnSave);
[1413]1612 }
1613 }
[4]1614 }
1615
1616 public int getWordSpacing() {
[1413]1617 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.WORD_SPACING_STR)) {
[1414]1618 return ((Text) this.getPrimary()).getWordSpacing();
[1410]1619 } else {
1620 return _word_spacing;
1621 }
[4]1622 }
1623
[594]1624 /**
1625 * Sets the spacing (proportional to the font size) between letters
1626 *
1627 * @param spacing
[1095]1628 * Additional spacing to add between letters. See
1629 * {@link java.awt.font.TextAttribute#TRACKING}
[594]1630 */
[659]1631 public void setLetterSpacing(float spacing) {
[4]1632 _letter_spacing = spacing;
[1095]1633
[1101]1634 Font currentFont = getPaintFont();
1635 currentFont.setSpacing(spacing);
[1210]1636 // setFont(currentFont);
[1413]1637
1638 if (isSurrogate()) {
1639 surrogatePropertyInheritance.put(DefaultFrameWriter.LETTER_SPACING_STR, false);
[1414]1640 Item primary = getPrimary();
[1413]1641 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.LETTER_SPACING_STR)) {
[1415]1642 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
1643 primary.primaryPropertyEncryption.put(DefaultFrameWriter.LETTER_SPACING_STR, inheritanceCheckOnSave);
[1413]1644 }
1645 }
[4]1646 }
1647
[594]1648 /**
[1095]1649 * @return The spacing (proportional to the font size) between letters. See
1650 * {@link java.awt.font.TextAttribute#TRACKING}
[594]1651 */
[659]1652 public float getLetterSpacing() {
[1413]1653 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.LETTER_SPACING_STR)) {
[1414]1654 return ((Text) this.getPrimary()).getLetterSpacing();
[1410]1655 } else {
1656 return _letter_spacing;
1657 }
[4]1658 }
1659
[659]1660 public void setInitialSpacing(float spacing) {
[4]1661 _initial_spacing = spacing;
[1413]1662
1663 if (isSurrogate()) {
1664 surrogatePropertyInheritance.put(DefaultFrameWriter.INITIAL_SPACING_STR, false);
[1414]1665 Item primary = getPrimary();
[1413]1666 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.INITIAL_SPACING_STR)) {
[1415]1667 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
1668 primary.primaryPropertyEncryption.put(DefaultFrameWriter.INITIAL_SPACING_STR, inheritanceCheckOnSave);
[1413]1669 }
1670 }
[4]1671 }
1672
[659]1673 public float getInitialSpacing() {
[1413]1674 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.INITIAL_SPACING_STR)) {
[1414]1675 return ((Text) this.getPrimary()).getInitialSpacing();
[1410]1676 } else {
1677 return _initial_spacing;
1678 }
[4]1679 }
1680
[1095]1681 // @Override
[1210]1682 /*
1683 * public boolean intersectsOLD(Polygon p) { if (super.intersects(p)) { float
1684 * textY = getY();
1685 *
1686 * for (TextLayout text : _textLayouts) { // check left and right of each box
1687 * Rectangle2D textOutline = text.getLogicalHighlightShape(0,
1688 * text.getCharacterCount()).getBounds2D();
1689 * textOutline.setRect(textOutline.getX() + getX() - 1, textOutline.getY() +
1690 * textY - 1, textOutline.getWidth() + 2, textOutline.getHeight() + 2); if
1691 * (p.intersects(textOutline)) return true; textY += getLineDrop(text); } }
1692 * return false; }
1693 */
[7]1694
[1095]1695 // The following version of intersect uses a tighter definition for the text,
1696 // based on
1697 @Override
[1101]1698 public boolean intersects(PolygonBounds p) {
[909]1699 if (super.intersects(p)) {
[1095]1700 // float textY = getY();
[7]1701
[1210]1702 for (TextLayout text : getTextLayouts()) {
[1095]1703
[1101]1704 AxisAlignedBoxBounds text_pixel_bounds_rect = getPixelBounds(text);
[1095]1705
[909]1706 if (p.intersects(text_pixel_bounds_rect)) {
1707 return true;
1708 }
1709 }
1710 }
[1095]1711
[909]1712 return false;
1713 }
1714
[7]1715 @Override
[1101]1716 public boolean contains(Point mousePosition) {
[1143]1717 return contains(mousePosition.getX(), mousePosition.getY(), getGravity() * NEARBY_GRAVITY);
[7]1718 }
[1210]1719
1720 public boolean contains(int mouseX, int mouseY) {
[1101]1721 return contains(new Point(mouseX, mouseY));
1722 }
[7]1723
[1210]1724 public boolean contains(int mouseX, int mouseY, int gravity) {
[1143]1725 mouseX += getOffset().getX();
1726 mouseY += getOffset().getY();
[4]1727
[107]1728 float textY = getY();
1729 float textX = getX();
[4]1730
[1101]1731 AxisAlignedBoxBounds outline = getBoundingBox();
[1210]1732
[1190]1733 if (outline == null) {
1734 return false;
1735 }
[4]1736
1737 // Check if its outside the top and left and bottom bounds
[1210]1738 if (outline.getMinX() - mouseX > gravity || outline.getMinY() - mouseY > gravity
[1101]1739 || mouseY - (outline.getMinY() + outline.getHeight()) > gravity
1740 || mouseX - (outline.getMinX() + outline.getWidth()) > gravity) {
[4]1741 return false;
1742 }
[1210]1743
1744 if (this.getMinWidth() != null && outline.contains(mouseX, mouseY)) {
[1200]1745 return true;
1746 }
[4]1747
[1210]1748 for (TextLayout text : getTextLayouts()) {
[4]1749 // check left and right of each box
[1101]1750 AxisAlignedBoxBounds textOutline = text.getLogicalHighlightShape(0, text.getCharacterCount());
[4]1751
1752 // check if the cursor is within the top, bottom and within the
1753 // gravity of right
[348]1754 int justOffset = getJustOffset(text);
[1210]1755
1756 if (mouseY - textY > textOutline.getMinY()
1757 && mouseY - textY < textOutline.getMinY() + textOutline.getHeight()
1758 && mouseX - textX - justOffset < textOutline.getWidth() + gravity + Item.MARGIN_RIGHT) {
[4]1759 return true;
[1101]1760 }
[1210]1761
[4]1762 textY += getLineDrop(text);
1763 }
1764
1765 return false;
1766 }
1767
1768 /**
1769 * Updates the Polygon (rectangle) that surrounds this Text on the screen.
1770 */
[1190]1771 @Override
[1210]1772 public AxisAlignedBoxBounds updateBounds() {
1773 boolean isFakeLayout = false;
[4]1774 // if there is no text, there is nothing to do
[1190]1775 if (_text == null) {
1776 return null;
1777 }
[4]1778
[1200]1779 // if there is no text layouts and the text has no min width, do nothing
[1210]1780 if (getTextLayouts() == null || (getTextLayouts().size() < 1 && this.getMinWidth() == null)) {
[1190]1781 return null;
[1210]1782 }
[1101]1783
[1210]1784 if (this.getMinWidth() != null && getTextLayouts().size() == 0 && this.getFont() != null) {
1785 getTextLayouts().add(TextLayout.getManager().layoutStringSimple("p", this.getFont()));
1786 isFakeLayout = true;
1787 }
1788
[906]1789 int preChangeWidth = 0;
[1101]1790 if (getOldBounds() != null) {
1791 preChangeWidth = AxisAlignedBoxBounds.getEnclosing(getOldBounds()).getWidth();
1792 }
[906]1793
[4]1794 int minX = Integer.MAX_VALUE;
[1415]1795 int maxX = Integer.MIN_VALUE + 1; // +1 makes it safe to do math.abs on
[4]1796
1797 int minY = Integer.MAX_VALUE;
[1415]1798 int maxY = Integer.MIN_VALUE + 1; // +1 makes it safe to do math.abs on
[4]1799
1800 float y = -1;
[1095]1801
[504]1802 // Fix concurrency error in ScaleFrameset
1803 List<TextLayout> tmpTextLayouts;
[1210]1804 synchronized (getTextLayouts()) {
1805 tmpTextLayouts = new LinkedList<TextLayout>(getTextLayouts());
[504]1806 }
[4]1807
[1200]1808 for (int index = 0; index < tmpTextLayouts.size(); index++) {
1809 final TextLayout layout = tmpTextLayouts.get(index);
[1101]1810 AxisAlignedBoxBounds bounds = layout.getLogicalHighlightShape(0, layout.getCharacterCount());
[1095]1811
[1190]1812 if (y < 0) {
[504]1813 y = 0;
[1210]1814 } else {
[504]1815 y += getLineDrop(layout);
[1190]1816 }
[1210]1817
[1200]1818 int minWidth = getAbsoluteMinWidth();
[4]1819
[1190]1820 minX = Math.min(minX, bounds.getMinX());
[1210]1821 maxX = minWidth < Integer.MAX_VALUE ? Math.max(minX + minWidth, bounds.getMaxX())
[1200]1822 : Math.max(maxX, bounds.getMaxX());
1823 minY = Math.min(minY, (int) (bounds.getMinY() + y));
[504]1824 maxY = Math.max(maxY, (int) (bounds.getMaxY() + y));
[4]1825 }
1826
[72]1827 minX -= getLeftMargin();
1828 maxX += Item.MARGIN_RIGHT;
[4]1829
[348]1830 // If its justification right or center then DONT limit the width
1831 if (getJustification() != null) {
[400]1832 maxX = Item.MARGIN_RIGHT + getAbsoluteWidth();
[348]1833 }
1834
[1190]1835 final int xPos = getX() + minX - getGravity();
1836 final int yPos = getY() + minY - getGravity();
1837 final int width = 2 * getGravity() + maxX - minX;
1838 final int height = 2 * getGravity() + maxY - minY;
1839 AxisAlignedBoxBounds ret = new AxisAlignedBoxBounds(xPos, yPos, width, height);
[1210]1840
[1101]1841 Dimension polySize = ret.getSize();
[1210]1842
1843 if (preChangeWidth != 0 && preChangeWidth != polySize.width) {
[1101]1844 if (polySize.width > preChangeWidth) {
1845 MagneticConstraints.getInstance().textGrown(this, polySize.width - preChangeWidth);
1846 } else {
1847 MagneticConstraints.getInstance().textShrunk(this, preChangeWidth - polySize.width);
1848 }
1849 }
[1210]1850
1851 if (isFakeLayout) {
1852 getTextLayouts().remove(0).release();
1853 }
1854
[1101]1855 return ret;
[72]1856 }
[4]1857
[1101]1858 // TODO it seems like this method has some exponential processing which
[4]1859 // makes items copy really slowly when there are lots of lines of text!
1860 // This needs to be fixed!!
[669]1861 public void rebuild(boolean limitWidth) {
[199]1862 rebuild(limitWidth, true);
1863 }
1864
1865 /**
1866 *
1867 * @param limitWidth
1868 * @param newLinebreakerAlways
1869 * true if a new line breaker should always be created.
1870 */
1871 private void rebuild(boolean limitWidth, boolean newLinebreakerAlways) {
[1210]1872 // TODO make this more efficient so it only clears annotation list when it
1873 // really has to
[156]1874 if (isAnnotation()) {
1875 Frame parent = getParent();
[176]1876 // parent can be null when running tests
[156]1877 if (parent != null) {
1878 parent.clearAnnotations();
1879 }
1880 }
1881
[4]1882 // if there is no text, there is nothing to do
[1296]1883 if ((_text == null || _text.length() == 0 ) && getPlaceholder() == null) {
[137]1884 // Frame parent = getParent();
1885 // if(parent != null)
1886 // parent.removeItem(this);
[4]1887 return;
[108]1888 }
[137]1889
[1210]1890 EcosystemManager.getTextLayoutManager().releaseLayouts(getTextLayouts());
1891 if (getTextLayouts() != null) {
1892 getTextLayouts().clear();
[1190]1893 }
[1210]1894 EcosystemManager.getTextLayoutManager().releaseLayouts(_maskTextLayouts);
1895 if (_maskTextLayouts != null) {
1896 _maskTextLayouts.clear();
1897 }
[1296]1898 EcosystemManager.getTextLayoutManager().releaseLayouts(_placeholderTextLayouts);
1899 if (_placeholderTextLayouts != null) {
1900 _placeholderTextLayouts.clear();
1901 }
[4]1902
[1101]1903 // Calculate the maximum allowable width of this line of text
1904 List<org.expeditee.core.Line> lines = null;
[1210]1905 if (_autoWrap || ExperimentalFeatures.AutoWrap.get()) {
[1101]1906 lines = new LinkedList<org.expeditee.core.Line>();
[1210]1907 if (DisplayController.getCurrentFrame() == null) {
1908 return;
1909 }
[1415]1910 for (Item item : DisplayController.getCurrentFrame().getSortedItems()) {
[1210]1911 if (item instanceof Line) {
1912 lines.add(new org.expeditee.core.Line(((Line) item).getStartItem().getPosition(),
1913 ((Line) item).getEndItem().getPosition()));
1914 }
1915 if (item instanceof Picture) {
1916 lines.add(new org.expeditee.core.Line(item.getPosition(),
1917 new Point(item.getX(), item.getY() + item.getHeight())));
1918 }
1919 }
1920 for (Item item : FreeItems.getInstance()) {
1921 if (item instanceof Line) {
1922 lines.add(new org.expeditee.core.Line(((Line) item).getStartItem().getPosition(),
1923 ((Line) item).getEndItem().getPosition()));
1924 }
1925 if (item instanceof Picture) {
1926 lines.add(new org.expeditee.core.Line(item.getPosition(),
1927 new Point(item.getX(), item.getY() + item.getHeight())));
1928 }
1929 }
[613]1930 }
[1095]1931
[1415]1932 float width = (float) Integer.MAX_VALUE;
[1210]1933 if (limitWidth) {
[1369]1934 if (_width == null) {
[1258]1935 width = DisplayController.getFramePaintAreaWidth() - getX();
[1210]1936 } else {
1937 width = getAbsoluteWidth();
1938 }
1939 }
[4]1940
[1296]1941 Font paintFont = getPaintFont();
1942 if (this._text != null && this._text.length() > 0) {
[1379]1943 List<TextLayout> proposedTextLayout =
[1296]1944 EcosystemManager.getTextLayoutManager().layoutString(
1945 _text.toString(),
1946 paintFont,
1947 new Point(getX(), getY()), lines != null ? lines.toArray(new org.expeditee.core.Line[1]) : null,
1948 (int) width,
1949 (int) getSpacing(),
1950 true,
1951 getJustification() == Justification.full
1952 );
[1379]1953 if (proposedTextLayout.size() > 1 && isSingleLineOnly()) {
1954 paintFont = paintFont.clone();
1955
1956 while(proposedTextLayout.size() > 1) {
[1392]1957 paintFont.setSize(Math.max(paintFont.getSize() - 1, MINIMUM_FONT_SIZE));
[1379]1958 proposedTextLayout =
1959 EcosystemManager.getTextLayoutManager().layoutString(
1960 _text.toString(),
1961 paintFont,
1962 new Point(getX(), getY()), lines != null ? lines.toArray(new org.expeditee.core.Line[1]) : null,
[1415]1963 paintFont.getSize() > MINIMUM_FONT_SIZE ? (int) width : Integer.MAX_VALUE,
[1379]1964 (int) getSpacing(),
1965 true,
1966 getJustification() == Justification.full
1967 );
1968 }
1969 }
1970 this._textLayouts = proposedTextLayout;
[1296]1971
1972 if (this.getMask() != null) {
1973 final Stream<Character> maskStream = _text.toString().chars().mapToObj(c -> (char) this.getMask().intValue());
1974 final StringBuilder sb = new StringBuilder();
1975 maskStream.forEach(c -> sb.append(c));
1976 this._maskTextLayouts =
1977 EcosystemManager.getTextLayoutManager().layoutString(
1978 sb.toString(),
1979 paintFont,
1980 new Point(getX(), getY()), lines != null ? lines.toArray(new org.expeditee.core.Line[1]) : null,
1981 (int) width,
1982 (int) getSpacing(),
1983 true,
1984 getJustification() == Justification.full
1985 );
1986 }
[1190]1987 }
[1296]1988 if (this.getPlaceholder() != null) {
1989 this._placeholderTextLayouts =
1990 EcosystemManager.getTextLayoutManager().layoutString(
1991 getPlaceholder(),
1992 paintFont,
1993 new Point(getX(), getY()), lines != null ? lines.toArray(new org.expeditee.core.Line[1]) : null,
1994 (int) width,
1995 (int) getSpacing(),
1996 true,
1997 getJustification() == Justification.full
1998 );
1999 }
[1101]2000 invalidateBounds();
[4]2001 }
[1095]2002
[1101]2003 /**
[1210]2004 * Calculates the maximum possible distance a line can extend to the right from
2005 * a given (x,y) point without crossing any of the lines in the given list.
[1101]2006 *
2007 * @param x
[1210]2008 * The x-coordinate of the beginning point.
[1101]2009 *
2010 * @param y
[1210]2011 * The y-coordinate of the beginning point.
[1101]2012 *
2013 * @param lines
[1210]2014 * A list of pairs of points describing the lines that should stop
2015 * the extension.
[1101]2016 *
[1210]2017 * @return The length of the extended line.
[1101]2018 */
[1210]2019 /*
2020 * private float getLineWidth(int x, float y, List<Point[]> lines) { float width
2021 * = FrameGraphics.getMaxFrameSize().width; for (Point[] l : lines) { // check
2022 * for lines that cross over our y if ((l[0].y >= y && l[1].y <= y) || (l[0].y
2023 * <= y && l[1].y >= y)) { float dX = l[0].x - l[1].x; float dY = l[0].y -
2024 * l[1].y; float newWidth; if (dX == 0) { newWidth = l[0].x; } else if (dY == 0)
2025 * { newWidth = Math.min(l[0].x, l[1].x); } else { //
2026 * System.out.print("gradient: " + (dY / dX)); newWidth = l[0].x + (y - l[0].y)
2027 * * dX / dY; } // System.out.println("dY:" + dY + " dX:" + dX + " width:" +
2028 * newWidth); if (newWidth < x) { continue; } if (newWidth < width) { width =
2029 * newWidth; } } } return width - x; }
2030 */
[4]2031
[400]2032 private boolean hasFixedWidth() {
[1369]2033 assert (_width != null);
2034 if (_width == null) {
[400]2035 justify(false);
2036 }
[1369]2037 return _width > 0;
[400]2038 }
2039
[4]2040 private int _alpha = -1;
2041
2042 public void setAlpha(int alpha) {
2043 _alpha = alpha;
2044 }
2045
[1101]2046 private Range<Integer> getSelectedRange(int line) {
[4]2047 if (_selectionEnd >= _text.length()) {
2048 _selectionEnd = _text.length();
2049 }
2050
[1190]2051 if (_selectionStart < 0) {
[4]2052 _selectionStart = 0;
[1190]2053 }
[4]2054
[1190]2055 if (_selectionStart < 0 || _selectionEnd < 0) {
[4]2056 return null;
[1190]2057 }
[4]2058
2059 int selectionLeft = Math.min(_selectionStart, _selectionEnd);
2060 int selectionRight = Math.max(_selectionStart, _selectionEnd);
2061
2062 // if the selection is after this line, return null
[1210]2063 if (getTextLayouts().get(line).getStartCharIndex() > selectionRight) {
[1190]2064 return null;
2065 }
[4]2066
2067 // if the selection is before this line, return null
[1210]2068 if (getTextLayouts().get(line).getEndCharIndex() < selectionLeft) {
[1190]2069 return null;
2070 }
[4]2071
2072 // Dont highlight a single char
2073 // if (selectionRight - selectionLeft <= MINIMUM_RANGED_CHARS)
2074 // return null;
2075
2076 // the selection occurs on this line, determine where it lies on the
2077 // line
[1210]2078 int start = Math.max(0, selectionLeft - getTextLayouts().get(line).getStartCharIndex());
[4]2079 // int end = Math.min(_lineOffsets.get(line) +
2080 // _textLayouts.get(line).getCharacterCount(), _selectionEnd);
[1210]2081 int end = Math.min(selectionRight - getTextLayouts().get(line).getStartCharIndex(),
2082 getTextLayouts().get(line).getCharacterCount());
[4]2083
2084 // System.out.println(line + ": " + start + "x" + end + " (" +
2085 // _selectionStart + "x" + _selectionEnd + ")");
[1101]2086 return new Range<Integer>(start, end, true, true);
[4]2087 }
[242]2088
[1101]2089 /** Sets the colour that should be used to render the selected range. */
[1210]2090 public void setSelectionColour(Colour colour) {
[1190]2091 if (colour == null) {
2092 colour = RANGE_SELECT_COLOUR;
2093 }
[1210]2094
[1101]2095 _selectionColour = colour;
[229]2096 }
[1210]2097
[1101]2098 /** Gets the colour that should be used to render the selected range. */
[1210]2099 public Colour getSelectionColour() {
[1101]2100 return _selectionColour;
2101 }
[229]2102
[4]2103 @Override
[1210]2104 public void paint() {
[1190]2105 if (!isVisible()) {
2106 return;
2107 }
[1296]2108
2109 boolean hasContent = !(_text == null || (_text.length() == 0 && getMinWidth() == null));
2110 boolean hasPlaceholderContent = getPlaceholder() != null;
[108]2111
[1210]2112
[1200]2113 // if the text to paint is empty string and there is no min width, do nothing.
[1296]2114 if (!hasContent && !hasPlaceholderContent) {
[1200]2115 return;
2116 }
[1095]2117
2118 if (_autoWrap || ExperimentalFeatures.AutoWrap.get()) {
[613]2119 invalidateAll();
[1095]2120
[4]2121 rebuild(true);
[1210]2122 } else if (getTextLayouts().size() < 1) {
[663]2123 clipFrameMargin();
[613]2124 rebuild(true);
[410]2125 // return;
[4]2126 }
[1210]2127
[137]2128 // check if its a vector item and paint all the vector stuff too if this
2129 // item is a free item
2130 // This will allow for dragging vectors around the place!
2131 if (hasVector() && isFloating()) {
[1101]2132 DisplayController.requestRefresh(false);
[137]2133 // TODO make this use a more efficient paint method...
2134 // Have the text item return a bigger repaint area if it has an
2135 // associated vector
2136 }
[1210]2137
[1101]2138 GraphicsManager g = EcosystemManager.getGraphicsManager();
2139 AxisAlignedBoxBounds bounds = (AxisAlignedBoxBounds) getBounds();
[137]2140
[4]2141 // the background is only cleared if required
2142 if (getBackgroundColor() != null) {
[1101]2143 Colour bgc = getBackgroundColor();
[278]2144 if (_alpha > 0) {
[1210]2145 bgc = new Colour(bgc.getRed(), bgc.getGreen(), bgc.getBlue(), Colour.FromComponent255(_alpha));
[247]2146 }
[278]2147
[1101]2148 Colour gradientColor = getGradientColor();
[1210]2149
[1101]2150 Fill fill;
[1210]2151
[1101]2152 if (gradientColor != null && bounds != null) {
[1210]2153 fill = new GradientFill(bgc,
2154 new Point((int) (bounds.getMinX() + bounds.getWidth() * 0.3), bounds.getMinY()), gradientColor,
2155 new Point((int) (bounds.getMinX() + bounds.getWidth() * 1.3), bounds.getMinY()));
[1101]2156 } else {
2157 fill = new Fill(bgc);
[247]2158 }
[278]2159
[1101]2160 g.drawRectangle(bounds, 0.0, fill, null, null, null);
[4]2161 }
[247]2162
[242]2163 if (hasVisibleBorder()) {
[1101]2164 Stroke borderStroke = new Stroke(getThickness(), DEFAULT_CAP, DEFAULT_JOIN);
2165 g.drawRectangle(bounds, 0.0, null, getPaintBorderColor(), borderStroke, null);
[242]2166 }
[247]2167
[282]2168 if (hasFormula()) {
[1101]2169 Stroke highlightStroke = new Stroke(1F, DEFAULT_CAP, DEFAULT_JOIN);
2170 Point start = getEdgePosition(0, true);
2171 Point end = getEdgePosition(0, false);
2172 g.drawLine(start, end, getPaintHighlightColor(), highlightStroke);
[282]2173 }
2174
[1210]2175 if (isHighlighted()) {
[1190]2176 Stroke highlightStroke = new Stroke(getHighlightThickness(), DEFAULT_CAP, DEFAULT_JOIN);
[1101]2177 Fill fill;
2178 if (HighlightMode.Enclosed.equals(getHighlightMode())) {
2179 fill = new Fill(getPaintHighlightColor());
2180 } else {
2181 fill = null;
2182 }
2183 g.drawRectangle(bounds, 0.0, fill, getPaintHighlightColor(), highlightStroke, null);
[108]2184 }
2185
[4]2186 float y = getY();
[1101]2187 Colour paintColour = getPaintColor();
2188 if (_alpha > 0) {
2189 paintColour = new Colour(paintColour);
2190 paintColour.setAlpha(Colour.FromComponent255(_alpha));
2191 }
[1296]2192 if (getTextLayouts() == _placeholderTextLayouts) {
2193 paintColour = paintColour.clone();
2194 paintColour.setAlpha(Colour.FromComponent255(60));
2195 }
[4]2196
[1101]2197 Colour selectionColour = getSelectionColour();
[4]2198
2199 // width -= getX();
2200 // int line = 0;
2201 // boolean tab = false;
[1210]2202 synchronized (getTextLayouts()) {
2203 for (int i = 0; i < getTextLayouts().size(); i++) {
2204 TextLayout layout = getTextLayouts().get(i);
[4]2205
[1101]2206 Range<Integer> selectedRange = getSelectedRange(i);
2207 if (selectedRange != null) {
[1210]2208 AxisAlignedBoxBounds highlight = layout.getLogicalHighlightShape(selectedRange.lowerBound,
2209 selectedRange.upperBound);
[1101]2210 highlight.getTopLeft().add(getX() + getJustOffset(layout), (int) y);
[1210]2211 g.drawRectangle(highlight, 0.0, new Fill(selectionColour), null, null, null);
[4]2212 }
2213
[1210]2214 if (layout.getCharacterCount() == 0) {
2215 continue;
2216 }
[1095]2217 int ldx = 1 + getX() + getJustOffset(layout); // Layout draw x
[1210]2218 g.drawTextLayout(layout, new Point(ldx, (int) y), paintColour);
[1095]2219
[4]2220 y += getLineDrop(layout);
2221 }
2222 }
[1210]2223
[1101]2224 paintLink();
[4]2225 }
[137]2226
[1101]2227 // TODO: Revise
[130]2228 @Override
[1210]2229 protected AxisAlignedBoxBounds getLinkDrawArea() {
[1101]2230 return getDrawingArea();
[130]2231 }
[4]2232
2233 /**
2234 * Determines if this text has any text in it.
2235 *
2236 * @return True if this Item has no text in it, false otherwise.
2237 */
[1210]2238 public boolean isEmpty() {
[1101]2239 return (_text == null || _text.length() == 0);
[4]2240 }
2241
2242 @Override
2243 public Text copy() {
2244 Text copy = new Text(getID());
2245 // copy standard item values
2246 Item.DuplicateItem(this, copy);
[98]2247
[4]2248 // copy values specific to text items
2249 copy.setSpacing(getSpacing());
2250 copy.setInitialSpacing(getInitialSpacing());
[400]2251
[410]2252 copy.setWidth(getWidthToSave());
[4]2253 copy.setJustification(getJustification());
2254 copy.setLetterSpacing(getLetterSpacing());
2255 copy.setWordSpacing(getWordSpacing());
[410]2256 copy.setWidth(getWidthToSave());
[1125]2257 copy.setFont(getFont().clone());
[1200]2258 copy.setMinWidth(getMinWidthToSave());
2259 copy.setMask(_mask);
[376]2260 if (hasFormula()) {
2261 copy.calculate(getFormula());
2262 } else {
2263 copy.setText(_text.toString());
2264 }
[108]2265 copy.setHidden(!isVisible());
[4]2266 return copy;
2267 }
2268
2269 @Override
[108]2270 public float getSize() {
[1416]2271 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.FONT_STR)) {
2272 return ((Text) this.getPrimary()).getSize();
2273 } else {
2274 return getPaintFont().getSize();
2275 }
2276
2277 //return getPaintFont().getSize();
[4]2278 }
2279
2280 /**
2281 * Returns the number of characters in this Text, excluding new lines.
2282 *
2283 * @return The sum of the length of each line of text
2284 */
2285 public int getLength() {
2286 return _text.length();
2287 }
2288
2289 @Override
[108]2290 public void setSize(float size) {
[130]2291 invalidateAll();
[311]2292 // size *= UserSettings.ScaleFactor;
[80]2293 // Dont want to have size set when duplicating a point which has size 0
[1190]2294 if (size < 0) {
[80]2295 return;
[1190]2296 }
[80]2297
[1190]2298 if (size < MINIMUM_FONT_SIZE) {
[80]2299 size = MINIMUM_FONT_SIZE;
[1190]2300 }
[1101]2301 Font currentFont = getPaintFont();
2302 currentFont.setSize((int) size);
[1210]2303 // setFont(currentFont);
[348]2304 rebuild(true);
[130]2305 invalidateAll();
[1416]2306
2307 if (isSurrogate()) {
2308 surrogatePropertyInheritance.put(DefaultFrameWriter.FONT_STR, false);
2309 Item primary = getPrimary();
2310 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.FONT_STR)) {
2311 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
2312 primary.primaryPropertyEncryption.put(DefaultFrameWriter.FONT_STR, inheritanceCheckOnSave);
2313 }
2314 }
[4]2315 }
2316
2317 @Override
2318 public void setAnnotation(boolean val) {
[1101]2319 float mouseX = DisplayController.getFloatMouseX();
2320 float mouseY = DisplayController.getFloatMouseY();
2321 Point newPoint = new Point();
[4]2322 if (val) {
2323 // if this is already an annotation, do nothing
[1190]2324 if (isAnnotation()) {
[4]2325 return;
[1190]2326 }
[1095]2327 if (!isLineEnd() && _text.length() > 0 && _text.charAt(0) == DEFAULT_BULLET) {
[1101]2328 newPoint.set(insertText("\b", mouseX, mouseY, 1));
[1190]2329 if (_text.length() > 0 && _text.charAt(0) == ' ') {
[1143]2330 newPoint.set(insertText("\b", newPoint.getX(), newPoint.getY(), 1));
[1190]2331 }
[80]2332 } else {
[1101]2333 newPoint.set(insertText("@", mouseX, mouseY, 0));
[4]2334 }
2335 } else {
2336 // if this is not an annotation, do nothing
[1190]2337 if (!isAnnotation()) {
[4]2338 return;
[1190]2339 }
[108]2340 if (!isLineEnd() && _text.charAt(0) == '@') {
[1101]2341 newPoint.set(insertText("\b", mouseX, mouseY, 1));
[1143]2342 newPoint.set(insertText(DEFAULT_BULLET_STRING, newPoint.getX(), newPoint.getY(), 0));
[80]2343 } else {
[1101]2344 newPoint.set(insertText("\b", mouseX, mouseY, 1));
[80]2345 }
2346 }
[247]2347 FrameUtils.setLastEdited(this);
[242]2348 rebuild(true);
[1143]2349 DisplayController.setCursorPosition(newPoint.getX(), newPoint.getY(), false);
[80]2350 }
2351
2352 /**
2353 *
2354 */
2355 private void insertString(String toInsert, int pos) {
2356 assert (toInsert.length() > 0);
[1101]2357 invalidateAll();
[80]2358 _text.insert(pos, toInsert);
[1101]2359 rebuild(false);
2360 invalidateAll();
[80]2361 }
2362
[108]2363 private void deleteChar(int pos) {
[80]2364 _text.deleteCharAt(pos);
2365
2366 if (_text.length() == 0) {
[1200]2367 if (this.getMinWidth() != null) {
[1210]2368 // final TextLayout base = _textLayouts.get(0);
2369 // _textLayouts.set(0, TextLayout.get(" ", base.getFont(), 0, 1));
2370 getTextLayouts().clear();
[1200]2371 }
[108]2372 if (this.isLineEnd()) {
[72]2373 // Remove and replace with a dot
[1101]2374 Item.replaceText(this);
2375 DisplayController.setCursorPosition(this._x, this._y);
[72]2376 }
[80]2377 return;
2378 }
[72]2379
[4]2380 }
2381
2382 @Override
2383 public boolean isAnnotation() {
[1190]2384 if (_text != null && _text.length() > 0 && _text.charAt(0) == '@') {
[4]2385 return true;
[1190]2386 }
[4]2387
2388 return false;
2389 }
2390
2391 public boolean isSpecialAnnotation() {
2392 assert _text != null;
2393 String s = _text.toString().toLowerCase();
2394 if (s.length() > 0 && s.indexOf("@") == 0) {
[1095]2395 if (s.equals("@old") || s.equals("@ao") || s.equals("@itemtemplate") || s.equals("@parent")
2396 || s.equals("@next") || s.equals("@previous") || s.equals("@first") || s.equals("@i")
[1190]2397 || s.equals("@iw") || s.equals("@f")) {
[4]2398 return true;
[1190]2399 }
[4]2400 }
2401
2402 return false;
2403 }
2404
2405 @Override
2406 public Item merge(Item merger, int mouseX, int mouseY) {
[50]2407 if (merger.isLineEnd()) {
[98]2408 // Merging line ends onto non line end text is a no-op
[1190]2409 if (!isLineEnd()) {
[87]2410 return null;
[1190]2411 }
[98]2412
[1190]2413 if (merger instanceof Text) {
[80]2414 insertText(((Text) merger).getText(), mouseX, mouseY);
[1190]2415 }
[71]2416
2417 // Set the position by moving the cursor before calling this
2418 // method!!
2419
[4]2420 List<Line> lines = new LinkedList<Line>();
[50]2421 lines.addAll(merger.getLines());
[71]2422 for (Line line : lines) {
[97]2423 line.replaceLineEnd(merger, this);
[50]2424 }
2425 merger.delete();
[71]2426 this.setOffset(0, 0);
[4]2427 return null;
2428 }
2429
[1190]2430 if (!(merger instanceof Text)) {
[4]2431 return merger;
[1190]2432 }
[4]2433
2434 Text merge = (Text) merger;
2435
2436 // insertText(merge.getText(), mouseX, mouseY);
2437
2438 // if the item being merged has a link
2439 if (merge.getLink() != null) {
2440 // if this item has a link, keep it on the cursor
2441 if (getLink() != null) {
2442 merge.setText(merge.getLink());
2443 merge.setLink(null);
2444 // return merge;
2445 // TODO get this to return the merged item and attach it to the
2446 // cursor only when the user presses the middle button.
[1190]2447 } else {
[4]2448 setLink(merge.getLink());
[1190]2449 }
[4]2450 }
2451
2452 return null;
2453 }
2454
2455 /**
2456 * Resets the position of the item to the default position for a title item.
2457 *
2458 */
2459 public void resetTitlePosition() {
[1190]2460 final int boundsHeight = getBoundsHeight();
2461 setPosition(MARGIN_LEFT, MARGIN_LEFT + boundsHeight);
[371]2462 Frame modelFrame = getParentOrCurrentFrame();
[400]2463 if (modelFrame != null) {
[1244]2464 int model_frame_name_x = modelFrame.getNameItem().getX();
2465 if (model_frame_name_x < DisplayController.MINIMUM_FRAME_WIDTH) {
[1258]2466 System.err.println("**** Text::resetTitlePostion(): value to be used as right margin from position of frameName < 512");
[1245]2467 System.err.println(" Overriding to ensure reasonable width for title");
[1244]2468 model_frame_name_x = DisplayController.MINIMUM_FRAME_WIDTH;
2469 }
2470
2471 setRightMargin(model_frame_name_x - MARGIN_LEFT, true);
[371]2472 } else {
[1095]2473 System.out.print("Error: text.resetTitlePosition, getParent or currentFrame returned null");
[400]2474 setRightMargin(MARGIN_LEFT, true);
[371]2475 }
[4]2476 }
2477
[80]2478 /**
2479 * Removes the set of characters up to the first space in this text item.
2480 *
2481 * @return the string that was removed.
2482 */
[4]2483 public String stripFirstWord() {
2484 int firstSpace = _text.toString().indexOf(' ');
2485
2486 // if there is only one word just make it blank
2487 if (firstSpace < 0 || firstSpace + 1 >= _text.length()) {
2488 String text = _text.toString();
2489 setText("");
2490 return text;
2491 }
2492
2493 String firstWord = _text.toString().substring(0, firstSpace);
[80]2494 setText(_text.toString().substring(firstSpace).trim());
[4]2495
2496 return firstWord;
2497 }
2498
[1190]2499 @Override
[4]2500 public String toString() {
2501 String message = "[" + getFirstLine() + "]" + FRAME_NAME_SEPARATOR;
2502
[1190]2503 if (getParent() != null) {
[80]2504 return message + getParent().getName();
[1190]2505 }
[4]2506 return message + getDateCreated();
2507 }
2508
2509 public Text getTemplateForm() {
2510 Text template = this.copy();
2511 template.setID(-1);
[1095]2512 // reset width of global templates so the widths of the items on the settings
2513 // frames don't cause issues
2514 // this is in response to the fact that FrameCreator.addItem() sets rightMargin
2515 // when it adds items
[776]2516 template.setWidth(null);
[376]2517 /*
[1095]2518 * The template must have text otherwise the bounds height will be zero!! This
2519 * will stop escape drop down from working if there is no item template
[376]2520 */
[4]2521 template.setText("@");
2522 return template;
2523 }
[7]2524
[1101]2525 /**
2526 * Checks if the given point is 'near' any line of the text item.
2527 */
[7]2528 @Override
2529 public boolean isNear(int x, int y) {
2530 if (super.isNear(x, y)) {
2531 // TODO check that it is actually near one of the lines of space
2532 // return contains(x, y, getGravity() * 2 + NEAR_DISTANCE);
2533 // at the moment contains ignores gravity when checking the top and
2534 // bottom of text lines... so the cursor must be between two text
2535 // lines
[107]2536 float textY = getY();
2537 float textX = getX();
[7]2538
[1210]2539 for (TextLayout text : getTextLayouts()) {
[7]2540 // check left and right of each box
[1101]2541 AxisAlignedBoxBounds textOutline = text.getLogicalHighlightShape(0, text.getCharacterCount());
[13]2542
[7]2543 // check if the cursor is within the top, bottom and within the
2544 // gravity of right
[1210]2545 if (y - textY > textOutline.getMinY() - NEAR_DISTANCE
2546 && y - textY < textOutline.getMinY() + textOutline.getHeight() + NEAR_DISTANCE
2547 && x - textX < textOutline.getWidth() + NEAR_DISTANCE) {
[7]2548 return true;
[1101]2549 }
[1210]2550
[7]2551 textY += getLineDrop(text);
2552 }
2553 }
2554 return false;
2555 }
[13]2556
[7]2557 @Override
[50]2558 public void anchor() {
2559 super.anchor();
2560 // ensure all text items have their selection cleared
2561 clearSelection();
2562 setAlpha(0);
[1190]2563 if (isLineEnd()) {
[1101]2564 DisplayController.setCursor(Item.DEFAULT_CURSOR);
[1190]2565 }
[400]2566
[421]2567 String text = _text.toString().trim();
2568
[663]2569 clipFrameMargin();
2570
2571 // Show the overlay stuff immediately if this is an overlay item
2572 if (hasLink() && (text.startsWith("@ao") || text.startsWith("@o"))) {
[1101]2573 StandardGestureActions.Refresh();
[663]2574 }
2575 }
[1095]2576
[663]2577 private void clipFrameMargin() {
[400]2578 if (!hasFixedWidth()) {
[1258]2579 int frameWidth = DisplayController.getFramePaintAreaWidth();
[400]2580 /*
[1095]2581 * Only change width if it is more than 150 pixels from the right of the screen
[400]2582 */
[663]2583 if (!_text.toString().contains(" ")) {
[421]2584 Integer width = getWidth();
[1190]2585 if (width == null || width < 0) {
[1415]2586 setWidth(Integer.MIN_VALUE + 1); // +1 makes it safe to do math.abs on
[1190]2587 }
[421]2588 } else if (frameWidth - getX() > ADJUST_WIDTH_THRESHOLD) {
[400]2589 justify(false);
[404]2590 // setRightMargin(frameWidth, false);
[400]2591 }
2592 }
[50]2593 }
[1095]2594
[1101]2595 public void justify(boolean fixWidth, PolygonBounds enclosure) {
[613]2596 // if autowrap is on, wrapping is done every time we draw
[1210]2597 if (ExperimentalFeatures.AutoWrap.get()) {
[1190]2598 return;
2599 }
[1095]2600
[1258]2601 Integer width = DisplayController.getFramePaintAreaWidth();
[71]2602
[400]2603 // Check if that text item is inside an enclosing rectangle...
2604 // Set its max width accordingly
2605 if (enclosure != null) {
[1101]2606 AxisAlignedBoxBounds bounds = AxisAlignedBoxBounds.getEnclosing(enclosure);
2607 if (bounds.getWidth() > 200 && getX() < bounds.getWidth() / 3 + bounds.getMinX()) {
2608 width = bounds.getMinX() + bounds.getWidth();
[400]2609 }
2610 }
2611
[1190]2612 if (getWidth() == null) {
2613 setRightMargin(width, fixWidth);
2614 }
[400]2615
[1101]2616 // Check for the annotation that restricts the width of text items on the frame
[400]2617 String widthString;
[1095]2618 if ((widthString = getParentOrCurrentFrame().getAnnotationValue("maxwidth")) != null) {
[400]2619 try {
2620 int oldWidth = getWidth();
2621 int maxWidth = Integer.parseInt(widthString);
[1190]2622 if (maxWidth < oldWidth) {
[400]2623 setWidth(maxWidth);
[1190]2624 }
[400]2625 } catch (NumberFormatException nfe) {
2626 }
2627
2628 }
2629 }
2630
[920]2631 public void justify(boolean fixWidth) {
2632 // if autowrap is on, wrapping is done every time we draw
[1210]2633 if (ExperimentalFeatures.AutoWrap.get()) {
[1190]2634 return;
2635 }
[1101]2636
[920]2637 this.justify(fixWidth, FrameUtils.getEnlosingPolygon());
2638 }
2639
[71]2640 public void resetFrameNamePosition() {
[1258]2641 //Dimension maxSize = DisplayController.getSizeEnforceMinimum();
2642 Dimension maxSize = DisplayController.getFramePaintAreaSize();
2643
[80]2644 if (maxSize != null) {
[147]2645 // setMaxWidth(maxSize.width);
[80]2646 setPosition(maxSize.width - getBoundsWidth(), getBoundsHeight());
2647 }
[71]2648 }
[80]2649
[78]2650 @Override
[80]2651 protected int getLinkYOffset() {
[1210]2652 if (getTextLayouts().size() == 0) {
[137]2653 return 0;
[1190]2654 }
[1210]2655 return Math.round(-(getTextLayouts().get(0).getAscent() / 2));
[78]2656 }
[80]2657
2658 @Override
2659 public String getName() {
2660 return getFirstLine();
2661 }
2662
2663 public static final String TAB_STRING = " ";
2664
[1101]2665 public Point insertTab(char ch, float mouseX, float mouseY) {
[80]2666 return insertText("" + ch, mouseX, mouseY);
2667 }
2668
[1101]2669 public Point removeTab(char ch, float mouseX, float mouseY) {
[80]2670 // Insert a space as a flag that it is a backwards tab
2671 return insertText(ch + " ", mouseX, mouseY);
2672 }
[108]2673
[115]2674 public static boolean isBulletChar(char c) {
[137]2675 for (int i = 0; i < BULLETS.length; i++) {
[1190]2676 if (BULLETS[i] == c) {
[115]2677 return true;
[1190]2678 }
[115]2679 }
[137]2680 return c == '*' || c == '+' || c == '>' || c == '-' || c == 'o';
[115]2681 }
[137]2682
[1190]2683 @Override
[116]2684 public boolean hasOverlay() {
[1190]2685 if (!isAnnotation() || getLink() == null) {
[116]2686 return false;
[1190]2687 }
[145]2688 String text = getText().toLowerCase();
[147]2689 // TODO make it so can just check the _overlay variable
[1101]2690 // Mike can't remember the reason _overlay var can't be use! oops
[1190]2691 if (!text.startsWith("@")) {
[154]2692 return false;
[1190]2693 }
[1095]2694 return text.startsWith("@o") || text.startsWith("@ao") || text.startsWith("@v") || text.startsWith("@av");
[116]2695 }
[131]2696
2697 public boolean hasSelection() {
2698 return getSelectionSize() > 0;
2699 }
[143]2700
2701 /**
2702 * Dont save text items that are all white space.
2703 */
2704 @Override
2705 public boolean dontSave() {
2706 String text = getText();
2707 assert (text != null);
[1200]2708 return super.dontSave() || (text.trim().length() == 0 && this.getMinWidth() == null);
[143]2709 }
[147]2710
[176]2711 @Override
[291]2712 public boolean calculate(String formula) {
[1190]2713 if (DisplayController.isXRayMode()) {
[291]2714 return false;
[1190]2715 }
[199]2716
[176]2717 super.calculate(formula);
[242]2718 if (isFloating() || formula == null || formula.length() == 0) {
[291]2719 return false;
[176]2720 }
[376]2721 formula = formula.replace(':', '=');
2722
[196]2723 String lowercaseFormula = formula.toLowerCase();
[176]2724 ExpediteeJEP myParser = new ExpediteeJEP();
[278]2725
[247]2726 int nextVarNo = 1;
[278]2727
[242]2728 // Add variables from the containing rectangle if the item being
2729 // calculated is inside the enclosing rectangle
2730 Collection<Item> enclosed = getItemsInSameEnclosure();
2731 for (Item i : enclosed) {
[1190]2732 if (i == this) {
[247]2733 continue;
[1190]2734 }
[247]2735 if (i instanceof Text && !i.isAnnotation()) {
[376]2736 AttributeValuePair pair = i.getAttributeValuePair();
[242]2737 if (pair.hasPair()) {
2738 try {
[278]2739 double value = pair.getDoubleValue();
[247]2740 myParser.addVariable(pair.getAttribute(), value);
[278]2741 // myParser.addVariable("$" + nextVarNo++, value);
[247]2742 } catch (NumberFormatException nfe) {
2743 continue;
[242]2744 } catch (Exception e) {
2745 e.printStackTrace();
2746 }
[282]2747 } // else {
[1095]2748 // Add anonomous vars
[282]2749 try {
2750 double value = pair.getDoubleValue();
[1190]2751 if (value != Double.NaN) {
[376]2752 myParser.addVariable("$" + nextVarNo++, value);
[1190]2753 }
[282]2754 } catch (NumberFormatException nfe) {
2755 continue;
2756 } catch (Exception e) {
2757 e.printStackTrace();
[242]2758 }
[282]2759 // }
[242]2760 }
2761 }
2762
2763 // Add the variables from this frame
[176]2764 myParser.addVariables(this.getParentOrCurrentFrame());
2765 String linkedFrame = getAbsoluteLink();
[199]2766 // Add the relative frame variable if the item is linked
[176]2767 if (linkedFrame != null) {
[196]2768 Frame frame = FrameIO.LoadFrame(linkedFrame);
2769 myParser.addVariables(frame);
[199]2770 // If the frame is linked add vector variable for the frame
2771 if (lowercaseFormula.contains("$frame")) {
[1095]2772 myParser.addVectorVariable(frame.getNonAnnotationItems(true), "$frame");
[196]2773 }
[176]2774 }
[199]2775 // Add the relative box variable if this item is a line end
2776 if (this.isLineEnd()) {
2777 // if its a line end add the enclosed stuff as an @variable
2778 if (lowercaseFormula.contains("$box")) {
[214]2779 myParser.addVectorVariable(getEnclosedItems(), "$box");
[196]2780 }
2781 }
[176]2782 myParser.resetObserver();
[190]2783 try {
[220]2784 Node node = myParser.parse(formula);
[190]2785 String result = myParser.evaluate(node);
[176]2786 if (result != null) {
2787 this.setText(result);
[196]2788 this.setFormula(formula);
[376]2789
2790 if (!this.hasAction()) {
2791 setActionMark(false);
2792 setAction("extract formula");
2793 }
[176]2794 }
[196]2795 } catch (Throwable e) {
[1095]2796 // e.printStackTrace();
[376]2797 String formula2 = getFormula();
2798 this.setText(formula2);
2799 this.setFormula(formula2);
[291]2800 return false;
[199]2801 }
[376]2802
2803 _attributeValuePair = null;
2804
[291]2805 return true;
[176]2806 }
[190]2807
[199]2808 /**
[1095]2809 * Gets items which are in the same enclosure as this item. In the event more
2810 * than one enclosure meets this criteria, then the one returned is the one with
2811 * the smallest area. TODO: Improve the efficiency of this method
[247]2812 *
[242]2813 * @return
2814 */
2815 public Collection<Item> getItemsInSameEnclosure() {
2816 Collection<Item> sameEnclosure = null;
2817 Collection<Item> seen = new HashSet<Item>();
2818 Frame parent = getParentOrCurrentFrame();
2819 double enclosureArea = Double.MAX_VALUE;
2820 for (Item i : parent.getVisibleItems()) {
[376]2821 /*
[1095]2822 * Go through all the enclosures looking for one that includes this item
[376]2823 */
[247]2824 if (!seen.contains(i) && i.isEnclosed()) {
[242]2825 seen.addAll(i.getEnclosingDots());
2826 Collection<Item> enclosed = i.getEnclosedItems();
[247]2827 // Check if we have found an enclosure containing this item
2828 // Check it is smaller than any other enclosure found containing
2829 // this item
[1095]2830 if (enclosed.contains(this) && i.getEnclosedArea() < enclosureArea) {
[242]2831 sameEnclosure = enclosed;
2832 }
2833 }
2834 }
2835
[1190]2836 if (sameEnclosure == null) {
[242]2837 return new LinkedList<Item>();
[1190]2838 }
[247]2839
[242]2840 return sameEnclosure;
2841 }
[1095]2842
[242]2843 /**
[1095]2844 * Returns true if items of the parent frame should be recalculated when this
2845 * item is modified
[199]2846 */
[1190]2847 @Override
[190]2848 public boolean recalculateWhenChanged() {
[278]2849 if (/*
2850 * !isAnnotation() &&
[1190]2851 */(hasFormula() || isLineEnd())) {
[190]2852 return true;
[1190]2853 }
[190]2854 try {
[376]2855 AttributeValuePair avp = getAttributeValuePair();
2856
[1190]2857 if (!avp.getDoubleValue().equals(Double.NaN)) {
[376]2858 return true;
[1190]2859 }
[190]2860 } catch (Exception e) {
[376]2861 e.printStackTrace();
[190]2862 }
[376]2863
[190]2864 return false;
2865 }
[226]2866
2867 public float getLineHeight() {
[1210]2868 return getLineDrop(getTextLayouts().get(0));
[226]2869 }
[278]2870
2871 @Override
[1101]2872 public void setAnchorLeft(Integer anchor) {
[720]2873 if (!isLineEnd()) {
2874 super.setAnchorLeft(anchor);
2875 // Subtract off the link width
2876 if (anchor != null) {
2877 setX(anchor + getLeftMargin());
2878 }
2879 return;
2880 }
2881 invalidateFill();
2882 invalidateCommonTrait(ItemAppearence.PreMoved);
[1095]2883
[1101]2884 this._anchoring.setLeftAnchor(anchor);
[1095]2885
[720]2886 int oldX = getX();
2887 if (anchor != null) {
2888 float deltaX = anchor + getLeftMargin() - oldX;
2889 anchorConnected(AnchorEdgeType.Left, deltaX);
2890 }
[812]2891
[720]2892 invalidateCommonTrait(ItemAppearence.PostMoved);
2893 invalidateFill();
[1413]2894
2895 if (isSurrogate()) {
2896 surrogatePropertyInheritance.put(DefaultFrameWriter.ANCHOR_LEFT_STR, false);
[1414]2897 Item primary = getPrimary();
[1413]2898 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.ANCHOR_LEFT_STR)) {
[1415]2899 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
2900 primary.primaryPropertyEncryption.put(DefaultFrameWriter.ANCHOR_LEFT_STR, inheritanceCheckOnSave);
[1413]2901 }
2902 }
[720]2903 }
2904
2905 @Override
[1101]2906 public void setAnchorRight(Integer anchor) {
[278]2907 if (!isLineEnd()) {
2908 super.setAnchorRight(anchor);
2909 // Subtract off the link width
2910 if (anchor != null) {
[1258]2911 setX(DisplayController.getFramePaintAreaWidth() - anchor - getBoundsWidth() + getLeftMargin());
[278]2912 }
2913 return;
2914 }
2915 invalidateFill();
2916 invalidateCommonTrait(ItemAppearence.PreMoved);
[1095]2917
[1101]2918 this._anchoring.setRightAnchor(anchor);
[1095]2919
[278]2920 int oldX = getX();
2921 if (anchor != null) {
[1258]2922 float deltaX = DisplayController.getFramePaintAreaWidth() - anchor - getBoundsWidth()
[1210]2923 + getLeftMargin() - oldX;
[720]2924 anchorConnected(AnchorEdgeType.Right, deltaX);
[278]2925 }
[812]2926
[278]2927 invalidateCommonTrait(ItemAppearence.PostMoved);
2928 invalidateFill();
[1413]2929
2930 if (isSurrogate()) {
2931 surrogatePropertyInheritance.put(DefaultFrameWriter.ANCHOR_RIGHT_STR, false);
[1414]2932 Item primary = getPrimary();
[1413]2933 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.ANCHOR_RIGHT_STR)) {
[1415]2934 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
2935 primary.primaryPropertyEncryption.put(DefaultFrameWriter.ANCHOR_RIGHT_STR, inheritanceCheckOnSave);
[1413]2936 }
2937 }
[278]2938 }
[282]2939
[278]2940 @Override
[1101]2941 public void setAnchorTop(Integer anchor) {
[1210]2942 if (!isLineEnd()) {
[720]2943 super.setAnchorTop(anchor);
2944 if (anchor != null) {
[1210]2945 if (!getTextLayouts().isEmpty()) {
2946 final float ascent = getTextLayouts().get(0).getAscent();
[1200]2947 setY(anchor + ascent);
[1210]2948 } else if (this.getFont() != null) {
[1200]2949 // p could be any character
2950 final TextLayout fakeLayout = TextLayout.getManager().layoutStringSimple("p", this.getFont());
2951 final float ascent = fakeLayout.getAscent();
2952 EcosystemManager.getTextLayoutManager().releaseLayout(fakeLayout);
2953 setY(anchor + ascent);
[1210]2954 }
[720]2955 }
2956 return;
2957 }
2958 invalidateFill();
2959 invalidateCommonTrait(ItemAppearence.PreMoved);
[1095]2960
[1101]2961 this._anchoring.setTopAnchor(anchor);
[1095]2962
[720]2963 int oldY = getY();
2964 if (anchor != null) {
2965 float deltaY = anchor - oldY;
2966 anchorConnected(AnchorEdgeType.Top, deltaY);
2967 }
[812]2968
[720]2969 invalidateCommonTrait(ItemAppearence.PostMoved);
2970 invalidateFill();
[1413]2971
2972 if (isSurrogate()) {
2973 surrogatePropertyInheritance.put(DefaultFrameWriter.ANCHOR_TOP_STR, false);
[1414]2974 Item primary = getPrimary();
[1413]2975 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.ANCHOR_TOP_STR)) {
[1415]2976 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
2977 primary.primaryPropertyEncryption.put(DefaultFrameWriter.ANCHOR_TOP_STR, inheritanceCheckOnSave);
[1413]2978 }
2979 }
[720]2980 }
2981
2982 @Override
[1101]2983 public void setAnchorBottom(Integer anchor) {
[282]2984 if (!isLineEnd()) {
[278]2985 super.setAnchorBottom(anchor);
[873]2986 if (anchor != null) {
[1210]2987 if (!getTextLayouts().isEmpty()) {
2988 final float ascent = getTextLayouts().get(0).getAscent();
2989 final float descent = getTextLayouts().get(0).getDescent();
[1258]2990 setY(DisplayController.getFramePaintAreaHeight()
[1210]2991 - (anchor + this.getBoundsHeight() - ascent - descent));
[1200]2992 } else if (this.getFont() != null) {
2993 // p could be any character
2994 final TextLayout fakeLayout = TextLayout.getManager().layoutStringSimple("p", this.getFont());
2995 final float ascent = fakeLayout.getAscent();
2996 final float descent = fakeLayout.getDescent();
[1258]2997 setY(DisplayController.getFramePaintAreaHeight()
[1210]2998 - (anchor + this.getBoundsHeight() - ascent - descent));
[1200]2999 }
[1210]3000
[873]3001 }
[278]3002 return;
3003 }
3004 invalidateFill();
3005 invalidateCommonTrait(ItemAppearence.PreMoved);
[1095]3006
[1101]3007 this._anchoring.setBottomAnchor(anchor);
[1095]3008
[278]3009 int oldY = getY();
3010 if (anchor != null) {
[720]3011
[1258]3012 float deltaY = DisplayController.getFramePaintAreaHeight() - anchor - oldY;
[720]3013 anchorConnected(AnchorEdgeType.Bottom, deltaY);
[278]3014 }
[812]3015
[278]3016 invalidateCommonTrait(ItemAppearence.PostMoved);
3017 invalidateFill();
[1413]3018
3019 if (isSurrogate()) {
3020 surrogatePropertyInheritance.put(DefaultFrameWriter.ANCHOR_BOTTOM_STR, false);
[1414]3021 Item primary = getPrimary();
[1413]3022 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.ANCHOR_BOTTOM_STR)) {
[1415]3023 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
3024 primary.primaryPropertyEncryption.put(DefaultFrameWriter.ANCHOR_BOTTOM_STR, inheritanceCheckOnSave);
[1413]3025 }
3026 }
[278]3027 }
[311]3028
3029 @Override
3030 public void scale(Float scale, int originX, int originY) {
3031 setSize(getSize() * scale);
[400]3032
[475]3033 Integer width = getWidth();
3034 if (width != null) {
3035 setWidth(Math.round(width * scale));
3036 }
[1095]3037
[311]3038 super.scale(scale, originX, originY);
3039 rebuild(true);
3040 }
[410]3041
[1210]3042 protected AxisAlignedBoxBounds getPixelBounds(TextLayout layout) {
[909]3043 // Does 'layout' need to be synchronized (similar to _textLayouts below)??
3044 int x = getX();
3045 int y = getY();
3046
[1095]3047 int ldx = 1 + x + getJustOffset(layout); // Layout draw x
[1101]3048 AxisAlignedBoxBounds layout_rect = layout.getPixelBounds(ldx, y);
[909]3049
[1095]3050 return layout_rect;
[909]3051 }
[1210]3052
[1095]3053 /**
3054 * Creates the smallest possible rectangle object to enclose the Text Item
3055 * completely. Width of the rectangle is determined by the line in the Text Item
3056 * that protrudes to the right the most. Height of the rectangle is determined
3057 * by the number of lines in the Text Item.
3058 *
3059 * @return A rectangle enclosing the Text Item, without gravity represented.
3060 * @see #getPixelBoundsUnionTight()
3061 */
[1210]3062 public AxisAlignedBoxBounds getPixelBoundsUnion() {
3063 final AxisAlignedBoxBounds rect = getPixelBounds(getTextLayouts().get(0));
[1095]3064
[1101]3065 int cumulativeHeight = rect.getSize().height;
3066 int maxWidth = rect.getSize().width;
[1095]3067
[1210]3068 if (getTextLayouts().size() > 1) {
3069 for (int i = 1; i < getTextLayouts().size(); i++) {
3070 final AxisAlignedBoxBounds r = getPixelBounds(getTextLayouts().get(i));
3071 cumulativeHeight += getTextLayouts().get(i).getDescent() + getTextLayouts().get(i).getAscent();
[1190]3072 if (r.getSize().width > maxWidth) {
[1101]3073 maxWidth = r.getSize().width;
[1190]3074 }
[905]3075 }
[1095]3076 }
[1210]3077
[1101]3078 rect.getSize().width = maxWidth;
3079 rect.getSize().height = cumulativeHeight;
[1095]3080
3081 return rect;
3082 }
3083
3084 /**
3085 * Creates the smallest possible polygon to enclose the Text Item completely.
3086 * The shape of the polygon is determined by the length of each line, tightly
3087 * fitting the shape so that no white space is inside the resulting polygon.
3088 *
3089 * @return A polygon enclosing the Text Item, without gravity represented.
3090 * @see #getPixelBoundsUnion()
3091 */
[1210]3092 public PolygonBounds getPixelBoundsUnionTight() {
3093 final AxisAlignedBoxBounds rect = getPixelBounds(getTextLayouts().get(0));
3094 if (getTextLayouts().size() == 1) {
[1101]3095 return PolygonBounds.fromBox(rect);
[1095]3096 } else {
[1101]3097 final PolygonBounds poly = new PolygonBounds();
3098 poly.addPoint(rect.getMinX(), rect.getMinY());
3099 poly.addPoint(rect.getMaxX(), rect.getMinY());
[1210]3100 poly.addPoint(rect.getMaxX(), Math.round(rect.getMaxY() + getTextLayouts().get(0).getDescent()));
3101 int y = (int) (rect.getMaxY() + getTextLayouts().get(0).getDescent());
3102 for (int i = 1; i < getTextLayouts().size(); i++) {
3103 final AxisAlignedBoxBounds r = getPixelBounds(getTextLayouts().get(i));
[1101]3104 poly.addPoint(r.getMaxX(), y);
[1210]3105 poly.addPoint(r.getMaxX(), Math.round(y + r.getHeight() + getTextLayouts().get(i).getDescent()));
3106 y = Math.round(y + r.getHeight() + getTextLayouts().get(i).getDescent());
[905]3107 }
[1210]3108 poly.addPoint(rect.getMinX() + getPixelBounds(getTextLayouts().get(getTextLayouts().size() - 1)).getWidth(),
3109 Math.round(y + getTextLayouts().get(getTextLayouts().size() - 1).getDescent()));
3110 poly.addPoint(rect.getMinX(), Math.round(y + getTextLayouts().get(getTextLayouts().size() - 1).getDescent()));
[1095]3111 return poly;
[905]3112 }
3113 }
[1210]3114
[1101]3115 /*
[1210]3116 * public AxisAlignedBoxBounds getPixelBoundsUnion() { synchronized
3117 * (_textLayouts) {
3118 *
3119 * CombinationBoxBounds c = null;
3120 *
3121 * for (TextLayout layout: _textLayouts) { if (c == null) { c = new
3122 * CombinationBoxBounds(getPixelBounds(layout)); } else {
3123 * c.add(getPixelBounds(layout)); } }
3124 *
3125 * return AxisAlignedBoxBounds.getEnclosing(c);
3126 *
3127 * } }
3128 */
[1095]3129
3130 // public Rectangle getPixelBoundsUnion()
3131 // {
3132 // synchronized (_textLayouts) {
3133 //
3134 // int x = getX();
3135 // int y = getY();
3136 //
3137 // int min_xl = Integer.MAX_VALUE;
[1415]3138 // int max_xr = Integer.MIN_VALUE + 1; // +1 makes it safe to do math.abs on
[1095]3139 //
3140 // int min_yt = Integer.MAX_VALUE;
[1415]3141 // int max_yb = Integer.MIN_VALUE + 1; // +1 makes it safe to do math.abs on
[1095]3142 //
3143 //
3144 // for (int i = 0; i < _textLayouts.size(); i++) {
3145 // TextLayout layout = _textLayouts.get(i);
3146 //
3147 // int ldx = 1+x+getJustOffset(layout); // Layout draw x
3148 // Rectangle layout_rect = layout.getPixelBounds(null, ldx, y);
3149 //
3150 // int xl = layout_rect.x;
3151 // int xr = xl + layout_rect.width -1;
3152 //
3153 // int yt = layout_rect.y;
3154 // int yb = yt + layout_rect.height -1;
3155 //
3156 // min_xl = Math.min(min_xl,xl);
3157 // max_xr = Math.max(max_xr,xr);
3158 //
3159 // min_yt = Math.min(min_yt,yt);
3160 // max_yb = Math.max(max_yb,yb);
3161 // }
3162 //
3163 // if ((min_xl >= max_xr) || (min_yt >= max_yb)) {
3164 // // No valid rectangle are found
3165 // return null;
3166 // }
3167 //
3168 // return new Rectangle(min_xl,min_yt,max_xr-min_xl+1,max_yb-min_yt+1);
3169 //
3170 // }
3171 //
3172 // }
[410]3173 /*
3174 * Returns the SIMPLE statement contained by this text item.
3175 *
3176 */
3177 public String getStatement() {
3178 return getText().split("\\s+")[0];
3179 }
[1095]3180
[613]3181 public boolean getAutoWrap() {
[1413]3182 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.AUTO_WRAP_TO_SAVE_STR)) {
[1414]3183 return ((Text) this.getPrimary()).getAutoWrap();
[1410]3184 } else {
3185 return _autoWrap;
3186 }
[613]3187 }
[1095]3188
3189 // workaround since true is the default value and would not be displayed
3190 // normally
[613]3191 public String getAutoWrapToSave() {
[1413]3192 if (!getAutoWrap()) {
[613]3193 return null;
3194 }
3195 return "true";
3196 }
[1095]3197
[613]3198 public void setAutoWrap(boolean autoWrap) {
3199 _autoWrap = autoWrap;
[1413]3200
3201 if (isSurrogate()) {
3202 surrogatePropertyInheritance.put(DefaultFrameWriter.AUTO_WRAP_TO_SAVE_STR, false);
[1414]3203 Item primary = getPrimary();
[1413]3204 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.AUTO_WRAP_TO_SAVE_STR)) {
[1415]3205 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
3206 primary.primaryPropertyEncryption.put(DefaultFrameWriter.AUTO_WRAP_TO_SAVE_STR, inheritanceCheckOnSave);
[1413]3207 }
3208 }
[613]3209 }
[1101]3210
3211 /**
[1210]3212 * Creates a new Text Item whose text contains the given character. This method
3213 * also moves the mouse cursor to be pointing at the newly created Text Item
3214 * ready to insert the next character.
[1101]3215 *
3216 * @param start
3217 * The character to use as the initial text of this Item.
3218 * @return The newly created Text Item
3219 */
3220 public static Text createText(char start) {
[1210]3221 Text t = DisplayController.getCurrentFrame().createBlankText("" + start);
[1101]3222
3223 Point newMouse = t.insertChar(start, DisplayController.getMouseX(), DisplayController.getMouseY());
[1143]3224 DisplayController.setCursorPosition(newMouse.getX(), newMouse.getY(), false);
[1101]3225
3226 return t;
3227 }
3228
3229 /**
[1210]3230 * Creates a new Text Item with no text. The newly created Item is a copy of any
3231 * ItemTemplate if one is present, and inherits all the attributes of the
3232 * Template
[1101]3233 *
3234 * @return The newly created Text Item
3235 */
3236 public static Text createText() {
3237 return DisplayController.getCurrentFrame().createNewText();
3238 }
3239
3240 /**
[1210]3241 * If the given Item is null, then a new Text item is created with the current
3242 * date. If the given Item is not null, then the current date is prepended to
3243 * the Item's text
[1101]3244 *
3245 * @param toAdd
3246 * The Item to prepend the date to, or null
3247 */
3248 public static void AddDate(Item toAdd) {
3249 String date1 = Formatter.getDateTime();
3250 String date2 = Formatter.getDate();
3251 final String leftSeparator = " :";
3252 final String rightSeparator = ": ";
3253 String dateToAdd = date1 + rightSeparator;
3254 boolean prepend = false;
3255 boolean append = false;
3256
3257 // if the user is pointing at an item, add the date where ever the
3258 // cursor is pointing
3259 if (toAdd != null && toAdd instanceof Text) {
3260 // permission check
3261 if (!toAdd.hasPermission(UserAppliedPermission.full)) {
3262 MessageBay.displayMessage("Insufficicent permission to add the date to that item");
3263 return;
3264 }
3265
3266 Text textItem = (Text) toAdd;
3267
3268 String text = textItem.getText();
3269
3270 // check if the default date has already been put on this item
3271 if (text.startsWith(date1 + rightSeparator)) {
3272 textItem.removeText(date1 + rightSeparator);
3273 dateToAdd = date2 + rightSeparator;
3274 prepend = true;
3275 } else if (text.startsWith(date2 + rightSeparator)) {
3276 textItem.removeText(date2 + rightSeparator);
3277 dateToAdd = leftSeparator + date2;
3278 append = true;
3279 } else if (text.endsWith(leftSeparator + date2)) {
3280 textItem.removeEndText(leftSeparator + date2);
3281 append = true;
3282 dateToAdd = leftSeparator + date1;
3283 } else if (text.endsWith(leftSeparator + date1)) {
3284 textItem.removeEndText(leftSeparator + date1);
3285 if (textItem.getLength() > 0) {
3286 dateToAdd = "";
3287 prepend = true;
3288 } else {
3289 // use the default date format
3290 prepend = true;
3291 }
3292 }
3293
3294 if (prepend) {
3295 // add the date to the text item
3296 textItem.prependText(dateToAdd);
[1190]3297 if (dateToAdd.length() == textItem.getLength()) {
[1101]3298 DisplayController.setCursorPosition(textItem.getParagraphEndPosition());
[1190]3299 }
[1101]3300 } else if (append) {
3301 textItem.appendText(dateToAdd);
[1190]3302 if (dateToAdd.length() == textItem.getLength()) {
[1101]3303 DisplayController.setCursorPosition(textItem.getPosition());
[1190]3304 }
[1101]3305 } else {
3306 for (int i = 0; i < date1.length(); i++) {
3307 StandardGestureActions.processChar(date1.charAt(i), false);
3308 }
3309 }
3310
3311 textItem.getParent().setChanged(true);
3312 DisplayController.requestRefresh(true);
3313 // } else {
3314 // MessageBay
3315 // .displayMessage("Only text items can have the date prepended to
3316 // them");
3317 // }
3318 // otherwise, create a new text item
3319 } else {
3320 Text newText = createText();
3321 newText.setText(dateToAdd);
3322 DisplayController.getCurrentFrame().addItem(newText);
3323 DisplayController.getCurrentFrame().setChanged(true);
3324 DisplayController.requestRefresh(true);
3325
3326 DisplayController.setCursorPosition(newText.getParagraphEndPosition());
3327 }
3328 }
[1210]3329
3330 public Integer getMask() {
[1413]3331 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.MASK_STR)) {
[1414]3332 return ((Text) this.getPrimary()).getMask();
[1210]3333 } else {
[1413]3334 return _mask;
[1210]3335 }
[1200]3336 }
[1210]3337
3338 public void setMask(final Integer c) {
3339 _mask = c;
[1413]3340
3341 if (isSurrogate()) {
3342 surrogatePropertyInheritance.put(DefaultFrameWriter.MASK_STR, false);
[1414]3343 Item primary = getPrimary();
[1413]3344 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.MASK_STR)) {
[1415]3345 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
3346 primary.primaryPropertyEncryption.put(DefaultFrameWriter.MASK_STR, inheritanceCheckOnSave);
[1413]3347 }
3348 }
[1210]3349 }
[1296]3350
3351 public String getPlaceholder() {
[1413]3352 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.PLACEHOLDER_STR)) {
[1414]3353 return ((Text) this.getPrimary()).getPlaceholder();
[1410]3354 } else {
3355 if (_placeholder == null || _placeholder.length() == 0) {
3356 return null;
3357 }
3358 return _placeholder.toString();
[1296]3359 }
3360 }
[1210]3361
[1296]3362 public void setPlaceholder(String _placeholder) {
3363 this._placeholder = new StringBuffer(_placeholder);
[1413]3364
3365 if (isSurrogate()) {
3366 surrogatePropertyInheritance.put(DefaultFrameWriter.PLACEHOLDER_STR, false);
[1414]3367 Item primary = getPrimary();
[1413]3368 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.PLACEHOLDER_STR)) {
[1415]3369 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
3370 primary.primaryPropertyEncryption.put(DefaultFrameWriter.PLACEHOLDER_STR, inheritanceCheckOnSave);
[1413]3371 }
3372 }
[1296]3373 }
3374
[1210]3375 protected List<TextLayout> getTextLayouts() {
[1296]3376 if (this.getPlaceholder() != null && (this._text == null || this._text.length() == 0)) {
3377 return _placeholderTextLayouts;
3378 } else if (this.getMask() != null) {
[1210]3379 return _maskTextLayouts;
3380 } else {
3381 return _textLayouts;
3382 }
3383 }
[1379]3384
3385 public boolean isSingleLineOnly() {
[1413]3386 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.SINGLE_LINE_ONLY_STR)) {
[1414]3387 return ((Text) this.getPrimary()).isSingleLineOnly();
[1410]3388 } else {
3389 return _singleLine;
3390 }
[1379]3391 }
3392
3393 public void setSingleLineOnly(boolean _singleLine) {
3394 this._singleLine = _singleLine;
[1413]3395
3396 if (isSurrogate()) {
3397 surrogatePropertyInheritance.put(DefaultFrameWriter.SINGLE_LINE_ONLY_STR, false);
[1414]3398 Item primary = getPrimary();
[1413]3399 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.SINGLE_LINE_ONLY_STR)) {
[1415]3400 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
3401 primary.primaryPropertyEncryption.put(DefaultFrameWriter.SINGLE_LINE_ONLY_STR, inheritanceCheckOnSave);
[1413]3402 }
3403 }
[1379]3404 }
[1381]3405
3406 public int getTabIndex() {
[1413]3407 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.TAB_INDEX_STR)) {
[1414]3408 return ((Text) this.getPrimary()).getTabIndex();
[1410]3409 } else {
3410 return this._tabIndex;
3411 }
[1381]3412 }
3413
3414 public void setTabIndex(int index) {
3415 this._tabIndex = index;
[1413]3416
3417 if (isSurrogate()) {
3418 surrogatePropertyInheritance.put(DefaultFrameWriter.TAB_INDEX_STR, false);
[1414]3419 Item primary = getPrimary();
[1413]3420 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.TAB_INDEX_STR)) {
[1415]3421 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
3422 primary.primaryPropertyEncryption.put(DefaultFrameWriter.TAB_INDEX_STR, inheritanceCheckOnSave);
[1413]3423 }
3424 }
[1381]3425 }
[1382]3426
3427 public Text getTabNext() {
3428 if (this._tabIndex >= 0) {
3429 Collection<Text> textItems = this.getParent().getTextItems();
3430 Text ret = null;
3431
3432 for (Text t: textItems) {
3433 if (t._tabIndex > this._tabIndex) {
3434 if (ret == null) {
3435 ret = t;
3436 } else if (t._tabIndex < ret._tabIndex) {
3437 ret = t;
3438 }
3439 }
3440 }
3441
3442 return ret;
3443 } else {
3444 return null;
3445 }
3446 }
3447
3448 public Text getTabPrevious() {
3449 if (this._tabIndex >= 0) {
3450 Collection<Text> textItems = this.getParent().getTextItems();
3451 Text ret = null;
3452
3453 for (Text t: textItems) {
3454 if (t._tabIndex < this._tabIndex) {
3455 if (ret == null) {
3456 ret = t;
3457 } else if (t._tabIndex > ret._tabIndex) {
3458 ret = t;
3459 }
3460 }
3461 }
3462
3463 return ret;
3464 } else {
3465 return null;
3466 }
3467 }
[4]3468}
Note: See TracBrowser for help on using the repository browser.