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

Last change on this file since 1415 was 1415, checked in by bln4, 5 years ago

Renamed Frame.getItems() to Frame.getSortedItems() to better represent its functionality.

-> org.apollo.ApolloGestureActions
-> org.apollo.ApolloSystem
-> org.expeditee.actions.Actions
-> org.expeditee.actions.Debug
-> org.expeditee.actions.ExploratorySearchActions
-> org.expeditee.actions.JfxBrowserActions
-> org.expeditee.actions.Misc
-> org.expeditee.actions.Navigation
-> org.expeditee.actions.ScriptBase
-> org.expeditee.actions.Simple
-> org.expeditee.agents.ComputeTree
-> org.expeditee.agents.CopyTree
-> org.expeditee.agents.DisplayComet
-> org.expeditee.agents.DisplayTree
-> org.expeditee.agents.DisplayTreeLeaves
-> org.expeditee.agents.GraphFramesetLinks
-> org.expeditee.agents.TreeProcessor
-> org.expeditee.gio.gesture.StandardGestureActions
-> org.expeditee.gui.DisplayController
-> org.expeditee.gui.FrameCreator
-> org.expeditee.gui.FrameIO
-> org.expeditee.io.DefaultTreeWriter
-> org.expeditee.io.JavaWriter
-> org.expeditee.io.PDF2Writer
-> org.expeditee.io.TXTWriter
-> org.expeditee.io.WebParser
-> org.expeditee.io.flowlayout.XGroupItem
-> org.expeditee.items.Dot
-> org.expeditee.items.Item
-> org.expeditee.items.ItemUtils
-> org.expeditee.network.FrameShare
-> org.expeditee.stats.TreeStats


Created ItemsList class to wrap ArrayList<Item>. Frames now use this new class to store its body list (used for display) as well as its primaryBody and surrogateBody.

-> org.expeditee.agents.Format
-> org.expeditee.agents.HFormat
-> org.expeditee.gio.gesture.StandardGestureActions
-> org.expeditee.gui.Frame
-> org.expeditee.gui.FrameUtils


Refactorted Frame.setResort(bool) to Frame.invalidateSorted() to better function how it is intended to with a more accurate name.

-> org.expeditee.agents.Sort


When writing out .exp files and getting attributes to respond to LEFT + RIGHT click, boolean items are by default true. This has always been the case. An ammendment to this is that defaults can now be established.
Also added 'EnterClick' functionality. If cursored over a item with this property and you press enter, it acts as if you have clicked on it instead.

-> org.expeditee.assets.resources-public.framesets.authentication.1.exp to 6.exp
-> org.expeditee.gio.gesture.StandardGestureActions
-> org.expeditee.gio.input.KBMInputEvent
-> org.expeditee.gio.javafx.JavaFXConversions
-> org.expeditee.gio.swing.SwingConversions
-> org.expeditee.gui.AttributeUtils
-> org.expeditee.io.Conversion
-> org.expeditee.io.DefaultFrameWriter
-> org.expeditee.items.Item


Fixed a bug caused by calling Math.abs on Integer.MIN_VALUE returning unexpected result. Due to zero being a thing, you cannot represent Math.abs(Integer.MIN_VALUE) in a Integer object. The solution is to use Integer.MIN_VALUE + 1 instead of Integer.MIN_VALUE.

-> org.expeditee.core.bounds.CombinationBounds
-> org.expeditee.io.flowlayout.DimensionExtent


Recoded the contains function in EllipticalBounds so that intersection tests containing circles work correctly.

-> org.expeditee.core.bounds.EllipticalBounds


Added toString() to PolygonBounds to allow for useful printing during debugging.

-> org.expeditee.core.bounds.PolygonBounds

Implemented Surrogate Mode!

-> org.expeditee.encryption.io.EncryptedExpReader
-> org.expeditee.encryption.io.EncryptedExpWriter
-> org.expeditee.encryption.items.surrogates.EncryptionDetail
-> org.expeditee.encryption.items.surrogates.Label
-> org.expeditee.gui.FrameUtils
-> org.expeditee.gui.ItemsList
-> org.expeditee.items.Item
-> org.expeditee.items.Text


???? Use Integer.MAX_VALUE cast to a float instead of Float.MAX_VALUE. This fixed some bug which I cannot remember.

-> org.expeditee.gio.TextLayoutManager
-> org.expeditee.gio.swing.SwingTextLayoutManager


Improved solution for dealing with the F10 key taking focus away from Expeditee due to it being a assessibility key.

-> org.expeditee.gio.swing.SwingInputManager


Renamed variable visibleItems in FrameGraphics.paintFrame to itemsToPaintCanditates to better represent functional intent.

-> org.expeditee.gui.FrameGraphics


Improved checking for if personal resources exist before recreating them

-> org.expeditee.gui.FrameIO


Repeated messages to message bay now have a visual feedback instead of just a beep. This visual feedback is in the form of a count of the amount of times it has repeated.

-> org.expeditee.gui.MessageBay


Updated comment on the Vector class to explain what vectors are.

-> org.expeditee.gui.Vector


Added constants to represent all of the property keys in DefaultFrameReader and DefaultFrameWriter.

-> org.expeditee.io.DefaultFrameReader
-> org.expeditee.io.DefaultFrameWriter


Updated the KeyList setting to be more heirarcial with how users will store their Secrets.

-> org.expeditee.settings.identity.secrets.KeyList

