diff options
Diffstat (limited to 'khtml/rendering/render_container.cpp')
-rw-r--r-- | khtml/rendering/render_container.cpp | 597 |
1 files changed, 597 insertions, 0 deletions
diff --git a/khtml/rendering/render_container.cpp b/khtml/rendering/render_container.cpp new file mode 100644 index 000000000..69f987477 --- /dev/null +++ b/khtml/rendering/render_container.cpp @@ -0,0 +1,597 @@ +/** + * This file is part of the html renderer for KDE. + * + * Copyright (C) 2001-2003 Lars Knoll (knoll@kde.org) + * (C) 2001 Antti Koivisto (koivisto@kde.org) + * (C) 2000-2003 Dirk Mueller (mueller@kde.org) + * (C) 2002-2003 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. + * + */ + +//#define DEBUG_LAYOUT + +#include "rendering/render_container.h" +#include "rendering/render_table.h" +#include "rendering/render_text.h" +#include "rendering/render_image.h" +#include "rendering/render_canvas.h" +#include "rendering/render_generated.h" +#include "rendering/render_inline.h" +#include "xml/dom_docimpl.h" +#include "css/css_valueimpl.h" + +#include <kdebug.h> +#include <assert.h> + +using namespace khtml; + +RenderContainer::RenderContainer(DOM::NodeImpl* node) + : RenderObject(node) +{ + m_first = 0; + m_last = 0; +} + +void RenderContainer::detach() +{ + if (continuation()) + continuation()->detach(); + + // We simulate removeNode calls for all our children + // and set parent to 0 to avoid removeNode from being called. + // First call removeLayers and removeFromObjectLists since they assume + // a valid render-tree + for(RenderObject* n = m_first; n; n = n->nextSibling() ) { + n->removeLayers(enclosingLayer()); + n->removeFromObjectLists(); + } + + RenderObject* next; + for(RenderObject* n = m_first; n; n = next ) { + n->setParent(0); + next = n->nextSibling(); + n->detach(); + } + m_first = 0; + m_last = 0; + + RenderObject::detach(); +} + +void RenderContainer::addChild(RenderObject *newChild, RenderObject *beforeChild) +{ +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << this << ": " << renderName() << "(RenderObject)::addChild( " << newChild << ": " << + newChild->renderName() << ", " << (beforeChild ? beforeChild->renderName() : "0") << " )" << endl; +#endif + // protect ourselves from deletion + setDoNotDelete(true); + + bool needsTable = false; + + if(!newChild->isText() && !newChild->isReplaced()) { + switch(newChild->style()->display()) { + case INLINE: + case BLOCK: + case LIST_ITEM: + case RUN_IN: + case COMPACT: + case INLINE_BLOCK: + case TABLE: + case INLINE_TABLE: + break; + case TABLE_COLUMN: + if ( isTableCol() ) + break; + // nobreak + case TABLE_COLUMN_GROUP: + case TABLE_CAPTION: + case TABLE_ROW_GROUP: + case TABLE_HEADER_GROUP: + case TABLE_FOOTER_GROUP: + + //kdDebug( 6040 ) << "adding section" << endl; + if ( !isTable() ) + needsTable = true; + break; + case TABLE_ROW: + //kdDebug( 6040 ) << "adding row" << endl; + if ( !isTableSection() ) + needsTable = true; + break; + case TABLE_CELL: + //kdDebug( 6040 ) << "adding cell" << endl; + if ( !isTableRow() ) + needsTable = true; + // I'm not 100% sure this is the best way to fix this, but without this + // change we recurse infinitely when trying to render the CSS2 test page: + // http://www.bath.ac.uk/%7Epy8ieh/internet/eviltests/htmlbodyheadrendering2.html. + if ( isTableCell() && !firstChild() && !newChild->isTableCell() ) + needsTable = false; + + break; + case NONE: + // RenderHtml and some others can have display:none + // KHTMLAssert(false); + break; + } + } + + if ( needsTable ) { + RenderTable *table; + RenderObject *last = beforeChild ? beforeChild->previousSibling() : lastChild(); + if ( last && last->isTable() && last->isAnonymous() ) { + table = static_cast<RenderTable *>(last); + } else { + //kdDebug( 6040 ) << "creating anonymous table, before=" << beforeChild << endl; + table = new (renderArena()) RenderTable(document() /* is anonymous */); + RenderStyle *newStyle = new RenderStyle(); + newStyle->inheritFrom(style()); + newStyle->setDisplay( TABLE ); + newStyle->setFlowAroundFloats( true ); + table->setParent( this ); // so it finds the arena + table->setStyle(newStyle); + table->setParent( 0 ); + addChild(table, beforeChild); + } + table->addChild(newChild); + } else { + // just add it... + insertChildNode(newChild, beforeChild); + } + + if (newChild->isText() && newChild->style()->textTransform() == CAPITALIZE) { + DOM::DOMStringImpl* textToTransform = static_cast<RenderText*>(newChild)->originalString(); + if (textToTransform) + static_cast<RenderText*>(newChild)->setText(textToTransform, true); + } + newChild->attach(); + + setDoNotDelete(false); +} + +RenderObject* RenderContainer::removeChildNode(RenderObject* oldChild) +{ + KHTMLAssert(oldChild->parent() == this); + + // So that we'll get the appropriate dirty bit set (either that a normal flow child got yanked or + // that a positioned child got yanked). We also repaint, so that the area exposed when the child + // disappears gets repainted properly. + if ( document()->renderer() ) { + oldChild->setNeedsLayoutAndMinMaxRecalc(); + oldChild->repaint(); + + // Keep our layer hierarchy updated. + oldChild->removeLayers(enclosingLayer()); + // remove the child from any special layout lists + oldChild->removeFromObjectLists(); + + // if oldChild is the start or end of the selection, then clear + // the selection to avoid problems of invalid pointers + + // ### This is not the "proper" solution... ideally the selection + // ### should be maintained based on DOM Nodes and a Range, which + // ### gets adjusted appropriately when nodes are deleted/inserted + // ### near etc. But this at least prevents crashes caused when + // ### the start or end of the selection is deleted and then + // ### accessed when the user next selects something. + + if (oldChild->isSelectionBorder()) { + RenderObject *root = oldChild; + while (root->parent()) + root = root->parent(); + if (root->isCanvas()) { + static_cast<RenderCanvas*>(root)->clearSelection(); + } + } + } + + // remove the child from the render-tree + 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(); + + oldChild->setPreviousSibling(0); + oldChild->setNextSibling(0); + oldChild->setParent(0); + + return oldChild; +} + +void RenderContainer::setStyle(RenderStyle* _style) +{ + RenderObject::setStyle(_style); + + // If we are a pseudo-container we need to restyle the children + if (style()->isGenerated()) + { + // ### we could save this call when the change only affected + // non inherited properties + RenderStyle *pseudoStyle = new RenderStyle(); + pseudoStyle->inheritFrom(style()); + pseudoStyle->ref(); + RenderObject *child = firstChild(); + while (child != 0) + { + child->setStyle(pseudoStyle); + child = child->nextSibling(); + } + pseudoStyle->deref(); + } +} + +void RenderContainer::updatePseudoChildren() +{ + // In CSS2, before/after pseudo-content cannot nest. Check this first. + // Remove when CSS 3 Generated Content becomes Candidate Recommendation + if (style()->styleType() == RenderStyle::BEFORE + || style()->styleType() == RenderStyle::AFTER) + return; + + updatePseudoChild(RenderStyle::BEFORE); + updatePseudoChild(RenderStyle::AFTER); + // updatePseudoChild(RenderStyle::MARKER, marker()); +} + +void RenderContainer::updatePseudoChild(RenderStyle::PseudoId type) +{ + // The head manages generated content for its continuations + if (isInlineContinuation()) return; + + RenderStyle* pseudo = style()->getPseudoStyle(type); + + RenderObject* child = pseudoContainer(type); + + // Whether or not we currently have generated content attached. + bool oldContentPresent = child && (child->style()->styleType() == type); + + // Whether or not we now want generated content. + bool newContentWanted = pseudo && pseudo->display() != NONE; + + // No generated content + if (!oldContentPresent && !newContentWanted) + return; + + bool movedContent = (type == RenderStyle::AFTER && isRenderInline() && continuation()); + + // Whether or not we want the same old content. + bool sameOldContent = oldContentPresent && newContentWanted && !movedContent + && (child->style()->contentDataEquivalent(pseudo)); + + // No change in content, update style + if( sameOldContent ) { + child->setStyle(pseudo); + return; + } + + // If we don't want generated content any longer, or if we have generated content, + // but it's no longer identical to the new content data we want to build + // render objects for, then we nuke all of the old generated content. + if (oldContentPresent && (!newContentWanted || !sameOldContent)) + { + // The child needs to be removed. + oldContentPresent = false; + child->detach(); + child = 0; + } + + // If we have no pseudo-style or if the pseudo's display type is NONE, then we + // have no generated content and can now return. + if (!newContentWanted) + return; + + // Generated content consists of a single container that houses multiple children (specified + // by the content property). This pseudo container gets the pseudo style set on it. + RenderContainer* pseudoContainer = 0; + pseudoContainer = RenderFlow::createFlow(element(), pseudo, renderArena()); + pseudoContainer->setIsAnonymous( true ); + pseudoContainer->createGeneratedContent(); + + // Only add the container if it had content + if (pseudoContainer->firstChild()) { + addPseudoContainer(pseudoContainer); + pseudoContainer->close(); + } +} + +void RenderContainer::createGeneratedContent() +{ + RenderStyle* pseudo = style(); + RenderStyle* style = new RenderStyle(); + style->ref(); + style->inheritFrom(pseudo); + + // Now walk our list of generated content and create render objects for every type + // we encounter. + for (ContentData* contentData = pseudo->contentData(); + contentData; contentData = contentData->_nextContent) + { + if (contentData->_contentType == CONTENT_TEXT) + { + RenderText* t = new (renderArena()) RenderText( node(), 0); + t->setIsAnonymous( true ); + t->setStyle(style); + t->setText(contentData->contentText()); + addChild(t); + } + else if (contentData->_contentType == CONTENT_OBJECT) + { + RenderImage* img = new (renderArena()) RenderImage(node()); + img->setIsAnonymous( true ); + img->setStyle(style); + img->setContentObject(contentData->contentObject()); + addChild(img); + } + else if (contentData->_contentType == CONTENT_COUNTER) + { + // really a counter or just a glyph? + EListStyleType type = (EListStyleType)contentData->contentCounter()->listStyle(); + RenderObject *t = 0; + if (isListStyleCounted(type)) { + t = new (renderArena()) RenderCounter( node(), contentData->contentCounter() ); + } + else { + t = new (renderArena()) RenderGlyph( node(), type ); + } + t->setIsAnonymous( true ); + t->setStyle(style); + addChild(t); + } + else if (contentData->_contentType == CONTENT_QUOTE) + { + RenderQuote* t = new (renderArena()) RenderQuote( node(), contentData->contentQuote() ); + t->setIsAnonymous( true ); + t->setStyle(style); + addChild(t); + } + } + style->deref(); +} + +RenderContainer* RenderContainer::pseudoContainer(RenderStyle::PseudoId type) const +{ + RenderObject *child = 0; + switch (type) { + case RenderStyle::AFTER: + child = lastChild(); + break; + case RenderStyle::BEFORE: + child = firstChild(); + break; + case RenderStyle::REPLACED: + child = lastChild(); + if (child && child->style()->styleType() == RenderStyle::AFTER) + child = child->previousSibling(); + break; + default: + child = 0; + } + + if (child && child->style()->styleType() == type) { + assert(child->isRenderBlock() || child->isRenderInline()); + return static_cast<RenderContainer*>(child); + } + if (type == RenderStyle::AFTER) { + // check continuations + if (continuation()) + return continuation()->pseudoContainer(type); + } + if (child && child->isAnonymousBlock()) + return static_cast<RenderBlock*>(child)->pseudoContainer(type); + return 0; +} + +void RenderContainer::addPseudoContainer(RenderObject* child) +{ + RenderStyle::PseudoId type = child->style()->styleType(); + switch (type) { + case RenderStyle::AFTER: { + RenderObject *o = this; + while (o->continuation()) o = o->continuation(); + + // Coalesce inlines + if (child->style()->display() == INLINE && o->lastChild() && o->lastChild()->isAnonymousBlock()) { + o->lastChild()->addChild(child, 0); + } else + o->addChild(child, 0); + break; + } + case RenderStyle::BEFORE: + // Coalesce inlines + if (child->style()->display() == INLINE && firstChild() && firstChild()->isAnonymousBlock()) { + firstChild()->addChild(child, firstChild()->firstChild()); + } else + addChild(child, firstChild()); + break; + case RenderStyle::REPLACED: + addChild(child, pseudoContainer(RenderStyle::AFTER)); + break; + default: + break; + } +} + +void RenderContainer::updateReplacedContent() +{ + // Only for normal elements + if (!style() || style()->styleType() != RenderStyle::NOPSEUDO) + return; + + // delete old generated content + RenderContainer *container = pseudoContainer(RenderStyle::REPLACED); + if (container) { + container->detach(); + } + + if (style()->useNormalContent()) return; + + // create generated content + RenderStyle* pseudo = style()->getPseudoStyle(RenderStyle::REPLACED); + if (!pseudo) { + pseudo = new RenderStyle(); + pseudo->inheritFrom(style()); + pseudo->setStyleType(RenderStyle::REPLACED); + } + if (pseudo->useNormalContent()) + pseudo->setContentData(style()->contentData()); + + container = RenderFlow::createFlow(node(), pseudo, renderArena()); + container->setIsAnonymous( true ); + container->createGeneratedContent(); + + addChild(container, pseudoContainer(RenderStyle::AFTER)); +} + +void RenderContainer::appendChildNode(RenderObject* newChild) +{ + KHTMLAssert(newChild->parent() == 0); + + newChild->setParent(this); + RenderObject* lChild = lastChild(); + + if(lChild) + { + newChild->setPreviousSibling(lChild); + lChild->setNextSibling(newChild); + } + else + setFirstChild(newChild); + + setLastChild(newChild); + + // Keep our layer hierarchy updated. Optimize for the common case where we don't have any children + // and don't have a layer attached to ourselves. + if (newChild->firstChild() || newChild->layer()) { + RenderLayer* layer = enclosingLayer(); + newChild->addLayers(layer, newChild); + } + + newChild->setNeedsLayoutAndMinMaxRecalc(); // Goes up the containing block hierarchy. + if (!normalChildNeedsLayout()) + setChildNeedsLayout(true); // We may supply the static position for an absolute positioned child. +} + +void RenderContainer::insertChildNode(RenderObject* child, RenderObject* beforeChild) +{ + if(!beforeChild) { + appendChildNode(child); + return; + } + + KHTMLAssert(!child->parent()); + while ( beforeChild->parent() != this && beforeChild->parent()->isAnonymousBlock() ) + beforeChild = beforeChild->parent(); + KHTMLAssert(beforeChild->parent() == this); + + if(beforeChild == firstChild()) + setFirstChild(child); + + RenderObject* prev = beforeChild->previousSibling(); + child->setNextSibling(beforeChild); + beforeChild->setPreviousSibling(child); + if(prev) prev->setNextSibling(child); + child->setPreviousSibling(prev); + child->setParent(this); + + // Keep our layer hierarchy updated. + RenderLayer* layer = enclosingLayer(); + child->addLayers(layer, child); + + child->setNeedsLayoutAndMinMaxRecalc(); + if (!normalChildNeedsLayout()) + setChildNeedsLayout(true); // We may supply the static position for an absolute positioned child. +} + + +void RenderContainer::layout() +{ + KHTMLAssert( needsLayout() ); + KHTMLAssert( minMaxKnown() ); + const bool pagedMode = canvas()->pagedMode(); + RenderObject *child = firstChild(); + while( child ) { + if (pagedMode) child->setNeedsLayout(true); + child->layoutIfNeeded(); + if (child->containsPageBreak()) setContainsPageBreak(true); + if (child->needsPageClear()) setNeedsPageClear(true); + child = child->nextSibling(); + } + setNeedsLayout(false); +} + +void RenderContainer::removeLeftoverAnonymousBoxes() +{ + // we have to go over all child nodes and remove anonymous boxes, that do _not_ + // have inline children to keep the tree flat + RenderObject *child = firstChild(); + while( child ) { + RenderObject *next = child->nextSibling(); + + if ( child->isRenderBlock() && child->isAnonymousBlock() && !child->continuation() && + !child->childrenInline() && !child->isTableCell() && !child->doNotDelete()) { + RenderObject *firstAnChild = child->firstChild(); + RenderObject *lastAnChild = child->lastChild(); + if ( firstAnChild ) { + RenderObject *o = firstAnChild; + while( o ) { + o->setParent( this ); + o = o->nextSibling(); + } + firstAnChild->setPreviousSibling( child->previousSibling() ); + lastAnChild->setNextSibling( child->nextSibling() ); + if ( child->previousSibling() ) + child->previousSibling()->setNextSibling( firstAnChild ); + if ( child->nextSibling() ) + child->nextSibling()->setPreviousSibling( lastAnChild ); + if ( child == firstChild() ) + m_first = firstAnChild; + if ( child == lastChild() ) + m_last = lastAnChild; + } else { + if ( child->previousSibling() ) + child->previousSibling()->setNextSibling( child->nextSibling() ); + if ( child->nextSibling() ) + child->nextSibling()->setPreviousSibling( child->previousSibling() ); + if ( child == firstChild() ) + m_first = child->nextSibling(); + if ( child == lastChild() ) + m_last = child->previousSibling(); + } + child->setParent( 0 ); + child->setPreviousSibling( 0 ); + child->setNextSibling( 0 ); + if ( !child->isText() ) { + RenderContainer *c = static_cast<RenderContainer *>(child); + c->m_first = 0; + c->m_next = 0; + } + child->detach(); + } + child = next; + } + if ( parent() ) + parent()->removeLeftoverAnonymousBoxes(); +} + +#undef DEBUG_LAYOUT |