001 /* CardLayout.java -- Card-based layout engine
002 Copyright (C) 1999, 2000, 2002, 2003, 2004 Free Software Foundation
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 java.awt;
040
041 import java.io.Serializable;
042 import java.util.Enumeration;
043 import java.util.Hashtable;
044
045 /**
046 * This class implements a card-based layout scheme. Each included
047 * component is treated as a card. Only one card can be shown at a
048 * time. This class includes methods for changing which card is
049 * shown.
050 *
051 * @author Tom Tromey (tromey@redhat.com)
052 * @author Aaron M. Renn (arenn@urbanophile.com)
053 */
054 public class CardLayout implements LayoutManager2, Serializable
055 {
056 private static final long serialVersionUID = -4328196481005934313L;
057
058 /**
059 * Initializes a new instance of <code>CardLayout</code> with horizontal
060 * and vertical gaps of 0.
061 */
062 public CardLayout ()
063 {
064 this (0, 0);
065 }
066
067 /**
068 * Create a new <code>CardLayout</code> object with the specified
069 * horizontal and vertical gaps.
070 *
071 * @param hgap The horizontal gap
072 * @param vgap The vertical gap
073 */
074 public CardLayout (int hgap, int vgap)
075 {
076 this.hgap = hgap;
077 this.vgap = vgap;
078 this.tab = new Hashtable ();
079 }
080
081 /**
082 * Add a new component to the layout. The constraint must be a
083 * string which is used to name the component. This string can
084 * later be used to refer to the particular component.
085 *
086 * @param comp The component to add
087 * @param constraints The name by which the component can later be called
088 *
089 * @exception IllegalArgumentException If `constraints' is not a
090 * <code>String</code>
091 */
092 public void addLayoutComponent (Component comp, Object constraints)
093 {
094 if (! (constraints instanceof String))
095 throw new IllegalArgumentException ("Object " + constraints
096 + " is not a string");
097 addLayoutComponent ((String) constraints, comp);
098 }
099
100 /**
101 * Add a new component to the layout. The name can be used later
102 * to refer to the component.
103 *
104 * @param name The name by which the component can later be called
105 * @param comp The component to add
106 *
107 * @deprecated This method is deprecated in favor of
108 * <code>addLayoutComponent(Component, Object)</code>.
109 */
110 public void addLayoutComponent (String name, Component comp)
111 {
112 tab.put (name, comp);
113 // First component added is the default component.
114 comp.setVisible(tab.size() == 1);
115 }
116
117 /**
118 * Cause the first component in the container to be displayed.
119 *
120 * @param parent The parent container, not <code>null</code>.
121 */
122 public void first (Container parent)
123 {
124 gotoComponent (parent, FIRST);
125 }
126
127 /**
128 * Return this layout manager's horizontal gap.
129 *
130 * @return the horizontal gap
131 */
132 public int getHgap ()
133 {
134 return hgap;
135 }
136
137 /**
138 * Return this layout manager's x alignment. This method always
139 * returns Component.CENTER_ALIGNMENT.
140 *
141 * @param parent Container using this layout manager instance
142 *
143 * @return the x-axis alignment
144 */
145 public float getLayoutAlignmentX (Container parent)
146 {
147 return Component.CENTER_ALIGNMENT;
148 }
149
150 /**
151 * Returns this layout manager's y alignment. This method always
152 * returns Component.CENTER_ALIGNMENT.
153 *
154 * @param parent Container using this layout manager instance
155 *
156 * @return the y-axis alignment
157 */
158 public float getLayoutAlignmentY (Container parent)
159 {
160 return Component.CENTER_ALIGNMENT;
161 }
162
163 /**
164 * Return this layout manager's vertical gap.
165 *
166 * @return the vertical gap
167 */
168 public int getVgap ()
169 {
170 return vgap;
171 }
172
173 /**
174 * Invalidate this layout manager's state.
175 */
176 public void invalidateLayout (Container target)
177 {
178 // Do nothing.
179 }
180
181 /**
182 * Cause the last component in the container to be displayed.
183 *
184 * @param parent The parent container, not <code>null</code>.
185 */
186 public void last (Container parent)
187 {
188 gotoComponent (parent, LAST);
189 }
190
191 /**
192 * Lays out the container. This is done by resizing the child components
193 * to be the same size as the parent, less insets and gaps.
194 *
195 * @param parent The parent container.
196 */
197 public void layoutContainer (Container parent)
198 {
199 synchronized (parent.getTreeLock ())
200 {
201 int width = parent.width;
202 int height = parent.height;
203
204 Insets ins = parent.getInsets ();
205
206 int num = parent.ncomponents;
207 Component[] comps = parent.component;
208
209 int x = ins.left + hgap;
210 int y = ins.top + vgap;
211 width = width - 2 * hgap - ins.left - ins.right;
212 height = height - 2 * vgap - ins.top - ins.bottom;
213
214 for (int i = 0; i < num; ++i)
215 comps[i].setBounds (x, y, width, height);
216 }
217 }
218
219 /**
220 * Get the maximum layout size of the container.
221 *
222 * @param target The parent container
223 *
224 * @return the maximum layout size
225 */
226 public Dimension maximumLayoutSize (Container target)
227 {
228 if (target == null || target.ncomponents == 0)
229 return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
230 // The JCL says that this returns Integer.MAX_VALUE for both
231 // dimensions. But that just seems wrong to me.
232 return getSize (target, MAX);
233 }
234
235 /**
236 * Get the minimum layout size of the container.
237 *
238 * @param target The parent container
239 *
240 * @return the minimum layout size
241 */
242 public Dimension minimumLayoutSize (Container target)
243 {
244 return getSize (target, MIN);
245 }
246
247 /**
248 * Cause the next component in the container to be displayed. If
249 * this current card is the last one in the deck, the first
250 * component is displayed.
251 *
252 * @param parent The parent container, not <code>null</code>.
253 */
254 public void next (Container parent)
255 {
256 gotoComponent (parent, NEXT);
257 }
258
259 /**
260 * Get the preferred layout size of the container.
261 *
262 * @param parent The parent container
263 *
264 * @return the preferred layout size
265 */
266 public Dimension preferredLayoutSize (Container parent)
267 {
268 return getSize (parent, PREF);
269 }
270
271 /**
272 * Cause the previous component in the container to be displayed.
273 * If this current card is the first one in the deck, the last
274 * component is displayed.
275 *
276 * @param parent The parent container, not <code>null</code>.
277 */
278 public void previous (Container parent)
279 {
280 gotoComponent (parent, PREV);
281 }
282
283 /**
284 * Remove the indicated component from this layout manager.
285 *
286 * @param comp The component to remove
287 */
288 public void removeLayoutComponent (Component comp)
289 {
290 Enumeration e = tab.keys ();
291 while (e.hasMoreElements ())
292 {
293 Object key = e.nextElement ();
294 if (tab.get (key) == comp)
295 {
296 tab.remove (key);
297 Container parent = comp.getParent();
298 next(parent);
299 break;
300 }
301 }
302 }
303
304 /**
305 * Set this layout manager's horizontal gap.
306 *
307 * @param hgap The new gap
308 */
309 public void setHgap (int hgap)
310 {
311 this.hgap = hgap;
312 }
313
314 /**
315 * Set this layout manager's vertical gap.
316 *
317 * @param vgap The new gap
318 */
319 public void setVgap (int vgap)
320 {
321 this.vgap = vgap;
322 }
323
324 /**
325 * Cause the named component to be shown. If the component name is
326 * unknown or <code>null</code>, this method does nothing.
327 *
328 * @param parent The parent container, not <code>null</code>.
329 * @param name The name of the component to show
330 */
331 public void show (Container parent, String name)
332 {
333 if (name == null)
334 return;
335
336 if (parent.getLayout() != this)
337 throw new IllegalArgumentException("parent's layout is not this CardLayout");
338
339 Object target = tab.get (name);
340 if (target != null)
341 {
342 int num = parent.ncomponents;
343 // This is more efficient than calling getComponents().
344 Component[] comps = parent.component;
345 for (int i = 0; i < num; ++i)
346 {
347 if (comps[i].isVisible())
348 {
349 if (target == comps[i])
350 return;
351 comps[i].setVisible (false);
352 }
353 }
354 ((Component) target).setVisible (true);
355 parent.validate();
356 }
357 }
358
359 /**
360 * Returns a string representation of this layout manager.
361 *
362 * @return A string representation of this object.
363 */
364 public String toString ()
365 {
366 return getClass ().getName () + "[hgap=" + hgap + ",vgap=" + vgap + "]";
367 }
368
369 /**
370 * This implements first(), last(), next(), and previous().
371 *
372 * @param parent The parent container
373 * @param what The type of goto: FIRST, LAST, NEXT or PREV
374 *
375 * @throws IllegalArgumentException if parent has not this
376 * CardLayout set as its layout.
377 */
378 private void gotoComponent (Container parent, int what)
379 {
380 if (parent.getLayout() != this)
381 throw new IllegalArgumentException("parent's layout is not this CardLayout");
382
383 synchronized (parent.getTreeLock ())
384 {
385 int num = parent.ncomponents;
386 // This is more efficient than calling getComponents().
387 Component[] comps = parent.component;
388
389 if (num == 1)
390 {
391 comps[0].setVisible(true);
392 return;
393 }
394
395 int choice = -1;
396
397 if (what == FIRST)
398 choice = 0;
399 else if (what == LAST)
400 choice = num - 1;
401
402 for (int i = 0; i < num; ++i)
403 {
404 if (comps[i].isVisible ())
405 {
406 if (choice == i)
407 {
408 // Do nothing if we're already looking at the right
409 // component.
410 return;
411 }
412 else if (what == PREV)
413 {
414 choice = i - 1;
415 if (choice < 0)
416 choice = num - 1;
417 }
418 else if (what == NEXT)
419 {
420 choice = i + 1;
421 if (choice == num)
422 choice = 0;
423 }
424 comps[i].setVisible (false);
425
426 if (choice >= 0)
427 break;
428 } else
429 {
430 comps[i].setVisible(true);
431 }
432 }
433
434 if (choice >= 0 && choice < num)
435 comps[choice].setVisible (true);
436 }
437 }
438
439 // Compute the size according to WHAT.
440 private Dimension getSize (Container parent, int what)
441 {
442 synchronized (parent.getTreeLock ())
443 {
444 int w = 0, h = 0, num = parent.ncomponents;
445 Component[] comps = parent.component;
446
447 for (int i = 0; i < num; ++i)
448 {
449 Dimension d;
450
451 if (what == MIN)
452 d = comps[i].getMinimumSize ();
453 else if (what == MAX)
454 d = comps[i].getMaximumSize ();
455 else
456 d = comps[i].getPreferredSize ();
457
458 w = Math.max (d.width, w);
459 h = Math.max (d.height, h);
460 }
461
462 Insets i = parent.getInsets ();
463 w += 2 * hgap + i.right + i.left;
464 h += 2 * vgap + i.bottom + i.top;
465
466 // Handle overflow.
467 if (w < 0)
468 w = Integer.MAX_VALUE;
469 if (h < 0)
470 h = Integer.MAX_VALUE;
471
472 return new Dimension (w, h);
473 }
474 }
475
476 /**
477 * @serial Horizontal gap value.
478 */
479 private int hgap;
480
481 /**
482 * @serial Vertical gap value.
483 */
484 private int vgap;
485
486 /**
487 * @serial Table of named components.
488 */
489 private Hashtable tab;
490
491 // These constants are used by the private gotoComponent method.
492 private static final int FIRST = 0;
493 private static final int LAST = 1;
494 private static final int NEXT = 2;
495 private static final int PREV = 3;
496
497 // These constants are used by the private getSize method.
498 private static final int MIN = 0;
499 private static final int MAX = 1;
500 private static final int PREF = 2;
501 }