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

Last change on this file since 278 was 278, checked in by ra33, 16 years ago

Fixed some bugs...
AbstractCharts can not be copied, resized, etc

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