/**
* This file is part of the KDE project.
*
* Copyright (C) 1999 Lars Knoll (knoll@kde.org)
* (C) 2000 Simon Hausmann <hausmann@kde.org>
* (C) 2000 Stefan Schimanski (1Stein@gmx.de)
* Copyright (C) 2004, 2005, 2006 Apple Computer, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*
*/
#include "config.h"
#include "RenderFrameSet.h"
#include "Document.h"
#include "EventHandler.h"
#include "EventNames.h"
#include "Frame.h"
#include "FrameView.h"
#include "GraphicsContext.h"
#include "HTMLFrameSetElement.h"
#include "HitTestRequest.h"
#include "HitTestResult.h"
#include "MouseEvent.h"
#include "RenderFrame.h"
#include "RenderView.h"
namespace WebCore {
RenderFrameSet::RenderFrameSet(HTMLFrameSetElement* frameSet)
: RenderContainer(frameSet)
, m_isResizing(false)
, m_isChildResizing(false)
#ifdef FLATTEN_FRAMESET
, m_gridCalculated(false)
#endif
{
setInline(false);
}
RenderFrameSet::~RenderFrameSet()
{
}
RenderFrameSet::GridAxis::GridAxis()
: m_splitBeingResized(noSplit)
{
}
inline HTMLFrameSetElement* RenderFrameSet::frameSet() const
{
return static_cast<HTMLFrameSetElement*>(node());
}
static Color borderStartEdgeColor()
{
return Color(170,170,170);
}
static Color borderEndEdgeColor()
{
return Color::black;
}
static Color borderFillColor()
{
return Color(208, 208, 208);
}
void RenderFrameSet::paintColumnBorder(const PaintInfo& paintInfo, const IntRect& borderRect)
{
if (!paintInfo.rect.intersects(borderRect))
return;
// FIXME: We should do something clever when borders from distinct framesets meet at a join.
// Fill first.
GraphicsContext* context = paintInfo.context;
context->fillRect(borderRect, frameSet()->hasBorderColor() ? style()->borderLeftColor() : borderFillColor());
// Now stroke the edges but only if we have enough room to paint both edges with a little
// bit of the fill color showing through.
if (borderRect.width() >= 3) {
context->fillRect(IntRect(borderRect.topLeft(), IntSize(1, height())), borderStartEdgeColor());
context->fillRect(IntRect(borderRect.topRight(), IntSize(1, height())), borderEndEdgeColor());
}
}
void RenderFrameSet::paintRowBorder(const PaintInfo& paintInfo, const IntRect& borderRect)
{
if (!paintInfo.rect.intersects(borderRect))
return;
// FIXME: We should do something clever when borders from distinct framesets meet at a join.
// Fill first.
GraphicsContext* context = paintInfo.context;
context->fillRect(borderRect, frameSet()->hasBorderColor() ? style()->borderLeftColor() : borderFillColor());
// Now stroke the edges but only if we have enough room to paint both edges with a little
// bit of the fill color showing through.
if (borderRect.height() >= 3) {
context->fillRect(IntRect(borderRect.topLeft(), IntSize(width(), 1)), borderStartEdgeColor());
context->fillRect(IntRect(borderRect.bottomLeft(), IntSize(width(), 1)), borderEndEdgeColor());
}
}
void RenderFrameSet::paint(PaintInfo& paintInfo, int tx, int ty)
{
if (paintInfo.phase != PaintPhaseForeground)
return;
RenderObject* child = firstChild();
if (!child)
return;
// Add in our offsets.
tx += x();
ty += y();
int rows = frameSet()->totalRows();
int cols = frameSet()->totalCols();
int borderThickness = frameSet()->border();
int yPos = 0;
for (int r = 0; r < rows; r++) {
int xPos = 0;
for (int c = 0; c < cols; c++) {
child->paint(paintInfo, tx, ty);
xPos += m_cols.m_sizes[c];
if (borderThickness && m_cols.m_allowBorder[c + 1]) {
paintColumnBorder(paintInfo, IntRect(tx + xPos, ty + yPos, borderThickness, height()));
xPos += borderThickness;
}
child = child->nextSibling();
if (!child)
return;
}
yPos += m_rows.m_sizes[r];
if (borderThickness && m_rows.m_allowBorder[r + 1]) {
paintRowBorder(paintInfo, IntRect(tx, ty + yPos, width(), borderThickness));
yPos += borderThickness;
}
}
}
bool RenderFrameSet::nodeAtPoint(const HitTestRequest& request, HitTestResult& result,
int x, int y, int tx, int ty, HitTestAction action)
{
if (action != HitTestForeground)
return false;
bool inside = RenderContainer::nodeAtPoint(request, result, x, y, tx, ty, action)
|| m_isResizing || canResize(IntPoint(x, y));
if (inside && frameSet()->noResize()
&& !request.readonly && !result.innerNode()) {
result.setInnerNode(node());
result.setInnerNonSharedNode(node());
}
return inside || m_isChildResizing;
}
void RenderFrameSet::GridAxis::resize(int size)
{
m_sizes.resize(size);
m_deltas.resize(size);
m_deltas.fill(0);
// To track edges for resizability and borders, we need to be (size + 1). This is because a parent frameset
// may ask us for information about our left/top/right/bottom edges in order to make its own decisions about
// what to do. We are capable of tainting that parent frameset's borders, so we have to cache this info.
m_preventResize.resize(size + 1);
m_allowBorder.resize(size + 1);
}
void RenderFrameSet::layOutAxis(GridAxis& axis, const Length* grid, int availableLen)
{
availableLen = max(availableLen, 0);
int* gridLayout = axis.m_sizes.data();
if (!grid) {
gridLayout[0] = availableLen;
return;
}
int gridLen = axis.m_sizes.size();
ASSERT(gridLen);
int totalRelative = 0;
int totalFixed = 0;
int totalPercent = 0;
int countRelative = 0;
int countFixed = 0;
int countPercent = 0;
// First we need to investigate how many columns of each type we have and
// how much space these columns are going to require.
for (int i = 0; i < gridLen; ++i) {
// Count the total length of all of the fixed columns/rows -> totalFixed
// Count the number of columns/rows which are fixed -> countFixed
if (grid[i].isFixed()) {
gridLayout[i] = max(grid[i].value(), 0);
totalFixed += gridLayout[i];
countFixed++;
}
// Count the total percentage of all of the percentage columns/rows -> totalPercent
// Count the number of columns/rows which are percentages -> countPercent
if (grid[i].isPercent()) {
gridLayout[i] = max(grid[i].calcValue(availableLen), 0);
totalPercent += gridLayout[i];
countPercent++;
}
// Count the total relative of all the relative columns/rows -> totalRelative
// Count the number of columns/rows which are relative -> countRelative
if (grid[i].isRelative()) {
totalRelative += max(grid[i].value(), 1);
countRelative++;
}
}
int remainingLen = availableLen;
// Fixed columns/rows are our first priority. If there is not enough space to fit all fixed
// columns/rows we need to proportionally adjust their size.
if (totalFixed > remainingLen) {
int remainingFixed = remainingLen;
for (int i = 0; i < gridLen; ++i) {
if (grid[i].isFixed()) {
gridLayout[i] = (gridLayout[i] * remainingFixed) / totalFixed;
remainingLen -= gridLayout[i];
}
}
} else
remainingLen -= totalFixed;
// Percentage columns/rows are our second priority. Divide the remaining space proportionally
// over all percentage columns/rows. IMPORTANT: the size of each column/row is not relative
// to 100%, but to the total percentage. For example, if there are three columns, each of 75%,
// and the available space is 300px, each column will become 100px in width.
if (totalPercent > remainingLen) {
int remainingPercent = remainingLen;
for (int i = 0; i < gridLen; ++i) {
if (grid[i].isPercent()) {
gridLayout[i] = (gridLayout[i] * remainingPercent) / totalPercent;
remainingLen -= gridLayout[i];
}
}
} else
remainingLen -= totalPercent;
// Relative columns/rows are our last priority. Divide the remaining space proportionally
// over all relative columns/rows. IMPORTANT: the relative value of 0* is treated as 1*.
if (countRelative) {
int lastRelative = 0;
int remainingRelative = remainingLen;
for (int i = 0; i < gridLen; ++i) {
if (grid[i].isRelative()) {
gridLayout[i] = (max(grid[i].value(), 1) * remainingRelative) / totalRelative;
remainingLen -= gridLayout[i];
lastRelative = i;
}
}
// If we could not evenly distribute the available space of all of the relative
// columns/rows, the remainder will be added to the last column/row.
// For example: if we have a space of 100px and three columns (*,*,*), the remainder will
// be 1px and will be added to the last column: 33px, 33px, 34px.
if (remainingLen) {
gridLayout[lastRelative] += remainingLen;
remainingLen = 0;
}
}
// If we still have some left over space we need to divide it over the already existing
// columns/rows
if (remainingLen) {
// Our first priority is to spread if over the percentage columns. The remaining
// space is spread evenly, for example: if we have a space of 100px, the columns
// definition of 25%,25% used to result in two columns of 25px. After this the
// columns will each be 50px in width.
if (countPercent && totalPercent) {
int remainingPercent = remainingLen;
int changePercent = 0;
for (int i = 0; i < gridLen; ++i) {
if (grid[i].isPercent()) {
changePercent = (remainingPercent * gridLayout[i]) / totalPercent;
gridLayout[i] += changePercent;
remainingLen -= changePercent;
}
}
} else if (totalFixed) {
// Our last priority is to spread the remaining space over the fixed columns.
// For example if we have 100px of space and two column of each 40px, both
// columns will become exactly 50px.
int remainingFixed = remainingLen;
int changeFixed = 0;
for (int i = 0; i < gridLen; ++i) {
if (grid[i].isFixed()) {
changeFixed = (remainingFixed * gridLayout[i]) / totalFixed;
gridLayout[i] += changeFixed;
remainingLen -= changeFixed;
}
}
}
}
// If we still have some left over space we probably ended up with a remainder of
// a division. We cannot spread it evenly anymore. If we have any percentage
// columns/rows simply spread the remainder equally over all available percentage columns,
// regardless of their size.
if (remainingLen && countPercent) {
int remainingPercent = remainingLen;
int changePercent = 0;
for (int i = 0; i < gridLen; ++i) {
if (grid[i].isPercent()) {
changePercent = remainingPercent / countPercent;
gridLayout[i] += changePercent;
remainingLen -= changePercent;
}
}
}
// If we don't have any percentage columns/rows we only have fixed columns. Spread
// the remainder equally over all fixed columns/rows.
else if (remainingLen && countFixed) {
int remainingFixed = remainingLen;
int changeFixed = 0;
for (int i = 0; i < gridLen; ++i) {
if (grid[i].isFixed()) {
changeFixed = remainingFixed / countFixed;
gridLayout[i] += changeFixed;
remainingLen -= changeFixed;
}
}
}
// Still some left over. Add it to the last column, because it is impossible
// spread it evenly or equally.
if (remainingLen)
gridLayout[gridLen - 1] += remainingLen;
// now we have the final layout, distribute the delta over it
bool worked = true;
int* gridDelta = axis.m_deltas.data();
for (int i = 0; i < gridLen; ++i) {
if (gridLayout[i] && gridLayout[i] + gridDelta[i] <= 0)
worked = false;
gridLayout[i] += gridDelta[i];
}
// if the deltas broke something, undo them
if (!worked) {
for (int i = 0; i < gridLen; ++i)
gridLayout[i] -= gridDelta[i];
axis.m_deltas.fill(0);
}
}
void RenderFrameSet::fillFromEdgeInfo(const FrameEdgeInfo& edgeInfo, int r, int c)
{
if (edgeInfo.allowBorder(LeftFrameEdge))
m_cols.m_allowBorder[c] = true;
if (edgeInfo.allowBorder(RightFrameEdge))
m_cols.m_allowBorder[c + 1] = true;
if (edgeInfo.preventResize(LeftFrameEdge))
m_cols.m_preventResize[c] = true;
if (edgeInfo.preventResize(RightFrameEdge))
m_cols.m_preventResize[c + 1] = true;
if (edgeInfo.allowBorder(TopFrameEdge))
m_rows.m_allowBorder[r] = true;
if (edgeInfo.allowBorder(BottomFrameEdge))
m_rows.m_allowBorder[r + 1] = true;
if (edgeInfo.preventResize(TopFrameEdge))
m_rows.m_preventResize[r] = true;
if (edgeInfo.preventResize(BottomFrameEdge))
m_rows.m_preventResize[r + 1] = true;
}
void RenderFrameSet::computeEdgeInfo()
{
m_rows.m_preventResize.fill(frameSet()->noResize());
m_rows.m_allowBorder.fill(false);
m_cols.m_preventResize.fill(frameSet()->noResize());
m_cols.m_allowBorder.fill(false);
RenderObject* child = firstChild();
if (!child)
return;
int rows = frameSet()->totalRows();
int cols = frameSet()->totalCols();
for (int r = 0; r < rows; ++r) {
for (int c = 0; c < cols; ++c) {
FrameEdgeInfo edgeInfo;
if (child->isFrameSet())
edgeInfo = static_cast<RenderFrameSet*>(child)->edgeInfo();
else
edgeInfo = static_cast<RenderFrame*>(child)->edgeInfo();
fillFromEdgeInfo(edgeInfo, r, c);
child = child->nextSibling();
if (!child)
return;
}
}
}
FrameEdgeInfo RenderFrameSet::edgeInfo() const
{
FrameEdgeInfo result(frameSet()->noResize(), true);
int rows = frameSet()->totalRows();
int cols = frameSet()->totalCols();
if (rows && cols) {
result.setPreventResize(LeftFrameEdge, m_cols.m_preventResize[0]);
result.setAllowBorder(LeftFrameEdge, m_cols.m_allowBorder[0]);
result.setPreventResize(RightFrameEdge, m_cols.m_preventResize[cols]);
result.setAllowBorder(RightFrameEdge, m_cols.m_allowBorder[cols]);
result.setPreventResize(TopFrameEdge, m_rows.m_preventResize[0]);
result.setAllowBorder(TopFrameEdge, m_rows.m_allowBorder[0]);
result.setPreventResize(BottomFrameEdge, m_rows.m_preventResize[rows]);
result.setAllowBorder(BottomFrameEdge, m_rows.m_allowBorder[rows]);
}
return result;
}
void RenderFrameSet::layout()
{
ASSERT(needsLayout());
bool doFullRepaint = selfNeedsLayout() && checkForRepaintDuringLayout();
IntRect oldBounds;
if (doFullRepaint)
oldBounds = absoluteClippedOverflowRect();
if (!parent()->isFrameSet() && !document()->printing()) {
#ifdef FLATTEN_FRAMESET
// Force a grid recalc.
m_gridCalculated = false;
#endif
setWidth(view()->viewWidth());
setHeight(view()->viewHeight());
}
size_t cols = frameSet()->totalCols();
size_t rows = frameSet()->totalRows();
if (m_rows.m_sizes.size() != rows || m_cols.m_sizes.size() != cols) {
m_rows.resize(rows);
m_cols.resize(cols);
#ifdef FLATTEN_FRAMESET
m_gridCalculated = false;
#endif
}
#ifdef FLATTEN_FRAMESET
if (!m_gridCalculated) {
m_gridCalculated = true;
// Make all the child framesets recalculate their grid.
RenderObject* child = firstChild();
for (; child; child = child->nextSibling()) {
if (child->isFrameSet())
static_cast<RenderFrameSet*>(child)->setGridNeedsLayout();
}
#endif
int borderThickness = frameSet()->border();
layOutAxis(m_rows, frameSet()->rowLengths(), height() - (rows - 1) * borderThickness);
layOutAxis(m_cols, frameSet()->colLengths(), width() - (cols - 1) * borderThickness);
#ifdef FLATTEN_FRAMESET
}
#endif
positionFrames();
RenderContainer::layout();
computeEdgeInfo();
if (doFullRepaint) {
view()->repaintViewRectangle(oldBounds);
IntRect newBounds = absoluteClippedOverflowRect();
if (newBounds != oldBounds)
view()->repaintViewRectangle(newBounds);
}
setNeedsLayout(false);
}
void RenderFrameSet::positionFrames()
{
RenderBox* child = firstChildBox();
if (!child)
return;
int rows = frameSet()->totalRows();
int cols = frameSet()->totalCols();
int yPos = 0;
int borderThickness = frameSet()->border();
#ifdef FLATTEN_FRAMESET
// Keep track of the maximum width of a row which will become the maximum width of the frameset.
int maxWidth = 0;
const Length* rowLengths = frameSet()->rowLengths();
const Length* colLengths = frameSet()->colLengths();
for (int r = 0; r < rows && child; r++) {
int xPos = 0;
int height = m_rows.m_sizes[r];
int rowHeight = -1;
if (rowLengths) {
Length l = rowLengths[r];
if (l.isFixed())
rowHeight = l.value();
}
for (int c = 0; c < cols && child; c++) {
child->setX(xPos);
child->setY(yPos);
child->setWidth(m_cols.m_sizes[c]);
child->setHeight(height);
int colWidth = -1;
if (colLengths) {
Length l = colLengths[c];
if (l.isFixed())
colWidth = l.value();
}
if (colWidth && rowHeight) {
child->setNeedsLayout(true);
child->layout();
} else {
child->layoutIfNeeded();
}
ASSERT(child->width() >= m_cols.m_sizes[c]);
m_cols.m_sizes[c] = child->width();
height = max(child->height(), height);
xPos += child->width() + borderThickness;
child = (RenderBox*)child->nextSibling();
}
ASSERT(height >= m_rows.m_sizes[r]);
m_rows.m_sizes[r] = height;
maxWidth = max(xPos, maxWidth);
yPos += height + borderThickness;
}
// Compute a new width and height according to the positioning of each expanded child frame.
// Note: we subtract borderThickness because we only count borders between frames.
int newWidth = maxWidth - borderThickness;
int newHeight = yPos - borderThickness;
// Distribute the extra width and height evenly across the grid.
int dWidth = (width() - newWidth) / cols;
int dHeight = (height() - newHeight) / rows;
if (dWidth > 0) {
int availableWidth = width() - (cols - 1) * borderThickness;
for (int c = 0; c < cols; c++)
availableWidth -= m_cols.m_sizes[c] += dWidth;
// If the extra width did not distribute evenly, add the remainder to
// the last column.
if (availableWidth)
m_cols.m_sizes[cols - 1] += availableWidth;
}
if (dHeight > 0) {
int availableHeight = height() - (rows - 1) * borderThickness;
for (int r = 0; r < rows; r++)
availableHeight -= m_rows.m_sizes[r] += dHeight;
// If the extra height did not distribute evenly, add the remainder to
// the last row.
if (availableHeight)
m_rows.m_sizes[rows - 1] += availableHeight;
}
// Ensure the rows and columns are filled by falling through to the normal
// layout
setHeight(max(height(), newHeight));
setWidth(max(width(), newWidth));
child = (RenderBox*)firstChild();
yPos = 0;
#endif // FLATTEN_FRAMESET
for (int r = 0; r < rows; r++) {
int xPos = 0;
int height = m_rows.m_sizes[r];
for (int c = 0; c < cols; c++) {
child->setLocation(xPos, yPos);
int width = m_cols.m_sizes[c];
// has to be resized and itself resize its contents
if (width != child->width() || height != child->height()) {
child->setWidth(width);
child->setHeight(height);
child->setNeedsLayout(true);
child->layout();
}
xPos += width + borderThickness;
child = child->nextSiblingBox();
if (!child)
return;
}
yPos += height + borderThickness;
}
// all the remaining frames are hidden to avoid ugly spurious unflowed frames
for (; child; child = child->nextSiblingBox()) {
child->setWidth(0);
child->setHeight(0);
child->setNeedsLayout(false);
}
}
void RenderFrameSet::startResizing(GridAxis& axis, int position)
{
int split = hitTestSplit(axis, position);
if (split == noSplit || !axis.m_allowBorder[split] || axis.m_preventResize[split]) {
axis.m_splitBeingResized = noSplit;
return;
}
axis.m_splitBeingResized = split;
axis.m_splitResizeOffset = position - splitPosition(axis, split);
}
void RenderFrameSet::continueResizing(GridAxis& axis, int position)
{
if (needsLayout())
return;
if (axis.m_splitBeingResized == noSplit)
return;
int currentSplitPosition = splitPosition(axis, axis.m_splitBeingResized);
int delta = (position - currentSplitPosition) - axis.m_splitResizeOffset;
if (delta == 0)
return;
axis.m_deltas[axis.m_splitBeingResized - 1] += delta;
axis.m_deltas[axis.m_splitBeingResized] -= delta;
setNeedsLayout(true);
}
bool RenderFrameSet::userResize(MouseEvent* evt)
{
if (!m_isResizing) {
if (needsLayout())
return false;
if (evt->type() == eventNames().mousedownEvent && evt->button() == LeftButton) {
FloatPoint pos = localToAbsolute();
startResizing(m_cols, evt->pageX() - pos.x());
startResizing(m_rows, evt->pageY() - pos.y());
if (m_cols.m_splitBeingResized != noSplit || m_rows.m_splitBeingResized != noSplit) {
setIsResizing(true);
return true;
}
}
} else {
if (evt->type() == eventNames().mousemoveEvent || (evt->type() == eventNames().mouseupEvent && evt->button() == LeftButton)) {
FloatPoint pos = localToAbsolute();
continueResizing(m_cols, evt->pageX() - pos.x());
continueResizing(m_rows, evt->pageY() - pos.y());
if (evt->type() == eventNames().mouseupEvent && evt->button() == LeftButton) {
setIsResizing(false);
return true;
}
}
}
return false;
}
void RenderFrameSet::setIsResizing(bool isResizing)
{
m_isResizing = isResizing;
for (RenderObject* p = parent(); p; p = p->parent())
if (p->isFrameSet())
static_cast<RenderFrameSet*>(p)->m_isChildResizing = isResizing;
if (Frame* frame = document()->frame())
frame->eventHandler()->setResizingFrameSet(isResizing ? frameSet() : 0);
}
bool RenderFrameSet::isResizingRow() const
{
return m_isResizing && m_rows.m_splitBeingResized != noSplit;
}
bool RenderFrameSet::isResizingColumn() const
{
return m_isResizing && m_cols.m_splitBeingResized != noSplit;
}
bool RenderFrameSet::canResize(const IntPoint& p) const
{
return hitTestSplit(m_cols, p.x()) != noSplit || hitTestSplit(m_rows, p.y()) != noSplit;
}
bool RenderFrameSet::canResizeRow(const IntPoint& p) const
{
int r = hitTestSplit(m_rows, p.y());
return r != noSplit && m_rows.m_allowBorder[r] && !m_rows.m_preventResize[r];
}
bool RenderFrameSet::canResizeColumn(const IntPoint& p) const
{
int c = hitTestSplit(m_cols, p.x());
return c != noSplit && m_cols.m_allowBorder[c] && !m_cols.m_preventResize[c];
}
int RenderFrameSet::splitPosition(const GridAxis& axis, int split) const
{
if (needsLayout())
return 0;
int borderThickness = frameSet()->border();
int size = axis.m_sizes.size();
if (!size)
return 0;
int position = 0;
for (int i = 0; i < split && i < size; ++i)
position += axis.m_sizes[i] + borderThickness;
return position - borderThickness;
}
int RenderFrameSet::hitTestSplit(const GridAxis& axis, int position) const
{
if (needsLayout())
return noSplit;
int borderThickness = frameSet()->border();
if (borderThickness <= 0)
return noSplit;
size_t size = axis.m_sizes.size();
if (!size)
return noSplit;
int splitPosition = axis.m_sizes[0];
for (size_t i = 1; i < size; ++i) {
if (position >= splitPosition && position < splitPosition + borderThickness)
return i;
splitPosition += borderThickness + axis.m_sizes[i];
}
return noSplit;
}
bool RenderFrameSet::isChildAllowed(RenderObject* child, RenderStyle*) const
{
return child->isFrame() || child->isFrameSet();
}
} // namespace WebCore