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

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

Add panning action

  • Use by shift-left-click and dragging the mouse, or by adding a "Pan" action
  • Mouse binding is disabled by default, enable via "MousePan" setting in in settings/experimental (also "AutoWrap" setting was moved to experimental too)
  • Frames now save all visible items instead of deleting items that have negative position values, this change was made so items would not be lost by panning the frame.
File size: 63.3 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
1470 // --- Get the output of the LineBreakMeasurer and store it in a
1471 while (_lineBreaker.getPosition() < _text.length()) {
1472
1473 layout = _lineBreaker.nextLayout(width, _text.length(), true);
1474
1475 // lineBreaker does not break on newline
1476 // characters so they have to be check manually
1477 int start = _lineOffsets.get(_lineOffsets.size() - 1);
1478
1479 // int y = getY() + (getLineDrop(layout) * (_lineOffsets.size() - 1)
1480
1481 // check through the current line for newline characters
1482 for (int i = start + 1; i < _text.length(); i++) {
1483 if (_text.charAt(i) == '\n') {// || c == '\t'){
1484 _lineBreaker.setPosition(start);
1485 layout = _lineBreaker.nextLayout(width, i, true);
1486 break;
1487 }
1488 }
1489
1490 _lineOffsets.add(_lineBreaker.getPosition());
1491
1492 if(layout == null) {
1493 layout = new TextLayout(" ", getPaintFont(), frc);
1494 }
1495
1496 if (/* hasWidth() && */getJustification() == Justification.full
1497 && _lineBreaker.getPosition() < _text.length())
1498 layout = layout.getJustifiedLayout(width);
1499
1500 _textLayouts.add(layout);
1501
1502 if(_autoWrap || ExperimentalFeatures.AutoWrap) {
1503
1504 if(lineHeight != Float.NaN) {
1505 lineHeight = getLineDrop(layout);
1506 }
1507 width = getLineWidth(getX(), getY() + (lineHeight * (_textLayouts.size() - 1)), lines);
1508 }
1509 }
1510
1511 updatePolygon();
1512
1513 }
1514
1515 private float getLineWidth(int x, float y, List<Point[]> lines) {
1516 float width = FrameGraphics.getMaxFrameSize().width;
1517 for(Point[] l : lines) {
1518 // check for lines that cross over our y
1519 if((l[0].y >= y && l[1].y <= y) || (l[0].y <= y && l[1].y >= y)) {
1520 float dX = l[0].x - l[1].x;
1521 float dY = l[0].y - l[1].y;
1522 float newWidth;
1523 if(dX == 0) {
1524 newWidth = l[0].x;
1525 } else if(dY == 0) {
1526 newWidth = Math.min(l[0].x, l[1].x);
1527 } else {
1528 // System.out.print("gradient: " + (dY / dX));
1529 newWidth = l[0].x + (y - l[0].y) * dX / dY;
1530 }
1531 // System.out.println("dY:" + dY + " dX:" + dX + " width:" + newWidth);
1532 if(newWidth < x) {
1533 continue;
1534 }
1535 if(newWidth < width) {
1536 width = newWidth;
1537 }
1538 }
1539 }
1540 return width - x;
1541 }
1542
1543 private boolean hasFixedWidth() {
1544 assert (_maxWidth != null);
1545 if (_maxWidth == null) {
1546 justify(false);
1547 }
1548 return _maxWidth > 0;
1549 }
1550
1551 private int _alpha = -1;
1552
1553 public void setAlpha(int alpha) {
1554 _alpha = alpha;
1555 }
1556
1557 private Point getSelectedRange(int line) {
1558 if (_selectionEnd >= _text.length()) {
1559 _selectionEnd = _text.length();
1560 }
1561
1562 if (_selectionStart < 0)
1563 _selectionStart = 0;
1564
1565 if (_selectionStart < 0 || _selectionEnd < 0)
1566 return null;
1567
1568 int selectionLeft = Math.min(_selectionStart, _selectionEnd);
1569 int selectionRight = Math.max(_selectionStart, _selectionEnd);
1570
1571 // if the selection is after this line, return null
1572 if (_lineOffsets.get(line) > selectionRight)
1573 return null;
1574
1575 // if the selection is before this line, return null
1576 if (_lineOffsets.get(line) < selectionLeft
1577 && _lineOffsets.get(line)
1578 + _textLayouts.get(line).getCharacterCount() < selectionLeft)
1579 return null;
1580
1581 // Dont highlight a single char
1582 // if (selectionRight - selectionLeft <= MINIMUM_RANGED_CHARS)
1583 // return null;
1584
1585 // the selection occurs on this line, determine where it lies on the
1586 // line
1587 int start = Math.max(0, selectionLeft - _lineOffsets.get(line));
1588 // int end = Math.min(_lineOffsets.get(line) +
1589 // _textLayouts.get(line).getCharacterCount(), _selectionEnd);
1590 int end = Math.min(selectionRight - _lineOffsets.get(line),
1591 +_textLayouts.get(line).getCharacterCount());
1592
1593 // System.out.println(line + ": " + start + "x" + end + " (" +
1594 // _selectionStart + "x" + _selectionEnd + ")");
1595 return new Point(start, end);
1596 }
1597
1598 /**
1599 * @param mouseButton
1600 * Either MouseEvent.BUTTON1, MouseEvent.BUTTON2 or
1601 * MouseEvent.BUTTON3.
1602 *
1603 * @return The color for the text selection based on the given mouse click
1604 */
1605 protected Color getSelectionColor(int mouseButton) {
1606
1607 /*
1608 * Color main = getPaintColor(); Color back = getPaintBackgroundColor();
1609 *
1610 * if (Math.abs(main.getRed() - back.getRed()) < 10 &&
1611 * Math.abs(main.getGreen() - back.getGreen()) < 10 &&
1612 * Math.abs(main.getBlue() - back.getBlue()) < 10) { selection = new
1613 * Color(Math.abs(255 - main.getRed()), Math .abs(255 -
1614 * main.getGreen()), Math.abs(255 - main.getBlue())); } else { selection =
1615 * new Color((main.getRed() + (back.getRed() * 2)) / 3, (main.getGreen() +
1616 * (back.getGreen() * 2)) / 3, (main .getBlue() + (back.getBlue() * 2)) /
1617 * 3); }
1618 */
1619 int green = 160;
1620 int red = 160;
1621 int blue = 160;
1622
1623 if (FrameMouseActions.wasDeleteClicked()) {
1624 green = 235;
1625 red = 235;
1626 blue = 140;
1627 } else if (mouseButton == MouseEvent.BUTTON1) {
1628 red = 255;
1629 } else if (mouseButton == MouseEvent.BUTTON2) {
1630 green = 255;
1631 } else if (mouseButton == MouseEvent.BUTTON3) {
1632 blue = 255;
1633 }
1634
1635 return new Color(red, green, blue);
1636 }
1637
1638 @Override
1639 public void paint(Graphics2D g) {
1640 if (!isVisible())
1641 return;
1642
1643 // if there is no text to paint, do nothing.
1644 if (_text == null || _text.length() == 0)
1645 return;
1646
1647 if(_autoWrap || ExperimentalFeatures.AutoWrap) {
1648 invalidateAll();
1649
1650 rebuild(true);
1651 } else if (_textLayouts.size() < 1) {
1652 rebuild(true);
1653 // System.out.println("Error: " + _text);
1654 // return;
1655 }
1656
1657 // check if its a vector item and paint all the vector stuff too if this
1658 // item is a free item
1659 // This will allow for dragging vectors around the place!
1660 if (hasVector() && isFloating()) {
1661 FrameGraphics.requestRefresh(false);
1662 // TODO make this use a more efficient paint method...
1663 // Have the text item return a bigger repaint area if it has an
1664 // associated vector
1665 }
1666
1667 // the background is only cleared if required
1668 if (getBackgroundColor() != null) {
1669 Color bgc = getBackgroundColor();
1670 if (_alpha > 0) {
1671 bgc = new Color(bgc.getRed(), bgc.getGreen(), bgc.getBlue(),
1672 _alpha);
1673 }
1674 g.setColor(bgc);
1675
1676 Color gradientColor = getGradientColor();
1677 if (gradientColor != null) {
1678 // The painting is not efficient enough for gradients...
1679 Shape s = getPolygon();
1680 if (s != null) {
1681 Rectangle b = s.getBounds();
1682 GradientPaint gp = new GradientPaint(
1683 (int) (b.x + b.width * 0.3), b.y, bgc,
1684 (int) (b.x + b.width * 1.3), b.y, gradientColor);
1685 g.setPaint(gp);
1686 }
1687 }
1688
1689 g.fillPolygon(getPolygon());
1690 }
1691
1692 if (hasVisibleBorder()) {
1693 g.setColor(getPaintBorderColor());
1694 Stroke borderStroke = new BasicStroke(getThickness(), CAP, JOIN);
1695 g.setStroke(borderStroke);
1696 g.drawPolygon(getPolygon());
1697 }
1698
1699 if (hasFormula()) {
1700 g.setColor(getPaintHighlightColor());
1701 Stroke highlightStroke = new BasicStroke(1F, CAP, JOIN);
1702 g.setStroke(highlightStroke);
1703
1704 Point2D.Float start = getEdgePosition(0, true);
1705 Point2D.Float end = getEdgePosition(0, false);
1706 g.drawLine(Math.round(start.x), Math.round(start.y), Math
1707 .round(end.x), Math.round(end.y));
1708 }
1709
1710 if (isHighlighted()) {
1711 g.setColor(getPaintHighlightColor());
1712 Stroke highlightStroke = new BasicStroke(
1713 (float) getHighlightThickness(), CAP, JOIN);
1714 g.setStroke(highlightStroke);
1715 if (HighlightMode.Enclosed.equals(getHighlightMode()))
1716 g.fillPolygon(getPolygon());
1717 else
1718 g.drawPolygon(getPolygon());
1719 }
1720
1721 float y = getY();
1722 Color c = getPaintColor();
1723 if (_alpha > 0)
1724 c = new Color(c.getRed(), c.getGreen(), c.getBlue(), _alpha);
1725
1726 g.setColor(c);
1727
1728 Color selection = getSelectionColor(FrameMouseActions
1729 .getLastMouseButton());
1730
1731 // width -= getX();
1732 // int line = 0;
1733 // boolean tab = false;
1734 synchronized (_textLayouts) {
1735 for (int i = 0; i < _textLayouts.size(); i++) {
1736 TextLayout layout = _textLayouts.get(i);
1737
1738 Point p = getSelectedRange(i);
1739 if (p != null) {
1740 AffineTransform at = new AffineTransform();
1741 AffineTransform orig = g.getTransform();
1742 at.translate(getX() + getJustOffset(layout), y);
1743 g.setTransform(at);
1744
1745 g.setColor(selection);
1746 g.fill(layout.getLogicalHighlightShape(p.x, p.y));
1747
1748 g.setTransform(orig);
1749 g.setColor(c);
1750 }
1751
1752 layout.draw(g, 1 + getX() + getJustOffset(layout), y);
1753
1754 /*
1755 * AffineTransform at = new AffineTransform(); AffineTransform
1756 * orig = g.getTransform(); at.translate(getX() +
1757 * getJustOffset(layout), y); g.setTransform(at);
1758 * g.draw(layout.getLogicalHighlightShape(0,
1759 * layout.getCharacterCount())); g.setTransform(orig); /*
1760 * if(_text.charAt(_lineOffsets.get(line) +
1761 * (layout.getCharacterCount() - 1)) == '\t'){ tab = true; x =
1762 * (int) (getX() + x + 20 + layout.getVisibleAdvance()); }else{
1763 */
1764 y += getLineDrop(layout);
1765 /*
1766 * tab = false; }
1767 *
1768 * line++;
1769 */
1770 }
1771 }
1772
1773 paintLink(g);
1774 }
1775
1776 @Override
1777 protected Rectangle getLinkDrawArea() { // TODO: Revise
1778 return getDrawingArea()[0];
1779 }
1780
1781 /**
1782 * Determines if this text has any text in it.
1783 *
1784 * @return True if this Item has no text in it, false otherwise.
1785 */
1786 public boolean isEmpty() {
1787 if (_text == null || _text.length() == 0)
1788 return true;
1789
1790 return false;
1791 }
1792
1793 @Override
1794 public Text copy() {
1795 Text copy = new Text(getID());
1796 // copy standard item values
1797 Item.DuplicateItem(this, copy);
1798
1799 // copy values specific to text items
1800 copy.setSpacing(getSpacing());
1801 copy.setInitialSpacing(getInitialSpacing());
1802
1803 copy.setWidth(getWidthToSave());
1804 copy.setJustification(getJustification());
1805 copy.setLetterSpacing(getLetterSpacing());
1806 copy.setWordSpacing(getWordSpacing());
1807 copy.setWidth(getWidthToSave());
1808 copy.setFont(getFont());
1809 if (hasFormula()) {
1810 copy.calculate(getFormula());
1811 } else {
1812 copy.setText(_text.toString());
1813 }
1814 copy.setHidden(!isVisible());
1815 return copy;
1816 }
1817
1818 @Override
1819 public float getSize() {
1820 return getPaintFont().getSize2D();
1821 }
1822
1823 /**
1824 * Returns the number of characters in this Text, excluding new lines.
1825 *
1826 * @return The sum of the length of each line of text
1827 */
1828 public int getLength() {
1829 return _text.length();
1830 }
1831
1832 @Override
1833 public void setSize(float size) {
1834 invalidateAll();
1835 // size *= UserSettings.ScaleFactor;
1836 // Dont want to have size set when duplicating a point which has size 0
1837 if (size < 0)
1838 return;
1839
1840 if (size < MINIMUM_FONT_SIZE)
1841 size = MINIMUM_FONT_SIZE;
1842 setFont(getPaintFont().deriveFont(size));
1843 rebuild(true);
1844 invalidateAll();
1845 }
1846
1847 @Override
1848 public void setAnnotation(boolean val) {
1849 float mouseX = DisplayIO.getFloatMouseX();
1850 float mouseY = FrameMouseActions.MouseY;
1851 Point2D.Float newPoint = new Point2D.Float();
1852 if (val) {
1853 // if this is already an annotation, do nothing
1854 if (isAnnotation())
1855 return;
1856 if (!isLineEnd() && _text.length() > 0
1857 && _text.charAt(0) == DEFAULT_BULLET) {
1858 newPoint.setLocation(insertText(""
1859 + (char) KeyEvent.VK_BACK_SPACE, mouseX, mouseY, 1));
1860 if (_text.length() > 0 && _text.charAt(0) == ' ')
1861 newPoint.setLocation(insertText(""
1862 + (char) KeyEvent.VK_BACK_SPACE, newPoint.x,
1863 newPoint.y, 1));
1864 } else {
1865 newPoint.setLocation(insertText("@", mouseX, mouseY, 0));
1866 }
1867 } else {
1868 // if this is not an annotation, do nothing
1869 if (!isAnnotation())
1870 return;
1871 if (!isLineEnd() && _text.charAt(0) == '@') {
1872 newPoint.setLocation(insertText(""
1873 + (char) KeyEvent.VK_BACK_SPACE, mouseX, mouseY, 1));
1874 newPoint.setLocation(insertText(DEFAULT_BULLET_STRING,
1875 newPoint.x, newPoint.y, 0));
1876 } else {
1877 newPoint.setLocation(insertText(""
1878 + (char) KeyEvent.VK_BACK_SPACE, mouseX, mouseY, 1));
1879 }
1880 }
1881 FrameUtils.setLastEdited(this);
1882 rebuild(true);
1883 DisplayIO.setCursorPosition(newPoint.x, newPoint.y, false);
1884 }
1885
1886 /**
1887 *
1888 */
1889 private void insertString(String toInsert, int pos) {
1890 assert (toInsert.length() > 0);
1891
1892 _text.insert(pos, toInsert);
1893
1894 if (toInsert.length() > 1) {
1895 _lineBreaker = null;
1896 }
1897
1898 if (_lineBreaker != null) {
1899 AttributedString inserting = new AttributedString(_text.toString());
1900 inserting.addAttribute(TextAttribute.FONT, getPaintFont());
1901 _lineBreaker.insertChar(inserting.getIterator(), pos);
1902 }
1903 }
1904
1905 private void deleteChar(int pos) {
1906 _text.deleteCharAt(pos);
1907
1908 if (_text.length() == 0) {
1909 if (this.isLineEnd()) {
1910 // Remove and replace with a dot
1911 FrameKeyboardActions.replaceText(this);
1912 DisplayIO.setCursorPosition(this._x, this._y);
1913 }
1914 return;
1915 }
1916
1917 if (_lineBreaker != null) {
1918 AttributedString inserting = new AttributedString(_text.toString());
1919 inserting.addAttribute(TextAttribute.FONT, getPaintFont());
1920 _lineBreaker.deleteChar(inserting.getIterator(), pos);
1921 }
1922
1923 }
1924
1925 @Override
1926 public boolean isAnnotation() {
1927 if (_text != null && _text.length() > 0 && _text.charAt(0) == '@')
1928 return true;
1929
1930 return false;
1931 }
1932
1933 public boolean isSpecialAnnotation() {
1934 assert _text != null;
1935 String s = _text.toString().toLowerCase();
1936 if (s.length() > 0 && s.indexOf("@") == 0) {
1937 if (s.equals("@old") || s.equals("@ao")
1938 || s.equals("@itemtemplate") || s.equals("@parent")
1939 || s.equals("@next") || s.equals("@previous")
1940 || s.equals("@first") || s.equals("@i") || s.equals("@iw")
1941 || s.equals("@f"))
1942 return true;
1943 }
1944
1945 return false;
1946 }
1947
1948 @Override
1949 public Item merge(Item merger, int mouseX, int mouseY) {
1950 if (merger.isLineEnd()) {
1951 // Merging line ends onto non line end text is a no-op
1952 if (!isLineEnd())
1953 return null;
1954
1955 if (merger instanceof Text)
1956 insertText(((Text) merger).getText(), mouseX, mouseY);
1957
1958 // Set the position by moving the cursor before calling this
1959 // method!!
1960
1961 List<Line> lines = new LinkedList<Line>();
1962 lines.addAll(merger.getLines());
1963 for (Line line : lines) {
1964 line.replaceLineEnd(merger, this);
1965 }
1966 merger.delete();
1967 this.setOffset(0, 0);
1968 return null;
1969 }
1970
1971 if (!(merger instanceof Text))
1972 return merger;
1973
1974 Text merge = (Text) merger;
1975
1976 // insertText(merge.getText(), mouseX, mouseY);
1977
1978 // if the item being merged has a link
1979 if (merge.getLink() != null) {
1980 // if this item has a link, keep it on the cursor
1981 if (getLink() != null) {
1982 merge.setText(merge.getLink());
1983 merge.setLink(null);
1984 // return merge;
1985 // TODO get this to return the merged item and attach it to the
1986 // cursor only when the user presses the middle button.
1987 } else
1988 setLink(merge.getLink());
1989 }
1990
1991 return null;
1992 }
1993
1994 /**
1995 * Resets the position of the item to the default position for a title item.
1996 *
1997 */
1998 public void resetTitlePosition() {
1999 setPosition(MARGIN_LEFT, MARGIN_LEFT + getBoundsHeight());
2000 Frame modelFrame = getParentOrCurrentFrame();
2001 if (modelFrame != null) {
2002 setRightMargin(modelFrame.getNameItem().getX() - MARGIN_LEFT, true);
2003 } else {
2004 System.out
2005 .print("Error: text.resetTitlePosition, getParent or currentFrame returned null");
2006 setRightMargin(MARGIN_LEFT, true);
2007 }
2008 }
2009
2010 /**
2011 * Removes the set of characters up to the first space in this text item.
2012 *
2013 * @return the string that was removed.
2014 */
2015 public String stripFirstWord() {
2016 int firstSpace = _text.toString().indexOf(' ');
2017
2018 // if there is only one word just make it blank
2019 if (firstSpace < 0 || firstSpace + 1 >= _text.length()) {
2020 String text = _text.toString();
2021 setText("");
2022 return text;
2023 }
2024
2025 String firstWord = _text.toString().substring(0, firstSpace);
2026 setText(_text.toString().substring(firstSpace).trim());
2027
2028 return firstWord;
2029 }
2030
2031 public String toString() {
2032 String message = "[" + getFirstLine() + "]" + FRAME_NAME_SEPARATOR;
2033
2034 if (getParent() != null)
2035 return message + getParent().getName();
2036 return message + getDateCreated();
2037 }
2038
2039 public Text getTemplateForm() {
2040 Text template = this.copy();
2041 template.setID(-1);
2042 /*
2043 * The template must have text otherwise the bounds height will be
2044 * zero!! This will stop escape drop down from working if there is no
2045 * item template
2046 */
2047 template.setText("@");
2048 return template;
2049 }
2050
2051 @Override
2052 public boolean isNear(int x, int y) {
2053 if (super.isNear(x, y)) {
2054 // TODO check that it is actually near one of the lines of space
2055 // return contains(x, y, getGravity() * 2 + NEAR_DISTANCE);
2056 // at the moment contains ignores gravity when checking the top and
2057 // bottom of text lines... so the cursor must be between two text
2058 // lines
2059 float textY = getY();
2060 float textX = getX();
2061
2062 for (TextLayout text : _textLayouts) {
2063 // check left and right of each box
2064 Rectangle2D textOutline = text.getLogicalHighlightShape(0,
2065 text.getCharacterCount()).getBounds2D();
2066
2067 // check if the cursor is within the top, bottom and within the
2068 // gravity of right
2069 if (y - textY > textOutline.getY() - NEAR_DISTANCE
2070 && y - textY < textOutline.getY()
2071 + textOutline.getHeight() + NEAR_DISTANCE
2072 && x - textX < textOutline.getWidth() + NEAR_DISTANCE)
2073 return true;
2074 textY += getLineDrop(text);
2075 }
2076 }
2077 return false;
2078 }
2079
2080 @Override
2081 public void anchor() {
2082 super.anchor();
2083 // ensure all text items have their selection cleared
2084 clearSelection();
2085 setAlpha(0);
2086 if (isLineEnd())
2087 DisplayIO.setCursor(Item.DEFAULT_CURSOR);
2088
2089 String text = _text.toString().trim();
2090
2091 if (!hasFixedWidth()) {
2092 int frameWidth = FrameGraphics.getMaxFrameSize().width;
2093 /*
2094 * Only change width if it is more than 150 pixels from the right of
2095 * the screen
2096 */
2097 if (!text.contains(" ")) {
2098 Integer width = getWidth();
2099 if (width == null || width < 0)
2100 setWidth(Integer.MIN_VALUE + 1);
2101 } else if (frameWidth - getX() > ADJUST_WIDTH_THRESHOLD) {
2102 justify(false);
2103 // setRightMargin(frameWidth, false);
2104 }
2105 }
2106
2107 // Show the overlay stuff immediately if this is an overlay item
2108 if (hasLink() && (text.startsWith("@ao") || text.startsWith("@o"))) {
2109 FrameKeyboardActions.Refresh();
2110 }
2111 }
2112
2113 public void justify(boolean fixWidth) {
2114
2115 // if autowrap is on, wrapping is done every time we draw
2116 if(ExperimentalFeatures.AutoWrap) {
2117 return;
2118 }
2119
2120 Integer width = FrameGraphics.getMaxFrameSize().width;
2121
2122 // Check if that text item is inside an enclosing rectangle...
2123 // Set its max width accordingly
2124 Polygon enclosure = FrameUtils.getEnlosingPolygon();
2125 if (enclosure != null) {
2126 Rectangle bounds = enclosure.getBounds();
2127 if (bounds.width > 200 && getX() < bounds.width / 3 + bounds.x) {
2128 width = bounds.x + bounds.width;
2129 }
2130 }
2131
2132 if (getWidth() == null)
2133 setRightMargin(width, fixWidth);
2134
2135 // Check for the annotation that restricts the width of text items
2136 // on the frame
2137 String widthString;
2138 if ((widthString = getParentOrCurrentFrame().getAnnotationValue(
2139 "maxwidth")) != null) {
2140 try {
2141 int oldWidth = getWidth();
2142 int maxWidth = Integer.parseInt(widthString);
2143 if (maxWidth < oldWidth)
2144 setWidth(maxWidth);
2145 } catch (NumberFormatException nfe) {
2146 }
2147
2148 }
2149 }
2150
2151 public void resetFrameNamePosition() {
2152 Dimension maxSize = FrameGraphics.getMaxFrameSize();
2153 if (maxSize != null) {
2154 // setMaxWidth(maxSize.width);
2155 setPosition(maxSize.width - getBoundsWidth(), getBoundsHeight());
2156 }
2157 }
2158
2159 @Override
2160 protected int getLinkYOffset() {
2161 if (_textLayouts.size() == 0)
2162 return 0;
2163 return Math.round(-(_textLayouts.get(0).getAscent() / 2));
2164 }
2165
2166 @Override
2167 public String getName() {
2168 return getFirstLine();
2169 }
2170
2171 public static final String TAB_STRING = " ";
2172
2173 public Point2D.Float insertTab(char ch, float mouseX, float mouseY) {
2174 return insertText("" + ch, mouseX, mouseY);
2175 }
2176
2177 public Point2D.Float removeTab(char ch, float mouseX, float mouseY) {
2178 // Insert a space as a flag that it is a backwards tab
2179 return insertText(ch + " ", mouseX, mouseY);
2180 }
2181
2182 public static boolean isBulletChar(char c) {
2183 for (int i = 0; i < BULLETS.length; i++) {
2184 if (BULLETS[i] == c)
2185 return true;
2186 }
2187 return c == '*' || c == '+' || c == '>' || c == '-' || c == 'o';
2188 }
2189
2190 public boolean hasOverlay() {
2191 if (!isAnnotation() || getLink() == null)
2192 return false;
2193 String text = getText().toLowerCase();
2194 // TODO make it so can just check the _overlay variable
2195 // Mike cant remember the reason _overlay var cant be use! opps
2196 if (!text.startsWith("@"))
2197 return false;
2198 return text.startsWith("@o") || text.startsWith("@ao")
2199 || text.startsWith("@v") || text.startsWith("@av");
2200 }
2201
2202 public boolean hasSelection() {
2203 return getSelectionSize() > 0;
2204 }
2205
2206 /**
2207 * Dont save text items that are all white space.
2208 */
2209 @Override
2210 public boolean dontSave() {
2211 String text = getText();
2212 assert (text != null);
2213 return text.trim().length() == 0 || super.dontSave();
2214 }
2215
2216 @Override
2217 public boolean calculate(String formula) {
2218 if (FrameGraphics.isXRayMode())
2219 return false;
2220
2221 super.calculate(formula);
2222 if (isFloating() || formula == null || formula.length() == 0) {
2223 return false;
2224 }
2225 formula = formula.replace(':', '=');
2226
2227 String lowercaseFormula = formula.toLowerCase();
2228 ExpediteeJEP myParser = new ExpediteeJEP();
2229
2230 int nextVarNo = 1;
2231
2232 // Add variables from the containing rectangle if the item being
2233 // calculated is inside the enclosing rectangle
2234 Collection<Item> enclosed = getItemsInSameEnclosure();
2235 for (Item i : enclosed) {
2236 if (i == this)
2237 continue;
2238 if (i instanceof Text && !i.isAnnotation()) {
2239 AttributeValuePair pair = i.getAttributeValuePair();
2240 if (pair.hasPair()) {
2241 try {
2242 double value = pair.getDoubleValue();
2243 myParser.addVariable(pair.getAttribute(), value);
2244 // myParser.addVariable("$" + nextVarNo++, value);
2245 } catch (NumberFormatException nfe) {
2246 continue;
2247 } catch (Exception e) {
2248 e.printStackTrace();
2249 }
2250 } // else {
2251 // Add anonomous vars
2252 try {
2253 double value = pair.getDoubleValue();
2254 if (value != Double.NaN)
2255 myParser.addVariable("$" + nextVarNo++, value);
2256 } catch (NumberFormatException nfe) {
2257 continue;
2258 } catch (Exception e) {
2259 e.printStackTrace();
2260 }
2261 // }
2262 }
2263 }
2264
2265 // Add the variables from this frame
2266 myParser.addVariables(this.getParentOrCurrentFrame());
2267 String linkedFrame = getAbsoluteLink();
2268 // Add the relative frame variable if the item is linked
2269 if (linkedFrame != null) {
2270 Frame frame = FrameIO.LoadFrame(linkedFrame);
2271 myParser.addVariables(frame);
2272 // If the frame is linked add vector variable for the frame
2273 if (lowercaseFormula.contains("$frame")) {
2274 myParser.addVectorVariable(frame.getNonAnnotationItems(true),
2275 "$frame");
2276 }
2277 }
2278 // Add the relative box variable if this item is a line end
2279 if (this.isLineEnd()) {
2280 // if its a line end add the enclosed stuff as an @variable
2281 if (lowercaseFormula.contains("$box")) {
2282 myParser.addVectorVariable(getEnclosedItems(), "$box");
2283 }
2284 }
2285 myParser.resetObserver();
2286 try {
2287 Node node = myParser.parse(formula);
2288 String result = myParser.evaluate(node);
2289 if (result != null) {
2290 this.setText(result);
2291 this.setFormula(formula);
2292
2293 if (!this.hasAction()) {
2294 setActionMark(false);
2295 setAction("extract formula");
2296 }
2297 }
2298 } catch (Throwable e) {
2299 //e.printStackTrace();
2300 String formula2 = getFormula();
2301 this.setText(formula2);
2302 this.setFormula(formula2);
2303 return false;
2304 }
2305
2306 _attributeValuePair = null;
2307
2308 return true;
2309 }
2310
2311 /**
2312 * Gets items which are in the same enclosure as this item.
2313 * In the event more than one enclosure meets this criteria, then
2314 * the one returned is the one with the smallest area.
2315 * TODO: Improve the efficiency of this method
2316 *
2317 * @return
2318 */
2319 public Collection<Item> getItemsInSameEnclosure() {
2320 Collection<Item> sameEnclosure = null;
2321 Collection<Item> seen = new HashSet<Item>();
2322 Frame parent = getParentOrCurrentFrame();
2323 double enclosureArea = Double.MAX_VALUE;
2324 for (Item i : parent.getVisibleItems()) {
2325 /*
2326 * Go through all the enclosures looking for one that includes this
2327 * item
2328 */
2329 if (!seen.contains(i) && i.isEnclosed()) {
2330 seen.addAll(i.getEnclosingDots());
2331 Collection<Item> enclosed = i.getEnclosedItems();
2332 // Check if we have found an enclosure containing this item
2333 // Check it is smaller than any other enclosure found containing
2334 // this item
2335 if (enclosed.contains(this)
2336 && i.getEnclosedArea() < enclosureArea) {
2337 sameEnclosure = enclosed;
2338 }
2339 }
2340 }
2341
2342 if (sameEnclosure == null)
2343 return new LinkedList<Item>();
2344
2345 return sameEnclosure;
2346 }
2347
2348 /**
2349 * Returns true if items of the parent frame should be recalculated when
2350 * this item is modified
2351 */
2352 public boolean recalculateWhenChanged() {
2353 if (/*
2354 * !isAnnotation() &&
2355 */(hasFormula() || isLineEnd()))
2356 return true;
2357 try {
2358 AttributeValuePair avp = getAttributeValuePair();
2359
2360 if (!avp.getDoubleValue().equals(Double.NaN))
2361 return true;
2362 } catch (Exception e) {
2363 e.printStackTrace();
2364 }
2365
2366 return false;
2367 }
2368
2369 public float getLineHeight() {
2370 return getLineDrop(_textLayouts.get(0));
2371 }
2372
2373 @Override
2374 public void setAnchorRight(Float anchor) {
2375 if (!isLineEnd()) {
2376 super.setAnchorRight(anchor);
2377 // Subtract off the link width
2378 if (anchor != null) {
2379 setX(FrameGraphics.getMaxFrameSize().width - anchor
2380 - getBoundsWidth() + getLeftMargin());
2381 }
2382 return;
2383 }
2384 invalidateFill();
2385 invalidateCommonTrait(ItemAppearence.PreMoved);
2386 int oldX = getX();
2387 if (anchor != null) {
2388 float deltaX = FrameGraphics.getMaxFrameSize().width - anchor
2389 - getBoundsWidth() + getLeftMargin() - oldX;
2390 anchorConnected(deltaX, null);
2391 }
2392 this._anchorRight = anchor;
2393 invalidateCommonTrait(ItemAppearence.PostMoved);
2394 invalidateFill();
2395 }
2396
2397 @Override
2398 public void setAnchorBottom(Float anchor) {
2399 if (!isLineEnd()) {
2400 super.setAnchorBottom(anchor);
2401 return;
2402 }
2403 invalidateFill();
2404 invalidateCommonTrait(ItemAppearence.PreMoved);
2405 int oldY = getY();
2406 if (anchor != null) {
2407 float deltaY = FrameGraphics.getMaxFrameSize().height - anchor
2408 - oldY;
2409 anchorConnected(null, deltaY);
2410 }
2411 this._anchorBottom = anchor;
2412 invalidateCommonTrait(ItemAppearence.PostMoved);
2413 invalidateFill();
2414 }
2415
2416 @Override
2417 public void scale(Float scale, int originX, int originY) {
2418 setSize(getSize() * scale);
2419
2420 Integer width = getWidth();
2421 if (width != null) {
2422 setWidth(Math.round(width * scale));
2423 }
2424
2425 super.scale(scale, originX, originY);
2426 rebuild(true);
2427 }
2428
2429 /*
2430 * Returns the SIMPLE statement contained by this text item.
2431 *
2432 */
2433 public String getStatement() {
2434 return getText().split("\\s+")[0];
2435 }
2436
2437 public boolean getAutoWrap() {
2438 return _autoWrap;
2439 }
2440
2441 // workaround since true is the default value and would not be displayed normally
2442 public String getAutoWrapToSave() {
2443 if(!_autoWrap) {
2444 return null;
2445 }
2446 return "true";
2447 }
2448
2449 public void setAutoWrap(boolean autoWrap) {
2450 _autoWrap = autoWrap;
2451 }
2452}
Note: See TracBrowser for help on using the repository browser.