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

Last change on this file since 1511 was 1511, checked in by bnemhaus, 4 years ago

Frame::Parse has been updated to include a new boolean parameter. When true, widgets that are created as a result of the parse send not only notify the widget framework that they have been added, but are also visible. When false, they only notify they have been added.

The widget framework now distinguishes between added and visible widgets, this fixes a bug. Bug: when programmatically adding a widget to not the current frame, it never gets properly removed and therefore still catches click events from users. By distinguishing between adding and making visible this is avoided.


Another bug has been fixed. Bug: When setting a text item to have a right anchor, and then subsequently reducing the size of the window, this text item would get a width of zero assigned. This was caused by some issues with the logic of how right margins for items were calculated.

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