001 /* SpringLayout.java --
002 Copyright (C) 2004, 2006, Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING. If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library. Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module. An independent module is a module which is not derived from
033 or based on this library. If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so. If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039 package javax.swing;
040
041 import java.awt.Component;
042 import java.awt.Container;
043 import java.awt.Dimension;
044 import java.awt.LayoutManager2;
045 import java.util.HashMap;
046 import java.util.Map;
047
048 /**
049 * A very flexible layout manager. Components are laid out by defining the
050 * relationships between them. The relationships are expressed as
051 * {@link Spring}s. You can attach a Spring for each edge of a component and
052 * link it to an edge of a different component. For example, you can say,
053 * the northern edge of component A should be attached to the southern edge
054 * of component B, and the space between them should be something between
055 * x and y pixels, and preferably z pixels.
056 * <p>While quite simple, this layout manager can be used to emulate most other
057 * layout managers, and can also be used to solve some layout problems, which
058 * would be hard to solve with other layout managers.</p>
059 *
060 * @author Roman Kennke (roman@ontographics.com)
061 */
062 public class SpringLayout implements LayoutManager2
063 {
064
065 /** The right edge of a component. */
066 public static final String EAST = "East";
067
068 /** The top edge of a component. */
069 public static final String NORTH = "North";
070
071 /** The bottom edge of a component. */
072 public static final String SOUTH = "South";
073
074 /** The left edge of a component. */
075 public static final String WEST = "West";
076
077 /** maps components to their constraints. */
078 private Map constraintsMap;
079
080 /**
081 * The constraints that define the relationships between components.
082 * Each Constraints object can hold 4 Springs: one for each edge of the
083 * component. Additionally it can hold Springs for the components width
084 * and the components height. Since the height and width constraints are
085 * dependend on the other constraints, a component can be over-constraint.
086 * In this case (like when all of NORTH, SOUTH and HEIGHT are constraint),
087 * the values are adjusted, so that the mathematics still hold true.
088 *
089 * @author Roman Kennke (roman@ontographics.com)
090 */
091 public static class Constraints
092 {
093
094 // The constraints for each edge, and width and height.
095 /** The Spring for the left edge. */
096 private Spring x;
097
098 /** The Spring for the upper edge. */
099 private Spring y;
100
101 /** The Spring for the height. */
102 private Spring height;
103
104 /** The Spring for the width. */
105 private Spring width;
106
107 /** The Spring for the right edge. */
108 private Spring east;
109
110 /** The Spring for the bottom edge. */
111 private Spring south;
112
113 /**
114 In each axis the user can set three values, i.e. x, width, east, if all
115 three are set, then there's no room for manoeuvre so in those cases the
116 third will be described by the below spring which is calculated in terms
117 of the other two
118 */
119 private Spring v;
120 private Spring h;
121
122 /**
123 * Creates a new Constraints object.
124 * There is no constraint set.
125 */
126 public Constraints()
127 {
128 x = y = height = width = east = south = v = h = null;
129 }
130
131 /**
132 * Creates a new Constraints object.
133 *
134 * @param x the constraint for the left edge of the component.
135 * @param y the constraint for the upper edge of the component.
136 */
137 public Constraints(Spring x, Spring y)
138 {
139 this.x = x;
140 this.y = y;
141 width = height = east = south = v = h = null;
142 }
143
144 /**
145 * Creates a new Constraints object.
146 *
147 * @param x the constraint for the left edge of the component.
148 * @param y the constraint for the upper edge of the component.
149 * @param width the constraint for the width of the component.
150 * @param height the constraint for the height of the component.
151 */
152 public Constraints(Spring x, Spring y, Spring width, Spring height)
153 {
154 this.x = x;
155 this.y = y;
156 this.width = width;
157 this.height = height;
158 east = south = v = h = null;
159 }
160
161 /**
162 * Create a new Constraints object which tracks the indicated
163 * component. The x and y positions for this Constraints object
164 * are constant Springs created with the component's location at
165 * the time this constructor is called. The width and height
166 * of this Constraints are Springs created using
167 * {@link Spring#width(Component)} and {@link Spring#height(Component)},
168 * respectively.
169 * @param component the component to track
170 * @since 1.5
171 */
172 public Constraints(Component component)
173 {
174 this(Spring.constant(component.getX()),
175 Spring.constant(component.getY()),
176 Spring.width(component),
177 Spring.height(component));
178 }
179
180 /**
181 * Returns the constraint for the edge with the <code>edgeName</code>.
182 * This is expected to be one of
183 * {@link #EAST}, {@link #WEST}, {@link #NORTH} or {@link #SOUTH}.
184 *
185 * @param edgeName the name of the edge.
186 * @return the constraint for the specified edge.
187 */
188 public Spring getConstraint(String edgeName)
189 {
190 Spring retVal = null;
191 if (edgeName.equals(SpringLayout.NORTH))
192 retVal = getY();
193 else if (edgeName.equals(SpringLayout.WEST))
194 retVal = getX();
195 else if (edgeName.equals(SpringLayout.SOUTH))
196 retVal = getSouth();
197 else if (edgeName.equals(SpringLayout.EAST))
198 retVal = getEast();
199 return retVal;
200 }
201
202 /**
203 * Returns the constraint for the height of the component.
204 *
205 * @return the height constraint.
206 */
207 public Spring getHeight()
208 {
209 if (height != null)
210 return height;
211 else if ((v == null) && (y != null) && (south != null))
212 v = Spring.sum(south, Spring.minus(y));
213 return v;
214 }
215
216 /**
217 * Returns the constraint for the width of the component.
218 *
219 * @return the width constraint.
220 */
221 public Spring getWidth()
222 {
223 if (width != null)
224 return width;
225 else if ((h == null) && (x != null) && (east != null))
226 h = Spring.sum(east, Spring.minus(x));
227 return h;
228 }
229
230 /**
231 * Returns the constraint for the left edge of the component.
232 *
233 * @return the left-edge constraint (== WEST).
234 */
235 public Spring getX()
236 {
237 if (x != null)
238 return x;
239 else if ((h == null) && (width != null) && (east != null))
240 h = Spring.sum(east, Spring.minus(width));
241 return h;
242 }
243
244 /**
245 * Returns the constraint for the upper edge of the component.
246 *
247 * @return the upper-edge constraint (== NORTH).
248 */
249 public Spring getY()
250 {
251 if (y != null)
252 return y;
253 else if ((v == null) && (height != null) && (south != null))
254 v = Spring.sum(south, Spring.minus(height));
255 return v;
256 }
257
258 /**
259 * Returns the constraint for the lower edge of the component.
260 *
261 * @return the lower-edge constraint (== SOUTH).
262 */
263 public Spring getSouth()
264 {
265 if (south != null)
266 return south;
267 else if ((v == null) && (height != null) && (y != null))
268 v = Spring.sum(y, height);
269 return v;
270 }
271
272 /**
273 * Returns the constraint for the right edge of the component.
274 *
275 * @return the right-edge constraint (== EAST).
276 */
277 public Spring getEast()
278 {
279 if (east != null)
280 return east;
281 else if ((h == null) && (width != null) && (x != null))
282 h = Spring.sum(x, width);
283 return h;
284 }
285
286 /**
287 * Sets a constraint for the specified edge. If this leads to an
288 * over-constrained situation, the constraints get adjusted, so that
289 * the mathematics still hold true.
290 *
291 * @param edgeName the name of the edge, one of {@link #EAST},
292 * {@link #WEST}, {@link #NORTH} or {@link #SOUTH}.
293 * @param s the constraint to be set.
294 */
295 public void setConstraint(String edgeName, Spring s)
296 {
297
298 if (edgeName.equals(SpringLayout.WEST))
299 setX(s);
300 else if (edgeName.equals(SpringLayout.NORTH))
301 setY(s);
302 else if (edgeName.equals(SpringLayout.EAST))
303 setEast(s);
304 else if (edgeName.equals(SpringLayout.SOUTH))
305 setSouth(s);
306
307 }
308
309 /**
310 * Sets the height-constraint.
311 *
312 * @param s the constraint to be set.
313 */
314 public void setHeight(Spring s)
315 {
316 height = s;
317 v = null;
318 if ((south != null) && (y != null) && (height != null))
319 south = null;
320 }
321
322 /**
323 * Sets the width-constraint.
324 *
325 * @param s the constraint to be set.
326 */
327 public void setWidth(Spring s)
328 {
329 width = s;
330 h = null;
331 if ((east != null) && (x != null) && (width != null))
332 east = null;
333 }
334
335 /**
336 * Sets the WEST-constraint.
337 *
338 * @param s the constraint to be set.
339 */
340 public void setX(Spring s)
341 {
342 x = s;
343 h = null;
344 if ((width != null) && (east != null) && (x != null))
345 width = null;
346 }
347
348 /**
349 * Sets the NORTH-constraint.
350 *
351 * @param s the constraint to be set.
352 */
353 public void setY(Spring s)
354 {
355 y = s;
356 v = null;
357 if ((height != null) && (south != null) && (y != null))
358 height = null;
359 }
360
361 /**
362 * Sets the SOUTH-constraint.
363 *
364 * @param s the constraint to be set.
365 */
366 public void setSouth(Spring s)
367 {
368 south = s;
369 v = null;
370 if ((height != null) && (south != null) && (y != null))
371 y = null;
372 }
373
374 /**
375 * Sets the EAST-constraint.
376 *
377 * @param s the constraint to be set.
378 */
379 public void setEast(Spring s)
380 {
381 east = s;
382 h = null;
383 if ((width != null) && (east != null) && (x != null))
384 x = null;
385 }
386
387 public void dropCalcResult()
388 {
389 if (x != null)
390 x.setValue(Spring.UNSET);
391 if (y != null)
392 y.setValue(Spring.UNSET);
393 if (width != null)
394 width.setValue(Spring.UNSET);
395 if (height != null)
396 height.setValue(Spring.UNSET);
397 if (east != null)
398 east.setValue(Spring.UNSET);
399 if (south != null)
400 south.setValue(Spring.UNSET);
401 if (h != null)
402 h.setValue(Spring.UNSET);
403 if (v != null)
404 v.setValue(Spring.UNSET);
405 }
406 }
407
408 /**
409 * Creates a new SpringLayout.
410 */
411 public SpringLayout()
412 {
413 constraintsMap = new HashMap();
414 }
415
416 /**
417 * Adds a layout component and a constraint object to this layout.
418 * This method is usually only called by a {@link java.awt.Container}s add
419 * method.
420 *
421 * @param component the component to be added.
422 * @param constraint the constraint to be set.
423 */
424 public void addLayoutComponent(Component component, Object constraint)
425 {
426 constraintsMap.put(component, constraint);
427 }
428
429 /**
430 * Adds a layout component and a constraint object to this layout.
431 * This method is usually only called by a {@link java.awt.Container}s add
432 * method. This method does nothing, since SpringLayout does not manage
433 * String-indexed components.
434 *
435 * @param name the name.
436 * @param c the component to be added.
437 */
438 public void addLayoutComponent(String name, Component c)
439 {
440 // do nothing here.
441 }
442
443 /**
444 * The trick to SpringLayout is that the network of Springs needs to
445 * completely created before the positioning results are generated.
446 *
447 * Using the springs directly during network creation will set their values
448 * before the network is completed, Using Deferred Springs during creation of
449 * the network allows all the edges to be connected together and the network
450 * to be created without resolving the Springs until their results need to be
451 * known, at which point the network is complete and the spring addition and
452 * and substitution calculations will work on a complete and valid network.
453 *
454 * @author Caolan McNamara (caolanm@redhat.com)
455 */
456 private static class DeferredSpring extends Spring
457 {
458 private SpringLayout sl;
459 private String edgeName;
460 private Component c;
461
462 public String toString()
463 {
464 return "DeferredSpring of edge" + edgeName + " of " + "something";
465 }
466
467 public DeferredSpring(SpringLayout s, String edge, Component component)
468 {
469 sl = s;
470 edgeName = edge;
471 c = component;
472 }
473
474 private Spring resolveSpring()
475 {
476 return sl.getConstraints(c).getConstraint(edgeName);
477 }
478
479 public int getMaximumValue()
480 {
481 return resolveSpring().getMaximumValue();
482 }
483
484 public int getMinimumValue()
485 {
486 return resolveSpring().getMinimumValue();
487 }
488
489 public int getPreferredValue()
490 {
491 return resolveSpring().getPreferredValue();
492 }
493
494 public int getValue()
495 {
496 int nRet = resolveSpring().getValue();
497 if (nRet == Spring.UNSET)
498 nRet = getPreferredValue();
499 return nRet;
500 }
501
502 public void setValue(int size)
503 {
504 resolveSpring().setValue(size);
505 }
506 }
507
508 private abstract static class DeferredDimension extends Spring
509 {
510 private int value;
511
512 public DeferredDimension()
513 {
514 value = Spring.UNSET;
515 }
516
517 public void setValue(int val)
518 {
519 value = val;
520 }
521
522 public int getValue()
523 {
524 if (value == Spring.UNSET)
525 return getPreferredValue();
526 return value;
527 }
528 }
529
530 private static class DeferredWidth extends DeferredDimension
531 {
532 private Component c;
533
534
535 public DeferredWidth(Component component)
536 {
537 c = component;
538 }
539
540 public String toString()
541 {
542 return "DeferredWidth of " + "something";
543 }
544
545 //clip max to a value we can do meaningful calculation with
546 public int getMaximumValue()
547 {
548 int widget_width = c.getMaximumSize().width;
549 return Math.min(Short.MAX_VALUE, widget_width);
550 }
551
552 public int getMinimumValue()
553 {
554 return c.getMinimumSize().width;
555 }
556
557 public int getPreferredValue()
558 {
559 return c.getPreferredSize().width;
560 }
561 }
562
563 private static class DeferredHeight extends DeferredDimension
564 {
565 private Component c;
566
567 public String toString()
568 {
569 return "DeferredHeight of " + "something";
570 }
571
572 public DeferredHeight(Component component)
573 {
574 c = component;
575 }
576
577 //clip max to a value we can do meaningful calculations with it
578 public int getMaximumValue()
579 {
580 int widget_height = c.getMaximumSize().height;
581 return Math.min(Short.MAX_VALUE, widget_height);
582 }
583
584 public int getMinimumValue()
585 {
586 return c.getMinimumSize().height;
587 }
588
589 public int getPreferredValue()
590 {
591 return c.getPreferredSize().height;
592 }
593 }
594
595 /**
596 * Returns the constraint of the edge named by <code>edgeName</code>.
597 *
598 * @param c the component from which to get the constraint.
599 * @param edgeName the name of the edge, one of {@link #EAST},
600 * {@link #WEST}, {@link #NORTH} or {@link #SOUTH}.
601 * @return the constraint of the edge <code>edgeName</code> of the
602 * component c.
603 */
604 public Spring getConstraint(String edgeName, Component c)
605 {
606 return new DeferredSpring(this, edgeName, c);
607 }
608
609 /**
610 * Returns the {@link Constraints} object associated with the specified
611 * component.
612 *
613 * @param c the component for which to determine the constraint.
614 * @return the {@link Constraints} object associated with the specified
615 * component.
616 */
617 public SpringLayout.Constraints getConstraints(Component c)
618 {
619 Constraints constraints = (Constraints) constraintsMap.get(c);
620
621 if (constraints == null)
622 {
623 constraints = new Constraints();
624
625 constraints.setWidth(new DeferredWidth(c));
626 constraints.setHeight(new DeferredHeight(c));
627 constraints.setX(Spring.constant(0));
628 constraints.setY(Spring.constant(0));
629
630 constraintsMap.put(c, constraints);
631 }
632
633 return constraints;
634 }
635
636 /**
637 * Returns the X alignment of the Container <code>p</code>.
638 *
639 * @param p
640 * the {@link java.awt.Container} for which to determine the X
641 * alignment.
642 * @return always 0.0
643 */
644 public float getLayoutAlignmentX(Container p)
645 {
646 return 0.0F;
647 }
648
649 /**
650 * Returns the Y alignment of the Container <code>p</code>.
651 *
652 * @param p the {@link java.awt.Container} for which to determine the Y
653 * alignment.
654 * @return always 0.0
655 */
656 public float getLayoutAlignmentY(Container p)
657 {
658 return 0.0F;
659 }
660
661 /**
662 * Recalculate a possibly cached layout.
663 */
664 public void invalidateLayout(Container p)
665 {
666 // nothing to do here yet
667 }
668
669 private Constraints initContainer(Container p)
670 {
671 Constraints c = getConstraints(p);
672
673 c.setX(Spring.constant(0));
674 c.setY(Spring.constant(0));
675 c.setWidth(null);
676 c.setHeight(null);
677 if (c.getEast() == null)
678 c.setEast(Spring.constant(0, 0, Integer.MAX_VALUE));
679 if (c.getSouth() == null)
680 c.setSouth(Spring.constant(0, 0, Integer.MAX_VALUE));
681
682 return c;
683 }
684
685 /**
686 * Lays out the container <code>p</code>.
687 *
688 * @param p the container to be laid out.
689 */
690 public void layoutContainer(Container p)
691 {
692 java.awt.Insets insets = p.getInsets();
693
694 Component[] components = p.getComponents();
695
696 Constraints cs = initContainer(p);
697 cs.dropCalcResult();
698
699 for (int index = 0 ; index < components.length; index++)
700 {
701 Component c = components[index];
702 getConstraints(c).dropCalcResult();
703 }
704
705 int offsetX = p.getInsets().left;
706 int offsetY = p.getInsets().right;
707
708 cs.getX().setValue(0);
709 cs.getY().setValue(0);
710 cs.getWidth().setValue(p.getWidth() - offsetX - insets.right);
711 cs.getHeight().setValue(p.getHeight() - offsetY - insets.bottom);
712
713 for (int index = 0; index < components.length; index++)
714 {
715 Component c = components[index];
716
717 Constraints constraints = getConstraints(c);
718
719 int x = constraints.getX().getValue();
720 int y = constraints.getY().getValue();
721 int width = constraints.getWidth().getValue();
722 int height = constraints.getHeight().getValue();
723
724 c.setBounds(x + offsetX, y + offsetY, width, height);
725 }
726 }
727
728 /**
729 * Calculates the maximum size of the layed out container. This
730 * respects the maximum sizes of all contained components.
731 *
732 * @param p the container to be laid out.
733 * @return the maximum size of the container.
734 */
735 public Dimension maximumLayoutSize(Container p)
736 {
737 java.awt.Insets insets = p.getInsets();
738
739 Constraints cs = initContainer(p);
740
741 int maxX = cs.getWidth().getMaximumValue() + insets.left + insets.right;
742 int maxY = cs.getHeight().getMaximumValue() + insets.top + insets.bottom;
743
744 return new Dimension(maxX, maxY);
745 }
746
747
748 /**
749 * Calculates the minimum size of the layed out container. This
750 * respects the minimum sizes of all contained components.
751 *
752 * @param p the container to be laid out.
753 * @return the minimum size of the container.
754 */
755 public Dimension minimumLayoutSize(Container p)
756 {
757 java.awt.Insets insets = p.getInsets();
758
759 Constraints cs = initContainer(p);
760
761 int maxX = cs.getWidth().getMinimumValue() + insets.left + insets.right;
762 int maxY = cs.getHeight().getMinimumValue() + insets.top + insets.bottom;
763
764 return new Dimension(maxX, maxY);
765 }
766
767 /**
768 * Calculates the preferred size of the layed out container. This
769 * respects the preferred sizes of all contained components.
770 *
771 * @param p the container to be laid out.
772 * @return the preferred size of the container.
773 */
774 public Dimension preferredLayoutSize(Container p)
775 {
776 java.awt.Insets insets = p.getInsets();
777
778 Constraints cs = initContainer(p);
779
780 int maxX = cs.getWidth().getPreferredValue() + insets.left + insets.right;
781 int maxY = cs.getHeight().getPreferredValue() + insets.top + insets.bottom;
782
783 return new Dimension(maxX, maxY);
784 }
785
786 /**
787 * Attaches the edge <code>e1</code> of component <code>c1</code> to
788 * the edge <code>e2</code> of component <code>c2</code> width the
789 * fixed strut <code>pad</code>.
790 *
791 * @param e1 the edge of component 1.
792 * @param c1 the component 1.
793 * @param pad the space between the components in pixels.
794 * @param e2 the edge of component 2.
795 * @param c2 the component 2.
796 */
797 public void putConstraint(String e1, Component c1, int pad, String e2,
798 Component c2)
799 {
800 putConstraint(e1, c1, Spring.constant(pad), e2, c2);
801 }
802
803 /**
804 * Attaches the edge <code>e1</code> of component <code>c1</code> to
805 * the edge <code>e2</code> of component <code>c2</code> width the
806 * {@link Spring} <code>s</code>.
807 *
808 * @param e1 the edge of component 1.
809 * @param c1 the component 1.
810 * @param s the space between the components as a {@link Spring} object.
811 * @param e2 the edge of component 2.
812 * @param c2 the component 2.
813 */
814 public void putConstraint(String e1, Component c1, Spring s, String e2,
815 Component c2)
816 {
817 Constraints constraints1 = getConstraints(c1);
818
819 Spring otherEdge = getConstraint(e2, c2);
820 constraints1.setConstraint(e1, Spring.sum(s, otherEdge));
821
822 }
823
824 /**
825 * Removes a layout component.
826 * @param c the layout component to remove.
827 */
828 public void removeLayoutComponent(Component c)
829 {
830 // do nothing here
831 }
832 }