Mozilla/mozilla/xpfc/layout/src/nsBoxLayout.cpp
dmose%mozilla.org ce50f7d151 updated license boilerplate to xPL 1.1, a=chofmann@netscape.com,r=endico@mozilla.org
git-svn-id: svn://10.0.0.236/trunk@52900 18797224-902f-48f8-a5cc-f745e15eee43
1999-11-06 02:47:15 +00:00

711 lines
16 KiB
C++

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
*
* The contents of this file are subject to the Netscape Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/NPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is mozilla.org code.
*
* The Initial Developer of the Original Code is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All
* Rights Reserved.
*
* Contributor(s):
*/
/*
* Note that throughout this code we assume (from a performance perspective)
* that the number of widgets being laid out is very small (< 50). If this
* assumption does not hold, the massive while loops found in this logic
* should be optimized to be one for loop.
*
* This code is recommended as a clean way to layout small numbers of
* containers holding objects, in an extensible way. For large lists,
* use widgets geared towards them.
*/
#include "nsxpfcCIID.h"
#include "nsBoxLayout.h"
#include "nsIXPFCCanvas.h"
static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
static NS_DEFINE_IID(kBoxLayoutCID, NS_BOXLAYOUT_CID);
static NS_DEFINE_IID(kIXPFCCanvasIID, NS_IXPFC_CANVAS_IID);
nsBoxLayout :: nsBoxLayout() : nsLayout()
{
NS_INIT_REFCNT();
mContainer = nsnull;
mLayoutAlignment = eLayoutAlignment_horizontal;
mVerticalGap = 0;
mHorizontalGap = 0;
mVerticalFill = 1.0;
mHorizontalFill = 1.0;
}
nsBoxLayout :: ~nsBoxLayout()
{
}
nsresult nsBoxLayout::QueryInterface(REFNSIID aIID, void** aInstancePtr)
{
if (NULL == aInstancePtr) {
return NS_ERROR_NULL_POINTER;
}
static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID);
static NS_DEFINE_IID(kClassIID, kBoxLayoutCID);
if (aIID.Equals(kClassIID)) {
*aInstancePtr = (void*) this;
AddRef();
return NS_OK;
}
if (aIID.Equals(kISupportsIID)) {
*aInstancePtr = (void*) (this);
AddRef();
return NS_OK;
}
return (nsLayout::QueryInterface(aIID,aInstancePtr));
}
NS_IMPL_ADDREF(nsBoxLayout)
NS_IMPL_RELEASE(nsBoxLayout)
nsresult nsBoxLayout :: Init()
{
return NS_OK ;
}
nsresult nsBoxLayout :: Init(nsIXPFCCanvas * aContainer)
{
mContainer = aContainer;
return NS_OK ;
}
nsresult nsBoxLayout :: Layout()
{
if (!mContainer)
return NS_OK;
LayoutContainer();
LayoutChildren();
return NS_OK ;
}
nsresult nsBoxLayout :: LayoutContainer()
{
nsIIterator * iterator ;
nsIXPFCCanvas * canvas ;
nsRect rect;
PRUint32 width, height;
PRUint32 wsize, hsize;
PRUint32 count = 0;
PRBool bFloater = PR_FALSE;
PRInt32 spaceleft = 0;
PRInt32 space_per_object = 0;
PRUint32 totalfloaters = 0;
PRUint32 start = 0;
nsSize prefSize;
nsSize minSize;
nsSize maxSize;
PRUint32 startX = 0, startY = 0;
// Default case is everyone gets equal space
mContainer->GetBounds(rect);
// Offset by the gaps
rect.x += mVerticalGap;
rect.width-=(2*mVerticalGap);
rect.y += mHorizontalGap;
rect.height-=(2*mHorizontalGap);
wsize = width = rect.width;
hsize = height = rect.height;
// Iterate through the children
CreateIterator(&iterator);
iterator->Init();
if (iterator->Count() == 0)
{
NS_RELEASE(iterator);
return NS_OK;
}
if (GetLayoutAlignment() == eLayoutAlignment_horizontal) {
wsize /= iterator->Count();
start = rect.x;
} else {
hsize /= iterator->Count();
start = rect.y;
}
startY = rect.y;
startX = rect.x;
/*
* First, we need to see if there is a floater so that preferred sizes
* can be used
*/
bFloater = CanLayoutUsingPreferredSizes(iterator);
/*
* If we have atleast one floater, all the non-floaters will get their preferred
* sizes and the floaters will then *deal* with the remaining space. So, compute
* the remaining space here.
*
* Then, we lay out all widgets with a preferred size using that size. The
* remaining widgets will get the average size which is the remaining space
* divided by the number of floating objects
*/
if (bFloater == PR_TRUE) {
spaceleft = ComputeFreeFloatingSpace(iterator);
totalfloaters = QueryNumberFloatingWidgets(iterator);
/*
* XXX Need to deal with case where space left is not enough.
* See ComputeFreeFloatingSpace at bottom for comment.
*
* for now, just fallback on equal layout for all widgets
*/
/*
* If spaceleft is negative, then no floaters are visible and we should
* give each canvas its preferred size. Some may not be visible.
*/
if (spaceleft < 0) {
bFloater = PR_FALSE;
} else {
space_per_object = spaceleft / totalfloaters;
}
}
iterator->First();
while(!(iterator->IsDone()))
{
canvas = (nsIXPFCCanvas *) iterator->CurrentItem();
if (GetLayoutAlignment() == eLayoutAlignment_horizontal) {
if (bFloater == PR_FALSE)
{
if (canvas->HasPreferredSize() == PR_TRUE) {
canvas->GetPreferredSize(prefSize);
rect.x = start;
rect.width = prefSize.width;
start += rect.width;
} else {
rect.x = start;
rect.width = 0;
}
// rect.x = wsize * count;
// rect.width = wsize;
} else {
/*
* In this case, if we have a preferred size, use it.
* If no preferred size, use the weighted size if it
* falls within min/max range, else use min/max
*/
if (canvas->HasPreferredSize() == PR_TRUE) {
canvas->GetPreferredSize(prefSize);
rect.x = start;
rect.width = prefSize.width;
start += rect.width;
}
else {
/*
* check to see that space_per_object is between min and max
* and if so, use it else use the min or max
*/
PRBool bSetToMinMax = PR_FALSE;
if (canvas->HasMinimumSize() == PR_TRUE) {
canvas->GetMinimumSize(minSize);
if (space_per_object < minSize.width) {
rect.x = start;
rect.width = minSize.width;
start += rect.width;
bSetToMinMax = PR_TRUE;
}
}
if (bSetToMinMax == PR_FALSE && canvas->HasMaximumSize() == PR_TRUE) {
canvas->GetMaximumSize(maxSize);
if (space_per_object > maxSize.width) {
rect.x = start;
rect.width = maxSize.width;
start += rect.width;
}
}
/*
* Yeah, this object can fill space_per_object
*/
if (bSetToMinMax == PR_FALSE) {
rect.x = start;
rect.width = space_per_object;
start += rect.width;
}
}
}
rect.height = (PRInt32) (hsize * mHorizontalFill) ;
rect.y = (((PRUint32)(height - rect.height)) >> 2) + startY ;
canvas->SetBounds(rect);
} else {
if (bFloater == PR_FALSE)
{
if (canvas->HasPreferredSize() == PR_TRUE) {
canvas->GetPreferredSize(prefSize);
rect.y = start;
rect.height = prefSize.height;
start += rect.height;
} else {
rect.y = start;
rect.height = 0;
}
// rect.y = hsize * count;
// rect.height = hsize;
} else {
/*
* In this case, if we have a preferred size, use it.
* If no preferred size, use the weighted size if it
* falls within min/max range, else use min/max
*/
if (canvas->HasPreferredSize() == PR_TRUE) {
canvas->GetPreferredSize(prefSize);
rect.y = start;
rect.height = prefSize.height;
start += rect.height;
}
else {
/*
* check to see that space_per_object is between min and max
* and if so, use it else use the min or max
*/
PRBool bSetToMinMax = PR_FALSE;
if (canvas->HasMinimumSize() == PR_TRUE) {
canvas->GetMinimumSize(minSize);
if (space_per_object < minSize.height) {
rect.y = start;
rect.height = minSize.height;
start += rect.height;
bSetToMinMax = PR_TRUE;
}
}
if (bSetToMinMax == PR_FALSE && canvas->HasMaximumSize() == PR_TRUE) {
canvas->GetMaximumSize(maxSize);
if (space_per_object > maxSize.height) {
rect.y = start;
rect.height = maxSize.height;
start += rect.height;
}
}
/*
* Yeah, this object can fill space_per_object
*/
if (bSetToMinMax == PR_FALSE) {
rect.y = start;
rect.height = space_per_object;
start += rect.height;
}
}
}
rect.width = (PRInt32) (wsize * mVerticalFill) ;
rect.x = (((PRUint32)(width - rect.width)) >> 2) + startX ;
canvas->SetBounds(rect);
}
count++;
iterator->Next();
}
NS_RELEASE(iterator);
return NS_OK ;
}
nsresult nsBoxLayout :: LayoutChildren()
{
nsIIterator * iterator ;
nsIXPFCCanvas * canvas;
nsresult res ;
// Iterate through the children
CreateIterator(&iterator);
iterator->Init();
while(!(iterator->IsDone()))
{
canvas = (nsIXPFCCanvas *) iterator->CurrentItem();
canvas->Layout();
iterator->Next();
}
NS_RELEASE(iterator);
return NS_OK ;
}
PRBool nsBoxLayout :: CanLayoutUsingPreferredSizes(nsIIterator * aIterator)
{
nsresult res;
nsIXPFCCanvas * canvas;
aIterator->First();
while(!(aIterator->IsDone()))
{
canvas = (nsIXPFCCanvas *) aIterator->CurrentItem();
if (canvas->HasPreferredSize() == PR_FALSE)
return PR_TRUE;
aIterator->Next();
}
return PR_FALSE;
}
PRUint32 nsBoxLayout :: QueryNumberFloatingWidgets(nsIIterator * aIterator)
{
nsresult res;
nsIXPFCCanvas * canvas;
PRUint32 count = 0;
aIterator->First();
while(!(aIterator->IsDone()))
{
canvas = (nsIXPFCCanvas *) aIterator->CurrentItem();
if (canvas->HasPreferredSize() == PR_FALSE) {
count ++;
}
aIterator->Next();
}
return count;
}
/*
* Compute free floating space. Start with full area and then subtract
* the preferred space of every absolutely positioned widget.
*/
PRInt32 nsBoxLayout :: ComputeFreeFloatingSpace(nsIIterator * aIterator)
{
nsresult res;
nsIXPFCCanvas * canvas;
PRInt32 space = 0;
PRInt32 space_per_object = 0;
nsRect rect;
nsSize prefSize;
nsSize minSize;
nsSize maxSize;
PRUint32 totalfloaters = 0;
/*
* compute overall layout size
*/
mContainer->GetBounds(rect);
if (GetLayoutAlignment() == eLayoutAlignment_horizontal) {
space = rect.width;
} else {
space = rect.height;
}
/*
* subtract off all the preferred sizes
*/
aIterator->First();
while(!(aIterator->IsDone()))
{
canvas = (nsIXPFCCanvas *) aIterator->CurrentItem();
if (canvas->HasPreferredSize() == PR_TRUE)
{
canvas->GetPreferredSize(prefSize);
if (GetLayoutAlignment() == eLayoutAlignment_horizontal)
{
space -= prefSize.width;
}
else
{
space -= prefSize.height;
}
}
aIterator->Next();
}
/*
* Now compute the temporary space per object
*/
totalfloaters = QueryNumberFloatingWidgets(aIterator);
space_per_object = space / totalfloaters;
/*
* Ok, so now we know how to lay out preferred size widgets.
* Normally, the floating widgets would use 'spaceleft' space,
* but that amount *might* fall outside their preferred ranges.
* When it does fall outside, we can set that widget to the
* specified min/max, but the space remaining will be altered,
* so compute that now
*/
aIterator->First();
while(!(aIterator->IsDone()))
{
canvas = (nsIXPFCCanvas *) aIterator->CurrentItem();
if (canvas->HasPreferredSize() == PR_FALSE) {
/*
* Check to see if the space assigned for this widget
* falls within it's min/max range
*/
if (canvas->HasMinimumSize() == PR_TRUE) {
canvas->GetMinimumSize(minSize);
if (GetLayoutAlignment() == eLayoutAlignment_horizontal) {
if (space_per_object < minSize.width)
space -= (minSize.width - space_per_object);
} else {
if (space_per_object < minSize.height)
space -= (minSize.height - space_per_object);
}
}
if (canvas->HasMaximumSize() == PR_TRUE) {
canvas->GetMaximumSize(maxSize);
if (GetLayoutAlignment() == eLayoutAlignment_horizontal) {
if (space_per_object > maxSize.width)
space += (space_per_object - maxSize.width);
} else {
if (space_per_object < maxSize.height)
space += (space_per_object - maxSize.height);
}
}
}
aIterator->Next();
}
/*
* At this point, if space is positive, we are ready to lay out.
* If space is negative, we need to shrink the floaters which have
* a maximum specified by the amount we are negative by the total
* number of floaters (it's getting ugly, maybe we can clean up later)
*
* We'll leave that logic for the actual layout
*/
return space;
}
nsresult nsBoxLayout :: CreateIterator(nsIIterator ** aIterator)
{
return (mContainer->CreateIterator(aIterator));
}
nsresult nsBoxLayout :: PreferredSize(nsSize &aSize)
{
return NS_OK ;
}
nsresult nsBoxLayout :: MinimumSize(nsSize &aSize)
{
return NS_OK ;
}
nsresult nsBoxLayout :: MaximumSize(nsSize &aSize)
{
return NS_OK ;
}
PRFloat64 nsBoxLayout :: GetHorizontalFill()
{
return mHorizontalFill ;
}
PRFloat64 nsBoxLayout :: GetVerticalFill()
{
return mVerticalFill ;
}
void nsBoxLayout :: SetHorizontalFill(PRFloat64 aFillSize)
{
mHorizontalFill = aFillSize;
}
void nsBoxLayout :: SetVerticalFill(PRFloat64 aFillSize)
{
mVerticalFill = aFillSize;
}
PRUint32 nsBoxLayout :: GetHorizontalGap()
{
return mHorizontalGap ;
}
PRUint32 nsBoxLayout :: GetVerticalGap()
{
return mVerticalGap ;
}
void nsBoxLayout :: SetHorizontalGap(PRUint32 aGapSize)
{
mHorizontalGap = aGapSize;
}
void nsBoxLayout :: SetVerticalGap(PRUint32 aGapSize)
{
mVerticalGap = aGapSize;
}
void nsBoxLayout :: SetLayoutAlignment(nsLayoutAlignment aLayoutAlignment)
{
mLayoutAlignment = aLayoutAlignment;
}
nsLayoutAlignment nsBoxLayout :: GetLayoutAlignment()
{
return mLayoutAlignment;
}