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

Last change on this file since 812 was 812, checked in by davidb, 10 years ago

When setting an anchor left/rightr/top/bottom on a Dot, it now looks for connected items that are constrained to the the same x (vertical) or y (horizontal) value, and sets the anchor status of that item to be the same.

File size: 67.0 KB
Line 
1package org.expeditee.items;
2
3import java.awt.BasicStroke;
4import java.awt.Color;
5import java.awt.Dimension;
6import java.awt.Font;
7import java.awt.GradientPaint;
8import java.awt.Graphics2D;
9import java.awt.GraphicsEnvironment;
10import java.awt.Point;
11import java.awt.Polygon;
12import java.awt.Rectangle;
13import java.awt.Shape;
14import java.awt.Stroke;
15import java.awt.event.KeyEvent;
16import java.awt.event.MouseEvent;
17import java.awt.font.FontRenderContext;
18import java.awt.font.LineBreakMeasurer;
19import java.awt.font.TextAttribute;
20import java.awt.font.TextHitInfo;
21import java.awt.font.TextLayout;
22import java.awt.geom.AffineTransform;
23import java.awt.geom.Point2D;
24import java.awt.geom.Rectangle2D;
25import java.io.File;
26import java.text.AttributedString;
27import java.util.Collection;
28import java.util.HashMap;
29import java.util.HashSet;
30import java.util.LinkedList;
31import java.util.List;
32
33import org.expeditee.gui.AttributeValuePair;
34import org.expeditee.gui.DisplayIO;
35import org.expeditee.gui.Frame;
36import org.expeditee.gui.FrameGraphics;
37import org.expeditee.gui.FrameIO;
38import org.expeditee.gui.FrameKeyboardActions;
39import org.expeditee.gui.FrameMouseActions;
40import org.expeditee.gui.FrameUtils;
41import org.expeditee.gui.FreeItems;
42import org.expeditee.math.ExpediteeJEP;
43import org.expeditee.settings.experimental.ExperimentalFeatures;
44import org.nfunk.jep.Node;
45
46/**
47 * Represents text displayed on the screen, which may include multiple lines.
48 * All standard text properties can be set (font, style, size).
49 *
50 * @author jdm18
51 *
52 */
53public class Text extends Item {
54 private static final int ADJUST_WIDTH_THRESHOLD = 200;
55
56 public static String LINE_SEPARATOR = System.getProperty("line.separator");
57
58 // public static char[] BULLETS = { '\u2219', '\u2218', '\u2217' };
59 public static char[] BULLETS = { '\u25AA', '\u25AB', '\u2217' };
60
61 private static char DEFAULT_BULLET = BULLETS[2];
62
63 private static String DEFAULT_BULLET_STRING = DEFAULT_BULLET + " ";
64
65 private String[] _processedText = null;
66
67 public String[] getProcessedText() {
68 return _processedText;
69 }
70
71 public void setProcessedText(String[] tokens) {
72 _processedText = tokens;
73 }
74
75 public static final String FRAME_NAME_SEPARATOR = " on frame ";
76
77 /**
78 * The default font used to display text items if no font is specified.
79 */
80 public static final String DEFAULT_FONT = "Serif-Plain-18";
81
82 public static final Color DEFAULT_COLOR = Color.BLACK;
83
84 public static final int MINIMUM_RANGED_CHARS = 2;
85
86 public static final int NONE = 0;
87
88 public static final int UP = 1;
89
90 public static final int DOWN = 2;
91
92 public static final int LEFT = 3;
93
94 public static final int RIGHT = 4;
95
96 public static final int HOME = 5;
97
98 public static final int LINE_HOME = 9;
99
100 public static final int LINE_END = 10;
101
102 public static final int END = 6;
103
104 public static final int PAGE_DOWN = 7;
105
106 public static final int PAGE_UP = 8;
107
108 /*
109 * Set the width to be IMPLICIT, but as wide as possible, a negative width
110 * value is one that is implicitly set by the system... a positive value is
111 * one explicitly set by the user.
112 */
113 private Integer _maxWidth = Integer.MIN_VALUE + 1;
114
115 private Justification _justification = Justification.left;
116
117 private float _spacing = -1;
118
119 private int _word_spacing = -1;
120
121 private float _initial_spacing = 0;
122
123 private float _letter_spacing = 0;
124
125 // used during ranging out
126 private int _selectionStart = -1;
127
128 private int _selectionEnd = -1;
129
130 // whether autowrap is on/off for this item
131 protected boolean _autoWrap = false;
132
133 // text is broken up into lines
134 private StringBuffer _text = new StringBuffer();
135
136 private List<TextLayout> _textLayouts = new LinkedList<TextLayout>();
137
138 private List<Integer> _lineOffsets = new LinkedList<Integer>();
139
140 private FontRenderContext frc = null;
141 private LineBreakMeasurer _lineBreaker = null;
142
143 // The font to display this text in
144 private Font _font;
145
146 public static void InitFonts() {
147 try {
148 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
149
150 if (ge != null) {
151
152 File fontDirectory = new File(FrameIO.FONT_PATH);
153 if (fontDirectory != null) {
154 File[] fontFiles = fontDirectory.listFiles();
155 if (fontFiles != null) {
156
157 if (fontFiles.length>0) {
158 System.out.println("Loading custom fonts:");
159 }
160
161 boolean first_item = true;
162 for (File fontFile : fontFiles) {
163 String ext = "";
164 String fileName = fontFile.getName().toLowerCase();
165
166 int i = fileName.lastIndexOf('.');
167 int p = Math.max(fileName.lastIndexOf('/'), fileName.lastIndexOf('\\'));
168
169 if (i > p) {
170 ext = fileName.substring(i+1);
171 }
172
173 if (ext.equals("ttf")) {
174 if (first_item) {
175 System.out.print(" " + fontFile.getName());
176 }
177 else {
178 System.out.print(", " + fontFile.getName());
179 }
180 System.out.flush();
181
182 Font font = Font.createFont(Font.TRUETYPE_FONT, fontFile);
183
184 ge.registerFont(font);
185 first_item = false;
186 }
187 }
188 System.out.println();
189
190 }
191 }
192 }
193 else {
194 System.err.println("No graphics environment detected. Skipping the loading of the custom fonts");
195 }
196 }
197 catch (Exception e) {
198 System.err.println("Failed to load custom fonts");
199 e.printStackTrace();
200 }
201 }
202
203 /**
204 * Creates a new Text Item with the given ID and text.
205 *
206 * @param id
207 * The id of this item
208 * @param text
209 * The text to use in this item
210 */
211 public Text(int id, String text) {
212 super();
213 _text.append(text);
214 rebuild(false);
215
216 setID(id);
217 }
218
219 /**
220 * Creates a text item which is not added to the frame.
221 *
222 * @param text
223 */
224 public Text(String text) {
225 super();
226 _text.append(text);
227 rebuild(false);
228 setID(-1);
229 }
230
231 /**
232 * Creates a new Text Item with the given ID
233 *
234 * @param id
235 * The ID to of this item
236 */
237 public Text(int id) {
238 super();
239 setID(id);
240 }
241
242 public Text(int i, String string, Color foreground, Color background) {
243 this(i, string);
244 this.setColor(foreground);
245 this.setBackgroundColor(background);
246 }
247
248 /**
249 * Sets the maximum width of this Text item when justifcation is used.
250 * passing in 0 or -1 means there is no maximum width
251 *
252 * @param width
253 * The maximum width of this item when justification is applied
254 * to it.
255 */
256 @Override
257 public void setWidth(Integer width) {
258 invalidateAll();
259
260 if (width == null) {
261 setJustification(Justification.left);
262 setRightMargin(FrameGraphics.getMaxFrameSize().width, false);
263 return;
264 }
265
266 _maxWidth = width;
267 rebuild(true);
268 invalidateAll();
269 }
270
271 /**
272 * Returns the maximum width of this Text item when justifcation is used. If
273 * the width is negative, it means no explicit width has been set
274 *
275 * @return The maximum width of this Text item when justification is used
276 */
277 @Override
278 public Integer getWidth() {
279 if (_maxWidth == null || _maxWidth <= 0)
280 return null;
281 return _maxWidth;
282 }
283
284 public Integer getAbsoluteWidth() {
285 if (_maxWidth == null) {
286 return Integer.MAX_VALUE;
287 }
288
289 return Math.abs(_maxWidth);
290 }
291
292 @Override
293 public Color getHighlightColor() {
294 if (_highlightColor.equals(getPaintBackgroundColor()))
295 return ALTERNATE_HIGHLIGHT;
296 return _highlightColor;
297 }
298
299 /**
300 * Sets the justification of this Text item. The given integer should
301 * correspond to one of the JUSTIFICATION constants defined in Item
302 *
303 * @param just
304 * The justification to apply to this Text item
305 */
306 public void setJustification(Justification just) {
307 invalidateAll();
308
309 // Only justification left works with 0 width
310 // if (just != null && just != Justification.left && !hasWidth()) {
311 // // TODO Tighten this up so it subtracts the margin widths
312 // setWidth(getBoundsWidth());
313 // }
314
315 _justification = just;
316 rebuild(true);
317 invalidateAll();
318 }
319
320 /**
321 * Returns the current justification of this Text item. The default value
322 * left justification.
323 *
324 * @return The justification of this Text item
325 */
326 public Justification getJustification() {
327 if (_justification == null || _justification.equals(Justification.left))
328 return null;
329 return _justification;
330 }
331
332 private int getJustOffset(TextLayout layout) {
333 if (getJustification() == Justification.center)
334 return (int) ((getAbsoluteWidth() - layout.getAdvance()) / 2);
335 else if (getJustification() == Justification.right)
336 return (int) (getAbsoluteWidth() - layout.getAdvance());
337
338 return 0;
339 }
340
341 /**
342 * Sets the text displayed on the screen to the given String. It does not
343 * reset the formula, attributeValuePair or other cached values.
344 *
345 * @param text
346 * The String to display on the screen when drawing this Item.
347 */
348 @Override
349 public void setText(String text) {
350 setText(text, false);
351 }
352
353 public void setText(String text, Boolean clearCache) {
354 // if (_text != null && text.length() < _text.length())
355 invalidateAll();
356 _text = new StringBuffer(text);
357
358 /*
359 * Always clearingCach remove formulas when moving in and out of XRay
360 * mode
361 */
362 if (clearCache) {
363 clearCache();
364 }
365
366 rebuild(true);
367 invalidateAll();
368 }
369
370 public void setTextList(List<String> text) {
371 if (text == null || text.size() <= 0)
372 return;
373
374 invalidateAll();
375
376 StringBuffer sb = new StringBuffer();
377
378 for (int i = 0; i < text.size(); i++) {
379 sb.append(text.get(i)).append('\n');
380 }
381
382 if (sb.length() > 0)
383 sb.deleteCharAt(sb.length() - 1);
384
385 setText(sb.toString());
386
387 rebuild(true);
388 invalidateAll();
389 }
390
391 public void setAttributeValue(String value) {
392 AttributeValuePair avp = new AttributeValuePair(getText(), false);
393 avp.setValue(value);
394 setText(avp.toString());
395 }
396
397 /**
398 * Inserts the given String at the start of the first line of this Text
399 * Item.
400 *
401 * @param text
402 * The String to insert.
403 */
404 public void prependText(String text) {
405
406 _text.insert(0, text);
407 rebuild(false);
408 }
409
410 /**
411 * If the first line of text starts with the given String, then it is
412 * removed otherwise no action is taken.
413 *
414 * @param text
415 * The String to remove from the first line of Text
416 */
417 public void removeText(String text) {
418 if (_text.length() > 0 && _text.indexOf(text) == 0) {
419 // Need the invalidate all for dateStamp toggling
420 invalidateAll();
421 _text.delete(0, text.length());
422 }
423
424 }
425
426 public void removeEndText(String textToRemove) {
427 int length = _text.length();
428 if (length > 0) {
429 // invalidateAll();
430 int pos = _text.indexOf(textToRemove);
431 int textToRemoveLength = textToRemove.length();
432 if (pos + textToRemoveLength == length) {
433 _text.delete(pos, length);
434 }
435 }
436
437 }
438
439 /**
440 * Appends the given String to any text already present in this Item
441 *
442 * @param text
443 * The String to append.
444 */
445 public void appendText(String text) {
446 _text.append(text);
447 rebuild(false);
448 }
449
450 /**
451 * Used by the frame reader to construct multi-line text items. It must run
452 * quickly, so that the system still responds well for long text items.
453 *
454 * @param text
455 */
456 public void appendLine(String text) {
457 if (text == null)
458 text = "";
459
460 if (_text.length() > 0)
461 _text.append('\n');
462
463 _text.append(text);
464
465 rebuild(true);
466 }
467
468 /**
469 * Tests if the first line of this Text starts with the given String.
470 *
471 * @param text
472 * The prefix to check for
473 * @return True if the first line starts with the given String, False
474 * otherwise.
475 */
476 public boolean startsWith(String text) {
477 return startsWith(text, true);
478 }
479
480 public boolean startsWith(String text, boolean ignoreCase) {
481 if (text == null || _text == null || _text.length() < 1)
482 return false;
483
484 if (ignoreCase)
485 return _text.toString().toLowerCase()
486 .startsWith(text.toLowerCase());
487 else
488 return _text.indexOf(text) == 0;
489 }
490
491 /**
492 * Inserts a character into the Text of this Item.
493 *
494 * @param ch
495 * The character insert.
496 * @param mouseX
497 * The X position to insert the Strings at.
498 * @param mouseY
499 * The Y position to insert the Strings at.
500 */
501 public Point2D.Float insertChar(char ch, float mouseX, float mouseY) {
502 if (ch != '\t') /* && ch != '\n' */
503 return insertText("" + ch, mouseX, mouseY);
504
505 return insertText(" " + ch, mouseX, mouseY);
506 }
507
508 /**
509 * @param index
510 * @return
511 */
512 private char getNextBullet(char bullet) {
513 for (int i = 0; i < BULLETS.length - 1; i++) {
514 if (BULLETS[i] == bullet)
515 return BULLETS[i + 1];
516 }
517 return BULLETS[0];
518 }
519
520 private char getPreviousBullet(char bullet) {
521 for (int i = 1; i < BULLETS.length; i++) {
522 if (BULLETS[i] == bullet)
523 return BULLETS[i - 1];
524 }
525 return BULLETS[BULLETS.length - 1];
526 }
527
528 public Point2D.Float getLineEndPosition(float mouseY) {
529 return getEdgePosition(getLinePosition(mouseY), false);
530 }
531
532 public Point2D.Float getLineStartPosition(float mouseY) {
533 return getEdgePosition(getLinePosition(mouseY), true);
534 }
535
536 public Point2D.Float getParagraphEndPosition() {
537 return getEdgePosition(_textLayouts.size() - 1, false);
538 }
539
540 public Point2D.Float getParagraphStartPosition() {
541 return getEdgePosition(0, true);
542 }
543
544 private Point2D.Float getEdgePosition(int line, boolean start) {
545 // if there is no text yet, or the line is invalid
546 if (_text == null || _text.length() == 0 || line < 0
547 || line > _textLayouts.size() - 1)
548 return new Point2D.Float(getX(), getY());
549
550 TextLayout last = _textLayouts.get(line);
551 TextHitInfo hit;
552 if (start)
553 hit = last.getNextLeftHit(1);
554 else
555 hit = last.getNextRightHit(last.getCharacterCount() - 1);
556
557 // move the cursor to the new location
558 float[] caret = last.getCaretInfo(hit);
559 float y = getLineDrop(last) * line;
560
561 float x = getX() + caret[0] + getJustOffset(last);
562 x = Math
563 .min(
564 x,
565 (getX() - Item.MARGIN_RIGHT - (2 * getGravity()) + getBoundsWidth()));
566 return new Point2D.Float(x, getY() + y + caret[1]);
567 }
568
569 public void setSelectionStart(float mouseX, float mouseY) {
570 // determine what line is being pointed to
571 int line = getLinePosition(mouseY);
572
573 // get the character being pointed to
574 TextHitInfo hit = getCharPosition(line, mouseX);
575 _selectionStart = hit.getInsertionIndex() + _lineOffsets.get(line);
576 invalidateAll();
577 }
578
579 public void setSelectionEnd(float mouseX, float mouseY) {
580 // determine what line is being pointed to
581 int line = getLinePosition(mouseY);
582
583 // get the character being pointed to
584 TextHitInfo hit = getCharPosition(line, mouseX);
585 _selectionEnd = hit.getInsertionIndex() + _lineOffsets.get(line);
586 invalidateAll();
587 }
588
589 public void clearSelection() {
590 _selectionStart = -1;
591 _selectionEnd = -1;
592 invalidateAll();
593 }
594
595 public void clearSelectionEnd() {
596 _selectionEnd = -1;
597 invalidateAll();
598 }
599
600 public String copySelectedText() {
601 if (_selectionStart < 0 || _selectionEnd < 0)
602 return null;
603 else if (_selectionEnd > _text.length())
604 _selectionEnd = _text.length();
605
606 return _text.substring(Math.min(_selectionStart, _selectionEnd), Math
607 .max(_selectionStart, _selectionEnd));
608 }
609
610 public String cutSelectedText() {
611 return replaceSelectedText("");
612 }
613
614 public String replaceSelectedText(String newText) {
615 if (_selectionStart < 0 || _selectionEnd < 0)
616 return null;
617
618 invalidateAll();
619
620 if (_selectionEnd > _text.length())
621 _selectionEnd = _text.length();
622
623 int left = Math.min(_selectionStart, _selectionEnd);
624 int right = Math.max(_selectionStart, _selectionEnd);
625
626 // Trim the text to remove new lines on the beginning and end of the
627 // string
628 if (_text.charAt(left) == '\n') {
629 // if the entire line is being removed then remove one of the new
630 // lines, the first case checks if the last line is being removed
631 if (right >= _text.length() || _text.charAt(right) == '\n') {
632 _text.deleteCharAt(left);
633 right--;
634 } else {
635 left++;
636 }
637 }
638 // New lines are always at the start of the line for now...
639 // if(_text.charAt(right - 1) == '\n' && left < right){
640 // right--;
641 // }
642 String s = _text.substring(left, right);
643
644 _text.delete(left, right);
645 _text.insert(left, newText);
646 rebuild(true);
647
648 clearCache();
649
650 invalidateAll();
651
652 return s;
653 }
654
655 public int getSelectionSize() {
656 if (_selectionEnd < 0 || _selectionStart < 0)
657 return 0;
658
659 // System.out.println(_selectionStart + ":" + _selectionEnd);
660
661 return Math.abs(_selectionEnd - _selectionStart);
662 }
663
664 /**
665 * Inserts the given String into the Text at the position given by the
666 * mouseX and mouseY coordinates
667 *
668 * @param text
669 * The String to insert into this Text.
670 * @param mouseX
671 * The X position to insert the String
672 * @param mouseY
673 * The Y position to insert the String
674 * @return The new location that the mouse cursor should be moved to
675 */
676 public Point2D.Float insertText(String text, float mouseX, float mouseY) {
677 return insertText(text, mouseX, mouseY, -1);
678 }
679
680 public Point2D.Float insertText(String text, float mouseX, float mouseY,
681 int insertPos) {
682 TextHitInfo hit;
683 TextLayout current = null;
684 int line;
685
686 invalidateAll();
687
688 // check for empty string
689 if (text == null || text.length() == 0)
690 return new Point2D.Float(mouseX, mouseY);
691
692 // if there is no text yet
693 if (_text == null || _text.length() == 0) {
694 _text = new StringBuffer().append(text);
695 // create the linebreaker and layouts
696 rebuild(true);
697 assert (_textLayouts.size() == 1);
698 current = _textLayouts.get(0);
699 hit = current.getNextRightHit(0);
700 line = 0;
701
702 // otherwise, we are inserting text
703 } else {
704 clearCache();
705 // determine what line is being pointed to
706 line = getLinePosition(mouseY);
707
708 // get the character being pointed to
709 hit = getCharPosition(line, mouseX);
710
711 int pos = hit.getInsertionIndex() + _lineOffsets.get(line);
712
713 if (line > 0 && hit.getInsertionIndex() == 0) {
714 // Only move forward a char if the line begins with a hard line
715 // break... not a soft line break
716 if (_text.charAt(pos) == '\n') {
717 pos++;
718 }
719 }
720
721 if (insertPos < 0)
722 insertPos = pos;
723
724 // if this is a backspace key
725 if (text.charAt(0) == KeyEvent.VK_BACK_SPACE) {
726 if (hasSelection()) {
727 pos = deleteSelection(pos);
728 } else if (insertPos > 0) {
729 deleteChar(insertPos - 1);
730 if (pos > 0)
731 pos--;
732 }
733 // if this is a delete key
734 } else if (text.charAt(0) == KeyEvent.VK_DELETE) {
735 if (hasSelection()) {
736 pos = deleteSelection(pos);
737 } else if (insertPos < _text.length()) {
738 deleteChar(insertPos);
739 }
740 // this is a tab
741 } else if (text.charAt(0) == KeyEvent.VK_TAB) {
742 // Text length greater than 1 signals a backwards tab
743 if (text.length() > 1) {
744 // Find the first non space char to see if its a bullet
745 int index = 0;
746 for (index = 0; index < _text.length(); index++) {
747 if (!Character.isSpaceChar(_text.charAt(index)))
748 break;
749 }
750 // Check if there is a space after the bullet
751 if (index < _text.length() - 1
752 && _text.charAt(index + 1) == ' ') {
753 // Change the bullet
754 _text.setCharAt(index, getPreviousBullet(_text
755 .charAt(index)));
756 }
757 // Remove the spacing at the start
758 for (int i = 0; i < TAB_STRING.length(); i++) {
759 if (_text.length() > 0
760 && Character.isSpaceChar(_text.charAt(0))) {
761 deleteChar(0);
762 pos--;
763 } else
764 break;
765 }
766 _lineBreaker = null;
767 } else {
768 // / Find the first non space char to see if its a bullet
769 int index = 0;
770 for (index = 0; index < _text.length(); index++) {
771 if (!Character.isSpaceChar(_text.charAt(index)))
772 break;
773 }
774 // Check if there is a space after the bullet
775 if (index < _text.length() - 1
776 && _text.charAt(index + 1) == ' ') {
777 char nextBullet = getNextBullet(_text.charAt(index));
778 // Change the bullet
779 _text.setCharAt(index, nextBullet);
780 }
781 // Insert the spacing at the start
782 insertString(TAB_STRING, 0);
783 pos += TAB_STRING.length();
784 }
785 // this is a normal insert
786 } else {
787 insertString(text, insertPos);
788 pos += text.length();
789 }
790
791 if (_text.length() == 0) {
792 rebuild(false);
793 return new Point2D.Float(this._x, this._y);
794 }
795
796 int newLine = line;
797
798 // if a rebuild is required
799 rebuild(true, false);
800
801 // determine the new position the cursor should have
802 for (int i = 1; i < _lineOffsets.size(); i++) {
803 if (_lineOffsets.get(i) >= pos) {
804 newLine = i - 1;
805 break;
806 }
807 }
808
809 current = _textLayouts.get(newLine);
810 pos -= _lineOffsets.get(newLine);
811
812 if (newLine == line) {
813 if (pos > 0)
814 hit = current.getNextRightHit(pos - 1);
815 else
816 hit = current.getNextLeftHit(1);
817 } else if (newLine < line) {
818 hit = current.getNextRightHit(pos - 1);
819 } else {
820 hit = current.getNextRightHit(pos - 1);
821 }
822
823 line = newLine;
824 }
825
826 // move the cursor to the new location
827 float[] caret = current.getCaretInfo(hit);
828 float y = getLineDrop(current) * line;
829
830 float x = getX() + caret[0] + getJustOffset(current);
831 x = Math
832 .min(
833 x,
834 (getX() - Item.MARGIN_RIGHT - (2 * getGravity()) + getBoundsWidth()));
835
836 invalidateAll();
837
838 return new Point2D.Float(Math.round(x), Math.round(getY() + y
839 + caret[1]));
840 }
841
842 /**
843 *
844 */
845 private void clearCache() {
846 _attributeValuePair = null;
847 setProcessedText(null);
848 setFormula(null);
849 }
850
851 /**
852 * @param pos
853 * @return
854 */
855 private int deleteSelection(int pos) {
856 int selectionLength = getSelectionSize();
857 cutSelectedText();
858 clearSelection();
859 pos -= selectionLength;
860 return pos;
861 }
862
863 public Point2D.Float moveCursor(int direction, float mouseX, float mouseY,
864 boolean setSelection, boolean wholeWord) {
865 if (setSelection) {
866 if (!hasSelection()) {
867 setSelectionStart(mouseX, mouseY);
868 }
869 } else {
870 // clearSelection();
871 }
872
873 Point2D.Float resultPos = null;
874
875 // check for home or end keys
876 switch (direction) {
877 case HOME:
878 resultPos = getParagraphStartPosition();
879 break;
880 case END:
881 resultPos = getParagraphEndPosition();
882 break;
883 case LINE_HOME:
884 resultPos = getLineStartPosition(mouseY);
885 break;
886 case LINE_END:
887 resultPos = getLineEndPosition(mouseY);
888 break;
889 default:
890 TextHitInfo hit;
891 TextLayout current;
892 int line;
893
894 // if there is no text yet
895 if (_text == null || _text.length() == 0) {
896 return new Point2D.Float(mouseX, mouseY);
897 // otherwise, move the cursor
898 } else {
899 // determine the line of text to check
900 line = getLinePosition(mouseY);
901 if (line < 0)
902 line = _textLayouts.size() - 1;
903
904 // if the cursor is moving up or down, change the line
905 if (direction == UP)
906 line = Math.max(line - 1, 0);
907 else if (direction == DOWN)
908 line = Math.min(line + 1, _textLayouts.size() - 1);
909
910 hit = getCharPosition(line, mouseX);
911
912 if (direction == LEFT) {
913 if (hit.getInsertionIndex() > 0) {
914
915 char prevChar = ' ';
916 do {
917 hit = _textLayouts.get(line).getNextLeftHit(hit);
918
919 // Stop if at the start of the line
920 if (hit.getInsertionIndex() == 0)
921 break;
922 // Keep going if the char to the left is a
923 // letterOrDigit
924 prevChar = _text.charAt(hit.getInsertionIndex() - 1
925 + _lineOffsets.get(line));
926 } while (wholeWord
927 && Character.isLetterOrDigit(prevChar));
928 // TODO Go to the start of the word instead of before
929 // the word
930 char nextChar = _text.charAt(hit.getInsertionIndex()
931 + _lineOffsets.get(line));
932 /*
933 * This takes care of hard line break in
934 */
935 if (line > 0 && nextChar == '\n') {
936 line--;
937 hit = _textLayouts.get(line)
938 .getNextRightHit(
939 _textLayouts.get(line)
940 .getCharacterCount() - 1);
941 }
942 // This takes care of soft line breaks.
943 } else if (line > 0) {
944 line--;
945 hit = _textLayouts.get(line).getNextRightHit(
946 _textLayouts.get(line).getCharacterCount() - 1);
947 /*
948 * Skip the spaces at the end of a line with soft
949 * linebreak
950 */
951 while (hit.getCharIndex() > 0
952 && _text.charAt(_lineOffsets.get(line)
953 + hit.getCharIndex() - 1) == ' ') {
954 hit = _textLayouts.get(line).getNextLeftHit(hit);
955 }
956 }
957 } else if (direction == RIGHT) {
958 if (hit.getInsertionIndex() < _textLayouts.get(line)
959 .getCharacterCount()) {
960 hit = _textLayouts.get(line).getNextRightHit(hit);
961 // Skip whole word if needs be
962 while (wholeWord
963 && hit.getCharIndex() > 0
964 && hit.getCharIndex() < _textLayouts.get(line)
965 .getCharacterCount()
966 && Character.isLetterOrDigit(_text
967 .charAt(_lineOffsets.get(line)
968 + hit.getCharIndex() - 1)))
969 hit = _textLayouts.get(line).getNextRightHit(hit);
970 } else if (line < _textLayouts.size() - 1) {
971 line++;
972 hit = _textLayouts.get(line).getNextLeftHit(1);
973 }
974 }
975 current = _textLayouts.get(line);
976 }
977
978 // move the cursor to the new location
979 float[] caret = current.getCaretInfo(hit);
980 float y = getLineDrop(current) * line;
981
982 resultPos = new Point2D.Float(getX() + caret[0]
983 + getJustOffset(current), getY() + y + caret[1]);
984 break;
985 }
986 if (setSelection)
987 setSelectionEnd(resultPos.x, resultPos.y);
988 return resultPos;
989 }
990
991 /**
992 * Iterates through the given line string and returns the position of the
993 * character being pointed at by the mouse.
994 *
995 * @param line
996 * The index of the _text array of the String to be searched.
997 * @param mouseX
998 * The X coordinate of the mouse
999 * @return The position in the string of the character being pointed at.
1000 */
1001 public TextHitInfo getCharPosition(int line, float mouseX) {
1002 if (line < 0 || line >= _textLayouts.size())
1003 return null;
1004
1005 TextLayout layout = _textLayouts.get(line);
1006 mouseX += getOffset().x;
1007 mouseX -= getJustOffset(layout);
1008
1009 return layout.hitTestChar(mouseX - getX(), 0);
1010 }
1011
1012 public int getLinePosition(float mouseY) {
1013 mouseY += getOffset().y;
1014
1015 float y = getY();
1016
1017 for (TextLayout text : _textLayouts) {
1018 // calculate X to ensure it is in the shape
1019 Rectangle2D bounds = text.getLogicalHighlightShape(0,
1020 text.getCharacterCount()).getBounds2D();
1021
1022 if (bounds.getWidth() < 1)
1023 bounds.setRect(bounds.getMinX(), bounds.getMinY(), 10, bounds
1024 .getHeight());
1025
1026 double x = bounds.getCenterX();
1027
1028 if (bounds.contains(x, mouseY - getY() - (y - getY())))
1029 return _textLayouts.indexOf(text);
1030
1031 // check if the cursor is between lines
1032 if (mouseY - getY() - (y - getY()) < bounds.getMinY())
1033 return Math.max(0, _textLayouts.indexOf(text) - 1);
1034
1035 y += getLineDrop(text);
1036 }
1037
1038 return _textLayouts.size() - 1;
1039 }
1040
1041 /**
1042 * Sets the Font that this text will be displayed with on the screen.
1043 *
1044 * @param font
1045 * The Font to display the Text of this Item in.
1046 */
1047 public void setFont(Font font) {
1048 invalidateAll();
1049 // all decoding occurs in the Utils class
1050 _font = font;
1051 // rejustify();
1052 rebuild(false);
1053
1054 invalidateAll();
1055 }
1056
1057 /**
1058 * Returns the Font that this Text is currently using when painting to the
1059 * screen
1060 *
1061 * @return The Font used to display this Text on the screen.
1062 */
1063 public Font getFont() {
1064 return _font;
1065 }
1066
1067 public Font getPaintFont() {
1068 if (getFont() == null)
1069 return Font.decode(DEFAULT_FONT);
1070
1071 return getFont();
1072 }
1073
1074 public String getFamily() {
1075 return getPaintFont().getFamily();
1076 }
1077
1078 public void setFamily(String newFamily) {
1079 String toDecode = newFamily + "-" + getFontStyle() + "-"
1080 + Math.round(getSize());
1081 setFont(Font.decode(toDecode));
1082 }
1083
1084 public String getFontStyle() {
1085 Font f = getPaintFont();
1086 String s = "";
1087
1088 if (f.isPlain())
1089 s += "Plain";
1090
1091 if (f.isBold())
1092 s += "Bold";
1093
1094 if (f.isItalic())
1095 s += "Italic";
1096
1097 return s;
1098 }
1099
1100 public static final String MONOSPACED_FONT = "monospaced";
1101
1102 public static final String[] FONT_WHEEL = { "sansserif", "monospaced",
1103 "serif", "dialog", "dialoginput" };
1104
1105 public static final char[] FONT_CHARS = { 's', 'm', 't', 'd', 'i' };
1106
1107 private static final int NEARBY_GRAVITY = 2;
1108
1109 public static final int MINIMUM_FONT_SIZE = 6;
1110
1111 public void toggleFontFamily() {
1112 String fontFamily = getFamily().toLowerCase();
1113 // set it to the first font by default
1114 setFamily(FONT_WHEEL[0]);
1115
1116 for (int i = 0; i < FONT_WHEEL.length - 3; i++) {
1117 if (fontFamily.equals(FONT_WHEEL[i])) {
1118 setFamily(FONT_WHEEL[i + 1]);
1119 break;
1120 }
1121 }
1122 }
1123
1124 public void toggleFontStyle() {
1125 invalidateAll();
1126 Font currentFont = getPaintFont();
1127 if (currentFont.isPlain())
1128 setFont(currentFont.deriveFont(Font.BOLD));
1129 else if (currentFont.isBold() && currentFont.isItalic())
1130 setFont(currentFont.deriveFont(Font.PLAIN));
1131 else if (currentFont.isBold())
1132 setFont(currentFont.deriveFont(Font.ITALIC));
1133 else
1134 setFont(currentFont.deriveFont(Font.ITALIC + Font.BOLD));
1135 rebuild(true);
1136 invalidateAll();
1137 }
1138
1139 public void toggleBold() {
1140 invalidateAll();
1141 Font currentFont = getPaintFont();
1142 int newStyle = currentFont.getStyle();
1143 if (currentFont.isBold()) {
1144 newStyle -= Font.BOLD;
1145 } else {
1146 newStyle += Font.BOLD;
1147 }
1148 setFont(currentFont.deriveFont(newStyle));
1149 rebuild(true);
1150 invalidateAll();
1151 }
1152
1153 public void toggleItalics() {
1154 invalidateAll();
1155 Font currentFont = getPaintFont();
1156 int newStyle = currentFont.getStyle();
1157 if (currentFont.isItalic()) {
1158 newStyle -= Font.ITALIC;
1159 } else {
1160 newStyle += Font.ITALIC;
1161 }
1162 setFont(currentFont.deriveFont(newStyle));
1163 rebuild(true);
1164 invalidateAll();
1165 }
1166
1167 public void setFontStyle(String newFace) {
1168 if (newFace == null || newFace.trim().length() == 0) {
1169 setFont(getPaintFont().deriveFont(Font.PLAIN));
1170 return;
1171 }
1172
1173 newFace = newFace.toLowerCase().trim();
1174
1175 if (newFace.equals("plain") || newFace.equals("p")) {
1176 setFont(getPaintFont().deriveFont(Font.PLAIN));
1177 } else if (newFace.equals("bold") || newFace.equals("b")) {
1178 setFont(getPaintFont().deriveFont(Font.BOLD));
1179 } else if (newFace.equals("italic") || newFace.equals("i")) {
1180 setFont(getPaintFont().deriveFont(Font.ITALIC));
1181 } else if (newFace.equals("bolditalic") || newFace.equals("italicbold")
1182 || newFace.equals("bi") || newFace.equals("ib")) {
1183 setFont(getPaintFont().deriveFont(Font.BOLD + Font.ITALIC));
1184 }
1185
1186 }
1187
1188 /**
1189 * Returns a String array of this Text object's text, split up into separate
1190 * lines.
1191 *
1192 * @return The String array with one element per line of text in this Item.
1193 */
1194 public List<String> getTextList() {
1195 if (_text == null)
1196 return null;
1197 try {
1198 List<String> list = new LinkedList<String>();
1199
1200 // Rebuilding prevents errors when displaying frame bitmaps
1201 if (_lineOffsets.size() == 0) {
1202 rebuild(false);
1203 }
1204
1205 int last = 0;
1206 for (int offset : _lineOffsets) {
1207 if (offset != last) {
1208 list
1209 .add(_text.substring(last, offset).replaceAll("\n",
1210 ""));
1211 }
1212 last = offset;
1213 }
1214
1215 return list;
1216 } catch (Exception e) {
1217 System.out.println(e.getMessage());
1218 return null;
1219 }
1220 }
1221
1222 public String getText() {
1223 return _text.toString();
1224 }
1225
1226 /**
1227 * Returns the first line of text in this Text Item
1228 *
1229 * @return The first line of Text
1230 */
1231 public String getFirstLine() {
1232 if (_text == null || _text.length() == 0)
1233 return null;
1234
1235 // start at the first non-newLine char
1236 int index = 0;
1237 while (_text.charAt(index) == '\n') {
1238 index++;
1239 }
1240
1241 int nextNewLine = _text.indexOf("\n", index);
1242
1243 /* If there are no more newLines return the remaining text */
1244 if (nextNewLine < 0)
1245 return _text.substring(index);
1246
1247 return _text.substring(index, nextNewLine);
1248 }
1249
1250 /**
1251 * Sets the inter-line spacing (in pixels) of this text.
1252 *
1253 * @param spacing
1254 * The number of pixels to allow between each line
1255 */
1256 public void setSpacing(float spacing) {
1257 _spacing = spacing;
1258 updatePolygon();
1259 }
1260
1261 /**
1262 * Returns the inter-line spacing (in pixels) of this Text.
1263 *
1264 * @return The spacing (inter-line) in pixels of this Text.
1265 */
1266 public float getSpacing() {
1267 return _spacing;
1268 }
1269
1270 private float getLineDrop(TextLayout layout) {
1271 if (getSpacing() < 0)
1272 return layout.getAscent() + layout.getDescent()
1273 + layout.getLeading();
1274
1275 return layout.getAscent() + layout.getDescent() + getSpacing();
1276 }
1277
1278 public void setWordSpacing(int spacing) {
1279 _word_spacing = spacing;
1280 }
1281
1282 public int getWordSpacing() {
1283 return _word_spacing;
1284 }
1285
1286 /**
1287 * Sets the spacing (proportional to the font size) between letters
1288 *
1289 * @param spacing
1290 * Additional spacing to add between letters. See {@link java.awt.font.TextAttribute#TRACKING}
1291 */
1292 public void setLetterSpacing(float spacing) {
1293 _letter_spacing = spacing;
1294 HashMap<TextAttribute, Object> attr = new HashMap<TextAttribute, Object>();
1295 attr.put(TextAttribute.TRACKING, spacing);
1296
1297 if (this._font == null) {
1298 this._font = Font.decode(DEFAULT_FONT);
1299 }
1300
1301 this.setFont(this._font.deriveFont(attr));
1302 }
1303
1304 /**
1305 * @return The spacing (proportional to the font size) between letters. See {@link java.awt.font.TextAttribute#TRACKING}
1306 */
1307 public float getLetterSpacing() {
1308 return _letter_spacing;
1309 }
1310
1311 public void setInitialSpacing(float spacing) {
1312 _initial_spacing = spacing;
1313 }
1314
1315 public float getInitialSpacing() {
1316 return _initial_spacing;
1317 }
1318
1319 @Override
1320 public boolean intersects(Polygon p) {
1321 if (super.intersects(p)) {
1322 float textY = getY();
1323
1324 for (TextLayout text : _textLayouts) {
1325 // check left and right of each box
1326 Rectangle2D textOutline = text.getLogicalHighlightShape(0,
1327 text.getCharacterCount()).getBounds2D();
1328 textOutline
1329 .setRect(textOutline.getX() + getX() - 1, textOutline
1330 .getY()
1331 + textY - 1, textOutline.getWidth() + 2,
1332 textOutline.getHeight() + 2);
1333 if (p.intersects(textOutline))
1334 return true;
1335 textY += getLineDrop(text);
1336 }
1337 }
1338 return false;
1339 }
1340
1341 @Override
1342 public boolean contains(int mouseX, int mouseY) {
1343 return contains(mouseX, mouseY, getGravity() * NEARBY_GRAVITY);
1344 }
1345
1346 public boolean contains(int mouseX, int mouseY, int gravity) {
1347 mouseX += getOffset().x;
1348 mouseY += getOffset().y;
1349
1350 float textY = getY();
1351 float textX = getX();
1352
1353 Rectangle2D outline = getPolygon().getBounds2D();
1354
1355 // Check if its outside the top and left and bottom bounds
1356 if (outline.getX() - mouseX > gravity
1357 || outline.getY() - mouseY > gravity
1358 || mouseY - (outline.getY() + outline.getHeight()) > gravity
1359 || mouseX - (outline.getX() + outline.getWidth()) > gravity) {
1360 return false;
1361 }
1362
1363 for (TextLayout text : _textLayouts) {
1364 // check left and right of each box
1365 Rectangle2D textOutline = text.getLogicalHighlightShape(0,
1366 text.getCharacterCount()).getBounds2D();
1367
1368 // check if the cursor is within the top, bottom and within the
1369 // gravity of right
1370 int justOffset = getJustOffset(text);
1371
1372 if (mouseY - textY > textOutline.getY()
1373 && mouseY - textY < textOutline.getY()
1374 + textOutline.getHeight()
1375 && mouseX - textX - justOffset < textOutline.getWidth()
1376 + gravity + Item.MARGIN_RIGHT
1377 /* &&(justOffset == 0 || mouseX > textX + justOffset - gravity ) */)
1378 return true;
1379 textY += getLineDrop(text);
1380 }
1381
1382 return false;
1383 }
1384
1385 /**
1386 * Updates the Polygon (rectangle) that surrounds this Text on the screen.
1387 */
1388 public void updatePolygon() {
1389 // if there is no text, there is nothing to do
1390 if (_text == null)
1391 return;
1392
1393 _poly = new Polygon();
1394
1395 if (_textLayouts.size() < 1)
1396 return;
1397
1398 int minX = Integer.MAX_VALUE;
1399 int maxX = Integer.MIN_VALUE;
1400
1401 int minY = Integer.MAX_VALUE;
1402 int maxY = Integer.MIN_VALUE;
1403
1404 float y = -1;
1405
1406 // Fix concurrency error in ScaleFrameset
1407 List<TextLayout> tmpTextLayouts;
1408 synchronized(_textLayouts) {
1409 tmpTextLayouts = new LinkedList<TextLayout>(_textLayouts);
1410 }
1411
1412 for (TextLayout layout : tmpTextLayouts) {
1413 Rectangle2D bounds = layout.getLogicalHighlightShape(0,
1414 layout.getCharacterCount()).getBounds2D();
1415
1416 if (y < 0)
1417 y = 0;
1418 else
1419 y += getLineDrop(layout);
1420
1421 maxX = Math.max(maxX, (int) bounds.getMaxX());
1422 minX = Math.min(minX, (int) bounds.getMinX());
1423 maxY = Math.max(maxY, (int) (bounds.getMaxY() + y));
1424 minY = Math.min(minY, (int) (bounds.getMinY() + y));
1425 }
1426
1427 minX -= getLeftMargin();
1428 maxX += Item.MARGIN_RIGHT;
1429
1430 // If its justification right or center then DONT limit the width
1431 if (getJustification() != null) {
1432 maxX = Item.MARGIN_RIGHT + getAbsoluteWidth();
1433 }
1434
1435 _poly.addPoint(minX - getGravity(), minY - getGravity());
1436 _poly.addPoint(maxX + getGravity(), minY - getGravity());
1437 _poly.addPoint(maxX + getGravity(), maxY + getGravity());
1438 _poly.addPoint(minX - getGravity(), maxY + getGravity());
1439
1440 _poly.translate(getX(), getY());
1441 }
1442
1443 // TODO it seems like this method has some exponencial processing which
1444 // makes items copy really slowly when there are lots of lines of text!
1445 // This needs to be fixed!!
1446 public void rebuild(boolean limitWidth) {
1447 rebuild(limitWidth, true);
1448 }
1449
1450 /**
1451 *
1452 * @param limitWidth
1453 * @param newLinebreakerAlways
1454 * true if a new line breaker should always be created.
1455 */
1456 private void rebuild(boolean limitWidth, boolean newLinebreakerAlways) {
1457 // TODO make this more efficient so it only clears annotation list when
1458 // it really has to
1459 if (isAnnotation()) {
1460 Frame parent = getParent();
1461 // parent can be null when running tests
1462 if (parent != null) {
1463 parent.clearAnnotations();
1464 }
1465 }
1466
1467 // if there is no text, there is nothing to do
1468 if (_text == null || _text.length() == 0) {
1469 // Frame parent = getParent();
1470 // if(parent != null)
1471 // parent.removeItem(this);
1472 return;
1473 }
1474
1475 if (_lineBreaker == null || newLinebreakerAlways) {
1476 AttributedString paragraphText = new AttributedString(_text
1477 .toString());
1478 paragraphText.addAttribute(TextAttribute.FONT, getPaintFont());
1479 frc = new FontRenderContext(null, true, true);
1480 _lineBreaker = new LineBreakMeasurer(paragraphText.getIterator(), frc);
1481 }
1482
1483 /* float width = Float.MAX_VALUE;
1484
1485 if (limitWidth) {
1486 width = getAbsoluteWidth();
1487 // else if (getMaxWidth() > 0)
1488 // width = Math.max(50, getMaxWidth() - getX()
1489 // - Item.MARGIN_RIGHT);
1490 } */
1491
1492 _textLayouts.clear();
1493 _lineOffsets.clear();
1494 // the first line always has a 0 offset
1495 _lineOffsets.add(0);
1496
1497 TextLayout layout;
1498
1499 float width;
1500 float lineHeight = Float.NaN;
1501 List<Point[]> lines = null;
1502
1503 if(_autoWrap || ExperimentalFeatures.AutoWrap.get()) {
1504 lines = new LinkedList<Point[]>();
1505 if(DisplayIO.getCurrentFrame() == null) {
1506 return;
1507 }
1508 for(Item item : DisplayIO.getCurrentFrame().getItems()) {
1509 if(item instanceof Line) {
1510 lines.add(new Point[] { ((Line) item).getStartItem().getPosition(), ((Line) item).getEndItem().getPosition() });
1511 }
1512 if(item instanceof Picture) {
1513 lines.add(new Point[] { item.getPosition(), new Point(item.getX(), item.getY() + item.getHeight()) });
1514 }
1515 }
1516 for(Item item : FreeItems.getInstance()) {
1517 if(item instanceof Line) {
1518 lines.add(new Point[] { ((Line) item).getStartItem().getPosition(), ((Line) item).getEndItem().getPosition() });
1519 }
1520 if(item instanceof Picture) {
1521 lines.add(new Point[] { item.getPosition(), new Point(item.getX(), item.getY() + item.getHeight()) });
1522 }
1523 }
1524 width = getLineWidth(getX(), getY(), lines);
1525 } else {
1526 width = Float.MAX_VALUE;
1527 if (limitWidth) {
1528 if(_maxWidth == null) {
1529 width = FrameGraphics.getMaxFrameSize().width - getX();
1530 } else {
1531 width = getAbsoluteWidth();
1532 }
1533 // else if (getMaxWidth() > 0)
1534 // width = Math.max(50, getMaxWidth() - getX()
1535 // - Item.MARGIN_RIGHT);
1536 }
1537 }
1538
1539 _lineBreaker.setPosition(0);
1540 boolean requireNextWord = false;
1541
1542 // --- Get the output of the LineBreakMeasurer and store it in a
1543 while (_lineBreaker.getPosition() < _text.length()) {
1544
1545 if(_autoWrap || ExperimentalFeatures.AutoWrap.get()) {
1546 requireNextWord = width < FrameGraphics.getMaxFrameSize().width - getX();
1547 }
1548
1549 layout = _lineBreaker.nextLayout(width, _text.length(), requireNextWord);
1550
1551 // lineBreaker does not break on newline
1552 // characters so they have to be check manually
1553 int start = _lineOffsets.get(_lineOffsets.size() - 1);
1554
1555 // int y = getY() + (getLineDrop(layout) * (_lineOffsets.size() - 1)
1556
1557 // check through the current line for newline characters
1558 for (int i = start + 1; i < _text.length(); i++) {
1559 if (_text.charAt(i) == '\n') {// || c == '\t'){
1560 _lineBreaker.setPosition(start);
1561 layout = _lineBreaker.nextLayout(width, i, requireNextWord);
1562 break;
1563 }
1564 }
1565
1566 _lineOffsets.add(_lineBreaker.getPosition());
1567
1568 if(layout == null) {
1569 layout = new TextLayout(" ", getPaintFont(), frc);
1570 }
1571
1572 if (/* hasWidth() && */getJustification() == Justification.full
1573 && _lineBreaker.getPosition() < _text.length())
1574 layout = layout.getJustifiedLayout(width);
1575
1576 _textLayouts.add(layout);
1577
1578 if(_autoWrap || ExperimentalFeatures.AutoWrap.get()) {
1579
1580 if(lineHeight != Float.NaN) {
1581 lineHeight = getLineDrop(layout);
1582 }
1583 width = getLineWidth(getX(), getY() + (lineHeight * (_textLayouts.size() - 1)), lines);
1584 }
1585 }
1586
1587 updatePolygon();
1588
1589 }
1590
1591 private float getLineWidth(int x, float y, List<Point[]> lines) {
1592 float width = FrameGraphics.getMaxFrameSize().width;
1593 for(Point[] l : lines) {
1594 // check for lines that cross over our y
1595 if((l[0].y >= y && l[1].y <= y) || (l[0].y <= y && l[1].y >= y)) {
1596 float dX = l[0].x - l[1].x;
1597 float dY = l[0].y - l[1].y;
1598 float newWidth;
1599 if(dX == 0) {
1600 newWidth = l[0].x;
1601 } else if(dY == 0) {
1602 newWidth = Math.min(l[0].x, l[1].x);
1603 } else {
1604 // System.out.print("gradient: " + (dY / dX));
1605 newWidth = l[0].x + (y - l[0].y) * dX / dY;
1606 }
1607 // System.out.println("dY:" + dY + " dX:" + dX + " width:" + newWidth);
1608 if(newWidth < x) {
1609 continue;
1610 }
1611 if(newWidth < width) {
1612 width = newWidth;
1613 }
1614 }
1615 }
1616 return width - x;
1617 }
1618
1619 private boolean hasFixedWidth() {
1620 assert (_maxWidth != null);
1621 if (_maxWidth == null) {
1622 justify(false);
1623 }
1624 return _maxWidth > 0;
1625 }
1626
1627 private int _alpha = -1;
1628
1629 public void setAlpha(int alpha) {
1630 _alpha = alpha;
1631 }
1632
1633 private Point getSelectedRange(int line) {
1634 if (_selectionEnd >= _text.length()) {
1635 _selectionEnd = _text.length();
1636 }
1637
1638 if (_selectionStart < 0)
1639 _selectionStart = 0;
1640
1641 if (_selectionStart < 0 || _selectionEnd < 0)
1642 return null;
1643
1644 int selectionLeft = Math.min(_selectionStart, _selectionEnd);
1645 int selectionRight = Math.max(_selectionStart, _selectionEnd);
1646
1647 // if the selection is after this line, return null
1648 if (_lineOffsets.get(line) > selectionRight)
1649 return null;
1650
1651 // if the selection is before this line, return null
1652 if (_lineOffsets.get(line) < selectionLeft
1653 && _lineOffsets.get(line)
1654 + _textLayouts.get(line).getCharacterCount() < selectionLeft)
1655 return null;
1656
1657 // Dont highlight a single char
1658 // if (selectionRight - selectionLeft <= MINIMUM_RANGED_CHARS)
1659 // return null;
1660
1661 // the selection occurs on this line, determine where it lies on the
1662 // line
1663 int start = Math.max(0, selectionLeft - _lineOffsets.get(line));
1664 // int end = Math.min(_lineOffsets.get(line) +
1665 // _textLayouts.get(line).getCharacterCount(), _selectionEnd);
1666 int end = Math.min(selectionRight - _lineOffsets.get(line),
1667 +_textLayouts.get(line).getCharacterCount());
1668
1669 // System.out.println(line + ": " + start + "x" + end + " (" +
1670 // _selectionStart + "x" + _selectionEnd + ")");
1671 return new Point(start, end);
1672 }
1673
1674 /**
1675 * @param mouseButton
1676 * Either MouseEvent.BUTTON1, MouseEvent.BUTTON2 or
1677 * MouseEvent.BUTTON3.
1678 *
1679 * @return The color for the text selection based on the given mouse click
1680 */
1681 protected Color getSelectionColor(int mouseButton) {
1682
1683 /*
1684 * Color main = getPaintColor(); Color back = getPaintBackgroundColor();
1685 *
1686 * if (Math.abs(main.getRed() - back.getRed()) < 10 &&
1687 * Math.abs(main.getGreen() - back.getGreen()) < 10 &&
1688 * Math.abs(main.getBlue() - back.getBlue()) < 10) { selection = new
1689 * Color(Math.abs(255 - main.getRed()), Math .abs(255 -
1690 * main.getGreen()), Math.abs(255 - main.getBlue())); } else { selection =
1691 * new Color((main.getRed() + (back.getRed() * 2)) / 3, (main.getGreen() +
1692 * (back.getGreen() * 2)) / 3, (main .getBlue() + (back.getBlue() * 2)) /
1693 * 3); }
1694 */
1695 int green = 160;
1696 int red = 160;
1697 int blue = 160;
1698
1699 if (FrameMouseActions.wasDeleteClicked()) {
1700 green = 235;
1701 red = 235;
1702 blue = 140;
1703 } else if (mouseButton == MouseEvent.BUTTON1) {
1704 red = 255;
1705 } else if (mouseButton == MouseEvent.BUTTON2) {
1706 green = 255;
1707 } else if (mouseButton == MouseEvent.BUTTON3) {
1708 blue = 255;
1709 }
1710
1711 return new Color(red, green, blue);
1712 }
1713
1714 @Override
1715 public void paint(Graphics2D g) {
1716 if (!isVisible())
1717 return;
1718
1719 // if there is no text to paint, do nothing.
1720 if (_text == null || _text.length() == 0)
1721 return;
1722
1723 if(_autoWrap || ExperimentalFeatures.AutoWrap.get()) {
1724 invalidateAll();
1725
1726 rebuild(true);
1727 } else if (_textLayouts.size() < 1) {
1728 clipFrameMargin();
1729 rebuild(true);
1730 // return;
1731 }
1732
1733 // check if its a vector item and paint all the vector stuff too if this
1734 // item is a free item
1735 // This will allow for dragging vectors around the place!
1736 if (hasVector() && isFloating()) {
1737 FrameGraphics.requestRefresh(false);
1738 // TODO make this use a more efficient paint method...
1739 // Have the text item return a bigger repaint area if it has an
1740 // associated vector
1741 }
1742
1743 // the background is only cleared if required
1744 if (getBackgroundColor() != null) {
1745 Color bgc = getBackgroundColor();
1746 if (_alpha > 0) {
1747 bgc = new Color(bgc.getRed(), bgc.getGreen(), bgc.getBlue(),
1748 _alpha);
1749 }
1750 g.setColor(bgc);
1751
1752 Color gradientColor = getGradientColor();
1753 if (gradientColor != null) {
1754 // The painting is not efficient enough for gradients...
1755 Shape s = getPolygon();
1756 if (s != null) {
1757 Rectangle b = s.getBounds();
1758 GradientPaint gp = new GradientPaint(
1759 (int) (b.x + b.width * 0.3), b.y, bgc,
1760 (int) (b.x + b.width * 1.3), b.y, gradientColor);
1761 g.setPaint(gp);
1762 }
1763 }
1764
1765 g.fillPolygon(getPolygon());
1766 }
1767
1768 if (hasVisibleBorder()) {
1769 g.setColor(getPaintBorderColor());
1770 Stroke borderStroke = new BasicStroke(getThickness(), CAP, JOIN);
1771 g.setStroke(borderStroke);
1772 g.drawPolygon(getPolygon());
1773 }
1774
1775 if (hasFormula()) {
1776 g.setColor(getPaintHighlightColor());
1777 Stroke highlightStroke = new BasicStroke(1F, CAP, JOIN);
1778 g.setStroke(highlightStroke);
1779
1780 Point2D.Float start = getEdgePosition(0, true);
1781 Point2D.Float end = getEdgePosition(0, false);
1782 g.drawLine(Math.round(start.x), Math.round(start.y), Math
1783 .round(end.x), Math.round(end.y));
1784 }
1785
1786 if (isHighlighted()) {
1787 g.setColor(getPaintHighlightColor());
1788 Stroke highlightStroke = new BasicStroke(
1789 (float) getHighlightThickness(), CAP, JOIN);
1790 g.setStroke(highlightStroke);
1791 if (HighlightMode.Enclosed.equals(getHighlightMode()))
1792 g.fillPolygon(getPolygon());
1793 else
1794 g.drawPolygon(getPolygon());
1795 }
1796
1797 float y = getY();
1798 Color c = getPaintColor();
1799 if (_alpha > 0)
1800 c = new Color(c.getRed(), c.getGreen(), c.getBlue(), _alpha);
1801
1802 g.setColor(c);
1803
1804 Color selection = getSelectionColor(FrameMouseActions
1805 .getLastMouseButton());
1806
1807 // width -= getX();
1808 // int line = 0;
1809 // boolean tab = false;
1810 synchronized (_textLayouts) {
1811 for (int i = 0; i < _textLayouts.size(); i++) {
1812 TextLayout layout = _textLayouts.get(i);
1813
1814 Point p = getSelectedRange(i);
1815 if (p != null) {
1816 AffineTransform at = new AffineTransform();
1817 AffineTransform orig = g.getTransform();
1818 at.translate(getX() + getJustOffset(layout), y);
1819 g.setTransform(at);
1820
1821 g.setColor(selection);
1822 g.fill(layout.getLogicalHighlightShape(p.x, p.y));
1823
1824 g.setTransform(orig);
1825 g.setColor(c);
1826 }
1827
1828 layout.draw(g, 1 + getX() + getJustOffset(layout), y);
1829
1830 /*
1831 * AffineTransform at = new AffineTransform(); AffineTransform
1832 * orig = g.getTransform(); at.translate(getX() +
1833 * getJustOffset(layout), y); g.setTransform(at);
1834 * g.draw(layout.getLogicalHighlightShape(0,
1835 * layout.getCharacterCount())); g.setTransform(orig); /*
1836 * if(_text.charAt(_lineOffsets.get(line) +
1837 * (layout.getCharacterCount() - 1)) == '\t'){ tab = true; x =
1838 * (int) (getX() + x + 20 + layout.getVisibleAdvance()); }else{
1839 */
1840 y += getLineDrop(layout);
1841 /*
1842 * tab = false; }
1843 *
1844 * line++;
1845 */
1846 }
1847 }
1848
1849 paintLink(g);
1850 }
1851
1852 @Override
1853 protected Rectangle getLinkDrawArea() { // TODO: Revise
1854 return getDrawingArea()[0];
1855 }
1856
1857 /**
1858 * Determines if this text has any text in it.
1859 *
1860 * @return True if this Item has no text in it, false otherwise.
1861 */
1862 public boolean isEmpty() {
1863 if (_text == null || _text.length() == 0)
1864 return true;
1865
1866 return false;
1867 }
1868
1869 @Override
1870 public Text copy() {
1871 Text copy = new Text(getID());
1872 // copy standard item values
1873 Item.DuplicateItem(this, copy);
1874
1875 // copy values specific to text items
1876 copy.setSpacing(getSpacing());
1877 copy.setInitialSpacing(getInitialSpacing());
1878
1879 copy.setWidth(getWidthToSave());
1880 copy.setJustification(getJustification());
1881 copy.setLetterSpacing(getLetterSpacing());
1882 copy.setWordSpacing(getWordSpacing());
1883 copy.setWidth(getWidthToSave());
1884 copy.setFont(getFont());
1885 if (hasFormula()) {
1886 copy.calculate(getFormula());
1887 } else {
1888 copy.setText(_text.toString());
1889 }
1890 copy.setHidden(!isVisible());
1891 return copy;
1892 }
1893
1894 @Override
1895 public float getSize() {
1896 return getPaintFont().getSize2D();
1897 }
1898
1899 /**
1900 * Returns the number of characters in this Text, excluding new lines.
1901 *
1902 * @return The sum of the length of each line of text
1903 */
1904 public int getLength() {
1905 return _text.length();
1906 }
1907
1908 @Override
1909 public void setSize(float size) {
1910 invalidateAll();
1911 // size *= UserSettings.ScaleFactor;
1912 // Dont want to have size set when duplicating a point which has size 0
1913 if (size < 0)
1914 return;
1915
1916 if (size < MINIMUM_FONT_SIZE)
1917 size = MINIMUM_FONT_SIZE;
1918 setFont(getPaintFont().deriveFont(size));
1919 rebuild(true);
1920 invalidateAll();
1921 }
1922
1923 @Override
1924 public void setAnnotation(boolean val) {
1925 float mouseX = DisplayIO.getFloatMouseX();
1926 float mouseY = FrameMouseActions.MouseY;
1927 Point2D.Float newPoint = new Point2D.Float();
1928 if (val) {
1929 // if this is already an annotation, do nothing
1930 if (isAnnotation())
1931 return;
1932 if (!isLineEnd() && _text.length() > 0
1933 && _text.charAt(0) == DEFAULT_BULLET) {
1934 newPoint.setLocation(insertText(""
1935 + (char) KeyEvent.VK_BACK_SPACE, mouseX, mouseY, 1));
1936 if (_text.length() > 0 && _text.charAt(0) == ' ')
1937 newPoint.setLocation(insertText(""
1938 + (char) KeyEvent.VK_BACK_SPACE, newPoint.x,
1939 newPoint.y, 1));
1940 } else {
1941 newPoint.setLocation(insertText("@", mouseX, mouseY, 0));
1942 }
1943 } else {
1944 // if this is not an annotation, do nothing
1945 if (!isAnnotation())
1946 return;
1947 if (!isLineEnd() && _text.charAt(0) == '@') {
1948 newPoint.setLocation(insertText(""
1949 + (char) KeyEvent.VK_BACK_SPACE, mouseX, mouseY, 1));
1950 newPoint.setLocation(insertText(DEFAULT_BULLET_STRING,
1951 newPoint.x, newPoint.y, 0));
1952 } else {
1953 newPoint.setLocation(insertText(""
1954 + (char) KeyEvent.VK_BACK_SPACE, mouseX, mouseY, 1));
1955 }
1956 }
1957 FrameUtils.setLastEdited(this);
1958 rebuild(true);
1959 DisplayIO.setCursorPosition(newPoint.x, newPoint.y, false);
1960 }
1961
1962 /**
1963 *
1964 */
1965 private void insertString(String toInsert, int pos) {
1966 assert (toInsert.length() > 0);
1967
1968 _text.insert(pos, toInsert);
1969
1970 if (toInsert.length() > 1) {
1971 _lineBreaker = null;
1972 }
1973
1974 if (_lineBreaker != null) {
1975 AttributedString inserting = new AttributedString(_text.toString());
1976 inserting.addAttribute(TextAttribute.FONT, getPaintFont());
1977 _lineBreaker.insertChar(inserting.getIterator(), pos);
1978 }
1979 }
1980
1981 private void deleteChar(int pos) {
1982 _text.deleteCharAt(pos);
1983
1984 if (_text.length() == 0) {
1985 if (this.isLineEnd()) {
1986 // Remove and replace with a dot
1987 FrameKeyboardActions.replaceText(this);
1988 DisplayIO.setCursorPosition(this._x, this._y);
1989 }
1990 return;
1991 }
1992
1993 if (_lineBreaker != null) {
1994 AttributedString inserting = new AttributedString(_text.toString());
1995 inserting.addAttribute(TextAttribute.FONT, getPaintFont());
1996 _lineBreaker.deleteChar(inserting.getIterator(), pos);
1997 }
1998
1999 }
2000
2001 @Override
2002 public boolean isAnnotation() {
2003 if (_text != null && _text.length() > 0 && _text.charAt(0) == '@')
2004 return true;
2005
2006 return false;
2007 }
2008
2009 public boolean isSpecialAnnotation() {
2010 assert _text != null;
2011 String s = _text.toString().toLowerCase();
2012 if (s.length() > 0 && s.indexOf("@") == 0) {
2013 if (s.equals("@old") || s.equals("@ao")
2014 || s.equals("@itemtemplate") || s.equals("@parent")
2015 || s.equals("@next") || s.equals("@previous")
2016 || s.equals("@first") || s.equals("@i") || s.equals("@iw")
2017 || s.equals("@f"))
2018 return true;
2019 }
2020
2021 return false;
2022 }
2023
2024 @Override
2025 public Item merge(Item merger, int mouseX, int mouseY) {
2026 if (merger.isLineEnd()) {
2027 // Merging line ends onto non line end text is a no-op
2028 if (!isLineEnd())
2029 return null;
2030
2031 if (merger instanceof Text)
2032 insertText(((Text) merger).getText(), mouseX, mouseY);
2033
2034 // Set the position by moving the cursor before calling this
2035 // method!!
2036
2037 List<Line> lines = new LinkedList<Line>();
2038 lines.addAll(merger.getLines());
2039 for (Line line : lines) {
2040 line.replaceLineEnd(merger, this);
2041 }
2042 merger.delete();
2043 this.setOffset(0, 0);
2044 return null;
2045 }
2046
2047 if (!(merger instanceof Text))
2048 return merger;
2049
2050 Text merge = (Text) merger;
2051
2052 // insertText(merge.getText(), mouseX, mouseY);
2053
2054 // if the item being merged has a link
2055 if (merge.getLink() != null) {
2056 // if this item has a link, keep it on the cursor
2057 if (getLink() != null) {
2058 merge.setText(merge.getLink());
2059 merge.setLink(null);
2060 // return merge;
2061 // TODO get this to return the merged item and attach it to the
2062 // cursor only when the user presses the middle button.
2063 } else
2064 setLink(merge.getLink());
2065 }
2066
2067 return null;
2068 }
2069
2070 /**
2071 * Resets the position of the item to the default position for a title item.
2072 *
2073 */
2074 public void resetTitlePosition() {
2075 setPosition(MARGIN_LEFT, MARGIN_LEFT + getBoundsHeight());
2076 Frame modelFrame = getParentOrCurrentFrame();
2077 if (modelFrame != null) {
2078 setRightMargin(modelFrame.getNameItem().getX() - MARGIN_LEFT, true);
2079 } else {
2080 System.out
2081 .print("Error: text.resetTitlePosition, getParent or currentFrame returned null");
2082 setRightMargin(MARGIN_LEFT, true);
2083 }
2084 }
2085
2086 /**
2087 * Removes the set of characters up to the first space in this text item.
2088 *
2089 * @return the string that was removed.
2090 */
2091 public String stripFirstWord() {
2092 int firstSpace = _text.toString().indexOf(' ');
2093
2094 // if there is only one word just make it blank
2095 if (firstSpace < 0 || firstSpace + 1 >= _text.length()) {
2096 String text = _text.toString();
2097 setText("");
2098 return text;
2099 }
2100
2101 String firstWord = _text.toString().substring(0, firstSpace);
2102 setText(_text.toString().substring(firstSpace).trim());
2103
2104 return firstWord;
2105 }
2106
2107 public String toString() {
2108 String message = "[" + getFirstLine() + "]" + FRAME_NAME_SEPARATOR;
2109
2110 if (getParent() != null)
2111 return message + getParent().getName();
2112 return message + getDateCreated();
2113 }
2114
2115 public Text getTemplateForm() {
2116 Text template = this.copy();
2117 template.setID(-1);
2118 // reset width of global templates so the widths of the items on the settings frames don't cause issues
2119 // this is in response to the fact that FrameCreator.addItem() sets rightMargin when it adds items
2120 template.setWidth(null);
2121 /*
2122 * The template must have text otherwise the bounds height will be
2123 * zero!! This will stop escape drop down from working if there is no
2124 * item template
2125 */
2126 template.setText("@");
2127 return template;
2128 }
2129
2130 @Override
2131 public boolean isNear(int x, int y) {
2132 if (super.isNear(x, y)) {
2133 // TODO check that it is actually near one of the lines of space
2134 // return contains(x, y, getGravity() * 2 + NEAR_DISTANCE);
2135 // at the moment contains ignores gravity when checking the top and
2136 // bottom of text lines... so the cursor must be between two text
2137 // lines
2138 float textY = getY();
2139 float textX = getX();
2140
2141 for (TextLayout text : _textLayouts) {
2142 // check left and right of each box
2143 Rectangle2D textOutline = text.getLogicalHighlightShape(0,
2144 text.getCharacterCount()).getBounds2D();
2145
2146 // check if the cursor is within the top, bottom and within the
2147 // gravity of right
2148 if (y - textY > textOutline.getY() - NEAR_DISTANCE
2149 && y - textY < textOutline.getY()
2150 + textOutline.getHeight() + NEAR_DISTANCE
2151 && x - textX < textOutline.getWidth() + NEAR_DISTANCE)
2152 return true;
2153 textY += getLineDrop(text);
2154 }
2155 }
2156 return false;
2157 }
2158
2159 @Override
2160 public void anchor() {
2161 super.anchor();
2162 // ensure all text items have their selection cleared
2163 clearSelection();
2164 setAlpha(0);
2165 if (isLineEnd())
2166 DisplayIO.setCursor(Item.DEFAULT_CURSOR);
2167
2168 String text = _text.toString().trim();
2169
2170 clipFrameMargin();
2171
2172 // Show the overlay stuff immediately if this is an overlay item
2173 if (hasLink() && (text.startsWith("@ao") || text.startsWith("@o"))) {
2174 FrameKeyboardActions.Refresh();
2175 }
2176 }
2177
2178 private void clipFrameMargin() {
2179 if (!hasFixedWidth()) {
2180 int frameWidth = FrameGraphics.getMaxFrameSize().width;
2181 /*
2182 * Only change width if it is more than 150 pixels from the right of
2183 * the screen
2184 */
2185 if (!_text.toString().contains(" ")) {
2186 Integer width = getWidth();
2187 if (width == null || width < 0)
2188 setWidth(Integer.MIN_VALUE + 1);
2189 } else if (frameWidth - getX() > ADJUST_WIDTH_THRESHOLD) {
2190 justify(false);
2191 // setRightMargin(frameWidth, false);
2192 }
2193 }
2194 }
2195
2196 public void justify(boolean fixWidth) {
2197
2198 // if autowrap is on, wrapping is done every time we draw
2199 if(ExperimentalFeatures.AutoWrap.get()) {
2200 return;
2201 }
2202
2203 Integer width = FrameGraphics.getMaxFrameSize().width;
2204
2205 // Check if that text item is inside an enclosing rectangle...
2206 // Set its max width accordingly
2207 Polygon enclosure = FrameUtils.getEnlosingPolygon();
2208 if (enclosure != null) {
2209 Rectangle bounds = enclosure.getBounds();
2210 if (bounds.width > 200 && getX() < bounds.width / 3 + bounds.x) {
2211 width = bounds.x + bounds.width;
2212 }
2213 }
2214
2215 if (getWidth() == null)
2216 setRightMargin(width, fixWidth);
2217
2218 // Check for the annotation that restricts the width of text items
2219 // on the frame
2220 String widthString;
2221 if ((widthString = getParentOrCurrentFrame().getAnnotationValue(
2222 "maxwidth")) != null) {
2223 try {
2224 int oldWidth = getWidth();
2225 int maxWidth = Integer.parseInt(widthString);
2226 if (maxWidth < oldWidth)
2227 setWidth(maxWidth);
2228 } catch (NumberFormatException nfe) {
2229 }
2230
2231 }
2232 }
2233
2234 public void resetFrameNamePosition() {
2235 Dimension maxSize = FrameGraphics.getMaxFrameSize();
2236 if (maxSize != null) {
2237 // setMaxWidth(maxSize.width);
2238 setPosition(maxSize.width - getBoundsWidth(), getBoundsHeight());
2239 }
2240 }
2241
2242 @Override
2243 protected int getLinkYOffset() {
2244 if (_textLayouts.size() == 0)
2245 return 0;
2246 return Math.round(-(_textLayouts.get(0).getAscent() / 2));
2247 }
2248
2249 @Override
2250 public String getName() {
2251 return getFirstLine();
2252 }
2253
2254 public static final String TAB_STRING = " ";
2255
2256 public Point2D.Float insertTab(char ch, float mouseX, float mouseY) {
2257 return insertText("" + ch, mouseX, mouseY);
2258 }
2259
2260 public Point2D.Float removeTab(char ch, float mouseX, float mouseY) {
2261 // Insert a space as a flag that it is a backwards tab
2262 return insertText(ch + " ", mouseX, mouseY);
2263 }
2264
2265 public static boolean isBulletChar(char c) {
2266 for (int i = 0; i < BULLETS.length; i++) {
2267 if (BULLETS[i] == c)
2268 return true;
2269 }
2270 return c == '*' || c == '+' || c == '>' || c == '-' || c == 'o';
2271 }
2272
2273 public boolean hasOverlay() {
2274 if (!isAnnotation() || getLink() == null)
2275 return false;
2276 String text = getText().toLowerCase();
2277 // TODO make it so can just check the _overlay variable
2278 // Mike cant remember the reason _overlay var cant be use! opps
2279 if (!text.startsWith("@"))
2280 return false;
2281 return text.startsWith("@o") || text.startsWith("@ao")
2282 || text.startsWith("@v") || text.startsWith("@av");
2283 }
2284
2285 public boolean hasSelection() {
2286 return getSelectionSize() > 0;
2287 }
2288
2289 /**
2290 * Dont save text items that are all white space.
2291 */
2292 @Override
2293 public boolean dontSave() {
2294 String text = getText();
2295 assert (text != null);
2296 return text.trim().length() == 0 || super.dontSave();
2297 }
2298
2299 @Override
2300 public boolean calculate(String formula) {
2301 if (FrameGraphics.isXRayMode())
2302 return false;
2303
2304 super.calculate(formula);
2305 if (isFloating() || formula == null || formula.length() == 0) {
2306 return false;
2307 }
2308 formula = formula.replace(':', '=');
2309
2310 String lowercaseFormula = formula.toLowerCase();
2311 ExpediteeJEP myParser = new ExpediteeJEP();
2312
2313 int nextVarNo = 1;
2314
2315 // Add variables from the containing rectangle if the item being
2316 // calculated is inside the enclosing rectangle
2317 Collection<Item> enclosed = getItemsInSameEnclosure();
2318 for (Item i : enclosed) {
2319 if (i == this)
2320 continue;
2321 if (i instanceof Text && !i.isAnnotation()) {
2322 AttributeValuePair pair = i.getAttributeValuePair();
2323 if (pair.hasPair()) {
2324 try {
2325 double value = pair.getDoubleValue();
2326 myParser.addVariable(pair.getAttribute(), value);
2327 // myParser.addVariable("$" + nextVarNo++, value);
2328 } catch (NumberFormatException nfe) {
2329 continue;
2330 } catch (Exception e) {
2331 e.printStackTrace();
2332 }
2333 } // else {
2334 // Add anonomous vars
2335 try {
2336 double value = pair.getDoubleValue();
2337 if (value != Double.NaN)
2338 myParser.addVariable("$" + nextVarNo++, value);
2339 } catch (NumberFormatException nfe) {
2340 continue;
2341 } catch (Exception e) {
2342 e.printStackTrace();
2343 }
2344 // }
2345 }
2346 }
2347
2348 // Add the variables from this frame
2349 myParser.addVariables(this.getParentOrCurrentFrame());
2350 String linkedFrame = getAbsoluteLink();
2351 // Add the relative frame variable if the item is linked
2352 if (linkedFrame != null) {
2353 Frame frame = FrameIO.LoadFrame(linkedFrame);
2354 myParser.addVariables(frame);
2355 // If the frame is linked add vector variable for the frame
2356 if (lowercaseFormula.contains("$frame")) {
2357 myParser.addVectorVariable(frame.getNonAnnotationItems(true),
2358 "$frame");
2359 }
2360 }
2361 // Add the relative box variable if this item is a line end
2362 if (this.isLineEnd()) {
2363 // if its a line end add the enclosed stuff as an @variable
2364 if (lowercaseFormula.contains("$box")) {
2365 myParser.addVectorVariable(getEnclosedItems(), "$box");
2366 }
2367 }
2368 myParser.resetObserver();
2369 try {
2370 Node node = myParser.parse(formula);
2371 String result = myParser.evaluate(node);
2372 if (result != null) {
2373 this.setText(result);
2374 this.setFormula(formula);
2375
2376 if (!this.hasAction()) {
2377 setActionMark(false);
2378 setAction("extract formula");
2379 }
2380 }
2381 } catch (Throwable e) {
2382 //e.printStackTrace();
2383 String formula2 = getFormula();
2384 this.setText(formula2);
2385 this.setFormula(formula2);
2386 return false;
2387 }
2388
2389 _attributeValuePair = null;
2390
2391 return true;
2392 }
2393
2394 /**
2395 * Gets items which are in the same enclosure as this item.
2396 * In the event more than one enclosure meets this criteria, then
2397 * the one returned is the one with the smallest area.
2398 * TODO: Improve the efficiency of this method
2399 *
2400 * @return
2401 */
2402 public Collection<Item> getItemsInSameEnclosure() {
2403 Collection<Item> sameEnclosure = null;
2404 Collection<Item> seen = new HashSet<Item>();
2405 Frame parent = getParentOrCurrentFrame();
2406 double enclosureArea = Double.MAX_VALUE;
2407 for (Item i : parent.getVisibleItems()) {
2408 /*
2409 * Go through all the enclosures looking for one that includes this
2410 * item
2411 */
2412 if (!seen.contains(i) && i.isEnclosed()) {
2413 seen.addAll(i.getEnclosingDots());
2414 Collection<Item> enclosed = i.getEnclosedItems();
2415 // Check if we have found an enclosure containing this item
2416 // Check it is smaller than any other enclosure found containing
2417 // this item
2418 if (enclosed.contains(this)
2419 && i.getEnclosedArea() < enclosureArea) {
2420 sameEnclosure = enclosed;
2421 }
2422 }
2423 }
2424
2425 if (sameEnclosure == null)
2426 return new LinkedList<Item>();
2427
2428 return sameEnclosure;
2429 }
2430
2431 /**
2432 * Returns true if items of the parent frame should be recalculated when
2433 * this item is modified
2434 */
2435 public boolean recalculateWhenChanged() {
2436 if (/*
2437 * !isAnnotation() &&
2438 */(hasFormula() || isLineEnd()))
2439 return true;
2440 try {
2441 AttributeValuePair avp = getAttributeValuePair();
2442
2443 if (!avp.getDoubleValue().equals(Double.NaN))
2444 return true;
2445 } catch (Exception e) {
2446 e.printStackTrace();
2447 }
2448
2449 return false;
2450 }
2451
2452 public float getLineHeight() {
2453 return getLineDrop(_textLayouts.get(0));
2454 }
2455
2456 @Override
2457 public void setAnchorLeft(Float anchor) {
2458 if (!isLineEnd()) {
2459 super.setAnchorLeft(anchor);
2460 // Subtract off the link width
2461 if (anchor != null) {
2462 setX(anchor + getLeftMargin());
2463 }
2464 return;
2465 }
2466 invalidateFill();
2467 invalidateCommonTrait(ItemAppearence.PreMoved);
2468
2469 this._anchorLeft = anchor;
2470 this._anchorRight = null;
2471
2472 int oldX = getX();
2473 if (anchor != null) {
2474 float deltaX = anchor + getLeftMargin() - oldX;
2475 anchorConnected(AnchorEdgeType.Left, deltaX);
2476 }
2477
2478 invalidateCommonTrait(ItemAppearence.PostMoved);
2479 invalidateFill();
2480 }
2481
2482 @Override
2483 public void setAnchorRight(Float anchor) {
2484 if (!isLineEnd()) {
2485 super.setAnchorRight(anchor);
2486 // Subtract off the link width
2487 if (anchor != null) {
2488 setX(FrameGraphics.getMaxFrameSize().width - anchor
2489 - getBoundsWidth() + getLeftMargin());
2490 }
2491 return;
2492 }
2493 invalidateFill();
2494 invalidateCommonTrait(ItemAppearence.PreMoved);
2495
2496 this._anchorRight = anchor;
2497 this._anchorLeft = null;
2498
2499 int oldX = getX();
2500 if (anchor != null) {
2501 float deltaX = FrameGraphics.getMaxFrameSize().width - anchor
2502 - getBoundsWidth() + getLeftMargin() - oldX;
2503 anchorConnected(AnchorEdgeType.Right, deltaX);
2504 }
2505
2506 invalidateCommonTrait(ItemAppearence.PostMoved);
2507 invalidateFill();
2508 }
2509
2510
2511
2512 @Override
2513 public void setAnchorTop(Float anchor) {
2514 if (!isLineEnd()) {
2515 super.setAnchorTop(anchor);
2516 if (anchor != null) {
2517 setY(anchor + getBoundsHeight());
2518 }
2519 return;
2520 }
2521 invalidateFill();
2522 invalidateCommonTrait(ItemAppearence.PreMoved);
2523
2524 this._anchorTop = anchor;
2525 this._anchorBottom = null;
2526
2527 int oldY = getY();
2528 if (anchor != null) {
2529 float deltaY = anchor - oldY;
2530 anchorConnected(AnchorEdgeType.Top, deltaY);
2531 }
2532
2533 invalidateCommonTrait(ItemAppearence.PostMoved);
2534 invalidateFill();
2535 }
2536
2537 @Override
2538 public void setAnchorBottom(Float anchor) {
2539 if (!isLineEnd()) {
2540 super.setAnchorBottom(anchor);
2541 return;
2542 }
2543 invalidateFill();
2544 invalidateCommonTrait(ItemAppearence.PreMoved);
2545
2546 this._anchorBottom = anchor;
2547 this._anchorTop = null;
2548
2549 int oldY = getY();
2550 if (anchor != null) {
2551
2552 float deltaY = FrameGraphics.getMaxFrameSize().height - anchor
2553 - oldY;
2554 anchorConnected(AnchorEdgeType.Bottom, deltaY);
2555 }
2556
2557 invalidateCommonTrait(ItemAppearence.PostMoved);
2558 invalidateFill();
2559 }
2560
2561 @Override
2562 public void scale(Float scale, int originX, int originY) {
2563 setSize(getSize() * scale);
2564
2565 Integer width = getWidth();
2566 if (width != null) {
2567 setWidth(Math.round(width * scale));
2568 }
2569
2570 super.scale(scale, originX, originY);
2571 rebuild(true);
2572 }
2573
2574 /*
2575 * Returns the SIMPLE statement contained by this text item.
2576 *
2577 */
2578 public String getStatement() {
2579 return getText().split("\\s+")[0];
2580 }
2581
2582 public boolean getAutoWrap() {
2583 return _autoWrap;
2584 }
2585
2586 // workaround since true is the default value and would not be displayed normally
2587 public String getAutoWrapToSave() {
2588 if(!_autoWrap) {
2589 return null;
2590 }
2591 return "true";
2592 }
2593
2594 public void setAutoWrap(boolean autoWrap) {
2595 _autoWrap = autoWrap;
2596 }
2597}
Note: See TracBrowser for help on using the repository browser.