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

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