]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.utils.ui/src/org/simantics/utils/ui/awt/WrapLayout.java
Introduce WrapLayout to replace FlowLayout
[simantics/platform.git] / bundles / org.simantics.utils.ui / src / org / simantics / utils / ui / awt / WrapLayout.java
1 /*******************************************************************************\r
2  * Copyright (c) 2016 Association for Decentralized Information Management\r
3  * in Industry THTH ry.\r
4  * All rights reserved. This program and the accompanying materials\r
5  * are made available under the terms of the Eclipse Public License v1.0\r
6  * which accompanies this distribution, and is available at\r
7  * http://www.eclipse.org/legal/epl-v10.html\r
8  *\r
9  * Contributors:\r
10  *     Semantum Oy - initial API and implementation, courtesy of Rob Camick\r
11  *                   https://tips4java.wordpress.com/2008/11/06/wrap-layout/\r
12  *******************************************************************************/\r
13 package org.simantics.utils.ui.awt;\r
14 \r
15 import java.awt.Component;\r
16 import java.awt.Container;\r
17 import java.awt.Dimension;\r
18 import java.awt.FlowLayout;\r
19 import java.awt.Insets;\r
20 \r
21 import javax.swing.JScrollPane;\r
22 import javax.swing.SwingUtilities;\r
23 \r
24 /**\r
25  *  FlowLayout subclass that fully supports wrapping of components.\r
26  */\r
27 public class WrapLayout extends FlowLayout\r
28 {\r
29         /**\r
30          * \r
31          */\r
32         private static final long serialVersionUID = -4312800883442354753L;\r
33 \r
34         /**\r
35         * Constructs a new <code>WrapLayout</code> with a left\r
36         * alignment and a default 5-unit horizontal and vertical gap.\r
37         */\r
38         public WrapLayout()\r
39         {\r
40                 super();\r
41         }\r
42 \r
43         /**\r
44         * Constructs a new <code>FlowLayout</code> with the specified\r
45         * alignment and a default 5-unit horizontal and vertical gap.\r
46         * The value of the alignment argument must be one of\r
47         * <code>WrapLayout</code>, <code>WrapLayout</code>,\r
48         * or <code>WrapLayout</code>.\r
49         * @param align the alignment value\r
50         */\r
51         public WrapLayout(int align)\r
52         {\r
53                 super(align);\r
54         }\r
55 \r
56         /**\r
57         * Creates a new flow layout manager with the indicated alignment\r
58         * and the indicated horizontal and vertical gaps.\r
59         * <p>\r
60         * The value of the alignment argument must be one of\r
61         * <code>WrapLayout</code>, <code>WrapLayout</code>,\r
62         * or <code>WrapLayout</code>.\r
63         * @param align the alignment value\r
64         * @param hgap the horizontal gap between components\r
65         * @param vgap the vertical gap between components\r
66         */\r
67         public WrapLayout(int align, int hgap, int vgap)\r
68         {\r
69                 super(align, hgap, vgap);\r
70         }\r
71 \r
72         /**\r
73         * Returns the preferred dimensions for this layout given the\r
74         * <i>visible</i> components in the specified target container.\r
75         * @param target the component which needs to be laid out\r
76         * @return the preferred dimensions to lay out the\r
77         * subcomponents of the specified container\r
78         */\r
79         @Override\r
80         public Dimension preferredLayoutSize(Container target)\r
81         {\r
82                 return layoutSize(target, true);\r
83         }\r
84 \r
85         /**\r
86         * Returns the minimum dimensions needed to layout the <i>visible</i>\r
87         * components contained in the specified target container.\r
88         * @param target the component which needs to be laid out\r
89         * @return the minimum dimensions to lay out the\r
90         * subcomponents of the specified container\r
91         */\r
92         @Override\r
93         public Dimension minimumLayoutSize(Container target)\r
94         {\r
95                 Dimension minimum = layoutSize(target, false);\r
96                 minimum.width -= (getHgap() + 1);\r
97                 return minimum;\r
98         }\r
99 \r
100         /**\r
101         * Returns the minimum or preferred dimension needed to layout the target\r
102         * container.\r
103         *\r
104         * @param target target to get layout size for\r
105         * @param preferred should preferred size be calculated\r
106         * @return the dimension to layout the target container\r
107         */\r
108         private Dimension layoutSize(Container target, boolean preferred)\r
109         {\r
110         synchronized (target.getTreeLock())\r
111         {\r
112                 //  Each row must fit with the width allocated to the containter.\r
113                 //  When the container width = 0, the preferred width of the container\r
114                 //  has not yet been calculated so lets ask for the maximum.\r
115 \r
116                 int targetWidth = target.getSize().width;\r
117                 Container container = target;\r
118 \r
119                 while (container.getSize().width == 0 && container.getParent() != null)\r
120                 {\r
121                         container = container.getParent();\r
122                 }\r
123 \r
124                 targetWidth = container.getSize().width;\r
125 \r
126                 if (targetWidth == 0)\r
127                         targetWidth = Integer.MAX_VALUE;\r
128 \r
129                 int hgap = getHgap();\r
130                 int vgap = getVgap();\r
131                 Insets insets = target.getInsets();\r
132                 int horizontalInsetsAndGap = insets.left + insets.right + (hgap * 2);\r
133                 int maxWidth = targetWidth - horizontalInsetsAndGap;\r
134 \r
135                 //  Fit components into the allowed width\r
136 \r
137                 Dimension dim = new Dimension(0, 0);\r
138                 int rowWidth = 0;\r
139                 int rowHeight = 0;\r
140 \r
141                 int nmembers = target.getComponentCount();\r
142 \r
143                 for (int i = 0; i < nmembers; i++)\r
144                 {\r
145                         Component m = target.getComponent(i);\r
146 \r
147                         if (m.isVisible())\r
148                         {\r
149                                 Dimension d = preferred ? m.getPreferredSize() : m.getMinimumSize();\r
150 \r
151                                 //  Can't add the component to current row. Start a new row.\r
152 \r
153                                 if (rowWidth + d.width > maxWidth)\r
154                                 {\r
155                                         addRow(dim, rowWidth, rowHeight);\r
156                                         rowWidth = 0;\r
157                                         rowHeight = 0;\r
158                                 }\r
159 \r
160                                 //  Add a horizontal gap for all components after the first\r
161 \r
162                                 if (rowWidth != 0)\r
163                                 {\r
164                                         rowWidth += hgap;\r
165                                 }\r
166 \r
167                                 rowWidth += d.width;\r
168                                 rowHeight = Math.max(rowHeight, d.height);\r
169                         }\r
170                 }\r
171 \r
172                 addRow(dim, rowWidth, rowHeight);\r
173 \r
174                 dim.width += horizontalInsetsAndGap;\r
175                 dim.height += insets.top + insets.bottom + vgap * 2;\r
176 \r
177                 //      When using a scroll pane or the DecoratedLookAndFeel we need to\r
178                 //  make sure the preferred size is less than the size of the\r
179                 //  target containter so shrinking the container size works\r
180                 //  correctly. Removing the horizontal gap is an easy way to do this.\r
181 \r
182                 Container scrollPane = SwingUtilities.getAncestorOfClass(JScrollPane.class, target);\r
183 \r
184                 if (scrollPane != null && target.isValid())\r
185                 {\r
186                         dim.width -= (hgap + 1);\r
187                 }\r
188 \r
189                 return dim;\r
190         }\r
191         }\r
192 \r
193         /*\r
194          *  A new row has been completed. Use the dimensions of this row\r
195          *  to update the preferred size for the container.\r
196          *\r
197          *  @param dim update the width and height when appropriate\r
198          *  @param rowWidth the width of the row to add\r
199          *  @param rowHeight the height of the row to add\r
200          */\r
201         private void addRow(Dimension dim, int rowWidth, int rowHeight)\r
202         {\r
203                 dim.width = Math.max(dim.width, rowWidth);\r
204 \r
205                 if (dim.height > 0)\r
206                 {\r
207                         dim.height += getVgap();\r
208                 }\r
209 \r
210                 dim.height += rowHeight;\r
211         }\r
212 }