New test.
[mono.git] / mcs / class / Managed.Windows.Forms / System.Windows.Forms.Layout / FlowLayout.cs
1 //
2 // FlowLayout.cs
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining
5 // a copy of this software and associated documentation files (the
6 // "Software"), to deal in the Software without restriction, including
7 // without limitation the rights to use, copy, modify, merge, publish,
8 // distribute, sublicense, and/or sell copies of the Software, and to
9 // permit persons to whom the Software is furnished to do so, subject to
10 // the following conditions:
11 // 
12 // The above copyright notice and this permission notice shall be
13 // included in all copies or substantial portions of the Software.
14 // 
15 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 //
23 // Copyright (c) 2006 Jonathan Pobst
24 //
25 // Authors:
26 //      Jonathan Pobst (monkey@jpobst.com)
27 //
28
29 #if NET_2_0
30 using System;
31 using System.Collections.Generic;
32 using System.Drawing;
33
34 namespace System.Windows.Forms.Layout
35 {
36         class FlowLayout : LayoutEngine
37         {
38                 public FlowLayout ()
39                 {
40                 }
41
42                 public override void InitLayout (object child, BoundsSpecified specified)
43                 {
44                         base.InitLayout (child, specified);
45                 }
46
47                 public override bool Layout (object container, LayoutEventArgs args)
48                 {
49                         Control parent = container as Control;
50                         FlowLayoutSettings settings = (parent as FlowLayoutPanel).LayoutSettings;
51
52                         // Nothing to layout, exit method
53                         if (parent.Controls.Count == 0) return false;
54
55                         // Use DisplayRectangle so that parent.Padding is honored.
56                         Rectangle parentDisplayRectangle = parent.DisplayRectangle;
57                         Point currentLocation;
58
59                         // Set our starting point based on flow direction
60                         switch (settings.FlowDirection) {
61                                 case FlowDirection.BottomUp:
62                                         currentLocation = new Point (parentDisplayRectangle.Left, parentDisplayRectangle.Bottom);
63                                         break;
64                                 case FlowDirection.LeftToRight:
65                                 case FlowDirection.TopDown:
66                                 default:
67                                         currentLocation = parentDisplayRectangle.Location;
68                                         break;
69                                 case FlowDirection.RightToLeft:
70                                         currentLocation = new Point (parentDisplayRectangle.Right, parentDisplayRectangle.Top);
71                                         break;
72                         }
73
74                         bool forceFlowBreak = false;
75
76                         List<Control> rowControls = new List<Control> ();
77
78                         foreach (Control c in parent.Controls) {
79                                 // Only apply layout to visible controls.
80                                 if (!c.Visible) { continue; }
81
82                                 // Resize any AutoSize controls to their preferred size
83                                 if (c.AutoSize == true)
84                                         c.Size = c.GetPreferredSize (c.Size);
85
86                                 switch (settings.FlowDirection) {
87                                         case FlowDirection.BottomUp:
88                                                 // Decide if it's time to start a new column
89                                                 // - Our settings must be WrapContents, and we ran out of room or the previous control's FlowBreak == true
90                                                 if (settings.WrapContents)
91                                                         if ((currentLocation.Y) < (c.Height + c.Margin.Top + c.Margin.Bottom) || forceFlowBreak) {
92
93                                                                 currentLocation.X = FinishColumn (rowControls);
94                                                                 currentLocation.Y = parentDisplayRectangle.Bottom;
95
96                                                                 forceFlowBreak = false;
97                                                                 rowControls.Clear ();
98                                                         }
99
100                                                 // Offset the right margin and set the control to our point
101                                                 currentLocation.Offset (0, c.Margin.Bottom * -1);
102                                                 c.Location = new Point (currentLocation.X + c.Margin.Left, currentLocation.Y - c.Height);
103
104                                                 // Update our location pointer
105                                                 currentLocation.Y -= (c.Height + c.Margin.Top);
106                                                 break;
107                                         case FlowDirection.LeftToRight:
108                                         default:
109                                                 // Decide if it's time to start a new row
110                                                 // - Our settings must be WrapContents, and we ran out of room or the previous control's FlowBreak == true
111                                                 if (settings.WrapContents)
112                                                         if ((parentDisplayRectangle.Width - currentLocation.X) < (c.Width + c.Margin.Left + c.Margin.Right) || forceFlowBreak) {
113
114                                                                 currentLocation.Y = FinishRow (rowControls);
115                                                                 currentLocation.X = parentDisplayRectangle.Left;
116
117                                                                 forceFlowBreak = false;
118                                                                 rowControls.Clear ();
119                                                         }
120
121                                                 // Offset the left margin and set the control to our point
122                                                 currentLocation.Offset (c.Margin.Left, 0);
123                                                 c.Location = new Point (currentLocation.X, currentLocation.Y + c.Margin.Top);
124
125                                                 // Update our location pointer
126                                                 currentLocation.X += c.Width + c.Margin.Right;
127                                                 break;
128                                         case FlowDirection.RightToLeft:
129                                                 // Decide if it's time to start a new row
130                                                 // - Our settings must be WrapContents, and we ran out of room or the previous control's FlowBreak == true
131                                                 if (settings.WrapContents)
132                                                         if ((currentLocation.X) < (c.Width + c.Margin.Left + c.Margin.Right) || forceFlowBreak) {
133
134                                                                 currentLocation.Y = FinishRow (rowControls);
135                                                                 currentLocation.X = parentDisplayRectangle.Right;
136
137                                                                 forceFlowBreak = false;
138                                                                 rowControls.Clear ();
139                                                         }
140
141                                                 // Offset the right margin and set the control to our point
142                                                 currentLocation.Offset (c.Margin.Right * -1, 0);
143                                                 c.Location = new Point (currentLocation.X - c.Width, currentLocation.Y + c.Margin.Top);
144
145                                                 // Update our location pointer
146                                                 currentLocation.X -= (c.Width + c.Margin.Left);
147                                                 break;
148                                         case FlowDirection.TopDown:
149                                                 // Decide if it's time to start a new column
150                                                 // - Our settings must be WrapContents, and we ran out of room or the previous control's FlowBreak == true
151                                                 if (settings.WrapContents)
152                                                         if ((parentDisplayRectangle.Height - currentLocation.Y) < (c.Height + c.Margin.Top + c.Margin.Bottom) || forceFlowBreak) {
153
154                                                                 currentLocation.X = FinishColumn (rowControls);
155                                                                 currentLocation.Y = parentDisplayRectangle.Top;
156
157                                                                 forceFlowBreak = false;
158                                                                 rowControls.Clear ();
159                                                         }
160
161                                                 // Offset the top margin and set the control to our point
162                                                 currentLocation.Offset (0, c.Margin.Top);
163                                                 c.Location = new Point (currentLocation.X + c.Margin.Left, currentLocation.Y);
164
165                                                 // Update our location pointer
166                                                 currentLocation.Y += c.Height + c.Margin.Bottom;
167                                                 break;
168                                 }
169                                 // Add it to our list of things to adjust the second dimension of
170                                 rowControls.Add (c);
171
172                                 // If user set a flowbreak on this control, it will be the last one in this row/column
173                                 if (settings.GetFlowBreak (c))
174                                         forceFlowBreak = true;
175                         }
176
177                         // Set the control heights/widths for the last row/column
178                         if (settings.FlowDirection == FlowDirection.LeftToRight || settings.FlowDirection == FlowDirection.RightToLeft)
179                                 FinishRow (rowControls);
180                         else
181                                 FinishColumn (rowControls);
182
183                         return false;
184                 }
185
186                 // Calculate the heights of the controls, returns the y coordinate of the greatest height it uses
187                 private int FinishRow (List<Control> row)
188                 {
189                         // Nothing to do
190                         if (row.Count == 0) return 0;
191
192                         int rowTop = int.MaxValue;
193                         int rowBottom = 0;
194                         bool allDockFill = true;
195                         bool noAuto = true;
196
197                         // Special semantics if all controls are Dock.Fill/Anchor:Top,Bottom or AutoSize = true
198                         foreach (Control c in row) {
199                                 if (c.Dock != DockStyle.Fill && !((c.Anchor & AnchorStyles.Top) == AnchorStyles.Top && (c.Anchor & AnchorStyles.Bottom) == AnchorStyles.Bottom))
200                                         allDockFill = false;
201                                 if (c.AutoSize == true)
202                                         noAuto = false;
203                         }
204
205                         // Find the tallest control with a concrete height
206                         foreach (Control c in row) {
207                                 if (c.Bottom + c.Margin.Bottom > rowBottom && (c.Dock != DockStyle.Fill) && ((c.Anchor & AnchorStyles.Top) != AnchorStyles.Top || (c.Anchor & AnchorStyles.Bottom) != AnchorStyles.Bottom || c.AutoSize == true))
208                                         rowBottom = c.Bottom + c.Margin.Bottom;
209                                 if (c.Top - c.Margin.Top < rowTop)
210                                         rowTop = c.Top - c.Margin.Top;
211                         }
212
213                         // Find the tallest control that is AutoSize = true
214                         if (rowBottom == 0)
215                                 foreach (Control c in row)
216                                         if (c.Bottom + c.Margin.Bottom > rowBottom && (c.Dock != DockStyle.Fill && c.AutoSize == true))
217                                                 rowBottom = c.Bottom + c.Margin.Bottom;
218
219                         // Find the tallest control that is Dock = Fill
220                         if (rowBottom == 0)
221                                 foreach (Control c in row)
222                                         if (c.Bottom + c.Margin.Bottom > rowBottom && (c.Dock == DockStyle.Fill))
223                                                 rowBottom = c.Bottom + c.Margin.Bottom;
224
225                         // Set the new heights for each control
226                         foreach (Control c in row)
227                                 if (allDockFill && noAuto)
228                                         c.Height = 0;
229                                 else if (c.Dock == DockStyle.Fill || ((c.Anchor & AnchorStyles.Top) == AnchorStyles.Top) && ((c.Anchor & AnchorStyles.Bottom) == AnchorStyles.Bottom))
230                                         c.Height = rowBottom - c.Top - c.Margin.Bottom;
231                                 else if (c.Dock == DockStyle.Bottom || ((c.Anchor & AnchorStyles.Bottom) == AnchorStyles.Bottom))
232                                         c.Top = rowBottom - c.Margin.Bottom - c.Height;
233                                 else if (c.Dock == DockStyle.Top || ((c.Anchor & AnchorStyles.Top) == AnchorStyles.Top))
234                                         continue;
235                                 else
236                                         c.Top = ((rowBottom - rowTop) / 2) - (c.Height / 2) + (int)Math.Floor (((c.Margin.Top - c.Margin.Bottom) / 2.0)) + rowTop;
237
238                         // Return bottom y of this row used
239                         if (rowBottom == 0)
240                                 return rowTop;
241
242                         return rowBottom;
243                 }
244
245                 // Calculate the widths of the controls, returns the x coordinate of the greatest width it uses
246                 private int FinishColumn (List<Control> col)
247                 {
248                         // Nothing to do
249                         if (col.Count == 0) return 0;
250
251                         int rowLeft = int.MaxValue;
252                         int rowRight = 0;
253                         bool allDockFill = true;
254                         bool noAuto = true;
255
256                         // Special semantics if all controls are Dock.Fill/Anchor:Left,Right or AutoSize = true
257                         foreach (Control c in col) {
258                                 if (c.Dock != DockStyle.Fill && !((c.Anchor & AnchorStyles.Left) == AnchorStyles.Left && (c.Anchor & AnchorStyles.Right) == AnchorStyles.Right))
259                                         allDockFill = false;
260                                 if (c.AutoSize == true)
261                                         noAuto = false;
262                         }
263
264                         // Find the widest control with a concrete width
265                         foreach (Control c in col) {
266                                 if (c.Right + c.Margin.Right > rowRight && (c.Dock != DockStyle.Fill) && ((c.Anchor & AnchorStyles.Left) != AnchorStyles.Left || (c.Anchor & AnchorStyles.Right) != AnchorStyles.Right || c.AutoSize == true))
267                                         rowRight = c.Right + c.Margin.Right;
268                                 if (c.Left - c.Margin.Left < rowLeft)
269                                         rowLeft = c.Left - c.Margin.Left;
270                         }
271
272                         // Find the widest control that is AutoSize = true
273                         if (rowRight == 0)
274                                 foreach (Control c in col)
275                                         if (c.Right + c.Margin.Right > rowRight && (c.Dock != DockStyle.Fill && c.AutoSize == true))
276                                                 rowRight = c.Right + c.Margin.Right;
277
278                         // Find the widest control that is Dock = Fill
279                         if (rowRight == 0)
280                                 foreach (Control c in col)
281                                         if (c.Right + c.Margin.Right > rowRight && (c.Dock == DockStyle.Fill))
282                                                 rowRight = c.Right + c.Margin.Right;
283
284                         // Set the new widths for each control
285                         foreach (Control c in col)
286                                 if (allDockFill && noAuto)
287                                         c.Width = 0;
288                                 else if (c.Dock == DockStyle.Fill || ((c.Anchor & AnchorStyles.Left) == AnchorStyles.Left) && ((c.Anchor & AnchorStyles.Right) == AnchorStyles.Right))
289                                         c.Width = rowRight - c.Left - c.Margin.Right;
290                                 else if (c.Dock == DockStyle.Right || ((c.Anchor & AnchorStyles.Right) == AnchorStyles.Right))
291                                         c.Left = rowRight - c.Margin.Right - c.Width;
292                                 else if (c.Dock == DockStyle.Left || ((c.Anchor & AnchorStyles.Left) == AnchorStyles.Left))
293                                         continue;
294                                 else
295                                         c.Left = ((rowRight - rowLeft) / 2) - (c.Width / 2) + (int)Math.Floor (((c.Margin.Left - c.Margin.Right) / 2.0)) + rowLeft;
296
297                         // Return rightmost x of this row used
298                         if (rowRight == 0)
299                                 return rowLeft;
300
301                         return rowRight;
302                 }
303         }
304 }
305 #endif