/* -*- 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.0 (the "NPL"); you may not use this file except in
* compliance with the NPL. You may obtain a copy of the NPL at
* http://www.mozilla.org/NPL/
*
* Software distributed under the NPL is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
* for the specific language governing rights and limitations under the
* NPL.
*
* The Initial Developer of this code under the NPL is Netscape
* Communications Corporation. Portions created by Netscape are
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
* Reserved.
*/
#include "nsFrameSetFrame.h"
#include "nsHTMLContainer.h"
#include "nsLeafFrame.h"
#include "nsHTMLContainerFrame.h"
#include "nsIWebShell.h"
#include "nsIPresContext.h"
#include "nsIPresShell.h"
#include "nsHTMLIIDs.h"
#include "nsRepository.h"
#include "nsIStreamListener.h"
#include "nsIURL.h"
#include "nsIDocument.h"
#include "nsIView.h"
#include "nsIViewManager.h"
#include "nsWidgetsCID.h"
#include "nsViewsCID.h"
#include "nsHTMLAtoms.h"
#include "nsIScrollableView.h"
#include "nsStyleCoord.h"
#include "nsStyleConsts.h"
#include "nsIStyleContext.h"
#include "nsCSSLayout.h"
#include "nsHTMLBase.h"
#include "nsIDocumentLoader.h"
#include "nsGenericHTMLElement.h"
#include "nsHTMLParts.h"
// masks for mEdgeVisibility
#define LEFT_VIS 0x0001
#define RIGHT_VIS 0x0002
#define TOP_VIS 0x0004
#define BOTTOM_VIS 0x0008
#define ALL_VIS 0x000F
#define NONE_VIS 0x0000
static nsPoint NULL_POINT(-1000000,1000000);
static PRInt32 LEFT_EDGE = -1;
static PRInt32 RIGHT_EDGE = 1000000;
static NS_DEFINE_IID(kIFramesetFrameIID, NS_IFRAMESETFRAME_IID);
/*******************************************************************************
* nsHTMLFramesetBorderFrame
******************************************************************************/
class nsHTMLFramesetBorderFrame : public nsLeafFrame {
public:
NS_IMETHOD ListTag(FILE* out = stdout) const;
NS_IMETHOD HandleEvent(nsIPresContext& aPresContext,
nsGUIEvent* aEvent,
nsEventStatus& aEventStatus);
NS_IMETHOD Paint(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect);
NS_IMETHOD Reflow(nsIPresContext& aPresContext,
nsReflowMetrics& aDesiredSize,
const nsReflowState& aReflowState,
nsReflowStatus& aStatus);
PRBool GetVisibility() { return mVisibility; }
void SetVisibility(PRBool aVisibility);
void SetColor(nscolor aColor);
protected:
nsHTMLFramesetBorderFrame(nsIContent* aContent, nsIFrame* aParentFrame,
PRInt32 aWidth, PRBool aVertical, PRBool aVisible);
virtual ~nsHTMLFramesetBorderFrame();
virtual void GetDesiredSize(nsIPresContext* aPresContext,
const nsReflowState& aReflowState,
nsReflowMetrics& aDesiredSize);
PRInt32 mWidth;
PRBool mVertical;
PRBool mVisibility;
nscolor mColor;
// the prev and next neighbors are indexes into the row (for a horizontal border) or col (for
// a vertical border) of nsHTMLFramesetFrames or nsHTMLFrames
PRInt32 mPrevNeighbor;
PRInt32 mNextNeighbor;
PRBool mCanResize;
friend class nsHTMLFramesetFrame;
};
/*******************************************************************************
* nsHTMLFramesetBlankFrame
******************************************************************************/
class nsHTMLFramesetBlankFrame : public nsLeafFrame {
public:
NS_IMETHOD List(FILE* out = stdout, PRInt32 aIndent = 0) const;
NS_IMETHOD Paint(nsIPresContext& aPresContext,
nsIRenderingContext& aRenderingContext,
const nsRect& aDirtyRect);
NS_IMETHOD Reflow(nsIPresContext& aPresContext,
nsReflowMetrics& aDesiredSize,
const nsReflowState& aReflowState,
nsReflowStatus& aStatus);
protected:
nsHTMLFramesetBlankFrame(nsIContent* aContent, nsIFrame* aParentFrame);
virtual ~nsHTMLFramesetBlankFrame();
virtual void GetDesiredSize(nsIPresContext* aPresContext,
const nsReflowState& aReflowState,
nsReflowMetrics& aDesiredSize);
friend class nsHTMLFramesetFrame;
friend class nsHTMLFrameset;
};
/*******************************************************************************
* nsHTMLFramesetFrame
******************************************************************************/
PRInt32 nsHTMLFramesetFrame::gMaxNumRowColSpecs = 25;
PRBool nsHTMLFramesetFrame::gDragInProgress = PR_FALSE;
nsHTMLFramesetFrame::nsHTMLFramesetFrame(nsIContent* aContent, nsIFrame* aParent)
: nsHTMLContainerFrame(aContent, aParent)
{
mNumRows = 0;
mRowSpecs = nsnull;
mRowSizes = nsnull;
mNumCols = 0;
mColSpecs = nsnull;
mColSizes = nsnull;
mEdgeVisibility = 0;
mEdgeColors.Set(NO_COLOR);
mParentFrameborder = eFrameborder_Yes; // default
mParentBorderWidth = -1; // default not set
mParentBorderColor = NO_COLOR; // default not set
mLastDragPoint.x = mLastDragPoint.y = 0;
mMinDrag = 0;
}
nsHTMLFramesetFrame::~nsHTMLFramesetFrame()
{
printf("nsFramesetFrame destructor %X \n", this);
if (mRowSizes) delete [] mRowSizes;
if (mRowSpecs) delete [] mRowSpecs;
if (mColSizes) delete [] mColSizes;
if (mColSpecs) delete [] mColSpecs;
mRowSizes = mColSizes = nsnull;
mRowSpecs = mColSpecs = nsnull;
}
nsresult nsHTMLFramesetFrame::QueryInterface(const nsIID& aIID, void** aInstancePtr)
{
if (NULL == aInstancePtr) {
return NS_ERROR_NULL_POINTER;
} else if (aIID.Equals(kIFramesetFrameIID)) {
*aInstancePtr = (void*)this;
return NS_OK;
}
return nsHTMLContainerFrame::QueryInterface(aIID, aInstancePtr);
}
/**
* Translate the rows/cols specs into an array of integer sizes for
* each cell in the frameset. The sizes are computed by first
* rationalizing the spec into a set of percentages that total
* 100. Then the cell sizes are computed.
*/
// XXX This doesn't do a good job of translating "*,625,*" specs
void nsHTMLFramesetFrame::CalculateRowCol(nsIPresContext* aPresContext, nscoord aSize,
PRInt32 aNumSpecs, nsFramesetSpec* aSpecs,
nscoord* aValues)
{
// Total up the three different type of grid cell
// percentages. Transfer the resulting values into result for
// temporary storage (we don't overwrite the cell specs)
PRInt32 free = 0;
PRInt32 percent = 0;
PRInt32 pixel = 0;
float p2t = aPresContext->GetPixelsToTwips();
int i; // feeble compiler
for (i = 0; i < aNumSpecs; i++) {
switch (aSpecs[i].mUnit) {
case eFramesetUnit_Pixel:
// Now that we know the size we are laying out in, turn fixed
// pixel dimensions into percents.
// XXX maybe use UnitConverter for proper rounding? MMP
aValues[i] = NSToCoordRound(100 * p2t * aSpecs[i].mValue / aSize);
if (aValues[i] < 1) aValues[i] = 1;
pixel += aValues[i];
break;
case eFramesetUnit_Percent:
aValues[i] = aSpecs[i].mValue;
percent += aValues[i];
break;
case eFramesetUnit_Free:
aValues[i] = aSpecs[i].mValue;
free += aValues[i];
break;
}
}
if (percent + pixel < 100) {
// The user didn't explicitly use up all the space. Spread the
// left over space to the free percentage cells first, then to
// the normal percentage cells second, and finally to the fixed
// cells as a last resort.
if (free != 0) {
// We have some free percentage cells that want to soak up the
// excess space. Spread out the left over space to those cells.
int used = 0;
int last = -1;
int leftOver = 100 - (percent + pixel);
for (int i = 0; i < aNumSpecs; i++) {
if (eFramesetUnit_Free == aSpecs[i].mUnit) {
aValues[i] = aValues[i] * leftOver / free;
if (aValues[i] < 1) aValues[i] = 1;
used += aValues[i];
last = i;
}
}
int remainder = 100 - percent - pixel - used;
if ((remainder > 0) && (last >= 0)) {
// Slop the extra space into the last qualifying cell
aValues[last] += remainder;
}
} else if (percent != 0) {
// We have no free cells but have some normal percentage
// cells. Distribute the extra space among them.
int used = 0;
int last = -1;
int leftOver = 100 - pixel;
for (int i = 0; i < aNumSpecs; i++) {
if (eFramesetUnit_Percent == aSpecs[i].mUnit) {
aValues[i] = aValues[i] * leftOver / percent;
used += aValues[i];
last = i;
}
}
if ((used < leftOver) && (last >= 0)) {
// Slop the extra space into the last qualifying cell
aValues[last] = aValues[last] + (leftOver - used);
}
} else {
// Give the leftover space to the fixed percentage
// cells. Recall that at the start of this method we converted
// the fixed pixel values into percentages.
int used = 0;
for (int i = 0; i < aNumSpecs; i++) {
aValues[i] = aValues[i] * 100 / pixel;
used += aValues[i];
}
if ((used < 100) && (aNumSpecs > 0)) {
// Slop the extra space into the last cell
aValues[aNumSpecs - 1] += (100 - used);
}
}
} else if (percent + pixel > 100) {
// The user overallocated the space. We need to shrink something.
// If there is not too much fixed percentage added, we can
// just shrink the normal percentage cells to make things fit.
if (pixel <= 100) {
int used = 0;
int last = -1;
int val = 100 - pixel;
for (int i = 0; i < aNumSpecs; i++) {
if (eFramesetUnit_Percent == aSpecs[i].mUnit) {
aValues[i] = aValues[i] * val / percent;
used += aValues[i];
last = i;
}
}
// Since integer division always truncates, we either made
// it fit exactly, or overcompensated and made it too small.
if ((used < val) && (last >= 0)) {
aValues[last] += (val - used);
}
} else {
// There is too much fixed percentage as well. We will just
// shrink all the cells.
int used = 0;
for (int i = 0; i < aNumSpecs; i++) {
if (eFramesetUnit_Free == aSpecs[i].mUnit) {
aValues[i] = 0;
} else {
aValues[i] = aValues[i] * 100 / (percent + pixel);
}
used += aValues[i];
}
if ((used < 100) && (aNumSpecs > 0)) {
aValues[aNumSpecs-1] += (100 - used);
}
}
}
// Now map the result which contains nothing but percentages into
// fixed pixel values.
int sum = 0;
for (i = 0; i < aNumSpecs; i++) {
aValues[i] = (aValues[i] * aSize) / 100;
sum += aValues[i];
}
//Assert.Assertion(sum <= aSize);
if ((sum < aSize) && (aNumSpecs > 0)) {
// Any remaining pixels (from roundoff) go into the last cell
aValues[aNumSpecs-1] += aSize - sum;
}
}
PRInt32 nsHTMLFramesetFrame::GetBorderWidth(nsIPresContext* aPresContext)
{
float p2t = aPresContext->GetPixelsToTwips();
nsHTMLValue htmlVal;
PRInt32 intVal;
nsIHTMLContent* content = nsnull;
mContent->QueryInterface(kIHTMLContentIID, (void**)&content);
if (nsnull != content) {
if (NS_CONTENT_ATTR_HAS_VALUE == (content->GetAttribute(nsHTMLAtoms::border, htmlVal))) {
if (eHTMLUnit_Pixel == htmlVal.GetUnit()) {
intVal = htmlVal.GetPixelValue();
} else {
intVal = htmlVal.GetIntValue();
}
if (intVal < 0) {
intVal = 0;
}
NS_RELEASE(content);
return NSIntPixelsToTwips(intVal, p2t);
}
NS_RELEASE(content);
}
if (mParentBorderWidth >= 0) {
return mParentBorderWidth;
}
return NSIntPixelsToTwips(6, p2t);
}
PRIntn
nsHTMLFramesetFrame::GetSkipSides() const
{
return 0;
}
void
nsHTMLFramesetFrame::GetDesiredSize(nsIPresContext* aPresContext,
const nsReflowState& aReflowState,
nsReflowMetrics& aDesiredSize)
{
nsHTMLFramesetFrame* framesetParent = GetFramesetParent(this);
if (nsnull == framesetParent) {
nsRect area;
aPresContext->GetVisibleArea(area);
aDesiredSize.width = area.width;
aDesiredSize.height= area.height;
} else {
nsSize size;
framesetParent->GetSizeOfChild(this, size);
aDesiredSize.width = size.width;
aDesiredSize.height = size.height;
}
aDesiredSize.ascent = aDesiredSize.height;
aDesiredSize.descent = 0;
}
nsHTMLFramesetFrame* nsHTMLFramesetFrame::GetFramesetParent(nsIFrame* aChild)
{
nsHTMLFramesetFrame* parent = nsnull;
nsIContent* content = nsnull;
aChild->GetContent(content);
if (nsnull != content) {
nsIContent* contentParent = nsnull;
content->GetParent(contentParent);
if (nsnull != contentParent) {
nsIHTMLContent* contentParent2 = nsnull;
contentParent->QueryInterface(kIHTMLContentIID, (void**)&contentParent2);
if (nsnull != contentParent2) {
nsIAtom* tag;
contentParent2->GetTag(tag);
if (nsHTMLAtoms::frameset == tag) {
aChild->GetGeometricParent((nsIFrame*&)parent);
}
NS_IF_RELEASE(tag);
NS_RELEASE(contentParent2);
}
NS_RELEASE(contentParent);
}
NS_RELEASE(content);
}
return parent;
}
// only valid for non border children
void nsHTMLFramesetFrame::GetSizeOfChildAt(PRInt32 aIndexInParent, nsSize& aSize, nsPoint& aCellIndex)
{
PRInt32 row = aIndexInParent / mNumCols;
PRInt32 col = aIndexInParent - (row * mNumCols); // remainder from dividing index by mNumCols
if ((row < mNumRows) && (col < mNumCols)) {
aSize.width = mColSizes[col];
aSize.height = mRowSizes[row];
aCellIndex.x = col;
aCellIndex.y = row;
} else {
aSize.width = aSize.height = aCellIndex.x = aCellIndex.y = 0;
}
}
// only valid for non border children
void nsHTMLFramesetFrame::GetSizeOfChild(nsIFrame* aChild,
nsSize& aSize)
{
// Reflow only creates children frames for