/** * This file is part of the KDE project. * * Copyright (C) 1999 Lars Knoll (knoll@kde.org) * (C) 2000 Simon Hausmann * (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., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. * */ #include "config.h" #include "RenderFrameSet.h" #include "Cursor.h" #include "EventNames.h" #include "Frame.h" #include "FrameView.h" #include "GraphicsContext.h" #include "HTMLFrameSetElement.h" #include "HTMLNames.h" #include "TextStream.h" #include "MouseEvent.h" #include "RenderFrame.h" #include "RenderView.h" #include "Settings.h" namespace WebCore { using namespace EventNames; using namespace HTMLNames; inline HTMLFrameSetElement* RenderFrameSet::frameSet() const { return static_cast(node()); } RenderFrameSet::RenderFrameSet(HTMLFrameSetElement* frameSet) : RenderContainer(frameSet) , m_hSplitVar(0) , m_vSplitVar(0) , m_hSplit(-1) , m_vSplit(-1) , m_resizing(false) , m_clientResizing(false) { // init RenderObject attributes setInline(false); for (int k = 0; k < 2; ++k) { m_gridLen[k] = -1; m_gridDelta[k] = 0; m_gridLayout[k] = 0; } } RenderFrameSet::~RenderFrameSet() { for (int k = 0; k < 2; ++k) { if (m_gridLayout[k]) delete [] m_gridLayout[k]; if (m_gridDelta[k]) delete [] m_gridDelta[k]; } if (m_hSplitVar) delete [] m_hSplitVar; if (m_vSplitVar) delete [] m_vSplitVar; } bool RenderFrameSet::nodeAtPoint(NodeInfo& info, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction) { if (hitTestAction != HitTestForeground) return false; bool inside = RenderContainer::nodeAtPoint(info, _x, _y, _tx, _ty, hitTestAction) || m_resizing || canResize(_x, _y); if (inside && element() && !element()->noResize() && !info.readonly() && !info.innerNode()) { info.setInnerNode(element()); info.setInnerNonSharedNode(element()); } return inside || m_clientResizing; } void RenderFrameSet::layout() { ASSERT(needsLayout()); ASSERT(minMaxKnown()); if (!parent()->isFrameSet()) { FrameView* frameView = view()->frameView(); m_width = frameView->visibleWidth(); m_height = frameView->visibleHeight(); if (flattenFrameset()) { // make the top level frameset at least 800*600 wide/high calcMinMaxWidth(); m_width = max(m_width, m_minWidth); if (!frameView->frame()->ownerElement()) m_height = max(m_height, 600); } } int remainingLen[2]; remainingLen[1] = m_width - (element()->totalCols()-1)*element()->border(); if (remainingLen[1] < 0) remainingLen[1] = 0; remainingLen[0] = m_height - (element()->totalRows()-1)*element()->border(); if (remainingLen[0] < 0) remainingLen[0] = 0; int availableLen[2]; availableLen[0] = remainingLen[0]; availableLen[1] = remainingLen[1]; if (m_gridLen[0] != element()->totalRows() || m_gridLen[1] != element()->totalCols()) { // number of rows or cols changed // need to zero out the deltas m_gridLen[0] = element()->totalRows(); m_gridLen[1] = element()->totalCols(); for (int k = 0; k < 2; ++k) { if (m_gridDelta[k]) delete [] m_gridDelta[k]; m_gridDelta[k] = new int[m_gridLen[k]]; if (m_gridLayout[k]) delete [] m_gridLayout[k]; m_gridLayout[k] = new int[m_gridLen[k]]; for (int i = 0; i < m_gridLen[k]; ++i) m_gridDelta[k][i] = 0; } } for (int k = 0; k < 2; ++k) { int totalRelative = 0; int totalFixed = 0; int totalPercent = 0; int countRelative = 0; int countFixed = 0; int countPercent = 0; int gridLen = m_gridLen[k]; int* gridDelta = m_gridDelta[k]; Length* grid = k ? element()->m_cols : element()->m_rows; int* gridLayout = m_gridLayout[k]; if (grid) { assert(gridLen); // 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[k]), 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++; } } // 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[k]) { int remainingFixed = remainingLen[k]; for (int i = 0; i < gridLen; ++i) { if (grid[i].isFixed()) { gridLayout[i] = (gridLayout[i] * remainingFixed) / totalFixed; remainingLen[k] -= gridLayout[i]; } } } else remainingLen[k] -= 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[k]) { int remainingPercent = remainingLen[k]; for (int i = 0; i < gridLen; ++i) { if (grid[i].isPercent()) { gridLayout[i] = (gridLayout[i] * remainingPercent) / totalPercent; remainingLen[k] -= gridLayout[i]; } } } else remainingLen[k] -= 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[k]; for (int i = 0; i < gridLen; ++i) { if (grid[i].isRelative()) { gridLayout[i] = (max(grid[i].value(), 1) * remainingRelative) / totalRelative; remainingLen[k] -= gridLayout[i]; lastRelative = i; } } // If we could not evently 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[k]) { gridLayout[lastRelative] += remainingLen[k]; remainingLen[k] = 0; } } // If we still have some left over space we need to divide it over the already existing // columns/rows if (remainingLen[k]) { // 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[k]; int changePercent = 0; for (int i = 0; i < gridLen; ++i) { if (grid[i].isPercent()) { changePercent = (remainingPercent * gridLayout[i]) / totalPercent; gridLayout[i] += changePercent; remainingLen[k] -= 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[k]; int changeFixed = 0; for (int i = 0; i < gridLen; ++i) { if (grid[i].isFixed()) { changeFixed = (remainingFixed * gridLayout[i]) / totalFixed; gridLayout[i] += changeFixed; remainingLen[k] -= changeFixed; } } } } // If we still have some left over space we probably ended up with a remainder of // a division. We can not 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[k] && countPercent) { int remainingPercent = remainingLen[k]; int changePercent = 0; for (int i = 0; i < gridLen; ++i) { if (grid[i].isPercent()) { changePercent = remainingPercent / countPercent; gridLayout[i] += changePercent; remainingLen[k] -= 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[k] && countFixed) { int remainingFixed = remainingLen[k]; int changeFixed = 0; for (int i = 0; i < gridLen; ++i) { if (grid[i].isFixed()) { changeFixed = remainingFixed / countFixed; gridLayout[i] += changeFixed; remainingLen[k] -= changeFixed; } } } // Still some left over... simply add it to the last column, because it is impossible // spread it evenly or equally. if (remainingLen[k]) gridLayout[gridLen - 1] += remainingLen[k]; // now we have the final layout, distribute the delta over it bool worked = true; for (int i = 0; i < gridLen; ++i) { if (gridLayout[i] && gridLayout[i] + gridDelta[i] <= 0) worked = false; gridLayout[i] += gridDelta[i]; } // now the delta's broke something, undo it and reset deltas if (!worked) { for (int i = 0; i < gridLen; ++i) { gridLayout[i] -= gridDelta[i]; gridDelta[i] = 0; } } } else gridLayout[0] = remainingLen[k]; } if (flattenFrameset()) positionFramesWithFlattening(); else positionFrames(); RenderObject *child = firstChild(); if (!child) goto end2; if (!m_hSplitVar && !m_vSplitVar) { if (!m_vSplitVar && element()->totalCols() > 1) { m_vSplitVar = new bool[element()->totalCols()]; for (int i = 0; i < element()->totalCols(); i++) m_vSplitVar[i] = true; } if (!m_hSplitVar && element()->totalRows() > 1) { m_hSplitVar = new bool[element()->totalRows()]; for (int i = 0; i < element()->totalRows(); i++) m_hSplitVar[i] = true; } for (int r = 0; r < element()->totalRows(); r++) { for (int c = 0; c < element()->totalCols(); c++) { bool fixed = false; if (child->isFrameSet()) fixed = static_cast(child)->element()->noResize(); else fixed = static_cast(child)->element()->noResize(); if (fixed) { if (element()->totalCols() > 1) { if (c>0) m_vSplitVar[c-1] = false; m_vSplitVar[c] = false; } if (element()->totalRows() > 1) { if (r>0) m_hSplitVar[r-1] = false; m_hSplitVar[r] = false; } child = child->nextSibling(); if (!child) goto end1; } } } } end1: RenderContainer::layout(); end2: setNeedsLayout(false); } void RenderFrameSet::calcMinMaxWidth() { RenderContainer::calcMinMaxWidth(); if (!flattenFrameset()) return; // make the top level frameset at least 800*600 wide/high if (!parent()->isFrameSet() && !element()->document()->frame()->ownerElement()) m_minWidth = max(m_minWidth, 200); m_maxWidth = m_minWidth; setMinMaxKnown(); } void RenderFrameSet::positionFrames() { int r; int c; RenderObject *child = firstChild(); if (!child) return; // Node *child = _first; // if (!child) return; int yPos = 0; for (r = 0; r < element()->totalRows(); r++) { int xPos = 0; for (c = 0; c < element()->totalCols(); c++) { child->setPos(xPos, yPos); // has to be resized and itself resize its contents if ((m_gridLayout[1][c] != child->width()) || (m_gridLayout[0][r] != child->height())) { child->setWidth(m_gridLayout[1][c]); child->setHeight(m_gridLayout[0][r]); child->setNeedsLayout(true, false); child->layout(); } xPos += m_gridLayout[1][c] + element()->border(); child = child->nextSibling(); if (!child) return; } yPos += m_gridLayout[0][r] + element()->border(); } // all the remaining frames are hidden to avoid ugly // spurious unflowed frames while (child) { child->setWidth(0); child->setHeight(0); child->setNeedsLayout(false); child = child->nextSibling(); } } void RenderFrameSet::positionFramesWithFlattening() { RenderObject* child = firstChild(); if (!child) return; int rows = frameSet()->totalRows(); int cols = frameSet()->totalCols(); int yPos = 0; int borderThickness = frameSet()->border(); bool changes = false; // calculate frameset height based on actual content height to eliminate scrolling bool out = false; int widest = 0; for(int r = 0; r < rows && !out; r++) { int highest = 0; int xPos = 0; int extra = 0; int height = m_gridLayout[0][r]; for(int c = 0; c < cols; c++) { IntRect oldRect(child->xPos(), child->yPos(), child->width(), child->height()); child->setPos(xPos, yPos); // has to be resized and itself resize its contents int width = m_gridLayout[1][c]; child->setWidth(width ? width + extra / (cols - c) : 0); child->setHeight(height); child->setNeedsLayout(true, false); bool flexibleWidth = true; bool flexibleHeight = true; if (frameSet()->m_cols) flexibleWidth = !frameSet()->m_cols[c].isFixed(); if (frameSet()->m_rows) flexibleHeight = !frameSet()->m_rows[r].isFixed(); if (child->isFrameSet()) // should pass flexibility info here too? static_cast(child)->layout(); else static_cast(child)->layoutWithFlattening(flexibleWidth, flexibleHeight); if (IntRect(child->xPos(), child->yPos(), child->width(), child->height()) != oldRect) changes = true; // difference between calculated frame width and the width it actually decides to have extra += width - child->width(); if (highest < child->height()) highest = child->height(); xPos += child->width() + borderThickness; child = child->nextSibling(); if (!child) { out = true; break; } } if (xPos > widest) widest = xPos; yPos += highest + frameSet()->border(); } m_height = yPos - borderThickness; m_width = max(m_width, widest - borderThickness); out = false; child = firstChild(); for(int r = 0; r < rows && !out; r++) { for(int c = 0; c < cols; c++) { // ensure the rows and columns are filled IntSize oldSize(child->width(), child->height()); if (r == rows - 1) child->setHeight(m_height - child->yPos()); if (c == cols - 1) child->setWidth(m_width - child->xPos()); // update rows/cols array to match reality m_gridLayout[1][c] = child->width(); m_gridLayout[0][r] = child->height(); if (IntSize(child->width(), child->height()) != oldSize) { // update to final size child->setNeedsLayout(true, false); if (child->isFrameSet()) static_cast(child)->layout(); else static_cast(child)->layoutWithFlattening(false, false); changes = true; } child = child->nextSibling(); if (!child) { out = true; break; } } } if (changes) repaint(); // all the remaining frames are hidden to avoid ugly spurious unflowed frames for (; child; child = child->nextSibling()) { child->setWidth(0); child->setHeight(0); child->setNeedsLayout(false); } } bool RenderFrameSet::flattenFrameset() const { return element()->document()->frame() && element()->document()->frame()->settings()->flatFrameSetLayoutEnabled(); } bool RenderFrameSet::userResize(MouseEvent* evt) { if (needsLayout()) return false; bool res = false; int _x = evt->pageX(); int _y = evt->pageY(); if (!m_resizing && evt->type() == mousemoveEvent || evt->type() == mousedownEvent) { m_hSplit = -1; m_vSplit = -1; //bool resizePossible = true; // check if we're over a horizontal or vertical boundary int pos = m_gridLayout[1][0] + xPos(); for (int c = 1; c < element()->totalCols(); c++) { if (_x >= pos && _x <= pos+element()->border()) { if (m_vSplitVar && m_vSplitVar[c - 1]) m_vSplit = c - 1; res = true; break; } pos += m_gridLayout[1][c] + element()->border(); } pos = m_gridLayout[0][0] + yPos(); for (int r = 1; r < element()->totalRows(); r++) { if (_y >= pos && _y <= pos+element()->border()) { if (m_hSplitVar && m_hSplitVar[r - 1]) m_hSplit = r - 1; res = true; break; } pos += m_gridLayout[0][r] + element()->border(); } if (evt->type() == mousedownEvent) { setResizing(true); m_vSplitPos = _x; m_hSplitPos = _y; m_oldpos = -1; } else view()->frameView()->setCursor(pointerCursor()); } // ### check the resize is not going out of bounds. if (m_resizing && evt->type() == mouseupEvent) { setResizing(false); if (m_vSplit != -1) { int delta = m_vSplitPos - _x; m_gridDelta[1][m_vSplit] -= delta; m_gridDelta[1][m_vSplit+1] += delta; } if (m_hSplit != -1) { int delta = m_hSplitPos - _y; m_gridDelta[0][m_hSplit] -= delta; m_gridDelta[0][m_hSplit+1] += delta; } // this just schedules the relayout // important, otherwise the moving indicator is not correctly erased setNeedsLayout(true); } else if (m_resizing || evt->type() == mouseupEvent) { FrameView* v = view()->frameView(); v->disableFlushDrawing(); GraphicsContext* context = v->lockDrawingFocus(); IntRect r(xPos(), yPos(), width(), height()); const int rBord = 3; int sw = element()->border(); int p = m_resizing ? (m_vSplit > -1 ? _x : _y) : -1; const RGBA32 greyQuarterOpacity = 0x40A0A0A0; if (m_vSplit > -1) { if (m_oldpos >= 0) v->updateContents(IntRect(m_oldpos + sw/2 - rBord, r.y(), 2 * rBord, r.height()), true); if (p >= 0) { context->setPen(Pen::NoPen); context->setFillColor(greyQuarterOpacity); context->drawRect(IntRect(p + sw/2 - rBord, r.y(), 2 * rBord, r.height())); } } else { if (m_oldpos >= 0) v->updateContents(IntRect(r.x(), m_oldpos + sw/2 - rBord, r.width(), 2 * rBord), true); if (p >= 0) { context->setPen(Pen::NoPen); context->setFillColor(greyQuarterOpacity); context->drawRect(IntRect(r.x(), p + sw/2 - rBord, r.width(), 2 * rBord)); } } m_oldpos = p; v->unlockDrawingFocus(context); v->enableFlushDrawing(); } return res; } void RenderFrameSet::setResizing(bool e) { m_resizing = e; for (RenderObject* p = parent(); p; p = p->parent()) if (p->isFrameSet()) static_cast(p)->m_clientResizing = m_resizing; view()->frameView()->setResizingFrameSet(e ? element() : 0); } bool RenderFrameSet::canResize(int _x, int _y) { // if we haven't received a layout, then the gridLayout doesn't contain useful data yet if (needsLayout() || !m_gridLayout[0] || !m_gridLayout[1]) return false; // check if we're over a horizontal or vertical boundary int pos = m_gridLayout[1][0]; for (int c = 1; c < element()->totalCols(); c++) if (_x >= pos && _x <= pos+element()->border()) return true; pos = m_gridLayout[0][0]; for (int r = 1; r < element()->totalRows(); r++) if (_y >= pos && _y <= pos+element()->border()) return true; return false; } bool RenderFrameSet::isChildAllowed(RenderObject* child, RenderStyle* style) const { return child->isFrame() || child->isFrameSet(); } #ifndef NDEBUG void RenderFrameSet::dump(TextStream* stream, DeprecatedString ind) const { *stream << " totalrows=" << element()->totalRows(); *stream << " totalcols=" << element()->totalCols(); unsigned i; for (i = 0; i < (unsigned)element()->totalRows(); i++) *stream << " hSplitvar(" << i << ")=" << m_hSplitVar[i]; for (i = 0; i < (unsigned)element()->totalCols(); i++) *stream << " vSplitvar(" << i << ")=" << m_vSplitVar[i]; RenderContainer::dump(stream,ind); } #endif }