001/* SpringLayout.java -- 
002   Copyright (C) 2004, 2006, Free Software Foundation, Inc.
003
004This file is part of GNU Classpath.
005
006GNU Classpath is free software; you can redistribute it and/or modify
007it under the terms of the GNU General Public License as published by
008the Free Software Foundation; either version 2, or (at your option)
009any later version.
010
011GNU Classpath is distributed in the hope that it will be useful, but
012WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014General Public License for more details.
015
016You should have received a copy of the GNU General Public License
017along with GNU Classpath; see the file COPYING.  If not, write to the
018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
01902110-1301 USA.
020
021Linking this library statically or dynamically with other modules is
022making a combined work based on this library.  Thus, the terms and
023conditions of the GNU General Public License cover the whole
024combination.
025
026As a special exception, the copyright holders of this library give you
027permission to link this library with independent modules to produce an
028executable, regardless of the license terms of these independent
029modules, and to copy and distribute the resulting executable under
030terms of your choice, provided that you also meet, for each linked
031independent module, the terms and conditions of the license of that
032module.  An independent module is a module which is not derived from
033or based on this library.  If you modify this library, you may extend
034this exception to your version of the library, but you are not
035obligated to do so.  If you do not wish to do so, delete this
036exception statement from your version. */
037
038
039package javax.swing;
040
041import java.awt.Component;
042import java.awt.Container;
043import java.awt.Dimension;
044import java.awt.LayoutManager2;
045import java.util.HashMap;
046import 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 */
062public 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}