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

Last change on this file since 1511 was 1511, checked in by bnemhaus, 4 years ago

Frame::Parse has been updated to include a new boolean parameter. When true, widgets that are created as a result of the parse send not only notify the widget framework that they have been added, but are also visible. When false, they only notify they have been added.

The widget framework now distinguishes between added and visible widgets, this fixes a bug. Bug: when programmatically adding a widget to not the current frame, it never gets properly removed and therefore still catches click events from users. By distinguishing between adding and making visible this is avoided.


Another bug has been fixed. Bug: When setting a text item to have a right anchor, and then subsequently reducing the size of the window, this text item would get a width of zero assigned. This was caused by some issues with the logic of how right margins for items were calculated.

File size: 52.7 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 System.err.println("Widget::onParentStateChanged: Added but not yet shown widget.");
1116 break;
1117 case ItemParentStateChangedEvent.EVENT_TYPE_SHOWN:
1118 case ItemParentStateChangedEvent.EVENT_TYPE_SHOWN_VIA_OVERLAY:
1119 System.err.println("Widget::onParentStateChanged: Shown widget.");
1120 EcosystemManager.addInteractiveWidget(this);
1121 addWidgetContent(e);
1122 break;
1123 }
1124
1125 DisplayController.invalidateItem(_d1, getContentBounds());
1126
1127 // Forward filtered event to upper classes...
1128 onParentStateChanged(e.getEventType());
1129 }
1130
1131 /**
1132 * Subclassing Widgets overwrite to add their specific content to the Frame.
1133 * For example, a widget being Java Swing must add their Swing component to Expeditee's content pane.
1134 * @param e Can be used to identify if it is appropriate to draw the widget.
1135 */
1136 abstract protected void addWidgetContent(final ItemParentStateChangedEvent e);
1137
1138 /**
1139 * Subclassing Widgets overwrite to specify their own functionality as to how key events
1140 * are to be forwarded to Expeditee.
1141 */
1142 abstract protected void addKeyListenerToWidget();
1143
1144 /**
1145 * Subclassing widgets overwrite to specify how content will respond to being added and removed.
1146 */
1147 abstract protected void addThisAsContainerListenerToContent();
1148
1149 /**
1150 * Subclassing widgets overwrite to provide the size and position of their content.
1151 * For example, a widget using Java Swing might return _swingComponent.getBounds().
1152 * @return The bounds of the content that is being drawn.
1153 */
1154 abstract public AxisAlignedBoxBounds getContentBounds();
1155
1156 /**
1157 * Due to absolute positioning...
1158 * @param parent
1159 */
1160 abstract protected void layout();
1161
1162 /**
1163 * Subclassing widgets overwrite to respond to changes in widget bounds.
1164 */
1165 abstract protected void onBoundsChanged();
1166
1167 /**
1168 * Override to make use of. Internally this is reported once by all corners,
1169 * but is filtered out so that this method is invoked once per event.
1170 *
1171 * @param eventType
1172 * The {@link ItemParentStateChangedEvent#getEventType()} that
1173 * occured.
1174 *
1175 */
1176 protected void onParentStateChanged(final int eventType)
1177 {
1178 }
1179
1180 /**
1181 * @return True if at least one corner is floating
1182 */
1183 public boolean isFloating()
1184 {
1185 return _d1.isFloating() || _d2.isFloating() || _d3.isFloating() || _d4.isFloating();
1186 }
1187
1188 public boolean areCornersFullyAnchored()
1189 {
1190 return _d1.getParent() != null && _d2.getParent() != null && _d3.getParent() != null && _d4.getParent() != null;
1191 }
1192
1193 /**
1194 *
1195 * @return The current bounds for this widget. Never null.
1196 */
1197 public AxisAlignedBoxBounds getBounds()
1198 {
1199 return getContentBounds();
1200 //return new AxisAlignedBoxBounds(getX(), getY(), getWidth(), getHeight());
1201 }
1202
1203 protected void paintLink()
1204 {
1205 // If this widget is linked .. then draw the link icon
1206 if (_textRepresentation.getLink() != null || _textRepresentation.hasAction()) {
1207 _textRepresentation.paintLinkGraphic(getLinkX(), getLinkY());
1208 }
1209
1210 }
1211
1212 private int getLinkX()
1213 {
1214 return getX() - Item.LEFT_MARGIN;
1215 }
1216
1217 private int getLinkY()
1218 {
1219 return getY() + (getHeight() / 2);
1220 }
1221
1222 /** Invoked whenever the widget is to be repainted in free space. */
1223 protected void paintInFreeSpace()
1224 {
1225 final GraphicsManager g = EcosystemManager.getGraphicsManager();
1226 g.drawRectangle(new Point(getX(), getY()), new Dimension(getWidth(), getHeight()), 0.0, new Fill(FREESPACE_BACKCOLOR), null, null, null);
1227 }
1228
1229 public final void paint()
1230 {
1231 paintWidget();
1232 paintLink();
1233 }
1234
1235 /** Should be overridden by native widgets to draw themselves. */
1236 protected abstract void paintWidget();
1237
1238 /**
1239 * Called from the widgets corners: Whenever one of the corners are invoked
1240 * for a refill of the enclosed area.
1241 *
1242 * If the widget is floating (e.g. currently picked up / rubberbanding) then
1243 * a shaded area is drawn instead of the actual widget so the manipulation
1244 * of the widget is as smooth as possible.
1245 *
1246 * @param g
1247 * @param notifier
1248 */
1249 void paintFill()
1250 {
1251 //if (_swingComponent.getParent() == null) {
1252 // Note that frames with @f may want to paint the widgets so do not
1253 // paint over the widget interface in these cases: must only
1254 // paint if an object is floating
1255 if (isFloating()) {
1256 paintInFreeSpace();
1257 paintLink();
1258 }
1259 //}
1260 }
1261
1262 /**
1263 * @return True if this widget cannot be resized in either directions
1264 */
1265 public boolean isFixedSize()
1266 {
1267 return this._minHeight == this._maxHeight &&
1268 this._minWidth == this._maxWidth &&
1269 this._minHeight >= 0 &&
1270 this._minWidth >= 0;
1271 }
1272
1273 /**
1274 * Removes this widget from the parent frame or free space.
1275 *
1276 * @return True if removed from a parent frame. Thus a parent changed event
1277 * will be invoked.
1278 *
1279 * False if removed purely from free space.
1280 */
1281 protected boolean removeSelf()
1282 {
1283 final Frame parent = getParentFrame();
1284
1285 if (parent != null) {
1286 parent.removeAllItems(_expediteeItems);
1287 }
1288
1289 FreeItems.getInstance().removeAll(_expediteeItems);
1290
1291 return (parent != null);
1292 }
1293
1294 /**
1295 * @return The parent frame. Null if has none. Note: Based on corners
1296 * parents.
1297 */
1298 public Frame getParentFrame()
1299 {
1300 Frame parent = null;
1301 if (_d1.getParent() != null) {
1302 parent = _d1.getParent();
1303 } else if (_d2.getParent() != null) {
1304 parent = _d2.getParent();
1305 } else if (_d3.getParent() != null) {
1306 parent = _d3.getParent();
1307 } else if (_d4.getParent() != null) {
1308 parent = _d4.getParent();
1309 }
1310
1311 return parent;
1312 }
1313
1314 protected void invalidateSelf()
1315 {
1316 final AxisAlignedBoxBounds dirty = new AxisAlignedBoxBounds(getX(), getY(), getWidth(), getHeight());
1317 DisplayController.invalidateArea(dirty);
1318 invalidateLink();
1319 }
1320
1321 /**
1322 * Invalidates the link for this widget - if it has one.
1323 */
1324 protected void invalidateLink()
1325 {
1326 if (_textRepresentation.getLink() != null || _textRepresentation.hasAction()) {
1327 final AxisAlignedBoxBounds linkArea = _textRepresentation.getLinkDrawArea(getLinkX(), getLinkY());
1328 DisplayController.invalidateArea(linkArea);
1329 }
1330 }
1331
1332 /**
1333 * @see ItemUtils#isVisible(Item)
1334 *
1335 * @return True if this widget is visible from the current frame. Considers
1336 * overlays and vectors.
1337 *
1338 */
1339 public boolean isVisible()
1340 {
1341 return ItemUtils.isVisible(_d1);
1342 }
1343
1344 /**
1345 * Invoked whenever the widget have moved. Can override.
1346 *
1347 */
1348 protected abstract void onMoved();
1349
1350 /**
1351 * Invoked whenever the widget have moved. Can override.
1352 *
1353 */
1354 protected abstract void onSizeChanged();
1355
1356 /**
1357 * Override to have a custom min border thickness for your widget.
1358 *
1359 * @see #DEFAULT_MINIMUM_BORDER_THICKNESS
1360 *
1361 * @return The minimum border thickness. Should be larger or equal to zero.
1362 *
1363 */
1364 public float getMinimumBorderThickness()
1365 {
1366 return DEFAULT_MINIMUM_BORDER_THICKNESS;
1367 }
1368
1369 /**
1370 * Looks fors a dataline in the current representation of the widget.
1371 *
1372 * @see #getCurrentRepresentation
1373 * @see #getStrippedDataInt(String, int)
1374 * @see #getStrippedDataLong(String, long)
1375 *
1376 * @param tag
1377 * The prefix of a dataline that will be matched. Must be larger
1378 * than zero and not null.
1379 *
1380 * @return The <i>first</i> dataline that matched the prefix - without the
1381 * prefix. Null if their was no data that matched the given prefix.
1382 *
1383 * @throws IllegalArgumentException
1384 * If tag is null.
1385 *
1386 * @throws NullPointerException
1387 * If tag is empty.
1388 */
1389 protected String getStrippedDataString(final String tag)
1390 {
1391 if (tag == null) {
1392 throw new NullPointerException("tag");
1393 }
1394 if (tag.length() == 0) {
1395 throw new IllegalArgumentException("tag is empty");
1396 }
1397
1398 if (getCurrentRepresentation().getData() != null) {
1399 for (final String str : getCurrentRepresentation().getData()) {
1400 if (str != null && str.startsWith(tag) && str.length() > tag.length()) {
1401 return str.substring(tag.length());
1402 }
1403 }
1404 }
1405 return null;
1406 }
1407
1408 /**
1409 * Looks fors a dataline in the current representation of the widget.
1410 *
1411 * @see #getCurrentRepresentation
1412 * @see #getStrippedDataString(String)
1413 * @see #getStrippedDataLong(String, long)
1414 *
1415 * @param tag
1416 * The prefix of a dataline that will be matched. Must be larger
1417 * the zero and not null.
1418 *
1419 * @param defaultValue
1420 * The default value if the tag does not exist or contains
1421 * invalid data.
1422 *
1423 * @return The <i>first</i> dataline that matched the prefix - parsed as an
1424 * int (after the prefix). defaultValue if their was no data that
1425 * matched the given prefix or the data was invalid.
1426 *
1427 * @throws IllegalArgumentException
1428 * If tag is null.
1429 *
1430 * @throws NullPointerException
1431 * If tag is empty.
1432 *
1433 */
1434 protected Integer getStrippedDataInt(final String tag, final Integer defaultValue)
1435 {
1436 String strippedStr = getStrippedDataString(tag);
1437
1438 if (strippedStr != null) {
1439 strippedStr = strippedStr.trim();
1440 if (strippedStr.length() > 0) {
1441 try {
1442 return Integer.parseInt(strippedStr);
1443 } catch (final NumberFormatException e) { /* Consume */
1444 }
1445 }
1446 }
1447
1448 return defaultValue;
1449 }
1450
1451
1452
1453 /**
1454 * Looks for a dataline in the current representation of the widget.
1455 *
1456 * @see #getCurrentRepresentation
1457 * @see #getStrippedDataString(String)
1458 * @see #getStrippedDataInt(String, int)
1459 *
1460 * @param tag
1461 * The prefix of a dataline that will be matched. Must be larger
1462 * the zero and not null.
1463 *
1464 * @param defaultValue
1465 * The default value if the tag does not exist or contains
1466 * invalid data.
1467 *
1468 * @return The <i>first</i> dataline that matched the prefix - parsed as a
1469 * long (after the prefix). defaultValue if their was no data that
1470 * matched the given prefix or the data was invalid.
1471 *
1472 * @throws IllegalArgumentException
1473 * If tag is null.
1474 *
1475 * @throws NullPointerException
1476 * If tag is empty.
1477 *
1478 */
1479 protected Long getStrippedDataLong(final String tag, final Long defaultValue)
1480 {
1481 String strippedStr = getStrippedDataString(tag);
1482
1483 if (strippedStr != null) {
1484 strippedStr = strippedStr.trim();
1485 if (strippedStr.length() > 0) {
1486 try {
1487 return Long.parseLong(strippedStr);
1488 } catch (final NumberFormatException e) { /* Consume */
1489 }
1490 }
1491 }
1492
1493 return defaultValue;
1494 }
1495
1496 /**
1497 * Looks for a data-line in the current representation of the widget.
1498 *
1499 * @see #getCurrentRepresentation
1500 * @see #getStrippedDataString(String)
1501 * @see #getStrippedDataLong(String, long)
1502 *
1503 * @param tag
1504 * The prefix of a data-line that will be matched. Must be larger
1505 * the zero and not null.
1506 *
1507 * @param defaultValue
1508 * The default value if the tag does not exist or contains
1509 * invalid data.
1510 *
1511 * @return The <i>first</i> data-line that matched the prefix - if
1512 * case insensitive match is 'true' then return true, otherwise
1513 * false. defaultValue if their was no data that
1514 * matched the given prefix or the data was invalid.
1515 *
1516 * @throws IllegalArgumentException
1517 * If tag is null.
1518 *
1519 * @throws NullPointerException
1520 * If tag is empty.
1521 *
1522 */
1523 protected Boolean getStrippedDataBool(final String tag, final Boolean defaultValue) {
1524
1525 String strippedStr = getStrippedDataString(tag);
1526
1527 if (strippedStr != null) {
1528 strippedStr = strippedStr.trim();
1529 if (strippedStr.length() > 0) {
1530 return strippedStr.equalsIgnoreCase("true") ? true : false;
1531 }
1532 }
1533
1534 return defaultValue;
1535 }
1536
1537
1538 /**
1539 * All data is removed that is prefixed with the given tag.
1540 *
1541 * @param tag
1542 * The prefix of the data lines to remove. Must be larger the
1543 * zero and not null.
1544 *
1545 * @throws IllegalArgumentException
1546 * If tag is null.
1547 *
1548 * @throws NullPointerException
1549 * If tag is empty.
1550 *
1551 */
1552 protected void removeData(final String tag)
1553 {
1554 updateData(tag, null);
1555 }
1556
1557 // TODO: Ambiguous name. Refactor. cts16
1558 protected void addDataIfCaseInsensitiveNotExists(String tag)
1559 {
1560 if (tag == null) {
1561 throw new NullPointerException("tag");
1562 }
1563
1564 List<String> data = getCurrentRepresentation().getData();
1565
1566 if (data == null) {
1567 data = new LinkedList<String>();
1568 }
1569
1570 for (final String s : data) {
1571 if (s != null && s.equalsIgnoreCase(tag)) {
1572 return;
1573 }
1574 }
1575
1576 data.add(tag);
1577 getCurrentRepresentation().setData(data);
1578 }
1579
1580
1581 /**
1582 * Updates the data with a given tag. All data is removed that is prefixed
1583 * with the given tag. Then a new line is added (if not null).
1584 *
1585 * Note that passing newData with null is the equivelant of removing tag
1586 * lines.
1587 *
1588 * @param tag
1589 * The prefix of the data lines to remove. Must be larger the
1590 * zero and not null.
1591 *
1592 * @param newData
1593 * The new line to add. Can be null - for not adding anything.
1594 *
1595 * @throws IllegalArgumentException
1596 * If tag is null.
1597 *
1598 * @throws NullPointerException
1599 * If tag is empty.
1600 *
1601 * @see #removeData(String)
1602 *
1603 */
1604 protected void updateData(final String tag, final String newData)
1605 {
1606 if (tag == null) {
1607 throw new NullPointerException("tag");
1608 } else if (tag.length() == 0) {
1609 throw new IllegalArgumentException("tag is empty");
1610 }
1611
1612 // Get current data
1613 List<String> data = getCurrentRepresentation().getData();
1614
1615 if (data != null) {
1616 for (int i = 0; i < data.size(); i++) {
1617 final String str = data.get(i);
1618 if (str != null && str.startsWith(tag)) {
1619 data.remove(i);
1620 }
1621 }
1622 }
1623
1624 if (newData != null) {
1625 if (data != null) {
1626 data.add(newData);
1627 } else {
1628 data = new LinkedList<String>();
1629 data.add(newData);
1630 getCurrentRepresentation().setData(data);
1631
1632 }
1633 }
1634 }
1635
1636 public boolean containsData(final String str) {
1637 assert(str != null);
1638 if (getCurrentRepresentation().getData() != null) {
1639 return getCurrentRepresentation().getData().contains(str);
1640 }
1641 return false;
1642 }
1643
1644 public boolean containsDataTrimmedIgnoreCase(final String str) {
1645 assert(str != null);
1646 if (getCurrentRepresentation().getData() != null) {
1647 for (final String data : getCurrentRepresentation().getData()) {
1648 if (data != null && data.trim().equalsIgnoreCase(str)) {
1649 return true;
1650 }
1651 }
1652 }
1653
1654 return false;
1655 }
1656
1657 /**
1658 * Sets the link for this widget.
1659 *
1660 * @param link
1661 * The new frame link. Can be null (for no link)
1662 *
1663 * @param linker
1664 * The text item creating the link. Null if not created from
1665 * a text item.
1666 */
1667 public void setLink(final String link, final Text linker)
1668 {
1669 // Make sure the link is redrawn when a link is added
1670 if (link == null) {
1671 invalidateLink();
1672 }
1673 getSource().setLink(link);
1674 if (link != null) {
1675 invalidateLink();
1676 }
1677 }
1678
1679 public void setBackgroundColor(final Colour c)
1680 {
1681 getSource().setBackgroundColor(c);
1682 }
1683
1684 /**
1685 * @return The link for this widget. Null if none.
1686 */
1687 public String getLink()
1688 {
1689 return _textRepresentation.getLink();
1690 }
1691
1692 /**
1693 * <b>Note:</b> That if the widget has no parent (e.g. the widget is a
1694 * free-item) then the absolute link returned will be for the frameset of
1695 * the current frame.
1696 *
1697 * @return The absolute link for this item. Null if there is no link, or if
1698 * there is no parent for this widget and the current frame is
1699 * unavailable.
1700 *
1701 */
1702 public String getAbsoluteLink()
1703 {
1704 // Note: cannot return the source absolute link since it does not have
1705 // a parent ... thus must manually format link
1706
1707 final String link = getLink();
1708
1709 if (link == null || link.length() == 0) {
1710 return null;
1711 }
1712
1713 if (FrameIO.isPositiveInteger(link)) { // relative - convert to
1714 // absolute
1715
1716 // Get the frameset of this item
1717 Frame parent = getParentFrame();
1718 if (parent == null) {
1719 parent = DisplayController.getCurrentFrame();
1720 }
1721 if (parent == null) {
1722 return null;
1723 }
1724
1725 final String framesetName = parent.getFramesetName();
1726
1727 if (framesetName == null) {
1728 return null;
1729 }
1730
1731 return framesetName + link;
1732
1733 } else if (FrameIO.isValidFrameName(link)) { // already absolute
1734 return link;
1735 }
1736
1737 return null;
1738 }
1739
1740 /**
1741 * Sets the border color for the widget.
1742 * That is, for the source (so it is remembered) and also for all the
1743 * corners/edges.
1744 *
1745 * @param c
1746 * The color to set.
1747 */
1748 public void setWidgetEdgeColor(final Colour c)
1749 {
1750 // Indirectly invokes setSourceBorderColor accordingly
1751 for (final Item i : _expediteeItems) {
1752 i.setColor(c);
1753 }
1754 }
1755
1756 /**
1757 * Sets the thickness of the widget edge.
1758 *
1759 * @see Item#setThickness(float)
1760 *
1761 * @param thickness
1762 * The new thickness to set.
1763 */
1764 public void setWidgetEdgeThickness(final float thickness) {
1765 _l1.setThickness(thickness, true);
1766 //for (Item i : _expediteeItems) i.setThickness(thickness);
1767// Above indirectly invokes setSourceThickness accordingly
1768 }
1769
1770 /**
1771 * Override to dis-allow widget thickness manipulation from the user.
1772 * @return
1773 */
1774 public boolean isWidgetEdgeThicknessAdjustable() {
1775 return true;
1776 }
1777
1778 // TODO: Maybe rename setSource* .. to update* ... These should actually be friendly!
1779 public void setSourceColor(final Colour c) {
1780 _textRepresentation.setColor(c);
1781 }
1782
1783 public void setSourceBorderColor(final Colour c) {
1784 _textRepresentation.setBorderColor(c);
1785 }
1786
1787 public void setSourceFillColor(final Colour c) {
1788 _textRepresentation.setFillColor(c);
1789 }
1790
1791 public void setSourceThickness(final float newThickness, final boolean setConnected) {
1792 _textRepresentation.setThickness(newThickness, setConnected);
1793 }
1794
1795 public void setSourceData(final List<String> data) {
1796 _textRepresentation.setData(data);
1797 }
1798
1799 protected Point getOrigin() {
1800 return _d1.getPosition(); // NOTE FROM BROOK: This flips around ... the origin can be any point
1801 }
1802
1803 protected Item getFirstCorner() {
1804 return _d1;
1805 }
1806
1807 public void setAnchorLeft(final Integer anchor)
1808 {
1809 _anchoring.setLeftAnchor(anchor);
1810 // Anchor left-edge corners (dots) as well
1811 _d1.setAnchorCornerX(anchor,null);
1812 _d4.setAnchorCornerX(anchor,null);
1813
1814 if (anchor != null) {
1815 setPositions(_d1, anchor, _d1.getY());
1816 invalidateSelf();
1817 }
1818
1819 // Move X-rayable item as well
1820 getCurrentRepresentation().setAnchorLeft(anchor);
1821 }
1822
1823 public void setAnchorRight(final Integer anchor) {
1824 _anchoring.setRightAnchor(anchor);
1825 // Anchor right-edge corners (dots) as well
1826 _d2.setAnchorCornerX(null,anchor); // right
1827 _d3.setAnchorCornerX(null,anchor); // right
1828
1829 if (anchor != null) {
1830 setPositions(_d2, DisplayController.getFramePaintAreaWidth() - anchor, _d2.getY());
1831 invalidateSelf();
1832 }
1833
1834 if (_anchoring.getLeftAnchor() == null) {
1835 // Prefer having the X-rayable item at anchorLeft position (if defined) over moving to anchorRight
1836 getCurrentRepresentation().setAnchorRight(anchor);
1837 }
1838 }
1839
1840 public void setAnchorTop(final Integer anchor) {
1841 _anchoring.setTopAnchor(anchor);
1842 // Anchor top-edge corners (dots) as well
1843 _d1.setAnchorCornerY(anchor,null);
1844 _d2.setAnchorCornerY(anchor,null);
1845
1846 if (anchor != null) {
1847 setPositions(_d2, _d2.getX(), anchor);
1848 invalidateSelf();
1849 }
1850
1851 // Move X-rayable item as well
1852 getCurrentRepresentation().setAnchorTop(anchor);
1853 }
1854
1855 public void setAnchorBottom(final Integer anchor) {
1856 _anchoring.setBottomAnchor(anchor);
1857 // Anchor bottom-edge corners (dots) as well
1858 _d3.setAnchorCornerY(null,anchor);
1859 _d4.setAnchorCornerY(null,anchor);
1860
1861 if (anchor != null) {
1862 setPositions(_d3, _d3.getX(), DisplayController.getFramePaintAreaHeight() - anchor);
1863 invalidateSelf();
1864 }
1865
1866 if (_anchoring.getTopAnchor() == null) {
1867 // Prefer having the X-rayable item at anchorTop position (if defined) over moving to anchorBottom
1868 getCurrentRepresentation().setAnchorBottom(anchor);
1869 }
1870 }
1871
1872
1873 public boolean isAnchored() {
1874 return (isAnchoredX()) || (isAnchoredY());
1875 }
1876
1877 public boolean isAnchoredX() {
1878 return _anchoring.isXAnchored();
1879 }
1880
1881 public boolean isAnchoredY() {
1882 return _anchoring.isYAnchored();
1883 }
1884
1885 /**
1886 * Call from expeditee for representing the name of the item.
1887 * Override to return custom name.
1888 *
1889 * Note: Used for the new frame title when creating links for widgets.
1890 *
1891 * @return
1892 * The name representing this widget
1893 */
1894 public String getName() {
1895 return this.toString();
1896 }
1897
1898 /**
1899 * Event called when the widget is left clicked while there are items attached to the FreeItems buffer.
1900 * Used to enable expeditee like text-widget interaction for left mouse clicks.
1901 * @return true if event was handled (no pass through), otherwise false.
1902 */
1903 public boolean ItemsLeftClickDropped() {
1904 return false;
1905 }
1906
1907 /**
1908 * Event called when the widget is middle clicked while there are items attached to the FreeItems buffer.
1909 * Used to enable expeditee like text-widget interaction for middle mouse clicks.
1910 * @return true if event was handled (no pass through), otherwise false.
1911 */
1912 public boolean ItemsMiddleClickDropped() {
1913 return false;
1914 }
1915
1916 /**
1917 * Event called when the widget is left clicked while there are items attached to the FreeItems buffer.
1918 * Used to enable expeditee like text-widget interaction for right mouse clicks
1919 * @return true if event was handled (no pass through), otherwise false.
1920 */
1921 public boolean ItemsRightClickDropped() {
1922 return false;
1923 }
1924
1925 /** Gets the clip area for this widget. */
1926 public Clip getClip()
1927 {
1928 final Clip clip = new Clip(getContentBounds());
1929 return clip;
1930 }
1931
1932 public void onResized() {
1933 invalidateSelf();
1934 onBoundsChanged();
1935 layout();
1936 }
1937}
Note: See TracBrowser for help on using the repository browser.