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

Last change on this file since 1244 was 1244, checked in by davidb, 5 years ago

After change to have resources-public and resources-private, some changes needed to support running Expeditee for a single user; other main change is to allow FrameDirs to specify relative directory paths, to help with when Expeditee is run on the cloud -- similar work still needs to occurr for ImageDir and AudioDir; some other minor changes also made.

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