Back to index

lightning-sunbird  0.9+nobinonly
nsStackLayout.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla Communicator client code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   David Hyatt (hyatt@netscape.com)
00024  *
00025  * Alternatively, the contents of this file may be used under the terms of
00026  * either of the GNU General Public License Version 2 or later (the "GPL"),
00027  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00028  * in which case the provisions of the GPL or the LGPL are applicable instead
00029  * of those above. If you wish to allow use of your version of this file only
00030  * under the terms of either the GPL or the LGPL, and not to allow others to
00031  * use your version of this file under the terms of the MPL, indicate your
00032  * decision by deleting the provisions above and replace them with the notice
00033  * and other provisions required by the GPL or the LGPL. If you do not delete
00034  * the provisions above, a recipient may use your version of this file under
00035  * the terms of any one of the MPL, the GPL or the LGPL.
00036  *
00037  * ***** END LICENSE BLOCK ***** */
00038 
00039 //
00040 // Eric Vaughan
00041 // Netscape Communications
00042 //
00043 // See documentation in associated header file
00044 //
00045 
00046 #include "nsStackLayout.h"
00047 #include "nsCOMPtr.h"
00048 #include "nsBoxLayoutState.h"
00049 #include "nsBox.h"
00050 #include "nsBoxFrame.h"
00051 #include "nsHTMLAtoms.h"
00052 #include "nsXULAtoms.h"
00053 #include "nsIContent.h"
00054 #include "nsINameSpaceManager.h"
00055 
00056 nsIBoxLayout* nsStackLayout::gInstance = nsnull;
00057 
00058 nsresult
00059 NS_NewStackLayout( nsIPresShell* aPresShell, nsCOMPtr<nsIBoxLayout>& aNewLayout)
00060 {
00061   if (!nsStackLayout::gInstance) {
00062     nsStackLayout::gInstance = new nsStackLayout();
00063     NS_IF_ADDREF(nsStackLayout::gInstance);
00064   }
00065   // we have not instance variables so just return our static one.
00066   aNewLayout = nsStackLayout::gInstance;
00067   return NS_OK;
00068 } 
00069 
00070 /*static*/ void
00071 nsStackLayout::Shutdown()
00072 {
00073   NS_IF_RELEASE(gInstance);
00074 }
00075 
00076 nsStackLayout::nsStackLayout()
00077 {
00078 }
00079 
00080 NS_IMETHODIMP
00081 nsStackLayout::GetPrefSize(nsIBox* aBox, nsBoxLayoutState& aState, nsSize& aSize)
00082 {
00083   aSize.width = 0;
00084   aSize.height = 0;
00085 
00086   // we are as wide as the widest child plus its left offset
00087   // we are tall as the tallest child plus its top offset
00088   nsIBox* child = nsnull;
00089   aBox->GetChildBox(&child);
00090  
00091   while (child) {  
00092     nsSize pref(0,0);
00093     child->GetPrefSize(aState, pref);
00094 
00095     AddMargin(child, pref);
00096     AddOffset(aState, child, pref);
00097     AddLargestSize(aSize, pref);
00098 
00099     child->GetNextBox(&child);
00100   }
00101 
00102   // now add our border and padding and insets
00103   AddBorderAndPadding(aBox, aSize);
00104   AddInset(aBox, aSize);
00105 
00106   return NS_OK;
00107 }
00108 
00109 NS_IMETHODIMP
00110 nsStackLayout::GetMinSize(nsIBox* aBox, nsBoxLayoutState& aState, nsSize& aSize)
00111 {
00112   aSize.width = 0;
00113   aSize.height = 0;
00114 
00115   // run through all the children and get their min, max, and preferred sizes
00116   
00117   nsIBox* child = nsnull;
00118   aBox->GetChildBox(&child);
00119    
00120   while (child) {  
00121     nsSize min(0,0);
00122     child->GetMinSize(aState, min);        
00123     AddMargin(child, min);
00124     AddOffset(aState, child, min);
00125     AddLargestSize(aSize, min);
00126 
00127     child->GetNextBox(&child);
00128   }
00129 
00130   // now add our border and padding and insets
00131   AddBorderAndPadding(aBox, aSize);
00132   AddInset(aBox,aSize);
00133 
00134   return NS_OK;
00135 }
00136 
00137 NS_IMETHODIMP
00138 nsStackLayout::GetMaxSize(nsIBox* aBox, nsBoxLayoutState& aState, nsSize& aSize)
00139 {
00140   aSize.width = NS_INTRINSICSIZE;
00141   aSize.height = NS_INTRINSICSIZE;
00142 
00143   // run through all the children and get their min, max, and preferred sizes
00144  
00145   nsIBox* child = nsnull;
00146   aBox->GetChildBox(&child);
00147    
00148   while (child) {  
00149     nsSize max(NS_INTRINSICSIZE, NS_INTRINSICSIZE);
00150     child->GetMaxSize(aState, max);
00151     nsSize min(NS_INTRINSICSIZE, NS_INTRINSICSIZE);
00152     child->GetMinSize(aState, min);
00153     nsBox::BoundsCheckMinMax(min, max);
00154 
00155     AddMargin(child, max);
00156     AddOffset(aState, child, max);
00157     AddSmallestSize(aSize, max);
00158 
00159     child->GetNextBox(&child);
00160   }
00161 
00162   // now add our border and padding and insets
00163   AddBorderAndPadding(aBox, aSize);
00164   AddInset(aBox, aSize);
00165 
00166   return NS_OK;
00167 }
00168 
00169 
00170 NS_IMETHODIMP
00171 nsStackLayout::GetAscent(nsIBox* aBox, nsBoxLayoutState& aState, nscoord& aAscent)
00172 {
00173   aAscent = 0;
00174   nsIBox* child = nsnull;
00175   aBox->GetChildBox(&child);
00176    
00177   while (child) {  
00178     nscoord ascent = 0;
00179     child->GetAscent(aState, ascent);
00180     nsMargin margin;
00181     child->GetMargin(margin);
00182     ascent += margin.top + margin.bottom;
00183     if (ascent > aAscent)
00184       aAscent = ascent;
00185     child->GetNextBox(&child);
00186   }
00187 
00188   return NS_OK;
00189 }
00190 
00191 PRBool
00192 nsStackLayout::AddOffset(nsBoxLayoutState& aState, nsIBox* aChild, nsSize& aSize)
00193 {
00194   nsSize offset(0,0);
00195   
00196   // get the left and top offsets
00197   
00198   // As an optimization, we cache the fact that we are not positioned to avoid
00199   // wasting time fetching attributes and checking style data.
00200   if (aChild->GetStateBits() & NS_STATE_STACK_NOT_POSITIONED)
00201     return PR_FALSE;
00202   
00203   PRBool offsetSpecified = PR_FALSE;
00204   const nsStylePosition* pos = aChild->GetStylePosition();
00205   if (eStyleUnit_Coord == pos->mOffset.GetLeftUnit()) {
00206      nsStyleCoord left = 0;
00207      pos->mOffset.GetLeft(left);
00208      offset.width = left.GetCoordValue();
00209      offsetSpecified = PR_TRUE;
00210   }
00211 
00212   if (eStyleUnit_Coord == pos->mOffset.GetTopUnit()) {
00213      nsStyleCoord top = 0;
00214      pos->mOffset.GetTop(top);
00215      offset.height = top.GetCoordValue();
00216      offsetSpecified = PR_TRUE;
00217   }
00218 
00219   nsIContent* content = aChild->GetContent();
00220 
00221   if (content) {
00222     nsPresContext* presContext = aState.PresContext();
00223     nsAutoString value;
00224     PRInt32 error;
00225 
00226     if (NS_CONTENT_ATTR_HAS_VALUE == content->GetAttr(kNameSpaceID_None, nsHTMLAtoms::left, value)) {
00227       value.Trim("%");
00228       offset.width = NSIntPixelsToTwips(value.ToInteger(&error),
00229                                         presContext->ScaledPixelsToTwips());
00230       offsetSpecified = PR_TRUE;
00231     }
00232 
00233     if (NS_CONTENT_ATTR_HAS_VALUE == content->GetAttr(kNameSpaceID_None, nsHTMLAtoms::top, value)) {
00234       value.Trim("%");
00235       offset.height = NSIntPixelsToTwips(value.ToInteger(&error),
00236                                          presContext->ScaledPixelsToTwips());
00237       offsetSpecified = PR_TRUE;
00238     }
00239   }
00240 
00241   aSize += offset;
00242 
00243   if (!offsetSpecified) {
00244     // If no offset was specified at all, then we cache this fact to avoid requerying
00245     // CSS or the content model.
00246     aChild->AddStateBits(NS_STATE_STACK_NOT_POSITIONED);
00247   }
00248   
00249   return offsetSpecified;
00250 }
00251 
00252 
00253 NS_IMETHODIMP
00254 nsStackLayout::Layout(nsIBox* aBox, nsBoxLayoutState& aState)
00255 {
00256   nsRect clientRect;
00257   aBox->GetClientRect(clientRect);
00258 
00259   PRBool grow;
00260 
00261   do {
00262     nsIBox* child = nsnull;
00263     aBox->GetChildBox(&child);
00264     grow = PR_FALSE;
00265 
00266     while (child) 
00267     {  
00268       nsMargin margin;
00269       child->GetMargin(margin);
00270       nsRect childRect(clientRect);
00271       childRect.Deflate(margin);
00272 
00273       if (childRect.width < 0)
00274         childRect.width = 0;
00275 
00276       if (childRect.height < 0)
00277         childRect.height = 0;
00278 
00279       nsRect oldRect(child->GetRect());
00280       PRBool sizeChanged = (oldRect != childRect);
00281 
00282       // only lay out dirty children or children whose sizes have changed
00283       PRBool isDirty = PR_FALSE;
00284       PRBool hasDirtyChildren = PR_FALSE;
00285 
00286       child->IsDirty(isDirty);
00287       child->HasDirtyChildren(hasDirtyChildren);
00288 
00289       if (sizeChanged || isDirty || hasDirtyChildren) {
00290           // add in the child's margin
00291           nsMargin margin;
00292           child->GetMargin(margin);
00293 
00294           // obtain our offset from the top left border of the stack's content box.
00295           nsSize offset(0,0);
00296           PRBool offsetSpecified = AddOffset(aState, child, offset);
00297 
00298           // Correct the child's x/y position by adding in both the margins
00299           // and the left/top offset.
00300           childRect.x = clientRect.x + offset.width + margin.left;
00301           childRect.y = clientRect.y + offset.height + margin.top;
00302           
00303           // If we have an offset, we don't stretch the child.  Just use
00304           // its preferred size.
00305           if (offsetSpecified) {
00306             nsSize pref(0,0);
00307             child->GetPrefSize(aState, pref);
00308             childRect.width = pref.width;
00309             childRect.height = pref.height;
00310           }
00311 
00312           // Now place the child.
00313           child->SetBounds(aState, childRect);
00314 
00315           // Flow the child.
00316           child->Layout(aState);
00317 
00318           // Get the child's new rect.
00319           nsRect childRectNoMargin;
00320           childRectNoMargin = childRect = child->GetRect();
00321           childRect.Inflate(margin);
00322 
00323           // Did the child push back on us and get bigger?
00324           if (offset.width + childRect.width > clientRect.width) {
00325             clientRect.width = childRect.width + offset.width;
00326             grow = PR_TRUE;
00327           }
00328 
00329           if (offset.height + childRect.height > clientRect.height) {
00330             clientRect.height = childRect.height + offset.height;
00331             grow = PR_TRUE;
00332           }
00333 
00334           if (childRectNoMargin != oldRect)
00335           {
00336             // redraw the new and old positions if the 
00337             // child moved or resized.
00338             // if the new and old rect intersect meaning we just moved a little
00339             // then just redraw the union. If they don't intersect (meaning
00340             // we moved a good distance) redraw both separately.
00341             if (childRectNoMargin.Intersects(oldRect)) {
00342               nsRect u;
00343               u.UnionRect(oldRect, childRectNoMargin);
00344               aBox->Redraw(aState, &u);
00345             } else {
00346               aBox->Redraw(aState, &oldRect);
00347               aBox->Redraw(aState, &childRectNoMargin);
00348             }
00349           }
00350        }
00351 
00352        child->GetNextBox(&child);
00353      }
00354    } while (grow);
00355    
00356    // if some HTML inside us got bigger we need to force ourselves to
00357    // get bigger
00358    nsRect bounds(aBox->GetRect());
00359    nsMargin bp;
00360    aBox->GetBorderAndPadding(bp);
00361    clientRect.Inflate(bp);
00362    aBox->GetInset(bp);
00363    clientRect.Inflate(bp);
00364 
00365    if (clientRect.width > bounds.width || clientRect.height > bounds.height)
00366    {
00367      if (clientRect.width > bounds.width)
00368        bounds.width = clientRect.width;
00369      if (clientRect.height > bounds.height)
00370        bounds.height = clientRect.height;
00371 
00372      aBox->SetBounds(aState, bounds);
00373    }
00374 
00375    return NS_OK;
00376 }
00377