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

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

Added some math stuff... able to group numbers inside a rectangle

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