View Javadoc

1   package org.catacomb.druid.swing.split;
2   
3   import java.awt.*;
4   import java.util.Hashtable;
5   
6   /**
7    * SplitterLayout is a layout manager that will layout a container holding other
8    * components and SplitterBars.
9    *
10   * <p>
11   * Each component added to a container to be laid out using SplitterLayout must
12   * provide a String containing a "weight" for the component. This weight will be
13   * used to determine the initial spacing of all components being laid out. The
14   * weight numbers are arbitrary integers. The amount of space initially
15   * allocated for a component is
16   *
17   * <pre>
18   * (wc / wt) * (size - insets - splitterSize)
19   * </pre>
20   *
21   * <p>
22   * where
23   * <dl>
24   * <dt>wc
25   * <dd>the weight number for the component
26   * <dt>wt
27   * <dd>the total weight of all visible components in the container
28   * <dt>size
29   * <dd>the space free to display the components
30   * <dt>insets
31   * <dd>space used by insets in the container
32   * <dt>splitterSize
33   * <dd>amount of space needed to display SplitterBars
34   * </dl>
35   *
36   * <p>
37   * If the container being laid out holds no SplitterBars, SplitterLayout acts
38   * like a relational-weight layout manager. All components are always laid out
39   * based on their proportionate weights.
40   *
41   * <p>
42   * If the container being laid out holds some SplitterBars, SplitterLayout will
43   * initially size all non JSplitterBar components based on their weights. Any
44   * succesive layouts are computed strictly on the locations of the SplitterBars.
45   *
46   * <p>
47   * SplitterLayout can be oriented Horizontally or Vertically. Any SpliterBars
48   * placed in the container will automatically be oriented.
49   *
50   * <p>
51   * If a JSplitterBar has been modified (adding components to it) you will need
52   * to add JSplitterSpace components to it. See JSplitterBar for more details.
53   *
54   * <p>
55   * <b>Known Problems</b>:
56   * <ul>
57   * <li>If there are any SplitterBars contained in the container, it is best to
58   * have them between <u>every</u> non-JSplitterBar. Otherwise, once
59   * SplitterBars are moved, some components will use their proportional size
60   * while others will use the JSplitterBar positions. (Non-Splitterbars will
61   * check the next component to see if it's a JSplitterBar. If it's not, it uses
62   * its proportional size.) This may eventually be changed...
63   * <li>Results of adding new SplitterBars to an existing (and user- interacted)
64   * SplitterLayout-laid container might be a bit unpredictable. The safest way to
65   * ensure the container is laid out correctly would be to explicitly set all
66   * pre-existing JSplitterBar positions to (0,0). This will cause the relational
67   * layout algorithm to take effect.
68   * </ul>
69   *
70   *
71   */
72  @SuppressWarnings("all")
73  public class SplitterLayout implements LayoutManager2, java.io.Serializable {
74  
75      private static final long serialVersionUID = 1L;
76  
77      public static final int VERTICAL   = 0;
78      public static final int HORIZONTAL = 1;
79  
80      protected static DSplitterBar dragee = null;
81  
82      private int lastW = -1;
83      private int lastH = -1;
84      private boolean newComponentAdded = false;
85      private Hashtable<Component, Object> relations = null;
86  
87  
88      private int fieldOrientation = VERTICAL;
89  
90  
91  
92      public SplitterLayout() {
93          this(VERTICAL);
94      }
95  
96      public SplitterLayout(int orientation) {
97          setOrientation(orientation);
98          relations = new Hashtable<Component, Object>();
99      }
100     /**
101     * Adds a component w/ constraints to the layout. This should only be called
102     * by java.awt.Container's add method.
103     */
104     public final void addLayoutComponent(Component comp, Object cin) {
105         Object constraints = cin;
106         if (constraints == null) constraints = "1";
107         if (constraints instanceof Integer) {
108             relations.put(comp, constraints);
109             newComponentAdded = true;
110         }
111         else
112             addLayoutComponent((String)constraints, comp);
113     }
114     /**
115     * Adds a component w/ a String constraint to the layout. This should only be
116     * called by java.awt.Container's add method.
117     */
118     public final void addLayoutComponent(String name, Component comp) {
119         newComponentAdded = true;
120         if (comp instanceof DSplitterBar) {
121             ((DSplitterBar)comp).setOrientation(getOrientation());
122         }
123         else {
124             if (name == null) name = "1";
125             try {
126                 relations.put(comp, Integer.decode(name));
127             }
128             catch (NumberFormatException e) {
129                 relations.put(comp, new Integer(1));
130             }
131         }
132     }
133     public final Dimension checkLayoutSize(Container target, boolean getPrefSize) {
134         Dimension dim = new Dimension(0, 0);
135         Component c[] = target.getComponents();
136 
137         Dimension d;
138         for (int i = 0; i < c.length; i++)
139             if (c[i].isVisible()) {
140                 if (getPrefSize || (c[i] instanceof DSplitterBar))
141                     d = c[i].getPreferredSize();
142                 else
143                     d = c[i].getMinimumSize();
144                 if (getOrientation() == VERTICAL) {
145                     dim.width = Math.max(d.width, dim.width);
146                     dim.height += d.height;
147                 }
148                 else {
149                     dim.height = Math.max(d.height, dim.height);
150                     dim.width += d.width;
151                 }
152             }
153 
154         Insets insets = target.getInsets();
155         dim.width += insets.left + insets.right;
156         dim.height += insets.top + insets.bottom;
157 
158         return dim;
159     }
160     /** Tells the caller that we prefer to be centered */
161     public final float getLayoutAlignmentX(Container parent) {
162         return 0.5f;
163     }
164     /** Tells the caller that we prefer to be centered */
165     public final float getLayoutAlignmentY(Container parent) {
166         return 0.5f;
167     }
168 
169     public int getOrientation() {
170         /* Returns the orientation property value. */
171         return fieldOrientation;
172     }
173     /** Does not have any effect (overridden to null the effect) */
174     public final void  invalidateLayout(Container target)     {}
175 
176     public final void layoutContainer(Container target) {
177         Insets insets = target.getInsets();
178         Dimension dim = target.getSize();
179         int top = insets.top;
180         int bottom = dim.height - insets.bottom;
181         int left = insets.left;
182         int right = dim.width - insets.right;
183 
184         boolean reScaleW = false, reScaleH=false;
185         float scaleW = 0, scaleH = 0;
186 
187         // if the width/height has changed, scale the splitter bar positions
188         if (lastW == -1) {  // save it the first time
189             lastW = dim.width;
190             lastH = dim.height;
191         }
192         else {
193             if (lastW != dim.width) {
194                 reScaleW = true;
195                 scaleW = (float)dim.width/(float)lastW;
196                 lastW = dim.width;
197             }
198             if (lastH != dim.height) {
199                 reScaleH = true;
200                 scaleH = (float)dim.height/(float)lastH;
201                 lastH = dim.height;
202             }
203         }
204 
205         dim.width = right - left;
206         dim.height = bottom - top;
207 
208         // find out the totals we need to deal with...
209 
210         int relativeSize = 0;
211         int numRelatives = 0;
212 
213         Component c[] = target.getComponents();
214         Object pSize[] = new Object[c.length];
215         int orientation = getOrientation();
216         for (int i = 0; i < c.length; i++) {
217             if (c[i].isVisible())
218                 if (c[i] instanceof DSplitterBar) {
219                     ((DSplitterBar)c[i]).setOrientation(orientation);
220                     pSize[i] = c[i].getPreferredSize();
221                     if (orientation == VERTICAL) {
222                         dim.height -= ((Dimension)pSize[i]).height;
223                         if (reScaleH) {
224                             Point p = c[i].getLocation();
225                             c[i].setLocation(p.x,(int)((p.y)*scaleH)); // dims set
226                             // later
227                         }
228                     }
229                     else {
230                         dim.width  -= ((Dimension)pSize[i]).width;
231                         if (reScaleW) {
232                             Point p = c[i].getLocation();
233                             c[i].setLocation((int)((p.x)*scaleW),p.y); // dims set
234                             // later
235                         }
236                     }
237                 }
238                 else {
239                     pSize[i] = relations.get(c[i]);
240                     relativeSize += ((Integer)pSize[i]).intValue();
241                     numRelatives++;
242                 }
243         }
244 
245 
246         // for each component being laid out, set its size
247         for (int i = 0; i < c.length; i++)
248             if (c[i].isVisible()) {
249                 Rectangle r = c[i].getBounds();
250                 if (c[i] instanceof DSplitterBar)
251                     if (orientation == VERTICAL) {
252                         if (r.x != left || r.y != top || r.width != dim.width || r.height != ((Dimension)pSize[i]).height)
253                             c[i].setBounds(left,top,dim.width,((Dimension)pSize[i]).height);
254                         top += ((Dimension)pSize[i]).height;
255                     }
256                     else {
257                         if (r.x != left || r.y != top || r.height != dim.height || r.width != ((Dimension)pSize[i]).width)
258                             c[i].setBounds(left,top,((Dimension)pSize[i]).width,dim.height);
259                         left += ((Dimension)pSize[i]).width;
260                     }
261                 else {
262                     if (i == (c.length-1)) {
263                         if (orientation == VERTICAL) {
264                             if (r.x != left || r.y != top || r.width != dim.width || r.height != (bottom-top))
265                                 c[i].setBounds(left,top,dim.width,bottom-top);
266                         }
267                         else {
268                             if (r.x != left || r.y != top || r.width != (right-left) || r.height != dim.height)
269                                 c[i].setBounds(left,top,right-left,dim.height);
270                         }
271                     }
272                     else {
273                         // get pos of splitter bar
274                         Point p = c[i+1].getLocation();
275                         if (!newComponentAdded &&
276                                 (c[i+1] instanceof DSplitterBar) && (p.x != 0 || p.y != 0)) {
277                             if (orientation == VERTICAL) {
278                                 if (r.x != left || r.y != top || r.width != dim.width || r.height != (p.y-top))
279                                     c[i].setBounds(left,top,dim.width,p.y-top);
280                                 top = p.y;
281                             }
282                             else {
283                                 if (r.x != left || r.y != top || r.width != (p.x-left) || r.height != dim.height)
284                                     c[i].setBounds(left,top,p.x-left,dim.height);
285                                 left = p.x;
286                             }
287                         }
288                         else {
289                             int rel = ((Integer)pSize[i]).intValue();
290                             float ratio = ((float)rel/(float)relativeSize);
291                             if (orientation == VERTICAL) {
292                                 ratio *= dim.height;
293                                 if (r.x != left || r.y != top || r.width != dim.width || r.height != (int)ratio)
294                                     c[i].setBounds(left,top,dim.width,(int)ratio);
295                                 top += (int)ratio;
296                             }
297                             else {
298                                 ratio *= dim.width;
299                                 if (r.x != left || r.y != top || r.width != (int)ratio || r.height != dim.height)
300                                     c[i].setBounds(left,top,(int)ratio,dim.height);
301                                 left += (int)ratio;
302                             }
303                         }
304                     }
305                 }
306             }
307         newComponentAdded = false;
308     }
309 
310     public final Dimension maximumLayoutSize(Container target) {
311         return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
312     }
313 
314 // public final Dimension minimumLayoutSize(Container target) {return
315 // checkLayoutSize(target, false);}
316     public final Dimension minimumLayoutSize(Container target)   {
317         return checkLayoutSize(target, true);
318     }
319     // TEMP -- CHECK TO SEE HOW minsize==prefsize seems
320 
321 
322     public final Dimension preferredLayoutSize(Container target) {
323         return checkLayoutSize(target, true);
324     }
325 
326     public final void removeLayoutComponent(Component comp) {
327         relations.remove(comp);
328         newComponentAdded = true; // so layout gets re-adjusted
329     }
330 
331     public void setOrientation(int orientation) {
332         fieldOrientation = orientation;
333         return;
334     }
335     public void swapOrientation(Container container) {
336         setOrientation((getOrientation() == HORIZONTAL)?VERTICAL:HORIZONTAL);
337         Component comps[] = container.getComponents();
338         for (int i = container.getComponentCount()-1; i>-1; i--) {
339             if (comps[i] instanceof DSplitterBar)
340                 ((DSplitterBar)comps[i]).swapOrientation();
341             comps[i].invalidate();
342         }
343         newComponentAdded = true; // to force re-position of splitter bars
344         container.validate();
345     }
346     /** Returns a String representation of the Layout */
347     public final String toString() {
348         if (getOrientation() == VERTICAL)
349             return getClass().getName() + "[orientation=VERTICAL]";
350         else
351             return getClass().getName() + "[orientation=HORIZONTAL]";
352     }
353 }