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

Last change on this file since 906 was 906, checked in by bln4, 10 years ago

Added Magnetic Constraints

Magnetic Constraints are a new type of constraint between items; particularly text. Items can have their neighbours set (for example: who is to my right) and then have custom actions run when a item and its neighbour interact in a defined way. For example: one text item grows enough for hits its right neighbour; so its right neighbour moves over to compensate.
Files changed: org.expeditee.{ gui.DisplayIO, io.DefaultFrameReader, io.DefaultFrameWriter, items.Item, items.Text }

Added ability to have 'delayed' properties on items. With the invention of Magnetic Constraints it was found that the saving and loading of properties now required (greatly benifited from) a way to set a property after all items have been loaded in; rather than as the perticular item was being loaded in. This is that ability.

Files changed: org.expeditee.io.{ DefaultFrameReader, ExpReader }

Let through some non letter characters as names of properties. The tokens ',', '', and '_' have been used as property names to describe neighbors. In order to do this the isValidLine(String) method was altered to let these through. This is not the ideal solution, and should be looked at later.

Files changed: org.expeditee.io.ExpReader

Bug fix: setting a custom title support was added previously. If a custom title had been set the title was: [Your Custom Title] ~ [Expeditee Title]. However the tildas where showing up even without a custom title set. This is now fixed.

