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    }