Skip to content

Commit 84d770e

Browse files
committed
Add a GridBagLayout
Currently making good layouts in SWT applications is quite hard, even though the BorderLayout already simplifies this but is most usefull for layout cases with some fixed size widget are aranged around a central widget. This adds another Layout known from Swing that is the GridBagLayout wich lets define one a number of bags organizeds in a grid where widgets take relative sizes as well as margins and paddings, that way it is possible to easily create layouts that react in a responsive way to size changes of the container.
1 parent 8a3980d commit 84d770e

3 files changed

Lines changed: 628 additions & 0 deletions

File tree

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
/********************************************************************************
2+
* Copyright (c) 2023 Christoph Läubrich and others
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Christoph Läubrich - initial API and implementation
12+
********************************************************************************/
13+
14+
package org.eclipse.swt.layout;
15+
16+
import java.util.*;
17+
18+
import org.eclipse.swt.*;
19+
import org.eclipse.swt.graphics.*;
20+
import org.eclipse.swt.widgets.*;
21+
22+
23+
/**
24+
* @since 3.123
25+
*
26+
*/
27+
public class GridBagConstraints {
28+
29+
/**
30+
*
31+
*/
32+
private static final String DATA_KEY = GridBag.class.getName();
33+
34+
/**
35+
*
36+
*/
37+
private static final GridBagConstraints DEFAULT = new GridBagConstraints();
38+
39+
public static final int RELATIVE = -1;
40+
41+
public static final int REMAINDER = -1;
42+
43+
/**
44+
* Specify the grid coordinate where the component will be placed, starting with 0 and defaulting to
45+
* {@link #RELATIVE}, it is recommended to always set a value to have more predictable layouts that are independent of
46+
* the order components are added to the parent composite.
47+
*/
48+
public final Point grid = new Point(RELATIVE, RELATIVE);
49+
50+
/**
51+
* Specify how much columns/rows the component spans (default to 1), specify {@link #REMAINDER} to take all remaining
52+
* space of the row/column, it is recommended to always specify a value to get more predictable layouts
53+
*/
54+
public final Point span = new Point(1, 1);
55+
56+
/**
57+
* The weighting factor for the column this component is placed with, at least one control needs to define a positive
58+
* weight factor for each col/row, usually one want to take values in the range 0...100 to ease recognition by a human
59+
* being in percentages.
60+
*/
61+
public final Point weight = new Point(0, 0);
62+
63+
/**
64+
* Height hint used to determine the preferred size of the component
65+
*/
66+
public int hHint = SWT.DEFAULT;
67+
68+
/**
69+
* Width hint used to determine the preferred size of the component
70+
*/
71+
public int wHint = SWT.DEFAULT;
72+
73+
/**
74+
* Controls how/if the control should fill:
75+
* <ul>
76+
* <li>{@link SWT#NONE} - the component does not fill (default)</li>
77+
* <li>{@link SWT#HORIZONTAL} - the component is filled horizontal</li>
78+
* <li>{@link SWT#VERTICAL} - the component fills vertical</li>
79+
* <li>{@link SWT#FILL} - the component is filled in both directions</li>
80+
* </ul>
81+
*/
82+
public int fill = SWT.NONE;
83+
84+
static GridBag getGridBag(Composite composite, boolean changed) {
85+
if(!changed) {
86+
Object data = composite.getData(DATA_KEY);
87+
if(data instanceof GridBag) {
88+
return (GridBag) data;
89+
}
90+
}
91+
Control[] children = composite.getChildren();
92+
GridBagConstraints[] contrains = new GridBagConstraints[children.length];
93+
GridBag grid = new GridBag();
94+
for(int i = 0; i < children.length; i++ ) {
95+
Control control = children[i];
96+
GridBagConstraints c = contrains[i] = getConstraints(control);
97+
int x = c.grid.x;
98+
int y = c.grid.y;
99+
if(x == GridBagConstraints.RELATIVE) {
100+
//TODO
101+
}
102+
if(y == GridBagConstraints.RELATIVE) {
103+
//TODO
104+
}
105+
//TODO calculate the span if it is RELATIVE or REMAINDER!
106+
//For this to work, we maybe need to move this to the second loop!
107+
//probably better handle this in the paint loop?!
108+
int spanx = c.span.x;
109+
int spany = c.span.y;
110+
int xd = x + spanx;
111+
if(xd > grid.dimension.x) {
112+
grid.dimension.x = xd;
113+
}
114+
int yd = y + spany;
115+
if(yd > grid.dimension.y) {
116+
grid.dimension.y = yd;
117+
}
118+
grid.positionMap.put(control, new Point(x, y));
119+
grid.constraintMap.put(control, c);
120+
}
121+
grid.controls = new Control[grid.dimension.x][grid.dimension.y];
122+
grid.cols = new int[grid.dimension.x];
123+
grid.rows = new int[grid.dimension.y];
124+
grid.rowWeights = new double[grid.rows.length];
125+
grid.colWeights = new double[grid.cols.length];
126+
for(int i = 0; i < children.length; i++ ) {
127+
Control control = children[i];
128+
GridBagConstraints c = contrains[i];
129+
Point size = control.computeSize(c.wHint, c.hHint, changed);
130+
grid.sizeMap.put(control, size);
131+
Point pos = grid.positionMap.get(control);
132+
int x = pos.x;
133+
int y = pos.y;
134+
int w = size.x / c.span.x;
135+
int h = size.y / c.span.y;
136+
grid.controls[x][y] = control;
137+
for(int gi = x; gi < x + c.span.x; gi++ ) {
138+
if(w > grid.cols[gi]) {
139+
grid.cols[gi] = w;
140+
}
141+
}
142+
for(int gi = y; gi < y + c.span.y; gi++ ) {
143+
if(h > grid.rows[gi]) {
144+
grid.rows[gi] = h;
145+
}
146+
}
147+
double rw = (double) c.weight.x / c.span.x;
148+
double cw = (double) c.weight.y / c.span.y;
149+
for(int gi = x; gi < x + c.span.x; gi++ ) {
150+
if(rw > grid.colWeights[gi]) {
151+
grid.colWeights[gi] = rw;
152+
}
153+
}
154+
for(int gi = y; gi < y + c.span.y; gi++ ) {
155+
if(cw > grid.rowWeights[gi]) {
156+
grid.rowWeights[gi] = cw;
157+
}
158+
}
159+
}
160+
composite.setData(DATA_KEY, grid);
161+
return grid;
162+
}
163+
164+
static GridBagConstraints getConstraints(Control control) {
165+
Object layoutData = control.getLayoutData();
166+
if(layoutData instanceof GridBagConstraints) {
167+
return (GridBagConstraints) layoutData;
168+
}
169+
return DEFAULT;
170+
}
171+
172+
static final class GridBag {
173+
174+
public int[] cols;
175+
176+
int[] rows;
177+
178+
double[] rowWeights;
179+
180+
double[] colWeights;
181+
182+
Point dimension = new Point(0, 0);
183+
184+
Map<Control, Point> positionMap = new HashMap<>();
185+
186+
Map<Control, GridBagConstraints> constraintMap = new HashMap<>();
187+
188+
Map<Control, Point> sizeMap = new HashMap<>();
189+
190+
Control[][] controls;
191+
192+
Point getSize(Control control) {
193+
return sizeMap.get(control);
194+
}
195+
196+
// int getColumnWidth(int column) {
197+
// return cols[column];
198+
// }
199+
//
200+
// int getRowHeight(int row) {
201+
// return rows[row];
202+
// }
203+
204+
public int[] getRowHeights(int height) {
205+
if(rows.length == 0) {
206+
return new int[0];
207+
}
208+
int[] is = new int[rows.length];
209+
double weights = 0;
210+
for(int i = 0; i < is.length; i++ ) {
211+
weights += rowWeights[i];
212+
}
213+
double dx = height / weights;
214+
int sum = 0;
215+
for(int i = 0; i < is.length; i++ ) {
216+
sum += is[i] += Math.rint(dx * rowWeights[i]);
217+
}
218+
//any rounding error go to the last column
219+
is[is.length - 1] += height - sum;
220+
return is;
221+
}
222+
223+
public int[] getColumnWidths(int width) {
224+
if(cols.length == 0) {
225+
return new int[0];
226+
}
227+
int[] is = new int[cols.length];
228+
double weights = 0;
229+
for(int i = 0; i < is.length; i++ ) {
230+
weights += colWeights[i];
231+
}
232+
double dx = width / weights;
233+
int sum = 0;
234+
for(int i = 0; i < is.length; i++ ) {
235+
sum += is[i] += Math.rint(dx * colWeights[i]);
236+
}
237+
//any rounding error go to the last column
238+
is[is.length - 1] += width - sum;
239+
return is;
240+
}
241+
242+
}
243+
}
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
/********************************************************************************
2+
* Copyright (c) 2023 Christoph Läubrich and others
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Christoph Läubrich - initial API and implementation
12+
********************************************************************************/
13+
14+
package org.eclipse.swt.layout;
15+
16+
import org.eclipse.swt.*;
17+
import org.eclipse.swt.graphics.*;
18+
import org.eclipse.swt.layout.GridBagConstraints.*;
19+
import org.eclipse.swt.widgets.*;
20+
21+
/**
22+
* This implements a layout similar to the <a href=
23+
* "https://docs.oracle.com/javase/tutorial/uiswing/layout/gridbag.html">Swing
24+
* GridBagLayout</a>
25+
*
26+
* @since 3.123
27+
*/
28+
public class GridBagLayout extends Layout {
29+
30+
/**
31+
*
32+
*/
33+
public GridBagLayout() {
34+
}
35+
36+
public GridBagLayout(Composite component) {
37+
component.setLayout(this);
38+
component.addPaintListener(e -> {
39+
Point size = component.getSize();
40+
GridBag gridBag = GridBagConstraints.getGridBag(component, false);
41+
GC gc = e.gc;
42+
int[] columnWidths = gridBag.getColumnWidths(size.x);
43+
int[] rowHeights = gridBag.getRowHeights(size.y);
44+
gc.setForeground(gc.getDevice().getSystemColor(SWT.COLOR_BLACK));
45+
gc.setLineWidth(3);
46+
for (int x = 0; x < gridBag.dimension.x; x++) {
47+
for (int y = 0; y < gridBag.dimension.y; y++) {
48+
Control control = gridBag.controls[x][y];
49+
if (control == null) {
50+
continue;
51+
}
52+
Point l = control.getLocation();
53+
GridBagConstraints constraints = gridBag.constraintMap.get(control);
54+
int w = columnWidths[x];
55+
int colspan = constraints.span.x;
56+
for (int i = 1; i < colspan; i++) {
57+
w += columnWidths[x + i];
58+
}
59+
int h = rowHeights[y];
60+
int rowspan = constraints.span.y;
61+
for (int i = 1; i < rowspan; i++) {
62+
h += rowHeights[y + i];
63+
}
64+
Point pos = gridBag.positionMap.get(control);
65+
gc.drawRectangle(l.x, l.y, w, h);
66+
String xs = String.valueOf(pos.x);
67+
if (colspan > 1) {
68+
xs += " + " + (colspan - 1);
69+
}
70+
String ys = String.valueOf(pos.y);
71+
if (rowspan > 1) {
72+
ys += " + " + (rowspan - 1);
73+
}
74+
String msg = control + " (" + xs + ", " + ys + ")";
75+
Point extent = gc.stringExtent(msg);
76+
gc.drawString(msg, l.x + w / 2 - extent.x / 2, l.y + h / 2 - extent.y / 2);
77+
}
78+
}
79+
});
80+
}
81+
82+
@Override
83+
protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
84+
// FIXME need to use max of summ of computed site
85+
// GridBag grid = GridBagConstraints.getGridBag(composite, flushCache);
86+
// if (wHint < 0) {
87+
// int width = 0;
88+
// for (int x = 0; x < grid.dimension.x; x++) {
89+
// width += grid.getColumnWidth(x);
90+
// }
91+
// wHint = width;
92+
// }
93+
// if (hHint < 0) {
94+
// int height = 0;
95+
// for (int y = 0; y < grid.dimension.y; y++) {
96+
// height += grid.getRowHeight(y);
97+
// }
98+
// hHint = height;
99+
// }
100+
return new Point(wHint, hHint);
101+
}
102+
103+
@Override
104+
protected void layout(Composite composite, boolean flushCache) {
105+
Rectangle clientArea = composite.getClientArea();
106+
GridBag grid = GridBagConstraints.getGridBag(composite, flushCache);
107+
int[] columnWidths = grid.getColumnWidths(clientArea.width);
108+
int[] rowHeights = grid.getRowHeights(clientArea.height);
109+
double offsetX = clientArea.x;
110+
for (int x = 0; x < grid.dimension.x; x++) {
111+
double offsetY = clientArea.y;
112+
int columnWidth = columnWidths[x];
113+
for (int y = 0; y < grid.dimension.y; y++) {
114+
int rowHeight = rowHeights[y];
115+
Control control = grid.controls[x][y];
116+
if (control != null) {
117+
GridBagConstraints c = grid.constraintMap.get(control);
118+
// TODO compute size according to grid size as hints!?
119+
Point size = grid.getSize(control);
120+
int width;
121+
int maxWidth = columnWidth;
122+
for (int i = 1; i < c.span.x; i++) {
123+
maxWidth += columnWidths[y + i];
124+
}
125+
if (c.fill == SWT.ALL || c.fill == SWT.HORIZONTAL) {
126+
width = maxWidth;
127+
} else {
128+
width = size.x;
129+
}
130+
int height;
131+
int maxHeight = rowHeight;
132+
for (int i = 1; i < c.span.y; i++) {
133+
maxHeight += rowHeights[y + i];
134+
}
135+
if (c.fill == SWT.ALL || c.fill == SWT.VERTICAL) {
136+
height = maxHeight;
137+
} else {
138+
height = size.y;
139+
}
140+
control.setBounds((int) offsetX, (int) offsetY, Math.min(width, maxWidth),
141+
Math.min(height, maxHeight));
142+
}
143+
offsetY += rowHeight;
144+
}
145+
offsetX += columnWidth;
146+
}
147+
148+
}
149+
150+
}

0 commit comments

Comments
 (0)