+/*******************************************************************************\r
+ * Copyright (c) 2016 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ * Semantum Oy - initial API and implementation, courtesy of Rob Camick\r
+ * https://tips4java.wordpress.com/2008/11/06/wrap-layout/\r
+ *******************************************************************************/\r
+package org.simantics.utils.ui.awt;\r
+\r
+import java.awt.Component;\r
+import java.awt.Container;\r
+import java.awt.Dimension;\r
+import java.awt.FlowLayout;\r
+import java.awt.Insets;\r
+\r
+import javax.swing.JScrollPane;\r
+import javax.swing.SwingUtilities;\r
+\r
+/**\r
+ * FlowLayout subclass that fully supports wrapping of components.\r
+ */\r
+public class WrapLayout extends FlowLayout\r
+{\r
+ /**\r
+ * \r
+ */\r
+ private static final long serialVersionUID = -4312800883442354753L;\r
+\r
+ /**\r
+ * Constructs a new <code>WrapLayout</code> with a left\r
+ * alignment and a default 5-unit horizontal and vertical gap.\r
+ */\r
+ public WrapLayout()\r
+ {\r
+ super();\r
+ }\r
+\r
+ /**\r
+ * Constructs a new <code>FlowLayout</code> with the specified\r
+ * alignment and a default 5-unit horizontal and vertical gap.\r
+ * The value of the alignment argument must be one of\r
+ * <code>WrapLayout</code>, <code>WrapLayout</code>,\r
+ * or <code>WrapLayout</code>.\r
+ * @param align the alignment value\r
+ */\r
+ public WrapLayout(int align)\r
+ {\r
+ super(align);\r
+ }\r
+\r
+ /**\r
+ * Creates a new flow layout manager with the indicated alignment\r
+ * and the indicated horizontal and vertical gaps.\r
+ * <p>\r
+ * The value of the alignment argument must be one of\r
+ * <code>WrapLayout</code>, <code>WrapLayout</code>,\r
+ * or <code>WrapLayout</code>.\r
+ * @param align the alignment value\r
+ * @param hgap the horizontal gap between components\r
+ * @param vgap the vertical gap between components\r
+ */\r
+ public WrapLayout(int align, int hgap, int vgap)\r
+ {\r
+ super(align, hgap, vgap);\r
+ }\r
+\r
+ /**\r
+ * Returns the preferred dimensions for this layout given the\r
+ * <i>visible</i> components in the specified target container.\r
+ * @param target the component which needs to be laid out\r
+ * @return the preferred dimensions to lay out the\r
+ * subcomponents of the specified container\r
+ */\r
+ @Override\r
+ public Dimension preferredLayoutSize(Container target)\r
+ {\r
+ return layoutSize(target, true);\r
+ }\r
+\r
+ /**\r
+ * Returns the minimum dimensions needed to layout the <i>visible</i>\r
+ * components contained in the specified target container.\r
+ * @param target the component which needs to be laid out\r
+ * @return the minimum dimensions to lay out the\r
+ * subcomponents of the specified container\r
+ */\r
+ @Override\r
+ public Dimension minimumLayoutSize(Container target)\r
+ {\r
+ Dimension minimum = layoutSize(target, false);\r
+ minimum.width -= (getHgap() + 1);\r
+ return minimum;\r
+ }\r
+\r
+ /**\r
+ * Returns the minimum or preferred dimension needed to layout the target\r
+ * container.\r
+ *\r
+ * @param target target to get layout size for\r
+ * @param preferred should preferred size be calculated\r
+ * @return the dimension to layout the target container\r
+ */\r
+ private Dimension layoutSize(Container target, boolean preferred)\r
+ {\r
+ synchronized (target.getTreeLock())\r
+ {\r
+ // Each row must fit with the width allocated to the containter.\r
+ // When the container width = 0, the preferred width of the container\r
+ // has not yet been calculated so lets ask for the maximum.\r
+\r
+ int targetWidth = target.getSize().width;\r
+ Container container = target;\r
+\r
+ while (container.getSize().width == 0 && container.getParent() != null)\r
+ {\r
+ container = container.getParent();\r
+ }\r
+\r
+ targetWidth = container.getSize().width;\r
+\r
+ if (targetWidth == 0)\r
+ targetWidth = Integer.MAX_VALUE;\r
+\r
+ int hgap = getHgap();\r
+ int vgap = getVgap();\r
+ Insets insets = target.getInsets();\r
+ int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2);\r
+ int maxWidth = targetWidth - horizontalInsetsAndGap;\r
+\r
+ // Fit components into the allowed width\r
+\r
+ Dimension dim = new Dimension(0, 0);\r
+ int rowWidth = 0;\r
+ int rowHeight = 0;\r
+\r
+ int nmembers = target.getComponentCount();\r
+\r
+ for (int i = 0; i < nmembers; i++)\r
+ {\r
+ Component m = target.getComponent(i);\r
+\r
+ if (m.isVisible())\r
+ {\r
+ Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize();\r
+\r
+ // Can't add the component to current row. Start a new row.\r
+\r
+ if (rowWidth + d.width > maxWidth)\r
+ {\r
+ addRow(dim, rowWidth, rowHeight);\r
+ rowWidth = 0;\r
+ rowHeight = 0;\r
+ }\r
+\r
+ // Add a horizontal gap for all components after the first\r
+\r
+ if (rowWidth != 0)\r
+ {\r
+ rowWidth += hgap;\r
+ }\r
+\r
+ rowWidth += d.width;\r
+ rowHeight = Math.max(rowHeight, d.height);\r
+ }\r
+ }\r
+\r
+ addRow(dim, rowWidth, rowHeight);\r
+\r
+ dim.width += horizontalInsetsAndGap;\r
+ dim.height += insets.top + insets.bottom + vgap * 2;\r
+\r
+ // When using a scroll pane or the DecoratedLookAndFeel we need to\r
+ // make sure the preferred size is less than the size of the\r
+ // target containter so shrinking the container size works\r
+ // correctly. Removing the horizontal gap is an easy way to do this.\r
+\r
+ Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target);\r
+\r
+ if (scrollPane != null && target.isValid())\r
+ {\r
+ dim.width -= (hgap + 1);\r
+ }\r
+\r
+ return dim;\r
+ }\r
+ }\r
+\r
+ /*\r
+ * A new row has been completed. Use the dimensions of this row\r
+ * to update the preferred size for the container.\r
+ *\r
+ * @param dim update the width and height when appropriate\r
+ * @param rowWidth the width of the row to add\r
+ * @param rowHeight the height of the row to add\r
+ */\r
+ private void addRow(Dimension dim, int rowWidth, int rowHeight)\r
+ {\r
+ dim.width = Math.max(dim.width, rowWidth);\r
+\r
+ if (dim.height > 0)\r
+ {\r
+ dim.height += getVgap();\r
+ }\r
+\r
+ dim.height += rowHeight;\r
+ }\r
+}
\ No newline at end of file