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

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

Simple changes

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