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

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

More amendments to the way surrogates are handled. Including renaming classic to primary.

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