1 //
2 // ToolStripSplitStackLayout.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 using System;
30 using System.Drawing;
31 using System.Windows.Forms.Layout;
32 
33 namespace System.Windows.Forms
34 {
35 	class ToolStripSplitStackLayout : LayoutEngine
36 	{
Layout(object container, LayoutEventArgs args)37 		public override bool Layout (object container, LayoutEventArgs args)
38 		{
39 			if (container is ToolStrip)
40 			{
41 				ToolStrip ts = (ToolStrip)container;
42 
43 				if (ts.Items == null)
44 					return false;
45 
46 				Rectangle ts_rect = ts.DisplayRectangle;
47 
48 				// Mono hasn't yet implemented ToolStripLayoutStyle.Table, so it comes here
49 				// for layout.  The default (minimal) Table layout is 1 column, any number of rows,
50 				// which translates effectively to Vertical orientation.
51 				if (ts.Orientation == Orientation.Horizontal && ts.LayoutStyle != ToolStripLayoutStyle.Table)
52 					LayoutHorizontalToolStrip (ts, ts_rect);
53 				else
54 					LayoutVerticalToolStrip (ts, ts_rect);
55 
56 				return false;
57 			} else	{
58 				ToolStripContentPanel ts = (ToolStripContentPanel)container;
59 				int x = ts.DisplayRectangle.Left;
60 				int y = ts.DisplayRectangle.Top;
61 
62 				foreach (ToolStrip tsi in ts.Controls) {
63 					Rectangle new_bounds = new Rectangle ();
64 
65 					x += tsi.Margin.Left;
66 
67 					new_bounds.Location = new Point (x, y + tsi.Margin.Top);
68 					new_bounds.Height = ts.DisplayRectangle.Height - tsi.Margin.Vertical;
69 					new_bounds.Width = tsi.GetToolStripPreferredSize (new Size (0, new_bounds.Height)).Width;
70 
71 					tsi.Width = new_bounds.Width + 12;
72 
73 					x += new_bounds.Width + tsi.Margin.Right;
74 				}
75 			}
76 
77 			return false;
78 		}
79 
LayoutHorizontalToolStrip(ToolStrip ts, Rectangle bounds)80 		private void LayoutHorizontalToolStrip (ToolStrip ts, Rectangle bounds)
81 		{
82 			//if (!ts.Visible) return;
83 
84 			ToolStripItemOverflow[] overflow = new ToolStripItemOverflow[ts.Items.Count];
85 			ToolStripItemPlacement[] placement = new ToolStripItemPlacement[ts.Items.Count];
86 			Size proposedSize = new Size (0, bounds.Height);
87 			int[] widths = new int[ts.Items.Count];
88 			int total_width = 0;
89 			int toolstrip_width = bounds.Width;
90 			int i = 0;
91 			bool can_overflow = ts.CanOverflow & !(ts is MenuStrip) & !(ts is StatusStrip);
92 			bool need_overflow = false;
93 
94 			foreach (ToolStripItem tsi in ts.Items) {
95 				overflow[i] = tsi.Overflow;
96 				placement[i] = tsi.Overflow == ToolStripItemOverflow.Always ? ToolStripItemPlacement.Overflow : ToolStripItemPlacement.Main;
97 				widths[i] = tsi.GetPreferredSize (proposedSize).Width + tsi.Margin.Horizontal;
98 				if (!tsi.Available)
99 					placement[i] = ToolStripItemPlacement.None;
100 				total_width += placement[i] == ToolStripItemPlacement.Main ? widths[i] : 0;
101 
102 				if (placement[i] == ToolStripItemPlacement.Overflow)
103 					need_overflow = true;
104 				i++;
105 			}
106 
107 			// This is needed for a button set to Overflow = Always
108 			if (need_overflow) {
109 				ts.OverflowButton.Visible = true;
110 				ts.OverflowButton.SetBounds (new Rectangle (ts.Width - 16, 0, 16, ts.Height));
111 				toolstrip_width -= ts.OverflowButton.Width;
112 			} else
113 				ts.OverflowButton.Visible = false;
114 
115 			while (total_width > toolstrip_width) {
116 				// If we can overflow, get our overflow button setup, and subtract it's width
117 				// from our available width
118 				if (can_overflow && !ts.OverflowButton.Visible) {
119 					ts.OverflowButton.Visible = true;
120 					ts.OverflowButton.SetBounds (new Rectangle (ts.Width - 16, 0, 16, ts.Height));
121 					toolstrip_width -= ts.OverflowButton.Width;
122 				}
123 
124 				bool removed_one = false;
125 
126 				// Start at the right, removing Overflow.AsNeeded first
127 				for (int j = widths.Length - 1; j >= 0; j--)
128 					if (overflow[j] == ToolStripItemOverflow.AsNeeded && placement[j] == ToolStripItemPlacement.Main) {
129 						placement[j] = ToolStripItemPlacement.Overflow;
130 						total_width -= widths[j];
131 						removed_one = true;
132 						break;
133 					}
134 
135 				// If we didn't remove any AsNeeded ones, we have to start removing Never ones
136 				// These are not put on the Overflow, they are simply not shown
137 				if (!removed_one)
138 					for (int j = widths.Length - 1; j >= 0; j--)
139 						if (overflow[j] == ToolStripItemOverflow.Never && placement[j] == ToolStripItemPlacement.Main) {
140 							placement[j] = ToolStripItemPlacement.None;
141 							total_width -= widths[j];
142 							removed_one = true;
143 							break;
144 						}
145 
146 				// There's nothing left to remove, break or we will loop forever
147 				if (!removed_one)
148 					break;
149 			}
150 
151 			i = 0;
152 			Point start_layout_pointer = new Point (ts.DisplayRectangle.Left, ts.DisplayRectangle.Top);
153 			Point end_layout_pointer = new Point (ts.DisplayRectangle.Right, ts.DisplayRectangle.Top);
154 			int button_height = ts.DisplayRectangle.Height;
155 
156 			// Now we should know where everything goes, so lay everything out
157 			foreach (ToolStripItem tsi in ts.Items) {
158 				tsi.SetPlacement (placement[i]);
159 
160 				if (placement[i] == ToolStripItemPlacement.Main) {
161 					if (tsi.Alignment == ToolStripItemAlignment.Left) {
162 						tsi.SetBounds (new Rectangle (start_layout_pointer.X + tsi.Margin.Left, start_layout_pointer.Y + tsi.Margin.Top, widths[i] - tsi.Margin.Horizontal, button_height - tsi.Margin.Vertical));
163 						start_layout_pointer.X += widths[i];
164 					} else {
165 						tsi.SetBounds (new Rectangle (end_layout_pointer.X - tsi.Margin.Right - tsi.Width, end_layout_pointer.Y + tsi.Margin.Top, widths[i] - tsi.Margin.Horizontal, button_height - tsi.Margin.Vertical));
166 						end_layout_pointer.X -= widths[i];
167 					}
168 				}
169 
170 				i++;
171 			}
172 		}
173 
LayoutVerticalToolStrip(ToolStrip ts, Rectangle bounds)174 		private void LayoutVerticalToolStrip (ToolStrip ts, Rectangle bounds)
175 		{
176 			if (!ts.Visible) return;
177 
178 			ToolStripItemOverflow[] overflow = new ToolStripItemOverflow[ts.Items.Count];
179 			ToolStripItemPlacement[] placement = new ToolStripItemPlacement[ts.Items.Count];
180 			Size proposedSize = new Size (bounds.Width, 0);
181 			int[] heights = new int[ts.Items.Count];
182 			int[] widths = new int[ts.Items.Count];	// needed if ts.LayoutStyle == ToolStripLayoutStyle.Table
183 			int total_height = 0;
184 			int toolstrip_height = bounds.Height;
185 			int i = 0;
186 			bool can_overflow = ts.CanOverflow & !(ts is MenuStrip) & !(ts is StatusStrip);
187 
188 			foreach (ToolStripItem tsi in ts.Items) {
189 				overflow[i] = tsi.Overflow;
190 				placement[i] = tsi.Overflow == ToolStripItemOverflow.Always ? ToolStripItemPlacement.Overflow : ToolStripItemPlacement.Main;
191 				var size = tsi.GetPreferredSize (proposedSize);
192 				heights[i] = size.Height + tsi.Margin.Vertical;
193 				widths[i] = size.Width + tsi.Margin.Horizontal;
194 				if (!tsi.Available)
195 					placement[i] = ToolStripItemPlacement.None;
196 				total_height += placement[i] == ToolStripItemPlacement.Main ? heights[i] : 0;
197 				i++;
198 			}
199 
200 			ts.OverflowButton.Visible = false;
201 
202 			while (total_height > toolstrip_height) {
203 				// If we can overflow, get our overflow button setup, and subtract it's width
204 				// from our available width
205 				if (can_overflow && !ts.OverflowButton.Visible) {
206 					ts.OverflowButton.Visible = true;
207 					ts.OverflowButton.SetBounds (new Rectangle (0, ts.Height - 16,  ts.Width, 16));
208 					toolstrip_height -= ts.OverflowButton.Height;
209 				}
210 
211 				bool removed_one = false;
212 
213 				// Start at the right, removing Overflow.AsNeeded first
214 				for (int j = heights.Length - 1; j >= 0; j--)
215 					if (overflow[j] == ToolStripItemOverflow.AsNeeded && placement[j] == ToolStripItemPlacement.Main) {
216 						placement[j] = ToolStripItemPlacement.Overflow;
217 						total_height -= heights[j];
218 						removed_one = true;
219 						break;
220 					}
221 
222 				// If we didn't remove any AsNeeded ones, we have to start removing Never ones
223 				// These are not put on the Overflow, they are simply not shown
224 				if (!removed_one)
225 					for (int j = heights.Length - 1; j >= 0; j--)
226 						if (overflow[j] == ToolStripItemOverflow.Never && placement[j] == ToolStripItemPlacement.Main) {
227 							placement[j] = ToolStripItemPlacement.None;
228 							total_height -= heights[j];
229 							removed_one = true;
230 							break;
231 						}
232 
233 				// There's nothing left to remove, break or we will loop forever
234 				if (!removed_one)
235 					break;
236 			}
237 
238 			i = 0;
239 			Point start_layout_pointer = new Point (ts.DisplayRectangle.Left, ts.DisplayRectangle.Top);
240 			Point end_layout_pointer = new Point (ts.DisplayRectangle.Left, ts.DisplayRectangle.Bottom);
241 			int button_width = ts.DisplayRectangle.Width;
242 
243 			// Now we should know where everything goes, so lay everything out
244 			foreach (ToolStripItem tsi in ts.Items) {
245 				tsi.SetPlacement (placement[i]);
246 				// Table layout is defined to lay out items flush left.
247 				if (ts.LayoutStyle == ToolStripLayoutStyle.Table)
248 					button_width = widths[i];
249 
250 				if (placement[i] == ToolStripItemPlacement.Main) {
251 					if (tsi.Alignment == ToolStripItemAlignment.Left) {
252 						tsi.SetBounds (new Rectangle (start_layout_pointer.X + tsi.Margin.Left, start_layout_pointer.Y + tsi.Margin.Top, button_width - tsi.Margin.Horizontal, heights[i] - tsi.Margin.Vertical));
253 						start_layout_pointer.Y += heights[i];
254 					} else {
255 						tsi.SetBounds (new Rectangle (end_layout_pointer.X + tsi.Margin.Left, end_layout_pointer.Y - tsi.Margin.Bottom - tsi.Height, button_width - tsi.Margin.Horizontal, heights[i] - tsi.Margin.Vertical));
256 						start_layout_pointer.Y += heights[i];
257 					}
258 				}
259 
260 				i++;
261 			}
262 		}
263 	}
264 }
265