711 lines
16 KiB
C++
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;
|
|
}
|