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

Last change on this file since 635 was 635, checked in by jts21, 11 years ago

Fix crash (infinite loop) caused by trying to wrap a text item at the edge of the frame when it is too large to wrap

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