source: trunk/src/org/expeditee/items/widgets/Widget.java@ 1258

Last change on this file since 1258 was 1258, checked in by davidb, 5 years ago

Changed how DisplayController width, height and size are retrieved. Now does this top-level, rather than going through the AxisAlignmentBox. In doing so, can now control for when the window size has you yet been correctly mapped to the screen, and fall back to pre-defined MINIMUM defaults

File size: 52.5 KB
Line 
1/**
2 * InteractiveWidget.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.widgets;
20
21import java.io.PrintWriter;
22import java.io.StringWriter;
23import java.lang.reflect.Constructor;
24import java.util.ArrayList;
25import java.util.Collection;
26import java.util.Collections;
27import java.util.LinkedList;
28import java.util.List;
29
30import org.apache.commons.cli.CommandLine;
31import org.apache.commons.cli.CommandLineParser;
32import org.apache.commons.cli.GnuParser;
33import org.apache.commons.cli.HelpFormatter;
34import org.apache.commons.cli.Options;
35import org.apache.commons.cli.ParseException;
36import org.expeditee.Util;
37import org.expeditee.actions.Actions;
38import org.expeditee.core.Anchoring;
39import org.expeditee.core.Clip;
40import org.expeditee.core.Colour;
41import org.expeditee.core.Dimension;
42import org.expeditee.core.EcosystemSpecific;
43import org.expeditee.core.Fill;
44import org.expeditee.core.Point;
45import org.expeditee.core.bounds.AxisAlignedBoxBounds;
46import org.expeditee.gio.EcosystemManager;
47import org.expeditee.gio.EcosystemManager.Ecosystem;
48import org.expeditee.gio.GraphicsManager;
49import org.expeditee.gui.DisplayController;
50import org.expeditee.gui.Frame;
51import org.expeditee.gui.FrameIO;
52import org.expeditee.gui.FreeItems;
53import org.expeditee.items.Item;
54import org.expeditee.items.ItemParentStateChangedEvent;
55import org.expeditee.items.ItemUtils;
56import org.expeditee.items.Text;
57
58/**
59 * The bridge between swing space and Expeditee space
60 *
61 * @author Brook
62 *
63 */
64public abstract class Widget implements EcosystemSpecific
65{
66
67 /** The colour to fill the widget area with when not rendering the widget itself (i.e. when picked up). */
68 protected final static Colour FREESPACE_BACKCOLOR = Colour.FromRGB255(100, 100, 100);
69
70 /** The minimum border thickness for widgets. */
71 public final static float DEFAULT_MINIMUM_BORDER_THICKNESS = 1.0f;
72
73 /* GUIDE:
74 * d1--l1---d2
75 * | |
76 * l4 | X | l2
77 * | |
78 * d4---l3--d3
79 */
80
81 /** The top-left corner of the widget. */
82 private WidgetCorner _d1;
83 /** The top-right corner of the widget. */
84 private WidgetCorner _d2;
85 /** The bottom-right corner of the widget. */
86 private WidgetCorner _d3;
87 /** The bottom-left corner of the widget. */
88 private WidgetCorner _d4;
89
90 /** The top edge of the widget. */
91 private WidgetEdge _l1;
92 /** The right edge of the widget. */
93 private WidgetEdge _l2;
94 /** The bottom edge of the widget. */
95 private WidgetEdge _l3;
96 /** The left edge of the widget. */
97 private WidgetEdge _l4;
98
99 /** Used for quickly returning item list. */
100 private List<Item> _expediteeItems;
101
102 // Widget size restrictions
103 /** Minimum width of the widget. */
104 private int _minWidth = 50;
105 /** Minimum height of the widget. */
106 private int _minHeight = 50;
107 /** Maximum width of the widget. */
108 private int _maxWidth = 300;
109 /** Maximum height of the widget. */
110 private int _maxHeight = 300;
111
112 /** The Expeditee item that is used for saving widget state. */
113 protected Text _textRepresentation;
114
115 /** The anchoring of the widget. */
116 protected Anchoring _anchoring = new Anchoring();
117
118 /**
119 * Creates a InteractiveWidget from a text item.
120 *
121 * @param source
122 * Must not be null, first line of text used - which the format
123 * must be as follows: "@iw: <<widget_class_name>> [<<width>>] [<<height>>] [: [<<arg1>>] [<<arg2>>]
124 * [...]]".
125 *
126 * e.g: "@iw: org.expeditee.items.SampleWidget1 100 20 : 2" creates a
127 * SampleWidget1 with width = 100 and height = 20 with 1 argument = "2"
128 *
129 * @return An InteractiveWidget instance. Never null.
130 *
131 * @throws NullPointerException
132 * if source is null
133 *
134 * @throws IllegalArgumentException
135 * if source's text is in the incorrect format or if source's
136 * parent is null
137 *
138 * @throws InteractiveWidgetNotAvailableException
139 * If the given widget class name in the source text doesn't
140 * exist or not an InteractiveWidget or the widget does not
141 * supply a valid constructor for creation.
142 *
143 * @throws InteractiveWidgetInitialisationFailedException
144 * The sub-constructor failed - depends on the type of widget
145 * being instantainiated.
146 *
147 * class names beginning with $, the $ will be replaced with
148 * "org.expeditee.items."
149 */
150 public static Widget createWidget(final Text source) throws InteractiveWidgetNotAvailableException,
151 InteractiveWidgetInitialisationFailedException
152 {
153
154 if (source == null) {
155 throw new NullPointerException("source");
156 }
157
158 if (source.getParent() == null) {
159 throw new IllegalArgumentException("source's parent is null, InteractiveWidget's must be created from Text items with non-null parents");
160 }
161
162 final String TAG = ItemUtils.GetTag(ItemUtils.TAG_IWIDGET);
163
164 String text = source.getText();
165 if (text == null) {
166 throw new IllegalArgumentException("source does not have any text");
167 }
168
169 text = text.trim();
170
171 // Check starts with the widget tag and separator
172 if (!text.startsWith(TAG + ":")) {
173 throw new IllegalArgumentException("Source text must begin with \"" + TAG + ":\"");
174 }
175
176 // skip over the '@iw:' preamble
177 text = text.substring(TAG.length() + 1).trim();
178
179 int index = 0;
180 if (text.length() > 0) {
181 // Having removed @iw:, then next ':' is used for signifying start of arguments
182 index = text.indexOf(':');
183 }
184
185 if (index == 0) {
186 throw new IllegalArgumentException("Source text must begin with \"" + TAG + "\"");
187 }
188
189 //
190 // Step 1:
191 // For an X-rayable text item in the form:
192 // @iw: <class name> [options] width height : <rest...>
193 //
194 // the 'core' part of the X-rayable text item
195 // i.e, the text between the first ':' and the second ':'
196 //
197 //
198
199 final String tokens_str = (index == -1) ? text : text.substring(0, index);
200
201 String[] tokens = Text.parseArgsApache(tokens_str);
202
203 // make anything starting with a '-' lowercase
204 for (int i=0; i<tokens.length; i++) {
205 if (tokens[i].startsWith("-")) {
206 tokens[i] = tokens[i].toLowerCase();
207 }
208 }
209
210 // create the command line parser
211 final CommandLineParser parser = new GnuParser();
212
213 // create the Options
214 final Options options = new Options();
215 options.addOption( "al", "anchorleft", true, "Anchor the vertical left-hand edge of the interactive widget to the value provided " );
216 options.addOption( "ar", "anchorright", true, "Anchor the vertical right-hand edge of the interactive widget to the value provided " );
217 options.addOption( "at", "anchortop", true, "Anchor the vertical top edge of the interactive widget to the value provided " );
218 options.addOption( "ab", "anchorbottom", true, "Anchor the vertical bottom edge of the interactive widget to the value provided " );
219
220 /*
221 * Alternative way to do the above, but with more control over how the values are set up and can be used
222 *
223 @SuppressWarnings("static-access")
224 Option anchor_left_opt = OptionBuilder.withLongOpt( "anchorleft" )
225 .withDescription( "use <x-pos> as left anchor for the vertical lefthand edge of the interactive widget" )
226 .hasArg()
227 .withArgName("<x-pos>")
228 .create("al");
229
230 @SuppressWarnings("static-access")
231 Option anchor_right_opt = OptionBuilder.withLongOpt( "anchorright" )
232 .withDescription( "use <x-pos> as right anchor for the vertical righthand edge of the interactive widget" )
233 .hasArg()
234 .withArgName("<x-pos>")
235 .create("ar");
236
237
238 @SuppressWarnings("static-access")
239 Option anchor_top_opt = OptionBuilder.withLongOpt( "anchortop" )
240 .withDescription( "use <y-pos> as top anchor for the horizontal top end of the interactive widget" )
241 .hasArg()
242 .withArgName("<x-pos>")
243 .create("at");
244
245 @SuppressWarnings("static-access")
246 Option anchor_bottom_opt = OptionBuilder.withLongOpt( "anchorbottom" )
247 .withDescription( "use <y-pos> as bottom anchor for the horizontal bottom edge of the interactive widget" )
248 .hasArg()
249 .withArgName("<x-pos>")
250 .create("ab");
251
252 options.addOption(anchor_left_opt);
253 options.addOption(anchor_right_opt);
254 options.addOption(anchor_top_opt);
255 options.addOption(anchor_bottom_opt);
256 */
257
258 CommandLine core_line;
259 try {
260 // parse the command line arguments
261 core_line = parser.parse( options, tokens );
262
263 // Update tokens to be version with the options removed
264 tokens = core_line.getArgs();
265
266 }
267 catch( final ParseException exp ) {
268 System.err.println( "Unexpected exception:" + exp.getMessage() );
269 core_line = null;
270
271 }
272
273 final HelpFormatter formatter = new HelpFormatter();
274 final StringWriter usage_str_writer = new StringWriter();
275 final PrintWriter printWriter = new PrintWriter(usage_str_writer);
276
277 String widget_name = tokens[0];
278 final String widget_prefix = "org.expeditee.items.widgets";
279 if (widget_name.startsWith(widget_prefix)) {
280 widget_name = widget_name.substring(widget_prefix.length());
281 }
282
283 formatter.printHelp(printWriter, 80, TAG + ":" + widget_name + " [options] width height", null, options, 4, 0, null);
284 //System.out.println(usage_str_writer.toString());
285
286 float width = -1, height = -1;
287
288 if (tokens.length < 1) {
289 throw new IllegalArgumentException("Missing widget class name in source text");
290 }
291
292 try {
293
294 if (tokens.length >= 2) { // parse optional width
295 width = Integer.parseInt(tokens[1]);
296 width = (width <= 0) ? width = -1 : width;
297 }
298
299 if (tokens.length >= 3) { // parse optional height
300 height = Integer.parseInt(tokens[2]);
301 height = (height <= 0) ? height = -1 : height;
302 }
303
304 } catch (final NumberFormatException nfe) {
305 throw new IllegalArgumentException(
306 "Bad width or height given in source text", nfe);
307 }
308
309 if (tokens.length > 3) {
310 throw new IllegalArgumentException(
311 "to many arguments given before \":\" in source text");
312 }
313
314 String classname = tokens[0];
315 if (classname.charAt(0) == '$'){
316 classname = classname.substring(1);
317 }
318
319 // Attempt to locate the class using reflection
320 final Class<?> iwclass = findIWidgetClass(classname);
321
322 if (iwclass == null) {
323 throw new InteractiveWidgetNotAvailableException(classname
324 + " does not exist or is not an InteractiveWidget");
325 }
326
327
328 //
329 // Step 2:
330 // For an X-rayable text item in the form:
331 // @iw: <class name> [options] width height : <rest...>
332 //
333 // Parse the <rest ...> part
334
335 // Extract out the parameters - if any
336 String[] args = null;
337 if (index > 0) { // index of the first ":"
338 if (text.length()>(index+1)) {
339 final String args_str = text.substring(index + 1);
340
341 args = Text.parseArgsApache(args_str);
342
343 }
344 }
345
346 Widget inst = null;
347 try {
348 // Instantiate the widget - passing the params
349 final Class parameterTypes[] = new Class[] { Text.class, String[].class };
350 final Constructor ct = iwclass.getConstructor(parameterTypes);
351
352 final Object arglist[] = new Object[] { source, args };
353
354 inst = (Widget) ct.newInstance(arglist);
355 } catch (final Exception e) {
356 throw new InteractiveWidgetNotAvailableException(
357 "Failed to create instance via reflection: " + e.toString(),
358 e);
359 }
360
361 // Check that the widget is suitable for the current platform
362 final Ecosystem systemType = EcosystemManager.getType();
363 if (!inst.isSupportedOnEcosystem(systemType)) {
364 // Try create a JavaFXSwingWidget if creating a Swing widget in JavaFX
365 if (systemType == Ecosystem.JavaFX && inst.isSupportedOnEcosystem(Ecosystem.Swing)) {
366 inst = new JavaFXSwingWidget((SwingWidget) inst);
367 } else {
368 throw new InteractiveWidgetNotAvailableException(classname + " is not supported while using a " + systemType + " system.");
369 }
370 }
371
372 // Step 3:
373 // Set up the size and position of the widget
374
375 // Use default dimensions if not provided (or provided as negative
376 // values)
377 if (width <= 0) {
378 width = inst.getWidth();
379 }
380 if (height <= 0) {
381 height = inst.getHeight();
382 }
383
384 inst.setSize(width, height);
385
386 // Apply any anchor values supplied in the core part of the @iw item
387 Integer anchor_left = null;
388 Integer anchor_right = null;
389 Integer anchor_top = null;
390 Integer anchor_bottom = null;
391
392 if(core_line.hasOption( "anchorleft" ) ) {
393 final String al_str = core_line.getOptionValue( "anchorleft" );
394
395 anchor_left = (int) Float.parseFloat(al_str);
396 }
397
398 if(core_line.hasOption( "anchorright" ) ) {
399 final String ar_str = core_line.getOptionValue( "anchorright" );
400
401 anchor_right = (int) Float.parseFloat(ar_str);
402 }
403
404 if(core_line.hasOption( "anchortop" ) ) {
405 final String at_str = core_line.getOptionValue( "anchortop" );
406
407 anchor_top = (int) Float.parseFloat(at_str);
408 }
409
410 if(core_line.hasOption( "anchorbottom" ) ) {
411 final String ab_str = core_line.getOptionValue( "anchorbottom" );
412
413 anchor_bottom = (int) Float.parseFloat(ab_str);
414 }
415
416 inst.setAnchorCorners(anchor_left,anchor_right,anchor_top,anchor_bottom);
417
418 return inst;
419 }
420
421 /**
422 * Locates the class from the classname of an InteractiveWidget class
423 *
424 * @param classname
425 * The name of the class to search
426 * @return Null if doesn't exist or not an InteractiveWidget
427 */
428 private static Class findIWidgetClass(String classname)
429 {
430 if(classname == null) {
431 return null;
432 }
433 // try just the classname
434 try {
435 final Class c = Class.forName(classname); // attempt to find the class
436
437 // If one is found, ensure that it is a descendant of an
438 // InteractiveWidget
439 for (Class superclass = c.getSuperclass(); superclass != null
440 && superclass != Item.class; superclass = superclass
441 .getSuperclass()) {
442 if (superclass == Widget.class) {
443 return c;
444 }
445 }
446
447 } catch (final ClassNotFoundException e) {
448 }
449 // see if the class is a widget with invalid capitalisation, or missing the widget package prefix
450 if(classname.startsWith(Actions.WIDGET_PACKAGE)) {
451 classname = classname.substring(Actions.WIDGET_PACKAGE.length());
452 }
453 try {
454 final Class c = Class.forName(Actions.getClassName(classname)); // attempt to find the class
455
456 // If one is found, ensure that it is a descendant of an
457 // InteractiveWidget
458 for (Class superclass = c.getSuperclass(); superclass != null
459 && superclass != Item.class; superclass = superclass
460 .getSuperclass()) {
461 if (superclass == Widget.class) {
462 return c;
463 }
464 }
465
466 } catch (final ClassNotFoundException e) {
467 }
468
469 // Doesn't exist or not an InteractiveWidget
470 return null;
471 }
472
473 /**
474 * Arguments represent the widgets <i>current state</i> state. They are
475 * used for saving, loading, creating and cloning Special formatting is done
476 * for you.
477 *
478 * @see #getData()
479 *
480 * @return Can be null for no params.
481 */
482 protected abstract String[] getArgs();
483
484 /**
485 * Data represents the widgets <i>current state</i> state. For any state
486 * information you do not wish to be defined as arguments (e.g. metadata),
487 * you can set as the widgets source items data.
488 *
489 * The default implementation returns null. Override to make use of.
490 *
491 * @see #getArgs()
492 *
493 * @return Null for for data. Otherwise the data that represent this widgets
494 * <i>current state</i>
495 */
496 protected List<String> getData()
497 {
498 return null;
499 }
500
501 /**
502 * Constructor
503 *
504 * @param source
505 * Must not be null. Neither must it's parent
506 *
507 * @param component
508 * Must not be null.
509 *
510 * @param minWidth
511 * The min width restriction for the widget. If negative, then
512 * there is no restriction.
513 *
514 * @param maxWidth
515 * The max width restriction for the widget. If negative, then
516 * there is no restriction.
517 *
518 * @param minHeight
519 * The min height restriction for the widget. If negative, then
520 * there is no restriction.
521 *
522 * @param maxHeight
523 * The max height restriction for the widget. If negative, then
524 * there is no restriction.
525 *
526 * @throws NullPointerException
527 * If source, component if null.
528 *
529 * @throws IllegalArgumentException
530 * If source's parent is null. If maxWidth smaller than minWidth
531 * and maxWidth larger or equal to zero or if maxHeight smaller
532 * than minHeight && maxHeight is larger or equal to zero
533 *
534 */
535 protected Widget(final Text source, final int minWidth, final int maxWidth, final int minHeight, final int maxHeight)
536 {
537 if (source == null) {
538 throw new NullPointerException("source");
539 }
540 if (source.getParent() == null) {
541 throw new IllegalArgumentException("source's parent is null, InteractiveWidget's must be created from Text items with non-null parents");
542 }
543
544 //addThisAsContainerListenerToContent();
545 //addKeyListenerToWidget();
546
547 _textRepresentation = source;
548
549 setSizeRestrictions(minWidth, maxWidth, minHeight, maxHeight); // throws IllegalArgumentException's
550
551 final int x = source.getX();
552 final int y = source.getY();
553 final int width = (_minWidth < 0) ? 10 : _minWidth;
554 final int height = (_minHeight < 0) ? 10 : _minHeight;
555
556 final Frame idAllocator = _textRepresentation.getParent();
557
558 // create WidgetCorners
559 _d1 = new WidgetCorner(x, y, idAllocator.getNextItemID(), this);
560 _d2 = new WidgetCorner(x + width, y, idAllocator.getNextItemID(), this);
561 _d3 = new WidgetCorner(x + width, y + height, idAllocator.getNextItemID(), this);
562 _d4 = new WidgetCorner(x, y + height, idAllocator.getNextItemID(), this);
563
564 // create WidgetEdges
565 _l1 = new WidgetEdge(_d1, _d2, idAllocator.getNextItemID(), this);
566 _l2 = new WidgetEdge(_d2, _d3, idAllocator.getNextItemID(), this);
567 _l3 = new WidgetEdge(_d3, _d4, idAllocator.getNextItemID(), this);
568 _l4 = new WidgetEdge(_d4, _d1, idAllocator.getNextItemID(), this);
569
570 final Collection<Item> enclist = new ArrayList<Item>(4);
571 enclist.add(_d1);
572 enclist.add(_d2);
573 enclist.add(_d3);
574 enclist.add(_d4);
575 _d1.setEnclosedList(enclist);
576 _d2.setEnclosedList(enclist);
577 _d3.setEnclosedList(enclist);
578 _d4.setEnclosedList(enclist);
579
580 _expediteeItems = new ArrayList<Item>(8); // Note: order important
581 _expediteeItems.add(_d1);
582 _expediteeItems.add(_d2);
583 _expediteeItems.add(_d3);
584 _expediteeItems.add(_d4);
585 _expediteeItems.add(_l1);
586 _expediteeItems.add(_l2);
587 _expediteeItems.add(_l3);
588 _expediteeItems.add(_l4);
589
590 setWidgetEdgeColor(source.getBorderColor());
591 setWidgetEdgeThickness(source.getThickness());
592 }
593
594 /**
595 * Sets the restrictions - checks values.
596 *
597 * @param minWidth
598 * The min width restriction for the widget. If negative, then
599 * there is no restriction.
600 *
601 * @param maxWidth
602 * The max width restriction for the widget. If negative, then
603 * there is no restriction.
604 *
605 * @param minHeight
606 * The min height restriction for the widget. If negative, then
607 * there is no restriction.
608 *
609 * @param maxHeight
610 * The max height restriction for the widget. If negative, then
611 * there is no restriction.
612 *
613 * @throws IllegalArgumentException
614 * If maxWidth smaller than minWidth and maxWidth larger or
615 * equal to zero or if maxHeight smaller than minHeight &&
616 * maxHeight is larger or equal to zero
617 *
618 */
619 private void setSizeRestrictions(final int minWidth, final int maxWidth, final int minHeight, final int maxHeight)
620 {
621 _minWidth = minWidth;
622
623 if (maxWidth < _minWidth && maxWidth >= 0) {
624 throw new IllegalArgumentException("maxWidth smaller than the min Width");
625 }
626 _maxWidth = maxWidth;
627
628 _minHeight = minHeight;
629 if (maxHeight < _minHeight && maxHeight >= 0) {
630 throw new IllegalArgumentException("maxHeight smaller than the min Height");
631 }
632 _maxHeight = maxHeight;
633 }
634
635 public int getMinWidth()
636 {
637 return _minWidth;
638 }
639
640 public int getMinHeight()
641 {
642 return _minHeight;
643 }
644
645 public int getMaxWidth()
646 {
647 return _maxWidth;
648 }
649
650 public int getMaxHeight()
651 {
652 return _maxHeight;
653 }
654
655 /**
656 * This can be overridden for creating custom copies. The default
657 * implementation creates a new widget based on the current state of the
658 * widget (via getArgs).
659 *
660 * @see InteractiveWidget#getArgs().
661 *
662 * @return A copy of this widget.
663 *
664 */
665 public Widget copy() throws InteractiveWidgetNotAvailableException, InteractiveWidgetInitialisationFailedException
666 {
667 final Text t = _textRepresentation.copy();
668 final String clonedAnnotation = getAnnotationString();
669 t.setText(clonedAnnotation);
670 t.setData(getData());
671 return Widget.createWidget(t);
672 }
673
674 /**
675 * Notifies widget of delete
676 */
677 public void onDelete()
678 {
679 // Allocate new ID's
680 Frame parent = getParentFrame();
681
682 if (parent == null) {
683 parent = DisplayController.getCurrentFrame();
684 }
685
686 if (parent != null) {
687 for (final Item i : _expediteeItems) {
688 i.setID(parent.getNextItemID());
689 }
690 }
691
692 invalidateLink();
693 }
694
695 /**
696 * Note updates the source text with current state info
697 *
698 * @return The Text item that this widget was created from.
699 */
700 public Text getSource()
701 {
702 // Build the annotation string such that it represents this widgets
703 // current state
704 final String newAnnotation = getAnnotationString();
705
706 // Set the new text
707 _textRepresentation.setText(newAnnotation);
708
709 // Set the data
710 _textRepresentation.setData(getData());
711
712 return _textRepresentation;
713 }
714
715 /**
716 * @return The current representation for this widget. The representation
717 * stores link information, data etc... It is used for saving and
718 * loading of the widget. Never null.
719 *
720 */
721 protected Item getCurrentRepresentation()
722 {
723 return _textRepresentation;
724 }
725
726 /**
727 * @return The Expeditee annotation string.
728 */
729 protected String getAnnotationString() {
730
731 // Create tag and append classname
732 final StringBuilder sb = new StringBuilder(ItemUtils.GetTag(ItemUtils.TAG_IWIDGET));
733 sb.append(':');
734 sb.append(' ');
735 sb.append(getClass().getName());
736
737 if (_anchoring.getLeftAnchor() != null) {
738 sb.append(" --anchorLeft " + Math.round(_anchoring.getLeftAnchor()));
739 }
740 if (_anchoring.getRightAnchor() != null) {
741 sb.append(" --anchorRight " + Math.round(_anchoring.getRightAnchor()));
742 }
743
744 if (_anchoring.getTopAnchor() != null) {
745 sb.append(" --anchorTop " + Math.round(_anchoring.getTopAnchor()));
746 }
747
748 if (_anchoring.getBottomAnchor() != null) {
749 sb.append(" --anchorBottom " + Math.round(_anchoring.getBottomAnchor()));
750 }
751
752 // Append size information if needed (not an attribute of text items)
753 if (!isFixedSize()) {
754 sb.append(' ');
755 sb.append(getWidth());
756 sb.append(' ');
757 sb.append(getHeight());
758 }
759
760 // Append arguments if any
761 final String stateArgs = Util.formatArgs(getArgs());
762 if (stateArgs != null) {
763 sb.append(':');
764 sb.append(stateArgs);
765 }
766
767 return sb.toString();
768 }
769
770 /**
771 * Sets both the new size as well as the new min/max widget/height
772 * restrictions.
773 *
774 * @param minWidth
775 * The min width restriction for the widget. If negative, then
776 * there is no restriction.
777 *
778 * @param maxWidth
779 * The max width restriction for the widget. If negative, then
780 * there is no restriction.
781 *
782 * @param minHeight
783 * The min height restriction for the widget. If negative, then
784 * there is no restriction.
785 *
786 * @param maxHeight
787 * The max height restriction for the widget. If negative, then
788 * there is no restriction.
789 *
790 * @param newWidth
791 * Clamped to new restrictions.
792 *
793 * @param newHeight
794 * Clamped to new restrictions.
795 *
796 * @throws IllegalArgumentException
797 * If maxWidth smaller than minWidth and maxWidth larger or
798 * equal to zero or if maxHeight smaller than minHeight &&
799 * maxHeight is larger or equal to zero
800 *
801 * @see #setSize(float, float)
802 *
803 */
804 public void setSize(final int minWidth, final int maxWidth, final int minHeight, final int maxHeight, final float newWidth, final float newHeight)
805 {
806 setSizeRestrictions(minWidth, maxWidth, minHeight, maxHeight);
807 setSize(newWidth, newHeight);
808 }
809
810 /**
811 * Clamped to current min/max width/height.
812 *
813 * @param width
814 * Clamped to current restrictions.
815 * @param height
816 * Clamped to current restrictions.
817 *
818 * @see #setSize(int, int, int, int, float, float)
819 */
820 public void setSize(float width, float height)
821 {
822 // If 'width' and 'height' exceed the min/max values for width/height
823 // => clamp to the relevant min/max value
824 if (width < _minWidth && _minWidth >= 0) {
825 width = _minWidth;
826 } else if (width > _maxWidth && _maxWidth >= 0) {
827 width = _maxWidth;
828 }
829
830 if (height < _minHeight && _minHeight >= 0) {
831 height = _minHeight;
832 } else if (height > _maxHeight && _maxHeight >= 0) {
833 height = _maxHeight;
834 }
835
836 // Remember current isFloating() values
837 final boolean vfloating[] = new boolean[] { _d1.isFloating(), _d2.isFloating(), _d3.isFloating(), _d4.isFloating() };
838
839 _d1.setFloating(true);
840 _d2.setFloating(true);
841 _d3.setFloating(true);
842 _d4.setFloating(true);
843
844 final float xr = _d1.getX() + width;
845 final float yb = _d1.getY() + height;
846
847 _d2.setX(xr);
848 _d3.setX(xr);
849 _d3.setY(yb);
850 _d4.setY(yb);
851
852 // Restore isFloating() values
853 _d1.setFloating(vfloating[0]);
854 _d2.setFloating(vfloating[1]);
855 _d3.setFloating(vfloating[2]);
856 _d4.setFloating(vfloating[3]);
857
858 onSizeChanged();
859 }
860
861 public void setAnchorCorners(final Integer left, final Integer right, final Integer top, final Integer bottom)
862 {
863 setAnchorLeft(left);
864 setAnchorRight(right);
865 setAnchorTop(top);
866 setAnchorBottom(bottom);
867 }
868
869
870 public void setPosition(final int x, final int y)
871 {
872 if (x == getX() && y == getY()) {
873 return;
874 }
875
876 // Remember current isFloating() values
877 final boolean vfloating[] = new boolean[] { _d1.isFloating(), _d2.isFloating(), _d3.isFloating(), _d4.isFloating() };
878
879 final int width = getWidth();
880 final int height = getHeight();
881
882 invalidateLink();
883
884 _d1.setFloating(true);
885 _d2.setFloating(true);
886 _d3.setFloating(true);
887 _d4.setFloating(true);
888
889 _d1.setPosition(x, y);
890 _d2.setPosition(x + width, y);
891 _d3.setPosition(x + width, y + height);
892 _d4.setPosition(x, y + height);
893
894 // Restore isFloating() values
895 _d1.setFloating(vfloating[0]);
896 _d2.setFloating(vfloating[1]);
897 _d3.setFloating(vfloating[2]);
898 _d4.setFloating(vfloating[3]);
899
900 invalidateLink();
901
902 onMoved();
903
904 }
905
906 private boolean _settingPositionFlag = false; // used for recursion
907
908 /**
909 * Updates position of given WidgetCorner to the given (x,y),
910 * and updates related values (connected corners, width and height)
911 *
912 * @param src
913 * @param x
914 * @param y
915 * @return False if need to call super.setPosition
916 */
917 public boolean setPositions(final WidgetCorner src, final float x, final float y)
918 {
919 if (_settingPositionFlag) {
920 return false;
921 }
922
923 _settingPositionFlag = true;
924
925 invalidateLink();
926
927 // Check to see if the widget is fully being picked up
928 final boolean isAllPickedUp = (_d1.isFloating() && _d2.isFloating() && _d3.isFloating() && _d4.isFloating());
929
930 // If so, then this will be called one by one ..
931 if (isAllPickedUp) {
932 src.setPosition(x, y);
933 } else {
934
935 float newX = x;
936
937 // Reference:
938 // D1 D2
939 // D3 D4
940
941 //
942 // GUIDE:
943 // d1--l1---d2
944 // | |
945 // l4 | X | l2
946 // | |
947 // d4---l3--d3
948 //
949
950 // X Positions
951 if (src == _d1 || src == _d4) {
952
953 // Check min X constraint
954 if (_minWidth >= 0) {
955 if ((_d2.getX() - x) < _minWidth) {
956 newX = _d2.getX() - _minWidth;
957 }
958 }
959 // Check max X constraint
960 if (_maxWidth >= 0) {
961 if ((_d2.getX() - x) > _maxWidth) {
962 newX = _d2.getX() - _maxWidth;
963 }
964 }
965
966 if (!(src == _d4 && _d1.isFloating() && _d4.isFloating())) {
967 _d1.setX(newX);
968 }
969 if (!(src == _d1 && _d4.isFloating() && _d1.isFloating())) {
970 _d4.setX(newX);
971 }
972
973 } else if (src == _d2 || src == _d3) {
974
975 // Check min X constraint
976 if (_minWidth >= 0) {
977 if ((x - _d1.getX()) < _minWidth) {
978 newX = _d1.getX() + _minWidth;
979 }
980 }
981 // Check max X constraint
982 if (_maxWidth >= 0) {
983 if ((x - _d1.getX()) > _maxWidth) {
984 newX = _d1.getX() + _maxWidth;
985 }
986 }
987
988 if (!(src == _d3 && _d2.isFloating() && _d3.isFloating())) {
989 _d2.setX(newX);
990 }
991 if (!(src == _d2 && _d3.isFloating() && _d2.isFloating())) {
992 _d3.setX(newX);
993 }
994 }
995
996 float newY = y;
997
998 // Y Positions
999 if (src == _d1 || src == _d2) {
1000
1001 // Check min Y constraint
1002 if (_minHeight >= 0) {
1003 if ((_d4.getY() - y) < _minHeight) {
1004 newY = _d4.getY() - _minHeight;
1005 }
1006 }
1007 // Check max Y constraint
1008 if (_maxHeight >= 0) {
1009 if ((_d4.getY() - y) > _maxHeight) {
1010 newY = _d4.getY() - _maxHeight;
1011 }
1012 }
1013
1014 if (!(src == _d2 && _d1.isFloating() && _d2.isFloating())) {
1015 _d1.setY(newY);
1016 }
1017 if (!(src == _d1 && _d2.isFloating() && _d1.isFloating())) {
1018 _d2.setY(newY);
1019 }
1020
1021 } else if (src == _d3 || src == _d4) {
1022
1023 // Check min Y constraint
1024 if (_minHeight >= 0) {
1025 if ((y - _d1.getY()) < _minHeight) {
1026 newY = _d1.getY() + _minHeight;
1027 }
1028 }
1029 // Check max Y constraint
1030 if (_maxHeight >= 0) {
1031 if ((y - _d1.getY()) > _maxHeight) {
1032 newY = _d1.getY() + _maxHeight;
1033 }
1034 }
1035
1036 if (!(src == _d4 && _d3.isFloating() && _d4.isFloating())) {
1037 _d3.setY(newY);
1038 }
1039 if (!(src == _d3 && _d4.isFloating() && _d3.isFloating())) {
1040 _d4.setY(newY);
1041 }
1042 }
1043 }
1044
1045 // Update source text position so position is remembered from loading
1046 final float newTextX = getX();
1047 final float newTextY = getY();
1048 if (_textRepresentation.getX() != newTextX || _textRepresentation.getY() != newTextY) {
1049 _textRepresentation.setPosition(newTextX, newTextY);
1050 }
1051
1052 _settingPositionFlag = false;
1053
1054 invalidateLink();
1055
1056 onMoved();
1057 onSizeChanged();
1058
1059 return true;
1060 }
1061
1062 public int getX()
1063 {
1064 return Math.min(_d1.getX(), _d2.getX());
1065 }
1066
1067 public int getY()
1068 {
1069 return Math.min(_d1.getY(), _d4.getY());
1070 }
1071
1072 public int getWidth()
1073 {
1074 return Math.abs(_d2.getX() - _d1.getX());
1075 }
1076
1077 public int getHeight()
1078 {
1079 return Math.abs(_d4.getY() - _d1.getY());
1080 }
1081
1082 public Point getPosition()
1083 {
1084 return new Point(getX(), getY());
1085 }
1086
1087 public Dimension getSize()
1088 {
1089 return new Dimension(getWidth(), getHeight());
1090 }
1091
1092 /**
1093 * The order of the items in the list is as specified: _d1 _d2 _d3 _d4 _l1
1094 * _l2 _l3 _l4
1095 *
1096 * @return All of the Expeditee items that form the boundaries of this
1097 * widget in an unmodifiable list
1098 */
1099 public List<Item> getItems()
1100 {
1101 return Collections.unmodifiableList(_expediteeItems);
1102 }
1103
1104 public final void onParentStateChanged(final ItemParentStateChangedEvent e)
1105 {
1106 switch (e.getEventType()) {
1107 case ItemParentStateChangedEvent.EVENT_TYPE_REMOVED:
1108 case ItemParentStateChangedEvent.EVENT_TYPE_REMOVED_VIA_OVERLAY:
1109 case ItemParentStateChangedEvent.EVENT_TYPE_HIDDEN:
1110 EcosystemManager.removeInteractiveWidget(this);
1111 break;
1112
1113 case ItemParentStateChangedEvent.EVENT_TYPE_ADDED:
1114 case ItemParentStateChangedEvent.EVENT_TYPE_ADDED_VIA_OVERLAY:
1115 case ItemParentStateChangedEvent.EVENT_TYPE_SHOWN:
1116 case ItemParentStateChangedEvent.EVENT_TYPE_SHOWN_VIA_OVERLAY:
1117 EcosystemManager.addInteractiveWidget(this);
1118 addWidgetContent(e);
1119 break;
1120 }
1121
1122 DisplayController.invalidateItem(_d1, getContentBounds());
1123
1124 // Forward filtered event to upper classes...
1125 onParentStateChanged(e.getEventType());
1126 }
1127
1128 /**
1129 * Subclassing Widgets overwrite to add their specific content to the Frame.
1130 * For example, a widget being Java Swing must add their Swing component to Expeditee's content pane.
1131 * @param e Can be used to identify if it is appropriate to draw the widget.
1132 */
1133 abstract protected void addWidgetContent(final ItemParentStateChangedEvent e);
1134
1135 /**
1136 * Subclassing Widgets overwrite to specify their own functionality as to how key events
1137 * are to be forwarded to Expeditee.
1138 */
1139 abstract protected void addKeyListenerToWidget();
1140
1141 /**
1142 * Subclassing widgets overwrite to specify how content will respond to being added and removed.
1143 */
1144 abstract protected void addThisAsContainerListenerToContent();
1145
1146 /**
1147 * Subclassing widgets overwrite to provide the size and position of their content.
1148 * For example, a widget using Java Swing might return _swingComponent.getBounds().
1149 * @return The bounds of the content that is being drawn.
1150 */
1151 abstract public AxisAlignedBoxBounds getContentBounds();
1152
1153 /**
1154 * Due to absolute positioning...
1155 * @param parent
1156 */
1157 abstract protected void layout();
1158
1159 /**
1160 * Subclassing widgets overwrite to respond to changes in widget bounds.
1161 */
1162 abstract protected void onBoundsChanged();
1163
1164 /**
1165 * Override to make use of. Internally this is reported once by all corners,
1166 * but is filtered out so that this method is invoked once per event.
1167 *
1168 * @param eventType
1169 * The {@link ItemParentStateChangedEvent#getEventType()} that
1170 * occured.
1171 *
1172 */
1173 protected void onParentStateChanged(final int eventType)
1174 {
1175 }
1176
1177 /**
1178 * @return True if at least one corner is floating
1179 */
1180 public boolean isFloating()
1181 {
1182 return _d1.isFloating() || _d2.isFloating() || _d3.isFloating() || _d4.isFloating();
1183 }
1184
1185 public boolean areCornersFullyAnchored()
1186 {
1187 return _d1.getParent() != null && _d2.getParent() != null && _d3.getParent() != null && _d4.getParent() != null;
1188 }
1189
1190 /**
1191 *
1192 * @return The current bounds for this widget. Never null.
1193 */
1194 public AxisAlignedBoxBounds getBounds()
1195 {
1196 return getContentBounds();
1197 //return new AxisAlignedBoxBounds(getX(), getY(), getWidth(), getHeight());
1198 }
1199
1200 protected void paintLink()
1201 {
1202 // If this widget is linked .. then draw the link icon
1203 if (_textRepresentation.getLink() != null || _textRepresentation.hasAction()) {
1204 _textRepresentation.paintLinkGraphic(getLinkX(), getLinkY());
1205 }
1206
1207 }
1208
1209 private int getLinkX()
1210 {
1211 return getX() - Item.LEFT_MARGIN;
1212 }
1213
1214 private int getLinkY()
1215 {
1216 return getY() + (getHeight() / 2);
1217 }
1218
1219 /** Invoked whenever the widget is to be repainted in free space. */
1220 protected void paintInFreeSpace()
1221 {
1222 final GraphicsManager g = EcosystemManager.getGraphicsManager();
1223 g.drawRectangle(new Point(getX(), getY()), new Dimension(getWidth(), getHeight()), 0.0, new Fill(FREESPACE_BACKCOLOR), null, null, null);
1224 }
1225
1226 public final void paint()
1227 {
1228 paintWidget();
1229 paintLink();
1230 }
1231
1232 /** Should be overridden by native widgets to draw themselves. */
1233 protected abstract void paintWidget();
1234
1235 /**
1236 * Called from the widgets corners: Whenever one of the corners are invoked
1237 * for a refill of the enclosed area.
1238 *
1239 * If the widget is floating (e.g. currently picked up / rubberbanding) then
1240 * a shaded area is drawn instead of the actual widget so the manipulation
1241 * of the widget is as smooth as possible.
1242 *
1243 * @param g
1244 * @param notifier
1245 */
1246 void paintFill()
1247 {
1248 //if (_swingComponent.getParent() == null) {
1249 // Note that frames with @f may want to paint the widgets so do not
1250 // paint over the widget interface in these cases: must only
1251 // paint if an object is floating
1252 if (isFloating()) {
1253 paintInFreeSpace();
1254 paintLink();
1255 }
1256 //}
1257 }
1258
1259 /**
1260 * @return True if this widget cannot be resized in either directions
1261 */
1262 public boolean isFixedSize()
1263 {
1264 return this._minHeight == this._maxHeight &&
1265 this._minWidth == this._maxWidth &&
1266 this._minHeight >= 0 &&
1267 this._minWidth >= 0;
1268 }
1269
1270 /**
1271 * Removes this widget from the parent frame or free space.
1272 *
1273 * @return True if removed from a parent frame. Thus a parent changed event
1274 * will be invoked.
1275 *
1276 * False if removed purely from free space.
1277 */
1278 protected boolean removeSelf()
1279 {
1280 final Frame parent = getParentFrame();
1281
1282 if (parent != null) {
1283 parent.removeAllItems(_expediteeItems);
1284 }
1285
1286 FreeItems.getInstance().removeAll(_expediteeItems);
1287
1288 return (parent != null);
1289 }
1290
1291 /**
1292 * @return The parent frame. Null if has none. Note: Based on corners
1293 * parents.
1294 */
1295 public Frame getParentFrame()
1296 {
1297 Frame parent = null;
1298 if (_d1.getParent() != null) {
1299 parent = _d1.getParent();
1300 } else if (_d2.getParent() != null) {
1301 parent = _d2.getParent();
1302 } else if (_d3.getParent() != null) {
1303 parent = _d3.getParent();
1304 } else if (_d4.getParent() != null) {
1305 parent = _d4.getParent();
1306 }
1307
1308 return parent;
1309 }
1310
1311 protected void invalidateSelf()
1312 {
1313 final AxisAlignedBoxBounds dirty = new AxisAlignedBoxBounds(getX(), getY(), getWidth(), getHeight());
1314 DisplayController.invalidateArea(dirty);
1315 invalidateLink();
1316 }
1317
1318 /**
1319 * Invalidates the link for this widget - if it has one.
1320 */
1321 protected void invalidateLink()
1322 {
1323 if (_textRepresentation.getLink() != null || _textRepresentation.hasAction()) {
1324 final AxisAlignedBoxBounds linkArea = _textRepresentation.getLinkDrawArea(getLinkX(), getLinkY());
1325 DisplayController.invalidateArea(linkArea);
1326 }
1327 }
1328
1329 /**
1330 * @see ItemUtils#isVisible(Item)
1331 *
1332 * @return True if this widget is visible from the current frame. Considers
1333 * overlays and vectors.
1334 *
1335 */
1336 public boolean isVisible()
1337 {
1338 return ItemUtils.isVisible(_d1);
1339 }
1340
1341 /**
1342 * Invoked whenever the widget have moved. Can override.
1343 *
1344 */
1345 protected abstract void onMoved();
1346
1347 /**
1348 * Invoked whenever the widget have moved. Can override.
1349 *
1350 */
1351 protected abstract void onSizeChanged();
1352
1353 /**
1354 * Override to have a custom min border thickness for your widget.
1355 *
1356 * @see #DEFAULT_MINIMUM_BORDER_THICKNESS
1357 *
1358 * @return The minimum border thickness. Should be larger or equal to zero.
1359 *
1360 */
1361 public float getMinimumBorderThickness()
1362 {
1363 return DEFAULT_MINIMUM_BORDER_THICKNESS;
1364 }
1365
1366 /**
1367 * Looks fors a dataline in the current representation of the widget.
1368 *
1369 * @see #getCurrentRepresentation
1370 * @see #getStrippedDataInt(String, int)
1371 * @see #getStrippedDataLong(String, long)
1372 *
1373 * @param tag
1374 * The prefix of a dataline that will be matched. Must be larger
1375 * than zero and not null.
1376 *
1377 * @return The <i>first</i> dataline that matched the prefix - without the
1378 * prefix. Null if their was no data that matched the given prefix.
1379 *
1380 * @throws IllegalArgumentException
1381 * If tag is null.
1382 *
1383 * @throws NullPointerException
1384 * If tag is empty.
1385 */
1386 protected String getStrippedDataString(final String tag)
1387 {
1388 if (tag == null) {
1389 throw new NullPointerException("tag");
1390 }
1391 if (tag.length() == 0) {
1392 throw new IllegalArgumentException("tag is empty");
1393 }
1394
1395 if (getCurrentRepresentation().getData() != null) {
1396 for (final String str : getCurrentRepresentation().getData()) {
1397 if (str != null && str.startsWith(tag) && str.length() > tag.length()) {
1398 return str.substring(tag.length());
1399 }
1400 }
1401 }
1402 return null;
1403 }
1404
1405 /**
1406 * Looks fors a dataline in the current representation of the widget.
1407 *
1408 * @see #getCurrentRepresentation
1409 * @see #getStrippedDataString(String)
1410 * @see #getStrippedDataLong(String, long)
1411 *
1412 * @param tag
1413 * The prefix of a dataline that will be matched. Must be larger
1414 * the zero and not null.
1415 *
1416 * @param defaultValue
1417 * The default value if the tag does not exist or contains
1418 * invalid data.
1419 *
1420 * @return The <i>first</i> dataline that matched the prefix - parsed as an
1421 * int (after the prefix). defaultValue if their was no data that
1422 * matched the given prefix or the data was invalid.
1423 *
1424 * @throws IllegalArgumentException
1425 * If tag is null.
1426 *
1427 * @throws NullPointerException
1428 * If tag is empty.
1429 *
1430 */
1431 protected Integer getStrippedDataInt(final String tag, final Integer defaultValue)
1432 {
1433 String strippedStr = getStrippedDataString(tag);
1434
1435 if (strippedStr != null) {
1436 strippedStr = strippedStr.trim();
1437 if (strippedStr.length() > 0) {
1438 try {
1439 return Integer.parseInt(strippedStr);
1440 } catch (final NumberFormatException e) { /* Consume */
1441 }
1442 }
1443 }
1444
1445 return defaultValue;
1446 }
1447
1448
1449
1450 /**
1451 * Looks for a dataline in the current representation of the widget.
1452 *
1453 * @see #getCurrentRepresentation
1454 * @see #getStrippedDataString(String)
1455 * @see #getStrippedDataInt(String, int)
1456 *
1457 * @param tag
1458 * The prefix of a dataline that will be matched. Must be larger
1459 * the zero and not null.
1460 *
1461 * @param defaultValue
1462 * The default value if the tag does not exist or contains
1463 * invalid data.
1464 *
1465 * @return The <i>first</i> dataline that matched the prefix - parsed as a
1466 * long (after the prefix). defaultValue if their was no data that
1467 * matched the given prefix or the data was invalid.
1468 *
1469 * @throws IllegalArgumentException
1470 * If tag is null.
1471 *
1472 * @throws NullPointerException
1473 * If tag is empty.
1474 *
1475 */
1476 protected Long getStrippedDataLong(final String tag, final Long defaultValue)
1477 {
1478 String strippedStr = getStrippedDataString(tag);
1479
1480 if (strippedStr != null) {
1481 strippedStr = strippedStr.trim();
1482 if (strippedStr.length() > 0) {
1483 try {
1484 return Long.parseLong(strippedStr);
1485 } catch (final NumberFormatException e) { /* Consume */
1486 }
1487 }
1488 }
1489
1490 return defaultValue;
1491 }
1492
1493 /**
1494 * Looks for a data-line in the current representation of the widget.
1495 *
1496 * @see #getCurrentRepresentation
1497 * @see #getStrippedDataString(String)
1498 * @see #getStrippedDataLong(String, long)
1499 *
1500 * @param tag
1501 * The prefix of a data-line that will be matched. Must be larger
1502 * the zero and not null.
1503 *
1504 * @param defaultValue
1505 * The default value if the tag does not exist or contains
1506 * invalid data.
1507 *
1508 * @return The <i>first</i> data-line that matched the prefix - if
1509 * case insensitive match is 'true' then return true, otherwise
1510 * false. defaultValue if their was no data that
1511 * matched the given prefix or the data was invalid.
1512 *
1513 * @throws IllegalArgumentException
1514 * If tag is null.
1515 *
1516 * @throws NullPointerException
1517 * If tag is empty.
1518 *
1519 */
1520 protected Boolean getStrippedDataBool(final String tag, final Boolean defaultValue) {
1521
1522 String strippedStr = getStrippedDataString(tag);
1523
1524 if (strippedStr != null) {
1525 strippedStr = strippedStr.trim();
1526 if (strippedStr.length() > 0) {
1527 return strippedStr.equalsIgnoreCase("true") ? true : false;
1528 }
1529 }
1530
1531 return defaultValue;
1532 }
1533
1534
1535 /**
1536 * All data is removed that is prefixed with the given tag.
1537 *
1538 * @param tag
1539 * The prefix of the data lines to remove. Must be larger the
1540 * zero and not null.
1541 *
1542 * @throws IllegalArgumentException
1543 * If tag is null.
1544 *
1545 * @throws NullPointerException
1546 * If tag is empty.
1547 *
1548 */
1549 protected void removeData(final String tag)
1550 {
1551 updateData(tag, null);
1552 }
1553
1554 // TODO: Ambiguous name. Refactor. cts16
1555 protected void addDataIfCaseInsensitiveNotExists(String tag)
1556 {
1557 if (tag == null) {
1558 throw new NullPointerException("tag");
1559 }
1560
1561 List<String> data = getCurrentRepresentation().getData();
1562
1563 if (data == null) {
1564 data = new LinkedList<String>();
1565 }
1566
1567 for (final String s : data) {
1568 if (s != null && s.equalsIgnoreCase(tag)) {
1569 return;
1570 }
1571 }
1572
1573 data.add(tag);
1574 getCurrentRepresentation().setData(data);
1575 }
1576
1577
1578 /**
1579 * Updates the data with a given tag. All data is removed that is prefixed
1580 * with the given tag. Then a new line is added (if not null).
1581 *
1582 * Note that passing newData with null is the equivelant of removing tag
1583 * lines.
1584 *
1585 * @param tag
1586 * The prefix of the data lines to remove. Must be larger the
1587 * zero and not null.
1588 *
1589 * @param newData
1590 * The new line to add. Can be null - for not adding anything.
1591 *
1592 * @throws IllegalArgumentException
1593 * If tag is null.
1594 *
1595 * @throws NullPointerException
1596 * If tag is empty.
1597 *
1598 * @see #removeData(String)
1599 *
1600 */
1601 protected void updateData(final String tag, final String newData)
1602 {
1603 if (tag == null) {
1604 throw new NullPointerException("tag");
1605 } else if (tag.length() == 0) {
1606 throw new IllegalArgumentException("tag is empty");
1607 }
1608
1609 // Get current data
1610 List<String> data = getCurrentRepresentation().getData();
1611
1612 if (data != null) {
1613 for (int i = 0; i < data.size(); i++) {
1614 final String str = data.get(i);
1615 if (str != null && str.startsWith(tag)) {
1616 data.remove(i);
1617 }
1618 }
1619 }
1620
1621 if (newData != null) {
1622 if (data != null) {
1623 data.add(newData);
1624 } else {
1625 data = new LinkedList<String>();
1626 data.add(newData);
1627 getCurrentRepresentation().setData(data);
1628
1629 }
1630 }
1631 }
1632
1633 public boolean containsData(final String str) {
1634 assert(str != null);
1635 if (getCurrentRepresentation().getData() != null) {
1636 return getCurrentRepresentation().getData().contains(str);
1637 }
1638 return false;
1639 }
1640
1641 public boolean containsDataTrimmedIgnoreCase(final String str) {
1642 assert(str != null);
1643 if (getCurrentRepresentation().getData() != null) {
1644 for (final String data : getCurrentRepresentation().getData()) {
1645 if (data != null && data.trim().equalsIgnoreCase(str)) {
1646 return true;
1647 }
1648 }
1649 }
1650
1651 return false;
1652 }
1653
1654 /**
1655 * Sets the link for this widget.
1656 *
1657 * @param link
1658 * The new frame link. Can be null (for no link)
1659 *
1660 * @param linker
1661 * The text item creating the link. Null if not created from
1662 * a text item.
1663 */
1664 public void setLink(final String link, final Text linker)
1665 {
1666 // Make sure the link is redrawn when a link is added
1667 if (link == null) {
1668 invalidateLink();
1669 }
1670 getSource().setLink(link);
1671 if (link != null) {
1672 invalidateLink();
1673 }
1674 }
1675
1676 public void setBackgroundColor(final Colour c)
1677 {
1678 getSource().setBackgroundColor(c);
1679 }
1680
1681 /**
1682 * @return The link for this widget. Null if none.
1683 */
1684 public String getLink()
1685 {
1686 return _textRepresentation.getLink();
1687 }
1688
1689 /**
1690 * <b>Note:</b> That if the widget has no parent (e.g. the widget is a
1691 * free-item) then the absolute link returned will be for the frameset of
1692 * the current frame.
1693 *
1694 * @return The absolute link for this item. Null if there is no link, or if
1695 * there is no parent for this widget and the current frame is
1696 * unavailable.
1697 *
1698 */
1699 public String getAbsoluteLink()
1700 {
1701 // Note: cannot return the source absolute link since it does not have
1702 // a parent ... thus must manually format link
1703
1704 final String link = getLink();
1705
1706 if (link == null || link.length() == 0) {
1707 return null;
1708 }
1709
1710 if (FrameIO.isPositiveInteger(link)) { // relative - convert to
1711 // absolute
1712
1713 // Get the frameset of this item
1714 Frame parent = getParentFrame();
1715 if (parent == null) {
1716 parent = DisplayController.getCurrentFrame();
1717 }
1718 if (parent == null) {
1719 return null;
1720 }
1721
1722 final String framesetName = parent.getFramesetName();
1723
1724 if (framesetName == null) {
1725 return null;
1726 }
1727
1728 return framesetName + link;
1729
1730 } else if (FrameIO.isValidFrameName(link)) { // already absolute
1731 return link;
1732 }
1733
1734 return null;
1735 }
1736
1737 /**
1738 * Sets the border color for the widget.
1739 * That is, for the source (so it is remembered) and also for all the
1740 * corners/edges.
1741 *
1742 * @param c
1743 * The color to set.
1744 */
1745 public void setWidgetEdgeColor(final Colour c)
1746 {
1747 // Indirectly invokes setSourceBorderColor accordingly
1748 for (final Item i : _expediteeItems) {
1749 i.setColor(c);
1750 }
1751 }
1752
1753 /**
1754 * Sets the thickness of the widget edge.
1755 *
1756 * @see Item#setThickness(float)
1757 *
1758 * @param thickness
1759 * The new thickness to set.
1760 */
1761 public void setWidgetEdgeThickness(final float thickness) {
1762 _l1.setThickness(thickness, true);
1763 //for (Item i : _expediteeItems) i.setThickness(thickness);
1764// Above indirectly invokes setSourceThickness accordingly
1765 }
1766
1767 /**
1768 * Override to dis-allow widget thickness manipulation from the user.
1769 * @return
1770 */
1771 public boolean isWidgetEdgeThicknessAdjustable() {
1772 return true;
1773 }
1774
1775 // TODO: Maybe rename setSource* .. to update* ... These should actually be friendly!
1776 public void setSourceColor(final Colour c) {
1777 _textRepresentation.setColor(c);
1778 }
1779
1780 public void setSourceBorderColor(final Colour c) {
1781 _textRepresentation.setBorderColor(c);
1782 }
1783
1784 public void setSourceFillColor(final Colour c) {
1785 _textRepresentation.setFillColor(c);
1786 }
1787
1788 public void setSourceThickness(final float newThickness, final boolean setConnected) {
1789 _textRepresentation.setThickness(newThickness, setConnected);
1790 }
1791
1792 public void setSourceData(final List<String> data) {
1793 _textRepresentation.setData(data);
1794 }
1795
1796 protected Point getOrigin() {
1797 return _d1.getPosition(); // NOTE FROM BROOK: This flips around ... the origin can be any point
1798 }
1799
1800 protected Item getFirstCorner() {
1801 return _d1;
1802 }
1803
1804 public void setAnchorLeft(final Integer anchor)
1805 {
1806 _anchoring.setLeftAnchor(anchor);
1807 // Anchor left-edge corners (dots) as well
1808 _d1.setAnchorCornerX(anchor,null);
1809 _d4.setAnchorCornerX(anchor,null);
1810
1811 if (anchor != null) {
1812 setPositions(_d1, anchor, _d1.getY());
1813 invalidateSelf();
1814 }
1815
1816 // Move X-rayable item as well
1817 getCurrentRepresentation().setAnchorLeft(anchor);
1818 }
1819
1820 public void setAnchorRight(final Integer anchor) {
1821 _anchoring.setRightAnchor(anchor);
1822 // Anchor right-edge corners (dots) as well
1823 _d2.setAnchorCornerX(null,anchor); // right
1824 _d3.setAnchorCornerX(null,anchor); // right
1825
1826 if (anchor != null) {
1827 setPositions(_d2, DisplayController.getFramePaintAreaWidth() - anchor, _d2.getY());
1828 invalidateSelf();
1829 }
1830
1831 if (_anchoring.getLeftAnchor() == null) {
1832 // Prefer having the X-rayable item at anchorLeft position (if defined) over moving to anchorRight
1833 getCurrentRepresentation().setAnchorRight(anchor);
1834 }
1835 }
1836
1837 public void setAnchorTop(final Integer anchor) {
1838 _anchoring.setTopAnchor(anchor);
1839 // Anchor top-edge corners (dots) as well
1840 _d1.setAnchorCornerY(anchor,null);
1841 _d2.setAnchorCornerY(anchor,null);
1842
1843 if (anchor != null) {
1844 setPositions(_d2, _d2.getX(), anchor);
1845 invalidateSelf();
1846 }
1847
1848 // Move X-rayable item as well
1849 getCurrentRepresentation().setAnchorTop(anchor);
1850 }
1851
1852 public void setAnchorBottom(final Integer anchor) {
1853 _anchoring.setBottomAnchor(anchor);
1854 // Anchor bottom-edge corners (dots) as well
1855 _d3.setAnchorCornerY(null,anchor);
1856 _d4.setAnchorCornerY(null,anchor);
1857
1858 if (anchor != null) {
1859 setPositions(_d3, _d3.getX(), DisplayController.getFramePaintAreaHeight() - anchor);
1860 invalidateSelf();
1861 }
1862
1863 if (_anchoring.getTopAnchor() == null) {
1864 // Prefer having the X-rayable item at anchorTop position (if defined) over moving to anchorBottom
1865 getCurrentRepresentation().setAnchorBottom(anchor);
1866 }
1867 }
1868
1869
1870 public boolean isAnchored() {
1871 return (isAnchoredX()) || (isAnchoredY());
1872 }
1873
1874 public boolean isAnchoredX() {
1875 return _anchoring.isXAnchored();
1876 }
1877
1878 public boolean isAnchoredY() {
1879 return _anchoring.isYAnchored();
1880 }
1881
1882 /**
1883 * Call from expeditee for representing the name of the item.
1884 * Override to return custom name.
1885 *
1886 * Note: Used for the new frame title when creating links for widgets.
1887 *
1888 * @return
1889 * The name representing this widget
1890 */
1891 public String getName() {
1892 return this.toString();
1893 }
1894
1895 /**
1896 * Event called when the widget is left clicked while there are items attached to the FreeItems buffer.
1897 * Used to enable expeditee like text-widget interaction for left mouse clicks.
1898 * @return true if event was handled (no pass through), otherwise false.
1899 */
1900 public boolean ItemsLeftClickDropped() {
1901 return false;
1902 }
1903
1904 /**
1905 * Event called when the widget is middle clicked while there are items attached to the FreeItems buffer.
1906 * Used to enable expeditee like text-widget interaction for middle mouse clicks.
1907 * @return true if event was handled (no pass through), otherwise false.
1908 */
1909 public boolean ItemsMiddleClickDropped() {
1910 return false;
1911 }
1912
1913 /**
1914 * Event called when the widget is left clicked while there are items attached to the FreeItems buffer.
1915 * Used to enable expeditee like text-widget interaction for right mouse clicks
1916 * @return true if event was handled (no pass through), otherwise false.
1917 */
1918 public boolean ItemsRightClickDropped() {
1919 return false;
1920 }
1921
1922 /** Gets the clip area for this widget. */
1923 public Clip getClip()
1924 {
1925 final Clip clip = new Clip(getContentBounds());
1926 return clip;
1927 }
1928
1929 public void onResized() {
1930 invalidateSelf();
1931 onBoundsChanged();
1932 layout();
1933 }
1934}
Note: See TracBrowser for help on using the repository browser.