1 /*******************************************************************************
2 * Copyright (c) 2016 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * Semantum Oy - initial API and implementation, courtesy of Rob Camick
11 * https://tips4java.wordpress.com/2008/11/06/wrap-layout/
12 *******************************************************************************/
13 package org.simantics.utils.ui.awt;
15 import java.awt.Component;
16 import java.awt.Container;
17 import java.awt.Dimension;
18 import java.awt.FlowLayout;
19 import java.awt.Insets;
21 import javax.swing.JScrollPane;
22 import javax.swing.SwingUtilities;
25 * FlowLayout subclass that fully supports wrapping of components.
27 public class WrapLayout extends FlowLayout
32 private static final long serialVersionUID = -4312800883442354753L;
35 * Constructs a new <code>WrapLayout</code> with a left
36 * alignment and a default 5-unit horizontal and vertical gap.
44 * Constructs a new <code>FlowLayout</code> with the specified
45 * alignment and a default 5-unit horizontal and vertical gap.
46 * The value of the alignment argument must be one of
47 * <code>WrapLayout</code>, <code>WrapLayout</code>,
48 * or <code>WrapLayout</code>.
49 * @param align the alignment value
51 public WrapLayout(int align)
57 * Creates a new flow layout manager with the indicated alignment
58 * and the indicated horizontal and vertical gaps.
60 * The value of the alignment argument must be one of
61 * <code>WrapLayout</code>, <code>WrapLayout</code>,
62 * or <code>WrapLayout</code>.
63 * @param align the alignment value
64 * @param hgap the horizontal gap between components
65 * @param vgap the vertical gap between components
67 public WrapLayout(int align, int hgap, int vgap)
69 super(align, hgap, vgap);
73 * Returns the preferred dimensions for this layout given the
74 * <i>visible</i> components in the specified target container.
75 * @param target the component which needs to be laid out
76 * @return the preferred dimensions to lay out the
77 * subcomponents of the specified container
80 public Dimension preferredLayoutSize(Container target)
82 return layoutSize(target, true);
86 * Returns the minimum dimensions needed to layout the <i>visible</i>
87 * components contained in the specified target container.
88 * @param target the component which needs to be laid out
89 * @return the minimum dimensions to lay out the
90 * subcomponents of the specified container
93 public Dimension minimumLayoutSize(Container target)
95 Dimension minimum = layoutSize(target, false);
96 minimum.width -= (getHgap() + 1);
101 * Returns the minimum or preferred dimension needed to layout the target
104 * @param target target to get layout size for
105 * @param preferred should preferred size be calculated
106 * @return the dimension to layout the target container
108 private Dimension layoutSize(Container target, boolean preferred)
110 synchronized (target.getTreeLock())
112 // Each row must fit with the width allocated to the containter.
113 // When the container width = 0, the preferred width of the container
114 // has not yet been calculated so lets ask for the maximum.
116 int targetWidth = target.getSize().width;
117 Container container = target;
119 while (container.getSize().width == 0 && container.getParent() != null)
121 container = container.getParent();
124 targetWidth = container.getSize().width;
126 if (targetWidth == 0)
127 targetWidth = Integer.MAX_VALUE;
129 int hgap = getHgap();
130 int vgap = getVgap();
131 Insets insets = target.getInsets();
132 int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2);
133 int maxWidth = targetWidth - horizontalInsetsAndGap;
135 // Fit components into the allowed width
137 Dimension dim = new Dimension(0, 0);
141 int nmembers = target.getComponentCount();
143 for (int i = 0; i < nmembers; i++)
145 Component m = target.getComponent(i);
149 Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize();
151 // Can't add the component to current row. Start a new row.
153 if (rowWidth + d.width > maxWidth)
155 addRow(dim, rowWidth, rowHeight);
160 // Add a horizontal gap for all components after the first
168 rowHeight = Math.max(rowHeight, d.height);
172 addRow(dim, rowWidth, rowHeight);
174 dim.width += horizontalInsetsAndGap;
175 dim.height += insets.top + insets.bottom + vgap * 2;
177 // When using a scroll pane or the DecoratedLookAndFeel we need to
178 // make sure the preferred size is less than the size of the
179 // target containter so shrinking the container size works
180 // correctly. Removing the horizontal gap is an easy way to do this.
182 Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target);
184 if (scrollPane != null && target.isValid())
186 dim.width -= (hgap + 1);
194 * A new row has been completed. Use the dimensions of this row
195 * to update the preferred size for the container.
197 * @param dim update the width and height when appropriate
198 * @param rowWidth the width of the row to add
199 * @param rowHeight the height of the row to add
201 private void addRow(Dimension dim, int rowWidth, int rowHeight)
203 dim.width = Math.max(dim.width, rowWidth);
207 dim.height += getVgap();
210 dim.height += rowHeight;