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

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

MaxWidth on text items is now a thing. Use this on text boxes in authentication frameset.

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