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