diff options
Diffstat (limited to 'khtml/rendering/render_layer.cpp')
-rw-r--r-- | khtml/rendering/render_layer.cpp | 1830 |
1 files changed, 1830 insertions, 0 deletions
diff --git a/khtml/rendering/render_layer.cpp b/khtml/rendering/render_layer.cpp new file mode 100644 index 000000000..b4af3536c --- /dev/null +++ b/khtml/rendering/render_layer.cpp @@ -0,0 +1,1830 @@ +/* + * Copyright (C) 2003 Apple Computer, Inc. + * (C) 2006 Germain Garand <germain@ebooksfrance.org> + * (C) 2006 Allan Sandfeld Jense <kde@carewolf.com> + * + * Portions are Copyright (C) 1998 Netscape Communications Corporation. + * + * Other contributors: + * Robert O'Callahan <roc+@cs.cmu.edu> + * David Baron <dbaron@fas.harvard.edu> + * Christian Biesinger <cbiesinger@web.de> + * Randall Jesup <rjesup@wgate.com> + * Roland Mainz <roland.mainz@informatik.med.uni-giessen.de> + * Josh Soref <timeless@mac.com> + * Boris Zbarsky <bzbarsky@mit.edu> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Alternatively, the contents of this file may be used under the terms + * of either the Mozilla Public License Version 1.1, found at + * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public + * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html + * (the "GPL"), in which case the provisions of the MPL or the GPL are + * applicable instead of those above. If you wish to allow use of your + * version of this file only under the terms of one of those two + * licenses (the MPL or the GPL) and not to allow others to use your + * version of this file under the LGPL, indicate your decision by + * deletingthe provisions above and replace them with the notice and + * other provisions required by the MPL or the GPL, as the case may be. + * If you do not delete the provisions above, a recipient may use your + * version of this file under any of the LGPL, the MPL or the GPL. + */ + +//#define BOX_DEBUG + +#include "render_layer.h" +#include <kdebug.h> +#include <assert.h> +#include "khtmlview.h" +#include "render_canvas.h" +#include "render_arena.h" +#include "render_replaced.h" +#include "xml/dom_docimpl.h" +#include "xml/dom2_eventsimpl.h" +#include "misc/htmltags.h" +#include "html/html_blockimpl.h" +#include "xml/dom_restyler.h" + +#include <qscrollbar.h> +#include <qptrvector.h> +#include <qstyle.h> + +using namespace DOM; +using namespace khtml; + +#ifdef APPLE_CHANGES +QScrollBar* RenderLayer::gScrollBar = 0; +#endif + +#ifndef NDEBUG +static bool inRenderLayerDetach; +#endif + +void +RenderScrollMediator::slotValueChanged() +{ + m_layer->updateScrollPositionFromScrollbars(); +} + +RenderLayer::RenderLayer(RenderObject* object) +: m_object( object ), +m_parent( 0 ), +m_previous( 0 ), +m_next( 0 ), +m_first( 0 ), +m_last( 0 ), +m_x( 0 ), +m_y( 0 ), +m_scrollX( 0 ), +m_scrollY( 0 ), +m_scrollWidth( 0 ), +m_scrollHeight( 0 ), +m_hBar( 0 ), +m_vBar( 0 ), +m_scrollMediator( 0 ), +m_posZOrderList( 0 ), +m_negZOrderList( 0 ), +m_overflowList(0), +m_zOrderListsDirty( true ), +m_overflowListDirty(true), +m_isOverflowOnly( shouldBeOverflowOnly() ), +m_markedForRepaint( false ), +m_hasOverlaidWidgets( false ), +m_marquee( 0 ) +{ +} + +RenderLayer::~RenderLayer() +{ + // Child layers will be deleted by their corresponding render objects, so + // our destructor doesn't have to do anything. + delete m_hBar; + delete m_vBar; + delete m_scrollMediator; + delete m_posZOrderList; + delete m_negZOrderList; + delete m_overflowList; + delete m_marquee; +} + +void RenderLayer::updateLayerPosition() +{ + + // The canvas is sized to the docWidth/Height over in RenderCanvas::layout, so we + // don't need to ever update our layer position here. + if (renderer()->isCanvas()) + return; + + int x = m_object->xPos(); + int y = m_object->yPos() - m_object->borderTopExtra(); + + if (!m_object->isPositioned()) { + // We must adjust our position by walking up the render tree looking for the + // nearest enclosing object with a layer. + RenderObject* curr = m_object->parent(); + while (curr && !curr->layer()) { + x += curr->xPos(); + y += curr->yPos(); + curr = curr->parent(); + } + if (curr) + y += curr->borderTopExtra(); + } + + if (m_object->isRelPositioned()) + static_cast<RenderBox*>(m_object)->relativePositionOffset(x, y); + + // Subtract our parent's scroll offset. + if (m_object->isPositioned() && enclosingPositionedAncestor()) { + RenderLayer* positionedParent = enclosingPositionedAncestor(); + + // For positioned layers, we subtract out the enclosing positioned layer's scroll offset. + positionedParent->subtractScrollOffset(x, y); + positionedParent->checkInlineRelOffset(m_object, x, y); + } + else if (parent()) + parent()->subtractScrollOffset(x, y); + + setPos(x,y); +} + +QRegion RenderLayer::paintedRegion(RenderLayer* rootLayer) +{ + updateZOrderLists(); + QRegion r; + if (m_negZOrderList) { + uint count = m_negZOrderList->count(); + for (uint i = 0; i < count; i++) { + RenderLayer* child = m_negZOrderList->at(i); + r += child->paintedRegion(rootLayer); + } + } + const RenderStyle *s= renderer()->style(); + if (s->visibility() == VISIBLE) { + int x = 0; int y = 0; + convertToLayerCoords(rootLayer,x,y); + QRect cr(x,y,width(),height()); + if ( s->backgroundImage() || s->backgroundColor().isValid() || s->hasBorder() || + renderer()->scrollsOverflow() || renderer()->isReplaced() ) { + r += cr; + } else { + r += renderer()->visibleFlowRegion(x, y); + } + } + + if (m_posZOrderList) { + uint count = m_posZOrderList->count(); + for (uint i = 0; i < count; i++) { + RenderLayer* child = m_posZOrderList->at(i); + r += child->paintedRegion(rootLayer); + } + } + return r; +} + +void RenderLayer::repaint( Priority p, bool markForRepaint ) +{ + if (markForRepaint && m_markedForRepaint) + return; + for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) + child->repaint( p, markForRepaint ); + QRect layerBounds, damageRect, fgrect; + calculateRects(renderer()->canvas()->layer(), renderer()->viewRect(), layerBounds, damageRect, fgrect); + m_visibleRect = damageRect.intersect( layerBounds ); + if (m_visibleRect.isValid()) + renderer()->canvas()->repaintViewRectangle( m_visibleRect.x(), m_visibleRect.y(), m_visibleRect.width(), m_visibleRect.height(), (p > NormalPriority) ); + if (markForRepaint) + m_markedForRepaint = true; +} + +void RenderLayer::updateLayerPositions(RenderLayer* rootLayer, bool doFullRepaint, bool checkForRepaint) +{ + if (doFullRepaint) { + m_object->repaint(); + checkForRepaint = doFullRepaint = false; + } + + updateLayerPosition(); // For relpositioned layers or non-positioned layers, + // we need to keep in sync, since we may have shifted relative + // to our parent layer. + + if (m_hBar || m_vBar) { + // Need to position the scrollbars. + int x = 0; + int y = 0; + convertToLayerCoords(rootLayer, x, y); + QRect layerBounds = QRect(x,y,width(),height()); + positionScrollbars(layerBounds); + } + +#ifdef APPLE_CHANGES + // FIXME: Child object could override visibility. + if (checkForRepaint && (m_object->style()->visibility() == VISIBLE)) + m_object->repaintAfterLayoutIfNeeded(m_repaintRect, m_fullRepaintRect); +#else + if (checkForRepaint && m_markedForRepaint) { + QRect layerBounds, damageRect, fgrect; + calculateRects(rootLayer, renderer()->viewRect(), layerBounds, damageRect, fgrect); + QRect vr = damageRect.intersect( layerBounds ); + if (vr != m_visibleRect && vr.isValid()) { + renderer()->canvas()->repaintViewRectangle( vr.x(), vr.y(), vr.width(), vr.height() ); + m_visibleRect = vr; + } + } + m_markedForRepaint = false; +#endif + + for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) + child->updateLayerPositions(rootLayer, doFullRepaint, checkForRepaint); + + // With all our children positioned, now update our marquee if we need to. + if (m_marquee) + m_marquee->updateMarqueePosition(); +} + +void RenderLayer::updateWidgetMasks(RenderLayer* rootLayer) +{ + if (hasOverlaidWidgets() && !renderer()->canvas()->pagedMode()) { + updateZOrderLists(); + uint count = m_posZOrderList ? m_posZOrderList->count() : 0; + bool needUpdate = (count || !m_region.isNull()); + if (count) { + QScrollView* sv = m_object->document()->view(); + m_region = QRect(0,0,sv->contentsWidth(),sv->contentsHeight()); + + for (uint i = 0; i < count; i++) { + RenderLayer* child = m_posZOrderList->at(i); + if (child->zIndex() == 0 && child->renderer()->style()->position() == STATIC) + continue; // we don't know the widget's exact stacking position within flow + m_region -= child->paintedRegion(rootLayer); + } + } else { + m_region = QRegion(); + } + if (needUpdate) + renderer()->updateWidgetMasks(); + } + for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) + child->updateWidgetMasks(rootLayer); +} + +short RenderLayer::width() const +{ + int w = m_object->width(); + if (!m_object->hasOverflowClip()) + w = kMax(m_object->overflowWidth(), w); + return w; +} + +int RenderLayer::height() const +{ + int h = m_object->height() + m_object->borderTopExtra() + m_object->borderBottomExtra(); + if (!m_object->hasOverflowClip()) + h = kMax(m_object->overflowHeight(), h); + return h; +} + + +RenderLayer *RenderLayer::stackingContext() const +{ + RenderLayer* curr = parent(); + for ( ; curr && !curr->m_object->isCanvas() && + curr->m_object->style()->hasAutoZIndex(); + curr = curr->parent()); + return curr; +} + +RenderLayer* RenderLayer::enclosingPositionedAncestor() const +{ + RenderLayer* curr = parent(); + for ( ; curr && !curr->m_object->isCanvas() && + !curr->m_object->isPositioned() && !curr->m_object->isRelPositioned(); + curr = curr->parent()); + + return curr; +} + +#ifdef APPLE_CHANGES +bool RenderLayer::isTransparent() +{ + return m_object->style()->opacity() < 1.0f; +} + +RenderLayer* RenderLayer::transparentAncestor() +{ + RenderLayer* curr = parent(); + for ( ; curr && curr->m_object->style()->opacity() == 1.0f; curr = curr->parent()); + return curr; +} +#endif + +void* RenderLayer::operator new(size_t sz, RenderArena* renderArena) throw() +{ + return renderArena->allocate(sz); +} + +void RenderLayer::operator delete(void* ptr, size_t sz) +{ + assert(inRenderLayerDetach); + + // Stash size where detach can find it. + *(size_t *)ptr = sz; +} + +void RenderLayer::detach(RenderArena* renderArena) +{ +#ifndef NDEBUG + inRenderLayerDetach = true; +#endif + delete this; +#ifndef NDEBUG + inRenderLayerDetach = false; +#endif + + // Recover the size left there for us by operator delete and free the memory. + renderArena->free(*(size_t *)this, this); +} + +void RenderLayer::addChild(RenderLayer *child, RenderLayer* beforeChild) +{ + RenderLayer* prevSibling = beforeChild ? beforeChild->previousSibling() : lastChild(); + if (prevSibling) { + child->setPreviousSibling(prevSibling); + prevSibling->setNextSibling(child); + } + else + setFirstChild(child); + + if (beforeChild) { + beforeChild->setPreviousSibling(child); + child->setNextSibling(beforeChild); + } + else + setLastChild(child); + + child->setParent(this); + + if (child->isOverflowOnly()) + dirtyOverflowList(); + else { + // Dirty the z-order list in which we are contained. The stackingContext() can be null in the + // case where we're building up generated content layers. This is ok, since the lists will start + // off dirty in that case anyway. + RenderLayer* stackingContext = child->stackingContext(); + if (stackingContext) + stackingContext->dirtyZOrderLists(); + } +} + +RenderLayer* RenderLayer::removeChild(RenderLayer* oldChild) +{ + // remove the child + if (oldChild->previousSibling()) + oldChild->previousSibling()->setNextSibling(oldChild->nextSibling()); + if (oldChild->nextSibling()) + oldChild->nextSibling()->setPreviousSibling(oldChild->previousSibling()); + + if (m_first == oldChild) + m_first = oldChild->nextSibling(); + if (m_last == oldChild) + m_last = oldChild->previousSibling(); + + if (oldChild->isOverflowOnly()) + dirtyOverflowList(); + else { + // Dirty the z-order list in which we are contained. When called via the + // reattachment process in removeOnlyThisLayer, the layer may already be disconnected + // from the main layer tree, so we need to null-check the |stackingContext| value. + RenderLayer* stackingContext = oldChild->stackingContext(); + if (stackingContext) + stackingContext->dirtyZOrderLists(); + } + + oldChild->setPreviousSibling(0); + oldChild->setNextSibling(0); + oldChild->setParent(0); + + return oldChild; +} + +void RenderLayer::removeOnlyThisLayer() +{ + if (!m_parent) + return; + + // Remove us from the parent. + RenderLayer* parent = m_parent; + RenderLayer* nextSib = nextSibling(); + parent->removeChild(this); + + // Now walk our kids and reattach them to our parent. + RenderLayer* current = m_first; + while (current) { + RenderLayer* next = current->nextSibling(); + removeChild(current); + parent->addChild(current, nextSib); + current = next; + } + + detach(renderer()->renderArena()); +} + +void RenderLayer::insertOnlyThisLayer() +{ + if (!m_parent && renderer()->parent()) { + // We need to connect ourselves when our renderer() has a parent. + // Find our enclosingLayer and add ourselves. + RenderLayer* parentLayer = renderer()->parent()->enclosingLayer(); + if (parentLayer) + parentLayer->addChild(this, + renderer()->parent()->findNextLayer(parentLayer, renderer())); + } + + // Remove all descendant layers from the hierarchy and add them to the new position. + for (RenderObject* curr = renderer()->firstChild(); curr; curr = curr->nextSibling()) + curr->moveLayers(m_parent, this); +} + +void RenderLayer::convertToLayerCoords(const RenderLayer* ancestorLayer, int& x, int& y) const +{ + if (ancestorLayer == this) + return; + + if (m_object->style()->position() == FIXED) { + // Add in the offset of the view. We can obtain this by calling + // absolutePosition() on the RenderCanvas. + int xOff, yOff; + m_object->absolutePosition(xOff, yOff, true); + x += xOff; + y += yOff; + return; + } + + RenderLayer* parentLayer; + if (m_object->style()->position() == ABSOLUTE) + parentLayer = enclosingPositionedAncestor(); + else + parentLayer = parent(); + + if (!parentLayer) return; + + parentLayer->convertToLayerCoords(ancestorLayer, x, y); + + x += xPos(); + y += yPos(); +} + +void RenderLayer::scrollOffset(int& x, int& y) +{ + x += scrollXOffset(); + y += scrollYOffset(); +} + +void RenderLayer::subtractScrollOffset(int& x, int& y) +{ + x -= scrollXOffset(); + y -= scrollYOffset(); +} + +void RenderLayer::checkInlineRelOffset(const RenderObject* o, int& x, int& y) +{ + if(o->style()->position() != ABSOLUTE || !renderer()->isRelPositioned() || !renderer()->isInlineFlow()) + return; + + // Our renderer is an enclosing relpositioned inline, we need to add in the offset of the first line + // box from the rest of the content, but only in the cases where we know our descendant is positioned + // relative to the inline itself. + assert( o->container() == m_object ); + + RenderFlow* flow = static_cast<RenderFlow*>(m_object); + int sx = 0, sy = 0; + if (flow->firstLineBox()) { + if (flow->style()->direction() == LTR) + sx = flow->firstLineBox()->xPos(); + else + sx = flow->lastLineBox()->xPos(); + sy = flow->firstLineBox()->yPos(); + } else { + sx = flow->staticX(); // ### + sy = flow->staticY(); + } + bool isInlineType = o->style()->isOriginalDisplayInlineType(); + + if (!o->hasStaticX()) + x += sx; + + // Despite the positioned child being a block display type inside an inline, we still keep + // its x locked to our left. Arguably the correct behavior would be to go flush left to + // the block that contains us, but that isn't what other browsers do. + if (o->hasStaticX() && !isInlineType) + // Avoid adding in the left border/padding of the containing block twice. Subtract it out. + x += sx - (o->containingBlock()->borderLeft() + o->containingBlock()->paddingLeft()); + + if (!o->hasStaticY()) + y += sy; +} + +void RenderLayer::scrollToOffset(int x, int y, bool updateScrollbars, bool repaint) +{ + if (renderer()->style()->overflowX() != OMARQUEE || !renderer()->hasOverflowClip()) { + if (x < 0) x = 0; + if (y < 0) y = 0; + + // Call the scrollWidth/Height functions so that the dimensions will be computed if they need + // to be (for overflow:hidden blocks). + // ### merge the scrollWidth()/scrollHeight() methods + int maxX = m_scrollWidth - m_object->clientWidth(); + int maxY = m_scrollHeight - m_object->clientHeight(); + + if (x > maxX) x = maxX; + if (y > maxY) y = maxY; + } + + // FIXME: Eventually, we will want to perform a blit. For now never + // blit, since the check for blitting is going to be very + // complicated (since it will involve testing whether our layer + // is either occluded by another layer or clipped by an enclosing + // layer or contains fixed backgrounds, etc.). + m_scrollX = x; + m_scrollY = y; + + // Update the positions of our child layers. + RenderLayer* rootLayer = root(); + for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) + child->updateLayerPositions(rootLayer); + + // Fire the scroll DOM event. + m_object->element()->dispatchHTMLEvent(EventImpl::SCROLL_EVENT, true, false); + + // Just schedule a full repaint of our object. + if (repaint) + m_object->repaint(RealtimePriority); + + if (updateScrollbars) { + if (m_hBar) + m_hBar->setValue(m_scrollX); + if (m_vBar) + m_vBar->setValue(m_scrollY); + } +} + +void RenderLayer::updateScrollPositionFromScrollbars() +{ + bool needUpdate = false; + int newX = m_scrollX; + int newY = m_scrollY; + + if (m_hBar) { + newX = m_hBar->value(); + if (newX != m_scrollX) + needUpdate = true; + } + + if (m_vBar) { + newY = m_vBar->value(); + if (newY != m_scrollY) + needUpdate = true; + } + + if (needUpdate) + scrollToOffset(newX, newY, false); +} + +void +RenderLayer::showScrollbar(Qt::Orientation o, bool show) +{ + QScrollBar *sb = (o == Qt::Horizontal) ? m_hBar : m_vBar; + + if (show && !sb) { + QScrollView* scrollView = m_object->document()->view(); + sb = new QScrollBar(o, scrollView, "__khtml"); + scrollView->addChild(sb, 0, -50000); + sb->setBackgroundMode(QWidget::NoBackground); + sb->show(); + if (!m_scrollMediator) + m_scrollMediator = new RenderScrollMediator(this); + m_scrollMediator->connect(sb, SIGNAL(valueChanged(int)), SLOT(slotValueChanged())); + } + else if (!show && sb) { + delete sb; + sb = 0; + } + + if (o == Qt::Horizontal) + m_hBar = sb; + else + m_vBar = sb; +} + +int RenderLayer::verticalScrollbarWidth() +{ + if (!m_vBar) + return 0; + +#ifdef APPLE_CHANGES + return m_vBar->width(); +#else + return m_vBar->style().pixelMetric(QStyle::PM_ScrollBarExtent); +#endif + +} + +int RenderLayer::horizontalScrollbarHeight() +{ + if (!m_hBar) + return 0; + +#ifdef APPLE_CHANGES + return m_hBar->height(); +#else + return m_hBar->style().pixelMetric(QStyle::PM_ScrollBarExtent); +#endif + +} + +void RenderLayer::positionScrollbars(const QRect& absBounds) +{ +#ifdef APPLE_CHANGES + if (m_vBar) { + scrollView->addChild(m_vBar, absBounds.x()+absBounds.width()-m_object->borderRight()-m_vBar->width(), + absBounds.y()+m_object->borderTop()); + m_vBar->resize(m_vBar->width(), absBounds.height() - + (m_object->borderTop()+m_object->borderBottom()) - + (m_hBar ? m_hBar->height()-1 : 0)); + } + + if (m_hBar) { + scrollView->addChild(m_hBar, absBounds.x()+m_object->borderLeft(), + absBounds.y()+absBounds.height()-m_object->borderBottom()-m_hBar->height()); + m_hBar->resize(absBounds.width() - (m_object->borderLeft()+m_object->borderRight()) - + (m_vBar ? m_vBar->width()-1 : 0), m_hBar->height()); + } +#else + int tx = absBounds.x(); + int ty = absBounds.y(); + int bl = m_object->borderLeft(); + int bt = m_object->borderTop(); + int w = width() - bl - m_object->borderRight(); + int h = height() - bt - m_object->borderBottom(); + + if (w <= 0 || h <= 0 || (!m_vBar && !m_hBar)) + return; + + QScrollView* scrollView = m_object->document()->view(); + + tx += bl; + ty += bt; + + QScrollBar *b = m_hBar; + if (!m_hBar) + b = m_vBar; + int sw = b->style().pixelMetric(QStyle::PM_ScrollBarExtent); + + if (m_vBar) { + QRect vBarRect = QRect(tx + w - sw + 1, ty, sw, h - (m_hBar ? sw : 0) + 1); + m_vBar->resize(vBarRect.width(), vBarRect.height()); + scrollView->addChild(m_vBar, vBarRect.x(), vBarRect.y()); + } + + if (m_hBar) { + QRect hBarRect = QRect(tx, ty + h - sw + 1, w - (m_vBar ? sw : 0) + 1, sw); + m_hBar->resize(hBarRect.width(), hBarRect.height()); + scrollView->addChild(m_hBar, hBarRect.x(), hBarRect.y()); + } +#endif +} + +#define LINE_STEP 10 +#define PAGE_KEEP 40 + +void RenderLayer::checkScrollbarsAfterLayout() +{ + int rightPos = m_object->rightmostPosition(true); + int bottomPos = m_object->lowestPosition(true); + +/* TODO + m_scrollLeft = m_object->leftmostPosition(true); + m_scrollTop = m_object->highestPosition(true); +*/ + + int clientWidth = m_object->clientWidth(); + int clientHeight = m_object->clientHeight(); + m_scrollWidth = clientWidth; + m_scrollHeight = clientHeight; + + if (rightPos - m_object->borderLeft() > m_scrollWidth) + m_scrollWidth = rightPos - m_object->borderLeft(); + if (bottomPos - m_object->borderTop() > m_scrollHeight) + m_scrollHeight = bottomPos - m_object->borderTop(); + + bool needHorizontalBar = rightPos > width(); + bool needVerticalBar = bottomPos > height(); + + bool haveHorizontalBar = m_hBar && m_hBar->isEnabled(); + bool haveVerticalBar = m_vBar && m_vBar->isEnabled(); + + bool hasOvf = m_object->hasOverflowClip(); + + // overflow:scroll should just enable/disable. + if (hasOvf && m_object->style()->overflowX() == OSCROLL) + m_hBar->setEnabled(needHorizontalBar); + if (hasOvf && m_object->style()->overflowY() == OSCROLL) + m_vBar->setEnabled(needVerticalBar); + + // overflow:auto may need to lay out again if scrollbars got added/removed. + bool scrollbarsChanged = (hasOvf && m_object->style()->overflowX() == OAUTO && haveHorizontalBar != needHorizontalBar) + || (hasOvf && m_object->style()->overflowY() == OAUTO && haveVerticalBar != needVerticalBar); + if (scrollbarsChanged) { + if (m_object->style()->overflowX() == OAUTO) { + showScrollbar(Qt::Horizontal, needHorizontalBar); + if (m_hBar) + m_hBar->setEnabled(true); + } + if (m_object->style()->overflowY() == OAUTO) { + showScrollbar(Qt::Vertical, needVerticalBar); + if (m_vBar) + m_vBar->setEnabled(true); + } + + m_object->setNeedsLayout(true); + if (m_object->isRenderBlock()) + static_cast<RenderBlock*>(m_object)->layoutBlock(true); + else + m_object->layout(); + return; + } + + // Set up the range (and page step/line step). + if (m_hBar) { + int pageStep = (clientWidth-PAGE_KEEP); + if (pageStep < 0) pageStep = clientWidth; + m_hBar->setSteps(LINE_STEP, pageStep); +#ifdef APPLE_CHANGES + m_hBar->setKnobProportion(clientWidth, m_scrollWidth); +#else + m_hBar->setRange(0, needHorizontalBar ? m_scrollWidth-clientWidth : 0); +#endif + } + if (m_vBar) { + int pageStep = (clientHeight-PAGE_KEEP); + if (pageStep < 0) pageStep = clientHeight; + m_vBar->setSteps(LINE_STEP, pageStep); +#ifdef APPLE_CHANGES + m_vBar->setKnobProportion(clientHeight, m_scrollHeight); +#else + m_vBar->setRange(0, needVerticalBar ? m_scrollHeight-clientHeight : 0); +#endif + } +} + +void RenderLayer::paintScrollbars(RenderObject::PaintInfo& pI) +{ +#ifdef APPLE_CHANGES + if (m_hBar) + m_hBar->paint(p, damageRect); + if (m_vBar) + m_vBar->paint(p, damageRect); +#else + if (!m_object->element()) + return; + + QScrollView* scrollView = m_object->document()->view(); + if (m_hBar) { + int x = m_hBar->x(); + int y = m_hBar->y(); + scrollView->viewportToContents(x, y, x, y); + RenderWidget::paintWidget(pI, m_hBar, x, y); + } + if (m_vBar) { + int x = m_vBar->x(); + int y = m_vBar->y(); + scrollView->viewportToContents(x, y, x, y); + RenderWidget::paintWidget(pI, m_vBar, x, y); + } +#endif +} + +void RenderLayer::paint(QPainter *p, const QRect& damageRect, bool selectionOnly) +{ + paintLayer(this, p, damageRect, selectionOnly); +} + +static void setClip(QPainter* p, const QRect& paintDirtyRect, const QRect& clipRect) +{ + if (paintDirtyRect == clipRect) + return; + p->save(); + +#ifdef APPLE_CHANGES + p->addClip(clipRect); +#else + + QRect clippedRect = p->xForm(clipRect); + QRegion creg(clippedRect); + QRegion old = p->clipRegion(); + if (!old.isNull()) + creg = old.intersect(creg); + p->setClipRegion(creg); +#endif + +} + +static void restoreClip(QPainter* p, const QRect& paintDirtyRect, const QRect& clipRect) +{ + if (paintDirtyRect == clipRect) + return; + p->restore(); +} + +void RenderLayer::paintLayer(RenderLayer* rootLayer, QPainter *p, + const QRect& paintDirtyRect, bool selectionOnly) +{ + // Calculate the clip rects we should use. + QRect layerBounds, damageRect, clipRectToApply; + calculateRects(rootLayer, paintDirtyRect, layerBounds, damageRect, clipRectToApply); + int x = layerBounds.x(); + int y = layerBounds.y(); + + // Ensure our lists are up-to-date. + updateZOrderLists(); + updateOverflowList(); + +#ifdef APPLE_CHANGES + // Set our transparency if we need to. + if (isTransparent()) + p->beginTransparencyLayer(renderer()->style()->opacity()); +#endif + + // We want to paint our layer, but only if we intersect the damage rect. + bool shouldPaint = intersectsDamageRect(layerBounds, damageRect); + if (shouldPaint && !selectionOnly) { + // Paint our background first, before painting any child layers. + if (!damageRect.isEmpty()) { + // Establish the clip used to paint our background. + setClip(p, paintDirtyRect, damageRect); + + // Paint the background. + RenderObject::PaintInfo paintInfo(p, damageRect, PaintActionElementBackground); + renderer()->paint(paintInfo, + x - renderer()->xPos(), y - renderer()->yPos() + renderer()->borderTopExtra()); + + // Position our scrollbars. + positionScrollbars(layerBounds); + + // Our scrollbar widgets paint exactly when we tell them to, so that they work properly with + // z-index. We paint after we painted the background/border, so that the scrollbars will + // sit above the background/border. + paintScrollbars(paintInfo); + + // Restore the clip. + restoreClip(p, paintDirtyRect, damageRect); + } + } + + // Now walk the sorted list of children with negative z-indices. + if (m_negZOrderList) { + uint count = m_negZOrderList->count(); + for (uint i = 0; i < count; i++) { + RenderLayer* child = m_negZOrderList->at(i); + child->paintLayer(rootLayer, p, paintDirtyRect, selectionOnly); + } + } + + // Now establish the appropriate clip and paint our child RenderObjects. + if (shouldPaint && !clipRectToApply.isEmpty()) { + // Set up the clip used when painting our children. + setClip(p, paintDirtyRect, clipRectToApply); + + RenderObject::PaintInfo paintInfo(p, clipRectToApply, PaintActionSelection); + + int tx = x - renderer()->xPos(); + int ty = y - renderer()->yPos() + renderer()->borderTopExtra(); + + if (selectionOnly) + renderer()->paint(paintInfo, tx, ty); + else { + paintInfo.phase = PaintActionChildBackgrounds; + renderer()->paint(paintInfo, tx, ty); + paintInfo.phase = PaintActionFloat; + renderer()->paint(paintInfo, tx, ty); + paintInfo.phase = PaintActionForeground; + renderer()->paint(paintInfo, tx, ty); + RenderCanvas *rc = static_cast<RenderCanvas*>(renderer()->document()->renderer()); + if (rc->maximalOutlineSize()) { + paintInfo.phase = PaintActionOutline; + renderer()->paint(paintInfo, tx, ty); + } + if (rc->selectionStart() && rc->selectionEnd()) { + paintInfo.phase = PaintActionSelection; + renderer()->paint(paintInfo, tx, ty); + } + } + + // Now restore our clip. + restoreClip(p, paintDirtyRect, clipRectToApply); + } + + // Paint any child layers that have overflow. + if (m_overflowList) + for (QValueList<RenderLayer*>::iterator it = m_overflowList->begin(); it != m_overflowList->end(); ++it) + (*it)->paintLayer(rootLayer, p, paintDirtyRect, selectionOnly); + + // Now walk the sorted list of children with positive z-indices. + if (m_posZOrderList) { + uint count = m_posZOrderList->count(); + for (uint i = 0; i < count; i++) { + RenderLayer* child = m_posZOrderList->at(i); + child->paintLayer(rootLayer, p, paintDirtyRect, selectionOnly); + } + } + +#ifdef BOX_DEBUG + { + int ax=0; + int ay=0; + renderer()->absolutePosition( ax, ay ); + p->setPen(QPen(QColor("yellow"), 1, Qt::DotLine)); + p->setBrush( Qt::NoBrush ); + p->drawRect(ax, ay, width(), height()); + } +#endif + +#ifdef APPLE_CHANGES + // End our transparency layer + if (isTransparent()) + p->endTransparencyLayer(); +#endif +} + +bool RenderLayer::nodeAtPoint(RenderObject::NodeInfo& info, int x, int y) +{ +#ifdef APPLE_CHANGES + // Clear our our scrollbar variable + RenderLayer::gScrollBar = 0; +#endif + + int stx = m_x; + int sty = m_y; + +#ifdef __GNUC__ +#warning HACK +#endif + if (renderer()->isCanvas()) { + stx += static_cast<RenderCanvas*>(renderer())->view()->contentsX(); + sty += static_cast<RenderCanvas*>(renderer())->view()->contentsY(); + } + + QRect damageRect(stx,sty, width(), height()); + RenderLayer* insideLayer = nodeAtPointForLayer(this, info, x, y, damageRect); + + // Now determine if the result is inside an anchor. + DOM::NodeImpl* node = info.innerNode(); + while (node) { + if (node->hasAnchor() && !info.URLElement()) + info.setURLElement(node); + node = node->parentNode(); + } + + // Next set up the correct :hover/:active state along the new chain. + updateHoverActiveState(info); + + // Now return whether we were inside this layer (this will always be true for the root + // layer). + return insideLayer; +} + +RenderLayer* RenderLayer::nodeAtPointForLayer(RenderLayer* rootLayer, RenderObject::NodeInfo& info, + int xMousePos, int yMousePos, const QRect& hitTestRect) +{ + // Calculate the clip rects we should use. + QRect layerBounds, bgRect, fgRect; + calculateRects(rootLayer, hitTestRect, layerBounds, bgRect, fgRect); + + // Ensure our lists are up-to-date. + updateZOrderLists(); + updateOverflowList(); + + // This variable tracks which layer the mouse ends up being inside. The minute we find an insideLayer, + // we are done and can return it. + RenderLayer* insideLayer = 0; + + // Begin by walking our list of positive layers from highest z-index down to the lowest + // z-index. + if (m_posZOrderList) { + uint count = m_posZOrderList->count(); + for (int i = count-1; i >= 0; i--) { + RenderLayer* child = m_posZOrderList->at(i); + insideLayer = child->nodeAtPointForLayer(rootLayer, info, xMousePos, yMousePos, hitTestRect); + if (insideLayer) + return insideLayer; + } + } + + // Now check our overflow objects. + if (m_overflowList) { + QValueList<RenderLayer*>::iterator it = m_overflowList->end(); + for (--it; it != m_overflowList->end(); --it) { + insideLayer = (*it)->nodeAtPointForLayer(rootLayer, info, xMousePos, yMousePos, hitTestRect); + if (insideLayer) + return insideLayer; + } + } + + // Next we want to see if the mouse pos is inside the child RenderObjects of the layer. + if (containsPoint(xMousePos, yMousePos, fgRect) && + renderer()->nodeAtPoint(info, xMousePos, yMousePos, + layerBounds.x() - renderer()->xPos(), + layerBounds.y() - renderer()->yPos() + m_object->borderTopExtra(), + HitTestChildrenOnly)) { + if (info.innerNode() != m_object->element()) + return this; + } + + // Now check our negative z-index children. + if (m_negZOrderList) { + uint count = m_negZOrderList->count(); + for (int i = count-1; i >= 0; i--) { + RenderLayer* child = m_negZOrderList->at(i); + insideLayer = child->nodeAtPointForLayer(rootLayer, info, xMousePos, yMousePos, hitTestRect); + if (insideLayer) + return insideLayer; + } + } + + // Next we want to see if the mouse pos is inside this layer but not any of its children. + if (containsPoint(xMousePos, yMousePos, bgRect) && + renderer()->nodeAtPoint(info, xMousePos, yMousePos, + layerBounds.x() - renderer()->xPos(), + layerBounds.y() - renderer()->yPos() + m_object->borderTopExtra(), + HitTestSelfOnly)) + return this; + + // No luck. + return 0; +} + +void RenderLayer::calculateClipRects(const RenderLayer* rootLayer, QRect& overflowClipRect, + QRect& posClipRect, QRect& fixedClipRect) +{ + if (parent()) + parent()->calculateClipRects(rootLayer, overflowClipRect, posClipRect, fixedClipRect); + + switch (m_object->style()->position()) { + // A fixed object is essentially the root of its containing block hierarchy, so when + // we encounter such an object, we reset our clip rects to the fixedClipRect. + case FIXED: + posClipRect = fixedClipRect; + overflowClipRect = fixedClipRect; + break; + case ABSOLUTE: + overflowClipRect = posClipRect; + break; + case RELATIVE: + posClipRect = overflowClipRect; + break; + default: + break; + } + + // Update the clip rects that will be passed to child layers. + if (m_object->hasOverflowClip() || m_object->hasClip()) { + // This layer establishes a clip of some kind. + int x = 0; + int y = 0; + convertToLayerCoords(rootLayer, x, y); + + if (m_object->hasOverflowClip()) { + QRect newOverflowClip = m_object->getOverflowClipRect(x,y); + overflowClipRect = newOverflowClip.intersect(overflowClipRect); + if (m_object->isPositioned() || m_object->isRelPositioned()) + posClipRect = newOverflowClip.intersect(posClipRect); + } + if (m_object->hasClip()) { + QRect newPosClip = m_object->getClipRect(x,y); + posClipRect = posClipRect.intersect(newPosClip); + overflowClipRect = overflowClipRect.intersect(newPosClip); + fixedClipRect = fixedClipRect.intersect(newPosClip); + } + } +} + +void RenderLayer::calculateRects(const RenderLayer* rootLayer, const QRect& paintDirtyRect, QRect& layerBounds, + QRect& backgroundRect, QRect& foregroundRect) +{ + QRect overflowClipRect = paintDirtyRect; + QRect posClipRect = paintDirtyRect; + QRect fixedClipRect = paintDirtyRect; + if (parent()) + parent()->calculateClipRects(rootLayer, overflowClipRect, posClipRect, fixedClipRect); + + int x = 0; + int y = 0; + convertToLayerCoords(rootLayer, x, y); + layerBounds = QRect(x,y,width(),height()); + + backgroundRect = m_object->style()->position() == FIXED ? fixedClipRect : + (m_object->isPositioned() ? posClipRect : overflowClipRect); + foregroundRect = backgroundRect; + + // Update the clip rects that will be passed to child layers. + if (m_object->hasOverflowClip() || m_object->hasClip()) { + // This layer establishes a clip of some kind. + if (m_object->hasOverflowClip()) + foregroundRect = foregroundRect.intersect(m_object->getOverflowClipRect(x,y)); + + if (m_object->hasClip()) { + // Clip applies to *us* as well, so go ahead and update the damageRect. + QRect newPosClip = m_object->getClipRect(x,y); + backgroundRect = backgroundRect.intersect(newPosClip); + foregroundRect = foregroundRect.intersect(newPosClip); + } + + // If we establish a clip at all, then go ahead and make sure our background + // rect is intersected with our layer's bounds. + backgroundRect = backgroundRect.intersect(layerBounds); + } +} + +bool RenderLayer::intersectsDamageRect(const QRect& layerBounds, const QRect& damageRect) const +{ + return (renderer()->isCanvas() || renderer()->isRoot() || renderer()->isBody() || + (renderer()->hasOverhangingFloats() && !renderer()->hasOverflowClip()) || + (renderer()->isInline() && !renderer()->isReplaced()) || + layerBounds.intersects(damageRect)); +} + +bool RenderLayer::containsPoint(int x, int y, const QRect& damageRect) const +{ + return (renderer()->isCanvas() || renderer()->isRoot() || renderer()->isBody() || + renderer()->hasOverhangingFloats() || + (renderer()->isInline() && !renderer()->isReplaced()) || + damageRect.contains(x, y)); +} + +// This code has been written to anticipate the addition of CSS3-::outside and ::inside generated +// content (and perhaps XBL). That's why it uses the render tree and not the DOM tree. +static RenderObject* hoverAncestor(RenderObject* obj) +{ + return (!obj->isInline() && obj->continuation()) ? obj->continuation() : obj->parent(); +} + +static RenderObject* commonAncestor(RenderObject* obj1, RenderObject* obj2) +{ + if (!obj1 || !obj2) + return 0; + + for (RenderObject* currObj1 = obj1; currObj1; currObj1 = hoverAncestor(currObj1)) + for (RenderObject* currObj2 = obj2; currObj2; currObj2 = hoverAncestor(currObj2)) + if (currObj1 == currObj2) + return currObj1; + + return 0; +} + + +void RenderLayer::updateHoverActiveState(RenderObject::NodeInfo& info) +{ + // We don't update :hover/:active state when the info is marked as readonly. + if (info.readonly()) + return; + + DOM::NodeImpl *e = m_object->element(); + DOM::DocumentImpl *doc = e ? e->getDocument() : 0; + if (!doc) return; + + // Check to see if the hovered node has changed. If not, then we don't need to + // do anything. + DOM::NodeImpl* oldHoverNode = doc->hoverNode(); + DOM::NodeImpl* newHoverNode = info.innerNode(); + + if (oldHoverNode == newHoverNode && (!oldHoverNode || oldHoverNode->active() == info.active())) + return; + + // Update our current hover node. + doc->setHoverNode(newHoverNode); + if (info.active()) + doc->setActiveNode(newHoverNode); + else + doc->setActiveNode(0); + + // We have two different objects. Fetch their renderers. + RenderObject* oldHoverObj = oldHoverNode ? oldHoverNode->renderer() : 0; + RenderObject* newHoverObj = newHoverNode ? newHoverNode->renderer() : 0; + + // Locate the common ancestor render object for the two renderers. + RenderObject* ancestor = commonAncestor(oldHoverObj, newHoverObj); + + // The old hover path only needs to be cleared up to (and not including) the common ancestor; + for (RenderObject* curr = oldHoverObj; curr && curr != ancestor; curr = hoverAncestor(curr)) { + curr->setMouseInside(false); + if (curr->element()) { + curr->element()->setActive(false); + curr->element()->setHovered(false); + } + } + + // Now set the hover state for our new object up to the root. + for (RenderObject* curr = newHoverObj; curr; curr = hoverAncestor(curr)) { + curr->setMouseInside(true); + if (curr->element()) { + curr->element()->setActive(info.active()); + curr->element()->setHovered(true); + } + } +} + +// Sort the buffer from lowest z-index to highest. The common scenario will have +// most z-indices equal, so we optimize for that case (i.e., the list will be mostly +// sorted already). +static void sortByZOrder(QPtrVector<RenderLayer>* buffer, + QPtrVector<RenderLayer>* mergeBuffer, + uint start, uint end) +{ + if (start >= end) + return; // Sanity check. + + if (end - start <= 6) { + // Apply a bubble sort for smaller lists. + for (uint i = end-1; i > start; i--) { + bool sorted = true; + for (uint j = start; j < i; j++) { + RenderLayer* elt = buffer->at(j); + RenderLayer* elt2 = buffer->at(j+1); + if (elt->zIndex() > elt2->zIndex()) { + sorted = false; + buffer->insert(j, elt2); + buffer->insert(j+1, elt); + } + } + if (sorted) + return; + } + } + else { + // Peform a merge sort for larger lists. + uint mid = (start+end)/2; + sortByZOrder(buffer, mergeBuffer, start, mid); + sortByZOrder(buffer, mergeBuffer, mid, end); + + RenderLayer* elt = buffer->at(mid-1); + RenderLayer* elt2 = buffer->at(mid); + + // Handle the fast common case (of equal z-indices). The list may already + // be completely sorted. + if (elt->zIndex() <= elt2->zIndex()) + return; + + // We have to merge sort. Ensure our merge buffer is big enough to hold + // all the items. + mergeBuffer->resize(end - start); + uint i1 = start; + uint i2 = mid; + + elt = buffer->at(i1); + elt2 = buffer->at(i2); + + while (i1 < mid || i2 < end) { + if (i1 < mid && (i2 == end || elt->zIndex() <= elt2->zIndex())) { + mergeBuffer->insert(mergeBuffer->count(), elt); + i1++; + if (i1 < mid) + elt = buffer->at(i1); + } + else { + mergeBuffer->insert(mergeBuffer->count(), elt2); + i2++; + if (i2 < end) + elt2 = buffer->at(i2); + } + } + + for (uint i = start; i < end; i++) + buffer->insert(i, mergeBuffer->at(i-start)); + + mergeBuffer->clear(); + } +} + +void RenderLayer::dirtyZOrderLists() +{ + if (m_posZOrderList) + m_posZOrderList->clear(); + if (m_negZOrderList) + m_negZOrderList->clear(); + m_zOrderListsDirty = true; +} + +void RenderLayer::dirtyOverflowList() +{ + if (m_overflowList) + m_overflowList->clear(); + m_overflowListDirty = true; +} + +void RenderLayer::updateZOrderLists() +{ + if (!isStackingContext() || !m_zOrderListsDirty) + return; + + for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) + child->collectLayers(m_posZOrderList, m_negZOrderList); + + // Sort the two lists. + if (m_posZOrderList) { + QPtrVector<RenderLayer> mergeBuffer; + sortByZOrder(m_posZOrderList, &mergeBuffer, 0, m_posZOrderList->count()); + } + if (m_negZOrderList) { + QPtrVector<RenderLayer> mergeBuffer; + sortByZOrder(m_negZOrderList, &mergeBuffer, 0, m_negZOrderList->count()); + } + + m_zOrderListsDirty = false; +} + +void RenderLayer::updateOverflowList() +{ + if (!m_overflowListDirty) + return; + + for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) { + if (child->isOverflowOnly()) { + if (!m_overflowList) + m_overflowList = new QValueList<RenderLayer*>; + m_overflowList->append(child); + } + } + + m_overflowListDirty = false; +} + +void RenderLayer::collectLayers(QPtrVector<RenderLayer>*& posBuffer, QPtrVector<RenderLayer>*& negBuffer) +{ + // FIXME: A child render object or layer could override visibility. Don't remove this + // optimization though until RenderObject's nodeAtPoint is patched to understand what to do + // when visibility is overridden by a child. + if (renderer()->style()->visibility() != VISIBLE) + return; + + // Overflow layers are just painted by their enclosing layers, so they don't get put in zorder lists. + if (!isOverflowOnly()) { + + // Determine which buffer the child should be in. + QPtrVector<RenderLayer>*& buffer = (zIndex() >= 0) ? posBuffer : negBuffer; + + // Create the buffer if it doesn't exist yet. + if (!buffer) + buffer = new QPtrVector<RenderLayer>(); + + // Resize by a power of 2 when our buffer fills up. + if (buffer->count() == buffer->size()) + buffer->resize(2*(buffer->size()+1)); + + // Append ourselves at the end of the appropriate buffer. + buffer->insert(buffer->count(), this); + } + + // Recur into our children to collect more layers, but only if we don't establish + // a stacking context. + if (!isStackingContext()) { + for (RenderLayer* child = firstChild(); child; child = child->nextSibling()) + child->collectLayers(posBuffer, negBuffer); + } +} + +#ifdef ENABLE_DUMP +#ifndef KDE_USE_FINAL +static QTextStream &operator<<(QTextStream &ts, const QRect &r) +{ + return ts << "at (" << r.x() << "," << r.y() << ") size " << r.width() << "x" << r.height(); +} +#endif + +static void write(QTextStream &ts, RenderObject& o, const QString& indent ) +{ + o.dump(ts, indent); + + for (RenderObject *child = o.firstChild(); child; child = child->nextSibling()) { + if (child->layer()) continue; + write( ts, *child, indent + " " ); + } +} + +static void write(QTextStream &ts, const RenderLayer &l, + const QRect& layerBounds, const QRect& backgroundClipRect, const QRect& clipRect, + int layerType = 0, const QString& indent = QString::null) + +{ + ts << indent << "layer"; + + ts << " at (" << l.xPos() << "," << l.yPos() << ") size " << l.width() << "x" << l.height(); + + if (layerBounds != layerBounds.intersect(backgroundClipRect)) { + ts << " backgroundClip " << backgroundClipRect; + } + if (layerBounds != layerBounds.intersect(clipRect)) { + ts << " clip " << clipRect; + } + + if (layerType == -1) + ts << " layerType: background only"; + else if (layerType == 1) + ts << " layerType: foreground only"; + + ts << "\n"; + + if (layerType != -1) + write( ts, *l.renderer(), indent + " " ); + + ts << "\n"; +} + +static void writeLayers(QTextStream &ts, const RenderLayer* rootLayer, RenderLayer* l, + const QRect& paintDirtyRect, const QString& indent) +{ + // Calculate the clip rects we should use. + QRect layerBounds, damageRect, clipRectToApply; + l->calculateRects(rootLayer, paintDirtyRect, layerBounds, damageRect, clipRectToApply); + + // Ensure our lists are up-to-date. + l->updateZOrderLists(); + l->updateOverflowList(); + + bool shouldPaint = l->intersectsDamageRect(layerBounds, damageRect); + QPtrVector<RenderLayer>* negList = l->negZOrderList(); + QValueList<RenderLayer*>* ovfList = l->overflowList(); + if (shouldPaint && negList && negList->count() > 0) + write(ts, *l, layerBounds, damageRect, clipRectToApply, -1, indent); + + if (negList) { + for (unsigned i = 0; i != negList->count(); ++i) + writeLayers(ts, rootLayer, negList->at(i), paintDirtyRect, indent ); + } + + if (shouldPaint) + write(ts, *l, layerBounds, damageRect, clipRectToApply, negList && negList->count() > 0, indent); + + if (ovfList) { + for (QValueList<RenderLayer*>::iterator it = ovfList->begin(); it != ovfList->end(); ++it) + writeLayers(ts, rootLayer, *it, paintDirtyRect, indent); + } + + QPtrVector<RenderLayer>* posList = l->posZOrderList(); + if (posList) { + for (unsigned i = 0; i != posList->count(); ++i) + writeLayers(ts, rootLayer, posList->at(i), paintDirtyRect, indent); + } +} + + +void RenderLayer::dump(QTextStream &ts, const QString &ind) +{ + assert( renderer()->isCanvas() ); + + writeLayers(ts, this, this, QRect(xPos(), yPos(), width(), height()), ind); +} + + +#endif + +bool RenderLayer::shouldBeOverflowOnly() const +{ + return renderer()->style() && renderer()->hasOverflowClip() && + !renderer()->isPositioned() && !renderer()->isRelPositioned(); + /* && !isTransparent(); */ +} + +void RenderLayer::styleChanged() +{ + bool isOverflowOnly = shouldBeOverflowOnly(); + if (isOverflowOnly != m_isOverflowOnly) { + m_isOverflowOnly = isOverflowOnly; + RenderLayer* p = parent(); + RenderLayer* sc = stackingContext(); + if (p) + p->dirtyOverflowList(); + if (sc) + sc->dirtyZOrderLists(); + } + + if (m_object->hasOverflowClip() && + m_object->style()->overflowX() == OMARQUEE && m_object->style()->marqueeBehavior() != MNONE) { + if (!m_marquee) + m_marquee = new Marquee(this); + m_marquee->updateMarqueeStyle(); + } + else if (m_marquee) { + delete m_marquee; + m_marquee = 0; + } +} + +void RenderLayer::suspendMarquees() +{ + if (m_marquee) + m_marquee->suspend(); + + for (RenderLayer* curr = firstChild(); curr; curr = curr->nextSibling()) + curr->suspendMarquees(); +} + +// -------------------------------------------------------------------------- +// Marquee implementation + +Marquee::Marquee(RenderLayer* l) +:m_layer(l), m_currentLoop(0), m_totalLoops(0), m_timerId(0), m_start(0), m_end(0), m_speed(0), m_unfurlPos(0), m_reset(false), + m_suspended(false), m_stopped(false), m_whiteSpace(NORMAL), m_direction(MAUTO) +{ +} + +int Marquee::marqueeSpeed() const +{ + int result = m_layer->renderer()->style()->marqueeSpeed(); + DOM::NodeImpl* elt = m_layer->renderer()->element(); + if (elt && elt->id() == ID_MARQUEE) { + HTMLMarqueeElementImpl* marqueeElt = static_cast<HTMLMarqueeElementImpl*>(elt); + result = kMax(result, marqueeElt->minimumDelay()); + } + return result; +} + +EMarqueeDirection Marquee::direction() const +{ + // FIXME: Support the CSS3 "auto" value for determining the direction of the marquee. + // For now just map MAUTO to MBACKWARD + EMarqueeDirection result = m_layer->renderer()->style()->marqueeDirection(); + EDirection dir = m_layer->renderer()->style()->direction(); + if (result == MAUTO) + result = MBACKWARD; + if (result == MFORWARD) + result = (dir == LTR) ? MRIGHT : MLEFT; + if (result == MBACKWARD) + result = (dir == LTR) ? MLEFT : MRIGHT; + + // Now we have the real direction. Next we check to see if the increment is negative. + // If so, then we reverse the direction. + Length increment = m_layer->renderer()->style()->marqueeIncrement(); + if (increment.value() < 0) + result = static_cast<EMarqueeDirection>(-result); + + return result; +} + +bool Marquee::isHorizontal() const +{ + return direction() == MLEFT || direction() == MRIGHT; +} + +bool Marquee::isUnfurlMarquee() const +{ + EMarqueeBehavior behavior = m_layer->renderer()->style()->marqueeBehavior(); + return (behavior == MUNFURL); +} + +int Marquee::computePosition(EMarqueeDirection dir, bool stopAtContentEdge) +{ + RenderObject* o = m_layer->renderer(); + RenderStyle* s = o->style(); + if (isHorizontal()) { + bool ltr = s->direction() == LTR; + int clientWidth = o->clientWidth(); + int contentWidth = ltr ? o->rightmostPosition(true, false) : o->leftmostPosition(true, false); + if (ltr) + contentWidth += (o->paddingRight() - o->borderLeft()); + else { + contentWidth = o->width() - contentWidth; + contentWidth += (o->paddingLeft() - o->borderRight()); + } + if (dir == MRIGHT) { + if (stopAtContentEdge) + return kMax(0, ltr ? (contentWidth - clientWidth) : (clientWidth - contentWidth)); + else + return ltr ? contentWidth : clientWidth; + } + else { + if (stopAtContentEdge) + return kMin(0, ltr ? (contentWidth - clientWidth) : (clientWidth - contentWidth)); + else + return ltr ? -clientWidth : -contentWidth; + } + } + else { + int contentHeight = m_layer->renderer()->lowestPosition(true, false) - + m_layer->renderer()->borderTop() + m_layer->renderer()->paddingBottom(); + int clientHeight = m_layer->renderer()->clientHeight(); + if (dir == MUP) { + if (stopAtContentEdge) + return kMin(contentHeight - clientHeight, 0); + else + return -clientHeight; + } + else { + if (stopAtContentEdge) + return kMax(contentHeight - clientHeight, 0); + else + return contentHeight; + } + } +} + +void Marquee::start() +{ + if (m_timerId || m_layer->renderer()->style()->marqueeIncrement().value() == 0) + return; + + if (!m_suspended && !m_stopped) { + if (isUnfurlMarquee()) { + bool forward = direction() == MDOWN || direction() == MRIGHT; + bool isReversed = (forward && m_currentLoop % 2) || (!forward && !(m_currentLoop % 2)); + m_unfurlPos = isReversed ? m_end : m_start; + m_layer->renderer()->setChildNeedsLayout(true); + } + else { + if (isHorizontal()) + m_layer->scrollToOffset(m_start, 0, false, false); + else + m_layer->scrollToOffset(0, m_start, false, false); + } + } + else + m_suspended = false; + + m_stopped = false; + m_timerId = startTimer(speed()); +} + +void Marquee::suspend() +{ + if (m_timerId) { + killTimer(m_timerId); + m_timerId = 0; + } + + m_suspended = true; +} + +void Marquee::stop() +{ + if (m_timerId) { + killTimer(m_timerId); + m_timerId = 0; + } + + m_stopped = true; +} + +void Marquee::updateMarqueePosition() +{ + bool activate = (m_totalLoops <= 0 || m_currentLoop < m_totalLoops); + if (activate) { + if (isUnfurlMarquee()) { + if (m_unfurlPos < m_start) { + m_unfurlPos = m_start; + m_layer->renderer()->setChildNeedsLayout(true); + } + else if (m_unfurlPos > m_end) { + m_unfurlPos = m_end; + m_layer->renderer()->setChildNeedsLayout(true); + } + } + else { + EMarqueeBehavior behavior = m_layer->renderer()->style()->marqueeBehavior(); + m_start = computePosition(direction(), behavior == MALTERNATE); + m_end = computePosition(reverseDirection(), behavior == MALTERNATE || behavior == MSLIDE); + } + if (!m_stopped) start(); + } +} + +void Marquee::updateMarqueeStyle() +{ + RenderStyle* s = m_layer->renderer()->style(); + + if (m_direction != s->marqueeDirection() || (m_totalLoops != s->marqueeLoopCount() && m_currentLoop >= m_totalLoops)) + m_currentLoop = 0; // When direction changes or our loopCount is a smaller number than our current loop, reset our loop. + + m_totalLoops = s->marqueeLoopCount(); + m_direction = s->marqueeDirection(); + m_whiteSpace = s->whiteSpace(); + + if (m_layer->renderer()->isHTMLMarquee()) { + // Hack for WinIE. In WinIE, a value of 0 or lower for the loop count for SLIDE means to only do + // one loop. + if (m_totalLoops <= 0 && (s->marqueeBehavior() == MSLIDE || s->marqueeBehavior() == MUNFURL)) + m_totalLoops = 1; + + // Hack alert: Set the white-space value to nowrap for horizontal marquees with inline children, thus ensuring + // all the text ends up on one line by default. Limit this hack to the <marquee> element to emulate + // WinIE's behavior. Someone using CSS3 can use white-space: nowrap on their own to get this effect. + // Second hack alert: Set the text-align back to auto. WinIE completely ignores text-align on the + // marquee element. + // FIXME: Bring these up with the CSS WG. + if (isHorizontal() && m_layer->renderer()->childrenInline()) { + s->setWhiteSpace(NOWRAP); + s->setTextAlign(TAAUTO); + } + } + + if (speed() != marqueeSpeed()) { + m_speed = marqueeSpeed(); + if (m_timerId) { + killTimer(m_timerId); + m_timerId = startTimer(speed()); + } + } + + // Check the loop count to see if we should now stop. + bool activate = (m_totalLoops <= 0 || m_currentLoop < m_totalLoops); + if (activate && !m_timerId) + m_layer->renderer()->setNeedsLayout(true); + else if (!activate && m_timerId) { + // Destroy the timer. + killTimer(m_timerId); + m_timerId = 0; + } +} + +void Marquee::timerEvent(QTimerEvent* /*evt*/) +{ + if (m_layer->renderer()->needsLayout()) + return; + + if (m_reset) { + m_reset = false; + if (isHorizontal()) + m_layer->scrollToXOffset(m_start); + else + m_layer->scrollToYOffset(m_start); + return; + } + + RenderStyle* s = m_layer->renderer()->style(); + + int endPoint = m_end; + int range = m_end - m_start; + int newPos; + if (range == 0) + newPos = m_end; + else { + bool addIncrement = direction() == MUP || direction() == MLEFT; + bool isReversed = s->marqueeBehavior() == MALTERNATE && m_currentLoop % 2; + if (isUnfurlMarquee()) { + isReversed = (!addIncrement && m_currentLoop % 2) || (addIncrement && !(m_currentLoop % 2)); + addIncrement = !isReversed; + } + if (isReversed) { + // We're going in the reverse direction. + endPoint = m_start; + range = -range; + if (!isUnfurlMarquee()) + addIncrement = !addIncrement; + } + bool positive = range > 0; + int clientSize = isUnfurlMarquee() ? abs(range) : + (isHorizontal() ? m_layer->renderer()->clientWidth() : m_layer->renderer()->clientHeight()); + int increment = kMax(1, abs(m_layer->renderer()->style()->marqueeIncrement().width(clientSize))); + int currentPos = isUnfurlMarquee() ? m_unfurlPos : + (isHorizontal() ? m_layer->scrollXOffset() : m_layer->scrollYOffset()); + newPos = currentPos + (addIncrement ? increment : -increment); + if (positive) + newPos = kMin(newPos, endPoint); + else + newPos = kMax(newPos, endPoint); + } + + if (newPos == endPoint) { + m_currentLoop++; + if (m_totalLoops > 0 && m_currentLoop >= m_totalLoops) { + killTimer(m_timerId); + m_timerId = 0; + } + else if (s->marqueeBehavior() != MALTERNATE && s->marqueeBehavior() != MUNFURL) + m_reset = true; + } + + if (isUnfurlMarquee()) { + m_unfurlPos = newPos; + m_layer->renderer()->setChildNeedsLayout(true); + } + else { + if (isHorizontal()) + m_layer->scrollToXOffset(newPos); + else + m_layer->scrollToYOffset(newPos); + } +} + +#include "render_layer.moc" |