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

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

Fixed up search stuff... to adjust to getting params off the cursor

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