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

Last change on this file since 108 was 108, checked in by ra33, 16 years ago

Heaps of changes!!!!
Added circles...
Better drawing of lines etc etc

File size: 24.3 KB
Line 
1package org.expeditee.items;
2
3import java.awt.Color;
4import java.awt.Component;
5import java.awt.Container;
6import java.awt.Graphics;
7import java.awt.Point;
8import java.lang.reflect.Constructor;
9import java.util.ArrayList;
10import java.util.Collection;
11import java.util.Collections;
12import java.util.LinkedList;
13import java.util.List;
14
15import javax.swing.JComponent;
16import javax.swing.SwingUtilities;
17
18import org.expeditee.gui.Browser;
19import org.expeditee.gui.DisplayIO;
20import org.expeditee.gui.Frame;
21import org.expeditee.gui.FrameGraphics;
22
23/**
24 * The bridge between swing space and expeditee space
25 *
26 * @author Brook
27 *
28 */
29public abstract class InteractiveWidget {
30
31 protected JComponent _swingComponent;
32
33 /** A widget is comprised of dots and lines that basically form a rectangle */
34 private WidgetCorner _d1, _d2, _d3, _d4;
35
36 private WidgetEdge _l1, _l2, _l3, _l4;
37
38 /*
39 * GUIDE: l1 d1-------d2 | | l4 | X | 12 | | d4-------d3 13
40 */
41 private List<Item> _expediteeItems; // used for quickly returning item list
42
43 // Widget size restrictions
44 private int _minWidth = 50;
45
46 private int _minHeight = 50;
47
48 private int _maxWidth = 300;
49
50 private int _maxHeight = 300;
51
52 // The Expeditee item that is used for saving widget state in expiditee
53 // world
54 private Text _textRepresentation;
55
56 // A flag for signifying whether the swing components are ready to paint.
57 // If the swing components has not been layed out, if they are painted they
58 // will
59 // not draw in the correct positions.
60 private boolean _isReadyToPaint = false;
61
62 /**
63 * Creates a InteractiveWidget from a text item.
64 *
65 * @param source
66 * Must not be null, first line of text used - which the format
67 * must be as follows: "@iw: <<widget_class_name>> [<<width>>] [<<height>>] [: [<<arg1>>] [<<arg2>>]
68 * [...]]".
69 *
70 * e.g: "@iw: org.expeditee.items.SampleWidget1 100 20 : 2" creates a
71 * SampleWidget1 with width = 100 and height = 20 with 1 argument = "2"
72 *
73 * @return An InteractiveWidget instance. Never null.
74 *
75 * @throws NullPointerException
76 * if source is null
77 *
78 * @throws IllegalArgumentException
79 * if source's text is in the incorrect format or if source's
80 * parent is null
81 *
82 * @throws InteractiveWidgetNotAvailableException
83 * If the given widget class name in the source text doesn't
84 * exist or not an InteractiveWidget or the widget does not
85 * supply a valid constructor for creation.
86 *
87 * @throws InteractiveWidgetInitialisationFailedException
88 * The sub-constructor failed - depends on the type of widget being
89 * instantainiated.
90 *
91 * class names beginning with $, the $ will be replaced with
92 * "org.expeditee.items."
93 */
94 public static InteractiveWidget CreateWidget(Text source)
95 throws InteractiveWidgetNotAvailableException, InteractiveWidgetInitialisationFailedException {
96
97 if (source == null)
98 throw new NullPointerException("source");
99 if (source.getParent() == null)
100 throw new IllegalArgumentException(
101 "source's parent is null, InteractiveWidget's must be created from Text items with non-null parents");
102
103 String TAG = ItemUtils.GetTag(ItemUtils.TAG_IWIDGET);
104
105 String text = source.getText();
106 if (text == null)
107 throw new IllegalArgumentException("source does not have any text");
108
109 text = text.trim();
110
111 int index = 0;
112 if (text.length() > TAG.length() + 1) {
113 index = text.indexOf(':', TAG.length() + 1); // used for signifying start of
114 }
115
116 // arguments
117 if (index == 0)
118 throw new IllegalArgumentException("Source text must begin with \""
119 + TAG + "\"");
120
121 String[] tokens = (index == -1) ? text.split("\\s+") : text.substring(
122 0, index).split(" ");
123
124 // Check starts with the widget tag and seporator
125 if (tokens.length == 0 || !tokens[0].equals(TAG + ":"))
126 throw new IllegalArgumentException("Source text must begin with \""
127 + TAG + ":\"");
128
129 int width = -1, height = -1;
130
131 if (tokens.length < 2)
132 throw new IllegalArgumentException(
133 "Missing widget class name in source text");
134
135 try {
136
137 if (tokens.length >= 3) { // parse optional width
138 width = Integer.parseInt(tokens[2]);
139 width = (width <= 0) ? width = -1 : width;
140 }
141
142 if (tokens.length >= 4) { // parse optional height
143 height = Integer.parseInt(tokens[3]);
144 height = (height <= 0) ? height = -1 : height;
145 }
146
147 } catch (NumberFormatException nfe) {
148 throw new IllegalArgumentException(
149 "Bad width or height given in source text", nfe);
150 }
151
152 if (tokens.length > 4)
153 throw new IllegalArgumentException(
154 "to many arguments given before \":\" in source text");
155
156 String classname = tokens[1];
157 if (classname.charAt(0) == '$')
158 classname = "org.expeditee.items." + classname.substring(1);
159 // Attempt to locate the class using reflection
160 Class<?> iwclass = findIWidgetClass(classname);
161
162 if (iwclass == null) // ensure it exists
163 throw new InteractiveWidgetNotAvailableException(classname
164 + " does not exist or is not an InteractiveWidget");
165
166 // Extract out the parameters - if any
167 String[] args = null;
168 if (index > 0) { // index of the first ":"
169 args = (text.length() == (index + 1)) ? null : parseArgs(text
170 .substring(index + 1));
171 }
172
173 InteractiveWidget inst = null;
174 try {
175 // Instantainiate the widget - passing the params
176 Class parameterTypes[] = new Class[] { Text.class, String[].class };
177 Constructor ct = iwclass.getConstructor(parameterTypes);
178
179 Object arglist[] = new Object[] { source, args };
180
181 inst = (InteractiveWidget) ct.newInstance(arglist);
182 } catch (Exception e) {
183 throw new InteractiveWidgetNotAvailableException(
184 "Failed to create instance via reflection: " + e.toString(),
185 e);
186 }
187
188 // Use default dimensions if not provided (or provided as negitive
189 // values)
190 if (width <= 0)
191 width = inst.getWidth();
192 if (height <= 0)
193 height = inst.getHeight();
194
195 inst.setSize(width, height);
196
197 return inst;
198 }
199
200 /**
201 * Locates the class from the classname of an InteractiveWidget class
202 *
203 * @param classname
204 * The name of the class to search
205 * @return Null if doesn't exist or not an InteractiveWidget
206 */
207 private static Class findIWidgetClass(String classname) {
208 try {
209 Class c = Class.forName(classname); // attempt to find the class
210
211 // If one is found, ensure that it is a descendant of an
212 // InteractiveWidget
213 for (Class superclass = c.getSuperclass(); superclass != null
214 && superclass != Item.class; superclass = superclass
215 .getSuperclass()) {
216 if (superclass == InteractiveWidget.class)
217 return c;
218 }
219
220 } catch (ClassNotFoundException e) {
221 }
222
223 // Doesn't exist or not an InteractiveWidget
224 return null;
225 }
226
227 /**
228 * Using Microsofts commandline convention: Args seperated with white
229 * spaces. Options with white spaces enclosed with quotes. Args with quotes
230 * must be double quoted args1 args2=sfasas args3="option with spaces"
231 * arg""4""
232 *
233 * @param args
234 * Null and empty excepted
235 * @return An array of args. null if none provided
236 */
237 static String[] parseArgs(String args) {
238
239 if (args == null)
240 return null;
241
242 args = args.trim();
243 if (args.length() == 0)
244 return null;
245
246 List<String> vargs = new LinkedList<String>();
247 StringBuilder sb = new StringBuilder();
248 boolean quoteOn = false;
249 for (int i = 0; i < args.length(); i++) {
250
251 char c = args.charAt(i);
252 if (c == ' ' && !quoteOn) {
253
254 // Extract arg
255 vargs.add(sb.toString());
256 sb = new StringBuilder();
257
258 // Consume white spaces
259 while (args.charAt(++i) == ' ' && i < args.length()) {
260 }
261 i--;
262
263 } else if (c == '\"') {
264 // If double qouted
265 if (args.length() >= (i + 2) && args.charAt(i + 1) == '\"') {
266
267 sb.append(c); // add escaped
268 i++;
269
270 } else {
271 quoteOn = !quoteOn;
272 }
273
274 } else {
275 sb.append(c);
276 }
277 }
278
279 if (sb.length() > 0)
280 vargs.add(sb.toString());
281
282 if (vargs.size() == 0)
283 return null;
284 else
285 return vargs.toArray(new String[vargs.size()]);
286 }
287
288 /**
289 * Reverse of parseArgs
290 *
291 * @return Null if args is null or empty / all whitespace
292 */
293 static String formatArgs(String[] args) {
294 if (args == null)
295 return null;
296
297 StringBuilder sb = new StringBuilder();
298
299 for (String s : args) {
300 if (s == null)
301 continue;
302
303 // Escape quotes
304 StringBuilder formatted = new StringBuilder(s.replaceAll("\"",
305 "\"\""));
306
307 // Encapsulate spaces
308 int index = formatted.indexOf(" ");
309 if (index >= 0) {
310 formatted.insert(index, "\"");
311 formatted.append('\"');
312 }
313
314 if (sb.length() > 0)
315 sb.append(' ');
316 sb.append(formatted);
317 }
318
319 return sb.length() > 0 ? sb.toString() : null;
320 }
321
322 /**
323 * Arguments represent state. They are used for saving, loading, creating
324 * and cloning Special formatting is done for you.
325 *
326 * @param isCopy
327 * If true, then the requested arguments returned should
328 * represent a copy of this instance Otherwise the arguments
329 * returned should represent this instance. This gives the widget
330 * the chance to allocate new/duplicate resources if they need
331 * to.
332 *
333 * @return Can be null for no params.
334 */
335 protected abstract String[] getArgs(boolean isCopy);
336
337 /**
338 * Constructor
339 *
340 * @param source
341 * Must not be null. Neither must it's parent
342 * @param component
343 * Must not be null
344 */
345 protected InteractiveWidget(Text source, JComponent component,
346 int minWidth, int maxWidth, int minHeight, int maxHeight) {
347
348 if (component == null)
349 throw new NullPointerException("component");
350 if (source == null)
351 throw new NullPointerException("source");
352 if (source.getParent() == null)
353 throw new IllegalArgumentException(
354 "source's parent is null, InteractiveWidget's must be created from Text items with non-null parents");
355
356 _swingComponent = component;
357 _textRepresentation = source;
358
359 _minWidth = minWidth;
360
361 if (maxWidth < _minWidth && maxWidth >= 0)
362 throw new IllegalArgumentException(
363 "maxWidth smaller than the min Width");
364 _maxWidth = maxWidth;
365
366 _minHeight = minHeight;
367 if (maxHeight < _minHeight && maxHeight >= 0)
368 throw new IllegalArgumentException(
369 "maxHeight smaller than the min Height");
370 _maxHeight = maxHeight;
371
372 int x = source.getX();
373 int y = source.getY();
374 int width = (_minWidth < 0) ? 10 : _minWidth;
375 int height = (_minHeight < 0) ? 10 : _minHeight;
376
377 Frame idAllocator = _textRepresentation.getParent();
378
379 // create WidgetCorners
380 _d1 = new WidgetCorner(x, y, idAllocator.getNextItemID(), this);
381 _d2 = new WidgetCorner(x + width, y, idAllocator.getNextItemID(), this);
382 _d3 = new WidgetCorner(x + width, y + height, idAllocator
383 .getNextItemID(), this);
384 _d4 = new WidgetCorner(x, y + height, idAllocator.getNextItemID(), this);
385
386 // create WidgetEdges
387 _l1 = new WidgetEdge(_d1, _d2, idAllocator.getNextItemID());
388 _l2 = new WidgetEdge(_d2, _d3, idAllocator.getNextItemID());
389 _l3 = new WidgetEdge(_d3, _d4, idAllocator.getNextItemID());
390 _l4 = new WidgetEdge(_d4, _d1, idAllocator.getNextItemID());
391
392 Collection<Item> enclist = new ArrayList<Item>(4);
393 enclist.add(_d1);
394 enclist.add(_d2);
395 enclist.add(_d3);
396 enclist.add(_d4);
397 _d1.setEnclosedList(enclist);
398 _d2.setEnclosedList(enclist);
399 _d3.setEnclosedList(enclist);
400 _d4.setEnclosedList(enclist);
401
402 _expediteeItems = new ArrayList<Item>(8); // Note: order important
403 _expediteeItems.add(_d1);
404 _expediteeItems.add(_d2);
405 _expediteeItems.add(_d3);
406 _expediteeItems.add(_d4);
407 _expediteeItems.add(_l1);
408 _expediteeItems.add(_l2);
409 _expediteeItems.add(_l3);
410 _expediteeItems.add(_l4);
411 }
412
413 /**
414 * @return A copy of this widget
415 */
416 public final InteractiveWidget copy()
417 throws InteractiveWidgetNotAvailableException,
418 InteractiveWidgetInitialisationFailedException {
419
420 Text t = _textRepresentation.copy();
421 String clonedAnnotation = getAnnotationString(true);
422 t.setText(clonedAnnotation);
423 return InteractiveWidget.CreateWidget(t);
424 }
425
426 /**
427 * @return The top-most class name including the package.
428 */
429 protected abstract String getTopMostClassName();
430
431 /**
432 * Note updates the source text with current state info
433 *
434 * @return The Text item that this widget was created from.
435 */
436 public Item getSource() {
437
438 // Build the annotation string such that it represents this widgets
439 // current state
440 String newAnnotation = getAnnotationString(false);
441
442 // Set the new text
443 _textRepresentation.setText(newAnnotation);
444
445 return _textRepresentation;
446 }
447
448 /**
449 * @param copiedVersion
450 * True to return a copied representation of this instance. False
451 * to return this instances current state version.
452 *
453 * @return The expeditee anotation string.
454 */
455 private String getAnnotationString(boolean copiedVersion) {
456
457 // Create tag and append classname
458 StringBuilder sb = new StringBuilder(ItemUtils
459 .GetTag(ItemUtils.TAG_IWIDGET));
460 sb.append(':');
461 sb.append(' ');
462 sb.append(getTopMostClassName());
463
464 // Append size information if needed (not an attibute of text items)
465 if (!isFixedSize()) {
466 sb.append(' ');
467 sb.append(getWidth());
468 sb.append(' ');
469 sb.append(getHeight());
470 }
471
472 // Append arguments if any
473 String stateArgs = InteractiveWidget.formatArgs(getArgs(copiedVersion));
474 if (stateArgs != null) {
475 sb.append(':');
476 sb.append(stateArgs);
477 }
478
479 return sb.toString();
480 }
481
482 /**
483 * Clamped to current min/max width/height.
484 *
485 * @param width
486 * @param height
487 */
488 public void setSize(int width, int height) {
489
490 // Clamp
491 if (width < _minWidth && _minWidth >= 0)
492 width = _minWidth;
493 else if (width > _maxWidth && _maxWidth >= 0)
494 width = _maxWidth;
495
496 if (height < _minHeight && _minHeight >= 0)
497 height = _minHeight;
498 else if (height > _maxHeight && _maxHeight >= 0)
499 height = _maxHeight;
500
501 boolean vfloating[] = new boolean[] { _d1.isFloating(),
502 _d2.isFloating(), _d3.isFloating(), _d4.isFloating() };
503
504 _d1.setFloating(true);
505 _d2.setFloating(true);
506 _d3.setFloating(true);
507 _d4.setFloating(true);
508
509 int x = _d1.getX() + width;
510 int y = _d1.getY() + height;
511 _d2.setX(x);
512 _d3.setX(x);
513 _d3.setY(y);
514 _d4.setY(y);
515
516 _d1.setFloating(vfloating[0]);
517 _d2.setFloating(vfloating[1]);
518 _d3.setFloating(vfloating[2]);
519 _d4.setFloating(vfloating[3]);
520
521 }
522
523 public void setPosition(int x, int y) {
524
525 boolean vfloating[] = new boolean[] { _d1.isFloating(),
526 _d2.isFloating(), _d3.isFloating(), _d4.isFloating() };
527
528 int width = getWidth();
529 int height = getHeight();
530
531 _d1.setFloating(true);
532 _d2.setFloating(true);
533 _d3.setFloating(true);
534 _d4.setFloating(true);
535
536 _d1.setPosition(x, y);
537 _d2.setPosition(x + width, y);
538 _d3.setPosition(x + width, y + height);
539 _d4.setPosition(x, y + height);
540
541 _d1.setFloating(vfloating[0]);
542 _d2.setFloating(vfloating[1]);
543 _d3.setFloating(vfloating[2]);
544 _d4.setFloating(vfloating[3]);
545
546 }
547
548 private boolean _settingPositionFlag = false; // used for recursion
549
550 /**
551 * to be called from corners only
552 *
553 * @param src
554 * @param x
555 * @param y
556 * @return False if need to call super.setPosition
557 */
558 boolean setPositions(WidgetCorner src, int x, int y) {
559
560 if (_settingPositionFlag)
561 return false;
562 _settingPositionFlag = true;
563
564 // Check to see if the widget is fully being picked up
565 boolean isAllPickedUp = (_d1.isFloating() && _d2.isFloating()
566 && _d3.isFloating() && _d4.isFloating());
567
568 int newX = x;
569
570 // X Positions
571 if (src == _d1 || src == _d4) {
572
573 if (src == _d1 && isAllPickedUp)
574 _d1.setX(newX);
575 else if (src == _d4 && isAllPickedUp)
576 _d4.setX(newX);
577 else {
578 // Check min X constraint
579 if (_minWidth >= 0) {
580 if ((_d2.getX() - x) < _minWidth) {
581 newX = _d2.getX() - _minWidth;
582 }
583 }
584 // Check max X constraint
585 if (_maxWidth >= 0) {
586 if ((_d2.getX() - x) > _maxWidth) {
587 newX = _d2.getX() - _maxWidth;
588 }
589 }
590
591 if (!(src == _d4 && _d1.isFloating() && _d4.isFloating()))
592 _d1.setX(newX);
593 if (!(src == _d1 && _d4.isFloating() && _d1.isFloating()))
594 _d4.setX(newX);
595 }
596
597 } else if (src == _d2 || src == _d3) {
598
599 if (src == _d2 && isAllPickedUp)
600 _d2.setX(newX);
601 else if (src == _d3 && isAllPickedUp)
602 _d3.setX(newX);
603 else {
604 // Check min X constraint
605 if (_minWidth >= 0) {
606 if ((x - _d1.getX()) < _minWidth) {
607 newX = _d1.getX() + _minWidth;
608 }
609 }
610 // Check max X constraint
611 if (_maxWidth >= 0) {
612 if ((x - _d1.getX()) > _maxWidth) {
613 newX = _d1.getX() + _maxWidth;
614 }
615 }
616
617 if (!(src == _d3 && _d2.isFloating() && _d3.isFloating()))
618 _d2.setX(newX);
619 if (!(src == _d2 && _d3.isFloating() && _d2.isFloating()))
620 _d3.setX(newX);
621 }
622
623 }
624
625 int newY = y;
626
627 // Y Positions
628 if (src == _d1 || src == _d2) {
629
630 if (src == _d1 && isAllPickedUp)
631 _d1.setY(newY);
632 else if (src == _d2 && isAllPickedUp)
633 _d2.setY(newY);
634 else {
635 // Check min Y constraint
636 if (_minHeight >= 0) {
637 if ((_d4.getY() - y) < _minHeight) {
638 newY = _d4.getY() - _minHeight;
639 }
640 }
641 // Check max Y constraint
642 if (_maxHeight >= 0) {
643 if ((_d4.getY() - y) > _maxHeight) {
644 newY = _d4.getY() - _maxHeight;
645 }
646 }
647
648 if (!(src == _d2 && _d1.isFloating() && _d2.isFloating()))
649 _d1.setY(newY);
650 if (!(src == _d1 && _d2.isFloating() && _d1.isFloating()))
651 _d2.setY(newY);
652
653 }
654
655 } else if (src == _d3 || src == _d4) {
656
657 if (src == _d3 && isAllPickedUp)
658 _d3.setY(newY);
659 else if (src == _d4 && isAllPickedUp)
660 _d4.setY(newY);
661 else {
662 // Check min Y constraint
663 if (_minHeight >= 0) {
664 if ((y - _d1.getY()) < _minHeight) {
665 newY = _d1.getY() + _minHeight;
666 }
667 }
668 // Check max Y constraint
669 if (_maxHeight >= 0) {
670 if ((y - _d1.getY()) > _maxHeight) {
671 newY = _d1.getY() + _maxHeight;
672 }
673 }
674
675 if (!(src == _d4 && _d3.isFloating() && _d4.isFloating()))
676 _d3.setY(newY);
677 if (!(src == _d3 && _d4.isFloating() && _d3.isFloating()))
678 _d4.setY(newY);
679 }
680 }
681
682 // Update source text position so position is remembered from loading
683 int newTextX = getX();
684 int newTextY = getY();
685 if (_textRepresentation.getX() != newTextX || _textRepresentation.getY() != newTextY)
686 _textRepresentation.setPosition(newTextX, newTextY);
687
688 _settingPositionFlag = false;
689 return true;
690 }
691
692 public int getX() {
693 return Math.min(_d1.getX(), _d2.getX());
694 }
695
696 public int getY() {
697 return Math.min(_d1.getY(), _d4.getY());
698 }
699
700 public int getWidth() {
701 return Math.abs(_d2.getX() - _d1.getX());
702 }
703
704 public int getHeight() {
705 return Math.abs(_d4.getY() - _d1.getY());
706 }
707
708 /**
709 * The order of the items in the list is as specified: _d1 _d2 _d3 _d4 _l1
710 * _l2 _l3 _l4
711 *
712 * @return All of the Expeditee items that form the bounderies of this
713 * widget in an unmodifiable list
714 */
715 public List<Item> getItems() {
716 return Collections.unmodifiableList(_expediteeItems);
717 }
718
719 public JComponent getComponant() {
720 return _swingComponent;
721 }
722
723 public void onParentStateChanged(ItemParentStateChangedEvent e) {
724 switch (e.getEventType()) {
725
726 case ItemParentStateChangedEvent.EVENT_TYPE_REMOVED:
727 case ItemParentStateChangedEvent.EVENT_TYPE_REMOVED_VIA_OVERLAY:
728 case ItemParentStateChangedEvent.EVENT_TYPE_HIDDEN:
729 if (_swingComponent.getParent() != null) {
730 _swingComponent.getParent().remove(_swingComponent);
731 }
732 break;
733
734 case ItemParentStateChangedEvent.EVENT_TYPE_ADDED:
735 case ItemParentStateChangedEvent.EVENT_TYPE_ADDED_VIA_OVERLAY:
736 case ItemParentStateChangedEvent.EVENT_TYPE_SHOWN:
737 case ItemParentStateChangedEvent.EVENT_TYPE_SHOWN_VIA_OVERLAY:
738 if (_swingComponent.getParent() == null) {
739 addJComponantToFrame(e);
740 }
741 break;
742
743 }
744 }
745
746 protected void addJComponantToFrame(ItemParentStateChangedEvent e) {
747
748 if ((e.getEventType() == ItemParentStateChangedEvent.EVENT_TYPE_ADDED_VIA_OVERLAY || e
749 .getEventType() == ItemParentStateChangedEvent.EVENT_TYPE_SHOWN_VIA_OVERLAY)
750 && e.getOverlayLevel().equals(Permission.none)) {
751 return; // item belongs to a non-active overlay
752 }
753
754 if (_swingComponent.getParent() == null) {
755
756 if (Browser._theBrowser != null) {
757 // Due to precaching - before adding physical swing
758 // componant must check to see that this widget belongs to a
759 // frame that is
760 // considered current. If the widget is shown however this does
761 // not apply -
762 // since it has been explicitly made clear the the widget is
763 // shown.
764 if (e.getEventType() == ItemParentStateChangedEvent.EVENT_TYPE_SHOWN
765 || e.getEventType() == ItemParentStateChangedEvent.EVENT_TYPE_SHOWN_VIA_OVERLAY
766 || e.getSource() == DisplayIO.getCurrentFrame()) {
767 Browser._theBrowser.getContentPane().add(_swingComponent);
768 layout(_swingComponent);
769 }
770
771 } else { // if widgets exist on startup frame this will occur
772
773 synchronized (_widgetsToAddLater) {
774 _widgetsToAddLater.add(new WidgetInfo(this, e));
775 }
776 SwingUtilities.invokeLater(new AddToFrameLater());
777 }
778
779 }
780
781 }
782
783 /**
784 * Used for passing info to the swing thread
785 *
786 * @author Brook Novak
787 */
788 class WidgetInfo {
789
790 WidgetInfo(InteractiveWidget widget, ItemParentStateChangedEvent e) {
791 _widget = widget;
792 _e = e;
793 }
794
795 InteractiveWidget _widget;
796
797 ItemParentStateChangedEvent _e;
798 }
799
800 /**
801 * Must be able to add widgets on first loaded frame: these are loaded
802 * before the browser singleton is made available.
803 */
804 private static List<WidgetInfo> _widgetsToAddLater = new LinkedList<WidgetInfo>();
805
806 /**
807 * Ensures widgets are added correctly to first loaded frame
808 */
809 class AddToFrameLater implements Runnable {
810 public void run() {
811 if (!_widgetsToAddLater.isEmpty()) {
812 List<WidgetInfo> tmp = null;
813 synchronized (_widgetsToAddLater) {
814 tmp = new LinkedList<WidgetInfo>(_widgetsToAddLater);
815 }
816 _widgetsToAddLater.clear();
817 for (WidgetInfo iwi : tmp) {
818 iwi._widget.addJComponantToFrame(iwi._e);
819 }
820 }
821 }
822 }
823
824 /**
825 * Due to absolute positioning...
826 *
827 * @param parent
828 */
829 private void layout(Component parent) {
830
831 parent.doLayout();
832
833 if (parent instanceof Container) {
834 for (Component c : ((Container) parent).getComponents()) {
835
836 if (c instanceof Container)
837 layout(c);
838 else
839 c.doLayout();
840 }
841 }
842
843 _isReadyToPaint = true;
844 }
845
846 public void onBoundsChanged() {
847
848 _swingComponent.setBounds(getX(), getY(), getWidth(), getHeight());
849
850 }
851
852 /**
853 * Paints the widget excluding the boundries. That is, the Swing graphics
854 *
855 * @param g
856 */
857 public void paint(Graphics g) {
858
859 if (!_isReadyToPaint) {
860 layout(_swingComponent);
861 }
862
863 Point loc = _swingComponent.getLocation();
864 g.translate(loc.x, loc.y - 1);
865 _swingComponent.paint(g);
866 g.translate(-loc.x, -(loc.y - 1));
867
868 }
869
870 /**
871 * Called from the widgets corners: Whenever the corners are repainted and
872 * the widget is floating (e.g. currently picked up / rubberbanding) then a
873 * shaded area is drawn instead of the actual widget so the manipulation of
874 * the widget is as smooth as possible. The shaded area is only draw once so
875 * it selects one of the 4 possible notifiers to be the actual trigger to
876 * draw the shaded area
877 *
878 * @param g
879 * @param notifier
880 */
881 void paint(Graphics g, WidgetCorner notifier) {
882 if (_swingComponent.getParent() == null) {
883 // Note that frames with @f may want to paint the widgets so do not
884 // paint over the widget interface in these cases: must only
885 // paint if an object is floating
886 if (notifier == _d4
887 && (_d1.isFloating() || _d2.isFloating()
888 || _d3.isFloating() || _d4.isFloating())) {
889 g.setColor(Color.DARK_GRAY);
890 g.fillRect(getX(), getY(), getWidth(), getHeight());
891 }
892 }
893 }
894
895 /**
896 * @return True if this widget cannot be resized in either directions
897 */
898 public boolean isFixedSize() {
899 return this._minHeight == this._maxHeight
900 && this._minWidth == this._maxWidth && this._minHeight >= 0
901 && this._minWidth >= 0;
902 }
903
904 private static boolean _isExepiteeHighlightingEnabled = true;
905
906 /**
907 * Enables/Disables highlighting of all widgets - if the enable value has
908 * changed the current frame will repaint.
909 *
910 * The purpose of this is so that widgets can have an extra context: no
911 * expeditee highlighting if focus is on a swing componet.
912 *
913 * @param enable
914 * Set to true to enable painting, false to disable painting.
915 */
916 public static void enableExepiteeHighlighting(boolean enable) {
917 if (_isExepiteeHighlightingEnabled != enable) {
918 _isExepiteeHighlightingEnabled = enable;
919 FrameGraphics.Repaint();
920 }
921 }
922
923 public static boolean isExepiteeHighlightingEnabled() {
924 return _isExepiteeHighlightingEnabled;
925 }
926
927}
Note: See TracBrowser for help on using the repository browser.