PageRenderTime 45ms CodeModel.GetById 16ms app.highlight 24ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/gui/VariableGridLayout.java

#
Java | 643 lines | 443 code | 44 blank | 156 comment | 77 complexity | 216a6b6f57dbe6e8e902866f784361c4 MD5 | raw file
  1/*
  2 * VariableGridLayout.java - a grid layout manager with variable cell sizes
  3 * :tabSize=8:indentSize=8:noTabs=false:
  4 *
  5 * Originally written by Dirk Moebius for the jEdit project. This work has been
  6 * placed into the public domain. You may use this work in any way and for any
  7 * purpose you wish.
  8 *
  9 * THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND, NOT EVEN THE
 10 * IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR OF THIS SOFTWARE, ASSUMES
 11 * _NO_ RESPONSIBILITY FOR ANY CONSEQUENCE RESULTING FROM THE USE, MODIFICATION,
 12 * OR REDISTRIBUTION OF THIS SOFTWARE.
 13 */
 14
 15package org.gjt.sp.jedit.gui;
 16
 17import java.awt.Component;
 18import java.awt.Container;
 19import java.awt.Dimension;
 20import java.awt.Insets;
 21import java.awt.LayoutManager2;
 22
 23import java.util.Arrays;
 24
 25/**
 26 * The <code>VariableGridLayout</code> class is a layout manager
 27 * that lays out a container's components in a rectangular grid
 28 * with variable cell sizes.<p>
 29 *
 30 * The container is divided into rectangles, and one component is placed
 31 * in each rectangle. Each row is as large as the largest component in
 32 * that row, and each column is as wide as the widest component in
 33 * that column.<p>
 34 *
 35 * This behavior is basically the same as in
 36 * <code>java.awt.GridLayout</code>, but with different row heights and
 37 * column widths for each row/column.<p>
 38 *
 39 * For example, the following is an applet that lays out six buttons
 40 * into three rows and two columns:<p>
 41 *
 42 * <blockquote><pre>
 43 * import java.awt.*;
 44 * import java.applet.Applet;
 45 * public class ButtonGrid extends Applet {
 46 *     public void init() {
 47 *         setLayout(new VariableGridLayout(VariableGridLayout.FIXED_NUM_COLUMNS, 2));
 48 *         add(new Button("1"));
 49 *         add(new Button("2"));
 50 *         add(new Button("3"));
 51 *         add(new Button("4"));
 52 *         add(new Button("5"));
 53 *         add(new Button("6"));
 54 *     }
 55 * }
 56 * </pre></blockquote><p>
 57 *
 58 * <b>Programmer's remark:</b> VariableGridLayout could be faster, if it would
 59 * reside in the package java.awt, because then it could access some
 60 * package private fields of <code>Container</code> or
 61 * <code>Component</code>. Instead, it has to call
 62 * <code>Component.getSize()</code>,
 63 * which allocates memory on the heap.<p>
 64 *
 65 * <b>Todo:</b>
 66 * <ul>
 67 * <li>Ability to span components over more than one cell horizontally and vertically.
 68 * </ul>
 69 *
 70 * @author Dirk Moebius, Bj??rn "Vampire" Kautler
 71 * @version 1.5
 72 * @see java.awt.GridLayout
 73 */
 74public class VariableGridLayout implements LayoutManager2, java.io.Serializable
 75{
 76	public static final int FIXED_NUM_ROWS = 1;
 77	public static final int FIXED_NUM_COLUMNS = 2;
 78
 79	private static enum LayoutSize { MINIMUM, MAXIMUM, PREFERRED }
 80
 81	/**
 82	 * Creates a variable grid layout manager with the specified mode,
 83	 * size, horizontal and vertical gap, eventually taking minimum and maximum
 84	 * sizes into account when distributing free space, depending on takeSizesIntoAccount
 85	 * and the specified distance to the borders.
 86	 *
 87	 * @param mode The mode in which to operate. Either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS
 88	 * @param size The amount of rows for mode FIXED_NUM_ROWS or the amount of columns for mode FIXED_NUM_COLUMNS (>0)
 89	 * @param hgap The horizontal space between cells (>=0)
 90	 * @param vgap The vertical space between cells (>=0)
 91	 * @param takeSizesIntoAccount Whether to take minimum and maximum sizes into account when distributing free space
 92	 * @param distanceToBorders The distances to the borders
 93	 * @throws IllegalArgumentException if mode is not either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS or size is <= 0 or hgap or vgap is < 0
 94	 */
 95	public VariableGridLayout(int mode, int size, int hgap, int vgap, boolean takeSizesIntoAccount, Insets distanceToBorders)
 96	{
 97		if (mode != FIXED_NUM_ROWS && mode != FIXED_NUM_COLUMNS)
 98		{
 99			throw new IllegalArgumentException("illegal mode; value is " + mode);
100		}
101		if (size <= 0)
102		{
103			throw new IllegalArgumentException("size cannot be zero or less; value is " + size);
104		}
105		if (hgap < 0)
106		{
107			throw new IllegalArgumentException("hgap cannot be negative; value is " + hgap);
108		}
109		if (vgap < 0)
110		{
111			throw new IllegalArgumentException("vgap cannot be negative; value is " + vgap);
112		}
113		this.mode = mode;
114		this.size = size;
115		this.hgap = hgap;
116		this.vgap = vgap;
117		this.takeSizesIntoAccount = takeSizesIntoAccount;
118		this.distanceToBorders = (Insets)distanceToBorders.clone();
119	}
120
121	/**
122	 * Creates a variable grid layout manager with the specified mode,
123	 * size, horizontal and vertical gap, eventually taking minimum and maximum
124	 * sizes into account when distributing free space, depending on takeSizesIntoAccount
125	 * and zero distance to borders.
126	 *
127	 * @param mode The mode in which to operate. Either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS
128	 * @param size The amount of rows for mode FIXED_NUM_ROWS or the amount of columns for mode FIXED_NUM_COLUMNS (>0)
129	 * @param hgap The horizontal space between cells (>=0)
130	 * @param vgap The vertical space between cells (>=0)
131	 * @param takeSizesIntoAccount Whether to take minimum and maximum sizes into account when distributing free space
132	 * @throws IllegalArgumentException if mode is not either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS or size is <= 0 or hgap or vgap is < 0
133	 */
134	public VariableGridLayout(int mode, int size, int hgap, int vgap, boolean takeSizesIntoAccount)
135	{
136		this(mode, size, hgap, vgap, takeSizesIntoAccount, new Insets(0,0,0,0));
137	}
138
139	/**
140	 * Creates a variable grid layout manager with the specified mode,
141	 * size, horizontal and vertical gap, and zero distance to borders.
142	 * The minimum and maximum Component sizes are not taken into account
143	 * when distributing free space.
144	 *
145	 * @param mode The mode in which to operate. Either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS
146	 * @param size The amount of rows for mode FIXED_NUM_ROWS or the amount of columns for mode FIXED_NUM_COLUMNS
147	 * @param hgap The horizontal space between cells
148	 * @param vgap The vertical space between cells
149	 * @throws IllegalArgumentException if mode is not either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS or size is <= 0 or hgap or vgap is < 0
150	 */
151	public VariableGridLayout(int mode, int size, int hgap, int vgap)
152	{
153		this(mode, size, hgap, vgap, false, new Insets(0,0,0,0));
154	}
155
156	/**
157	 * Creates a variable grid layout manager with the specified mode
158	 * and size, zero horizontal and vertical gap, and zero distance to borders. 
159	 * Does not take minimum and maximum Component sizes into account when distributing
160	 * free space.
161	 *
162	 * @param mode The mode in which to operate. Either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS
163	 * @param size The amount of rows for mode FIXED_NUM_ROWS or the amount of columns for mode FIXED_NUM_COLUMNS
164	 * @throws IllegalArgumentException if mode is not either FIXED_NUM_ROWS or FIXED_NUM_COLUMNS or size is <= 0
165	 */
166	public VariableGridLayout(int mode, int size)
167	{
168		this(mode, size, 0, 0, false, new Insets(0,0,0,0));
169	}
170
171	/**
172	 * Creates a variable grid layout manager with mode FIXED_NUM_ROWS,
173	 * number of rows == 1, zero horizontal and vertical gap, and zero distance to borders.
174	 * Does not take minimum and maximum Component sizes into account when
175	 * distributing free space.
176	 */
177	public VariableGridLayout()
178	{
179		this(FIXED_NUM_ROWS, 1, 0, 0, false, new Insets(0,0,0,0));
180	}
181
182	/**
183	 * Not used in this class.
184	 */
185	public void addLayoutComponent(String name, Component component)
186	{
187	}
188
189	/**
190	 * Not used in this class.
191	 */
192	public void addLayoutComponent(Component component, Object constraints)
193	{
194	}
195
196	/**
197	 * Not used in this class.
198	 */
199	public void removeLayoutComponent(Component component)
200	{
201	}
202
203	/**
204	 * Always returns 0.5.
205	 */
206	public float getLayoutAlignmentX(Container container)
207	{
208		return 0.5f;
209	}
210
211	/**
212	 * Always returns 0.5.
213	 */
214	public float getLayoutAlignmentY(Container container)
215	{
216		return 0.5f;
217	}
218
219	public Dimension preferredLayoutSize(Container parent)
220	{
221		return getLayoutSize(parent,LayoutSize.PREFERRED);
222	}
223
224	public Dimension minimumLayoutSize(Container parent)
225	{
226		return getLayoutSize(parent,LayoutSize.MINIMUM);
227	}
228
229	public Dimension maximumLayoutSize(Container parent)
230	{
231		return getLayoutSize(parent,LayoutSize.MAXIMUM);
232	}
233
234	public void layoutContainer(Container parent)
235	{
236		synchronized (parent.getTreeLock())
237		{
238			update(parent);
239
240			int ncomponents = parent.getComponentCount();
241
242			if (ncomponents == 0)
243			{
244				return;
245			}
246
247			// Pass 1: compute minimum, preferred and maximum row heights / column widths
248			int total_height = 0;
249			Arrays.fill(row_heights,0);
250			Arrays.fill(col_widths,0);
251			if (takeSizesIntoAccount)
252			{
253				Arrays.fill(minimum_row_heights,0);
254				Arrays.fill(minimum_col_widths,0);
255				Arrays.fill(maximum_row_heights,Integer.MAX_VALUE);
256				Arrays.fill(maximum_col_widths,Integer.MAX_VALUE);
257			}
258			for (int r = 0, i = 0; r < nrows; r++)
259			{
260				for (int c = 0; c < ncols; c++, i++)
261				{
262					if (i < ncomponents)
263					{
264						Component comp = parent.getComponent(i);
265						Dimension d = comp.getPreferredSize();
266						row_heights[r] = Math.max(row_heights[r], d.height);
267						col_widths[c] = Math.max(col_widths[c], d.width);
268						if (takeSizesIntoAccount)
269						{
270							d = comp.getMinimumSize();
271							minimum_row_heights[r] = Math.max(minimum_row_heights[r], d.height);
272							minimum_col_widths[c] = Math.max(minimum_col_widths[c], d.width);
273							d = comp.getMaximumSize();
274							maximum_row_heights[r] = Math.min(maximum_row_heights[r], d.height);
275							maximum_col_widths[c] = Math.min(maximum_col_widths[c], d.width);
276						}
277					}
278					else
279					{
280						break;
281					}
282				}
283				if (takeSizesIntoAccount)
284				{
285					// correct cases where
286					// minimum_row_heights[row] <= row_heights[row] <= maximum_row_heights[row]
287					// is not true by clipping to the minimum_row_heights and maximum_row_heights
288					if (minimum_row_heights[r] >= maximum_row_heights[r])
289					{
290						maximum_row_heights[r] = minimum_row_heights[r];
291						row_heights[r] = minimum_row_heights[r];
292					}
293					else if (row_heights[r] < minimum_row_heights[r])
294					{
295						row_heights[r] = minimum_row_heights[r];
296					}
297					else if (row_heights[r] > maximum_row_heights[r])
298					{
299						row_heights[r] = maximum_row_heights[r];
300					}
301				}
302				total_height += row_heights[r];
303			}
304
305			int total_width = 0;
306			for (int c = 0; c < ncols; c++)
307			{
308				if (takeSizesIntoAccount)
309				{
310					// correct cases where
311					// minimum_col_widths[col] <= col_widths[col] <= maximum_col_widths[col]
312					// is not true by clipping to the minimum_col_widths and maximum_col_widths
313					if (minimum_col_widths[c] >= maximum_col_widths[c])
314					{
315						maximum_col_widths[c] = minimum_col_widths[c];
316						col_widths[c] = minimum_col_widths[c];
317					}
318					else if (col_widths[c] < minimum_col_widths[c])
319					{
320						col_widths[c] = minimum_col_widths[c];
321					}
322					else if (col_widths[c] > maximum_col_widths[c])
323					{
324						col_widths[c] = maximum_col_widths[c];
325					}
326				}
327				total_width += col_widths[c];
328			}
329
330			// Pass 2: redistribute free space
331			Dimension parent_size = parent.getSize();
332			Insets insets = parent.getInsets();
333			int free_height = parent_size.height
334					  - insets.top - insets.bottom
335					  - (nrows - 1) * vgap
336					  - distanceToBorders.top - distanceToBorders.bottom;
337			int free_width = parent_size.width
338					 - insets.left - insets.right
339					 - (ncols - 1) * hgap
340					 - distanceToBorders.left - distanceToBorders.right;
341
342			redistributeSpace(total_height,free_height,
343					  takeSizesIntoAccount,
344					  nrows,row_heights,
345					  minimum_row_heights,
346					  maximum_row_heights);
347
348			redistributeSpace(total_width,free_width,
349					  takeSizesIntoAccount,
350					  ncols,col_widths,
351					  minimum_col_widths,
352					  maximum_col_widths);
353
354			// Pass 3: layout components
355			for (int r = 0, y = insets.top + distanceToBorders.top, i = 0; r < nrows; y += row_heights[r] + vgap, r++)
356			{
357				for (int c = 0, x = insets.left + distanceToBorders.left; c < ncols; x += col_widths[c] + hgap, c++, i++)
358				{
359					if (i < ncomponents)
360					{
361						Component comp = parent.getComponent(i);
362						Dimension d = comp.getMaximumSize();
363						int width = col_widths[c];
364						int height = row_heights[r];
365						int xCorrection = 0;
366						int yCorrection = 0;
367						if (width > d.width)
368						{
369							xCorrection = (int)((width - d.width) * comp.getAlignmentX());
370							width = d.width;
371						}
372						if (height > d.height)
373						{
374							yCorrection = (int)((height-d.height) * comp.getAlignmentY());
375							height = d.height;
376						}
377						
378						comp.setBounds(x + xCorrection, y + yCorrection, width, height);
379					}
380				}
381			}
382		} // synchronized
383	}
384
385	public void invalidateLayout(Container container)
386	{
387	}
388
389	/**
390	 * Returns the string representation of this variable grid layout's values.
391	 * @return  a string representation of this variable grid layout.
392	 */
393	public String toString()
394	{
395		return getClass().getName() + "[mode="
396			+ ((FIXED_NUM_ROWS == mode) ? "FIXED_NUM_ROWS"
397			   : ((FIXED_NUM_COLUMNS == mode) ? "FIXED_NUM_COLUMNS"
398			      : "UNKNOWN(" + mode + ")")) + ",size=" + size
399			+ ",hgap=" + hgap + ",vgap=" + vgap
400			+ ",takeSizesIntoAccount=" + takeSizesIntoAccount
401			+ ",distanceToBorders=" + distanceToBorders + "]";
402	}
403
404	/**
405	 * @param  which  if LayoutSize.MINIMUM compute minimum layout size,
406	 *                if LayoutSize.MAXIMUM compute maximum layout size,
407	 *                if LayoutSize.PREFERRED compute preferred layout size.
408	 */
409	private Dimension getLayoutSize(Container parent, LayoutSize which)
410	{
411		synchronized (parent.getTreeLock())
412		{
413			update(parent);
414
415			int ncomponents = parent.getComponentCount();
416			long h = 0;
417			long w = 0;
418
419			for (int r = 0, i = 0; r < nrows; r++)
420			{
421				int row_height = 0;
422				for (int c = 0; c < ncols; c++, i++)
423				{
424					if (i < ncomponents)
425					{
426						switch (which)
427						{
428							case MINIMUM:
429								row_height = Math.max(row_height, parent.getComponent(i).getMinimumSize().height);
430								break;
431							
432							case MAXIMUM:
433								row_height = Math.max(row_height, parent.getComponent(i).getMaximumSize().height);
434								break;
435							
436							case PREFERRED:
437								row_height = Math.max(row_height, parent.getComponent(i).getPreferredSize().height);
438								break;
439							
440							default:
441								throw new InternalError("Missing case branch for LayoutSize: " + which);
442						}
443					}
444				}
445				h += row_height;
446			}
447
448			for (int c = 0; c < ncols; c++)
449			{
450				int col_width = 0;
451				for (int r = 0; r < nrows; r++)
452				{
453					int i = r * ncols + c;
454					if (i < ncomponents)
455					{
456						switch (which)
457						{
458							case MINIMUM:
459								col_width = Math.max(col_width, parent.getComponent(i).getMinimumSize().width);
460								break;
461							
462							case MAXIMUM:
463								col_width = Math.max(col_width, parent.getComponent(i).getMaximumSize().width);
464								break;
465							
466							case PREFERRED:
467								col_width = Math.max(col_width, parent.getComponent(i).getPreferredSize().width);
468								break;
469							
470							default:
471								throw new InternalError("Missing case branch for LayoutSize: " + which);
472						}
473					}
474				}
475				w += col_width;
476			}
477
478			Insets insets = parent.getInsets();
479			w += insets.left + insets.right + ((ncols - 1) * hgap) + distanceToBorders.left + distanceToBorders.right;
480			h += insets.top + insets.bottom + ((nrows - 1) * vgap) + distanceToBorders.top + distanceToBorders.bottom;
481			if (w > Integer.MAX_VALUE)
482			{
483				w = Integer.MAX_VALUE;
484			}
485			if (h > Integer.MAX_VALUE)
486			{
487				h = Integer.MAX_VALUE;
488			}
489			return new Dimension((int)w,(int)h);
490		}
491	}
492
493	private void update(Container container)
494	{
495		int ncomponents = container.getComponentCount();
496		int old_nrows = nrows;
497		int old_ncols = ncols;
498		if (this.mode == FIXED_NUM_ROWS)
499		{
500			nrows = this.size;
501			ncols = (ncomponents + nrows - 1) / nrows;
502		}
503		else
504		{
505			ncols = this.size;
506			nrows = (ncomponents + ncols - 1) / ncols;
507		}
508		if (old_nrows != nrows)
509		{
510			row_heights = new int[nrows];
511			if (takeSizesIntoAccount)
512			{
513				minimum_row_heights = new int[nrows];
514				maximum_row_heights = new int[nrows];
515			}
516		}
517		if (old_ncols != ncols)
518		{
519			col_widths = new int[ncols];
520			if (takeSizesIntoAccount)
521			{
522				minimum_col_widths = new int[ncols];
523				maximum_col_widths = new int[ncols];
524			}
525		}
526	}
527
528	private void redistributeSpace(int total_size, int free_size, boolean takeSizesIntoAccount,
529				       int nelements, int[] element_sizes,
530				       int[] minimum_element_sizes, int[] maximum_element_sizes)
531	{
532		if (total_size != free_size)
533		{
534			if (takeSizesIntoAccount)
535			{
536				boolean grow = total_size < free_size;
537				// calculate the size that is available for redistribution
538				free_size = (free_size - total_size) * (grow ? 1 : -1);
539				while (free_size != 0)
540				{
541					// calculate the amount of elements that can be resized without violating
542					// the minimum and maximum sizes and their current cumulated size
543					int modifyableAmount = 0;
544					int modifySize = 0;
545					for (int i = 0 ; i < nelements ; i++)
546					{
547						if ((grow && (element_sizes[i] < maximum_element_sizes[i])) ||
548						    (!grow && (element_sizes[i] > minimum_element_sizes[i])))
549						{
550							modifyableAmount++;
551							modifySize += element_sizes[i];
552						}
553					}
554					boolean checkBounds = true;
555					// if all elements are at their minimum or maximum size, resize all elements
556					if (0 == modifyableAmount)
557					{
558						for (int i = 0 ; i < nelements ; i++)
559						{
560							modifySize += element_sizes[i];
561						}
562						checkBounds = false;
563						modifyableAmount = nelements;
564					}
565					// to prevent an endless loop if the container gets resized to a very small amount
566					if (modifySize == 0)
567					{
568						break;
569					}
570					// resize the elements
571					if (free_size < modifyableAmount)
572					{
573						for (int i = 0 ; i < nelements ; i++)
574						{
575							if ((free_size != 0) &&
576							    (!checkBounds ||
577							     (checkBounds &&
578							      (grow && (element_sizes[i] < maximum_element_sizes[i])) ||
579							      (!grow && (element_sizes[i] > minimum_element_sizes[i])))))
580							{
581								element_sizes[i] += (grow ? 1 : -1);
582								if (0 > element_sizes[i])
583								{
584									element_sizes[i] = 0;
585								}
586								free_size--;
587							}
588						}
589					}
590					else
591					{
592						int modifySizeAddition = 0;
593						for (int i = 0 ; i < nelements ; i++)
594						{
595							int modifyableSize = (checkBounds ? (grow ? maximum_element_sizes[i] - element_sizes[i] : element_sizes[i] - minimum_element_sizes[i]) : Integer.MAX_VALUE - element_sizes[i]);
596							int elementModifySize = (int)((double)free_size / (double)modifySize * (double)element_sizes[i]);
597							if (elementModifySize <= modifyableSize)
598							{
599								element_sizes[i] += (grow ? elementModifySize : -elementModifySize);
600								modifySizeAddition += (grow ? elementModifySize : -elementModifySize);
601								free_size -= elementModifySize;
602							}
603							else
604							{
605								element_sizes[i] += (grow ? modifyableSize : -modifyableSize);
606								modifySizeAddition += (grow ? modifyableSize : -modifyableSize);
607								free_size -= modifyableSize;
608							}
609							if (0 > element_sizes[i])
610							{
611								element_sizes[i] = 0;
612							}
613						}
614						modifySize += modifySizeAddition;
615					}
616				}
617			}
618			else
619			{
620				double d = (double)free_size / (double)total_size;
621				for (int i = 0; i < nelements; i++)
622				{
623					element_sizes[i] = (int)(element_sizes[i] * d);
624				}
625			}
626		}
627	}
628
629	private int mode;
630	private int size;
631	private int hgap;
632	private int vgap;
633	private boolean takeSizesIntoAccount;
634	private Insets distanceToBorders;
635	private transient int nrows = -1;
636	private transient int ncols = -1;
637	private transient int[] minimum_row_heights = null;
638	private transient int[] minimum_col_widths = null;
639	private transient int[] row_heights = null;
640	private transient int[] col_widths = null;
641	private transient int[] maximum_row_heights = null;
642	private transient int[] maximum_col_widths = null;
643}