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

Last change on this file since 218 was 218, checked in by bjn8, 16 years ago

Fixed bug

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