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

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