File size: 98.5 KB
Line 
1/**
2 * Text.java
3 * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19package org.expeditee.items;
20
21import java.io.File;
22import java.util.ArrayList;
23import java.util.Collection;
24import java.util.HashMap;
25import java.util.HashSet;
26import java.util.LinkedList;
27import java.util.List;
28import java.util.StringTokenizer;
29import java.util.stream.Stream;
30
31import org.expeditee.core.Colour;
32import org.expeditee.core.Dimension;
33import org.expeditee.core.Fill;
34import org.expeditee.core.Font;
35import org.expeditee.core.GradientFill;
36import org.expeditee.core.Point;
37import org.expeditee.core.Range;
38import org.expeditee.core.Stroke;
39import org.expeditee.core.TextHitInfo;
40import org.expeditee.core.TextLayout;
41import org.expeditee.core.bounds.AxisAlignedBoxBounds;
42import org.expeditee.core.bounds.PolygonBounds;
43import org.expeditee.encryption.items.surrogates.EncryptionDetail;
44import org.expeditee.gio.EcosystemManager;
45import org.expeditee.gio.GraphicsManager;
46import org.expeditee.gio.gesture.StandardGestureActions;
47import org.expeditee.gui.AttributeValuePair;
48import org.expeditee.gui.DisplayController;
49import org.expeditee.gui.Frame;
50import org.expeditee.gui.FrameIO;
51import org.expeditee.gui.FrameUtils;
52import org.expeditee.gui.FreeItems;
53import org.expeditee.gui.MessageBay;
54import org.expeditee.io.DefaultFrameWriter;
55import org.expeditee.items.MagneticConstraint.MagneticConstraints;
56import org.expeditee.math.ExpediteeJEP;
57import org.expeditee.settings.experimental.ExperimentalFeatures;
58import org.expeditee.stats.Formatter;
59import org.nfunk.jep.Node;
60
61/**
62 * Represents text displayed on the screen, which may include multiple lines.
63 * All standard text properties can be set (font, style, size).
64 *
65 * @author jdm18
66 *
67 */
68public class Text extends Item {
69 private static final int ADJUST_WIDTH_THRESHOLD = 200;
70
71 public static final char DELETE_CHARACTER = 0x7F;
72
73 public static final char BACKSPACE_CHARACTER = '\b';
74
75 public static final char TAB_CHARACTER = '\t';
76
77 public static final char ESC_CHARACTER = 0x1B;
78
79 public static String LINE_SEPARATOR = System.getProperty("line.separator");
80
81 // public static char[] BULLETS = { '\u2219', '\u2218', '\u2217' };
82 public static char[] BULLETS = { '\u25AA', '\u25AB', '\u2217' };
83
84 private static char DEFAULT_BULLET = BULLETS[2];
85
86 private static String DEFAULT_BULLET_STRING = DEFAULT_BULLET + " ";
87
88 private String[] _processedText = null;
89
90 public String[] getProcessedText() {
91 return _processedText;
92 }
93
94 public void setProcessedText(String[] tokens) {
95 _processedText = tokens;
96 }
97
98 public static final String FRAME_NAME_SEPARATOR = " on frame ";
99
100 /** The default font used to display text items if no font is specified. */
101 public static final String DEFAULT_FONT = "Serif-Plain-18";
102
103 public static final Colour DEFAULT_COLOR = Colour.BLACK;
104
105 public static final int MINIMUM_RANGED_CHARS = 2;
106
107 public static final int NONE = 0;
108
109 public static final int UP = 1;
110
111 public static final int DOWN = 2;
112
113 public static final int LEFT = 3;
114
115 public static final int RIGHT = 4;
116
117 public static final int HOME = 5;
118
119 public static final int LINE_HOME = 9;
120
121 public static final int LINE_END = 10;
122
123 public static final int END = 6;
124
125 public static final int PAGE_DOWN = 7;
126
127 public static final int PAGE_UP = 8;
128
129 /**
130 * Set the width to be IMPLICIT, but as wide as possible, a negative width value
131 * is one that is implicitly set by the system... a positive value is one
132 * explicitly set by the user.
133 */
134 /**
135 * The maximum allowable width of the Text item. Actual width may be less than
136 * this value, subject to text wrapping. Negative values indicate the width was
137 * implicitly set by the system, positive values indicate explicit setting by
138 * the user. Initially set to be as wide as possible.
139 */
140 private Integer _width = -Integer.MAX_VALUE;
141
142 private Integer _minWidth = -Integer.MAX_VALUE;
143
144 private boolean _singleLine = false;
145
146 private Justification _justification = Justification.left;
147
148 private float _spacing = -1;
149
150 private int _word_spacing = -1;
151
152 private float _initial_spacing = 0;
153
154 private float _letter_spacing = 0;
155
156 // used during ranging out
157 private int _selectionStart = -1;
158
159 private int _selectionEnd = -1;
160
161 private int _tabIndex = -1;
162
163 /** Keeps track of the last Text item selected. */
164 private static Text _lastSelected = null;
165
166 // Range selection colours
167 /** Colour of selected range when for selecting text. */
168 public static final Colour RANGE_SELECT_COLOUR = Colour.FromRGB255(255, 160, 160);
169 /** Colour of selected range when for cutting text. */
170 public static final Colour RANGE_CUT_COLOUR = Colour.FromRGB255(160, 255, 160);
171 /** Colour of selected range when for copying text. */
172 public static final Colour RANGE_COPY_COLOUR = Colour.FromRGB255(160, 160, 255);
173 /** Colour of selected range when for deleting text. */
174 public static final Colour RANGE_DELETE_COLOUR = Colour.FromRGB255(235, 235, 140);
175
176 /** The colour to draw range selections in. */
177 private Colour _selectionColour = RANGE_SELECT_COLOUR;
178
179 // whether autowrap is on/off for this item
180 protected boolean _autoWrap = false;
181
182 // text is broken up into lines
183 protected StringBuffer _text = new StringBuffer();
184
185 // place holder text to use if _text is empty.
186 protected StringBuffer _placeholder = new StringBuffer();
187
188 private List<TextLayout> _textLayouts = new LinkedList<TextLayout>();
189 private List<TextLayout> _maskTextLayouts = new LinkedList<TextLayout>();
190 private List<TextLayout> _placeholderTextLayouts = new LinkedList<TextLayout>();
191
192 // The font to display this text in
193 private Font _font;
194
195 // The optional mask character to us in place of the text's content.
196 private Integer _mask = null;
197
198 protected static void InitFontFamily(File fontFamilyDir) {
199 File[] fontFiles = fontFamilyDir.listFiles();
200
201 for (File fontFile : fontFiles) {
202 String ext = "";
203 String fileName = fontFile.getName().toLowerCase();
204
205 int i = fileName.lastIndexOf('.');
206 int p = Math.max(fileName.lastIndexOf('/'), fileName.lastIndexOf('\\'));
207
208 if (i > p) {
209 ext = fileName.substring(i + 1);
210 }
211
212 if (ext.equals("ttf")) {
213
214 try {
215 Font font = EcosystemManager.getFontManager().registerFontFile(fontFile);
216
217 if (font != null) {
218
219 String font_family = font.getFamilyName();
220 if (!FONT_WHEEL_ADDITIONAL_LOOKUP.containsKey(font_family)) {
221
222 if (FONT_WHEEL_ADDITIONAL_LOOKUP.size() > 0) {
223 System.out.print(", ");
224 }
225 System.out.print("'" + font_family + "'");
226
227 FONT_WHEEL_ADDITIONAL_LOOKUP.put(font_family, font);
228
229 /*
230 * int cdut = font.canDisplayUpTo("09AZaz"); if (cdut >= 0) { // Some problem
231 * has occured (should return -1 to show all chars possible)
232 *
233 * System.out.println(" [Non-ASCII font]"); }
234 */
235 System.out.flush();
236 }
237
238 //System.out.print("'" + font_family + "'");
239
240 } else {
241 System.err.println("Error: Failed to add custom True-Type Font file: " + fontFile);
242 }
243
244 } catch (Exception e) {
245 System.err.println("Failed to load custon font file: " + fontFile);
246 }
247 }
248 }
249 }
250
251 public static void InitFonts() {
252
253 File fontDirectory = new File(FrameIO.FONT_PATH);
254 if (fontDirectory != null) {
255 File[] fontFamilyDirs = fontDirectory.listFiles();
256 if (fontFamilyDirs != null) {
257
258 if (fontFamilyDirs.length > 0) {
259 System.out.println("Loading custom fonts:");
260 }
261
262 for (File fontFamilyDir : fontFamilyDirs) {
263 if (fontFamilyDir.isDirectory()) {
264 InitFontFamily(fontFamilyDir);
265 }
266 }
267 }
268 }
269 }
270
271 /**
272 * Similar to parseArgs() in InteractiveWidget. Code based on routine used in
273 * Apache Ant: org.apache.tools.ant.types.Commandline::translateCommandline()
274 *
275 * @param toProcess
276 * the command line to process.
277 * @return the command line broken into strings. An empty or null toProcess
278 * parameter results in a zero sized array.
279 */
280 public static String[] parseArgsApache(String toProcess) {
281 if (toProcess == null || toProcess.length() == 0) {
282 // no command? no string
283 return new String[0];
284 }
285 // parse with a simple finite state machine
286
287 final int normal = 0;
288 final int inQuote = 1;
289 final int inDoubleQuote = 2;
290 int state = normal;
291 final StringTokenizer tok = new StringTokenizer(toProcess, "\"\' ", true);
292 final ArrayList<String> result = new ArrayList<String>();
293 final StringBuilder current = new StringBuilder();
294 boolean lastTokenHasBeenQuoted = false;
295
296 while (tok.hasMoreTokens()) {
297 String nextTok = tok.nextToken();
298 switch (state) {
299 case inQuote:
300 if ("\'".equals(nextTok)) {
301 lastTokenHasBeenQuoted = true;
302 state = normal;
303 } else {
304 current.append(nextTok);
305 }
306 break;
307 case inDoubleQuote:
308 if ("\"".equals(nextTok)) {
309 lastTokenHasBeenQuoted = true;
310 state = normal;
311 } else {
312 current.append(nextTok);
313 }
314 break;
315 default:
316 if ("\'".equals(nextTok)) {
317 state = inQuote;
318 } else if ("\"".equals(nextTok)) {
319 state = inDoubleQuote;
320 } else if (" ".equals(nextTok)) {
321 if (lastTokenHasBeenQuoted || current.length() != 0) {
322 result.add(current.toString());
323 current.setLength(0);
324 }
325 } else {
326 current.append(nextTok);
327 }
328 lastTokenHasBeenQuoted = false;
329 break;
330 }
331 }
332 if (lastTokenHasBeenQuoted || current.length() != 0) {
333 result.add(current.toString());
334 }
335 if (state == inQuote || state == inDoubleQuote) {
336 System.err.println("Error: Unbalanced quotes -- failed to parse '" + toProcess + "'");
337 return null;
338 }
339
340 return result.toArray(new String[result.size()]);
341 }
342
343 /**
344 * Creates a new Text Item with the given ID and text.
345 *
346 * @param id
347 * The id of this item
348 * @param text
349 * The text to use in this item
350 */
351 public Text(int id, String text) {
352 super();
353 _text.append(text);
354 rebuild(false);
355
356 setID(id);
357 }
358
359 /**
360 * Creates a text item which is not added to the frame.
361 *
362 * @param text
363 */
364 public Text(String text) {
365 super();
366 _text.append(text);
367 rebuild(false);
368 setID(-1);
369 }
370
371 /**
372 * Creates a new Text Item with the given ID
373 *
374 * @param id
375 * The ID to of this item
376 */
377 public Text(int id) {
378 super();
379 setID(id);
380 }
381
382 public Text(int i, String string, Colour foreground, Colour background) {
383 this(i, string);
384 this.setColor(foreground);
385 this.setBackgroundColor(background);
386 }
387
388 /**
389 * @param width
390 * The maximum width of this item when justification is applied to
391 * it.
392 */
393 @Override
394 public void setWidth(Integer width) {
395 invalidateAll();
396
397 if (width == null) {
398 setJustification(Justification.left);
399 setRightMargin(DisplayController.getFramePaintAreaWidth(), false);
400 return;
401 }
402
403 _width = width;
404 rebuild(true);
405 invalidateAll();
406
407 if (isSurrogate()) {
408 surrogatePropertyInheritance.put(DefaultFrameWriter.WIDTH_TO_SAVE_STR, false);
409 Item primary = getPrimary();
410 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.WIDTH_TO_SAVE_STR)) {
411 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
412 primary.primaryPropertyEncryption.put(DefaultFrameWriter.WIDTH_TO_SAVE_STR, inheritanceCheckOnSave);
413 }
414 }
415 }
416
417 @Override
418 public void setMinWidth(final Integer width) {
419 invalidateAll();
420
421 if (width == null) {
422 setJustification(Justification.left);
423 setRightMargin(DisplayController.getFramePaintAreaWidth(), false);
424 return;
425 }
426
427 _minWidth = width;
428 rebuild(true);
429 invalidateAll();
430
431 if (isSurrogate()) {
432 surrogatePropertyInheritance.put(DefaultFrameWriter.MIN_WIDTH_TO_SAVE_STR, false);
433 Item primary = getPrimary();
434 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.MIN_WIDTH_TO_SAVE_STR)) {
435 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
436 primary.primaryPropertyEncryption.put(DefaultFrameWriter.MIN_WIDTH_TO_SAVE_STR, inheritanceCheckOnSave);
437 }
438 }
439 }
440
441 /**
442 * <<<<<<< .mine Returns the maximum width of this Text item when justification
443 * is used. If the width is negative, it means no explicit width has been set
444 * ||||||| .r1094 Returns the maximum width of this Text item when justifcation
445 * is used. If the width is negative, it means no explicit width has been set
446 * ======= Returns the maximum width of this Text item when justifcation is
447 * used. If the width is negative, it means no explicit width has been set
448 * >>>>>>> .r1100
449 *
450 * @return The maximum width of this Text item when justification is used
451 */
452 @Override
453 public Integer getWidth() {
454 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.WIDTH_TO_SAVE_STR)) {
455 return this.getPrimary().getWidth();
456 } else {
457 if (_width == null || _width <= 0)
458 return null;
459 return _width;
460 }
461 }
462
463 public Integer getAbsoluteWidth() {
464 if (_width == null || _width == Integer.MIN_VALUE) {
465 // When absoluting Integer.MIN_VALUE, the java API is defined to give you back Integer.MIN_VALUE!!?!
466 // This is because of the asymmetry of two's complement integer representation.
467 // We would prefer to use Integer.MAX_VALUE in this circumstance.
468 return Integer.MAX_VALUE;
469 }
470
471 return Math.abs(_width);
472 }
473
474 public Integer getMinWidth() {
475 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.MIN_WIDTH_TO_SAVE_STR)) {
476 return this.getPrimary().getMinWidth();
477 } else {
478 if (_minWidth == null || _minWidth <= 0)
479 return null;
480 return _minWidth;
481 }
482 }
483
484 public Integer getAbsoluteMinWidth() {
485 if (_minWidth == null) {
486 return Integer.MAX_VALUE;
487 }
488 return Math.abs(_minWidth);
489 }
490
491 @Override
492 public Colour getHighlightColor() {
493 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.HIGHLIGHT_STR)) {
494 return this.getPrimary().getHighlightColor();
495 } else {
496 return _highlightColour;
497 }
498 }
499
500 /**
501 * Sets the justification of this Text item. The given integer should correspond
502 * to one of the JUSTIFICATION constants defined in Item
503 *
504 * @param just
505 * The justification to apply to this Text item
506 */
507 public void setJustification(Justification just) {
508 invalidateAll();
509
510 // Only justification left works with 0 width
511 // if (just != null && just != Justification.left && !hasWidth()) {
512 // // TODO Tighten this up so it subtracts the margin widths
513 // setWidth(getBoundsWidth());
514 // }
515
516 _justification = just;
517 rebuild(true);
518 invalidateAll();
519
520 if (isSurrogate()) {
521 surrogatePropertyInheritance.put(DefaultFrameWriter.JUSTIFICATION_STR, false);
522 Item primary = getPrimary();
523 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.JUSTIFICATION_STR)) {
524 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
525 primary.primaryPropertyEncryption.put(DefaultFrameWriter.JUSTIFICATION_STR, inheritanceCheckOnSave);
526 }
527 }
528 }
529
530 /**
531 * Returns the current justification of this Text item. The default value left
532 * justification.
533 *
534 * TODO: Why return null when justification is set to left? cts16
535 *
536 * @return The justification of this Text item
537 */
538 public Justification getJustification() {
539 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.JUSTIFICATION_STR)) {
540 return ((Text) this.getPrimary()).getJustification();
541 } else {
542 if (_justification == null || _justification.equals(Justification.left)) {
543 return null;
544 }
545
546 return _justification;
547 }
548 }
549
550 /**
551 * Gets the distance from the left of the bounding box that the given layout
552 * should be shifted to justify it.
553 *
554 * @param layout
555 * The line of text to calculate the justification offset for.
556 *
557 * @return The distance to shift the line of text by.
558 */
559 private int getJustOffset(TextLayout layout) {
560 if (getJustification() == Justification.center) {
561 return (int) ((getAbsoluteWidth() - layout.getAdvance()) / 2);
562 } else if (getJustification() == Justification.right) {
563 return (int) (getAbsoluteWidth() - layout.getAdvance());
564 }
565
566 return 0;
567 }
568
569 /**
570 * Sets the text displayed on the screen to the given String. It does not reset
571 * the formula, attributeValuePair or other cached values.
572 *
573 * @param text
574 * The String to display on the screen when drawing this Item.
575 */
576 @Override
577 public void setText(String text) {
578 setText(text, false);
579 }
580
581 public void setText(String text, Boolean clearCache) {
582 // if (_text != null && text.length() < _text.length())
583 invalidateAll();
584 _text = new StringBuffer(text);
585
586 /*
587 * Always clearingCach remove formulas when moving in and out of XRay mode
588 */
589 if (clearCache) {
590 clearCache();
591 }
592
593 rebuild(true);
594 invalidateAll();
595 }
596
597 public void setTextList(List<String> text) {
598 if (text == null || text.size() <= 0) {
599 return;
600 }
601
602 invalidateAll();
603
604 StringBuffer sb = new StringBuffer();
605
606 for (int i = 0; i < text.size(); i++) {
607 sb.append(text.get(i)).append('\n');
608 }
609
610 if (sb.length() > 0) {
611 sb.deleteCharAt(sb.length() - 1);
612 }
613
614 setText(sb.toString());
615
616 rebuild(true);
617 invalidateAll();
618 }
619
620 public void setAttributeValue(String value) {
621 AttributeValuePair avp = new AttributeValuePair(getText(), false);
622 avp.setValue(value);
623 setText(avp.toString());
624 }
625
626 /**
627 * Inserts the given String at the start of the first line of this Text Item.
628 *
629 * @param text
630 * The String to insert.
631 */
632 public void prependText(String text) {
633 invalidateAll();
634 _text.insert(0, text);
635 rebuild(false);
636 invalidateAll();
637 }
638
639 /**
640 * If the first line of text starts with the given String, then it is removed
641 * otherwise no action is taken.
642 *
643 * @param text
644 * The String to remove from the first line of Text
645 */
646 public void removeText(String text) {
647 if (_text.length() > 0 && _text.indexOf(text) == 0) {
648 invalidateAll();
649 _text.delete(0, text.length());
650 invalidateAll();
651 }
652
653 }
654
655 public void removeEndText(String textToRemove) {
656 int length = _text.length();
657 if (length > 0) {
658 int pos = _text.indexOf(textToRemove);
659 int textToRemoveLength = textToRemove.length();
660 if (pos + textToRemoveLength == length) {
661 // Need the invalidate all for dateStamp toggling
662 invalidateAll();
663 _text.delete(pos, length);
664 invalidateAll();
665 }
666 }
667
668 }
669
670 /**
671 * Appends the given String to any text already present in this Item
672 *
673 * @param text
674 * The String to append.
675 */
676 public void appendText(String text) {
677 invalidateAll();
678 _text.append(text);
679 rebuild(false);
680 invalidateAll();
681 }
682
683 /**
684 * Used by the frame reader to construct multi-line text items. It must run
685 * quickly, so that the system still responds well for long text items.
686 *
687 * @param text
688 */
689 public void appendLine(String text) {
690 if (text == null) {
691 text = "";
692 }
693
694 if (_text.length() > 0) {
695 _text.append('\n');
696 }
697
698 _text.append(text);
699
700 rebuild(true);
701
702 if (isSurrogate()) {
703 surrogatePropertyInheritance.put(DefaultFrameWriter.TEXT_STR, false);
704 Item primary = getPrimary();
705 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.TEXT_STR)) {
706 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
707 primary.primaryPropertyEncryption.put(DefaultFrameWriter.TEXT_STR, inheritanceCheckOnSave);
708 }
709 }
710 }
711
712 /**
713 * Tests if the first line of this Text starts with the given String.
714 *
715 * @param text
716 * The prefix to check for
717 * @return True if the first line starts with the given String, False otherwise.
718 */
719 public boolean startsWith(String text) {
720 return startsWith(text, true);
721 }
722
723 public boolean startsWith(String text, boolean ignoreCase) {
724 if (text == null || _text == null || _text.length() < 1) {
725 return false;
726 }
727
728 if (ignoreCase) {
729 return _text.toString().toLowerCase().startsWith(text.toLowerCase());
730 } else {
731 return _text.indexOf(text) == 0;
732 }
733 }
734
735 /**
736 * Inserts a character into the Text of this Item.
737 *
738 * @param ch
739 * The character insert.
740 * @param mouseX
741 * The X position to insert the Strings at.
742 * @param mouseY
743 * The Y position to insert the Strings at.
744 */
745 public Point insertChar(char ch, float mouseX, float mouseY) {
746 if (ch != '\t') {
747 return insertText("" + ch, mouseX, mouseY);
748 }
749
750 return insertText(" " + ch, mouseX, mouseY);
751 }
752
753 /**
754 * @param index
755 * @return
756 */
757 private char getNextBullet(char bullet) {
758 for (int i = 0; i < BULLETS.length - 1; i++) {
759 if (BULLETS[i] == bullet) {
760 return BULLETS[i + 1];
761 }
762 }
763 return BULLETS[0];
764 }
765
766 private char getPreviousBullet(char bullet) {
767 for (int i = 1; i < BULLETS.length; i++) {
768 if (BULLETS[i] == bullet) {
769 return BULLETS[i - 1];
770 }
771 }
772 return BULLETS[BULLETS.length - 1];
773 }
774
775 public Point getLineEndPosition(float mouseY) {
776 return getEdgePosition(getLinePosition(mouseY), false);
777 }
778
779 public Point getLineStartPosition(float mouseY) {
780 return getEdgePosition(getLinePosition(mouseY), true);
781 }
782
783 public Point getParagraphEndPosition() {
784 return getEdgePosition(getTextLayouts().size() - 1, false);
785 }
786
787 public Point getParagraphStartPosition() {
788 return getEdgePosition(0, true);
789 }
790
791 private Point getEdgePosition(int line, boolean start) {
792 // if there is no text yet, or the line is invalid
793 if (_text == null || _text.length() == 0 || line < 0 || line > getTextLayouts().size() - 1) {
794 return new Point(getX(), getY());
795 }
796
797 TextLayout last = getTextLayouts().get(line);
798 TextHitInfo hit;
799 if (start) {
800 hit = last.getNextLeftHit(1);
801 } else {
802 hit = last.getNextRightHit(last.getCharacterCount() - 1);
803 }
804
805 // move the cursor to the new location
806 float[] caret = last.getCaretInfo(hit);
807 float y = getLineDrop(last) * line;
808
809 float x = getX() + caret[0] + getJustOffset(last);
810
811 x = Math.min(x, (getX() - Item.MARGIN_RIGHT - (2 * getGravity()) + getBoundsWidth()));
812 return new Point((int) x, (int) (getY() + y + caret[1]));
813 }
814
815 public void setSelectionStart(Point p) {
816 setSelectionStart(p.getX(), p.getY());
817 }
818
819 public void setSelectionStart(float mouseX, float mouseY) {
820 // determine what line is being pointed to
821 int line = getLinePosition(mouseY);
822
823 // get the character being pointed to
824 TextHitInfo hit = getCharPosition(line, mouseX);
825 _selectionStart = hit.getInsertionIndex() + getTextLayouts().get(line).getStartCharIndex();
826
827 // Clear the last selected
828 updateLastSelected();
829
830 invalidateAll();
831 }
832
833 public void setSelectionEnd(Point p) {
834 setSelectionEnd(p.getX(), p.getY());
835 }
836
837 public void setSelectionEnd(float mouseX, float mouseY) {
838 // determine what line is being pointed to
839 int line = getLinePosition(mouseY);
840
841 // get the character being pointed to
842 TextHitInfo hit = getCharPosition(line, mouseX);
843 _selectionEnd = hit.getInsertionIndex() + getTextLayouts().get(line).getStartCharIndex();
844
845 // Clear the last selected
846 updateLastSelected();
847
848 invalidateAll();
849 }
850
851 public void clearSelection() {
852 _selectionStart = -1;
853 _selectionEnd = -1;
854 invalidateAll();
855 }
856
857 public void clearSelectionEnd() {
858 _selectionEnd = -1;
859 invalidateAll();
860 }
861
862 /** Makes sure only one text has a selection at a time. */
863 public void updateLastSelected() {
864 if (_lastSelected != this) {
865 if (_lastSelected != null) {
866 _lastSelected.clearSelection();
867 }
868 _lastSelected = this;
869 }
870 }
871
872 public String copySelectedText() {
873 if (_selectionStart < 0 || _selectionEnd < 0) {
874 return null;
875 } else if (_selectionEnd > _text.length()) {
876 _selectionEnd = _text.length();
877 }
878
879 return _text.substring(Math.min(_selectionStart, _selectionEnd), Math.max(_selectionStart, _selectionEnd));
880 }
881
882 public String cutSelectedText() {
883 return replaceSelectedText("");
884 }
885
886 public String replaceSelectedText(String newText) {
887 if (_selectionStart < 0 || _selectionEnd < 0) {
888 return null;
889 }
890
891 invalidateAll();
892
893 if (_selectionEnd > _text.length()) {
894 _selectionEnd = _text.length();
895 }
896
897 int left = Math.min(_selectionStart, _selectionEnd);
898 int right = Math.max(_selectionStart, _selectionEnd);
899
900 // Trim the text to remove new lines on the beginning and end of the
901 // string
902 if (_text.charAt(left) == '\n') {
903 // if the entire line is being removed then remove one of the new
904 // lines, the first case checks if the last line is being removed
905 if (right >= _text.length() || _text.charAt(right) == '\n') {
906 _text.deleteCharAt(left);
907 right--;
908 } else {
909 left++;
910 }
911 }
912 // New lines are always at the start of the line for now...
913 // if(_text.charAt(right - 1) == '\n' && left < right){
914 // right--;
915 // }
916 String s = _text.substring(left, right);
917
918 _text.delete(left, right);
919 _text.insert(left, newText);
920 rebuild(true);
921
922 clearCache();
923
924 invalidateAll();
925
926 return s;
927 }
928
929 public int getSelectionSize() {
930 if (_selectionEnd < 0 || _selectionStart < 0) {
931 return 0;
932 }
933
934 // System.out.println(_selectionStart + ":" + _selectionEnd);
935
936 return Math.abs(_selectionEnd - _selectionStart);
937 }
938
939 /**
940 * Inserts the given String into the Text at the position given by the mouseX
941 * and mouseY coordinates
942 *
943 * @param text
944 * The String to insert into this Text.
945 * @param mouseX
946 * The X position to insert the String
947 * @param mouseY
948 * The Y position to insert the String
949 * @return The new location that the mouse cursor should be moved to
950 */
951 public Point insertText(String text, float mouseX, float mouseY) {
952 final Point newPos = insertText(text, mouseX, mouseY, -1);
953 return newPos;
954 }
955
956 public Point insertText(final String text, final float mouseX, final float mouseY, int insertPos) {
957 TextHitInfo hit;
958 TextLayout currentLayout = null;
959 int lineIndex;
960 invalidateAll();
961
962 // if it is a empty string then do not move the mouse
963 if (text == null || text.length() == 0) {
964 return new Point((int) mouseX, (int) mouseY);
965 }
966
967 // if there is no text yet then simply append parameter and rebuild before moving on.
968 // rebuild re-initialises the TextLayouts
969 // calculate were we are in the content (hit, lineIndex, currentLayout)
970 if (_text == null || _text.length() == 0) {
971 _text = new StringBuffer().append(text);
972 rebuild(true);
973
974 assert (getTextLayouts().size() == 1);
975 currentLayout = getTextLayouts().get(0);
976 hit = currentLayout.getNextRightHit(0);
977 lineIndex = 0;
978 }
979 // otherwise we are inserting text and calculating the index into the content that we are at
980 else {
981
982 clearCache();
983 // determine what line is being pointed to
984 lineIndex = getLinePosition(mouseY);
985
986 // get the character being pointed to
987 hit = getCharPosition(lineIndex, mouseX);
988
989 int insertionIndex = hit.getInsertionIndex() + getTextLayouts().get(lineIndex).getStartCharIndex();
990
991 if (lineIndex > 0 && hit.getInsertionIndex() == 0) {
992 // Only move forward a char if the line begins with a hard line
993 // break... not a soft line break
994 if (_text.charAt(insertionIndex) == '\n') {
995 insertionIndex++;
996 }
997 }
998
999 if (insertPos < 0) {
1000 insertPos = insertionIndex;
1001 }
1002
1003 // if this is a backspace key
1004 if (text.charAt(0) == '\b') {
1005 if (hasSelection()) {
1006 insertionIndex = deleteSelection(insertionIndex);
1007 } else if (insertPos > 0) {
1008 deleteChar(insertPos - 1);
1009 if (insertionIndex > 0) {
1010 insertionIndex--;
1011 }
1012 }
1013 // if this is a delete key
1014 } else if (text.charAt(0) == (char) 0x7F) {
1015 if (hasSelection()) {
1016 insertionIndex = deleteSelection(insertionIndex);
1017 } else if (insertPos < _text.length()) {
1018 deleteChar(insertPos);
1019 }
1020 // this is a tab
1021 } else if (text.charAt(0) == '\t') {
1022 // Text length greater than 1 signals a backwards tab
1023 if (text.length() > 1) {
1024 // Find the first non space char to see if its a bullet
1025 int index = 0;
1026 for (index = 0; index < _text.length(); index++) {
1027 if (!Character.isSpaceChar(_text.charAt(index))) {
1028 break;
1029 }
1030 }
1031 // Check if there is a space after the bullet
1032 if (index < _text.length() - 1 && _text.charAt(index + 1) == ' ') {
1033 // Change the bullet
1034 _text.setCharAt(index, getPreviousBullet(_text.charAt(index)));
1035 }
1036 // Remove the spacing at the start
1037 for (int i = 0; i < TAB_STRING.length(); i++) {
1038 if (_text.length() > 0 && Character.isSpaceChar(_text.charAt(0))) {
1039 deleteChar(0);
1040 insertionIndex--;
1041 } else {
1042 break;
1043 }
1044 }
1045 } else {
1046 // / Find the first non space char to see if its a bullet
1047 int index = 0;
1048 for (index = 0; index < _text.length(); index++) {
1049 if (!Character.isSpaceChar(_text.charAt(index))) {
1050 break;
1051 }
1052 }
1053 // Check if there is a space after the bullet
1054 if (index < _text.length() - 1 && _text.charAt(index + 1) == ' ') {
1055 char nextBullet = getNextBullet(_text.charAt(index));
1056 // Change the bullet
1057 _text.setCharAt(index, nextBullet);
1058 }
1059 // Insert the spacing at the start
1060 insertString(TAB_STRING, 0);
1061 insertionIndex += TAB_STRING.length();
1062 }
1063 // this is a normal insert
1064 } else {
1065 insertString(text, insertPos);
1066 insertionIndex += text.length();
1067 }
1068
1069 if (_text.length() == 0) {
1070 rebuild(false);
1071 return new Point((int) this._x, (int) this._y);
1072 }
1073
1074 int newLine = lineIndex;
1075
1076 // if a rebuild is required
1077 rebuild(true, false);
1078
1079 // determine the new position the cursor should have
1080 for (int i = 0; i < getTextLayouts().size(); i++) {
1081 if (getTextLayouts().get(i).getEndCharIndex() + 1 >= insertionIndex) {
1082 newLine = i;
1083 break;
1084 }
1085 }
1086
1087 currentLayout = getTextLayouts().get(newLine);
1088 insertionIndex -= currentLayout.getStartCharIndex();
1089
1090 if (newLine == lineIndex) {
1091 if (insertionIndex > 0) {
1092 hit = currentLayout.getNextRightHit(insertionIndex - 1);
1093 } else {
1094 hit = currentLayout.getNextLeftHit(1);
1095 }
1096 } else if (newLine < lineIndex) {
1097 hit = currentLayout.getNextRightHit(insertionIndex - 1);
1098 } else {
1099 hit = currentLayout.getNextRightHit(insertionIndex - 1);
1100 }
1101
1102 lineIndex = newLine;
1103 }
1104
1105 // If we have no mask then....
1106 // move the cursor to the new location
1107 float[] caret = currentLayout.getCaretInfo(hit);
1108 float y = getLineDrop(currentLayout) * lineIndex;
1109 y = getY() + y + caret[1];
1110
1111 float x = getX() + caret[0] + getJustOffset(currentLayout);
1112 x = Math.min(x, (getX() - Item.MARGIN_RIGHT - (2 * getGravity()) + getBoundsWidth()));
1113
1114 invalidateAll();
1115
1116 final Point newCursor = new Point(Math.round(x), Math.round(y));
1117 return newCursor;
1118 }
1119
1120 /**
1121 *
1122 */
1123 private void clearCache() {
1124 _attributeValuePair = null;
1125 setProcessedText(null);
1126 setFormula(null);
1127 }
1128
1129 /**
1130 * @param pos
1131 * @return
1132 */
1133 private int deleteSelection(int pos) {
1134 int selectionLength = getSelectionSize();
1135 cutSelectedText();
1136 clearSelection();
1137 pos -= selectionLength;
1138 return pos;
1139 }
1140
1141 public Point moveCursor(int direction, float mouseX, float mouseY, boolean setSelection, boolean wholeWord) {
1142 if (setSelection) {
1143 if (!hasSelection()) {
1144 setSelectionStart(mouseX, mouseY);
1145 }
1146 } else {
1147 // clearSelection();
1148 }
1149
1150 Point resultPos = null;
1151
1152 // check for home or end keys
1153 switch (direction) {
1154 case HOME:
1155 resultPos = getParagraphStartPosition();
1156 break;
1157 case END:
1158 resultPos = getParagraphEndPosition();
1159 break;
1160 case LINE_HOME:
1161 resultPos = getLineStartPosition(mouseY);
1162 break;
1163 case LINE_END:
1164 resultPos = getLineEndPosition(mouseY);
1165 break;
1166 default:
1167 TextHitInfo hit;
1168 TextLayout current;
1169 int line;
1170
1171 // if there is no text yet
1172 if (_text == null || _text.length() == 0) {
1173 return new Point((int) mouseX, (int) mouseY);
1174 // otherwise, move the cursor
1175 } else {
1176 // determine the line of text to check
1177 line = getLinePosition(mouseY);
1178 if (line < 0) {
1179 line = getTextLayouts().size() - 1;
1180 }
1181
1182 // if the cursor is moving up or down, change the line
1183 if (direction == UP) {
1184 line = Math.max(line - 1, 0);
1185 } else if (direction == DOWN) {
1186 line = Math.min(line + 1, getTextLayouts().size() - 1);
1187 }
1188
1189 hit = getCharPosition(line, mouseX);
1190
1191 if (direction == LEFT) {
1192 if (hit.getInsertionIndex() > 0) {
1193
1194 char prevChar = ' ';
1195 do {
1196 hit = getTextLayouts().get(line).getNextLeftHit(hit);
1197
1198 // Stop if at the start of the line
1199 if (hit.getInsertionIndex() == 0) {
1200 break;
1201 }
1202 // Keep going if the char to the left is a
1203 // letterOrDigit
1204 prevChar = _text
1205 .charAt(hit.getInsertionIndex() - 1 + getTextLayouts().get(line).getStartCharIndex());
1206 } while (wholeWord && Character.isLetterOrDigit(prevChar));
1207
1208 // TODO Go to the start of the word instead of before the word
1209 char nextChar = _text
1210 .charAt(hit.getInsertionIndex() + getTextLayouts().get(line).getStartCharIndex());
1211
1212 // This takes care of hard line break in
1213 if (line > 0 && nextChar == '\n') {
1214 line--;
1215 hit = getTextLayouts().get(line)
1216 .getNextRightHit(getTextLayouts().get(line).getCharacterCount() - 1);
1217 }
1218
1219 // This takes care of soft line breaks.
1220 } else if (line > 0) {
1221 line--;
1222 hit = getTextLayouts().get(line).getNextRightHit(getTextLayouts().get(line).getCharacterCount() - 1);
1223
1224 // Skip the spaces at the end of a line with soft linebreak
1225 while (hit.getCharIndex() > 0 && _text
1226 .charAt(getTextLayouts().get(line).getStartCharIndex() + hit.getCharIndex() - 1) == ' ') {
1227 hit = getTextLayouts().get(line).getNextLeftHit(hit);
1228 }
1229 }
1230 } else if (direction == RIGHT) {
1231 if (hit.getInsertionIndex() < getTextLayouts().get(line).getCharacterCount()) {
1232 hit = getTextLayouts().get(line).getNextRightHit(hit);
1233 // Skip whole word if needs be
1234 while (wholeWord && hit.getCharIndex() > 0
1235 && hit.getCharIndex() < getTextLayouts().get(line).getCharacterCount()
1236 && Character.isLetterOrDigit(_text
1237 .charAt(getTextLayouts().get(line).getStartCharIndex() + hit.getCharIndex() - 1))) {
1238 hit = getTextLayouts().get(line).getNextRightHit(hit);
1239 }
1240 } else if (line < getTextLayouts().size() - 1) {
1241 line++;
1242 hit = getTextLayouts().get(line).getNextLeftHit(1);
1243 }
1244 }
1245 current = getTextLayouts().get(line);
1246 }
1247
1248 // move the cursor to the new location
1249 float[] caret = current.getCaretInfo(hit);
1250 float y = getLineDrop(current) * line;
1251
1252 resultPos = new Point((int) (getX() + caret[0] + getJustOffset(current)), (int) (getY() + y + caret[1]));
1253
1254 break;
1255 }
1256
1257 if (setSelection) {
1258 setSelectionEnd(resultPos.getX(), resultPos.getY());
1259 }
1260
1261 return resultPos;
1262 }
1263
1264 /**
1265 * Iterates through the given line string and returns the position of the
1266 * character being pointed at by the mouse.
1267 *
1268 * @param line
1269 * The index of the _text array of the String to be searched.
1270 * @param mouseX
1271 * The X coordinate of the mouse
1272 * @return The position in the string of the character being pointed at.
1273 */
1274 public TextHitInfo getCharPosition(final int line, float mouseX) {
1275 if (line < 0 || line >= getTextLayouts().size()) {
1276 return null;
1277 }
1278
1279 final TextLayout layout = getTextLayouts().get(line);
1280 mouseX += getOffset().getX();
1281 mouseX -= getJustOffset(layout);
1282
1283 return layout.hitTestChar(mouseX - getX(), 0);
1284 }
1285
1286 /**
1287 * Gets the index into the <code>_textLayout</code> list which corresponds to
1288 * the line covered by the given <code>mouseY</code> position.
1289 *
1290 * @param mouseY
1291 * The y-coordinate to test for line coverage.
1292 *
1293 * @return The line which occupies the given y-coordinate, or the last line if
1294 * none do.
1295 */
1296 public int getLinePosition(float mouseY) {
1297 mouseY += getOffset().getY();
1298
1299 float y = getY();
1300
1301 for (TextLayout text : getTextLayouts()) {
1302 // calculate X to ensure it is in the shape
1303 AxisAlignedBoxBounds bounds = text.getLogicalHighlightShape(0, text.getCharacterCount());
1304
1305 if (bounds.getWidth() < 1) {
1306 bounds.getSize().width = 10;
1307 }
1308
1309 double x = bounds.getCentreX();
1310
1311 if (bounds.contains((int) x, (int) (mouseY - y))) {
1312 return getTextLayouts().indexOf(text);
1313 }
1314
1315 // check if the cursor is between lines
1316 if (mouseY - y < bounds.getMinY()) {
1317 return Math.max(0, getTextLayouts().indexOf(text) - 1);
1318 }
1319
1320 y += getLineDrop(text);
1321 }
1322
1323 return getTextLayouts().size() - 1;
1324 }
1325
1326 /**
1327 * Sets the Font that this text will be displayed with on the screen.
1328 *
1329 * @param font
1330 * The Font to display the Text of this Item in.
1331 */
1332 public void setFont(Font font) {
1333 invalidateAll();
1334
1335 _font = font;
1336
1337 rebuild(false);
1338
1339 invalidateAll();
1340
1341 if (isSurrogate()) {
1342 surrogatePropertyInheritance.put(DefaultFrameWriter.FONT_STR, false);
1343 Item primary = getPrimary();
1344 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.FONT_STR)) {
1345 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
1346 primary.primaryPropertyEncryption.put(DefaultFrameWriter.FONT_STR, inheritanceCheckOnSave);
1347 }
1348 }
1349 }
1350
1351 /**
1352 * Gets the font of this text item.
1353 *
1354 * @return The Font assigned to this text item, or null if none is assigned.
1355 */
1356 public Font getFont() {
1357 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.FONT_STR)) {
1358 return ((Text) this.getPrimary()).getFont();
1359 } else {
1360 return _font;
1361 }
1362 }
1363
1364 /**
1365 * Gets the font that should be used to paint this text item during drawing.
1366 *
1367 * @return The font to paint the text item with.
1368 */
1369 public Font getPaintFont() {
1370 Font f = getFont();
1371 if (f == null) {
1372 _font = EcosystemManager.getFontManager().getDefaultFont().clone();
1373 f = _font;
1374 }
1375 return f;
1376 }
1377
1378 public String getFamily() {
1379 return getPaintFont().getFamilyName();
1380 }
1381
1382 public void setFamily(String newFamily) {
1383 setFont(new Font(newFamily, getFontStyle(), Math.round(getSize())));
1384
1385 setLetterSpacing(this._letter_spacing);
1386 }
1387
1388 public Font.Style getFontStyle() {
1389 Font f = getPaintFont();
1390 return f.getStyle();
1391 }
1392
1393 public static final String MONOSPACED_FONT = "monospaced";
1394
1395 public static final String[] FONT_WHEEL = { "sansserif", "monospaced", "serif", "dialog", "dialoginput" };
1396 public static final char[] FONT_CHARS = { 's', 'm', 't', 'd', 'i' };
1397
1398 // A hashtable to store the font family names that are loaded in through the TTF
1399 // files
1400 // provided in the 'assets' folder
1401 public static HashMap<String, Font> FONT_WHEEL_ADDITIONAL_LOOKUP = new HashMap<>();
1402
1403 private static final int NEARBY_GRAVITY = 2;
1404
1405 public static final int MINIMUM_FONT_SIZE = 6;
1406
1407 public void toggleFontFamily() {
1408 String fontFamily = getFamily().toLowerCase();
1409 // set it to the first font by default
1410 setFamily(FONT_WHEEL[0]);
1411
1412 for (int i = 0; i < FONT_WHEEL.length - 3; i++) {
1413 if (fontFamily.equals(FONT_WHEEL[i])) {
1414 setFamily(FONT_WHEEL[i + 1]);
1415 break;
1416 }
1417 }
1418 }
1419
1420 public void toggleFontStyle() {
1421 invalidateAll();
1422 Font currentFont = getPaintFont();
1423 Font.Style currentStyle = currentFont.getStyle();
1424 Font.Style newStyle = Font.Style.PLAIN;
1425 switch (currentStyle) {
1426 case PLAIN:
1427 newStyle = Font.Style.BOLD;
1428 break;
1429 case BOLD:
1430 newStyle = Font.Style.ITALIC;
1431 break;
1432 case ITALIC:
1433 newStyle = Font.Style.BOLD_ITALIC;
1434 break;
1435 default:
1436 newStyle = Font.Style.PLAIN;
1437 break;
1438 }
1439 setFont(new Font(currentFont.getFamilyName(), newStyle, currentFont.getSize()));
1440 rebuild(true);
1441 invalidateAll();
1442 }
1443
1444 public void toggleBold() {
1445 invalidateAll();
1446 Font currentFont = getPaintFont();
1447 currentFont.toggleBold();
1448 // setFont(currentFont);
1449 rebuild(true);
1450 invalidateAll();
1451 }
1452
1453 public void toggleItalics() {
1454 invalidateAll();
1455 Font currentFont = getPaintFont();
1456 currentFont.toggleItalic();
1457 // setFont(currentFont);
1458 rebuild(true);
1459 invalidateAll();
1460 }
1461
1462 public void setFontStyle(String newFace) {
1463 Font currentFont = getPaintFont();
1464 if (newFace == null || newFace.trim().length() == 0) {
1465 currentFont.setStyle(Font.Style.PLAIN);
1466 // setFont(currentFont);
1467 return;
1468 }
1469
1470 newFace = newFace.toLowerCase().trim();
1471
1472 if (newFace.equals("plain") || newFace.equals("p")) {
1473 currentFont.setStyle(Font.Style.PLAIN);
1474 } else if (newFace.equals("bold") || newFace.equals("b")) {
1475 currentFont.setStyle(Font.Style.BOLD);
1476 } else if (newFace.equals("italic") || newFace.equals("i")) {
1477 currentFont.setStyle(Font.Style.ITALIC);
1478 } else if (newFace.equals("bolditalic") || newFace.equals("italicbold") || newFace.equals("bi")
1479 || newFace.equals("ib")) {
1480 currentFont.setStyle(Font.Style.BOLD_ITALIC);
1481 }
1482
1483 // setFont(currentFont);
1484
1485 }
1486
1487 /**
1488 * Returns a String array of this Text object's text, split up into separate
1489 * lines.
1490 *
1491 * @return The String array with one element per line of text in this Item.
1492 */
1493 public List<String> getTextList() {
1494 if (_text == null) {
1495 return null;
1496 }
1497 try {
1498 List<String> list = new LinkedList<String>();
1499
1500 // Rebuilding prevents errors when displaying frame bitmaps
1501 if (getTextLayouts().size() == 0) {
1502 rebuild(false);
1503 }
1504
1505 for (TextLayout layout : getTextLayouts()) {
1506 String text = layout.getLine().replaceAll("\n", "");
1507 if (!text.equals("")) {
1508 list.add(text);
1509 }
1510 }
1511
1512 return list;
1513 } catch (Exception e) {
1514 System.out.println("Exception in Text::getTextList::message is: " + e.getMessage());
1515 return null;
1516 }
1517 }
1518
1519 @Override
1520 public String getText() {
1521 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.TEXT_STR)) {
1522 return this.getPrimary().getText();
1523 } else {
1524 return _text.toString();
1525 }
1526 }
1527
1528 /**
1529 * Returns the first line of text in this Text Item
1530 *
1531 * @return The first line of Text
1532 */
1533 public String getFirstLine() {
1534 if (_text == null || _text.length() == 0) {
1535 return null;
1536 }
1537
1538 // start at the first non-newLine char
1539 int index = 0;
1540 while (_text.charAt(index) == '\n') {
1541 index++;
1542 }
1543
1544 int nextNewLine = _text.indexOf("\n", index);
1545
1546 /* If there are no more newLines return the remaining text */
1547 if (nextNewLine < 0) {
1548 return _text.substring(index);
1549 }
1550
1551 return _text.substring(index, nextNewLine);
1552 }
1553
1554 /**
1555 * Sets the inter-line spacing (in pixels) of this text.
1556 *
1557 * @param spacing
1558 * The number of pixels to allow between each line
1559 */
1560 public void setSpacing(float spacing) {
1561 _spacing = spacing;
1562 invalidateBounds();
1563
1564 if (isSurrogate()) {
1565 surrogatePropertyInheritance.put(DefaultFrameWriter.SPACING_STR, false);
1566 Item primary = getPrimary();
1567 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.SPACING_STR)) {
1568 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
1569 primary.primaryPropertyEncryption.put(DefaultFrameWriter.SPACING_STR, inheritanceCheckOnSave);
1570 }
1571 }
1572 }
1573
1574 /**
1575 * Returns the inter-line spacing (in pixels) of this Text.
1576 *
1577 * @return The spacing (inter-line) in pixels of this Text.
1578 */
1579 public float getSpacing() {
1580 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.SPACING_STR)) {
1581 return ((Text) this.getPrimary()).getSpacing();
1582 } else {
1583 return _spacing;
1584 }
1585 }
1586
1587 /**
1588 * Gets the y-distance that should be advanced between this layout and the next.
1589 *
1590 * @param layout
1591 * The TextLayout to calculate line-drop for.
1592 *
1593 * @return The distance to advance in the y-direction before the next line.
1594 */
1595 protected float getLineDrop(TextLayout layout) {
1596 if (getSpacing() < 0) {
1597 return layout.getAscent() + layout.getDescent() + layout.getLeading();
1598 }
1599
1600 return layout.getAscent() + layout.getDescent() + getSpacing();
1601 }
1602
1603 public void setWordSpacing(int spacing) {
1604 _word_spacing = spacing;
1605
1606 if (isSurrogate()) {
1607 surrogatePropertyInheritance.put(DefaultFrameWriter.WORD_SPACING_STR, false);
1608 Item primary = getPrimary();
1609 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.WORD_SPACING_STR)) {
1610 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
1611 primary.primaryPropertyEncryption.put(DefaultFrameWriter.WORD_SPACING_STR, inheritanceCheckOnSave);
1612 }
1613 }
1614 }
1615
1616 public int getWordSpacing() {
1617 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.WORD_SPACING_STR)) {
1618 return ((Text) this.getPrimary()).getWordSpacing();
1619 } else {
1620 return _word_spacing;
1621 }
1622 }
1623
1624 /**
1625 * Sets the spacing (proportional to the font size) between letters
1626 *
1627 * @param spacing
1628 * Additional spacing to add between letters. See
1629 * {@link java.awt.font.TextAttribute#TRACKING}
1630 */
1631 public void setLetterSpacing(float spacing) {
1632 _letter_spacing = spacing;
1633
1634 Font currentFont = getPaintFont();
1635 currentFont.setSpacing(spacing);
1636 // setFont(currentFont);
1637
1638 if (isSurrogate()) {
1639 surrogatePropertyInheritance.put(DefaultFrameWriter.LETTER_SPACING_STR, false);
1640 Item primary = getPrimary();
1641 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.LETTER_SPACING_STR)) {
1642 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
1643 primary.primaryPropertyEncryption.put(DefaultFrameWriter.LETTER_SPACING_STR, inheritanceCheckOnSave);
1644 }
1645 }
1646 }
1647
1648 /**
1649 * @return The spacing (proportional to the font size) between letters. See
1650 * {@link java.awt.font.TextAttribute#TRACKING}
1651 */
1652 public float getLetterSpacing() {
1653 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.LETTER_SPACING_STR)) {
1654 return ((Text) this.getPrimary()).getLetterSpacing();
1655 } else {
1656 return _letter_spacing;
1657 }
1658 }
1659
1660 public void setInitialSpacing(float spacing) {
1661 _initial_spacing = spacing;
1662
1663 if (isSurrogate()) {
1664 surrogatePropertyInheritance.put(DefaultFrameWriter.INITIAL_SPACING_STR, false);
1665 Item primary = getPrimary();
1666 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.INITIAL_SPACING_STR)) {
1667 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
1668 primary.primaryPropertyEncryption.put(DefaultFrameWriter.INITIAL_SPACING_STR, inheritanceCheckOnSave);
1669 }
1670 }
1671 }
1672
1673 public float getInitialSpacing() {
1674 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.INITIAL_SPACING_STR)) {
1675 return ((Text) this.getPrimary()).getInitialSpacing();
1676 } else {
1677 return _initial_spacing;
1678 }
1679 }
1680
1681 // @Override
1682 /*
1683 * public boolean intersectsOLD(Polygon p) { if (super.intersects(p)) { float
1684 * textY = getY();
1685 *
1686 * for (TextLayout text : _textLayouts) { // check left and right of each box
1687 * Rectangle2D textOutline = text.getLogicalHighlightShape(0,
1688 * text.getCharacterCount()).getBounds2D();
1689 * textOutline.setRect(textOutline.getX() + getX() - 1, textOutline.getY() +
1690 * textY - 1, textOutline.getWidth() + 2, textOutline.getHeight() + 2); if
1691 * (p.intersects(textOutline)) return true; textY += getLineDrop(text); } }
1692 * return false; }
1693 */
1694
1695 // The following version of intersect uses a tighter definition for the text,
1696 // based on
1697 @Override
1698 public boolean intersects(PolygonBounds p) {
1699 if (super.intersects(p)) {
1700 // float textY = getY();
1701
1702 for (TextLayout text : getTextLayouts()) {
1703
1704 AxisAlignedBoxBounds text_pixel_bounds_rect = getPixelBounds(text);
1705
1706 if (p.intersects(text_pixel_bounds_rect)) {
1707 return true;
1708 }
1709 }
1710 }
1711
1712 return false;
1713 }
1714
1715 @Override
1716 public boolean contains(Point mousePosition) {
1717 return contains(mousePosition.getX(), mousePosition.getY(), getGravity() * NEARBY_GRAVITY);
1718 }
1719
1720 public boolean contains(int mouseX, int mouseY) {
1721 return contains(new Point(mouseX, mouseY));
1722 }
1723
1724 public boolean contains(int mouseX, int mouseY, int gravity) {
1725 mouseX += getOffset().getX();
1726 mouseY += getOffset().getY();
1727
1728 float textY = getY();
1729 float textX = getX();
1730
1731 AxisAlignedBoxBounds outline = getBoundingBox();
1732
1733 if (outline == null) {
1734 return false;
1735 }
1736
1737 // Check if its outside the top and left and bottom bounds
1738 if (outline.getMinX() - mouseX > gravity || outline.getMinY() - mouseY > gravity
1739 || mouseY - (outline.getMinY() + outline.getHeight()) > gravity
1740 || mouseX - (outline.getMinX() + outline.getWidth()) > gravity) {
1741 return false;
1742 }
1743
1744 if (this.getMinWidth() != null && outline.contains(mouseX, mouseY)) {
1745 return true;
1746 }
1747
1748 for (TextLayout text : getTextLayouts()) {
1749 // check left and right of each box
1750 AxisAlignedBoxBounds textOutline = text.getLogicalHighlightShape(0, text.getCharacterCount());
1751
1752 // check if the cursor is within the top, bottom and within the
1753 // gravity of right
1754 int justOffset = getJustOffset(text);
1755
1756 if (mouseY - textY > textOutline.getMinY()
1757 && mouseY - textY < textOutline.getMinY() + textOutline.getHeight()
1758 && mouseX - textX - justOffset < textOutline.getWidth() + gravity + Item.MARGIN_RIGHT) {
1759 return true;
1760 }
1761
1762 textY += getLineDrop(text);
1763 }
1764
1765 return false;
1766 }
1767
1768 /**
1769 * Updates the Polygon (rectangle) that surrounds this Text on the screen.
1770 */
1771 @Override
1772 public AxisAlignedBoxBounds updateBounds() {
1773 boolean isFakeLayout = false;
1774 // if there is no text, there is nothing to do
1775 if (_text == null) {
1776 return null;
1777 }
1778
1779 // if there is no text layouts and the text has no min width, do nothing
1780 if (getTextLayouts() == null || (getTextLayouts().size() < 1 && this.getMinWidth() == null)) {
1781 return null;
1782 }
1783
1784 if (this.getMinWidth() != null && getTextLayouts().size() == 0 && this.getFont() != null) {
1785 getTextLayouts().add(TextLayout.getManager().layoutStringSimple("p", this.getFont()));
1786 isFakeLayout = true;
1787 }
1788
1789 int preChangeWidth = 0;
1790 if (getOldBounds() != null) {
1791 preChangeWidth = AxisAlignedBoxBounds.getEnclosing(getOldBounds()).getWidth();
1792 }
1793
1794 int minX = Integer.MAX_VALUE;
1795 int maxX = Integer.MIN_VALUE + 1; // +1 makes it safe to do math.abs on
1796
1797 int minY = Integer.MAX_VALUE;
1798 int maxY = Integer.MIN_VALUE + 1; // +1 makes it safe to do math.abs on
1799
1800 float y = -1;
1801
1802 // Fix concurrency error in ScaleFrameset
1803 List<TextLayout> tmpTextLayouts;
1804 synchronized (getTextLayouts()) {
1805 tmpTextLayouts = new LinkedList<TextLayout>(getTextLayouts());
1806 }
1807
1808 for (int index = 0; index < tmpTextLayouts.size(); index++) {
1809 final TextLayout layout = tmpTextLayouts.get(index);
1810 AxisAlignedBoxBounds bounds = layout.getLogicalHighlightShape(0, layout.getCharacterCount());
1811
1812 if (y < 0) {
1813 y = 0;
1814 } else {
1815 y += getLineDrop(layout);
1816 }
1817
1818 int minWidth = getAbsoluteMinWidth();
1819
1820 minX = Math.min(minX, bounds.getMinX());
1821 maxX = minWidth < Integer.MAX_VALUE ? Math.max(minX + minWidth, bounds.getMaxX())
1822 : Math.max(maxX, bounds.getMaxX());
1823 minY = Math.min(minY, (int) (bounds.getMinY() + y));
1824 maxY = Math.max(maxY, (int) (bounds.getMaxY() + y));
1825 }
1826
1827 minX -= getLeftMargin();
1828 maxX += Item.MARGIN_RIGHT;
1829
1830 // If its justification right or center then DONT limit the width
1831 if (getJustification() != null) {
1832 maxX = Item.MARGIN_RIGHT + getAbsoluteWidth();
1833 }
1834
1835 final int xPos = getX() + minX - getGravity();
1836 final int yPos = getY() + minY - getGravity();
1837 final int width = 2 * getGravity() + maxX - minX;
1838 final int height = 2 * getGravity() + maxY - minY;
1839 AxisAlignedBoxBounds ret = new AxisAlignedBoxBounds(xPos, yPos, width, height);
1840
1841 Dimension polySize = ret.getSize();
1842
1843 if (preChangeWidth != 0 && preChangeWidth != polySize.width) {
1844 if (polySize.width > preChangeWidth) {
1845 MagneticConstraints.getInstance().textGrown(this, polySize.width - preChangeWidth);
1846 } else {
1847 MagneticConstraints.getInstance().textShrunk(this, preChangeWidth - polySize.width);
1848 }
1849 }
1850
1851 if (isFakeLayout) {
1852 getTextLayouts().remove(0).release();
1853 }
1854
1855 return ret;
1856 }
1857
1858 // TODO it seems like this method has some exponential processing which
1859 // makes items copy really slowly when there are lots of lines of text!
1860 // This needs to be fixed!!
1861 public void rebuild(boolean limitWidth) {
1862 rebuild(limitWidth, true);
1863 }
1864
1865 /**
1866 *
1867 * @param limitWidth
1868 * @param newLinebreakerAlways
1869 * true if a new line breaker should always be created.
1870 */
1871 private void rebuild(boolean limitWidth, boolean newLinebreakerAlways) {
1872 // TODO make this more efficient so it only clears annotation list when it
1873 // really has to
1874 if (isAnnotation()) {
1875 Frame parent = getParent();
1876 // parent can be null when running tests
1877 if (parent != null) {
1878 parent.clearAnnotations();
1879 }
1880 }
1881
1882 // if there is no text, there is nothing to do
1883 if ((_text == null || _text.length() == 0 ) && getPlaceholder() == null) {
1884 // Frame parent = getParent();
1885 // if(parent != null)
1886 // parent.removeItem(this);
1887 return;
1888 }
1889
1890 EcosystemManager.getTextLayoutManager().releaseLayouts(getTextLayouts());
1891 if (getTextLayouts() != null) {
1892 getTextLayouts().clear();
1893 }
1894 EcosystemManager.getTextLayoutManager().releaseLayouts(_maskTextLayouts);
1895 if (_maskTextLayouts != null) {
1896 _maskTextLayouts.clear();
1897 }
1898 EcosystemManager.getTextLayoutManager().releaseLayouts(_placeholderTextLayouts);
1899 if (_placeholderTextLayouts != null) {
1900 _placeholderTextLayouts.clear();
1901 }
1902
1903 // Calculate the maximum allowable width of this line of text
1904 List<org.expeditee.core.Line> lines = null;
1905 if (_autoWrap || ExperimentalFeatures.AutoWrap.get()) {
1906 lines = new LinkedList<org.expeditee.core.Line>();
1907 if (DisplayController.getCurrentFrame() == null) {
1908 return;
1909 }
1910 for (Item item : DisplayController.getCurrentFrame().getSortedItems()) {
1911 if (item instanceof Line) {
1912 lines.add(new org.expeditee.core.Line(((Line) item).getStartItem().getPosition(),
1913 ((Line) item).getEndItem().getPosition()));
1914 }
1915 if (item instanceof Picture) {
1916 lines.add(new org.expeditee.core.Line(item.getPosition(),
1917 new Point(item.getX(), item.getY() + item.getHeight())));
1918 }
1919 }
1920 for (Item item : FreeItems.getInstance()) {
1921 if (item instanceof Line) {
1922 lines.add(new org.expeditee.core.Line(((Line) item).getStartItem().getPosition(),
1923 ((Line) item).getEndItem().getPosition()));
1924 }
1925 if (item instanceof Picture) {
1926 lines.add(new org.expeditee.core.Line(item.getPosition(),
1927 new Point(item.getX(), item.getY() + item.getHeight())));
1928 }
1929 }
1930 }
1931
1932 float width = (float) Integer.MAX_VALUE;
1933 if (limitWidth) {
1934 if (_width == null) {
1935 width = DisplayController.getFramePaintAreaWidth() - getX();
1936 } else {
1937 width = getAbsoluteWidth();
1938 }
1939 }
1940
1941 Font paintFont = getPaintFont();
1942 if (this._text != null && this._text.length() > 0) {
1943 List<TextLayout> proposedTextLayout =
1944 EcosystemManager.getTextLayoutManager().layoutString(
1945 _text.toString(),
1946 paintFont,
1947 new Point(getX(), getY()), lines != null ? lines.toArray(new org.expeditee.core.Line[1]) : null,
1948 (int) width,
1949 (int) getSpacing(),
1950 true,
1951 getJustification() == Justification.full
1952 );
1953 if (proposedTextLayout.size() > 1 && isSingleLineOnly()) {
1954 paintFont = paintFont.clone();
1955
1956 while(proposedTextLayout.size() > 1) {
1957 paintFont.setSize(Math.max(paintFont.getSize() - 1, MINIMUM_FONT_SIZE));
1958 proposedTextLayout =
1959 EcosystemManager.getTextLayoutManager().layoutString(
1960 _text.toString(),
1961 paintFont,
1962 new Point(getX(), getY()), lines != null ? lines.toArray(new org.expeditee.core.Line[1]) : null,
1963 paintFont.getSize() > MINIMUM_FONT_SIZE ? (int) width : Integer.MAX_VALUE,
1964 (int) getSpacing(),
1965 true,
1966 getJustification() == Justification.full
1967 );
1968 }
1969 }
1970 this._textLayouts = proposedTextLayout;
1971
1972 if (this.getMask() != null) {
1973 final Stream<Character> maskStream = _text.toString().chars().mapToObj(c -> (char) this.getMask().intValue());
1974 final StringBuilder sb = new StringBuilder();
1975 maskStream.forEach(c -> sb.append(c));
1976 this._maskTextLayouts =
1977 EcosystemManager.getTextLayoutManager().layoutString(
1978 sb.toString(),
1979 paintFont,
1980 new Point(getX(), getY()), lines != null ? lines.toArray(new org.expeditee.core.Line[1]) : null,
1981 (int) width,
1982 (int) getSpacing(),
1983 true,
1984 getJustification() == Justification.full
1985 );
1986 }
1987 }
1988 if (this.getPlaceholder() != null) {
1989 this._placeholderTextLayouts =
1990 EcosystemManager.getTextLayoutManager().layoutString(
1991 getPlaceholder(),
1992 paintFont,
1993 new Point(getX(), getY()), lines != null ? lines.toArray(new org.expeditee.core.Line[1]) : null,
1994 (int) width,
1995 (int) getSpacing(),
1996 true,
1997 getJustification() == Justification.full
1998 );
1999 }
2000 invalidateBounds();
2001 }
2002
2003 /**
2004 * Calculates the maximum possible distance a line can extend to the right from
2005 * a given (x,y) point without crossing any of the lines in the given list.
2006 *
2007 * @param x
2008 * The x-coordinate of the beginning point.
2009 *
2010 * @param y
2011 * The y-coordinate of the beginning point.
2012 *
2013 * @param lines
2014 * A list of pairs of points describing the lines that should stop
2015 * the extension.
2016 *
2017 * @return The length of the extended line.
2018 */
2019 /*
2020 * private float getLineWidth(int x, float y, List<Point[]> lines) { float width
2021 * = FrameGraphics.getMaxFrameSize().width; for (Point[] l : lines) { // check
2022 * for lines that cross over our y if ((l[0].y >= y && l[1].y <= y) || (l[0].y
2023 * <= y && l[1].y >= y)) { float dX = l[0].x - l[1].x; float dY = l[0].y -
2024 * l[1].y; float newWidth; if (dX == 0) { newWidth = l[0].x; } else if (dY == 0)
2025 * { newWidth = Math.min(l[0].x, l[1].x); } else { //
2026 * System.out.print("gradient: " + (dY / dX)); newWidth = l[0].x + (y - l[0].y)
2027 * * dX / dY; } // System.out.println("dY:" + dY + " dX:" + dX + " width:" +
2028 * newWidth); if (newWidth < x) { continue; } if (newWidth < width) { width =
2029 * newWidth; } } } return width - x; }
2030 */
2031
2032 private boolean hasFixedWidth() {
2033 assert (_width != null);
2034 if (_width == null) {
2035 justify(false);
2036 }
2037 return _width > 0;
2038 }
2039
2040 private int _alpha = -1;
2041
2042 public void setAlpha(int alpha) {
2043 _alpha = alpha;
2044 }
2045
2046 private Range<Integer> getSelectedRange(int line) {
2047 if (_selectionEnd >= _text.length()) {
2048 _selectionEnd = _text.length();
2049 }
2050
2051 if (_selectionStart < 0) {
2052 _selectionStart = 0;
2053 }
2054
2055 if (_selectionStart < 0 || _selectionEnd < 0) {
2056 return null;
2057 }
2058
2059 int selectionLeft = Math.min(_selectionStart, _selectionEnd);
2060 int selectionRight = Math.max(_selectionStart, _selectionEnd);
2061
2062 // if the selection is after this line, return null
2063 if (getTextLayouts().get(line).getStartCharIndex() > selectionRight) {
2064 return null;
2065 }
2066
2067 // if the selection is before this line, return null
2068 if (getTextLayouts().get(line).getEndCharIndex() < selectionLeft) {
2069 return null;
2070 }
2071
2072 // Dont highlight a single char
2073 // if (selectionRight - selectionLeft <= MINIMUM_RANGED_CHARS)
2074 // return null;
2075
2076 // the selection occurs on this line, determine where it lies on the
2077 // line
2078 int start = Math.max(0, selectionLeft - getTextLayouts().get(line).getStartCharIndex());
2079 // int end = Math.min(_lineOffsets.get(line) +
2080 // _textLayouts.get(line).getCharacterCount(), _selectionEnd);
2081 int end = Math.min(selectionRight - getTextLayouts().get(line).getStartCharIndex(),
2082 getTextLayouts().get(line).getCharacterCount());
2083
2084 // System.out.println(line + ": " + start + "x" + end + " (" +
2085 // _selectionStart + "x" + _selectionEnd + ")");
2086 return new Range<Integer>(start, end, true, true);
2087 }
2088
2089 /** Sets the colour that should be used to render the selected range. */
2090 public void setSelectionColour(Colour colour) {
2091 if (colour == null) {
2092 colour = RANGE_SELECT_COLOUR;
2093 }
2094
2095 _selectionColour = colour;
2096 }
2097
2098 /** Gets the colour that should be used to render the selected range. */
2099 public Colour getSelectionColour() {
2100 return _selectionColour;
2101 }
2102
2103 @Override
2104 public void paint() {
2105 if (!isVisible()) {
2106 return;
2107 }
2108
2109 boolean hasContent = !(_text == null || (_text.length() == 0 && getMinWidth() == null));
2110 boolean hasPlaceholderContent = getPlaceholder() != null;
2111
2112
2113 // if the text to paint is empty string and there is no min width, do nothing.
2114 if (!hasContent && !hasPlaceholderContent) {
2115 return;
2116 }
2117
2118 if (_autoWrap || ExperimentalFeatures.AutoWrap.get()) {
2119 invalidateAll();
2120
2121 rebuild(true);
2122 } else if (getTextLayouts().size() < 1) {
2123 clipFrameMargin();
2124 rebuild(true);
2125 // return;
2126 }
2127
2128 // check if its a vector item and paint all the vector stuff too if this
2129 // item is a free item
2130 // This will allow for dragging vectors around the place!
2131 if (hasVector() && isFloating()) {
2132 DisplayController.requestRefresh(false);
2133 // TODO make this use a more efficient paint method...
2134 // Have the text item return a bigger repaint area if it has an
2135 // associated vector
2136 }
2137
2138 GraphicsManager g = EcosystemManager.getGraphicsManager();
2139 AxisAlignedBoxBounds bounds = (AxisAlignedBoxBounds) getBounds();
2140
2141 // the background is only cleared if required
2142 if (getBackgroundColor() != null) {
2143 Colour bgc = getBackgroundColor();
2144 if (_alpha > 0) {
2145 bgc = new Colour(bgc.getRed(), bgc.getGreen(), bgc.getBlue(), Colour.FromComponent255(_alpha));
2146 }
2147
2148 Colour gradientColor = getGradientColor();
2149
2150 Fill fill;
2151
2152 if (gradientColor != null && bounds != null) {
2153 fill = new GradientFill(bgc,
2154 new Point((int) (bounds.getMinX() + bounds.getWidth() * 0.3), bounds.getMinY()), gradientColor,
2155 new Point((int) (bounds.getMinX() + bounds.getWidth() * 1.3), bounds.getMinY()));
2156 } else {
2157 fill = new Fill(bgc);
2158 }
2159
2160 g.drawRectangle(bounds, 0.0, fill, null, null, null);
2161 }
2162
2163 if (hasVisibleBorder()) {
2164 Stroke borderStroke = new Stroke(getThickness(), DEFAULT_CAP, DEFAULT_JOIN);
2165 g.drawRectangle(bounds, 0.0, null, getPaintBorderColor(), borderStroke, null);
2166 }
2167
2168 if (hasFormula()) {
2169 Stroke highlightStroke = new Stroke(1F, DEFAULT_CAP, DEFAULT_JOIN);
2170 Point start = getEdgePosition(0, true);
2171 Point end = getEdgePosition(0, false);
2172 g.drawLine(start, end, getPaintHighlightColor(), highlightStroke);
2173 }
2174
2175 if (isHighlighted()) {
2176 Stroke highlightStroke = new Stroke(getHighlightThickness(), DEFAULT_CAP, DEFAULT_JOIN);
2177 Fill fill;
2178 if (HighlightMode.Enclosed.equals(getHighlightMode())) {
2179 fill = new Fill(getPaintHighlightColor());
2180 } else {
2181 fill = null;
2182 }
2183 g.drawRectangle(bounds, 0.0, fill, getPaintHighlightColor(), highlightStroke, null);
2184 }
2185
2186 float y = getY();
2187 Colour paintColour = getPaintColor();
2188 if (_alpha > 0) {
2189 paintColour = new Colour(paintColour);
2190 paintColour.setAlpha(Colour.FromComponent255(_alpha));
2191 }
2192 if (getTextLayouts() == _placeholderTextLayouts) {
2193 paintColour = paintColour.clone();
2194 paintColour.setAlpha(Colour.FromComponent255(60));
2195 }
2196
2197 Colour selectionColour = getSelectionColour();
2198
2199 // width -= getX();
2200 // int line = 0;
2201 // boolean tab = false;
2202 synchronized (getTextLayouts()) {
2203 for (int i = 0; i < getTextLayouts().size(); i++) {
2204 TextLayout layout = getTextLayouts().get(i);
2205
2206 Range<Integer> selectedRange = getSelectedRange(i);
2207 if (selectedRange != null) {
2208 AxisAlignedBoxBounds highlight = layout.getLogicalHighlightShape(selectedRange.lowerBound,
2209 selectedRange.upperBound);
2210 highlight.getTopLeft().add(getX() + getJustOffset(layout), (int) y);
2211 g.drawRectangle(highlight, 0.0, new Fill(selectionColour), null, null, null);
2212 }
2213
2214 if (layout.getCharacterCount() == 0) {
2215 continue;
2216 }
2217 int ldx = 1 + getX() + getJustOffset(layout); // Layout draw x
2218 g.drawTextLayout(layout, new Point(ldx, (int) y), paintColour);
2219
2220 y += getLineDrop(layout);
2221 }
2222 }
2223
2224 paintLink();
2225 }
2226
2227 // TODO: Revise
2228 @Override
2229 protected AxisAlignedBoxBounds getLinkDrawArea() {
2230 return getDrawingArea();
2231 }
2232
2233 /**
2234 * Determines if this text has any text in it.
2235 *
2236 * @return True if this Item has no text in it, false otherwise.
2237 */
2238 public boolean isEmpty() {
2239 return (_text == null || _text.length() == 0);
2240 }
2241
2242 @Override
2243 public Text copy() {
2244 Text copy = new Text(getID());
2245 // copy standard item values
2246 Item.DuplicateItem(this, copy);
2247
2248 // copy values specific to text items
2249 copy.setSpacing(getSpacing());
2250 copy.setInitialSpacing(getInitialSpacing());
2251
2252 copy.setWidth(getWidthToSave());
2253 copy.setJustification(getJustification());
2254 copy.setLetterSpacing(getLetterSpacing());
2255 copy.setWordSpacing(getWordSpacing());
2256 copy.setWidth(getWidthToSave());
2257 copy.setFont(getFont().clone());
2258 copy.setMinWidth(getMinWidthToSave());
2259 copy.setMask(_mask);
2260 if (hasFormula()) {
2261 copy.calculate(getFormula());
2262 } else {
2263 copy.setText(_text.toString());
2264 }
2265 copy.setHidden(!isVisible());
2266 return copy;
2267 }
2268
2269 @Override
2270 public float getSize() {
2271 return getPaintFont().getSize();
2272 }
2273
2274 /**
2275 * Returns the number of characters in this Text, excluding new lines.
2276 *
2277 * @return The sum of the length of each line of text
2278 */
2279 public int getLength() {
2280 return _text.length();
2281 }
2282
2283 @Override
2284 public void setSize(float size) {
2285 invalidateAll();
2286 // size *= UserSettings.ScaleFactor;
2287 // Dont want to have size set when duplicating a point which has size 0
2288 if (size < 0) {
2289 return;
2290 }
2291
2292 if (size < MINIMUM_FONT_SIZE) {
2293 size = MINIMUM_FONT_SIZE;
2294 }
2295 Font currentFont = getPaintFont();
2296 currentFont.setSize((int) size);
2297 // setFont(currentFont);
2298 rebuild(true);
2299 invalidateAll();
2300 }
2301
2302 @Override
2303 public void setAnnotation(boolean val) {
2304 float mouseX = DisplayController.getFloatMouseX();
2305 float mouseY = DisplayController.getFloatMouseY();
2306 Point newPoint = new Point();
2307 if (val) {
2308 // if this is already an annotation, do nothing
2309 if (isAnnotation()) {
2310 return;
2311 }
2312 if (!isLineEnd() && _text.length() > 0 && _text.charAt(0) == DEFAULT_BULLET) {
2313 newPoint.set(insertText("\b", mouseX, mouseY, 1));
2314 if (_text.length() > 0 && _text.charAt(0) == ' ') {
2315 newPoint.set(insertText("\b", newPoint.getX(), newPoint.getY(), 1));
2316 }
2317 } else {
2318 newPoint.set(insertText("@", mouseX, mouseY, 0));
2319 }
2320 } else {
2321 // if this is not an annotation, do nothing
2322 if (!isAnnotation()) {
2323 return;
2324 }
2325 if (!isLineEnd() && _text.charAt(0) == '@') {
2326 newPoint.set(insertText("\b", mouseX, mouseY, 1));
2327 newPoint.set(insertText(DEFAULT_BULLET_STRING, newPoint.getX(), newPoint.getY(), 0));
2328 } else {
2329 newPoint.set(insertText("\b", mouseX, mouseY, 1));
2330 }
2331 }
2332 FrameUtils.setLastEdited(this);
2333 rebuild(true);
2334 DisplayController.setCursorPosition(newPoint.getX(), newPoint.getY(), false);
2335 }
2336
2337 /**
2338 *
2339 */
2340 private void insertString(String toInsert, int pos) {
2341 assert (toInsert.length() > 0);
2342 invalidateAll();
2343 _text.insert(pos, toInsert);
2344 rebuild(false);
2345 invalidateAll();
2346 }
2347
2348 private void deleteChar(int pos) {
2349 _text.deleteCharAt(pos);
2350
2351 if (_text.length() == 0) {
2352 if (this.getMinWidth() != null) {
2353 // final TextLayout base = _textLayouts.get(0);
2354 // _textLayouts.set(0, TextLayout.get(" ", base.getFont(), 0, 1));
2355 getTextLayouts().clear();
2356 }
2357 if (this.isLineEnd()) {
2358 // Remove and replace with a dot
2359 Item.replaceText(this);
2360 DisplayController.setCursorPosition(this._x, this._y);
2361 }
2362 return;
2363 }
2364
2365 }
2366
2367 @Override
2368 public boolean isAnnotation() {
2369 if (_text != null && _text.length() > 0 && _text.charAt(0) == '@') {
2370 return true;
2371 }
2372
2373 return false;
2374 }
2375
2376 public boolean isSpecialAnnotation() {
2377 assert _text != null;
2378 String s = _text.toString().toLowerCase();
2379 if (s.length() > 0 && s.indexOf("@") == 0) {
2380 if (s.equals("@old") || s.equals("@ao") || s.equals("@itemtemplate") || s.equals("@parent")
2381 || s.equals("@next") || s.equals("@previous") || s.equals("@first") || s.equals("@i")
2382 || s.equals("@iw") || s.equals("@f")) {
2383 return true;
2384 }
2385 }
2386
2387 return false;
2388 }
2389
2390 @Override
2391 public Item merge(Item merger, int mouseX, int mouseY) {
2392 if (merger.isLineEnd()) {
2393 // Merging line ends onto non line end text is a no-op
2394 if (!isLineEnd()) {
2395 return null;
2396 }
2397
2398 if (merger instanceof Text) {
2399 insertText(((Text) merger).getText(), mouseX, mouseY);
2400 }
2401
2402 // Set the position by moving the cursor before calling this
2403 // method!!
2404
2405 List<Line> lines = new LinkedList<Line>();
2406 lines.addAll(merger.getLines());
2407 for (Line line : lines) {
2408 line.replaceLineEnd(merger, this);
2409 }
2410 merger.delete();
2411 this.setOffset(0, 0);
2412 return null;
2413 }
2414
2415 if (!(merger instanceof Text)) {
2416 return merger;
2417 }
2418
2419 Text merge = (Text) merger;
2420
2421 // insertText(merge.getText(), mouseX, mouseY);
2422
2423 // if the item being merged has a link
2424 if (merge.getLink() != null) {
2425 // if this item has a link, keep it on the cursor
2426 if (getLink() != null) {
2427 merge.setText(merge.getLink());
2428 merge.setLink(null);
2429 // return merge;
2430 // TODO get this to return the merged item and attach it to the
2431 // cursor only when the user presses the middle button.
2432 } else {
2433 setLink(merge.getLink());
2434 }
2435 }
2436
2437 return null;
2438 }
2439
2440 /**
2441 * Resets the position of the item to the default position for a title item.
2442 *
2443 */
2444 public void resetTitlePosition() {
2445 final int boundsHeight = getBoundsHeight();
2446 setPosition(MARGIN_LEFT, MARGIN_LEFT + boundsHeight);
2447 Frame modelFrame = getParentOrCurrentFrame();
2448 if (modelFrame != null) {
2449 int model_frame_name_x = modelFrame.getNameItem().getX();
2450 if (model_frame_name_x < DisplayController.MINIMUM_FRAME_WIDTH) {
2451 System.err.println("**** Text::resetTitlePostion(): value to be used as right margin from position of frameName < 512");
2452 System.err.println(" Overriding to ensure reasonable width for title");
2453 model_frame_name_x = DisplayController.MINIMUM_FRAME_WIDTH;
2454 }
2455
2456 setRightMargin(model_frame_name_x - MARGIN_LEFT, true);
2457 } else {
2458 System.out.print("Error: text.resetTitlePosition, getParent or currentFrame returned null");
2459 setRightMargin(MARGIN_LEFT, true);
2460 }
2461 }
2462
2463 /**
2464 * Removes the set of characters up to the first space in this text item.
2465 *
2466 * @return the string that was removed.
2467 */
2468 public String stripFirstWord() {
2469 int firstSpace = _text.toString().indexOf(' ');
2470
2471 // if there is only one word just make it blank
2472 if (firstSpace < 0 || firstSpace + 1 >= _text.length()) {
2473 String text = _text.toString();
2474 setText("");
2475 return text;
2476 }
2477
2478 String firstWord = _text.toString().substring(0, firstSpace);
2479 setText(_text.toString().substring(firstSpace).trim());
2480
2481 return firstWord;
2482 }
2483
2484 @Override
2485 public String toString() {
2486 String message = "[" + getFirstLine() + "]" + FRAME_NAME_SEPARATOR;
2487
2488 if (getParent() != null) {
2489 return message + getParent().getName();
2490 }
2491 return message + getDateCreated();
2492 }
2493
2494 public Text getTemplateForm() {
2495 Text template = this.copy();
2496 template.setID(-1);
2497 // reset width of global templates so the widths of the items on the settings
2498 // frames don't cause issues
2499 // this is in response to the fact that FrameCreator.addItem() sets rightMargin
2500 // when it adds items
2501 template.setWidth(null);
2502 /*
2503 * The template must have text otherwise the bounds height will be zero!! This
2504 * will stop escape drop down from working if there is no item template
2505 */
2506 template.setText("@");
2507 return template;
2508 }
2509
2510 /**
2511 * Checks if the given point is 'near' any line of the text item.
2512 */
2513 @Override
2514 public boolean isNear(int x, int y) {
2515 if (super.isNear(x, y)) {
2516 // TODO check that it is actually near one of the lines of space
2517 // return contains(x, y, getGravity() * 2 + NEAR_DISTANCE);
2518 // at the moment contains ignores gravity when checking the top and
2519 // bottom of text lines... so the cursor must be between two text
2520 // lines
2521 float textY = getY();
2522 float textX = getX();
2523
2524 for (TextLayout text : getTextLayouts()) {
2525 // check left and right of each box
2526 AxisAlignedBoxBounds textOutline = text.getLogicalHighlightShape(0, text.getCharacterCount());
2527
2528 // check if the cursor is within the top, bottom and within the
2529 // gravity of right
2530 if (y - textY > textOutline.getMinY() - NEAR_DISTANCE
2531 && y - textY < textOutline.getMinY() + textOutline.getHeight() + NEAR_DISTANCE
2532 && x - textX < textOutline.getWidth() + NEAR_DISTANCE) {
2533 return true;
2534 }
2535
2536 textY += getLineDrop(text);
2537 }
2538 }
2539 return false;
2540 }
2541
2542 @Override
2543 public void anchor() {
2544 super.anchor();
2545 // ensure all text items have their selection cleared
2546 clearSelection();
2547 setAlpha(0);
2548 if (isLineEnd()) {
2549 DisplayController.setCursor(Item.DEFAULT_CURSOR);
2550 }
2551
2552 String text = _text.toString().trim();
2553
2554 clipFrameMargin();
2555
2556 // Show the overlay stuff immediately if this is an overlay item
2557 if (hasLink() && (text.startsWith("@ao") || text.startsWith("@o"))) {
2558 StandardGestureActions.Refresh();
2559 }
2560 }
2561
2562 private void clipFrameMargin() {
2563 if (!hasFixedWidth()) {
2564 int frameWidth = DisplayController.getFramePaintAreaWidth();
2565 /*
2566 * Only change width if it is more than 150 pixels from the right of the screen
2567 */
2568 if (!_text.toString().contains(" ")) {
2569 Integer width = getWidth();
2570 if (width == null || width < 0) {
2571 setWidth(Integer.MIN_VALUE + 1); // +1 makes it safe to do math.abs on
2572 }
2573 } else if (frameWidth - getX() > ADJUST_WIDTH_THRESHOLD) {
2574 justify(false);
2575 // setRightMargin(frameWidth, false);
2576 }
2577 }
2578 }
2579
2580 public void justify(boolean fixWidth, PolygonBounds enclosure) {
2581 // if autowrap is on, wrapping is done every time we draw
2582 if (ExperimentalFeatures.AutoWrap.get()) {
2583 return;
2584 }
2585
2586 Integer width = DisplayController.getFramePaintAreaWidth();
2587
2588 // Check if that text item is inside an enclosing rectangle...
2589 // Set its max width accordingly
2590 if (enclosure != null) {
2591 AxisAlignedBoxBounds bounds = AxisAlignedBoxBounds.getEnclosing(enclosure);
2592 if (bounds.getWidth() > 200 && getX() < bounds.getWidth() / 3 + bounds.getMinX()) {
2593 width = bounds.getMinX() + bounds.getWidth();
2594 }
2595 }
2596
2597 if (getWidth() == null) {
2598 setRightMargin(width, fixWidth);
2599 }
2600
2601 // Check for the annotation that restricts the width of text items on the frame
2602 String widthString;
2603 if ((widthString = getParentOrCurrentFrame().getAnnotationValue("maxwidth")) != null) {
2604 try {
2605 int oldWidth = getWidth();
2606 int maxWidth = Integer.parseInt(widthString);
2607 if (maxWidth < oldWidth) {
2608 setWidth(maxWidth);
2609 }
2610 } catch (NumberFormatException nfe) {
2611 }
2612
2613 }
2614 }
2615
2616 public void justify(boolean fixWidth) {
2617 // if autowrap is on, wrapping is done every time we draw
2618 if (ExperimentalFeatures.AutoWrap.get()) {
2619 return;
2620 }
2621
2622 this.justify(fixWidth, FrameUtils.getEnlosingPolygon());
2623 }
2624
2625 public void resetFrameNamePosition() {
2626 //Dimension maxSize = DisplayController.getSizeEnforceMinimum();
2627 Dimension maxSize = DisplayController.getFramePaintAreaSize();
2628
2629 if (maxSize != null) {
2630 // setMaxWidth(maxSize.width);
2631 setPosition(maxSize.width - getBoundsWidth(), getBoundsHeight());
2632 }
2633 }
2634
2635 @Override
2636 protected int getLinkYOffset() {
2637 if (getTextLayouts().size() == 0) {
2638 return 0;
2639 }
2640 return Math.round(-(getTextLayouts().get(0).getAscent() / 2));
2641 }
2642
2643 @Override
2644 public String getName() {
2645 return getFirstLine();
2646 }
2647
2648 public static final String TAB_STRING = " ";
2649
2650 public Point insertTab(char ch, float mouseX, float mouseY) {
2651 return insertText("" + ch, mouseX, mouseY);
2652 }
2653
2654 public Point removeTab(char ch, float mouseX, float mouseY) {
2655 // Insert a space as a flag that it is a backwards tab
2656 return insertText(ch + " ", mouseX, mouseY);
2657 }
2658
2659 public static boolean isBulletChar(char c) {
2660 for (int i = 0; i < BULLETS.length; i++) {
2661 if (BULLETS[i] == c) {
2662 return true;
2663 }
2664 }
2665 return c == '*' || c == '+' || c == '>' || c == '-' || c == 'o';
2666 }
2667
2668 @Override
2669 public boolean hasOverlay() {
2670 if (!isAnnotation() || getLink() == null) {
2671 return false;
2672 }
2673 String text = getText().toLowerCase();
2674 // TODO make it so can just check the _overlay variable
2675 // Mike can't remember the reason _overlay var can't be use! oops
2676 if (!text.startsWith("@")) {
2677 return false;
2678 }
2679 return text.startsWith("@o") || text.startsWith("@ao") || text.startsWith("@v") || text.startsWith("@av");
2680 }
2681
2682 public boolean hasSelection() {
2683 return getSelectionSize() > 0;
2684 }
2685
2686 /**
2687 * Dont save text items that are all white space.
2688 */
2689 @Override
2690 public boolean dontSave() {
2691 String text = getText();
2692 assert (text != null);
2693 return super.dontSave() || (text.trim().length() == 0 && this.getMinWidth() == null);
2694 }
2695
2696 @Override
2697 public boolean calculate(String formula) {
2698 if (DisplayController.isXRayMode()) {
2699 return false;
2700 }
2701
2702 super.calculate(formula);
2703 if (isFloating() || formula == null || formula.length() == 0) {
2704 return false;
2705 }
2706 formula = formula.replace(':', '=');
2707
2708 String lowercaseFormula = formula.toLowerCase();
2709 ExpediteeJEP myParser = new ExpediteeJEP();
2710
2711 int nextVarNo = 1;
2712
2713 // Add variables from the containing rectangle if the item being
2714 // calculated is inside the enclosing rectangle
2715 Collection<Item> enclosed = getItemsInSameEnclosure();
2716 for (Item i : enclosed) {
2717 if (i == this) {
2718 continue;
2719 }
2720 if (i instanceof Text && !i.isAnnotation()) {
2721 AttributeValuePair pair = i.getAttributeValuePair();
2722 if (pair.hasPair()) {
2723 try {
2724 double value = pair.getDoubleValue();
2725 myParser.addVariable(pair.getAttribute(), value);
2726 // myParser.addVariable("$" + nextVarNo++, value);
2727 } catch (NumberFormatException nfe) {
2728 continue;
2729 } catch (Exception e) {
2730 e.printStackTrace();
2731 }
2732 } // else {
2733 // Add anonomous vars
2734 try {
2735 double value = pair.getDoubleValue();
2736 if (value != Double.NaN) {
2737 myParser.addVariable("$" + nextVarNo++, value);
2738 }
2739 } catch (NumberFormatException nfe) {
2740 continue;
2741 } catch (Exception e) {
2742 e.printStackTrace();
2743 }
2744 // }
2745 }
2746 }
2747
2748 // Add the variables from this frame
2749 myParser.addVariables(this.getParentOrCurrentFrame());
2750 String linkedFrame = getAbsoluteLink();
2751 // Add the relative frame variable if the item is linked
2752 if (linkedFrame != null) {
2753 Frame frame = FrameIO.LoadFrame(linkedFrame);
2754 myParser.addVariables(frame);
2755 // If the frame is linked add vector variable for the frame
2756 if (lowercaseFormula.contains("$frame")) {
2757 myParser.addVectorVariable(frame.getNonAnnotationItems(true), "$frame");
2758 }
2759 }
2760 // Add the relative box variable if this item is a line end
2761 if (this.isLineEnd()) {
2762 // if its a line end add the enclosed stuff as an @variable
2763 if (lowercaseFormula.contains("$box")) {
2764 myParser.addVectorVariable(getEnclosedItems(), "$box");
2765 }
2766 }
2767 myParser.resetObserver();
2768 try {
2769 Node node = myParser.parse(formula);
2770 String result = myParser.evaluate(node);
2771 if (result != null) {
2772 this.setText(result);
2773 this.setFormula(formula);
2774
2775 if (!this.hasAction()) {
2776 setActionMark(false);
2777 setAction("extract formula");
2778 }
2779 }
2780 } catch (Throwable e) {
2781 // e.printStackTrace();
2782 String formula2 = getFormula();
2783 this.setText(formula2);
2784 this.setFormula(formula2);
2785 return false;
2786 }
2787
2788 _attributeValuePair = null;
2789
2790 return true;
2791 }
2792
2793 /**
2794 * Gets items which are in the same enclosure as this item. In the event more
2795 * than one enclosure meets this criteria, then the one returned is the one with
2796 * the smallest area. TODO: Improve the efficiency of this method
2797 *
2798 * @return
2799 */
2800 public Collection<Item> getItemsInSameEnclosure() {
2801 Collection<Item> sameEnclosure = null;
2802 Collection<Item> seen = new HashSet<Item>();
2803 Frame parent = getParentOrCurrentFrame();
2804 double enclosureArea = Double.MAX_VALUE;
2805 for (Item i : parent.getVisibleItems()) {
2806 /*
2807 * Go through all the enclosures looking for one that includes this item
2808 */
2809 if (!seen.contains(i) && i.isEnclosed()) {
2810 seen.addAll(i.getEnclosingDots());
2811 Collection<Item> enclosed = i.getEnclosedItems();
2812 // Check if we have found an enclosure containing this item
2813 // Check it is smaller than any other enclosure found containing
2814 // this item
2815 if (enclosed.contains(this) && i.getEnclosedArea() < enclosureArea) {
2816 sameEnclosure = enclosed;
2817 }
2818 }
2819 }
2820
2821 if (sameEnclosure == null) {
2822 return new LinkedList<Item>();
2823 }
2824
2825 return sameEnclosure;
2826 }
2827
2828 /**
2829 * Returns true if items of the parent frame should be recalculated when this
2830 * item is modified
2831 */
2832 @Override
2833 public boolean recalculateWhenChanged() {
2834 if (/*
2835 * !isAnnotation() &&
2836 */(hasFormula() || isLineEnd())) {
2837 return true;
2838 }
2839 try {
2840 AttributeValuePair avp = getAttributeValuePair();
2841
2842 if (!avp.getDoubleValue().equals(Double.NaN)) {
2843 return true;
2844 }
2845 } catch (Exception e) {
2846 e.printStackTrace();
2847 }
2848
2849 return false;
2850 }
2851
2852 public float getLineHeight() {
2853 return getLineDrop(getTextLayouts().get(0));
2854 }
2855
2856 @Override
2857 public void setAnchorLeft(Integer anchor) {
2858 if (!isLineEnd()) {
2859 super.setAnchorLeft(anchor);
2860 // Subtract off the link width
2861 if (anchor != null) {
2862 setX(anchor + getLeftMargin());
2863 }
2864 return;
2865 }
2866 invalidateFill();
2867 invalidateCommonTrait(ItemAppearence.PreMoved);
2868
2869 this._anchoring.setLeftAnchor(anchor);
2870
2871 int oldX = getX();
2872 if (anchor != null) {
2873 float deltaX = anchor + getLeftMargin() - oldX;
2874 anchorConnected(AnchorEdgeType.Left, deltaX);
2875 }
2876
2877 invalidateCommonTrait(ItemAppearence.PostMoved);
2878 invalidateFill();
2879
2880 if (isSurrogate()) {
2881 surrogatePropertyInheritance.put(DefaultFrameWriter.ANCHOR_LEFT_STR, false);
2882 Item primary = getPrimary();
2883 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.ANCHOR_LEFT_STR)) {
2884 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
2885 primary.primaryPropertyEncryption.put(DefaultFrameWriter.ANCHOR_LEFT_STR, inheritanceCheckOnSave);
2886 }
2887 }
2888 }
2889
2890 @Override
2891 public void setAnchorRight(Integer anchor) {
2892 if (!isLineEnd()) {
2893 super.setAnchorRight(anchor);
2894 // Subtract off the link width
2895 if (anchor != null) {
2896 setX(DisplayController.getFramePaintAreaWidth() - anchor - getBoundsWidth() + getLeftMargin());
2897 }
2898 return;
2899 }
2900 invalidateFill();
2901 invalidateCommonTrait(ItemAppearence.PreMoved);
2902
2903 this._anchoring.setRightAnchor(anchor);
2904
2905 int oldX = getX();
2906 if (anchor != null) {
2907 float deltaX = DisplayController.getFramePaintAreaWidth() - anchor - getBoundsWidth()
2908 + getLeftMargin() - oldX;
2909 anchorConnected(AnchorEdgeType.Right, deltaX);
2910 }
2911
2912 invalidateCommonTrait(ItemAppearence.PostMoved);
2913 invalidateFill();
2914
2915 if (isSurrogate()) {
2916 surrogatePropertyInheritance.put(DefaultFrameWriter.ANCHOR_RIGHT_STR, false);
2917 Item primary = getPrimary();
2918 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.ANCHOR_RIGHT_STR)) {
2919 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
2920 primary.primaryPropertyEncryption.put(DefaultFrameWriter.ANCHOR_RIGHT_STR, inheritanceCheckOnSave);
2921 }
2922 }
2923 }
2924
2925 @Override
2926 public void setAnchorTop(Integer anchor) {
2927 if (!isLineEnd()) {
2928 super.setAnchorTop(anchor);
2929 if (anchor != null) {
2930 if (!getTextLayouts().isEmpty()) {
2931 final float ascent = getTextLayouts().get(0).getAscent();
2932 setY(anchor + ascent);
2933 } else if (this.getFont() != null) {
2934 // p could be any character
2935 final TextLayout fakeLayout = TextLayout.getManager().layoutStringSimple("p", this.getFont());
2936 final float ascent = fakeLayout.getAscent();
2937 EcosystemManager.getTextLayoutManager().releaseLayout(fakeLayout);
2938 setY(anchor + ascent);
2939 }
2940 }
2941 return;
2942 }
2943 invalidateFill();
2944 invalidateCommonTrait(ItemAppearence.PreMoved);
2945
2946 this._anchoring.setTopAnchor(anchor);
2947
2948 int oldY = getY();
2949 if (anchor != null) {
2950 float deltaY = anchor - oldY;
2951 anchorConnected(AnchorEdgeType.Top, deltaY);
2952 }
2953
2954 invalidateCommonTrait(ItemAppearence.PostMoved);
2955 invalidateFill();
2956
2957 if (isSurrogate()) {
2958 surrogatePropertyInheritance.put(DefaultFrameWriter.ANCHOR_TOP_STR, false);
2959 Item primary = getPrimary();
2960 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.ANCHOR_TOP_STR)) {
2961 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
2962 primary.primaryPropertyEncryption.put(DefaultFrameWriter.ANCHOR_TOP_STR, inheritanceCheckOnSave);
2963 }
2964 }
2965 }
2966
2967 @Override
2968 public void setAnchorBottom(Integer anchor) {
2969 if (!isLineEnd()) {
2970 super.setAnchorBottom(anchor);
2971 if (anchor != null) {
2972 if (!getTextLayouts().isEmpty()) {
2973 final float ascent = getTextLayouts().get(0).getAscent();
2974 final float descent = getTextLayouts().get(0).getDescent();
2975 setY(DisplayController.getFramePaintAreaHeight()
2976 - (anchor + this.getBoundsHeight() - ascent - descent));
2977 } else if (this.getFont() != null) {
2978 // p could be any character
2979 final TextLayout fakeLayout = TextLayout.getManager().layoutStringSimple("p", this.getFont());
2980 final float ascent = fakeLayout.getAscent();
2981 final float descent = fakeLayout.getDescent();
2982 setY(DisplayController.getFramePaintAreaHeight()
2983 - (anchor + this.getBoundsHeight() - ascent - descent));
2984 }
2985
2986 }
2987 return;
2988 }
2989 invalidateFill();
2990 invalidateCommonTrait(ItemAppearence.PreMoved);
2991
2992 this._anchoring.setBottomAnchor(anchor);
2993
2994 int oldY = getY();
2995 if (anchor != null) {
2996
2997 float deltaY = DisplayController.getFramePaintAreaHeight() - anchor - oldY;
2998 anchorConnected(AnchorEdgeType.Bottom, deltaY);
2999 }
3000
3001 invalidateCommonTrait(ItemAppearence.PostMoved);
3002 invalidateFill();
3003
3004 if (isSurrogate()) {
3005 surrogatePropertyInheritance.put(DefaultFrameWriter.ANCHOR_BOTTOM_STR, false);
3006 Item primary = getPrimary();
3007 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.ANCHOR_BOTTOM_STR)) {
3008 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
3009 primary.primaryPropertyEncryption.put(DefaultFrameWriter.ANCHOR_BOTTOM_STR, inheritanceCheckOnSave);
3010 }
3011 }
3012 }
3013
3014 @Override
3015 public void scale(Float scale, int originX, int originY) {
3016 setSize(getSize() * scale);
3017
3018 Integer width = getWidth();
3019 if (width != null) {
3020 setWidth(Math.round(width * scale));
3021 }
3022
3023 super.scale(scale, originX, originY);
3024 rebuild(true);
3025 }
3026
3027 protected AxisAlignedBoxBounds getPixelBounds(TextLayout layout) {
3028 // Does 'layout' need to be synchronized (similar to _textLayouts below)??
3029 int x = getX();
3030 int y = getY();
3031
3032 int ldx = 1 + x + getJustOffset(layout); // Layout draw x
3033 AxisAlignedBoxBounds layout_rect = layout.getPixelBounds(ldx, y);
3034
3035 return layout_rect;
3036 }
3037
3038 /**
3039 * Creates the smallest possible rectangle object to enclose the Text Item
3040 * completely. Width of the rectangle is determined by the line in the Text Item
3041 * that protrudes to the right the most. Height of the rectangle is determined
3042 * by the number of lines in the Text Item.
3043 *
3044 * @return A rectangle enclosing the Text Item, without gravity represented.
3045 * @see #getPixelBoundsUnionTight()
3046 */
3047 public AxisAlignedBoxBounds getPixelBoundsUnion() {
3048 final AxisAlignedBoxBounds rect = getPixelBounds(getTextLayouts().get(0));
3049
3050 int cumulativeHeight = rect.getSize().height;
3051 int maxWidth = rect.getSize().width;
3052
3053 if (getTextLayouts().size() > 1) {
3054 for (int i = 1; i < getTextLayouts().size(); i++) {
3055 final AxisAlignedBoxBounds r = getPixelBounds(getTextLayouts().get(i));
3056 cumulativeHeight += getTextLayouts().get(i).getDescent() + getTextLayouts().get(i).getAscent();
3057 if (r.getSize().width > maxWidth) {
3058 maxWidth = r.getSize().width;
3059 }
3060 }
3061 }
3062
3063 rect.getSize().width = maxWidth;
3064 rect.getSize().height = cumulativeHeight;
3065
3066 return rect;
3067 }
3068
3069 /**
3070 * Creates the smallest possible polygon to enclose the Text Item completely.
3071 * The shape of the polygon is determined by the length of each line, tightly
3072 * fitting the shape so that no white space is inside the resulting polygon.
3073 *
3074 * @return A polygon enclosing the Text Item, without gravity represented.
3075 * @see #getPixelBoundsUnion()
3076 */
3077 public PolygonBounds getPixelBoundsUnionTight() {
3078 final AxisAlignedBoxBounds rect = getPixelBounds(getTextLayouts().get(0));
3079 if (getTextLayouts().size() == 1) {
3080 return PolygonBounds.fromBox(rect);
3081 } else {
3082 final PolygonBounds poly = new PolygonBounds();
3083 poly.addPoint(rect.getMinX(), rect.getMinY());
3084 poly.addPoint(rect.getMaxX(), rect.getMinY());
3085 poly.addPoint(rect.getMaxX(), Math.round(rect.getMaxY() + getTextLayouts().get(0).getDescent()));
3086 int y = (int) (rect.getMaxY() + getTextLayouts().get(0).getDescent());
3087 for (int i = 1; i < getTextLayouts().size(); i++) {
3088 final AxisAlignedBoxBounds r = getPixelBounds(getTextLayouts().get(i));
3089 poly.addPoint(r.getMaxX(), y);
3090 poly.addPoint(r.getMaxX(), Math.round(y + r.getHeight() + getTextLayouts().get(i).getDescent()));
3091 y = Math.round(y + r.getHeight() + getTextLayouts().get(i).getDescent());
3092 }
3093 poly.addPoint(rect.getMinX() + getPixelBounds(getTextLayouts().get(getTextLayouts().size() - 1)).getWidth(),
3094 Math.round(y + getTextLayouts().get(getTextLayouts().size() - 1).getDescent()));
3095 poly.addPoint(rect.getMinX(), Math.round(y + getTextLayouts().get(getTextLayouts().size() - 1).getDescent()));
3096 return poly;
3097 }
3098 }
3099
3100 /*
3101 * public AxisAlignedBoxBounds getPixelBoundsUnion() { synchronized
3102 * (_textLayouts) {
3103 *
3104 * CombinationBoxBounds c = null;
3105 *
3106 * for (TextLayout layout: _textLayouts) { if (c == null) { c = new
3107 * CombinationBoxBounds(getPixelBounds(layout)); } else {
3108 * c.add(getPixelBounds(layout)); } }
3109 *
3110 * return AxisAlignedBoxBounds.getEnclosing(c);
3111 *
3112 * } }
3113 */
3114
3115 // public Rectangle getPixelBoundsUnion()
3116 // {
3117 // synchronized (_textLayouts) {
3118 //
3119 // int x = getX();
3120 // int y = getY();
3121 //
3122 // int min_xl = Integer.MAX_VALUE;
3123 // int max_xr = Integer.MIN_VALUE + 1; // +1 makes it safe to do math.abs on
3124 //
3125 // int min_yt = Integer.MAX_VALUE;
3126 // int max_yb = Integer.MIN_VALUE + 1; // +1 makes it safe to do math.abs on
3127 //
3128 //
3129 // for (int i = 0; i < _textLayouts.size(); i++) {
3130 // TextLayout layout = _textLayouts.get(i);
3131 //
3132 // int ldx = 1+x+getJustOffset(layout); // Layout draw x
3133 // Rectangle layout_rect = layout.getPixelBounds(null, ldx, y);
3134 //
3135 // int xl = layout_rect.x;
3136 // int xr = xl + layout_rect.width -1;
3137 //
3138 // int yt = layout_rect.y;
3139 // int yb = yt + layout_rect.height -1;
3140 //
3141 // min_xl = Math.min(min_xl,xl);
3142 // max_xr = Math.max(max_xr,xr);
3143 //
3144 // min_yt = Math.min(min_yt,yt);
3145 // max_yb = Math.max(max_yb,yb);
3146 // }
3147 //
3148 // if ((min_xl >= max_xr) || (min_yt >= max_yb)) {
3149 // // No valid rectangle are found
3150 // return null;
3151 // }
3152 //
3153 // return new Rectangle(min_xl,min_yt,max_xr-min_xl+1,max_yb-min_yt+1);
3154 //
3155 // }
3156 //
3157 // }
3158 /*
3159 * Returns the SIMPLE statement contained by this text item.
3160 *
3161 */
3162 public String getStatement() {
3163 return getText().split("\\s+")[0];
3164 }
3165
3166 public boolean getAutoWrap() {
3167 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.AUTO_WRAP_TO_SAVE_STR)) {
3168 return ((Text) this.getPrimary()).getAutoWrap();
3169 } else {
3170 return _autoWrap;
3171 }
3172 }
3173
3174 // workaround since true is the default value and would not be displayed
3175 // normally
3176 public String getAutoWrapToSave() {
3177 if (!getAutoWrap()) {
3178 return null;
3179 }
3180 return "true";
3181 }
3182
3183 public void setAutoWrap(boolean autoWrap) {
3184 _autoWrap = autoWrap;
3185
3186 if (isSurrogate()) {
3187 surrogatePropertyInheritance.put(DefaultFrameWriter.AUTO_WRAP_TO_SAVE_STR, false);
3188 Item primary = getPrimary();
3189 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.AUTO_WRAP_TO_SAVE_STR)) {
3190 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
3191 primary.primaryPropertyEncryption.put(DefaultFrameWriter.AUTO_WRAP_TO_SAVE_STR, inheritanceCheckOnSave);
3192 }
3193 }
3194 }
3195
3196 /**
3197 * Creates a new Text Item whose text contains the given character. This method
3198 * also moves the mouse cursor to be pointing at the newly created Text Item
3199 * ready to insert the next character.
3200 *
3201 * @param start
3202 * The character to use as the initial text of this Item.
3203 * @return The newly created Text Item
3204 */
3205 public static Text createText(char start) {
3206 Text t = DisplayController.getCurrentFrame().createBlankText("" + start);
3207
3208 Point newMouse = t.insertChar(start, DisplayController.getMouseX(), DisplayController.getMouseY());
3209 DisplayController.setCursorPosition(newMouse.getX(), newMouse.getY(), false);
3210
3211 return t;
3212 }
3213
3214 /**
3215 * Creates a new Text Item with no text. The newly created Item is a copy of any
3216 * ItemTemplate if one is present, and inherits all the attributes of the
3217 * Template
3218 *
3219 * @return The newly created Text Item
3220 */
3221 public static Text createText() {
3222 return DisplayController.getCurrentFrame().createNewText();
3223 }
3224
3225 /**
3226 * If the given Item is null, then a new Text item is created with the current
3227 * date. If the given Item is not null, then the current date is prepended to
3228 * the Item's text
3229 *
3230 * @param toAdd
3231 * The Item to prepend the date to, or null
3232 */
3233 public static void AddDate(Item toAdd) {
3234 String date1 = Formatter.getDateTime();
3235 String date2 = Formatter.getDate();
3236 final String leftSeparator = " :";
3237 final String rightSeparator = ": ";
3238 String dateToAdd = date1 + rightSeparator;
3239 boolean prepend = false;
3240 boolean append = false;
3241
3242 // if the user is pointing at an item, add the date where ever the
3243 // cursor is pointing
3244 if (toAdd != null && toAdd instanceof Text) {
3245 // permission check
3246 if (!toAdd.hasPermission(UserAppliedPermission.full)) {
3247 MessageBay.displayMessage("Insufficicent permission to add the date to that item");
3248 return;
3249 }
3250
3251 Text textItem = (Text) toAdd;
3252
3253 String text = textItem.getText();
3254
3255 // check if the default date has already been put on this item
3256 if (text.startsWith(date1 + rightSeparator)) {
3257 textItem.removeText(date1 + rightSeparator);
3258 dateToAdd = date2 + rightSeparator;
3259 prepend = true;
3260 } else if (text.startsWith(date2 + rightSeparator)) {
3261 textItem.removeText(date2 + rightSeparator);
3262 dateToAdd = leftSeparator + date2;
3263 append = true;
3264 } else if (text.endsWith(leftSeparator + date2)) {
3265 textItem.removeEndText(leftSeparator + date2);
3266 append = true;
3267 dateToAdd = leftSeparator + date1;
3268 } else if (text.endsWith(leftSeparator + date1)) {
3269 textItem.removeEndText(leftSeparator + date1);
3270 if (textItem.getLength() > 0) {
3271 dateToAdd = "";
3272 prepend = true;
3273 } else {
3274 // use the default date format
3275 prepend = true;
3276 }
3277 }
3278
3279 if (prepend) {
3280 // add the date to the text item
3281 textItem.prependText(dateToAdd);
3282 if (dateToAdd.length() == textItem.getLength()) {
3283 DisplayController.setCursorPosition(textItem.getParagraphEndPosition());
3284 }
3285 } else if (append) {
3286 textItem.appendText(dateToAdd);
3287 if (dateToAdd.length() == textItem.getLength()) {
3288 DisplayController.setCursorPosition(textItem.getPosition());
3289 }
3290 } else {
3291 for (int i = 0; i < date1.length(); i++) {
3292 StandardGestureActions.processChar(date1.charAt(i), false);
3293 }
3294 }
3295
3296 textItem.getParent().setChanged(true);
3297 DisplayController.requestRefresh(true);
3298 // } else {
3299 // MessageBay
3300 // .displayMessage("Only text items can have the date prepended to
3301 // them");
3302 // }
3303 // otherwise, create a new text item
3304 } else {
3305 Text newText = createText();
3306 newText.setText(dateToAdd);
3307 DisplayController.getCurrentFrame().addItem(newText);
3308 DisplayController.getCurrentFrame().setChanged(true);
3309 DisplayController.requestRefresh(true);
3310
3311 DisplayController.setCursorPosition(newText.getParagraphEndPosition());
3312 }
3313 }
3314
3315 public Integer getMask() {
3316 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.MASK_STR)) {
3317 return ((Text) this.getPrimary()).getMask();
3318 } else {
3319 return _mask;
3320 }
3321 }
3322
3323 public void setMask(final Integer c) {
3324 _mask = c;
3325
3326 if (isSurrogate()) {
3327 surrogatePropertyInheritance.put(DefaultFrameWriter.MASK_STR, false);
3328 Item primary = getPrimary();
3329 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.MASK_STR)) {
3330 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
3331 primary.primaryPropertyEncryption.put(DefaultFrameWriter.MASK_STR, inheritanceCheckOnSave);
3332 }
3333 }
3334 }
3335
3336 public String getPlaceholder() {
3337 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.PLACEHOLDER_STR)) {
3338 return ((Text) this.getPrimary()).getPlaceholder();
3339 } else {
3340 if (_placeholder == null || _placeholder.length() == 0) {
3341 return null;
3342 }
3343 return _placeholder.toString();
3344 }
3345 }
3346
3347 public void setPlaceholder(String _placeholder) {
3348 this._placeholder = new StringBuffer(_placeholder);
3349
3350 if (isSurrogate()) {
3351 surrogatePropertyInheritance.put(DefaultFrameWriter.PLACEHOLDER_STR, false);
3352 Item primary = getPrimary();
3353 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.PLACEHOLDER_STR)) {
3354 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
3355 primary.primaryPropertyEncryption.put(DefaultFrameWriter.PLACEHOLDER_STR, inheritanceCheckOnSave);
3356 }
3357 }
3358 }
3359
3360 protected List<TextLayout> getTextLayouts() {
3361 if (this.getPlaceholder() != null && (this._text == null || this._text.length() == 0)) {
3362 return _placeholderTextLayouts;
3363 } else if (this.getMask() != null) {
3364 return _maskTextLayouts;
3365 } else {
3366 return _textLayouts;
3367 }
3368 }
3369
3370 public boolean isSingleLineOnly() {
3371 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.SINGLE_LINE_ONLY_STR)) {
3372 return ((Text) this.getPrimary()).isSingleLineOnly();
3373 } else {
3374 return _singleLine;
3375 }
3376 }
3377
3378 public void setSingleLineOnly(boolean _singleLine) {
3379 this._singleLine = _singleLine;
3380
3381 if (isSurrogate()) {
3382 surrogatePropertyInheritance.put(DefaultFrameWriter.SINGLE_LINE_ONLY_STR, false);
3383 Item primary = getPrimary();
3384 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.SINGLE_LINE_ONLY_STR)) {
3385 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
3386 primary.primaryPropertyEncryption.put(DefaultFrameWriter.SINGLE_LINE_ONLY_STR, inheritanceCheckOnSave);
3387 }
3388 }
3389 }
3390
3391 public int getTabIndex() {
3392 if (isSurrogate() && surrogatePropertyInheritance.get(DefaultFrameWriter.TAB_INDEX_STR)) {
3393 return ((Text) this.getPrimary()).getTabIndex();
3394 } else {
3395 return this._tabIndex;
3396 }
3397 }
3398
3399 public void setTabIndex(int index) {
3400 this._tabIndex = index;
3401
3402 if (isSurrogate()) {
3403 surrogatePropertyInheritance.put(DefaultFrameWriter.TAB_INDEX_STR, false);
3404 Item primary = getPrimary();
3405 if (subjectToInheritanceCheckOnSave(DefaultFrameWriter.TAB_INDEX_STR)) {
3406 EncryptionDetail inheritanceCheckOnSave = new EncryptionDetail(EncryptionDetail.Type.InheritanceCheckOnSave);
3407 primary.primaryPropertyEncryption.put(DefaultFrameWriter.TAB_INDEX_STR, inheritanceCheckOnSave);
3408 }
3409 }
3410 }
3411
3412 public Text getTabNext() {
3413 if (this._tabIndex >= 0) {
3414 Collection<Text> textItems = this.getParent().getTextItems();
3415 Text ret = null;
3416
3417 for (Text t: textItems) {
3418 if (t._tabIndex > this._tabIndex) {
3419 if (ret == null) {
3420 ret = t;
3421 } else if (t._tabIndex < ret._tabIndex) {
3422 ret = t;
3423 }
3424 }
3425 }
3426
3427 return ret;
3428 } else {
3429 return null;
3430 }
3431 }
3432
3433 public Text getTabPrevious() {
3434 if (this._tabIndex >= 0) {
3435 Collection<Text> textItems = this.getParent().getTextItems();
3436 Text ret = null;
3437
3438 for (Text t: textItems) {
3439 if (t._tabIndex < this._tabIndex) {
3440 if (ret == null) {
3441 ret = t;
3442 } else if (t._tabIndex > ret._tabIndex) {
3443 ret = t;
3444 }
3445 }
3446 }
3447
3448 return ret;
3449 } else {
3450 return null;
3451 }
3452 }
3453}
Note: See TracBrowser for help on using the repository browser.