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

Last change on this file since 725 was 725, checked in by jts21, 10 years ago

Auto-correct widget names with invalid capitalisation and/or missing widget package prefix

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