source: trunk/src/org/expeditee/items/widgets/InteractiveWidget.java@ 1047

Last change on this file since 1047 was 1047, checked in by davidb, 8 years ago

Anchor top and left added as minus options to an @i item. Relocation of ParseArgsApache(). Addition of getData type function for Bool. Some typos fixed in comments. Left click on image refinement: it check the pixel clicked on in the image, and if the same as the background, it does a back() operation rather than forming a new link

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