File size: 69.0 KB
RevLine 
[4]1package org.expeditee.items;
2
3import java.awt.BasicStroke;
4import java.awt.Color;
[80]5import java.awt.Dimension;
[4]6import java.awt.Font;
[247]7import java.awt.GradientPaint;
[4]8import java.awt.Graphics2D;
[777]9import java.awt.GraphicsEnvironment;
[4]10import java.awt.Point;
11import java.awt.Polygon;
[130]12import java.awt.Rectangle;
[247]13import java.awt.Shape;
[4]14import java.awt.Stroke;
15import java.awt.event.KeyEvent;
16import java.awt.event.MouseEvent;
17import java.awt.font.FontRenderContext;
18import java.awt.font.LineBreakMeasurer;
19import java.awt.font.TextAttribute;
20import java.awt.font.TextHitInfo;
21import java.awt.font.TextLayout;
22import java.awt.geom.AffineTransform;
[108]23import java.awt.geom.Point2D;
[4]24import java.awt.geom.Rectangle2D;
[777]25import java.io.File;
[906]26
[4]27import java.text.AttributedString;
[242]28import java.util.Collection;
[594]29import java.util.HashMap;
[242]30import java.util.HashSet;
[4]31import java.util.LinkedList;
32import java.util.List;
33
[242]34import org.expeditee.gui.AttributeValuePair;
[72]35import org.expeditee.gui.DisplayIO;
[156]36import org.expeditee.gui.Frame;
[71]37import org.expeditee.gui.FrameGraphics;
[176]38import org.expeditee.gui.FrameIO;
[72]39import org.expeditee.gui.FrameKeyboardActions;
[4]40import org.expeditee.gui.FrameMouseActions;
[247]41import org.expeditee.gui.FrameUtils;
[613]42import org.expeditee.gui.FreeItems;
[906]43import org.expeditee.items.MagneticConstraint.MagneticConstraints;
[190]44import org.expeditee.math.ExpediteeJEP;
[632]45import org.expeditee.settings.experimental.ExperimentalFeatures;
[176]46import org.nfunk.jep.Node;
[4]47
48/**
49 * Represents text displayed on the screen, which may include multiple lines.
50 * All standard text properties can be set (font, style, size).
51 *
52 * @author jdm18
53 *
54 */
55public class Text extends Item {
[400]56 private static final int ADJUST_WIDTH_THRESHOLD = 200;
57
[115]58 public static String LINE_SEPARATOR = System.getProperty("line.separator");
[137]59
[776]60 // public static char[] BULLETS = { '\u2219', '\u2218', '\u2217' };
61 public static char[] BULLETS = { '\u25AA', '\u25AB', '\u2217' };
[137]62
[115]63 private static char DEFAULT_BULLET = BULLETS[2];
[137]64
[115]65 private static String DEFAULT_BULLET_STRING = DEFAULT_BULLET + " ";
[137]66
[88]67 private String[] _processedText = null;
[7]68
[88]69 public String[] getProcessedText() {
70 return _processedText;
[7]71 }
72
[88]73 public void setProcessedText(String[] tokens) {
74 _processedText = tokens;
[7]75 }
76
[4]77 public static final String FRAME_NAME_SEPARATOR = " on frame ";
78
79 /**
80 * The default font used to display text items if no font is specified.
81 */
82 public static final String DEFAULT_FONT = "Serif-Plain-18";
83
84 public static final Color DEFAULT_COLOR = Color.BLACK;
85
86 public static final int MINIMUM_RANGED_CHARS = 2;
87
[105]88 public static final int NONE = 0;
[4]89
[105]90 public static final int UP = 1;
[4]91
[105]92 public static final int DOWN = 2;
[4]93
[105]94 public static final int LEFT = 3;
[4]95
[105]96 public static final int RIGHT = 4;
[4]97
[105]98 public static final int HOME = 5;
[4]99
[147]100 public static final int LINE_HOME = 9;
101
102 public static final int LINE_END = 10;
103
[105]104 public static final int END = 6;
[13]105
[105]106 public static final int PAGE_DOWN = 7;
[13]107
[105]108 public static final int PAGE_UP = 8;
109
[410]110 /*
111 * Set the width to be IMPLICIT, but as wide as possible, a negative width
112 * value is one that is implicitly set by the system... a positive value is
113 * one explicitly set by the user.
114 */
115 private Integer _maxWidth = Integer.MIN_VALUE + 1;
[4]116
[147]117 private Justification _justification = Justification.left;
[4]118
[659]119 private float _spacing = -1;
[4]120
121 private int _word_spacing = -1;
122
[659]123 private float _initial_spacing = 0;
[4]124
[659]125 private float _letter_spacing = 0;
[4]126
127 // used during ranging out
128 private int _selectionStart = -1;
129
130 private int _selectionEnd = -1;
[613]131
132 // whether autowrap is on/off for this item
133 protected boolean _autoWrap = false;
[137]134
[4]135 // text is broken up into lines
136 private StringBuffer _text = new StringBuffer();
137
138 private List<TextLayout> _textLayouts = new LinkedList<TextLayout>();
139
140 private List<Integer> _lineOffsets = new LinkedList<Integer>();
141
[615]142 private FontRenderContext frc = null;
[4]143 private LineBreakMeasurer _lineBreaker = null;
144
145 // The font to display this text in
146 private Font _font;
[777]147
[836]148
149 protected static void InitFontFamily(GraphicsEnvironment ge, File fontFamilyDir)
150 {
151 File[] fontFiles = fontFamilyDir.listFiles();
[4]152
[836]153 for (File fontFile : fontFiles) {
154 String ext = "";
155 String fileName = fontFile.getName().toLowerCase();
156
157 int i = fileName.lastIndexOf('.');
158 int p = Math.max(fileName.lastIndexOf('/'), fileName.lastIndexOf('\\'));
159
160 if (i > p) {
161 ext = fileName.substring(i+1);
[777]162 }
[836]163
164 if (ext.equals("ttf")) {
165
166 try {
167 Font font = Font.createFont(Font.TRUETYPE_FONT, fontFile);
168
169 ge.registerFont(font);
170 }
171 catch (Exception e) {
172 System.err.println("Failed to load custon font file: " + fontFile);
173 }
[777]174 }
175 }
[836]176 }
177
178 public static void InitFonts() {
179
180 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
181
182 if (ge != null) {
183
184 File fontDirectory = new File(FrameIO.FONT_PATH);
185 if (fontDirectory != null) {
186 File[] fontFamilyDirs = fontDirectory.listFiles();
187 if (fontFamilyDirs != null) {
188
189 if (fontFamilyDirs.length>0) {
190 System.out.println("Loading custom fonts:");
191 }
192
193 boolean first_item = true;
194 for (File fontFamilyDir : fontFamilyDirs) {
195 if (fontFamilyDir.isDirectory()) {
196
197 if (first_item) {
198 System.out.print(" " + fontFamilyDir.getName());
199 first_item = false;
200 }
201 else {
202 System.out.print(", " + fontFamilyDir.getName());
203 }
204 System.out.flush();
205
206 InitFontFamily(ge,fontFamilyDir);
207 }
208 }
209 System.out.println();
210 }
211 }
[777]212 }
[836]213 else {
214 System.err.println("No graphics environment detected. Skipping the loading of the custom fonts");
215 }
[777]216 }
217
[836]218
219
[4]220 /**
221 * Creates a new Text Item with the given ID and text.
222 *
223 * @param id
224 * The id of this item
225 * @param text
226 * The text to use in this item
227 */
228 public Text(int id, String text) {
229 super();
230 _text.append(text);
231 rebuild(false);
232
233 setID(id);
234 }
[137]235
[108]236 /**
237 * Creates a text item which is not added to the frame.
[137]238 *
[108]239 * @param text
240 */
241 public Text(String text) {
242 super();
243 _text.append(text);
244 rebuild(false);
245 setID(-1);
246 }
[4]247
248 /**
249 * Creates a new Text Item with the given ID
250 *
251 * @param id
252 * The ID to of this item
253 */
254 public Text(int id) {
255 super();
256 setID(id);
257 }
258
[97]259 public Text(int i, String string, Color foreground, Color background) {
260 this(i, string);
261 this.setColor(foreground);
262 this.setBackgroundColor(background);
263 }
264
[4]265 /**
266 * Sets the maximum width of this Text item when justifcation is used.
267 * passing in 0 or -1 means there is no maximum width
268 *
269 * @param width
270 * The maximum width of this item when justification is applied
271 * to it.
272 */
273 @Override
[400]274 public void setWidth(Integer width) {
[185]275 invalidateAll();
[400]276
277 if (width == null) {
[362]278 setJustification(Justification.left);
[400]279 setRightMargin(FrameGraphics.getMaxFrameSize().width, false);
280 return;
[362]281 }
[4]282
283 _maxWidth = width;
[776]284 rebuild(true);
[185]285 invalidateAll();
[4]286 }
287
288 /**
[404]289 * Returns the maximum width of this Text item when justifcation is used. If
290 * the width is negative, it means no explicit width has been set
[4]291 *
292 * @return The maximum width of this Text item when justification is used
293 */
294 @Override
[400]295 public Integer getWidth() {
296 if (_maxWidth == null || _maxWidth <= 0)
297 return null;
[4]298 return _maxWidth;
299 }
300
[400]301 public Integer getAbsoluteWidth() {
302 if (_maxWidth == null) {
303 return Integer.MAX_VALUE;
304 }
305
306 return Math.abs(_maxWidth);
307 }
308
[121]309 @Override
310 public Color getHighlightColor() {
311 if (_highlightColor.equals(getPaintBackgroundColor()))
312 return ALTERNATE_HIGHLIGHT;
313 return _highlightColor;
314 }
315
[4]316 /**
317 * Sets the justification of this Text item. The given integer should
318 * correspond to one of the JUSTIFICATION constants defined in Item
319 *
320 * @param just
321 * The justification to apply to this Text item
322 */
[97]323 public void setJustification(Justification just) {
[362]324 invalidateAll();
325
326 // Only justification left works with 0 width
[400]327 // if (just != null && just != Justification.left && !hasWidth()) {
328 // // TODO Tighten this up so it subtracts the margin widths
329 // setWidth(getBoundsWidth());
330 // }
[362]331
[4]332 _justification = just;
[199]333 rebuild(true);
[362]334 invalidateAll();
[4]335 }
336
337 /**
[427]338 * Returns the current justification of this Text item. The default value
339 * left justification.
[4]340 *
341 * @return The justification of this Text item
342 */
[97]343 public Justification getJustification() {
[147]344 if (_justification == null || _justification.equals(Justification.left))
[98]345 return null;
[4]346 return _justification;
347 }
348
349 private int getJustOffset(TextLayout layout) {
[97]350 if (getJustification() == Justification.center)
[400]351 return (int) ((getAbsoluteWidth() - layout.getAdvance()) / 2);
[97]352 else if (getJustification() == Justification.right)
[400]353 return (int) (getAbsoluteWidth() - layout.getAdvance());
[4]354
355 return 0;
356 }
357
358 /**
[376]359 * Sets the text displayed on the screen to the given String. It does not
360 * reset the formula, attributeValuePair or other cached values.
[4]361 *
362 * @param text
363 * The String to display on the screen when drawing this Item.
364 */
[176]365 @Override
[4]366 public void setText(String text) {
[390]367 setText(text, false);
368 }
[400]369
[390]370 public void setText(String text, Boolean clearCache) {
[376]371 // if (_text != null && text.length() < _text.length())
372 invalidateAll();
[4]373 _text = new StringBuffer(text);
[376]374
375 /*
[390]376 * Always clearingCach remove formulas when moving in and out of XRay
[376]377 * mode
378 */
[400]379 if (clearCache) {
[390]380 clearCache();
[400]381 }
382
[247]383 rebuild(true);
[126]384 invalidateAll();
[4]385 }
386
387 public void setTextList(List<String> text) {
388 if (text == null || text.size() <= 0)
389 return;
390
[376]391 invalidateAll();
392
393 StringBuffer sb = new StringBuffer();
394
395 for (int i = 0; i < text.size(); i++) {
396 sb.append(text.get(i)).append('\n');
[4]397 }
[376]398
399 if (sb.length() > 0)
400 sb.deleteCharAt(sb.length() - 1);
401
402 setText(sb.toString());
403
404 rebuild(true);
405 invalidateAll();
[4]406 }
407
408 public void setAttributeValue(String value) {
[289]409 AttributeValuePair avp = new AttributeValuePair(getText(), false);
[286]410 avp.setValue(value);
411 setText(avp.toString());
[4]412 }
413
414 /**
415 * Inserts the given String at the start of the first line of this Text
416 * Item.
417 *
418 * @param text
419 * The String to insert.
420 */
421 public void prependText(String text) {
[278]422
[4]423 _text.insert(0, text);
424 rebuild(false);
425 }
426
427 /**
428 * If the first line of text starts with the given String, then it is
429 * removed otherwise no action is taken.
430 *
431 * @param text
432 * The String to remove from the first line of Text
433 */
434 public void removeText(String text) {
[121]435 if (_text.length() > 0 && _text.indexOf(text) == 0) {
[278]436 // Need the invalidate all for dateStamp toggling
[247]437 invalidateAll();
[4]438 _text.delete(0, text.length());
[121]439 }
[4]440
441 }
[7]442
[4]443 public void removeEndText(String textToRemove) {
444 int length = _text.length();
445 if (length > 0) {
[137]446 // invalidateAll();
[4]447 int pos = _text.indexOf(textToRemove);
448 int textToRemoveLength = textToRemove.length();
449 if (pos + textToRemoveLength == length) {
450 _text.delete(pos, length);
451 }
452 }
[137]453
[4]454 }
455
456 /**
457 * Appends the given String to any text already present in this Item
458 *
459 * @param text
460 * The String to append.
461 */
462 public void appendText(String text) {
463 _text.append(text);
464 rebuild(false);
465 }
[7]466
[404]467 /**
468 * Used by the frame reader to construct multi-line text items. It must run
469 * quickly, so that the system still responds well for long text items.
470 *
471 * @param text
472 */
[4]473 public void appendLine(String text) {
474 if (text == null)
475 text = "";
[7]476
[4]477 if (_text.length() > 0)
478 _text.append('\n');
[7]479
[4]480 _text.append(text);
[663]481
[776]482 rebuild(true);
[4]483 }
484
485 /**
486 * Tests if the first line of this Text starts with the given String.
487 *
488 * @param text
489 * The prefix to check for
490 * @return True if the first line starts with the given String, False
491 * otherwise.
492 */
493 public boolean startsWith(String text) {
494 return startsWith(text, true);
495 }
496
497 public boolean startsWith(String text, boolean ignoreCase) {
498 if (text == null || _text == null || _text.length() < 1)
499 return false;
500
501 if (ignoreCase)
502 return _text.toString().toLowerCase()
503 .startsWith(text.toLowerCase());
504 else
505 return _text.indexOf(text) == 0;
506 }
507
508 /**
509 * Inserts a character into the Text of this Item.
510 *
511 * @param ch
512 * The character insert.
513 * @param mouseX
514 * The X position to insert the Strings at.
515 * @param mouseY
516 * The Y position to insert the Strings at.
517 */
[108]518 public Point2D.Float insertChar(char ch, float mouseX, float mouseY) {
[362]519 if (ch != '\t') /* && ch != '\n' */
[4]520 return insertText("" + ch, mouseX, mouseY);
[80]521
522 return insertText(" " + ch, mouseX, mouseY);
[4]523 }
524
[80]525 /**
526 * @param index
527 * @return
528 */
529 private char getNextBullet(char bullet) {
[137]530 for (int i = 0; i < BULLETS.length - 1; i++) {
531 if (BULLETS[i] == bullet)
532 return BULLETS[i + 1];
[80]533 }
[115]534 return BULLETS[0];
[80]535 }
536
537 private char getPreviousBullet(char bullet) {
[137]538 for (int i = 1; i < BULLETS.length; i++) {
539 if (BULLETS[i] == bullet)
[115]540 return BULLETS[i - 1];
[80]541 }
[115]542 return BULLETS[BULLETS.length - 1];
[80]543 }
544
[147]545 public Point2D.Float getLineEndPosition(float mouseY) {
[4]546 return getEdgePosition(getLinePosition(mouseY), false);
547 }
548
[147]549 public Point2D.Float getLineStartPosition(float mouseY) {
[4]550 return getEdgePosition(getLinePosition(mouseY), true);
551 }
552
[147]553 public Point2D.Float getParagraphEndPosition() {
[4]554 return getEdgePosition(_textLayouts.size() - 1, false);
555 }
[98]556
[147]557 public Point2D.Float getParagraphStartPosition() {
[86]558 return getEdgePosition(0, true);
559 }
[4]560
[108]561 private Point2D.Float getEdgePosition(int line, boolean start) {
[4]562 // if there is no text yet, or the line is invalid
563 if (_text == null || _text.length() == 0 || line < 0
564 || line > _textLayouts.size() - 1)
[108]565 return new Point2D.Float(getX(), getY());
[4]566
567 TextLayout last = _textLayouts.get(line);
568 TextHitInfo hit;
569 if (start)
570 hit = last.getNextLeftHit(1);
571 else
572 hit = last.getNextRightHit(last.getCharacterCount() - 1);
573
574 // move the cursor to the new location
575 float[] caret = last.getCaretInfo(hit);
[105]576 float y = getLineDrop(last) * line;
[4]577
[107]578 float x = getX() + caret[0] + getJustOffset(last);
[4]579 x = Math
580 .min(
581 x,
582 (getX() - Item.MARGIN_RIGHT - (2 * getGravity()) + getBoundsWidth()));
[108]583 return new Point2D.Float(x, getY() + y + caret[1]);
[4]584 }
585
[176]586 public void setSelectionStart(float mouseX, float mouseY) {
[4]587 // determine what line is being pointed to
588 int line = getLinePosition(mouseY);
589
590 // get the character being pointed to
591 TextHitInfo hit = getCharPosition(line, mouseX);
592 _selectionStart = hit.getInsertionIndex() + _lineOffsets.get(line);
[121]593 invalidateAll();
[4]594 }
595
[176]596 public void setSelectionEnd(float mouseX, float mouseY) {
[4]597 // determine what line is being pointed to
598 int line = getLinePosition(mouseY);
599
600 // get the character being pointed to
601 TextHitInfo hit = getCharPosition(line, mouseX);
602 _selectionEnd = hit.getInsertionIndex() + _lineOffsets.get(line);
[121]603 invalidateAll();
[4]604 }
605
606 public void clearSelection() {
607 _selectionStart = -1;
608 _selectionEnd = -1;
[121]609 invalidateAll();
[4]610 }
611
612 public void clearSelectionEnd() {
613 _selectionEnd = -1;
[121]614 invalidateAll();
[4]615 }
616
617 public String copySelectedText() {
618 if (_selectionStart < 0 || _selectionEnd < 0)
619 return null;
620 else if (_selectionEnd > _text.length())
621 _selectionEnd = _text.length();
622
623 return _text.substring(Math.min(_selectionStart, _selectionEnd), Math
624 .max(_selectionStart, _selectionEnd));
625 }
626
627 public String cutSelectedText() {
[7]628 return replaceSelectedText("");
629 }
630
631 public String replaceSelectedText(String newText) {
[4]632 if (_selectionStart < 0 || _selectionEnd < 0)
633 return null;
[137]634
[121]635 invalidateAll();
[137]636
[121]637 if (_selectionEnd > _text.length())
[4]638 _selectionEnd = _text.length();
639
640 int left = Math.min(_selectionStart, _selectionEnd);
641 int right = Math.max(_selectionStart, _selectionEnd);
642
[147]643 // Trim the text to remove new lines on the beginning and end of the
644 // string
[154]645 if (_text.charAt(left) == '\n') {
646 // if the entire line is being removed then remove one of the new
647 // lines, the first case checks if the last line is being removed
648 if (right >= _text.length() || _text.charAt(right) == '\n') {
[147]649 _text.deleteCharAt(left);
650 right--;
[154]651 } else {
[147]652 left++;
653 }
654 }
[154]655 // New lines are always at the start of the line for now...
656 // if(_text.charAt(right - 1) == '\n' && left < right){
657 // right--;
658 // }
[4]659 String s = _text.substring(left, right);
660
[7]661 _text.delete(left, right);
662 _text.insert(left, newText);
663 rebuild(true);
[421]664
[410]665 clearCache();
[137]666
[121]667 invalidateAll();
[4]668
669 return s;
670 }
671
[131]672 public int getSelectionSize() {
[4]673 if (_selectionEnd < 0 || _selectionStart < 0)
674 return 0;
675
[7]676 // System.out.println(_selectionStart + ":" + _selectionEnd);
[4]677
678 return Math.abs(_selectionEnd - _selectionStart);
679 }
680
681 /**
682 * Inserts the given String into the Text at the position given by the
683 * mouseX and mouseY coordinates
684 *
685 * @param text
686 * The String to insert into this Text.
687 * @param mouseX
688 * The X position to insert the String
689 * @param mouseY
690 * The Y position to insert the String
691 * @return The new location that the mouse cursor should be moved to
692 */
[108]693 public Point2D.Float insertText(String text, float mouseX, float mouseY) {
[906]694 final Point2D.Float newPos = insertText(text, mouseX, mouseY, -1);
695 return newPos;
[108]696 }
697
698 public Point2D.Float insertText(String text, float mouseX, float mouseY,
699 int insertPos) {
[4]700 TextHitInfo hit;
701 TextLayout current = null;
702 int line;
[137]703
[121]704 invalidateAll();
[4]705
706 // check for empty string
707 if (text == null || text.length() == 0)
[108]708 return new Point2D.Float(mouseX, mouseY);
[4]709
710 // if there is no text yet
711 if (_text == null || _text.length() == 0) {
712 _text = new StringBuffer().append(text);
713 // create the linebreaker and layouts
714 rebuild(true);
[80]715 assert (_textLayouts.size() == 1);
716 current = _textLayouts.get(0);
[4]717 hit = current.getNextRightHit(0);
718 line = 0;
719
720 // otherwise, we are inserting text
721 } else {
[376]722 clearCache();
[4]723 // determine what line is being pointed to
724 line = getLinePosition(mouseY);
725
726 // get the character being pointed to
727 hit = getCharPosition(line, mouseX);
728
729 int pos = hit.getInsertionIndex() + _lineOffsets.get(line);
730
[154]731 if (line > 0 && hit.getInsertionIndex() == 0) {
732 // Only move forward a char if the line begins with a hard line
733 // break... not a soft line break
734 if (_text.charAt(pos) == '\n') {
735 pos++;
736 }
737 }
[4]738
[108]739 if (insertPos < 0)
740 insertPos = pos;
741
[4]742 // if this is a backspace key
743 if (text.charAt(0) == KeyEvent.VK_BACK_SPACE) {
[324]744 if (hasSelection()) {
[336]745 pos = deleteSelection(pos);
[324]746 } else if (insertPos > 0) {
[108]747 deleteChar(insertPos - 1);
748 if (pos > 0)
749 pos--;
[4]750 }
751 // if this is a delete key
752 } else if (text.charAt(0) == KeyEvent.VK_DELETE) {
[324]753 if (hasSelection()) {
[336]754 pos = deleteSelection(pos);
[324]755 } else if (insertPos < _text.length()) {
[108]756 deleteChar(insertPos);
[80]757 }
758 // this is a tab
759 } else if (text.charAt(0) == KeyEvent.VK_TAB) {
760 // Text length greater than 1 signals a backwards tab
761 if (text.length() > 1) {
[86]762 // Find the first non space char to see if its a bullet
[80]763 int index = 0;
764 for (index = 0; index < _text.length(); index++) {
765 if (!Character.isSpaceChar(_text.charAt(index)))
766 break;
[4]767 }
[80]768 // Check if there is a space after the bullet
[86]769 if (index < _text.length() - 1
770 && _text.charAt(index + 1) == ' ') {
[80]771 // Change the bullet
[86]772 _text.setCharAt(index, getPreviousBullet(_text
773 .charAt(index)));
[80]774 }
775 // Remove the spacing at the start
776 for (int i = 0; i < TAB_STRING.length(); i++) {
[86]777 if (_text.length() > 0
778 && Character.isSpaceChar(_text.charAt(0))) {
[108]779 deleteChar(0);
[80]780 pos--;
781 } else
782 break;
783 }
784 _lineBreaker = null;
785 } else {
786 // / Find the first non space char to see if its a bullet
787 int index = 0;
788 for (index = 0; index < _text.length(); index++) {
789 if (!Character.isSpaceChar(_text.charAt(index)))
790 break;
791 }
792 // Check if there is a space after the bullet
793 if (index < _text.length() - 1
794 && _text.charAt(index + 1) == ' ') {
795 char nextBullet = getNextBullet(_text.charAt(index));
796 // Change the bullet
797 _text.setCharAt(index, nextBullet);
798 }
799 // Insert the spacing at the start
800 insertString(TAB_STRING, 0);
801 pos += TAB_STRING.length();
[4]802 }
803 // this is a normal insert
804 } else {
[108]805 insertString(text, insertPos);
[4]806 pos += text.length();
807 }
808
[137]809 if (_text.length() == 0) {
[108]810 rebuild(false);
811 return new Point2D.Float(this._x, this._y);
812 }
813
[4]814 int newLine = line;
815
816 // if a rebuild is required
[199]817 rebuild(true, false);
[4]818
819 // determine the new position the cursor should have
820 for (int i = 1; i < _lineOffsets.size(); i++) {
821 if (_lineOffsets.get(i) >= pos) {
822 newLine = i - 1;
823 break;
824 }
825 }
826
827 current = _textLayouts.get(newLine);
[80]828 pos -= _lineOffsets.get(newLine);
[4]829
830 if (newLine == line) {
831 if (pos > 0)
832 hit = current.getNextRightHit(pos - 1);
833 else
834 hit = current.getNextLeftHit(1);
835 } else if (newLine < line) {
836 hit = current.getNextRightHit(pos - 1);
837 } else {
838 hit = current.getNextRightHit(pos - 1);
839 }
840
841 line = newLine;
842 }
843
844 // move the cursor to the new location
845 float[] caret = current.getCaretInfo(hit);
[105]846 float y = getLineDrop(current) * line;
[4]847
[107]848 float x = getX() + caret[0] + getJustOffset(current);
[4]849 x = Math
850 .min(
851 x,
852 (getX() - Item.MARGIN_RIGHT - (2 * getGravity()) + getBoundsWidth()));
[137]853
[121]854 invalidateAll();
[137]855
[108]856 return new Point2D.Float(Math.round(x), Math.round(getY() + y
857 + caret[1]));
[4]858 }
859
[336]860 /**
[376]861 *
862 */
863 private void clearCache() {
864 _attributeValuePair = null;
865 setProcessedText(null);
866 setFormula(null);
867 }
868
869 /**
[336]870 * @param pos
871 * @return
872 */
873 private int deleteSelection(int pos) {
874 int selectionLength = getSelectionSize();
875 cutSelectedText();
876 clearSelection();
877 pos -= selectionLength;
878 return pos;
879 }
880
[176]881 public Point2D.Float moveCursor(int direction, float mouseX, float mouseY,
[376]882 boolean setSelection, boolean wholeWord) {
[176]883 if (setSelection) {
[376]884 if (!hasSelection()) {
[176]885 setSelectionStart(mouseX, mouseY);
886 }
[376]887 } else {
888 // clearSelection();
[176]889 }
890
891 Point2D.Float resultPos = null;
892
[4]893 // check for home or end keys
[147]894 switch (direction) {
895 case HOME:
[176]896 resultPos = getParagraphStartPosition();
897 break;
[147]898 case END:
[176]899 resultPos = getParagraphEndPosition();
900 break;
[147]901 case LINE_HOME:
[176]902 resultPos = getLineStartPosition(mouseY);
903 break;
[147]904 case LINE_END:
[176]905 resultPos = getLineEndPosition(mouseY);
906 break;
907 default:
908 TextHitInfo hit;
909 TextLayout current;
910 int line;
[4]911
[176]912 // if there is no text yet
913 if (_text == null || _text.length() == 0) {
914 return new Point2D.Float(mouseX, mouseY);
915 // otherwise, move the cursor
916 } else {
917 // determine the line of text to check
918 line = getLinePosition(mouseY);
919 if (line < 0)
920 line = _textLayouts.size() - 1;
[4]921
[176]922 // if the cursor is moving up or down, change the line
923 if (direction == UP)
924 line = Math.max(line - 1, 0);
925 else if (direction == DOWN)
926 line = Math.min(line + 1, _textLayouts.size() - 1);
[4]927
[176]928 hit = getCharPosition(line, mouseX);
[4]929
[176]930 if (direction == LEFT) {
931 if (hit.getInsertionIndex() > 0) {
[376]932
933 char prevChar = ' ';
934 do {
935 hit = _textLayouts.get(line).getNextLeftHit(hit);
936
937 // Stop if at the start of the line
938 if (hit.getInsertionIndex() == 0)
939 break;
940 // Keep going if the char to the left is a
941 // letterOrDigit
942 prevChar = _text.charAt(hit.getInsertionIndex() - 1
943 + _lineOffsets.get(line));
944 } while (wholeWord
945 && Character.isLetterOrDigit(prevChar));
946 // TODO Go to the start of the word instead of before
947 // the word
948 char nextChar = _text.charAt(hit.getInsertionIndex()
949 + _lineOffsets.get(line));
950 /*
951 * This takes care of hard line break in
952 */
953 if (line > 0 && nextChar == '\n') {
[176]954 line--;
955 hit = _textLayouts.get(line)
956 .getNextRightHit(
957 _textLayouts.get(line)
958 .getCharacterCount() - 1);
959 }
960 // This takes care of soft line breaks.
961 } else if (line > 0) {
[86]962 line--;
963 hit = _textLayouts.get(line).getNextRightHit(
964 _textLayouts.get(line).getCharacterCount() - 1);
[376]965 /*
966 * Skip the spaces at the end of a line with soft
967 * linebreak
968 */
[176]969 while (hit.getCharIndex() > 0
970 && _text.charAt(_lineOffsets.get(line)
971 + hit.getCharIndex() - 1) == ' ') {
972 hit = _textLayouts.get(line).getNextLeftHit(hit);
973 }
[86]974 }
[176]975 } else if (direction == RIGHT) {
976 if (hit.getInsertionIndex() < _textLayouts.get(line)
977 .getCharacterCount()) {
978 hit = _textLayouts.get(line).getNextRightHit(hit);
[376]979 // Skip whole word if needs be
980 while (wholeWord
981 && hit.getCharIndex() > 0
[176]982 && hit.getCharIndex() < _textLayouts.get(line)
983 .getCharacterCount()
[376]984 && Character.isLetterOrDigit(_text
985 .charAt(_lineOffsets.get(line)
986 + hit.getCharIndex() - 1)))
[176]987 hit = _textLayouts.get(line).getNextRightHit(hit);
988 } else if (line < _textLayouts.size() - 1) {
989 line++;
990 hit = _textLayouts.get(line).getNextLeftHit(1);
[86]991 }
[4]992 }
[176]993 current = _textLayouts.get(line);
[4]994 }
995
[176]996 // move the cursor to the new location
997 float[] caret = current.getCaretInfo(hit);
998 float y = getLineDrop(current) * line;
999
1000 resultPos = new Point2D.Float(getX() + caret[0]
1001 + getJustOffset(current), getY() + y + caret[1]);
1002 break;
[4]1003 }
[176]1004 if (setSelection)
1005 setSelectionEnd(resultPos.x, resultPos.y);
1006 return resultPos;
[4]1007 }
1008
1009 /**
1010 * Iterates through the given line string and returns the position of the
1011 * character being pointed at by the mouse.
1012 *
1013 * @param line
1014 * The index of the _text array of the String to be searched.
1015 * @param mouseX
1016 * The X coordinate of the mouse
1017 * @return The position in the string of the character being pointed at.
1018 */
[108]1019 public TextHitInfo getCharPosition(int line, float mouseX) {
[4]1020 if (line < 0 || line >= _textLayouts.size())
1021 return null;
1022
1023 TextLayout layout = _textLayouts.get(line);
1024 mouseX += getOffset().x;
1025 mouseX -= getJustOffset(layout);
1026
1027 return layout.hitTestChar(mouseX - getX(), 0);
1028 }
1029
[108]1030 public int getLinePosition(float mouseY) {
[4]1031 mouseY += getOffset().y;
1032
[107]1033 float y = getY();
[4]1034
1035 for (TextLayout text : _textLayouts) {
1036 // calculate X to ensure it is in the shape
[107]1037 Rectangle2D bounds = text.getLogicalHighlightShape(0,
1038 text.getCharacterCount()).getBounds2D();
[4]1039
1040 if (bounds.getWidth() < 1)
[108]1041 bounds.setRect(bounds.getMinX(), bounds.getMinY(), 10, bounds
1042 .getHeight());
[4]1043
1044 double x = bounds.getCenterX();
1045
1046 if (bounds.contains(x, mouseY - getY() - (y - getY())))
1047 return _textLayouts.indexOf(text);
1048
1049 // check if the cursor is between lines
1050 if (mouseY - getY() - (y - getY()) < bounds.getMinY())
1051 return Math.max(0, _textLayouts.indexOf(text) - 1);
1052
1053 y += getLineDrop(text);
1054 }
1055
1056 return _textLayouts.size() - 1;
1057 }
1058
1059 /**
1060 * Sets the Font that this text will be displayed with on the screen.
1061 *
1062 * @param font
1063 * The Font to display the Text of this Item in.
1064 */
1065 public void setFont(Font font) {
[130]1066 invalidateAll();
[4]1067 // all decoding occurs in the Utils class
1068 _font = font;
1069 // rejustify();
1070 rebuild(false);
[137]1071
[130]1072 invalidateAll();
[4]1073 }
1074
1075 /**
1076 * Returns the Font that this Text is currently using when painting to the
1077 * screen
1078 *
1079 * @return The Font used to display this Text on the screen.
1080 */
1081 public Font getFont() {
1082 return _font;
1083 }
1084
1085 public Font getPaintFont() {
1086 if (getFont() == null)
1087 return Font.decode(DEFAULT_FONT);
1088
1089 return getFont();
1090 }
1091
1092 public String getFamily() {
1093 return getPaintFont().getFamily();
1094 }
1095
1096 public void setFamily(String newFamily) {
[108]1097 String toDecode = newFamily + "-" + getFontStyle() + "-"
1098 + Math.round(getSize());
[4]1099 setFont(Font.decode(toDecode));
[879]1100
1101 setLetterSpacing(this._letter_spacing);
[4]1102 }
1103
1104 public String getFontStyle() {
1105 Font f = getPaintFont();
1106 String s = "";
[7]1107
[4]1108 if (f.isPlain())
1109 s += "Plain";
1110
1111 if (f.isBold())
1112 s += "Bold";
1113
1114 if (f.isItalic())
1115 s += "Italic";
1116
1117 return s;
1118 }
[7]1119
[72]1120 public static final String MONOSPACED_FONT = "monospaced";
1121
[7]1122 public static final String[] FONT_WHEEL = { "sansserif", "monospaced",
1123 "serif", "dialog", "dialoginput" };
1124
[4]1125 public static final char[] FONT_CHARS = { 's', 'm', 't', 'd', 'i' };
[7]1126
1127 private static final int NEARBY_GRAVITY = 2;
1128
[348]1129 public static final int MINIMUM_FONT_SIZE = 6;
[72]1130
[4]1131 public void toggleFontFamily() {
1132 String fontFamily = getFamily().toLowerCase();
[7]1133 // set it to the first font by default
[4]1134 setFamily(FONT_WHEEL[0]);
[7]1135
[4]1136 for (int i = 0; i < FONT_WHEEL.length - 3; i++) {
[7]1137 if (fontFamily.equals(FONT_WHEEL[i])) {
1138 setFamily(FONT_WHEEL[i + 1]);
[4]1139 break;
1140 }
1141 }
1142 }
[7]1143
[4]1144 public void toggleFontStyle() {
[362]1145 invalidateAll();
[4]1146 Font currentFont = getPaintFont();
1147 if (currentFont.isPlain())
1148 setFont(currentFont.deriveFont(Font.BOLD));
1149 else if (currentFont.isBold() && currentFont.isItalic())
1150 setFont(currentFont.deriveFont(Font.PLAIN));
1151 else if (currentFont.isBold())
1152 setFont(currentFont.deriveFont(Font.ITALIC));
1153 else
1154 setFont(currentFont.deriveFont(Font.ITALIC + Font.BOLD));
[362]1155 rebuild(true);
1156 invalidateAll();
[4]1157 }
1158
[362]1159 public void toggleBold() {
1160 invalidateAll();
1161 Font currentFont = getPaintFont();
1162 int newStyle = currentFont.getStyle();
1163 if (currentFont.isBold()) {
1164 newStyle -= Font.BOLD;
1165 } else {
1166 newStyle += Font.BOLD;
1167 }
1168 setFont(currentFont.deriveFont(newStyle));
1169 rebuild(true);
1170 invalidateAll();
1171 }
1172
1173 public void toggleItalics() {
1174 invalidateAll();
1175 Font currentFont = getPaintFont();
1176 int newStyle = currentFont.getStyle();
1177 if (currentFont.isItalic()) {
1178 newStyle -= Font.ITALIC;
1179 } else {
1180 newStyle += Font.ITALIC;
1181 }
1182 setFont(currentFont.deriveFont(newStyle));
1183 rebuild(true);
1184 invalidateAll();
1185 }
1186
[4]1187 public void setFontStyle(String newFace) {
1188 if (newFace == null || newFace.trim().length() == 0) {
1189 setFont(getPaintFont().deriveFont(Font.PLAIN));
1190 return;
1191 }
1192
1193 newFace = newFace.toLowerCase().trim();
1194
[311]1195 if (newFace.equals("plain") || newFace.equals("p")) {
[4]1196 setFont(getPaintFont().deriveFont(Font.PLAIN));
[311]1197 } else if (newFace.equals("bold") || newFace.equals("b")) {
[4]1198 setFont(getPaintFont().deriveFont(Font.BOLD));
[311]1199 } else if (newFace.equals("italic") || newFace.equals("i")) {
[4]1200 setFont(getPaintFont().deriveFont(Font.ITALIC));
[311]1201 } else if (newFace.equals("bolditalic") || newFace.equals("italicbold")
1202 || newFace.equals("bi") || newFace.equals("ib")) {
[4]1203 setFont(getPaintFont().deriveFont(Font.BOLD + Font.ITALIC));
1204 }
1205
1206 }
1207
1208 /**
1209 * Returns a String array of this Text object's text, split up into separate
1210 * lines.
1211 *
1212 * @return The String array with one element per line of text in this Item.
1213 */
[80]1214 public List<String> getTextList() {
[4]1215 if (_text == null)
1216 return null;
1217 try {
1218 List<String> list = new LinkedList<String>();
1219
[410]1220 // Rebuilding prevents errors when displaying frame bitmaps
1221 if (_lineOffsets.size() == 0) {
1222 rebuild(false);
1223 }
1224
[4]1225 int last = 0;
1226 for (int offset : _lineOffsets) {
1227 if (offset != last) {
1228 list
1229 .add(_text.substring(last, offset).replaceAll("\n",
1230 ""));
1231 }
1232 last = offset;
1233 }
1234
1235 return list;
1236 } catch (Exception e) {
1237 System.out.println(e.getMessage());
1238 return null;
1239 }
1240 }
1241
[80]1242 public String getText() {
[4]1243 return _text.toString();
1244 }
1245
1246 /**
1247 * Returns the first line of text in this Text Item
1248 *
1249 * @return The first line of Text
1250 */
1251 public String getFirstLine() {
1252 if (_text == null || _text.length() == 0)
1253 return null;
1254
[348]1255 // start at the first non-newLine char
1256 int index = 0;
1257 while (_text.charAt(index) == '\n') {
1258 index++;
1259 }
[4]1260
[348]1261 int nextNewLine = _text.indexOf("\n", index);
1262
1263 /* If there are no more newLines return the remaining text */
1264 if (nextNewLine < 0)
1265 return _text.substring(index);
1266
1267 return _text.substring(index, nextNewLine);
[4]1268 }
1269
1270 /**
1271 * Sets the inter-line spacing (in pixels) of this text.
1272 *
1273 * @param spacing
1274 * The number of pixels to allow between each line
1275 */
[659]1276 public void setSpacing(float spacing) {
[4]1277 _spacing = spacing;
1278 updatePolygon();
1279 }
1280
1281 /**
1282 * Returns the inter-line spacing (in pixels) of this Text.
1283 *
1284 * @return The spacing (inter-line) in pixels of this Text.
1285 */
[659]1286 public float getSpacing() {
[4]1287 return _spacing;
1288 }
1289
[105]1290 private float getLineDrop(TextLayout layout) {
[4]1291 if (getSpacing() < 0)
[105]1292 return layout.getAscent() + layout.getDescent()
1293 + layout.getLeading();
[4]1294
[105]1295 return layout.getAscent() + layout.getDescent() + getSpacing();
[4]1296 }
1297
1298 public void setWordSpacing(int spacing) {
1299 _word_spacing = spacing;
1300 }
1301
1302 public int getWordSpacing() {
1303 return _word_spacing;
1304 }
1305
[594]1306 /**
1307 * Sets the spacing (proportional to the font size) between letters
1308 *
1309 * @param spacing
1310 * Additional spacing to add between letters. See {@link java.awt.font.TextAttribute#TRACKING}
1311 */
[659]1312 public void setLetterSpacing(float spacing) {
[4]1313 _letter_spacing = spacing;
[594]1314 HashMap<TextAttribute, Object> attr = new HashMap<TextAttribute, Object>();
1315 attr.put(TextAttribute.TRACKING, spacing);
[659]1316
1317 if (this._font == null) {
1318 this._font = Font.decode(DEFAULT_FONT);
1319 }
1320
1321 this.setFont(this._font.deriveFont(attr));
[4]1322 }
1323
[594]1324 /**
1325 * @return The spacing (proportional to the font size) between letters. See {@link java.awt.font.TextAttribute#TRACKING}
1326 */
[659]1327 public float getLetterSpacing() {
[4]1328 return _letter_spacing;
1329 }
1330
[659]1331 public void setInitialSpacing(float spacing) {
[4]1332 _initial_spacing = spacing;
1333 }
1334
[659]1335 public float getInitialSpacing() {
[4]1336 return _initial_spacing;
1337 }
1338
1339 @Override
[7]1340 public boolean intersects(Polygon p) {
1341 if (super.intersects(p)) {
[107]1342 float textY = getY();
[7]1343
1344 for (TextLayout text : _textLayouts) {
1345 // check left and right of each box
[107]1346 Rectangle2D textOutline = text.getLogicalHighlightShape(0,
1347 text.getCharacterCount()).getBounds2D();
1348 textOutline
1349 .setRect(textOutline.getX() + getX() - 1, textOutline
1350 .getY()
1351 + textY - 1, textOutline.getWidth() + 2,
1352 textOutline.getHeight() + 2);
[7]1353 if (p.intersects(textOutline))
1354 return true;
1355 textY += getLineDrop(text);
1356 }
1357 }
1358 return false;
1359 }
1360
1361 @Override
[4]1362 public boolean contains(int mouseX, int mouseY) {
[7]1363 return contains(mouseX, mouseY, getGravity() * NEARBY_GRAVITY);
1364 }
1365
1366 public boolean contains(int mouseX, int mouseY, int gravity) {
[4]1367 mouseX += getOffset().x;
1368 mouseY += getOffset().y;
1369
[107]1370 float textY = getY();
1371 float textX = getX();
[4]1372
[107]1373 Rectangle2D outline = getPolygon().getBounds2D();
[4]1374
1375 // Check if its outside the top and left and bottom bounds
[107]1376 if (outline.getX() - mouseX > gravity
1377 || outline.getY() - mouseY > gravity
1378 || mouseY - (outline.getY() + outline.getHeight()) > gravity
1379 || mouseX - (outline.getX() + outline.getWidth()) > gravity) {
[4]1380 return false;
1381 }
1382
1383 for (TextLayout text : _textLayouts) {
1384 // check left and right of each box
[107]1385 Rectangle2D textOutline = text.getLogicalHighlightShape(0,
1386 text.getCharacterCount()).getBounds2D();
[4]1387
1388 // check if the cursor is within the top, bottom and within the
1389 // gravity of right
[348]1390 int justOffset = getJustOffset(text);
1391
[107]1392 if (mouseY - textY > textOutline.getY()
1393 && mouseY - textY < textOutline.getY()
1394 + textOutline.getHeight()
[348]1395 && mouseX - textX - justOffset < textOutline.getWidth()
1396 + gravity + Item.MARGIN_RIGHT
1397 /* &&(justOffset == 0 || mouseX > textX + justOffset - gravity ) */)
[4]1398 return true;
1399 textY += getLineDrop(text);
1400 }
1401
1402 return false;
1403 }
1404
1405 /**
1406 * Updates the Polygon (rectangle) that surrounds this Text on the screen.
1407 */
[115]1408 public void updatePolygon() {
[4]1409 // if there is no text, there is nothing to do
1410 if (_text == null)
1411 return;
1412
[906]1413 int preChangeWidth = 0;
1414 if (_poly != null)
1415 preChangeWidth = _poly.getBounds().width;
1416
[4]1417 _poly = new Polygon();
1418
1419 if (_textLayouts.size() < 1)
1420 return;
1421
1422 int minX = Integer.MAX_VALUE;
1423 int maxX = Integer.MIN_VALUE;
1424
1425 int minY = Integer.MAX_VALUE;
1426 int maxY = Integer.MIN_VALUE;
1427
1428 float y = -1;
[504]1429
1430 // Fix concurrency error in ScaleFrameset
1431 List<TextLayout> tmpTextLayouts;
1432 synchronized(_textLayouts) {
1433 tmpTextLayouts = new LinkedList<TextLayout>(_textLayouts);
1434 }
[4]1435
[504]1436 for (TextLayout layout : tmpTextLayouts) {
1437 Rectangle2D bounds = layout.getLogicalHighlightShape(0,
1438 layout.getCharacterCount()).getBounds2D();
1439
1440 if (y < 0)
1441 y = 0;
1442 else
1443 y += getLineDrop(layout);
[4]1444
[504]1445 maxX = Math.max(maxX, (int) bounds.getMaxX());
1446 minX = Math.min(minX, (int) bounds.getMinX());
1447 maxY = Math.max(maxY, (int) (bounds.getMaxY() + y));
1448 minY = Math.min(minY, (int) (bounds.getMinY() + y));
[4]1449 }
1450
[72]1451 minX -= getLeftMargin();
1452 maxX += Item.MARGIN_RIGHT;
[4]1453
[348]1454 // If its justification right or center then DONT limit the width
1455 if (getJustification() != null) {
[400]1456 maxX = Item.MARGIN_RIGHT + getAbsoluteWidth();
[348]1457 }
1458
[4]1459 _poly.addPoint(minX - getGravity(), minY - getGravity());
1460 _poly.addPoint(maxX + getGravity(), minY - getGravity());
1461 _poly.addPoint(maxX + getGravity(), maxY + getGravity());
1462 _poly.addPoint(minX - getGravity(), maxY + getGravity());
[108]1463
1464 _poly.translate(getX(), getY());
[906]1465
1466 if(preChangeWidth != 0 && preChangeWidth != _poly.getBounds().width)
1467 if(_poly.getBounds().width > preChangeWidth) MagneticConstraints.getInstance().textGrown(this, _poly.getBounds().width - preChangeWidth);
1468 else MagneticConstraints.getInstance().textShrunk(this, preChangeWidth - _poly.getBounds().width);
[72]1469 }
[4]1470
1471 // TODO it seems like this method has some exponencial processing which
1472 // makes items copy really slowly when there are lots of lines of text!
1473 // This needs to be fixed!!
[669]1474 public void rebuild(boolean limitWidth) {
[199]1475 rebuild(limitWidth, true);
1476 }
1477
1478 /**
1479 *
1480 * @param limitWidth
1481 * @param newLinebreakerAlways
1482 * true if a new line breaker should always be created.
1483 */
1484 private void rebuild(boolean limitWidth, boolean newLinebreakerAlways) {
[156]1485 // TODO make this more efficient so it only clears annotation list when
1486 // it really has to
1487 if (isAnnotation()) {
1488 Frame parent = getParent();
[176]1489 // parent can be null when running tests
[156]1490 if (parent != null) {
1491 parent.clearAnnotations();
1492 }
1493 }
1494
[4]1495 // if there is no text, there is nothing to do
[137]1496 if (_text == null || _text.length() == 0) {
1497 // Frame parent = getParent();
1498 // if(parent != null)
1499 // parent.removeItem(this);
[4]1500 return;
[108]1501 }
[137]1502
[199]1503 if (_lineBreaker == null || newLinebreakerAlways) {
1504 AttributedString paragraphText = new AttributedString(_text
1505 .toString());
1506 paragraphText.addAttribute(TextAttribute.FONT, getPaintFont());
[615]1507 frc = new FontRenderContext(null, true, true);
1508 _lineBreaker = new LineBreakMeasurer(paragraphText.getIterator(), frc);
[199]1509 }
[4]1510
[613]1511 /* float width = Float.MAX_VALUE;
[4]1512
1513 if (limitWidth) {
[400]1514 width = getAbsoluteWidth();
[147]1515 // else if (getMaxWidth() > 0)
1516 // width = Math.max(50, getMaxWidth() - getX()
1517 // - Item.MARGIN_RIGHT);
[613]1518 } */
[4]1519
1520 _textLayouts.clear();
1521 _lineOffsets.clear();
1522 // the first line always has a 0 offset
1523 _lineOffsets.add(0);
1524
1525 TextLayout layout;
[613]1526
1527 float width;
1528 float lineHeight = Float.NaN;
1529 List<Point[]> lines = null;
1530
[655]1531 if(_autoWrap || ExperimentalFeatures.AutoWrap.get()) {
[613]1532 lines = new LinkedList<Point[]>();
1533 if(DisplayIO.getCurrentFrame() == null) {
1534 return;
1535 }
1536 for(Item item : DisplayIO.getCurrentFrame().getItems()) {
1537 if(item instanceof Line) {
1538 lines.add(new Point[] { ((Line) item).getStartItem().getPosition(), ((Line) item).getEndItem().getPosition() });
1539 }
1540 if(item instanceof Picture) {
1541 lines.add(new Point[] { item.getPosition(), new Point(item.getX(), item.getY() + item.getHeight()) });
1542 }
1543 }
1544 for(Item item : FreeItems.getInstance()) {
1545 if(item instanceof Line) {
1546 lines.add(new Point[] { ((Line) item).getStartItem().getPosition(), ((Line) item).getEndItem().getPosition() });
1547 }
1548 if(item instanceof Picture) {
1549 lines.add(new Point[] { item.getPosition(), new Point(item.getX(), item.getY() + item.getHeight()) });
1550 }
1551 }
1552 width = getLineWidth(getX(), getY(), lines);
1553 } else {
1554 width = Float.MAX_VALUE;
1555 if (limitWidth) {
[776]1556 if(_maxWidth == null) {
1557 width = FrameGraphics.getMaxFrameSize().width - getX();
1558 } else {
1559 width = getAbsoluteWidth();
1560 }
[613]1561 // else if (getMaxWidth() > 0)
1562 // width = Math.max(50, getMaxWidth() - getX()
1563 // - Item.MARGIN_RIGHT);
1564 }
1565 }
1566
[4]1567 _lineBreaker.setPosition(0);
[635]1568 boolean requireNextWord = false;
[4]1569
1570 // --- Get the output of the LineBreakMeasurer and store it in a
[615]1571 while (_lineBreaker.getPosition() < _text.length()) {
[613]1572
[655]1573 if(_autoWrap || ExperimentalFeatures.AutoWrap.get()) {
[635]1574 requireNextWord = width < FrameGraphics.getMaxFrameSize().width - getX();
1575 }
[615]1576
[635]1577 layout = _lineBreaker.nextLayout(width, _text.length(), requireNextWord);
1578
[290]1579 // lineBreaker does not break on newline
[7]1580 // characters so they have to be check manually
[4]1581 int start = _lineOffsets.get(_lineOffsets.size() - 1);
[613]1582
1583 // int y = getY() + (getLineDrop(layout) * (_lineOffsets.size() - 1)
1584
[4]1585 // check through the current line for newline characters
1586 for (int i = start + 1; i < _text.length(); i++) {
1587 if (_text.charAt(i) == '\n') {// || c == '\t'){
1588 _lineBreaker.setPosition(start);
[635]1589 layout = _lineBreaker.nextLayout(width, i, requireNextWord);
[4]1590 break;
1591 }
1592 }
1593
1594 _lineOffsets.add(_lineBreaker.getPosition());
[615]1595
1596 if(layout == null) {
1597 layout = new TextLayout(" ", getPaintFont(), frc);
1598 }
[4]1599
[400]1600 if (/* hasWidth() && */getJustification() == Justification.full
[4]1601 && _lineBreaker.getPosition() < _text.length())
1602 layout = layout.getJustifiedLayout(width);
[615]1603
[4]1604 _textLayouts.add(layout);
[615]1605
[655]1606 if(_autoWrap || ExperimentalFeatures.AutoWrap.get()) {
[615]1607
[613]1608 if(lineHeight != Float.NaN) {
1609 lineHeight = getLineDrop(layout);
1610 }
[615]1611 width = getLineWidth(getX(), getY() + (lineHeight * (_textLayouts.size() - 1)), lines);
[613]1612 }
[4]1613 }
1614
1615 updatePolygon();
[137]1616
[4]1617 }
[613]1618
1619 private float getLineWidth(int x, float y, List<Point[]> lines) {
1620 float width = FrameGraphics.getMaxFrameSize().width;
1621 for(Point[] l : lines) {
1622 // check for lines that cross over our y
1623 if((l[0].y >= y && l[1].y <= y) || (l[0].y <= y && l[1].y >= y)) {
1624 float dX = l[0].x - l[1].x;
1625 float dY = l[0].y - l[1].y;
1626 float newWidth;
1627 if(dX == 0) {
1628 newWidth = l[0].x;
1629 } else if(dY == 0) {
1630 newWidth = Math.min(l[0].x, l[1].x);
1631 } else {
1632 // System.out.print("gradient: " + (dY / dX));
1633 newWidth = l[0].x + (y - l[0].y) * dX / dY;
1634 }
1635 // System.out.println("dY:" + dY + " dX:" + dX + " width:" + newWidth);
1636 if(newWidth < x) {
1637 continue;
1638 }
1639 if(newWidth < width) {
1640 width = newWidth;
1641 }
1642 }
1643 }
1644 return width - x;
1645 }
[4]1646
[400]1647 private boolean hasFixedWidth() {
1648 assert (_maxWidth != null);
[404]1649 if (_maxWidth == null) {
[400]1650 justify(false);
1651 }
1652 return _maxWidth > 0;
1653 }
1654
[4]1655 private int _alpha = -1;
1656
1657 public void setAlpha(int alpha) {
1658 _alpha = alpha;
1659 }
1660
1661 private Point getSelectedRange(int line) {
1662 if (_selectionEnd >= _text.length()) {
1663 _selectionEnd = _text.length();
1664 }
1665
1666 if (_selectionStart < 0)
1667 _selectionStart = 0;
1668
1669 if (_selectionStart < 0 || _selectionEnd < 0)
1670 return null;
1671
1672 int selectionLeft = Math.min(_selectionStart, _selectionEnd);
1673 int selectionRight = Math.max(_selectionStart, _selectionEnd);
1674
1675 // if the selection is after this line, return null
1676 if (_lineOffsets.get(line) > selectionRight)
1677 return null;
1678
1679 // if the selection is before this line, return null
1680 if (_lineOffsets.get(line) < selectionLeft
1681 && _lineOffsets.get(line)
1682 + _textLayouts.get(line).getCharacterCount() < selectionLeft)
1683 return null;
1684
1685 // Dont highlight a single char
1686 // if (selectionRight - selectionLeft <= MINIMUM_RANGED_CHARS)
1687 // return null;
1688
1689 // the selection occurs on this line, determine where it lies on the
1690 // line
1691 int start = Math.max(0, selectionLeft - _lineOffsets.get(line));
1692 // int end = Math.min(_lineOffsets.get(line) +
1693 // _textLayouts.get(line).getCharacterCount(), _selectionEnd);
1694 int end = Math.min(selectionRight - _lineOffsets.get(line),
1695 +_textLayouts.get(line).getCharacterCount());
1696
1697 // System.out.println(line + ": " + start + "x" + end + " (" +
1698 // _selectionStart + "x" + _selectionEnd + ")");
1699 return new Point(start, end);
1700 }
[242]1701
[229]1702 /**
1703 * @param mouseButton
[242]1704 * Either MouseEvent.BUTTON1, MouseEvent.BUTTON2 or
1705 * MouseEvent.BUTTON3.
1706 *
1707 * @return The color for the text selection based on the given mouse click
[229]1708 */
1709 protected Color getSelectionColor(int mouseButton) {
[4]1710
[229]1711 /*
1712 * Color main = getPaintColor(); Color back = getPaintBackgroundColor();
1713 *
1714 * if (Math.abs(main.getRed() - back.getRed()) < 10 &&
1715 * Math.abs(main.getGreen() - back.getGreen()) < 10 &&
1716 * Math.abs(main.getBlue() - back.getBlue()) < 10) { selection = new
1717 * Color(Math.abs(255 - main.getRed()), Math .abs(255 -
1718 * main.getGreen()), Math.abs(255 - main.getBlue())); } else { selection =
1719 * new Color((main.getRed() + (back.getRed() * 2)) / 3, (main.getGreen() +
1720 * (back.getGreen() * 2)) / 3, (main .getBlue() + (back.getBlue() * 2)) /
1721 * 3); }
1722 */
1723 int green = 160;
1724 int red = 160;
1725 int blue = 160;
1726
1727 if (FrameMouseActions.wasDeleteClicked()) {
1728 green = 235;
1729 red = 235;
1730 blue = 140;
1731 } else if (mouseButton == MouseEvent.BUTTON1) {
1732 red = 255;
1733 } else if (mouseButton == MouseEvent.BUTTON2) {
1734 green = 255;
1735 } else if (mouseButton == MouseEvent.BUTTON3) {
1736 blue = 255;
1737 }
[242]1738
[229]1739 return new Color(red, green, blue);
1740 }
1741
[4]1742 @Override
1743 public void paint(Graphics2D g) {
[108]1744 if (!isVisible())
1745 return;
1746
[4]1747 // if there is no text to paint, do nothing.
[147]1748 if (_text == null || _text.length() == 0)
[4]1749 return;
[613]1750
[655]1751 if(_autoWrap || ExperimentalFeatures.AutoWrap.get()) {
[613]1752 invalidateAll();
1753
[4]1754 rebuild(true);
[776]1755 } else if (_textLayouts.size() < 1) {
[663]1756 clipFrameMargin();
[613]1757 rebuild(true);
[410]1758 // return;
[4]1759 }
[137]1760
1761 // check if its a vector item and paint all the vector stuff too if this
1762 // item is a free item
1763 // This will allow for dragging vectors around the place!
1764 if (hasVector() && isFloating()) {
1765 FrameGraphics.requestRefresh(false);
1766 // TODO make this use a more efficient paint method...
1767 // Have the text item return a bigger repaint area if it has an
1768 // associated vector
1769 }
1770
[4]1771 // the background is only cleared if required
1772 if (getBackgroundColor() != null) {
[86]1773 Color bgc = getBackgroundColor();
[278]1774 if (_alpha > 0) {
[86]1775 bgc = new Color(bgc.getRed(), bgc.getGreen(), bgc.getBlue(),
1776 _alpha);
[247]1777 }
[86]1778 g.setColor(bgc);
[278]1779
[247]1780 Color gradientColor = getGradientColor();
1781 if (gradientColor != null) {
1782 // The painting is not efficient enough for gradients...
1783 Shape s = getPolygon();
1784 if (s != null) {
1785 Rectangle b = s.getBounds();
1786 GradientPaint gp = new GradientPaint(
1787 (int) (b.x + b.width * 0.3), b.y, bgc,
1788 (int) (b.x + b.width * 1.3), b.y, gradientColor);
1789 g.setPaint(gp);
1790 }
1791 }
[278]1792
[247]1793 g.fillPolygon(getPolygon());
[4]1794 }
[247]1795
[242]1796 if (hasVisibleBorder()) {
1797 g.setColor(getPaintBorderColor());
1798 Stroke borderStroke = new BasicStroke(getThickness(), CAP, JOIN);
1799 g.setStroke(borderStroke);
1800 g.drawPolygon(getPolygon());
1801 }
[247]1802
[282]1803 if (hasFormula()) {
1804 g.setColor(getPaintHighlightColor());
[289]1805 Stroke highlightStroke = new BasicStroke(1F, CAP, JOIN);
[282]1806 g.setStroke(highlightStroke);
1807
1808 Point2D.Float start = getEdgePosition(0, true);
1809 Point2D.Float end = getEdgePosition(0, false);
1810 g.drawLine(Math.round(start.x), Math.round(start.y), Math
1811 .round(end.x), Math.round(end.y));
1812 }
1813
[108]1814 if (isHighlighted()) {
1815 g.setColor(getPaintHighlightColor());
[214]1816 Stroke highlightStroke = new BasicStroke(
[376]1817 (float) getHighlightThickness(), CAP, JOIN);
[214]1818 g.setStroke(highlightStroke);
[115]1819 if (HighlightMode.Enclosed.equals(getHighlightMode()))
[108]1820 g.fillPolygon(getPolygon());
1821 else
1822 g.drawPolygon(getPolygon());
1823 }
1824
[4]1825 float y = getY();
1826 Color c = getPaintColor();
1827 if (_alpha > 0)
1828 c = new Color(c.getRed(), c.getGreen(), c.getBlue(), _alpha);
1829
1830 g.setColor(c);
1831
[242]1832 Color selection = getSelectionColor(FrameMouseActions
1833 .getLastMouseButton());
[4]1834
1835 // width -= getX();
1836 // int line = 0;
1837 // boolean tab = false;
1838 synchronized (_textLayouts) {
1839 for (int i = 0; i < _textLayouts.size(); i++) {
1840 TextLayout layout = _textLayouts.get(i);
1841
1842 Point p = getSelectedRange(i);
1843 if (p != null) {
1844 AffineTransform at = new AffineTransform();
1845 AffineTransform orig = g.getTransform();
1846 at.translate(getX() + getJustOffset(layout), y);
1847 g.setTransform(at);
1848
1849 g.setColor(selection);
1850 g.fill(layout.getLogicalHighlightShape(p.x, p.y));
1851
1852 g.setTransform(orig);
1853 g.setColor(c);
1854 }
1855
[905]1856 int ldx = 1+getX()+getJustOffset(layout); // Layout draw x
1857
1858 boolean debug = false;
1859 if (debug) {
1860 g.setColor(new Color(c.getRed(),c.getGreen(),c.getBlue(),40));
1861 Rectangle layout_rect = layout.getPixelBounds(null, ldx, y);
1862 g.fillRect(layout_rect.x, layout_rect.y, layout_rect.width, layout_rect.height);
1863 g.setColor(c);
1864 }
1865
1866
1867 layout.draw(g, ldx, y);
[4]1868
1869 /*
1870 * AffineTransform at = new AffineTransform(); AffineTransform
1871 * orig = g.getTransform(); at.translate(getX() +
1872 * getJustOffset(layout), y); g.setTransform(at);
1873 * g.draw(layout.getLogicalHighlightShape(0,
1874 * layout.getCharacterCount())); g.setTransform(orig); /*
1875 * if(_text.charAt(_lineOffsets.get(line) +
1876 * (layout.getCharacterCount() - 1)) == '\t'){ tab = true; x =
1877 * (int) (getX() + x + 20 + layout.getVisibleAdvance()); }else{
1878 */
1879 y += getLineDrop(layout);
1880 /*
1881 * tab = false; }
1882 *
1883 * line++;
1884 */
1885 }
1886 }
1887
[78]1888 paintLink(g);
[4]1889 }
[137]1890
[130]1891 @Override
1892 protected Rectangle getLinkDrawArea() { // TODO: Revise
1893 return getDrawingArea()[0];
1894 }
[4]1895
1896 /**
1897 * Determines if this text has any text in it.
1898 *
1899 * @return True if this Item has no text in it, false otherwise.
1900 */
1901 public boolean isEmpty() {
1902 if (_text == null || _text.length() == 0)
1903 return true;
1904
1905 return false;
1906 }
1907
1908 @Override
1909 public Text copy() {
1910 Text copy = new Text(getID());
1911 // copy standard item values
1912 Item.DuplicateItem(this, copy);
[98]1913
[4]1914 // copy values specific to text items
1915 copy.setSpacing(getSpacing());
1916 copy.setInitialSpacing(getInitialSpacing());
[400]1917
[410]1918 copy.setWidth(getWidthToSave());
[4]1919 copy.setJustification(getJustification());
1920 copy.setLetterSpacing(getLetterSpacing());
1921 copy.setWordSpacing(getWordSpacing());
[410]1922 copy.setWidth(getWidthToSave());
[4]1923 copy.setFont(getFont());
[376]1924 if (hasFormula()) {
1925 copy.calculate(getFormula());
1926 } else {
1927 copy.setText(_text.toString());
1928 }
[108]1929 copy.setHidden(!isVisible());
[4]1930 return copy;
1931 }
1932
1933 @Override
[108]1934 public float getSize() {
1935 return getPaintFont().getSize2D();
[4]1936 }
1937
1938 /**
1939 * Returns the number of characters in this Text, excluding new lines.
1940 *
1941 * @return The sum of the length of each line of text
1942 */
1943 public int getLength() {
1944 return _text.length();
1945 }
1946
1947 @Override
[108]1948 public void setSize(float size) {
[130]1949 invalidateAll();
[311]1950 // size *= UserSettings.ScaleFactor;
[80]1951 // Dont want to have size set when duplicating a point which has size 0
1952 if (size < 0)
1953 return;
1954
1955 if (size < MINIMUM_FONT_SIZE)
1956 size = MINIMUM_FONT_SIZE;
[108]1957 setFont(getPaintFont().deriveFont(size));
[348]1958 rebuild(true);
[130]1959 invalidateAll();
[4]1960 }
1961
1962 @Override
1963 public void setAnnotation(boolean val) {
[108]1964 float mouseX = DisplayIO.getFloatMouseX();
1965 float mouseY = FrameMouseActions.MouseY;
1966 Point2D.Float newPoint = new Point2D.Float();
[4]1967 if (val) {
1968 // if this is already an annotation, do nothing
1969 if (isAnnotation())
1970 return;
[137]1971 if (!isLineEnd() && _text.length() > 0
1972 && _text.charAt(0) == DEFAULT_BULLET) {
[108]1973 newPoint.setLocation(insertText(""
1974 + (char) KeyEvent.VK_BACK_SPACE, mouseX, mouseY, 1));
[80]1975 if (_text.length() > 0 && _text.charAt(0) == ' ')
[108]1976 newPoint.setLocation(insertText(""
1977 + (char) KeyEvent.VK_BACK_SPACE, newPoint.x,
1978 newPoint.y, 1));
[80]1979 } else {
[108]1980 newPoint.setLocation(insertText("@", mouseX, mouseY, 0));
[4]1981 }
1982 } else {
1983 // if this is not an annotation, do nothing
1984 if (!isAnnotation())
1985 return;
[108]1986 if (!isLineEnd() && _text.charAt(0) == '@') {
1987 newPoint.setLocation(insertText(""
1988 + (char) KeyEvent.VK_BACK_SPACE, mouseX, mouseY, 1));
[137]1989 newPoint.setLocation(insertText(DEFAULT_BULLET_STRING,
1990 newPoint.x, newPoint.y, 0));
[80]1991 } else {
[108]1992 newPoint.setLocation(insertText(""
1993 + (char) KeyEvent.VK_BACK_SPACE, mouseX, mouseY, 1));
[80]1994 }
1995 }
[247]1996 FrameUtils.setLastEdited(this);
[242]1997 rebuild(true);
[108]1998 DisplayIO.setCursorPosition(newPoint.x, newPoint.y, false);
[80]1999 }
2000
2001 /**
2002 *
2003 */
2004 private void insertString(String toInsert, int pos) {
2005 assert (toInsert.length() > 0);
2006
2007 _text.insert(pos, toInsert);
2008
2009 if (toInsert.length() > 1) {
2010 _lineBreaker = null;
2011 }
2012
2013 if (_lineBreaker != null) {
2014 AttributedString inserting = new AttributedString(_text.toString());
2015 inserting.addAttribute(TextAttribute.FONT, getPaintFont());
2016 _lineBreaker.insertChar(inserting.getIterator(), pos);
2017 }
2018 }
2019
[108]2020 private void deleteChar(int pos) {
[80]2021 _text.deleteCharAt(pos);
2022
2023 if (_text.length() == 0) {
[108]2024 if (this.isLineEnd()) {
[72]2025 // Remove and replace with a dot
2026 FrameKeyboardActions.replaceText(this);
[108]2027 DisplayIO.setCursorPosition(this._x, this._y);
[72]2028 }
[80]2029 return;
2030 }
[72]2031
[80]2032 if (_lineBreaker != null) {
2033 AttributedString inserting = new AttributedString(_text.toString());
2034 inserting.addAttribute(TextAttribute.FONT, getPaintFont());
2035 _lineBreaker.deleteChar(inserting.getIterator(), pos);
[4]2036 }
[137]2037
[4]2038 }
2039
2040 @Override
2041 public boolean isAnnotation() {
[72]2042 if (_text != null && _text.length() > 0 && _text.charAt(0) == '@')
[4]2043 return true;
2044
2045 return false;
2046 }
2047
2048 public boolean isSpecialAnnotation() {
2049 assert _text != null;
2050 String s = _text.toString().toLowerCase();
2051 if (s.length() > 0 && s.indexOf("@") == 0) {
2052 if (s.equals("@old") || s.equals("@ao")
2053 || s.equals("@itemtemplate") || s.equals("@parent")
[282]2054 || s.equals("@next") || s.equals("@previous")
2055 || s.equals("@first") || s.equals("@i") || s.equals("@iw")
[4]2056 || s.equals("@f"))
2057 return true;
2058 }
2059
2060 return false;
2061 }
2062
2063 @Override
2064 public Item merge(Item merger, int mouseX, int mouseY) {
[50]2065 if (merger.isLineEnd()) {
[98]2066 // Merging line ends onto non line end text is a no-op
2067 if (!isLineEnd())
[87]2068 return null;
[98]2069
[50]2070 if (merger instanceof Text)
[80]2071 insertText(((Text) merger).getText(), mouseX, mouseY);
[71]2072
2073 // Set the position by moving the cursor before calling this
2074 // method!!
2075
[4]2076 List<Line> lines = new LinkedList<Line>();
[50]2077 lines.addAll(merger.getLines());
[71]2078 for (Line line : lines) {
[97]2079 line.replaceLineEnd(merger, this);
[50]2080 }
2081 merger.delete();
[71]2082 this.setOffset(0, 0);
[4]2083 return null;
2084 }
2085
2086 if (!(merger instanceof Text))
2087 return merger;
2088
2089 Text merge = (Text) merger;
2090
2091 // insertText(merge.getText(), mouseX, mouseY);
2092
2093 // if the item being merged has a link
2094 if (merge.getLink() != null) {
2095 // if this item has a link, keep it on the cursor
2096 if (getLink() != null) {
2097 merge.setText(merge.getLink());
2098 merge.setLink(null);
2099 // return merge;
2100 // TODO get this to return the merged item and attach it to the
2101 // cursor only when the user presses the middle button.
2102 } else
2103 setLink(merge.getLink());
2104 }
2105
2106 return null;
2107 }
2108
2109 /**
2110 * Resets the position of the item to the default position for a title item.
2111 *
2112 */
2113 public void resetTitlePosition() {
[71]2114 setPosition(MARGIN_LEFT, MARGIN_LEFT + getBoundsHeight());
[371]2115 Frame modelFrame = getParentOrCurrentFrame();
[400]2116 if (modelFrame != null) {
2117 setRightMargin(modelFrame.getNameItem().getX() - MARGIN_LEFT, true);
[371]2118 } else {
[400]2119 System.out
2120 .print("Error: text.resetTitlePosition, getParent or currentFrame returned null");
2121 setRightMargin(MARGIN_LEFT, true);
[371]2122 }
[4]2123 }
2124
[80]2125 /**
2126 * Removes the set of characters up to the first space in this text item.
2127 *
2128 * @return the string that was removed.
2129 */
[4]2130 public String stripFirstWord() {
2131 int firstSpace = _text.toString().indexOf(' ');
2132
2133 // if there is only one word just make it blank
2134 if (firstSpace < 0 || firstSpace + 1 >= _text.length()) {
2135 String text = _text.toString();
2136 setText("");
2137 return text;
2138 }
2139
2140 String firstWord = _text.toString().substring(0, firstSpace);
[80]2141 setText(_text.toString().substring(firstSpace).trim());
[4]2142
2143 return firstWord;
2144 }
2145
2146 public String toString() {
2147 String message = "[" + getFirstLine() + "]" + FRAME_NAME_SEPARATOR;
2148
2149 if (getParent() != null)
[80]2150 return message + getParent().getName();
[4]2151 return message + getDateCreated();
2152 }
2153
2154 public Text getTemplateForm() {
2155 Text template = this.copy();
2156 template.setID(-1);
[776]2157 // reset width of global templates so the widths of the items on the settings frames don't cause issues
2158 // this is in response to the fact that FrameCreator.addItem() sets rightMargin when it adds items
2159 template.setWidth(null);
[376]2160 /*
2161 * The template must have text otherwise the bounds height will be
2162 * zero!! This will stop escape drop down from working if there is no
2163 * item template
2164 */
[4]2165 template.setText("@");
2166 return template;
2167 }
[7]2168
2169 @Override
2170 public boolean isNear(int x, int y) {
2171 if (super.isNear(x, y)) {
2172 // TODO check that it is actually near one of the lines of space
2173 // return contains(x, y, getGravity() * 2 + NEAR_DISTANCE);
2174 // at the moment contains ignores gravity when checking the top and
2175 // bottom of text lines... so the cursor must be between two text
2176 // lines
[107]2177 float textY = getY();
2178 float textX = getX();
[7]2179
2180 for (TextLayout text : _textLayouts) {
2181 // check left and right of each box
[107]2182 Rectangle2D textOutline = text.getLogicalHighlightShape(0,
2183 text.getCharacterCount()).getBounds2D();
[13]2184
[7]2185 // check if the cursor is within the top, bottom and within the
2186 // gravity of right
[107]2187 if (y - textY > textOutline.getY() - NEAR_DISTANCE
2188 && y - textY < textOutline.getY()
2189 + textOutline.getHeight() + NEAR_DISTANCE
2190 && x - textX < textOutline.getWidth() + NEAR_DISTANCE)
[7]2191 return true;
2192 textY += getLineDrop(text);
2193 }
2194 }
2195 return false;
2196 }
[13]2197
[7]2198 @Override
[50]2199 public void anchor() {
2200 super.anchor();
2201 // ensure all text items have their selection cleared
2202 clearSelection();
2203 setAlpha(0);
[156]2204 if (isLineEnd())
2205 DisplayIO.setCursor(Item.DEFAULT_CURSOR);
[400]2206
[421]2207 String text = _text.toString().trim();
2208
[663]2209 clipFrameMargin();
2210
2211 // Show the overlay stuff immediately if this is an overlay item
2212 if (hasLink() && (text.startsWith("@ao") || text.startsWith("@o"))) {
2213 FrameKeyboardActions.Refresh();
2214 }
2215 }
2216
2217 private void clipFrameMargin() {
[400]2218 if (!hasFixedWidth()) {
2219 int frameWidth = FrameGraphics.getMaxFrameSize().width;
2220 /*
2221 * Only change width if it is more than 150 pixels from the right of
2222 * the screen
2223 */
[663]2224 if (!_text.toString().contains(" ")) {
[421]2225 Integer width = getWidth();
2226 if (width == null || width < 0)
2227 setWidth(Integer.MIN_VALUE + 1);
2228 } else if (frameWidth - getX() > ADJUST_WIDTH_THRESHOLD) {
[400]2229 justify(false);
[404]2230 // setRightMargin(frameWidth, false);
[400]2231 }
2232 }
[50]2233 }
[404]2234
[400]2235 public void justify(boolean fixWidth) {
[613]2236
2237 // if autowrap is on, wrapping is done every time we draw
[655]2238 if(ExperimentalFeatures.AutoWrap.get()) {
[613]2239 return;
2240 }
2241
[400]2242 Integer width = FrameGraphics.getMaxFrameSize().width;
[71]2243
[400]2244 // Check if that text item is inside an enclosing rectangle...
2245 // Set its max width accordingly
2246 Polygon enclosure = FrameUtils.getEnlosingPolygon();
2247 if (enclosure != null) {
2248 Rectangle bounds = enclosure.getBounds();
[404]2249 if (bounds.width > 200 && getX() < bounds.width / 3 + bounds.x) {
[400]2250 width = bounds.x + bounds.width;
2251 }
2252 }
2253
2254 if (getWidth() == null)
2255 setRightMargin(width, fixWidth);
2256
2257 // Check for the annotation that restricts the width of text items
2258 // on the frame
2259 String widthString;
2260 if ((widthString = getParentOrCurrentFrame().getAnnotationValue(
2261 "maxwidth")) != null) {
2262 try {
2263 int oldWidth = getWidth();
2264 int maxWidth = Integer.parseInt(widthString);
2265 if (maxWidth < oldWidth)
2266 setWidth(maxWidth);
2267 } catch (NumberFormatException nfe) {
2268 }
2269
2270 }
2271 }
2272
[71]2273 public void resetFrameNamePosition() {
[80]2274 Dimension maxSize = FrameGraphics.getMaxFrameSize();
2275 if (maxSize != null) {
[147]2276 // setMaxWidth(maxSize.width);
[80]2277 setPosition(maxSize.width - getBoundsWidth(), getBoundsHeight());
2278 }
[71]2279 }
[80]2280
[78]2281 @Override
[80]2282 protected int getLinkYOffset() {
[137]2283 if (_textLayouts.size() == 0)
2284 return 0;
[78]2285 return Math.round(-(_textLayouts.get(0).getAscent() / 2));
2286 }
[80]2287
2288 @Override
2289 public String getName() {
2290 return getFirstLine();
2291 }
2292
2293 public static final String TAB_STRING = " ";
2294
[108]2295 public Point2D.Float insertTab(char ch, float mouseX, float mouseY) {
[80]2296 return insertText("" + ch, mouseX, mouseY);
2297 }
2298
[108]2299 public Point2D.Float removeTab(char ch, float mouseX, float mouseY) {
[80]2300 // Insert a space as a flag that it is a backwards tab
2301 return insertText(ch + " ", mouseX, mouseY);
2302 }
[108]2303
[115]2304 public static boolean isBulletChar(char c) {
[137]2305 for (int i = 0; i < BULLETS.length; i++) {
2306 if (BULLETS[i] == c)
[115]2307 return true;
2308 }
[137]2309 return c == '*' || c == '+' || c == '>' || c == '-' || c == 'o';
[115]2310 }
[137]2311
[116]2312 public boolean hasOverlay() {
[137]2313 if (!isAnnotation() || getLink() == null)
[116]2314 return false;
[145]2315 String text = getText().toLowerCase();
[147]2316 // TODO make it so can just check the _overlay variable
[156]2317 // Mike cant remember the reason _overlay var cant be use! opps
2318 if (!text.startsWith("@"))
[154]2319 return false;
[145]2320 return text.startsWith("@o") || text.startsWith("@ao")
[154]2321 || text.startsWith("@v") || text.startsWith("@av");
[116]2322 }
[131]2323
2324 public boolean hasSelection() {
2325 return getSelectionSize() > 0;
2326 }
[143]2327
2328 /**
2329 * Dont save text items that are all white space.
2330 */
2331 @Override
2332 public boolean dontSave() {
2333 String text = getText();
2334 assert (text != null);
2335 return text.trim().length() == 0 || super.dontSave();
2336 }
[147]2337
[176]2338 @Override
[291]2339 public boolean calculate(String formula) {
[199]2340 if (FrameGraphics.isXRayMode())
[291]2341 return false;
[199]2342
[176]2343 super.calculate(formula);
[242]2344 if (isFloating() || formula == null || formula.length() == 0) {
[291]2345 return false;
[176]2346 }
[376]2347 formula = formula.replace(':', '=');
2348
[196]2349 String lowercaseFormula = formula.toLowerCase();
[176]2350 ExpediteeJEP myParser = new ExpediteeJEP();
[278]2351
[247]2352 int nextVarNo = 1;
[278]2353
[242]2354 // Add variables from the containing rectangle if the item being
2355 // calculated is inside the enclosing rectangle
2356 Collection<Item> enclosed = getItemsInSameEnclosure();
2357 for (Item i : enclosed) {
[278]2358 if (i == this)
[247]2359 continue;
2360 if (i instanceof Text && !i.isAnnotation()) {
[376]2361 AttributeValuePair pair = i.getAttributeValuePair();
[242]2362 if (pair.hasPair()) {
2363 try {
[278]2364 double value = pair.getDoubleValue();
[247]2365 myParser.addVariable(pair.getAttribute(), value);
[278]2366 // myParser.addVariable("$" + nextVarNo++, value);
[247]2367 } catch (NumberFormatException nfe) {
2368 continue;
[242]2369 } catch (Exception e) {
2370 e.printStackTrace();
2371 }
[282]2372 } // else {
2373 // Add anonomous vars
2374 try {
2375 double value = pair.getDoubleValue();
[376]2376 if (value != Double.NaN)
2377 myParser.addVariable("$" + nextVarNo++, value);
[282]2378 } catch (NumberFormatException nfe) {
2379 continue;
2380 } catch (Exception e) {
2381 e.printStackTrace();
[242]2382 }
[282]2383 // }
[242]2384 }
2385 }
2386
2387 // Add the variables from this frame
[176]2388 myParser.addVariables(this.getParentOrCurrentFrame());
2389 String linkedFrame = getAbsoluteLink();
[199]2390 // Add the relative frame variable if the item is linked
[176]2391 if (linkedFrame != null) {
[196]2392 Frame frame = FrameIO.LoadFrame(linkedFrame);
2393 myParser.addVariables(frame);
[199]2394 // If the frame is linked add vector variable for the frame
2395 if (lowercaseFormula.contains("$frame")) {
2396 myParser.addVectorVariable(frame.getNonAnnotationItems(true),
2397 "$frame");
[196]2398 }
[176]2399 }
[199]2400 // Add the relative box variable if this item is a line end
2401 if (this.isLineEnd()) {
2402 // if its a line end add the enclosed stuff as an @variable
2403 if (lowercaseFormula.contains("$box")) {
[214]2404 myParser.addVectorVariable(getEnclosedItems(), "$box");
[196]2405 }
2406 }
[176]2407 myParser.resetObserver();
[190]2408 try {
[220]2409 Node node = myParser.parse(formula);
[190]2410 String result = myParser.evaluate(node);
[176]2411 if (result != null) {
2412 this.setText(result);
[196]2413 this.setFormula(formula);
[376]2414
2415 if (!this.hasAction()) {
2416 setActionMark(false);
2417 setAction("extract formula");
2418 }
[176]2419 }
[196]2420 } catch (Throwable e) {
[427]2421 //e.printStackTrace();
[376]2422 String formula2 = getFormula();
2423 this.setText(formula2);
2424 this.setFormula(formula2);
[291]2425 return false;
[199]2426 }
[376]2427
2428 _attributeValuePair = null;
2429
[291]2430 return true;
[176]2431 }
[190]2432
[199]2433 /**
[470]2434 * Gets items which are in the same enclosure as this item.
2435 * In the event more than one enclosure meets this criteria, then
2436 * the one returned is the one with the smallest area.
2437 * TODO: Improve the efficiency of this method
[247]2438 *
[242]2439 * @return
2440 */
2441 public Collection<Item> getItemsInSameEnclosure() {
2442 Collection<Item> sameEnclosure = null;
2443 Collection<Item> seen = new HashSet<Item>();
2444 Frame parent = getParentOrCurrentFrame();
2445 double enclosureArea = Double.MAX_VALUE;
2446 for (Item i : parent.getVisibleItems()) {
[376]2447 /*
2448 * Go through all the enclosures looking for one that includes this
2449 * item
2450 */
[247]2451 if (!seen.contains(i) && i.isEnclosed()) {
[242]2452 seen.addAll(i.getEnclosingDots());
2453 Collection<Item> enclosed = i.getEnclosedItems();
[247]2454 // Check if we have found an enclosure containing this item
2455 // Check it is smaller than any other enclosure found containing
2456 // this item
2457 if (enclosed.contains(this)
2458 && i.getEnclosedArea() < enclosureArea) {
[242]2459 sameEnclosure = enclosed;
2460 }
2461 }
2462 }
2463
[247]2464 if (sameEnclosure == null)
[242]2465 return new LinkedList<Item>();
[247]2466
[242]2467 return sameEnclosure;
2468 }
[470]2469
[242]2470 /**
[376]2471 * Returns true if items of the parent frame should be recalculated when
2472 * this item is modified
[199]2473 */
[190]2474 public boolean recalculateWhenChanged() {
[278]2475 if (/*
2476 * !isAnnotation() &&
[376]2477 */(hasFormula() || isLineEnd()))
[190]2478 return true;
2479 try {
[376]2480 AttributeValuePair avp = getAttributeValuePair();
2481
2482 if (!avp.getDoubleValue().equals(Double.NaN))
2483 return true;
[190]2484 } catch (Exception e) {
[376]2485 e.printStackTrace();
[190]2486 }
[376]2487
[190]2488 return false;
2489 }
[226]2490
2491 public float getLineHeight() {
2492 return getLineDrop(_textLayouts.get(0));
2493 }
[278]2494
2495 @Override
[720]2496 public void setAnchorLeft(Float anchor) {
2497 if (!isLineEnd()) {
2498 super.setAnchorLeft(anchor);
2499 // Subtract off the link width
2500 if (anchor != null) {
2501 setX(anchor + getLeftMargin());
2502 }
2503 return;
2504 }
2505 invalidateFill();
2506 invalidateCommonTrait(ItemAppearence.PreMoved);
[812]2507
2508 this._anchorLeft = anchor;
2509 this._anchorRight = null;
2510
[720]2511 int oldX = getX();
2512 if (anchor != null) {
2513 float deltaX = anchor + getLeftMargin() - oldX;
2514 anchorConnected(AnchorEdgeType.Left, deltaX);
2515 }
[812]2516
[720]2517 invalidateCommonTrait(ItemAppearence.PostMoved);
2518 invalidateFill();
2519 }
2520
2521 @Override
[278]2522 public void setAnchorRight(Float anchor) {
2523 if (!isLineEnd()) {
2524 super.setAnchorRight(anchor);
2525 // Subtract off the link width
2526 if (anchor != null) {
2527 setX(FrameGraphics.getMaxFrameSize().width - anchor
2528 - getBoundsWidth() + getLeftMargin());
2529 }
2530 return;
2531 }
2532 invalidateFill();
2533 invalidateCommonTrait(ItemAppearence.PreMoved);
[812]2534
2535 this._anchorRight = anchor;
2536 this._anchorLeft = null;
2537
[278]2538 int oldX = getX();
2539 if (anchor != null) {
2540 float deltaX = FrameGraphics.getMaxFrameSize().width - anchor
2541 - getBoundsWidth() + getLeftMargin() - oldX;
[720]2542 anchorConnected(AnchorEdgeType.Right, deltaX);
[278]2543 }
[812]2544
[278]2545 invalidateCommonTrait(ItemAppearence.PostMoved);
2546 invalidateFill();
2547 }
[282]2548
[720]2549
2550
[278]2551 @Override
[720]2552 public void setAnchorTop(Float anchor) {
2553 if (!isLineEnd()) {
2554 super.setAnchorTop(anchor);
2555 if (anchor != null) {
[873]2556 setY(anchor + _textLayouts.get(0).getAscent());
[720]2557 }
2558 return;
2559 }
2560 invalidateFill();
2561 invalidateCommonTrait(ItemAppearence.PreMoved);
[812]2562
2563 this._anchorTop = anchor;
2564 this._anchorBottom = null;
2565
[720]2566 int oldY = getY();
2567 if (anchor != null) {
2568 float deltaY = anchor - oldY;
2569 anchorConnected(AnchorEdgeType.Top, deltaY);
2570 }
[812]2571
[720]2572 invalidateCommonTrait(ItemAppearence.PostMoved);
2573 invalidateFill();
2574 }
2575
2576 @Override
[278]2577 public void setAnchorBottom(Float anchor) {
[282]2578 if (!isLineEnd()) {
[278]2579 super.setAnchorBottom(anchor);
[873]2580 if (anchor != null) {
2581 setY(FrameGraphics.getMaxFrameSize().height - (anchor + this.getBoundsHeight() - _textLayouts.get(0).getAscent() - _textLayouts.get(0).getDescent()));
2582 }
[278]2583 return;
2584 }
2585 invalidateFill();
2586 invalidateCommonTrait(ItemAppearence.PreMoved);
[812]2587
2588 this._anchorBottom = anchor;
2589 this._anchorTop = null;
2590
[278]2591 int oldY = getY();
2592 if (anchor != null) {
[720]2593
[873]2594 float deltaY = FrameGraphics.getMaxFrameSize().height - anchor - oldY;
[720]2595 anchorConnected(AnchorEdgeType.Bottom, deltaY);
[278]2596 }
[812]2597
[278]2598 invalidateCommonTrait(ItemAppearence.PostMoved);
2599 invalidateFill();
2600 }
[311]2601
2602 @Override
2603 public void scale(Float scale, int originX, int originY) {
2604 setSize(getSize() * scale);
[400]2605
[475]2606 Integer width = getWidth();
2607 if (width != null) {
2608 setWidth(Math.round(width * scale));
2609 }
2610
[311]2611 super.scale(scale, originX, originY);
2612 rebuild(true);
2613 }
[410]2614
[905]2615
2616
2617 public Rectangle getPixelBoundsUnion()
2618 {
2619 synchronized (_textLayouts) {
2620
2621 int x = getX();
2622 int y = getY();
2623
2624 int min_xl = Integer.MAX_VALUE;
2625 int max_xr = Integer.MIN_VALUE;
2626
2627 int min_yt = Integer.MAX_VALUE;
2628 int max_yb = Integer.MIN_VALUE;
2629
2630
2631 for (int i = 0; i < _textLayouts.size(); i++) {
2632 TextLayout layout = _textLayouts.get(i);
2633
2634 int ldx = 1+x+getJustOffset(layout); // Layout draw x
2635 Rectangle layout_rect = layout.getPixelBounds(null, ldx, y);
2636
2637 int xl = layout_rect.x;
2638 int xr = xl + layout_rect.width -1;
2639
2640 int yt = layout_rect.y;
2641 int yb = yt + layout_rect.height -1;
2642
2643 min_xl = Math.min(min_xl,xl);
2644 max_xr = Math.max(max_xr,xr);
2645
2646 min_yt = Math.min(min_yt,yt);
2647 max_yb = Math.max(max_yb,yb);
2648 }
2649
2650 if ((min_xl >= max_xr) || (min_yt >= max_yb)) {
2651 // No valid rectangle are found
2652 return null;
2653 }
2654
2655 return new Rectangle(min_xl,min_yt,max_xr-min_xl+1,max_yb-min_yt+1);
2656
2657 }
2658
2659 }
[410]2660 /*
2661 * Returns the SIMPLE statement contained by this text item.
2662 *
2663 */
2664 public String getStatement() {
2665 return getText().split("\\s+")[0];
2666 }
[613]2667
2668 public boolean getAutoWrap() {
2669 return _autoWrap;
2670 }
2671
2672 // workaround since true is the default value and would not be displayed normally
2673 public String getAutoWrapToSave() {
2674 if(!_autoWrap) {
2675 return null;
2676 }
2677 return "true";
2678 }
2679
2680 public void setAutoWrap(boolean autoWrap) {
2681 _autoWrap = autoWrap;
2682 }
[4]2683}
Note: See TracBrowser for help on using the repository browser.