1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Mozilla Communicator client code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 1998
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * David Hyatt (hyatt@netscape.com)
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either of the GNU General Public License Version 2 or later (the "GPL"),
27 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : //
40 : // Eric Vaughan
41 : // Netscape Communications
42 : //
43 : // See documentation in associated header file
44 : //
45 :
46 : #include "nsStackLayout.h"
47 : #include "nsCOMPtr.h"
48 : #include "nsBoxLayoutState.h"
49 : #include "nsBox.h"
50 : #include "nsBoxFrame.h"
51 : #include "nsGkAtoms.h"
52 : #include "nsIContent.h"
53 : #include "nsINameSpaceManager.h"
54 :
55 : using namespace mozilla;
56 :
57 : nsBoxLayout* nsStackLayout::gInstance = nsnull;
58 :
59 : #define SPECIFIED_LEFT (1 << NS_SIDE_LEFT)
60 : #define SPECIFIED_RIGHT (1 << NS_SIDE_RIGHT)
61 : #define SPECIFIED_TOP (1 << NS_SIDE_TOP)
62 : #define SPECIFIED_BOTTOM (1 << NS_SIDE_BOTTOM)
63 :
64 : nsresult
65 0 : NS_NewStackLayout( nsIPresShell* aPresShell, nsCOMPtr<nsBoxLayout>& aNewLayout)
66 : {
67 0 : if (!nsStackLayout::gInstance) {
68 0 : nsStackLayout::gInstance = new nsStackLayout();
69 0 : NS_IF_ADDREF(nsStackLayout::gInstance);
70 : }
71 : // we have not instance variables so just return our static one.
72 0 : aNewLayout = nsStackLayout::gInstance;
73 0 : return NS_OK;
74 : }
75 :
76 : /*static*/ void
77 1403 : nsStackLayout::Shutdown()
78 : {
79 1403 : NS_IF_RELEASE(gInstance);
80 1403 : }
81 :
82 0 : nsStackLayout::nsStackLayout()
83 : {
84 0 : }
85 :
86 : /*
87 : * Sizing: we are as wide as the widest child plus its left offset
88 : * we are tall as the tallest child plus its top offset.
89 : *
90 : * Only children which have -moz-stack-sizing set to stretch-to-fit
91 : * (the default) will be included in the size computations.
92 : */
93 :
94 : nsSize
95 0 : nsStackLayout::GetPrefSize(nsIBox* aBox, nsBoxLayoutState& aState)
96 : {
97 0 : nsSize prefSize (0, 0);
98 :
99 0 : nsIBox* child = aBox->GetChildBox();
100 0 : while (child) {
101 0 : if (child->GetStyleXUL()->mStretchStack) {
102 0 : nsSize pref = child->GetPrefSize(aState);
103 :
104 0 : AddMargin(child, pref);
105 0 : nsMargin offset;
106 0 : GetOffset(aState, child, offset);
107 0 : pref.width += offset.LeftRight();
108 0 : pref.height += offset.TopBottom();
109 0 : AddLargestSize(prefSize, pref);
110 : }
111 :
112 0 : child = child->GetNextBox();
113 : }
114 :
115 0 : AddBorderAndPadding(aBox, prefSize);
116 :
117 : return prefSize;
118 : }
119 :
120 : nsSize
121 0 : nsStackLayout::GetMinSize(nsIBox* aBox, nsBoxLayoutState& aState)
122 : {
123 0 : nsSize minSize (0, 0);
124 :
125 0 : nsIBox* child = aBox->GetChildBox();
126 0 : while (child) {
127 0 : if (child->GetStyleXUL()->mStretchStack) {
128 0 : nsSize min = child->GetMinSize(aState);
129 :
130 0 : AddMargin(child, min);
131 0 : nsMargin offset;
132 0 : GetOffset(aState, child, offset);
133 0 : min.width += offset.LeftRight();
134 0 : min.height += offset.TopBottom();
135 0 : AddLargestSize(minSize, min);
136 : }
137 :
138 0 : child = child->GetNextBox();
139 : }
140 :
141 0 : AddBorderAndPadding(aBox, minSize);
142 :
143 : return minSize;
144 : }
145 :
146 : nsSize
147 0 : nsStackLayout::GetMaxSize(nsIBox* aBox, nsBoxLayoutState& aState)
148 : {
149 0 : nsSize maxSize (NS_INTRINSICSIZE, NS_INTRINSICSIZE);
150 :
151 0 : nsIBox* child = aBox->GetChildBox();
152 0 : while (child) {
153 0 : if (child->GetStyleXUL()->mStretchStack) {
154 0 : nsSize min = child->GetMinSize(aState);
155 0 : nsSize max = child->GetMaxSize(aState);
156 :
157 0 : max = nsBox::BoundsCheckMinMax(min, max);
158 :
159 0 : AddMargin(child, max);
160 0 : nsMargin offset;
161 0 : GetOffset(aState, child, offset);
162 0 : max.width += offset.LeftRight();
163 0 : max.height += offset.TopBottom();
164 0 : AddSmallestSize(maxSize, max);
165 : }
166 :
167 0 : child = child->GetNextBox();
168 : }
169 :
170 0 : AddBorderAndPadding(aBox, maxSize);
171 :
172 : return maxSize;
173 : }
174 :
175 :
176 : nscoord
177 0 : nsStackLayout::GetAscent(nsIBox* aBox, nsBoxLayoutState& aState)
178 : {
179 0 : nscoord vAscent = 0;
180 :
181 0 : nsIBox* child = aBox->GetChildBox();
182 0 : while (child) {
183 0 : nscoord ascent = child->GetBoxAscent(aState);
184 0 : nsMargin margin;
185 0 : child->GetMargin(margin);
186 0 : ascent += margin.top;
187 0 : if (ascent > vAscent)
188 0 : vAscent = ascent;
189 :
190 0 : child = child->GetNextBox();
191 : }
192 :
193 0 : return vAscent;
194 : }
195 :
196 : PRUint8
197 0 : nsStackLayout::GetOffset(nsBoxLayoutState& aState, nsIBox* aChild, nsMargin& aOffset)
198 : {
199 0 : aOffset = nsMargin(0, 0, 0, 0);
200 :
201 : // get the left, right, top and bottom offsets
202 :
203 : // As an optimization, we cache the fact that we are not positioned to avoid
204 : // wasting time fetching attributes.
205 0 : if (aChild->IsBoxFrame() &&
206 0 : (aChild->GetStateBits() & NS_STATE_STACK_NOT_POSITIONED))
207 0 : return 0;
208 :
209 0 : PRUint8 offsetSpecified = 0;
210 0 : nsIContent* content = aChild->GetContent();
211 0 : if (content) {
212 0 : bool ltr = aChild->GetStyleVisibility()->mDirection == NS_STYLE_DIRECTION_LTR;
213 0 : nsAutoString value;
214 : PRInt32 error;
215 :
216 0 : content->GetAttr(kNameSpaceID_None, nsGkAtoms::start, value);
217 0 : if (!value.IsEmpty()) {
218 0 : value.Trim("%");
219 0 : if (ltr) {
220 : aOffset.left =
221 0 : nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
222 0 : offsetSpecified |= SPECIFIED_LEFT;
223 : } else {
224 : aOffset.right =
225 0 : nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
226 0 : offsetSpecified |= SPECIFIED_RIGHT;
227 : }
228 : }
229 :
230 0 : content->GetAttr(kNameSpaceID_None, nsGkAtoms::end, value);
231 0 : if (!value.IsEmpty()) {
232 0 : value.Trim("%");
233 0 : if (ltr) {
234 : aOffset.right =
235 0 : nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
236 0 : offsetSpecified |= SPECIFIED_RIGHT;
237 : } else {
238 : aOffset.left =
239 0 : nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
240 0 : offsetSpecified |= SPECIFIED_LEFT;
241 : }
242 : }
243 :
244 0 : content->GetAttr(kNameSpaceID_None, nsGkAtoms::left, value);
245 0 : if (!value.IsEmpty()) {
246 0 : value.Trim("%");
247 : aOffset.left =
248 0 : nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
249 0 : offsetSpecified |= SPECIFIED_LEFT;
250 : }
251 :
252 0 : content->GetAttr(kNameSpaceID_None, nsGkAtoms::right, value);
253 0 : if (!value.IsEmpty()) {
254 0 : value.Trim("%");
255 : aOffset.right =
256 0 : nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
257 0 : offsetSpecified |= SPECIFIED_RIGHT;
258 : }
259 :
260 0 : content->GetAttr(kNameSpaceID_None, nsGkAtoms::top, value);
261 0 : if (!value.IsEmpty()) {
262 0 : value.Trim("%");
263 : aOffset.top =
264 0 : nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
265 0 : offsetSpecified |= SPECIFIED_TOP;
266 : }
267 :
268 0 : content->GetAttr(kNameSpaceID_None, nsGkAtoms::bottom, value);
269 0 : if (!value.IsEmpty()) {
270 0 : value.Trim("%");
271 : aOffset.bottom =
272 0 : nsPresContext::CSSPixelsToAppUnits(value.ToInteger(&error));
273 0 : offsetSpecified |= SPECIFIED_BOTTOM;
274 : }
275 : }
276 :
277 0 : if (!offsetSpecified && aChild->IsBoxFrame()) {
278 : // If no offset was specified at all, then we cache this fact to avoid requerying
279 : // CSS or the content model.
280 0 : aChild->AddStateBits(NS_STATE_STACK_NOT_POSITIONED);
281 : }
282 :
283 0 : return offsetSpecified;
284 : }
285 :
286 :
287 : NS_IMETHODIMP
288 0 : nsStackLayout::Layout(nsIBox* aBox, nsBoxLayoutState& aState)
289 : {
290 0 : nsRect clientRect;
291 0 : aBox->GetClientRect(clientRect);
292 :
293 : bool grow;
294 :
295 0 : do {
296 0 : nsIBox* child = aBox->GetChildBox();
297 0 : grow = false;
298 :
299 0 : while (child)
300 : {
301 0 : nsMargin margin;
302 0 : child->GetMargin(margin);
303 0 : nsRect childRect(clientRect);
304 0 : childRect.Deflate(margin);
305 :
306 0 : if (childRect.width < 0)
307 0 : childRect.width = 0;
308 :
309 0 : if (childRect.height < 0)
310 0 : childRect.height = 0;
311 :
312 0 : nsRect oldRect(child->GetRect());
313 0 : bool sizeChanged = !oldRect.IsEqualEdges(childRect);
314 :
315 : // only lay out dirty children or children whose sizes have changed
316 0 : if (sizeChanged || NS_SUBTREE_DIRTY(child)) {
317 : // add in the child's margin
318 0 : nsMargin margin;
319 0 : child->GetMargin(margin);
320 :
321 : // obtain our offset from the top left border of the stack's content box.
322 0 : nsMargin offset;
323 0 : PRUint8 offsetSpecified = GetOffset(aState, child, offset);
324 :
325 : // Set the position and size based on which offsets have been specified:
326 : // left only - offset from left edge, preferred width
327 : // right only - offset from right edge, preferred width
328 : // left and right - offset from left and right edges, width in between this
329 : // neither - no offset, full width of stack
330 : // Vertical direction is similar.
331 : //
332 : // Margins on the child are also included in the edge offsets
333 0 : if (offsetSpecified) {
334 0 : if (offsetSpecified & SPECIFIED_LEFT) {
335 0 : childRect.x = clientRect.x + offset.left + margin.left;
336 0 : if (offsetSpecified & SPECIFIED_RIGHT) {
337 0 : nsSize min = child->GetMinSize(aState);
338 0 : nsSize max = child->GetMaxSize(aState);
339 0 : nscoord width = clientRect.width - offset.LeftRight() - margin.LeftRight();
340 0 : childRect.width = clamped(width, min.width, max.width);
341 : }
342 : else {
343 0 : childRect.width = child->GetPrefSize(aState).width;
344 : }
345 : }
346 0 : else if (offsetSpecified & SPECIFIED_RIGHT) {
347 0 : childRect.width = child->GetPrefSize(aState).width;
348 0 : childRect.x = clientRect.XMost() - offset.right - margin.right - childRect.width;
349 : }
350 :
351 0 : if (offsetSpecified & SPECIFIED_TOP) {
352 0 : childRect.y = clientRect.y + offset.top + margin.top;
353 0 : if (offsetSpecified & SPECIFIED_BOTTOM) {
354 0 : nsSize min = child->GetMinSize(aState);
355 0 : nsSize max = child->GetMaxSize(aState);
356 0 : nscoord height = clientRect.height - offset.TopBottom() - margin.TopBottom();
357 0 : childRect.height = clamped(height, min.height, max.height);
358 : }
359 : else {
360 0 : childRect.height = child->GetPrefSize(aState).height;
361 : }
362 : }
363 0 : else if (offsetSpecified & SPECIFIED_BOTTOM) {
364 0 : childRect.height = child->GetPrefSize(aState).height;
365 0 : childRect.y = clientRect.YMost() - offset.bottom - margin.bottom - childRect.height;
366 : }
367 : }
368 :
369 : // Now place the child.
370 0 : child->SetBounds(aState, childRect);
371 :
372 : // Flow the child.
373 0 : child->Layout(aState);
374 :
375 : // Get the child's new rect.
376 0 : nsRect childRectNoMargin;
377 0 : childRectNoMargin = childRect = child->GetRect();
378 0 : childRect.Inflate(margin);
379 :
380 0 : if (child->GetStyleXUL()->mStretchStack) {
381 : // Did the child push back on us and get bigger?
382 0 : if (offset.LeftRight() + childRect.width > clientRect.width) {
383 0 : clientRect.width = childRect.width + offset.LeftRight();
384 0 : grow = true;
385 : }
386 :
387 0 : if (offset.TopBottom() + childRect.height > clientRect.height) {
388 0 : clientRect.height = childRect.height + offset.TopBottom();
389 0 : grow = true;
390 : }
391 : }
392 :
393 0 : if (!childRectNoMargin.IsEqualInterior(oldRect))
394 : {
395 : // redraw the new and old positions if the
396 : // child moved or resized.
397 : // if the new and old rect intersect meaning we just moved a little
398 : // then just redraw the union. If they don't intersect (meaning
399 : // we moved a good distance) redraw both separately.
400 0 : if (childRectNoMargin.Intersects(oldRect)) {
401 0 : nsRect u;
402 0 : u.UnionRect(oldRect, childRectNoMargin);
403 0 : aBox->Redraw(aState, &u);
404 : } else {
405 0 : aBox->Redraw(aState, &oldRect);
406 0 : aBox->Redraw(aState, &childRectNoMargin);
407 : }
408 : }
409 : }
410 :
411 0 : child = child->GetNextBox();
412 : }
413 : } while (grow);
414 :
415 : // if some HTML inside us got bigger we need to force ourselves to
416 : // get bigger
417 0 : nsRect bounds(aBox->GetRect());
418 0 : nsMargin bp;
419 0 : aBox->GetBorderAndPadding(bp);
420 0 : clientRect.Inflate(bp);
421 :
422 0 : if (clientRect.width > bounds.width || clientRect.height > bounds.height)
423 : {
424 0 : if (clientRect.width > bounds.width)
425 0 : bounds.width = clientRect.width;
426 0 : if (clientRect.height > bounds.height)
427 0 : bounds.height = clientRect.height;
428 :
429 0 : aBox->SetBounds(aState, bounds);
430 : }
431 :
432 0 : return NS_OK;
433 : }
434 :
|