001: /*
002: * Copyright 2000,2005 wingS development team.
003: *
004: * This file is part of wingS (http://wingsframework.org).
005: *
006: * wingS is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU Lesser General Public License
008: * as published by the Free Software Foundation; either version 2.1
009: * of the License, or (at your option) any later version.
010: *
011: * Please see COPYING for the complete licence.
012: */
013: package org.wings;
014:
015: import org.wings.event.SComponentEvent;
016: import org.wings.event.SComponentListener;
017:
018: import java.awt.*;
019: import java.util.HashMap;
020: import java.util.Iterator;
021:
022: /**
023: * Tries to simulate swing GridBag behaviour as close as possible.
024: * <p/>
025: * This layout is similar to Swing's GridBagLayout, though it can't
026: * implement all functionalities because of the limitations of
027: * HTML-table. It probably doesn't work exactly like its
028: * Swing-counterpart - as a general hint: don't be too clever...
029: * <p/>
030: * <p/>
031: * SComponents are usually added using an instance of
032: * java.awt.GridBagConstraints which is copied while adding it (so you
033: * might reuse it to add other SComponents). There are basically two
034: * ways of adding: explicitly setting gridx and gridy or leaving
035: * those at the default (RELATIVE) and let SGridBagLayout decide where
036: * to put them. Normally they will be added horizontally, unless you
037: * explicitly set gridx, which will add the SComponents
038: * vertically. With setting gridy you can choose a row in which the
039: * SComponents will be added. If you want to finish a row/column, you
040: * can set gridwidth/gridheight to REMAINDER or RELATIVE - REMAINDER
041: * marks the row/column to be finished while RELATIVE tells
042: * SGridBagLayout that the <em>next</em> added SComponent will be the
043: * last cell of the row/column which will always be placed at the end
044: * (while the 'RELATIVE'-SComponent will be expanded to fill the gap).
045: * <p/>
046: * <p/>
047: * <em>Important:</em> When choosing a new row/column, the next
048: * gridx/gridy-value that SGridLayout will choose will always be 0,
049: * even if there is already a SComponent at that position. If you
050: * really need to be clever, explicitly set gridx and gridy,
051: * especially if you plan to dynamically add and remove SComponents.
052: * <p/>
053: * <p/>
054: * The size of a cell can be influenced in two ways: either set
055: * gridwidth/gridheight to a value larger than 1 to say how many
056: * regular cells this cell should span or use weightx/weighty to tell
057: * the browser how much of the empty space this cell should eat up
058: * (e.g. if there are 3 cells with each weight=1, they will all get
059: * 33%). The last method has two disadvantages: firstly, it uses the
060: * deprecated width/height-parameters of the HTML-td statement and
061: * secondly, it must be carefully used to get the correct result: all
062: * cells of a row/column should have the same weighty/weightx or 0, so
063: * it might be easier to set these values only in the first
064: * column/row.
065: * <p/>
066: * <p/>
067: * GridBagConstraints has many more options than those described
068: * above, but the current implementation can't use them.
069: *
070: * @author <a href="mailto:js@trollhead.net">Jochen Scharrlach</a>
071: */
072: public class SGridBagLayout extends SAbstractLayoutManager implements
073: SComponentListener {
074: /**
075: * Map of all managed components (key: component, value: constraint)
076: */
077: private HashMap components = new HashMap();
078:
079: /**
080: * @see #getBorder
081: */
082: protected int border = 0;
083:
084: /**
085: * The horizontal gap (in pixels) specifiying the space
086: * between columns. They can be changed at any time.
087: * This should be a non-negative integer.
088: */
089: protected int hgap = -1;
090:
091: /**
092: * The vertical gap (in pixels) which specifiying the space
093: * between rows. They can be changed at any time.
094: * This should be a non negative integer.
095: */
096: protected int vgap = -1;
097:
098: /**
099: * @see #getHeader
100: */
101: protected boolean header = false;
102:
103: /**
104: * The defaults to use if the addComponent()-call does not give
105: * the constraints.
106: */
107: protected GridBagConstraints defaultConstraints = new GridBagConstraints();
108:
109: /**
110: * Contains a pre-calculated grid (or null)
111: */
112: protected transient Grid currentGrid;
113:
114: /**
115: * Indicates that the corresponding SComponent should be at the
116: * end of the row/column. This value is only for internal use and
117: * cannot be used with addComponent.
118: */
119: public static final int LAST_CELL = -1;
120:
121: /**
122: * creats a new gridbag layout
123: */
124: public SGridBagLayout() {
125: setPreferredSize(SDimension.FULLWIDTH);
126: }
127:
128: /**
129: * Add the given component with the given constraints to the
130: * layout.
131: *
132: * @param comp the component to add
133: * @param constraint instance of GridBagConstraints or null
134: * @param index ignored
135: */
136: public void addComponent(SComponent comp, Object constraint,
137: int index) {
138: // The grid has to be rebuilt
139: currentGrid = null;
140:
141: GridBagConstraints c = (GridBagConstraints) constraint;
142: if (c == null) {
143: c = defaultConstraints;
144: }
145: c = (GridBagConstraints) c.clone();
146:
147: comp.addComponentListener(this );
148: components.put(comp, c);
149: }
150:
151: public void removeComponent(SComponent c) {
152: // The grid has to be rebuilt
153: currentGrid = null;
154: components.remove(c);
155: c.removeComponentListener(this );
156: }
157:
158: public void componentHidden(SComponentEvent e) {
159: // The grid has to be rebuilt
160: currentGrid = null;
161: }
162:
163: public void componentMoved(SComponentEvent e) {
164: // ignored
165: }
166:
167: public void componentResized(SComponentEvent e) {
168: // ignored
169: }
170:
171: public void componentShown(SComponentEvent e) {
172: // The grid has to be rebuilt
173: currentGrid = null;
174: }
175:
176: /**
177: * Gets the horizontal gap between components in pixel. Rendered half as margin left and margin right
178: * Some PLAFs might ignore this property.
179: *
180: * @return the horizontal gap between components
181: */
182: public int getHgap() {
183: return hgap;
184: }
185:
186: /**
187: * Sets the horizontal gap between components to the specified value in pixe. Rendered half as margin left and margin right
188: * Some PLAFs might ignore this property.
189: *
190: * @param hgap the horizontal gap between components
191: */
192: public void setHgap(int hgap) {
193: this .hgap = hgap;
194: }
195:
196: /**
197: * Gets the vertical gap between components in pixel. Rendered half as margin top and margin bottom
198: * Some PLAFs might ignore this property.
199: *
200: * @return the vertical gap between components
201: */
202: public int getVgap() {
203: return vgap;
204: }
205:
206: /**
207: * Sets the vertical gap between components to the specified value in pixel.
208: * Rendered half as margin top and margin bottom. Some PLAFs might ignore this property.
209: *
210: * @param vgap the vertical gap between components
211: */
212: public void setVgap(int vgap) {
213: this .vgap = vgap;
214: }
215:
216: /**
217: * Set the border width.
218: *
219: * @param pixel the new border width in pixels
220: */
221: public void setBorder(int pixel) {
222: border = pixel;
223: }
224:
225: /**
226: * Get the border width.
227: *
228: * @return the border width in pixels
229: */
230: public int getBorder() {
231: return border;
232: }
233:
234: /**
235: * Specify if the first row should be printed as header
236: *
237: * @param b true=the first row is used as header
238: */
239: public void setHeader(boolean b) {
240: header = b;
241: }
242:
243: /**
244: * Query if the first row will be printed as header
245: *
246: * @return true=the first row is used as header
247: */
248: public boolean getHeader() {
249: return header;
250: }
251:
252: // Some helper functions for CGs
253:
254: /**
255: * This class prepares all information necessary to plot the
256: * layout to the output device. The information will be outdated as
257: * soon as components will be added or removed from the layout.
258: */
259: public class Grid {
260: /**
261: * Number of columns
262: */
263: public int cols;
264:
265: /**
266: * Number of rows
267: */
268: public int rows;
269:
270: /**
271: * The matrix with all known SComponents. A SComponent might
272: * appear in more than one cell, indicating that it spans more
273: * than one cell - usually it will only be plotted if its
274: * value for gridx/gridy matches the current cell (exception:
275: * gridx/gridy might also be set to LAST_CELL).
276: */
277: public SComponent[][] grid;
278:
279: /**
280: * The total column-weight of a row(!). The cumulated weightx
281: * of all cells of a row..
282: */
283: public double[] colweight;
284:
285: /**
286: * The total row-weight of a column(!). The cumulated weighty
287: * of all cells of a column..
288: */
289: public double[] rowweight;
290:
291: /**
292: * The first row that contains cells
293: */
294: public int firstRow;
295:
296: /**
297: * The first column that contains cells
298: */
299: public int firstCol;
300:
301: /**
302: * Row for the next horizontal add (gridx=RELATIVE). If gridy is
303: * not RELATIVE and does not match nextHorRow, the SComponent will
304: * be added at gridx=0.
305: */
306: private int nextHorRow = 0;
307:
308: /**
309: * Column for the next horizontal add (gridx=RELATIVE)
310: */
311: private int nextHorCol = 0;
312:
313: /**
314: * Row for the next vertical add (gridx != RELATIVE, gridy =
315: * RELATIVE).
316: */
317: private int nextVertRow = 0;
318:
319: /**
320: * Column for the next vertical add (gridx != RELATIVE, gridy =
321: * RELATIVE). If gridx does not match nextVertCol, the SComponent
322: * will be added at gridy=0.
323: */
324: private int nextVertCol = 0;
325:
326: private final HashMap modifiedConstraints = new HashMap(
327: components.size());
328:
329: /**
330: * Initialize all members
331: */
332: public Grid() {
333: cols = 0;
334: rows = 0;
335:
336: for (Iterator i = getContainer().getComponentList()
337: .iterator(); i.hasNext();) {
338: SComponent comp = (SComponent) i.next();
339: if (!comp.isVisible()) {
340: //continue;
341: }
342:
343: GridBagConstraints c = (GridBagConstraints) components
344: .get(comp);
345: c = (GridBagConstraints) c.clone();
346: modifiedConstraints.put(comp, c);
347:
348: if (c.gridx >= 0) {
349: if (c.gridx != nextVertCol) {
350: nextVertRow = 0;
351: }
352:
353: if (c.gridy < 0) {
354: c.gridy = nextVertRow;
355: }
356: } else {
357: if (c.gridy >= 0 && c.gridy != nextHorRow) {
358: nextHorCol = 0;
359: }
360:
361: if (c.gridy < 0) {
362: c.gridy = nextHorRow;
363: } else if (c.gridy != nextHorRow) {
364: nextHorCol = 0;
365: }
366: c.gridx = nextHorCol;
367: }
368: if (c.gridx == LAST_CELL) {
369: if (c.gridy == LAST_CELL) {
370: nextHorRow = 0;
371: nextVertRow = 0;
372: } else {
373: nextHorRow = c.gridy + 1;
374: nextVertRow = c.gridy + 1;
375: }
376: nextHorCol = 0;
377: nextVertCol = 0;
378: } else {
379: if (c.gridy == LAST_CELL) {
380: nextHorRow = 0;
381: nextVertRow = 0;
382: nextHorCol = c.gridx + 1;
383: nextVertCol = c.gridx + 1;
384: } else {
385: nextHorCol = c.gridx;
386: nextVertCol = c.gridx;
387: nextHorRow = c.gridy;
388: nextVertRow = c.gridy;
389:
390: if (c.gridwidth == GridBagConstraints.RELATIVE) {
391: nextHorCol = LAST_CELL;
392: } else if (c.gridwidth == GridBagConstraints.REMAINDER) {
393: nextHorCol = 0;
394: nextHorRow++;
395: } else {
396: if (c.gridwidth > 0) {
397: nextHorCol += c.gridwidth;
398: } else {
399: nextHorCol++;
400: }
401: }
402:
403: if (c.gridheight == GridBagConstraints.RELATIVE) {
404: nextVertRow = LAST_CELL;
405: } else if (c.gridheight == GridBagConstraints.REMAINDER) {
406: nextVertRow = 0;
407: nextVertCol++;
408: } else {
409: if (c.gridheight > 0) {
410: nextVertRow += c.gridheight;
411: } else {
412: nextVertRow++;
413: }
414: }
415: }
416: }
417:
418: if (c.gridx != SGridBagLayout.LAST_CELL) {
419: int col = c.gridx;
420: if (c.gridwidth == GridBagConstraints.RELATIVE) {
421: col++;
422: } else if (c.gridwidth > 1) {
423: col += c.gridwidth - 1;
424: }
425:
426: int row = c.gridy;
427: if (c.gridheight == GridBagConstraints.RELATIVE) {
428: row++;
429: } else if (c.gridheight > 1) {
430: row += c.gridheight - 1;
431: }
432:
433: if (col >= cols) {
434: cols = col + 1;
435: }
436: if (row >= rows) {
437: rows = row + 1;
438: }
439: }
440: }
441:
442: grid = new SComponent[cols][rows];
443: rowweight = new double[cols];
444: colweight = new double[rows];
445:
446: for (Iterator i = getContainer().getComponentList()
447: .iterator(); i.hasNext();) {
448: SComponent comp = (SComponent) i.next();
449: if (!comp.isVisible()) {
450: //continue;
451: }
452: GridBagConstraints c = (GridBagConstraints) modifiedConstraints
453: .get(comp);
454:
455: int maxcol = c.gridx + c.gridwidth;
456: int maxrow = c.gridy + c.gridheight;
457:
458: if (c.gridwidth == GridBagConstraints.RELATIVE) {
459: maxcol = cols - 1;
460: } else if (c.gridwidth == GridBagConstraints.REMAINDER) {
461: maxcol = cols;
462: }
463: if (c.gridheight == GridBagConstraints.RELATIVE) {
464: maxrow = rows - 1;
465: } else if (c.gridheight == GridBagConstraints.REMAINDER) {
466: maxrow = rows;
467: }
468: int col = c.gridx;
469: if (col == SGridBagLayout.LAST_CELL) {
470: col = cols - 1;
471: maxcol = cols;
472: }
473: int row = c.gridy;
474: if (row == SGridBagLayout.LAST_CELL) {
475: row = rows - 1;
476: maxrow = rows;
477: }
478:
479: for (; col < maxcol; col++) {
480: rowweight[col] += c.weighty;
481: for (int r = row; r < maxrow; r++) {
482: grid[col][r] = comp;
483: colweight[r] += c.weightx;
484: }
485: }
486: }
487: /*
488: for (firstRow = 0; firstRow < rows; firstRow++) {
489: int col;
490: for (col = 0; col < cols; col++) {
491: if (grid[col][firstRow] != null) {
492: break;
493: }
494: }
495: if (col < cols) {
496: break;
497: }
498: }
499: for (firstCol = 0; firstCol < cols; firstCol++) {
500: int row;
501: for (row = 0; row < rows; row++) {
502: if (grid[firstCol][row] != null) {
503: break;
504: }
505: }
506: if (row < rows) {
507: break;
508: }
509: }
510: */
511: }
512: }
513:
514: /**
515: * Build a grid from the current configuration. Make sure the
516: * layout is not altered while using the Grid!
517: *
518: * @return the Grid-instance
519: */
520: public Grid getGrid() {
521: if (currentGrid == null) {
522: currentGrid = new Grid();
523: }
524: return currentGrid;
525: }
526:
527: /**
528: * Retrieve the constraint of a SComponent. The constraint must
529: * not be altered!
530: *
531: * @param comp the component
532: * @return the constraint or null if the component is unknown
533: */
534: final public GridBagConstraints getConstraints(SComponent comp) {
535: // It might be better to return a copy of the constraint,
536: // but that would hurt the performance
537: return (GridBagConstraints) getGrid().modifiedConstraints
538: .get(comp);
539: }
540: }
|