source: trunk/org/expeditee/items/Text.java@ 4

Last change on this file since 4 was 4, checked in by davidb, 16 years ago

Starting source code to Expeditee

File size: 38.3 KB
Line 
1package org.expeditee.items;
2
3import java.awt.BasicStroke;
4import java.awt.Color;
5import java.awt.Font;
6import java.awt.Graphics2D;
7import java.awt.Point;
8import java.awt.Polygon;
9import java.awt.Rectangle;
10import java.awt.Stroke;
11import java.awt.event.KeyEvent;
12import java.awt.event.MouseEvent;
13import java.awt.font.FontRenderContext;
14import java.awt.font.LineBreakMeasurer;
15import java.awt.font.TextAttribute;
16import java.awt.font.TextHitInfo;
17import java.awt.font.TextLayout;
18import java.awt.geom.AffineTransform;
19import java.awt.geom.Rectangle2D;
20import java.text.AttributedString;
21import java.util.LinkedList;
22import java.util.List;
23
24import org.expeditee.gui.FrameMouseActions;
25
26/**
27 * Represents text displayed on the screen, which may include multiple lines.
28 * All standard text properties can be set (font, style, size).
29 *
30 * @author jdm18
31 *
32 */
33public class Text extends Item {
34
35 public static final String FRAME_NAME_SEPARATOR = " on frame ";
36
37 /**
38 * The default font used to display text items if no font is specified.
39 */
40 public static final String DEFAULT_FONT = "Serif-Plain-18";
41
42 public static final Color DEFAULT_COLOR = Color.BLACK;
43
44 public static final int MINIMUM_RANGED_CHARS = 2;
45
46 public static final int LEFT_MARGIN = 13;
47
48 public static final int UP = 0;
49
50 public static final int DOWN = 1;
51
52 public static final int LEFT = 2;
53
54 public static final int RIGHT = 3;
55
56 public static final int HOME = 4;
57
58 public static final int END = 5;
59
60 private int _maxWidth = -1;
61
62 private int _justification = Item.JUSTIFICATION_NONE;
63
64 private int _spacing = -1;
65
66 private int _word_spacing = -1;
67
68 private int _initial_spacing = -1;
69
70 private int _letter_spacing = -1;
71
72 // used during ranging out
73 private int _selectionStart = -1;
74
75 private int _selectionEnd = -1;
76
77 // text is broken up into lines
78 private StringBuffer _text = new StringBuffer();
79
80 private List<TextLayout> _textLayouts = new LinkedList<TextLayout>();
81
82 private List<Integer> _lineOffsets = new LinkedList<Integer>();
83
84 private LineBreakMeasurer _lineBreaker = null;
85
86 // The font to display this text in
87 private Font _font;
88
89 // the rectangle surrounding this text item
90 private Polygon _poly = null;
91
92 /**
93 * Creates a new Text Item with the given ID and text.
94 *
95 * @param id
96 * The id of this item
97 * @param text
98 * The text to use in this item
99 */
100 public Text(int id, String text) {
101 super();
102 _text.append(text);
103 rebuild(false);
104
105 setID(id);
106 }
107
108 /**
109 * Creates a new Text Item with the given ID
110 *
111 * @param id
112 * The ID to of this item
113 */
114 public Text(int id) {
115 super();
116 setID(id);
117 }
118
119 /**
120 * Sets the maximum width of this Text item when justifcation is used.
121 * passing in 0 or -1 means there is no maximum width
122 *
123 * @param width
124 * The maximum width of this item when justification is applied
125 * to it.
126 */
127 @Override
128 public void setWidth(int width) {
129 // 0 is the default
130 if (width == 0)
131 width = -1;
132
133 _maxWidth = width;
134 }
135
136 /**
137 * Returns the maximum width of this Text item when justifcation is used.
138 * Note the returned value may be -1, which indicates that there is no
139 * maximum size set
140 *
141 * @return The maximum width of this Text item when justification is used
142 */
143 @Override
144 public int getWidth() {
145 return _maxWidth;
146 }
147
148 /**
149 * Sets the justification of this Text item. The given integer should
150 * correspond to one of the JUSTIFICATION constants defined in Item
151 *
152 * @param just
153 * The justification to apply to this Text item
154 */
155 public void setJustification(int just) {
156 _justification = just;
157 rejustify();
158 }
159
160 /**
161 * Returns the current justification of this Text item, the default value is
162 * Item.JUSTIFICATION_NONE
163 *
164 * @return The justification of this Text item
165 */
166 public int getJustification() {
167 return _justification;
168 }
169
170 private int getJustOffset(TextLayout layout) {
171 if (getWidth() < 0)
172 return 0;
173
174 if (getJustification() == Item.JUSTIFICATION_CENTER)
175 return (int) ((getWidth() - layout.getAdvance()) / 2);
176 else if (getJustification() == Item.JUSTIFICATION_RIGHT)
177 return (int) (getWidth() - layout.getAdvance());
178
179 return 0;
180 }
181
182 /**
183 * Sets the text displayed on the screen to the given String
184 *
185 * @param text
186 * The String to display on the screen when drawing this Item.
187 */
188 public void setText(String text) {
189 _text = new StringBuffer(text);
190 rebuild(false);
191 }
192
193 public void setTextList(List<String> text) {
194 if (text == null || text.size() <= 0)
195 return;
196
197 setText(text.get(0));
198 for (int i = 1; i < text.size(); i++) {
199 appendText("\n");
200 appendText(text.get(i));
201 }
202 rebuild(false);
203 }
204
205 public void setAttributeValue(String value) {
206
207 }
208
209 /**
210 * Inserts the given String at the start of the first line of this Text
211 * Item.
212 *
213 * @param text
214 * The String to insert.
215 */
216 public void prependText(String text) {
217 _text.insert(0, text);
218 rebuild(false);
219 }
220
221 /**
222 * If the first line of text starts with the given String, then it is
223 * removed otherwise no action is taken.
224 *
225 * @param text
226 * The String to remove from the first line of Text
227 */
228 public void removeText(String text) {
229 if (_text.length() > 0 && _text.indexOf(text) == 0)
230 _text.delete(0, text.length());
231
232 }
233
234 public void removeEndText(String textToRemove) {
235 int length = _text.length();
236 if (length > 0) {
237 int pos = _text.indexOf(textToRemove);
238 int textToRemoveLength = textToRemove.length();
239 if (pos + textToRemoveLength == length) {
240 _text.delete(pos, length);
241 }
242 }
243 }
244
245 /**
246 * Appends the given String to any text already present in this Item
247 *
248 * @param text
249 * The String to append.
250 */
251 public void appendText(String text) {
252 _text.append(text);
253 rebuild(false);
254 }
255
256 public void appendLine(String text) {
257 if (text == null)
258 text = "";
259
260 if (_text.length() > 0)
261 _text.append('\n');
262
263 _text.append(text);
264 rebuild(false);
265 }
266
267 /**
268 * Tests if the first line of this Text starts with the given String.
269 *
270 * @param text
271 * The prefix to check for
272 * @return True if the first line starts with the given String, False
273 * otherwise.
274 */
275 public boolean startsWith(String text) {
276 return startsWith(text, true);
277 }
278
279 public boolean startsWith(String text, boolean ignoreCase) {
280 if (text == null || _text == null || _text.length() < 1)
281 return false;
282
283 if (ignoreCase)
284 return _text.toString().toLowerCase()
285 .startsWith(text.toLowerCase());
286 else
287 return _text.indexOf(text) == 0;
288 }
289
290 /**
291 * Inserts a character into the Text of this Item.
292 *
293 * @param ch
294 * The character insert.
295 * @param mouseX
296 * The X position to insert the Strings at.
297 * @param mouseY
298 * The Y position to insert the Strings at.
299 */
300 public Point insertChar(char ch, int mouseX, int mouseY) {
301 if (ch != '\t')
302 return insertText("" + ch, mouseX, mouseY);
303
304 return new Point(mouseX, mouseY);
305 }
306
307 public Point getEndLinePosition(int mouseY) {
308 return getEdgePosition(getLinePosition(mouseY), false);
309 }
310
311 public Point getStartLinePosition(int mouseY) {
312 return getEdgePosition(getLinePosition(mouseY), true);
313 }
314
315 public Point getEndParagraphPosition() {
316 return getEdgePosition(_textLayouts.size() - 1, false);
317 }
318
319 private Point getEdgePosition(int line, boolean start) {
320 // if there is no text yet, or the line is invalid
321 if (_text == null || _text.length() == 0 || line < 0
322 || line > _textLayouts.size() - 1)
323 return new Point(getX(), getY());
324
325 TextLayout last = _textLayouts.get(line);
326 TextHitInfo hit;
327 if (start)
328 hit = last.getNextLeftHit(1);
329 else
330 hit = last.getNextRightHit(last.getCharacterCount() - 1);
331
332 // move the cursor to the new location
333 float[] caret = last.getCaretInfo(hit);
334 int y = getLineDrop(last) * line;
335
336 int x = ((int) (getX() + caret[0]) + getJustOffset(last));
337 x = Math
338 .min(
339 x,
340 (getX() - Item.MARGIN_RIGHT - (2 * getGravity()) + getBoundsWidth()));
341 return new Point(x, getY() + /* DisplayIO.getOffset() + */y
342 + (int) caret[1]);
343 }
344
345 public void setSelectionStart(int mouseX, int mouseY) {
346 // determine what line is being pointed to
347 int line = getLinePosition(mouseY);
348
349 // get the character being pointed to
350 TextHitInfo hit = getCharPosition(line, mouseX);
351 _selectionStart = hit.getInsertionIndex() + _lineOffsets.get(line);
352 }
353
354 public void setSelectionEnd(int mouseX, int mouseY) {
355 // determine what line is being pointed to
356 int line = getLinePosition(mouseY);
357
358 // get the character being pointed to
359 TextHitInfo hit = getCharPosition(line, mouseX);
360 _selectionEnd = hit.getInsertionIndex() + _lineOffsets.get(line);
361 }
362
363 public void clearSelection() {
364 _selectionStart = -1;
365 _selectionEnd = -1;
366 }
367
368 public void clearSelectionEnd() {
369 _selectionEnd = -1;
370 }
371
372 public String copySelectedText() {
373 if (_selectionStart < 0 || _selectionEnd < 0)
374 return null;
375 else if (_selectionEnd > _text.length())
376 _selectionEnd = _text.length();
377
378 return _text.substring(Math.min(_selectionStart, _selectionEnd), Math
379 .max(_selectionStart, _selectionEnd));
380 }
381
382 public String cutSelectedText() {
383 if (_selectionStart < 0 || _selectionEnd < 0)
384 return null;
385 else if (_selectionEnd > _text.length())
386 _selectionEnd = _text.length();
387
388 int left = Math.min(_selectionStart, _selectionEnd);
389 int right = Math.max(_selectionStart, _selectionEnd);
390
391 String s = _text.substring(left, right);
392 for (int i = right; i > left; i--) {
393 _text.delete(i - 1, i);
394 if (_text.length() > 0) {
395 AttributedString inserting = new AttributedString(_text
396 .toString());
397 inserting.addAttribute(TextAttribute.FONT, getPaintFont());
398 _lineBreaker.deleteChar(inserting.getIterator(), i - 1);
399 }
400
401 rejustify();
402 }
403
404 return s;
405 }
406
407 public int getSelectedSize() {
408 if (_selectionEnd < 0 || _selectionStart < 0)
409 return 0;
410
411 //System.out.println(_selectionStart + ":" + _selectionEnd);
412
413 return Math.abs(_selectionEnd - _selectionStart);
414 }
415
416 /**
417 * Inserts the given String into the Text at the position given by the
418 * mouseX and mouseY coordinates
419 *
420 * @param text
421 * The String to insert into this Text.
422 * @param mouseX
423 * The X position to insert the String
424 * @param mouseY
425 * The Y position to insert the String
426 * @return The new location that the mouse cursor should be moved to
427 */
428 public java.awt.Point insertText(String text, int mouseX, int mouseY) {
429 TextHitInfo hit;
430 TextLayout current = null;
431 int line;
432
433 // check for empty string
434 if (text == null || text.length() == 0)
435 return new Point(mouseX, mouseY);
436
437 // if there is no text yet
438 if (_text == null || _text.length() == 0) {
439 if (text.equals("@")) {
440
441 }
442
443 _text = new StringBuffer().append(text);
444 // create the linebreaker and layouts
445 rebuild(true);
446
447 current = _textLayouts.get(_textLayouts.size() - 1);
448 hit = current.getNextRightHit(0);
449 line = 0;
450
451 // otherwise, we are inserting text
452 } else {
453 // determine what line is being pointed to
454 line = getLinePosition(mouseY);
455
456 // get the character being pointed to
457 hit = getCharPosition(line, mouseX);
458
459 int pos = hit.getInsertionIndex() + _lineOffsets.get(line);
460
461 if (line > 0 && hit.getInsertionIndex() == 0)
462 pos++;
463
464 // compensate for newline character
465 // if(line > 0 && hit.getInsertionIndex() == 0)
466 // if(pos < (_text.length() - 1) && _text.charAt(pos) == '\n')
467 // pos++;
468
469 // if this is a backspace key
470 if (text.charAt(0) == KeyEvent.VK_BACK_SPACE) {
471 if (pos > 0) {
472 _text.delete(pos - 1, pos);
473
474 if (_text.length() > 0) {
475 AttributedString inserting = new AttributedString(_text
476 .toString());
477 inserting.addAttribute(TextAttribute.FONT,
478 getPaintFont());
479 _lineBreaker.deleteChar(inserting.getIterator(),
480 pos - 1);
481 }
482
483 pos--;
484 }
485 // if this is a delete key
486 } else if (text.charAt(0) == KeyEvent.VK_DELETE) {
487 if (pos < _text.length()) {
488 _text.delete(pos, pos + 1);
489
490 if (_text.length() > 0) {
491 AttributedString inserting = new AttributedString(_text
492 .toString());
493 inserting.addAttribute(TextAttribute.FONT,
494 getPaintFont());
495 _lineBreaker.deleteChar(inserting.getIterator(), pos);
496 }
497 }
498 // this is a normal insert
499 } else {
500 _text.insert(pos, text);
501 if (text.length() < 2) {
502 AttributedString inserting = new AttributedString(_text
503 .toString());
504 inserting.addAttribute(TextAttribute.FONT, getPaintFont());
505 _lineBreaker.insertChar(inserting.getIterator(), pos);
506 } else
507 _lineBreaker = null;
508
509 pos += text.length();
510 }
511
512 int newLine = line;
513
514 // if a rebuild is required
515 if (_lineBreaker == null)
516 rebuild(true);
517 else
518 rejustify();
519
520 // determine the new position the cursor should have
521 for (int i = 1; i < _lineOffsets.size(); i++) {
522 if (_lineOffsets.get(i) >= pos) {
523 newLine = i - 1;
524 break;
525 }
526 }
527
528 current = _textLayouts.get(newLine);
529 pos = pos - _lineOffsets.get(newLine);
530
531 if (newLine == line) {
532 if (pos > 0)
533 hit = current.getNextRightHit(pos - 1);
534 else
535 hit = current.getNextLeftHit(1);
536 } else if (newLine < line) {
537 hit = current.getNextRightHit(pos - 1);
538 } else {
539 hit = current.getNextRightHit(pos - 1);
540 }
541
542 line = newLine;
543 }
544
545 // move the cursor to the new location
546 float[] caret = current.getCaretInfo(hit);
547 int y = getLineDrop(current) * line;
548
549 int x = ((int) (getX() + caret[0]) + getJustOffset(current));
550 x = Math
551 .min(
552 x,
553 (getX() - Item.MARGIN_RIGHT - (2 * getGravity()) + getBoundsWidth()));
554 return new Point(x, getY() /* + DisplayIO.getOffset() */+ y
555 + (int) caret[1]);
556 }
557
558 public Point moveCursor(int direction, int mouseX, int mouseY) {
559 // check for home or end keys
560 if (direction == HOME)
561 return getStartLinePosition(mouseY);
562
563 if (direction == END)
564 return getEndLinePosition(mouseY);
565
566 TextHitInfo hit;
567 TextLayout current;
568 int line;
569
570 // if there is no text yet
571 if (_text == null || _text.length() == 0) {
572 return new Point(mouseX, mouseY);
573 // otherwise, move the cursor
574 } else {
575 // determine the line of text to check
576 line = getLinePosition(mouseY);
577 if (line < 0)
578 line = _textLayouts.size() - 1;
579
580 // if the cursor is moving up or down, change the line
581 if (direction == UP)
582 line = Math.max(line - 1, 0);
583 else if (direction == DOWN)
584 line = Math.min(line + 1, _textLayouts.size() - 1);
585
586 hit = getCharPosition(line, mouseX);
587
588 if (direction == LEFT) {
589 if (hit.getInsertionIndex() > 0)
590 hit = _textLayouts.get(line).getNextLeftHit(hit);
591
592 if (line > 0
593 && _text.charAt(hit.getInsertionIndex()
594 + _lineOffsets.get(line)) == '\n') {
595 line--;
596 hit = _textLayouts.get(line).getNextRightHit(
597 _textLayouts.get(line).getCharacterCount() - 1);
598 }
599 } else if (direction == RIGHT) {
600 if (hit.getInsertionIndex() < _textLayouts.get(line)
601 .getCharacterCount()) {
602 hit = _textLayouts.get(line).getNextRightHit(hit);
603 while (hit.getCharIndex() > 0
604 && hit.getCharIndex() < _textLayouts.get(line)
605 .getCharacterCount()
606 && _text.charAt(_lineOffsets.get(line)
607 + hit.getCharIndex() - 1) == '\t')
608 hit = _textLayouts.get(line).getNextRightHit(hit);
609 } else if (line < _textLayouts.size() - 1) {
610 line++;
611 hit = _textLayouts.get(line).getNextLeftHit(1);
612 }
613 }
614
615 current = _textLayouts.get(line);
616 }
617
618 // move the cursor to the new location
619 float[] caret = current.getCaretInfo(hit);
620 int y = getLineDrop(current) * line;
621
622 return new Point((int) (getX() + caret[0]) + getJustOffset(current),
623 getY() /* + DisplayIO.getOffset() */+ y + (int) caret[1]);
624 }
625
626 /**
627 * Iterates through the given line string and returns the position of the
628 * character being pointed at by the mouse.
629 *
630 * @param line
631 * The index of the _text array of the String to be searched.
632 * @param mouseX
633 * The X coordinate of the mouse
634 * @return The position in the string of the character being pointed at.
635 */
636 private TextHitInfo getCharPosition(int line, int mouseX) {
637 if (line < 0 || line >= _textLayouts.size())
638 return null;
639
640 TextLayout layout = _textLayouts.get(line);
641 mouseX += getOffset().x;
642 mouseX -= getJustOffset(layout);
643
644 return layout.hitTestChar(mouseX - getX(), 0);
645 }
646
647 private int getLinePosition(int mouseY) {
648 mouseY += getOffset().y;
649
650 int y = getY();
651
652 for (TextLayout text : _textLayouts) {
653 // calculate X to ensure it is in the shape
654 Rectangle bounds = text.getLogicalHighlightShape(0,
655 text.getCharacterCount()).getBounds();
656
657 if (bounds.getWidth() < 1)
658 bounds = new Rectangle((int) bounds.getMinX(), (int) bounds
659 .getMinY(), 10, (int) bounds.getHeight());
660
661 double x = bounds.getCenterX();
662
663 // if(text.getLogicalHighlightShape(0,
664 // text.getCharacterCount()).contains(text.getBounds().getMinX()
665 // + 10, mouseY - getY() - (y - getY())))
666 if (bounds.contains(x, mouseY - getY() - (y - getY())))
667 return _textLayouts.indexOf(text);
668
669 // check if the cursor is between lines
670 if (mouseY - getY() - (y - getY()) < bounds.getMinY())
671 return Math.max(0, _textLayouts.indexOf(text) - 1);
672
673 y += getLineDrop(text);
674 }
675
676 return _textLayouts.size() - 1;
677 }
678
679 /**
680 * Sets the Font that this text will be displayed with on the screen.
681 *
682 * @param font
683 * The Font to display the Text of this Item in.
684 */
685 public void setFont(Font font) {
686 // all decoding occurs in the Utils class
687 _font = font;
688
689 // rejustify();
690 rebuild(false);
691 }
692
693 /**
694 * Returns the Font that this Text is currently using when painting to the
695 * screen
696 *
697 * @return The Font used to display this Text on the screen.
698 */
699 public Font getFont() {
700 return _font;
701 }
702
703 public Font getPaintFont() {
704 if (getFont() == null)
705 return Font.decode(DEFAULT_FONT);
706
707 return getFont();
708 }
709
710 public String getFamily() {
711 return getPaintFont().getFamily();
712 }
713
714 public void setFamily(String newFamily) {
715 String toDecode = newFamily + "-" + getFontStyle() + "-" + getSize();
716 setFont(Font.decode(toDecode));
717 }
718
719 public String getFontStyle() {
720 Font f = getPaintFont();
721 String s = "";
722
723 if (f.isPlain())
724 s += "Plain";
725
726 if (f.isBold())
727 s += "Bold";
728
729 if (f.isItalic())
730 s += "Italic";
731
732 return s;
733 }
734
735 public static final String[] FONT_WHEEL = { "sansserif", "monospaced", "serif", "dialog", "dialoginput" };
736 public static final char[] FONT_CHARS = { 's', 'm', 't', 'd', 'i' };
737
738 public void toggleFontFamily() {
739 String fontFamily = getFamily().toLowerCase();
740 //set it to the first font by default
741 setFamily(FONT_WHEEL[0]);
742
743 for (int i = 0; i < FONT_WHEEL.length - 3; i++) {
744 if(fontFamily.equals(FONT_WHEEL[i])){
745 setFamily(FONT_WHEEL[i+1]);
746 break;
747 }
748 }
749 }
750
751 public void toggleFontStyle() {
752 Font currentFont = getPaintFont();
753 if (currentFont.isPlain())
754 setFont(currentFont.deriveFont(Font.BOLD));
755 else if (currentFont.isBold() && currentFont.isItalic())
756 setFont(currentFont.deriveFont(Font.PLAIN));
757 else if (currentFont.isBold())
758 setFont(currentFont.deriveFont(Font.ITALIC));
759 else
760 setFont(currentFont.deriveFont(Font.ITALIC + Font.BOLD));
761 }
762
763 public void setFontStyle(String newFace) {
764 if (newFace == null || newFace.trim().length() == 0) {
765 setFont(getPaintFont().deriveFont(Font.PLAIN));
766 return;
767 }
768
769 newFace = newFace.toLowerCase().trim();
770
771 if (newFace.equals("plain")) {
772 setFont(getPaintFont().deriveFont(Font.PLAIN));
773 } else if (newFace.equals("bold")) {
774 setFont(getPaintFont().deriveFont(Font.BOLD));
775 } else if (newFace.equals("italic")) {
776 setFont(getPaintFont().deriveFont(Font.ITALIC));
777 } else if (newFace.contains("bold") && newFace.contains("italic")) {
778 setFont(getPaintFont().deriveFont(Font.BOLD + Font.ITALIC));
779 }
780
781 }
782
783 /**
784 * Returns a String array of this Text object's text, split up into separate
785 * lines.
786 *
787 * @return The String array with one element per line of text in this Item.
788 */
789 public List<String> getText() {
790 if (_text == null)
791 return null;
792 try {
793 List<String> list = new LinkedList<String>();
794
795 int last = 0;
796 for (int offset : _lineOffsets) {
797 if (offset != last) {
798 list
799 .add(_text.substring(last, offset).replaceAll("\n",
800 ""));
801 }
802 last = offset;
803 }
804
805 return list;
806 } catch (Exception e) {
807 System.out.println(e.getMessage());
808 return null;
809 }
810 }
811
812 public String getTextNoList() {
813 return _text.toString();
814 }
815
816 /**
817 * Returns the first line of text in this Text Item
818 *
819 * @return The first line of Text
820 */
821 public String getFirstLine() {
822 if (_text == null || _text.length() == 0)
823 return null;
824
825 if (_text.indexOf("\n") < 0)
826 return _text.toString();
827
828 return _text.substring(0, _text.indexOf("\n"));
829 }
830
831 /**
832 * Sets the inter-line spacing (in pixels) of this text.
833 *
834 * @param spacing
835 * The number of pixels to allow between each line
836 */
837 public void setSpacing(int spacing) {
838 _spacing = spacing;
839 updatePolygon();
840 }
841
842 /**
843 * Returns the inter-line spacing (in pixels) of this Text.
844 *
845 * @return The spacing (inter-line) in pixels of this Text.
846 */
847 public int getSpacing() {
848 return _spacing;
849 }
850
851 private int getLineDrop(TextLayout layout) {
852 if (getSpacing() < 0)
853 return (int) (layout.getAscent() + layout.getDescent() + layout
854 .getLeading());
855
856 return (int) (layout.getAscent() + layout.getDescent() + getSpacing());
857 }
858
859 public void setWordSpacing(int spacing) {
860 _word_spacing = spacing;
861 }
862
863 public int getWordSpacing() {
864 return _word_spacing;
865 }
866
867 public void setLetterSpacing(int spacing) {
868 _letter_spacing = spacing;
869 }
870
871 public int getLetterSpacing() {
872 return _letter_spacing;
873 }
874
875 public void setInitialSpacing(int spacing) {
876 _initial_spacing = spacing;
877 }
878
879 public int getInitialSpacing() {
880 return _initial_spacing;
881 }
882
883 @Override
884 public boolean contains(int mouseX, int mouseY) {
885 mouseX += getOffset().x;
886 mouseY += getOffset().y;
887
888 int textY = getY();
889 int textX = getX();
890 int gravity = getGravity() * 2;
891
892 Rectangle outline = getPolygon().getBounds();
893
894 // Check if its outside the top and left and bottom bounds
895 if (outline.x - mouseX > gravity || outline.y - mouseY > gravity
896 || mouseY - (outline.y + outline.height) > gravity
897 || mouseX - (outline.x + outline.width) > gravity) {
898 return false;
899 }
900
901 // check if the cursor is on the left border of the bounding box
902 if (Math.abs(mouseX - LEFT_MARGIN - outline.x) <= gravity) {
903 return true;
904 }
905
906 for (TextLayout text : _textLayouts) {
907 // check left and right of each box
908 Rectangle textOutline = text.getLogicalHighlightShape(0,
909 text.getCharacterCount()).getBounds();
910
911 // check if the cursor is within the top, bottom and within the
912 // gravity of right
913 if (mouseY - textY > textOutline.y
914 && mouseY - textY < textOutline.y + textOutline.height
915 && mouseX - textX < textOutline.width + gravity)
916 return true;
917 textY += getLineDrop(text);
918 }
919
920 return false;
921 }
922
923 /**
924 * Updates the Polygon (rectangle) that surrounds this Text on the screen.
925 */
926 protected void updatePolygon() {
927 // if there is no text, there is nothing to do
928 if (_text == null)
929 return;
930
931 _poly = new Polygon();
932
933 if (_textLayouts.size() < 1)
934 return;
935
936 int minX = Integer.MAX_VALUE;
937 int maxX = Integer.MIN_VALUE;
938
939 int minY = Integer.MAX_VALUE;
940 int maxY = Integer.MIN_VALUE;
941
942 float y = -1;
943
944 synchronized (_textLayouts) {
945 for (TextLayout layout : _textLayouts) {
946 Rectangle2D bounds = layout.getLogicalHighlightShape(0,
947 layout.getCharacterCount()).getBounds2D();
948
949 if (y < 0)
950 y = 0;
951 else
952 y += getLineDrop(layout);
953
954 maxX = Math.max(maxX, (int) bounds.getMaxX());
955 minX = Math.min(minX, (int) bounds.getMinX());
956 maxY = Math.max(maxY, (int) (bounds.getMaxY() + y));
957 minY = Math.min(minY, (int) (bounds.getMinY() + y));
958 }
959 }
960
961 minX -= Item.MARGIN_RIGHT;
962
963 if (getWidth() > 0)
964 maxX = getWidth();
965
966 _poly.addPoint(minX - getGravity(), minY - getGravity());
967 _poly.addPoint(maxX + getGravity(), minY - getGravity());
968 _poly.addPoint(maxX + getGravity(), maxY + getGravity());
969 _poly.addPoint(minX - getGravity(), maxY + getGravity());
970
971 // _poly.addPoint(minX, minY);
972 // _poly.addPoint(maxX, minY);
973 // _poly.addPoint(maxX, maxY);
974 // _poly.addPoint(minX, maxY);
975 }
976
977 // TODO it seems like this method has some exponencial processing which
978 // makes items copy really slowly when there are lots of lines of text!
979 // This needs to be fixed!!
980 private void rebuild(boolean limitWidth) {
981 // if there is no text, there is nothing to do
982 if (_text == null || _text.length() == 0)
983 return;
984
985 AttributedString paragraphText = new AttributedString(_text.toString());
986 paragraphText.addAttribute(TextAttribute.FONT, getPaintFont());
987 _lineBreaker = new LineBreakMeasurer(paragraphText.getIterator(),
988 new FontRenderContext(null, true, true));
989
990 float width = Float.MAX_VALUE;
991
992 if (limitWidth) {
993 if (getWidth() > 0)
994 width = getWidth();
995 else if (getMaxSize() != null)
996 width = Math.max(50, getMaxSize().width - getX()
997 - Item.MARGIN_RIGHT);
998 }
999
1000 _textLayouts.clear();
1001 _lineOffsets.clear();
1002 // the first line always has a 0 offset
1003 _lineOffsets.add(0);
1004
1005 TextLayout layout;
1006 _lineBreaker.setPosition(0);
1007
1008 // --- Get the output of the LineBreakMeasurer and store it in a
1009 while ((layout = _lineBreaker.nextLayout(width)) != null) {
1010 // for some reason lineBreaker will not break on newline
1011 // characters
1012 // so they have to be check manually
1013 int start = _lineOffsets.get(_lineOffsets.size() - 1);
1014
1015 // check through the current line for newline characters
1016 for (int i = start + 1; i < _text.length(); i++) {
1017 if (_text.charAt(i) == '\n') {// || c == '\t'){
1018 _lineBreaker.setPosition(start);
1019 layout = _lineBreaker.nextLayout(width, i, false);
1020 break;
1021 }
1022 }
1023
1024 _lineOffsets.add(_lineBreaker.getPosition());
1025
1026 if (getWidth() > 0 && getJustification() == Item.JUSTIFICATION_FULL
1027 && _lineBreaker.getPosition() < _text.length())
1028 layout = layout.getJustifiedLayout(width);
1029
1030 _textLayouts.add(layout);
1031 }
1032
1033 updatePolygon();
1034 }
1035
1036 private void rejustify() {
1037 // if there is no text, there is nothing to do
1038 if (_text == null || _text.length() == 0)
1039 return;
1040
1041 // only recreate linebreaker if necessary
1042 if (_lineBreaker == null) {
1043 rebuild(true);
1044 return;
1045
1046 /*
1047 * AttributedString paragraphText = new
1048 * AttributedString(_text.toString());
1049 * paragraphText.addAttribute(TextAttribute.FONT, getPaintFont());
1050 * _lineBreaker = new LineBreakMeasurer(paragraphText.getIterator(),
1051 * new FontRenderContext(null, true, true));
1052 */
1053 }
1054
1055 float width = Float.MAX_VALUE;
1056 if (getWidth() > 0)
1057 width = getWidth();
1058 else if (getMaxSize() != null)
1059 width = Math.max(50, getMaxSize().width - getX()
1060 - Item.MARGIN_RIGHT);
1061
1062 _textLayouts.clear();
1063 _lineOffsets.clear();
1064 // the first line always has a 0 offset
1065 _lineOffsets.add(0);
1066
1067 TextLayout layout;
1068 _lineBreaker.setPosition(0);
1069
1070 // --- Get the output of the LineBreakMeasurer and store it in a
1071 while ((layout = _lineBreaker.nextLayout(width)) != null) {
1072 // for some reason lineBreaker will not break on newline
1073 // characters
1074 // so they have to be check manually
1075 int start = _lineOffsets.get(_lineOffsets.size() - 1);
1076
1077 // check through the current line for newline characters
1078 for (int i = start + 1; i < _text.length(); i++) {
1079 if (_text.charAt(i) == '\n') {// || c == '\t'){
1080 _lineBreaker.setPosition(start);
1081 layout = _lineBreaker.nextLayout(width, i, false);
1082 break;
1083 }
1084 }
1085
1086 _lineOffsets.add(_lineBreaker.getPosition());
1087
1088 if (getWidth() > 0 && getJustification() == Item.JUSTIFICATION_FULL
1089 && _lineBreaker.getPosition() < _text.length())
1090 layout = layout.getJustifiedLayout(width);
1091
1092 _textLayouts.add(layout);
1093 }
1094
1095 updatePolygon();
1096 }
1097
1098 private int _alpha = -1;
1099
1100 public void setAlpha(int alpha) {
1101 _alpha = alpha;
1102 }
1103
1104 private Point getSelectedRange(int line) {
1105 if (_selectionEnd >= _text.length()) {
1106 _selectionEnd = _text.length();
1107 }
1108
1109 if (_selectionStart < 0)
1110 _selectionStart = 0;
1111
1112 if (_selectionStart < 0 || _selectionEnd < 0)
1113 return null;
1114
1115 int selectionLeft = Math.min(_selectionStart, _selectionEnd);
1116 int selectionRight = Math.max(_selectionStart, _selectionEnd);
1117
1118 // if the selection is after this line, return null
1119 if (_lineOffsets.get(line) > selectionRight)
1120 return null;
1121
1122 // if the selection is before this line, return null
1123 if (_lineOffsets.get(line) < selectionLeft
1124 && _lineOffsets.get(line)
1125 + _textLayouts.get(line).getCharacterCount() < selectionLeft)
1126 return null;
1127
1128 // Dont highlight a single char
1129 // if (selectionRight - selectionLeft <= MINIMUM_RANGED_CHARS)
1130 // return null;
1131
1132 // the selection occurs on this line, determine where it lies on the
1133 // line
1134 int start = Math.max(0, selectionLeft - _lineOffsets.get(line));
1135 // int end = Math.min(_lineOffsets.get(line) +
1136 // _textLayouts.get(line).getCharacterCount(), _selectionEnd);
1137 int end = Math.min(selectionRight - _lineOffsets.get(line),
1138 +_textLayouts.get(line).getCharacterCount());
1139
1140 // System.out.println(line + ": " + start + "x" + end + " (" +
1141 // _selectionStart + "x" + _selectionEnd + ")");
1142 return new Point(start, end);
1143 }
1144
1145 @Override
1146 public void paint(Graphics2D g) {
1147 // if there is no text to paint, do nothing.
1148 if (_text == null || _text.length() == 0 || getMaxSize() == null)// ||
1149 // _textLayouts.size()
1150 // < 1)
1151 return;
1152
1153 if (_textLayouts.size() < 1) {
1154 rebuild(true);
1155 System.out.println("Error: " + _text);
1156 return;
1157 }
1158
1159 // the background is only cleared if required
1160 if (getBackgroundColor() != null) {
1161 g.setColor(getBackgroundColor());
1162 g.fill(getPolygon());
1163 }
1164
1165 float y = getY();
1166 Color c = getPaintColor();
1167 if (_alpha > 0)
1168 c = new Color(c.getRed(), c.getGreen(), c.getBlue(), _alpha);
1169
1170 g.setColor(c);
1171
1172 Color selection;
1173
1174 /*
1175 * Color main = getPaintColor(); Color back = getPaintBackgroundColor();
1176 *
1177 * if (Math.abs(main.getRed() - back.getRed()) < 10 &&
1178 * Math.abs(main.getGreen() - back.getGreen()) < 10 &&
1179 * Math.abs(main.getBlue() - back.getBlue()) < 10) { selection = new
1180 * Color(Math.abs(255 - main.getRed()), Math .abs(255 -
1181 * main.getGreen()), Math.abs(255 - main.getBlue())); } else { selection =
1182 * new Color((main.getRed() + (back.getRed() * 2)) / 3, (main.getGreen() +
1183 * (back.getGreen() * 2)) / 3, (main .getBlue() + (back.getBlue() * 2)) /
1184 * 3); }
1185 */
1186 int green = 160;
1187 int red = 160;
1188 int blue = 160;
1189 if (FrameMouseActions.wasDeleteClicked()) {
1190 green = 235;
1191 red = 235;
1192 blue = 140;
1193 } else if (FrameMouseActions.getLastMouseButton() == MouseEvent.BUTTON1) {
1194 red = 255;
1195 } else if (FrameMouseActions.getLastMouseButton() == MouseEvent.BUTTON2) {
1196 green = 255;
1197 } else if (FrameMouseActions.getLastMouseButton() == MouseEvent.BUTTON3) {
1198 blue = 255;
1199 }
1200 selection = new Color(red, green, blue);
1201
1202 // width -= getX();
1203 // int line = 0;
1204 // boolean tab = false;
1205 synchronized (_textLayouts) {
1206 for (int i = 0; i < _textLayouts.size(); i++) {
1207 TextLayout layout = _textLayouts.get(i);
1208
1209 Point p = getSelectedRange(i);
1210 if (p != null) {
1211 AffineTransform at = new AffineTransform();
1212 AffineTransform orig = g.getTransform();
1213 at.translate(getX() + getJustOffset(layout), y);
1214 g.setTransform(at);
1215
1216 g.setColor(selection);
1217 g.fill(layout.getLogicalHighlightShape(p.x, p.y));
1218
1219 g.setTransform(orig);
1220 g.setColor(c);
1221 }
1222
1223 layout.draw(g, getX() + getJustOffset(layout), y);
1224
1225 /*
1226 * AffineTransform at = new AffineTransform(); AffineTransform
1227 * orig = g.getTransform(); at.translate(getX() +
1228 * getJustOffset(layout), y); g.setTransform(at);
1229 * g.draw(layout.getLogicalHighlightShape(0,
1230 * layout.getCharacterCount())); g.setTransform(orig); /*
1231 * if(_text.charAt(_lineOffsets.get(line) +
1232 * (layout.getCharacterCount() - 1)) == '\t'){ tab = true; x =
1233 * (int) (getX() + x + 20 + layout.getVisibleAdvance()); }else{
1234 */
1235 y += getLineDrop(layout);
1236 /*
1237 * tab = false; }
1238 *
1239 * line++;
1240 */
1241 }
1242 }
1243
1244 if (getLink() != null || getAction() != null) {
1245 if (getLink() != null && getAction() != null) {
1246 g.setColor(LINK_ACTION_COLOR);
1247 } else if (getLink() != null) {
1248 g.setColor(LINK_COLOR);
1249 } else if (getAction() != null) {
1250 g.setColor(ACTION_COLOR);
1251 }
1252
1253 AffineTransform at = new AffineTransform();
1254 AffineTransform orig = g.getTransform();
1255 at.translate(getX() - LEFT_MARGIN, getY()
1256 - (_textLayouts.get(0).getAscent() / 2));
1257 g.setTransform(at);
1258
1259 if (getLinkMark() && getLink() != null) {
1260 g.drawPolygon(getCircle());
1261
1262 // if the link is not valid, cross out the circle
1263 if (!isLinkValid())
1264 g.drawPolygon(getCircleCross());
1265 }
1266
1267 if (getActionMark() && getAction() != null) {
1268 g.drawPolygon(getCircle());
1269 g.fillPolygon(getCircle());
1270
1271 // if the link is not valid, cross out the circle
1272 if (!isLinkValid() && getLink() != null) {
1273 g.setColor(getParent().getPaintBackgroundColor());
1274 g.drawPolygon(getCircleCross());
1275 }
1276 }
1277
1278 // reset the graphics tranformation
1279 g.setTransform(orig);
1280 }
1281
1282 if (isHighlighted()) {
1283 g.setColor(getPaintHighlightColor());
1284 Stroke _highlightStroke = new BasicStroke(
1285 (float) _highlightThickness, BasicStroke.CAP_BUTT,
1286 BasicStroke.JOIN_ROUND);
1287 g.setStroke(_highlightStroke);
1288 g.drawPolygon(getPolygon());
1289 }
1290 }
1291
1292 @Override
1293 public int showHighlight(boolean val) {
1294 super.showHighlight(val);
1295
1296 if (val)
1297 return Item.UNCHANGED_CURSOR;
1298 else
1299 return Item.DEFAULT_CURSOR;
1300 }
1301
1302 /**
1303 * Determines if this text has any text in it.
1304 *
1305 * @return True if this Item has no text in it, false otherwise.
1306 */
1307 public boolean isEmpty() {
1308 if (_text == null || _text.length() == 0)
1309 return true;
1310
1311 return false;
1312 }
1313
1314 @Override
1315 public Polygon getPolygon() {
1316 updatePolygon();
1317
1318 Polygon external = new Polygon(_poly.xpoints, _poly.ypoints,
1319 _poly.npoints);
1320 external.translate(getX(), getY());
1321
1322 return external;
1323 }
1324
1325 @Override
1326 public Text copy() {
1327 Text copy = new Text(getID());
1328
1329 // copy standard item values
1330 Item.DuplicateItem(this, copy);
1331
1332 // copy values specific to text items
1333 copy.setSpacing(getSpacing());
1334 copy.setInitialSpacing(getInitialSpacing());
1335 copy.setJustification(getJustification());
1336 copy.setLetterSpacing(getLetterSpacing());
1337 copy.setWordSpacing(getWordSpacing());
1338 copy.setWidth(getWidth());
1339 copy.setFont(getFont());
1340 copy.setText(_text.toString());
1341
1342 return copy;
1343 }
1344
1345 @Override
1346 public int getSize() {
1347 return getPaintFont().getSize();
1348 }
1349
1350 /**
1351 * Returns the number of characters in this Text, excluding new lines.
1352 *
1353 * @return The sum of the length of each line of text
1354 */
1355 public int getLength() {
1356 return _text.length();
1357 }
1358
1359 @Override
1360 public void setSize(int size) {
1361 setFont(getPaintFont().deriveFont((float) size));
1362 }
1363
1364 @Override
1365 public void setAnnotation(boolean val) {
1366 if (val) {
1367 // if this is already an annotation, do nothing
1368 if (isAnnotation())
1369 return;
1370
1371 //Remove 'a: ' from the begining if it is there
1372 if (_text.length() > 3 && _text.charAt(0) == 'a' && _text.charAt(1) == ':'){
1373 _text.delete(0, 2);
1374 while (_text.charAt(0) == ' ')
1375 _text.delete(0, 1);
1376 }
1377 _text.insert(0, "@");
1378
1379 if (_lineBreaker != null) {
1380 AttributedString inserting = new AttributedString(_text
1381 .toString());
1382 inserting.addAttribute(TextAttribute.FONT, getPaintFont());
1383 _lineBreaker.insertChar(inserting.getIterator(), 0);
1384 }
1385 } else {
1386 // if this is not an annotation, do nothing
1387 if (!isAnnotation())
1388 return;
1389
1390 _text.deleteCharAt(0);
1391
1392 if (_lineBreaker != null) {
1393 AttributedString inserting = new AttributedString(_text
1394 .toString());
1395 inserting.addAttribute(TextAttribute.FONT, getPaintFont());
1396 _lineBreaker.deleteChar(inserting.getIterator(), 0);
1397 }
1398 }
1399 rebuild(false);
1400 }
1401
1402 @Override
1403 public boolean isAnnotation() {
1404 if (_text != null && _text.length() > 0 && _text.indexOf("@") == 0)
1405 return true;
1406
1407 return false;
1408 }
1409
1410 public boolean isSpecialAnnotation() {
1411 assert _text != null;
1412 String s = _text.toString().toLowerCase();
1413 if (s.length() > 0 && s.indexOf("@") == 0) {
1414 if (s.equals("@old") || s.equals("@ao")
1415 || s.equals("@itemtemplate") || s.equals("@parent")
1416 || s.equals("@more") || s.equals("@next")
1417 || s.equals("@previous") || s.equals("@i")
1418 || s.equals("@f"))
1419 return true;
1420 }
1421
1422 return false;
1423 }
1424
1425 @Override
1426 public Item merge(Item merger, int mouseX, int mouseY) {
1427 if (merger instanceof Dot) {
1428 Dot d = (Dot) merger;
1429 List<Line> lines = new LinkedList<Line>();
1430 lines.addAll(d.getLines());
1431 for (Line line : lines)
1432 line.replaceEnd(d, this);
1433
1434 d.removeAllLines();
1435 return null;
1436 }
1437
1438 if (!(merger instanceof Text))
1439 return merger;
1440
1441 Text merge = (Text) merger;
1442
1443 // insertText(merge.getText(), mouseX, mouseY);
1444
1445 // if the item being merged has a link
1446 if (merge.getLink() != null) {
1447 // if this item has a link, keep it on the cursor
1448 if (getLink() != null) {
1449 merge.setText(merge.getLink());
1450 merge.setLink(null);
1451 // return merge;
1452 // TODO get this to return the merged item and attach it to the
1453 // cursor only when the user presses the middle button.
1454 } else
1455 setLink(merge.getLink());
1456 }
1457
1458 return null;
1459 }
1460
1461 /**
1462 * Resets the position of the item to the default position for a title item.
1463 *
1464 */
1465 public void resetTitlePosition() {
1466 setPosition(org.expeditee.io.Conversion.X_ADJUST, getSize());
1467 }
1468
1469 public String stripFirstWord() {
1470 int firstSpace = _text.toString().indexOf(' ');
1471
1472 // if there is only one word just make it blank
1473 if (firstSpace < 0 || firstSpace + 1 >= _text.length()) {
1474 String text = _text.toString();
1475 setText("");
1476 return text;
1477 }
1478
1479 String firstWord = _text.toString().substring(0, firstSpace);
1480
1481 String text = _text.toString();
1482 int secondWord;
1483 for (secondWord = firstSpace; secondWord < _text.length(); secondWord++) {
1484 if (!Character.isSpaceChar(text.charAt(secondWord))) {
1485 break;
1486 }
1487 }
1488 setText(_text.toString().substring(secondWord));
1489
1490 return firstWord;
1491 }
1492
1493 public String toString() {
1494 String message = "[" + getFirstLine() + "]" + FRAME_NAME_SEPARATOR;
1495
1496 if (getParent() != null)
1497 return message + getParent().getFrameName();
1498 return message + getDateCreated();
1499 }
1500
1501 public Text getTemplateForm() {
1502 Text template = this.copy();
1503 template.setID(-1);
1504 //The template must have text otherwise the bounds height will be zero!!
1505 //This will stop escape drop down from working if there is no item template
1506 template.setText("@");
1507 return template;
1508 }
1509}
Note: See TracBrowser for help on using the repository browser.