1 package se.datadosen.component;
2
3 import java.awt.*;
4 import java.util.*;
5
6 /***
7 * <p>RiverLayout makes it very simple to construct user interfaces as components
8 * are laid out similar to how text is added to a word processor (Components flow
9 * like a "river". RiverLayout is however much more powerful than FlowLayout:
10 * Components added with the add() method generally gets laid out horizontally,
11 * but one may add a string before the component being added to specify "constraints"
12 * like this:
13 * add("br hfill", new JTextField("Your name here");
14 * The code above forces a "line break" and extends the added component horizontally.
15 * Without the "hfill" constraint, the component would take on its preferred size.
16 *</p>
17 * <p>
18 * List of constraints:<ul>
19 * <li>br - Add a line break
20 * <li>p - Add a paragraph break
21 * <li>tab - Add a tab stop (handy for constructing forms with labels followed by fields)
22 * <li>hfill - Extend component horizontally
23 * <li>vfill - Extent component vertically (currently only one allowed)
24 * <li>left - Align following components to the left (default)
25 * <li>center - Align following components horizontally centered
26 * <li>right - Align following components to the right
27 * <li>vtop - Align following components vertically top aligned
28 * <li>vcenter - Align following components vertically centered (default)
29 * </ul>
30 * </p>
31 * RiverLayout is LGPL licenced - use it freely in free and commercial programs
32 *
33 * @author David Ekholm
34 * @version 1.0
35 */
36 public class RiverLayout
37 extends FlowLayout
38 implements LayoutManager, java.io.Serializable {
39
40 public static final String LINE_BREAK = "br";
41 public static final String PARAGRAPH_BREAK = "p";
42 public static final String TAB_STOP = "tab";
43 public static final String HFILL = "hfill";
44 public static final String VFILL = "vfill";
45 public static final String LEFT = "left";
46 public static final String RIGHT = "right";
47 public static final String CENTER = "center";
48 public static final String VTOP = "vtop";
49 public static final String VCENTER = "vcenter";
50
51 Map constraints = new HashMap();
52 String valign = VCENTER;
53 int hgap;
54 int vgap;
55 Insets extraInsets;
56 Insets totalInsets = new Insets(0, 0, 0, 0);
57
58
59 public RiverLayout() {
60 this(10, 5);
61 }
62
63 public RiverLayout(int hgap, int vgap) {
64 this.hgap = hgap;
65 this.vgap = vgap;
66 setExtraInsets(new Insets(0, hgap, hgap, hgap));
67 }
68
69 /***
70 * Gets the horizontal gap between components.
71 */
72 public int getHgap() {
73 return hgap;
74 }
75
76 /***
77 * Sets the horizontal gap between components.
78 */
79 public void setHgap(int hgap) {
80 this.hgap = hgap;
81 }
82
83 /***
84 * Gets the vertical gap between components.
85 */
86 public int getVgap() {
87 return vgap;
88 }
89
90 public Insets getExtraInsets() {
91 return extraInsets;
92 }
93
94 public void setExtraInsets(Insets newExtraInsets) {
95 extraInsets = newExtraInsets;
96 }
97
98 protected Insets getInsets(Container target) {
99 Insets insets = target.getInsets();
100 totalInsets.top = insets.top + extraInsets.top;
101 totalInsets.left = insets.left + extraInsets.left;
102 totalInsets.bottom = insets.bottom + extraInsets.bottom;
103 totalInsets.right = insets.right + extraInsets.right;
104 return totalInsets;
105 }
106
107 /***
108 * Sets the vertical gap between components.
109 */
110 public void setVgap(int vgap) {
111 this.vgap = vgap;
112 }
113
114
115 /***
116 * @param name the name of the component
117 * @param comp the component to be added
118 */
119 public void addLayoutComponent(String name, Component comp) {
120 constraints.put(comp, name);
121 }
122
123 /***
124 * Removes the specified component from the layout. Not used by
125 * this class.
126 * @param comp the component to remove
127 * @see java.awt.Container#removeAll
128 */
129 public void removeLayoutComponent(Component comp) {
130 constraints.remove(comp);
131 }
132
133 boolean isFirstInRow(Component comp) {
134 String cons = (String) constraints.get(comp);
135 return cons != null && (cons.indexOf(RiverLayout.LINE_BREAK) != -1 ||
136 cons.indexOf(RiverLayout.PARAGRAPH_BREAK) != -1);
137 }
138
139 boolean hasHfill(Component comp) {
140 return hasConstraint(comp, RiverLayout.HFILL);
141 }
142
143 boolean hasVfill(Component comp) {
144 return hasConstraint(comp, RiverLayout.VFILL);
145 }
146
147 boolean hasConstraint(Component comp, String test) {
148 String cons = (String) constraints.get(comp);
149 if (cons == null) return false;
150 StringTokenizer tokens = new StringTokenizer(cons);
151 while (tokens.hasMoreTokens())
152 if (tokens.nextToken().equals(test)) return true;
153 return false;
154 }
155
156 /***
157 * Figure out tab stop x-positions
158 */
159 protected Ruler calcTabs(Container target) {
160 Ruler ruler = new Ruler();
161 int nmembers = target.getComponentCount();
162
163 int x = 0;
164 int tabIndex = 0;
165 for (int i = 0; i < nmembers; i++) {
166 Component m = target.getComponent(i);
167
168 if (isFirstInRow(m) || i == 0) {
169 x = 0;
170 tabIndex = 0;
171 }
172 else x+= hgap;
173 if (hasConstraint(m, TAB_STOP)) {
174 ruler.setTab(tabIndex, x);
175 x = ruler.getTab(tabIndex++);
176 }
177 Dimension d = m.getPreferredSize();
178 x += d.width;
179 }
180
181 return ruler;
182 }
183
184 /***
185 * Returns the preferred dimensions for this layout given the
186 * <i>visible</i> components in the specified target container.
187 * @param target the component which needs to be laid out
188 * @return the preferred dimensions to lay out the
189 * subcomponents of the specified container
190 * @see Container
191 * @see #minimumLayoutSize
192 * @see java.awt.Container#getPreferredSize
193 */
194 public Dimension preferredLayoutSize(Container target) {
195 synchronized (target.getTreeLock()) {
196 Dimension dim = new Dimension(0, 0);
197 Dimension rowDim = new Dimension(0, 0);
198 int nmembers = target.getComponentCount();
199 boolean firstVisibleComponent = true;
200 int tabIndex = 0;
201 Ruler ruler = calcTabs(target);
202
203 for (int i = 0; i < nmembers; i++) {
204 Component m = target.getComponent(i);
205
206 if (isFirstInRow(m)) {
207 tabIndex = 0;
208 dim.width = Math.max(dim.width, rowDim.width);
209 dim.height += rowDim.height + vgap;
210 if (hasConstraint(m, PARAGRAPH_BREAK)) dim.height += 2*vgap;
211 rowDim = new Dimension(0, 0);
212 }
213 if (hasConstraint(m, TAB_STOP)) rowDim.width = ruler.getTab(tabIndex++);
214 Dimension d = m.getPreferredSize();
215 rowDim.height = Math.max(rowDim.height, d.height);
216 if (firstVisibleComponent) {
217 firstVisibleComponent = false;
218 }
219 else {
220 rowDim.width += hgap;
221 }
222 rowDim.width += d.width;
223
224 }
225 dim.width = Math.max(dim.width, rowDim.width);
226 dim.height += rowDim.height;
227
228 Insets insets = getInsets(target);
229 dim.width += insets.left + insets.right;
230 dim.height += insets.top + insets.bottom;
231 return dim;
232 }
233 }
234
235 /***
236 * Returns the minimum dimensions needed to layout the <i>visible</i>
237 * components contained in the specified target container.
238 * @param target the component which needs to be laid out
239 * @return the minimum dimensions to lay out the
240 * subcomponents of the specified container
241 * @see #preferredLayoutSize
242 * @see java.awt.Container
243 * @see java.awt.Container#doLayout
244 */
245 public Dimension minimumLayoutSize(Container target) {
246 synchronized (target.getTreeLock()) {
247 Dimension dim = new Dimension(0, 0);
248 Dimension rowDim = new Dimension(0, 0);
249 int nmembers = target.getComponentCount();
250 boolean firstVisibleComponent = true;
251 int tabIndex = 0;
252 Ruler ruler = calcTabs(target);
253
254 for (int i = 0; i < nmembers; i++) {
255 Component m = target.getComponent(i);
256
257 if (isFirstInRow(m)) {
258 tabIndex = 0;
259 dim.width = Math.max(dim.width, rowDim.width);
260 dim.height += rowDim.height + vgap;
261 if (hasConstraint(m, PARAGRAPH_BREAK)) dim.height += 2*vgap;
262 rowDim = new Dimension(0, 0);
263 }
264 if (hasConstraint(m, TAB_STOP)) rowDim.width = ruler.getTab(tabIndex++);
265 Dimension d = m.getMinimumSize();
266 rowDim.height = Math.max(rowDim.height, d.height);
267 if (firstVisibleComponent) {
268 firstVisibleComponent = false;
269 }
270 else {
271 rowDim.width += hgap;
272 }
273 rowDim.width += d.width;
274
275 }
276 dim.width = Math.max(dim.width, rowDim.width);
277 dim.height += rowDim.height;
278
279 Insets insets = getInsets(target);
280 dim.width += insets.left + insets.right;
281 dim.height += insets.top + insets.bottom;
282 return dim;
283 }
284 }
285
286 /***
287 * Centers the elements in the specified row, if there is any slack.
288 * @param target the component which needs to be moved
289 * @param x the x coordinate
290 * @param y the y coordinate
291 * @param width the width dimensions
292 * @param height the height dimensions
293 * @param rowStart the beginning of the row
294 * @param rowEnd the the ending of the row
295 */
296 protected void moveComponents(Container target, int x, int y, int width,
297 int height,
298 int rowStart, int rowEnd, boolean ltr, Ruler ruler) {
299 synchronized (target.getTreeLock()) {
300 switch (getAlignment()) {
301 case FlowLayout.LEFT:
302 x += ltr ? 0 : width;
303 break;
304 case FlowLayout.CENTER:
305 x += width / 2;
306 break;
307 case FlowLayout.RIGHT:
308 x += ltr ? width : 0;
309 break;
310 case LEADING:
311 break;
312 case TRAILING:
313 x += width;
314 break;
315 }
316 int tabIndex = 0;
317 for (int i = rowStart; i < rowEnd; i++) {
318 Component m = target.getComponent(i);
319
320 if (hasConstraint(m, TAB_STOP)) x = getInsets(target).left + ruler.getTab(tabIndex++);
321 int dy = (valign == VTOP) ? 0 : (height - m.getHeight()) / 2;
322 if (ltr) {
323 m.setLocation(x, y + dy);
324 }
325 else {
326 m.setLocation(target.getWidth() - x - m.getWidth(),
327 y + dy);
328 }
329 x += m.getWidth() + hgap;
330
331 }
332 }
333 }
334
335
336 protected void relMove(Container target, int dx, int dy, int rowStart,
337 int rowEnd) {
338 synchronized (target.getTreeLock()) {
339 for (int i = rowStart; i < rowEnd; i++) {
340 Component m = target.getComponent(i);
341
342 m.setLocation(m.getX() + dx, m.getY() + dy);
343
344 }
345
346 }
347 }
348
349 protected void adjustAlignment(Component m) {
350 if (hasConstraint(m, RiverLayout.LEFT)) setAlignment(FlowLayout.LEFT);
351 else if (hasConstraint(m, RiverLayout.RIGHT)) setAlignment(FlowLayout.RIGHT);
352 else if (hasConstraint(m, RiverLayout.CENTER)) setAlignment(FlowLayout.CENTER);
353 if (hasConstraint(m, RiverLayout.VTOP)) valign = VTOP;
354 else if (hasConstraint(m, RiverLayout.VCENTER)) valign = VCENTER;
355
356 }
357 /***
358 * Lays out the container. This method lets each component take
359 * its preferred size by reshaping the components in the
360 * target container in order to satisfy the constraints of
361 * this <code>FlowLayout</code> object.
362 * @param target the specified component being laid out
363 * @see Container
364 * @see java.awt.Container#doLayout
365 */
366 public void layoutContainer(Container target) {
367 setAlignment(FlowLayout.LEFT);
368 synchronized (target.getTreeLock()) {
369 Insets insets = getInsets(target);
370 int maxwidth = target.getWidth() -
371 (insets.left + insets.right);
372 int maxheight = target.getHeight() -
373 (insets.top + insets.bottom);
374
375 int nmembers = target.getComponentCount();
376 int x = 0, y = insets.top + vgap;
377 int rowh = 0, start = 0, moveDownStart = 0;
378
379 boolean ltr = target.getComponentOrientation().isLeftToRight();
380 Component toHfill = null;
381 Component toVfill = null;
382 Ruler ruler = calcTabs(target);
383 int tabIndex = 0;
384
385 for (int i = 0; i < nmembers; i++) {
386 Component m = target.getComponent(i);
387
388 Dimension d = m.getPreferredSize();
389 m.setSize(d.width, d.height);
390
391 if (isFirstInRow(m)) tabIndex = 0;
392 if (hasConstraint(m, TAB_STOP)) x = ruler.getTab(tabIndex++);
393 if (!isFirstInRow(m)) {
394 if (i > 0 && !hasConstraint(m, TAB_STOP)) {
395 x += hgap;
396 }
397 x += d.width;
398 rowh = Math.max(rowh, d.height);
399 }
400 else {
401 if (toVfill != null && moveDownStart == 0) {
402 moveDownStart = i;
403 }
404 if (toHfill != null) {
405 toHfill.setSize(toHfill.getWidth() + maxwidth - x,
406 toHfill.getHeight());
407 x = maxwidth;
408 }
409 moveComponents(target, insets.left, y,
410 maxwidth - x,
411 rowh, start, i, ltr, ruler);
412 x = d.width;
413 y += vgap + rowh;
414 if (hasConstraint(m, PARAGRAPH_BREAK)) y += 2*vgap;
415 rowh = d.height;
416 start = i;
417 toHfill = null;
418 }
419
420 if (hasHfill(m)) {
421 toHfill = m;
422 }
423 if (hasVfill(m)) {
424 toVfill = m;
425 }
426 adjustAlignment(m);
427 }
428
429 if (toVfill != null && moveDownStart == 0) {
430 moveDownStart = nmembers;
431 }
432 if (toHfill != null) {
433 toHfill.setSize(toHfill.getWidth() + maxwidth - x,
434 toHfill.getHeight());
435 x = maxwidth;
436 }
437 moveComponents(target, insets.left, y, maxwidth - x, rowh,
438 start, nmembers, ltr, ruler);
439 int yslack = maxheight - (y+rowh);
440 if (yslack > 0 && toVfill != null) {
441 toVfill.setSize(toVfill.getWidth(), yslack + toVfill.getHeight());
442 relMove(target, 0, yslack, moveDownStart, nmembers);
443 }
444 }
445 }
446
447 }
448
449 class Ruler {
450 private Vector tabs = new Vector();
451
452 public void setTab(int num, int xpos) {
453 if (num >= tabs.size()) tabs.add(num, new Integer(xpos));
454 else {
455
456 int delta = xpos - getTab(num);
457 if (delta > 0) {
458 for (int i = num; i < tabs.size(); i++) {
459 tabs.set(i, new Integer(getTab(i) + delta));
460 }
461 }
462 }
463 }
464
465 public int getTab(int num) {
466 return ((Integer)tabs.get(num)).intValue();
467 }
468
469 public String toString() {
470 StringBuffer ret = new StringBuffer(getClass().getName() + " {");
471 for (int i=0; i<tabs.size(); i++) {
472 ret.append(tabs.get(i));
473 if (i < tabs.size()-1) ret.append(',');
474 }
475 ret.append('}');
476 return ret.toString();
477 }
478
479 public static void main(String[] args) {
480 Ruler r = new Ruler();
481 r.setTab(0,10);
482 r.setTab(1,20);
483 r.setTab(2,30);
484 System.out.println(r);
485 r.setTab(1,25);
486 System.out.println(r);
487 System.out.println(r.getTab(0));
488 }
489 }