diff options
Diffstat (limited to 'khtml/rendering')
60 files changed, 37375 insertions, 0 deletions
diff --git a/khtml/rendering/Makefile.am b/khtml/rendering/Makefile.am new file mode 100644 index 000000000..4389b29f8 --- /dev/null +++ b/khtml/rendering/Makefile.am @@ -0,0 +1,57 @@ +# This file is part of the KDE libraries +# Copyright (C) 1997 Martin Jones (mjones@kde.org) +# (C) 1997 Torben Weis (weis@kde.org) + +# 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. + +KDE_CXXFLAGS = $(WOVERLOADED_VIRTUAL) + +noinst_LTLIBRARIES = libkhtmlrender.la +libkhtmlrender_la_SOURCES = \ + bidi.cpp break_lines.cpp render_block.cpp render_inline.cpp \ + render_style.cpp render_object.cpp render_container.cpp render_box.cpp \ + render_flow.cpp render_text.cpp render_arena.cpp render_layer.cpp \ + render_image.cpp render_table.cpp table_layout.cpp \ + render_replaced.cpp render_form.cpp render_list.cpp \ + render_canvas.cpp render_frames.cpp render_br.cpp \ + render_body.cpp font.cpp render_line.cpp render_generated.cpp \ + enumerate.cpp counter_tree.cpp + +libkhtmlrender_la_METASOURCES = AUTO + +noinst_HEADERS = \ + bidi.h break_lines.h \ + render_arena.h render_layer.h \ + render_style.h render_object.h render_container.h render_box.h \ + render_flow.h render_text.h render_table.h render_replaced.h \ + render_form.h render_list.h render_canvas.h render_frames.h \ + render_br.h render_applet.h font.h table_layout.h render_line.h \ + render_generated.h enumerate.h + +INCLUDES = -I$(top_srcdir)/kimgio -I$(top_srcdir)/kio -I$(top_srcdir)/dcop \ + -I$(top_srcdir)/kfile -I$(top_srcdir)/khtml -I$(top_srcdir)/kutils -I$(top_srcdir) $(all_includes) + +SRCDOC_DEST=$(kde_htmldir)/en/kdelibs/khtml + +## generate lib documentation +srcdoc: + $(mkinstalldirs) $(SRCDOC_DEST) + kdoc -H -d $(SRCDOC_DEST) kdecore -lqt + +## maintainer: regen loading icon +loading-icon: + bin2c -sploading_icon $(srcdir)/img-loading.png > $(srcdir)/loading_icon.cpp + diff --git a/khtml/rendering/bidi.cpp b/khtml/rendering/bidi.cpp new file mode 100644 index 000000000..a220641b2 --- /dev/null +++ b/khtml/rendering/bidi.cpp @@ -0,0 +1,2253 @@ +/** + * This file is part of the html renderer for KDE. + * + * Copyright (C) 2000-2003 Lars Knoll (knoll@kde.org) + * (C) 2003-2005 Apple Computer, Inc. + * (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ +#include "rendering/bidi.h" +#include "rendering/break_lines.h" +#include "rendering/render_block.h" +#include "rendering/render_text.h" +#include "rendering/render_arena.h" +#include "rendering/render_layer.h" +#include "rendering/render_canvas.h" +#include "xml/dom_docimpl.h" + +#include "kdebug.h" +#include "qdatetime.h" +#include "qfontmetrics.h" + +#define BIDI_DEBUG 0 +//#define DEBUG_LINEBREAKS +//#define PAGE_DEBUG + +namespace khtml { + + +// an iterator which goes through a BidiParagraph +struct BidiIterator +{ + BidiIterator() : par(0), obj(0), pos(0) {} + BidiIterator(RenderBlock *_par, RenderObject *_obj, unsigned int _pos) : par(_par), obj(_obj), pos(_pos) {} + + void increment( BidiState &bidi ); + + bool atEnd() const; + + const QChar ¤t() const; + QChar::Direction direction() const; + + RenderBlock *par; + RenderObject *obj; + unsigned int pos; +}; + + +struct BidiStatus { + BidiStatus() : eor(QChar::DirON), lastStrong(QChar::DirON), last(QChar::DirON) {} + + QChar::Direction eor; + QChar::Direction lastStrong; + QChar::Direction last; +}; + +struct BidiState { + BidiState() : context(0) {} + + BidiIterator sor; + BidiIterator eor; + BidiIterator last; + BidiIterator current; + BidiContext *context; + BidiStatus status; +}; + +// Used to track a list of chained bidi runs. +static BidiRun* sFirstBidiRun; +static BidiRun* sLastBidiRun; +static int sBidiRunCount; +static BidiRun* sCompactFirstBidiRun; +static BidiRun* sCompactLastBidiRun; +static int sCompactBidiRunCount; +static bool sBuildingCompactRuns; + +// Midpoint globals. The goal is not to do any allocation when dealing with +// these midpoints, so we just keep an array around and never clear it. We track +// the number of items and position using the two other variables. +static QMemArray<BidiIterator> *smidpoints; +static uint sNumMidpoints; +static uint sCurrMidpoint; +static bool betweenMidpoints; + +static bool isLineEmpty = true; +static bool previousLineBrokeAtBR = true; +static QChar::Direction dir; +static bool adjustEmbedding; +static bool emptyRun = true; +static int numSpaces; + +static void embed( QChar::Direction d, BidiState &bidi ); +static void appendRun( BidiState &bidi ); + +static int getBPMWidth(int childValue, Length cssUnit) +{ + if (!cssUnit.isVariable()) + return (cssUnit.isFixed() ? cssUnit.value() : childValue); + return 0; +} + +static int getBorderPaddingMargin(RenderObject* child, bool endOfInline) +{ + RenderStyle* cstyle = child->style(); + int result = 0; + bool leftSide = (cstyle->direction() == LTR) ? !endOfInline : endOfInline; + result += getBPMWidth((leftSide ? child->marginLeft() : child->marginRight()), + (leftSide ? cstyle->marginLeft() : + cstyle->marginRight())); + result += getBPMWidth((leftSide ? child->paddingLeft() : child->paddingRight()), + (leftSide ? cstyle->paddingLeft() : + cstyle->paddingRight())); + result += leftSide ? child->borderLeft() : child->borderRight(); + return result; +} + +static int inlineWidth(RenderObject* child, bool start = true, bool end = true) +{ + int extraWidth = 0; + RenderObject* parent = child->parent(); + while (parent->isInline() && !parent->isInlineBlockOrInlineTable()) { + if (start && parent->firstChild() == child) + extraWidth += getBorderPaddingMargin(parent, false); + if (end && parent->lastChild() == child) + extraWidth += getBorderPaddingMargin(parent, true); + child = parent; + parent = child->parent(); + } + return extraWidth; +} + +#ifndef NDEBUG +static bool inBidiRunDetach; +#endif + +void BidiRun::detach(RenderArena* renderArena) +{ +#ifndef NDEBUG + inBidiRunDetach = true; +#endif + delete this; +#ifndef NDEBUG + inBidiRunDetach = false; +#endif + + // Recover the size left there for us by operator delete and free the memory. + renderArena->free(*(size_t *)this, this); +} + +void* BidiRun::operator new(size_t sz, RenderArena* renderArena) throw() +{ + return renderArena->allocate(sz); +} + +void BidiRun::operator delete(void* ptr, size_t sz) +{ + assert(inBidiRunDetach); + + // Stash size where detach can find it. + *(size_t*)ptr = sz; +} + +static void deleteBidiRuns(RenderArena* arena) +{ + if (!sFirstBidiRun) + return; + + BidiRun* curr = sFirstBidiRun; + while (curr) { + BidiRun* s = curr->nextRun; + curr->detach(arena); + curr = s; + } + + sFirstBidiRun = 0; + sLastBidiRun = 0; + sBidiRunCount = 0; +} + +// --------------------------------------------------------------------- + +/* a small helper class used internally to resolve Bidi embedding levels. + Each line of text caches the embedding level at the start of the line for faster + relayouting +*/ +BidiContext::BidiContext(unsigned char l, QChar::Direction e, BidiContext *p, bool o) + : level(l) , override(o), dir(e) +{ + parent = p; + if(p) { + p->ref(); + basicDir = p->basicDir; + } else + basicDir = e; + count = 0; +} + +BidiContext::~BidiContext() +{ + if(parent) parent->deref(); +} + +void BidiContext::ref() const +{ + count++; +} + +void BidiContext::deref() const +{ + count--; + if(count <= 0) delete this; +} + +// --------------------------------------------------------------------- + +inline bool operator==( const BidiIterator &it1, const BidiIterator &it2 ) +{ + if(it1.pos != it2.pos) return false; + if(it1.obj != it2.obj) return false; + return true; +} + +inline bool operator!=( const BidiIterator &it1, const BidiIterator &it2 ) +{ + if(it1.pos != it2.pos) return true; + if(it1.obj != it2.obj) return true; + return false; +} + +static inline RenderObject *Bidinext(RenderObject *par, RenderObject *current, BidiState &bidi, + bool skipInlines = true) +{ + RenderObject *next = 0; + + while(current != 0) + { + //kdDebug( 6040 ) << "current = " << current << endl; + if (!current->isFloating() && !current->isReplaced() && !current->isPositioned()) { + next = current->firstChild(); + if ( next && adjustEmbedding ) { + EUnicodeBidi ub = next->style()->unicodeBidi(); + if ( ub != UBNormal && !emptyRun ) { + EDirection dir = next->style()->direction(); + QChar::Direction d = ( ub == Embed ? ( dir == RTL ? QChar::DirRLE : QChar::DirLRE ) + : ( dir == RTL ? QChar::DirRLO : QChar::DirLRO ) ); + embed( d, bidi ); + } + } + } + if (!next) { + while (current && current != par) { + next = current->nextSibling(); + if (next) break; + if ( adjustEmbedding && current->style()->unicodeBidi() != UBNormal && !emptyRun ) { + embed( QChar::DirPDF, bidi ); + } + current = current->parent(); + } + } + + if (!next) break; + + if (next->isText() || next->isBR() || next->isFloating() || next->isReplaced() || next->isPositioned() || next->isGlyph() + || ((!skipInlines || !next->firstChild()) // Always return EMPTY inlines. + && next->isInlineFlow())) + break; + current = next; + } + return next; +} + +static RenderObject *first( RenderObject *par, BidiState &bidi, bool skipInlines = true ) +{ + if(!par->firstChild()) return 0; + RenderObject *o = par->firstChild(); + + if (o->isInlineFlow()) { + if (skipInlines && o->firstChild()) + o = Bidinext( par, o, bidi, skipInlines ); + else + return o; // Never skip empty inlines. + } + + if (o && !o->isText() && !o->isBR() && !o->isReplaced() && !o->isFloating() && !o->isPositioned() && !o->isGlyph()) + o = Bidinext( par, o, bidi, skipInlines ); + return o; +} + +inline void BidiIterator::increment (BidiState &bidi) +{ + if(!obj) return; + if(obj->isText()) { + pos++; + if(pos >= static_cast<RenderText *>(obj)->stringLength()) { + obj = Bidinext( par, obj, bidi ); + pos = 0; + } + } else { + obj = Bidinext( par, obj, bidi ); + pos = 0; + } +} + +inline bool BidiIterator::atEnd() const +{ + if(!obj) return true; + return false; +} + +const QChar &BidiIterator::current() const +{ + static QChar nonBreakingSpace(0xA0); + + if (!obj || !obj->isText()) + return nonBreakingSpace; + + RenderText* text = static_cast<RenderText*>(obj); + if (!text->text()) + return nonBreakingSpace; + + return text->text()[pos]; +} + +inline QChar::Direction BidiIterator::direction() const +{ + if(!obj || !obj->isText() ) return QChar::DirON; + + RenderText *renderTxt = static_cast<RenderText *>( obj ); + if ( pos >= renderTxt->stringLength() ) + return QChar::DirON; + return renderTxt->text()[pos].direction(); +} + +// ------------------------------------------------------------------------------------------------- + +static void addRun(BidiRun* bidiRun) +{ + if (!sFirstBidiRun) + sFirstBidiRun = sLastBidiRun = bidiRun; + else { + sLastBidiRun->nextRun = bidiRun; + sLastBidiRun = bidiRun; + } + sBidiRunCount++; + bidiRun->compact = sBuildingCompactRuns; + + // Compute the number of spaces in this run, + if (bidiRun->obj && bidiRun->obj->isText()) { + RenderText* text = static_cast<RenderText*>(bidiRun->obj); + if (text->text()) { + for (int i = bidiRun->start; i < bidiRun->stop; i++) { + const QChar c = text->text()[i]; + if (c.category() == QChar::Separator_Space || c == '\n') + numSpaces++; + } + } + } +} + +static void reverseRuns(int start, int end) +{ + if (start >= end) + return; + + assert(start >= 0 && end < sBidiRunCount); + + // Get the item before the start of the runs to reverse and put it in + // |beforeStart|. |curr| should point to the first run to reverse. + BidiRun* curr = sFirstBidiRun; + BidiRun* beforeStart = 0; + int i = 0; + while (i < start) { + i++; + beforeStart = curr; + curr = curr->nextRun; + } + + BidiRun* startRun = curr; + while (i < end) { + i++; + curr = curr->nextRun; + } + BidiRun* endRun = curr; + BidiRun* afterEnd = curr->nextRun; + + i = start; + curr = startRun; + BidiRun* newNext = afterEnd; + while (i <= end) { + // Do the reversal. + BidiRun* next = curr->nextRun; + curr->nextRun = newNext; + newNext = curr; + curr = next; + i++; + } + + // Now hook up beforeStart and afterEnd to the newStart and newEnd. + if (beforeStart) + beforeStart->nextRun = endRun; + else + sFirstBidiRun = endRun; + + startRun->nextRun = afterEnd; + if (!afterEnd) + sLastBidiRun = startRun; +} + +static void chopMidpointsAt(RenderObject* obj, uint pos) +{ + if (!sNumMidpoints) return; + BidiIterator* midpoints = smidpoints->data(); + for (uint i = 0; i < sNumMidpoints; i++) { + const BidiIterator& point = midpoints[i]; + if (point.obj == obj && point.pos == pos) { + sNumMidpoints = i; + break; + } + } +} + +static void checkMidpoints(BidiIterator& lBreak, BidiState &bidi) +{ + // Check to see if our last midpoint is a start point beyond the line break. If so, + // shave it off the list, and shave off a trailing space if the previous end point isn't + // white-space: pre. + if (lBreak.obj && sNumMidpoints && sNumMidpoints%2 == 0) { + BidiIterator* midpoints = smidpoints->data(); + BidiIterator& endpoint = midpoints[sNumMidpoints-2]; + const BidiIterator& startpoint = midpoints[sNumMidpoints-1]; + BidiIterator currpoint = endpoint; + while (!currpoint.atEnd() && currpoint != startpoint && currpoint != lBreak) + currpoint.increment( bidi ); + if (currpoint == lBreak) { + // We hit the line break before the start point. Shave off the start point. + sNumMidpoints--; + if (!endpoint.obj->style()->preserveWS()) { + if (endpoint.obj->isText()) { + // Don't shave a character off the endpoint if it was from a soft hyphen. + RenderText* textObj = static_cast<RenderText*>(endpoint.obj); + if (endpoint.pos+1 < textObj->length() && + textObj->text()[endpoint.pos+1].unicode() == SOFT_HYPHEN) + return; + } + endpoint.pos--; + } + } + } +} + +static void addMidpoint(const BidiIterator& midpoint) +{ + if (!smidpoints) + return; + + if (smidpoints->size() <= sNumMidpoints) + smidpoints->resize(sNumMidpoints+10); + + BidiIterator* midpoints = smidpoints->data(); + midpoints[sNumMidpoints++] = midpoint; +} + +static void appendRunsForObject(int start, int end, RenderObject* obj, BidiState &bidi) +{ + if (start > end || obj->isFloating() || + (obj->isPositioned() && !obj->hasStaticX() && !obj->hasStaticY())) + return; + + bool haveNextMidpoint = (smidpoints && sCurrMidpoint < sNumMidpoints); + BidiIterator nextMidpoint; + if (haveNextMidpoint) + nextMidpoint = smidpoints->at(sCurrMidpoint); + if (betweenMidpoints) { + if (!(haveNextMidpoint && nextMidpoint.obj == obj)) + return; + // This is a new start point. Stop ignoring objects and + // adjust our start. + betweenMidpoints = false; + start = nextMidpoint.pos; + sCurrMidpoint++; + if (start < end) + return appendRunsForObject(start, end, obj, bidi); + } + else { + if (!smidpoints || !haveNextMidpoint || (obj != nextMidpoint.obj)) { + addRun(new (obj->renderArena()) BidiRun(start, end, obj, bidi.context, dir)); + return; + } + + // An end midpoint has been encountered within our object. We + // need to go ahead and append a run with our endpoint. + if (int(nextMidpoint.pos+1) <= end) { + betweenMidpoints = true; + sCurrMidpoint++; + if (nextMidpoint.pos != UINT_MAX) { // UINT_MAX means stop at the object and don't include any of it. + addRun(new (obj->renderArena()) + BidiRun(start, nextMidpoint.pos+1, obj, bidi.context, dir)); + return appendRunsForObject(nextMidpoint.pos+1, end, obj, bidi); + } + } + else + addRun(new (obj->renderArena()) BidiRun(start, end, obj, bidi.context, dir)); + } +} + +static void appendRun( BidiState &bidi ) +{ + if ( emptyRun ) return; +#if BIDI_DEBUG > 1 + kdDebug(6041) << "appendRun: dir="<<(int)dir<<endl; +#endif + + bool b = adjustEmbedding; + adjustEmbedding = false; + + int start = bidi.sor.pos; + RenderObject *obj = bidi.sor.obj; + while( obj && obj != bidi.eor.obj ) { + appendRunsForObject(start, obj->length(), obj, bidi); + start = 0; + obj = Bidinext( bidi.sor.par, obj, bidi ); + } + if (obj) + appendRunsForObject(start, bidi.eor.pos+1, obj, bidi); + + bidi.eor.increment( bidi ); + bidi.sor = bidi.eor; + dir = QChar::DirON; + bidi.status.eor = QChar::DirON; + adjustEmbedding = b; +} + +static void embed( QChar::Direction d, BidiState &bidi ) +{ +#if BIDI_DEBUG > 1 + qDebug("*** embed dir=%d emptyrun=%d", d, emptyRun ); +#endif + bool b = adjustEmbedding ; + adjustEmbedding = false; + if ( d == QChar::DirPDF ) { + BidiContext *c = bidi.context->parent; + if (c) { + if ( bidi.eor != bidi.last ) { + appendRun( bidi ); + bidi.eor = bidi.last; + } + appendRun( bidi ); + emptyRun = true; + bidi.status.last = bidi.context->dir; + bidi.context->deref(); + bidi.context = c; + if(bidi.context->override) + dir = bidi.context->dir; + else + dir = QChar::DirON; + bidi.status.lastStrong = bidi.context->dir; + } + } else { + QChar::Direction runDir; + if( d == QChar::DirRLE || d == QChar::DirRLO ) + runDir = QChar::DirR; + else + runDir = QChar::DirL; + bool override; + if( d == QChar::DirLRO || d == QChar::DirRLO ) + override = true; + else + override = false; + + unsigned char level = bidi.context->level; + if ( runDir == QChar::DirR ) { + if(level%2) // we have an odd level + level += 2; + else + level++; + } else { + if(level%2) // we have an odd level + level++; + else + level += 2; + } + + if(level < 61) { + if ( bidi.eor != bidi.last ) { + appendRun( bidi ); + bidi.eor = bidi.last; + } + appendRun( bidi ); + emptyRun = true; + + bidi.context = new BidiContext(level, runDir, bidi.context, override); + bidi.context->ref(); + dir = runDir; + bidi.status.last = runDir; + bidi.status.lastStrong = runDir; + bidi.status.eor = runDir; + } + } + adjustEmbedding = b; +} + +InlineFlowBox* RenderBlock::createLineBoxes(RenderObject* obj) +{ + // See if we have an unconstructed line box for this object that is also + // the last item on the line. + KHTMLAssert(obj->isInlineFlow() || obj == this); + RenderFlow* flow = static_cast<RenderFlow*>(obj); + + // Get the last box we made for this render object. + InlineFlowBox* box = flow->lastLineBox(); + + // If this box is constructed then it is from a previous line, and we need + // to make a new box for our line. If this box is unconstructed but it has + // something following it on the line, then we know we have to make a new box + // as well. In this situation our inline has actually been split in two on + // the same line (this can happen with very fancy language mixtures). + if (!box || box->isConstructed() || box->nextOnLine()) { + // We need to make a new box for this render object. Once + // made, we need to place it at the end of the current line. + InlineBox* newBox = obj->createInlineBox(false, obj == this); + KHTMLAssert(newBox->isInlineFlowBox()); + box = static_cast<InlineFlowBox*>(newBox); + box->setFirstLineStyleBit(m_firstLine); + + // We have a new box. Append it to the inline box we get by constructing our + // parent. If we have hit the block itself, then |box| represents the root + // inline box for the line, and it doesn't have to be appended to any parent + // inline. + if (obj != this) { + InlineFlowBox* parentBox = createLineBoxes(obj->parent()); + parentBox->addToLine(box); + } + } + + return box; +} + +InlineFlowBox* RenderBlock::constructLine(const BidiIterator &/*start*/, const BidiIterator &end) +{ + if (!sFirstBidiRun) + return 0; // We had no runs. Don't make a root inline box at all. The line is empty. + + InlineFlowBox* parentBox = 0; + for (BidiRun* r = sFirstBidiRun; r; r = r->nextRun) { + // Create a box for our object. + r->box = r->obj->createInlineBox(r->obj->isPositioned(), false); + + // If we have no parent box yet, or if the run is not simply a sibling, + // then we need to construct inline boxes as necessary to properly enclose the + // run's inline box. + if (!parentBox || (parentBox->object() != r->obj->parent())) + // Create new inline boxes all the way back to the appropriate insertion point. + parentBox = createLineBoxes(r->obj->parent()); + + // Append the inline box to this line. + parentBox->addToLine(r->box); + } + + // We should have a root inline box. It should be unconstructed and + // be the last continuation of our line list. + KHTMLAssert(lastLineBox() && !lastLineBox()->isConstructed()); + + // Set bits on our inline flow boxes that indicate which sides should + // paint borders/margins/padding. This knowledge will ultimately be used when + // we determine the horizontal positions and widths of all the inline boxes on + // the line. + RenderObject* endObject = 0; + bool lastLine = !end.obj; + if (end.obj && end.pos == 0) + endObject = end.obj; + lastLineBox()->determineSpacingForFlowBoxes(lastLine, endObject); + + // Now mark the line boxes as being constructed. + lastLineBox()->setConstructed(); + + // Return the last line. + return lastLineBox(); +} + +void RenderBlock::computeHorizontalPositionsForLine(InlineFlowBox* lineBox, BidiState &bidi) +{ + // First determine our total width. + int totWidth = lineBox->getFlowSpacingWidth(); + BidiRun* r = 0; + for (r = sFirstBidiRun; r; r = r->nextRun) { + if (r->obj->isPositioned()) + continue; // Positioned objects are only participating to figure out their + // correct static x position. They have no effect on the width. + if (r->obj->isText()) + r->box->setWidth(static_cast<RenderText *>(r->obj)->width(r->start, r->stop-r->start, m_firstLine)); + else if (!r->obj->isInlineFlow()) { + r->obj->calcWidth(); + r->box->setWidth(r->obj->width()); + totWidth += r->obj->marginLeft() + r->obj->marginRight(); + } + totWidth += r->box->width(); + } + + // Armed with the total width of the line (without justification), + // we now examine our text-align property in order to determine where to position the + // objects horizontally. The total width of the line can be increased if we end up + // justifying text. + int x = leftOffset(m_height); + int availableWidth = lineWidth(m_height); + switch(style()->textAlign()) { + case LEFT: + case KHTML_LEFT: + numSpaces = 0; + break; + case JUSTIFY: + if (numSpaces != 0 && !bidi.current.atEnd() && !bidi.current.obj->isBR() ) + break; + // fall through + case TAAUTO: + numSpaces = 0; + // for right to left fall through to right aligned + if (bidi.context->basicDir == QChar::DirL) + break; + case RIGHT: + case KHTML_RIGHT: + x += availableWidth - totWidth; + numSpaces = 0; + break; + case CENTER: + case KHTML_CENTER: + int xd = (availableWidth - totWidth)/2; + x += xd >0 ? xd : 0; + numSpaces = 0; + break; + } + + if (numSpaces > 0) { + for (r = sFirstBidiRun; r; r = r->nextRun) { + int spaceAdd = 0; + if (numSpaces > 0 && r->obj->isText()) { + // get the number of spaces in the run + int spaces = 0; + for ( int i = r->start; i < r->stop; i++ ) { + const QChar c = static_cast<RenderText *>(r->obj)->text()[i]; + if (c.category() == QChar::Separator_Space || c == '\n') + spaces++; + } + + KHTMLAssert(spaces <= numSpaces); + + // Only justify text with white-space: normal. + if (r->obj->style()->whiteSpace() == NORMAL) { + spaceAdd = (availableWidth - totWidth)*spaces/numSpaces; + static_cast<InlineTextBox*>(r->box)->setSpaceAdd(spaceAdd); + totWidth += spaceAdd; + } + numSpaces -= spaces; + } + } + } + + // The widths of all runs are now known. We can now place every inline box (and + // compute accurate widths for the inline flow boxes). + int rightPos = lineBox->placeBoxesHorizontally(x); + if (rightPos > m_overflowWidth) + m_overflowWidth = rightPos; // FIXME: Work for rtl overflow also. + if (x < 0) + m_overflowLeft = kMin(m_overflowLeft, x); +} + +void RenderBlock::computeVerticalPositionsForLine(InlineFlowBox* lineBox) +{ + lineBox->verticallyAlignBoxes(m_height); +// lineBox->setBlockHeight(m_height); + + // Check for page-breaks + if (canvas()->pagedMode() && !lineBox->afterPageBreak()) + // If we get a page-break we might need to redo the line-break + if (clearLineOfPageBreaks(lineBox) && hasFloats()) return; + + // See if the line spilled out. If so set overflow height accordingly. + int bottomOfLine = lineBox->bottomOverflow(); + if (bottomOfLine > m_height && bottomOfLine > m_overflowHeight) + m_overflowHeight = bottomOfLine; + + bool beforeContent = true; + + // Now make sure we place replaced render objects correctly. + for (BidiRun* r = sFirstBidiRun; r; r = r->nextRun) { + + // For positioned placeholders, cache the static Y position an object with non-inline display would have. + // Either it is unchanged if it comes before any real linebox, or it must clear the current line (already accounted in m_height). + // This value will be picked up by position() if relevant. + if (r->obj->isPositioned()) + r->box->setYPos( beforeContent && r->obj->isBox() ? static_cast<RenderBox*>(r->obj)->staticY() : m_height ); + else if (beforeContent) + beforeContent = false; + + // Position is used to properly position both replaced elements and + // to update the static normal flow x/y of positioned elements. + r->obj->position(r->box, r->start, r->stop - r->start, r->level%2); + } +} + +bool RenderBlock::clearLineOfPageBreaks(InlineFlowBox* lineBox) +{ + bool doPageBreak = false; + // Check for physical page-breaks + int xpage = crossesPageBreak(lineBox->topOverflow(), lineBox->bottomOverflow()); + if (xpage) { +#ifdef PAGE_DEBUG + kdDebug(6040) << renderName() << " Line crosses to page " << xpage << endl; + kdDebug(6040) << renderName() << " at pos " << lineBox->yPos() << " height " << lineBox->height() << endl; +#endif + + doPageBreak = true; + // check page-break-inside + if (!style()->pageBreakInside()) { + if (parent()->canClear(this, PageBreakNormal)) { + setNeedsPageClear(true); + doPageBreak = false; + } +#ifdef PAGE_DEBUG + else + kdDebug(6040) << "Ignoring page-break-inside: avoid" << endl; +#endif + } + // check orphans + int orphans = 0; + InlineRunBox* box = lineBox->prevLineBox(); + while (box && orphans < style()->orphans()) { + orphans++; + box = box->prevLineBox(); + } + + if (orphans == 0) { + setNeedsPageClear(true); + doPageBreak = false; + } else + if (orphans < style()->orphans() ) { +#ifdef PAGE_DEBUG + kdDebug(6040) << "Orphans: " << orphans << endl; +#endif + // Orphans is a level 2 page-break rule and can be broken only + // if the break is physically required. + if (parent()->canClear(this, PageBreakHarder)) { + // move block instead + setNeedsPageClear(true); + doPageBreak = false; + } +#ifdef PAGE_DEBUG + else + kdDebug(6040) << "Ignoring violated orphans" << endl; +#endif + } + if (doPageBreak) { + int pTop = pageTopAfter(lineBox->yPos()); + + m_height = pTop; + lineBox->setAfterPageBreak(true); + lineBox->verticallyAlignBoxes(m_height); + if (lineBox->yPos() < pTop) { + // ### serious crap. render_line is sometimes placing lines too high + kdDebug(6040) << "page top overflow by repositioned line" << endl; + int heightIncrease = pTop - lineBox->yPos(); + m_height = pTop + heightIncrease; + lineBox->verticallyAlignBoxes(m_height); + } +#ifdef PAGE_DEBUG + kdDebug(6040) << "Cleared line " << lineBox->yPos() - oldYPos << "px" << endl; +#endif + setContainsPageBreak(true); + } + } + return doPageBreak; +} + +// collects one line of the paragraph and transforms it to visual order +void RenderBlock::bidiReorderLine(const BidiIterator &start, const BidiIterator &end, BidiState &bidi) +{ + if ( start == end ) { + if ( start.current() == '\n' ) { + m_height += lineHeight( m_firstLine ); + } + return; + } + +#if BIDI_DEBUG > 1 + kdDebug(6041) << "reordering Line from " << start.obj << "/" << start.pos << " to " << end.obj << "/" << end.pos << endl; +#endif + + sFirstBidiRun = 0; + sLastBidiRun = 0; + sBidiRunCount = 0; + + // context->ref(); + + dir = QChar::DirON; + emptyRun = true; + + numSpaces = 0; + + bidi.current = start; + bidi.last = bidi.current; + bool atEnd = false; + while( 1 ) { + + QChar::Direction dirCurrent; + if (atEnd) { + //kdDebug(6041) << "atEnd" << endl; + BidiContext *c = bidi.context; + if ( bidi.current.atEnd()) + while ( c->parent ) + c = c->parent; + dirCurrent = c->dir; + } else { + dirCurrent = bidi.current.direction(); + } + +#ifndef QT_NO_UNICODETABLES + +#if BIDI_DEBUG > 1 + kdDebug(6041) << "directions: dir=" << (int)dir << " current=" << (int)dirCurrent << " last=" << status.last << " eor=" << status.eor << " lastStrong=" << status.lastStrong << " embedding=" << (int)context->dir << " level =" << (int)context->level << endl; +#endif + + switch(dirCurrent) { + + // embedding and overrides (X1-X9 in the Bidi specs) + case QChar::DirRLE: + case QChar::DirLRE: + case QChar::DirRLO: + case QChar::DirLRO: + case QChar::DirPDF: + embed( dirCurrent, bidi ); + break; + + // strong types + case QChar::DirL: + if(dir == QChar::DirON) + dir = QChar::DirL; + switch(bidi.status.last) + { + case QChar::DirL: + bidi.eor = bidi.current; bidi.status.eor = QChar::DirL; break; + case QChar::DirR: + case QChar::DirAL: + case QChar::DirEN: + case QChar::DirAN: + appendRun( bidi ); + break; + case QChar::DirES: + case QChar::DirET: + case QChar::DirCS: + case QChar::DirBN: + case QChar::DirB: + case QChar::DirS: + case QChar::DirWS: + case QChar::DirON: + if( bidi.status.eor != QChar::DirL ) { + //last stuff takes embedding dir + if(bidi.context->dir == QChar::DirL || bidi.status.lastStrong == QChar::DirL) { + if ( bidi.status.eor != QChar::DirEN && bidi.status.eor != QChar::DirAN && bidi.status.eor != QChar::DirON ) + appendRun( bidi ); + dir = QChar::DirL; + bidi.eor = bidi.current; + bidi.status.eor = QChar::DirL; + } else { + if ( bidi.status.eor == QChar::DirEN || bidi.status.eor == QChar::DirAN ) + { + dir = bidi.status.eor; + appendRun( bidi ); + } + dir = QChar::DirR; + bidi.eor = bidi.last; + appendRun( bidi ); + dir = QChar::DirL; + bidi.status.eor = QChar::DirL; + } + } else { + bidi.eor = bidi.current; bidi.status.eor = QChar::DirL; + } + default: + break; + } + bidi.status.lastStrong = QChar::DirL; + break; + case QChar::DirAL: + case QChar::DirR: + if(dir == QChar::DirON) dir = QChar::DirR; + switch(bidi.status.last) + { + case QChar::DirR: + case QChar::DirAL: + bidi.eor = bidi.current; bidi.status.eor = QChar::DirR; break; + case QChar::DirL: + case QChar::DirEN: + case QChar::DirAN: + appendRun( bidi ); + dir = QChar::DirR; + bidi.eor = bidi.current; + bidi.status.eor = QChar::DirR; + break; + case QChar::DirES: + case QChar::DirET: + case QChar::DirCS: + case QChar::DirBN: + case QChar::DirB: + case QChar::DirS: + case QChar::DirWS: + case QChar::DirON: + if( !(bidi.status.eor == QChar::DirR) && !(bidi.status.eor == QChar::DirAL) ) { + //last stuff takes embedding dir + if(bidi.context->dir == QChar::DirR || bidi.status.lastStrong == QChar::DirR + || bidi.status.lastStrong == QChar::DirAL) { + appendRun( bidi ); + dir = QChar::DirR; + bidi.eor = bidi.current; + bidi.status.eor = QChar::DirR; + } else { + dir = QChar::DirL; + bidi.eor = bidi.last; + appendRun( bidi ); + dir = QChar::DirR; + bidi.status.eor = QChar::DirR; + } + } else { + bidi.eor = bidi.current; bidi.status.eor = QChar::DirR; + } + default: + break; + } + bidi.status.lastStrong = dirCurrent; + break; + + // weak types: + + case QChar::DirNSM: + // ### if @sor, set dir to dirSor + break; + case QChar::DirEN: + if(!(bidi.status.lastStrong == QChar::DirAL)) { + // if last strong was AL change EN to AN + if(dir == QChar::DirON) { + dir = QChar::DirL; + } + switch(bidi.status.last) + { + case QChar::DirET: + if ( bidi.status.lastStrong == QChar::DirR || bidi.status.lastStrong == QChar::DirAL ) { + appendRun( bidi ); + dir = QChar::DirEN; + bidi.status.eor = QChar::DirEN; + } + // fall through + case QChar::DirEN: + case QChar::DirL: + bidi.eor = bidi.current; + bidi.status.eor = dirCurrent; + break; + case QChar::DirR: + case QChar::DirAL: + case QChar::DirAN: + appendRun( bidi ); + bidi.status.eor = QChar::DirEN; + dir = QChar::DirEN; break; + case QChar::DirES: + case QChar::DirCS: + if(bidi.status.eor == QChar::DirEN) { + bidi.eor = bidi.current; break; + } + case QChar::DirBN: + case QChar::DirB: + case QChar::DirS: + case QChar::DirWS: + case QChar::DirON: + if(bidi.status.eor == QChar::DirR) { + // neutrals go to R + bidi.eor = bidi.last; + appendRun( bidi ); + dir = QChar::DirEN; + bidi.status.eor = QChar::DirEN; + } + else if( bidi.status.eor == QChar::DirL || + (bidi.status.eor == QChar::DirEN && bidi.status.lastStrong == QChar::DirL)) { + bidi.eor = bidi.current; bidi.status.eor = dirCurrent; + } else { + // numbers on both sides, neutrals get right to left direction + if(dir != QChar::DirL) { + appendRun( bidi ); + bidi.eor = bidi.last; + dir = QChar::DirR; + appendRun( bidi ); + dir = QChar::DirEN; + bidi.status.eor = QChar::DirEN; + } else { + bidi.eor = bidi.current; bidi.status.eor = dirCurrent; + } + } + default: + break; + } + break; + } + case QChar::DirAN: + dirCurrent = QChar::DirAN; + if(dir == QChar::DirON) dir = QChar::DirAN; + switch(bidi.status.last) + { + case QChar::DirL: + case QChar::DirAN: + bidi.eor = bidi.current; bidi.status.eor = QChar::DirAN; break; + case QChar::DirR: + case QChar::DirAL: + case QChar::DirEN: + appendRun( bidi ); + dir = QChar::DirAN; bidi.status.eor = QChar::DirAN; + break; + case QChar::DirCS: + if(bidi.status.eor == QChar::DirAN) { + bidi.eor = bidi.current; break; + } + case QChar::DirES: + case QChar::DirET: + case QChar::DirBN: + case QChar::DirB: + case QChar::DirS: + case QChar::DirWS: + case QChar::DirON: + if(bidi.status.eor == QChar::DirR) { + // neutrals go to R + bidi.eor = bidi.last; + appendRun( bidi ); + dir = QChar::DirAN; + bidi.status.eor = QChar::DirAN; + } else if( bidi.status.eor == QChar::DirL || + (bidi.status.eor == QChar::DirEN && bidi.status.lastStrong == QChar::DirL)) { + bidi.eor = bidi.current; bidi.status.eor = dirCurrent; + } else { + // numbers on both sides, neutrals get right to left direction + if(dir != QChar::DirL) { + appendRun( bidi ); + bidi.eor = bidi.last; + dir = QChar::DirR; + appendRun( bidi ); + dir = QChar::DirAN; + bidi.status.eor = QChar::DirAN; + } else { + bidi.eor = bidi.current; bidi.status.eor = dirCurrent; + } + } + default: + break; + } + break; + case QChar::DirES: + case QChar::DirCS: + break; + case QChar::DirET: + if(bidi.status.last == QChar::DirEN) { + dirCurrent = QChar::DirEN; + bidi.eor = bidi.current; bidi.status.eor = dirCurrent; + break; + } + break; + + // boundary neutrals should be ignored + case QChar::DirBN: + break; + // neutrals + case QChar::DirB: + // ### what do we do with newline and paragraph seperators that come to here? + break; + case QChar::DirS: + // ### implement rule L1 + break; + case QChar::DirWS: + break; + case QChar::DirON: + break; + default: + break; + } + + //cout << " after: dir=" << // dir << " current=" << dirCurrent << " last=" << status.last << " eor=" << status.eor << " lastStrong=" << status.lastStrong << " embedding=" << context->dir << endl; + + if(bidi.current.atEnd()) break; + + // set status.last as needed. + switch(dirCurrent) + { + case QChar::DirET: + case QChar::DirES: + case QChar::DirCS: + case QChar::DirS: + case QChar::DirWS: + case QChar::DirON: + switch(bidi.status.last) + { + case QChar::DirL: + case QChar::DirR: + case QChar::DirAL: + case QChar::DirEN: + case QChar::DirAN: + bidi.status.last = dirCurrent; + break; + default: + bidi.status.last = QChar::DirON; + } + break; + case QChar::DirNSM: + case QChar::DirBN: + // ignore these + break; + case QChar::DirEN: + if ( bidi.status.last == QChar::DirL ) { + break; + } + // fall through + default: + bidi.status.last = dirCurrent; + } +#endif + + if ( atEnd ) break; + bidi.last = bidi.current; + + if ( emptyRun ) { + bidi.sor = bidi.current; + bidi.eor = bidi.current; + emptyRun = false; + } + + // this causes the operator ++ to open and close embedding levels as needed + // for the CSS unicode-bidi property + adjustEmbedding = true; + bidi.current.increment( bidi ); + adjustEmbedding = false; + + if ( bidi.current == end ) { + if ( emptyRun ) + break; + atEnd = true; + } + } + +#if BIDI_DEBUG > 0 + kdDebug(6041) << "reached end of line current=" << current.obj << "/" << current.pos + << ", eor=" << eor.obj << "/" << eor.pos << endl; +#endif + if ( !emptyRun && bidi.sor != bidi.current ) { + bidi.eor = bidi.last; + appendRun( bidi ); + } + + // reorder line according to run structure... + + // first find highest and lowest levels + uchar levelLow = 128; + uchar levelHigh = 0; + BidiRun *r = sFirstBidiRun; + while ( r ) { + if ( r->level > levelHigh ) + levelHigh = r->level; + if ( r->level < levelLow ) + levelLow = r->level; + r = r->nextRun; + } + + // implements reordering of the line (L2 according to Bidi spec): + // L2. From the highest level found in the text to the lowest odd level on each line, + // reverse any contiguous sequence of characters that are at that level or higher. + + // reversing is only done up to the lowest odd level + if( !(levelLow%2) ) levelLow++; + +#if BIDI_DEBUG > 0 + kdDebug(6041) << "lineLow = " << (uint)levelLow << ", lineHigh = " << (uint)levelHigh << endl; + kdDebug(6041) << "logical order is:" << endl; + QPtrListIterator<BidiRun> it2(runs); + BidiRun *r2; + for ( ; (r2 = it2.current()); ++it2 ) + kdDebug(6041) << " " << r2 << " start=" << r2->start << " stop=" << r2->stop << " level=" << (uint)r2->level << endl; +#endif + + int count = sBidiRunCount - 1; + + // do not reverse for visually ordered web sites + if(!style()->visuallyOrdered()) { + while(levelHigh >= levelLow) { + int i = 0; + BidiRun* currRun = sFirstBidiRun; + while ( i < count ) { + while(i < count && currRun && currRun->level < levelHigh) { + i++; + currRun = currRun->nextRun; + } + int start = i; + while(i <= count && currRun && currRun->level >= levelHigh) { + i++; + currRun = currRun->nextRun; + } + int end = i-1; + reverseRuns(start, end); + } + levelHigh--; + } + } + +#if BIDI_DEBUG > 0 + kdDebug(6041) << "visual order is:" << endl; + for (BidiRun* curr = sFirstRun; curr; curr = curr->nextRun) + kdDebug(6041) << " " << curr << endl; +#endif +} + +#ifdef APPLE_CHANGES // KDE handles compact blocks differently +static void buildCompactRuns(RenderObject* compactObj, BidiState &bidi) +{ + sBuildingCompactRuns = true; + if (!compactObj->isRenderBlock()) { + // Just append a run for our object. + isLineEmpty = false; + addRun(new (compactObj->renderArena()) BidiRun(0, compactObj->length(), compactObj, bidi.context, dir)); + } + else { + // Format the compact like it is its own single line. We build up all the runs for + // the little compact and then reorder them for bidi. + RenderBlock* compactBlock = static_cast<RenderBlock*>(compactObj); + adjustEmbedding = true; + BidiIterator start(compactBlock, first(compactBlock, bidi), 0); + adjustEmbedding = false; + BidiIterator end = start; + + betweenMidpoints = false; + isLineEmpty = true; + previousLineBrokeAtBR = true; + + end = compactBlock->findNextLineBreak(start, bidi); + if (!isLineEmpty) + compactBlock->bidiReorderLine(start, end, bidi); + } + + + sCompactFirstBidiRun = sFirstBidiRun; + sCompactLastBidiRun = sLastBidiRun; + sCompactBidiRunCount = sBidiRunCount; + + sNumMidpoints = 0; + sCurrMidpoint = 0; + betweenMidpoints = false; + sBuildingCompactRuns = false; +} +#endif + +void RenderBlock::layoutInlineChildren(bool relayoutChildren, int breakBeforeLine) +{ + BidiState bidi; + + m_overflowHeight = 0; + + invalidateVerticalPositions(); +#ifdef DEBUG_LAYOUT + QTime qt; + qt.start(); + kdDebug( 6040 ) << renderName() << " layoutInlineChildren( " << this <<" )" << endl; +#endif +#if BIDI_DEBUG > 1 || defined( DEBUG_LINEBREAKS ) + kdDebug(6041) << " ------- bidi start " << this << " -------" << endl; +#endif + + m_height = borderTop() + paddingTop(); + int toAdd = borderBottom() + paddingBottom(); + if (m_layer && scrollsOverflowX() && style()->height().isVariable()) + toAdd += m_layer->horizontalScrollbarHeight(); + + // Clear out our line boxes. + deleteInlineBoxes(); + + // Text truncation only kicks in if your overflow isn't visible and your + // text-overflow-mode isn't clip. + bool hasTextOverflow = style()->textOverflow() && hasOverflowClip(); + + // Walk all the lines and delete our ellipsis line boxes if they exist. + if (hasTextOverflow) + deleteEllipsisLineBoxes(); + + if (firstChild()) { + // layout replaced elements + RenderObject *o = first( this, bidi, false ); + while ( o ) { + if (o->markedForRepaint()) { + o->repaintDuringLayout(); + o->setMarkedForRepaint(false); + } + if (o->isReplaced() || o->isFloating() || o->isPositioned()) { + // clear the placeHolderBox + if (o->isBox()) + static_cast<RenderBox*>(o)->RenderBox::deleteInlineBoxes(); + + //kdDebug(6041) << "layouting replaced or floating child" << endl; + if (relayoutChildren || o->style()->width().isPercent() || o->style()->height().isPercent()) + o->setChildNeedsLayout(true, false); + if (o->isPositioned()) + o->containingBlock()->insertPositionedObject(o); + else + o->layoutIfNeeded(); + } + else { + o->deleteInlineBoxes(); + o->setNeedsLayout(false); + } + o = Bidinext( this, o, bidi, false ); + } + + BidiContext *startEmbed; + if( style()->direction() == LTR ) { + startEmbed = new BidiContext( 0, QChar::DirL ); + bidi.status.eor = QChar::DirL; + } else { + startEmbed = new BidiContext( 1, QChar::DirR ); + bidi.status.eor = QChar::DirR; + } + startEmbed->ref(); + + bidi.status.lastStrong = QChar::DirON; + bidi.status.last = QChar::DirON; + + bidi.context = startEmbed; + adjustEmbedding = true; + BidiIterator start(this, first(this, bidi), 0); + adjustEmbedding = false; + BidiIterator end = start; + + m_firstLine = true; + + if (!smidpoints) + smidpoints = new QMemArray<BidiIterator>; + + sNumMidpoints = 0; + sCurrMidpoint = 0; + sCompactFirstBidiRun = sCompactLastBidiRun = 0; + sCompactBidiRunCount = 0; + + previousLineBrokeAtBR = true; + + int lineCount = 0; + bool pagebreakHint = false; + int oldPos = 0; + BidiIterator oldStart; + BidiState oldBidi; + const bool pagedMode = canvas()->pagedMode(); +// + while( !end.atEnd() ) { + start = end; + lineCount++; + betweenMidpoints = false; + isLineEmpty = true; + pagebreakHint = false; + if (pagedMode) { + oldPos = m_height; + oldStart = start; + oldBidi = bidi; + } +#ifdef APPLE_CHANGES // KDE handles compact blocks differently + if (m_firstLine && firstChild() && firstChild()->isCompact()) { + buildCompactRuns(firstChild(), bidi); + start.obj = firstChild()->nextSibling(); + end = start; + } +#endif + if (lineCount == breakBeforeLine) { + m_height = pageTopAfter(oldPos); + pagebreakHint = true; + } +redo_linebreak: + end = findNextLineBreak(start, bidi); + if( start.atEnd() ) break; + if (!isLineEmpty) { + bidiReorderLine(start, end, bidi); + + // Now that the runs have been ordered, we create the line boxes. + // At the same time we figure out where border/padding/margin should be applied for + // inline flow boxes. + +#ifdef APPLE_CHANGES // KDE handles compact blocks differently + if (sCompactFirstBidiRun) { + // We have a compact line sharing this line. Link the compact runs + // to our runs to create a single line of runs. + sCompactLastBidiRun->nextRun = sFirstBidiRun; + sFirstBidiRun = sCompactFirstBidiRun; + sBidiRunCount += sCompactBidiRunCount; + } +#endif + if (sBidiRunCount) { + InlineFlowBox* lineBox = constructLine(start, end); + if (lineBox) { + if (pagebreakHint) lineBox->setAfterPageBreak(true); + + // Now we position all of our text runs horizontally. + computeHorizontalPositionsForLine(lineBox, bidi); + + // Now position our text runs vertically. + computeVerticalPositionsForLine(lineBox); + + deleteBidiRuns(renderArena()); + + if (lineBox->afterPageBreak() && hasFloats() && !pagebreakHint) { + start = end = oldStart; + bidi = oldBidi; + m_height = pageTopAfter(oldPos); + deleteLastLineBox(renderArena()); + pagebreakHint = true; + goto redo_linebreak; + } + } + } + + if( end == start || (end.obj && end.obj->isBR() && !start.obj->isBR() ) ) { + adjustEmbedding = true; + end.increment(bidi); + adjustEmbedding = false; + } else if (end.obj && end.obj->style()->preserveLF() && end.current() == QChar('\n')) { + adjustEmbedding = true; + end.increment(bidi); + adjustEmbedding = false; + } + + m_firstLine = false; + newLine(); + } + + sNumMidpoints = 0; + sCurrMidpoint = 0; + sCompactFirstBidiRun = sCompactLastBidiRun = 0; + sCompactBidiRunCount = 0; + } + startEmbed->deref(); + //embed->deref(); + } + + sNumMidpoints = 0; + sCurrMidpoint = 0; + + // If we violate widows page-breaking rules, we set a hint and relayout. + // Note that the widows rule might still be violated afterwards if the lines have become wider + if (canvas()->pagedMode() && containsPageBreak() && breakBeforeLine == 0) + { + int orphans = 0; + int widows = 0; + // find breaking line + InlineRunBox* lineBox = firstLineBox(); + while (lineBox) { + if (lineBox->isInlineFlowBox()) { + InlineFlowBox* flowBox = static_cast<InlineFlowBox*>(lineBox); + if (flowBox->afterPageBreak()) break; + } + orphans++; + lineBox = lineBox->nextLineBox(); + } + InlineFlowBox* pageBreaker = static_cast<InlineFlowBox*>(lineBox); + if (!pageBreaker) goto no_break; + // count widows + while (lineBox && widows < style()->widows()) { + if (lineBox->hasTextChildren()) + widows++; + lineBox = lineBox->nextLineBox(); + } + // Widows rule broken and more orphans left to use + if (widows < style()->widows() && orphans > 0) { + kdDebug( 6040 ) << "Widows: " << widows << endl; + // Check if we have enough orphans after respecting widows count + int newOrphans = orphans - (style()->widows() - widows); + if (newOrphans < style()->orphans()) { + if (parent()->canClear(this,PageBreakHarder)) { + // Relayout to remove incorrect page-break + setNeedsPageClear(true); + setContainsPageBreak(false); + layoutInlineChildren(relayoutChildren, -1); + return; + } + } else { + // Set hint and try again + layoutInlineChildren(relayoutChildren, newOrphans+1); + return; + } + } + } + no_break: + + // in case we have a float on the last line, it might not be positioned up to now. + // This has to be done before adding in the bottom border/padding, or the float will + // include the padding incorrectly. -dwh + positionNewFloats(); + + // Now add in the bottom border/padding. + m_height += toAdd; + + // Always make sure this is at least our height. + m_overflowHeight = kMax(m_height, m_overflowHeight); + + // See if any lines spill out of the block. If so, we need to update our overflow width. + checkLinesForOverflow(); + + // See if we have any lines that spill out of our block. If we do, then we will + // possibly need to truncate text. + if (hasTextOverflow) + checkLinesForTextOverflow(); + +#if BIDI_DEBUG > 1 + kdDebug(6041) << " ------- bidi end " << this << " -------" << endl; +#endif + //kdDebug() << "RenderBlock::layoutInlineChildren time used " << qt.elapsed() << endl; + //kdDebug(6040) << "height = " << m_height <<endl; +} + +static void setStaticPosition( RenderBlock* p, RenderObject *o, bool *needToSetStaticX = 0, bool *needToSetStaticY = 0 ) +{ + // If our original display wasn't an inline type, then we can + // determine our static x position now. + bool nssx, nssy; + bool isInlineType = o->style()->isOriginalDisplayInlineType(); + nssx = o->hasStaticX(); + if (nssx && !isInlineType && o->isBox()) { + static_cast<RenderBox*>(o)->setStaticX(o->parent()->style()->direction() == LTR ? + p->borderLeft()+p->paddingLeft() : + p->borderRight()+p->paddingRight()); + nssx = false; + } + + // If our original display was an INLINE type, then we can + // determine our static y position now. + nssy = o->hasStaticY(); + if (nssy && o->isBox()) { + static_cast<RenderBox*>(o)->setStaticY(p->height()); + nssy = !isInlineType; + } + if (needToSetStaticX) *needToSetStaticX = nssx; + if (needToSetStaticY) *needToSetStaticY = nssy; +} + +BidiIterator RenderBlock::findNextLineBreak(BidiIterator &start, BidiState &bidi) +{ + int width = lineWidth(m_height); + int w = 0; + int tmpW = 0; +#ifdef DEBUG_LINEBREAKS + kdDebug(6041) << "findNextLineBreak: line at " << m_height << " line width " << width << endl; + kdDebug(6041) << "sol: " << start.obj << " " << start.pos << endl; +#endif + + BidiIterator posStart = start; + bool hadPosStart = false; + + // eliminate spaces at beginning of line + // remove leading spaces. Any inline flows we encounter will be empty and should also + // be skipped. + while (!start.atEnd() && (start.obj->isInlineFlow() || (!start.obj->style()->preserveWS() && !start.obj->isBR() && +#ifndef QT_NO_UNICODETABLES + ( (start.current().unicode() == (ushort)0x0020) || // ASCII space + (start.current().unicode() == (ushort)0x0009) || // ASCII tab + (start.current().unicode() == (ushort)0x000A) || // ASCII line feed + (start.current().unicode() == (ushort)0x000C) || // ASCII form feed + (start.current().unicode() == (ushort)0x200B) || // Zero-width space + start.obj->isFloatingOrPositioned() ) +#else + ( start.current() == ' ' || start.current() == '\n' || start.obj->isFloatingOrPositioned() ) +#endif + ))) { + if( start.obj->isFloatingOrPositioned() ) { + RenderObject *o = start.obj; + // add to special objects... + if (o->isFloating()) { + insertFloatingObject(o); + positionNewFloats(); + width = lineWidth(m_height); + } + else if (o->isBox() && o->isPositioned()) { + if (!hadPosStart) { + hadPosStart = true; + posStart = start; + // end + addMidpoint(BidiIterator(0, o, 0)); + } else { + // start/end + addMidpoint(BidiIterator(0, o, 0)); + addMidpoint(BidiIterator(0, o, 0)); + } + setStaticPosition(this, o); + } + } + adjustEmbedding = true; + start.increment(bidi); + adjustEmbedding = false; + } + + if (hadPosStart && !start.atEnd()) + addMidpoint(start); + + if ( start.atEnd() ){ + if (hadPosStart) { + start = posStart; + posStart.increment(bidi); + return posStart; + } + return start; + } + + // This variable is used only if whitespace isn't set to PRE, and it tells us whether + // or not we are currently ignoring whitespace. + bool ignoringSpaces = false; + BidiIterator ignoreStart; + + // This variable tracks whether the very last character we saw was a space. We use + // this to detect when we encounter a second space so we know we have to terminate + // a run. + bool currentCharacterIsSpace = false; + RenderObject* trailingSpaceObject = 0; + + BidiIterator lBreak = start; + + RenderObject *o = start.obj; + RenderObject *last = o; + int pos = start.pos; + + bool prevLineBrokeCleanly = previousLineBrokeAtBR; + previousLineBrokeAtBR = false; + + while( o ) { +#ifdef DEBUG_LINEBREAKS + kdDebug(6041) << "new object "<< o <<" width = " << w <<" tmpw = " << tmpW << endl; +#endif + if(o->isBR()) { + if( w + tmpW <= width ) { + lBreak.obj = o; + lBreak.pos = 0; + + // A <br> always breaks a line, so don't let the line be collapsed + // away. Also, the space at the end of a line with a <br> does not + // get collapsed away. It only does this if the previous line broke + // cleanly. Otherwise the <br> has no effect on whether the line is + // empty or not. + if (prevLineBrokeCleanly) + isLineEmpty = false; + trailingSpaceObject = 0; + previousLineBrokeAtBR = true; + + if (!isLineEmpty) { + // only check the clear status for non-empty lines. + EClear clear = o->style()->clear(); + if(clear != CNONE) + m_clearStatus = (EClear) (m_clearStatus | clear); + } + } + goto end; + } + if( o->isFloatingOrPositioned() ) { + // add to special objects... + if(o->isFloating()) { + insertFloatingObject(o); + // check if it fits in the current line. + // If it does, position it now, otherwise, position + // it after moving to next line (in newLine() func) + if (o->width()+o->marginLeft()+o->marginRight()+w+tmpW <= width) { + positionNewFloats(); + width = lineWidth(m_height); + } + } + else if (o->isPositioned()) { + bool needToSetStaticX; + bool needToSetStaticY; + setStaticPosition(this, o, &needToSetStaticX, &needToSetStaticY); + + // If we're ignoring spaces, we have to stop and include this object and + // then start ignoring spaces again. + if (needToSetStaticX || needToSetStaticY) { + trailingSpaceObject = 0; + ignoreStart.obj = o; + ignoreStart.pos = 0; + if (ignoringSpaces) { + addMidpoint(ignoreStart); // Stop ignoring spaces. + addMidpoint(ignoreStart); // Start ignoring again. + } + } + } + } else if (o->isInlineFlow()) { + // Only empty inlines matter. We treat those similarly to replaced elements. + KHTMLAssert(!o->firstChild()); + tmpW += o->marginLeft()+o->borderLeft()+o->paddingLeft()+ + o->marginRight()+o->borderRight()+o->paddingRight(); + } else if ( o->isReplaced() || o->isGlyph() ) { + EWhiteSpace currWS = o->style()->whiteSpace(); + EWhiteSpace lastWS = last->style()->whiteSpace(); + + // WinIE marquees have different whitespace characteristics by default when viewed from + // the outside vs. the inside. Text inside is NOWRAP, and so we altered the marquee's + // style to reflect this, but we now have to get back to the original whitespace value + // for the marquee when checking for line breaking. + if (o->isHTMLMarquee() && o->layer() && o->layer()->marquee()) + currWS = o->layer()->marquee()->whiteSpace(); + if (last->isHTMLMarquee() && last->layer() && last->layer()->marquee()) + lastWS = last->layer()->marquee()->whiteSpace(); + + // Break on replaced elements if either has normal white-space. + if (currWS == NORMAL || lastWS == NORMAL) { + w += tmpW; + tmpW = 0; + lBreak.obj = o; + lBreak.pos = 0; + } + + tmpW += o->width()+o->marginLeft()+o->marginRight()+inlineWidth(o); + if (ignoringSpaces) { + BidiIterator startMid( 0, o, 0 ); + addMidpoint(startMid); + } + isLineEmpty = false; + ignoringSpaces = false; + currentCharacterIsSpace = false; + trailingSpaceObject = 0; + + if (o->isListMarker() && o->style()->listStylePosition() == OUTSIDE) { + // The marker must not have an effect on whitespace at the start + // of the line. We start ignoring spaces to make sure that any additional + // spaces we see will be discarded. + // + // Optimize for a common case. If we can't find whitespace after the list + // item, then this is all moot. -dwh + RenderObject* next = Bidinext( start.par, o, bidi ); + if (!style()->preserveWS() && next && next->isText() && static_cast<RenderText*>(next)->stringLength() > 0 && + (static_cast<RenderText*>(next)->text()[0].category() == QChar::Separator_Space || + static_cast<RenderText*>(next)->text()[0] == '\n')) { + currentCharacterIsSpace = true; + ignoringSpaces = true; + BidiIterator endMid( 0, o, 0 ); + addMidpoint(endMid); + } + } + } else if ( o->isText() ) { + RenderText *t = static_cast<RenderText *>(o); + int strlen = t->stringLength(); + int len = strlen - pos; + QChar *str = t->text(); + + const Font *f = t->htmlFont( m_firstLine ); + // proportional font, needs a bit more work. + int lastSpace = pos; + bool autoWrap = o->style()->autoWrap(); + bool preserveWS = o->style()->preserveWS(); + bool preserveLF = o->style()->preserveLF(); +#ifdef APPLE_CHANGES + int wordSpacing = o->style()->wordSpacing(); +#endif + bool appliedStartWidth = pos > 0; // If the span originated on a previous line, + // then assume the start width has been applied. + bool appliedEndWidth = false; + bool nextIsSoftBreakable = false; + + while(len) { + bool previousCharacterIsSpace = currentCharacterIsSpace; + bool isSoftBreakable = nextIsSoftBreakable; + nextIsSoftBreakable = false; + const QChar c = str[pos]; + currentCharacterIsSpace = c == ' '; + + if (preserveWS || !currentCharacterIsSpace) + isLineEmpty = false; + + // Check for soft hyphens. Go ahead and ignore them. + if (c.unicode() == SOFT_HYPHEN && pos > 0) { + nextIsSoftBreakable = true; + if (!ignoringSpaces) { + // Ignore soft hyphens + BidiIterator endMid(0, o, pos-1); + addMidpoint(endMid); + + // Add the width up to but not including the hyphen. + tmpW += t->width(lastSpace, pos - lastSpace, f); + + // For wrapping text only, include the hyphen. We need to ensure it will fit + // on the line if it shows when we break. + if (o->style()->autoWrap()) + tmpW += t->width(pos, 1, f); + + BidiIterator startMid(0, o, pos+1); + addMidpoint(startMid); + } + + pos++; + len--; + lastSpace = pos; // Cheesy hack to prevent adding in widths of the run twice. + continue; + } +#ifdef APPLE_CHANGES // KDE applies wordspacing differently + bool applyWordSpacing = false; +#endif + if (ignoringSpaces) { + // We need to stop ignoring spaces, if we encounter a non-space or + // a run that doesn't collapse spaces. + if (!currentCharacterIsSpace || preserveWS) { + // Stop ignoring spaces and begin at this + // new point. + ignoringSpaces = false; + lastSpace = pos; // e.g., "Foo goo", don't add in any of the ignored spaces. + BidiIterator startMid ( 0, o, pos ); + addMidpoint(startMid); + } + else { + // Just keep ignoring these spaces. + pos++; + len--; + continue; + } + } + + if ( (preserveLF && c == '\n') || (autoWrap && (isBreakable( str, pos, strlen ) || isSoftBreakable)) ) { + + tmpW += t->width(lastSpace, pos - lastSpace, f); + if (!appliedStartWidth) { + tmpW += inlineWidth(o, true, false); + appliedStartWidth = true; + } +#ifdef APPLE_CHANGES + applyWordSpacing = (wordSpacing && currentCharacterIsSpace && !previousCharacterIsSpace && + !t->containsOnlyWhitespace(pos+1, strlen-(pos+1))); +#endif +#ifdef DEBUG_LINEBREAKS + kdDebug(6041) << "found space at " << pos << " in string '" << QString( str, strlen ).latin1() << "' adding " << tmpW << " new width = " << w << endl; +#endif + if ( autoWrap && w + tmpW > width && w == 0 ) { + int fb = nearestFloatBottom(m_height); + int newLineWidth = lineWidth(fb); + // See if |tmpW| will fit on the new line. As long as it does not, + // keep adjusting our float bottom until we find some room. + int lastFloatBottom = m_height; + while (lastFloatBottom < fb && tmpW > newLineWidth) { + lastFloatBottom = fb; + fb = nearestFloatBottom(fb); + newLineWidth = lineWidth(fb); + } + + if(!w && m_height < fb && width < newLineWidth) { + m_height = fb; + width = newLineWidth; +#ifdef DEBUG_LINEBREAKS + kdDebug() << "RenderBlock::findNextLineBreak new position at " << m_height << " newWidth " << width << endl; +#endif + } + } + + if (autoWrap) { + if (w+tmpW > width) + goto end; + else if ( (pos > 1 && str[pos-1].unicode() == SOFT_HYPHEN) ) + // Subtract the width of the soft hyphen out since we fit on a line. + tmpW -= t->width(pos-1, 1, f); + } + + if( preserveLF && *(str+pos) == '\n' ) { + lBreak.obj = o; + lBreak.pos = pos; + +#ifdef DEBUG_LINEBREAKS + kdDebug(6041) << "forced break sol: " << start.obj << " " << start.pos << " end: " << lBreak.obj << " " << lBreak.pos << " width=" << w << endl; +#endif + return lBreak; + } + + if ( autoWrap ) { + w += tmpW; + tmpW = 0; + lBreak.obj = o; + lBreak.pos = pos; + } + + lastSpace = pos; +#ifdef APPLE_CHANGES + if (applyWordSpacing) + w += wordSpacing; +#endif + } + + if (!ignoringSpaces && !preserveWS) { + // If we encounter a second space, we need to go ahead and break up this run + // and enter a mode where we start collapsing spaces. + if (currentCharacterIsSpace && previousCharacterIsSpace) { + ignoringSpaces = true; + + // We just entered a mode where we are ignoring + // spaces. Create a midpoint to terminate the run + // before the second space. + addMidpoint(ignoreStart); + lastSpace = pos; + } + } + + if (currentCharacterIsSpace && !previousCharacterIsSpace) { + ignoreStart.obj = o; + ignoreStart.pos = pos; + } + + if (!preserveWS && currentCharacterIsSpace && !ignoringSpaces) + trailingSpaceObject = o; + else if (preserveWS || !currentCharacterIsSpace) + trailingSpaceObject = 0; + + pos++; + len--; + } + + // IMPORTANT: pos is > length here! + if (!ignoringSpaces) + tmpW += t->width(lastSpace, pos - lastSpace, f); + if (!appliedStartWidth) + tmpW += inlineWidth(o, true, false); + if (!appliedEndWidth) + tmpW += inlineWidth(o, false, true); + } else + KHTMLAssert( false ); + + RenderObject* next = Bidinext(start.par, o, bidi); + bool autoWrap = o->style()->autoWrap(); + bool checkForBreak = autoWrap; + if (w && w + tmpW > width && lBreak.obj && !o->style()->preserveLF() && !autoWrap) + checkForBreak = true; + else if (next && o->isText() && next->isText() && !next->isBR()) { + if (autoWrap || next->style()->autoWrap()) { + if (currentCharacterIsSpace) + checkForBreak = true; + else { + checkForBreak = false; + RenderText* nextText = static_cast<RenderText*>(next); + if (nextText->stringLength() != 0) { + QChar c = nextText->text()[0]; + if (c == ' ' || c == '\t' || (c == '\n' && !next->style()->preserveLF())) { + // If the next item on the line is text, and if we did not end with + // a space, then the next text run continues our word (and so it needs to + // keep adding to |tmpW|. Just update and continue. + checkForBreak = true; + } + } + + bool canPlaceOnLine = (w + tmpW <= width) || !autoWrap; + if (canPlaceOnLine && checkForBreak) { + w += tmpW; + tmpW = 0; + lBreak.obj = next; + lBreak.pos = 0; + } + } + } + } + + if (checkForBreak && (w + tmpW > width)) { + //kdDebug() << " too wide w=" << w << " tmpW = " << tmpW << " width = " << width << endl; + //kdDebug() << "start=" << start.obj << " current=" << o << endl; + // if we have floats, try to get below them. + if (currentCharacterIsSpace && !ignoringSpaces && !o->style()->preserveWS()) + trailingSpaceObject = 0; + + int fb = nearestFloatBottom(m_height); + int newLineWidth = lineWidth(fb); + // See if |tmpW| will fit on the new line. As long as it does not, + // keep adjusting our float bottom until we find some room. + int lastFloatBottom = m_height; + while (lastFloatBottom < fb && tmpW > newLineWidth) { + lastFloatBottom = fb; + fb = nearestFloatBottom(fb); + newLineWidth = lineWidth(fb); + } + if( !w && m_height < fb && width < newLineWidth ) { + m_height = fb; + width = newLineWidth; +#ifdef DEBUG_LINEBREAKS + kdDebug() << "RenderBlock::findNextLineBreak new position at " << m_height << " newWidth " << width << endl; +#endif + } + + // |width| may have been adjusted because we got shoved down past a float (thus + // giving us more room), so we need to retest, and only jump to + // the end label if we still don't fit on the line. -dwh + if (w + tmpW > width) + goto end; + } + + last = o; + o = next; + + if (!last->isFloatingOrPositioned() && last->isReplaced() && last->style()->autoWrap()) { + // Go ahead and add in tmpW. + w += tmpW; + tmpW = 0; + lBreak.obj = o; + lBreak.pos = 0; + } + + // Clear out our character space bool, since inline <pre>s don't collapse whitespace + // with adjacent inline normal/nowrap spans. + if (last->style()->preserveWS()) + currentCharacterIsSpace = false; + + pos = 0; + } + +#ifdef DEBUG_LINEBREAKS + kdDebug( 6041 ) << "end of par, width = " << width << " linewidth = " << w + tmpW << endl; +#endif + if( w + tmpW <= width || (last && !last->style()->autoWrap())) { + lBreak.obj = 0; + lBreak.pos = 0; + } + + end: + + if( lBreak == start && !lBreak.obj->isBR() ) { + // we just add as much as possible + if ( style()->whiteSpace() == PRE ) { + // FIXME: Don't really understand this case. + if(pos != 0) { + lBreak.obj = o; + lBreak.pos = pos - 1; + } else { + lBreak.obj = last; + lBreak.pos = last->isText() ? last->length() : 0; + } + } else if( lBreak.obj ) { + if( last != o ) { + // better to break between object boundaries than in the middle of a word + lBreak.obj = o; + lBreak.pos = 0; + } else { + // Don't ever break in the middle of a word if we can help it. + // There's no room at all. We just have to be on this line, + // even though we'll spill out. + lBreak.obj = o; + lBreak.pos = pos; + } + } + } + + if (hadPosStart) + start = posStart; + + // make sure we consume at least one char/object. + if( lBreak == start ) + lBreak.increment(bidi); + +#ifdef DEBUG_LINEBREAKS + kdDebug(6041) << "regular break sol: " << start.obj << " " << start.pos << " end: " << lBreak.obj << " " << lBreak.pos << " width=" << w << endl; +#endif + + // Sanity check our midpoints. + checkMidpoints(lBreak, bidi); + + if (trailingSpaceObject) { + // This object is either going to be part of the last midpoint, or it is going + // to be the actual endpoint. In both cases we just decrease our pos by 1 level to + // exclude the space, allowing it to - in effect - collapse into the newline. + if (sNumMidpoints%2==1) { + BidiIterator* midpoints = smidpoints->data(); + midpoints[sNumMidpoints-1].pos--; + } + //else if (lBreak.pos > 0) + // lBreak.pos--; + else if (lBreak.obj == 0 && trailingSpaceObject->isText()) { + // Add a new end midpoint that stops right at the very end. + RenderText* text = static_cast<RenderText *>(trailingSpaceObject); + unsigned pos = text->length() >=2 ? text->length() - 2 : UINT_MAX; + BidiIterator endMid ( 0, trailingSpaceObject, pos ); + addMidpoint(endMid); + } + } + + // We might have made lBreak an iterator that points past the end + // of the object. Do this adjustment to make it point to the start + // of the next object instead to avoid confusing the rest of the + // code. + if (lBreak.pos > 0) { + lBreak.pos--; + lBreak.increment(bidi); + } + + if (lBreak.obj && lBreak.pos >= 2 && lBreak.obj->isText()) { + // For soft hyphens on line breaks, we have to chop out the midpoints that made us + // ignore the hyphen so that it will render at the end of the line. + QChar c = static_cast<RenderText*>(lBreak.obj)->text()[lBreak.pos-1]; + if (c.unicode() == SOFT_HYPHEN) + chopMidpointsAt(lBreak.obj, lBreak.pos-2); + } + + return lBreak; +} + +void RenderBlock::checkLinesForOverflow() +{ + for (RootInlineBox* curr = static_cast<khtml::RootInlineBox*>(firstLineBox()); curr; curr = static_cast<khtml::RootInlineBox*>(curr->nextLineBox())) { +// m_overflowLeft = min(curr->leftOverflow(), m_overflowLeft); + m_overflowTop = kMin(curr->topOverflow(), m_overflowTop); +// m_overflowWidth = max(curr->rightOverflow(), m_overflowWidth); + m_overflowHeight = kMax(curr->bottomOverflow(), m_overflowHeight); + } +} + +void RenderBlock::deleteEllipsisLineBoxes() +{ + for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox()) + curr->clearTruncation(); +} + +void RenderBlock::checkLinesForTextOverflow() +{ + // Determine the width of the ellipsis using the current font. + QChar ellipsis = 0x2026; // FIXME: CSS3 says this is configurable, also need to use 0x002E (FULL STOP) if 0x2026 not renderable + static QString ellipsisStr(ellipsis); + const Font& firstLineFont = style(true)->htmlFont(); + const Font& font = style()->htmlFont(); + int firstLineEllipsisWidth = firstLineFont.width(&ellipsis, 1, 0); + int ellipsisWidth = (font == firstLineFont) ? firstLineEllipsisWidth : font.width(&ellipsis, 1, 0); + + // For LTR text truncation, we want to get the right edge of our padding box, and then we want to see + // if the right edge of a line box exceeds that. For RTL, we use the left edge of the padding box and + // check the left edge of the line box to see if it is less + // Include the scrollbar for overflow blocks, which means we want to use "contentWidth()" + bool ltr = style()->direction() == LTR; + for (RootInlineBox* curr = firstRootBox(); curr; curr = curr->nextRootBox()) { + int blockEdge = ltr ? rightOffset(curr->yPos()) : leftOffset(curr->yPos()); + int lineBoxEdge = ltr ? curr->xPos() + curr->width() : curr->xPos(); + if ((ltr && lineBoxEdge > blockEdge) || (!ltr && lineBoxEdge < blockEdge)) { + // This line spills out of our box in the appropriate direction. Now we need to see if the line + // can be truncated. In order for truncation to be possible, the line must have sufficient space to + // accommodate our truncation string, and no replaced elements (images, tables) can overlap the ellipsis + // space. + int width = curr == firstRootBox() ? firstLineEllipsisWidth : ellipsisWidth; + if (curr->canAccommodateEllipsis(ltr, blockEdge, lineBoxEdge, width)) + curr->placeEllipsis(ellipsisStr, ltr, blockEdge, width); + } + } +} + +// For --enable-final +#undef BIDI_DEBUG +#undef DEBUG_LINEBREAKS +#undef DEBUG_LAYOUT + +} diff --git a/khtml/rendering/bidi.h b/khtml/rendering/bidi.h new file mode 100644 index 000000000..4d6b6aaf1 --- /dev/null +++ b/khtml/rendering/bidi.h @@ -0,0 +1,106 @@ +/* + * This file is part of the html renderer for KDE. + * + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * Copyright (C) 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. + * + */ +#ifndef BIDI_H +#define BIDI_H + +#include <qstring.h> + +namespace khtml { + class RenderArena; + class RenderBlock; + class RenderObject; + class InlineBox; + + class BidiContext { + public: + BidiContext(unsigned char level, QChar::Direction embedding, BidiContext *parent = 0, bool override = false); + ~BidiContext(); + + void ref() const; + void deref() const; + + unsigned char level; + bool override : 1; + QChar::Direction dir : 5; + QChar::Direction basicDir : 5; + + BidiContext *parent; + + + // refcounting.... + mutable int count; + }; + + struct BidiRun { + BidiRun(int _start, int _stop, RenderObject *_obj, BidiContext *context, QChar::Direction dir) + : start( _start ), stop( _stop ), obj( _obj ), box(0), nextRun(0) + { + if(dir == QChar::DirON) dir = context->dir; + + level = context->level; + + // add level of run (cases I1 & I2) + if( level % 2 ) { + if(dir == QChar::DirL || dir == QChar::DirAN || dir == QChar::DirEN) + level++; + } else { + if( dir == QChar::DirR ) + level++; + else if( dir == QChar::DirAN || dir == QChar::DirEN) + level += 2; + } + } + + void detach(RenderArena* renderArena); + + // Overloaded new operator. + void* operator new(size_t sz, RenderArena* renderArena) throw(); + + // Overridden to prevent the normal delete from being called. + void operator delete(void* ptr, size_t sz); + +private: + // The normal operator new is disallowed. + void* operator new(size_t sz) throw(); + +public: + int start; + int stop; + + RenderObject *obj; + InlineBox* box; + + // explicit + implicit levels here + uchar level; + + bool compact : 1; + + BidiRun* nextRun; + }; + + struct BidiIterator; + struct BidiState; + +} + +#endif diff --git a/khtml/rendering/break_lines.cpp b/khtml/rendering/break_lines.cpp new file mode 100644 index 000000000..483455127 --- /dev/null +++ b/khtml/rendering/break_lines.cpp @@ -0,0 +1,126 @@ +#include <break_lines.h> +#include <klibloader.h> +#include "qcstring.h" +#include <qtextcodec.h> +#include <qcleanuphandler.h> +#include <config.h> + + +/* If HAVE_LIBTHAI is defined, libkhtml will link against + * libthai since compile time. Otherwise it will try to + * dlopen at run-time + * + * Ott Pattara Nov 14, 2004 + */ + +#ifndef HAVE_LIBTHAI +typedef int (*th_brk_def)(const unsigned char*, int[], int); +static th_brk_def th_brk; +#else +#include <thai/thailib.h> +#include <thai/thbrk.h> +#endif + +namespace khtml { + struct ThaiCache + { + ThaiCache() { + string = 0; + allocated = 0x400; + wbrpos = (int *) malloc(allocated*sizeof(int)); + numwbrpos = 0; + numisbreakable = 0x400; + isbreakable = (int *) malloc(numisbreakable*sizeof(int)); + library = 0; + } + ~ThaiCache() { + free(wbrpos); + free(isbreakable); + if (library) library->unload(); + } + const QChar *string; + int *wbrpos; + int *isbreakable; + int allocated; + int numwbrpos,numisbreakable; + KLibrary *library; + }; + static ThaiCache *cache = 0; + + void cleanup_thaibreaks() + { + delete cache; + cache = 0; +#ifndef HAVE_LIBTHAI + th_brk = 0; +#endif + } + + bool isBreakableThai( const QChar *string, const int pos, const int len) + { + static QTextCodec *thaiCodec = QTextCodec::codecForMib(2259); + //printf("Entering isBreakableThai with pos = %d\n", pos); + +#ifndef HAVE_LIBTHAI + + KLibrary *lib = 0; + + /* load libthai dynamically */ + if (( !th_brk ) && thaiCodec ) { + printf("Try to load libthai dynamically...\n"); + KLibLoader *loader = KLibLoader::self(); + lib = loader->library("libthai"); + if (lib && lib->hasSymbol("th_brk")) { + th_brk = (th_brk_def) lib->symbol("th_brk"); + } else { + // indication that loading failed and we shouldn't try to load again + printf("Error, can't load libthai...\n"); + thaiCodec = 0; + if (lib) + lib->unload(); + } + } + + if (!th_brk ) { + return true; + } +#endif + + if (!cache ) { + cache = new ThaiCache; +#ifndef HAVE_LIBTHAI + cache->library = lib; +#endif + } + + // build up string of thai chars + if ( string != cache->string ) { + //fprintf(stderr,"new string found (not in cache), calling libthai\n"); + QCString cstr = thaiCodec->fromUnicode( QConstString(string,len).string()); + //printf("About to call libthai::th_brk with str: %s",cstr.data()); + + cache->numwbrpos = th_brk((const unsigned char*) cstr.data(), cache->wbrpos, cache->allocated); + //fprintf(stderr,"libthai returns with value %d\n",cache->numwbrpos); + if (cache->numwbrpos > cache->allocated) { + cache->allocated = cache->numwbrpos; + cache->wbrpos = (int *)realloc(cache->wbrpos, cache->allocated*sizeof(int)); + cache->numwbrpos = th_brk((const unsigned char*) cstr.data(), cache->wbrpos, cache->allocated); + } + if ( len > cache->numisbreakable ) { + cache->numisbreakable=len; + cache->isbreakable = (int *)realloc(cache->isbreakable, cache->numisbreakable*sizeof(int)); + } + for (int i = 0 ; i < len ; ++i) { + cache->isbreakable[i] = 0; + } + if ( cache->numwbrpos > 0 ) { + for (int i = cache->numwbrpos-1; i >= 0; --i) { + cache->isbreakable[cache->wbrpos[i]] = 1; + } + } + cache->string = string; + } + //printf("Returning %d\n", cache->isbreakable[pos]); + return cache->isbreakable[pos]; + } +} diff --git a/khtml/rendering/break_lines.h b/khtml/rendering/break_lines.h new file mode 100644 index 000000000..e33fce247 --- /dev/null +++ b/khtml/rendering/break_lines.h @@ -0,0 +1,163 @@ +#ifndef BREAK_LINES_H +#define BREAK_LINES_H + +#include <qstring.h> + +namespace khtml { + + /* + array of unicode codes where breaking shouldn't occur. + (in sorted order because of using with binary search) + these are currently for Japanese, though simply adding + Korean, Chinese ones should work as well + */ + /* + dontbreakbefore[] contains characters not covered by QChar::Punctuation_Close that shouldn't be broken before. + chars included in QChar::Punctuation_Close are listed below.(look at UAX #14) + - 3001 ideographic comma + - 3002 ideographic full stop + - FE50 small comma + - FF52 small full stop + - FF0C fullwidth comma + - FF0E fullwidth full stop + - FF61 halfwidth ideographic full stop + - FF64 halfwidth ideographic comma + these character is commented out. + */ + const ushort dontbreakbefore[] = { + //0x3001, //ideographic comma + //0x3002, //ideographic full stop + 0x3005, //ideographic iteration mark + 0x3009, //right angle bracket + 0x300b, //right double angle bracket + 0x300d, //right corner bracket + 0x300f, //right white corner bracket + 0x3011, //right black lenticular bracket + 0x3015, //right tortoise shell bracket + 0x3041, //small a hiragana + 0x3043, //small i hiragana + 0x3045, //small u hiragana + 0x3047, //small e hiragana + 0x3049, //small o hiragana + 0x3063, //small tsu hiragana + 0x3083, //small ya hiragana + 0x3085, //small yu hiragana + 0x3087, //small yo hiragana + 0x308E, //small wa hiragana + 0x309B, //jap voiced sound mark + 0x309C, //jap semi-voiced sound mark + 0x309D, //jap iteration mark hiragana + 0x309E, //jap voiced iteration mark hiragana + 0x30A1, //small a katakana + 0x30A3, //small i katakana + 0x30A5, //small u katakana + 0x30A7, //small e katakana + 0x30A9, //small o katakana + 0x30C3, //small tsu katakana + 0x30E3, //small ya katakana + 0x30E5, //small yu katakana + 0x30E7, //small yo katakana + 0x30EE, //small wa katakana + 0x30F5, //small ka katakana + 0x30F6, //small ke katakana + 0x30FC, //jap prolonged sound mark + 0x30FD, //jap iteration mark katakana + 0x30FE, //jap voiced iteration mark katakana + //0xFE50, //small comma + //0xFF52, //small full stop + 0xFF01, //fullwidth exclamation mark + 0xFF09, //fullwidth right parenthesis + //0xFF0C, //fullwidth comma + 0xFF0D, //fullwidth hypen-minus + //0xFF0E, //fullwidth full stop + 0xFF1F, //fullwidth question mark + 0xFF3D, //fullwidth right square bracket + 0xFF5D, //fullwidth right curly bracket + //0xFF61, //halfwidth ideographic full stop + 0xFF63, //halfwidth right corner bracket + //0xFF64, //halfwidth ideographic comma + 0xFF67, //halfwidth katakana letter small a + 0xFF68, //halfwidth katakana letter small i + 0xFF69, //halfwidth katakana letter small u + 0xFF6a, //halfwidth katakana letter small e + 0xFF6b, //halfwidth katakana letter small o + 0xFF6c, //halfwidth katakana letter small ya + 0xFF6d, //halfwidth katakana letter small yu + 0xFF6e, //halfwidth katakana letter small yo + 0xFF6f, //halfwidth katakana letter small tu + 0xFF70 //halfwidth katakana-hiragana prolonged sound mark + }; + + // characters that aren't covered by QChar::Punctuation_Open + const ushort dontbreakafter[] = { + 0x3012, //postal mark + 0xFF03, //full width pound mark + 0xFF04, //full width dollar sign + 0xFF20, //full width @ + 0xFFE1, //full width british pound sign + 0xFFE5 //full width yen sign + }; + + inline bool break_bsearch( const ushort* arr, const ushort val ) { + int left = 0; + int right = (sizeof(arr) / sizeof(ushort)) - 1; + + while (1) { + if (left == right) + return val != arr[left]; + + int i = (left + right) >> 1; + if ( val == arr[i] ) + return false; + if ( val < arr[i] ) + right = i; + else + left = i + 1; + } + } + + bool isBreakableThai( const QChar *string, const int pos, const int len); + void cleanup_thaibreaks(); + + inline bool isBreakable( const QChar *str, const int pos, int len ) + { + const QChar *c = str+pos; + unsigned short ch = c->unicode(); + if ( ch > 0xff ) { + // not latin1, need to do more sophisticated checks for asian fonts + unsigned char row = c->row(); + if ( row == 0x0e ) { + // 0e00 - 0e7f == Thai + if ( c->cell() < 0x80 ) { + // consult libthai + return isBreakableThai(str, pos, len); + } else + return false; + } + if ( row > 0x2d && row < 0xfb || row == 0x11 ) { + /* asian line breaking. */ + if ( pos == 0 ) + return false; // never break before first character + + // check for simple punctuation cases + QChar::Category cat = c->category(); + if ( cat == QChar::Punctuation_Close || + cat == QChar::Punctuation_Other || + (str+(pos-1))->category() == QChar::Punctuation_Open ) + return false; + + // do binary search in dontbreak[] + return break_bsearch(dontbreakbefore, c->unicode()) && + break_bsearch(dontbreakafter, (str+(pos-1))->unicode()); + } else // no asian font + return c->isSpace(); + } else { + if ( ch == ' ' || ch == '\n' ) + return true; + } + return false; + } + +} + +#endif diff --git a/khtml/rendering/counter_tree.cpp b/khtml/rendering/counter_tree.cpp new file mode 100644 index 000000000..5b178d96e --- /dev/null +++ b/khtml/rendering/counter_tree.cpp @@ -0,0 +1,222 @@ +/* + * This file is part of the HTML rendering engine for KDE. + * + * Copyright (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include "rendering/counter_tree.h" +#include "rendering/render_object.h" + +namespace khtml { + +CounterNode::CounterNode(RenderObject *o) + : m_hasCounters(false), m_isVisual(false), + m_value(0), m_count(0), m_parent(0), m_previous(0), m_next(0), + m_renderer(o) {} + +CounterNode::~CounterNode() +{ + if (m_parent) m_parent->removeChild(this); +} + +void CounterNode::insertAfter ( CounterNode *, CounterNode *) +{ + Q_ASSERT( false); +} + +void CounterNode::removeChild ( CounterNode *) +{ + Q_ASSERT( false); +} + +void CounterNode::remove () +{ + if (m_parent) m_parent->removeChild(this); + else { + Q_ASSERT(isReset()); + Q_ASSERT(!firstChild()); + Q_ASSERT(!lastChild()); + } +} + +void CounterNode::setHasCounters () +{ + m_hasCounters = true; + if (parent()) + parent()->setHasCounters(); +} + +void CounterNode::recount (bool first) +{ + int old_count = m_count; + if (m_previous) + m_count = m_previous->count() + m_value; + else { + assert(m_parent->firstChild() == this); + m_count = m_parent->value() + m_value; + } + if (old_count != m_count && !first) + setSelfDirty(); + if (old_count != m_count || first) { + if (m_parent) m_parent->updateTotal(m_count); + if (m_next) m_next->recount(); + } +} + +void CounterNode::setSelfDirty () +{ + if (m_renderer && m_isVisual) + m_renderer->setNeedsLayoutAndMinMaxRecalc(); +} + +void CounterNode::setParentDirty () +{ + if (m_renderer && m_isVisual && m_hasCounters) + m_renderer->setNeedsLayoutAndMinMaxRecalc(); +} + +CounterReset::CounterReset(RenderObject *o) : CounterNode(o), m_total(0), m_first(0), m_last(0) {} +CounterReset::~CounterReset() {} + +void CounterReset::insertAfter ( CounterNode *newChild, CounterNode *refChild ) +{ + Q_ASSERT( newChild ); + Q_ASSERT( !refChild || refChild->parent() == this ); + + newChild->m_parent = this; + newChild->m_previous = refChild; + + if (refChild) { + newChild->m_next = refChild->m_next; + refChild->m_next = newChild; + } else { + newChild->m_next = m_first; + m_first = newChild; + } + + if (newChild->m_next) { + assert(newChild->m_next->m_previous == refChild); + newChild->m_next->m_previous = newChild; + } + else { + assert (m_last == refChild); + m_last = newChild; + } + + newChild->recount(true); +} + +void CounterReset::removeChild ( CounterNode *oldChild ) +{ + Q_ASSERT( oldChild ); + + CounterNode* next = oldChild->m_next; + CounterNode* prev = oldChild->m_previous; + + if (oldChild->firstChild()) { + CounterNode* first = oldChild->firstChild(); + CounterNode* last = oldChild->lastChild(); + if (prev) { + prev->m_next = first; + first->m_previous = prev; + } + else { + assert ( m_first == oldChild ); + m_first = first; + } + + if (next) { + next->m_previous = last; + last->m_next = next; + } + else { + assert ( m_last == oldChild ); + m_last = last; + } + + next = first; + while (next) { + next->m_parent = this; + if (next == last) break; + next = next->m_next; + } + + first->recount(true); + } + else { + if (prev) prev->m_next = next; + else { + assert ( m_first == oldChild ); + m_first = next; + } + if (next) next->m_previous = prev; + else { + assert ( m_last == oldChild ); + m_last = prev; + } + if (next) + next->recount(); + } + + + oldChild->m_next = 0; + oldChild->m_previous = 0; + oldChild->m_parent = 0; +} + +void CounterReset::recount (bool first) +{ + int old_count = m_count; + if (m_previous) + m_count = m_previous->count(); + else if (m_parent) + m_count = m_parent->value(); + else + m_count = 0; + + updateTotal(m_value); + if (!first) setSelfDirty(); + if (first || m_count != old_count) { + if (m_next) m_next->recount(); + } +} + +void CounterReset::setSelfDirty () +{ + setParentDirty(); +} + +void CounterReset::setParentDirty () +{ + if (hasCounters()) { + if (m_renderer && m_isVisual) m_renderer->setNeedsLayoutAndMinMaxRecalc(); + CounterNode* n = firstChild(); + for(; n; n = n->nextSibling()) + { + n->setParentDirty(); + } + } +} + +void CounterReset::updateTotal (int value) +{ + if (value > m_total) m_total = value; +} + +} // namespace diff --git a/khtml/rendering/counter_tree.h b/khtml/rendering/counter_tree.h new file mode 100644 index 000000000..55b924b80 --- /dev/null +++ b/khtml/rendering/counter_tree.h @@ -0,0 +1,114 @@ +/* + * This file is part of the HTML rendering engine for KDE. + * + * Copyright (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) + * + * 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. + * + */ +#ifndef _Counter_Tree_h_ +#define _Counter_Tree_h_ + +#include "misc/shared.h" +#include "rendering/render_object.h" + +namespace khtml { + +class CounterReset; + +// This file implements a counter-tree that is used for finding all parents in counters() lookup, +// and for propagating count-changes when nodes are added or removed. +// Please note that only counter-reset and root can be parents here, and that render-tree parents +// are just counter-tree siblings + +// Implementation of counter-increment and counter-content +class CounterNode +{ +public: + CounterNode(RenderObject *o); + virtual ~CounterNode(); + + CounterReset* parent() const { return m_parent; } + CounterNode* previousSibling() const { return m_previous; } + CounterNode* nextSibling() const { return m_next; } + virtual CounterNode* firstChild() const { return 0; } ; + virtual CounterNode* lastChild() const { return 0; }; + virtual void insertAfter ( CounterNode *newChild, CounterNode *refChild ); + virtual void removeChild ( CounterNode *oldChild ); + // Convenient self-refering version of the above + void remove(); + + int value() const { return m_value; }; + void setValue(short v) { m_value = v; }; + int count() const { return m_count; }; + + virtual bool isReset() { return false; }; + virtual void recount( bool first = false ); + virtual void setSelfDirty(); + virtual void setParentDirty(); + + bool hasCounters() const { return m_hasCounters; }; + bool isVisual() const { return m_isVisual; }; + void setHasCounters(); + void setIsVisual() { m_isVisual = true; }; + bool isRoot() { return m_renderer && m_renderer->isRoot(); }; + + void setRenderer(RenderObject *o) { m_renderer = o; }; + RenderObject* renderer() const { return m_renderer; }; + + friend class CounterReset; +protected: + bool m_hasCounters : 1; + bool m_isVisual : 1; + short m_value; + short m_count; + CounterReset *m_parent; + CounterNode *m_previous; + CounterNode *m_next; + RenderObject *m_renderer; +}; + +// Implementation of counter-reset and root +class CounterReset : public CounterNode +{ +public: + CounterReset(RenderObject *o); + virtual ~CounterReset(); + + virtual CounterNode *firstChild() const { return m_first; }; + virtual CounterNode *lastChild() const { return m_last; }; + virtual void insertAfter ( CounterNode *newChild, CounterNode *refChild ); + virtual void removeChild ( CounterNode *oldChild ); + + virtual bool isReset() { return true; }; + virtual void recount( bool first = false ); + virtual void setSelfDirty(); + virtual void setParentDirty(); + + void updateTotal(int value); + // The highest value among children + int total() const { return m_total; }; + +protected: + int m_total; + CounterNode *m_first; + CounterNode *m_last; +}; + +} // namespace + +#endif + diff --git a/khtml/rendering/enumerate.cpp b/khtml/rendering/enumerate.cpp new file mode 100644 index 000000000..75af71465 --- /dev/null +++ b/khtml/rendering/enumerate.cpp @@ -0,0 +1,411 @@ +/** + * This file is part of the HTML rendering engine for KDE. + * + * Copyright (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com) + * + * (C) Hebrew algorithm by herouth@netvision.net.il + * and schlpbch@iam.unibe.ch + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include "rendering/enumerate.h" + +#include <qstring.h> +#include <qvaluelist.h> + +namespace khtml { + +namespace Enumerate { + +QString toRoman( int number, bool upper ) +{ + if (number < 1 || number > 3999) return QString::number(number); + QString roman; + static const QChar ldigits[] = { 'i', 'v', 'x', 'l', 'c', 'd', 'm' }; + static const QChar udigits[] = { 'I', 'V', 'X', 'L', 'C', 'D', 'M' }; + const QChar *digits = upper ? udigits : ldigits; + int i, d = 0; + + do + { + int num = number % 10; + + if ( num % 5 < 4 ) + for ( i = num % 5; i > 0; i-- ) + roman.prepend( digits[ d ] ); + + if ( num >= 4 && num <= 8) + roman.prepend( digits[ d+1 ] ); + + if ( num == 9 ) + roman.prepend( digits[ d+2 ] ); + + if ( num % 5 == 4 ) + roman.prepend( digits[ d ] ); + + number /= 10; + d += 2; + } + while ( number ); + + return roman; +} + +QString toGeorgian( int number ) +{ + QString georgian; + const QChar tenthousand = 0x10ef; + static const QChar thousands[9] = {0x10e8, 0x10e9, 0x10ea, 0x10eb, 0x10ec, + 0x10ed, 0x10ee, 0x10f4, 0x10f5 }; + static const QChar hundreds[9] = {0x10e0, 0x10e1, 0x10e2, 0x10e3, 0x10f3, + 0x10e4, 0x10e5, 0x10e6, 0x10e7 }; + static const QChar tens[9] = {0x10d8, 0x10d9, 0x10da, 0x10db, 0x10dc, + 0x10f2, 0x10dd, 0x10de, 0x10df }; + static const QChar units[9] = {0x10d0, 0x10d1, 0x10d2, 0x10d3, 0x10d4, + 0x10d5, 0x10d6, 0x10f1, 0x10d7 }; + + if (number < 1 || number > 19999) return QString::number(number); + if (number >= 10000) { + georgian.append(tenthousand); + number = number - 10000; + } + if (number >= 1000) { + georgian.append(thousands[number/1000-1]); + number = number % 1000; + } + if (number >= 100) { + georgian.append(hundreds[number/100-1]); + number = number % 100; + } + if (number >= 10) { + georgian.append(tens[number/10-1]); + number = number % 10; + } + if (number >= 1) { + georgian.append(units[number-1]); + } + + return georgian; +} + +QString toArmenian( int number ) +{ + QString armenian; + int onethousand = 0x57c; + int hundreds = 0x572; + int tens = 0x569; + int units = 0x560; + + // The standard defines values over 1999, but 7000 is very hard to render + if (number < 1 || number > 1999) return QString::number(number); + if (number >= 1000) { + armenian.append(QChar(onethousand)); + number = number - 1000; + } + if (number >= 100) { + armenian.append(QChar(hundreds+number/100)); + number = number % 100; + } + if (number >= 10) { + armenian.append(QChar(tens+number/10)); + number = number % 10; + } + if (number >= 1) { + armenian.append(QChar(units+number)); + } + + return armenian; +} + +QString toHebrew( int number ) { + static const QChar tenDigit[] = {1497, 1499, 1500, 1502, 1504, 1505, 1506, 1508, 1510}; + + QString letter; + if (number < 1) return QString::number(number); + if (number>999) { + letter = toHebrew(number/1000) + QString::fromLatin1("'"); + number = number%1000; + } + + int hunderts = (number/400); + if (hunderts > 0) { + for(int i=0; i<hunderts; i++) { + letter += QChar(1511 + 3); + } + } + number = number % 400; + if ((number / 100) != 0) { + letter += QChar (1511 + (number / 100) -1); + } + number = number % 100; + int tens = number/10; + if (tens > 0 && !(number == 15 || number == 16)) { + letter += tenDigit[tens-1]; + } + if (number == 15 || number == 16) { // special because of religious + letter += QChar(1487 + 9); // reasons + letter += QChar(1487 + number - 9); + } else { + number = number % 10; + if (number != 0) { + letter += QChar (1487 + number); + } + } + return letter; +} + +static inline QString toLatin( int number, int base ) { + if (number < 1) return QString::number(number); + QValueList<QChar> letters; + while(number > 0) { + number--; // number 0 is letter a + QChar letter = (QChar) (base + (number % 26)); + letters.prepend(letter); + number /= 26; + } + QString str; + str.setLength(letters.size()); + int i=0; + while(!letters.isEmpty()) { + str[i++] = letters.front(); + letters.pop_front(); + } + return str; +} + +QString toLowerLatin( int number ) { + return toLatin( number, 'a' ); +} + +QString toUpperLatin( int number ) { + return toLatin( number, 'A' ); +} + +static inline QString toAlphabetic( int number, int base, const QChar alphabet[] ) { + if (number < 1) return QString::number(number); + QValueList<QChar> letters; + while(number > 0) { + number--; // number 0 is letter 1 + QChar letter = alphabet[number % base]; + letters.prepend(letter); + number /= base; + } + QString str; + str.setLength(letters.size()); + int i=0; + while(!letters.isEmpty()) { + str[i++] = letters.front(); + letters.pop_front(); + } + return str; +} + +QString toHiragana( int number ) { + static const QChar hiragana[48] = {0x3042, 0x3044, 0x3046, 0x3048, 0x304A, 0x304B, 0x304D, + 0x304F, 0x3051, 0x3053, 0x3055, 0x3057, 0x3059, 0x305B, 0x305D, + 0x305F, 0x3061, 0x3064, 0x3066, 0x3068, 0x306A, 0x306B, + 0x306C, 0x306D, 0x306E, 0x306F, 0x3072, 0x3075, 0x3078, + 0x307B, 0x307E, 0x307F, 0x3080, 0x3081, 0x3082, 0x3084, 0x3086, + 0x3088, 0x3089, 0x308A, 0x308B, 0x308C, 0x308D, 0x308F, + 0x3090, 0x3091, 0x9092, 0x3093}; + return toAlphabetic( number, 48, hiragana ); +} + +QString toHiraganaIroha( int number ) { + static const QChar hiragana[47] = {0x3044, 0x308D, 0x306F, 0x306B, 0x307B, 0x3078, 0x3068, + 0x3061, 0x308A, 0x306C, 0x308B, 0x3092, 0x308F, 0x304B, + 0x3088, 0x305F, 0x308C, 0x305D, 0x3064, 0x306D, 0x306A, + 0x3089, 0x3080, 0x3046, 0x3090, 0x306E, 0x304A, 0x304F, 0x3084, + 0x307E, 0x3051, 0x3075, 0x3053, 0x3048, 0x3066, 0x3042, 0x3055, + 0x304D, 0x3086, 0x3081, 0x307F, 0x3057, 0x3091, 0x3072, 0x3082, + 0x305B, 0x3059 }; + return toAlphabetic( number, 47, hiragana ); +} + +QString toKatakana( int number ) { + static const QChar katakana[48] = {0x30A2, 0x30A4, 0x30A6, 0x30A8, 0x30AA, 0x30AB, 0x30AD, + 0x30AF, 0x30B1, 0x30B3, 0x30B5, 0x30B7, 0x30B9, 0x30BB, + 0x30BD, 0x30BF, 0x30C1, 0x30C4, 0x30C6, 0x30C8, 0x30CA, + 0x30CB, 0x30CC, 0x30CD, 0x30CE, 0x30CF, 0x30D2, 0x30D5, + 0x30D8, 0x30DB, 0x30DE, 0x30DF, 0x30E0, 0x30E1, 0x30E2, + 0x30E4, 0x30E6, 0x30E8, 0x30E9, 0x30EA, 0x30EB, 0x30EC, + 0x30ED, 0x30EF, 0x30F0, 0x30F1, 0x90F2, 0x30F3}; + return toAlphabetic( number, 48, katakana ); +} + +QString toKatakanaIroha( int number ) { + static const QChar katakana[47] = {0x30A4, 0x30ED, 0x30CF, 0x30CB, 0x30DB, 0x30D8, 0x30C8, + 0x30C1, 0x30EA, 0x30CC, 0x30EB, 0x30F2, 0x30EF, 0x30AB, + 0x30E8, 0x30BF, 0x30EC, 0x30ED, 0x30C4, 0x30CD, 0x30CA, + 0x30E9, 0x30E0, 0x30A6, 0x30F0, 0x30CE, 0x30AA, 0x30AF, + 0x30E4, 0x30DE, 0x30B1, 0x30D5, 0x30B3, 0x30A8, 0x30C6, + 0x30A2, 0x30B5, 0x30AD, 0x30E6, 0x30E1, 0x30DF, 0x30B7, + 0x30F1, 0x30D2, 0x30E2, 0x30BB, 0x90B9}; + return toAlphabetic( number, 47, katakana ); +} + +QString toLowerGreek( int number ) { + static const QChar greek[24] = { 0x3B1, 0x3B2, 0x3B3, 0x3B4, 0x3B5, 0x3B6, 0x3B7, + 0x3B8, 0x3B9, 0x3BA, 0x3BB, 0x3BC, 0x3BD, 0x3BE, + 0x3BF, 0x3C0, 0x3C1, 0x3C3, 0x3C4, 0x3C5, 0x3C6, + 0x3C7, 0x3C8, 0x3C0}; + + return toAlphabetic( number, 24, greek ); +} + +QString toUpperGreek( int number ) { + // The standard claims to be base 24, but only lists 19 letters. + static const QChar greek[19] = { 0x391, 0x392, 0x393, 0x394, 0x395, 0x396, 0x397, 0x398, + 0x399, 0x39A, 0x39B, 0x39C, 0x39D, 0x39E, 0x39F, + 0x3A0, 0x3A1, 0x3A3, 0x3A9}; + + return toAlphabetic( number, 19, greek ); +} + +static inline QString toNumeric( int number, int base ) { + QString letter = QString::number(number); + for(unsigned int i = 0; i < letter.length(); i++) { + if (letter[i].isDigit()) + letter[i] = QChar(letter[i].digitValue()+base); + } + return letter; +} + +QString toArabicIndic( int number ) { + return toNumeric(number, 0x660); +} + +QString toPersianUrdu( int number ) { + return toNumeric(number, 0x6F0); +} + +QString toLao( int number ) { + return toNumeric(number, 0xED0); +} + +QString toThai( int number ) { + return toNumeric(number, 0xE50); +} + +QString toTibetan( int number ) { + return toNumeric(number, 0xF20); +} + +static inline QString toIdeographic(int number, const QChar digits[], const QChar digitmarkers[]) { + if (number < 0 || number > 9999) return QString::number(number); + + QString grp = QString::number(number); + + // ### Append group markers to handle numbers > 9999 + + QString str; + + // special case + if (number < 20 && number >= 10) { + str.append(digitmarkers[0]); + str.append(digits[grp[1].digitValue()]); + return str; + } + + int len = grp.length(); + bool collapseZero = false; + for (int i = 0; i < len ; i++) { + int digit = grp[i].digitValue(); + // Add digit markers to digits > 0 + if ((len-i-1) > 0 && digit > 0) + str.append(digitmarkers[(len-i-2)]); + // Add digit, but collapse consecutive zeros + if (!collapseZero || digit > 0) { + str.append(digits[digit]); + + if (digit == 0) + collapseZero = true; + else + collapseZero = false; + } + } + return str; +} + +QString toTradChineseFormal( int number ) { +// static const QChar groupMarkers[3] = {0x4e07, 0x4ebf, 0x5146}; + static const QChar digitMarkers[3] = {0x4e07, 0x4ebf, 0x5146}; + static const QChar digits[10] = {0x96f6, 0x4e00, + 0x4ebc, 0x4e09, + 0x56db, 0x4e94, + 0x516d, 0x4e03, + 0x516b, 0x4e5d}; + return toIdeographic(number, digits, digitMarkers); +} + +QString toTradChineseInformal( int number ) { +// static const QChar groupMarkers[3] = {0x842c, 0x5104, 0x5146}; + static const QChar digitMarkers[3] = {0x842c, 0x5104, 0x5146}; + static const QChar digits[10] = {0x96f6, 0x4e00, + 0x4ebc, 0x4e09, + 0x56db, 0x4e94, + 0x516d, 0x4e03, + 0x516b, 0x4e5d}; + return toIdeographic(number, digits, digitMarkers); +} + +QString toSimpChineseFormal( int number ) { +// static const QChar groupMarkers[3] = {0x4e07, 0x5104, 0x5146}; + static const QChar digitMarkers[3] = {0x4e07, 0x4ebf, 0x5146}; + static const QChar digits[10] = {0x96f6, 0x58f9, + 0x8cb3, 0x53c3, + 0x8086, 0x4f0d, + 0x9678, 0x67d2, + 0x634c, 0x7396}; + return toIdeographic(number, digits, digitMarkers); +} + +QString toSimpChineseInformal( int number ) { +// static const QChar groupMarkers[3] = {0x842c, 0x5104, 0x5146}; + static const QChar digitMarkers[3] = {0x842c, 0x5104, 0x5146}; + static const QChar digits[10] = {0x96f6, 0x58f9, + 0x8cb3, 0x53c3, + 0x8086, 0x4f0d, + 0x9678, 0x67d2, + 0x634c, 0x7396}; + return toIdeographic(number, digits, digitMarkers); +} + +QString toJapaneseFormal( int number ) { +// static const QChar groupMarkers[3] = {0x4e07, 0x5104, 0x5146}; + static const QChar digitMarkers[3] = {0x62fe, 0x4f70, 0x4edf}; + static const QChar digits[10] = {0x96f6, 0x58f9, + 0x8cb3, 0x53c3, + 0x8086, 0x4f0d, + 0x9678, 0x67d2, + 0x634c, 0x7396}; + return toIdeographic(number, digits, digitMarkers); +} + +QString toJapaneseInformal( int number ) { +// static const QChar groupMarkers[3] = {0x842c, 0x5104, 0x5146}; + static const QChar digitMarkers[3] = {0x842c, 0x5104, 0x5146}; + static const QChar digits[10] = {0x96f6, 0x58f9, + 0x8d30, 0x53c1, + 0x8086, 0x4f0d, + 0x9646, 0x67d2, + 0x634c, 0x7396}; + return toIdeographic(number, digits, digitMarkers); +} + +}} // namespace diff --git a/khtml/rendering/enumerate.h b/khtml/rendering/enumerate.h new file mode 100644 index 000000000..518a8e9d6 --- /dev/null +++ b/khtml/rendering/enumerate.h @@ -0,0 +1,66 @@ +/** + * This file is part of the HTML rendering engine for KDE. + * + * Copyright (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com) + * + * 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. + * + */ + +#ifndef ENUMERATE_H +#define ENUMERATE_H + +class QChar; +class QString; + +namespace khtml { + +namespace Enumerate { + +// Numeric + QString toArabicIndic( int number ); + QString toLao( int number ); + QString toPersianUrdu( int number ); + QString toThai( int number ); + QString toTibetan( int number ); + +// Alphabetic + QString toLowerLatin( int number ); + QString toUpperLatin( int number ); + QString toLowerGreek( int number ); + QString toUpperGreek( int number ); + QString toHiragana( int number ); + QString toHiraganaIroha( int number ); + QString toKatakana( int number ); + QString toKatakanaIroha( int number ); + +// Algorithmic + QString toRoman( int number, bool upper ); + QString toHebrew( int number ); + QString toGeorgian( int number ); + QString toArmenian( int number ); + +// Ideographic + QString toJapaneseFormal ( int number ); + QString toJapaneseInformal ( int number ); + QString toSimpChineseFormal ( int number ); + QString toSimpChineseInformal ( int number ); + QString toTradChineseFormal ( int number ); + QString toTradChineseInformal ( int number ); + +}} // namespaces + +#endif diff --git a/khtml/rendering/font.cpp b/khtml/rendering/font.cpp new file mode 100644 index 000000000..cdd580596 --- /dev/null +++ b/khtml/rendering/font.cpp @@ -0,0 +1,502 @@ +/** + * This file is part of the html renderer for KDE. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#ifdef HAVE_ALLOCA_H +#include <alloca.h> +#endif + +#include "font.h" +#include "khtml_factory.h" +#include "khtml_settings.h" + +#include <kdebug.h> +#include <kglobal.h> + +#include <qpainter.h> +#include <qfontdatabase.h> +#include <qpaintdevicemetrics.h> + +using namespace khtml; + +/** closes the current word and returns its width in pixels + * @param fm metrics of font to be used + * @param str string + * @param pos zero-indexed position within @p str upon which all other + * indices are based + * @param wordStart relative index pointing to the position where the word started + * @param wordEnd relative index pointing one position after the word ended + * @return the width in pixels. May be 0 if @p wordStart and @p wordEnd were + * equal. + */ +inline int closeWordAndGetWidth(const QFontMetrics &fm, const QChar *str, int pos, + int wordStart, int wordEnd) +{ + if (wordEnd <= wordStart) return 0; + + QConstString s(str + pos + wordStart, wordEnd - wordStart); + return fm.width(s.string()); +} + +/** closes the current word and draws it + * @param p painter + * @param d text direction + * @param x current x position, will be inc-/decremented correctly according + * to text direction + * @param y baseline of text + * @param widths list of widths; width of word is expected at position + * wordStart + * @param str string + * @param pos zero-indexed position within @p str upon which all other + * indices are based + * @param wordStart relative index pointing to the position where the word started, + * will be set to wordEnd after function + * @param wordEnd relative index pointing one position after the word ended + */ +inline void closeAndDrawWord(QPainter *p, QPainter::TextDirection d, + int &x, int y, const short widths[], const QChar *str, int pos, + int &wordStart, int wordEnd) +{ + if (wordEnd <= wordStart) return; + + int width = widths[wordStart]; + if (d == QPainter::RTL) + x -= width; + + QConstString s(str + pos + wordStart, wordEnd - wordStart); + p->drawText(x, y, s.string(), -1, d); + + if (d != QPainter::RTL) + x += width; + + wordStart = wordEnd; +} + +void Font::drawText( QPainter *p, int x, int y, QChar *str, int slen, int pos, int len, + int toAdd, QPainter::TextDirection d, int from, int to, QColor bg, int uy, int h, int deco ) const +{ + if (!str) return; + QConstString cstr = QConstString(str, slen); + QString qstr = cstr.string(); + + // ### fixme for RTL + if ( !scFont && !letterSpacing && !wordSpacing && !toAdd && from==-1 ) { + // simply draw it + // Due to some unfounded cause QPainter::drawText traverses the + // *whole* string when painting, not only the specified + // [pos, pos + len) segment. This makes painting *extremely* slow for + // long render texts (in the order of several megabytes). + // Hence, only hand over the piece of text of the actual inline text box + QConstString cstr = QConstString(str + pos, len); + p->drawText( x, y, cstr.string(), 0, len, d ); + } else { + if (from < 0) from = 0; + if (to < 0) to = len; + + int numSpaces = 0; + if ( toAdd ) { + for( int i = 0; i < len; ++i ) + if ( str[i+pos].category() == QChar::Separator_Space ) + ++numSpaces; + } + + const int totWidth = width( str, slen, pos, len ); + if ( d == QPainter::RTL ) { + x += totWidth + toAdd; + } + QString upper = qstr; + QFontMetrics sc_fm = fm; + if ( scFont ) { + // draw in small caps + upper = qstr.upper(); + sc_fm = QFontMetrics( *scFont ); + } + + // ### sc could be optimized by only painting uppercase letters extra, + // and treat the rest WordWise, but I think it's not worth it. + // Somebody else may volunteer, and implement this ;-) (LS) + + // The mode determines whether the text is displayed character by + // character, word by word, or as a whole + enum { CharacterWise, WordWise, Whole } + mode = Whole; + if (!letterSpacing && !scFont && (wordSpacing || toAdd > 0)) + mode = WordWise; + else if (letterSpacing || scFont) + mode = CharacterWise; + + if (mode == Whole) { // most likely variant is treated extra + + if (to < 0) to = len; + const QConstString cstr(str + pos, len); + const QConstString segStr(str + pos + from, to - from); + const int preSegmentWidth = fm.width(cstr.string(), from); + const int segmentWidth = fm.width(segStr.string()); + const int eff_x = d == QPainter::RTL ? x - preSegmentWidth - segmentWidth + : x + preSegmentWidth; + + // draw whole string segment, with optional background + if ( bg.isValid() ) + p->fillRect( eff_x, uy, segmentWidth, h, bg ); + p->drawText(eff_x, y, segStr.string(), -1, d); + if (deco) + drawDecoration(p, eff_x, uy, y - uy, segmentWidth - 1, h, deco); + return; + } + + // We are using two passes. In the first pass, the widths are collected, + // and stored. In the second, the actual characters are drawn. + + // For each letter in the text box, save the width of the character. + // When word-wise, only the first letter contains the width, but of the + // whole word. + short* const widthList = (short *)alloca(to*sizeof(short)); + + // First pass: gather widths + int preSegmentWidth = 0; + int segmentWidth = 0; + int lastWordBegin = 0; + bool onSegment = from == 0; + for( int i = 0; i < to; ++i ) { + if (i == from) { + // Also close words on visibility boundary + if (mode == WordWise) { + const int width = closeWordAndGetWidth(fm, str, pos, lastWordBegin, i); + + if (lastWordBegin < i) { + widthList[lastWordBegin] = (short)width; + lastWordBegin = i; + preSegmentWidth += width; + } + } + onSegment = true; + } + + const QChar ch = str[pos+i]; + bool lowercase = (ch.category() == QChar::Letter_Lowercase); + bool is_space = (ch.category() == QChar::Separator_Space); + int chw = 0; + if ( letterSpacing ) + chw += letterSpacing; + if ( (wordSpacing || toAdd) && is_space ) { + if (mode == WordWise) { + const int width = closeWordAndGetWidth(fm, str, pos, lastWordBegin, i); + if (lastWordBegin < i) { + widthList[lastWordBegin] = (short)width; + lastWordBegin = i; + (onSegment ? segmentWidth : preSegmentWidth) += width; + } + ++lastWordBegin; // ignore this space + } + chw += wordSpacing; + if ( numSpaces ) { + const int a = toAdd/numSpaces; + chw += a; + toAdd -= a; + --numSpaces; + } + } + if (is_space || mode == CharacterWise) { + chw += lowercase ? sc_fm.charWidth( upper, pos+i ) : fm.charWidth( qstr, pos+i ); + widthList[i] = (short)chw; + + (onSegment ? segmentWidth : preSegmentWidth) += chw; + } + + } + + // close last word + Q_ASSERT(onSegment); + if (mode == WordWise) { + const int width = closeWordAndGetWidth(fm, str, pos, lastWordBegin, to); + segmentWidth += width; + widthList[lastWordBegin] = (short)width; + } + + if (d == QPainter::RTL) x -= preSegmentWidth; + else x += preSegmentWidth; + + const int startx = d == QPainter::RTL ? x-segmentWidth : x; + + // optionally draw background + if ( bg.isValid() ) + p->fillRect( startx, uy, segmentWidth, h, bg ); + + // second pass: do the actual drawing + lastWordBegin = from; + for( int i = from; i < to; ++i ) { + const QChar ch = str[pos+i]; + bool lowercase = (ch.category() == QChar::Letter_Lowercase); + bool is_space = ch.category() == QChar::Separator_Space; + if ( is_space ) { + if (mode == WordWise) { + closeAndDrawWord(p, d, x, y, widthList, str, pos, lastWordBegin, i); + ++lastWordBegin; // jump over space + } + } + if (is_space || mode == CharacterWise) { + const int chw = widthList[i]; + if (d == QPainter::RTL) + x -= chw; + + if ( scFont ) + p->setFont( lowercase ? *scFont : f ); + p->drawText( x, y, (lowercase ? upper : qstr), pos+i, 1, d ); + + if (d != QPainter::RTL) + x += chw; + } + + } + + // don't forget to draw last word + if (mode == WordWise) { + closeAndDrawWord(p, d, x, y, widthList, str, pos, lastWordBegin, to); + } + + if (deco) + drawDecoration(p, startx, uy, y - uy, segmentWidth - 1, h, deco); + + if ( scFont ) + p->setFont( f ); + } +} + + +int Font::width( QChar *chs, int, int pos, int len, int start, int end, int toAdd ) const +{ + const QConstString cstr(chs+pos, len); + int w = 0; + + const QString qstr = cstr.string(); + if ( scFont ) { + const QString upper = qstr.upper(); + const QChar *uc = qstr.unicode(); + const QFontMetrics sc_fm( *scFont ); + for ( int i = 0; i < len; ++i ) { + if ( (uc+i)->category() == QChar::Letter_Lowercase ) + w += sc_fm.charWidth( upper, i ); + else + w += fm.charWidth( qstr, i ); + } + } else { + // ### might be a little inaccurate + w = fm.width( qstr ); + } + + if ( letterSpacing ) + w += len*letterSpacing; + + if ( wordSpacing ) + // add amount + for( int i = 0; i < len; ++i ) { + if( chs[i+pos].category() == QChar::Separator_Space ) + w += wordSpacing; + } + + if ( toAdd ) { + // first gather count of spaces + int numSpaces = 0; + for( int i = start; i != end; ++i ) + if ( chs[i].category() == QChar::Separator_Space ) + ++numSpaces; + // distribute pixels evenly among spaces, but count only those within + // [pos, pos+len) + for ( int i = start; numSpaces && i != pos + len; i++ ) + if ( chs[i].category() == QChar::Separator_Space ) { + const int a = toAdd/numSpaces; + if ( i >= pos ) w += a; + toAdd -= a; + --numSpaces; + } + } + + return w; +} + +int Font::width( QChar *chs, int slen, int pos ) const +{ + int w; + if ( scFont && chs[pos].category() == QChar::Letter_Lowercase ) { + QString str( chs, slen ); + str[pos] = chs[pos].upper(); + w = QFontMetrics( *scFont ).charWidth( str, pos ); + } else { + const QConstString cstr( chs, slen ); + w = fm.charWidth( cstr.string(), pos ); + } + if ( letterSpacing ) + w += letterSpacing; + + if ( wordSpacing && (chs+pos)->category() == QChar::Separator_Space ) + w += wordSpacing; + return w; +} + +/** Querying QFontDB whether something is scalable is expensive, so we cache. */ +struct Font::ScalKey +{ + QString family; + int weight; + int italic; + + ScalKey(const QFont& font) : + family(font.family()), weight(font.weight()), italic(font.italic()) + {} + + ScalKey() {} + + bool operator<(const ScalKey& other) const { + if (weight < other.weight) + return true; + if (weight > other.weight) + return false; + + if (italic < other.italic) + return true; + if (italic > other.italic) + return false; + + return family < other.family; + } + + bool operator==(const ScalKey& other) const { + return (weight == other.weight && + italic == other.italic && + family == other.family); + } +}; + +QMap<Font::ScalKey, Font::ScalInfo>* Font::scalCache; +QMap<Font::ScalKey, QValueList<int> >* Font::scalSizesCache; + +bool Font::isFontScalable(QFontDatabase& db, const QFont& font) +{ + if (!scalCache) + scalCache = new QMap<ScalKey, ScalInfo>; + + ScalKey key(font); + + ScalInfo &s = (*scalCache)[key]; + if (s == Unknown) { + s = db.isSmoothlyScalable(font.family(), db.styleString(font)) ? Yes : No; + + if (s == No) { + /* Cache size info */ + if (!scalSizesCache) + scalSizesCache = new QMap<ScalKey, QValueList<int> >; + (*scalSizesCache)[key] = db.smoothSizes(font.family(), db.styleString(font)); + } + } + + return (s == Yes); +} + + +void Font::update( QPaintDeviceMetrics* devMetrics ) const +{ + f.setFamily( fontDef.family.isEmpty() ? KHTMLFactory::defaultHTMLSettings()->stdFontName() : fontDef.family ); + f.setItalic( fontDef.italic ); + f.setWeight( fontDef.weight ); + + QFontDatabase db; + + int size = fontDef.size; + const int lDpiY = kMax(devMetrics->logicalDpiY(), 96); + + // ok, now some magic to get a nice unscaled font + // all other font properties should be set before this one!!!! + if( !isFontScalable(db, f) ) + { + const QValueList<int>& pointSizes = (*scalSizesCache)[ScalKey(f)]; + // lets see if we find a nice looking font, which is not too far away + // from the requested one. + // kdDebug(6080) << "khtml::setFontSize family = " << f.family() << " size requested=" << size << endl; + + + float diff = 1; // ### 100% deviation + float bestSize = 0; + + QValueList<int>::ConstIterator it = pointSizes.begin(); + const QValueList<int>::ConstIterator itEnd = pointSizes.end(); + + for( ; it != itEnd; ++it ) + { + float newDiff = ((*it)*(lDpiY/72.) - float(size))/float(size); + //kdDebug( 6080 ) << "smooth font size: " << *it << " diff=" << newDiff << endl; + if(newDiff < 0) newDiff = -newDiff; + if(newDiff < diff) + { + diff = newDiff; + bestSize = *it; + } + } + //kdDebug( 6080 ) << "best smooth font size: " << bestSize << " diff=" << diff << endl; + if ( bestSize != 0 && diff < 0.2 ) // 20% deviation, otherwise we use a scaled font... + size = (int)((bestSize*lDpiY) / 72); + } + + // make sure we don't bust up X11 + // Also, Qt does not support sizing a QFont to zero. + size = kMax(1, kMin(255, size)); + +// qDebug("setting font to %s, italic=%d, weight=%d, size=%d", fontDef.family.latin1(), fontDef.italic, +// fontDef.weight, size ); + + + f.setPixelSize( size ); + + fm = QFontMetrics( f ); + + // small caps + delete scFont; + scFont = 0; + + if ( fontDef.smallCaps ) { + scFont = new QFont( f ); + scFont->setPixelSize( kMax(1, f.pixelSize()*7/10) ); + } +} + +void Font::drawDecoration(QPainter *pt, int _tx, int _ty, int baseline, int width, int height, int deco) const +{ + Q_UNUSED(height); + // thick lines on small fonts look ugly + const int thickness = fm.height() > 20 ? fm.lineWidth() : 1; + const QBrush brush = pt->pen().color(); + if (deco & UNDERLINE) { + int underlineOffset = ( fm.height() + baseline ) / 2; + if (underlineOffset <= baseline) underlineOffset = baseline+1; + + pt->fillRect(_tx, _ty + underlineOffset, width + 1, thickness, brush ); + } + if (deco & OVERLINE) { + pt->fillRect(_tx, _ty, width + 1, thickness, brush ); + } + if (deco & LINE_THROUGH) { + pt->fillRect(_tx, _ty + 2*baseline/3, width + 1, thickness, brush ); + } +} + diff --git a/khtml/rendering/font.h b/khtml/rendering/font.h new file mode 100644 index 000000000..c35ab53aa --- /dev/null +++ b/khtml/rendering/font.h @@ -0,0 +1,188 @@ +/* + * This file is part of the html renderer for KDE. + * + * Copyright (C) 2000-2003 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * + * 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. + * + */ + +#ifndef KHTMLFONT_H +#define KHTMLFONT_H + +#include <qfont.h> +#include <qfontmetrics.h> +#include <qmap.h> +#include <qpainter.h> + +class QFontDatabase; +class QPaintDeviceMetrics; + +namespace khtml +{ +class RenderStyle; +class CSSStyleSelector; + +class FontDef +{ +public: + FontDef() + : size( 0 ), italic( false ), smallCaps( false ), weight( 50 ) {} + bool operator == ( const FontDef &other ) const { + return ( family == other.family && + size == other.size && + italic == other.italic && + smallCaps == other.smallCaps && + weight == other.weight ); + } + + QString family; + short int size; + bool italic : 1; + bool smallCaps : 1; + unsigned int weight : 8; +}; + + +class Font +{ + friend class RenderStyle; + friend class CSSStyleSelector; + +public: + Font() : fontDef(), f(), fm( f ), scFont( 0 ), letterSpacing( 0 ), wordSpacing( 0 ) {} + Font( const FontDef &fd ) + : fontDef( fd ), f(), fm( f ), scFont( 0 ), letterSpacing( 0 ), wordSpacing( 0 ) + {} + Font(const Font& o) + : fontDef(o.fontDef), f(o.f), fm(o.fm), scFont(o.scFont), letterSpacing(o.letterSpacing), wordSpacing(o.wordSpacing) { if (o.scFont) scFont = new QFont(*o.scFont); } + ~Font() { delete scFont; } + + bool operator == ( const Font &other ) const { + return (fontDef == other.fontDef && + letterSpacing == other.letterSpacing && + wordSpacing == other.wordSpacing ); + } + + const FontDef& getFontDef() const { return fontDef; } + + void update( QPaintDeviceMetrics *devMetrics ) const; + + /** + * Draws a piece from the given piece of text. + * @param p painter + * @param x x-coordinate to begin drawing, always denotes leftmost position + * @param y y-coordinate of baseline of text + * @param str string to draw a piece from + * @param slen total length of string + * @param pos zero-based offset of beginning of piece + * @param len length of piece + * @param width additional pixels to be distributed equally among all + * spaces + * @param d text direction + * @param from begin with this position relative to @p pos, -1 to start + * at @p pos + * @param to stop before this position relative to @p pos, -1 to use full + * length of piece + * @param bg if valid, fill the background of the drawn piece with this + * color + * @param uy y-coordinate of top position, used for background and text + * decoration painting + * @param h total height of line, only used for background and text + * decoration painting + * @param deco combined text decoration (see Decoration) + */ + void drawText( QPainter *p, int x, int y, QChar *str, int slen, int pos, int len, int width, + QPainter::TextDirection d, int from=-1, int to=-1, QColor bg=QColor(), + int uy=-1, int h=-1, int deco=0 ) const; + + /** returns the width of the given string chunk in pixels. + * + * The method also considers various styles like text-align and font-variant + * @param str pointer to string + * @param slen total length of string + * @param pos zero-based position in string where to start measuring + * @param len count of characters up to which the width should be determined + * @param start starting position of inline text box within str, only + * used when toAdd is specified. + * @param end ending position of inline text box within str, only + * used when toAdd is specified. + * @param toAdd amount of pixels to distribute evenly among all spaces of + * str. Note that toAdd applies to all spaces within str, but only those + * within [pos, pos+len) are counted towards the width. + */ + int width( QChar *str, int slen, int pos, int len, int start = 0, int end = 0, int toAdd = 0 ) const; + /** return the width of the given char in pixels. + * + * The method also considers various styles like text-align and font-variant + * @param str pointer to string + * @param slen total length of string + * @param pos zero-based position of char in string + */ + int width( QChar *str, int slen, int pos) const; + + /** Text decoration constants. + * + * The enumeration constant values match those of ETextDecoration, but only + * a subset is supported. + */ + enum Decoration { UNDERLINE = 0x1, OVERLINE = 0x2, LINE_THROUGH= 0x4 }; + // Keep in sync with ETextDecoration + + /** draws text decoration + * @param p painter + * @param x x-coordinate + * @param y top y-coordinate of line box + * @param baseline baseline + * @param width length of decoration in pixels + * @param height height of line box + * @param deco decoration to be drawn (see Decoration). The enumeration + * constants may be combined. + */ + void drawDecoration(QPainter *p, int x, int y, int baseline, int width, int height, int deco) const; + + /** returns letter spacing + */ + int getLetterSpacing() const { return letterSpacing; } + /** returns word spacing + */ + int getWordSpacing() const { return wordSpacing; } + +private: + mutable FontDef fontDef; + mutable QFont f; + mutable QFontMetrics fm; + mutable QFont *scFont; + short letterSpacing; + short wordSpacing; + + struct ScalKey; + enum ScalInfo { + Unknown, + No, + Yes + }; + + static QMap<ScalKey, ScalInfo>* scalCache; + static QMap<ScalKey, QValueList<int> >* scalSizesCache; + static bool isFontScalable(QFontDatabase& db, const QFont& font); +}; + +} // namespace + +#endif diff --git a/khtml/rendering/img-loading.png b/khtml/rendering/img-loading.png Binary files differnew file mode 100644 index 000000000..ae5a9732f --- /dev/null +++ b/khtml/rendering/img-loading.png diff --git a/khtml/rendering/loading_icon.cpp b/khtml/rendering/loading_icon.cpp new file mode 100644 index 000000000..218befcf8 --- /dev/null +++ b/khtml/rendering/loading_icon.cpp @@ -0,0 +1,25 @@ +static const unsigned char loading_icon_data[] = { +0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a,0x00,0x00,0x00,0x0d,0x49,0x48,0x44, +0x52,0x00,0x00,0x00,0x0e,0x00,0x00,0x00,0x10,0x08,0x04,0x00,0x00,0x00,0x8c, +0x9d,0x86,0xb1,0x00,0x00,0x00,0x04,0x67,0x41,0x4d,0x41,0x00,0x00,0xb1,0x8f, +0x0b,0xfc,0x61,0x05,0x00,0x00,0x00,0x02,0x62,0x4b,0x47,0x44,0x00,0xff,0x87, +0x8f,0xcc,0xbf,0x00,0x00,0x00,0x09,0x70,0x48,0x59,0x73,0x00,0x00,0x0b,0x12, +0x00,0x00,0x0b,0x12,0x01,0xd2,0xdd,0x7e,0xfc,0x00,0x00,0x00,0x07,0x74,0x49, +0x4d,0x45,0x07,0xd4,0x0c,0x17,0x10,0x00,0x23,0xf3,0x04,0xa4,0xbc,0x00,0x00, +0x00,0xbf,0x49,0x44,0x41,0x54,0x78,0x9c,0x75,0xd1,0x31,0x4e,0x04,0x31,0x0c, +0x85,0xe1,0x2f,0xc9,0x1c,0x63,0xee,0x02,0x82,0x9a,0x23,0x70,0x01,0xba,0xad, +0xe8,0x98,0x0d,0x35,0xab,0xe9,0x10,0xf7,0x80,0x76,0xe0,0x32,0x7b,0x01,0x44, +0x9d,0x84,0x22,0xc3,0x32,0xd2,0x82,0x5d,0xd9,0xbf,0xf5,0xe4,0x67,0x07,0xd9, +0x59,0x4c,0xf2,0x04,0x91,0xf6,0x47,0x3e,0x64,0x18,0xe0,0x1d,0x51,0x15,0x05, +0x05,0xc1,0x5e,0xc8,0x79,0x1a,0xba,0x50,0x54,0x3d,0x49,0x76,0xa2,0xaa,0x61, +0x4f,0x8e,0x1d,0x05,0xb3,0x1b,0xc5,0xb5,0x2a,0x79,0x14,0x04,0xb9,0xcb,0x06, +0x45,0xf1,0x2a,0x21,0x2a,0x16,0x5c,0x09,0x1d,0x16,0xd1,0xbd,0x4b,0x2c,0xaa, +0xa4,0x48,0x4e,0x0b,0xf5,0x58,0x24,0xcd,0x2d,0x38,0x6a,0xdd,0x0a,0xd1,0x41, +0x41,0x01,0xcf,0xa7,0xf1,0x08,0xd5,0x68,0x16,0x11,0x70,0xe7,0x73,0x0b,0x13, +0x46,0x07,0x34,0x47,0x47,0x5f,0x2b,0x1c,0xa0,0x81,0xd1,0x6c,0x67,0xd9,0x9c, +0x71,0xe8,0xb2,0x2f,0x6b,0xf9,0xe6,0x43,0xd0,0xd4,0x5f,0x98,0x2c,0x48,0x9a, +0xba,0xb6,0x37,0x56,0x2e,0xce,0x1f,0xf3,0x03,0xc3,0x3f,0x88,0x6f,0xc1,0xe0, +0x3e,0x9e,0x60,0xe9,0x10,0xaa,0x00,0x00,0x00,0x00,0x49,0x45,0x4e,0x44,0xae, +0x42,0x60,0x82 +}; +static const unsigned int loading_icon_len = 318; diff --git a/khtml/rendering/render_applet.cpp b/khtml/rendering/render_applet.cpp new file mode 100644 index 000000000..f6ce1ebab --- /dev/null +++ b/khtml/rendering/render_applet.cpp @@ -0,0 +1,145 @@ +/** + * This file is part of the HTML widget for KDE. + * + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ +#include <config.h> +#include <klocale.h> + +#include <kdebug.h> + +#include "rendering/render_applet.h" +#include "rendering/render_canvas.h" +#include "xml/dom_docimpl.h" +#include "khtmlview.h" +#include "khtml_part.h" + +#include <qlabel.h> + +#ifndef Q_WS_QWS // We don't have Java in Qt Embedded + +#include "java/kjavaappletwidget.h" +#include "misc/htmltags.h" +#include "html/html_objectimpl.h" + +using namespace khtml; +using namespace DOM; + +RenderApplet::RenderApplet(HTMLElementImpl *applet, const QMap<QString, QString> &args ) + : RenderWidget(applet) +{ + // init RenderObject attributes + setInline(true); + + KJavaAppletContext *context = 0; + KHTMLView *_view = applet->getDocument()->view(); + if ( _view ) { + KHTMLPart *part = _view->part(); + context = part->createJavaContext(); + } + + if ( context ) { + //kdDebug(6100) << "RenderApplet::RenderApplet, setting QWidget" << endl; + setQWidget( new KJavaAppletWidget(context, _view->viewport()) ); + processArguments(args); + } +} + +RenderApplet::~RenderApplet() +{ +} + +short RenderApplet::intrinsicWidth() const +{ + int rval = 300; + + if( m_widget ) + rval = ((KJavaAppletWidget*)(m_widget))->sizeHint().width(); + + return rval > 10 ? rval : 50; +} + +int RenderApplet::intrinsicHeight() const +{ + int rval = 150; + + if( m_widget ) + rval = m_widget->sizeHint().height(); + + return rval > 10 ? rval : 50; +} + +void RenderApplet::layout() +{ + //kdDebug(6100) << "RenderApplet::layout" << endl; + + KHTMLAssert( needsLayout() ); + KHTMLAssert( minMaxKnown() ); + + calcWidth(); + calcHeight(); + + KJavaAppletWidget *tmp = static_cast<KJavaAppletWidget*>(m_widget); + if ( tmp ) { + NodeImpl *child = element()->firstChild(); + + while(child) { + + if(child->id() == ID_PARAM) { + HTMLParamElementImpl *p = static_cast<HTMLParamElementImpl *>(child); + if(tmp->applet()) + tmp->applet()->setParameter( p->name(), p->value()); + } + child = child->nextSibling(); + } + //kdDebug(6100) << "setting applet widget to size: " << m_width << ", " << m_height << endl; + m_widget->resize(m_width-borderLeft()-borderRight()-paddingLeft()-paddingRight(), + m_height-borderTop()-borderBottom()-paddingTop()-paddingBottom()); + tmp->showApplet(); + } + + setNeedsLayout(false); +} + +void RenderApplet::processArguments(const QMap<QString, QString> &args) +{ + KJavaAppletWidget *w = static_cast<KJavaAppletWidget*>(m_widget); + KJavaApplet* applet = w ? w->applet() : 0; + + if ( applet ) { + applet->setBaseURL( args[QString::fromLatin1("baseURL") ] ); + applet->setAppletClass( args[QString::fromLatin1("code") ] ); + + QString str = args[QString::fromLatin1("codeBase") ]; + if( !str.isEmpty() ) + applet->setCodeBase( str ); + + str = args[QString::fromLatin1("name") ]; + if( !str.isNull() ) + applet->setAppletName( str ); + else + applet->setAppletName( args[QString::fromLatin1("code") ] ); + + str = args[QString::fromLatin1("archive") ]; + if( !str.isEmpty() ) + applet->setArchives( args[QString::fromLatin1("archive") ] ); + } +} + +#endif diff --git a/khtml/rendering/render_applet.h b/khtml/rendering/render_applet.h new file mode 100644 index 000000000..54af6f280 --- /dev/null +++ b/khtml/rendering/render_applet.h @@ -0,0 +1,60 @@ +/* + * This file is part of the HTML widget for KDE. + * + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * + * 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. + * + */ +#ifndef render_applet_h +#define render_applet_h + +#include "rendering/render_replaced.h" +#include "html/html_objectimpl.h" + +#include <qwidget.h> +#include <qmap.h> + +class KHTMLView; + +namespace DOM { + class HTMLElementImpl; +} + +namespace khtml { + +class RenderApplet : public RenderWidget +{ +public: + RenderApplet(DOM::HTMLElementImpl* node, const QMap<QString, QString> &args); + virtual ~RenderApplet(); + + virtual const char *renderName() const { return "RenderApplet"; } + + virtual void layout(); + virtual short intrinsicWidth() const; + virtual int intrinsicHeight() const; + virtual bool isApplet() const { return true; } + + DOM::HTMLElementImpl *element() const + { return static_cast<DOM::HTMLElementImpl*>(RenderObject::element()); } + +private: + void processArguments( const QMap<QString, QString> &args ); +}; + +} +#endif diff --git a/khtml/rendering/render_arena.cpp b/khtml/rendering/render_arena.cpp new file mode 100644 index 000000000..99fbf4f4d --- /dev/null +++ b/khtml/rendering/render_arena.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2002 Apple Computer, Inc. + * Copyright (C) 2003 Dirk Mueller (mueller@kde.org) + * + * Portions are Copyright (C) 1998 Netscape Communications Corporation. + * + * 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. + */ + +#include "render_arena.h" + +#include <string.h> +#include <assert.h> + +using namespace khtml; + +namespace khtml { + +//#ifdef NDEBUG +#define KHTML_USE_ARENA_ALLOCATOR +//#endif + +typedef struct { + RenderArena *arena; + size_t size; +} RenderArenaDebugHeader; + +#ifdef VALGRIND_SUPPORT +Arena* findContainingArena(ArenaPool* pool, void* ptr) { + uword ptrBits = reinterpret_cast<uword>(ptr); + for (Arena* a = &pool->first; a; a = a->next) + if (ptrBits >= a->base && ptrBits < a->limit) + return a; + return 0; //Should not happen +} +#endif + +RenderArena::RenderArena(unsigned int arenaSize) +{ + // Initialize the arena pool + INIT_ARENA_POOL(&m_pool, "RenderArena", arenaSize); + + // Zero out the recyclers array + memset(m_recyclers, 0, sizeof(m_recyclers)); +} + +RenderArena::~RenderArena() +{ + // Free the arena in the pool and finish using it + FreeArenaPool(&m_pool); +} + +void* RenderArena::allocate(size_t size) +{ +#ifndef KHTML_USE_ARENA_ALLOCATOR + // Use standard malloc so that memory debugging tools work. + void *block = ::malloc(sizeof(RenderArenaDebugHeader) + size); + RenderArenaDebugHeader *header = (RenderArenaDebugHeader *)block; + header->arena = this; + header->size = size; + return header + 1; +#else + void* result = 0; + + // Ensure we have correct alignment for pointers. Important for Tru64 + size = KHTML_ROUNDUP(size, sizeof(void*)); + + // Check recyclers first + if (size < KHTML_MAX_RECYCLED_SIZE) { + const int index = size >> 2; + + result = m_recyclers[index]; + if (result) { +#ifdef VALGRIND_SUPPORT + VALGRIND_MEMPOOL_ALLOC(findContainingArena(&m_pool, result)->base, result, size); +#endif + // Need to move to the next object + void* next = *((void**)result); + m_recyclers[index] = next; + } + } + + if (!result) { + // Allocate a new chunk from the arena + ARENA_ALLOCATE(result, &m_pool, size); + } + + return result; +#endif +} + +void RenderArena::free(size_t size, void* ptr) +{ +#ifndef KHTML_USE_ARENA_ALLOCATOR + // Use standard free so that memory debugging tools work. + assert(this); + RenderArenaDebugHeader *header = (RenderArenaDebugHeader *)ptr - 1; + assert(header->size == size); + assert(header->arena == this); + ::free(header); +#else + +#ifdef VALGRIND_SUPPORT + VALGRIND_MEMPOOL_FREE(findContainingArena(&m_pool, ptr)->base, ptr); +#endif + + // Ensure we have correct alignment for pointers. Important for Tru64 + size = KHTML_ROUNDUP(size, sizeof(void*)); + + // See if it's a size that we recycle + if (size < KHTML_MAX_RECYCLED_SIZE) { + const int index = size >> 2; + void* currentTop = m_recyclers[index]; + m_recyclers[index] = ptr; + *((void**)ptr) = currentTop; + } +#endif +} + +} diff --git a/khtml/rendering/render_arena.h b/khtml/rendering/render_arena.h new file mode 100644 index 000000000..786cd208a --- /dev/null +++ b/khtml/rendering/render_arena.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2002 Apple Computer, Inc. + * Copyright (C) 2003 Dirk Mueller (mueller@kde.org) + * + * Portions are Copyright (C) 1998 Netscape Communications Corporation. + * + * 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. + */ + +#ifndef RENDERARENA_H +#define RENDERARENA_H + +#include "misc/arena.h" +#include "misc/shared.h" + +#include <stdlib.h> + +namespace khtml { + +#define KHTML_MAX_RECYCLED_SIZE 400 +#define KHTML_ROUNDUP(x,y) ((((x)+((y)-1))/(y))*(y)) + +class RenderArena: public Shared<RenderArena> { +public: + RenderArena(unsigned int arenaSize = 4096); + ~RenderArena(); + + // Memory management functions + void* allocate(size_t size); + void free(size_t size, void* ptr); + +private: + // Underlying arena pool + ArenaPool m_pool; + + // The recycler array is sparse with the indices being multiples of 4, + // i.e., 0, 4, 8, 12, 16, 20, ... + void* m_recyclers[KHTML_MAX_RECYCLED_SIZE >> 2]; +}; + + +} // namespace + + +#endif + diff --git a/khtml/rendering/render_block.cpp b/khtml/rendering/render_block.cpp new file mode 100644 index 000000000..ba9645a62 --- /dev/null +++ b/khtml/rendering/render_block.cpp @@ -0,0 +1,3223 @@ +/* + * This file is part of the render object implementation for KHTML. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 1999-2003 Antti Koivisto (koivisto@kde.org) + * (C) 2002-2003 Dirk Mueller (mueller@kde.org) + * (C) 2003,2005 Apple Computer, Inc. + * (C) 2004 Germain Garand (germain@ebooksfrance.org) + * (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) + * (C) 2006 Charles Samuels (charles@kde.org) + * + * 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 +//#define DEBUG_LAYOUT +//#define BOX_DEBUG +//#define FLOAT_DEBUG +//#define PAGE_DEBUG + +#include <kdebug.h> +#include "rendering/render_text.h" +#include "rendering/render_table.h" +#include "rendering/render_canvas.h" +#include "rendering/render_layer.h" +#include "rendering/render_block.h" + +#include "xml/dom_nodeimpl.h" +#include "xml/dom_docimpl.h" +#include "html/html_formimpl.h" +#include "misc/htmltags.h" + +#include "khtmlview.h" + +using namespace DOM; + +namespace khtml { + +// ------------------------------------------------------------------------------------------------------- + +// Our MarginInfo state used when laying out block children. +RenderBlock::MarginInfo::MarginInfo(RenderBlock* block, int top, int bottom) +{ + // Whether or not we can collapse our own margins with our children. We don't do this + // if we had any border/padding (obviously), if we're the root or HTML elements, or if + // we're positioned, floating, a table cell. + m_canCollapseWithChildren = !block->isCanvas() && !block->isRoot() && !block->isPositioned() && + !block->isFloating() && !block->isTableCell() && !block->hasOverflowClip() && !block->isInlineBlockOrInlineTable(); + + m_canCollapseTopWithChildren = m_canCollapseWithChildren && (top == 0) /*&& block->style()->marginTopCollapse() != MSEPARATE */; + + // If any height other than auto is specified in CSS, then we don't collapse our bottom + // margins with our children's margins. To do otherwise would be to risk odd visual + // effects when the children overflow out of the parent block and yet still collapse + // with it. We also don't collapse if we have any bottom border/padding. + m_canCollapseBottomWithChildren = m_canCollapseWithChildren && (bottom == 0) && + (block->style()->height().isVariable() && block->style()->height().value() == 0) /*&& block->style()->marginBottomCollapse() != MSEPARATE*/; + + m_quirkContainer = block->isTableCell() || block->isBody() /*|| block->style()->marginTopCollapse() == MDISCARD || + block->style()->marginBottomCollapse() == MDISCARD*/; + + m_atTopOfBlock = true; + m_atBottomOfBlock = false; + + m_posMargin = m_canCollapseTopWithChildren ? block->maxTopMargin(true) : 0; + m_negMargin = m_canCollapseTopWithChildren ? block->maxTopMargin(false) : 0; + + m_selfCollapsingBlockClearedFloat = false; + + m_topQuirk = m_bottomQuirk = m_determinedTopQuirk = false; +} + +// ------------------------------------------------------------------------------------------------------- + +RenderBlock::RenderBlock(DOM::NodeImpl* node) + : RenderFlow(node) +{ + m_childrenInline = true; + m_floatingObjects = 0; + m_positionedObjects = 0; + m_firstLine = false; + m_avoidPageBreak = false; + m_clearStatus = CNONE; + m_maxTopPosMargin = m_maxTopNegMargin = m_maxBottomPosMargin = m_maxBottomNegMargin = 0; + m_topMarginQuirk = m_bottomMarginQuirk = false; + m_overflowHeight = m_overflowWidth = 0; + m_overflowLeft = m_overflowTop = 0; +} + +RenderBlock::~RenderBlock() +{ + delete m_floatingObjects; + delete m_positionedObjects; +} + +void RenderBlock::setStyle(RenderStyle* _style) +{ + setReplaced(_style->isDisplayReplacedType()); + + RenderFlow::setStyle(_style); + + // ### we could save this call when the change only affected + // non inherited properties + RenderObject *child = firstChild(); + while (child != 0) + { + if (child->isAnonymousBlock()) + { + RenderStyle* newStyle = new RenderStyle(); + newStyle->inheritFrom(style()); + newStyle->setDisplay(BLOCK); + child->setStyle(newStyle); + } + child = child->nextSibling(); + } + + if (attached()) { + // Update generated content and ::inside + updateReplacedContent(); + // Update pseudos for :before and :after + updatePseudoChildren(); + } + + // handled by close() during parsing + // ### remove close move upto updatePseudo + if (!document()->parsing()) { + updateFirstLetter(); + } +} + +// Attach handles initial setStyle that requires parent nodes +void RenderBlock::attach() +{ + RenderFlow::attach(); + + updateReplacedContent(); + updatePseudoChildren(); +} + +void RenderBlock::updateFirstLetter() +{ + // Only blocks with inline-children can generate a first-letter + if (!childrenInline() || !firstChild()) return; + + // Don't recurse + if (style()->styleType() == RenderStyle::FIRST_LETTER) return; + + // The first-letter style is inheritable. + RenderStyle *pseudoStyle = style()->getPseudoStyle(RenderStyle::FIRST_LETTER); + RenderObject *o = this; + while (o && !pseudoStyle) { + // ### We should ignore empty preceding siblings + if (o->parent() && o->parent()->firstChild() == this) + o = o->parent(); + else + break; + pseudoStyle = o->style()->getPseudoStyle(RenderStyle::FIRST_LETTER); + }; + + // FIXME: Currently we don't delete first-letters, this is + // handled instead in NodeImpl::diff by issuing Detach on first-letter changes. + if (!pseudoStyle) { + return; + } + + // Drill into inlines looking for our first text child. + RenderObject* firstText = firstChild(); + while (firstText && firstText->needsLayout() && !firstText->isFloating() && !firstText->isRenderBlock() && !firstText->isReplaced() && !firstText->isText()) + // ### We should skip first children with only white-space and punctuation + firstText = firstText->firstChild(); + + if (firstText && firstText->isText() && !firstText->isBR()) { + RenderObject* firstLetterObject = 0; + // Find the old first-letter + if (firstText->parent()->style()->styleType() == RenderStyle::FIRST_LETTER) + firstLetterObject = firstText->parent(); + + // Force inline display (except for floating first-letters) + pseudoStyle->setDisplay( pseudoStyle->isFloating() ? BLOCK : INLINE); + pseudoStyle->setPosition( STATIC ); // CSS2 says first-letter can't be positioned. + + if (firstLetterObject != 0) { + firstLetterObject->setStyle( pseudoStyle ); + RenderStyle* newStyle = new RenderStyle(); + newStyle->inheritFrom( pseudoStyle ); + firstText->setStyle( newStyle ); + return; + } + + RenderText* textObj = static_cast<RenderText*>(firstText); + RenderObject* firstLetterContainer = firstText->parent(); + + firstLetterObject = RenderFlow::createFlow(node(), pseudoStyle, renderArena() ); + firstLetterObject->setIsAnonymous( true ); + firstLetterContainer->addChild(firstLetterObject, firstLetterContainer->firstChild()); + + // if this object is the result of a :begin, then the text may have not been + // generated yet if it is a counter + if (textObj->recalcMinMax()) + textObj->recalcMinMaxWidths(); + + // The original string is going to be either a generated content string or a DOM node's + // string. We want the original string before it got transformed in case first-letter has + // no text-transform or a different text-transform applied to it. + DOMStringImpl* oldText = textObj->originalString(); + if (!oldText) + oldText = textObj->string(); + // ### In theory a first-letter can stretch across multiple text objects, if they only contain + // punctuation and white-space + if(oldText->l >= 1) { + oldText->ref(); + // begin: we need skip leading whitespace so that RenderBlock::findNextLineBreak + // won't think we're continuing from a previous run + unsigned int begin = 0; // the position that first-letter begins + unsigned int length = 0; // the position that "the rest" begins + while ( length < oldText->l && (oldText->s+length)->isSpace() ) + length++; + begin = length; + while ( length < oldText->l && + ( (oldText->s+length)->isPunct()) || (oldText->s+length)->isSpace() ) + length++; + if ( length < oldText->l && + !( (oldText->s+length)->isSpace() || (oldText->s+length)->isPunct() )) + length++; + while ( length < oldText->l && (oldText->s+length)->isMark() ) + length++; + + // we need to generated a remainingText object even if no text is left + // because it holds the place and style for the old textObj + RenderTextFragment* remainingText = + new (renderArena()) RenderTextFragment(textObj->node(), oldText, length, oldText->l-length); + remainingText->setIsAnonymous( textObj->isAnonymous() ); + remainingText->setStyle(textObj->style()); + if (remainingText->element()) + remainingText->element()->setRenderer(remainingText); + + RenderObject* nextObj = textObj->nextSibling(); + textObj->detach(); + firstLetterContainer->addChild(remainingText, nextObj); + + RenderTextFragment* letter = + new (renderArena()) RenderTextFragment(remainingText->node(), oldText, begin, length-begin); + letter->setIsAnonymous( remainingText->isAnonymous() ); + RenderStyle* newStyle = new RenderStyle(); + newStyle->inheritFrom(pseudoStyle); + letter->setStyle(newStyle); + firstLetterObject->addChild(letter); + oldText->deref(); + } + firstLetterObject->close(); + } +} + +void RenderBlock::addChildToFlow(RenderObject* newChild, RenderObject* beforeChild) +{ + // Make sure we don't append things after :after-generated content if we have it. + if ( !beforeChild && lastChild() && lastChild()->style()->styleType() == RenderStyle::AFTER ) + beforeChild = lastChild(); + + bool madeBoxesNonInline = false; + + // If the requested beforeChild is not one of our children, then this is most likely because + // there is an anonymous block box within this object that contains the beforeChild. So + // just insert the child into the anonymous block box instead of here. This may also be + // needed in cases of things like anonymous tables. + if (beforeChild && beforeChild->parent() != this) { + + KHTMLAssert(beforeChild->parent()); + + // In the special case where we are prepending a block-level element before + // something contained inside an anonymous block, we can just prepend it before + // the anonymous block. + if (!newChild->isInline() && beforeChild->parent()->isAnonymousBlock() && + beforeChild->parent()->parent() == this && + beforeChild->parent()->firstChild() == beforeChild) + return addChildToFlow(newChild, beforeChild->parent()); + + // Otherwise find our kid inside which the beforeChild is, and delegate to it. + // This may be many levels deep due to anonymous tables, table sections, etc. + RenderObject* responsible = beforeChild->parent(); + while (responsible->parent() != this) + responsible = responsible->parent(); + + return responsible->addChild(newChild,beforeChild); + } + + // prevent elements that haven't received a layout yet from getting painted by pushing + // them far above the top of the page + if (!newChild->isInline()) + newChild->setPos(newChild->xPos(), -500000); + + if (!newChild->isText() && newChild->style()->position() != STATIC) + setOverhangingContents(); + + // A block has to either have all of its children inline, or all of its children as blocks. + // So, if our children are currently inline and a block child has to be inserted, we move all our + // inline children into anonymous block boxes + if ( m_childrenInline && !newChild->isInline() && !newChild->isFloatingOrPositioned() ) + { + // This is a block with inline content. Wrap the inline content in anonymous blocks. + makeChildrenNonInline(beforeChild); + madeBoxesNonInline = true; + + if (beforeChild && beforeChild->parent() != this) { + beforeChild = beforeChild->parent(); + KHTMLAssert(beforeChild->isAnonymousBlock()); + KHTMLAssert(beforeChild->parent() == this); + } + } + else if (!m_childrenInline && !newChild->isFloatingOrPositioned()) + { + // If we're inserting an inline child but all of our children are blocks, then we have to make sure + // it is put into an anomyous block box. We try to use an existing anonymous box if possible, otherwise + // a new one is created and inserted into our list of children in the appropriate position. + if (newChild->isInline()) { + if (beforeChild) { + if ( beforeChild->previousSibling() && beforeChild->previousSibling()->isAnonymousBlock() ) { + beforeChild->previousSibling()->addChild(newChild); + return; + } + } + else { + if ( m_last && m_last->isAnonymousBlock() ) { + m_last->addChild(newChild); + return; + } + } + + // no suitable existing anonymous box - create a new one + RenderBlock* newBox = createAnonymousBlock(); + RenderBox::addChild(newBox,beforeChild); + newBox->addChild(newChild); + + //the above may actually destroy newBox in case an anonymous + //table got created, and made the anonymous block redundant. + //so look up what to hide indirectly. + RenderObject* toHide = newChild; + while (toHide->parent() != this) + toHide = toHide->parent(); + + toHide->setPos(toHide->xPos(), -500000); + return; + } + else { + // We are adding another block child... if the current last child is an anonymous box + // then it needs to be closed. + // ### get rid of the closing thing altogether this will only work during initial parsing + if (lastChild() && lastChild()->isAnonymous()) { + lastChild()->close(); + } + } + } + + RenderBox::addChild(newChild,beforeChild); + // ### care about aligned stuff + + if ( madeBoxesNonInline ) + removeLeftoverAnonymousBoxes(); +} + +static void getInlineRun(RenderObject* start, RenderObject* stop, + RenderObject*& inlineRunStart, + RenderObject*& inlineRunEnd) +{ + // Beginning at |start| we find the largest contiguous run of inlines that + // we can. We denote the run with start and end points, |inlineRunStart| + // and |inlineRunEnd|. Note that these two values may be the same if + // we encounter only one inline. + // + // We skip any non-inlines we encounter as long as we haven't found any + // inlines yet. + // + // |stop| indicates a non-inclusive stop point. Regardless of whether |stop| + // is inline or not, we will not include it in a run with inlines before it. It's as though we encountered + // a non-inline. + + RenderObject * curr = start; + bool sawInline; + do { + while (curr && !(curr->isInline() || curr->isFloatingOrPositioned())) + curr = curr->nextSibling(); + + inlineRunStart = inlineRunEnd = curr; + + if (!curr) + return; // No more inline children to be found. + + sawInline = curr->isInline(); + + curr = curr->nextSibling(); + while (curr && (curr->isInline() || curr->isFloatingOrPositioned()) && (curr != stop)) { + inlineRunEnd = curr; + if (curr->isInline()) + sawInline = true; + curr = curr->nextSibling(); + } + } while (!sawInline); + +} + +void RenderBlock::makeChildrenNonInline(RenderObject *insertionPoint) +{ + // makeChildrenNonInline takes a block whose children are *all* inline and it + // makes sure that inline children are coalesced under anonymous + // blocks. If |insertionPoint| is defined, then it represents the insertion point for + // the new block child that is causing us to have to wrap all the inlines. This + // means that we cannot coalesce inlines before |insertionPoint| with inlines following + // |insertionPoint|, because the new child is going to be inserted in between the inlines, + // splitting them. + KHTMLAssert(isReplacedBlock() || !isInline()); + KHTMLAssert(!insertionPoint || insertionPoint->parent() == this); + + m_childrenInline = false; + + RenderObject *child = firstChild(); + + while (child) { + RenderObject *inlineRunStart, *inlineRunEnd; + getInlineRun(child, insertionPoint, inlineRunStart, inlineRunEnd); + + if (!inlineRunStart) + break; + + child = inlineRunEnd->nextSibling(); + + RenderBlock* box = createAnonymousBlock(); + insertChildNode(box, inlineRunStart); + RenderObject* o = inlineRunStart; + while(o != inlineRunEnd) + { + RenderObject* no = o; + o = no->nextSibling(); + box->appendChildNode(removeChildNode(no)); + } + box->appendChildNode(removeChildNode(inlineRunEnd)); + box->close(); + box->setPos(box->xPos(), -500000); + } +} + +void RenderBlock::makePageBreakAvoidBlocks() +{ + KHTMLAssert(!childrenInline()); + KHTMLAssert(canvas()->pagedMode()); + + RenderObject *breakAfter = firstChild(); + RenderObject *breakBefore = breakAfter ? breakAfter->nextSibling() : 0; + + RenderBlock* pageRun = 0; + + // ### Should follow margin-collapsing rules, skipping self-collapsing blocks + // and exporting page-breaks from first/last child when collapsing with parent margin. + while (breakAfter) { + if (breakAfter->isRenderBlock() && !breakAfter->childrenInline()) + static_cast<RenderBlock*>(breakAfter)->makePageBreakAvoidBlocks(); + EPageBreak pbafter = breakAfter->style()->pageBreakAfter(); + EPageBreak pbbefore = breakBefore ? breakBefore->style()->pageBreakBefore() : PBALWAYS; + if ((pbafter == PBAVOID && pbbefore == PBAVOID) || + (pbafter == PBAVOID && pbbefore == PBAUTO) || + (pbafter == PBAUTO && pbbefore == PBAVOID)) + { + if (!pageRun) { + pageRun = createAnonymousBlock(); + pageRun->m_avoidPageBreak = true; + pageRun->setChildrenInline(false); + } + pageRun->appendChildNode(removeChildNode(breakAfter)); + } else + { + if (pageRun) { + pageRun->appendChildNode(removeChildNode(breakAfter)); + pageRun->close(); + insertChildNode(pageRun, breakBefore); + pageRun = 0; + } + } + breakAfter = breakBefore; + breakBefore = breakBefore ? breakBefore->nextSibling() : 0; + } + + // recurse into positioned block children as well. + if (m_positionedObjects) { + QPtrListIterator<RenderObject> it(*m_positionedObjects); + for ( ; it.current(); ++it ) { + if (it.current()->isRenderBlock() && !it.current()->childrenInline()) { + static_cast<RenderBlock*>(it.current())->makePageBreakAvoidBlocks(); + } + } + } + + // recurse into floating block children. + if (m_floatingObjects) { + QPtrListIterator<FloatingObject> it(*m_floatingObjects); + for ( ; it.current(); ++it ) { + if (it.current()->node->isRenderBlock() && !it.current()->node->childrenInline()) { + static_cast<RenderBlock*>(it.current()->node)->makePageBreakAvoidBlocks(); + } + } + } +} + +void RenderBlock::removeChild(RenderObject *oldChild) +{ + // If this child is a block, and if our previous and next siblings are + // both anonymous blocks with inline content, then we can go ahead and + // fold the inline content back together. + RenderObject* prev = oldChild->previousSibling(); + RenderObject* next = oldChild->nextSibling(); + bool mergedBlocks = false; + if (document()->renderer() && !isInline() && !oldChild->isInline() && !oldChild->continuation() && + prev && prev->isAnonymousBlock() && prev->childrenInline() && + next && next->isAnonymousBlock() && next->childrenInline()) { + // Take all the children out of the |next| block and put them in + // the |prev| block. + RenderObject* o = next->firstChild(); + while (o) { + RenderObject* no = o; + o = no->nextSibling(); + prev->appendChildNode(next->removeChildNode(no)); + no->setNeedsLayoutAndMinMaxRecalc(); + } + prev->setNeedsLayoutAndMinMaxRecalc(); + + // Nuke the now-empty block. + next->detach(); + + mergedBlocks = true; + } + + RenderFlow::removeChild(oldChild); + + if (mergedBlocks && prev && !prev->previousSibling() && !prev->nextSibling()) { + // The remerge has knocked us down to containing only a single anonymous + // box. We can go ahead and pull the content right back up into our + // box. + RenderObject* anonBlock = removeChildNode(prev); + m_childrenInline = true; + RenderObject* o = anonBlock->firstChild(); + while (o) { + RenderObject* no = o; + o = no->nextSibling(); + appendChildNode(anonBlock->removeChildNode(no)); + no->setNeedsLayoutAndMinMaxRecalc(); + } + + // Nuke the now-empty block. + anonBlock->detach(); + } +} + +bool RenderBlock::isSelfCollapsingBlock() const +{ + // We are not self-collapsing if we + // (a) have a non-zero height according to layout (an optimization to avoid wasting time) + // (b) are a table, + // (c) have border/padding, + // (d) have a min-height + if (m_height > 0 || + isTable() || (borderBottom() + paddingBottom() + borderTop() + paddingTop()) != 0 || + style()->minHeight().value() > 0) + return false; + + bool hasAutoHeight = style()->height().isVariable(); + if (style()->height().isPercent() && !style()->htmlHacks()) { + hasAutoHeight = true; + for (RenderBlock* cb = containingBlock(); !cb->isCanvas(); cb = cb->containingBlock()) { + if (cb->style()->height().isFixed() || cb->isTableCell()) + hasAutoHeight = false; + } + } + + // If the height is 0 or auto, then whether or not we are a self-collapsing block depends + // on whether we have content that is all self-collapsing or not. + if (hasAutoHeight || ((style()->height().isFixed() || style()->height().isPercent()) && style()->height().value() == 0)) { + // If the block has inline children, see if we generated any line boxes. If we have any + // line boxes, then we can't be self-collapsing, since we have content. + if (childrenInline()) + return !firstLineBox(); + + // Whether or not we collapse is dependent on whether all our normal flow children + // are also self-collapsing. + for (RenderObject* child = firstChild(); child; child = child->nextSibling()) { + if (child->isFloatingOrPositioned()) + continue; + if (!child->isSelfCollapsingBlock()) + return false; + } + return true; + } + return false; +} + +void RenderBlock::layout() +{ + // Table cells call layoutBlock directly, so don't add any logic here. Put code into + // layoutBlock(). + layoutBlock(false); +} + +void RenderBlock::layoutBlock(bool relayoutChildren) +{ + if (isInline() && !isReplacedBlock()) { + setNeedsLayout(false); + return; + } + // kdDebug( 6040 ) << renderName() << " " << this << "::layoutBlock() start" << endl; + // QTime t; + // t.start(); + KHTMLAssert( needsLayout() ); + KHTMLAssert( minMaxKnown() ); + + if (canvas()->pagedMode()) relayoutChildren = true; + + if (!relayoutChildren && posChildNeedsLayout() && !normalChildNeedsLayout() && !selfNeedsLayout()) { + // All we have to is lay out our positioned objects. + layoutPositionedObjects(relayoutChildren); + if (hasOverflowClip()) + m_layer->checkScrollbarsAfterLayout(); + setNeedsLayout(false); + return; + } + + if (markedForRepaint()) { + repaintDuringLayout(); + setMarkedForRepaint(false); + } + + int oldWidth = m_width; + + calcWidth(); + m_overflowWidth = m_width; + m_overflowLeft = 0; + if (style()->direction() == LTR ) + { + int cw=0; + if (style()->textIndent().isPercent()) + cw = containingBlock()->contentWidth(); + m_overflowLeft = kMin(0, style()->textIndent().minWidth(cw)); + } + + if ( oldWidth != m_width ) + relayoutChildren = true; + + // kdDebug( 6040 ) << floatingObjects << "," << oldWidth << "," + // << m_width << ","<< needsLayout() << "," << isAnonymousBox() << "," + // << overhangingContents() << "," << isPositioned() << endl; + +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(RenderBlock) " << this << " ::layout() width=" << m_width << ", needsLayout=" << needsLayout() << endl; + if(containingBlock() == static_cast<RenderObject *>(this)) + kdDebug( 6040 ) << renderName() << ": containingBlock == this" << endl; +#endif + + clearFloats(); + + int previousHeight = m_height; + m_height = 0; + m_overflowHeight = 0; + m_clearStatus = CNONE; + + // We use four values, maxTopPos, maxPosNeg, maxBottomPos, and maxBottomNeg, to track + // our current maximal positive and negative margins. These values are used when we + // are collapsed with adjacent blocks, so for example, if you have block A and B + // collapsing together, then you'd take the maximal positive margin from both A and B + // and subtract it from the maximal negative margin from both A and B to get the + // true collapsed margin. This algorithm is recursive, so when we finish layout() + // our block knows its current maximal positive/negative values. + // + // Start out by setting our margin values to our current margins. Table cells have + // no margins, so we don't fill in the values for table cells. + if (!isTableCell()) { + initMaxMarginValues(); + + m_topMarginQuirk = style()->marginTop().isQuirk(); + m_bottomMarginQuirk = style()->marginBottom().isQuirk(); + + if (element() && element()->id() == ID_FORM && static_cast<HTMLFormElementImpl*>(element())->isMalformed()) + // See if this form is malformed (i.e., unclosed). If so, don't give the form + // a bottom margin. + m_maxBottomPosMargin = m_maxBottomNegMargin = 0; + } + + if (scrollsOverflow() && m_layer) { + // For overflow:scroll blocks, ensure we have both scrollbars in place always. + if (style()->overflowX() == OSCROLL) + m_layer->showScrollbar( Qt::Horizontal, true ); + if (style()->overflowY() == OSCROLL) + m_layer->showScrollbar( Qt::Vertical, true ); + } + + setContainsPageBreak(false); + + if (childrenInline()) + layoutInlineChildren( relayoutChildren ); + else + layoutBlockChildren( relayoutChildren ); + + // Expand our intrinsic height to encompass floats. + int toAdd = borderBottom() + paddingBottom(); + if (m_layer && scrollsOverflowX() && style()->height().isVariable()) + toAdd += m_layer->horizontalScrollbarHeight(); + if ( hasOverhangingFloats() && (isFloatingOrPositioned() || flowAroundFloats()) ) + m_overflowHeight = m_height = floatBottom() + toAdd; + + int oldHeight = m_height; + calcHeight(); + if (oldHeight != m_height) { + m_overflowHeight -= toAdd; + if (m_layer && scrollsOverflowY()) { + // overflow-height only includes padding-bottom when it scrolls + m_overflowHeight += paddingBottom(); + } + // If the block got expanded in size, then increase our overflowheight to match. + if (m_overflowHeight < m_height) + m_overflowHeight = m_height; + } + if (previousHeight != m_height) + relayoutChildren = true; + + if (isTableCell()) { + // Table cells need to grow to accommodate both overhanging floats and + // blocks that have overflowed content. + // Check for an overhanging float first. + // FIXME: This needs to look at the last flow, not the last child. + if (lastChild() && lastChild()->hasOverhangingFloats() && !lastChild()->hasOverflowClip()) { + KHTMLAssert(lastChild()->isRenderBlock()); + m_height = lastChild()->yPos() + static_cast<RenderBlock*>(lastChild())->floatBottom(); + m_height += borderBottom() + paddingBottom(); + } + + if (m_overflowHeight > m_height && !hasOverflowClip()) + m_height = m_overflowHeight + borderBottom() + paddingBottom(); + } + + if( hasOverhangingFloats() && ((isFloating() && style()->height().isVariable()) || isTableCell())) { + m_height = floatBottom(); + m_height += borderBottom() + paddingBottom(); + } + + if (canvas()->pagedMode()) { +#ifdef PAGE_DEBUG + kdDebug(6040) << renderName() << " Page Bottom: " << pageTopAfter(0) << endl; + kdDebug(6040) << renderName() << " Bottom: " << m_height << endl; +#endif + bool needsPageBreak = false; + int xpage = crossesPageBreak(0, m_height); + if (xpage) { + needsPageBreak = true; +#ifdef PAGE_DEBUG + kdDebug( 6040 ) << renderName() << " crosses to page " << xpage << endl; +#endif + } + if (needsPageBreak && !containsPageBreak()) { + setNeedsPageClear(true); +#ifdef PAGE_DEBUG + kdDebug( 6040 ) << renderName() << " marked for page-clear" << endl; +#endif + } + } + + layoutPositionedObjects( relayoutChildren ); + + // Always ensure our overflow width/height are at least as large as our width/height. + m_overflowWidth = kMax(m_overflowWidth, (int)m_width); + m_overflowHeight = kMax(m_overflowHeight, m_height); + + // Update our scrollbars if we're overflow:auto/scroll now that we know if + // we overflow or not. + if (hasOverflowClip() && m_layer) + m_layer->checkScrollbarsAfterLayout(); + + setNeedsLayout(false); +} + +void RenderBlock::adjustPositionedBlock(RenderObject* child, const MarginInfo& marginInfo) +{ + if (child->isBox() && child->hasStaticX()) { + if (style()->direction() == LTR) + static_cast<RenderBox*>(child)->setStaticX(borderLeft() + paddingLeft()); + else + static_cast<RenderBox*>(child)->setStaticX(borderRight() + paddingRight()); + } + + if (child->isBox() && child->hasStaticY()) { + int y = m_height; + if (!marginInfo.canCollapseWithTop()) { + child->calcVerticalMargins(); + int marginTop = child->marginTop(); + int collapsedTopPos = marginInfo.posMargin(); + int collapsedTopNeg = marginInfo.negMargin(); + if (marginTop > 0) { + if (marginTop > collapsedTopPos) + collapsedTopPos = marginTop; + } else { + if (-marginTop > collapsedTopNeg) + collapsedTopNeg = -marginTop; + } + y += (collapsedTopPos - collapsedTopNeg) - marginTop; + } + static_cast<RenderBox*>(child)->setStaticY(y); + } +} + +void RenderBlock::adjustFloatingBlock(const MarginInfo& marginInfo) +{ + // The float should be positioned taking into account the bottom margin + // of the previous flow. We add that margin into the height, get the + // float positioned properly, and then subtract the margin out of the + // height again. In the case of self-collapsing blocks, we always just + // use the top margins, since the self-collapsing block collapsed its + // own bottom margin into its top margin. + // + // Note also that the previous flow may collapse its margin into the top of + // our block. If this is the case, then we do not add the margin in to our + // height when computing the position of the float. This condition can be tested + // for by simply calling canCollapseWithTop. See + // http://www.hixie.ch/tests/adhoc/css/box/block/margin-collapse/046.html for + // an example of this scenario. + int marginOffset = marginInfo.canCollapseWithTop() ? 0 : marginInfo.margin(); + m_height += marginOffset; + positionNewFloats(); + m_height -= marginOffset; +} + +RenderObject* RenderBlock::handleSpecialChild(RenderObject* child, const MarginInfo& marginInfo, CompactInfo& compactInfo, bool& handled) +{ + // Handle positioned children first. + RenderObject* next = handlePositionedChild(child, marginInfo, handled); + if (handled) return next; + + // Handle floating children next. + next = handleFloatingChild(child, marginInfo, handled); + if (handled) return next; + + // See if we have a compact element. If we do, then try to tuck the compact element into the margin space of the next block. + next = handleCompactChild(child, compactInfo, marginInfo, handled); + if (handled) return next; + + // Finally, see if we have a run-in element. + return handleRunInChild(child, handled); +} + +RenderObject* RenderBlock::handlePositionedChild(RenderObject* child, const MarginInfo& marginInfo, bool& handled) +{ + if (child->isPositioned()) { + handled = true; + child->containingBlock()->insertPositionedObject(child); + adjustPositionedBlock(child, marginInfo); + return child->nextSibling(); + } + return 0; +} + +RenderObject* RenderBlock::handleFloatingChild(RenderObject* child, const MarginInfo& marginInfo, bool& handled) +{ + if (child->isFloating()) { + handled = true; + insertFloatingObject(child); + adjustFloatingBlock(marginInfo); + return child->nextSibling(); + } + return 0; +} + +static inline bool isAnonymousWhitespace( RenderObject* o ) { + if (!o->isAnonymous()) + return false; + RenderObject *fc = o->firstChild(); + return fc && fc == o->lastChild() && fc->isText() && static_cast<RenderText *>(fc)->stringLength() == 1 && + static_cast<RenderText *>(fc)->text()[0].unicode() == ' '; +} + +RenderObject* RenderBlock::handleCompactChild(RenderObject* child, CompactInfo& compactInfo, const MarginInfo& marginInfo, bool& handled) +{ + // FIXME: We only deal with one compact at a time. It is unclear what should be + // done if multiple contiguous compacts are encountered. For now we assume that + // compact A followed by another compact B should simply be treated as block A. + if (child->isCompact() && !compactInfo.compact() && (child->childrenInline() || child->isReplaced())) { + // Get the next non-positioned/non-floating RenderBlock. + RenderObject* next = child->nextSibling(); + RenderObject* curr = next; + while (curr && (curr->isFloatingOrPositioned() || isAnonymousWhitespace(curr)) ) + curr = curr->nextSibling(); + if (curr && curr->isRenderBlock() && !curr->isCompact() && !curr->isRunIn()) { + curr->calcWidth(); // So that horizontal margins are correct. + // Need to compute margins for the child as though it is a block. + child->style()->setDisplay(BLOCK); + child->calcWidth(); + child->style()->setDisplay(COMPACT); + + int childMargins = child->marginLeft() + child->marginRight(); + int margin = style()->direction() == LTR ? curr->marginLeft() : curr->marginRight(); + if (margin >= (childMargins + child->maxWidth())) { + // The compact will fit in the margin. + handled = true; + compactInfo.set(child, curr); + child->layoutIfNeeded(); + int off = marginInfo.margin(); + m_height += off + curr->marginTop() < child->marginTop() ? + child->marginTop() - curr->marginTop() -off: 0; + + child->setPos(0,0); // This position will be updated to reflect the compact's + // desired position and the line box for the compact will + // pick that position up. + return next; + } + } + } + return 0; +} + +void RenderBlock::adjustSizeForCompactIfNeeded(RenderObject* child, CompactInfo& compactInfo) +{ + // if the compact is bigger than the block it was run into + // then "this" block should take the height of the compact + if (compactInfo.matches(child)) { + // We have a compact child to squeeze in. + RenderObject* compactChild = compactInfo.compact(); + if (compactChild->height() > child->height()) + m_height += compactChild->height() - child->height(); + } +} + +void RenderBlock::insertCompactIfNeeded(RenderObject* child, CompactInfo& compactInfo) +{ + if (compactInfo.matches(child)) { + // We have a compact child to squeeze in. + RenderObject* compactChild = compactInfo.compact(); + int compactXPos = borderLeft() + paddingLeft() + compactChild->marginLeft(); + if (style()->direction() == RTL) { + compactChild->calcWidth(); // have to do this because of the capped maxwidth + compactXPos = width() - borderRight() - paddingRight() - + compactChild->width() - compactChild->marginRight(); + } + + int compactYPos = child->yPos() + child->borderTop() + child->paddingTop() + - compactChild->paddingTop() - compactChild->borderTop(); + int adj = 0; + KHTMLAssert(child->isRenderBlock()); + InlineRunBox *b = static_cast<RenderBlock*>(child)->firstLineBox(); + InlineRunBox *c = static_cast<RenderBlock*>(compactChild)->firstLineBox(); + if (b && c) { + // adjust our vertical position + int vpos = compactChild->getVerticalPosition( true, child ); + if (vpos == PositionBottom) + adj = b->height() > c->height() ? (b->height() + b->yPos() - c->height() - c->yPos()) : 0; + else if (vpos == PositionTop) + adj = b->yPos() - c->yPos(); + else + adj = vpos; + compactYPos += adj; + } + Length newLineHeight( kMax(compactChild->lineHeight(true)+adj, (int)child->lineHeight(true)), khtml::Fixed); + child->style()->setLineHeight( newLineHeight ); + child->setNeedsLayout( true, false ); + child->layout(); + + compactChild->setPos(compactXPos, compactYPos); // Set the x position. + compactInfo.clear(); + } +} + +RenderObject* RenderBlock::handleRunInChild(RenderObject* child, bool& handled) +{ + // See if we have a run-in element with inline children. If the + // children aren't inline, then just treat the run-in as a normal + // block. + if (child->isRunIn() && (child->childrenInline() || child->isReplaced())) { + // Get the next non-positioned/non-floating RenderBlock. + RenderObject* curr = child->nextSibling(); + while (curr && (curr->isFloatingOrPositioned() || isAnonymousWhitespace(curr)) ) + curr = curr->nextSibling(); + if (curr && (curr->isRenderBlock() && curr->childrenInline() && !curr->isCompact() && !curr->isRunIn())) { + // The block acts like an inline, so just null out its + // position. + handled = true; + child->setInline(true); + child->setPos(0,0); + + // Remove the child. + RenderObject* next = child->nextSibling(); + removeChildNode(child); + + // Now insert the child under |curr|. + curr->insertChildNode(child, curr->firstChild()); + return next; + } + } + return 0; +} + +void RenderBlock::collapseMargins(RenderObject* child, MarginInfo& marginInfo, int yPosEstimate) +{ + // Get our max pos and neg top margins. + int posTop = child->maxTopMargin(true); + int negTop = child->maxTopMargin(false); + + // For self-collapsing blocks, collapse our bottom margins into our + // top to get new posTop and negTop values. + if (child->isSelfCollapsingBlock()) { + posTop = kMax(posTop, (int)child->maxBottomMargin(true)); + negTop = kMax(negTop, (int)child->maxBottomMargin(false)); + } + + // See if the top margin is quirky. We only care if this child has + // margins that will collapse with us. + bool topQuirk = child->isTopMarginQuirk() /*|| style()->marginTopCollapse() == MDISCARD*/; + + if (marginInfo.canCollapseWithTop()) { + // This child is collapsing with the top of the + // block. If it has larger margin values, then we need to update + // our own maximal values. + if (!style()->htmlHacks() || !marginInfo.quirkContainer() || !topQuirk) { + m_maxTopPosMargin = kMax(posTop, (int)m_maxTopPosMargin); + m_maxTopNegMargin = kMax(negTop, (int)m_maxTopNegMargin); + } + + // The minute any of the margins involved isn't a quirk, don't + // collapse it away, even if the margin is smaller (www.webreference.com + // has an example of this, a <dt> with 0.8em author-specified inside + // a <dl> inside a <td>. + if (!marginInfo.determinedTopQuirk() && !topQuirk && (posTop-negTop)) { + m_topMarginQuirk = false; + marginInfo.setDeterminedTopQuirk(true); + } + + if (!marginInfo.determinedTopQuirk() && topQuirk && marginTop() == 0) + // We have no top margin and our top child has a quirky margin. + // We will pick up this quirky margin and pass it through. + // This deals with the <td><div><p> case. + // Don't do this for a block that split two inlines though. You do + // still apply margins in this case. + m_topMarginQuirk = true; + } + + if (marginInfo.quirkContainer() && marginInfo.atTopOfBlock() && (posTop - negTop)) + marginInfo.setTopQuirk(topQuirk); + + int ypos = m_height; + if (child->isSelfCollapsingBlock()) { + // This child has no height. We need to compute our + // position before we collapse the child's margins together, + // so that we can get an accurate position for the zero-height block. + int collapsedTopPos = kMax(marginInfo.posMargin(), (int)child->maxTopMargin(true)); + int collapsedTopNeg = kMax(marginInfo.negMargin(), (int)child->maxTopMargin(false)); + marginInfo.setMargin(collapsedTopPos, collapsedTopNeg); + + // Now collapse the child's margins together, which means examining our + // bottom margin values as well. + marginInfo.setPosMarginIfLarger(child->maxBottomMargin(true)); + marginInfo.setNegMarginIfLarger(child->maxBottomMargin(false)); + + if (!marginInfo.canCollapseWithTop()) + // We need to make sure that the position of the self-collapsing block + // is correct, since it could have overflowing content + // that needs to be positioned correctly (e.g., a block that + // had a specified height of 0 but that actually had subcontent). + ypos = m_height + collapsedTopPos - collapsedTopNeg; + } + else { +#ifdef APPLE_CHANGES + if (child->style()->marginTopCollapse() == MSEPARATE) { + m_height += marginInfo.margin() + child->marginTop(); + ypos = m_height; + } + else +#endif + if (!marginInfo.atTopOfBlock() || + (!marginInfo.canCollapseTopWithChildren() + && (!style()->htmlHacks() || !marginInfo.quirkContainer() || !marginInfo.topQuirk()))) { + // We're collapsing with a previous sibling's margins and not + // with the top of the block. + m_height += kMax(marginInfo.posMargin(), posTop) - kMax(marginInfo.negMargin(), negTop); + ypos = m_height; + } + + marginInfo.setPosMargin(child->maxBottomMargin(true)); + marginInfo.setNegMargin(child->maxBottomMargin(false)); + + if (marginInfo.margin()) + marginInfo.setBottomQuirk(child->isBottomMarginQuirk() /*|| style()->marginBottomCollapse() == MDISCARD*/); + + marginInfo.setSelfCollapsingBlockClearedFloat(false); + } + + child->setPos(child->xPos(), ypos); + if (ypos != yPosEstimate) { + if (child->style()->width().isPercent() && child->usesLineWidth()) + // The child's width is a percentage of the line width. + // When the child shifts to clear an item, its width can + // change (because it has more available line width). + // So go ahead and mark the item as dirty. + child->setChildNeedsLayout(true); + + if (!child->flowAroundFloats() && child->hasFloats()) + child->markAllDescendantsWithFloatsForLayout(); + + // Our guess was wrong. Make the child lay itself out again. + child->layoutIfNeeded(); + } +} + +void RenderBlock::clearFloatsIfNeeded(RenderObject* child, MarginInfo& marginInfo, int oldTopPosMargin, int oldTopNegMargin) +{ + int heightIncrease = getClearDelta(child); + if (heightIncrease) { + // The child needs to be lowered. Move the child so that it just clears the float. + child->setPos(child->xPos(), child->yPos() + heightIncrease); + + // Increase our height by the amount we had to clear. + bool selfCollapsing = child->isSelfCollapsingBlock(); + if (!selfCollapsing) + m_height += heightIncrease; + else { + // For self-collapsing blocks that clear, they may end up collapsing + // into the bottom of the parent block. We simulate this behavior by + // setting our positive margin value to compensate for the clear. + marginInfo.setPosMargin(kMax(0, child->yPos() - m_height)); + marginInfo.setNegMargin(0); + marginInfo.setSelfCollapsingBlockClearedFloat(true); + } + + if (marginInfo.canCollapseWithTop()) { + // We can no longer collapse with the top of the block since a clear + // occurred. The empty blocks collapse into the cleared block. + // FIXME: This isn't quite correct. Need clarification for what to do + // if the height the cleared block is offset by is smaller than the + // margins involved. + m_maxTopPosMargin = oldTopPosMargin; + m_maxTopNegMargin = oldTopNegMargin; + marginInfo.setAtTopOfBlock(false); + } + + // If our value of clear caused us to be repositioned vertically to be + // underneath a float, we might have to do another layout to take into account + // the extra space we now have available. + if (!selfCollapsing && !child->style()->width().isFixed() && child->usesLineWidth()) + // The child's width is a percentage of the line width. + // When the child shifts to clear an item, its width can + // change (because it has more available line width). + // So go ahead and mark the item as dirty. + child->setChildNeedsLayout(true); + if (!child->flowAroundFloats() && child->hasFloats()) + child->markAllDescendantsWithFloatsForLayout(); + child->layoutIfNeeded(); + } +} + +bool RenderBlock::canClear(RenderObject *child, PageBreakLevel level) +{ + KHTMLAssert(child->parent() && child->parent() == this); + KHTMLAssert(canvas()->pagedMode()); + + // Positioned elements cannot be moved. Only normal flow and floating. + if (child->isPositioned() || child->isRelPositioned()) return false; + + switch(level) { + case PageBreakNormal: + // check page-break-inside: avoid + if (!style()->pageBreakInside()) + // we cannot, but can our parent? + if(!parent()->canClear(this, level)) return false; + case PageBreakHarder: + // check page-break-after/before: avoid + if (m_avoidPageBreak) + // we cannot, but can our parent? + if(!parent()->canClear(this, level)) return false; + case PageBreakForced: + // child is larger than page-height and is forced to break + if(child->height() > canvas()->pageHeight()) return false; + return true; + } + assert(false); + return false; +} + +void RenderBlock::clearPageBreak(RenderObject* child, int pageBottom) +{ + KHTMLAssert(child->parent() && child->parent() == this); + KHTMLAssert(canvas()->pagedMode()); + + if (child->yPos() >= pageBottom) return; + + int heightIncrease = 0; + + heightIncrease = pageBottom - child->yPos(); + + // ### should never happen, canClear should have been called to detect it. + if (child->height() > canvas()->pageHeight()) { + kdDebug(6040) << "### child is too large to clear: " << child->height() << " > " << canvas()->pageHeight() << endl; + return; + } + + // The child needs to be lowered. Move the child so that it just clears the break. + child->setPos(child->xPos(), pageBottom); + +#ifdef PAGE_DEBUG + kdDebug(6040) << "Cleared block " << heightIncrease << "px" << endl; +#endif + + // Increase our height by the amount we had to clear. + m_height += heightIncrease; + + // We might have to do another layout to take into account + // the extra space we now have available. + if (!child->style()->width().isFixed() && child->usesLineWidth()) + // The child's width is a percentage of the line width. + // When the child shifts to clear a page-break, its width can + // change (because it has more available line width). + // So go ahead and mark the item as dirty. + child->setChildNeedsLayout(true); + if (!child->flowAroundFloats() && child->hasFloats()) + child->markAllDescendantsWithFloatsForLayout(); + if (child->containsPageBreak()) + child->setNeedsLayout(true); + child->layoutIfNeeded(); + + child->setAfterPageBreak(true); +} + +int RenderBlock::estimateVerticalPosition(RenderObject* child, const MarginInfo& marginInfo) +{ + // FIXME: We need to eliminate the estimation of vertical position, because + // when it's wrong we sometimes trigger a pathological relayout if there are + // intruding floats. + int yPosEstimate = m_height; + if (!marginInfo.canCollapseWithTop()) { + int childMarginTop = child->selfNeedsLayout() ? child->marginTop() : child->collapsedMarginTop(); + yPosEstimate += kMax(marginInfo.margin(), childMarginTop); + } + return yPosEstimate; +} + +void RenderBlock::determineHorizontalPosition(RenderObject* child) +{ + if (style()->direction() == LTR) { + int xPos = borderLeft() + paddingLeft(); + + // Add in our left margin. + int chPos = xPos + child->marginLeft(); + + // Some objects (e.g., tables, horizontal rules, overflow:auto blocks) avoid floats. They need + // to shift over as necessary to dodge any floats that might get in the way. + if (child->flowAroundFloats()) { + int leftOff = leftOffset(m_height); + if (style()->textAlign() != KHTML_CENTER && !child->style()->marginLeft().isVariable()) { + if (child->marginLeft() < 0) + leftOff += child->marginLeft(); + chPos = kMax(chPos, leftOff); // Let the float sit in the child's margin if it can fit. + } + else if (leftOff != xPos) { + // The object is shifting right. The object might be centered, so we need to + // recalculate our horizontal margins. Note that the containing block content + // width computation will take into account the delta between |leftOff| and |xPos| + // so that we can just pass the content width in directly to the |calcHorizontalMargins| + // function. + static_cast<RenderBox*>(child)->calcHorizontalMargins(child->style()->marginLeft(), child->style()->marginRight(), lineWidth(child->yPos())); + chPos = leftOff + child->marginLeft(); + } + } + + child->setPos(chPos, child->yPos()); + } else { + int xPos = m_width - borderRight() - paddingRight(); + if (m_layer && scrollsOverflowY()) + xPos -= m_layer->verticalScrollbarWidth(); + int chPos = xPos - (child->width() + child->marginRight()); + if (child->flowAroundFloats()) { + int rightOff = rightOffset(m_height); + if (style()->textAlign() != KHTML_CENTER && !child->style()->marginRight().isVariable()) { + if (child->marginRight() < 0) + rightOff -= child->marginRight(); + chPos = kMin(chPos, rightOff - child->width()); // Let the float sit in the child's margin if it can fit. + } else if (rightOff != xPos) { + // The object is shifting left. The object might be centered, so we need to + // recalculate our horizontal margins. Note that the containing block content + // width computation will take into account the delta between |rightOff| and |xPos| + // so that we can just pass the content width in directly to the |calcHorizontalMargins| + // function. + static_cast<RenderBox*>(child)->calcHorizontalMargins(child->style()->marginLeft(), child->style()->marginRight(), lineWidth(child->yPos())); + chPos = rightOff - child->marginRight() - child->width(); + } + } + child->setPos(chPos, child->yPos()); + } +} + +void RenderBlock::setCollapsedBottomMargin(const MarginInfo& marginInfo) +{ + if (marginInfo.canCollapseWithBottom() && !marginInfo.canCollapseWithTop()) { + // Update our max pos/neg bottom margins, since we collapsed our bottom margins + // with our children. + m_maxBottomPosMargin = kMax((int)m_maxBottomPosMargin, marginInfo.posMargin()); + m_maxBottomNegMargin = kMax((int)m_maxBottomNegMargin, marginInfo.negMargin()); + + if (!marginInfo.bottomQuirk()) + m_bottomMarginQuirk = false; + + if (marginInfo.bottomQuirk() && marginBottom() == 0) + // We have no bottom margin and our last child has a quirky margin. + // We will pick up this quirky margin and pass it through. + // This deals with the <td><div><p> case. + m_bottomMarginQuirk = true; + } +} + +void RenderBlock::handleBottomOfBlock(int top, int bottom, MarginInfo& marginInfo) +{ + // If our last flow was a self-collapsing block that cleared a float, then we don't + // collapse it with the bottom of the block. + if (!marginInfo.selfCollapsingBlockClearedFloat()) + marginInfo.setAtBottomOfBlock(true); + + // If we can't collapse with children then go ahead and add in the bottom margin. + if (!marginInfo.canCollapseWithBottom() && !marginInfo.canCollapseWithTop() + && (!style()->htmlHacks() || !marginInfo.quirkContainer() || !marginInfo.bottomQuirk())) + m_height += marginInfo.margin(); + + // Now add in our bottom border/padding. + m_height += bottom; + + // Negative margins can cause our height to shrink below our minimal height (border/padding). + // If this happens, ensure that the computed height is increased to the minimal height. + m_height = kMax(m_height, top + bottom); + + // Always make sure our overflow height is at least our height. + m_overflowHeight = kMax(m_height, m_overflowHeight); + + // Update our bottom collapsed margin info. + setCollapsedBottomMargin(marginInfo); +} + +void RenderBlock::layoutBlockChildren( bool relayoutChildren ) +{ +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << " layoutBlockChildren( " << this <<" ), relayoutChildren="<< relayoutChildren << endl; +#endif + + int top = borderTop() + paddingTop(); + int bottom = borderBottom() + paddingBottom(); + if (m_layer && scrollsOverflowX() && style()->height().isVariable()) + bottom += m_layer->horizontalScrollbarHeight(); + + m_height = m_overflowHeight = top; + + // The margin struct caches all our current margin collapsing state. + // The compact struct caches state when we encounter compacts. + MarginInfo marginInfo(this, top, bottom); + CompactInfo compactInfo; + + // Fieldsets need to find their legend and position it inside the border of the object. + // The legend then gets skipped during normal layout. + RenderObject* legend = layoutLegend(relayoutChildren); + + PageBreakInfo pageBreakInfo(pageTopAfter(0)); + + RenderObject* child = firstChild(); + while( child != 0 ) + { + if (legend == child) { + child = child->nextSibling(); + continue; // Skip the legend, since it has already been positioned up in the fieldset's border. + } + + int oldTopPosMargin = m_maxTopPosMargin; + int oldTopNegMargin = m_maxTopNegMargin; + + // make sure we relayout children if we need it. + if (!child->isPositioned() && (relayoutChildren || + (child->isReplaced() && (child->style()->width().isPercent() || child->style()->height().isPercent())) || + (child->isRenderBlock() && child->style()->height().isPercent()) || + (child->isBody() && child->style()->htmlHacks()))) + { + child->setChildNeedsLayout(true); + } + + // Handle the four types of special elements first. These include positioned content, floating content, compacts and + // run-ins. When we encounter these four types of objects, we don't actually lay them out as normal flow blocks. + bool handled = false; + RenderObject* next = handleSpecialChild(child, marginInfo, compactInfo, handled); + if (handled) { child = next; continue; } + + // The child is a normal flow object. Compute its vertical margins now. + child->calcVerticalMargins(); + +#ifdef APPLE_CHANGES /* margin-*-collapse not merged yet */ + // Do not allow a collapse if the margin top collapse style is set to SEPARATE. + if (child->style()->marginTopCollapse() == MSEPARATE) { + marginInfo.setAtTopOfBlock(false); + marginInfo.clearMargin(); + } +#endif + + // Try to guess our correct y position. In most cases this guess will + // be correct. Only if we're wrong (when we compute the real y position) + // will we have to potentially relayout. + int yPosEstimate = estimateVerticalPosition(child, marginInfo); + + // If an element might be affected by the presence of floats, then always mark it for + // layout. + if ( !child->flowAroundFloats() || child->usesLineWidth() ) { + int fb = floatBottom(); + if (fb > m_height || fb > yPosEstimate) + child->setChildNeedsLayout(true); + } + + // Go ahead and position the child as though it didn't collapse with the top. + child->setPos(child->xPos(), yPosEstimate); + child->layoutIfNeeded(); + + // Now determine the correct ypos based on examination of collapsing margin + // values. + collapseMargins(child, marginInfo, yPosEstimate); + + // Now check for clear. + clearFloatsIfNeeded(child, marginInfo, oldTopPosMargin, oldTopNegMargin); + + // We are no longer at the top of the block if we encounter a non-empty child. + // This has to be done after checking for clear, so that margins can be reset if a clear occurred. + if (marginInfo.atTopOfBlock() && !child->isSelfCollapsingBlock()) + marginInfo.setAtTopOfBlock(false); + + // Now place the child in the correct horizontal position + determineHorizontalPosition(child); + + adjustSizeForCompactIfNeeded(child, compactInfo); + // Update our height now that the child has been placed in the correct position. + m_height += child->height(); + +#ifdef APPLE_CHANGES + if (child->style()->marginBottomCollapse() == MSEPARATE) { + m_height += child->marginBottom(); + marginInfo.clearMargin(); + } +#endif + + // Check for page-breaks + if (canvas()->pagedMode()) + clearChildOfPageBreaks(child, pageBreakInfo, marginInfo); + + if (child->hasOverhangingFloats() && !child->flowAroundFloats()) { + // need to add the child's floats to our floating objects list, but not in the case where + // overflow is auto/scroll + addOverHangingFloats( static_cast<RenderBlock *>(child), -child->xPos(), -child->yPos(), true ); + } + + // See if this child has made our overflow need to grow. + int effX = child->effectiveXPos(); + int effY = child->effectiveYPos(); + m_overflowWidth = kMax(effX + child->effectiveWidth(), m_overflowWidth); + m_overflowLeft = kMin(effX, m_overflowLeft); + m_overflowHeight = kMax(effY + child->effectiveHeight(), m_overflowHeight); + m_overflowTop = kMin(effY, m_overflowTop); + + // Insert our compact into the block margin if we have one. + insertCompactIfNeeded(child, compactInfo); + + child = child->nextSibling(); + } + + // The last child had forced page-break-after + if (pageBreakInfo.forcePageBreak()) + m_height = pageBreakInfo.pageBottom(); + + // Now do the handling of the bottom of the block, adding in our bottom border/padding and + // determining the correct collapsed bottom margin information. + handleBottomOfBlock(top, bottom, marginInfo); + + setNeedsLayout(false); +} + +void RenderBlock::clearChildOfPageBreaks(RenderObject *child, PageBreakInfo &pageBreakInfo, MarginInfo &marginInfo) +{ + (void)marginInfo; + int childTop = child->yPos(); + int childBottom = child->yPos()+child->height(); +#ifdef PAGE_DEBUG + kdDebug(6040) << renderName() << " ChildTop: " << childTop << " ChildBottom: " << childBottom << endl; +#endif + + bool forcePageBreak = pageBreakInfo.forcePageBreak() || child->style()->pageBreakBefore() == PBALWAYS; +#ifdef PAGE_DEBUG + if (forcePageBreak) + kdDebug(6040) << renderName() << "Forced break required" << endl; +#endif + + int xpage = crossesPageBreak(childTop, childBottom); + if (xpage || forcePageBreak) + { + if (!forcePageBreak && child->containsPageBreak() && !child->needsPageClear()) { +#ifdef PAGE_DEBUG + kdDebug(6040) << renderName() << " Child contains page-break to page " << xpage << endl; +#endif + // ### Actually this assumes floating children are breaking/clearing + // nicely as well. + setContainsPageBreak(true); + } + else { + bool doBreak = true; + // don't break before the first child or when page-break-inside is avoid + if (!forcePageBreak && (!style()->pageBreakInside() || m_avoidPageBreak || child == firstChild())) { + if (parent()->canClear(this, (m_avoidPageBreak) ? PageBreakHarder : PageBreakNormal )) { +#ifdef PAGE_DEBUG + kdDebug(6040) << renderName() << "Avoid page-break inside" << endl; +#endif + child->setNeedsPageClear(false); + setNeedsPageClear(true); + doBreak = false; + } +#ifdef PAGE_DEBUG + else + kdDebug(6040) << renderName() << "Ignoring page-break avoid" << endl; +#endif + } + if (doBreak) { +#ifdef PAGE_DEBUG + kdDebug(6040) << renderName() << " Clearing child of page-break" << endl; + kdDebug(6040) << renderName() << " child top of page " << xpage << endl; +#endif + clearPageBreak(child, pageBreakInfo.pageBottom()); + child->setNeedsPageClear(false); + setContainsPageBreak(true); + } + } + pageBreakInfo.setPageBottom(pageBreakInfo.pageBottom() + canvas()->pageHeight()); + } + else + if (child->yPos() >= pageBreakInfo.pageBottom()) { + bool doBreak = true; +#ifdef PAGE_DEBUG + kdDebug(6040) << "Page-break between children" << endl; +#endif + if (!style()->pageBreakInside() || m_avoidPageBreak) { + if(parent()->canClear(this, (m_avoidPageBreak) ? PageBreakHarder : PageBreakNormal )) { +#ifdef PAGE_DEBUG + kdDebug(6040) << "Avoid page-break inside" << endl; +#endif + child->setNeedsPageClear(false); + setNeedsPageClear(true); + doBreak = false; + } +#ifdef PAGE_DEBUG + else + kdDebug(6040) << "Ignoring page-break avoid" << endl; +#endif + } + if (doBreak) { + // Break between children + setContainsPageBreak(true); + // ### Should collapse top-margin with page-margin + } + pageBreakInfo.setPageBottom(pageBreakInfo.pageBottom() + canvas()->pageHeight()); + } + + // Do we need a forced page-break before next child? + pageBreakInfo.setForcePageBreak(false); + if (child->style()->pageBreakAfter() == PBALWAYS) + pageBreakInfo.setForcePageBreak(true); +} + +void RenderBlock::layoutPositionedObjects(bool relayoutChildren) +{ + if (m_positionedObjects) { + //kdDebug( 6040 ) << renderName() << " " << this << "::layoutPositionedObjects() start" << endl; + RenderObject* r; + QPtrListIterator<RenderObject> it(*m_positionedObjects); + for ( ; (r = it.current()); ++it ) { + //kdDebug(6040) << " have a positioned object" << endl; + if (r->markedForRepaint()) { + r->repaintDuringLayout(); + r->setMarkedForRepaint(false); + } + if ( relayoutChildren || r->style()->position() == FIXED || + ((r->hasStaticY()||r->hasStaticX()) && r->parent() != this && r->parent()->isBlockFlow()) ) { + r->setChildNeedsLayout(true); + r->dirtyFormattingContext(false); + } + r->layoutIfNeeded(); + } + } +} + +void RenderBlock::paint(PaintInfo& pI, int _tx, int _ty) +{ + _tx += m_x; + _ty += m_y; + + // check if we need to do anything at all... + if (!isRoot() && !isInlineFlow() && !overhangingContents() && !isRelPositioned() && !isPositioned() ) + { + int h = m_overflowHeight; + int yPos = _ty; + if (m_floatingObjects && floatBottom() > h) + h = floatBottom(); + + yPos += overflowTop(); + + int os = maximalOutlineSize(pI.phase); + if( (yPos > pI.r.bottom() + os) || (_ty + h <= pI.r.y() - os)) + return; + } + + paintObject(pI, _tx, _ty); +} + +void RenderBlock::paintObject(PaintInfo& pI, int _tx, int _ty, bool shouldPaintOutline) +{ +#ifdef DEBUG_LAYOUT + //kdDebug( 6040 ) << renderName() << "(RenderBlock) " << this << " ::paintObject() w/h = (" << width() << "/" << height() << ")" << endl; +#endif + + // If we're a repositioned run-in, don't paint background/borders. + bool inlineFlow = isInlineFlow(); + + // 1. paint background, borders etc + if (!inlineFlow && + (pI.phase == PaintActionElementBackground || pI.phase == PaintActionChildBackground ) && + shouldPaintBackgroundOrBorder() && style()->visibility() == VISIBLE) + paintBoxDecorations(pI, _tx, _ty); + + if ( pI.phase == PaintActionElementBackground ) + return; + if ( pI.phase == PaintActionChildBackgrounds ) + pI.phase = PaintActionChildBackground; + + // 2. paint contents + int scrolledX = _tx; + int scrolledY = _ty; + if (hasOverflowClip() && m_layer) + m_layer->subtractScrollOffset(scrolledX, scrolledY); + + if (childrenInline()) + paintLines(pI, scrolledX, scrolledY); + else { + for(RenderObject *child = firstChild(); child; child = child->nextSibling()) + if(!child->layer() && !child->isFloating()) + child->paint(pI, scrolledX, scrolledY); + } + + // 3. paint floats. + if (!inlineFlow && (pI.phase == PaintActionFloat || pI.phase == PaintActionSelection)) + paintFloats(pI, scrolledX, scrolledY, pI.phase == PaintActionSelection); + + // 4. paint outline. + if (shouldPaintOutline && !inlineFlow && pI.phase == PaintActionOutline && + style()->outlineWidth() && style()->visibility() == VISIBLE) + paintOutline(pI.p, _tx, _ty, width(), height(), style()); + +#ifdef BOX_DEBUG + if ( style() && style()->visibility() == VISIBLE ) { + if(isAnonymous()) + outlineBox(pI.p, _tx, _ty, "green"); + if(isFloating()) + outlineBox(pI.p, _tx, _ty, "yellow"); + else + outlineBox(pI.p, _tx, _ty); + } +#endif +} + +void RenderBlock::paintFloats(PaintInfo& pI, int _tx, int _ty, bool paintSelection) +{ + if (!m_floatingObjects) + return; + + FloatingObject* r; + QPtrListIterator<FloatingObject> it(*m_floatingObjects); + for ( ; (r = it.current()); ++it) { + // Only paint the object if our noPaint flag isn't set. + if (r->node->isFloating() && !r->noPaint && !r->node->layer()) { + PaintAction oldphase = pI.phase; + if (paintSelection) { + pI.phase = PaintActionSelection; + r->node->paint(pI, _tx + r->left - r->node->xPos() + r->node->marginLeft(), + _ty + r->startY - r->node->yPos() + r->node->marginTop()); + } + else { + pI.phase = PaintActionElementBackground; + r->node->paint(pI, + _tx + r->left - r->node->xPos() + r->node->marginLeft(), + _ty + r->startY - r->node->yPos() + r->node->marginTop()); + pI.phase = PaintActionChildBackgrounds; + r->node->paint(pI, + _tx + r->left - r->node->xPos() + r->node->marginLeft(), + _ty + r->startY - r->node->yPos() + r->node->marginTop()); + pI.phase = PaintActionFloat; + r->node->paint(pI, + _tx + r->left - r->node->xPos() + r->node->marginLeft(), + _ty + r->startY - r->node->yPos() + r->node->marginTop()); + pI.phase = PaintActionForeground; + r->node->paint(pI, + _tx + r->left - r->node->xPos() + r->node->marginLeft(), + _ty + r->startY - r->node->yPos() + r->node->marginTop()); + pI.phase = PaintActionOutline; + r->node->paint(pI, + _tx + r->left - r->node->xPos() + r->node->marginLeft(), + _ty + r->startY - r->node->yPos() + r->node->marginTop()); + } + pI.phase = oldphase; + } + } +} + +void RenderBlock::insertPositionedObject(RenderObject *o) +{ + // Create the list of special objects if we don't aleady have one + if (!m_positionedObjects) { + m_positionedObjects = new QPtrList<RenderObject>; + m_positionedObjects->setAutoDelete(false); + } + else { + // Don't insert the object again if it's already in the list + QPtrListIterator<RenderObject> it(*m_positionedObjects); + RenderObject* f; + while ( (f = it.current()) ) { + if (f == o) return; + ++it; + } + } + + // Create the special object entry & append it to the list + setOverhangingContents(); + m_positionedObjects->append(o); +} + +void RenderBlock::removePositionedObject(RenderObject *o) +{ + if (m_positionedObjects) { + QPtrListIterator<RenderObject> it(*m_positionedObjects); + while (it.current()) { + if (it.current() == o) + m_positionedObjects->removeRef(it.current()); + ++it; + } + if (m_positionedObjects->isEmpty()) { + delete m_positionedObjects; + m_positionedObjects = 0; + } + } +} + +void RenderBlock::insertFloatingObject(RenderObject *o) +{ + // Create the list of special objects if we don't aleady have one + if (!m_floatingObjects) { + m_floatingObjects = new QPtrList<FloatingObject>; + m_floatingObjects->setAutoDelete(true); + } + else { + // Don't insert the object again if it's already in the list + QPtrListIterator<FloatingObject> it(*m_floatingObjects); + FloatingObject* f; + while ( (f = it.current()) ) { + if (f->node == o) return; + ++it; + } + } + + // Create the special object entry & append it to the list + + FloatingObject *newObj; + if (o->isFloating()) { + // floating object + o->layoutIfNeeded(); + + if(o->style()->floating() & FLEFT) + newObj = new FloatingObject(FloatingObject::FloatLeft); + else + newObj = new FloatingObject(FloatingObject::FloatRight); + + newObj->startY = -500000; + newObj->endY = -500000; + newObj->width = o->width() + o->marginLeft() + o->marginRight(); + } + else { + // We should never get here, as insertFloatingObject() should only ever be called with floating + // objects. + KHTMLAssert(false); + newObj = 0; // keep gcc's uninitialized variable warnings happy + } + + newObj->node = o; + + m_floatingObjects->append(newObj); +} + +void RenderBlock::removeFloatingObject(RenderObject *o) +{ + if (m_floatingObjects) { + QPtrListIterator<FloatingObject> it(*m_floatingObjects); + while (it.current()) { + if (it.current()->node == o) + m_floatingObjects->removeRef(it.current()); + ++it; + } + } +} + +void RenderBlock::positionNewFloats() +{ + if(!m_floatingObjects) return; + FloatingObject *f = m_floatingObjects->getLast(); + if(!f || f->startY != -500000) return; + FloatingObject *lastFloat; + while(1) + { + lastFloat = m_floatingObjects->prev(); + if (!lastFloat || lastFloat->startY != -500000) { + m_floatingObjects->next(); + break; + } + f = lastFloat; + } + + int y = m_height; + + + // the float can not start above the y position of the last positioned float. + if(lastFloat && lastFloat->startY > y) + y = lastFloat->startY; + + while(f) + { + //skip elements copied from elsewhere and positioned elements + if (f->node->containingBlock()!=this) + { + f = m_floatingObjects->next(); + continue; + } + + RenderObject *o = f->node; + int _height = o->height() + o->marginTop() + o->marginBottom(); + + // floats avoid page-breaks + if(canvas()->pagedMode()) + { + int top = y; + int bottom = y + o->height(); + if (crossesPageBreak(top, bottom) && o->height() < canvas()->pageHeight() ) { + int newY = pageTopAfter(top); +#ifdef PAGE_DEBUG + kdDebug(6040) << renderName() << " clearing float " << newY - y << "px" << endl; +#endif + y = newY; + } + } + + int ro = rightOffset(); // Constant part of right offset. + int lo = leftOffset(); // Constant part of left offset. + int fwidth = f->width; // The width we look for. + //kdDebug( 6040 ) << " Object width: " << fwidth << " available width: " << ro - lo << endl; + + // in quirk mode, floated auto-width tables try to fit within remaining linewidth + bool ftQuirk = o->isTable() && style()->htmlHacks() && o->style()->width().isVariable(); + if (ftQuirk) + fwidth = kMin( o->minWidth()+o->marginLeft()+o->marginRight(), fwidth ); + + if (ro - lo < fwidth) + fwidth = ro - lo; // Never look for more than what will be available. + + if ( o->style()->clear() & CLEFT ) + y = kMax( leftBottom(), y ); + if ( o->style()->clear() & CRIGHT ) + y = kMax( rightBottom(), y ); + + bool canClearLine; + if (o->style()->floating() & FLEFT) + { + int heightRemainingLeft = 1; + int heightRemainingRight = 1; + int fx = leftRelOffset(y,lo, false, &heightRemainingLeft, &canClearLine); + if (canClearLine) + { + while (rightRelOffset(y,ro, false, &heightRemainingRight)-fx < fwidth) + { + y += kMin( heightRemainingLeft, heightRemainingRight ); + fx = leftRelOffset(y,lo, false, &heightRemainingLeft); + } + } + if (ftQuirk && (rightRelOffset(y,ro, false)-fx < f->width)) { + o->setPos( o->xPos(), y + o->marginTop() ); + o->setChildNeedsLayout(true, false); + o->layoutIfNeeded(); + _height = o->height() + o->marginTop() + o->marginBottom(); + f->width = o->width() + o->marginLeft() + o->marginRight(); + } + f->left = fx; + //kdDebug( 6040 ) << "positioning left aligned float at (" << fx + o->marginLeft() << "/" << y + o->marginTop() << ") fx=" << fx << endl; + o->setPos(fx + o->marginLeft(), y + o->marginTop()); + } + else + { + int heightRemainingLeft = 1; + int heightRemainingRight = 1; + int fx = rightRelOffset(y,ro, false, &heightRemainingRight, &canClearLine); + if (canClearLine) + { + while (fx - leftRelOffset(y,lo, false, &heightRemainingLeft) < fwidth) + { + y += kMin(heightRemainingLeft, heightRemainingRight); + fx = rightRelOffset(y,ro, false, &heightRemainingRight); + } + } + if (ftQuirk && (fx - leftRelOffset(y,lo, false) < f->width)) { + o->setPos( o->xPos(), y + o->marginTop() ); + o->setChildNeedsLayout(true, false); + o->layoutIfNeeded(); + _height = o->height() + o->marginTop() + o->marginBottom(); + f->width = o->width() + o->marginLeft() + o->marginRight(); + } + f->left = fx - f->width; + //kdDebug( 6040 ) << "positioning right aligned float at (" << fx - o->marginRight() - o->width() << "/" << y + o->marginTop() << ")" << endl; + o->setPos(fx - o->marginRight() - o->width(), y + o->marginTop()); + } + + if ( m_layer && hasOverflowClip()) { + if (o->xPos()+o->width() > m_overflowWidth) + m_overflowWidth = o->xPos()+o->width(); + else + if (o->xPos() < m_overflowLeft) + m_overflowLeft = o->xPos(); + } + + f->startY = y; + f->endY = f->startY + _height; + + + //kdDebug( 6040 ) << "floatingObject x/y= (" << f->left << "/" << f->startY << "-" << f->width << "/" << f->endY - f->startY << ")" << endl; + + f = m_floatingObjects->next(); + } +} + +void RenderBlock::newLine() +{ + positionNewFloats(); + // set y position + int newY = 0; + switch(m_clearStatus) + { + case CLEFT: + newY = leftBottom(); + break; + case CRIGHT: + newY = rightBottom(); + break; + case CBOTH: + newY = floatBottom(); + default: + break; + } + if(m_height < newY) + { + // kdDebug( 6040 ) << "adjusting y position" << endl; + m_height = newY; + } + m_clearStatus = CNONE; +} + +int +RenderBlock::leftOffset() const +{ + return borderLeft()+paddingLeft(); +} + +int +RenderBlock::leftRelOffset(int y, int fixedOffset, bool applyTextIndent, int *heightRemaining, bool *canClearLine ) const +{ + int left = fixedOffset; + if (canClearLine) *canClearLine = true; + + if (m_floatingObjects) { + if ( heightRemaining ) *heightRemaining = 1; + FloatingObject* r; + QPtrListIterator<FloatingObject> it(*m_floatingObjects); + for ( ; (r = it.current()); ++it ) + { + //kdDebug( 6040 ) <<(void *)this << " left: sy, ey, x, w " << r->startY << "," << r->endY << "," << r->left << "," << r->width << " " << endl; + if (r->startY <= y && r->endY > y && + r->type == FloatingObject::FloatLeft && + r->left + r->width > left) { + left = r->left + r->width; + if ( heightRemaining ) *heightRemaining = r->endY - y; + if ( canClearLine ) *canClearLine = (r->node->style()->floating() != FLEFT_ALIGN); + } + } + } + + if (applyTextIndent && m_firstLine && style()->direction() == LTR ) { + int cw=0; + if (style()->textIndent().isPercent()) + cw = containingBlock()->contentWidth(); + left += style()->textIndent().minWidth(cw); + } + + //kdDebug( 6040 ) << "leftOffset(" << y << ") = " << left << endl; + return left; +} + +int +RenderBlock::rightOffset() const +{ + int right = m_width - borderRight() - paddingRight(); + if (m_layer && scrollsOverflowY()) + right -= m_layer->verticalScrollbarWidth(); + return right; +} + +int +RenderBlock::rightRelOffset(int y, int fixedOffset, bool applyTextIndent, int *heightRemaining, bool *canClearLine ) const +{ + int right = fixedOffset; + if (canClearLine) *canClearLine = true; + + if (m_floatingObjects) { + if (heightRemaining) *heightRemaining = 1; + FloatingObject* r; + QPtrListIterator<FloatingObject> it(*m_floatingObjects); + for ( ; (r = it.current()); ++it ) + { + //kdDebug( 6040 ) << "right: sy, ey, x, w " << r->startY << "," << r->endY << "," << r->left << "," << r->width << " " << endl; + if (r->startY <= y && r->endY > y && + r->type == FloatingObject::FloatRight && + r->left < right) { + right = r->left; + if ( heightRemaining ) *heightRemaining = r->endY - y; + if ( canClearLine ) *canClearLine = (r->node->style()->floating() != FRIGHT_ALIGN); + } + } + } + + if (applyTextIndent && m_firstLine && style()->direction() == RTL ) { + int cw=0; + if (style()->textIndent().isPercent()) + cw = containingBlock()->contentWidth(); + right -= style()->textIndent().minWidth(cw); + } + + //kdDebug( 6040 ) << "rightOffset(" << y << ") = " << right << endl; + return right; +} + +unsigned short +RenderBlock::lineWidth(int y, bool *canClearLine) const +{ + //kdDebug( 6040 ) << "lineWidth(" << y << ")=" << rightOffset(y) - leftOffset(y) << endl; + int result; + if (canClearLine) { + bool rightCanClearLine; + bool leftCanClearLine; + result = rightOffset(y, &rightCanClearLine) - leftOffset(y, &leftCanClearLine); + *canClearLine = rightCanClearLine && leftCanClearLine; + } else + result = rightOffset(y) - leftOffset(y); + return (result < 0) ? 0 : result; +} + +int +RenderBlock::nearestFloatBottom(int height) const +{ + if (!m_floatingObjects) return 0; + int bottom = 0; + FloatingObject* r; + QPtrListIterator<FloatingObject> it(*m_floatingObjects); + for ( ; (r = it.current()); ++it ) + if (r->endY>height && (r->endY<bottom || bottom==0)) + bottom=r->endY; + return kMax(bottom, height); +} + +int RenderBlock::floatBottom() const +{ + if (!m_floatingObjects) return 0; + int bottom=0; + FloatingObject* r; + QPtrListIterator<FloatingObject> it(*m_floatingObjects); + for ( ; (r = it.current()); ++it ) + if (r->endY>bottom) + bottom=r->endY; + return bottom; +} + +int RenderBlock::lowestPosition(bool includeOverflowInterior, bool includeSelf) const +{ + int bottom = RenderFlow::lowestPosition(includeOverflowInterior, includeSelf); + if (!includeOverflowInterior && hasOverflowClip()) + return bottom; + if (includeSelf && m_overflowHeight > bottom) + bottom = m_overflowHeight; + + if (m_floatingObjects) { + FloatingObject* r; + QPtrListIterator<FloatingObject> it(*m_floatingObjects); + for ( ; (r = it.current()); ++it ) { + if (!r->noPaint) { + int lp = r->startY + r->node->marginTop() + r->node->lowestPosition(false); + bottom = kMax(bottom, lp); + } + } + } + bottom = kMax(bottom, lowestAbsolutePosition()); + + if (!includeSelf && lastLineBox()) { + int lp = lastLineBox()->yPos() + lastLineBox()->height(); + bottom = kMax(bottom, lp); + } + + return bottom; +} + +int RenderBlock::lowestAbsolutePosition() const +{ + if (!m_positionedObjects) + return 0; + + // Fixed positioned objects do not scroll and thus should not constitute + // part of the lowest position. + int bottom = 0; + RenderObject* r; + QPtrListIterator<RenderObject> it(*m_positionedObjects); + for ( ; (r = it.current()); ++it ) { + if (r->style()->position() == FIXED) + continue; + int lp = r->yPos() + r->lowestPosition(false); + bottom = kMax(bottom, lp); + } + return bottom; +} + +int RenderBlock::rightmostPosition(bool includeOverflowInterior, bool includeSelf) const +{ + int right = RenderFlow::rightmostPosition(includeOverflowInterior, includeSelf); + if (!includeOverflowInterior && hasOverflowClip()) + return right; + if (includeSelf && m_overflowWidth > right) + right = m_overflowWidth; + + if (m_floatingObjects) { + FloatingObject* r; + QPtrListIterator<FloatingObject> it(*m_floatingObjects); + for ( ; (r = it.current()); ++it ) { + if (!r->noPaint) { + int rp = r->left + r->node->marginLeft() + r->node->rightmostPosition(false); + right = kMax(right, rp); + } + } + } + right = kMax(right, rightmostAbsolutePosition()); + + if (!includeSelf && firstLineBox()) { + for (InlineRunBox* currBox = firstLineBox(); currBox; currBox = currBox->nextLineBox()) { + int rp = currBox->xPos() + currBox->width(); + right = kMax(right, rp); + } + } + + return right; +} + +int RenderBlock::rightmostAbsolutePosition() const +{ + if (!m_positionedObjects) + return 0; + int right = 0; + RenderObject* r; + QPtrListIterator<RenderObject> it(*m_positionedObjects); + for ( ; (r = it.current()); ++it ) { + if (r->style()->position() == FIXED) + continue; + int rp = r->xPos() + r->rightmostPosition(false); + right = kMax(right, rp); + } + return right; +} + +int RenderBlock::leftmostPosition(bool includeOverflowInterior, bool includeSelf) const +{ + int left = RenderFlow::leftmostPosition(includeOverflowInterior, includeSelf); + if (!includeOverflowInterior && hasOverflowClip()) + return left; + + if (includeSelf && m_overflowLeft < left) + left = m_overflowLeft; + + if (m_floatingObjects) { + FloatingObject* r; + QPtrListIterator<FloatingObject> it(*m_floatingObjects); + for ( ; (r = it.current()); ++it ) { + if (!r->noPaint) { + int lp = r->left + r->node->marginLeft() + r->node->leftmostPosition(false); + left = kMin(left, lp); + } + } + } + left = kMin(left, leftmostAbsolutePosition()); + + if (!includeSelf && firstLineBox()) { + for (InlineRunBox* currBox = firstLineBox(); currBox; currBox = currBox->nextLineBox()) + left = kMin(left, (int)currBox->xPos()); + } + + return left; +} + +int RenderBlock::leftmostAbsolutePosition() const +{ + if (!m_positionedObjects) + return 0; + int left = 0; + RenderObject* r; + QPtrListIterator<RenderObject> it(*m_positionedObjects); + for ( ; (r = it.current()); ++it ) { + if (r->style()->position() == FIXED) + continue; + int lp = r->xPos() + r->leftmostPosition(false); + left = kMin(left, lp); + } + return left; +} + +int RenderBlock::highestPosition(bool includeOverflowInterior, bool includeSelf) const +{ + int top = RenderFlow::highestPosition(includeOverflowInterior, includeSelf); + if (!includeOverflowInterior && hasOverflowClip()) + return top; + + if (includeSelf && m_overflowTop < top) + top = m_overflowTop; + + if (m_floatingObjects) { + FloatingObject* r; + QPtrListIterator<FloatingObject> it(*m_floatingObjects); + for ( ; (r = it.current()); ++it ) { + if (!r->noPaint) { + int hp = r->startY + r->node->marginTop() + r->node->highestPosition(false); + top = kMin(top, hp); + } + } + } + top = kMin(top, highestAbsolutePosition()); + + if (!includeSelf && firstLineBox()) { + top = kMin(top, (int)firstLineBox()->yPos()); + } + + return top; +} + +int RenderBlock::highestAbsolutePosition() const +{ + if (!m_positionedObjects) + return 0; + int top = 0; + RenderObject* r; + QPtrListIterator<RenderObject> it(*m_positionedObjects); + for ( ; (r = it.current()); ++it ) { + if (r->style()->position() == FIXED) + continue; + int hp = r->yPos() + r->highestPosition(false); + hp = kMin(top, hp); + } + return top; +} + +int +RenderBlock::leftBottom() +{ + if (!m_floatingObjects) return 0; + int bottom=0; + FloatingObject* r; + QPtrListIterator<FloatingObject> it(*m_floatingObjects); + for ( ; (r = it.current()); ++it ) + if (r->endY>bottom && r->type == FloatingObject::FloatLeft) + bottom=r->endY; + + return bottom; +} + +int +RenderBlock::rightBottom() +{ + if (!m_floatingObjects) return 0; + int bottom=0; + FloatingObject* r; + QPtrListIterator<FloatingObject> it(*m_floatingObjects); + for ( ; (r = it.current()); ++it ) + if (r->endY>bottom && r->type == FloatingObject::FloatRight) + bottom=r->endY; + + return bottom; +} + +void +RenderBlock::clearFloats() +{ + if (m_floatingObjects) + m_floatingObjects->clear(); + + // we are done if the element defines a new block formatting context + if (flowAroundFloats() || isRoot() || isCanvas() || isFloatingOrPositioned() || isTableCell()) return; + + RenderObject *prev = previousSibling(); + + // find the element to copy the floats from + // pass non-flows + // pass fAF's + bool parentHasFloats = false; + while (prev) { + if (!prev->isRenderBlock() || prev->isFloatingOrPositioned() || prev->flowAroundFloats()) { + if ( prev->isFloating() && parent()->isRenderBlock() ) { + parentHasFloats = true; + } + prev = prev->previousSibling(); + } else + break; + } + + int offset = m_y; + if (parentHasFloats) + { + addOverHangingFloats( static_cast<RenderBlock *>( parent() ), + parent()->borderLeft() + parent()->paddingLeft(), offset, false ); + } + + int xoffset = 0; + if (prev) { + if(prev->isTableCell()) return; + offset -= prev->yPos(); + } else { + prev = parent(); + if(!prev) return; + xoffset += prev->borderLeft() + prev->paddingLeft(); + } + //kdDebug() << "RenderBlock::clearFloats found previous "<< (void *)this << " prev=" << (void *)prev<< endl; + + // add overhanging special objects from the previous RenderBlock + if(!prev->isRenderBlock()) return; + RenderBlock * flow = static_cast<RenderBlock *>(prev); + if(!flow->m_floatingObjects) return; + if(flow->floatBottom() > offset) + addOverHangingFloats( flow, xoffset, offset, false ); +} + +void RenderBlock::addOverHangingFloats( RenderBlock *flow, int xoff, int offset, bool child ) +{ +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << (void *)this << ": adding overhanging floats xoff=" << xoff << " offset=" << offset << " child=" << child << endl; +#endif + + // Prevent floats from being added to the canvas by the root element, e.g., <html>. + if ( !flow->m_floatingObjects || (child && flow->isRoot()) ) + return; + + // if I am clear of my floats, don't add them + // the CSS spec also mentions that child floats + // are not cleared. + if (!child && style()->clear() == CBOTH) + { + return; + } + + QPtrListIterator<FloatingObject> it(*flow->m_floatingObjects); + FloatingObject *r; + for ( ; (r = it.current()); ++it ) { + + if (!child && r->type == FloatingObject::FloatLeft && style()->clear() == CLEFT ) + continue; + if (!child && r->type == FloatingObject::FloatRight && style()->clear() == CRIGHT ) + continue; + + if ( ( !child && r->endY > offset ) || + ( child && flow->yPos() + r->endY > height() ) ) { + if (child && !r->crossedLayer) { + if (flow->enclosingLayer() == enclosingLayer()) { + // Set noPaint to true only if we didn't cross layers. + r->noPaint = true; + } else { + r->crossedLayer = true; + } + } + + FloatingObject* f = 0; + // don't insert it twice! + if (m_floatingObjects) { + QPtrListIterator<FloatingObject> it(*m_floatingObjects); + while ( (f = it.current()) ) { + if (f->node == r->node) break; + ++it; + } + } + if ( !f ) { + FloatingObject *floatingObj = new FloatingObject(r->type); + floatingObj->startY = r->startY - offset; + floatingObj->endY = r->endY - offset; + floatingObj->left = r->left - xoff; + // Applying the child's margin makes no sense in the case where the child was passed in. + // since his own margin was added already through the subtraction of the |xoff| variable + // above. |xoff| will equal -flow->marginLeft() in this case, so it's already been taken + // into account. Only apply this code if |child| is false, since otherwise the left margin + // will get applied twice. -dwh + if (!child && flow != parent()) + floatingObj->left += flow->marginLeft(); + if ( !child ) { + floatingObj->left -= marginLeft(); + floatingObj->noPaint = true; + } + else { + floatingObj->noPaint = (r->crossedLayer || !r->noPaint); + floatingObj->crossedLayer = r->crossedLayer; + } + + floatingObj->width = r->width; + floatingObj->node = r->node; + if (!m_floatingObjects) { + m_floatingObjects = new QPtrList<FloatingObject>; + m_floatingObjects->setAutoDelete(true); + } + m_floatingObjects->append(floatingObj); +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << "addOverHangingFloats x/y= (" << floatingObj->left << "/" << floatingObj->startY << "-" << floatingObj->width << "/" << floatingObj->endY - floatingObj->startY << ")" << endl; +#endif + } + } + } +} + +bool RenderBlock::containsFloat(RenderObject* o) const +{ + if (m_floatingObjects) { + QPtrListIterator<FloatingObject> it(*m_floatingObjects); + while (it.current()) { + if (it.current()->node == o) + return true; + ++it; + } + } + return false; +} + +void RenderBlock::markAllDescendantsWithFloatsForLayout(RenderObject* floatToRemove) +{ + dirtyFormattingContext(false); + setChildNeedsLayout(true); + + if (floatToRemove) + removeFloatingObject(floatToRemove); + + // Iterate over our children and mark them as needed. + if (!childrenInline()) { + for (RenderObject* child = firstChild(); child; child = child->nextSibling()) { + if (isBlockFlow() && !child->isFloatingOrPositioned() && + ((floatToRemove ? child->containsFloat(floatToRemove) : child->hasFloats()) || child->usesLineWidth())) + child->markAllDescendantsWithFloatsForLayout(floatToRemove); + } + } +} + +int RenderBlock::getClearDelta(RenderObject *child) +{ + if (!hasFloats()) + return 0; + + //kdDebug( 6040 ) << "getClearDelta on child " << child << " oldheight=" << m_height << endl; + bool clearSet = child->style()->clear() != CNONE; + int bottom = 0; + switch(child->style()->clear()) + { + case CNONE: + break; + case CLEFT: + bottom = leftBottom(); + break; + case CRIGHT: + bottom = rightBottom(); + break; + case CBOTH: + bottom = floatBottom(); + break; + } + + // We also clear floats if we are too big to sit on the same line as a float, and happen to flow around floats. + // FIXME: Note that the remaining space checks aren't quite accurate, since you should be able to clear only some floats (the minimum # needed + // to fit) and not all (we should be using nearestFloatBottom and looping). + + int result = clearSet ? kMax(0, bottom - child->yPos()) : 0; + if (!result && child->flowAroundFloats() && !style()->width().isVariable()) { + bool canClearLine; + int lw = lineWidth(child->yPos(), &canClearLine); + if (((child->style()->width().isPercent() && child->width() > lw) || + (child->style()->width().isFixed() && child->minWidth() > lw)) && + child->minWidth() <= contentWidth() && canClearLine) + result = kMax(0, floatBottom() - child->yPos()); + } + return result; +} + +bool RenderBlock::isPointInScrollbar(int _x, int _y, int _tx, int _ty) +{ + if (!scrollsOverflow() || !m_layer) + return false; + + if (m_layer->verticalScrollbarWidth()) { + QRect vertRect(_tx + width() - borderRight() - m_layer->verticalScrollbarWidth(), + _ty + borderTop() - borderTopExtra(), + m_layer->verticalScrollbarWidth(), + height() + borderTopExtra() + borderBottomExtra()-borderTop()-borderBottom()); + if (vertRect.contains(_x, _y)) { +#ifdef APPLE_CHANGES + RenderLayer::gScrollBar = m_layer->verticalScrollbar(); +#endif + return true; + } + } + + if (m_layer->horizontalScrollbarHeight()) { + QRect horizRect(_tx + borderLeft(), + _ty + height() + borderTop() + borderBottomExtra() - borderBottom() - m_layer->horizontalScrollbarHeight(), + width()-borderLeft()-borderRight(), + m_layer->horizontalScrollbarHeight()); + if (horizRect.contains(_x, _y)) { +#ifdef APPLE_CHANGES + RenderLayer::gScrollBar = m_layer->horizontalScrollbar(); +#endif + return true; + } + } + + return false; +} + +bool RenderBlock::nodeAtPoint(NodeInfo& info, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction, bool inBox) +{ + bool inScrollbar = isPointInScrollbar(_x, _y, _tx+xPos(), _ty+yPos()); + if (inScrollbar && hitTestAction != HitTestChildrenOnly) + inBox = true; + + if (hitTestAction != HitTestSelfOnly && m_floatingObjects && !inScrollbar) { + int stx = _tx + xPos(); + int sty = _ty + yPos(); + if (hasOverflowClip() && m_layer) + m_layer->subtractScrollOffset(stx, sty); + FloatingObject* o; + QPtrListIterator<FloatingObject> it(*m_floatingObjects); + for (it.toLast(); (o = it.current()); --it) + if (!o->noPaint && !o->node->layer()) + inBox |= o->node->nodeAtPoint(info, _x, _y, + stx+o->left + o->node->marginLeft() - o->node->xPos(), + sty+o->startY + o->node->marginTop() - o->node->yPos(), HitTestAll ) ; + } + + inBox |= RenderFlow::nodeAtPoint(info, _x, _y, _tx, _ty, hitTestAction, inBox); + return inBox; +} + +void RenderBlock::calcMinMaxWidth() +{ + KHTMLAssert( !minMaxKnown() ); + +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(RenderBlock)::calcMinMaxWidth() this=" << this << endl; +#endif + + m_minWidth = 0; + m_maxWidth = 0; + + bool noWrap = !style()->autoWrap(); + if (childrenInline()) + calcInlineMinMaxWidth(); + else + calcBlockMinMaxWidth(); + + if(m_maxWidth < m_minWidth) m_maxWidth = m_minWidth; + + if (noWrap && childrenInline()) { + m_minWidth = m_maxWidth; + + // A horizontal marquee with inline children has no minimum width. + if (style()->overflowX() == OMARQUEE && m_layer && m_layer->marquee() && + m_layer->marquee()->isHorizontal() && !m_layer->marquee()->isUnfurlMarquee()) + m_minWidth = 0; + } + + if (isTableCell()) { + Length w = static_cast<RenderTableCell*>(this)->styleOrColWidth(); + if (w.isFixed() && w.value() > 0) + m_maxWidth = kMax((int)m_minWidth, calcContentWidth(w.value())); + } else if (style()->width().isFixed() && style()->width().value() > 0) + m_minWidth = m_maxWidth = calcContentWidth(style()->width().value()); + + if (style()->minWidth().isFixed() && style()->minWidth().value() > 0) { + m_maxWidth = kMax(m_maxWidth, (int)calcContentWidth(style()->minWidth().value())); + m_minWidth = kMax(m_minWidth, (short)calcContentWidth(style()->minWidth().value())); + } + + if (style()->maxWidth().isFixed() && style()->maxWidth().value() != UNDEFINED) { + m_maxWidth = kMin(m_maxWidth, (int)calcContentWidth(style()->maxWidth().value())); + m_minWidth = kMin(m_minWidth, (short)calcContentWidth(style()->maxWidth().value())); + } + + int toAdd = 0; + toAdd = borderLeft() + borderRight() + paddingLeft() + paddingRight(); + + m_minWidth += toAdd; + m_maxWidth += toAdd; + + setMinMaxKnown(); + + //kdDebug( 6040 ) << "Text::calcMinMaxWidth(" << this << "): min = " << m_minWidth << " max = " << m_maxWidth << endl; + // ### compare with min/max width set in style sheet... +} + +struct InlineMinMaxIterator +{ +/* InlineMinMaxIterator is a class that will iterate over all render objects that contribute to + inline min/max width calculations. Note the following about the way it walks: + (1) Positioned content is skipped (since it does not contribute to min/max width of a block) + (2) We do not drill into the children of floats or replaced elements, since you can't break + in the middle of such an element. + (3) Inline flows (e.g., <a>, <span>, <i>) are walked twice, since each side can have + distinct borders/margin/padding that contribute to the min/max width. +*/ + RenderObject* parent; + RenderObject* current; + bool endOfInline; + + InlineMinMaxIterator(RenderObject* p, RenderObject* o, bool end = false) + :parent(p), current(o), endOfInline(end) {} + + RenderObject* next(); +}; + +RenderObject* InlineMinMaxIterator::next() +{ + RenderObject* result = 0; + bool oldEndOfInline = endOfInline; + endOfInline = false; + while (current != 0 || (current == parent)) + { + //kdDebug( 6040 ) << "current = " << current << endl; + if (!oldEndOfInline && + (current == parent || + (!current->isFloating() && !current->isReplaced() && !current->isPositioned()))) + result = current->firstChild(); + if (!result) { + // We hit the end of our inline. (It was empty, e.g., <span></span>.) + if (!oldEndOfInline && current->isInlineFlow()) { + result = current; + endOfInline = true; + break; + } + + while (current && current != parent) { + result = current->nextSibling(); + if (result) break; + current = current->parent(); + if (current && current != parent && current->isInlineFlow()) { + result = current; + endOfInline = true; + break; + } + } + } + + if (!result) break; + + if (!result->isPositioned() && (result->isText() || result->isBR() || + result->isFloating() || result->isReplaced() || + result->isInlineFlow())) + break; + + current = result; + result = 0; + } + + // Update our position. + current = result; + return current; +} + +// bidi.cpp defines the following functions too. +// Maybe these should not be static, after all... + +#ifndef KDE_USE_FINAL + +static int getBPMWidth(int childValue, Length cssUnit) +{ + if (!cssUnit.isVariable()) + return (cssUnit.isFixed() ? cssUnit.value() : childValue); + return 0; +} + +static int getBorderPaddingMargin(RenderObject* child, bool endOfInline) +{ + RenderStyle* cstyle = child->style(); + int result = 0; + bool leftSide = (cstyle->direction() == LTR) ? !endOfInline : endOfInline; + result += getBPMWidth((leftSide ? child->marginLeft() : child->marginRight()), + (leftSide ? cstyle->marginLeft() : + cstyle->marginRight())); + result += getBPMWidth((leftSide ? child->paddingLeft() : child->paddingRight()), + (leftSide ? cstyle->paddingLeft() : + cstyle->paddingRight())); + result += leftSide ? child->borderLeft() : child->borderRight(); + return result; +} +#endif + +static void stripTrailingSpace(bool preserveWS, + int& inlineMax, int& inlineMin, + RenderObject* trailingSpaceChild) +{ + if (!preserveWS && trailingSpaceChild && trailingSpaceChild->isText()) { + // Collapse away the trailing space at the end of a block. + RenderText* t = static_cast<RenderText *>(trailingSpaceChild); + const Font *f = t->htmlFont( false ); + QChar space[1]; space[0] = ' '; + int spaceWidth = f->width(space, 1, 0); + inlineMax -= spaceWidth; + if (inlineMin > inlineMax) + inlineMin = inlineMax; + } +} + +void RenderBlock::calcInlineMinMaxWidth() +{ + int inlineMax=0; + int inlineMin=0; + + int cw = containingBlock()->contentWidth(); + int floatMaxWidth = 0; + + // If we are at the start of a line, we want to ignore all white-space. + // Also strip spaces if we previously had text that ended in a trailing space. + bool stripFrontSpaces = true; + + bool isTcQuirk = isTableCell() && style()->htmlHacks() && style()->width().isVariable(); + + RenderObject* trailingSpaceChild = 0; + + bool autoWrap, oldAutoWrap; + autoWrap = oldAutoWrap = style()->autoWrap(); + + InlineMinMaxIterator childIterator(this, this); + bool addedTextIndent = false; // Only gets added in once. + RenderObject* prevFloat = 0; + while (RenderObject* child = childIterator.next()) + { + autoWrap = child->style()->autoWrap(); + + if( !child->isBR() ) + { + // Step One: determine whether or not we need to go ahead and + // terminate our current line. Each discrete chunk can become + // the new min-width, if it is the widest chunk seen so far, and + // it can also become the max-width. + + // Children fall into three categories: + // (1) An inline flow object. These objects always have a min/max of 0, + // and are included in the iteration solely so that their margins can + // be added in. + // + // (2) An inline non-text non-flow object, e.g., an inline replaced element. + // These objects can always be on a line by themselves, so in this situation + // we need to go ahead and break the current line, and then add in our own + // margins and min/max width on its own line, and then terminate the line. + // + // (3) A text object. Text runs can have breakable characters at the start, + // the middle or the end. They may also lose whitespace off the front if + // we're already ignoring whitespace. In order to compute accurate min-width + // information, we need three pieces of information. + // (a) the min-width of the first non-breakable run. Should be 0 if the text string + // starts with whitespace. + // (b) the min-width of the last non-breakable run. Should be 0 if the text string + // ends with whitespace. + // (c) the min/max width of the string (trimmed for whitespace). + // + // If the text string starts with whitespace, then we need to go ahead and + // terminate our current line (unless we're already in a whitespace stripping + // mode. + // + // If the text string has a breakable character in the middle, but didn't start + // with whitespace, then we add the width of the first non-breakable run and + // then end the current line. We then need to use the intermediate min/max width + // values (if any of them are larger than our current min/max). We then look at + // the width of the last non-breakable run and use that to start a new line + // (unless we end in whitespace). + RenderStyle* cstyle = child->style(); + short childMin = 0; + short childMax = 0; + + if (!child->isText()) { + // Case (1) and (2). Inline replaced and inline flow elements. + if (child->isInlineFlow()) { + // Add in padding/border/margin from the appropriate side of + // the element. + int bpm = getBorderPaddingMargin(child, childIterator.endOfInline); + childMin += bpm; + childMax += bpm; + + inlineMin += childMin; + inlineMax += childMax; + } + else { + // Inline replaced elements add in their margins to their min/max values. + int margins = 0; + LengthType type = cstyle->marginLeft().type(); + if ( type != Variable ) + margins += (type == Fixed ? cstyle->marginLeft().value() : child->marginLeft()); + type = cstyle->marginRight().type(); + if ( type != Variable ) + margins += (type == Fixed ? cstyle->marginRight().value() : child->marginRight()); + childMin += margins; + childMax += margins; + } + } + + if (!child->isRenderInline() && !child->isText()) { + + bool qBreak = isTcQuirk && !child->isFloatingOrPositioned(); + // Case (2). Inline replaced elements and floats. + // Go ahead and terminate the current line as far as + // minwidth is concerned. + childMin += child->minWidth(); + childMax += child->maxWidth(); + + if (!qBreak && (autoWrap || oldAutoWrap)) { + if(m_minWidth < inlineMin) m_minWidth = inlineMin; + inlineMin = 0; + } + + // Check our "clear" setting. If we're supposed to clear the previous float, then + // go ahead and terminate maxwidth as well. + if (child->isFloating()) { + if (prevFloat && + ((inlineMax + childMax > floatMaxWidth) || + ((prevFloat->style()->floating() & FLEFT) && (child->style()->clear() & CLEFT)) || + ((prevFloat->style()->floating() & FRIGHT) && (child->style()->clear() & CRIGHT)))) { + m_maxWidth = kMax(inlineMax, (int)m_maxWidth); + inlineMax = 0; + } + prevFloat = child; + if (!floatMaxWidth) + floatMaxWidth = availableWidth(); + } + + // Add in text-indent. This is added in only once. + int ti = 0; + if ( !addedTextIndent ) { + addedTextIndent = true; + ti = style()->textIndent().minWidth( cw ); + childMin+=ti; + childMax+=ti; + } + + // Add our width to the max. + inlineMax += childMax; + + if (!autoWrap||qBreak) + inlineMin += childMin; + else { + // Now check our line. + inlineMin = childMin; + if(m_minWidth < inlineMin) m_minWidth = inlineMin; + + // Now start a new line. + inlineMin = 0; + } + + // We are no longer stripping whitespace at the start of + // a line. + if (!child->isFloating()) { + stripFrontSpaces = false; + trailingSpaceChild = 0; + } + } + else if (child->isText()) + { + // Case (3). Text. + RenderText* t = static_cast<RenderText *>(child); + + // Determine if we have a breakable character. Pass in + // whether or not we should ignore any spaces at the front + // of the string. If those are going to be stripped out, + // then they shouldn't be considered in the breakable char + // check. + bool hasBreakableChar, hasBreak; + short beginMin, endMin; + bool beginWS, endWS; + short beginMax, endMax; + t->trimmedMinMaxWidth(beginMin, beginWS, endMin, endWS, hasBreakableChar, + hasBreak, beginMax, endMax, + childMin, childMax, stripFrontSpaces); + + // This text object is insignificant and will not be rendered. Just + // continue. + if (!hasBreak && childMax == 0) continue; + + if (stripFrontSpaces) + trailingSpaceChild = child; + else + trailingSpaceChild = 0; + + // Add in text-indent. This is added in only once. + int ti = 0; + if (!addedTextIndent) { + addedTextIndent = true; + ti = style()->textIndent().minWidth(cw); + childMin+=ti; beginMin += ti; + childMax+=ti; beginMax += ti; + } + + // If we have no breakable characters at all, + // then this is the easy case. We add ourselves to the current + // min and max and continue. + if (!hasBreakableChar) { + inlineMin += childMin; + } + else { + // We have a breakable character. Now we need to know if + // we start and end with whitespace. + if (beginWS) { + // Go ahead and end the current line. + if(m_minWidth < inlineMin) m_minWidth = inlineMin; + } + else { + inlineMin += beginMin; + if(m_minWidth < inlineMin) m_minWidth = inlineMin; + childMin -= ti; + } + + inlineMin = childMin; + + if (endWS) { + // We end in whitespace, which means we can go ahead + // and end our current line. + if(m_minWidth < inlineMin) m_minWidth = inlineMin; + inlineMin = 0; + } + else { + if(m_minWidth < inlineMin) m_minWidth = inlineMin; + inlineMin = endMin; + } + } + + if (hasBreak) { + inlineMax += beginMax; + if (m_maxWidth < inlineMax) m_maxWidth = inlineMax; + if (m_maxWidth < childMax) m_maxWidth = childMax; + inlineMax = endMax; + } + else + inlineMax += childMax; + } + } + else + { + if(m_minWidth < inlineMin) m_minWidth = inlineMin; + if(m_maxWidth < inlineMax) m_maxWidth = inlineMax; + inlineMin = inlineMax = 0; + stripFrontSpaces = true; + trailingSpaceChild = 0; + } + + oldAutoWrap = autoWrap; + } + + stripTrailingSpace(style()->preserveWS(), inlineMax, inlineMin, trailingSpaceChild); + + if(m_minWidth < inlineMin) m_minWidth = inlineMin; + if(m_maxWidth < inlineMax) m_maxWidth = inlineMax; + // kdDebug( 6040 ) << "m_minWidth=" << m_minWidth + // << " m_maxWidth=" << m_maxWidth << endl; +} + +// Use a very large value (in effect infinite). +#define BLOCK_MAX_WIDTH 15000 + +void RenderBlock::calcBlockMinMaxWidth() +{ + bool nowrap = !style()->autoWrap(); + + RenderObject *child = firstChild(); + RenderObject* prevFloat = 0; + int floatWidths = 0; + int floatMaxWidth = 0; + + while(child != 0) + { + // positioned children don't affect the minmaxwidth + if (child->isPositioned()) { + child = child->nextSibling(); + continue; + } + + if (prevFloat && (!child->isFloating() || + ((prevFloat->style()->floating() & FLEFT) && (child->style()->clear() & CLEFT)) || + ((prevFloat->style()->floating() & FRIGHT) && (child->style()->clear() & CRIGHT)))) { + m_maxWidth = kMax(floatWidths, m_maxWidth); + floatWidths = 0; + } + + Length ml = child->style()->marginLeft(); + Length mr = child->style()->marginRight(); + + // Call calcWidth on the child to ensure that our margins are + // up to date. This method can be called before the child has actually + // calculated its margins (which are computed inside calcWidth). + if (ml.isPercent() || mr.isPercent()) + calcWidth(); + + // A margin basically has three types: fixed, percentage, and auto (variable). + // Auto margins simply become 0 when computing min/max width. + // Fixed margins can be added in as is. + // Percentage margins are computed as a percentage of the width we calculated in + // the calcWidth call above. In this case we use the actual cached margin values on + // the RenderObject itself. + int margin = 0; + if (ml.isFixed()) + margin += ml.value(); + else if (ml.isPercent()) + margin += child->marginLeft(); + + if (mr.isFixed()) + margin += mr.value(); + else if (mr.isPercent()) + margin += child->marginRight(); + + if (margin < 0) margin = 0; + + int w = child->minWidth() + margin; + if(m_minWidth < w) m_minWidth = w; + // IE ignores tables for calculation of nowrap. Makes some sense. + if ( nowrap && !child->isTable() && m_maxWidth < w ) + m_maxWidth = w; + + w = child->maxWidth() + margin; + + if(m_maxWidth < w) m_maxWidth = w; + + if (child->isFloating()) { + if (prevFloat && (floatWidths + w > floatMaxWidth)) { + m_maxWidth = kMax(floatWidths, m_maxWidth); + floatWidths = w; + } else + floatWidths += w; + } else if (m_maxWidth < w) + m_maxWidth = w; + + // A very specific WinIE quirk. + // Example: + /* + <div style="position:absolute; width:100px; top:50px;"> + <div style="position:absolute;left:0px;top:50px;height:50px;background-color:green"> + <table style="width:100%"><tr><td></table> + </div> + </div> + */ + // In the above example, the inner absolute positioned block should have a computed width + // of 100px because of the table. + // We can achieve this effect by making the maxwidth of blocks that contain tables + // with percentage widths be infinite (as long as they are not inside a table cell). + if (style()->htmlHacks() && child->style()->width().isPercent() && + !isTableCell() && child->isTable() && m_maxWidth < BLOCK_MAX_WIDTH) { + RenderBlock* cb = containingBlock(); + while (!cb->isCanvas() && !cb->isTableCell()) + cb = cb->containingBlock(); + if (!cb->isTableCell()) + m_maxWidth = BLOCK_MAX_WIDTH; + } + if (child->isFloating()) { + prevFloat = child; + if (!floatMaxWidth) + floatMaxWidth = availableWidth(); + } + child = child->nextSibling(); + } + m_maxWidth = kMax(floatWidths, m_maxWidth); +} + +void RenderBlock::close() +{ + if (lastChild() && lastChild()->isAnonymousBlock()) + lastChild()->close(); + updateFirstLetter(); + RenderFlow::close(); +} + +int RenderBlock::getBaselineOfFirstLineBox() +{ + if (m_firstLineBox) + return m_firstLineBox->yPos() + m_firstLineBox->baseline(); + + if (isInline()) + return -1; // We're inline and had no line box, so we have no baseline we can return. + + for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) { + int result = curr->getBaselineOfFirstLineBox(); + if (result != -1) + return curr->yPos() + result; // Translate to our coordinate space. + } + + return -1; +} + +InlineFlowBox* RenderBlock::getFirstLineBox() +{ + if (m_firstLineBox) + return m_firstLineBox; + + if (isInline()) + return 0; // We're inline and had no line box, so we have no baseline we can return. + + for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) { + InlineFlowBox* result = curr->getFirstLineBox(); + if (result) + return result; + } + + return 0; +} + +bool RenderBlock::inRootBlockContext() const +{ + if (isTableCell() || isFloatingOrPositioned() || hasOverflowClip()) + return false; + + if (isRoot() || isCanvas()) + return true; + + return containingBlock()->inRootBlockContext(); +} + +const char *RenderBlock::renderName() const +{ + if (isFloating()) + return "RenderBlock (floating)"; + if (isPositioned()) + return "RenderBlock (positioned)"; + if (isAnonymousBlock() && m_avoidPageBreak) + return "RenderBlock (avoidPageBreak)"; + if (isAnonymousBlock()) + return "RenderBlock (anonymous)"; + else if (isAnonymous()) + return "RenderBlock (generated)"; + if (isRelPositioned()) + return "RenderBlock (relative positioned)"; + if (style() && style()->display() == COMPACT) + return "RenderBlock (compact)"; + if (style() && style()->display() == RUN_IN) + return "RenderBlock (run-in)"; + return "RenderBlock"; +} + +#ifdef ENABLE_DUMP +void RenderBlock::printTree(int indent) const +{ + RenderFlow::printTree(indent); + + if (m_floatingObjects) + { + QPtrListIterator<FloatingObject> it(*m_floatingObjects); + FloatingObject *r; + for ( ; (r = it.current()); ++it ) + { + QString s; + s.fill(' ', indent); + kdDebug() << s << renderName() << ": " << + (r->type == FloatingObject::FloatLeft ? "FloatLeft" : "FloatRight" ) << + "[" << r->node->renderName() << ": " << (void*)r->node << "] (" << r->startY << " - " << r->endY << ")" << "width: " << r->width << + endl; + } + } +} + +void RenderBlock::dump(QTextStream &stream, const QString &ind) const +{ + RenderFlow::dump(stream,ind); + + if (m_childrenInline) { stream << " childrenInline"; } + // FIXME: currently only print pre to not mess up regression + if (style()->preserveWS()) { stream << " pre"; } + if (m_firstLine) { stream << " firstLine"; } + + if (m_floatingObjects && !m_floatingObjects->isEmpty()) + { + stream << " special("; + QPtrListIterator<FloatingObject> it(*m_floatingObjects); + FloatingObject *r; + bool first = true; + for ( ; (r = it.current()); ++it ) + { + if (!first) + stream << ","; + stream << r->node->renderName(); + first = false; + } + stream << ")"; + } + + // ### EClear m_clearStatus +} +#endif + +#undef DEBUG +#undef DEBUG_LAYOUT +#undef BOX_DEBUG + +} // namespace khtml + diff --git a/khtml/rendering/render_block.h b/khtml/rendering/render_block.h new file mode 100644 index 000000000..112d331ca --- /dev/null +++ b/khtml/rendering/render_block.h @@ -0,0 +1,378 @@ +/* + * This file is part of the render object implementation for KHTML. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 1999-2003 Antti Koivisto (koivisto@kde.org) + * (C) 2002-2003 Dirk Mueller (mueller@kde.org) + * (C) 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. + * + */ + +#ifndef RENDER_BLOCK_H +#define RENDER_BLOCK_H + +#include <qptrlist.h> + +#include "render_flow.h" + +namespace khtml { + +class RenderBlock : public RenderFlow +{ +public: + RenderBlock(DOM::NodeImpl* node); + virtual ~RenderBlock(); + + virtual const char *renderName() const; + + virtual bool isRenderBlock() const { return true; } + virtual bool isBlockFlow() const { return !isInline() && !isTable(); } + virtual bool isInlineFlow() const { return isInline() && !isReplaced(); } + virtual bool isInlineBlockOrInlineTable() const { return isInline() && isReplaced(); } + + virtual bool childrenInline() const { return m_childrenInline; } + virtual void setChildrenInline(bool b) { m_childrenInline = b; } + void makeChildrenNonInline(RenderObject* insertionPoint = 0); + + void makePageBreakAvoidBlocks(); + + // The height (and width) of a block when you include overflow spillage out of the bottom + // of the block (e.g., a <div style="height:25px"> that has a 100px tall image inside + // it would have an overflow height of borderTop() + paddingTop() + 100px. + virtual int overflowHeight() const { return m_overflowHeight; } + virtual int overflowWidth() const { return m_overflowWidth; } + virtual int overflowLeft() const { return m_overflowLeft; } + virtual int overflowTop() const { return m_overflowTop; } + virtual void setOverflowHeight(int h) { m_overflowHeight = h; } + virtual void setOverflowWidth(int w) { m_overflowWidth = w; } + virtual void setOverflowLeft(int l) { m_overflowLeft = l; } + virtual void setOverflowTop(int t) { m_overflowTop = t; } + + virtual bool isSelfCollapsingBlock() const; + virtual bool isTopMarginQuirk() const { return m_topMarginQuirk; } + virtual bool isBottomMarginQuirk() const { return m_bottomMarginQuirk; } + + virtual short maxTopMargin(bool positive) const { + if (positive) + return m_maxTopPosMargin; + else + return m_maxTopNegMargin; + } + virtual short maxBottomMargin(bool positive) const { + if (positive) + return m_maxBottomPosMargin; + else + return m_maxBottomNegMargin; + } + + void initMaxMarginValues() { + if (m_marginTop >= 0) + m_maxTopPosMargin = m_marginTop; + else + m_maxTopNegMargin = -m_marginTop; + if (m_marginBottom >= 0) + m_maxBottomPosMargin = m_marginBottom; + else + m_maxBottomNegMargin = -m_marginBottom; + } + + virtual void addChildToFlow(RenderObject* newChild, RenderObject* beforeChild); + virtual void removeChild(RenderObject *oldChild); + + virtual void setStyle(RenderStyle* _style); + virtual void attach(); + void updateFirstLetter(); + + virtual void layout(); + void layoutBlock( bool relayoutChildren ); + void layoutBlockChildren( bool relayoutChildren ); + void layoutInlineChildren( bool relayoutChildren, int breakBeforeLine = 0); + + void layoutPositionedObjects( bool relayoutChildren ); + void insertPositionedObject(RenderObject *o); + void removePositionedObject(RenderObject *o); + + // Called to lay out the legend for a fieldset. + virtual RenderObject* layoutLegend(bool /*relayoutChildren*/) { return 0; }; + + // the implementation of the following functions is in bidi.cpp + void bidiReorderLine(const BidiIterator &start, const BidiIterator &end, BidiState &bidi ); + BidiIterator findNextLineBreak(BidiIterator &start, BidiState &info ); + InlineFlowBox* constructLine(const BidiIterator& start, const BidiIterator& end); + InlineFlowBox* createLineBoxes(RenderObject* obj); + void computeHorizontalPositionsForLine(InlineFlowBox* lineBox, BidiState &bidi); + void computeVerticalPositionsForLine(InlineFlowBox* lineBox); + bool clearLineOfPageBreaks(InlineFlowBox* lineBox); + void checkLinesForOverflow(); + void deleteEllipsisLineBoxes(); + void checkLinesForTextOverflow(); + // end bidi.cpp functions + + virtual void paint(PaintInfo& i, int tx, int ty); + void paintObject(PaintInfo& i, int tx, int ty, bool paintOutline = true); + void paintFloats(PaintInfo& i, int _tx, int _ty, bool paintSelection = false); + + void insertFloatingObject(RenderObject *o); + void removeFloatingObject(RenderObject *o); + + // called from lineWidth, to position the floats added in the last line. + void positionNewFloats(); + void clearFloats(); + int getClearDelta(RenderObject *child); + virtual void markAllDescendantsWithFloatsForLayout(RenderObject* floatToRemove = 0); + + // FIXME: containsFloats() should not return true if the floating objects list + // is empty. However, layoutInlineChildren() relies on the current behavior. + // http://bugzilla.opendarwin.org/show_bug.cgi?id=7395#c3 + virtual bool hasFloats() const { return m_floatingObjects!=0; } + virtual bool containsFloat(RenderObject* o) const; + + virtual bool hasOverhangingFloats() const { return floatBottom() > m_height; } + void addOverHangingFloats( RenderBlock *block, int xoffset, int yoffset, bool child ); + + int nearestFloatBottom(int height) const; + int floatBottom() const; + inline int leftBottom(); + inline int rightBottom(); + + virtual unsigned short lineWidth(int y, bool *canClearLine = 0) const; + virtual int lowestPosition(bool includeOverflowInterior=true, bool includeSelf=true) const; + virtual int rightmostPosition(bool includeOverflowInterior=true, bool includeSelf=true) const; + virtual int leftmostPosition(bool includeOverflowInterior=true, bool includeSelf=true) const; + virtual int highestPosition(bool includeOverflowInterior, bool includeSelf) const; + int lowestAbsolutePosition() const; + int leftmostAbsolutePosition() const; + int rightmostAbsolutePosition() const; + int highestAbsolutePosition() const; + + int rightOffset() const; + int rightRelOffset(int y, int fixedOffset, bool applyTextIndent=true, int *heightRemaining = 0, bool *canClearLine = 0) const; + int rightOffset(int y, bool *canClearLine = 0) const { return rightRelOffset(y, rightOffset(), true, 0, canClearLine); } + + int leftOffset() const; + int leftRelOffset(int y, int fixedOffset, bool applyTextIndent=true, int *heightRemaining = 0, bool *canClearLine = 0) const; + int leftOffset(int y, bool *canClearLine = 0) const { return leftRelOffset(y, leftOffset(), true, 0, canClearLine); } + + virtual bool nodeAtPoint(NodeInfo& info, int x, int y, int _tx, int _ty, HitTestAction hitTestAction = HitTestAll, bool inside=false); + + bool isPointInScrollbar(int x, int y, int tx, int ty); + + virtual void calcMinMaxWidth(); + void calcInlineMinMaxWidth(); + void calcBlockMinMaxWidth(); + + virtual void close(); + + virtual int getBaselineOfFirstLineBox(); + virtual InlineFlowBox* getFirstLineBox(); + + RootInlineBox* firstRootBox() { return static_cast<RootInlineBox*>(m_firstLineBox); } + RootInlineBox* lastRootBox() { return static_cast<RootInlineBox*>(m_lastLineBox); } + + bool inRootBlockContext() const; + +#ifdef ENABLE_DUMP + virtual void printTree(int indent=0) const; + virtual void dump(QTextStream &stream, const QString &ind) const; +#endif + +protected: + void newLine(); + +protected: + struct FloatingObject { + enum Type { + FloatLeft, + FloatRight + }; + + FloatingObject(Type _type) { + node = 0; + startY = 0; + endY = 0; + type = _type; + left = 0; + width = 0; + noPaint = false; + crossedLayer = false; + + } + RenderObject* node; + int startY; + int endY; + short left; + short width; + Type type : 1; // left or right aligned + bool noPaint : 1; + bool crossedLayer : 1; // lock noPaint flag + }; + + // The following helper functions and structs are used by layoutBlockChildren. + class CompactInfo { + // A compact child that needs to be collapsed into the margin of the following block. + RenderObject* m_compact; + + // The block with the open margin that the compact child is going to place itself within. + RenderObject* m_block; + bool m_treatAsBlock : 1; + + public: + RenderObject* compact() const { return m_compact; } + RenderObject* block() const { return m_block; } + void setTreatAsBlock(bool b) { m_treatAsBlock = b; } + bool treatAsBlock() const { return m_treatAsBlock; } + bool matches(RenderObject* child) const { return m_compact && m_block == child; } + + void clear() { set(0, 0); } + void set(RenderObject* c, RenderObject* b) { m_compact = c; m_block = b; } + + CompactInfo() { clear(); } + }; + + class MarginInfo { + // Collapsing flags for whether we can collapse our margins with our children's margins. + bool m_canCollapseWithChildren : 1; + bool m_canCollapseTopWithChildren : 1; + bool m_canCollapseBottomWithChildren : 1; + + // Whether or not we are a quirky container, i.e., do we collapse away top and bottom + // margins in our container. Table cells and the body are the common examples. We + // also have a custom style property for Safari RSS to deal with TypePad blog articles. + bool m_quirkContainer : 1; + + // This flag tracks whether we are still looking at child margins that can all collapse together at the beginning of a block. + // They may or may not collapse with the top margin of the block (|m_canCollapseTopWithChildren| tells us that), but they will + // always be collapsing with one another. This variable can remain set to true through multiple iterations + // as long as we keep encountering self-collapsing blocks. + bool m_atTopOfBlock : 1; + + // This flag is set when we know we're examining bottom margins and we know we're at the bottom of the block. + bool m_atBottomOfBlock : 1; + + // If our last normal flow child was a self-collapsing block that cleared a float, + // we track it in this variable. + bool m_selfCollapsingBlockClearedFloat : 1; + + // These variables are used to detect quirky margins that we need to collapse away (in table cells + // and in the body element). + bool m_topQuirk : 1; + bool m_bottomQuirk : 1; + bool m_determinedTopQuirk : 1; + + // These flags track the previous maximal positive and negative margins. + int m_posMargin; + int m_negMargin; + + public: + MarginInfo(RenderBlock* b, int top, int bottom); + + void setAtTopOfBlock(bool b) { m_atTopOfBlock = b; } + void setAtBottomOfBlock(bool b) { m_atBottomOfBlock = b; } + void clearMargin() { m_posMargin = m_negMargin = 0; } + void setSelfCollapsingBlockClearedFloat(bool b) { m_selfCollapsingBlockClearedFloat = b; } + void setTopQuirk(bool b) { m_topQuirk = b; } + void setBottomQuirk(bool b) { m_bottomQuirk = b; } + void setDeterminedTopQuirk(bool b) { m_determinedTopQuirk = b; } + void setPosMargin(int p) { m_posMargin = p; } + void setNegMargin(int n) { m_negMargin = n; } + void setPosMarginIfLarger(int p) { if (p > m_posMargin) m_posMargin = p; } + void setNegMarginIfLarger(int n) { if (n > m_negMargin) m_negMargin = n; } + + void setMargin(int p, int n) { m_posMargin = p; m_negMargin = n; } + + bool atTopOfBlock() const { return m_atTopOfBlock; } + bool canCollapseWithTop() const { return m_atTopOfBlock && m_canCollapseTopWithChildren; } + bool canCollapseWithBottom() const { return m_atBottomOfBlock && m_canCollapseBottomWithChildren; } + bool canCollapseTopWithChildren() const { return m_canCollapseTopWithChildren; } + bool canCollapseBottomWithChildren() const { return m_canCollapseBottomWithChildren; } + bool selfCollapsingBlockClearedFloat() const { return m_selfCollapsingBlockClearedFloat; } + bool quirkContainer() const { return m_quirkContainer; } + bool determinedTopQuirk() const { return m_determinedTopQuirk; } + bool topQuirk() const { return m_topQuirk; } + bool bottomQuirk() const { return m_bottomQuirk; } + int posMargin() const { return m_posMargin; } + int negMargin() const { return m_negMargin; } + int margin() const { return m_posMargin - m_negMargin; } + }; + + class PageBreakInfo { + int m_pageBottom; // Next calculated page-break + bool m_forcePageBreak : 1; // Must break before next block + // ### to do better "page-break-after/before: avoid" this struct + // should keep a pagebreakAvoid block and gather children in it + public: + PageBreakInfo(int pageBottom) : m_pageBottom(pageBottom), m_forcePageBreak(false) {}; + bool forcePageBreak() { return m_forcePageBreak; } + void setForcePageBreak(bool b) { m_forcePageBreak = b; } + int pageBottom() { return m_pageBottom; }; + void setPageBottom(int bottom) { m_pageBottom = bottom; } + }; + + virtual bool canClear(RenderObject *child, PageBreakLevel level); + void clearPageBreak(RenderObject* child, int pageBottom); + + void adjustPositionedBlock(RenderObject* child, const MarginInfo& marginInfo); + void adjustFloatingBlock(const MarginInfo& marginInfo); + RenderObject* handleSpecialChild(RenderObject* child, const MarginInfo& marginInfo, CompactInfo& compactInfo, bool& handled); + RenderObject* handleFloatingChild(RenderObject* child, const MarginInfo& marginInfo, bool& handled); + RenderObject* handlePositionedChild(RenderObject* child, const MarginInfo& marginInfo, bool& handled); + RenderObject* handleCompactChild(RenderObject* child, CompactInfo& compactInfo, const MarginInfo& marginInfo, bool& handled); + RenderObject* handleRunInChild(RenderObject* child, bool& handled); + void collapseMargins(RenderObject* child, MarginInfo& marginInfo, int yPosEstimate); + void clearFloatsIfNeeded(RenderObject* child, MarginInfo& marginInfo, int oldTopPosMargin, int oldTopNegMargin); + void adjustSizeForCompactIfNeeded(RenderObject* child, CompactInfo& compactInfo); + void insertCompactIfNeeded(RenderObject* child, CompactInfo& compactInfo); + int estimateVerticalPosition(RenderObject* child, const MarginInfo& info); + void determineHorizontalPosition(RenderObject* child); + void handleBottomOfBlock(int top, int bottom, MarginInfo& marginInfo); + void setCollapsedBottomMargin(const MarginInfo& marginInfo); + void clearChildOfPageBreaks(RenderObject* child, PageBreakInfo &pageBreakInfo, MarginInfo &marginInfo); + // End helper functions and structs used by layoutBlockChildren. + +protected: + // How much content overflows out of our block vertically or horizontally (all we support + // for now is spillage out of the bottom and the right, which are the common cases). + int m_overflowHeight; + int m_overflowWidth; + + // Left and top overflow. + int m_overflowTop; + int m_overflowLeft; + +private: + QPtrList<FloatingObject>* m_floatingObjects; + QPtrList<RenderObject>* m_positionedObjects; + + bool m_childrenInline : 1; + bool m_firstLine : 1; // used in inline layouting + EClear m_clearStatus : 2; // used during layuting of paragraphs + bool m_avoidPageBreak : 1; // anonymous avoid page-break block + bool m_topMarginQuirk : 1; + bool m_bottomMarginQuirk : 1; + + short m_maxTopPosMargin; + short m_maxTopNegMargin; + short m_maxBottomPosMargin; + short m_maxBottomNegMargin; + +}; + +} // namespace + +#endif // RENDER_BLOCK_H + diff --git a/khtml/rendering/render_body.cpp b/khtml/rendering/render_body.cpp new file mode 100644 index 000000000..e9da3c71c --- /dev/null +++ b/khtml/rendering/render_body.cpp @@ -0,0 +1,121 @@ +/** + * This file is part of the html renderer for KDE. + * + * Copyright (C) 2000-2003 Lars Knoll (knoll@kde.org) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ +#include "rendering/render_body.h" +#include "rendering/render_canvas.h" +#include "html/html_baseimpl.h" +#include "xml/dom_docimpl.h" +#include "khtmlview.h" + +#include <kglobal.h> +#include <kdebug.h> + +using namespace khtml; +using namespace DOM; + +RenderBody::RenderBody(HTMLBodyElementImpl* element) + : RenderBlock(element) +{ + scrollbarsStyled = false; +} + +RenderBody::~RenderBody() +{ +} + +void RenderBody::setStyle(RenderStyle* style) +{ +// qDebug("RenderBody::setStyle()"); + // ignore position: fixed on body + if (style->htmlHacks() && style->position() == FIXED) + style->setPosition(STATIC); + + RenderBlock::setStyle(style); + document()->setTextColor( style->color() ); + scrollbarsStyled = false; +} + +void RenderBody::paintBoxDecorations(PaintInfo& paintInfo, int _tx, int _ty) +{ + //kdDebug( 6040 ) << renderName() << "::paintDecorations()" << endl; + QColor bgColor; + const BackgroundLayer *bgLayer = 0; + + if( parent()->style()->hasBackground() ) { + // the root element already has a non-transparent background of its own + // so we must fork our own. (CSS2.1 - 14.2 §4) + bgColor = style()->backgroundColor(); + bgLayer = style()->backgroundLayers(); + } + + int w = width(); + int h = height() + borderTopExtra() + borderBottomExtra(); + _ty -= borderTopExtra(); + + int my = kMax(_ty, paintInfo.r.y()); + int end = kMin( paintInfo.r.y()+paintInfo.r.height(), _ty + h ); + int mh = end - my; + + paintBackgrounds(paintInfo.p, bgColor, bgLayer, my, mh, _tx, _ty, w, h); + + if(style()->hasBorder()) + paintBorder( paintInfo.p, _tx, _ty, w, h, style() ); + +} + +void RenderBody::repaint(Priority p) +{ + RenderObject *cb = containingBlock(); + if(cb) + cb->repaint(p); +} + +void RenderBody::layout() +{ + // in quirk mode, we'll need to have our margins determined + // for percentage height calculations + if (style()->htmlHacks()) + calcHeight(); + RenderBlock::layout(); + + if (!scrollbarsStyled) + { + RenderCanvas* canvas = this->canvas(); + if (canvas->view()) + { + canvas->view()->horizontalScrollBar()->setPalette(style()->palette()); + canvas->view()->verticalScrollBar()->setPalette(style()->palette()); + } + scrollbarsStyled=true; + } +} + +int RenderBody::availableHeight() const +{ + int h = RenderBlock::availableHeight(); + + if( style()->marginTop().isFixed() ) + h -= style()->marginTop().value(); + if( style()->marginBottom().isFixed() ) + h -= style()->marginBottom().value(); + + return kMax(0, h); +} diff --git a/khtml/rendering/render_body.h b/khtml/rendering/render_body.h new file mode 100644 index 000000000..7951f73e1 --- /dev/null +++ b/khtml/rendering/render_body.h @@ -0,0 +1,56 @@ +/* + * This file is part of the html renderer for KDE. + * + * Copyright (C) 2000-2003 Lars Knoll (knoll@kde.org) + * + * 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. + * + */ +#ifndef RENDER_BODY +#define RENDER_BODY + +#include "rendering/render_block.h" + +namespace DOM +{ + class HTMLBodyElementImpl; +} + +namespace khtml { + +class RenderBody : public RenderBlock +{ +public: + RenderBody(DOM::HTMLBodyElementImpl* node); + virtual ~RenderBody(); + + virtual bool isBody() const { return true; } + + virtual const char *renderName() const { return "RenderBody"; } + virtual void repaint(Priority p=NormalPriority); + + virtual void layout(); + virtual void setStyle(RenderStyle* style); + + virtual int availableHeight() const; + +protected: + virtual void paintBoxDecorations(PaintInfo&, int _tx, int _ty); + bool scrollbarsStyled; +}; + +} // end namespace +#endif diff --git a/khtml/rendering/render_box.cpp b/khtml/rendering/render_box.cpp new file mode 100644 index 000000000..56a3109d5 --- /dev/null +++ b/khtml/rendering/render_box.cpp @@ -0,0 +1,2325 @@ +/** + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2002-2003 Apple Computer, Inc. + * (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) + * (C) 2006 Samuel Weinig (sam.weinig@gmail.com) + * + * 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 +//#define CLIP_DEBUG + + +#include <qpainter.h> + +#include "misc/loader.h" +#include "rendering/render_replaced.h" +#include "rendering/render_canvas.h" +#include "rendering/render_table.h" +#include "rendering/render_inline.h" +#include "rendering/render_block.h" +#include "rendering/render_line.h" +#include "rendering/render_layer.h" +#include "misc/htmlhashes.h" +#include "xml/dom_nodeimpl.h" +#include "xml/dom_docimpl.h" +#include "html/html_elementimpl.h" + +#include <khtmlview.h> +#include <kdebug.h> +#include <kglobal.h> +#include <assert.h> + + +using namespace DOM; +using namespace khtml; + +#define TABLECELLMARGIN -0x4000 + +RenderBox::RenderBox(DOM::NodeImpl* node) + : RenderContainer(node) +{ + m_minWidth = -1; + m_maxWidth = -1; + m_width = m_height = 0; + m_x = 0; + m_y = 0; + m_marginTop = 0; + m_marginBottom = 0; + m_marginLeft = 0; + m_marginRight = 0; + m_staticX = 0; + m_staticY = 0; + + m_placeHolderBox = 0; + m_layer = 0; +} + +RenderBlock* RenderBox::createAnonymousBlock() +{ + RenderStyle *newStyle = new RenderStyle(); + newStyle->inheritFrom(style()); + newStyle->setDisplay(BLOCK); + + RenderBlock *newBox = new (renderArena()) RenderBlock(document() /* anonymous*/); + newBox->setStyle(newStyle); + return newBox; +} + +void RenderBox::restructureParentFlow() { + if (!parent() || parent()->childrenInline() == isInline()) + return; + // We have gone from not affecting the inline status of the parent flow to suddenly + // having an impact. See if there is a mismatch between the parent flow's + // childrenInline() state and our state. + if (!isInline()) { + if (parent()->isRenderInline()) { + // We have to split the parent flow. + RenderInline* parentInline = static_cast<RenderInline*>(parent()); + RenderBlock* newBox = parentInline->createAnonymousBlock(); + + RenderFlow* oldContinuation = parent()->continuation(); + parentInline->setContinuation(newBox); + + RenderObject* beforeChild = nextSibling(); + parent()->removeChildNode(this); + parentInline->splitFlow(beforeChild, newBox, this, oldContinuation); + } + else if (parent()->isRenderBlock()) + static_cast<RenderBlock*>(parent())->makeChildrenNonInline(); + } + else { + // An anonymous block must be made to wrap this inline. + RenderBlock* box = createAnonymousBlock(); + parent()->insertChildNode(box, this); + box->appendChildNode(parent()->removeChildNode(this)); + } +} + +static inline bool overflowAppliesTo(RenderObject* o) +{ + // css 2.1-11.1.1 + // 1) overflow only applies to non-replaced block-level elements, table cells, and inline-block elements + if (o->isRenderBlock() || o->isTableRow() || o->isTableSection()) + // 2) overflow on root applies to the viewport (cf. KHTMLView::layout) + if (!o->isRoot()) + // 3) overflow on body may apply to the viewport... + if (!o->isBody() + // ...but only for HTML documents... + || !o->document()->isHTMLDocument() + // ...and only when the root has a visible overflow + || !o->document()->documentElement()->renderer() + || !o->document()->documentElement()->renderer()->style() + || o->document()->documentElement()->renderer()->style()->hidesOverflow()) + return true; + + return false; +} + +void RenderBox::setStyle(RenderStyle *_style) +{ + bool affectsParent = style() && isFloatingOrPositioned() && + (!_style->isFloating() && _style->position() != ABSOLUTE && _style->position() != FIXED) && + parent() && (parent()->isBlockFlow() || parent()->isInlineFlow()); + + RenderContainer::setStyle(_style); + + // The root always paints its background/border. + if (isRoot()) + setShouldPaintBackgroundOrBorder(true); + + switch(_style->display()) + { + case INLINE: + case INLINE_BLOCK: + case INLINE_TABLE: + setInline(true); + break; + case RUN_IN: + if (isInline() && parent() && parent()->childrenInline()) + break; + default: + setInline(false); + } + + switch(_style->position()) + { + case ABSOLUTE: + case FIXED: + setPositioned(true); + break; + default: + setPositioned(false); + if( !isTableCell() && _style->isFloating() ) + setFloating(true); + + if( _style->position() == RELATIVE ) + setRelPositioned(true); + } + + if (overflowAppliesTo(this) && _style->hidesOverflow()) + setHasOverflowClip(); + + if (requiresLayer()) { + if (!m_layer) { + m_layer = new (renderArena()) RenderLayer(this); + m_layer->insertOnlyThisLayer(); + if (parent() && containingBlock()) + m_layer->updateLayerPosition(); + } + } + else if (m_layer && !isCanvas()) { + m_layer->removeOnlyThisLayer(); + m_layer = 0; + } + + if (m_layer) + m_layer->styleChanged(); + + if (style()->outlineWidth() > 0 && style()->outlineSize() > maximalOutlineSize(PaintActionOutline)) + static_cast<RenderCanvas*>(document()->renderer())->setMaximalOutlineSize(style()->outlineSize()); + if (affectsParent) + restructureParentFlow(); +} + +RenderBox::~RenderBox() +{ + //kdDebug( 6040 ) << "Element destructor: this=" << nodeName().string() << endl; +} + +void RenderBox::detach() +{ + RenderLayer* layer = m_layer; + RenderArena* arena = renderArena(); + + RenderContainer::detach(); + + if (layer) + layer->detach(arena); +} + +InlineBox* RenderBox::createInlineBox(bool /*makePlaceHolderBox*/, bool /*isRootLineBox*/) +{ + if (m_placeHolderBox) + m_placeHolderBox->detach(renderArena()); + return (m_placeHolderBox = new (renderArena()) InlineBox(this)); +} + +void RenderBox::deleteInlineBoxes(RenderArena* arena) +{ + if (m_placeHolderBox) { + m_placeHolderBox->detach( arena ? arena : renderArena() ); + m_placeHolderBox = 0; + } +} + +short RenderBox::contentWidth() const +{ + short w = m_width - style()->borderLeftWidth() - style()->borderRightWidth(); + w -= paddingLeft() + paddingRight(); + + if (m_layer && scrollsOverflowY()) + w -= m_layer->verticalScrollbarWidth(); + + //kdDebug( 6040 ) << "RenderBox::contentWidth(2) = " << w << endl; + return w; +} + +int RenderBox::contentHeight() const +{ + int h = m_height - style()->borderTopWidth() - style()->borderBottomWidth(); + h -= paddingTop() + paddingBottom(); + + if (m_layer && scrollsOverflowX()) + h -= m_layer->horizontalScrollbarHeight(); + + return h; +} + +void RenderBox::setPos( int xPos, int yPos ) +{ + m_x = xPos; m_y = yPos; +} + +short RenderBox::width() const +{ + return m_width; +} + +int RenderBox::height() const +{ + return m_height; +} + +void RenderBox::setWidth( int width ) +{ + m_width = width; +} + +void RenderBox::setHeight( int height ) +{ + m_height = height; +} + +int RenderBox::calcBoxHeight(int h) const +{ + if (style()->boxSizing() == CONTENT_BOX) + h += borderTop() + borderBottom() + paddingTop() + paddingBottom(); + + return h; +} + +int RenderBox::calcBoxWidth(int w) const +{ + if (style()->boxSizing() == CONTENT_BOX) + w += borderLeft() + borderRight() + paddingLeft() + paddingRight(); + + return w; +} + +int RenderBox::calcContentHeight(int h) const +{ + if (style()->boxSizing() == BORDER_BOX) + h -= borderTop() + borderBottom() + paddingTop() + paddingBottom(); + + return kMax(0, h); +} + +int RenderBox::calcContentWidth(int w) const +{ + if (style()->boxSizing() == BORDER_BOX) + w -= borderLeft() + borderRight() + paddingLeft() + paddingRight(); + + return kMax(0, w); +} + +// --------------------- painting stuff ------------------------------- + +void RenderBox::paint(PaintInfo& i, int _tx, int _ty) +{ + _tx += m_x; + _ty += m_y; + + if (hasOverflowClip() && m_layer) + m_layer->subtractScrollOffset(_tx, _ty); + + // default implementation. Just pass things through to the children + for(RenderObject* child = firstChild(); child; child = child->nextSibling()) + child->paint(i, _tx, _ty); +} + +void RenderBox::paintRootBoxDecorations(PaintInfo& paintInfo, int _tx, int _ty) +{ + //kdDebug( 6040 ) << renderName() << "::paintRootBoxDecorations()" << _tx << "/" << _ty << endl; + const BackgroundLayer* bgLayer = style()->backgroundLayers(); + QColor bgColor = style()->backgroundColor(); + if (document()->isHTMLDocument() && !style()->hasBackground()) { + // Locate the <body> element using the DOM. This is easier than trying + // to crawl around a render tree with potential :before/:after content and + // anonymous blocks created by inline <body> tags etc. We can locate the <body> + // render object very easily via the DOM. + HTMLElementImpl* body = document()->body(); + RenderObject* bodyObject = (body && body->id() == ID_BODY) ? body->renderer() : 0; + + if (bodyObject) { + bgLayer = bodyObject->style()->backgroundLayers(); + bgColor = bodyObject->style()->backgroundColor(); + } + } + + if( !bgColor.isValid() && canvas()->view()) + bgColor = canvas()->view()->palette().active().color(QColorGroup::Base); + + int w = width(); + int h = height(); + + // kdDebug(0) << "width = " << w <<endl; + + int rw, rh; + if (canvas()->view()) { + rw = canvas()->view()->contentsWidth(); + rh = canvas()->view()->contentsHeight(); + } else { + rw = canvas()->docWidth(); + rh = canvas()->docHeight(); + } + + // kdDebug(0) << "rw = " << rw <<endl; + + int bx = _tx - marginLeft(); + int by = _ty - marginTop(); + int bw = QMAX(w + marginLeft() + marginRight() + borderLeft() + borderRight(), rw); + int bh = QMAX(h + marginTop() + marginBottom() + borderTop() + borderBottom(), rh); + + // CSS2 14.2: + // " The background of the box generated by the root element covers the entire canvas." + // hence, paint the background even in the margin areas (unlike for every other element!) + // I just love these little inconsistencies .. :-( (Dirk) + int my = kMax(by, paintInfo.r.y()); + + paintBackgrounds(paintInfo.p, bgColor, bgLayer, my, paintInfo.r.height(), bx, by, bw, bh); + + if(style()->hasBorder()) + paintBorder( paintInfo.p, _tx, _ty, w, h, style() ); +} + +void RenderBox::paintBoxDecorations(PaintInfo& paintInfo, int _tx, int _ty) +{ + //kdDebug( 6040 ) << renderName() << "::paintDecorations()" << endl; + + if(isRoot()) + return paintRootBoxDecorations(paintInfo, _tx, _ty); + + int w = width(); + int h = height() + borderTopExtra() + borderBottomExtra(); + _ty -= borderTopExtra(); + + int my = kMax(_ty,paintInfo.r.y()); + int end = kMin( paintInfo.r.y() + paintInfo.r.height(), _ty + h ); + int mh = end - my; + + // The <body> only paints its background if the root element has defined a background + // independent of the body. Go through the DOM to get to the root element's render object, + // since the root could be inline and wrapped in an anonymous block. + + if (!isBody() || !document()->isHTMLDocument() || document()->documentElement()->renderer()->style()->hasBackground()) + paintBackgrounds(paintInfo.p, style()->backgroundColor(), style()->backgroundLayers(), my, mh, _tx, _ty, w, h); + + if(style()->hasBorder()) { + paintBorder(paintInfo.p, _tx, _ty, w, h, style()); + } +} + +void RenderBox::paintBackgrounds(QPainter *p, const QColor& c, const BackgroundLayer* bgLayer, int clipy, int cliph, int _tx, int _ty, int w, int height) + { + if (!bgLayer) return; + paintBackgrounds(p, c, bgLayer->next(), clipy, cliph, _tx, _ty, w, height); + paintBackground(p, c, bgLayer, clipy, cliph, _tx, _ty, w, height); +} + +void RenderBox::paintBackground(QPainter *p, const QColor& c, const BackgroundLayer* bgLayer, int clipy, int cliph, int _tx, int _ty, int w, int height) +{ + paintBackgroundExtended(p, c, bgLayer, clipy, cliph, _tx, _ty, w, height, + borderLeft(), borderRight(), paddingLeft(), paddingRight()); +} + +static void calculateBackgroundSize(const BackgroundLayer* bgLayer, int& scaledWidth, int& scaledHeight) +{ + CachedImage* bg = bgLayer->backgroundImage(); + + if (bgLayer->isBackgroundSizeSet()) { + Length bgWidth = bgLayer->backgroundSize().width; + Length bgHeight = bgLayer->backgroundSize().height; + + if (bgWidth.isPercent()) + scaledWidth = scaledWidth * bgWidth.value() / 100; + else if (bgWidth.isFixed()) + scaledWidth = bgWidth.value(); + else if (bgWidth.isVariable()) { + // If the width is auto and the height is not, we have to use the appropriate + // scale to maintain our aspect ratio. + if (bgHeight.isPercent()) { + int scaledH = scaledHeight * bgHeight.value() / 100; + scaledWidth = bg->pixmap_size().width() * scaledH / bg->pixmap_size().height(); + } else if (bgHeight.isFixed()) + scaledWidth = bg->pixmap_size().width() * bgHeight.value() / bg->pixmap_size().height(); + } + + if (bgHeight.isPercent()) + scaledHeight = scaledHeight * bgHeight.value() / 100; + else if (bgHeight.isFixed()) + scaledHeight = bgHeight.value(); + else if (bgHeight.isVariable()) { + // If the height is auto and the width is not, we have to use the appropriate + // scale to maintain our aspect ratio. + if (bgWidth.isPercent()) + scaledHeight = bg->pixmap_size().height() * scaledWidth / bg->pixmap_size().width(); + else if (bgWidth.isFixed()) + scaledHeight = bg->pixmap_size().height() * bgWidth.value() / bg->pixmap_size().width(); + else if (bgWidth.isVariable()) { + // If both width and height are auto, we just want to use the image's + // intrinsic size. + scaledWidth = bg->pixmap_size().width(); + scaledHeight = bg->pixmap_size().height(); + } + } + } else { + scaledWidth = bg->pixmap_size().width(); + scaledHeight = bg->pixmap_size().height(); + } +} + +void RenderBox::paintBackgroundExtended(QPainter *p, const QColor &c, const BackgroundLayer* bgLayer, int clipy, int cliph, + int _tx, int _ty, int w, int h, + int bleft, int bright, int pleft, int pright) +{ + if ( cliph < 0 ) + return; + + if (bgLayer->backgroundClip() != BGBORDER) { + // Clip to the padding or content boxes as necessary. + bool includePadding = bgLayer->backgroundClip() == BGCONTENT; + int x = _tx + bleft + (includePadding ? pleft : 0); + int y = _ty + borderTop() + (includePadding ? paddingTop() : 0); + int width = w - bleft - bright - (includePadding ? pleft + pright : 0); + int height = h - borderTop() - borderBottom() - (includePadding ? paddingTop() + paddingBottom() : 0); + p->save(); + p->setClipRect(QRect(x, y, width, height), QPainter::CoordPainter); + } + + CachedImage* bg = bgLayer->backgroundImage(); + bool shouldPaintBackgroundImage = bg && bg->pixmap_size() == bg->valid_rect().size() && !bg->isTransparent() && !bg->isErrorImage(); + QColor bgColor = c; + + // Paint the color first underneath all images. + if (!bgLayer->next() && bgColor.isValid() && qAlpha(bgColor.rgb()) > 0) + p->fillRect(_tx, clipy, w, cliph, bgColor); + + // no progressive loading of the background image + if (shouldPaintBackgroundImage) { + int sx = 0; + int sy = 0; + int cw,ch; + int cx,cy; + int scaledImageWidth, scaledImageHeight; + + // CSS2 chapter 14.2.1 + + if (bgLayer->backgroundAttachment()) { + //scroll + int hpab = 0, vpab = 0, left = 0, top = 0; // Init to 0 for background-origin of 'border' + if (bgLayer->backgroundOrigin() != BGBORDER) { + hpab += bleft + bright; + vpab += borderTop() + borderBottom(); + left += bleft; + top += borderTop(); + if (bgLayer->backgroundOrigin() == BGCONTENT) { + hpab += pleft + pright; + vpab += paddingTop() + paddingBottom(); + left += pleft; + top += paddingTop(); + } + } + + int pw = w - hpab; + int ph = h - vpab; + scaledImageWidth = pw; + scaledImageHeight = ph; + calculateBackgroundSize(bgLayer, scaledImageWidth, scaledImageHeight); + + EBackgroundRepeat bgr = bgLayer->backgroundRepeat(); + if (bgr == NO_REPEAT || bgr == REPEAT_Y) { + cw = scaledImageWidth; + int xPosition = bgLayer->backgroundXPosition().minWidth(pw-scaledImageWidth); + if ( xPosition >= 0 ) { + cx = _tx + xPosition; + cw = kMin(scaledImageWidth, pw - xPosition); + } + else { + cx = _tx; + if (scaledImageWidth > 0) { + sx = -xPosition; + cw = kMin(scaledImageWidth+xPosition, pw); + } + } + cx += left; + } else { + // repeat over x + cw = w; + cx = _tx; + if (scaledImageWidth > 0) { + int xPosition = bgLayer->backgroundXPosition().minWidth(pw-scaledImageWidth); + sx = scaledImageWidth - (xPosition % scaledImageWidth); + sx -= left % scaledImageWidth; + } + } + if (bgr == NO_REPEAT || bgr == REPEAT_X) { + ch = scaledImageHeight; + int yPosition = bgLayer->backgroundYPosition().minWidth(ph - scaledImageHeight); + if ( yPosition >= 0 ) { + cy = _ty + yPosition; + ch = kMin(ch, ph - yPosition); + } + else { + cy = _ty; + if (scaledImageHeight > 0) { + sy = -yPosition; + ch = kMin(scaledImageHeight+yPosition, ph); + } + } + + cy += top; + } else { + // repeat over y + ch = h; + cy = _ty; + if (scaledImageHeight > 0) { + int yPosition = bgLayer->backgroundYPosition().minWidth(ph - scaledImageHeight); + sy = scaledImageHeight - (yPosition % scaledImageHeight); + sy -= top % scaledImageHeight; + } + } + if (layer()) + layer()->scrollOffset(sx, sy); + } + else + { + //fixed + QRect vr = viewRect(); + int pw = vr.width(); + int ph = vr.height(); + scaledImageWidth = pw; + scaledImageHeight = ph; + calculateBackgroundSize(bgLayer, scaledImageWidth, scaledImageHeight); + EBackgroundRepeat bgr = bgLayer->backgroundRepeat(); + + int xPosition = bgLayer->backgroundXPosition().minWidth(pw-scaledImageWidth); + if (bgr == NO_REPEAT || bgr == REPEAT_Y) { + cw = kMin(scaledImageWidth, pw - xPosition); + cx = vr.x() + xPosition; + } else { + cw = pw; + cx = vr.x(); + if (scaledImageWidth > 0) + sx = scaledImageWidth - xPosition % scaledImageWidth; + } + + int yPosition = bgLayer->backgroundYPosition().minWidth(ph-scaledImageHeight); + if (bgr == NO_REPEAT || bgr == REPEAT_X) { + ch = kMin(scaledImageHeight, ph - yPosition); + cy = vr.y() + yPosition; + } else { + ch = ph; + cy = vr.y(); + if (scaledImageHeight > 0) + sy = scaledImageHeight - yPosition % scaledImageHeight; + } + + QRect fix(cx, cy, cw, ch); + QRect ele(_tx, _ty, w, h); + QRect b = fix.intersect(ele); + + //kdDebug() <<" ele is " << ele << " b is " << b << " fix is " << fix << endl; + sx+=b.x()-cx; + sy+=b.y()-cy; + cx=b.x();cy=b.y();cw=b.width();ch=b.height(); + } + // restrict painting to repaint-clip + if (cy < clipy) { + ch -= (clipy - cy); + sy += (clipy - cy); + cy = clipy; + } + ch = kMin(ch, cliph); + +// kdDebug() << " clipy, cliph: " << clipy << ", " << cliph << endl; +// kdDebug() << " drawTiledPixmap(" << cx << ", " << cy << ", " << cw << ", " << ch << ", " << sx << ", " << sy << ")" << endl; + if (cw>0 && ch>0) + p->drawTiledPixmap(cx, cy, cw, ch, bg->tiled_pixmap(c, scaledImageWidth, scaledImageHeight), sx, sy); + + } + + if (bgLayer->backgroundClip() != BGBORDER) + p->restore(); // Undo the background clip + +} + +void RenderBox::outlineBox(QPainter *p, int _tx, int _ty, const char *color) +{ + p->setPen(QPen(QColor(color), 1, Qt::DotLine)); + p->setBrush( Qt::NoBrush ); + p->drawRect(_tx, _ty, m_width, m_height); +} + +QRect RenderBox::getOverflowClipRect(int tx, int ty) +{ + // XXX When overflow-clip (CSS3) is implemented, we'll obtain the property + // here. + int bl=borderLeft(),bt=borderTop(),bb=borderBottom(),br=borderRight(); + int clipx = tx+bl; + int clipy = ty+bt; + int clipw = m_width-bl-br; + int cliph = m_height-bt-bb+borderTopExtra()+borderBottomExtra(); + + // Substract out scrollbars if we have them. + if (m_layer) { + clipw -= m_layer->verticalScrollbarWidth(); + cliph -= m_layer->horizontalScrollbarHeight(); + } + + return QRect(clipx,clipy,clipw,cliph); +} + +QRect RenderBox::getClipRect(int tx, int ty) +{ + int bl=borderLeft(),bt=borderTop(),bb=borderBottom(),br=borderRight(); + // ### what about paddings? + int clipw = m_width-bl-br; + int cliph = m_height-bt-bb; + + bool rtl = (style()->direction() == RTL); + + int clipleft = 0; + int clipright = clipw; + int cliptop = 0; + int clipbottom = cliph; + + if ( style()->hasClip() && style()->position() == ABSOLUTE ) { + // the only case we use the clip property according to CSS 2.1 + if (!style()->clipLeft().isVariable()) { + int c = style()->clipLeft().width(clipw); + if ( rtl ) + clipleft = clipw - c; + else + clipleft = c; + } + if (!style()->clipRight().isVariable()) { + int w = style()->clipRight().width(clipw); + if ( rtl ) { + clipright = clipw - w; + } else { + clipright = w; + } + } + if (!style()->clipTop().isVariable()) + cliptop = style()->clipTop().width(cliph); + if (!style()->clipBottom().isVariable()) + clipbottom = style()->clipBottom().width(cliph); + } + int clipx = tx + clipleft; + int clipy = ty + cliptop; + clipw = clipright-clipleft; + cliph = clipbottom-cliptop; + + //kdDebug( 6040 ) << "setting clip("<<clipx<<","<<clipy<<","<<clipw<<","<<cliph<<")"<<endl; + + return QRect(clipx,clipy,clipw,cliph); +} + +void RenderBox::close() +{ + setNeedsLayoutAndMinMaxRecalc(); +} + +short RenderBox::containingBlockWidth() const +{ + if (isCanvas() && canvas()->view()) + { + if (canvas()->pagedMode()) + return canvas()->width(); + else + return canvas()->view()->visibleWidth(); + } + + RenderBlock* cb = containingBlock(); + if (isRenderBlock() && cb->isTable() && static_cast<RenderTable*>(cb)->caption() == this) { + //captions are not affected by table border or padding + return cb->width(); + } + if (usesLineWidth()) + return cb->lineWidth(m_y); + else + return cb->contentWidth(); +} + +bool RenderBox::absolutePosition(int &_xPos, int &_yPos, bool f) const +{ + if ( style()->position() == FIXED ) + f = true; + RenderObject *o = container(); + if( o && o->absolutePosition(_xPos, _yPos, f)) + { + if ( o->layer() ) { + if (o->hasOverflowClip()) + o->layer()->subtractScrollOffset( _xPos, _yPos ); + if (isPositioned()) + o->layer()->checkInlineRelOffset(this, _xPos, _yPos); + } + + if(!isInline() || isReplaced()) { + _xPos += xPos(), + _yPos += yPos(); + } + + if(isRelPositioned()) + relativePositionOffset(_xPos, _yPos); + return true; + } + else + { + _xPos = 0; + _yPos = 0; + return false; + } +} + +void RenderBox::position(InlineBox* box, int /*from*/, int /*len*/, bool /*reverse*/) +{ + if (isPositioned()) { + // Cache the x position only if we were an INLINE type originally. + bool wasInline = style()->isOriginalDisplayInlineType(); + + if (wasInline && hasStaticX()) { + // The value is cached in the xPos of the box. We only need this value if + // our object was inline originally, since otherwise it would have ended up underneath + // the inlines. + m_staticX = box->xPos(); + } + else if (!wasInline && hasStaticY()) { + // Our object was a block originally, so we make our normal flow position be + // just below the line box (as though all the inlines that came before us got + // wrapped in an anonymous block, which is what would have happened had we been + // in flow). This value was cached in the yPos() of the box. + m_staticY = box->yPos(); + } + } + else if (isReplaced()) + setPos( box->xPos(), box->yPos() ); +} + +void RenderBox::repaint(Priority prior) +{ + int ow = style() ? style()->outlineSize() : 0; + if( isInline() && !isReplaced() ) + { + RenderObject* p = parent(); + Q_ASSERT(p); + while( p->isInline() && !p->isReplaced() ) + p = p->parent(); + int xoff = p->hasOverflowClip() ? 0 : p->overflowLeft(); + int yoff = p->hasOverflowClip() ? 0 : p->overflowTop(); + p->repaintRectangle( -ow + xoff, -ow + yoff, p->effectiveWidth()+ow*2, p->effectiveHeight()+ow*2, prior); + } + else + { + int xoff = hasOverflowClip() ? 0 : overflowLeft(); + int yoff = hasOverflowClip() ? 0 : overflowTop(); + repaintRectangle( -ow + xoff, -ow + yoff, effectiveWidth()+ow*2, effectiveHeight()+ow*2, prior); + } +} + +void RenderBox::repaintRectangle(int x, int y, int w, int h, Priority p, bool f) +{ + x += m_x; + y += m_y; + + // Apply the relative position offset when invalidating a rectangle. The layer + // is translated, but the render box isn't, so we need to do this to get the + // right dirty rect. Since this is called from RenderObject::setStyle, the relative position + // flag on the RenderObject has been cleared, so use the one on the style(). + if (style()->position() == RELATIVE && m_layer) + relativePositionOffset(x,y); + + if (style()->position() == FIXED) f=true; + + // kdDebug( 6040 ) << "RenderBox(" <<this << ", " << renderName() << ")::repaintRectangle (" << x << "/" << y << ") (" << w << "/" << h << ")" << endl; + RenderObject *o = container(); + if( o ) { + if (o->layer()) { + if (o->style()->hidesOverflow() && o->layer() && !o->isInlineFlow()) + o->layer()->subtractScrollOffset(x,y); // For overflow:auto/scroll/hidden. + if (style()->position() == ABSOLUTE) + o->layer()->checkInlineRelOffset(this,x,y); + } + o->repaintRectangle(x, y, w, h, p, f); + } +} + +void RenderBox::relativePositionOffset(int &tx, int &ty) const +{ + if(!style()->left().isVariable()) + tx += style()->left().width(containingBlockWidth()); + else if(!style()->right().isVariable()) + tx -= style()->right().width(containingBlockWidth()); + if(!style()->top().isVariable()) + { + if (!style()->top().isPercent() + || containingBlock()->style()->height().isFixed()) + ty += style()->top().width(containingBlockHeight()); + } + else if(!style()->bottom().isVariable()) + { + if (!style()->bottom().isPercent() + || containingBlock()->style()->height().isFixed()) + ty -= style()->bottom().width(containingBlockHeight()); + } +} + +void RenderBox::calcWidth() +{ +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << "RenderBox("<<renderName()<<")::calcWidth()" << endl; +#endif + if (isPositioned()) + { + calcAbsoluteHorizontal(); + } + else + { + bool treatAsReplaced = isReplaced() && !isInlineBlockOrInlineTable(); + Length w; + if (treatAsReplaced) + w = Length( calcReplacedWidth(), Fixed ); + else + w = style()->width(); + + Length ml = style()->marginLeft(); + Length mr = style()->marginRight(); + + int cw = containingBlockWidth(); + if (cw<0) cw = 0; + + m_marginLeft = 0; + m_marginRight = 0; + + if (isInline() && !isInlineBlockOrInlineTable()) + { + // just calculate margins + m_marginLeft = ml.minWidth(cw); + m_marginRight = mr.minWidth(cw); + if (treatAsReplaced) + { + m_width = calcBoxWidth(w.width(cw)); + m_width = KMAX(m_width, m_minWidth); + } + + return; + } + else + { + LengthType widthType, minWidthType, maxWidthType; + if (treatAsReplaced) { + m_width = calcBoxWidth(w.width(cw)); + widthType = w.type(); + } else { + m_width = calcWidthUsing(Width, cw, widthType); + int minW = calcWidthUsing(MinWidth, cw, minWidthType); + int maxW = style()->maxWidth().value() == UNDEFINED ? + m_width : calcWidthUsing(MaxWidth, cw, maxWidthType); + + if (m_width > maxW) { + m_width = maxW; + widthType = maxWidthType; + } + if (m_width < minW) { + m_width = minW; + widthType = minWidthType; + } + } + + if (widthType == Variable) { + // kdDebug( 6040 ) << "variable" << endl; + m_marginLeft = ml.minWidth(cw); + m_marginRight = mr.minWidth(cw); + } + else + { +// kdDebug( 6040 ) << "non-variable " << w.type << ","<< w.value << endl; + calcHorizontalMargins(ml,mr,cw); + } + } + + if (cw && cw != m_width + m_marginLeft + m_marginRight && !isFloating() && !isInline()) + { + if (containingBlock()->style()->direction()==LTR) + m_marginRight = cw - m_width - m_marginLeft; + else + m_marginLeft = cw - m_width - m_marginRight; + } + } + +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << "RenderBox::calcWidth(): m_width=" << m_width << " containingBlockWidth()=" << containingBlockWidth() << endl; + kdDebug( 6040 ) << "m_marginLeft=" << m_marginLeft << " m_marginRight=" << m_marginRight << endl; +#endif +} + +int RenderBox::calcWidthUsing(WidthType widthType, int cw, LengthType& lengthType) +{ + int width = m_width; + Length w; + if (widthType == Width) + w = style()->width(); + else if (widthType == MinWidth) + w = style()->minWidth(); + else + w = style()->maxWidth(); + + lengthType = w.type(); + + if (lengthType == Variable) { + int marginLeft = style()->marginLeft().minWidth(cw); + int marginRight = style()->marginRight().minWidth(cw); + if (cw) width = cw - marginLeft - marginRight; + + // size to max width? + if (sizesToMaxWidth()) { + width = KMAX(width, (int)m_minWidth); + width = KMIN(width, (int)m_maxWidth); + } + } + else + { + width = calcBoxWidth(w.width(cw)); + } + + return width; +} + +void RenderBox::calcHorizontalMargins(const Length& ml, const Length& mr, int cw) +{ + if (isFloating() || isInline()) // Inline blocks/tables and floats don't have their margins increased. + { + m_marginLeft = ml.minWidth(cw); + m_marginRight = mr.minWidth(cw); + } + else + { + if ( (ml.isVariable() && mr.isVariable() && m_width<cw) || + (!ml.isVariable() && !mr.isVariable() && + containingBlock()->style()->textAlign() == KHTML_CENTER) ) + { + m_marginLeft = (cw - m_width)/2; + if (m_marginLeft<0) m_marginLeft=0; + m_marginRight = cw - m_width - m_marginLeft; + } + else if ( (mr.isVariable() && m_width<cw) || + (!ml.isVariable() && containingBlock()->style()->direction() == RTL && + containingBlock()->style()->textAlign() == KHTML_LEFT)) + { + m_marginLeft = ml.width(cw); + m_marginRight = cw - m_width - m_marginLeft; + } + else if ( (ml.isVariable() && m_width<cw) || + (!mr.isVariable() && containingBlock()->style()->direction() == LTR && + containingBlock()->style()->textAlign() == KHTML_RIGHT)) + { + m_marginRight = mr.width(cw); + m_marginLeft = cw - m_width - m_marginRight; + } + else + { + // this makes auto margins 0 if we failed a m_width<cw test above (css2.1, 10.3.3) + m_marginLeft = ml.minWidth(cw); + m_marginRight = mr.minWidth(cw); + } + } +} + +void RenderBox::calcHeight() +{ + +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << "RenderBox::calcHeight()" << endl; +#endif + + //cell height is managed by table, inline elements do not have a height property. + if ( isTableCell() || (isInline() && !isReplaced()) ) + return; + + if (isPositioned()) + calcAbsoluteVertical(); + else + { + calcVerticalMargins(); + + // For tables, calculate margins only + if (isTable()) + return; + + Length h; + bool treatAsReplaced = isReplaced() && !isInlineBlockOrInlineTable(); + bool checkMinMaxHeight = false; + + if ( treatAsReplaced ) + h = Length( calcReplacedHeight(), Fixed ); + else { + h = style()->height(); + checkMinMaxHeight = true; + } + + int height; + if (checkMinMaxHeight) { + height = calcHeightUsing(style()->height()); + if (height == -1) + height = m_height; + int minH = calcHeightUsing(style()->minHeight()); // Leave as -1 if unset. + int maxH = style()->maxHeight().value() == UNDEFINED ? height : calcHeightUsing(style()->maxHeight()); + if (maxH == -1) + maxH = height; + height = kMin(maxH, height); + height = kMax(minH, height); + } + else { + // The only times we don't check min/max height are when a fixed length has + // been given as an override. Just use that. + height = calcBoxHeight(h.value()); + } + + if (height<m_height && !overhangingContents() && !hasOverflowClip()) + setOverhangingContents(); + + m_height = height; + } + + // Unfurling marquees override with the furled height. + if (style()->overflowX() == OMARQUEE && m_layer && m_layer->marquee() && + m_layer->marquee()->isUnfurlMarquee() && !m_layer->marquee()->isHorizontal()) { + m_layer->marquee()->setEnd(m_height); + m_height = kMin(m_height, m_layer->marquee()->unfurlPos()); + } + +} + +int RenderBox::calcHeightUsing(const Length& h) +{ + int height = -1; + if (!h.isVariable()) { + if (h.isFixed()) + height = h.value(); + else if (h.isPercent()) + height = calcPercentageHeight(h); + if (height != -1) { + height = calcBoxHeight(height); + return height; + } + } + return height; +} + +int RenderBox::calcImplicitHeight() const { + assert(hasImplicitHeight()); + + RenderBlock* cb = containingBlock(); + // padding-box height + int ch = cb->height() - cb->borderTop() + cb->borderBottom(); + int top = style()->top().width(ch); + int bottom = style()->bottom().width(ch); + + return ch - top - bottom; +} + +int RenderBox::calcPercentageHeight(const Length& height, bool treatAsReplaced) const +{ + int result = -1; + RenderBlock* cb = containingBlock(); + // In quirk mode, table cells violate what the CSS spec says to do with heights. + if (cb->isTableCell() && style()->htmlHacks()) { + result = static_cast<RenderTableCell*>(cb)->cellPercentageHeight(); + } + + // Otherwise we only use our percentage height if our containing block had a specified + // height. + else if (cb->style()->height().isFixed()) + result = cb->calcContentHeight(cb->style()->height().value()); + else if (cb->style()->height().isPercent()) { + // We need to recur and compute the percentage height for our containing block. + result = cb->calcPercentageHeight(cb->style()->height(), treatAsReplaced); + if (result != -1) + result = cb->calcContentHeight(result); + } + else if (cb->isCanvas()) { + if (!canvas()->pagedMode()) + result = static_cast<RenderCanvas*>(cb)->viewportHeight(); + else + result = static_cast<RenderCanvas*>(cb)->height(); + result -= cb->style()->borderTopWidth() - cb->style()->borderBottomWidth(); + result -= cb->paddingTop() + cb->paddingBottom(); + } + else if (cb->isBody() && style()->htmlHacks() && + cb->style()->height().isVariable() && !cb->isFloatingOrPositioned()) { + int margins = cb->collapsedMarginTop() + cb->collapsedMarginBottom(); + int visHeight = canvas()->viewportHeight(); + RenderObject* p = cb->parent(); + result = visHeight - (margins + p->marginTop() + p->marginBottom() + + p->borderTop() + p->borderBottom() + + p->paddingTop() + p->paddingBottom()); + } + else if (cb->isRoot() && style()->htmlHacks() && cb->style()->height().isVariable()) { + int visHeight = canvas()->viewportHeight(); + result = visHeight - (marginTop() + marginBottom() + + borderTop() + borderBottom() + + paddingTop() + paddingBottom()); + } + else if (cb->isAnonymousBlock() || treatAsReplaced && style()->htmlHacks()) { + // IE quirk. + result = cb->calcPercentageHeight(cb->style()->height(), treatAsReplaced); + } + else if (cb->hasImplicitHeight()) { + result = cb->calcImplicitHeight(); + } + + if (result != -1) { + result = height.width(result); + if (cb->isTableCell() && style()->boxSizing() != BORDER_BOX) { + result -= (borderTop() + paddingTop() + borderBottom() + paddingBottom()); + result = kMax(0, result); + } + } + return result; +} + +short RenderBox::calcReplacedWidth() const +{ + int width = calcReplacedWidthUsing(Width); + int minW = calcReplacedWidthUsing(MinWidth); + int maxW = style()->maxWidth().value() == UNDEFINED ? width : calcReplacedWidthUsing(MaxWidth); + + if (width > maxW) + width = maxW; + + if (width < minW) + width = minW; + + return width; +} + +int RenderBox::calcReplacedWidthUsing(WidthType widthType) const +{ + Length w; + if (widthType == Width) + w = style()->width(); + else if (widthType == MinWidth) + w = style()->minWidth(); + else + w = style()->maxWidth(); + + switch (w.type()) { + case Fixed: + return w.value(); + case Percent: + { + const int cw = containingBlockWidth(); + if (cw > 0) { + int result = w.minWidth(cw); + return result; + } + } + // fall through + default: + return intrinsicWidth(); + } +} + +int RenderBox::calcReplacedHeight() const +{ + int height = calcReplacedHeightUsing(Height); + int minH = calcReplacedHeightUsing(MinHeight); + int maxH = style()->maxHeight().value() == UNDEFINED ? height : calcReplacedHeightUsing(MaxHeight); + + if (height > maxH) + height = maxH; + + if (height < minH) + height = minH; + + return height; +} + +int RenderBox::calcReplacedHeightUsing(HeightType heightType) const +{ + Length h; + if (heightType == Height) + h = style()->height(); + else if (heightType == MinHeight) + h = style()->minHeight(); + else + h = style()->maxHeight(); + switch( h.type() ) { + case Fixed: + return h.value(); + case Percent: + { + int th = calcPercentageHeight(h, true); + if (th != -1) + return th; + // fall through + } + default: + return intrinsicHeight(); + }; +} + +int RenderBox::availableHeight() const +{ + return availableHeightUsing(style()->height()); +} + +int RenderBox::availableHeightUsing(const Length& h) const +{ + if (h.isFixed()) + return calcContentHeight(h.value()); + + if (isCanvas()) + if (static_cast<const RenderCanvas*>(this)->pagedMode()) + return static_cast<const RenderCanvas*>(this)->pageHeight(); + else + return static_cast<const RenderCanvas*>(this)->viewportHeight(); + + // We need to stop here, since we don't want to increase the height of the table + // artificially. We're going to rely on this cell getting expanded to some new + // height, and then when we lay out again we'll use the calculation below. + if (isTableCell() && (h.isVariable() || h.isPercent())) { + const RenderTableCell* tableCell = static_cast<const RenderTableCell*>(this); + return tableCell->cellPercentageHeight() - + (borderTop()+borderBottom()+paddingTop()+paddingBottom()); + } + + if (h.isPercent()) + return calcContentHeight(h.width(containingBlock()->availableHeight())); + + // Check for implicit height + if (hasImplicitHeight()) + return calcImplicitHeight(); + + return containingBlock()->availableHeight(); +} + +int RenderBox::availableWidth() const +{ + return availableWidthUsing(style()->width()); +} + +int RenderBox::availableWidthUsing(const Length& w) const +{ + if (w.isFixed()) + return calcContentWidth(w.value()); + + if (isCanvas()) + return static_cast<const RenderCanvas*>(this)->viewportWidth(); + + if (w.isPercent()) + return calcContentWidth(w.width(containingBlock()->availableWidth())); + + return containingBlock()->availableWidth(); +} + +void RenderBox::calcVerticalMargins() +{ + if( isTableCell() ) { + // table margins are basically infinite + m_marginTop = TABLECELLMARGIN; + m_marginBottom = TABLECELLMARGIN; + return; + } + + Length tm = style()->marginTop(); + Length bm = style()->marginBottom(); + + // margins are calculated with respect to the _width_ of + // the containing block (8.3) + int cw = containingBlock()->contentWidth(); + + m_marginTop = tm.minWidth(cw); + m_marginBottom = bm.minWidth(cw); +} + +void RenderBox::setStaticX(short staticX) +{ + m_staticX = staticX; +} + +void RenderBox::setStaticY(int staticY) +{ + m_staticY = staticY; +} + +void RenderBox::calcAbsoluteHorizontal() +{ + if (isReplaced()) { + calcAbsoluteHorizontalReplaced(); + return; + } + + // QUESTIONS + // FIXME 1: Which RenderObject's 'direction' property should used: the + // containing block (cb) as the spec seems to imply, the parent (parent()) as + // was previously done in calculating the static distances, or ourself, which + // was also previously done for deciding what to override when you had + // over-constrained margins? Also note that the container block is used + // in similar situations in other parts of the RenderBox class (see calcWidth() + // and calcHorizontalMargins()). For now we are using the parent for quirks + // mode and the containing block for strict mode. + + // FIXME 2: Can perhaps optimize out cases when max-width/min-width are greater + // than or less than the computed m_width. Be careful of box-sizing and + // percentage issues. + + // The following is based off of the W3C Working Draft from April 11, 2006 of + // CSS 2.1: Section 10.3.7 "Absolutely positioned, non-replaced elements" + // <http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-width> + // (block-style-comments in this function and in calcAbsoluteHorizontalValues() + // correspond to text from the spec) + + + // We don't use containingBlock(), since we may be positioned by an enclosing + // relative positioned inline. + const RenderObject* containerBlock = container(); + + // FIXME: This is incorrect for cases where the container block is a relatively + // positioned inline. + const int containerWidth = containingBlockWidth() + containerBlock->paddingLeft() + containerBlock->paddingRight(); + + // To match WinIE, in quirks mode use the parent's 'direction' property + // instead of the the container block's. + EDirection containerDirection = (style()->htmlHacks()) ? parent()->style()->direction() : containerBlock->style()->direction(); + + const int bordersPlusPadding = borderLeft() + borderRight() + paddingLeft() + paddingRight(); + const Length marginLeft = style()->marginLeft(); + const Length marginRight = style()->marginRight(); + Length left = style()->left(); + Length right = style()->right(); + + /*---------------------------------------------------------------------------*\ + * For the purposes of this section and the next, the term "static position" + * (of an element) refers, roughly, to the position an element would have had + * in the normal flow. More precisely: + * + * * The static position for 'left' is the distance from the left edge of the + * containing block to the left margin edge of a hypothetical box that would + * have been the first box of the element if its 'position' property had + * been 'static' and 'float' had been 'none'. The value is negative if the + * hypothetical box is to the left of the containing block. + * * The static position for 'right' is the distance from the right edge of the + * containing block to the right margin edge of the same hypothetical box as + * above. The value is positive if the hypothetical box is to the left of the + * containing block's edge. + * + * But rather than actually calculating the dimensions of that hypothetical box, + * user agents are free to make a guess at its probable position. + * + * For the purposes of calculating the static position, the containing block of + * fixed positioned elements is the initial containing block instead of the + * viewport, and all scrollable boxes should be assumed to be scrolled to their + * origin. + \*---------------------------------------------------------------------------*/ + + // Calculate the static distance if needed. + if (left.isVariable() && right.isVariable()) { + if (containerDirection == LTR) { + // 'm_staticX' should already have been set through layout of the parent. + int staticPosition = m_staticX - containerBlock->borderLeft(); + for (RenderObject* po = parent(); po && po != containerBlock; po = po->parent()) + staticPosition += po->xPos(); + left = Length(staticPosition, Fixed); + } else { + RenderObject* po = parent(); + // 'm_staticX' should already have been set through layout of the parent. + int staticPosition = m_staticX + containerWidth + containerBlock->borderRight() - po->width(); + for (; po && po != containerBlock; po = po->parent()) + staticPosition -= po->xPos(); + right = Length(staticPosition, Fixed); + } + } + + // Calculate constraint equation values for 'width' case. + calcAbsoluteHorizontalValues(style()->width(), containerBlock, containerDirection, + containerWidth, bordersPlusPadding, + left, right, marginLeft, marginRight, + m_width, m_marginLeft, m_marginRight, m_x); + // Calculate constraint equation values for 'max-width' case.calcContentWidth(width.width(containerWidth)); + if (style()->maxWidth().value() != UNDEFINED) { + short maxWidth; + short maxMarginLeft; + short maxMarginRight; + short maxXPos; + + calcAbsoluteHorizontalValues(style()->maxWidth(), containerBlock, containerDirection, + containerWidth, bordersPlusPadding, + left, right, marginLeft, marginRight, + maxWidth, maxMarginLeft, maxMarginRight, maxXPos); + + if (m_width > maxWidth) { + m_width = maxWidth; + m_marginLeft = maxMarginLeft; + m_marginRight = maxMarginRight; + m_x = maxXPos; + } + } + + // Calculate constraint equation values for 'min-width' case. + if (style()->minWidth().value()) { + short minWidth; + short minMarginLeft; + short minMarginRight; + short minXPos; + + calcAbsoluteHorizontalValues(style()->minWidth(), containerBlock, containerDirection, + containerWidth, bordersPlusPadding, + left, right, marginLeft, marginRight, + minWidth, minMarginLeft, minMarginRight, minXPos); + + if (m_width < minWidth) { + m_width = minWidth; + m_marginLeft = minMarginLeft; + m_marginRight = minMarginRight; + m_x = minXPos; + } + } + + // Put m_width into correct form. + m_width += bordersPlusPadding; +} + +void RenderBox::calcAbsoluteHorizontalValues(Length width, const RenderObject* containerBlock, EDirection containerDirection, + const int containerWidth, const int bordersPlusPadding, + const Length left, const Length right, const Length marginLeft, const Length marginRight, + short& widthValue, short& marginLeftValue, short& marginRightValue, short& xPos) +{ + // 'left' and 'right' cannot both be 'auto' because one would of been + // converted to the static postion already + assert(!(left.isVariable() && right.isVariable())); + + int leftValue = 0; + + bool widthIsAuto = width.isVariable(); + bool leftIsAuto = left.isVariable(); + bool rightIsAuto = right.isVariable(); + + if (!leftIsAuto && !widthIsAuto && !rightIsAuto) { + /*-----------------------------------------------------------------------*\ + * If none of the three is 'auto': If both 'margin-left' and 'margin- + * right' are 'auto', solve the equation under the extra constraint that + * the two margins get equal values, unless this would make them negative, + * in which case when direction of the containing block is 'ltr' ('rtl'), + * set 'margin-left' ('margin-right') to zero and solve for 'margin-right' + * ('margin-left'). If one of 'margin-left' or 'margin-right' is 'auto', + * solve the equation for that value. If the values are over-constrained, + * ignore the value for 'left' (in case the 'direction' property of the + * containing block is 'rtl') or 'right' (in case 'direction' is 'ltr') + * and solve for that value. + \*-----------------------------------------------------------------------*/ + // NOTE: It is not necessary to solve for 'right' in the over constrained + // case because the value is not used for any further calculations. + + leftValue = left.width(containerWidth); + widthValue = calcContentWidth(width.width(containerWidth)); + + const int availableSpace = containerWidth - (leftValue + widthValue + right.width(containerWidth) + bordersPlusPadding); + + // Margins are now the only unknown + if (marginLeft.isVariable() && marginRight.isVariable()) { + // Both margins auto, solve for equality + if (availableSpace >= 0) { + marginLeftValue = availableSpace / 2; // split the diference + marginRightValue = availableSpace - marginLeftValue; // account for odd valued differences + } else { + // see FIXME 1 + if (containerDirection == LTR) { + marginLeftValue = 0; + marginRightValue = availableSpace; // will be negative + } else { + marginLeftValue = availableSpace; // will be negative + marginRightValue = 0; + } + } + } else if (marginLeft.isVariable()) { + // Solve for left margin + marginRightValue = marginRight.width(containerWidth); + marginLeftValue = availableSpace - marginRightValue; + } else if (marginRight.isVariable()) { + // Solve for right margin + marginLeftValue = marginLeft.width(containerWidth); + marginRightValue = availableSpace - marginLeftValue; + } else { + // Over-constrained, solve for left if direction is RTL + marginLeftValue = marginLeft.width(containerWidth); + marginRightValue = marginRight.width(containerWidth); + + // see FIXME 1 -- used to be "this->style()->direction()" + if (containerDirection == RTL) + leftValue = (availableSpace + leftValue) - marginLeftValue - marginRightValue; + } + } else { + /*--------------------------------------------------------------------*\ + * Otherwise, set 'auto' values for 'margin-left' and 'margin-right' + * to 0, and pick the one of the following six rules that applies. + * + * 1. 'left' and 'width' are 'auto' and 'right' is not 'auto', then the + * width is shrink-to-fit. Then solve for 'left' + * + * OMIT RULE 2 AS IT SHOULD NEVER BE HIT + * ------------------------------------------------------------------ + * 2. 'left' and 'right' are 'auto' and 'width' is not 'auto', then if + * the 'direction' property of the containing block is 'ltr' set + * 'left' to the static position, otherwise set 'right' to the + * static position. Then solve for 'left' (if 'direction is 'rtl') + * or 'right' (if 'direction' is 'ltr'). + * ------------------------------------------------------------------ + * + * 3. 'width' and 'right' are 'auto' and 'left' is not 'auto', then the + * width is shrink-to-fit . Then solve for 'right' + * 4. 'left' is 'auto', 'width' and 'right' are not 'auto', then solve + * for 'left' + * 5. 'width' is 'auto', 'left' and 'right' are not 'auto', then solve + * for 'width' + * 6. 'right' is 'auto', 'left' and 'width' are not 'auto', then solve + * for 'right' + * + * Calculation of the shrink-to-fit width is similar to calculating the + * width of a table cell using the automatic table layout algorithm. + * Roughly: calculate the preferred width by formatting the content + * without breaking lines other than where explicit line breaks occur, + * and also calculate the preferred minimum width, e.g., by trying all + * possible line breaks. CSS 2.1 does not define the exact algorithm. + * Thirdly, calculate the available width: this is found by solving + * for 'width' after setting 'left' (in case 1) or 'right' (in case 3) + * to 0. + * + * Then the shrink-to-fit width is: + * kMin(kMax(preferred minimum width, available width), preferred width). + \*--------------------------------------------------------------------*/ + // NOTE: For rules 3 and 6 it is not necessary to solve for 'right' + // because the value is not used for any further calculations. + + // Calculate margins, 'auto' margins are ignored. + marginLeftValue = marginLeft.minWidth(containerWidth); + marginRightValue = marginRight.minWidth(containerWidth); + + const int availableSpace = containerWidth - (marginLeftValue + marginRightValue + bordersPlusPadding); + + // FIXME: Is there a faster way to find the correct case? + // Use rule/case that applies. + if (leftIsAuto && widthIsAuto && !rightIsAuto) { + // RULE 1: (use shrink-to-fit for width, and solve of left) + int rightValue = right.width(containerWidth); + + // FIXME: would it be better to have shrink-to-fit in one step? + int preferredWidth = m_maxWidth - bordersPlusPadding; + int preferredMinWidth = m_minWidth - bordersPlusPadding; + int availableWidth = availableSpace - rightValue; + widthValue = kMin(kMax(preferredMinWidth, availableWidth), preferredWidth); + leftValue = availableSpace - (widthValue + rightValue); + } else if (!leftIsAuto && widthIsAuto && rightIsAuto) { + // RULE 3: (use shrink-to-fit for width, and no need solve of right) + leftValue = left.width(containerWidth); + + // FIXME: would it be better to have shrink-to-fit in one step? + int preferredWidth = m_maxWidth - bordersPlusPadding; + int preferredMinWidth = m_minWidth - bordersPlusPadding; + int availableWidth = availableSpace - leftValue; + widthValue = kMin(kMax(preferredMinWidth, availableWidth), preferredWidth); + } else if (leftIsAuto && !width.isVariable() && !rightIsAuto) { + // RULE 4: (solve for left) + widthValue = calcContentWidth(width.width(containerWidth)); + leftValue = availableSpace - (widthValue + right.width(containerWidth)); + } else if (!leftIsAuto && widthIsAuto && !rightIsAuto) { + // RULE 5: (solve for width) + leftValue = left.width(containerWidth); + widthValue = availableSpace - (leftValue + right.width(containerWidth)); + } else if (!leftIsAuto&& !widthIsAuto && rightIsAuto) { + // RULE 6: (no need solve for right) + leftValue = left.width(containerWidth); + widthValue = calcContentWidth(width.width(containerWidth)); + } + } + + // Use computed values to calculate the horizontal position. + xPos = leftValue + marginLeftValue + containerBlock->borderLeft(); +} + + +void RenderBox::calcAbsoluteVertical() +{ + if (isReplaced()) { + calcAbsoluteVerticalReplaced(); + return; + } + + // The following is based off of the W3C Working Draft from April 11, 2006 of + // CSS 2.1: Section 10.6.4 "Absolutely positioned, non-replaced elements" + // <http://www.w3.org/TR/2005/WD-CSS21-20050613/visudet.html#abs-non-replaced-height> + // (block-style-comments in this function and in calcAbsoluteVerticalValues() + // correspond to text from the spec) + + + // We don't use containingBlock(), since we may be positioned by an enclosing relpositioned inline. + const RenderObject* containerBlock = container(); + const int containerHeight = containerBlock->height() - containerBlock->borderTop() - containerBlock->borderBottom(); + + const int bordersPlusPadding = borderTop() + borderBottom() + paddingTop() + paddingBottom(); + const Length marginTop = style()->marginTop(); + const Length marginBottom = style()->marginBottom(); + Length top = style()->top(); + Length bottom = style()->bottom(); + + /*---------------------------------------------------------------------------*\ + * For the purposes of this section and the next, the term "static position" + * (of an element) refers, roughly, to the position an element would have had + * in the normal flow. More precisely, the static position for 'top' is the + * distance from the top edge of the containing block to the top margin edge + * of a hypothetical box that would have been the first box of the element if + * its 'position' property had been 'static' and 'float' had been 'none'. The + * value is negative if the hypothetical box is above the containing block. + * + * But rather than actually calculating the dimensions of that hypothetical + * box, user agents are free to make a guess at its probable position. + * + * For the purposes of calculating the static position, the containing block + * of fixed positioned elements is the initial containing block instead of + * the viewport. + \*---------------------------------------------------------------------------*/ + + // Calculate the static distance if needed. + if (top.isVariable() && bottom.isVariable()) { + // m_staticY should already have been set through layout of the parent() + int staticTop = m_staticY - containerBlock->borderTop(); + for (RenderObject* po = parent(); po && po != containerBlock; po = po->parent()) { + staticTop += po->yPos(); + } + top.setValue(Fixed, staticTop); + } + + + int height; // Needed to compute overflow. + + // Calculate constraint equation values for 'height' case. + calcAbsoluteVerticalValues(style()->height(), containerBlock, containerHeight, bordersPlusPadding, + top, bottom, marginTop, marginBottom, + height, m_marginTop, m_marginBottom, m_y); + + // Avoid doing any work in the common case (where the values of min-height and max-height are their defaults). + // see FIXME 2 + + // Calculate constraint equation values for 'max-height' case. + if (style()->maxHeight().value() != UNDEFINED) { + int maxHeight; + short maxMarginTop; + short maxMarginBottom; + int maxYPos; + + calcAbsoluteVerticalValues(style()->maxHeight(), containerBlock, containerHeight, bordersPlusPadding, + top, bottom, marginTop, marginBottom, + maxHeight, maxMarginTop, maxMarginBottom, maxYPos); + + if (height > maxHeight) { + height = maxHeight; + m_marginTop = maxMarginTop; + m_marginBottom = maxMarginBottom; + m_y = maxYPos; + } + } + + // Calculate constraint equation values for 'min-height' case. + if (style()->minHeight().value()) { + int minHeight; + short minMarginTop; + short minMarginBottom; + int minYPos; + + calcAbsoluteVerticalValues(style()->minHeight(), containerBlock, containerHeight, bordersPlusPadding, + top, bottom, marginTop, marginBottom, + minHeight, minMarginTop, minMarginBottom, minYPos); + + if (height < minHeight) { + height = minHeight; + m_marginTop = minMarginTop; + m_marginBottom = minMarginBottom; + m_y = minYPos; + } + } + + height += bordersPlusPadding; + + // Set final height value. + m_height = height; +} + +void RenderBox::calcAbsoluteVerticalValues(Length height, const RenderObject* containerBlock, + const int containerHeight, const int bordersPlusPadding, + const Length top, const Length bottom, const Length marginTop, const Length marginBottom, + int& heightValue, short& marginTopValue, short& marginBottomValue, int& yPos) +{ + // 'top' and 'bottom' cannot both be 'auto' because 'top would of been + // converted to the static position in calcAbsoluteVertical() + assert(!(top.isVariable() && bottom.isVariable())); + + int contentHeight = m_height - bordersPlusPadding; + + int topValue = 0; + + bool heightIsAuto = height.isVariable(); + bool topIsAuto = top.isVariable(); + bool bottomIsAuto = bottom.isVariable(); + + if (isTable() && heightIsAuto) { + // Height is never unsolved for tables. "auto" means shrink to fit. + // Use our height instead. + heightValue = contentHeight; + heightIsAuto = false; + } else if (!heightIsAuto) { + heightValue = calcContentHeight(height.width(containerHeight)); + if (contentHeight > heightValue) { + if (!isTable()) + contentHeight = heightValue; + else + heightValue = contentHeight; + } + } + + + if (!topIsAuto && !heightIsAuto && !bottomIsAuto) { + /*-----------------------------------------------------------------------*\ + * If none of the three are 'auto': If both 'margin-top' and 'margin- + * bottom' are 'auto', solve the equation under the extra constraint that + * the two margins get equal values. If one of 'margin-top' or 'margin- + * bottom' is 'auto', solve the equation for that value. If the values + * are over-constrained, ignore the value for 'bottom' and solve for that + * value. + \*-----------------------------------------------------------------------*/ + // NOTE: It is not necessary to solve for 'bottom' in the over constrained + // case because the value is not used for any further calculations. + + topValue = top.width(containerHeight); + + const int availableSpace = containerHeight - (topValue + heightValue + bottom.width(containerHeight) + bordersPlusPadding); + + // Margins are now the only unknown + if (marginTop.isVariable() && marginBottom.isVariable()) { + // Both margins auto, solve for equality + // NOTE: This may result in negative values. + marginTopValue = availableSpace / 2; // split the diference + marginBottomValue = availableSpace - marginTopValue; // account for odd valued differences + } else if (marginTop.isVariable()) { + // Solve for top margin + marginBottomValue = marginBottom.width(containerHeight); + marginTopValue = availableSpace - marginBottomValue; + } else if (marginBottom.isVariable()) { + // Solve for bottom margin + marginTopValue = marginTop.width(containerHeight); + marginBottomValue = availableSpace - marginTopValue; + } else { + // Over-constrained, (no need solve for bottom) + marginTopValue = marginTop.width(containerHeight); + marginBottomValue = marginBottom.width(containerHeight); + } + } else { + /*--------------------------------------------------------------------*\ + * Otherwise, set 'auto' values for 'margin-top' and 'margin-bottom' + * to 0, and pick the one of the following six rules that applies. + * + * 1. 'top' and 'height' are 'auto' and 'bottom' is not 'auto', then + * the height is based on the content, and solve for 'top'. + * + * OMIT RULE 2 AS IT SHOULD NEVER BE HIT + * ------------------------------------------------------------------ + * 2. 'top' and 'bottom' are 'auto' and 'height' is not 'auto', then + * set 'top' to the static position, and solve for 'bottom'. + * ------------------------------------------------------------------ + * + * 3. 'height' and 'bottom' are 'auto' and 'top' is not 'auto', then + * the height is based on the content, and solve for 'bottom'. + * 4. 'top' is 'auto', 'height' and 'bottom' are not 'auto', and + * solve for 'top'. + * 5. 'height' is 'auto', 'top' and 'bottom' are not 'auto', and + * solve for 'height'. + * 6. 'bottom' is 'auto', 'top' and 'height' are not 'auto', and + * solve for 'bottom'. + \*--------------------------------------------------------------------*/ + // NOTE: For rules 3 and 6 it is not necessary to solve for 'bottom' + // because the value is not used for any further calculations. + + // Calculate margins, 'auto' margins are ignored. + marginTopValue = marginTop.minWidth(containerHeight); + marginBottomValue = marginBottom.minWidth(containerHeight); + + const int availableSpace = containerHeight - (marginTopValue + marginBottomValue + bordersPlusPadding); + + // Use rule/case that applies. + if (topIsAuto && heightIsAuto && !bottomIsAuto) { + // RULE 1: (height is content based, solve of top) + heightValue = contentHeight; + topValue = availableSpace - (heightValue + bottom.width(containerHeight)); + } + else if (topIsAuto && !heightIsAuto && bottomIsAuto) { + // RULE 2: (shouldn't happen) + } + else if (!topIsAuto && heightIsAuto && bottomIsAuto) { + // RULE 3: (height is content based, no need solve of bottom) + heightValue = contentHeight; + topValue = top.width(containerHeight); + } else if (topIsAuto && !heightIsAuto && !bottomIsAuto) { + // RULE 4: (solve of top) + topValue = availableSpace - (heightValue + bottom.width(containerHeight)); + } else if (!topIsAuto && heightIsAuto && !bottomIsAuto) { + // RULE 5: (solve of height) + topValue = top.width(containerHeight); + heightValue = kMax(0, availableSpace - (topValue + bottom.width(containerHeight))); + } else if (!topIsAuto && !heightIsAuto && bottomIsAuto) { + // RULE 6: (no need solve of bottom) + topValue = top.width(containerHeight); + } + } + + // Use computed values to calculate the vertical position. + yPos = topValue + marginTopValue + containerBlock->borderTop(); +} + +void RenderBox::calcAbsoluteHorizontalReplaced() +{ + // The following is based off of the W3C Working Draft from April 11, 2006 of + // CSS 2.1: Section 10.3.8 "Absolutly positioned, replaced elements" + // <http://www.w3.org/TR/2005/WD-CSS21-20050613/visudet.html#abs-replaced-width> + // (block-style-comments in this function correspond to text from the spec and + // the numbers correspond to numbers in spec) + + // We don't use containingBlock(), since we may be positioned by an enclosing relpositioned inline. + const RenderObject* containerBlock = container(); + + // FIXME: This is incorrect for cases where the container block is a relatively + // positioned inline. + const int containerWidth = containingBlockWidth() + containerBlock->paddingLeft() + containerBlock->paddingRight(); + + // To match WinIE, in quirks mode use the parent's 'direction' property + // instead of the the container block's. + EDirection containerDirection = (style()->htmlHacks()) ? parent()->style()->direction() : containerBlock->style()->direction(); + + // Variables to solve. + Length left = style()->left(); + Length right = style()->right(); + Length marginLeft = style()->marginLeft(); + Length marginRight = style()->marginRight(); + + + /*-----------------------------------------------------------------------*\ + * 1. The used value of 'width' is determined as for inline replaced + * elements. + \*-----------------------------------------------------------------------*/ + // NOTE: This value of width is FINAL in that the min/max width calculations + // are dealt with in calcReplacedWidth(). This means that the steps to produce + // correct max/min in the non-replaced version, are not necessary. + m_width = calcReplacedWidth() + borderLeft() + borderRight() + paddingLeft() + paddingRight(); + const int availableSpace = containerWidth - m_width; + + /*-----------------------------------------------------------------------*\ + * 2. If both 'left' and 'right' have the value 'auto', then if 'direction' + * of the containing block is 'ltr', set 'left' to the static position; + * else if 'direction' is 'rtl', set 'right' to the static position. + \*-----------------------------------------------------------------------*/ + if (left.isVariable() && right.isVariable()) { + // see FIXME 1 + if (containerDirection == LTR) { + // 'm_staticX' should already have been set through layout of the parent. + int staticPosition = m_staticX - containerBlock->borderLeft(); + for (RenderObject* po = parent(); po && po != containerBlock; po = po->parent()) + staticPosition += po->xPos(); + left.setValue(Fixed, staticPosition); + } else { + RenderObject* po = parent(); + // 'm_staticX' should already have been set through layout of the parent. + int staticPosition = m_staticX + containerWidth + containerBlock->borderRight() - po->width(); + for (; po && po != containerBlock; po = po->parent()) + staticPosition -= po->xPos(); + right.setValue(Fixed, staticPosition); + } + } + + /*-----------------------------------------------------------------------*\ + * 3. If 'left' or 'right' are 'auto', replace any 'auto' on 'margin-left' + * or 'margin-right' with '0'. + \*-----------------------------------------------------------------------*/ + if (left.isVariable() || right.isVariable()) { + if (marginLeft.isVariable()) + marginLeft.setValue(Fixed, 0); + if (marginRight.isVariable()) + marginRight.setValue(Fixed, 0); + } + + /*-----------------------------------------------------------------------*\ + * 4. If at this point both 'margin-left' and 'margin-right' are still + * 'auto', solve the equation under the extra constraint that the two + * margins must get equal values, unless this would make them negative, + * in which case when the direction of the containing block is 'ltr' + * ('rtl'), set 'margin-left' ('margin-right') to zero and solve for + * 'margin-right' ('margin-left'). + \*-----------------------------------------------------------------------*/ + int leftValue = 0; + int rightValue = 0; + + if (marginLeft.isVariable() && marginRight.isVariable()) { + // 'left' and 'right' cannot be 'auto' due to step 3 + assert(!(left.isVariable() && right.isVariable())); + + leftValue = left.width(containerWidth); + rightValue = right.width(containerWidth); + + int difference = availableSpace - (leftValue + rightValue); + if (difference > 0) { + m_marginLeft = difference / 2; // split the diference + m_marginRight = difference - m_marginLeft; // account for odd valued differences + } else { + // see FIXME 1 + if (containerDirection == LTR) { + m_marginLeft = 0; + m_marginRight = difference; // will be negative + } else { + m_marginLeft = difference; // will be negative + m_marginRight = 0; + } + } + + /*-----------------------------------------------------------------------*\ + * 5. If at this point there is an 'auto' left, solve the equation for + * that value. + \*-----------------------------------------------------------------------*/ + } else if (left.isVariable()) { + m_marginLeft = marginLeft.width(containerWidth); + m_marginRight = marginRight.width(containerWidth); + rightValue = right.width(containerWidth); + + // Solve for 'left' + leftValue = availableSpace - (rightValue + m_marginLeft + m_marginRight); + } else if (right.isVariable()) { + m_marginLeft = marginLeft.width(containerWidth); + m_marginRight = marginRight.width(containerWidth); + leftValue = left.width(containerWidth); + + // Solve for 'right' + rightValue = availableSpace - (leftValue + m_marginLeft + m_marginRight); + } else if (marginLeft.isVariable()) { + m_marginRight = marginRight.width(containerWidth); + leftValue = left.width(containerWidth); + rightValue = right.width(containerWidth); + + // Solve for 'margin-left' + m_marginLeft = availableSpace - (leftValue + rightValue + m_marginRight); + } else if (marginRight.isVariable()) { + m_marginLeft = marginLeft.width(containerWidth); + leftValue = left.width(containerWidth); + rightValue = right.width(containerWidth); + + // Solve for 'margin-right' + m_marginRight = availableSpace - (leftValue + rightValue + m_marginLeft); + } + + /*-----------------------------------------------------------------------*\ + * 6. If at this point the values are over-constrained, ignore the value + * for either 'left' (in case the 'direction' property of the + * containing block is 'rtl') or 'right' (in case 'direction' is + * 'ltr') and solve for that value. + \*-----------------------------------------------------------------------*/ + else { + m_marginLeft = marginLeft.width(containerWidth); + m_marginRight = marginRight.width(containerWidth); + if (containerDirection == LTR) { + leftValue = left.width(containerWidth); + rightValue = availableSpace - (leftValue + m_marginLeft + m_marginRight); + } + else { + rightValue = right.width(containerWidth); + leftValue = availableSpace - (rightValue + m_marginLeft + m_marginRight); + } + } + + int totalWidth = m_width + leftValue + rightValue + m_marginLeft + m_marginRight; + if (totalWidth > containerWidth && (containerDirection == RTL)) + leftValue = containerWidth - (totalWidth - leftValue); + + // Use computed values to calculate the horizontal position. + m_x = leftValue + m_marginLeft + containerBlock->borderLeft(); +} + +void RenderBox::calcAbsoluteVerticalReplaced() +{ + // The following is based off of the W3C Working Draft from April 11, 2006 of + // CSS 2.1: Section 10.6.5 "Absolutly positioned, replaced elements" + // <http://www.w3.org/TR/2005/WD-CSS21-20050613/visudet.html#abs-replaced-height> + // (block-style-comments in this function correspond to text from the spec and + // the numbers correspond to numbers in spec) + + // We don't use containingBlock(), since we may be positioned by an enclosing relpositioned inline. + const RenderObject* containerBlock = container(); + const int containerHeight = containerBlock->height() - containerBlock->borderTop() - containerBlock->borderBottom(); + + // Variables to solve. + Length top = style()->top(); + Length bottom = style()->bottom(); + Length marginTop = style()->marginTop(); + Length marginBottom = style()->marginBottom(); + + + /*-----------------------------------------------------------------------*\ + * 1. The used value of 'height' is determined as for inline replaced + * elements. + \*-----------------------------------------------------------------------*/ + // NOTE: This value of height is FINAL in that the min/max height calculations + // are dealt with in calcReplacedHeight(). This means that the steps to produce + // correct max/min in the non-replaced version, are not necessary. + m_height = calcReplacedHeight() + borderTop() + borderBottom() + paddingTop() + paddingBottom(); + const int availableSpace = containerHeight - m_height; + + /*-----------------------------------------------------------------------*\ + * 2. If both 'top' and 'bottom' have the value 'auto', replace 'top' + * with the element's static position. + \*-----------------------------------------------------------------------*/ + if (top.isVariable() && bottom.isVariable()) { + // m_staticY should already have been set through layout of the parent(). + int staticTop = m_staticY - containerBlock->borderTop(); + for (RenderObject* po = parent(); po && po != containerBlock; po = po->parent()) { + staticTop += po->yPos(); + } + top.setValue(Fixed, staticTop); + } + + /*-----------------------------------------------------------------------*\ + * 3. If 'bottom' is 'auto', replace any 'auto' on 'margin-top' or + * 'margin-bottom' with '0'. + \*-----------------------------------------------------------------------*/ + // FIXME: The spec. says that this step should only be taken when bottom is + // auto, but if only top is auto, this makes step 4 impossible. + if (top.isVariable() || bottom.isVariable()) { + if (marginTop.isVariable()) + marginTop.setValue(Fixed, 0); + if (marginBottom.isVariable()) + marginBottom.setValue(Fixed, 0); + } + + /*-----------------------------------------------------------------------*\ + * 4. If at this point both 'margin-top' and 'margin-bottom' are still + * 'auto', solve the equation under the extra constraint that the two + * margins must get equal values. + \*-----------------------------------------------------------------------*/ + int topValue = 0; + int bottomValue = 0; + + if (marginTop.isVariable() && marginBottom.isVariable()) { + // 'top' and 'bottom' cannot be 'auto' due to step 2 and 3 combinded. + assert(!(top.isVariable() || bottom.isVariable())); + + topValue = top.width(containerHeight); + bottomValue = bottom.width(containerHeight); + + int difference = availableSpace - (topValue + bottomValue); + // NOTE: This may result in negative values. + m_marginTop = difference / 2; // split the difference + m_marginBottom = difference - m_marginTop; // account for odd valued differences + + /*-----------------------------------------------------------------------*\ + * 5. If at this point there is only one 'auto' left, solve the equation + * for that value. + \*-----------------------------------------------------------------------*/ + } else if (top.isVariable()) { + m_marginTop = marginTop.width(containerHeight); + m_marginBottom = marginBottom.width(containerHeight); + bottomValue = bottom.width(containerHeight); + + // Solve for 'top' + topValue = availableSpace - (bottomValue + m_marginTop + m_marginBottom); + } else if (bottom.isVariable()) { + m_marginTop = marginTop.width(containerHeight); + m_marginBottom = marginBottom.width(containerHeight); + topValue = top.width(containerHeight); + + // Solve for 'bottom' + // NOTE: It is not necessary to solve for 'bottom' because we don't ever + // use the value. + } else if (marginTop.isVariable()) { + m_marginBottom = marginBottom.width(containerHeight); + topValue = top.width(containerHeight); + bottomValue = bottom.width(containerHeight); + + // Solve for 'margin-top' + m_marginTop = availableSpace - (topValue + bottomValue + m_marginBottom); + } else if (marginBottom.isVariable()) { + m_marginTop = marginTop.width(containerHeight); + topValue = top.width(containerHeight); + bottomValue = bottom.width(containerHeight); + + // Solve for 'margin-bottom' + m_marginBottom = availableSpace - (topValue + bottomValue + m_marginTop); + } + + /*-----------------------------------------------------------------------*\ + * 6. If at this point the values are over-constrained, ignore the value + * for 'bottom' and solve for that value. + \*-----------------------------------------------------------------------*/ + else { + m_marginTop = marginTop.width(containerHeight); + m_marginBottom = marginBottom.width(containerHeight); + topValue = top.width(containerHeight); + + // Solve for 'bottom' + // NOTE: It is not necessary to solve for 'bottom' because we don't ever + // use the value. + } + + // Use computed values to calculate the vertical position. + m_y = topValue + m_marginTop + containerBlock->borderTop(); +} + +int RenderBox::highestPosition(bool /*includeOverflowInterior*/, bool includeSelf) const +{ + return includeSelf ? 0 : m_height; +} + +int RenderBox::lowestPosition(bool /*includeOverflowInterior*/, bool includeSelf) const +{ + return includeSelf ? m_height : 0; +} + +int RenderBox::rightmostPosition(bool /*includeOverflowInterior*/, bool includeSelf) const +{ + return includeSelf ? m_width : 0; +} + +int RenderBox::leftmostPosition(bool /*includeOverflowInterior*/, bool includeSelf) const +{ + return includeSelf ? 0 : m_width; +} + +int RenderBox::pageTopAfter(int y) const +{ + RenderObject* cb = container(); + if (cb) + return cb->pageTopAfter(y+yPos()) - yPos(); + else + return 0; +} + +int RenderBox::crossesPageBreak(int t, int b) const +{ + RenderObject* cb = container(); + if (cb) + return cb->crossesPageBreak(yPos()+t, yPos()+b); + else + return false; +} + +void RenderBox::caretPos(int /*offset*/, int flags, int &_x, int &_y, int &width, int &height) +{ +#if 0 + _x = -1; + + // propagate it downwards to its children, someone will feel responsible + RenderObject *child = firstChild(); +// if (child) kdDebug(6040) << "delegating caretPos to " << child->renderName() << endl; + if (child) child->caretPos(offset, override, _x, _y, width, height); + + // if not, use the extents of this box. offset 0 means left, offset 1 means + // right + if (_x == -1) { + //kdDebug(6040) << "no delegation" << endl; + _x = xPos() + (offset == 0 ? 0 : m_width); + _y = yPos(); + height = m_height; + width = override && offset == 0 ? m_width : 1; + + // If height of box is smaller than font height, use the latter one, + // otherwise the caret might become invisible. + // FIXME: ignoring :first-line, missing good reason to take care of + int fontHeight = style()->fontMetrics().height(); + if (fontHeight > height) + height = fontHeight; + + int absx, absy; + + RenderObject *cb = containingBlock(); + + if (cb && cb != this && cb->absolutePosition(absx,absy)) { + //kdDebug(6040) << "absx=" << absx << " absy=" << absy << endl; + _x += absx; + _y += absy; + } else { + // we don't know our absolute position, and there is no point returning + // just a relative one + _x = _y = -1; + } + } +#endif + + _x = xPos(); + _y = yPos(); +// kdDebug(6040) << "_x " << _x << " _y " << _y << endl; + width = 1; // no override is indicated in boxes + + RenderBlock *cb = containingBlock(); + + // Place caret outside the border + if (flags & CFOutside) { + + RenderStyle *s = element() && element()->parent() + && element()->parent()->renderer() + ? element()->parent()->renderer()->style() + : cb->style(); + + const QFontMetrics &fm = s->fontMetrics(); + height = fm.height(); + + bool rtl = s->direction() == RTL; + bool outsideEnd = flags & CFOutsideEnd; + + if (outsideEnd) { + _x += this->width(); + } else { + _x--; + } + + int hl = fm.leading() / 2; + if (!isReplaced() || style()->display() == BLOCK) { + if (!outsideEnd ^ rtl) + _y -= hl; + else + _y += kMax(this->height() - fm.ascent() - hl, 0); + } else { + _y += baselinePosition(false) - fm.ascent() - hl; + } + + // Place caret inside the element + } else { + const QFontMetrics &fm = style()->fontMetrics(); + height = fm.height(); + + RenderStyle *s = style(); + + _x += borderLeft() + paddingLeft(); + _y += borderTop() + paddingTop(); + + // ### regard direction + switch (s->textAlign()) { + case LEFT: + case KHTML_LEFT: + case TAAUTO: // ### find out what this does + case JUSTIFY: + break; + case CENTER: + case KHTML_CENTER: + _x += contentWidth() / 2; + break; + case KHTML_RIGHT: + case RIGHT: + _x += contentWidth(); + break; + } + } + + int absx, absy; + if (cb && cb != this && cb->absolutePosition(absx,absy)) { +// kdDebug(6040) << "absx=" << absx << " absy=" << absy << endl; + _x += absx; + _y += absy; + } else { + // we don't know our absolute position, and there is no point returning + // just a relative one + _x = _y = -1; + } +} + +#undef DEBUG_LAYOUT diff --git a/khtml/rendering/render_box.h b/khtml/rendering/render_box.h new file mode 100644 index 000000000..ad0a5b9fb --- /dev/null +++ b/khtml/rendering/render_box.h @@ -0,0 +1,213 @@ +/* + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@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. + * + */ +#ifndef RENDER_BOX_H +#define RENDER_BOX_H + +#include "render_container.h" + +namespace khtml { + +enum WidthType { Width, MinWidth, MaxWidth }; +enum HeightType { Height, MinHeight, MaxHeight }; + +class RenderBlock; + +class RenderBox : public RenderContainer +{ + + +// combines ElemImpl & PosElImpl (all rendering objects are positioned) +// should contain all border and padding handling + +public: + RenderBox(DOM::NodeImpl* node); + virtual ~RenderBox(); + + virtual const char *renderName() const { return "RenderBox"; } + virtual bool isBox() const { return true; } + + virtual void setStyle(RenderStyle *style); + virtual void paint(PaintInfo& i, int _tx, int _ty); + + virtual void close(); + + virtual InlineBox* createInlineBox(bool makePlaceHolderBox, bool isRootLineBox); + virtual void deleteInlineBoxes(RenderArena* arena=0); + + virtual void detach(); + + virtual short minWidth() const { return m_minWidth; } + virtual int maxWidth() const { return m_maxWidth; } + + virtual short contentWidth() const; + virtual int contentHeight() const; + + virtual bool absolutePosition(int &xPos, int &yPos, bool f = false) const; + + virtual void setPos( int xPos, int yPos ); + + virtual int xPos() const { return m_x; } + virtual int yPos() const { return m_y; } + virtual short width() const; + virtual int height() const; + + virtual short marginTop() const { return m_marginTop; } + virtual short marginBottom() const { return m_marginBottom; } + virtual short marginLeft() const { return m_marginLeft; } + virtual short marginRight() const { return m_marginRight; } + + virtual void setWidth( int width ); + virtual void setHeight( int height ); + + virtual void position(InlineBox* box, int from, int len, bool reverse); + + virtual int highestPosition(bool includeOverflowInterior=true, bool includeSelf=true) const; + virtual int lowestPosition(bool includeOverflowInterior=true, bool includeSelf=true) const; + virtual int rightmostPosition(bool includeOverflowInterior=true, bool includeSelf=true) const; + virtual int leftmostPosition(bool includeOverflowInterior=true, bool includeSelf=true) const; + + virtual void repaint(Priority p=NormalPriority); + + virtual void repaintRectangle(int x, int y, int w, int h, Priority p=NormalPriority, bool f=false); + + virtual short containingBlockWidth() const; + void relativePositionOffset(int &tx, int &ty) const; + + virtual void calcWidth(); + virtual void calcHeight(); + + virtual short calcReplacedWidth() const; + virtual int calcReplacedHeight() const; + + virtual int availableHeight() const; + virtual int availableWidth() const; + + void calcVerticalMargins(); + + virtual RenderLayer* layer() const { return m_layer; } + + void setStaticX(short staticX); + void setStaticY(int staticY); + int staticX() const { return m_staticX; } + int staticY() const { return m_staticY; } + + virtual void caretPos(int offset, int flags, int &_x, int &_y, int &width, int &height); + + void calcHorizontalMargins(const Length& ml, const Length& mr, int cw); + RenderBlock* createAnonymousBlock(); + + virtual int pageTopAfter(int y) const; + virtual int crossesPageBreak(int t, int b) const; + + int calcBoxWidth(int w) const; + int calcBoxHeight(int h) const; + int calcContentWidth(int w) const; + int calcContentHeight(int h) const; + +protected: + int calcWidthUsing(WidthType widthType, int cw, LengthType& lengthType); + int calcHeightUsing(const Length& height); + int calcReplacedWidthUsing(WidthType widthType) const; + int calcReplacedHeightUsing(HeightType heightType) const; + int calcPercentageHeight(const Length& height, bool treatAsReplaced = false) const; + int availableHeightUsing(const Length& h) const; + int availableWidthUsing(const Length& w) const; + int calcImplicitHeight() const; + bool hasImplicitHeight() const { + return isPositioned() && !style()->top().isVariable() && !style()->bottom().isVariable(); + } + +protected: + virtual void paintBoxDecorations(PaintInfo& paintInfo, int _tx, int _ty); + void paintRootBoxDecorations( PaintInfo& paintInfo, int _tx, int _ty); + + void paintBackgrounds(QPainter *p, const QColor& c, const BackgroundLayer* bgLayer, int clipy, int cliph, int _tx, int _ty, int w, int h); + void paintBackground(QPainter *p, const QColor& c, const BackgroundLayer* bgLayer, int clipy, int cliph, int _tx, int _ty, int w, int h); + + virtual void paintBackgroundExtended(QPainter* /*p*/, const QColor& /*c*/, const BackgroundLayer* /*bgLayer*/, + int /*clipy*/, int /*cliph*/, int /*_tx*/, int /*_ty*/, + int /*w*/, int /*height*/, int /*bleft*/, int /*bright*/, int /*pleft*/, int /*pright*/ ); + + void outlineBox(QPainter *p, int _tx, int _ty, const char *color = "red"); + + void calcAbsoluteHorizontal(); + void calcAbsoluteVertical(); + void calcAbsoluteHorizontalValues(Length width, const RenderObject* cb, EDirection containerDirection, + const int containerWidth, const int bordersPlusPadding, + const Length left, const Length right, const Length marginLeft, const Length marginRight, + short& widthValue, short& marginLeftValue, short& marginRightValue, short& xPos); + void calcAbsoluteVerticalValues(Length height, const RenderObject* cb, + const int containerHeight, const int bordersPlusPadding, + const Length top, const Length bottom, const Length marginTop, const Length marginBottom, + int& heightValue, short& marginTopValue, short& marginBottomValue, int& yPos); + + void calcAbsoluteVerticalReplaced(); + void calcAbsoluteHorizontalReplaced(); + + QRect getOverflowClipRect(int tx, int ty); + QRect getClipRect(int tx, int ty); + + void restructureParentFlow(); + + + // the actual height of the contents + borders + padding (border-box) + int m_height; + int m_y; + + short m_width; + short m_x; + + short m_marginTop; + short m_marginBottom; + + short m_marginLeft; + short m_marginRight; + + /* + * the minimum width the element needs, to be able to render + * its content without clipping + */ + short m_minWidth; + /* The maximum width the element can fill horizontally + * ( = the width of the element with line breaking disabled) + */ + int m_maxWidth; + + // Cached normal flow values for absolute positioned elements with static left/top values. + short m_staticX; + int m_staticY; + + RenderLayer *m_layer; + + /* A box used to represent this object on a line + * when its inner content isn't contextually relevant + * (e.g replaced or positioned elements) + */ + InlineBox *m_placeHolderBox; +}; + + +} //namespace + +#endif diff --git a/khtml/rendering/render_br.cpp b/khtml/rendering/render_br.cpp new file mode 100644 index 000000000..42709839a --- /dev/null +++ b/khtml/rendering/render_br.cpp @@ -0,0 +1,79 @@ +/** + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ +#include "render_br.h" + +using namespace khtml; + + +RenderBR::RenderBR(DOM::NodeImpl* node) + : RenderText(node, new DOM::DOMStringImpl(QChar('\n'))) +{ + m_hasReturn = true; +} + +RenderBR::~RenderBR() +{ +} + +#if 0 +void RenderBR::caretPos(int offset, int flags, int &_x, int &_y, int &width, int &height) +{ + RenderText::caretPos(offset,flags,_x,_y,width,height); + return; +#if 0 + if (previousSibling() && !previousSibling()->isBR() && !previousSibling()->isFloating()) { + int offset = 0; + if (previousSibling()->isText()) + offset = static_cast<RenderText*>(previousSibling())->maxOffset(); + + // FIXME: this won't return a big width in override mode (LS) + previousSibling()->caretPos(offset,override,_x,_y,width,height); + return; + } + + int absx, absy; + absolutePosition(absx,absy); + if (absx == -1) { + // we don't know out absolute position, and there is no point returning + // just a relative one + _x = _y = -1; + } + else { + _x += absx; + _y += absy; + } + height = RenderText::verticalPositionHint( false ); + width = override ? height / 2 : 1; +#endif +} +#endif + +FindSelectionResult RenderBR::checkSelectionPoint(int _x, int _y, int _tx, int _ty, DOM::NodeImpl*& node, int &offset, SelPointState &state) +{ + // Simply take result of previous one + RenderText *prev = static_cast<RenderText *>(previousSibling()); + if (!prev || !prev->isText() || !prev->inlineTextBoxCount() || prev->isBR()) + prev = this; + + //kdDebug(6040) << "delegated to " << prev->renderName() << "@" << prev << endl; + return prev->RenderText::checkSelectionPoint(_x, _y, _tx, _ty, node, offset, state); +} diff --git a/khtml/rendering/render_br.h b/khtml/rendering/render_br.h new file mode 100644 index 000000000..f4175015f --- /dev/null +++ b/khtml/rendering/render_br.h @@ -0,0 +1,77 @@ +/* + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 2000 Lars Knoll (knoll@kde.org) + * + * 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. + * + */ +#ifndef RENDER_BR_H +#define RENDER_BR_H + +#include "render_text.h" + +/* + * The whole class here is a hack to get <br> working, as long as we don't have support for + * CSS2 :before and :after pseudo elements + */ +namespace khtml { + +class RenderBR : public RenderText +{ +public: + RenderBR(DOM::NodeImpl* node); + virtual ~RenderBR(); + + virtual const char *renderName() const { return "RenderBR"; } + + virtual void paint( PaintInfo&, int, int) {} + + virtual unsigned int width(unsigned int, unsigned int, const Font *) const { return 0; } + virtual unsigned int width( unsigned int, unsigned int, bool) const { return 0; } + virtual short width() const { return RenderText::width(); } + + virtual int height() const { return 0; } + + // overrides + virtual void calcMinMaxWidth() {} + virtual short minWidth() const { return 0; } + virtual int maxWidth() const { return 0; } + + virtual FindSelectionResult checkSelectionPoint( int _x, int _y, int _tx, int _ty, + DOM::NodeImpl*& node, int & offset, + SelPointState & ); + + virtual bool isBR() const { return true; } +#if 0 + virtual void caretPos(int offset, int flags, int &_x, int &_y, int &width, int &height); +#endif + /** returns the lowest possible value the caret offset may have to + * still point to a valid position. + * + * Returns 0. + */ + virtual long minOffset() const { return 0; } + /** returns the highest possible value the caret offset may have to + * still point to a valid position. + * + * Returns also 0, as BRs have no width. + */ + virtual long maxOffset() const { return 0; } +}; + +} +#endif diff --git a/khtml/rendering/render_canvas.cpp b/khtml/rendering/render_canvas.cpp new file mode 100644 index 000000000..50cad914a --- /dev/null +++ b/khtml/rendering/render_canvas.cpp @@ -0,0 +1,780 @@ +/** + * This file is part of the HTML widget for KDE. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 2003 Apple Computer, Inc. + * (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + + +#include "rendering/render_canvas.h" +#include "rendering/render_layer.h" +#include "xml/dom_docimpl.h" + +#include "khtmlview.h" +#include "khtml_part.h" +#include <kdebug.h> +#include <kglobal.h> + +using namespace khtml; + +//#define BOX_DEBUG +//#define SPEED_DEBUG + +RenderCanvas::RenderCanvas(DOM::NodeImpl* node, KHTMLView *view) + : RenderBlock(node) +{ + // init RenderObject attributes + setInline(false); + setIsAnonymous(false); + + m_view = view; + // try to contrain the width to the views width + + m_minWidth = 0; + m_height = 0; + + m_width = m_minWidth; + m_maxWidth = m_minWidth; + + m_rootWidth = m_rootHeight = 0; + m_viewportWidth = m_viewportHeight = 0; + m_cachedDocWidth = m_cachedDocHeight = -1; + + setPositioned(true); // to 0,0 :) + + m_staticMode = false; + m_pagedMode = false; + m_printImages = true; + + m_pageTop = 0; + m_pageBottom = 0; + + m_page = 0; + + m_maximalOutlineSize = 0; + + m_selectionStart = 0; + m_selectionEnd = 0; + m_selectionStartPos = -1; + m_selectionEndPos = -1; + + m_needsWidgetMasks = false; + + // Create a new root layer for our layer hierarchy. + m_layer = new (node->getDocument()->renderArena()) RenderLayer(this); +} + +RenderCanvas::~RenderCanvas() +{ + delete m_page; +} + +void RenderCanvas::setStyle(RenderStyle* style) +{ + /* + if (m_pagedMode) + style->setOverflow(OHIDDEN); */ + RenderBlock::setStyle(style); +} + +void RenderCanvas::calcHeight() +{ + if (m_pagedMode || !m_view) + m_height = m_rootHeight; + else + m_height = m_view->visibleHeight(); +} + +void RenderCanvas::calcWidth() +{ + // the width gets set by KHTMLView::print when printing to a printer. + if(m_pagedMode || !m_view) + { + m_width = m_rootWidth; + return; + } + + m_width = m_view ? m_view->frameWidth() : m_minWidth; + + if (style()->marginLeft().isFixed()) + m_marginLeft = style()->marginLeft().value(); + else + m_marginLeft = 0; + + if (style()->marginRight().isFixed()) + m_marginRight = style()->marginRight().value(); + else + m_marginRight = 0; +} + +void RenderCanvas::calcMinMaxWidth() +{ + KHTMLAssert( !minMaxKnown() ); + + RenderBlock::calcMinMaxWidth(); + + m_maxWidth = m_minWidth; + + setMinMaxKnown(); +} + +//#define SPEED_DEBUG + +void RenderCanvas::layout() +{ + if (m_pagedMode) { + m_minWidth = m_width; +// m_maxWidth = m_width; + } + + m_needsFullRepaint = markedForRepaint() || !view() || view()->needsFullRepaint() || m_pagedMode; + + setChildNeedsLayout(true); + setMinMaxKnown(false); + for(RenderObject* c = firstChild(); c; c = c->nextSibling()) + c->setChildNeedsLayout(true); + + int oldWidth = m_width; + int oldHeight = m_height; + + m_cachedDocWidth = m_cachedDocHeight = -1; + + if (m_pagedMode || !m_view) { + m_width = m_rootWidth; + m_height = m_rootHeight; + } + else + { + m_viewportWidth = m_width = m_view->visibleWidth(); + m_viewportHeight = m_height = m_view->visibleHeight(); + } + +#ifdef SPEED_DEBUG + QTime qt; + qt.start(); +#endif + + if ( recalcMinMax() ) + recalcMinMaxWidths(); + +#ifdef SPEED_DEBUG + kdDebug() << "RenderCanvas::calcMinMax time used=" << qt.elapsed() << endl; + qt.start(); +#endif + + bool relayoutChildren = (oldWidth != m_width) || (oldHeight != m_height); + + RenderBlock::layoutBlock( relayoutChildren ); + +#ifdef SPEED_DEBUG + kdDebug() << "RenderCanvas::layout time used=" << qt.elapsed() << endl; + qt.start(); +#endif + + updateDocumentSize(); + + layer()->updateLayerPositions( layer(), needsFullRepaint(), true ); + + if (!m_pagedMode && m_needsWidgetMasks) + layer()->updateWidgetMasks(layer()); + + scheduleDeferredRepaints(); + setNeedsLayout(false); + +#ifdef SPEED_DEBUG + kdDebug() << "RenderCanvas::end time used=" << qt.elapsed() << endl; +#endif +} + +void RenderCanvas::updateDocumentSize() +{ + // update our cached document size + int hDocH = m_cachedDocHeight = docHeight(); + int hDocW = m_cachedDocWidth = docWidth(); + + if (!m_pagedMode && m_view) { + + bool vss = m_view->verticalScrollBar()->isShown(); + bool hss = m_view->horizontalScrollBar()->isShown(); + QSize s = m_view->viewportSize(m_cachedDocWidth, m_cachedDocHeight); + + // if we are about to show a scrollbar, and the document is sized to the viewport w or h, + // then reserve the scrollbar space so that it doesn't trigger the _other_ scrollbar + + if (!vss && m_width - m_view->verticalScrollBar()->sizeHint().width() == s.width() && + m_cachedDocWidth <= m_width) + hDocW = kMin( m_cachedDocWidth, s.width() ); + + if (!hss && m_height - m_view->horizontalScrollBar()->sizeHint().height() == s.height() && + m_cachedDocHeight <= m_height) + hDocH = kMin( m_cachedDocHeight, s.height() ); + + // likewise, if a scrollbar is shown, and we have a cunning plan to turn it off, + // think again if we are falling downright in the hysteresis zone + + if (vss && s.width() > m_cachedDocWidth && m_cachedDocWidth > m_view->visibleWidth()) + hDocW = s.width()+1; + + if (hss && s.height() > m_cachedDocHeight && m_cachedDocHeight > m_view->visibleHeight()) + hDocH = s.height()+1; + + m_view->resizeContents(hDocW, hDocH); + + setWidth( m_viewportWidth = s.width() ); + setHeight( m_viewportHeight = s.height() ); + } + layer()->resize( kMax( m_cachedDocWidth,int( m_width ) ), kMax( m_cachedDocHeight,m_height ) ); +} + +void RenderCanvas::updateDocSizeAfterLayerTranslation( RenderObject* o, bool posXOffset, bool posYOffset ) +{ + if (needsLayout()) + return; + int rightmost, lowest; + o->absolutePosition( rightmost, lowest ); + if (posXOffset) { + rightmost += o->rightmostPosition(false, true); + setCachedDocWidth( kMax(docWidth(), rightmost) ); + } else { + setCachedDocWidth( -1 ); + } + if (posYOffset) { + lowest += o->lowestPosition(false, true); + setCachedDocHeight( kMax(docHeight(), lowest) ); + } else { + setCachedDocHeight( -1 ); + } +// kdDebug() << " posXOffset: " << posXOffset << " posYOffset " << posYOffset << " m_cachedDocWidth " << m_cachedDocWidth << " m_cachedDocHeight " << m_cachedDocHeight << endl; + updateDocumentSize(); +} + +bool RenderCanvas::needsFullRepaint() const +{ + return m_needsFullRepaint || m_pagedMode; +} + +void RenderCanvas::repaintViewRectangle(int x, int y, int w, int h, bool asap) +{ + KHTMLAssert( view() ); + view()->scheduleRepaint( x, y, w, h, asap ); +} + +bool RenderCanvas::absolutePosition(int &xPos, int &yPos, bool f) const +{ + if ( f && m_pagedMode) { + xPos = 0; + yPos = m_pageTop; + } + else if ( f && m_view) { + xPos = m_view->contentsX(); + yPos = m_view->contentsY(); + } + else { + xPos = yPos = 0; + } + return true; +} + +void RenderCanvas::paint(PaintInfo& paintInfo, int _tx, int _ty) +{ +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << this << " ::paintObject() w/h = (" << width() << "/" << height() << ")" << endl; +#endif + + // 1. paint background, borders etc + if(paintInfo.phase == PaintActionElementBackground) { + paintBoxDecorations(paintInfo, _tx, _ty); + return; + } + + // 2. paint contents + for( RenderObject *child = firstChild(); child; child=child->nextSibling()) + if(!child->layer() && !child->isFloating()) + child->paint(paintInfo, _tx, _ty); + + // 3. paint floats. + if (paintInfo.phase == PaintActionFloat) + paintFloats(paintInfo, _tx, _ty); + +#ifdef BOX_DEBUG + if (m_view) + { + _tx += m_view->contentsX(); + _ty += m_view->contentsY(); + } + + outlineBox(p, _tx, _ty); +#endif + +} + +void RenderCanvas::paintBoxDecorations(PaintInfo& paintInfo, int /*_tx*/, int /*_ty*/) +{ + if ((firstChild() && firstChild()->style()->visibility() == VISIBLE) || !view()) + return; + + paintInfo.p->fillRect(paintInfo.r, view()->palette().active().color(QColorGroup::Base)); +} + +void RenderCanvas::repaintRectangle(int x, int y, int w, int h, Priority p, bool f) +{ + if (m_staticMode) return; +// kdDebug( 6040 ) << "updating views contents (" << x << "/" << y << ") (" << w << "/" << h << ")" << endl; + + if (f && m_pagedMode) { + y += m_pageTop; + } else + if ( f && m_view ) { + x += m_view->contentsX(); + y += m_view->contentsY(); + } + + QRect vr = viewRect(); + QRect ur(x, y, w, h); + + if (m_view && ur.intersects(vr)) { + + if (p == RealtimePriority) + // ### KWQ's updateContents has an additional parameter "now". + // It's not clear what the difference between updateContents(...,true) + // and repaintContents(...) is. As Qt doesn't have this, I'm leaving it out. (LS) + m_view->updateContents(ur/*, true*/); + else if (p == HighPriority) + m_view->scheduleRepaint(x, y, w, h, true /*asap*/); + else + m_view->scheduleRepaint(x, y, w, h); + } +} + +void RenderCanvas::deferredRepaint( RenderObject* o ) +{ + m_dirtyChildren.append( o ); +} + +void RenderCanvas::scheduleDeferredRepaints() +{ + if (!needsFullRepaint()) { + QValueList<RenderObject*>::const_iterator it; + for ( it = m_dirtyChildren.begin(); it != m_dirtyChildren.end(); ++it ) + (*it)->repaint(); + } + //kdDebug(6040) << "scheduled deferred repaints: " << m_dirtyChildren.count() << " needed full repaint: " << needsFullRepaint() << endl; + m_dirtyChildren.clear(); +} + +void RenderCanvas::repaint(Priority p) +{ + if (m_view && !m_staticMode) { + if (p == RealtimePriority) { + //m_view->resizeContents(docWidth(), docHeight()); + m_view->unscheduleRepaint(); + if (needsLayout()) { + m_view->scheduleRelayout(); + return; + } + // ### same as in repaintRectangle + m_view->updateContents(m_view->contentsX(), m_view->contentsY(), + m_view->visibleWidth(), m_view->visibleHeight()/*, true*/); + } + else if (p == HighPriority) + m_view->scheduleRepaint(m_view->contentsX(), m_view->contentsY(), + m_view->visibleWidth(), m_view->visibleHeight(), true /*asap*/); + else + m_view->scheduleRepaint(m_view->contentsX(), m_view->contentsY(), + m_view->visibleWidth(), m_view->visibleHeight()); + } +} + +static QRect enclosingPositionedRect (RenderObject *n) +{ + RenderObject *enclosingParent = n->containingBlock(); + QRect rect(0,0,0,0); + if (enclosingParent) { + int ox, oy; + enclosingParent->absolutePosition(ox, oy); + int off = 0; + if (!enclosingParent->hasOverflowClip()) { + ox += enclosingParent->overflowLeft(); + oy += enclosingParent->overflowTop(); + } + rect.setX(ox); + rect.setY(oy); + rect.setWidth(enclosingParent->effectiveWidth()); + rect.setHeight(enclosingParent->effectiveHeight()); + } + return rect; +} + +QRect RenderCanvas::selectionRect() const +{ + RenderObject *r = m_selectionStart; + if (!r) + return QRect(); + + QRect selectionRect = enclosingPositionedRect(r); + + while (r && r != m_selectionEnd) + { + RenderObject* n; + if ( !(n = r->firstChild()) ){ + if ( !(n = r->nextSibling()) ) + { + n = r->parent(); + while (n && !n->nextSibling()) + n = n->parent(); + if (n) + n = n->nextSibling(); + } + } + r = n; + if (r) { + selectionRect = selectionRect.unite(enclosingPositionedRect(r)); + } + } + + return selectionRect; +} + +void RenderCanvas::setSelection(RenderObject *s, int sp, RenderObject *e, int ep) +{ + // Check we got valid renderobjects. www.msnbc.com and clicking + // around, to find the case where this happened. + if ( !s || !e ) + { + kdWarning(6040) << "RenderCanvas::setSelection() called with start=" << s << " end=" << e << endl; + return; + } +// kdDebug( 6040 ) << "RenderCanvas::setSelection(" << s << "," << sp << "," << e << "," << ep << ")" << endl; + + bool changedSelectionBorder = ( s != m_selectionStart || e != m_selectionEnd ); + + // Cut out early if the selection hasn't changed. + if ( !changedSelectionBorder && m_selectionStartPos == sp && m_selectionEndPos == ep ) + return; + + // Record the old selected objects. Will be used later + // to delta against the selected objects. + + RenderObject *oldStart = m_selectionStart; + int oldStartPos = m_selectionStartPos; + RenderObject *oldEnd = m_selectionEnd; + int oldEndPos = m_selectionEndPos; + QPtrList<RenderObject> oldSelectedInside; + QPtrList<RenderObject> newSelectedInside; + RenderObject *os = oldStart; + + while (os && os != oldEnd) + { + RenderObject* no; + if ( !(no = os->firstChild()) ){ + if ( !(no = os->nextSibling()) ) + { + no = os->parent(); + while (no && !no->nextSibling()) + no = no->parent(); + if (no) + no = no->nextSibling(); + } + } + if (os->selectionState() == SelectionInside && !oldSelectedInside.containsRef(os)) + oldSelectedInside.append(os); + + os = no; + } + if (changedSelectionBorder) + clearSelection(false); + + while (s->firstChild()) + s = s->firstChild(); + while (e->lastChild()) + e = e->lastChild(); + +#if 0 + bool changedSelectionBorder = ( s != m_selectionStart || e != m_selectionEnd ); + + if ( !changedSelectionBorder && m_selectionStartPos == sp && m_selectionEndPos = ep ) + return; +#endif + + // set selection start + if (m_selectionStart) + m_selectionStart->setIsSelectionBorder(false); + m_selectionStart = s; + if (m_selectionStart) + m_selectionStart->setIsSelectionBorder(true); + m_selectionStartPos = sp; + + // set selection end + if (m_selectionEnd) + m_selectionEnd->setIsSelectionBorder(false); + m_selectionEnd = e; + if (m_selectionEnd) + m_selectionEnd->setIsSelectionBorder(true); + m_selectionEndPos = ep; + +#if 0 + kdDebug( 6040 ) << "old selection (" << oldStart << "," << oldStartPos << "," << oldEnd << "," << oldEndPos << ")" << endl; + kdDebug( 6040 ) << "new selection (" << s << "," << sp << "," << e << "," << ep << ")" << endl; +#endif + + // update selection status of all objects between m_selectionStart and m_selectionEnd + RenderObject* o = s; + + while (o && o!=e) + { + o->setSelectionState(SelectionInside); +// kdDebug( 6040 ) << "setting selected " << o << ", " << o->isText() << endl; + RenderObject* no; + if ( !(no = o->firstChild()) ) + if ( !(no = o->nextSibling()) ) + { + no = o->parent(); + while (no && !no->nextSibling()) + no = no->parent(); + if (no) + no = no->nextSibling(); + } + if (o->selectionState() == SelectionInside && !newSelectedInside.containsRef(o)) + newSelectedInside.append(o); + + o=no; + } + s->setSelectionState(SelectionStart); + e->setSelectionState(SelectionEnd); + if(s == e) s->setSelectionState(SelectionBoth); + + if (!m_view) + return; + + newSelectedInside.removeRef(s); + newSelectedInside.removeRef(e); + + QRect updateRect; + + // Don't use repaint() because it will cause all rects to + // be united (see khtmlview::scheduleRepaint()). Instead + // just draw damage rects for objects that have a change + // in selection state. + // ### for Qt, updateContents will unite them, too. This has to be + // circumvented somehow (LS) + + // Are any of the old fully selected objects not in the new selection? + // If so we have to draw them. + // Could be faster by building list of non-intersecting rectangles rather + // than unioning rectangles. + QPtrListIterator<RenderObject> oldIterator(oldSelectedInside); + bool firstRect = true; + for (; oldIterator.current(); ++oldIterator){ + if (!newSelectedInside.containsRef(oldIterator.current())){ + if (firstRect){ + updateRect = enclosingPositionedRect(oldIterator.current()); + firstRect = false; + } + else + updateRect = updateRect.unite(enclosingPositionedRect(oldIterator.current())); + } + } + if (!firstRect){ + m_view->updateContents( updateRect ); + } + + // Are any of the new fully selected objects not in the previous selection? + // If so we have to draw them. + // Could be faster by building list of non-intersecting rectangles rather + // than unioning rectangles. + QPtrListIterator<RenderObject> newIterator(newSelectedInside); + firstRect = true; + for (; newIterator.current(); ++newIterator){ + if (!oldSelectedInside.containsRef(newIterator.current())){ + if (firstRect){ + updateRect = enclosingPositionedRect(newIterator.current()); + firstRect = false; + } + else + updateRect = updateRect.unite(enclosingPositionedRect(newIterator.current())); + } + } + if (!firstRect) { + m_view->updateContents( updateRect ); + } + + // Is the new starting object different, or did the position in the starting + // element change? If so we have to draw it. + if (oldStart != m_selectionStart || + (oldStart == oldEnd && (oldStartPos != m_selectionStartPos || oldEndPos != m_selectionEndPos)) || + (oldStart == m_selectionStart && oldStartPos != m_selectionStartPos)){ + m_view->updateContents( enclosingPositionedRect(m_selectionStart) ); + } + + // Draw the old selection start object if it's different than the new selection + // start object. + if (oldStart && oldStart != m_selectionStart){ + m_view->updateContents( enclosingPositionedRect(oldStart) ); + } + + // Does the selection span objects and is the new end object different, or did the position + // in the end element change? If so we have to draw it. + if (/*(oldStart != oldEnd || !oldEnd) &&*/ + (oldEnd != m_selectionEnd || + (oldEnd == m_selectionEnd && oldEndPos != m_selectionEndPos))){ + m_view->updateContents( enclosingPositionedRect(m_selectionEnd) ); + } + + // Draw the old selection end object if it's different than the new selection + // end object. + if (oldEnd && oldEnd != m_selectionEnd){ + m_view->updateContents( enclosingPositionedRect(oldEnd) ); + } +} + +void RenderCanvas::clearSelection(bool doRepaint) +{ + // update selection status of all objects between m_selectionStart and m_selectionEnd + RenderObject* o = m_selectionStart; + while (o && o!=m_selectionEnd) + { + if (o->selectionState()!=SelectionNone) + if (doRepaint) + o->repaint(); + o->setSelectionState(SelectionNone); + o->repaint(); + RenderObject* no; + if ( !(no = o->firstChild()) ) + if ( !(no = o->nextSibling()) ) + { + no = o->parent(); + while (no && !no->nextSibling()) + no = no->parent(); + if (no) + no = no->nextSibling(); + } + o=no; + } + if (m_selectionEnd) { + m_selectionEnd->setSelectionState(SelectionNone); + if (doRepaint) + m_selectionEnd->repaint(); + } + + // set selection start & end to 0 + if (m_selectionStart) + m_selectionStart->setIsSelectionBorder(false); + m_selectionStart = 0; + m_selectionStartPos = -1; + + if (m_selectionEnd) + m_selectionEnd->setIsSelectionBorder(false); + m_selectionEnd = 0; + m_selectionEndPos = -1; +} + +void RenderCanvas::selectionStartEnd(int& spos, int& epos) +{ + spos = m_selectionStartPos; + epos = m_selectionEndPos; +} + +QRect RenderCanvas::viewRect() const +{ + if (m_pagedMode) + if (m_pageTop == m_pageBottom) { + kdDebug(6040) << "viewRect: " << QRect(0, m_pageTop, m_width, m_height) << endl; + return QRect(0, m_pageTop, m_width, m_height); + } + else { + kdDebug(6040) << "viewRect: " << QRect(0, m_pageTop, m_width, m_pageBottom - m_pageTop) << endl; + return QRect(0, m_pageTop, m_width, m_pageBottom - m_pageTop); + } + else if (m_view) + return QRect(m_view->contentsX(), + m_view->contentsY(), + m_view->visibleWidth(), + m_view->visibleHeight()); + else + return QRect(0,0,m_rootWidth,m_rootHeight); +} + +int RenderCanvas::docHeight() const +{ + if (m_cachedDocHeight != -1) + return m_cachedDocHeight; + + int h; + if (m_pagedMode || !m_view) + h = m_height; + else + h = 0; + + RenderObject *fc = firstChild(); + if(fc) { + int dh = fc->overflowHeight() + fc->marginTop() + fc->marginBottom(); + int lowestPos = fc->lowestPosition(false); +// kdDebug(6040) << "h " << h << " lowestPos " << lowestPos << " dh " << dh << " fc->rh " << fc->effectiveHeight() << " fc->height() " << fc->height() << endl; + if( lowestPos > dh ) + dh = lowestPos; + lowestPos = lowestAbsolutePosition(); + if( lowestPos > dh ) + dh = lowestPos; + if( dh > h ) + h = dh; + } + + RenderLayer *layer = m_layer; + h = kMax( h, layer->yPos() + layer->height() ); +// kdDebug(6040) << "h " << h << " layer(" << layer->renderer()->renderName() << "@" << layer->renderer() << ")->height " << layer->height() << " lp " << (layer->yPos() + layer->height()) << " height() " << layer->renderer()->height() << " rh " << layer->renderer()->effectiveHeight() << endl; + return h; +} + +int RenderCanvas::docWidth() const +{ + if (m_cachedDocWidth != -1) + return m_cachedDocWidth; + + int w; + if (m_pagedMode || !m_view) + w = m_width; + else + w = 0; + + RenderObject *fc = firstChild(); + if(fc) { + // ow: like effectiveWidth() but without the negative + const int ow = fc->hasOverflowClip() ? fc->width() : fc->overflowWidth(); + int dw = ow + fc->marginLeft() + fc->marginRight(); + int rightmostPos = fc->rightmostPosition(false); +// kdDebug(6040) << "w " << w << " rightmostPos " << rightmostPos << " dw " << dw << " fc->rw " << fc->effectiveWidth() << " fc->width() " << fc->width() << endl; + if( rightmostPos > dw ) + dw = rightmostPos; + rightmostPos = rightmostAbsolutePosition(); + if ( rightmostPos > dw ) + dw = rightmostPos; + if( dw > w ) + w = dw; + } + + RenderLayer *layer = m_layer; + w = kMax( w, layer->xPos() + layer->width() ); +// kdDebug(6040) << "w " << w << " layer(" << layer->renderer()->renderName() << ")->width " << layer->width() << " rm " << (layer->xPos() + layer->width()) << " width() " << layer->renderer()->width() << " rw " << layer->renderer()->effectiveWidth() << endl; + return w; +} + +RenderPage* RenderCanvas::page() { + if (!m_page) m_page = new RenderPage(this); + return m_page; +} diff --git a/khtml/rendering/render_canvas.h b/khtml/rendering/render_canvas.h new file mode 100644 index 000000000..017bd02b2 --- /dev/null +++ b/khtml/rendering/render_canvas.h @@ -0,0 +1,250 @@ +/* + * This file is part of the HTML widget for KDE. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) + * + * 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. + * + */ +#ifndef render_canvas_h +#define render_canvas_h + +#include "render_block.h" + +class KHTMLView; +class QScrollView; + +namespace khtml { + +class RenderPage; +class RenderStyle; + +enum CanvasMode { + CanvasViewPort, // Paints inside a viewport + CanvasPage, // Paints one page + CanvasDocument // Paints the whole document +}; + +class RenderCanvas : public RenderBlock +{ +public: + RenderCanvas(DOM::NodeImpl* node, KHTMLView *view); + ~RenderCanvas(); + + virtual const char *renderName() const { return "RenderCanvas"; } + + virtual bool isCanvas() const { return true; } + + virtual void setStyle(RenderStyle *style); + virtual void layout(); + virtual void calcWidth(); + virtual void calcHeight(); + virtual void calcMinMaxWidth(); + virtual bool absolutePosition(int &xPos, int&yPos, bool f = false) const; + + int docHeight() const; + int docWidth() const; + + KHTMLView *view() const { return m_view; } + + virtual void repaint(Priority p=NormalPriority); + virtual void repaintRectangle(int x, int y, int w, int h, Priority p=NormalPriority, bool f=false); + void repaintViewRectangle(int x, int y, int w, int h, bool asap=false); + bool needsFullRepaint() const; + void deferredRepaint( RenderObject* o ); + void scheduleDeferredRepaints(); + + virtual void paint(PaintInfo&, int tx, int ty); + virtual void paintBoxDecorations(PaintInfo& paintInfo, int _tx, int _ty); + virtual void setSelection(RenderObject *s, int sp, RenderObject *e, int ep); + virtual void clearSelection(bool doRepaint=true); + virtual RenderObject *selectionStart() const { return m_selectionStart; } + virtual RenderObject *selectionEnd() const { return m_selectionEnd; } + + void setPrintImages(bool enable) { m_printImages = enable; } + bool printImages() const { return m_printImages; } + + void setCanvasMode(CanvasMode mode) { m_canvasMode = mode; } + CanvasMode canvasMode() const { return m_canvasMode; } + + void setPagedMode(bool b) { m_pagedMode = b; } + void setStaticMode(bool b) { m_staticMode = b; } + + bool pagedMode() const { return m_pagedMode; } + bool staticMode() const { return m_staticMode; } + + void setPageTop(int top) { + m_pageTop = top; +// m_y = top; + } + void setPageBottom(int bottom) { m_pageBottom = bottom; } + int pageTop() const { return m_pageTop; } + int pageBottom() const { return m_pageBottom; } + + int pageTopAfter(int y) const { + if (pageHeight() == 0) return 0; + return (y / pageHeight() + 1) * pageHeight() ; + } + + int crossesPageBreak(int top, int bottom) const { + if (pageHeight() == 0) return false; + int pT = top / pageHeight(); + // bottom is actually the first line not in the box + int pB = (bottom-1) / pageHeight(); + return (pT == pB) ? 0 : (pB + 1); + } + + void setPageNumber(int number) { m_pageNr = number; } + int pageNumber() const { return m_pageNr; } + +public: + virtual void setWidth( int width ) { m_rootWidth = m_width = width; } + virtual void setHeight( int height ) { m_rootHeight = m_height = height; } + +// void setPageHeight( int height ) { m_viewportHeight = m_pageHeight = height; } + int pageHeight() const { return m_pageBottom - m_pageTop; } + + int viewportWidth() const { return m_viewportWidth; } + int viewportHeight() const { return m_viewportHeight; } + + RenderPage* page(); + + QRect selectionRect() const; + + void setMaximalOutlineSize(int o) { m_maximalOutlineSize = o; } + int maximalOutlineSize() const { return m_maximalOutlineSize; } + + void setNeedsWidgetMasks( bool b=true) { m_needsWidgetMasks = b; } + bool needsWidgetMasks() const { return m_needsWidgetMasks; } + + void updateDocSizeAfterLayerTranslation( RenderObject* o, bool posXOffset, bool posYOffset ); +protected: + // makes sure document, scrollbars and viewport size are accurate + void updateDocumentSize(); + + // internal setters for cached values of document width/height + // Setting to -1/-1 invalidates the cache. + void setCachedDocWidth(int w ) { m_cachedDocWidth = w; } + void setCachedDocHeight(int h) { m_cachedDocHeight = h; } + + virtual void selectionStartEnd(int& spos, int& epos); + + virtual QRect viewRect() const; + + KHTMLView *m_view; + + RenderObject* m_selectionStart; + RenderObject* m_selectionEnd; + int m_selectionStartPos; + int m_selectionEndPos; + + CanvasMode m_canvasMode; + + int m_rootWidth; + int m_rootHeight; + + int m_viewportWidth; + int m_viewportHeight; + + int m_cachedDocWidth; + int m_cachedDocHeight; + + bool m_printImages; + bool m_needsFullRepaint; + + // Canvas is not interactive + bool m_staticMode; + // Canvas is paged + bool m_pagedMode; + // Canvas contains overlaid widgets + bool m_needsWidgetMasks; + + short m_pageNr; + + int m_pageTop; + int m_pageBottom; + + RenderPage* m_page; + + int m_maximalOutlineSize; // Used to apply a fudge factor to dirty-rect checks on blocks/tables. + QValueList<RenderObject*> m_dirtyChildren; +}; + +inline RenderCanvas* RenderObject::canvas() const +{ + return static_cast<RenderCanvas*>(document()->renderer()); +} + +// Represents the page-context of CSS +class RenderPage +{ +public: + RenderPage(RenderCanvas* canvas) : m_canvas(canvas), + m_marginTop(0), m_marginBottom(0), + m_marginLeft(0), m_marginRight(0), + m_pageWidth(0), m_pageHeight(0), + m_fixedSize(false) + { + m_style = new RenderPageStyle(); + } + virtual ~RenderPage() + { + delete m_style; + } + + int marginTop() const { return m_marginTop; } + int marginBottom() const { return m_marginBottom; } + int marginLeft() const { return m_marginLeft; } + int marginRight() const { return m_marginRight; } + + void setMarginTop(int margin) { m_marginTop = margin; } + void setMarginBottom(int margin) { m_marginBottom = margin; } + void setMarginLeft(int margin) { m_marginLeft = margin; } + void setMarginRight(int margin) { m_marginRight = margin; } + + int pageWidth() const { return m_pageWidth; } + int pageHeight() const { return m_pageHeight; } + + void setPageSize(int width, int height) { + m_pageWidth = width; + m_pageHeight = height; + } + + // Returns true if size was set by document, false if set by user-agent + bool fixedSize() const { return m_fixedSize; } + void setFixedSize(bool b) { m_fixedSize = b; } + + RenderPageStyle* style() { return m_style; } + const RenderPageStyle* style() const { return m_style; } + +protected: + RenderCanvas* m_canvas; + RenderPageStyle* m_style; + + int m_marginTop; + int m_marginBottom; + int m_marginLeft; + int m_marginRight; + + int m_pageWidth; + int m_pageHeight; + + bool m_fixedSize; +}; + +} +#endif 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 diff --git a/khtml/rendering/render_container.h b/khtml/rendering/render_container.h new file mode 100644 index 000000000..4cf386140 --- /dev/null +++ b/khtml/rendering/render_container.h @@ -0,0 +1,85 @@ +/* + * This file is part of the html renderer for KDE. + * + * Copyright (C) 2001 Antti Koivisto (koivisto@kde.org) + * + * 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. + * + */ +#ifndef render_container_h +#define render_container_h + +#include "render_object.h" + +namespace khtml +{ + + +/** + * Base class for rendering objects that can have children + */ +class RenderContainer : public RenderObject +{ +public: + RenderContainer(DOM::NodeImpl* node); + + void detach(); + + RenderObject *firstChild() const { return m_first; } + RenderObject *lastChild() const { return m_last; } + + virtual bool childAllowed() const { + // Prevent normal children when we are replaced by generated content + if (style()) return style()->useNormalContent(); + return true; + } + + virtual void addChild(RenderObject *newChild, RenderObject *beforeChild = 0); + + virtual RenderObject* removeChildNode(RenderObject* child); + virtual void appendChildNode(RenderObject* child); + virtual void insertChildNode(RenderObject* child, RenderObject* before); + + virtual void layout(); + virtual void calcMinMaxWidth() { setMinMaxKnown( true ); } + + virtual void removeLeftoverAnonymousBoxes(); + + virtual void setStyle(RenderStyle* _style); + +protected: + // Generate CSS content + void createGeneratedContent(); + void updateReplacedContent(); + + void updatePseudoChildren(); + void updatePseudoChild(RenderStyle::PseudoId type); + + RenderContainer* pseudoContainer( RenderStyle::PseudoId type ) const; + void addPseudoContainer(RenderObject* child); +private: + + void setFirstChild(RenderObject *first) { m_first = first; } + void setLastChild(RenderObject *last) { m_last = last; } + +protected: + + RenderObject *m_first; + RenderObject *m_last; +}; + +} +#endif diff --git a/khtml/rendering/render_flow.cpp b/khtml/rendering/render_flow.cpp new file mode 100644 index 000000000..88e06aab9 --- /dev/null +++ b/khtml/rendering/render_flow.cpp @@ -0,0 +1,412 @@ +/** + * This file is part of the html renderer for KDE. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 1999-2003 Antti Koivisto (koivisto@kde.org) + * (C) 2002-2003 Dirk Mueller (mueller@kde.org) + * (C) 2003-2006 Apple Computer, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +// ------------------------------------------------------------------------- + +#include <kdebug.h> +#include <assert.h> +#include <qpainter.h> +#include <kglobal.h> + +#include "rendering/render_flow.h" +#include "rendering/render_text.h" +#include "rendering/render_table.h" +#include "rendering/render_canvas.h" +#include "rendering/render_inline.h" +#include "rendering/render_block.h" +#include "rendering/render_arena.h" +#include "rendering/render_line.h" +#include "xml/dom_nodeimpl.h" +#include "xml/dom_docimpl.h" +#include "misc/htmltags.h" +#include "html/html_formimpl.h" + +#include "khtmlview.h" + +using namespace DOM; +using namespace khtml; + +RenderFlow* RenderFlow::createFlow(DOM::NodeImpl* node, RenderStyle* style, RenderArena* arena) +{ + RenderFlow* result; + if (style->display() == INLINE) + result = new (arena) RenderInline(node); + else + result = new (arena) RenderBlock(node); + result->setStyle(style); + return result; +} + +RenderFlow* RenderFlow::continuationBefore(RenderObject* beforeChild) +{ + if (beforeChild && beforeChild->parent() == this) + return this; + + RenderFlow* curr = continuation(); + RenderFlow* nextToLast = this; + RenderFlow* last = this; + while (curr) { + if (beforeChild && beforeChild->parent() == curr) { + if (curr->firstChild() == beforeChild) + return last; + return curr; + } + + nextToLast = last; + last = curr; + curr = curr->continuation(); + } + + if (!beforeChild && !last->firstChild()) + return nextToLast; + return last; +} + +void RenderFlow::addChildWithContinuation(RenderObject* newChild, RenderObject* beforeChild) +{ + RenderFlow* flow = continuationBefore(beforeChild); + while(beforeChild && beforeChild->parent() != flow && !beforeChild->parent()->isAnonymousBlock()) { + // skip implicit containers around beforeChild + beforeChild = beforeChild->parent(); + } + RenderFlow* beforeChildParent = beforeChild ? static_cast<RenderFlow*>(beforeChild->parent()) : + (flow->continuation() ? flow->continuation() : flow); + + if (newChild->isFloatingOrPositioned()) + return beforeChildParent->addChildToFlow(newChild, beforeChild); + + // A continuation always consists of two potential candidates: an inline or an anonymous + // block box holding block children. + bool childInline = newChild->isInline(); + bool bcpInline = beforeChildParent->isInline(); + bool flowInline = flow->isInline(); + + if (flow == beforeChildParent) + return flow->addChildToFlow(newChild, beforeChild); + else { + // The goal here is to match up if we can, so that we can coalesce and create the + // minimal # of continuations needed for the inline. + if (childInline == bcpInline) + return beforeChildParent->addChildToFlow(newChild, beforeChild); + else if (flowInline == childInline) + return flow->addChildToFlow(newChild, 0); // Just treat like an append. + else + return beforeChildParent->addChildToFlow(newChild, beforeChild); + } +} + +void RenderFlow::addChild(RenderObject *newChild, RenderObject *beforeChild) +{ +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(RenderFlow)::addChild( " << newChild->renderName() << + ", " << (beforeChild ? beforeChild->renderName() : "0") << " )" << endl; + kdDebug( 6040 ) << "current height = " << m_height << endl; +#endif + + if (continuation()) + return addChildWithContinuation(newChild, beforeChild); + return addChildToFlow(newChild, beforeChild); +} + +void RenderFlow::deleteInlineBoxes(RenderArena* arena) +{ + RenderBox::deleteInlineBoxes(arena); //In case we upcalled + //during construction + if (m_firstLineBox) { + if (!arena) + arena = renderArena(); + InlineRunBox *curr=m_firstLineBox, *next=0; + while (curr) { + next = curr->nextLineBox(); + curr->detach(arena); + curr = next; + } + m_firstLineBox = 0; + m_lastLineBox = 0; + } +} + +void RenderFlow::deleteLastLineBox(RenderArena* arena) +{ + if (m_lastLineBox) { + if (!arena) + arena = renderArena(); + InlineRunBox *curr=m_lastLineBox, *prev = m_lastLineBox; + if (m_firstLineBox == m_lastLineBox) + m_firstLineBox = m_lastLineBox = 0; + else { + prev = curr->prevLineBox(); + while (!prev->isInlineFlowBox()) { + prev = prev->prevLineBox(); + prev->detach(arena); + } + m_lastLineBox = static_cast<InlineFlowBox*>(prev); + prev->setNextLineBox(0); + } + if (curr->parent()) { + curr->parent()->removeFromLine(curr); + } + curr->detach(arena); + } +} + +InlineBox* RenderFlow::createInlineBox(bool makePlaceHolderBox, bool isRootLineBox) +{ + if ( !isRootLineBox && + (isReplaced() || makePlaceHolderBox) ) // Inline tables and inline blocks + return RenderBox::createInlineBox(false, false); // (or positioned element placeholders). + + InlineFlowBox* flowBox = 0; + if (isInlineFlow()) + flowBox = new (renderArena()) InlineFlowBox(this); + else + flowBox = new (renderArena()) RootInlineBox(this); + + if (!m_firstLineBox) { + m_firstLineBox = m_lastLineBox = flowBox; + } else { + m_lastLineBox->setNextLineBox(flowBox); + flowBox->setPreviousLineBox(m_lastLineBox); + m_lastLineBox = flowBox; + } + + return flowBox; +} + +void RenderFlow::paintLines(PaintInfo& i, int _tx, int _ty) +{ + // Only paint during the foreground/selection phases. + if (i.phase != PaintActionForeground && i.phase != PaintActionSelection && i.phase != PaintActionOutline) + return; + + if (!firstLineBox()) + return; + + // We can check the first box and last box and avoid painting if we don't + // intersect. This is a quick short-circuit that we can take to avoid walking any lines. + // FIXME: This check is flawed in two extremely obscure ways. + // (1) If some line in the middle has a huge overflow, it might actually extend below the last line. + // (2) The overflow from an inline block on a line is not reported to the line. + int maxOutlineSize = maximalOutlineSize(i.phase); + int yPos = firstLineBox()->root()->topOverflow() - maxOutlineSize; + int h = maxOutlineSize + lastLineBox()->root()->bottomOverflow() - yPos; + yPos += _ty; + if ((yPos >= i.r.y() + i.r.height()) || (yPos + h <= i.r.y())) + return; + for (InlineFlowBox* curr = firstLineBox(); curr; curr = curr->nextFlowBox()) { + yPos = curr->root()->topOverflow() - maxOutlineSize; + h = curr->root()->bottomOverflow() + maxOutlineSize - yPos; + yPos += _ty; + if ((yPos < i.r.y() + i.r.height()) && (yPos + h > i.r.y())) + curr->paint(i, _tx, _ty); + } + + if (i.phase == PaintActionOutline && i.outlineObjects) { + QValueList<RenderFlow *>::iterator it;; + for( it = (*i.outlineObjects).begin(); it != (*i.outlineObjects).end(); ++it ) + if ((*it)->isRenderInline()) + static_cast<RenderInline*>(*it)->paintOutlines(i.p, _tx, _ty); + i.outlineObjects->clear(); + } +} + + +bool RenderFlow::hitTestLines(NodeInfo& i, int x, int y, int tx, int ty, HitTestAction hitTestAction) +{ + (void) hitTestAction; + /* + if (hitTestAction != HitTestForeground) // ### port hitTest + return false; + */ + + if (!firstLineBox()) + return false; + + // We can check the first box and last box and avoid hit testing if we don't + // contain the point. This is a quick short-circuit that we can take to avoid walking any lines. + // FIXME: This check is flawed in two extremely obscure ways. + // (1) If some line in the middle has a huge overflow, it might actually extend below the last line. + // (2) The overflow from an inline block on a line is not reported to the line. + if ((y >= ty + lastLineBox()->root()->bottomOverflow()) || (y < ty + firstLineBox()->root()->topOverflow())) + return false; + + // See if our root lines contain the point. If so, then we hit test + // them further. Note that boxes can easily overlap, so we can't make any assumptions + // based off positions of our first line box or our last line box. + for (InlineFlowBox* curr = lastLineBox(); curr; curr = curr->prevFlowBox()) { + if (y >= ty + curr->root()->topOverflow() && y < ty + curr->root()->bottomOverflow()) { + bool inside = curr->nodeAtPoint(i, x, y, tx, ty); + if (inside) { + setInnerNode(i); + return true; + } + } + } + + return false; +} + + +void RenderFlow::repaint(Priority prior) +{ + if (isInlineFlow()) { + // Find our leftmost position. + int left = 0; + // root inline box not reliably availabe during relayout + int top = firstLineBox() ? ( + needsLayout() ? firstLineBox()->xPos() : firstLineBox()->root()->topOverflow() + ) : 0; + for (InlineRunBox* curr = firstLineBox(); curr; curr = curr->nextLineBox()) + if (curr == firstLineBox() || curr->xPos() < left) + left = curr->xPos(); + + // Now invalidate a rectangle. + int ow = style() ? style()->outlineSize() : 0; + + // We need to add in the relative position offsets of any inlines (including us) up to our + // containing block. + RenderBlock* cb = containingBlock(); + for (RenderObject* inlineFlow = this; inlineFlow && inlineFlow->isInlineFlow() && inlineFlow != cb; + inlineFlow = inlineFlow->parent()) { + if (inlineFlow->style() && inlineFlow->style()->position() == RELATIVE && inlineFlow->layer()) { + KHTMLAssert(inlineFlow->isBox()); + static_cast<RenderBox*>(inlineFlow)->relativePositionOffset(left, top); + } + } + + RootInlineBox *lastRoot = lastLineBox() && !needsLayout() ? lastLineBox()->root() : 0; + containingBlock()->repaintRectangle(-ow+left, -ow+top, + width()+ow*2, + (lastRoot ? lastRoot->bottomOverflow() - top : height())+ow*2, prior); + } + else { + if (firstLineBox() && firstLineBox()->topOverflow() < 0) { + int ow = style() ? style()->outlineSize() : 0; + repaintRectangle(-ow, -ow+firstLineBox()->topOverflow(), + effectiveWidth()+ow*2, effectiveHeight()+ow*2, prior); + } + else + return RenderBox::repaint(prior); + } +} + +int +RenderFlow::lowestPosition(bool includeOverflowInterior, bool includeSelf) const +{ + int bottom = RenderBox::lowestPosition(includeOverflowInterior, includeSelf); + if (!includeOverflowInterior && hasOverflowClip()) + return bottom; + + // FIXME: Come up with a way to use the layer tree to avoid visiting all the kids. + // For now, we have to descend into all the children, since we may have a huge abs div inside + // a tiny rel div buried somewhere deep in our child tree. In this case we have to get to + // the abs div. + for (RenderObject *c = firstChild(); c; c = c->nextSibling()) { + if (!c->isFloatingOrPositioned() && !c->isText() && !c->isInlineFlow()) { + int lp = c->yPos() + c->lowestPosition(false); + bottom = kMax(bottom, lp); + } + } + + if (isRelPositioned()) { + int x; + relativePositionOffset(x, bottom); + } + + return bottom; +} + +int RenderFlow::rightmostPosition(bool includeOverflowInterior, bool includeSelf) const +{ + int right = RenderBox::rightmostPosition(includeOverflowInterior, includeSelf); + if (!includeOverflowInterior && hasOverflowClip()) + return right; + + // FIXME: Come up with a way to use the layer tree to avoid visiting all the kids. + // For now, we have to descend into all the children, since we may have a huge abs div inside + // a tiny rel div buried somewhere deep in our child tree. In this case we have to get to + // the abs div. + for (RenderObject *c = firstChild(); c; c = c->nextSibling()) { + if (!c->isFloatingOrPositioned() && !c->isText() && !c->isInlineFlow()) { + int rp = c->xPos() + c->rightmostPosition(false); + right = kMax(right, rp); + } + } + + if (isRelPositioned()) { + int y; + relativePositionOffset(right, y); + } + + return right; +} + +int RenderFlow::leftmostPosition(bool includeOverflowInterior, bool includeSelf) const +{ + int left = RenderBox::leftmostPosition(includeOverflowInterior, includeSelf); + if (!includeOverflowInterior && hasOverflowClip()) + return left; + + // FIXME: Come up with a way to use the layer tree to avoid visiting all the kids. + // For now, we have to descend into all the children, since we may have a huge abs div inside + // a tiny rel div buried somewhere deep in our child tree. In this case we have to get to + // the abs div. + for (RenderObject *c = firstChild(); c; c = c->nextSibling()) { + if (!c->isFloatingOrPositioned() && !c->isText() && !c->isInlineFlow()) { + int lp = c->xPos() + c->leftmostPosition(false); + left = kMin(left, lp); + } + } + + if (isRelPositioned()) { + int y; + relativePositionOffset(left, y); + } + + return left; +} + +int RenderFlow::highestPosition(bool includeOverflowInterior, bool includeSelf) const +{ + int top = RenderBox::highestPosition(includeOverflowInterior, includeSelf); + if (!includeOverflowInterior && hasOverflowClip()) + return top; + + // FIXME: Come up with a way to use the layer tree to avoid visiting all the kids. + // For now, we have to descend into all the children, since we may have a huge abs div inside + // a tiny rel div buried somewhere deep in our child tree. In this case we have to get to + // the abs div. + for (RenderObject *c = firstChild(); c; c = c->nextSibling()) { + if (!c->isFloatingOrPositioned() && !c->isText() && !c->isInlineFlow()) { + int hp = c->yPos() + c->highestPosition(false); + top = kMin(top, hp); + } + } + + if (isRelPositioned()) { + int x; + relativePositionOffset(x, top); + } + + return top; +} diff --git a/khtml/rendering/render_flow.h b/khtml/rendering/render_flow.h new file mode 100644 index 000000000..1e23822bb --- /dev/null +++ b/khtml/rendering/render_flow.h @@ -0,0 +1,96 @@ +/* + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 1999-2003 Antti Koivisto (koivisto@kde.org) + * (C) 2002-2003 Dirk Mueller (mueller@kde.org) + * (C) 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. + * + */ +#ifndef RENDER_FLOW_H +#define RENDER_FLOW_H + +#include "render_box.h" +#include "bidi.h" +#include "render_line.h" + +namespace khtml { + +/** + * all geometry managing stuff is only in the block elements. + * + * Inline elements don't layout themselves, but the whole paragraph + * gets flowed by the surrounding block element. This is, because + * one needs to know the whole paragraph to calculate bidirectional + * behaviour of text, so putting the layouting routines in the inline + * elements is impossible. + */ +class RenderFlow : public RenderBox +{ +public: + RenderFlow(DOM::NodeImpl* node) + : RenderBox(node) + { m_continuation = 0; m_firstLineBox = 0; m_lastLineBox = 0; } + + virtual RenderFlow* continuation() const { return m_continuation; } + void setContinuation(RenderFlow* c) { m_continuation = c; } + RenderFlow* continuationBefore(RenderObject* beforeChild); + + void addChildWithContinuation(RenderObject* newChild, RenderObject* beforeChild); + virtual void addChildToFlow(RenderObject* newChild, RenderObject* beforeChild) = 0; + virtual void addChild(RenderObject *newChild, RenderObject *beforeChild = 0); + + static RenderFlow* createFlow(DOM::NodeImpl* node, RenderStyle* style, RenderArena* arena); + + virtual void deleteLastLineBox(RenderArena* arena=0); + virtual void deleteInlineBoxes(RenderArena* arena=0); + + + InlineFlowBox* firstLineBox() const { return m_firstLineBox; } + InlineFlowBox* lastLineBox() const { return m_lastLineBox; } + + virtual InlineBox* createInlineBox(bool makePlaceHolderBox, bool isRootLineBox); + + void paintLines(PaintInfo& i, int _tx, int _ty); + bool hitTestLines(NodeInfo& i, int x, int y, int tx, int ty, HitTestAction hitTestAction); + + virtual void repaint(Priority p=NormalPriority); + + virtual int highestPosition(bool includeOverflowInterior=true, bool includeSelf=true) const; + virtual int lowestPosition(bool includeOverflowInterior=true, bool includeSelf=true) const; + virtual int rightmostPosition(bool includeOverflowInterior=true, bool includeSelf=true) const; + virtual int leftmostPosition(bool includeOverflowInterior=true, bool includeSelf=true) const; + +protected: + // An inline can be split with blocks occurring in between the inline content. + // When this occurs we need a pointer to our next object. We can basically be + // split into a sequence of inlines and blocks. The continuation will either be + // an anonymous block (that houses other blocks) or it will be an inline flow. + RenderFlow* m_continuation; + + // For block flows, each box represents the root inline box for a line in the + // paragraph. + // For inline flows, each box represents a portion of that inline. + InlineFlowBox* m_firstLineBox; + InlineFlowBox* m_lastLineBox; +}; + + +} //namespace + +#endif diff --git a/khtml/rendering/render_form.cpp b/khtml/rendering/render_form.cpp new file mode 100644 index 000000000..f8daba1e3 --- /dev/null +++ b/khtml/rendering/render_form.cpp @@ -0,0 +1,1898 @@ +/* + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000 Dirk Mueller (mueller@kde.org) + * (C) 2006 Maksim Orlovich (maksim@kde.org) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include <kcompletionbox.h> +#include <kcursor.h> +#include <kdebug.h> +#include <kfiledialog.h> +#include <kfind.h> +#include <kfinddialog.h> +#include <kiconloader.h> +#include <klocale.h> +#include <kmessagebox.h> +#include <kreplace.h> +#include <kreplacedialog.h> +#include <kspell.h> +#include <kurlcompletion.h> +#include <kwin.h> + +#include <qstyle.h> + +#include "misc/helper.h" +#include "xml/dom2_eventsimpl.h" +#include "html/html_formimpl.h" +#include "misc/htmlhashes.h" + +#include "rendering/render_form.h" +#include <assert.h> + +#include "khtmlview.h" +#include "khtml_ext.h" +#include "xml/dom_docimpl.h" + +#include <qpopupmenu.h> +#include <qbitmap.h> + +using namespace khtml; + +RenderFormElement::RenderFormElement(HTMLGenericFormElementImpl *element) + : RenderWidget(element) +{ + // init RenderObject attributes + setInline(true); // our object is Inline + + m_state = 0; +} + +RenderFormElement::~RenderFormElement() +{ +} + +short RenderFormElement::baselinePosition( bool f ) const +{ + return RenderWidget::baselinePosition( f ) - 2 - style()->fontMetrics().descent(); +} + +void RenderFormElement::updateFromElement() +{ + m_widget->setEnabled(!element()->disabled()); + RenderWidget::updateFromElement(); +} + +void RenderFormElement::layout() +{ + KHTMLAssert( needsLayout() ); + KHTMLAssert( minMaxKnown() ); + + // minimum height + m_height = 0; + + calcWidth(); + calcHeight(); + + if ( m_widget ) + resizeWidget(m_width-borderLeft()-borderRight()-paddingLeft()-paddingRight(), + m_height-borderTop()-borderBottom()-paddingTop()-paddingBottom()); + + setNeedsLayout(false); +} + +Qt::AlignmentFlags RenderFormElement::textAlignment() const +{ + switch (style()->textAlign()) { + case LEFT: + case KHTML_LEFT: + return AlignLeft; + case RIGHT: + case KHTML_RIGHT: + return AlignRight; + case CENTER: + case KHTML_CENTER: + return AlignHCenter; + case JUSTIFY: + // Just fall into the auto code for justify. + case TAAUTO: + return style()->direction() == RTL ? AlignRight : AlignLeft; + } + assert(false); // Should never be reached. + return AlignLeft; +} + +// ------------------------------------------------------------------------- + +RenderButton::RenderButton(HTMLGenericFormElementImpl *element) + : RenderFormElement(element) +{ +} + +short RenderButton::baselinePosition( bool f ) const +{ + return RenderWidget::baselinePosition( f ) - 2; +} + +// ------------------------------------------------------------------------------- + + +RenderCheckBox::RenderCheckBox(HTMLInputElementImpl *element) + : RenderButton(element) +{ + QCheckBox* b = new QCheckBox(view()->viewport(), "__khtml"); + b->setAutoMask(true); + b->setMouseTracking(true); + setQWidget(b); + + // prevent firing toggled() signals on initialization + b->setChecked(element->checked()); + + connect(b,SIGNAL(stateChanged(int)),this,SLOT(slotStateChanged(int))); +} + + +void RenderCheckBox::calcMinMaxWidth() +{ + KHTMLAssert( !minMaxKnown() ); + + QCheckBox *cb = static_cast<QCheckBox *>( m_widget ); + QSize s( cb->style().pixelMetric( QStyle::PM_IndicatorWidth ), + cb->style().pixelMetric( QStyle::PM_IndicatorHeight ) ); + setIntrinsicWidth( s.width() ); + setIntrinsicHeight( s.height() ); + + RenderButton::calcMinMaxWidth(); +} + +void RenderCheckBox::updateFromElement() +{ + widget()->setChecked(element()->checked()); + + RenderButton::updateFromElement(); +} + +void RenderCheckBox::slotStateChanged(int state) +{ + element()->setChecked(state == QButton::On); + element()->setIndeterminate(state == QButton::NoChange); + + ref(); + element()->onChange(); + deref(); +} + +// ------------------------------------------------------------------------------- + +RenderRadioButton::RenderRadioButton(HTMLInputElementImpl *element) + : RenderButton(element) +{ + QRadioButton* b = new QRadioButton(view()->viewport(), "__khtml"); + b->setMouseTracking(true); + setQWidget(b); + + // prevent firing toggled() signals on initialization + b->setChecked(element->checked()); + + connect(b,SIGNAL(toggled(bool)),this,SLOT(slotToggled(bool))); +} + +void RenderRadioButton::updateFromElement() +{ + widget()->setChecked(element()->checked()); + + RenderButton::updateFromElement(); +} + +void RenderRadioButton::calcMinMaxWidth() +{ + KHTMLAssert( !minMaxKnown() ); + + QRadioButton *rb = static_cast<QRadioButton *>( m_widget ); + QSize s( rb->style().pixelMetric( QStyle::PM_ExclusiveIndicatorWidth ), + rb->style().pixelMetric( QStyle::PM_ExclusiveIndicatorHeight ) ); + setIntrinsicWidth( s.width() ); + setIntrinsicHeight( s.height() ); + + RenderButton::calcMinMaxWidth(); +} + +void RenderRadioButton::slotToggled(bool activated) +{ + if(activated) { + ref(); + element()->onChange(); + deref(); + } +} + +// ------------------------------------------------------------------------------- + + +RenderSubmitButton::RenderSubmitButton(HTMLInputElementImpl *element) + : RenderButton(element) +{ + QPushButton* p = new QPushButton(view()->viewport(), "__khtml"); + setQWidget(p); + p->setAutoMask(true); + p->setMouseTracking(true); +} + +QString RenderSubmitButton::rawText() +{ + QString value = element()->valueWithDefault().string(); + value = value.stripWhiteSpace(); + QString raw; + for(unsigned int i = 0; i < value.length(); i++) { + raw += value[i]; + if(value[i] == '&') + raw += '&'; + } + return raw; +} + +void RenderSubmitButton::calcMinMaxWidth() +{ + KHTMLAssert( !minMaxKnown() ); + + QString raw = rawText(); + QPushButton* pb = static_cast<QPushButton*>(m_widget); + pb->setText(raw); + pb->setFont(style()->font()); + + bool empty = raw.isEmpty(); + if ( empty ) + raw = QString::fromLatin1("X"); + QFontMetrics fm = pb->fontMetrics(); + QSize ts = fm.size( ShowPrefix, raw); + QSize s(pb->style().sizeFromContents( QStyle::CT_PushButton, pb, ts ) + .expandedTo(QApplication::globalStrut())); + int margin = pb->style().pixelMetric( QStyle::PM_ButtonMargin, pb) + + pb->style().pixelMetric( QStyle::PM_DefaultFrameWidth, pb ) * 2; + int w = ts.width() + margin; + int h = s.height(); + if (pb->isDefault() || pb->autoDefault()) { + int dbw = pb->style().pixelMetric( QStyle::PM_ButtonDefaultIndicator, pb ) * 2; + w += dbw; + } + + // add 30% margins to the width (heuristics to make it look similar to IE) + s = QSize( w*13/10, h ).expandedTo(QApplication::globalStrut()); + + setIntrinsicWidth( s.width() ); + setIntrinsicHeight( s.height() ); + + RenderButton::calcMinMaxWidth(); +} + +void RenderSubmitButton::updateFromElement() +{ + QString oldText = static_cast<QPushButton*>(m_widget)->text(); + QString newText = rawText(); + static_cast<QPushButton*>(m_widget)->setText(newText); + if ( oldText != newText ) + setNeedsLayoutAndMinMaxRecalc(); + RenderFormElement::updateFromElement(); +} + +short RenderSubmitButton::baselinePosition( bool f ) const +{ + return RenderFormElement::baselinePosition( f ); +} + +// ------------------------------------------------------------------------------- + +RenderResetButton::RenderResetButton(HTMLInputElementImpl *element) + : RenderSubmitButton(element) +{ +} + +// ------------------------------------------------------------------------------- + +LineEditWidget::LineEditWidget(DOM::HTMLInputElementImpl* input, KHTMLView* view, QWidget* parent) + : KLineEdit(parent, "__khtml"), m_input(input), m_view(view), m_spell(0) +{ + setMouseTracking(true); + KActionCollection *ac = new KActionCollection(this); + m_spellAction = KStdAction::spelling( this, SLOT( slotCheckSpelling() ), ac ); +} + +LineEditWidget::~LineEditWidget() +{ + delete m_spell; + m_spell = 0L; +} + +void LineEditWidget::slotCheckSpelling() +{ + if ( text().isEmpty() ) { + return; + } + + delete m_spell; + m_spell = new KSpell( this, i18n( "Spell Checking" ), this, SLOT( slotSpellCheckReady( KSpell *) ), 0, true, true); + + connect( m_spell, SIGNAL( death() ),this, SLOT( spellCheckerFinished() ) ); + connect( m_spell, SIGNAL( misspelling( const QString &, const QStringList &, unsigned int ) ),this, SLOT( spellCheckerMisspelling( const QString &, const QStringList &, unsigned int ) ) ); + connect( m_spell, SIGNAL( corrected( const QString &, const QString &, unsigned int ) ),this, SLOT( spellCheckerCorrected( const QString &, const QString &, unsigned int ) ) ); +} + +void LineEditWidget::spellCheckerMisspelling( const QString &_text, const QStringList &, unsigned int pos) +{ + highLightWord( _text.length(),pos ); +} + +void LineEditWidget::highLightWord( unsigned int length, unsigned int pos ) +{ + setSelection ( pos, length ); +} + +void LineEditWidget::spellCheckerCorrected( const QString &old, const QString &corr, unsigned int pos ) +{ + if( old!= corr ) + { + setSelection ( pos, old.length() ); + insert( corr ); + setSelection ( pos, corr.length() ); + } +} + +void LineEditWidget::spellCheckerFinished() +{ +} + +void LineEditWidget::slotSpellCheckReady( KSpell *s ) +{ + s->check( text() ); + connect( s, SIGNAL( done( const QString & ) ), this, SLOT( slotSpellCheckDone( const QString & ) ) ); +} + +void LineEditWidget::slotSpellCheckDone( const QString &s ) +{ + if( s != text() ) + setText( s ); +} + + +QPopupMenu *LineEditWidget::createPopupMenu() +{ + QPopupMenu *popup = KLineEdit::createPopupMenu(); + + if ( !popup ) { + return 0L; + } + + connect( popup, SIGNAL( activated( int ) ), + this, SLOT( extendedMenuActivated( int ) ) ); + + if (m_input->autoComplete()) { + popup->insertSeparator(); + int id = popup->insertItem( SmallIconSet("history_clear"), i18n("Clear &History"), ClearHistory ); + popup->setItemEnabled( id, (compObj() && !compObj()->isEmpty()) ); + } + + if (echoMode() == QLineEdit::Normal && + !isReadOnly()) { + popup->insertSeparator(); + + m_spellAction->plug(popup); + m_spellAction->setEnabled( !text().isEmpty() ); + } + + return popup; +} + + +void LineEditWidget::extendedMenuActivated( int id) +{ + switch ( id ) + { + case ClearHistory: + m_view->clearCompletionHistory(m_input->name().string()); + if (compObj()) + compObj()->clear(); + default: + break; + } +} + +bool LineEditWidget::event( QEvent *e ) +{ + if (KLineEdit::event(e)) + return true; + + if ( e->type() == QEvent::AccelAvailable && isReadOnly() ) { + QKeyEvent* ke = (QKeyEvent*) e; + if ( ke->state() & ControlButton ) { + switch ( ke->key() ) { + case Key_Left: + case Key_Right: + case Key_Up: + case Key_Down: + case Key_Home: + case Key_End: + ke->accept(); + default: + break; + } + } + } + return false; +} + +void LineEditWidget::mouseMoveEvent(QMouseEvent *e) +{ + // hack to prevent Qt from calling setCursor on the widget + setDragEnabled(false); + KLineEdit::mouseMoveEvent(e); + setDragEnabled(true); +} + + +// ----------------------------------------------------------------------------- + +RenderLineEdit::RenderLineEdit(HTMLInputElementImpl *element) + : RenderFormElement(element) +{ + LineEditWidget *edit = new LineEditWidget(element, view(), view()->viewport()); + connect(edit,SIGNAL(returnPressed()), this, SLOT(slotReturnPressed())); + connect(edit,SIGNAL(textChanged(const QString &)),this,SLOT(slotTextChanged(const QString &))); + + if(element->inputType() == HTMLInputElementImpl::PASSWORD) + edit->setEchoMode( QLineEdit::Password ); + + if ( element->autoComplete() ) { + QStringList completions = view()->formCompletionItems(element->name().string()); + if (completions.count()) { + edit->completionObject()->setItems(completions); + edit->setContextMenuEnabled(true); + edit->completionBox()->setTabHandling( false ); + } + } + + setQWidget(edit); +} + +void RenderLineEdit::setStyle(RenderStyle* _style) +{ + RenderFormElement::setStyle( _style ); + + widget()->setAlignment(textAlignment()); +} + +void RenderLineEdit::highLightWord( unsigned int length, unsigned int pos ) +{ + LineEditWidget* w = static_cast<LineEditWidget*>(m_widget); + if ( w ) + w->highLightWord( length, pos ); +} + + +void RenderLineEdit::slotReturnPressed() +{ + // don't submit the form when return was pressed in a completion-popup + KCompletionBox *box = widget()->completionBox(false); + + if ( box && box->isVisible() && box->currentItem() != -1 ) { + box->hide(); + return; + } + + // Emit onChange if necessary + // Works but might not be enough, dirk said he had another solution at + // hand (can't remember which) - David + handleFocusOut(); + + HTMLFormElementImpl* fe = element()->form(); + if ( fe ) + fe->submitFromKeyboard(); +} + +void RenderLineEdit::handleFocusOut() +{ + if ( widget() && widget()->edited() ) { + element()->onChange(); + widget()->setEdited( false ); + } +} + +void RenderLineEdit::calcMinMaxWidth() +{ + KHTMLAssert( !minMaxKnown() ); + + const QFontMetrics &fm = style()->fontMetrics(); + QSize s; + + int size = element()->size(); + + int h = fm.lineSpacing(); + int w = fm.width( 'x' ) * (size > 0 ? size+1 : 17); // "some" + s = QSize(w + 2 + 2*widget()->frameWidth(), + kMax(h, 14) + 2 + 2*widget()->frameWidth()) + .expandedTo(QApplication::globalStrut()); + + setIntrinsicWidth( s.width() ); + setIntrinsicHeight( s.height() ); + + RenderFormElement::calcMinMaxWidth(); +} + +void RenderLineEdit::updateFromElement() +{ + int ml = element()->maxLength(); + if ( ml < 0 ) + ml = 32767; + + if ( widget()->maxLength() != ml ) { + widget()->setMaxLength( ml ); + } + + if (element()->value().string() != widget()->text()) { + widget()->blockSignals(true); + int pos = widget()->cursorPosition(); + widget()->setText(element()->value().string()); + + widget()->setEdited( false ); + + widget()->setCursorPosition(pos); + widget()->blockSignals(false); + } + widget()->setReadOnly(element()->readOnly()); + + RenderFormElement::updateFromElement(); +} + +void RenderLineEdit::slotTextChanged(const QString &string) +{ + // don't use setValue here! + element()->m_value = string; + element()->m_unsubmittedFormChange = true; +} + +void RenderLineEdit::select() +{ + static_cast<LineEditWidget*>(m_widget)->selectAll(); +} + +long RenderLineEdit::selectionStart() +{ + LineEditWidget* w = static_cast<LineEditWidget*>(m_widget); + if (w->hasSelectedText()) + return w->selectionStart(); + else + return w->cursorPosition(); +} + + +long RenderLineEdit::selectionEnd() +{ + LineEditWidget* w = static_cast<LineEditWidget*>(m_widget); + if (w->hasSelectedText()) + return w->selectionStart() + w->selectedText().length(); + else + return w->cursorPosition(); +} + +void RenderLineEdit::setSelectionStart(long pos) +{ + LineEditWidget* w = static_cast<LineEditWidget*>(m_widget); + //See whether we have a non-empty selection now. + long end = selectionEnd(); + if (end > pos) + w->setSelection(pos, end - pos); + w->setCursorPosition(pos); +} + +void RenderLineEdit::setSelectionEnd(long pos) +{ + LineEditWidget* w = static_cast<LineEditWidget*>(m_widget); + //See whether we have a non-empty selection now. + long start = selectionStart(); + if (start < pos) + w->setSelection(start, pos - start); + + w->setCursorPosition(pos); +} + +void RenderLineEdit::setSelectionRange(long start, long end) +{ + LineEditWidget* w = static_cast<LineEditWidget*>(m_widget); + w->setCursorPosition(end); + w->setSelection(start, end - start); +} + +// --------------------------------------------------------------------------- + +RenderFieldset::RenderFieldset(HTMLGenericFormElementImpl *element) + : RenderBlock(element) +{ +} + +RenderObject* RenderFieldset::layoutLegend(bool relayoutChildren) +{ + RenderObject* legend = findLegend(); + if (legend) { + if (relayoutChildren) + legend->setNeedsLayout(true); + legend->layoutIfNeeded(); + + int xPos = borderLeft() + paddingLeft() + legend->marginLeft(); + if (style()->direction() == RTL) + xPos = m_width - paddingRight() - borderRight() - legend->width() - legend->marginRight(); + int b = borderTop(); + int h = legend->height(); + legend->setPos(xPos, kMax((b-h)/2, 0)); + m_height = kMax(b,h) + paddingTop(); + } + return legend; +} + +RenderObject* RenderFieldset::findLegend() +{ + for (RenderObject* legend = firstChild(); legend; legend = legend->nextSibling()) { + if (!legend->isFloatingOrPositioned() && legend->element() && + legend->element()->id() == ID_LEGEND) + return legend; + } + return 0; +} + +void RenderFieldset::paintBoxDecorations(PaintInfo& pI, int _tx, int _ty) +{ + //kdDebug( 6040 ) << renderName() << "::paintDecorations()" << endl; + + RenderObject* legend = findLegend(); + if (!legend) + return RenderBlock::paintBoxDecorations(pI, _tx, _ty); + + int w = width(); + int h = height() + borderTopExtra() + borderBottomExtra(); + int yOff = (legend->yPos() > 0) ? 0 : (legend->height()-borderTop())/2; + h -= yOff; + _ty += yOff - borderTopExtra(); + + int my = kMax(_ty,pI.r.y()); + int end = kMin( pI.r.y() + pI.r.height(), _ty + h ); + int mh = end - my; + + paintBackground(pI.p, style()->backgroundColor(), style()->backgroundLayers(), my, mh, _tx, _ty, w, h); + + if ( style()->hasBorder() ) + paintBorderMinusLegend(pI.p, _tx, _ty, w, h, style(), legend->xPos(), legend->width()); +} + +void RenderFieldset::paintBorderMinusLegend(QPainter *p, int _tx, int _ty, int w, int h, + const RenderStyle* style, int lx, int lw) +{ + + const QColor& tc = style->borderTopColor(); + const QColor& bc = style->borderBottomColor(); + + EBorderStyle ts = style->borderTopStyle(); + EBorderStyle bs = style->borderBottomStyle(); + EBorderStyle ls = style->borderLeftStyle(); + EBorderStyle rs = style->borderRightStyle(); + + bool render_t = ts > BHIDDEN; + bool render_l = ls > BHIDDEN; + bool render_r = rs > BHIDDEN; + bool render_b = bs > BHIDDEN; + + if(render_t) { + drawBorder(p, _tx, _ty, _tx + lx, _ty + style->borderTopWidth(), BSTop, tc, style->color(), ts, + (render_l && (ls == DOTTED || ls == DASHED || ls == DOUBLE)?style->borderLeftWidth():0), 0); + drawBorder(p, _tx+lx+lw, _ty, _tx + w, _ty + style->borderTopWidth(), BSTop, tc, style->color(), ts, + 0, (render_r && (rs == DOTTED || rs == DASHED || rs == DOUBLE)?style->borderRightWidth():0)); + } + + if(render_b) + drawBorder(p, _tx, _ty + h - style->borderBottomWidth(), _tx + w, _ty + h, BSBottom, bc, style->color(), bs, + (render_l && (ls == DOTTED || ls == DASHED || ls == DOUBLE)?style->borderLeftWidth():0), + (render_r && (rs == DOTTED || rs == DASHED || rs == DOUBLE)?style->borderRightWidth():0)); + + if(render_l) + { + const QColor& lc = style->borderLeftColor(); + + bool ignore_top = + (tc == lc) && + (ls >= OUTSET) && + (ts == DOTTED || ts == DASHED || ts == SOLID || ts == OUTSET); + + bool ignore_bottom = + (bc == lc) && + (ls >= OUTSET) && + (bs == DOTTED || bs == DASHED || bs == SOLID || bs == INSET); + + drawBorder(p, _tx, _ty, _tx + style->borderLeftWidth(), _ty + h, BSLeft, lc, style->color(), ls, + ignore_top?0:style->borderTopWidth(), + ignore_bottom?0:style->borderBottomWidth()); + } + + if(render_r) + { + const QColor& rc = style->borderRightColor(); + + bool ignore_top = + (tc == rc) && + (rs >= DOTTED || rs == INSET) && + (ts == DOTTED || ts == DASHED || ts == SOLID || ts == OUTSET); + + bool ignore_bottom = + (bc == rc) && + (rs >= DOTTED || rs == INSET) && + (bs == DOTTED || bs == DASHED || bs == SOLID || bs == INSET); + + drawBorder(p, _tx + w - style->borderRightWidth(), _ty, _tx + w, _ty + h, BSRight, rc, style->color(), rs, + ignore_top?0:style->borderTopWidth(), + ignore_bottom?0:style->borderBottomWidth()); + } +} + +void RenderFieldset::setStyle(RenderStyle* _style) +{ + RenderBlock::setStyle(_style); + + // WinIE renders fieldsets with display:inline like they're inline-blocks. For us, + // an inline-block is just a block element with replaced set to true and inline set + // to true. Ensure that if we ended up being inline that we set our replaced flag + // so that we're treated like an inline-block. + if (isInline()) + setReplaced(true); +} + +// ------------------------------------------------------------------------- + +RenderFileButton::RenderFileButton(HTMLInputElementImpl *element) + : RenderFormElement(element) +{ + KURLRequester* w = new KURLRequester( view()->viewport(), "__khtml" ); + + w->setMode(KFile::File | KFile::ExistingOnly); + w->completionObject()->setDir(KGlobalSettings::documentPath()); + + connect(w->lineEdit(), SIGNAL(returnPressed()), this, SLOT(slotReturnPressed())); + connect(w->lineEdit(), SIGNAL(textChanged(const QString &)),this,SLOT(slotTextChanged(const QString &))); + connect(w, SIGNAL(urlSelected(const QString &)),this,SLOT(slotUrlSelected(const QString &))); + + setQWidget(w); + m_haveFocus = false; +} + + + +void RenderFileButton::calcMinMaxWidth() +{ + KHTMLAssert( !minMaxKnown() ); + + const QFontMetrics &fm = style()->fontMetrics(); + int size = element()->size(); + + int h = fm.lineSpacing(); + int w = fm.width( 'x' ) * (size > 0 ? size+1 : 17); // "some" + KLineEdit* edit = static_cast<KURLRequester*>( m_widget )->lineEdit(); + QSize s = edit->style().sizeFromContents(QStyle::CT_LineEdit, + edit, + QSize(w + 2 + 2*edit->frameWidth(), kMax(h, 14) + 2 + 2*edit->frameWidth())) + .expandedTo(QApplication::globalStrut()); + QSize bs = static_cast<KURLRequester*>( m_widget )->minimumSizeHint() - edit->minimumSizeHint(); + + setIntrinsicWidth( s.width() + bs.width() ); + setIntrinsicHeight( kMax(s.height(), bs.height()) ); + + RenderFormElement::calcMinMaxWidth(); +} + +void RenderFileButton::handleFocusOut() +{ + if ( widget()->lineEdit() && widget()->lineEdit()->edited() ) { + element()->onChange(); + widget()->lineEdit()->setEdited( false ); + } +} + +void RenderFileButton::updateFromElement() +{ + KLineEdit* edit = widget()->lineEdit(); + edit->blockSignals(true); + edit->setText(element()->value().string()); + edit->blockSignals(false); + edit->setEdited( false ); + + RenderFormElement::updateFromElement(); +} + +void RenderFileButton::slotReturnPressed() +{ + handleFocusOut(); + + if (element()->form()) + element()->form()->submitFromKeyboard(); +} + +void RenderFileButton::slotTextChanged(const QString &/*string*/) +{ + element()->m_value = KURL( widget()->url() ).prettyURL( 0, KURL::StripFileProtocol ); +} + +void RenderFileButton::slotUrlSelected(const QString &) +{ + element()->onChange(); +} + +void RenderFileButton::select() +{ + widget()->lineEdit()->selectAll(); +} + +// ------------------------------------------------------------------------- + +RenderLabel::RenderLabel(HTMLGenericFormElementImpl *element) + : RenderFormElement(element) +{ + +} + +// ------------------------------------------------------------------------- + +RenderLegend::RenderLegend(HTMLGenericFormElementImpl *element) + : RenderBlock(element) +{ +} + +// ------------------------------------------------------------------------------- + +ComboBoxWidget::ComboBoxWidget(QWidget *parent) + : KComboBox(false, parent, "__khtml") +{ + setAutoMask(true); + if (listBox()) listBox()->installEventFilter(this); + setMouseTracking(true); +} + +bool ComboBoxWidget::event(QEvent *e) +{ + if (KComboBox::event(e)) + return true; + if (e->type()==QEvent::KeyPress) + { + QKeyEvent *ke = static_cast<QKeyEvent *>(e); + switch(ke->key()) + { + case Key_Return: + case Key_Enter: + popup(); + ke->accept(); + return true; + default: + return false; + } + } + return false; +} + +bool ComboBoxWidget::eventFilter(QObject *dest, QEvent *e) +{ + if (dest==listBox() && e->type()==QEvent::KeyPress) + { + QKeyEvent *ke = static_cast<QKeyEvent *>(e); + bool forward = false; + switch(ke->key()) + { + case Key_Tab: + forward=true; + case Key_BackTab: + // ugly hack. emulate popdownlistbox() (private in QComboBox) + // we re-use ke here to store the reference to the generated event. + ke = new QKeyEvent(QEvent::KeyPress, Key_Escape, 0, 0); + QApplication::sendEvent(dest,ke); + focusNextPrevChild(forward); + delete ke; + return true; + default: + return KComboBox::eventFilter(dest, e); + } + } + return KComboBox::eventFilter(dest, e); +} + +// ------------------------------------------------------------------------- + +RenderSelect::RenderSelect(HTMLSelectElementImpl *element) + : RenderFormElement(element) +{ + m_ignoreSelectEvents = false; + m_multiple = element->multiple(); + m_size = element->size(); + m_useListBox = (m_multiple || m_size > 1); + m_selectionChanged = true; + m_optionsChanged = true; + + if(m_useListBox) + setQWidget(createListBox()); + else + setQWidget(createComboBox()); +} + +void RenderSelect::updateFromElement() +{ + m_ignoreSelectEvents = true; + + // change widget type + bool oldMultiple = m_multiple; + unsigned oldSize = m_size; + bool oldListbox = m_useListBox; + + m_multiple = element()->multiple(); + m_size = element()->size(); + m_useListBox = (m_multiple || m_size > 1); + + if (oldMultiple != m_multiple || oldSize != m_size) { + if (m_useListBox != oldListbox) { + // type of select has changed + if(m_useListBox) + setQWidget(createListBox()); + else + setQWidget(createComboBox()); + } + + if (m_useListBox && oldMultiple != m_multiple) { + static_cast<KListBox*>(m_widget)->setSelectionMode(m_multiple ? QListBox::Extended : QListBox::Single); + } + m_selectionChanged = true; + m_optionsChanged = true; + } + + // update contents listbox/combobox based on options in m_element + if ( m_optionsChanged ) { + if (element()->m_recalcListItems) + element()->recalcListItems(); + QMemArray<HTMLGenericFormElementImpl*> listItems = element()->listItems(); + int listIndex; + + if(m_useListBox) { + static_cast<KListBox*>(m_widget)->clear(); + } + + else + static_cast<KComboBox*>(m_widget)->clear(); + + for (listIndex = 0; listIndex < int(listItems.size()); listIndex++) { + if (listItems[listIndex]->id() == ID_OPTGROUP) { + DOMString text = listItems[listIndex]->getAttribute(ATTR_LABEL); + if (text.isNull()) + text = ""; + + if(m_useListBox) { + QListBoxText *item = new QListBoxText(QString(text.implementation()->s, text.implementation()->l)); + static_cast<KListBox*>(m_widget) + ->insertItem(item, listIndex); + item->setSelectable(false); + } + else { + static_cast<KComboBox*>(m_widget) + ->insertItem(QString(text.implementation()->s, text.implementation()->l), listIndex); + static_cast<KComboBox*>(m_widget)->listBox()->item(listIndex)->setSelectable(false); + } + } + else if (listItems[listIndex]->id() == ID_OPTION) { + HTMLOptionElementImpl* optElem = static_cast<HTMLOptionElementImpl*>(listItems[listIndex]); + QString text = optElem->text().string(); + if (optElem->parentNode()->id() == ID_OPTGROUP) + { + // Prefer label if set + DOMString label = optElem->getAttribute(ATTR_LABEL); + if (!label.isEmpty()) + text = label.string(); + text = QString::fromLatin1(" ")+text; + } + + if(m_useListBox) { + KListBox *l = static_cast<KListBox*>(m_widget); + l->insertItem(text, listIndex); + DOMString disabled = optElem->getAttribute(ATTR_DISABLED); + if (!disabled.isNull() && l->item( listIndex )) { + l->item( listIndex )->setSelectable( false ); + } + } else + static_cast<KComboBox*>(m_widget)->insertItem(text, listIndex); + } + else + KHTMLAssert(false); + m_selectionChanged = true; + } + + // QComboBox caches the size hint unless you call setFont (ref: TT docu) + if(!m_useListBox) { + KComboBox *that = static_cast<KComboBox*>(m_widget); + that->setFont( that->font() ); + } + setNeedsLayoutAndMinMaxRecalc(); + m_optionsChanged = false; + } + + // update selection + if (m_selectionChanged) { + updateSelection(); + } + + + m_ignoreSelectEvents = false; + + RenderFormElement::updateFromElement(); +} + +void RenderSelect::calcMinMaxWidth() +{ + KHTMLAssert( !minMaxKnown() ); + + if (m_optionsChanged) + updateFromElement(); + + // ### ugly HACK FIXME!!! + setMinMaxKnown(); + layoutIfNeeded(); + setNeedsLayoutAndMinMaxRecalc(); + // ### end FIXME + + RenderFormElement::calcMinMaxWidth(); +} + +void RenderSelect::layout( ) +{ + KHTMLAssert(needsLayout()); + KHTMLAssert(minMaxKnown()); + + // ### maintain selection properly between type/size changes, and work + // out how to handle multiselect->singleselect (probably just select + // first selected one) + + // calculate size + if(m_useListBox) { + KListBox* w = static_cast<KListBox*>(m_widget); + + QListBoxItem* p = w->firstItem(); + int width = 0; + int height = 0; + while(p) { + width = kMax(width, p->width(p->listBox())); + height = kMax(height, p->height(p->listBox())); + p = p->next(); + } + if ( !height ) + height = w->fontMetrics().height(); + if ( !width ) + width = w->fontMetrics().width( 'x' ); + + int size = m_size; + // check if multiple and size was not given or invalid + // Internet Exploder sets size to kMin(number of elements, 4) + // Netscape seems to simply set it to "number of elements" + // the average of that is IMHO kMin(number of elements, 10) + // so I did that ;-) + if(size < 1) + size = kMin(static_cast<KListBox*>(m_widget)->count(), 10u); + + width += 2*w->frameWidth() + w->verticalScrollBar()->sizeHint().width(); + height = size*height + 2*w->frameWidth(); + + setIntrinsicWidth( width ); + setIntrinsicHeight( height ); + } + else { + QSize s(m_widget->sizeHint()); + setIntrinsicWidth( s.width() ); + setIntrinsicHeight( s.height() ); + } + + /// uuh, ignore the following line.. + setNeedsLayout(true); + RenderFormElement::layout(); + + // and now disable the widget in case there is no <option> given + QMemArray<HTMLGenericFormElementImpl*> listItems = element()->listItems(); + + bool foundOption = false; + for (uint i = 0; i < listItems.size() && !foundOption; i++) + foundOption = (listItems[i]->id() == ID_OPTION); + + m_widget->setEnabled(foundOption && ! element()->disabled()); +} + +void RenderSelect::slotSelected(int index) // emitted by the combobox only +{ + if ( m_ignoreSelectEvents ) return; + + KHTMLAssert( !m_useListBox ); + + QMemArray<HTMLGenericFormElementImpl*> listItems = element()->listItems(); + if(index >= 0 && index < int(listItems.size())) + { + bool found = ( listItems[index]->id() == ID_OPTION ); + + if ( !found ) { + // this one is not selectable, we need to find an option element + while ( ( unsigned ) index < listItems.size() ) { + if ( listItems[index]->id() == ID_OPTION ) { + found = true; + break; + } + ++index; + } + + if ( !found ) { + while ( index >= 0 ) { + if ( listItems[index]->id() == ID_OPTION ) { + found = true; + break; + } + --index; + } + } + } + + if ( found ) { + bool changed = false; + + for ( unsigned int i = 0; i < listItems.size(); ++i ) + if ( listItems[i]->id() == ID_OPTION && i != (unsigned int) index ) + { + HTMLOptionElementImpl* opt = static_cast<HTMLOptionElementImpl*>( listItems[i] ); + changed |= (opt->m_selected == true); + opt->m_selected = false; + } + + HTMLOptionElementImpl* opt = static_cast<HTMLOptionElementImpl*>(listItems[index]); + changed |= (opt->m_selected == false); + opt->m_selected = true; + + if ( index != static_cast<ComboBoxWidget*>( m_widget )->currentItem() ) + static_cast<ComboBoxWidget*>( m_widget )->setCurrentItem( index ); + + // When selecting an optgroup item, and we move forward to we + // shouldn't emit onChange. Hence this bool, the if above doesn't do it. + if ( changed ) + { + ref(); + element()->onChange(); + deref(); + } + } + } +} + + +void RenderSelect::slotSelectionChanged() // emitted by the listbox only +{ + if ( m_ignoreSelectEvents ) return; + + // don't use listItems() here as we have to avoid recalculations - changing the + // option list will make use update options not in the way the user expects them + QMemArray<HTMLGenericFormElementImpl*> listItems = element()->m_listItems; + for ( unsigned i = 0; i < listItems.count(); i++ ) + // don't use setSelected() here because it will cause us to be called + // again with updateSelection. + if ( listItems[i]->id() == ID_OPTION ) + static_cast<HTMLOptionElementImpl*>( listItems[i] ) + ->m_selected = static_cast<KListBox*>( m_widget )->isSelected( i ); + + ref(); + element()->onChange(); + deref(); +} + +void RenderSelect::setOptionsChanged(bool _optionsChanged) +{ + m_optionsChanged = _optionsChanged; +} + +KListBox* RenderSelect::createListBox() +{ + KListBox *lb = new KListBox(view()->viewport(), "__khtml"); + lb->setSelectionMode(m_multiple ? QListBox::Extended : QListBox::Single); + // ### looks broken + //lb->setAutoMask(true); + connect( lb, SIGNAL( selectionChanged() ), this, SLOT( slotSelectionChanged() ) ); +// connect( lb, SIGNAL( clicked( QListBoxItem * ) ), this, SLOT( slotClicked() ) ); + m_ignoreSelectEvents = false; + lb->setMouseTracking(true); + + return lb; +} + +ComboBoxWidget *RenderSelect::createComboBox() +{ + ComboBoxWidget *cb = new ComboBoxWidget(view()->viewport()); + connect(cb, SIGNAL(activated(int)), this, SLOT(slotSelected(int))); + return cb; +} + +void RenderSelect::updateSelection() +{ + QMemArray<HTMLGenericFormElementImpl*> listItems = element()->listItems(); + int i; + if (m_useListBox) { + // if multi-select, we select only the new selected index + KListBox *listBox = static_cast<KListBox*>(m_widget); + for (i = 0; i < int(listItems.size()); i++) + listBox->setSelected(i,listItems[i]->id() == ID_OPTION && + static_cast<HTMLOptionElementImpl*>(listItems[i])->selected()); + } + else { + bool found = false; + unsigned firstOption = listItems.size(); + i = listItems.size(); + while (i--) + if (listItems[i]->id() == ID_OPTION) { + if (found) + static_cast<HTMLOptionElementImpl*>(listItems[i])->m_selected = false; + else if (static_cast<HTMLOptionElementImpl*>(listItems[i])->selected()) { + static_cast<KComboBox*>( m_widget )->setCurrentItem(i); + found = true; + } + firstOption = i; + } + + Q_ASSERT(firstOption == listItems.size() || found); + } + + m_selectionChanged = false; +} + + +// ------------------------------------------------------------------------- + +TextAreaWidget::TextAreaWidget(int wrap, QWidget* parent) + : KTextEdit(parent, "__khtml"), m_findDlg(0), m_find(0), m_repDlg(0), m_replace(0) +{ + if(wrap != DOM::HTMLTextAreaElementImpl::ta_NoWrap) { + setWordWrap(QTextEdit::WidgetWidth); + setHScrollBarMode( AlwaysOff ); + setVScrollBarMode( AlwaysOn ); + } + else { + setWordWrap(QTextEdit::NoWrap); + setHScrollBarMode( Auto ); + setVScrollBarMode( Auto ); + } + KCursor::setAutoHideCursor(viewport(), true); + setTextFormat(QTextEdit::PlainText); + setAutoMask(true); + setMouseTracking(true); + + KActionCollection *ac = new KActionCollection(this); + m_findAction = KStdAction::find( this, SLOT( slotFind() ), ac ); + m_findNextAction = KStdAction::findNext( this, SLOT( slotFindNext() ), ac ); + m_replaceAction = KStdAction::replace( this, SLOT( slotReplace() ), ac ); +} + + +TextAreaWidget::~TextAreaWidget() +{ + delete m_replace; + m_replace = 0L; + delete m_find; + m_find = 0L; + delete m_repDlg; + m_repDlg = 0L; + delete m_findDlg; + m_findDlg = 0L; +} + + +QPopupMenu *TextAreaWidget::createPopupMenu(const QPoint& pos) +{ + QPopupMenu *popup = KTextEdit::createPopupMenu(pos); + + if ( !popup ) { + return 0L; + } + + if (!isReadOnly()) { + popup->insertSeparator(); + + m_findAction->plug(popup); + m_findAction->setEnabled( !text().isEmpty() ); + + m_findNextAction->plug(popup); + m_findNextAction->setEnabled( m_find != 0 ); + + m_replaceAction->plug(popup); + m_replaceAction->setEnabled( !text().isEmpty() ); + } + + return popup; +} + + +void TextAreaWidget::slotFindHighlight(const QString& text, int matchingIndex, int matchingLength) +{ + Q_UNUSED(text) + //kdDebug() << "Highlight: [" << text << "] mi:" << matchingIndex << " ml:" << matchingLength << endl; + if (sender() == m_replace) { + setSelection(m_repPara, matchingIndex, m_repPara, matchingIndex + matchingLength); + setCursorPosition(m_repPara, matchingIndex); + } else { + setSelection(m_findPara, matchingIndex, m_findPara, matchingIndex + matchingLength); + setCursorPosition(m_findPara, matchingIndex); + } + ensureCursorVisible(); +} + + +void TextAreaWidget::slotReplaceText(const QString &text, int replacementIndex, int /*replacedLength*/, int matchedLength) { + Q_UNUSED(text) + //kdDebug() << "Replace: [" << text << "] ri:" << replacementIndex << " rl:" << replacedLength << " ml:" << matchedLength << endl; + setSelection(m_repPara, replacementIndex, m_repPara, replacementIndex + matchedLength); + removeSelectedText(); + insertAt(m_repDlg->replacement(), m_repPara, replacementIndex); + if (m_replace->options() & KReplaceDialog::PromptOnReplace) { + ensureCursorVisible(); + } +} + + +void TextAreaWidget::slotDoReplace() +{ + if (!m_repDlg) { + // Should really assert() + return; + } + + delete m_replace; + m_replace = new KReplace(m_repDlg->pattern(), m_repDlg->replacement(), m_repDlg->options(), this); + if (m_replace->options() & KFindDialog::FromCursor) { + getCursorPosition(&m_repPara, &m_repIndex); + } else if (m_replace->options() & KFindDialog::FindBackwards) { + m_repPara = paragraphs() - 1; + m_repIndex = paragraphLength(m_repPara) - 1; + } else { + m_repPara = 0; + m_repIndex = 0; + } + + // Connect highlight signal to code which handles highlighting + // of found text. + connect(m_replace, SIGNAL(highlight(const QString &, int, int)), + this, SLOT(slotFindHighlight(const QString &, int, int))); + connect(m_replace, SIGNAL(findNext()), this, SLOT(slotReplaceNext())); + connect(m_replace, SIGNAL(replace(const QString &, int, int, int)), + this, SLOT(slotReplaceText(const QString &, int, int, int))); + + m_repDlg->close(); + slotReplaceNext(); +} + + +void TextAreaWidget::slotReplaceNext() +{ + if (!m_replace) { + // assert? + return; + } + + if (!(m_replace->options() & KReplaceDialog::PromptOnReplace)) { + viewport()->setUpdatesEnabled(false); + } + + KFind::Result res = KFind::NoMatch; + while (res == KFind::NoMatch) { + // If we're done..... + if (m_replace->options() & KFindDialog::FindBackwards) { + if (m_repIndex == 0 && m_repPara == 0) { + break; + } + } else { + if (m_repPara == paragraphs() - 1 && + m_repIndex == paragraphLength(m_repPara) - 1) { + break; + } + } + + if (m_replace->needData()) { + m_replace->setData(text(m_repPara), m_repIndex); + } + + res = m_replace->replace(); + + if (res == KFind::NoMatch) { + if (m_replace->options() & KFindDialog::FindBackwards) { + if (m_repPara == 0) { + m_repIndex = 0; + } else { + m_repPara--; + m_repIndex = paragraphLength(m_repPara) - 1; + } + } else { + if (m_repPara == paragraphs() - 1) { + m_repIndex = paragraphLength(m_repPara) - 1; + } else { + m_repPara++; + m_repIndex = 0; + } + } + } + } + + if (!(m_replace->options() & KReplaceDialog::PromptOnReplace)) { + viewport()->setUpdatesEnabled(true); + repaintChanged(); + } + + if (res == KFind::NoMatch) { // at end + m_replace->displayFinalDialog(); + delete m_replace; + m_replace = 0; + ensureCursorVisible(); + //or if ( m_replace->shouldRestart() ) { reinit (w/o FromCursor) and call slotReplaceNext(); } + } else { + //m_replace->closeReplaceNextDialog(); + } +} + + +void TextAreaWidget::slotDoFind() +{ + if (!m_findDlg) { + // Should really assert() + return; + } + + delete m_find; + m_find = new KFind(m_findDlg->pattern(), m_findDlg->options(), this); + if (m_find->options() & KFindDialog::FromCursor) { + getCursorPosition(&m_findPara, &m_findIndex); + } else if (m_find->options() & KFindDialog::FindBackwards) { + m_findPara = paragraphs() - 1; + m_findIndex = paragraphLength(m_findPara) - 1; + } else { + m_findPara = 0; + m_findIndex = 0; + } + + // Connect highlight signal to code which handles highlighting + // of found text. + connect(m_find, SIGNAL(highlight(const QString &, int, int)), + this, SLOT(slotFindHighlight(const QString &, int, int))); + connect(m_find, SIGNAL(findNext()), this, SLOT(slotFindNext())); + + m_findDlg->close(); + m_find->closeFindNextDialog(); + slotFindNext(); +} + + +void TextAreaWidget::slotFindNext() +{ + if (!m_find) { + // assert? + return; + } + + KFind::Result res = KFind::NoMatch; + while (res == KFind::NoMatch) { + // If we're done..... + if (m_find->options() & KFindDialog::FindBackwards) { + if (m_findIndex == 0 && m_findPara == 0) { + break; + } + } else { + if (m_findPara == paragraphs() - 1 && + m_findIndex == paragraphLength(m_findPara) - 1) { + break; + } + } + + if (m_find->needData()) { + m_find->setData(text(m_findPara), m_findIndex); + } + + res = m_find->find(); + + if (res == KFind::NoMatch) { + if (m_find->options() & KFindDialog::FindBackwards) { + if (m_findPara == 0) { + m_findIndex = 0; + } else { + m_findPara--; + m_findIndex = paragraphLength(m_findPara) - 1; + } + } else { + if (m_findPara == paragraphs() - 1) { + m_findIndex = paragraphLength(m_findPara) - 1; + } else { + m_findPara++; + m_findIndex = 0; + } + } + } + } + + if (res == KFind::NoMatch) { // at end + m_find->displayFinalDialog(); + delete m_find; + m_find = 0; + //or if ( m_find->shouldRestart() ) { reinit (w/o FromCursor) and call slotFindNext(); } + } else { + //m_find->closeFindNextDialog(); + } +} + + +void TextAreaWidget::slotFind() +{ + if( text().isEmpty() ) // saves having to track the text changes + return; + + if ( m_findDlg ) { + KWin::activateWindow( m_findDlg->winId() ); + } else { + m_findDlg = new KFindDialog(false, this, "KHTML Text Area Find Dialog"); + connect( m_findDlg, SIGNAL(okClicked()), this, SLOT(slotDoFind()) ); + } + m_findDlg->show(); +} + + +void TextAreaWidget::slotReplace() +{ + if( text().isEmpty() ) // saves having to track the text changes + return; + + if ( m_repDlg ) { + KWin::activateWindow( m_repDlg->winId() ); + } else { + m_repDlg = new KReplaceDialog(this, "KHTMLText Area Replace Dialog", 0, + QStringList(), QStringList(), false); + connect( m_repDlg, SIGNAL(okClicked()), this, SLOT(slotDoReplace()) ); + } + m_repDlg->show(); +} + + +bool TextAreaWidget::event( QEvent *e ) +{ + if ( e->type() == QEvent::AccelAvailable && isReadOnly() ) { + QKeyEvent* ke = (QKeyEvent*) e; + if ( ke->state() & ControlButton ) { + switch ( ke->key() ) { + case Key_Left: + case Key_Right: + case Key_Up: + case Key_Down: + case Key_Home: + case Key_End: + ke->accept(); + default: + break; + } + } + } + return KTextEdit::event( e ); +} + +// ------------------------------------------------------------------------- + +RenderTextArea::RenderTextArea(HTMLTextAreaElementImpl *element) + : RenderFormElement(element) +{ + scrollbarsStyled = false; + + TextAreaWidget *edit = new TextAreaWidget(element->wrap(), view()); + setQWidget(edit); + const KHTMLSettings *settings = view()->part()->settings(); + edit->setCheckSpellingEnabled( settings->autoSpellCheck() ); + edit->setTabChangesFocus( ! settings->allowTabulation() ); + + connect(edit,SIGNAL(textChanged()),this,SLOT(slotTextChanged())); +} + +RenderTextArea::~RenderTextArea() +{ + if ( element()->m_dirtyvalue ) { + element()->m_value = text(); + element()->m_dirtyvalue = false; + } +} + +void RenderTextArea::handleFocusOut() +{ + TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget); + if ( w && element()->m_dirtyvalue ) { + element()->m_value = text(); + element()->m_dirtyvalue = false; + } + + if ( w && element()->m_changed ) { + element()->m_changed = false; + element()->onChange(); + } +} + +void RenderTextArea::calcMinMaxWidth() +{ + KHTMLAssert( !minMaxKnown() ); + + TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget); + const QFontMetrics &m = style()->fontMetrics(); + w->setTabStopWidth(8 * m.width(" ")); + QSize size( kMax(element()->cols(), 1L)*m.width('x') + w->frameWidth() + + w->verticalScrollBar()->sizeHint().width(), + kMax(element()->rows(), 1L)*m.lineSpacing() + w->frameWidth()*4 + + (w->wordWrap() == QTextEdit::NoWrap ? + w->horizontalScrollBar()->sizeHint().height() : 0) + ); + + setIntrinsicWidth( size.width() ); + setIntrinsicHeight( size.height() ); + + RenderFormElement::calcMinMaxWidth(); +} + +void RenderTextArea::setStyle(RenderStyle* _style) +{ + bool unsubmittedFormChange = element()->m_unsubmittedFormChange; + + RenderFormElement::setStyle(_style); + + widget()->blockSignals(true); + widget()->setAlignment(textAlignment()); + widget()->blockSignals(false); + + scrollbarsStyled = false; + + element()->m_unsubmittedFormChange = unsubmittedFormChange; +} + +void RenderTextArea::layout() +{ + KHTMLAssert( needsLayout() ); + + RenderFormElement::layout(); + + TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget); + + if (!scrollbarsStyled) { + w->horizontalScrollBar()->setPalette(style()->palette()); + w->verticalScrollBar()->setPalette(style()->palette()); + scrollbarsStyled=true; + } +} + +void RenderTextArea::updateFromElement() +{ + TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget); + w->setReadOnly(element()->readOnly()); + QString elementText = element()->value().string(); + if ( elementText != text() ) + { + w->blockSignals(true); + int line, col; + w->getCursorPosition( &line, &col ); + int cx = w->contentsX(); + int cy = w->contentsY(); + w->setText( elementText ); + w->setCursorPosition( line, col ); + w->scrollBy( cx, cy ); + w->blockSignals(false); + } + element()->m_dirtyvalue = false; + + RenderFormElement::updateFromElement(); +} + +void RenderTextArea::close( ) +{ + element()->setValue( element()->defaultValue() ); + + RenderFormElement::close(); +} + + +QString RenderTextArea::text() +{ + QString txt; + TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget); + + if(element()->wrap() == DOM::HTMLTextAreaElementImpl::ta_Physical) { + // yeah, QTextEdit has no accessor for getting the visually wrapped text + for (int p=0; p < w->paragraphs(); ++p) { + int ll = 0; + int lindex = w->lineOfChar(p, 0); + QString paragraphText = w->text(p); + int pl = w->paragraphLength(p); + paragraphText = paragraphText.left(pl); //Snip invented space. + for (int l = 0; l < pl; ++l) { + if (lindex != w->lineOfChar(p, l)) { + paragraphText.insert(l+ll++, QString::fromLatin1("\n")); + lindex = w->lineOfChar(p, l); + } + } + txt += paragraphText; + if (p < w->paragraphs() - 1) + txt += QString::fromLatin1("\n"); + } + } + else + txt = w->text(); + + return txt; +} + +int RenderTextArea::queryParagraphInfo(int para, Mode m, int param) { + /* We have to be a bit careful here, as we need to match up the positions + to what our value returns here*/ + TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget); + int length = 0; + + bool physWrap = element()->wrap() == DOM::HTMLTextAreaElementImpl::ta_Physical; + + QString paragraphText = w->text(para); + int pl = w->paragraphLength(para); + int physicalPL = pl; + if (m == ParaPortionLength) + pl = param; + + if (physWrap) { + //Go through all the chars of paragraph, and count line changes, chars, etc. + int lindex = w->lineOfChar(para, 0); + for (int c = 0; c < pl; ++c) { + ++length; + // Is there a change after this char? + if (c+1 < physicalPL && lindex != w->lineOfChar(para, c+1)) { + lindex = w->lineOfChar(para, c+1); + ++length; + } + if (m == ParaPortionOffset && length > param) + return c; + } + } else { + //Make sure to count the LF, CR as appropriate. ### this is stupid now, simplify + for (int c = 0; c < pl; ++c) { + ++length; + if (m == ParaPortionOffset && length > param) + return c; + } + } + if (m == ParaPortionOffset) + return pl; + if (m == ParaPortionLength) + return length; + return length + 1; +} + +long RenderTextArea::computeCharOffset(int para, int index) { + if (para < 0) + return 0; + + long pos = 0; + for (int cp = 0; cp < para; ++cp) + pos += queryParagraphInfo(cp, ParaLength); + + if (index >= 0) + pos += queryParagraphInfo(para, ParaPortionLength, index); + return pos; +} + +void RenderTextArea::computeParagraphAndIndex(long offset, int* para, int* index) { + TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget); + + if (!w->paragraphs()) { + *para = -1; + *index = -1; + return; + } + + //Find the paragraph that contains us.. + int containingPar = 0; + long endPos = 0; + long startPos = 0; + for (int p = 0; p < w->paragraphs(); ++p) { + int len = queryParagraphInfo(p, ParaLength); + endPos += len; + if (endPos > offset) { + containingPar = p; + break; + } + startPos += len; + } + + *para = containingPar; + + //Now, scan within the paragraph to find the position.. + long localOffset = offset - startPos; + + *index = queryParagraphInfo(containingPar, ParaPortionOffset, localOffset); +} + +void RenderTextArea::highLightWord( unsigned int length, unsigned int pos ) +{ + TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget); + if ( w ) + w->highLightWord( length, pos ); +} + + +void RenderTextArea::slotTextChanged() +{ + element()->m_dirtyvalue = true; + element()->m_changed = true; + if (element()->m_value != text()) + element()->m_unsubmittedFormChange = true; +} + +void RenderTextArea::select() +{ + static_cast<TextAreaWidget *>(m_widget)->selectAll(); +} + +long RenderTextArea::selectionStart() +{ + TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget); + int para, index, dummy1, dummy2; + w->getSelection(¶, &index, &dummy1, &dummy2); + if (para == -1 || index == -1) + w->getCursorPosition(¶, &index); + + return computeCharOffset(para, index); +} + +long RenderTextArea::selectionEnd() +{ + TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget); + int para, index, dummy1, dummy2; + w->getSelection(&dummy1, &dummy2, ¶, &index); + if (para == -1 || index == -1) + w->getCursorPosition(¶, &index); + + return computeCharOffset(para, index); +} + +void RenderTextArea::setSelectionStart(long offset) { + TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget); + int fromPara, fromIndex, toPara, toIndex; + w->getSelection(&fromPara, &fromIndex, &toPara, &toIndex); + computeParagraphAndIndex(offset, &fromPara, &fromIndex); + if (toPara == -1 || toIndex == -1) { + toPara = fromPara; + toIndex = fromIndex; + } + w->setSelection(fromPara, fromIndex, toPara, toIndex); +} + +void RenderTextArea::setSelectionEnd(long offset) { + TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget); + int fromPara, fromIndex, toPara, toIndex; + w->getSelection(&fromPara, &fromIndex, &toPara, &toIndex); + computeParagraphAndIndex(offset, &toPara, &toIndex); + w->setSelection(fromPara, fromIndex, toPara, toIndex); +} + +void RenderTextArea::setSelectionRange(long start, long end) { + TextAreaWidget* w = static_cast<TextAreaWidget*>(m_widget); + int fromPara, fromIndex, toPara, toIndex; + computeParagraphAndIndex(start, &fromPara, &fromIndex); + computeParagraphAndIndex(end, &toPara, &toIndex); + w->setSelection(fromPara, fromIndex, toPara, toIndex); +} +// --------------------------------------------------------------------------- + +#include "render_form.moc" diff --git a/khtml/rendering/render_form.h b/khtml/rendering/render_form.h new file mode 100644 index 000000000..f4d35b927 --- /dev/null +++ b/khtml/rendering/render_form.h @@ -0,0 +1,509 @@ +/* + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000-2003 Dirk Mueller (mueller@kde.org) + * + * 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. + * + */ +#ifndef RENDER_FORM_H +#define RENDER_FORM_H + +#include "rendering/render_replaced.h" +#include "rendering/render_image.h" +#include "rendering/render_flow.h" +#include "rendering/render_style.h" +#include "html/html_formimpl.h" + +class QWidget; +class QLineEdit; +class QListboxItem; + +#include <ktextedit.h> +#include <kurlrequester.h> +#include <klineedit.h> +#include <qcheckbox.h> +#include <qradiobutton.h> +#include <qpushbutton.h> +#include <qhbox.h> +#include <klistbox.h> +#include <kcombobox.h> +#include "dom/dom_misc.h" + +class KHTMLPartBrowserExtension; +class KSpell; +class KFindDialog; +class KReplaceDialog; +class KFind; +class KReplace; +class KAction; +class KURLRequester; + +namespace DOM { + class HTMLFormElementImpl; + class HTMLInputElementImpl; + class HTMLSelectElementImpl; + class HTMLGenericFormElementImpl; + class HTMLTextAreaElementImpl; +} + +namespace khtml { + +class DocLoader; + +// ------------------------------------------------------------------------- + +class RenderFormElement : public khtml::RenderWidget +{ +public: + RenderFormElement(DOM::HTMLGenericFormElementImpl* node); + virtual ~RenderFormElement(); + + virtual const char *renderName() const { return "RenderForm"; } + + virtual bool isFormElement() const { return true; } + + // form elements never have padding + virtual int paddingTop() const { return 0; } + virtual int paddingBottom() const { return 0; } + virtual int paddingLeft() const { return 0; } + virtual int paddingRight() const { return 0; } + + virtual void updateFromElement(); + + virtual void layout(); + virtual short baselinePosition( bool ) const; + + DOM::HTMLGenericFormElementImpl *element() const + { return static_cast<DOM::HTMLGenericFormElementImpl*>(RenderObject::element()); } + +protected: + virtual bool isRenderButton() const { return false; } + virtual bool isEditable() const { return false; } + AlignmentFlags textAlignment() const; + + QPoint m_mousePos; + int m_state; +}; + +// ------------------------------------------------------------------------- + +// generic class for all buttons +class RenderButton : public RenderFormElement +{ + Q_OBJECT +public: + RenderButton(DOM::HTMLGenericFormElementImpl* node); + + virtual const char *renderName() const { return "RenderButton"; } + + virtual short baselinePosition( bool ) const; + + // don't even think about making this method virtual! + DOM::HTMLInputElementImpl* element() const + { return static_cast<DOM::HTMLInputElementImpl*>(RenderObject::element()); } + +protected: + virtual bool isRenderButton() const { return true; } +}; + +// ------------------------------------------------------------------------- + +class RenderCheckBox : public RenderButton +{ + Q_OBJECT +public: + RenderCheckBox(DOM::HTMLInputElementImpl* node); + + virtual const char *renderName() const { return "RenderCheckBox"; } + virtual void updateFromElement(); + virtual void calcMinMaxWidth(); + + virtual bool handleEvent(const DOM::EventImpl&) { return false; } + + QCheckBox *widget() const { return static_cast<QCheckBox*>(m_widget); } + +public slots: + virtual void slotStateChanged(int state); +}; + +// ------------------------------------------------------------------------- + +class RenderRadioButton : public RenderButton +{ + Q_OBJECT +public: + RenderRadioButton(DOM::HTMLInputElementImpl* node); + + virtual const char *renderName() const { return "RenderRadioButton"; } + + virtual void calcMinMaxWidth(); + virtual void updateFromElement(); + + virtual bool handleEvent(const DOM::EventImpl&) { return false; } + + QRadioButton *widget() const { return static_cast<QRadioButton*>(m_widget); } + +public slots: + virtual void slotToggled(bool); +}; + +// ------------------------------------------------------------------------- + +class RenderSubmitButton : public RenderButton +{ +public: + RenderSubmitButton(DOM::HTMLInputElementImpl *element); + + virtual const char *renderName() const { return "RenderSubmitButton"; } + + virtual void calcMinMaxWidth(); + virtual void updateFromElement(); + virtual short baselinePosition( bool ) const; +private: + QString rawText(); +}; + +// ------------------------------------------------------------------------- + +class RenderImageButton : public RenderImage +{ +public: + RenderImageButton(DOM::HTMLInputElementImpl *element) + : RenderImage(element) {} + + virtual const char *renderName() const { return "RenderImageButton"; } +}; + + +// ------------------------------------------------------------------------- + +class RenderResetButton : public RenderSubmitButton +{ +public: + RenderResetButton(DOM::HTMLInputElementImpl *element); + + virtual const char *renderName() const { return "RenderResetButton"; } + +}; + +// ------------------------------------------------------------------------- + +class RenderPushButton : public RenderSubmitButton +{ +public: + RenderPushButton(DOM::HTMLInputElementImpl *element) + : RenderSubmitButton(element) {} + +}; + +// ------------------------------------------------------------------------- + +class RenderLineEdit : public RenderFormElement +{ + Q_OBJECT +public: + RenderLineEdit(DOM::HTMLInputElementImpl *element); + + virtual void calcMinMaxWidth(); + + virtual const char *renderName() const { return "RenderLineEdit"; } + virtual void updateFromElement(); + virtual void setStyle(RenderStyle *style); + + void select(); + + KLineEdit *widget() const { return static_cast<KLineEdit*>(m_widget); } + DOM::HTMLInputElementImpl* element() const + { return static_cast<DOM::HTMLInputElementImpl*>(RenderObject::element()); } + void highLightWord( unsigned int length, unsigned int pos ); + + long selectionStart(); + long selectionEnd(); + void setSelectionStart(long pos); + void setSelectionEnd(long pos); + void setSelectionRange(long start, long end); +public slots: + void slotReturnPressed(); + void slotTextChanged(const QString &string); +protected: + virtual void handleFocusOut(); + +private: + virtual bool isEditable() const { return true; } + virtual bool canHaveBorder() const { return true; } +}; + +// ------------------------------------------------------------------------- + +class LineEditWidget : public KLineEdit +{ + Q_OBJECT +public: + LineEditWidget(DOM::HTMLInputElementImpl* input, + KHTMLView* view, QWidget* parent); + ~LineEditWidget(); + void highLightWord( unsigned int length, unsigned int pos ); + +protected: + virtual bool event( QEvent *e ); + virtual void mouseMoveEvent(QMouseEvent *e); + virtual QPopupMenu *createPopupMenu(); +private slots: + void extendedMenuActivated( int id); + void slotCheckSpelling(); + void slotSpellCheckReady( KSpell *s ); + void slotSpellCheckDone( const QString &s ); + void spellCheckerMisspelling( const QString &text, const QStringList &, unsigned int pos); + void spellCheckerCorrected( const QString &, const QString &, unsigned int ); + void spellCheckerFinished(); + +private: + enum LineEditMenuID { + ClearHistory + }; + DOM::HTMLInputElementImpl* m_input; + KHTMLView* m_view; + KSpell *m_spell; + KAction *m_spellAction; +}; + +// ------------------------------------------------------------------------- + +class RenderFieldset : public RenderBlock +{ +public: + RenderFieldset(DOM::HTMLGenericFormElementImpl *element); + + virtual const char *renderName() const { return "RenderFieldSet"; } + virtual RenderObject* layoutLegend(bool relayoutChildren); + virtual void setStyle(RenderStyle* _style); + +protected: + virtual void paintBoxDecorations(PaintInfo& pI, int _tx, int _ty); + void paintBorderMinusLegend(QPainter *p, int _tx, int _ty, int w, + int h, const RenderStyle *style, int lx, int lw); + RenderObject* findLegend(); +}; + +// ------------------------------------------------------------------------- + +class RenderFileButton : public RenderFormElement +{ + Q_OBJECT +public: + RenderFileButton(DOM::HTMLInputElementImpl *element); + + virtual const char *renderName() const { return "RenderFileButton"; } + virtual void calcMinMaxWidth(); + virtual void updateFromElement(); + void select(); + + KURLRequester *widget() const { return static_cast<KURLRequester*>(m_widget); } + + DOM::HTMLInputElementImpl *element() const + { return static_cast<DOM::HTMLInputElementImpl*>(RenderObject::element()); } + +public slots: + void slotReturnPressed(); + void slotTextChanged(const QString &string); + void slotUrlSelected(const QString &string); + +protected: + virtual void handleFocusOut(); + + virtual bool isEditable() const { return true; } + virtual bool canHaveBorder() const { return true; } + virtual bool acceptsSyntheticEvents() const { return false; } + + bool m_clicked; + bool m_haveFocus; +}; + + +// ------------------------------------------------------------------------- + +class RenderLabel : public RenderFormElement +{ +public: + RenderLabel(DOM::HTMLGenericFormElementImpl *element); + + virtual const char *renderName() const { return "RenderLabel"; } + +protected: + virtual bool canHaveBorder() const { return true; } +}; + + +// ------------------------------------------------------------------------- + +class RenderLegend : public RenderBlock +{ +public: + RenderLegend(DOM::HTMLGenericFormElementImpl *element); + + virtual const char *renderName() const { return "RenderLegend"; } +}; + +// ------------------------------------------------------------------------- + +class ComboBoxWidget : public KComboBox +{ +public: + ComboBoxWidget(QWidget *parent); + +protected: + virtual bool event(QEvent *); + virtual bool eventFilter(QObject *dest, QEvent *e); +}; + +// ------------------------------------------------------------------------- + +class RenderSelect : public RenderFormElement +{ + Q_OBJECT +public: + RenderSelect(DOM::HTMLSelectElementImpl *element); + + virtual const char *renderName() const { return "RenderSelect"; } + + virtual void calcMinMaxWidth(); + virtual void layout(); + + void setOptionsChanged(bool _optionsChanged); + + bool selectionChanged() { return m_selectionChanged; } + void setSelectionChanged(bool _selectionChanged) { m_selectionChanged = _selectionChanged; } + virtual void updateFromElement(); + + void updateSelection(); + + DOM::HTMLSelectElementImpl *element() const + { return static_cast<DOM::HTMLSelectElementImpl*>(RenderObject::element()); } + +protected: + KListBox *createListBox(); + ComboBoxWidget *createComboBox(); + + unsigned m_size; + bool m_multiple; + bool m_useListBox; + bool m_selectionChanged; + bool m_ignoreSelectEvents; + bool m_optionsChanged; + +protected slots: + void slotSelected(int index); + void slotSelectionChanged(); +}; + +// ------------------------------------------------------------------------- +class TextAreaWidget : public KTextEdit +{ + Q_OBJECT +public: + TextAreaWidget(int wrap, QWidget* parent); + virtual ~TextAreaWidget(); + +protected: + virtual bool event (QEvent *e ); + virtual QPopupMenu *createPopupMenu(const QPoint& pos); + virtual QPopupMenu* createPopupMenu() { return KTextEdit::createPopupMenu(); } +private slots: + void slotFind(); + void slotDoFind(); + void slotFindNext(); + void slotReplace(); + void slotDoReplace(); + void slotReplaceNext(); + void slotReplaceText(const QString&, int, int, int); + void slotFindHighlight(const QString&, int, int); +private: + KFindDialog *m_findDlg; + KFind *m_find; + KReplaceDialog *m_repDlg; + KReplace *m_replace; + KAction *m_findAction; + KAction *m_findNextAction; + KAction *m_replaceAction; + int m_findIndex, m_findPara; + int m_repIndex, m_repPara; +}; + + +// ------------------------------------------------------------------------- + +class RenderTextArea : public RenderFormElement +{ + Q_OBJECT +public: + RenderTextArea(DOM::HTMLTextAreaElementImpl *element); + ~RenderTextArea(); + + virtual const char *renderName() const { return "RenderTextArea"; } + virtual void calcMinMaxWidth(); + virtual void layout(); + virtual void setStyle(RenderStyle *style); + + virtual void close ( ); + virtual void updateFromElement(); + + // don't even think about making this method virtual! + TextAreaWidget *widget() const { return static_cast<TextAreaWidget*>(m_widget); } + DOM::HTMLTextAreaElementImpl* element() const + { return static_cast<DOM::HTMLTextAreaElementImpl*>(RenderObject::element()); } + + QString text(); + void highLightWord( unsigned int length, unsigned int pos ); + + void select(); + + long selectionStart(); + long selectionEnd(); + void setSelectionStart(long pos); + void setSelectionEnd(long pos); + void setSelectionRange(long start, long end); +protected slots: + void slotTextChanged(); + +protected: + virtual void handleFocusOut(); + + virtual bool isEditable() const { return true; } + virtual bool canHaveBorder() const { return true; } + + bool scrollbarsStyled; +private: + //Convert para, index -> offset + long computeCharOffset(int para, int index); + + //Convert offset -> para, index + void computeParagraphAndIndex(long offset, int* para, int* index); + + //Helper for doing the conversion.. + enum Mode { ParaLength, //Returns the length of the entire paragraph + ParaPortionLength, //Return length of paragraph portion set by threshold + ParaPortionOffset }; //Return offset that matches the length threshold. + int queryParagraphInfo(int para, Mode m, int param = -1); +}; + +// ------------------------------------------------------------------------- + +} //namespace + +#endif diff --git a/khtml/rendering/render_frames.cpp b/khtml/rendering/render_frames.cpp new file mode 100644 index 000000000..7ee69b239 --- /dev/null +++ b/khtml/rendering/render_frames.cpp @@ -0,0 +1,1010 @@ +/** + * This file is part of the KDE project. + * + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 2000 Simon Hausmann <hausmann@kde.org> + * (C) 2000 Stefan Schimanski (1Stein@gmx.de) + * (C) 2003 Apple Computer, Inc. + * (C) 2005 Niels Leenheer <niels.leenheer@gmail.com> + * + * 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_frames.h" +#include "rendering/render_canvas.h" +#include "html/html_baseimpl.h" +#include "html/html_objectimpl.h" +#include "html/htmltokenizer.h" +#include "misc/htmlattrs.h" +#include "xml/dom2_eventsimpl.h" +#include "xml/dom_docimpl.h" +#include "misc/htmltags.h" +#include "khtmlview.h" +#include "khtml_part.h" + +#include <kapplication.h> +#include <kmessagebox.h> +#include <kmimetype.h> +#include <klocale.h> +#include <kdebug.h> +#include <qtimer.h> +#include <qpainter.h> +#include <qcursor.h> + +#include <assert.h> + +using namespace khtml; +using namespace DOM; + +RenderFrameSet::RenderFrameSet( HTMLFrameSetElementImpl *frameSet) + : RenderBox(frameSet) +{ + // init RenderObject attributes + setInline(false); + + for (int k = 0; k < 2; ++k) { + m_gridLen[k] = -1; + m_gridDelta[k] = 0; + m_gridLayout[k] = 0; + } + + m_resizing = m_clientresizing= false; + + m_cursor = Qt::ArrowCursor; + + m_hSplit = -1; + m_vSplit = -1; + + m_hSplitVar = 0; + m_vSplitVar = 0; +} + +RenderFrameSet::~RenderFrameSet() +{ + for (int k = 0; k < 2; ++k) { + delete [] m_gridLayout[k]; + delete [] m_gridDelta[k]; + } + delete [] m_hSplitVar; + delete [] m_vSplitVar; +} + +bool RenderFrameSet::nodeAtPoint(NodeInfo& info, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction, bool inBox) +{ + RenderBox::nodeAtPoint(info, _x, _y, _tx, _ty, hitTestAction, inBox); + + bool inside = m_resizing || canResize(_x, _y); + + if ( inside && element() && !element()->noResize() && !info.readonly()) { + info.setInnerNode(element()); + info.setInnerNonSharedNode(element()); + } + + return inside || m_clientresizing; +} + +void RenderFrameSet::layout( ) +{ + KHTMLAssert( needsLayout() ); + KHTMLAssert( minMaxKnown() ); + + if ( !parent()->isFrameSet() ) { + KHTMLView* view = canvas()->view(); + m_width = view ? view->visibleWidth() : 0; + m_height = view ? view->visibleHeight() : 0; + } + +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(FrameSet)::layout( ) width=" << width() << ", height=" << height() << endl; +#endif + + 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) { + delete [] m_gridDelta[k]; + m_gridDelta[k] = new int[m_gridLen[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]; + khtml::Length* grid = k ? element()->m_cols : element()->m_rows; + int* gridLayout = m_gridLayout[k]; + + if (grid) { + // 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] = kMax(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] = kMax(grid[i].width(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 += kMax(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] = (kMax(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]; + } + + positionFrames(); + + RenderObject *child = firstChild(); + if ( !child ) + goto end2; + + if(!m_hSplitVar && !m_vSplitVar) + { +#ifdef DEBUG_LAYOUT + kdDebug( 6031 ) << "calculationg fixed Splitters" << endl; +#endif + 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<RenderFrameSet *>(child)->element()->noResize(); + else + fixed = static_cast<RenderFrame *>(child)->element()->noResize(); + + if(fixed) + { +#ifdef DEBUG_LAYOUT + kdDebug( 6031 ) << "found fixed cell " << r << "/" << c << "!" << endl; +#endif + 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 end2; + } +#ifdef DEBUG_LAYOUT + else + kdDebug( 6031 ) << "not fixed: " << r << "/" << c << "!" << endl; +#endif + } + } + + } + RenderContainer::layout(); + end2: + setNeedsLayout(false); +} + +void RenderFrameSet::positionFrames() +{ + int r; + int c; + + RenderObject *child = firstChild(); + if ( !child ) + return; + + // NodeImpl *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 ); +#ifdef DEBUG_LAYOUT + kdDebug(6040) << "child frame at (" << xPos << "/" << yPos << ") size (" << m_gridLayout[1][c] << "/" << m_gridLayout[0][r] << ")" << endl; +#endif + // 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); + 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(); + } +} + +bool RenderFrameSet::userResize( MouseEventImpl *evt ) +{ + if (needsLayout()) return false; + + bool res = false; + int _x = evt->clientX(); + int _y = evt->clientY(); + + if ( !m_resizing && evt->id() == EventImpl::MOUSEMOVE_EVENT || evt->id() == EventImpl::MOUSEDOWN_EVENT ) + { +#ifdef DEBUG_LAYOUT + kdDebug( 6031 ) << "mouseEvent:check" << endl; +#endif + + 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] == true) m_vSplit = c-1; +#ifdef DEBUG_LAYOUT + kdDebug( 6031 ) << "vsplit!" << endl; +#endif + 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] == true) m_hSplit = r-1; +#ifdef DEBUG_LAYOUT + kdDebug( 6031 ) << "hsplitvar = " << m_hSplitVar << endl; + kdDebug( 6031 ) << "hsplit!" << endl; +#endif + res = true; + break; + } + pos += m_gridLayout[0][r] + element()->border(); + } +#ifdef DEBUG_LAYOUT + kdDebug( 6031 ) << m_hSplit << "/" << m_vSplit << endl; +#endif + } + + + m_cursor = Qt::ArrowCursor; + if(m_hSplit != -1 && m_vSplit != -1) + m_cursor = Qt::SizeAllCursor; + else if( m_vSplit != -1 ) + m_cursor = Qt::SizeHorCursor; + else if( m_hSplit != -1 ) + m_cursor = Qt::SizeVerCursor; + + if(!m_resizing && evt->id() == EventImpl::MOUSEDOWN_EVENT) + { + setResizing(true); + KApplication::setOverrideCursor(QCursor(m_cursor)); + m_vSplitPos = _x; + m_hSplitPos = _y; + m_oldpos = -1; + } + + // ### check the resize is not going out of bounds. + if(m_resizing && evt->id() == EventImpl::MOUSEUP_EVENT) + { + setResizing(false); + KApplication::restoreOverrideCursor(); + + if(m_vSplit != -1 ) + { +#ifdef DEBUG_LAYOUT + kdDebug( 6031 ) << "split xpos=" << _x << endl; +#endif + int delta = m_vSplitPos - _x; + m_gridDelta[1][m_vSplit] -= delta; + m_gridDelta[1][m_vSplit+1] += delta; + } + if(m_hSplit != -1 ) + { +#ifdef DEBUG_LAYOUT + kdDebug( 6031 ) << "split ypos=" << _y << endl; +#endif + 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); + } + + KHTMLView *view = canvas()->view(); + if ((m_resizing || evt->id() == EventImpl::MOUSEUP_EVENT) && view) { + QPainter paint( view ); + paint.setPen( Qt::gray ); + paint.setBrush( Qt::gray ); + paint.setRasterOp( Qt::XorROP ); + QRect r(xPos(), yPos(), width(), height()); + const int rBord = 3; + int sw = element()->border(); + int p = m_resizing ? (m_vSplit > -1 ? _x : _y) : -1; + if (m_vSplit > -1) { + if ( m_oldpos >= 0 ) + paint.drawRect( m_oldpos + sw/2 - rBord , r.y(), + 2*rBord, r.height() ); + if ( p >= 0 ) + paint.drawRect( p + sw/2 - rBord, r.y(), 2*rBord, r.height() ); + } else { + if ( m_oldpos >= 0 ) + paint.drawRect( r.x(), m_oldpos + sw/2 - rBord, + r.width(), 2*rBord ); + if ( p >= 0 ) + paint.drawRect( r.x(), p + sw/2 - rBord, r.width(), 2*rBord ); + } + m_oldpos = p; + } + + return res; +} + +void RenderFrameSet::setResizing(bool e) +{ + m_resizing = e; + for (RenderObject* p = parent(); p; p = p->parent()) + if (p->isFrameSet()) static_cast<RenderFrameSet*>(p)->m_clientresizing = m_resizing; +} + +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; +} + +#ifdef ENABLE_DUMP +void RenderFrameSet::dump(QTextStream &stream, const QString &ind) const +{ + RenderBox::dump(stream,ind); + stream << " totalrows=" << element()->totalRows(); + stream << " totalcols=" << element()->totalCols(); + + if ( m_hSplitVar ) + for (uint i = 0; i < (uint)element()->totalRows(); i++) { + stream << " hSplitvar(" << i << ")=" << m_hSplitVar[i]; + } + + if ( m_vSplitVar ) + for (uint i = 0; i < (uint)element()->totalCols(); i++) + stream << " vSplitvar(" << i << ")=" << m_vSplitVar[i]; +} +#endif + +/**************************************************************************************/ + +RenderPart::RenderPart(DOM::HTMLElementImpl* node) + : RenderWidget(node) +{ + // init RenderObject attributes + setInline(false); +} + +void RenderPart::setWidget( QWidget *widget ) +{ +#ifdef DEBUG_LAYOUT + kdDebug(6031) << "RenderPart::setWidget()" << endl; +#endif + + setQWidget( widget ); + widget->setFocusPolicy(QWidget::WheelFocus); + if(widget->inherits("KHTMLView")) + connect( widget, SIGNAL( cleared() ), this, SLOT( slotViewCleared() ) ); + + setNeedsLayoutAndMinMaxRecalc(); + + // make sure the scrollbars are set correctly for restore + // ### find better fix + slotViewCleared(); +} + +bool RenderPart::partLoadingErrorNotify(khtml::ChildFrame *, const KURL& , const QString& ) +{ + return false; +} + +short RenderPart::intrinsicWidth() const +{ + return 300; +} + +int RenderPart::intrinsicHeight() const +{ + return 150; +} + +void RenderPart::slotViewCleared() +{ +} + +/***************************************************************************************/ + +RenderFrame::RenderFrame( DOM::HTMLFrameElementImpl *frame ) + : RenderPart(frame) +{ + setInline( false ); +} + +void RenderFrame::slotViewCleared() +{ + if(m_widget->inherits("QScrollView")) { +#ifdef DEBUG_LAYOUT + kdDebug(6031) << "frame is a scrollview!" << endl; +#endif + QScrollView *view = static_cast<QScrollView *>(m_widget); + if(!element()->frameBorder || !((static_cast<HTMLFrameSetElementImpl *>(element()->parentNode()))->frameBorder())) + view->setFrameStyle(QFrame::NoFrame); + view->setVScrollBarMode(element()->scrolling ); + view->setHScrollBarMode(element()->scrolling ); + if(view->inherits("KHTMLView")) { +#ifdef DEBUG_LAYOUT + kdDebug(6031) << "frame is a KHTMLview!" << endl; +#endif + KHTMLView *htmlView = static_cast<KHTMLView *>(view); + if(element()->marginWidth != -1) htmlView->setMarginWidth(element()->marginWidth); + if(element()->marginHeight != -1) htmlView->setMarginHeight(element()->marginHeight); + } + } +} + +/****************************************************************************************/ + +RenderPartObject::RenderPartObject( DOM::HTMLElementImpl* element ) + : RenderPart( element ) +{ + // init RenderObject attributes + setInline(true); +} + +void RenderPartObject::updateWidget() +{ + QString url; + KHTMLPart *part = m_view->part(); + + setNeedsLayoutAndMinMaxRecalc(); + + if (element()->id() == ID_IFRAME) { + + HTMLIFrameElementImpl *o = static_cast<HTMLIFrameElementImpl *>(element()); + url = o->url.string(); + if (!o->getDocument()->isURLAllowed(url)) return; + part->requestFrame( this, url, o->name.string(), QStringList(), true ); + // ### this should be constant true - move iframe to somewhere else + } else { + + QStringList params; + HTMLObjectBaseElementImpl * objbase = static_cast<HTMLObjectBaseElementImpl *>(element()); + url = objbase->url; + + for (NodeImpl* child = element()->firstChild(); child; child=child->nextSibling()) { + if ( child->id() == ID_PARAM ) { + HTMLParamElementImpl *p = static_cast<HTMLParamElementImpl *>( child ); + + QString aStr = p->name(); + aStr += QString::fromLatin1("=\""); + aStr += p->value(); + aStr += QString::fromLatin1("\""); + QString name_lower = p->name().lower(); + if (name_lower == QString::fromLatin1("type") && objbase->id() != ID_APPLET) { + objbase->setServiceType(p->value()); + } else if (url.isEmpty() && + (name_lower == QString::fromLatin1("src") || + name_lower == QString::fromLatin1("movie") || + name_lower == QString::fromLatin1("code"))) { + url = p->value(); + } + params.append(aStr); + } + } + if (element()->id() != ID_OBJECT) { + // add all attributes set on the embed object + NamedAttrMapImpl* a = objbase->attributes(); + if (a) { + for (unsigned long i = 0; i < a->length(); ++i) { + NodeImpl::Id id = a->idAt(i); + DOMString value = a->valueAt(i); + params.append(objbase->getDocument()->getName(NodeImpl::AttributeId, id).string() + "=\"" + value.string() + "\""); + } + } + } + params.append( QString::fromLatin1("__KHTML__PLUGINEMBED=\"YES\"") ); + params.append( QString::fromLatin1("__KHTML__PLUGINBASEURL=\"%1\"").arg(element()->getDocument()->baseURL().url())); + + HTMLEmbedElementImpl *embed = 0; + QString classId; + QString serviceType = objbase->serviceType; + if ( element()->id() == ID_EMBED ) { + + embed = static_cast<HTMLEmbedElementImpl *>( objbase ); + + } + else { // if(element()->id() == ID_OBJECT || element()->id() == ID_APPLET) + + // check for embed child object + for (NodeImpl *child = objbase->firstChild(); child; child = child->nextSibling()) + if ( child->id() == ID_EMBED ) { + embed = static_cast<HTMLEmbedElementImpl *>( child ); + break; + } + classId = objbase->classId; + + params.append( QString::fromLatin1("__KHTML__CLASSID=\"%1\"").arg( classId ) ); + params.append( QString::fromLatin1("__KHTML__CODEBASE=\"%1\"").arg( objbase->getAttribute(ATTR_CODEBASE).string() ) ); + if (!objbase->getAttribute(ATTR_WIDTH).isEmpty()) + params.append( QString::fromLatin1("WIDTH=\"%1\"").arg( objbase->getAttribute(ATTR_WIDTH).string() ) ); + else if (embed && !embed->getAttribute(ATTR_WIDTH).isEmpty()) { + params.append( QString::fromLatin1("WIDTH=\"%1\"").arg( embed->getAttribute(ATTR_WIDTH).string() ) ); + objbase->setAttribute(ATTR_WIDTH, embed->getAttribute(ATTR_WIDTH)); + } + if (!objbase->getAttribute(ATTR_HEIGHT).isEmpty()) + params.append( QString::fromLatin1("HEIGHT=\"%1\"").arg( objbase->getAttribute(ATTR_HEIGHT).string() ) ); + else if (embed && !embed->getAttribute(ATTR_HEIGHT).isEmpty()) { + params.append( QString::fromLatin1("HEIGHT=\"%1\"").arg( embed->getAttribute(ATTR_HEIGHT).string() ) ); + objbase->setAttribute(ATTR_HEIGHT, embed->getAttribute(ATTR_HEIGHT)); + } + + if ( embed ) { + // render embed object + url = embed->url; + if (!embed->serviceType.isEmpty()) + serviceType = embed->serviceType; + } else if (url.isEmpty() && objbase->classId.startsWith("java:")) { + serviceType = "application/x-java-applet"; + url = objbase->classId.mid(5); + } + if ( (serviceType.isEmpty() || + serviceType == "application/x-oleobject") && + !objbase->classId.isEmpty()) + { +#if 0 + // We have a clsid, means this is activex (Niko) + serviceType = "application/x-activex-handler"; +#endif + + if(classId.find(QString::fromLatin1("D27CDB6E-AE6D-11cf-96B8-444553540000")) >= 0) { + // It is ActiveX, but the nsplugin system handling + // should also work, that's why we don't override the + // serviceType with application/x-activex-handler + // but let the KTrader in khtmlpart::createPart() detect + // the user's preference: launch with activex viewer or + // with nspluginviewer (Niko) + serviceType = "application/x-shockwave-flash"; + } + else if(classId.find(QString::fromLatin1("CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA")) >= 0) + serviceType = "audio/x-pn-realaudio-plugin"; + else if(classId.find(QString::fromLatin1("8AD9C840-044E-11D1-B3E9-00805F499D93")) >= 0 || + objbase->classId.find(QString::fromLatin1("CAFEEFAC-0014-0000-0000-ABCDEFFEDCBA")) >= 0) + serviceType = "application/x-java-applet"; + // http://www.apple.com/quicktime/tools_tips/tutorials/activex.html + else if(classId.find(QString::fromLatin1("02BF25D5-8C17-4B23-BC80-D3488ABDDC6B")) >= 0) + serviceType = "video/quicktime"; + // http://msdn.microsoft.com/library/en-us/dnwmt/html/adding_windows_media_to_web_pages__etse.asp?frame=true + else if(objbase->classId.find(QString::fromLatin1("6BF52A52-394A-11d3-B153-00C04F79FAA6")) >= 0 || + classId.find(QString::fromLatin1("22D6f312-B0F6-11D0-94AB-0080C74C7E95")) >= 0) + serviceType = "video/x-msvideo"; + + else + kdDebug(6031) << "ActiveX classId " << objbase->classId << endl; + + // TODO: add more plugins here + } + } + if ((url.isEmpty() && !embed && + (serviceType.isEmpty() || classId.isEmpty())) || + !document()->isURLAllowed(url) || + !part->requestObject( this, url, serviceType, params )) + objbase->renderAlternative(); + } +} + +// ugly.. +void RenderPartObject::close() +{ + RenderPart::close(); + + if ( element()->id() != ID_IFRAME ) + updateWidget(); + // deleted here +} + + +bool RenderPartObject::partLoadingErrorNotify( khtml::ChildFrame *childFrame, const KURL& url, const QString& serviceType ) +{ + KHTMLPart *part = static_cast<KHTMLView *>(m_view)->part(); + kdDebug(6031) << "RenderPartObject::partLoadingErrorNotify serviceType=" << serviceType << endl; + // Check if we just tried with e.g. nsplugin + // and fallback to the activexhandler if there is a classid + // and a codebase, where we may download the ocx if it's missing + if( serviceType != "application/x-activex-handler" && element()->id()==ID_OBJECT ) { + + // check for embed child object + HTMLObjectElementImpl *o = static_cast<HTMLObjectElementImpl *>(element()); + HTMLEmbedElementImpl *embed = 0; + NodeImpl *child = o->firstChild(); + while ( child ) { + if ( child->id() == ID_EMBED ) + embed = static_cast<HTMLEmbedElementImpl *>( child ); + + child = child->nextSibling(); + } + if( embed && !o->classId.isEmpty() && + !( static_cast<ElementImpl *>(o)->getAttribute(ATTR_CODEBASE).string() ).isEmpty() ) + { + KParts::URLArgs args; + args.serviceType = "application/x-activex-handler"; + kdDebug(6031) << "set to activex" << endl; + if (part->requestObject( childFrame, url, args )) + return true; // success + + return false; + } + } + // Dissociate ourselves from the current event loop (to prevent crashes + // due to the message box staying up) + QTimer::singleShot( 0, this, SLOT( slotPartLoadingErrorNotify() ) ); +#if 0 + Tokenizer *tokenizer = static_cast<DOM::DocumentImpl *>(part->document().handle())->tokenizer(); + if (tokenizer) tokenizer->setOnHold( true ); + slotPartLoadingErrorNotify(); + if (tokenizer) tokenizer->setOnHold( false ); +#endif + return false; +} + +void RenderPartObject::slotPartLoadingErrorNotify() +{ + // First we need to find out the servicetype - again - this code is too duplicated ! + HTMLEmbedElementImpl *embed = 0; + QString serviceType; + if( element()->id()==ID_OBJECT ) { + + // check for embed child object + HTMLObjectElementImpl *o = static_cast<HTMLObjectElementImpl *>(element()); + serviceType = o->serviceType; + NodeImpl *child = o->firstChild(); + while ( child ) { + if ( child->id() == ID_EMBED ) + embed = static_cast<HTMLEmbedElementImpl *>( child ); + + child = child->nextSibling(); + } + + } else if( element()->id()==ID_EMBED ) { + embed = static_cast<HTMLEmbedElementImpl *>(element()); + } + if ( embed ) + serviceType = embed->serviceType; + + // prepare for the local eventloop in KMessageBox + ref(); + + KHTMLPart *part = static_cast<KHTMLView *>(m_view)->part(); + KParts::BrowserExtension *ext = part->browserExtension(); + if( embed && !embed->pluginPage.isEmpty() && ext ) { + // Prepare the mimetype to show in the question (comment if available, name as fallback) + QString mimeName = serviceType; + KMimeType::Ptr mime = KMimeType::mimeType(serviceType); + if ( mime->name() != KMimeType::defaultMimeType() ) + mimeName = mime->comment(); + + // Check if we already asked the user, for this page + if (!mimeName.isEmpty() && part->docImpl() && !part->pluginPageQuestionAsked( serviceType ) ) + { + part->setPluginPageQuestionAsked( serviceType ); + // Prepare the URL to show in the question (host only if http, to make it short) + KURL pluginPageURL( embed->pluginPage ); + QString shortURL = pluginPageURL.protocol() == "http" ? pluginPageURL.host() : pluginPageURL.prettyURL(); + int res = KMessageBox::questionYesNo( m_view, + i18n("No plugin found for '%1'.\nDo you want to download one from %2?").arg(mimeName).arg(shortURL), + i18n("Missing Plugin"), i18n("Download"), i18n("Do Not Download"), QString("plugin-")+serviceType); + if ( res == KMessageBox::Yes ) + { + // Display vendor download page + ext->createNewWindow( pluginPageURL ); + return; + } + } + } + + // didn't work, render alternative content. + if ( element() && ( + element()->id() == ID_OBJECT || element()->id() == ID_EMBED || element()->id() == ID_APPLET)) + static_cast<HTMLObjectBaseElementImpl*>( element() )->renderAlternative(); + + deref(); +} + +void RenderPartObject::layout( ) +{ + KHTMLAssert( needsLayout() ); + KHTMLAssert( minMaxKnown() ); + + calcWidth(); + calcHeight(); + + RenderPart::layout(); + + setNeedsLayout(false); +} + +void RenderPartObject::slotViewCleared() +{ + if(m_widget->inherits("QScrollView") ) { +#ifdef DEBUG_LAYOUT + kdDebug(6031) << "iframe is a scrollview!" << endl; +#endif + QScrollView *view = static_cast<QScrollView *>(m_widget); + int frameStyle = QFrame::NoFrame; + QScrollView::ScrollBarMode scroll = QScrollView::Auto; + int marginw = -1; + int marginh = -1; + if ( element()->id() == ID_IFRAME) { + HTMLIFrameElementImpl *frame = static_cast<HTMLIFrameElementImpl *>(element()); + if(frame->frameBorder) + frameStyle = QFrame::Box; + scroll = frame->scrolling; + marginw = frame->marginWidth; + marginh = frame->marginHeight; + } + view->setFrameStyle(frameStyle); + view->setVScrollBarMode(scroll ); + view->setHScrollBarMode(scroll ); + if(view->inherits("KHTMLView")) { +#ifdef DEBUG_LAYOUT + kdDebug(6031) << "frame is a KHTMLview!" << endl; +#endif + KHTMLView *htmlView = static_cast<KHTMLView *>(view); + htmlView->setIgnoreWheelEvents( element()->id() == ID_IFRAME ); + if(marginw != -1) htmlView->setMarginWidth(marginw); + if(marginh != -1) htmlView->setMarginHeight(marginh); + } + } +} + +#include "render_frames.moc" diff --git a/khtml/rendering/render_frames.h b/khtml/rendering/render_frames.h new file mode 100644 index 000000000..c6e050525 --- /dev/null +++ b/khtml/rendering/render_frames.h @@ -0,0 +1,172 @@ +/* + * This file is part of the KDE project. + * + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * (C) 2000 Simon Hausmann <hausmann@kde.org> + * + * 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. + * + */ +#ifndef __render_frames_h__ +#define __render_frames_h__ + +#include "rendering/render_replaced.h" +#include "xml/dom_nodeimpl.h" +#include "html/html_baseimpl.h" +class KHTMLView; + +namespace DOM +{ + class HTMLFrameElementImpl; + class HTMLElementImpl; + class MouseEventImpl; +} + +namespace khtml +{ + class ChildFrame; + +class RenderFrameSet : public RenderBox +{ + friend class DOM::HTMLFrameSetElementImpl; +public: + RenderFrameSet( DOM::HTMLFrameSetElementImpl *frameSet ); + + virtual ~RenderFrameSet(); + + virtual const char *renderName() const { return "RenderFrameSet"; } + virtual bool isFrameSet() const { return true; } + + virtual void layout(); + + void positionFrames( ); + + bool resizing() const { return m_resizing; } + bool noResize() const { return element()->noResize(); } + + bool userResize( DOM::MouseEventImpl *evt ); + bool canResize( int _x, int _y); + void setResizing(bool e); + + Qt::CursorShape cursorShape() const { return m_cursor; } + + bool nodeAtPoint(NodeInfo& info, int x, int y, int tx, int ty, HitTestAction hitTestAction, bool inside); + + DOM::HTMLFrameSetElementImpl *element() const + { return static_cast<DOM::HTMLFrameSetElementImpl*>(RenderObject::element()); } + +#ifdef ENABLE_DUMP + virtual void dump(QTextStream &stream, const QString &ind) const; +#endif + +private: + Qt::CursorShape m_cursor; + int m_oldpos; + int m_gridLen[2]; + int* m_gridDelta[2]; + int* m_gridLayout[2]; + + bool *m_hSplitVar; // is this split variable? + bool *m_vSplitVar; + + int m_hSplit; // the split currently resized + int m_vSplit; + int m_hSplitPos; + int m_vSplitPos; + + bool m_resizing; + bool m_clientresizing; +}; + +class RenderPart : public khtml::RenderWidget +{ + Q_OBJECT +public: + RenderPart(DOM::HTMLElementImpl* node); + + virtual const char *renderName() const { return "RenderPart"; } + + virtual void setWidget( QWidget *widget ); + + /** + * Called by KHTMLPart to notify the frame object that loading the + * part was not successfuly. (called either asyncroniously after a + * after the servicetype of the given url (the one passed with requestObject) + * has been determined or syncroniously from within requestObject) + * + * The default implementation does nothing. + * + * Return false in the normal case, return true if a fallback was found + * and the url was successfully opened. + */ + virtual bool partLoadingErrorNotify( khtml::ChildFrame *childFrame, const KURL& url, const QString& serviceType ); + + virtual short intrinsicWidth() const; + virtual int intrinsicHeight() const; + +public slots: + virtual void slotViewCleared(); +}; + +class RenderFrame : public khtml::RenderPart +{ + Q_OBJECT +public: + RenderFrame( DOM::HTMLFrameElementImpl *frame ); + + virtual const char *renderName() const { return "RenderFrame"; } + virtual bool isFrame() const { return true; } + + // frames never have padding + virtual int paddingTop() const { return 0; } + virtual int paddingBottom() const { return 0; } + virtual int paddingLeft() const { return 0; } + virtual int paddingRight() const { return 0; } + + DOM::HTMLFrameElementImpl *element() const + { return static_cast<DOM::HTMLFrameElementImpl*>(RenderObject::element()); } + +public slots: + void slotViewCleared(); +}; + +// I can hardly call the class RenderObject ;-) +class RenderPartObject : public khtml::RenderPart +{ + Q_OBJECT +public: + RenderPartObject( DOM::HTMLElementImpl * ); + + virtual const char *renderName() const { return "RenderPartObject"; } + + virtual void close(); + + virtual void layout( ); + virtual void updateWidget(); + + virtual bool canHaveBorder() const { return true; } + + virtual bool partLoadingErrorNotify( khtml::ChildFrame *childFrame, const KURL& url, const QString& serviceType ); + +public slots: + void slotViewCleared(); +private slots: + void slotPartLoadingErrorNotify(); +}; + +} + +#endif diff --git a/khtml/rendering/render_generated.cpp b/khtml/rendering/render_generated.cpp new file mode 100644 index 000000000..aeab48630 --- /dev/null +++ b/khtml/rendering/render_generated.cpp @@ -0,0 +1,392 @@ +/** + * This file is part of the HTML rendering engine for KDE. + * + * Copyright (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include "rendering/render_generated.h" +#include "rendering/render_style.h" +#include "rendering/enumerate.h" +#include "rendering/counter_tree.h" +#include "css/css_valueimpl.h" + +using namespace khtml; +using namespace Enumerate; + +// ------------------------------------------------------------------------- + +RenderCounterBase::RenderCounterBase(DOM::NodeImpl* node) + : RenderText(node,0), m_counterNode(0) +{ +} + +void RenderCounterBase::layout() +{ + KHTMLAssert( needsLayout() ); + + if ( !minMaxKnown() ) + calcMinMaxWidth(); + + RenderText::layout(); +} + +void RenderCounterBase::calcMinMaxWidth() +{ + KHTMLAssert( !minMaxKnown() ); + + generateContent(); + + if (str) str->deref(); + str = new DOM::DOMStringImpl(m_item.unicode(), m_item.length()); + str->ref(); + + RenderText::calcMinMaxWidth(); +} + + +void RenderCounterBase::updateContent() +{ + setMinMaxKnown(false); +} + +// ------------------------------------------------------------------------- + +RenderCounter::RenderCounter(DOM::NodeImpl* node, const DOM::CounterImpl* counter) + : RenderCounterBase(node), m_counter(counter) +{ +} + +QString RenderCounter::toListStyleType(int value, int total, EListStyleType type) +{ + QString item; + switch(type) + { + case LNONE: + break; +// Glyphs: (these values are not really used and instead handled by RenderGlyph) + case LDISC: + item = QChar(0x2022); + break; + case LCIRCLE: + item = QChar(0x25e6); + break; + case LSQUARE: + item = QChar(0x25a0); + break; + case LBOX: + item = QChar(0x25a1); + break; + case LDIAMOND: + item = QChar(0x25c6); + break; +// Numeric: + case LDECIMAL: + item.setNum ( value ); + break; + case DECIMAL_LEADING_ZERO: { + int decimals = 2; + int t = total/100; + while (t>0) { + t = t/10; + decimals++; + } + decimals = kMax(decimals, 2); + QString num = QString::number(value); + item.fill('0',decimals-num.length()); + item.append(num); + break; + } + case ARABIC_INDIC: + item = toArabicIndic( value ); + break; + case LAO: + item = toLao( value ); + break; + case PERSIAN: + case URDU: + item = toPersianUrdu( value ); + break; + case THAI: + item = toThai( value ); + break; + case TIBETAN: + item = toTibetan( value ); + break; +// Algoritmic: + case LOWER_ROMAN: + item = toRoman( value, false ); + break; + case UPPER_ROMAN: + item = toRoman( value, true ); + break; + case HEBREW: + item = toHebrew( value ); + break; + case ARMENIAN: + item = toArmenian( value ); + break; + case GEORGIAN: + item = toGeorgian( value ); + break; +// Alphabetic: + case LOWER_ALPHA: + case LOWER_LATIN: + item = toLowerLatin( value ); + break; + case UPPER_ALPHA: + case UPPER_LATIN: + item = toUpperLatin( value ); + break; + case LOWER_GREEK: + item = toLowerGreek( value ); + break; + case UPPER_GREEK: + item = toUpperGreek( value ); + break; + case HIRAGANA: + item = toHiragana( value ); + break; + case HIRAGANA_IROHA: + item = toHiraganaIroha( value ); + break; + case KATAKANA: + item = toKatakana( value ); + break; + case KATAKANA_IROHA: + item = toKatakanaIroha( value ); + break; +// Ideographic: + case JAPANESE_FORMAL: + item = toJapaneseFormal( value ); + break; + case JAPANESE_INFORMAL: + item = toJapaneseInformal( value ); + break; + case SIMP_CHINESE_FORMAL: + item = toSimpChineseFormal( value ); + break; + case SIMP_CHINESE_INFORMAL: + item = toSimpChineseInformal( value ); + break; + case TRAD_CHINESE_FORMAL: + item = toTradChineseFormal( value ); + break; + case CJK_IDEOGRAPHIC: + // CSS 3 List says treat as trad-chinese-informal + case TRAD_CHINESE_INFORMAL: + item = toTradChineseInformal( value ); + break; + default: + item.setNum ( value ); + break; + } + return item; +} + +void RenderCounter::generateContent() +{ + bool counters; + counters = !m_counter->separator().isNull(); + + if (!m_counterNode) + m_counterNode = getCounter(m_counter->identifier().string(), true, counters); + + int value = m_counterNode->count(); + if (m_counterNode->isReset()) value = m_counterNode->value(); + int total = value; + if (m_counterNode->parent()) total = m_counterNode->parent()->total(); + m_item = toListStyleType(value, total, (EListStyleType)m_counter->listStyle()); + + if (counters) { + CounterNode *counter = m_counterNode->parent(); + // we deliberately do not render the root counter-node + while(counter->parent() && !(counter->isReset() && counter->parent()->isRoot())) { + value = counter->count(); + total = counter->parent()->total(); + m_item = toListStyleType(value, total, (EListStyleType)m_counter->listStyle()) + m_counter->separator().string() + m_item; + counter = counter->parent(); + }; + } + +} + +// ------------------------------------------------------------------------- + +RenderQuote::RenderQuote(DOM::NodeImpl* node, EQuoteContent type) + : RenderCounterBase(node), m_quoteType(type) +{ +} + + +int RenderQuote::quoteCount() const +{ + switch(m_quoteType) { + case OPEN_QUOTE: + case NO_OPEN_QUOTE: + return 1; + case CLOSE_QUOTE: + case NO_CLOSE_QUOTE: + return -1; + case NO_QUOTE: + return 0; + } + assert(false); + return 0; +} + +void RenderQuote::generateContent() +{ + bool visual; + if (m_quoteType == NO_CLOSE_QUOTE || m_quoteType == NO_OPEN_QUOTE) + visual = false; + else + visual = true; + + if (!m_counterNode) + m_counterNode = getCounter("-khtml-quotes", visual, false); + + int value = m_counterNode->count(); + if (m_counterNode->isReset()) value = m_counterNode->value(); + switch (m_quoteType) { + case OPEN_QUOTE: + m_item = style()->openQuote( value ); + break; + case CLOSE_QUOTE: + m_item = style()->closeQuote( value ); + break; + case NO_OPEN_QUOTE: + case NO_CLOSE_QUOTE: + case NO_QUOTE: + m_item = QString(); + } +} + +// ------------------------------------------------------------------------- + +RenderGlyph::RenderGlyph(DOM::NodeImpl* node, EListStyleType type) + : RenderBox(node), m_type(type) +{ + setInline(true); +// setReplaced(true); +} + +void RenderGlyph::setStyle(RenderStyle *_style) +{ + RenderBox::setStyle(_style); + + const QFontMetrics &fm = style()->fontMetrics(); + QRect xSize= fm.boundingRect('x'); + m_height = xSize.height(); + m_width = xSize.width();; + + switch(m_type) { + // Glyphs: + case LDISC: + case LCIRCLE: + case LSQUARE: + case LBOX: + case LDIAMOND: + case LNONE: + break; + default: + // not a glyph ! + assert(false); + break; + } +} + +void RenderGlyph::calcMinMaxWidth() +{ + m_minWidth = m_width; + m_maxWidth = m_width; + + setMinMaxKnown(); +} + +short RenderGlyph::lineHeight(bool /*b*/) const +{ + return height(); +} + +short RenderGlyph::baselinePosition(bool /*b*/) const +{ + return height(); +} + +void RenderGlyph::paint(PaintInfo& paintInfo, int _tx, int _ty) +{ + if (paintInfo.phase != PaintActionForeground) + return; + + if (style()->visibility() != VISIBLE) return; + + _tx += m_x; + _ty += m_y; + + if((_ty > paintInfo.r.bottom()) || (_ty + m_height <= paintInfo.r.top())) + return; + + QPainter* p = paintInfo.p; + + const QColor color( style()->color() ); + p->setPen( color ); + + int xHeight = m_height; + int bulletWidth = (xHeight+1)/2; + int yoff = (xHeight - 1)/4; + QRect marker(_tx, _ty + yoff, bulletWidth, bulletWidth); + + switch(m_type) { + case LDISC: + p->setBrush( color ); + p->drawEllipse( marker ); + return; + case LCIRCLE: + p->setBrush( Qt::NoBrush ); + p->drawEllipse( marker ); + return; + case LSQUARE: + p->setBrush( color ); + p->drawRect( marker ); + return; + case LBOX: + p->setBrush( Qt::NoBrush ); + p->drawRect( marker ); + return; + case LDIAMOND: { + static QPointArray diamond(4); + int x = marker.x(); + int y = marker.y(); + int s = bulletWidth/2; + diamond[0] = QPoint(x+s, y); + diamond[1] = QPoint(x+2*s, y+s); + diamond[2] = QPoint(x+s, y+2*s); + diamond[3] = QPoint(x, y+s); + p->setBrush( color ); + p->drawConvexPolygon( diamond, 0, 4 ); + return; + } + case LNONE: + return; + default: + // not a glyph + assert(false); + } +} + diff --git a/khtml/rendering/render_generated.h b/khtml/rendering/render_generated.h new file mode 100644 index 000000000..15b30b8c7 --- /dev/null +++ b/khtml/rendering/render_generated.h @@ -0,0 +1,125 @@ +/* + * This file is part of the HTML rendering engine for KDE. + * + * Copyright (C) 2004,2005 Allan Sandfeld Jensen (kde@carewolf.com) + * + * 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. + * + */ +#ifndef RENDER_GENERATED_H +#define RENDER_GENERATED_H + +#include "rendering/render_text.h" +#include "rendering/render_box.h" + +namespace DOM { + class CounterImpl; +} + +namespace khtml { + class CounterNode; + +// ----------------------------------------------------------------------------- + +class RenderCounterBase : public RenderText +{ +public: + RenderCounterBase(DOM::NodeImpl* node); + + virtual const char *renderName() const { return "RenderCounterBase"; } + + virtual void layout( ); + virtual void calcMinMaxWidth(); + virtual bool isCounter() const { return true; } + + virtual void generateContent() = 0; + void updateContent(); + +protected: + QString m_item; + CounterNode *m_counterNode; // Cache of the counternode +}; + +// ----------------------------------------------------------------------------- + +class RenderCounter : public RenderCounterBase +{ +public: + RenderCounter(DOM::NodeImpl* node, const DOM::CounterImpl* counter); + virtual ~RenderCounter() {}; + + virtual const char *renderName() const { return "RenderCounter"; } + + virtual void generateContent(); + +protected: + QString toListStyleType(int value, int total, EListStyleType type); + + const DOM::CounterImpl* m_counter; +}; + +// ----------------------------------------------------------------------------- + +class RenderQuote : public RenderCounterBase +{ +public: + RenderQuote(DOM::NodeImpl* node, EQuoteContent type); + virtual ~RenderQuote() {}; + + virtual const char *renderName() const { return "RenderQuote"; } + + virtual bool isQuote() const { return true; } + virtual int quoteCount() const; + + virtual void generateContent(); + +protected: + EQuoteContent m_quoteType; +}; + +// ----------------------------------------------------------------------------- + +// Is actually a special case of renderCounter for non-counted list-styles +// These have traditionally been drawn rather than use Unicode characters +class RenderGlyph : public RenderBox +{ +public: + RenderGlyph(DOM::NodeImpl* node, EListStyleType type); + virtual ~RenderGlyph() {}; + + virtual const char *renderName() const { return "RenderGlyph"; } + + virtual void paint(PaintInfo& paintInfo, int _tx, int _ty); + virtual void calcMinMaxWidth(); + + virtual void setStyle(RenderStyle *_style); + + virtual short lineHeight( bool firstLine ) const; + virtual short baselinePosition( bool firstLine ) const; + + virtual bool isGlyph() const { return true; } + + virtual void position(InlineBox* box, int /*from*/, int /*len*/, bool /*reverse*/) { + setPos( box->xPos(), box->yPos() ); + } + +protected: + EListStyleType m_type; +}; + +} //namespace + +#endif diff --git a/khtml/rendering/render_image.cpp b/khtml/rendering/render_image.cpp new file mode 100644 index 000000000..5d33da2ab --- /dev/null +++ b/khtml/rendering/render_image.cpp @@ -0,0 +1,604 @@ +/** + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000-2003 Dirk Mueller (mueller@kde.org) + * (C) 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 "render_image.h" +#include "render_canvas.h" + +#include <qdrawutil.h> +#include <qpainter.h> + +#include <kapplication.h> +#include <kdebug.h> +#include <kglobalsettings.h> + +#include "css/csshelper.h" +#include "misc/helper.h" +#include "misc/htmlattrs.h" +#include "misc/loader.h" +#include "misc/htmltags.h" +#include "html/html_formimpl.h" +#include "html/html_imageimpl.h" +#include "html/dtd.h" +#include "xml/dom2_eventsimpl.h" +#include "html/html_documentimpl.h" +#include "html/html_objectimpl.h" +#include "khtmlview.h" +#include "khtml_part.h" +#include <math.h> + +#include "loading_icon.cpp" + +using namespace DOM; +using namespace khtml; + +// ------------------------------------------------------------------------- + +RenderImage::RenderImage(NodeImpl *_element) + : RenderReplaced(_element) +{ + m_oldImage = m_cachedImage = 0; + + m_selectionState = SelectionNone; + berrorPic = false; + + const KHTMLSettings *settings = _element->getDocument()->view()->part()->settings(); + bUnfinishedImageFrame = settings->unfinishedImageFrame(); + + setIntrinsicWidth( 0 ); + setIntrinsicHeight( 0 ); +} + +RenderImage::~RenderImage() +{ + if(m_cachedImage) m_cachedImage->deref( this ); + if(m_oldImage) m_oldImage->deref( this ); +} + +QPixmap RenderImage::pixmap() const +{ + return m_cachedImage ? m_cachedImage->pixmap() : QPixmap(); +} + +void RenderImage::setStyle(RenderStyle* _style) +{ + RenderReplaced::setStyle(_style); + // init RenderObject attributes + //setOverhangingContents(style()->height().isPercent()); + setShouldPaintBackgroundOrBorder(true); +} + +void RenderImage::setContentObject(CachedObject* co ) +{ + if (co && m_cachedImage != co) + updateImage( static_cast<CachedImage*>( co ) ); +} + +void RenderImage::setPixmap( const QPixmap &p, const QRect& r, CachedImage *o) +{ + if ( o == m_oldImage ) + return; + + if(o != m_cachedImage) { + RenderReplaced::setPixmap(p, r, o); + return; + } + + bool iwchanged = false; + + if(o->isErrorImage()) { + int iw = p.width() + 8; + int ih = p.height() + 8; + + // we have an alt and the user meant it (its not a text we invented) + if ( element() && !alt.isEmpty() && !element()->getAttribute( ATTR_ALT ).isNull()) { + const QFontMetrics &fm = style()->fontMetrics(); + QRect br = fm.boundingRect ( 0, 0, 1024, 256, Qt::AlignAuto|Qt::WordBreak, alt.string() ); + if ( br.width() > iw ) + iw = br.width(); + if ( br.height() > ih ) + ih = br.height(); + } + + if ( iw != intrinsicWidth() ) { + setIntrinsicWidth( iw ); + iwchanged = true; + } + if ( ih != intrinsicHeight() ) { + setIntrinsicHeight( ih ); + iwchanged = true; + } + if ( element() && element()->id() == ID_OBJECT ) { + static_cast<HTMLObjectElementImpl*>( element() )->renderAlternative(); + return; + } + } + berrorPic = o->isErrorImage(); + + bool needlayout = false; + + // Image dimensions have been changed, see what needs to be done + if( o->pixmap_size().width() != intrinsicWidth() || + o->pixmap_size().height() != intrinsicHeight() || iwchanged ) + { +// qDebug("image dimensions have been changed, old: %d/%d new: %d/%d", +// intrinsicWidth(), intrinsicHeight(), +// o->pixmap_size().width(), o->pixmap_size().height()); + + if(!o->isErrorImage()) { + setIntrinsicWidth( o->pixmap_size().width() ); + setIntrinsicHeight( o->pixmap_size().height() ); + } + + // lets see if we need to relayout at all.. + int oldwidth = m_width; + int oldheight = m_height; + int oldminwidth = m_minWidth; + m_minWidth = 0; + + if ( parent() ) { + calcWidth(); + calcHeight(); + } + + if(iwchanged || m_width != oldwidth || m_height != oldheight) + needlayout = true; + + m_minWidth = oldminwidth; + m_width = oldwidth; + m_height = oldheight; + } + + // we're not fully integrated in the tree yet.. we'll come back. + if ( !parent() ) + return; + + if(needlayout) + { + if (!selfNeedsLayout()) + setNeedsLayout(true); + if (minMaxKnown()) + setMinMaxKnown(false); + } + else + { + bool completeRepaint = !resizeCache.isNull(); + int cHeight = contentHeight(); + int scaledHeight = intrinsicHeight() ? ((o->valid_rect().height()*cHeight)/intrinsicHeight()) : 0; + + // don't bog down X server doing xforms + if(completeRepaint && cHeight >= 5 && o->valid_rect().height() < intrinsicHeight() && + (scaledHeight / (cHeight/5) == resizeCache.height() / (cHeight/5))) + return; + + resizeCache = QPixmap(); // for resized animations + + if(completeRepaint) + repaintRectangle(borderLeft()+paddingLeft(), borderTop()+paddingTop(), contentWidth(), contentHeight()); + else + { + repaintRectangle(r.x() + borderLeft() + paddingLeft(), r.y() + borderTop() + paddingTop(), + r.width(), r.height()); + } + } +} + +void RenderImage::paint(PaintInfo& paintInfo, int _tx, int _ty) +{ + if (paintInfo.phase == PaintActionOutline && style()->outlineWidth() && style()->visibility() == VISIBLE) + paintOutline(paintInfo.p, _tx + m_x, _ty + m_y, width(), height(), style()); + + if (paintInfo.phase != PaintActionForeground && paintInfo.phase != PaintActionSelection) + return; + + // not visible or not even once layouted? + if (style()->visibility() != VISIBLE || m_y <= -500000) return; + + _tx += m_x; + _ty += m_y; + + if((_ty > paintInfo.r.bottom()) || (_ty + m_height <= paintInfo.r.top())) return; + + if(shouldPaintBackgroundOrBorder()) + paintBoxDecorations(paintInfo, _tx, _ty); + + int cWidth = contentWidth(); + int cHeight = contentHeight(); + int leftBorder = borderLeft(); + int topBorder = borderTop(); + int leftPad = paddingLeft(); + int topPad = paddingTop(); + + if (!canvas()->printImages()) + return; + + CachedImage* i = m_oldImage && m_oldImage->valid_rect().size() == m_oldImage->pixmap_size() && + m_oldImage->pixmap_size().width() == intrinsicWidth() && + m_oldImage->pixmap_size().height() == intrinsicHeight() + ? m_oldImage : m_cachedImage; + + // paint frame around image as long as it is not completely loaded from web. + if (bUnfinishedImageFrame && paintInfo.phase == PaintActionForeground && cWidth > 2 && cHeight > 2 && !complete()) { + static QPixmap *loadingIcon; + QColor bg = khtml::retrieveBackgroundColor(this); + QColor fg = khtml::hasSufficientContrast(Qt::gray, bg) ? Qt::gray : + (hasSufficientContrast(Qt::white, bg) ? Qt::white : Qt::black); + paintInfo.p->setPen(QPen(fg, 1)); + paintInfo.p->setBrush( Qt::NoBrush ); + paintInfo.p->drawRect(_tx, _ty, m_width, m_height); + if (!(m_width <= 5 || m_height <= 5)) { + if (!loadingIcon) { + loadingIcon = new QPixmap(); + loadingIcon->loadFromData(loading_icon_data, loading_icon_len); + } + paintInfo.p->drawPixmap(_tx + 4, _ty + 4, *loadingIcon, 0, 0, m_width - 5, m_height - 5); + } + + } + + //kdDebug( 6040 ) << " contents (" << contentWidth << "/" << contentHeight << ") border=" << borderLeft() << " padding=" << paddingLeft() << endl; + if ( !i || berrorPic) + { + if(cWidth > 2 && cHeight > 2) + { + if ( !berrorPic ) { + //qDebug("qDrawShadePanel %d/%d/%d/%d", _tx + leftBorder, _ty + topBorder, cWidth, cHeight); + qDrawShadePanel( paintInfo.p, _tx + leftBorder + leftPad, _ty + topBorder + topPad, cWidth, cHeight, + KApplication::palette().inactive(), true, 1 ); + } + QPixmap const* pix = i ? &i->pixmap() : 0; + if(berrorPic && pix && (cWidth >= pix->width()+4) && (cHeight >= pix->height()+4) ) + { + QRect r(pix->rect()); + r = r.intersect(QRect(0, 0, cWidth-4, cHeight-4)); + paintInfo.p->drawPixmap( QPoint( _tx + leftBorder + leftPad+2, _ty + topBorder + topPad+2), *pix, r ); + } + if(!alt.isEmpty()) { + QString text = alt.string(); + paintInfo.p->setFont(style()->font()); + paintInfo.p->setPen( style()->color() ); + int ax = _tx + leftBorder + leftPad + 2; + int ay = _ty + topBorder + topPad + 2; + const QFontMetrics &fm = style()->fontMetrics(); + if (cWidth>5 && cHeight>=fm.height()) + paintInfo.p->drawText(ax, ay+1, cWidth - 4, cHeight - 4, Qt::WordBreak, text ); + } + } + } + else if (i && !i->isTransparent()) + { + paintInfo.p->setPen( Qt::black ); // used for bitmaps + const QPixmap& pix = i->pixmap(); + if ( (cWidth != intrinsicWidth() || cHeight != intrinsicHeight()) && + pix.width() > 0 && pix.height() > 0 && i->valid_rect().isValid()) + { + if (resizeCache.isNull() && cWidth && cHeight && intrinsicWidth() && intrinsicHeight()) + { + QRect scaledrect(i->valid_rect()); +// kdDebug(6040) << "time elapsed: " << dt->elapsed() << endl; +// kdDebug( 6040 ) << "have to scale: " << endl; +// qDebug("cw=%d ch=%d pw=%d ph=%d rcw=%d, rch=%d", +// cWidth, cHeight, intrinsicWidth(), intrinsicHeight(), resizeCache.width(), resizeCache.height()); + QWMatrix matrix; + matrix.scale( (float)(cWidth)/intrinsicWidth(), + (float)(cHeight)/intrinsicHeight() ); + resizeCache = pix.xForm( matrix ); + scaledrect.setWidth( ( cWidth*scaledrect.width() ) / intrinsicWidth() ); + scaledrect.setHeight( ( cHeight*scaledrect.height() ) / intrinsicHeight() ); +// qDebug("resizeCache size: %d/%d", resizeCache.width(), resizeCache.height()); +// qDebug("valid: %d/%d, scaled: %d/%d", +// i->valid_rect().width(), i->valid_rect().height(), +// scaledrect.width(), scaledrect.height()); + + // sometimes scaledrect.width/height are off by one because + // of rounding errors. if the i is fully loaded, we + // make sure that we don't do unnecessary resizes during painting + QSize s(scaledrect.size()); + if(i->valid_rect().size() == QSize( intrinsicWidth(), intrinsicHeight() )) // fully loaded + s = QSize(cWidth, cHeight); + if(kAbs(s.width() - cWidth) < 2) // rounding errors + s.setWidth(cWidth); + if(resizeCache.size() != s) + resizeCache.resize(s); + + paintInfo.p->drawPixmap( QPoint( _tx + leftBorder + leftPad, _ty + topBorder + topPad), + resizeCache, scaledrect ); + } + else + paintInfo.p->drawPixmap( QPoint( _tx + leftBorder + leftPad, _ty + topBorder + topPad), resizeCache ); + } + else + { + // we might be just about switching images + QRect rect(i->valid_rect().isValid() ? i->valid_rect() + : QRect(0, 0, intrinsicWidth(), intrinsicHeight())); + + QPoint offs( _tx + leftBorder + leftPad, _ty + topBorder + topPad); +// qDebug("normal paint rect %d/%d/%d/%d", rect.x(), rect.y(), rect.width(), rect.height()); +// rect = rect & QRect( 0 , y - offs.y() - 10, w, 10 + y + h - offs.y()); + +// qDebug("normal paint rect after %d/%d/%d/%d", rect.x(), rect.y(), rect.width(), rect.height()); +// qDebug("normal paint: offs.y(): %d, y: %d, diff: %d", offs.y(), y, y - offs.y()); +// qDebug(""); + +// p->setClipRect(QRect(x,y,w,h)); + + +// p->drawPixmap( offs.x(), y, pix, rect.x(), rect.y(), rect.width(), rect.height() ); + paintInfo.p->drawPixmap(offs, pix, rect); + + } + } + if (m_selectionState != SelectionNone) { +// kdDebug(6040) << "_tx " << _tx << " _ty " << _ty << " _x " << _x << " _y " << _y << endl; + // Draw in any case if inside selection. For selection borders, the + // offset will decide whether to draw selection or not + bool draw = true; + if (m_selectionState != SelectionInside) { + int startPos, endPos; + selectionStartEnd(startPos, endPos); + if(selectionState() == SelectionStart) + endPos = 1; + else if(selectionState() == SelectionEnd) + startPos = 0; + draw = endPos - startPos > 0; + } + if (draw) { + // setting the brush origin is important for compatibility, + // don't touch it unless you know what you're doing + paintInfo.p->setBrushOrigin(_tx, _ty - paintInfo.r.y()); + paintInfo.p->fillRect(_tx, _ty, width(), height(), + QBrush(style()->palette().active().highlight(), + Qt::Dense4Pattern)); + } + } +} + +void RenderImage::layout() +{ + KHTMLAssert( needsLayout()); + KHTMLAssert( minMaxKnown() ); + + short oldwidth = m_width; + int oldheight = m_height; + + // minimum height + m_height = m_cachedImage && m_cachedImage->isErrorImage() ? intrinsicHeight() : 0; + + calcWidth(); + calcHeight(); + + // if they are variable width and we calculate a huge height or width, we assume they + // actually wanted the intrinsic width. + if ( m_width > 4096 && !style()->width().isFixed() ) + m_width = intrinsicWidth() + paddingLeft() + paddingRight() + borderLeft() + borderRight(); + if ( m_height > 2048 && !style()->height().isFixed() ) + m_height = intrinsicHeight() + paddingTop() + paddingBottom() + borderTop() + borderBottom(); + + // limit total size to not run out of memory when doing the xform call. + if ( ( m_width * m_height > 4096*2048 ) && + ( contentWidth() > intrinsicWidth() || contentHeight() > intrinsicHeight() ) ) { + float scale = sqrt( m_width*m_height / ( 4096.*2048. ) ); + m_width = (int) (m_width/scale); + m_height = (int) (m_height/scale); + } + + if ( m_width != oldwidth || m_height != oldheight ) + resizeCache = QPixmap(); + + setNeedsLayout(false); +} + +void RenderImage::notifyFinished(CachedObject *finishedObj) +{ + if ( ( m_cachedImage == finishedObj || m_oldImage == finishedObj ) && m_oldImage ) { + m_oldImage->deref( this ); + m_oldImage = 0; + repaint(); + } + + RenderReplaced::notifyFinished(finishedObj); +} + +bool RenderImage::nodeAtPoint(NodeInfo& info, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction, bool inside) +{ + inside |= RenderReplaced::nodeAtPoint(info, _x, _y, _tx, _ty, hitTestAction, inside); + + if (inside && element()) { + int tx = _tx + m_x; + int ty = _ty + m_y; + if (isRelPositioned()) + relativePositionOffset(tx, ty); + + HTMLImageElementImpl* i = element()->id() == ID_IMG ? static_cast<HTMLImageElementImpl*>(element()) : 0; + HTMLMapElementImpl* map; + if (i && i->getDocument()->isHTMLDocument() && + (map = static_cast<HTMLDocumentImpl*>(i->getDocument())->getMap(i->imageMap()))) { + // we're a client side image map + inside = map->mapMouseEvent(_x - tx, _y - ty, contentWidth(), contentHeight(), info); + info.setInnerNonSharedNode(element()); + } + } + + return inside; +} + +void RenderImage::updateImage(CachedImage* new_image) +{ + CachedImage* tempimage = m_oldImage; + m_oldImage = m_cachedImage; + m_cachedImage = new_image; + assert( m_cachedImage != m_oldImage ); + + if ( m_cachedImage ) + m_cachedImage->ref(this); + + if ( tempimage ) + tempimage->deref(this); + + // if the loading finishes we might get an error and then the image is deleted + if ( m_cachedImage ) + berrorPic = m_cachedImage->isErrorImage(); + else + berrorPic = true; +} + +void RenderImage::updateFromElement() +{ + if (element()->id() == ID_INPUT) + alt = static_cast<HTMLInputElementImpl*>(element())->altText(); + else if (element()->id() == ID_IMG) + alt = static_cast<HTMLImageElementImpl*>(element())->altText(); + + DOMString u = element()->id() == ID_OBJECT ? + element()->getAttribute(ATTR_DATA) : element()->getAttribute(ATTR_SRC); + + if (!u.isEmpty() && + ( !m_cachedImage || m_cachedImage->url() != u ) ) { + CachedImage *new_image = element()->getDocument()->docLoader()-> + requestImage(khtml::parseURL(u)); + + if(new_image && new_image != m_cachedImage) + updateImage( new_image ); + } +} + +bool RenderImage::complete() const +{ + // "complete" means that the image has been loaded + // but also that its width/height (contentWidth(),contentHeight()) have been calculated. + return m_cachedImage && m_cachedImage->valid_rect().size() == m_cachedImage->pixmap_size() && !needsLayout(); +} + +bool RenderImage::isWidthSpecified() const +{ + switch (style()->width().type()) { + case Fixed: + case Percent: + return true; + default: + return false; + } + assert(false); + return false; +} + +bool RenderImage::isHeightSpecified() const +{ + switch (style()->height().type()) { + case Fixed: + case Percent: + return true; + default: + return false; + } + assert(false); + return false; +} + +short RenderImage::calcAspectRatioWidth() const +{ + if (intrinsicHeight() == 0) + return 0; + if (!m_cachedImage || m_cachedImage->isErrorImage()) + return intrinsicWidth(); // Don't bother scaling. + return RenderReplaced::calcReplacedHeight() * intrinsicWidth() / intrinsicHeight(); +} + +int RenderImage::calcAspectRatioHeight() const +{ + if (intrinsicWidth() == 0) + return 0; + if (!m_cachedImage || m_cachedImage->isErrorImage()) + return intrinsicHeight(); // Don't bother scaling. + return RenderReplaced::calcReplacedWidth() * intrinsicHeight() / intrinsicWidth(); +} + +short RenderImage::calcReplacedWidth() const +{ + int width; + if (isWidthSpecified()) + width = calcReplacedWidthUsing(Width); + else + width = calcAspectRatioWidth(); + int minW = calcReplacedWidthUsing(MinWidth); + int maxW = style()->maxWidth().value() == UNDEFINED ? width : calcReplacedWidthUsing(MaxWidth); + + if (width > maxW) + width = maxW; + + if (width < minW) + width = minW; + + return width; +} + +int RenderImage::calcReplacedHeight() const +{ + int height; + if (isHeightSpecified()) + height = calcReplacedHeightUsing(Height); + else + height = calcAspectRatioHeight(); + + int minH = calcReplacedHeightUsing(MinHeight); + int maxH = style()->maxHeight().value() == UNDEFINED ? height : calcReplacedHeightUsing(MaxHeight); + + if (height > maxH) + height = maxH; + + if (height < minH) + height = minH; + + return height; +} + +#if 0 +void RenderImage::caretPos(int offset, int flags, int &_x, int &_y, int &width, int &height) +{ + RenderReplaced::caretPos(offset, flags, _x, _y, width, height); + +#if 0 // doesn't work reliably + height = intrinsicHeight(); + width = override && offset == 0 ? intrinsicWidth() : 0; + _x = xPos(); + _y = yPos(); + if (offset > 0) _x += intrinsicWidth(); + + RenderObject *cb = containingBlock(); + + int absx, absy; + if (cb && cb != this && cb->absolutePosition(absx,absy)) + { + _x += absx; + _y += absy; + } else { + // we don't know our absolute position, and there is no point returning + // just a relative one + _x = _y = -1; + } +#endif +} +#endif diff --git a/khtml/rendering/render_image.h b/khtml/rendering/render_image.h new file mode 100644 index 000000000..bd887ddf5 --- /dev/null +++ b/khtml/rendering/render_image.h @@ -0,0 +1,105 @@ +/* + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * + * 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. + * + */ +#ifndef RENDER_IMAGE_H +#define RENDER_IMAGE_H + +#include "html/dtd.h" +#include "html/html_elementimpl.h" +#include "rendering/render_replaced.h" +#include "dom/dom_string.h" + +#include <qmap.h> +#include <qpixmap.h> + +namespace khtml { + +class DocLoader; +class CachedObject; + +class RenderImage : public RenderReplaced +{ +public: + RenderImage(DOM::NodeImpl* _element); + virtual ~RenderImage(); + + virtual const char *renderName() const { return "RenderImage"; } + virtual void paint( PaintInfo& i, int tx, int ty ); + + virtual void layout(); + + virtual void setPixmap( const QPixmap &, const QRect&, CachedImage *); + + // don't even think about making these methods virtual! + QPixmap pixmap() const; + DOM::HTMLElementImpl* element() const + { return static_cast<DOM::HTMLElementImpl*>(RenderObject::element()); } + + bool complete() const; + + CachedObject *contentObject() { return m_cachedImage; } + void setContentObject( CachedObject* ); + + // hook to keep RendeObject::m_inline() up to date + virtual void setStyle(RenderStyle *style); + virtual void updateFromElement(); + + virtual void notifyFinished(CachedObject *finishedObj); + virtual bool nodeAtPoint(NodeInfo& info, int x, int y, int tx, int ty, HitTestAction hitTestAction, bool inside); + + bool isWidthSpecified() const; + bool isHeightSpecified() const; + + short calcAspectRatioWidth() const; + int calcAspectRatioHeight() const; + + virtual short calcReplacedWidth() const; + virtual int calcReplacedHeight() const; + + virtual SelectionState selectionState() const {return m_selectionState;} + virtual void setSelectionState(SelectionState s) {m_selectionState = s; } +#if 0 + virtual void caretPos(int offset, int flags, int &_x, int &_y, int &width, int &height); +#endif + +private: + void updateImage(CachedImage* new_image); + /* + * Cache for images that need resizing + */ + QPixmap resizeCache; + + // text to display as long as the image isn't available + DOM::DOMString alt; + + CachedImage *m_cachedImage; + CachedImage *m_oldImage; + + bool berrorPic : 1; + bool bUnfinishedImageFrame :1; + SelectionState m_selectionState : 3; // FIXME: don't forget to enlarge this as the enum grows +}; + + +} //namespace + +#endif diff --git a/khtml/rendering/render_inline.cpp b/khtml/rendering/render_inline.cpp new file mode 100644 index 000000000..5848db47a --- /dev/null +++ b/khtml/rendering/render_inline.cpp @@ -0,0 +1,935 @@ +/* + * This file is part of the render object implementation for KHTML. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 1999-2003 Antti Koivisto (koivisto@kde.org) + * (C) 2002-2003 Dirk Mueller (mueller@kde.org) + * (C) 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. + * + */ + +#include <kglobal.h> + +#include "rendering/render_arena.h" +#include "rendering/render_inline.h" +#include "rendering/render_block.h" +#include "xml/dom_docimpl.h" + +#include <qvaluevector.h> + +using namespace khtml; + +void RenderInline::setStyle(RenderStyle* _style) +{ + RenderFlow::setStyle(_style); + setInline(true); + + // Ensure that all of the split inlines pick up the new style. We + // only do this if we're an inline, since we don't want to propagate + // a block's style to the other inlines. + // e.g., <font>foo <h4>goo</h4> moo</font>. The <font> inlines before + // and after the block share the same style, but the block doesn't + // need to pass its style on to anyone else. + RenderFlow* currCont = continuation(); + while (currCont) { + if (currCont->isInline()) { + RenderFlow* nextCont = currCont->continuation(); + currCont->setContinuation(0); + currCont->setStyle(style()); + currCont->setContinuation(nextCont); + } + currCont = currCont->continuation(); + } + + if (attached()) { + // Update replaced content + updateReplacedContent(); + // Update pseudos for ::before and ::after + updatePseudoChildren(); + } +} + +// Attach handles initial setStyle that requires parent nodes +void RenderInline::attach() +{ + RenderFlow::attach(); + + updateReplacedContent(); + updatePseudoChildren(); +} + +bool RenderInline::isInlineContinuation() const +{ + return m_isContinuation; +} + +void RenderInline::addChildToFlow(RenderObject* newChild, RenderObject* beforeChild) +{ + // Make sure we don't append things after :after-generated content if we have it. + if (!beforeChild && lastChild() && lastChild()->style()->styleType() == RenderStyle::AFTER) + beforeChild = lastChild(); + + if (!newChild->isText() && newChild->style()->position() != STATIC) + setOverhangingContents(); + + if (!newChild->isInline() && !newChild->isFloatingOrPositioned() ) + { + // We are placing a block inside an inline. We have to perform a split of this + // inline into continuations. This involves creating an anonymous block box to hold + // |newChild|. We then make that block box a continuation of this inline. We take all of + // the children after |beforeChild| and put them in a clone of this object. + + RenderBlock *newBox = createAnonymousBlock(); + RenderFlow* oldContinuation = continuation(); + setContinuation(newBox); + + splitFlow(beforeChild, newBox, newChild, oldContinuation); + return; + } + + RenderBox::addChild(newChild,beforeChild); + + newChild->setNeedsLayoutAndMinMaxRecalc(); +} + +RenderInline* RenderInline::cloneInline(RenderFlow* src) +{ + RenderInline *o = new (src->renderArena()) RenderInline(src->element()); + o->m_isContinuation = true; + o->setStyle(src->style()); + return o; +} + +void RenderInline::splitInlines(RenderBlock* fromBlock, RenderBlock* toBlock, + RenderBlock* middleBlock, + RenderObject* beforeChild, RenderFlow* oldCont) +{ + // Create a clone of this inline. + RenderInline* clone = cloneInline(this); + clone->setContinuation(oldCont); + + // Now take all of the children from beforeChild to the end and remove + // then from |this| and place them in the clone. + RenderObject* o = beforeChild; + while (o) { + RenderObject* tmp = o; + o = tmp->nextSibling(); + clone->addChildToFlow(removeChildNode(tmp), 0); + tmp->setNeedsLayoutAndMinMaxRecalc(); + } + + // Hook |clone| up as the continuation of the middle block. + middleBlock->setContinuation(clone); + + // We have been reparented and are now under the fromBlock. We need + // to walk up our inline parent chain until we hit the containing block. + // Once we hit the containing block we're done. + RenderFlow* curr = static_cast<RenderFlow*>(parent()); + RenderFlow* currChild = this; + while (curr && curr != fromBlock) { + // Create a new clone. + RenderInline* cloneChild = clone; + clone = cloneInline(curr); + + // Insert our child clone as the first child. + clone->addChildToFlow(cloneChild, 0); + + // Hook the clone up as a continuation of |curr|. + RenderFlow* oldCont = curr->continuation(); + curr->setContinuation(clone); + clone->setContinuation(oldCont); + + // Now we need to take all of the children starting from the first child + // *after* currChild and append them all to the clone. + o = currChild->nextSibling(); + while (o) { + RenderObject* tmp = o; + o = tmp->nextSibling(); + clone->appendChildNode(curr->removeChildNode(tmp)); + tmp->setNeedsLayoutAndMinMaxRecalc(); + } + + // Keep walking up the chain. + currChild = curr; + curr = static_cast<RenderFlow*>(curr->parent()); + } + + // Now we are at the block level. We need to put the clone into the toBlock. + toBlock->appendChildNode(clone); + + // Now take all the children after currChild and remove them from the fromBlock + // and put them in the toBlock. + o = currChild->nextSibling(); + while (o) { + RenderObject* tmp = o; + o = tmp->nextSibling(); + toBlock->appendChildNode(fromBlock->removeChildNode(tmp)); + } +} + +void RenderInline::splitFlow(RenderObject* beforeChild, RenderBlock* newBlockBox, + RenderObject* newChild, RenderFlow* oldCont) +{ + RenderBlock* pre = 0; + RenderBlock* block = containingBlock(); + bool madeNewBeforeBlock = false; + if (block->isAnonymousBlock()) { + // We can reuse this block and make it the preBlock of the next continuation. + pre = block; + block = block->containingBlock(); + } + else { + // No anonymous block available for use. Make one. + pre = block->createAnonymousBlock(); + madeNewBeforeBlock = true; + } + + RenderBlock* post = block->createAnonymousBlock(); + + RenderObject* boxFirst = madeNewBeforeBlock ? block->firstChild() : pre->nextSibling(); + if (madeNewBeforeBlock) + block->insertChildNode(pre, boxFirst); + block->insertChildNode(newBlockBox, boxFirst); + block->insertChildNode(post, boxFirst); + block->setChildrenInline(false); + + if (madeNewBeforeBlock) { + RenderObject* o = boxFirst; + while (o) + { + RenderObject* no = o; + o = no->nextSibling(); + pre->appendChildNode(block->removeChildNode(no)); + no->setNeedsLayoutAndMinMaxRecalc(); + } + } + + splitInlines(pre, post, newBlockBox, beforeChild, oldCont); + + // We already know the newBlockBox isn't going to contain inline kids, so avoid wasting + // time in makeChildrenNonInline by just setting this explicitly up front. + newBlockBox->setChildrenInline(false); + + // We don't just call addChild, since it would pass things off to the + // continuation, so we call addChildToFlow explicitly instead. We delayed + // adding the newChild until now so that the |newBlockBox| would be fully + // connected, thus allowing newChild access to a renderArena should it need + // to wrap itself in additional boxes (e.g., table construction). + newBlockBox->addChildToFlow(newChild, 0); + + // XXXdwh is any of this even necessary? I don't think it is. + pre->close(); + pre->setPos(0, -500000); + pre->setNeedsLayout(true); + newBlockBox->close(); + newBlockBox->setPos(0, -500000); + newBlockBox->setNeedsLayout(true); + post->close(); + post->setPos(0, -500000); + post->setNeedsLayout(true); + + updatePseudoChildren(); + + block->setNeedsLayoutAndMinMaxRecalc(); +} + +void RenderInline::paint(PaintInfo& i, int _tx, int _ty) +{ + paintLines(i, _tx, _ty); +} + +/** + * Appends the given coordinate-pair to the point-array if it is not + * equal to the last element. + * @param pointArray point-array + * @param pnt point to append + * @return \c true if \c pnt has actually been appended + */ +inline static bool appendIfNew(QValueVector<QPoint> &pointArray, const QPoint &pnt) +{ +// if (!pointArray.isEmpty()) kdDebug(6040) << "appifnew: " << pointArray.back() << " == " << pnt << ": " << (pointArray.back() == pnt) << endl; +// else kdDebug(6040) << "appifnew: " << pnt << " (unconditional)" << endl; + if (!pointArray.isEmpty() && pointArray.back() == pnt) return false; + pointArray.append(pnt); + return true; +} + +/** + * Does spike-reduction on the given point-array's stack-top. + * + * Spikes are path segments of which one goes forward, and the sucessor + * goes backward on the predecessor's segment: + * + * 2 0 1 + * x------x<-----x + * (0 is stack-top in point-array) + * + * This will be reduced to + * 1 0 + * x------x + * + * Preconditions: + * - No other spikes exist in the whole point-array except at most + * one at the end + * - No two succeeding points are ever equal + * - For each two succeeding points either p1.x == p2.x or p1.y == p2.y holds + * true + * - No such spike exists where 2 is situated between 0 and 1. + * + * Postcondition: + * - No spikes exist in the whole point-array + * + * If no spike is found, the point-array is left unchanged. + * @return \c true if an actual reduction was done + */ +inline static bool reduceSpike(QValueVector<QPoint> &pointArray) +{ + if (pointArray.size() < 3) return false; + QValueVector<QPoint>::Iterator it = pointArray.end(); + QPoint p0 = *--it; + QPoint p1 = *--it; + QPoint p2 = *--it; + + bool elide = false; + + if (p0.x() == p1.x() && p1.x() == p2.x() + && (p1.y() < p0.y() && p0.y() < p2.y() + || p2.y() < p0.y() && p0.y() < p1.y() + || p1.y() < p2.y() && p2.y() < p0.y() + || p0.y() < p2.y() && p2.y() < p1.y() + || (elide = p2.y() == p0.y() && p0.y() < p1.y()) + || (elide = p1.y() < p0.y() && p0.y() == p2.y())) + || p0.y() == p1.y() && p1.y() == p2.y() + && (p1.x() < p0.x() && p0.x() < p2.x() + || p2.x() < p0.x() && p0.x() < p1.x() + || p1.x() < p2.x() && p2.x() < p0.x() + || p0.x() < p2.x() && p2.x() < p1.x() + || (elide = p2.x() == p0.x() && p0.x() < p1.x()) + || (elide = p1.x() < p0.x() && p0.x() == p2.x()))) + { +// kdDebug(6040) << "spikered p2" << (elide ? " (elide)" : "") << ": " << p2 << " p1: " << p1 << " p0: " << p0 << endl; + pointArray.pop_back(); pointArray.pop_back(); + if (!elide) + pointArray.push_back(p0); + return true; + } + return false; +} + +/** + * Reduces segment separators. + * + * A segment separator separates a segment into two segments, thus causing + * two adjacent segment with the same orientation. + * + * 2 1 0 + * x-------x---->x + * (0 means stack-top) + * + * Here, 1 is a segment separator. As segment separators not only make + * the line drawing algorithm inefficient, but also make the spike-reduction + * fail, they must be eliminated: + * + * 1 0 + * x------------>x + * + * Preconditions: + * - No other segment separators exist in the whole point-array except + * at most one at the end + * - No two succeeding points are ever equal + * - For each two succeeding points either p1.x == p2.x or p1.y == p2.y holds + * true + * - No such spike exists where 2 is situated between 0 and 1. + * + * Postcondition: + * - No segment separators exist in the whole point-array + * + * If no segment separator is found at the end of the point-array, it is + * left unchanged. + * @return \c true if a segment separator was actually reduced. + */ +inline static bool reduceSegmentSeparator(QValueVector<QPoint> &pointArray) +{ + if (pointArray.size() < 3) return false; + QValueVector<QPoint>::Iterator it = pointArray.end(); + QPoint p0 = *--it; + QPoint p1 = *--it; + QPoint p2 = *--it; +// kdDebug(6040) << "checking p2: " << p2 << " p1: " << p1 << " p0: " << p0 << endl; + + if (p0.x() == p1.x() && p1.x() == p2.x() + && (p2.y() < p1.y() && p1.y() < p0.y() + || p0.y() < p1.y() && p1.y() < p2.y()) + || p0.y() == p1.y() && p1.y() == p2.y() + && (p2.x() < p1.x() && p1.x() < p0.x() + || p0.x() < p1.x() && p1.x() < p2.x())) + { +// kdDebug(6040) << "segred p2: " << p2 << " p1: " << p1 << " p0: " << p0 << endl; + pointArray.pop_back(); pointArray.pop_back(); + pointArray.push_back(p0); + return true; + } + return false; +} + +/** + * Appends the given point to the point-array, doing necessary reductions to + * produce a path without spikes and segment separators. + */ +static void appendPoint(QValueVector<QPoint> &pointArray, QPoint &pnt) +{ + if (!appendIfNew(pointArray, pnt)) return; +// kdDebug(6040) << "appendPoint: appended " << pnt << endl; + reduceSegmentSeparator(pointArray) + || reduceSpike(pointArray); +} + +/** + * Traverses the horizontal inline boxes and appends the point coordinates to + * the given array. + * @param box inline box + * @param pointArray array collecting coordinates + * @param bottom \c true, collect bottom coordinates, \c false, collect top + * coordinates. + * @param limit lower limit that an y-coordinate must at least reach. Note + * that limit designates the highest y-coordinate for \c bottom, and + * the lowest for !\c bottom. + */ +static void collectHorizontalBoxCoordinates(InlineBox *box, + QValueVector<QPoint> &pointArray, + bool bottom, int offset, int limit = -500000) +{ +// kdDebug(6000) << "collectHorizontalBoxCoordinates: " << endl; + offset = bottom ? offset:-offset; + int y = box->yPos() + bottom*box->height() + offset; + if (limit != -500000 && (bottom ? y < limit : y > limit)) + y = limit; + int x = box->xPos() + bottom*box->width() + offset; + QPoint newPnt(x, y); + // Add intersection point if point-array not empty. + if (!pointArray.isEmpty()) { + QPoint lastPnt = pointArray.back(); + QPoint insPnt(newPnt.x(), lastPnt.y()); + if (offset && ((bottom && lastPnt.y() > y) || (!bottom && lastPnt.y() < y))) { + insPnt.rx() = lastPnt.x(); + insPnt.ry() = y; + } +// kdDebug(6040) << "left: " << lastPnt << " == " << insPnt << ": " << (insPnt == lastPnt) << endl; + appendPoint(pointArray, insPnt); + } + // Insert starting point of box + appendPoint(pointArray, newPnt); + + newPnt.rx() += (bottom ? -box->width() : box->width()) - 2*offset; + + if (box->isInlineFlowBox()) { + InlineFlowBox *flowBox = static_cast<InlineFlowBox *>(box); + for (InlineBox *b = bottom ? flowBox->lastChild() : flowBox->firstChild(); b; b = bottom ? b->prevOnLine() : b->nextOnLine()) { + // Don't let boxes smaller than this flow box' height influence + // the vertical position of the outline if they have a different + // x-coordinate + int l2; + if (b->xPos() != box->xPos() && b->xPos() + b->width() != box->xPos() + box->width()) + l2 = y; + else + l2 = limit; + collectHorizontalBoxCoordinates(b, pointArray, bottom, kAbs(offset), l2); + } + + // Add intersection point if flow box contained any children + if (flowBox->firstChild()) { + QPoint lastPnt = pointArray.back(); + QPoint insPnt(lastPnt.x(), newPnt.y()); +// kdDebug(6040) << "right: " << lastPnt << " == " << insPnt << ": " << (insPnt == lastPnt) << endl; + appendPoint(pointArray, insPnt); + } + } + + // Insert ending point of box + appendPoint(pointArray, newPnt); + +// kdDebug(6000) << "collectHorizontalBoxCoordinates: " << "ende" << endl; +} + +/** + * Checks whether the given line box' extents and the following line box' + * extents are disjount (i. e. do not share the same x-coordinate range). + * @param line line box + * @param toBegin \c true, compare with preceding line box, \c false, with + * succeeding + * @return \c true if this and the next box are disjoint + */ +inline static bool lineBoxesDisjoint(InlineRunBox *line, int offset, bool toBegin) +{ + InlineRunBox *next = toBegin ? line->prevLineBox() : line->nextLineBox(); + return !next || next->xPos() + next->width() + 2*offset < line->xPos() + || next->xPos() > line->xPos() + line->width() + 2*offset; +} + +/** + * Traverses the vertical outer borders of the given render flow's line + * boxes and appends the point coordinates to the given point array. + * @param line line box to begin traversal + * @param pointArray point array + * @param left \c true, traverse the left vertical coordinates, + * \c false, traverse the right vertical coordinates. + * @param lastline if not 0, returns the pointer to the last line box traversed + */ +static void collectVerticalBoxCoordinates(InlineRunBox *line, + QValueVector<QPoint> &pointArray, + bool left, int offset, InlineRunBox **lastline = 0) +{ + InlineRunBox *last = 0; + offset = left ? -offset:offset; + for (InlineRunBox* curr = line; curr && !last; curr = left ? curr->prevLineBox() : curr->nextLineBox()) { + InlineBox *root = curr; + + bool isLast = lineBoxesDisjoint(curr, kAbs(offset), left); + if (isLast) last = curr; + + if (root != line && !isLast) + while (root->parent()) root = root->parent(); + QPoint newPnt(curr->xPos() + !left*curr->width() + offset, + (left ? root->topOverflow() : root->bottomOverflow()) + offset); + if (!pointArray.isEmpty()) { + QPoint lastPnt = pointArray.back(); + if (newPnt.x()>lastPnt.x() && !left) + pointArray.back().setY( kMin(lastPnt.y(), root->topOverflow()-offset) ); + else if (newPnt.x()<lastPnt.x() && left) + pointArray.back().setY( kMax(lastPnt.y(), root->bottomOverflow()+offset) ); + QPoint insPnt(newPnt.x(), pointArray.back().y()); +// kdDebug(6040) << "left: " << lastPnt << " == " << insPnt << ": " << (insPnt == lastPnt) << endl; + appendPoint(pointArray, insPnt); + } + appendPoint(pointArray, newPnt); + } + if (lastline) *lastline = last; +} + +/** + * Links up the end of the given point-array such that the starting point + * is not a segment separator. + * + * To achieve this, improper points are removed from the beginning of + * the point-array (by changing the array's starting iterator), and + * proper ones appended to the point-array's back. + * + * @param pointArray point-array + * @return actual begin of point array + */ +static QPoint *linkEndToBegin(QValueVector<QPoint> &pointArray) +{ + uint index = 0; + assert(pointArray.size() >= 3); + + // if first and last points match, ignore the last one. + bool linkup = false; QPoint linkupPnt; + if (pointArray.front() == pointArray.back()) { + linkupPnt = pointArray.back(); + pointArray.pop_back(); + linkup = true; + } + + const QPoint *it = pointArray.begin() + index; + QPoint pfirst = *it; + QPoint pnext = *++it; + QPoint plast = pointArray.back(); +// kdDebug(6040) << "linkcheck plast: " << plast << " pfirst: " << pfirst << " pnext: " << pnext << endl; + + if (plast.x() == pfirst.x() && pfirst.x() == pnext.x() + || plast.y() == pfirst.y() && pfirst.y() == pnext.y()) { + + ++index; + appendPoint(pointArray, pfirst); + appendPoint(pointArray, pnext); + } else if (linkup) + pointArray.push_back(linkupPnt); + return pointArray.begin() + index; +} + +void RenderInline::paintOutlines(QPainter *p, int _tx, int _ty) +{ + if (style()->outlineWidth() == 0 || style()->outlineStyle() <= BHIDDEN) + return; + int offset = style()->outlineOffset(); + + // We may have to draw more than one outline path as they may be + // disjoint. + for (InlineRunBox *curr = firstLineBox(); curr; curr = curr->nextLineBox()) { + QValueVector<QPoint> path; + + // collect topmost outline + collectHorizontalBoxCoordinates(curr, path, false, offset); + // collect right outline + collectVerticalBoxCoordinates(curr, path, false, offset, &curr); + // collect bottommost outline + collectHorizontalBoxCoordinates(curr, path, true, offset); + // collect left outline + collectVerticalBoxCoordinates(curr, path, true, offset); + + if (path.size() < 3) continue; + + const QPoint *begin = linkEndToBegin(path); + + // paint the outline + paintOutlinePath(p, _tx, _ty, begin, path.end(), BSLeft, -1, BSTop); + } +} + +template<class T> inline void kSwap(T &a1, T &a2) +{ + T tmp = a2; + a2 = a1; + a1 = tmp; +} + +enum BSOrientation { BSHorizontal, BSVertical }; + +/** + * Returns the orientation of the given border side. + */ +inline BSOrientation bsOrientation(RenderObject::BorderSide bs) +{ + switch (bs) { + case RenderObject::BSTop: + case RenderObject::BSBottom: + return BSHorizontal; + case RenderObject::BSLeft: + case RenderObject::BSRight: + return BSVertical; + } + return BSHorizontal; // make gcc happy (sigh) +} + +/** + * Determines the new border side by evaluating the new direction as determined + * by the given coordinates, the old border side, and the relative direction. + * + * The relative direction specifies whether the old border side meets with the + * straight given by the coordinates from below (negative), or above (positive). + */ +inline RenderObject::BorderSide newBorderSide(RenderObject::BorderSide oldBS, int direction, const QPoint &last, const QPoint &cur) +{ + bool below = direction < 0; + if (last.x() == cur.x()) { // new segment is vertical + bool t = oldBS == RenderObject::BSTop; + bool b = oldBS == RenderObject::BSBottom; + if ((t || b) && last.y() != cur.y()) + return (cur.y() < last.y()) ^ (t && below || b && !below) + ? RenderObject::BSLeft : RenderObject::BSRight; + } else /*if (last.y() == cur.y())*/ { // new segment is horizontal + bool l = oldBS == RenderObject::BSLeft; + bool r = oldBS == RenderObject::BSRight; + if ((l || r) && last.x() != cur.x()) + return (cur.x() < last.x()) ^ (l && below || r && !below) + ? RenderObject::BSTop : RenderObject::BSBottom; + } + return oldBS; // same direction +} + +/** + * Draws an outline segment between the given two points. + * @param o render object + * @param p painter + * @param tx absolute x-coordinate of containing block + * @param ty absolute y-coordinate of containing block + * @param p1 starting point + * @param p2 end point + * @param prevBS border side of previous segment + * @param curBS border side of this segment + * @param nextBS border side of next segment + */ +static void paintOutlineSegment(RenderObject *o, QPainter *p, int tx, int ty, + const QPoint &p1, const QPoint &p2, + RenderObject::BorderSide prevBS, + RenderObject::BorderSide curBS, + RenderObject::BorderSide nextBS) +{ + int ow = o->style()->outlineWidth(); + EBorderStyle os = o->style()->outlineStyle(); + QColor oc = o->style()->outlineColor(); + + int x1 = tx + p1.x(); + int y1 = ty + p1.y(); + int x2 = tx + p2.x(); + int y2 = ty + p2.y(); + if (x1 > x2) { + kSwap(x1, x2); + if (bsOrientation(curBS) == BSHorizontal) kSwap(prevBS, nextBS); + } + if (y1 > y2) { + kSwap(y1, y2); + if (bsOrientation(curBS) == BSVertical) kSwap(prevBS, nextBS); + } + +// kdDebug(6040) << "segment(" << x1 << "," << y1 << ") - (" << x2 << "," << y2 << ")" << endl; +/* p->setPen(Qt::gray); + p->drawLine(x1,y1,x2,y2);*/ + switch (curBS) { + case RenderObject::BSLeft: + case RenderObject::BSRight: +/* p->setPen(QColor("#ffe4dd")); + p->drawLine( + x1 - (curBS == RenderObject::BSLeft ? ow : 0), + y1 - (prevBS == RenderObject::BSTop ? ow : 0), + x2 + (curBS == RenderObject::BSRight ? ow : 0), + y2 + (nextBS == RenderObject::BSBottom ? ow : 0) + );*/ + o->drawBorder(p, + x1 - (curBS == RenderObject::BSLeft ? ow : 0), + y1 - (prevBS == RenderObject::BSTop ? ow : 0), + x2 + (curBS == RenderObject::BSRight ? ow : 0), + y2 + (nextBS == RenderObject::BSBottom ? ow : 0), + curBS, oc, o->style()->color(), os, + prevBS == RenderObject::BSTop ? ow + : prevBS == RenderObject::BSBottom ? -ow : 0, + nextBS == RenderObject::BSTop ? -ow + : nextBS == RenderObject::BSBottom ? ow : 0, + true); + break; + case RenderObject::BSBottom: + case RenderObject::BSTop: +// kdDebug(6040) << "BSTop/BSBottom: prevBS " << prevBS << " curBS " << curBS << " nextBS " << nextBS << endl; + o->drawBorder(p, + x1 - (prevBS == RenderObject::BSLeft ? ow : 0), + y1 - (curBS == RenderObject::BSTop ? ow : 0), + x2 + (nextBS == RenderObject::BSRight ? ow : 0), + y2 + (curBS == RenderObject::BSBottom ? ow : 0), + curBS, oc, o->style()->color(), os, + prevBS == RenderObject::BSLeft ? ow + : prevBS == RenderObject::BSRight ? -ow : 0, + nextBS == RenderObject::BSLeft ? -ow + : nextBS == RenderObject::BSRight ? ow : 0, + true); + break; + } +} + +void RenderInline::paintOutlinePath(QPainter *p, int tx, int ty, const QPoint *begin, const QPoint *end, BorderSide bs, int direction, BorderSide endingBS) +{ + int ow = style()->outlineWidth(); + if (ow == 0 || m_isContinuation) // Continuations get painted by the original inline. + return; + + QPoint last = *begin; + BorderSide lastBS = bs; + Q_ASSERT(begin != end); + ++begin; + +// kdDebug(6040) << "last: " << last << endl; + + bs = newBorderSide(bs, direction, last, *begin); +// kdDebug(6040) << "newBorderSide: " << lastBS << " " << direction << "d " << last << " - " << *begin << " => " << bs << endl; + + for (const QPoint *it = begin; it != end; ++it) { + QPoint cur = *it; +// kdDebug(6040) << "cur: " << cur << endl; + BorderSide nextBS; + if (it + 1 != end) { + QPoint diff = cur - last; + direction = diff.x() + diff.y(); + nextBS = newBorderSide(bs, direction, cur, *(it + 1)); +// kdDebug(6040) << "newBorderSide*: " << bs << " " << direction << "d " << cur << " - " << *(it + 1) << " => " << nextBS << endl; + } else + nextBS = endingBS; + + Q_ASSERT(bsOrientation(bs) != bsOrientation(nextBS)); + paintOutlineSegment(this, p, tx, ty, last, cur, + lastBS, bs, nextBS); + lastBS = bs; + last = cur; + bs = nextBS; + } + +} + +void RenderInline::calcMinMaxWidth() +{ + KHTMLAssert( !minMaxKnown() ); + +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(RenderInline)::calcMinMaxWidth() this=" << this << endl; +#endif + + // Irrelevant, since some enclosing block will actually measure us and our children. + m_minWidth = 0; + m_maxWidth = 0; + + setMinMaxKnown(); +} + +short RenderInline::width() const +{ + // Return the width of the minimal left side and the maximal right side. + short leftSide = 0; + short rightSide = 0; + for (InlineRunBox* curr = firstLineBox(); curr; curr = curr->nextLineBox()) { + if (curr == firstLineBox() || curr->xPos() < leftSide) + leftSide = curr->xPos(); + if (curr == firstLineBox() || curr->xPos() + curr->width() > rightSide) + rightSide = curr->xPos() + curr->width(); + } + + return rightSide - leftSide; +} + +int RenderInline::height() const +{ + int h = 0; + if (firstLineBox()) + h = lastLineBox()->yPos() + lastLineBox()->height() - firstLineBox()->yPos(); + return h; +} + +int RenderInline::offsetLeft() const +{ + int x = RenderFlow::offsetLeft(); + if (firstLineBox()) + x += firstLineBox()->xPos(); + return x; +} + +int RenderInline::offsetTop() const +{ + int y = RenderFlow::offsetTop(); + if (firstLineBox()) + y += firstLineBox()->yPos(); + return y; +} + +const char *RenderInline::renderName() const +{ + if (isRelPositioned()) + return "RenderInline (relative positioned)"; + if (isAnonymous()) + return "RenderInline (anonymous)"; + return "RenderInline"; +} + +bool RenderInline::nodeAtPoint(NodeInfo& info, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction, bool inside) +{ +/* + if ( hitTestAction != HitTestSelfOnly ) { + for (RenderObject* child = lastChild(); child; child = child->previousSibling()) + if (!child->layer() && !child->isFloating() && child->nodeAtPoint(info, _x, _y, _tx, _ty, HitTestAll)) + inside = true; + } +*/ + // Check our line boxes if we're still not inside. + if (/*hitTestAction != HitTestChildrenOnly &&*/ !inside && style()->visibility() != HIDDEN) { + // See if we're inside one of our line boxes. + inside = hitTestLines(info, _x, _y, _tx, _ty, hitTestAction); + } + + if (inside && element()) { + if (info.innerNode() && info.innerNode()->renderer() && + !info.innerNode()->renderer()->isInline()) { + // Within the same layer, inlines are ALWAYS fully above blocks. Change inner node. + info.setInnerNode(element()); + + // Clear everything else. + info.setInnerNonSharedNode(0); + info.setURLElement(0); + } + + if (!info.innerNode()) + info.setInnerNode(element()); + + if(!info.innerNonSharedNode()) + info.setInnerNonSharedNode(element()); + } + + return inside; +} + +void RenderInline::caretPos(int offset, int flags, int &_x, int &_y, int &width, int &height) +{ + _x = -1; + + RenderBlock *cb = containingBlock(); + bool rtl = cb->style()->direction() == RTL; + bool outsideEnd = flags & CFOutsideEnd; + // I need to explain that: outsideEnd contains a meaningful value if + // and only if flags & CFOutside is set. If it is not, then randomly + // either the first or the last line box is returned. + // This doesn't matter because the only case this can happen is on an + // empty inline element, whose first and last line boxes are actually + // the same. + InlineFlowBox *line = !outsideEnd ^ rtl ? firstLineBox() : lastLineBox(); + + if (!line) { // umpf, handle "gracefully" + RenderFlow::caretPos(offset, flags, _x, _y, width, height); + return; + } + + _x = line->xPos(); + width = 1; // ### regard CFOverride + + // Place caret outside the border + if (flags & CFOutside) { + RenderStyle *s = element() && element()->parent() + && element()->parent()->renderer() + ? element()->parent()->renderer()->style() + : style(); + const QFontMetrics &fm = s->fontMetrics(); + _y = line->yPos() + line->baseline() - fm.ascent(); + height = fm.height(); + + if (!outsideEnd ^ rtl) { + _x -= line->marginBorderPaddingLeft(); + } else { + _x += line->width() + line->marginBorderPaddingRight(); + } + + } else { + const QFontMetrics &fm = style()->fontMetrics(); + _y = line->yPos() + line->baseline() - fm.ascent(); + height = fm.height(); + } + + int absx, absy; + if (cb && cb->absolutePosition(absx,absy)) { + //kdDebug(6040) << "absx=" << absx << " absy=" << absy << endl; + _x += absx; + _y += absy; + } else { + // we don't know our absolute position, and there is no point returning + // just a relative one + _x = _y = -1; + } +} + +inline int minXPos(const RenderInline *o) +{ + int retval=6666666; + if (!o->firstLineBox()) return 0; + for (InlineRunBox* curr = o->firstLineBox(); curr; curr = curr->nextLineBox()) + retval = kMin( retval, int( curr->m_x )); + return retval; +} + +int RenderInline::inlineXPos() const +{ + return minXPos(this); +} + +int RenderInline::inlineYPos() const +{ + return firstLineBox() ? firstLineBox()->yPos() : 0; +} + diff --git a/khtml/rendering/render_inline.h b/khtml/rendering/render_inline.h new file mode 100644 index 000000000..b87e53037 --- /dev/null +++ b/khtml/rendering/render_inline.h @@ -0,0 +1,94 @@ +/* + * This file is part of the render object implementation for KHTML. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 1999-2003 Antti Koivisto (koivisto@kde.org) + * (C) 2002-2003 Dirk Mueller (mueller@kde.org) + * (C) 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. + * + */ + +#ifndef RENDER_INLINE_H +#define RENDER_INLINE_H + +#include "render_flow.h" + +namespace khtml { + +class RenderInline : public RenderFlow +{ +public: + RenderInline(DOM::NodeImpl* node) : RenderFlow( node ), m_isContinuation( false ) {} + + virtual const char *renderName() const; + + virtual bool isRenderInline() const { return true; } + virtual bool isInlineFlow() const { return true; } + virtual bool childrenInline() const { return true; } + + virtual bool isInlineContinuation() const; + + virtual void addChildToFlow(RenderObject* newChild, RenderObject* beforeChild); + + void splitInlines(RenderBlock* fromBlock, RenderBlock* toBlock, RenderBlock* middleBlock, + RenderObject* beforeChild, RenderFlow* oldCont); + + void splitFlow(RenderObject* beforeChild, RenderBlock* newBlockBox, + RenderObject* newChild, RenderFlow* oldCont); + + virtual void setStyle(RenderStyle* _style); + virtual void attach(); + + virtual void layout() {} // Do nothing for layout() + + virtual void paint(PaintInfo&, int tx, int ty); + + virtual bool nodeAtPoint(NodeInfo& info, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction, bool inside); + + virtual void calcMinMaxWidth(); + + // overrides RenderObject + virtual bool requiresLayer() const { return isRelPositioned(); } + + virtual short width() const; + virtual int height() const; + + virtual int inlineXPos() const; + virtual int inlineYPos() const; + + // used to calculate offsetWidth/Height. Overridden by inlines (render_flow) to return + // the remaining width on a given line (and the height of a single line). + virtual int offsetLeft() const; + virtual int offsetTop() const; + + virtual void caretPos(int offset, int flags, int &_x, int &_y, int &width, int &height); + void paintOutlines(QPainter *p, int tx, int ty); + +protected: + static RenderInline* cloneInline(RenderFlow* src); + void paintOutlinePath(QPainter *p, int tx, int ty, const QPoint *begin, const QPoint *end, BorderSide startingBS, int initialDirection, BorderSide endingBS); + +private: + bool m_isContinuation : 1; // Whether or not we're a continuation of an inline. + +}; + +} // namespace + +#endif // RENDER_BLOCK_H + 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" diff --git a/khtml/rendering/render_layer.h b/khtml/rendering/render_layer.h new file mode 100644 index 000000000..c7587ae39 --- /dev/null +++ b/khtml/rendering/render_layer.h @@ -0,0 +1,342 @@ +/* + * Copyright (C) 2003 Apple Computer, Inc. + * + * 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. + */ + +#ifndef render_layer_h +#define render_layer_h + +#include <qcolor.h> +#include <qrect.h> +#include <assert.h> + +#include "render_object.h" + +class QScrollBar; +template <class T> class QPtrVector; + +namespace khtml { + class RenderStyle; + class RenderTable; + class CachedObject; + class RenderCanvas; + class RenderText; + class RenderFrameSet; + class RenderObject; + class RenderScrollMediator; + +class RenderScrollMediator: public QObject +{ + Q_OBJECT +public: + RenderScrollMediator(RenderLayer* layer) + :m_layer(layer) {} + +public slots: + void slotValueChanged(); + +private: + RenderLayer* m_layer; +}; + +// This class handles the auto-scrolling of layers with overflow: marquee. +class Marquee: public QObject +{ + Q_OBJECT + +public: + Marquee(RenderLayer* l); + + void timerEvent(QTimerEvent*); + + int speed() const { return m_speed; } + int marqueeSpeed() const; + EMarqueeDirection direction() const; + EMarqueeDirection reverseDirection() const { return static_cast<EMarqueeDirection>(-direction()); } + bool isHorizontal() const; + bool isUnfurlMarquee() const; + int unfurlPos() const { return m_unfurlPos; } + + EWhiteSpace whiteSpace() { return m_whiteSpace; } + + int computePosition(EMarqueeDirection dir, bool stopAtClientEdge); + + void setEnd(int end) { m_end = end; } + + void start(); + void suspend(); + void stop(); + + void updateMarqueeStyle(); + void updateMarqueePosition(); + +private: + RenderLayer* m_layer; + int m_currentLoop; + int m_totalLoops; + int m_timerId; + int m_start; + int m_end; + int m_speed; + int m_unfurlPos; + bool m_reset:1; + bool m_suspended:1; + bool m_stopped:1; + EWhiteSpace m_whiteSpace : 3; + EMarqueeDirection m_direction : 4; +}; + +class RenderLayer +{ +public: +#ifdef APPLE_CHANGES + static QScrollBar* gScrollBar; +#endif + + RenderLayer(RenderObject* object); + ~RenderLayer(); + + RenderObject* renderer() const { return m_object; } + RenderLayer *parent() const { return m_parent; } + RenderLayer *previousSibling() const { return m_previous; } + RenderLayer *nextSibling() const { return m_next; } + + RenderLayer *firstChild() const { return m_first; } + RenderLayer *lastChild() const { return m_last; } + + void addChild(RenderLayer *newChild, RenderLayer* beforeChild = 0); + RenderLayer* removeChild(RenderLayer *oldChild); + + void removeOnlyThisLayer(); + void insertOnlyThisLayer(); + + void styleChanged(); + + Marquee* marquee() const { return m_marquee; } + void suspendMarquees(); + + bool isOverflowOnly() const { return m_isOverflowOnly; } + +#ifdef APPLE_CHANGES + bool isTransparent(); + RenderLayer* transparentAncestor(); +#endif + + RenderLayer* root() { + RenderLayer* curr = this; + while (curr->parent()) curr = curr->parent(); + return curr; + } + + int xPos() const { return m_x; } + int yPos() const { return m_y; } + + short width() const; + int height() const; + + short scrollWidth() const { return m_scrollWidth; } + int scrollHeight() const { return m_scrollHeight; } + + void resize( int w, int h ) { + m_scrollWidth = w; m_scrollHeight = h; + } + + void setPos( int xPos, int yPos ) { + m_x = xPos; + m_y = yPos; + } + + // Scrolling methods for layers that can scroll their overflow. + void scrollOffset(int& x, int& y); + void subtractScrollOffset(int& x, int& y); + void checkInlineRelOffset(const RenderObject* o, int& x, int& y); + short scrollXOffset() { return m_scrollX; } + int scrollYOffset() { return m_scrollY; } + void scrollToOffset(int x, int y, bool updateScrollbars = true, bool repaint = true); + void scrollToXOffset(int x) { scrollToOffset(x, m_scrollY); } + void scrollToYOffset(int y) { scrollToOffset(m_scrollX, y); } + void showScrollbar(Qt::Orientation, bool); + QScrollBar* horizontalScrollbar() { return m_hBar; } + QScrollBar* verticalScrollbar() { return m_vBar; } + int verticalScrollbarWidth(); + int horizontalScrollbarHeight(); + void positionScrollbars(const QRect &damageRect); + void paintScrollbars(RenderObject::PaintInfo& pI); + void checkScrollbarsAfterLayout(); + void slotValueChanged(int); + void repaint(Priority p=NormalPriority, bool markForRepaint = false); + void updateScrollPositionFromScrollbars(); + + void updateLayerPosition(); + void updateLayerPositions( RenderLayer* rootLayer, bool doFullRepaint = false, bool checkForRepaint = false); + + // Get the enclosing stacking context for this layer. A stacking context is a layer + // that has a non-auto z-index. + RenderLayer* stackingContext() const; + bool isStackingContext() const { return !hasAutoZIndex() || renderer()->isCanvas(); } + + void dirtyZOrderLists(); + void updateZOrderLists(); + QPtrVector<RenderLayer>* posZOrderList() const { return m_posZOrderList; } + QPtrVector<RenderLayer>* negZOrderList() const { return m_negZOrderList; } + + void dirtyOverflowList(); + void updateOverflowList(); + QValueList<RenderLayer*>* overflowList() const { return m_overflowList; } + + void setHasOverlaidWidgets(bool b=true) { m_hasOverlaidWidgets = b; } + bool hasOverlaidWidgets() const { return m_hasOverlaidWidgets; } + QRegion getMask() const { return m_region; } + QRegion paintedRegion(RenderLayer* rootLayer); + void updateWidgetMasks(RenderLayer* rootLayer); + + // Gets the nearest enclosing positioned ancestor layer (also includes + // the <html> layer and the root layer). + RenderLayer* enclosingPositionedAncestor() const; + + void convertToLayerCoords(const RenderLayer* ancestorLayer, int& x, int& y) const; + + bool hasAutoZIndex() const { return renderer()->style()->hasAutoZIndex(); } + int zIndex() const { return renderer()->style()->zIndex(); } + + // The two main functions that use the layer system. The paint method + // paints the layers that intersect the damage rect from back to + // front. The nodeAtPoint method looks for mouse events by walking + // layers that intersect the point from front to back. + KDE_EXPORT void paint(QPainter *p, const QRect& damageRect, bool selectionOnly=false); + bool nodeAtPoint(RenderObject::NodeInfo& info, int x, int y); + + // This method figures out our layerBounds in coordinates relative to + // |rootLayer}. It also computes our background and foreground clip rects + // for painting/event handling. + void calculateRects(const RenderLayer* rootLayer, const QRect& paintDirtyRect, QRect& layerBounds, + QRect& backgroundRect, QRect& foregroundRect); + void calculateClipRects(const RenderLayer* rootLayer, QRect& overflowClipRect, + QRect& posClipRect, QRect& fixedClipRect); + + bool intersectsDamageRect(const QRect& layerBounds, const QRect& damageRect) const; + bool containsPoint(int x, int y, const QRect& damageRect) const; + + void updateHoverActiveState(RenderObject::NodeInfo& info); + + void detach(RenderArena* renderArena); + +#ifdef ENABLE_DUMP + KDE_EXPORT void dump(QTextStream &stream, const QString &ind = QString::null); +#endif + + // Overloaded new operator. Derived classes must override operator new + // in order to allocate out of the RenderArena. + void* operator new(size_t sz, RenderArena* renderArena) throw(); + + // Overridden to prevent the normal delete from being called. + void operator delete(void* ptr, size_t sz); + +private: + // The normal operator new is disallowed on all render objects. + void* operator new(size_t sz) throw(); + +private: + void setNextSibling(RenderLayer* next) { m_next = next; } + void setPreviousSibling(RenderLayer* prev) { m_previous = prev; } + void setParent(RenderLayer* parent) { m_parent = parent; } + void setFirstChild(RenderLayer* first) { m_first = first; } + void setLastChild(RenderLayer* last) { m_last = last; } + + void collectLayers(QPtrVector<RenderLayer>*&, QPtrVector<RenderLayer>*&); + + KDE_EXPORT void paintLayer(RenderLayer* rootLayer, QPainter *p, const QRect& paintDirtyRect, bool selectionOnly=false); + RenderLayer* nodeAtPointForLayer(RenderLayer* rootLayer, RenderObject::NodeInfo& info, + int x, int y, const QRect& hitTestRect); + bool shouldBeOverflowOnly() const; + +protected: + RenderObject* m_object; + + RenderLayer* m_parent; + RenderLayer* m_previous; + RenderLayer* m_next; + + RenderLayer* m_first; + RenderLayer* m_last; + + // Our (x,y) coordinates are in our parent layer's coordinate space. + short m_x; + int m_y; + + // Our scroll offsets if the view is scrolled. + short m_scrollX; + int m_scrollY; + + // The width/height of our scrolled area. + short m_scrollWidth; + int m_scrollHeight; + + // For layers with overflow, we have a pair of scrollbars. + QScrollBar* m_hBar; + QScrollBar* m_vBar; + RenderScrollMediator* m_scrollMediator; + + // For layers that establish stacking contexts, m_posZOrderList holds a sorted list of all the + // descendant layers within the stacking context that have z-indices of 0 or greater + // (auto will count as 0). m_negZOrderList holds descendants within our stacking context with negative + // z-indices. + QPtrVector<RenderLayer>* m_posZOrderList; + QPtrVector<RenderLayer>* m_negZOrderList; + + // This list contains our overflow child layers. + QValueList<RenderLayer*>* m_overflowList; + + bool m_zOrderListsDirty: 1; + bool m_overflowListDirty: 1; + bool m_isOverflowOnly: 1; + bool m_markedForRepaint: 1; + bool m_hasOverlaidWidgets: 1; + + QRect m_visibleRect; + + QRegion m_region; // used by overlaid (non z-order aware) widgets + + Marquee* m_marquee; // Used by layers with overflow:marquee +}; + +} // namespace +#endif diff --git a/khtml/rendering/render_line.cpp b/khtml/rendering/render_line.cpp new file mode 100644 index 000000000..18a5f5101 --- /dev/null +++ b/khtml/rendering/render_line.cpp @@ -0,0 +1,996 @@ +/** +* This file is part of the html renderer for KDE. + * + * Copyright (C) 2003-2006 Apple Computer, Inc. + * (C) 2006 Germain Garand (germain@ebooksfrance.org) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ +// ------------------------------------------------------------------------- + +#include <kdebug.h> +#include <assert.h> +#include <qpainter.h> +#include <kglobal.h> + +#include "rendering/render_flow.h" +#include "rendering/render_text.h" +#include "rendering/render_table.h" +#include "rendering/render_inline.h" +#include "rendering/render_block.h" +#include "rendering/render_arena.h" +#include "rendering/render_line.h" +#include "xml/dom_nodeimpl.h" +#include "xml/dom_docimpl.h" +#include "html/html_formimpl.h" +#include "misc/htmltags.h" +#include "khtmlview.h" + +using namespace DOM; +using namespace khtml; + +#ifndef NDEBUG +static bool inInlineBoxDetach; +#endif + +class khtml::EllipsisBox : public InlineBox +{ +public: + EllipsisBox(RenderObject* obj, const DOM::DOMString& ellipsisStr, InlineFlowBox* p, + int w, int y, int h, int b, bool firstLine, InlineBox* markupBox) + :InlineBox(obj), m_str(ellipsisStr) { + m_parent = p; + m_width = w; + m_y = y; + m_height = h; + m_baseline = b; + m_firstLine = firstLine; + m_constructed = true; + m_markupBox = markupBox; + } + + void paint(RenderObject::PaintInfo& i, int _tx, int _ty); + bool nodeAtPoint(RenderObject::NodeInfo& info, int _x, int _y, int _tx, int _ty); + +private: + DOM::DOMString m_str; + InlineBox* m_markupBox; +}; + +void InlineBox::detach(RenderArena* renderArena) +{ + if (m_parent) + m_parent->removeFromLine(this); +#ifndef NDEBUG + inInlineBoxDetach = true; +#endif + delete this; +#ifndef NDEBUG + inInlineBoxDetach = false; +#endif + + // Recover the size left there for us by operator delete and free the memory. + renderArena->free(*(size_t *)this, this); +} + +void* InlineBox::operator new(size_t sz, RenderArena* renderArena) throw() +{ + return renderArena->allocate(sz); +} + +void InlineBox::operator delete(void* ptr, size_t sz) +{ + assert(inInlineBoxDetach); + + // Stash size where detach can find it. + *(size_t *)ptr = sz; +} + +static bool needsOutlinePhaseRepaint(RenderObject* o, RenderObject::PaintInfo& i, int tx, int ty) { + if (o->style()->outlineWidth() <= 0) + return false; + QRect r(tx+o->xPos(),ty+o->yPos(),o->width(),o->height()); + if (r.intersects(i.r)) + return false; + r.addCoords(-o->style()->outlineSize(), + -o->style()->outlineSize(), + o->style()->outlineSize(), + o->style()->outlineSize()); + if (!r.intersects(i.r)) + return false; + return true; +} + +void InlineBox::paint(RenderObject::PaintInfo& i, int tx, int ty) +{ + if ( i.phase == PaintActionOutline && !needsOutlinePhaseRepaint(object(), i, tx, ty) ) + return; + + // Paint all phases of replaced elements atomically, as though the replaced element established its + // own stacking context. (See Appendix E.2, section 6.4 on inline block/table elements in the CSS2.1 + // specification.) + bool paintSelectionOnly = i.phase == PaintActionSelection; + RenderObject::PaintInfo info(i.p, i.r, paintSelectionOnly ? i.phase : PaintActionElementBackground); + object()->paint(info, tx, ty); + if (!paintSelectionOnly) { + info.phase = PaintActionChildBackgrounds; + object()->paint(info, tx, ty); + info.phase = PaintActionFloat; + object()->paint(info, tx, ty); + info.phase = PaintActionForeground; + object()->paint(info, tx, ty); + info.phase = PaintActionOutline; + object()->paint(info, tx, ty); + } +} + +bool InlineBox::nodeAtPoint(RenderObject::NodeInfo& i, int x, int y, int tx, int ty) +{ + // Hit test all phases of replaced elements atomically, as though the replaced element established its + // own stacking context. (See Appendix E.2, section 6.4 on inline block/table elements in the CSS2.1 + // specification.) + bool inside = false; + return object()->nodeAtPoint(i, x, y, tx, ty, HitTestAll, inside); // ### port hitTest +} + +RootInlineBox* InlineBox::root() +{ + if (m_parent) + return m_parent->root(); + return static_cast<RootInlineBox*>(this); +} + +InlineFlowBox::~InlineFlowBox() +{ + /* If we're destroyed, set the children free, and break their links */ + while (m_firstChild) + removeFromLine(m_firstChild); +} + +void InlineFlowBox::removeFromLine(InlineBox *child) +{ + if (child == m_firstChild) { + m_firstChild = child->nextOnLine(); + } + if (child == m_lastChild) { + m_lastChild = child->prevOnLine(); + } + if (child->nextOnLine()) { + child->nextOnLine()->m_prev = child->prevOnLine(); + } + if (child->prevOnLine()) { + child->prevOnLine()->m_next = child->nextOnLine(); + } + + child->setParent(0); +} + +bool InlineBox::canAccommodateEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth) +{ + // Non-replaced elements can always accommodate an ellipsis. + if (!m_object || !m_object->isReplaced()) + return true; + + QRect boxRect(m_x, 0, m_width, 10); + QRect ellipsisRect(ltr ? blockEdge - ellipsisWidth : blockEdge, 0, ellipsisWidth, 10); + return !(boxRect.intersects(ellipsisRect)); +} + +int InlineBox::placeEllipsisBox(bool /*ltr*/, int /*blockEdge*/, int /*ellipsisWidth*/, bool&) +{ + // Use -1 to mean "we didn't set the position." + return -1; +} + +int InlineFlowBox::marginLeft() const +{ + if (!includeLeftEdge()) + return 0; + + RenderStyle* cstyle = object()->style(); + Length margin = cstyle->marginLeft(); + if (!margin.isVariable()) + return (margin.isFixed() ? margin.value() : object()->marginLeft()); + return 0; +} + +int InlineFlowBox::marginRight() const +{ + if (!includeRightEdge()) + return 0; + + RenderStyle* cstyle = object()->style(); + Length margin = cstyle->marginRight(); + if (!margin.isVariable()) + return (margin.isFixed() ? margin.value() : object()->marginRight()); + return 0; +} + +int InlineFlowBox::marginBorderPaddingLeft() const +{ + return marginLeft() + borderLeft() + paddingLeft(); +} + +int InlineFlowBox::marginBorderPaddingRight() const +{ + return marginRight() + borderRight() + paddingRight(); +} + +int InlineFlowBox::getFlowSpacingWidth() const +{ + int totWidth = marginBorderPaddingLeft() + marginBorderPaddingRight(); + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (curr->isInlineFlowBox()) + totWidth += static_cast<InlineFlowBox*>(curr)->getFlowSpacingWidth(); + } + return totWidth; +} + +bool InlineFlowBox::nextOnLineExists() +{ + if (!parent()) + return false; + + if (nextOnLine()) + return true; + + return parent()->nextOnLineExists(); +} + +bool InlineFlowBox::prevOnLineExists() +{ + if (!parent()) + return false; + + if (prevOnLine()) + return true; + + return parent()->prevOnLineExists(); +} + +bool InlineFlowBox::onEndChain(RenderObject* endObject) +{ + if (!endObject) + return false; + + if (endObject == object()) + return true; + + RenderObject* curr = endObject; + RenderObject* parent = curr->parent(); + while (parent && !parent->isRenderBlock() || parent == object()) { + if (parent->lastChild() != curr) + return false; + + curr = parent; + parent = curr->parent(); + } + + return true; +} + +void InlineFlowBox::determineSpacingForFlowBoxes(bool lastLine, RenderObject* endObject) +{ + // All boxes start off open. They will not apply any margins/border/padding on + // any side. + bool includeLeftEdge = false; + bool includeRightEdge = false; + + RenderFlow* flow = static_cast<RenderFlow*>(object()); + + if (!flow->firstChild()) + includeLeftEdge = includeRightEdge = true; // Empty inlines never split across lines. + else if (parent()) { // The root inline box never has borders/margins/padding. + bool ltr = flow->style()->direction() == LTR; + + // Check to see if all initial lines are unconstructed. If so, then + // we know the inline began on this line. + if (!flow->firstLineBox()->isConstructed()) { + if (ltr && flow->firstLineBox() == this) + includeLeftEdge = true; + else if (!ltr && flow->lastLineBox() == this) + includeRightEdge = true; + } + + // In order to determine if the inline ends on this line, we check three things: + // (1) If we are the last line and we don't have a continuation(), then we can + // close up. + // (2) If the last line box for the flow has an object following it on the line (ltr, + // reverse for rtl), then the inline has closed. + // (3) The line may end on the inline. If we are the last child (climbing up + // the end object's chain), then we just closed as well. + if (!flow->lastLineBox()->isConstructed()) { + if (ltr) { + if (!nextLineBox() && + ((lastLine && !object()->continuation()) || nextOnLineExists() + || onEndChain(endObject))) + includeRightEdge = true; + } + else { + if ((!prevLineBox() || !prevLineBox()->isConstructed()) && + ((lastLine && !object()->continuation()) || + prevOnLineExists() || onEndChain(endObject))) + includeLeftEdge = true; + } + + } + } + + setEdges(includeLeftEdge, includeRightEdge); + + // Recur into our children. + for (InlineBox* currChild = firstChild(); currChild; currChild = currChild->nextOnLine()) { + if (currChild->isInlineFlowBox()) { + InlineFlowBox* currFlow = static_cast<InlineFlowBox*>(currChild); + currFlow->determineSpacingForFlowBoxes(lastLine, endObject); + } + } +} + +int InlineFlowBox::placeBoxesHorizontally(int x) +{ + // Set our x position. + setXPos(x); + + int startX = x; + x += borderLeft() + paddingLeft(); + + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (curr->object()->isText()) { + InlineTextBox* text = static_cast<InlineTextBox*>(curr); + text->setXPos(x); + x += curr->width(); + } + else { + if (curr->object()->isPositioned()) { + if (curr->object()->parent()->style()->direction() == LTR) + curr->setXPos(x); + else { + // Our offset that we cache needs to be from the edge of the right border box and + // not the left border box. We have to subtract |x| from the width of the block + // (which can be obtained by walking up to the root line box). + InlineBox* root = this; + while (!root->isRootInlineBox()) + root = root->parent(); + curr->setXPos(root->object()->width()-x); + } + continue; // The positioned object has no effect on the width. + } + if (curr->object()->isInlineFlow()) { + InlineFlowBox* flow = static_cast<InlineFlowBox*>(curr); + x += flow->marginLeft(); + x = flow->placeBoxesHorizontally(x); + x += flow->marginRight(); + } + else { + x += curr->object()->marginLeft(); + curr->setXPos(x); + x += curr->width() + curr->object()->marginRight(); + } + } + } + + x += borderRight() + paddingRight(); + setWidth(x-startX); + return x; +} + +void InlineFlowBox::verticallyAlignBoxes(int& heightOfBlock) +{ + int maxPositionTop = 0; + int maxPositionBottom = 0; + int maxAscent = 0; + int maxDescent = 0; + + // Figure out if we're in strict mode. + RenderObject* curr = object(); + while (curr && !curr->element()) + curr = curr->container(); + bool strictMode = (curr && curr->element()->getDocument()->inStrictMode()); + + computeLogicalBoxHeights(maxPositionTop, maxPositionBottom, maxAscent, maxDescent, strictMode); + + if (maxAscent + maxDescent < kMax(maxPositionTop, maxPositionBottom)) + adjustMaxAscentAndDescent(maxAscent, maxDescent, maxPositionTop, maxPositionBottom); + + int maxHeight = maxAscent + maxDescent; + int topPosition = heightOfBlock; + int bottomPosition = heightOfBlock; + placeBoxesVertically(heightOfBlock, maxHeight, maxAscent, strictMode, topPosition, bottomPosition); + + setOverflowPositions(topPosition, bottomPosition); + + // Shrink boxes with no text children in quirks and almost strict mode. + if (!strictMode) + shrinkBoxesWithNoTextChildren(topPosition, bottomPosition); + + heightOfBlock += maxHeight; +} + +void InlineFlowBox::adjustMaxAscentAndDescent(int& maxAscent, int& maxDescent, + int maxPositionTop, int maxPositionBottom) +{ + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + // The computed lineheight needs to be extended for the + // positioned elements + // see khtmltests/rendering/html_align.html + + if (curr->object()->isPositioned()) + continue; // Positioned placeholders don't affect calculations. + if (curr->yPos() == PositionTop || curr->yPos() == PositionBottom) { + if (curr->yPos() == PositionTop) { + if (maxAscent + maxDescent < curr->height()) + maxDescent = curr->height() - maxAscent; + } + else { + if (maxAscent + maxDescent < curr->height()) + maxAscent = curr->height() - maxDescent; + } + + if ( maxAscent + maxDescent >= kMax( maxPositionTop, maxPositionBottom ) ) + break; + } + + if (curr->isInlineFlowBox()) + static_cast<InlineFlowBox*>(curr)->adjustMaxAscentAndDescent(maxAscent, maxDescent, maxPositionTop, maxPositionBottom); + } +} + +void InlineFlowBox::computeLogicalBoxHeights(int& maxPositionTop, int& maxPositionBottom, + int& maxAscent, int& maxDescent, bool strictMode) +{ + if (isRootInlineBox()) { + // Examine our root box. + setHeight(object()->lineHeight(m_firstLine)); + bool isTableCell = object()->isTableCell(); + if (isTableCell) { + RenderTableCell* tableCell = static_cast<RenderTableCell*>(object()); + setBaseline(tableCell->RenderBlock::baselinePosition(m_firstLine)); + } + else + setBaseline(object()->baselinePosition(m_firstLine)); + if (hasTextChildren() || strictMode) { + int ascent = baseline(); + int descent = height() - ascent; + if (maxAscent < ascent) + maxAscent = ascent; + if (maxDescent < descent) + maxDescent = descent; + } + } + + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (curr->object()->isPositioned()) + continue; // Positioned placeholders don't affect calculations. + + curr->setHeight(curr->object()->lineHeight(m_firstLine)); + curr->setBaseline(curr->object()->baselinePosition(m_firstLine)); + curr->setYPos(curr->object()->verticalPositionHint(m_firstLine)); + if (curr->yPos() == PositionTop) { + if (maxPositionTop < curr->height()) + maxPositionTop = curr->height(); + } + else if (curr->yPos() == PositionBottom) { + if (maxPositionBottom < curr->height()) + maxPositionBottom = curr->height(); + } + else if (curr->hasTextChildren() || strictMode) { + int ascent = curr->baseline() - curr->yPos(); + int descent = curr->height() - ascent; + if (maxAscent < ascent) + maxAscent = ascent; + if (maxDescent < descent) + maxDescent = descent; + } + + if (curr->isInlineFlowBox()) + static_cast<InlineFlowBox*>(curr)->computeLogicalBoxHeights(maxPositionTop, maxPositionBottom, maxAscent, maxDescent, strictMode); + } +} + +void InlineFlowBox::placeBoxesVertically(int y, int maxHeight, int maxAscent, bool strictMode, + int& topPosition, int& bottomPosition) +{ + if (isRootInlineBox()) { + setYPos(y + maxAscent - baseline());// Place our root box. + // CSS2: 10.8.1 - line-height on the block level element specifies the *minimum* + // height of the generated line box + if (hasTextChildren() && maxHeight < object()->lineHeight(m_firstLine)) + maxHeight = object()->lineHeight(m_firstLine); + } + + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (curr->object()->isPositioned()) + continue; // Positioned placeholders don't affect calculations. + + // Adjust boxes to use their real box y/height and not the logical height (as dictated by + // line-height). + if (curr->isInlineFlowBox()) + static_cast<InlineFlowBox*>(curr)->placeBoxesVertically(y, maxHeight, maxAscent, strictMode, + topPosition, bottomPosition); + + bool childAffectsTopBottomPos = true; + + if (curr->yPos() == PositionTop) + curr->setYPos(y); + else if (curr->yPos() == PositionBottom) + curr->setYPos(y + maxHeight - curr->height()); + else { + if (!strictMode && !curr->hasTextDescendant()) + childAffectsTopBottomPos = false; + curr->setYPos(curr->yPos() + y + maxAscent - curr->baseline()); + } + int newY = curr->yPos(); + int newHeight = curr->height(); + int newBaseline = curr->baseline(); + int overflowTop = 0; + int overflowBottom = 0; + if (curr->isInlineTextBox() || curr->isInlineFlowBox()) { + const QFontMetrics &fm = curr->object()->fontMetrics( m_firstLine ); +#ifdef APPLE_CHANGES + newBaseline = fm.ascent(); + newY += curr->baseline() - newBaseline; + newHeight = newBaseline+fm.descent(); +#else + // only adjust if the leading delta is superior to the font's natural leading + if ( kAbs(fm.ascent() - curr->baseline()) > fm.leading()/2 ) { + int ascent = fm.ascent()+fm.leading()/2; + newBaseline = ascent; + newY += curr->baseline() - newBaseline; + newHeight = fm.lineSpacing(); + } +#endif + for (ShadowData* shadow = curr->object()->style()->textShadow(); shadow; shadow = shadow->next) { + overflowTop = kMin(overflowTop, shadow->y - shadow->blur); + overflowBottom = kMax(overflowBottom, shadow->y + shadow->blur); + } + if (curr->isInlineFlowBox()) { + newHeight += curr->object()->borderTop() + curr->object()->paddingTop() + + curr->object()->borderBottom() + curr->object()->paddingBottom(); + newY -= curr->object()->borderTop() + curr->object()->paddingTop(); + newBaseline += curr->object()->borderTop() + curr->object()->paddingTop(); + } + } else { + newY += curr->object()->marginTop(); + newHeight = curr->height() - (curr->object()->marginTop() + curr->object()->marginBottom()); + overflowTop = curr->object()->overflowTop(); + overflowBottom = curr->object()->overflowHeight() - newHeight; + } + curr->setYPos(newY); + curr->setHeight(newHeight); + curr->setBaseline(newBaseline); + + if (childAffectsTopBottomPos) { + topPosition = kMin(topPosition, newY + overflowTop); + bottomPosition = kMax(bottomPosition, newY + newHeight + overflowBottom); + } + } + + if (isRootInlineBox()) { + const QFontMetrics &fm = object()->fontMetrics( m_firstLine ); +#ifdef APPLE_CHANGES + setHeight(fm.ascent()+fm.descent()); + setYPos(yPos() + baseline() - fm.ascent()); + setBaseline(fm.ascent()); +#else + if ( kAbs(fm.ascent() - baseline()) > fm.leading()/2 ) { + int ascent = fm.ascent()+fm.leading()/2; + setHeight(fm.lineSpacing()); + setYPos(yPos() + baseline() - ascent); + setBaseline(ascent); + } +#endif + if (hasTextDescendant() || strictMode) { + if (yPos() < topPosition) + topPosition = yPos(); + if (yPos() + height() > bottomPosition) + bottomPosition = yPos() + height(); + } + } +} + +void InlineFlowBox::shrinkBoxesWithNoTextChildren(int topPos, int bottomPos) +{ + // First shrink our kids. + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (curr->object()->isPositioned()) + continue; // Positioned placeholders don't affect calculations. + + if (curr->isInlineFlowBox()) + static_cast<InlineFlowBox*>(curr)->shrinkBoxesWithNoTextChildren(topPos, bottomPos); + } + + // See if we have text children. If not, then we need to shrink ourselves to fit on the line. + if (!hasTextDescendant()) { + if (yPos() < topPos) + setYPos(topPos); + if (yPos() + height() > bottomPos) + setHeight(bottomPos - yPos()); + if (baseline() > height()) + setBaseline(height()); + } +} + +bool InlineFlowBox::nodeAtPoint(RenderObject::NodeInfo& i, int x, int y, int tx, int ty) +{ + // Check children first. + for (InlineBox* curr = lastChild(); curr; curr = curr->prevOnLine()) { + if (!curr->object()->layer() && curr->nodeAtPoint(i, x, y, tx, ty)) { + object()->setInnerNode(i); + return true; + } + } + + // Now check ourselves. + QRect rect(tx + m_x, ty + m_y, m_width, m_height); + if (object()->style()->visibility() == VISIBLE && rect.contains(x, y)) { + object()->setInnerNode(i); + return true; + } + + return false; +} + + +void InlineFlowBox::paint(RenderObject::PaintInfo& i, int tx, int ty) +{ + bool intersectsDamageRect = true; + int xPos = tx + m_x - object()->maximalOutlineSize(i.phase); + int w = width() + 2 * object()->maximalOutlineSize(i.phase); + if ((xPos >= i.r.x() + i.r.width()) || (xPos + w <= i.r.x())) + intersectsDamageRect = false; + + if (intersectsDamageRect) { + if (i.phase == PaintActionOutline) { + // Add ourselves to the paint info struct's list of inlines that need to paint their + // outlines. + if (object()->style()->visibility() == VISIBLE && object()->style()->outlineWidth() > 0 && + !object()->isInlineContinuation() && !isRootInlineBox()) { + if (!i.outlineObjects) + i.outlineObjects = new QValueList<RenderFlow*>; + i.outlineObjects->append(static_cast<RenderFlow*>(object())); + } + } + else { + // 1. Paint our background and border. + paintBackgroundAndBorder(i, tx, ty); + + // 2. Paint our underline and overline. + paintDecorations(i, tx, ty, false); + } + } + + // 3. Paint our children. + for (InlineBox* curr = firstChild(); curr; curr = curr->nextOnLine()) { + if (!curr->object()->layer()) + curr->paint(i, tx, ty); + } + + // 4. Paint our strike-through + if (intersectsDamageRect && i.phase != PaintActionOutline) + paintDecorations(i, tx, ty, true); +} + + +void InlineFlowBox::paintBackgrounds(QPainter* p, const QColor& c, const BackgroundLayer* bgLayer, + int my, int mh, int _tx, int _ty, int w, int h) +{ + if (!bgLayer) + return; + paintBackgrounds(p, c, bgLayer->next(), my, mh, _tx, _ty, w, h); + paintBackground(p, c, bgLayer, my, mh, _tx, _ty, w, h); +} + +void InlineFlowBox::paintBackground(QPainter* p, const QColor& c, const BackgroundLayer* bgLayer, + int my, int mh, int _tx, int _ty, int w, int h) +{ + CachedImage* bg = bgLayer->backgroundImage(); + bool hasBackgroundImage = bg && (bg->pixmap_size() == bg->valid_rect().size()) && + !bg->isTransparent() && !bg->isErrorImage(); + if (!hasBackgroundImage || (!prevLineBox() && !nextLineBox()) || !parent()) + object()->paintBackgroundExtended(p, c, bgLayer, my, mh, _tx, _ty, w, h, borderLeft(), borderRight(), paddingLeft(), paddingRight()); + else { + // We have a background image that spans multiple lines. + // We need to adjust _tx and _ty by the width of all previous lines. + // Think of background painting on inlines as though you had one long line, a single continuous + // strip. Even though that strip has been broken up across multiple lines, you still paint it + // as though you had one single line. This means each line has to pick up the background where + // the previous line left off. + // FIXME: What the heck do we do with RTL here? The math we're using is obviously not right, + // but it isn't even clear how this should work at all. + int xOffsetOnLine = 0; + for (InlineRunBox* curr = prevLineBox(); curr; curr = curr->prevLineBox()) + xOffsetOnLine += curr->width(); + int startX = _tx - xOffsetOnLine; + int totalWidth = xOffsetOnLine; + for (InlineRunBox* curr = this; curr; curr = curr->nextLineBox()) + totalWidth += curr->width(); + p->save(); + p->setClipRect(QRect(_tx, _ty, width(), height()), QPainter::CoordPainter); + object()->paintBackgroundExtended(p, c, bgLayer, my, mh, startX, _ty, + totalWidth, h, borderLeft(), borderRight(), paddingLeft(), paddingRight()); + p->restore(); + } +} + +void InlineFlowBox::paintBackgroundAndBorder(RenderObject::PaintInfo& pI, int _tx, int _ty) +{ + if (object()->style()->visibility() != VISIBLE || pI.phase != PaintActionForeground) + return; + + // Move x/y to our coordinates. + _tx += m_x; + _ty += m_y; + + int w = width(); + int h = height(); + + int my = kMax(_ty, pI.r.y()); + int mh; + if (_ty<pI.r.y()) + mh= kMax(0,h-(pI.r.y()-_ty)); + else + mh = kMin(pI.r.height(),h); + + // You can use p::first-line to specify a background. If so, the root line boxes for + // a line may actually have to paint a background. + RenderStyle* styleToUse = object()->style(m_firstLine); + if ((!parent() && m_firstLine && styleToUse != object()->style()) || + (parent() && object()->shouldPaintBackgroundOrBorder())) { + QColor c = styleToUse->backgroundColor(); + paintBackgrounds(pI.p, c, styleToUse->backgroundLayers(), my, mh, _tx, _ty, w, h); + + // :first-line cannot be used to put borders on a line. Always paint borders with our + // non-first-line style. + if (parent() && object()->style()->hasBorder()) + object()->paintBorder(pI.p, _tx, _ty, w, h, object()->style(), includeLeftEdge(), includeRightEdge()); + } +} + +static bool shouldDrawDecoration(RenderObject* obj) +{ + bool shouldDraw = false; + for (RenderObject* curr = obj->firstChild(); + curr; curr = curr->nextSibling()) { + if (curr->isInlineFlow()) { + shouldDraw = true; + break; + } + else if (curr->isText() && !curr->isBR() && (curr->style()->preserveWS() || + !curr->element() || !curr->element()->containsOnlyWhitespace())) { + shouldDraw = true; + break; + } + } + return shouldDraw; +} + +void InlineFlowBox::paintDecorations(RenderObject::PaintInfo& pI, int _tx, int _ty, bool paintedChildren) +{ + // Now paint our text decorations. We only do this if we aren't in quirks mode (i.e., in + // almost-strict mode or strict mode). + if (object()->style()->htmlHacks() || object()->style()->visibility() != VISIBLE) + return; + + _tx += m_x; + _ty += m_y; + RenderStyle* styleToUse = object()->style(m_firstLine); + int deco = parent() ? styleToUse->textDecoration() : styleToUse->textDecorationsInEffect(); + if (deco != TDNONE && + ((!paintedChildren && ((deco & UNDERLINE) || (deco & OVERLINE))) || (paintedChildren && (deco & LINE_THROUGH))) && + shouldDrawDecoration(object())) { + // We must have child boxes and have decorations defined. + _tx += borderLeft() + paddingLeft(); + int w = m_width - (borderLeft() + paddingLeft() + borderRight() + paddingRight()); + if ( !w ) + return; + const QFontMetrics &fm = object()->fontMetrics( m_firstLine ); + // thick lines on small fonts look ugly + int thickness = fm.height() > 20 ? fm.lineWidth() : 1; + QColor underline, overline, linethrough; + underline = overline = linethrough = styleToUse->color(); + if (!parent()) + object()->getTextDecorationColors(deco, underline, overline, linethrough); + + if (styleToUse->font() != pI.p->font()) + pI.p->setFont(styleToUse->font()); + + if (deco & UNDERLINE && !paintedChildren) { + int underlineOffset = ( fm.height() + m_baseline ) / 2; + if (underlineOffset <= m_baseline) underlineOffset = m_baseline+1; + + pI.p->fillRect(_tx, _ty + underlineOffset, w, thickness, underline ); + } + if (deco & OVERLINE && !paintedChildren) { + pI.p->fillRect(_tx, _ty, w, thickness, overline ); + } + if (deco & LINE_THROUGH && paintedChildren) { + pI.p->fillRect(_tx, _ty + 2*m_baseline/3, w, thickness, linethrough ); + } + } +} + +bool InlineFlowBox::canAccommodateEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth) +{ + for (InlineBox *box = firstChild(); box; box = box->nextOnLine()) { + if (!box->canAccommodateEllipsisBox(ltr, blockEdge, ellipsisWidth)) + return false; + } + return true; +} + +int InlineFlowBox::placeEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth, bool& foundBox) +{ + int result = -1; + for (InlineBox *box = firstChild(); box; box = box->nextOnLine()) { + int currResult = box->placeEllipsisBox(ltr, blockEdge, ellipsisWidth, foundBox); + if (currResult != -1 && result == -1) + result = currResult; + } + return result; +} + +void InlineFlowBox::clearTruncation() +{ + for (InlineBox *box = firstChild(); box; box = box->nextOnLine()) + box->clearTruncation(); +} + +void EllipsisBox::paint(RenderObject::PaintInfo& i, int _tx, int _ty) +{ + QPainter* p = i.p; + RenderStyle* _style = m_firstLine ? m_object->style(true) : m_object->style(); + if (_style->font() != p->font()) + p->setFont(_style->font()); + + const Font* font = &_style->htmlFont(); + QColor textColor = _style->color(); + if (textColor != p->pen().color()) + p->setPen(textColor); + /* + bool setShadow = false; + if (_style->textShadow()) { + p->setShadow(_style->textShadow()->x, _style->textShadow()->y, + _style->textShadow()->blur, _style->textShadow()->color); + setShadow = true; + }*/ + + const DOMString& str = m_str.string(); + font->drawText(p, m_x + _tx, + m_y + _ty + m_baseline, + (str.implementation())->s, + str.length(), 0, str.length(), + 0, + QPainter::LTR, _style->visuallyOrdered()); + + /* + if (setShadow) + p->clearShadow(); + */ + + if (m_markupBox) { + // Paint the markup box + _tx += m_x + m_width - m_markupBox->xPos(); + _ty += m_y + m_baseline - (m_markupBox->yPos() + m_markupBox->baseline()); + m_markupBox->object()->paint(i, _tx, _ty); + } +} + +bool EllipsisBox::nodeAtPoint(RenderObject::NodeInfo& info, int _x, int _y, int _tx, int _ty) +{ + // Hit test the markup box. + if (m_markupBox) { + _tx += m_x + m_width - m_markupBox->xPos(); + _ty += m_y + m_baseline - (m_markupBox->yPos() + m_markupBox->baseline()); + if (m_markupBox->nodeAtPoint(info, _x, _y, _tx, _ty)) { + object()->setInnerNode(info); + return true; + } + } + + QRect rect(_tx + m_x, _ty + m_y, m_width, m_height); + if (object()->style()->visibility() == VISIBLE && rect.contains(_x, _y)) { + object()->setInnerNode(info); + return true; + } + return false; +} + +void RootInlineBox::detach(RenderArena* arena) +{ + detachEllipsisBox(arena); + InlineFlowBox::detach(arena); +} + +void RootInlineBox::detachEllipsisBox(RenderArena* arena) +{ + if (m_ellipsisBox) { + m_ellipsisBox->detach(arena); + m_ellipsisBox = 0; + } +} + +void RootInlineBox::clearTruncation() +{ + if (m_ellipsisBox) { + detachEllipsisBox(m_object->renderArena()); + InlineFlowBox::clearTruncation(); + } +} + +bool RootInlineBox::canAccommodateEllipsis(bool ltr, int blockEdge, int lineBoxEdge, int ellipsisWidth) +{ + // First sanity-check the unoverflowed width of the whole line to see if there is sufficient room. + int delta = ltr ? lineBoxEdge - blockEdge : blockEdge - lineBoxEdge; + if (width() - delta < ellipsisWidth) + return false; + + // Next iterate over all the line boxes on the line. If we find a replaced element that intersects + // then we refuse to accommodate the ellipsis. Otherwise we're ok. + return InlineFlowBox::canAccommodateEllipsisBox(ltr, blockEdge, ellipsisWidth); +} + +void RootInlineBox::placeEllipsis(const DOMString& ellipsisStr, bool ltr, int blockEdge, int ellipsisWidth, InlineBox* markupBox) +{ + // Create an ellipsis box. + m_ellipsisBox = new (m_object->renderArena()) EllipsisBox(m_object, ellipsisStr, this, + ellipsisWidth - (markupBox ? markupBox->width() : 0), + yPos(), height(), baseline(), !prevRootBox(), + markupBox); + + if (ltr && (xPos() + width() + ellipsisWidth) <= blockEdge) { + m_ellipsisBox->m_x = xPos() + width(); + return; + } + + // Now attempt to find the nearest glyph horizontally and place just to the right (or left in RTL) + // of that glyph. Mark all of the objects that intersect the ellipsis box as not painting (as being + // truncated). + bool foundBox = false; + m_ellipsisBox->m_x = placeEllipsisBox(ltr, blockEdge, ellipsisWidth, foundBox); +} + +int RootInlineBox::placeEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth, bool& foundBox) +{ + int result = InlineFlowBox::placeEllipsisBox(ltr, blockEdge, ellipsisWidth, foundBox); + if (result == -1) + result = ltr ? blockEdge - ellipsisWidth : blockEdge; + return result; +} + +void RootInlineBox::paintEllipsisBox(RenderObject::PaintInfo& i, int _tx, int _ty) const +{ + if (m_ellipsisBox) + m_ellipsisBox->paint(i, _tx, _ty); +} + +void RootInlineBox::paint(RenderObject::PaintInfo& i, int tx, int ty) +{ + InlineFlowBox::paint(i, tx, ty); + paintEllipsisBox(i, tx, ty); +} + +bool RootInlineBox::nodeAtPoint(RenderObject::NodeInfo& i, int x, int y, int tx, int ty) +{ + if (m_ellipsisBox && object()->style()->visibility() == VISIBLE) { + if (m_ellipsisBox->nodeAtPoint(i, x, y, tx, ty)) { + object()->setInnerNode(i); + return true; + } + } + return InlineFlowBox::nodeAtPoint(i, x, y, tx, ty); +} + diff --git a/khtml/rendering/render_line.h b/khtml/rendering/render_line.h new file mode 100644 index 000000000..43603d362 --- /dev/null +++ b/khtml/rendering/render_line.h @@ -0,0 +1,310 @@ +/* + * This file is part of the line box implementation for KDE. + * + * Copyright (C) 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. + * + */ +#ifndef RENDER_LINE_H +#define RENDER_LINE_H + +namespace khtml { + +class EllipsisBox; +class InlineFlowBox; +class RootInlineBox; + +// InlineBox represents a rectangle that occurs on a line. It corresponds to +// some RenderObject (i.e., it represents a portion of that RenderObject). +class InlineBox +{ +public: + InlineBox(RenderObject* obj) + :m_object(obj), m_x(0), m_width(0), m_y(0), m_height(0), m_baseline(0), + m_firstLine(false), m_constructed(false) + { + m_next = 0; + m_prev = 0; + m_parent = 0; + } + + virtual ~InlineBox() {} + + void detach(RenderArena* renderArena); + + virtual void paint(RenderObject::PaintInfo& i, int _tx, int _ty); + virtual bool nodeAtPoint(RenderObject::NodeInfo& i, int x, int y, int tx, int ty); + + // Overloaded new operator. + void* operator new(size_t sz, RenderArena* renderArena) throw(); + + // Overridden to prevent the normal delete from being called. + void operator delete(void* ptr, size_t sz); + +private: + // The normal operator new is disallowed. + void* operator new(size_t sz) throw(); + +public: + virtual bool isInlineBox() const { return false; } + virtual bool isInlineFlowBox() const { return false; } + virtual bool isContainer() const { return false; } + virtual bool isInlineTextBox() const { return false; } + virtual bool isRootInlineBox() const { return false; } + + bool isConstructed() const { return m_constructed; } + virtual void setConstructed() { + m_constructed = true; + if (m_next) + m_next->setConstructed(); + } + + void setFirstLineStyleBit(bool f) { m_firstLine = f; } + + InlineBox* nextOnLine() { return m_next; } + InlineBox* prevOnLine() { return m_prev; } + RenderObject* object() const { return m_object; } + + InlineFlowBox* parent() { return m_parent; } + void setParent(InlineFlowBox* par) { m_parent = par; } + + RootInlineBox* root(); + + void setWidth(short w) { m_width = w; } + short width() const { return m_width; } + + void setXPos(short x) { m_x = x; } + short xPos() const { return m_x; } + + void setYPos(int y) { m_y = y; } + int yPos() const { return m_y; } + + void setHeight(int h) { m_height = h; } + int height() const { return m_height; } + + void setBaseline(int b) { m_baseline = b; } + int baseline() const { return m_baseline; } + + virtual bool hasTextChildren() const { return true; } + virtual bool hasTextDescendant() const { return true; } + + virtual int topOverflow() const { return yPos(); } + virtual int bottomOverflow() const { return yPos()+height(); } + + virtual long minOffset() const { return 0; } + virtual long maxOffset() const { return 0; } + + virtual void clearTruncation() {}; + + virtual bool canAccommodateEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth); + virtual int placeEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth, bool&); + +public: // FIXME: Would like to make this protected, but methods are accessing these + // members over in the part. + RenderObject* m_object; + + short m_x; + short m_width; + int m_y; + int m_height; + int m_baseline; + + bool m_firstLine : 1; + bool m_constructed : 1; + + InlineBox* m_next; // The next element on the same line as us. + InlineBox* m_prev; // The previous element on the same line as us. + + InlineFlowBox* m_parent; // The box that contains us. +}; + +class InlineRunBox : public InlineBox +{ +public: + InlineRunBox(RenderObject* obj) + :InlineBox(obj) + { + m_prevLine = 0; + m_nextLine = 0; + } + + InlineRunBox* prevLineBox() { return m_prevLine; } + InlineRunBox* nextLineBox() { return m_nextLine; } + void setNextLineBox(InlineRunBox* n) { m_nextLine = n; } + void setPreviousLineBox(InlineRunBox* p) { m_prevLine = p; } + + virtual void paintBackgroundAndBorder(RenderObject::PaintInfo&, int /*_tx*/, int /*_ty*/) {} + virtual void paintDecorations(RenderObject::PaintInfo&, int /*_tx*/, int /*_ty*/, bool /*paintedChildren*/ = false) {} + +protected: + InlineRunBox* m_prevLine; // The previous box that also uses our RenderObject + InlineRunBox* m_nextLine; // The next box that also uses our RenderObject +}; + +class InlineFlowBox : public InlineRunBox +{ +public: + InlineFlowBox(RenderObject* obj) + :InlineRunBox(obj) + { + m_firstChild = 0; + m_lastChild = 0; + m_includeLeftEdge = m_includeRightEdge = false; + m_hasTextChildren = false; + m_hasTextDescendant = false; + m_afterPageBreak = false; + } + + ~InlineFlowBox(); + + virtual bool isInlineFlowBox() const { return true; } + + InlineFlowBox* prevFlowBox() const { return static_cast<InlineFlowBox*>(m_prevLine); } + InlineFlowBox* nextFlowBox() const { return static_cast<InlineFlowBox*>(m_nextLine); } + + InlineBox* firstChild() const { return m_firstChild; } + InlineBox* lastChild() const { return m_lastChild; } + + virtual void setConstructed() { + InlineBox::setConstructed(); + if (m_firstChild) + m_firstChild->setConstructed(); + } + void addToLine(InlineBox* child) { + if (!m_firstChild) + m_firstChild = m_lastChild = child; + else { + m_lastChild->m_next = child; + child->m_prev = m_lastChild; + m_lastChild = child; + } + child->setFirstLineStyleBit(m_firstLine); + child->setParent(this); + if (!m_hasTextChildren && child->isInlineTextBox()) { + m_hasTextDescendant = m_hasTextChildren = true; + for (InlineFlowBox* p = m_parent; p && !p->hasTextDescendant(); p = p->parent()) + p->m_hasTextDescendant = true; + } + } + + virtual void clearTruncation(); + + void removeFromLine(InlineBox* child); + virtual void paintBackgroundAndBorder(RenderObject::PaintInfo&, int _tx, int _ty); + void paintBackgrounds(QPainter* p, const QColor& c, const BackgroundLayer* bgLayer, + int my, int mh, int _tx, int _ty, int w, int h); + void paintBackground(QPainter* p, const QColor& c, const BackgroundLayer* bgLayer, + int my, int mh, int _tx, int _ty, int w, int h); + virtual void paint(RenderObject::PaintInfo& i, int _tx, int _ty); + virtual void paintDecorations(RenderObject::PaintInfo&, int _tx, int _ty, bool paintedChildren = false); + virtual bool nodeAtPoint(RenderObject::NodeInfo& i, int x, int y, int tx, int ty); + + int marginBorderPaddingLeft() const; + int marginBorderPaddingRight() const; + int marginLeft() const; + int marginRight( )const; + int borderLeft() const { if (includeLeftEdge()) return object()->borderLeft(); return 0; } + int borderRight() const { if (includeRightEdge()) return object()->borderRight(); return 0; } + int paddingLeft() const { if (includeLeftEdge()) return object()->paddingLeft(); return 0; } + int paddingRight() const { if (includeRightEdge()) return object()->paddingRight(); return 0; } + + bool includeLeftEdge() const { return m_includeLeftEdge; } + bool includeRightEdge() const { return m_includeRightEdge; } + void setEdges(bool includeLeft, bool includeRight) { + m_includeLeftEdge = includeLeft; + m_includeRightEdge = includeRight; + } + virtual bool hasTextChildren() const { return m_hasTextChildren; } + bool hasTextDescendant() const { return m_hasTextDescendant; } + + // Helper functions used during line construction and placement. + void determineSpacingForFlowBoxes(bool lastLine, RenderObject* endObject); + int getFlowSpacingWidth() const; + bool nextOnLineExists(); + bool prevOnLineExists(); + bool onEndChain(RenderObject* endObject); + int placeBoxesHorizontally(int x); + void verticallyAlignBoxes(int& heightOfBlock); + void computeLogicalBoxHeights(int& maxPositionTop, int& maxPositionBottom, + int& maxAscent, int& maxDescent, bool strictMode); + void adjustMaxAscentAndDescent(int& maxAscent, int& maxDescent, + int maxPositionTop, int maxPositionBottom); + void placeBoxesVertically(int y, int maxHeight, int maxAscent, bool strictMode, + int& topPosition, int& bottomPosition); + void shrinkBoxesWithNoTextChildren(int topPosition, int bottomPosition); + + virtual void setOverflowPositions(int /*top*/, int /*bottom*/) {} + + void setAfterPageBreak(bool b = true) { m_afterPageBreak = b; } + bool afterPageBreak() const { return m_afterPageBreak; } + + virtual bool canAccommodateEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth); + virtual int placeEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth, bool&); + +protected: + InlineBox* m_firstChild; + InlineBox* m_lastChild; + bool m_includeLeftEdge : 1; + bool m_includeRightEdge : 1; + bool m_hasTextChildren : 1; + bool m_hasTextDescendant : 1; + bool m_afterPageBreak : 1; +}; + +class RootInlineBox : public InlineFlowBox +{ +public: + RootInlineBox(RenderObject* obj) : InlineFlowBox(obj), m_ellipsisBox(0) + { + m_topOverflow = m_bottomOverflow = 0; + } + + virtual void detach(RenderArena* renderArena); + void detachEllipsisBox(RenderArena* renderArena); + + RootInlineBox* nextRootBox() { return static_cast<RootInlineBox*>(m_nextLine); } + RootInlineBox* prevRootBox() { return static_cast<RootInlineBox*>(m_prevLine); } + + virtual bool isRootInlineBox() const { return true; } + virtual int topOverflow() const { return m_topOverflow; } + virtual int bottomOverflow() const { return m_bottomOverflow; } + virtual void setOverflowPositions(int top, int bottom) { m_topOverflow = top; m_bottomOverflow = bottom; } + + bool canAccommodateEllipsis(bool ltr, int blockEdge, int lineBoxEdge, int ellipsisWidth); + void placeEllipsis(const DOM::DOMString& ellipsisStr, bool ltr, int blockEdge, int ellipsisWidth, InlineBox* markupBox = 0); + virtual int placeEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth, bool&); + + EllipsisBox* ellipsisBox() const { return m_ellipsisBox; } + void paintEllipsisBox(RenderObject::PaintInfo& i, int _tx, int _ty) const; + bool hitTestEllipsisBox(RenderObject::NodeInfo& info, int _x, int _y, int _tx, int _ty); + + virtual void clearTruncation(); + + virtual void paint(RenderObject::PaintInfo& i, int _tx, int _ty); + virtual bool nodeAtPoint(RenderObject::NodeInfo& i, int x, int y, int tx, int ty); + +protected: + int m_topOverflow; + int m_bottomOverflow; + + // An inline text box that represents our text truncation string. + EllipsisBox* m_ellipsisBox; +}; + +} //namespace + +#endif diff --git a/khtml/rendering/render_list.cpp b/khtml/rendering/render_list.cpp new file mode 100644 index 000000000..139201e03 --- /dev/null +++ b/khtml/rendering/render_list.cpp @@ -0,0 +1,586 @@ +/** + * This file is part of the HTML rendering engine for KDE. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000-2002 Dirk Mueller (mueller@kde.org) + * (C) 2003 Apple Computer, Inc. + * (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include "rendering/render_list.h" +#include "rendering/render_canvas.h" +#include "rendering/enumerate.h" +#include "rendering/counter_tree.h" +#include "html/html_listimpl.h" +#include "misc/helper.h" +#include "misc/htmltags.h" +#include "misc/loader.h" +#include "xml/dom_docimpl.h" + +#include <kdebug.h> +#include <kglobal.h> +#include <qvaluelist.h> + +//#define BOX_DEBUG + +using namespace khtml; +using namespace Enumerate; + +const int cMarkerPadding = 7; + +// ------------------------------------------------------------------------- + +RenderListItem::RenderListItem(DOM::NodeImpl* node) + : RenderBlock(node) +{ + // init RenderObject attributes + setInline(false); // our object is not Inline + + predefVal = -1; + m_marker = 0; + m_counter = 0; + m_insideList = false; + m_deleteMarker = false; +} + +void RenderListItem::setStyle(RenderStyle *_style) +{ + RenderBlock::setStyle(_style); + + RenderStyle *newStyle = new RenderStyle(); + newStyle->ref(); + + newStyle->inheritFrom(style()); + + if(!m_marker && style()->listStyleType() != LNONE) { + m_marker = new (renderArena()) RenderListMarker(element()); + m_marker->setIsAnonymous( true ); + m_marker->setStyle(newStyle); + m_marker->setListItem( this ); + m_deleteMarker = true; + } else if ( m_marker && style()->listStyleType() == LNONE) { + m_marker->detach(); + m_marker = 0; + } + else if ( m_marker ) { + m_marker->setStyle(newStyle); + } + + newStyle->deref(); +} + +void RenderListItem::detach() +{ + if ( m_marker && m_deleteMarker ) + m_marker->detach(); + RenderBlock::detach(); +} + +static RenderObject* getParentOfFirstLineBox(RenderObject* curr, RenderObject* marker) +{ + RenderObject* firstChild = curr->firstChild(); + if (!firstChild) + return 0; + + for (RenderObject* currChild = firstChild; + currChild; currChild = currChild->nextSibling()) { + if (currChild == marker) + continue; + + if (currChild->isInline()) + return curr; + + if (currChild->isFloating() || currChild->isPositioned()) + continue; + + if (currChild->isTable() || !currChild->isRenderBlock()) + break; + + if (currChild->style()->htmlHacks() && currChild->element() && + (currChild->element()->id() == ID_UL || currChild->element()->id() == ID_OL)) + break; + + RenderObject* lineBox = getParentOfFirstLineBox(currChild, marker); + if (lineBox) + return lineBox; + } + + return 0; +} + + +void RenderListItem::updateMarkerLocation() +{ + // Sanity check the location of our marker. + if (m_marker) { + RenderObject* markerPar = m_marker->parent(); + RenderObject* lineBoxParent = getParentOfFirstLineBox(this, m_marker); + if (!lineBoxParent) { + // If the marker is currently contained inside an anonymous box, + // then we are the only item in that anonymous box (since no line box + // parent was found). It's ok to just leave the marker where it is + // in this case. + if (markerPar && markerPar->isAnonymousBlock()) + lineBoxParent = markerPar; + else + lineBoxParent = this; + } + if (markerPar != lineBoxParent) + { + if (markerPar) + markerPar->removeChild(m_marker); + if (!lineBoxParent) + lineBoxParent = this; + lineBoxParent->addChild(m_marker, lineBoxParent->firstChild()); + m_deleteMarker = false; + if (!m_marker->minMaxKnown()) + m_marker->calcMinMaxWidth(); + recalcMinMaxWidths(); + } + } +} + +void RenderListItem::calcMinMaxWidth() +{ + // Make sure our marker is in the correct location. + updateMarkerLocation(); + if (!minMaxKnown()) + RenderBlock::calcMinMaxWidth(); +} +/* +short RenderListItem::marginLeft() const +{ + if (m_insideList) + return RenderBlock::marginLeft(); + else + return kMax(m_marker->markerWidth(), RenderBlock::marginLeft()); +} + +short RenderListItem::marginRight() const +{ + return RenderBlock::marginRight(); +}*/ + +void RenderListItem::layout( ) +{ + KHTMLAssert( needsLayout() ); + KHTMLAssert( minMaxKnown() ); + + updateMarkerLocation(); + RenderBlock::layout(); +} + +// ----------------------------------------------------------- + +RenderListMarker::RenderListMarker(DOM::NodeImpl* node) + : RenderBox(node), m_listImage(0), m_markerWidth(0) +{ + // init RenderObject attributes + setInline(true); // our object is Inline + setReplaced(true); // pretend to be replaced + // val = -1; + // m_listImage = 0; +} + +RenderListMarker::~RenderListMarker() +{ + if(m_listImage) + m_listImage->deref(this); + if (m_listItem) + m_listItem->resetListMarker(); +} + +void RenderListMarker::setStyle(RenderStyle *s) +{ + if ( s && style() && s->listStylePosition() != style()->listStylePosition() ) + setNeedsLayoutAndMinMaxRecalc(); + + RenderBox::setStyle(s); + + if ( m_listImage != style()->listStyleImage() ) { + if(m_listImage) m_listImage->deref(this); + m_listImage = style()->listStyleImage(); + if(m_listImage) m_listImage->ref(this); + } +} + + +void RenderListMarker::paint(PaintInfo& paintInfo, int _tx, int _ty) +{ + if (paintInfo.phase != PaintActionForeground) + return; + + if (style()->visibility() != VISIBLE) return; + + _tx += m_x; + _ty += m_y; + + if((_ty > paintInfo.r.bottom()) || (_ty + m_height <= paintInfo.r.top())) + return; + + if(shouldPaintBackgroundOrBorder()) + paintBoxDecorations(paintInfo, _tx, _ty); + + QPainter* p = paintInfo.p; +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << nodeName().string() << "(ListMarker)::paintObject(" << _tx << ", " << _ty << ")" << endl; +#endif + p->setFont(style()->font()); + const QFontMetrics fm = p->fontMetrics(); + + + // The marker needs to adjust its tx, for the case where it's an outside marker. + RenderObject* listItem = 0; + int leftLineOffset = 0; + int rightLineOffset = 0; + if (!listPositionInside()) { + listItem = this; + int yOffset = 0; + int xOffset = 0; + while (listItem && listItem != m_listItem) { + yOffset += listItem->yPos(); + xOffset += listItem->xPos(); + listItem = listItem->parent(); + } + + // Now that we have our xoffset within the listbox, we need to adjust ourselves by the delta + // between our current xoffset and our desired position (which is just outside the border box + // of the list item). + if (style()->direction() == LTR) { + leftLineOffset = m_listItem->leftRelOffset(yOffset, m_listItem->leftOffset(yOffset)); + _tx -= (xOffset - leftLineOffset) + m_listItem->paddingLeft() + m_listItem->borderLeft(); + } + else { + rightLineOffset = m_listItem->rightRelOffset(yOffset, m_listItem->rightOffset(yOffset)); + _tx += (rightLineOffset-xOffset) + m_listItem->paddingRight() + m_listItem->borderRight(); + } + } + + int offset = fm.ascent()*2/3; + bool haveImage = m_listImage && !m_listImage->isErrorImage(); + if (haveImage) + offset = m_listImage->pixmap().width(); + + int xoff = 0; + int yoff = fm.ascent() - offset; + + int bulletWidth = offset/2; + if (offset%2) + bulletWidth++; + if (!listPositionInside()) { + if (listItem && listItem->style()->direction() == LTR) + xoff = -cMarkerPadding - offset; + else + xoff = cMarkerPadding + (haveImage ? 0 : (offset - bulletWidth)); + } + else if (style()->direction() == RTL) + xoff += haveImage ? cMarkerPadding : (m_width - bulletWidth); + + if ( m_listImage && !m_listImage->isErrorImage()) { + p->drawPixmap( QPoint( _tx + xoff, _ty ), m_listImage->pixmap()); + return; + } + +#ifdef BOX_DEBUG + p->setPen( Qt::red ); + p->drawRect( _tx + xoff, _ty + yoff, offset, offset ); +#endif + + const QColor color( style()->color() ); + p->setPen( color ); + + switch(style()->listStyleType()) { + case LDISC: + p->setBrush( color ); + p->drawEllipse( _tx + xoff, _ty + (3 * yoff)/2, (offset>>1)+1, (offset>>1)+1 ); + return; + case LCIRCLE: + p->setBrush( Qt::NoBrush ); + p->drawEllipse( _tx + xoff, _ty + (3 * yoff)/2, (offset>>1)+1, (offset>>1)+1 ); + return; + case LSQUARE: + p->setBrush( color ); + p->drawRect( _tx + xoff, _ty + (3 * yoff)/2, (offset>>1)+1, (offset>>1)+1 ); + return; + case LBOX: + p->setBrush( Qt::NoBrush ); + p->drawRect( _tx + xoff, _ty + (3 * yoff)/2, (offset>>1)+1, (offset>>1)+1 ); + return; + case LDIAMOND: { + static QPointArray diamond(4); + int x = _tx + xoff; + int y = _ty + (3 * yoff)/2 - 1; + int s = (offset>>2)+1; + diamond[0] = QPoint(x+s, y); + diamond[1] = QPoint(x+2*s, y+s); + diamond[2] = QPoint(x+s, y+2*s); + diamond[3] = QPoint(x, y+s); + p->setBrush( color ); + p->drawConvexPolygon( diamond, 0, 4 ); + return; + } + case LNONE: + return; + default: + if (!m_item.isEmpty()) { + if(listPositionInside()) { + if( style()->direction() == LTR) { + p->drawText(_tx, _ty, 0, 0, Qt::AlignLeft|Qt::DontClip, m_item); + p->drawText(_tx + fm.width(m_item), _ty, 0, 0, Qt::AlignLeft|Qt::DontClip, + QString::fromLatin1(". ")); + } + else { + const QString& punct(QString::fromLatin1(" .")); + p->drawText(_tx, _ty, 0, 0, Qt::AlignLeft|Qt::DontClip, punct); + p->drawText(_tx + fm.width(punct), _ty, 0, 0, Qt::AlignLeft|Qt::DontClip, m_item); + } + } else { + if (style()->direction() == LTR) { + const QString& punct(QString::fromLatin1(". ")); + p->drawText(_tx-offset/2, _ty, 0, 0, Qt::AlignRight|Qt::DontClip, punct); + p->drawText(_tx-offset/2-fm.width(punct), _ty, 0, 0, Qt::AlignRight|Qt::DontClip, m_item); + } + else { + const QString& punct(QString::fromLatin1(" .")); + p->drawText(_tx+offset/2, _ty, 0, 0, Qt::AlignLeft|Qt::DontClip, punct); + p->drawText(_tx+offset/2+fm.width(punct), _ty, 0, 0, Qt::AlignLeft|Qt::DontClip, m_item); + } + } + } + } +} + +void RenderListMarker::layout() +{ + KHTMLAssert( needsLayout() ); + + if ( !minMaxKnown() ) + calcMinMaxWidth(); + + setNeedsLayout(false); +} + +void RenderListMarker::setPixmap( const QPixmap &p, const QRect& r, CachedImage *o) +{ + if(o != m_listImage) { + RenderBox::setPixmap(p, r, o); + return; + } + + if(m_width != m_listImage->pixmap_size().width() || m_height != m_listImage->pixmap_size().height()) + setNeedsLayoutAndMinMaxRecalc(); + else + repaintRectangle(0, 0, m_width, m_height); +} + +void RenderListMarker::calcMinMaxWidth() +{ + KHTMLAssert( !minMaxKnown() ); + + m_markerWidth = m_width = 0; + + if(m_listImage && !m_listImage->isErrorImage()) { + m_markerWidth = m_listImage->pixmap().width() + cMarkerPadding; + if (listPositionInside()) + m_width = m_markerWidth; + m_height = m_listImage->pixmap().height(); + m_minWidth = m_maxWidth = m_width; + setMinMaxKnown(); + return; + } + + const QFontMetrics &fm = style()->fontMetrics(); + m_height = fm.ascent(); + + // Skip uncounted elements + switch(style()->listStyleType()) { + // Glyphs: + case LDISC: + case LCIRCLE: + case LSQUARE: + case LBOX: + case LDIAMOND: + m_markerWidth = fm.ascent(); + goto end; + default: + break; + } + + { // variable scope + CounterNode *counter = m_listItem->m_counter; + if (!counter) { + counter = m_listItem->getCounter("list-item", true); + counter->setRenderer(this); + m_listItem->m_counter = counter; + } + + + assert(counter); + int value = counter->count(); + if (counter->isReset()) value = counter->value(); + int total = value; + if (counter->parent()) total = counter->parent()->total(); + + switch(style()->listStyleType()) + { +// Numeric: + case LDECIMAL: + m_item.setNum ( value ); + break; + case DECIMAL_LEADING_ZERO: { + int decimals = 2; + int t = total/100; + while (t>0) { + t = t/10; + decimals++; + } + decimals = kMax(decimals, 2); + QString num = QString::number(value); + m_item.fill('0',decimals-num.length()); + m_item.append(num); + break; + } + case ARABIC_INDIC: + m_item = toArabicIndic( value ); + break; + case LAO: + m_item = toLao( value ); + break; + case PERSIAN: + case URDU: + m_item = toPersianUrdu( value ); + break; + case THAI: + m_item = toThai( value ); + break; + case TIBETAN: + m_item = toTibetan( value ); + break; +// Algoritmic: + case LOWER_ROMAN: + m_item = toRoman( value, false ); + break; + case UPPER_ROMAN: + m_item = toRoman( value, true ); + break; + case HEBREW: + m_item = toHebrew( value ); + break; + case ARMENIAN: + m_item = toArmenian( value ); + break; + case GEORGIAN: + m_item = toGeorgian( value ); + break; +// Alphabetic: + case LOWER_ALPHA: + case LOWER_LATIN: + m_item = toLowerLatin( value ); + break; + case UPPER_ALPHA: + case UPPER_LATIN: + m_item = toUpperLatin( value ); + break; + case LOWER_GREEK: + m_item = toLowerGreek( value ); + break; + case UPPER_GREEK: + m_item = toUpperGreek( value ); + break; + case HIRAGANA: + m_item = toHiragana( value ); + break; + case HIRAGANA_IROHA: + m_item = toHiraganaIroha( value ); + break; + case KATAKANA: + m_item = toKatakana( value ); + break; + case KATAKANA_IROHA: + m_item = toKatakanaIroha( value ); + break; +// Ideographic: + case JAPANESE_FORMAL: + m_item = toJapaneseFormal( value ); + break; + case JAPANESE_INFORMAL: + m_item = toJapaneseInformal( value ); + break; + case SIMP_CHINESE_FORMAL: + m_item = toSimpChineseFormal( value ); + break; + case SIMP_CHINESE_INFORMAL: + m_item = toSimpChineseInformal( value ); + break; + case TRAD_CHINESE_FORMAL: + m_item = toTradChineseFormal( value ); + break; + case CJK_IDEOGRAPHIC: + // CSS 3 List says treat as trad-chinese-informal + case TRAD_CHINESE_INFORMAL: + m_item = toTradChineseInformal( value ); + break; +// special: + case LNONE: + break; + default: + KHTMLAssert(false); + } + m_markerWidth = fm.width(m_item) + fm.width(QString::fromLatin1(". ")); + } + +end: + if(listPositionInside()) + m_width = m_markerWidth; + + m_minWidth = m_width; + m_maxWidth = m_width; + + setMinMaxKnown(); +} + +short RenderListMarker::lineHeight(bool /*b*/) const +{ + return height(); +} + +short RenderListMarker::baselinePosition(bool /*b*/) const +{ + return height(); +} + +void RenderListMarker::calcWidth() +{ + RenderBox::calcWidth(); +} + +/* +int CounterListItem::recount() const +{ + static_cast<RenderListItem*>(m_renderer)->m_marker->setNeedsLayoutAndMinMaxRecalc(); +} + +void CounterListItem::setSelfDirty() +{ + +}*/ + + +#undef BOX_DEBUG diff --git a/khtml/rendering/render_list.h b/khtml/rendering/render_list.h new file mode 100644 index 000000000..ff4e24842 --- /dev/null +++ b/khtml/rendering/render_list.h @@ -0,0 +1,140 @@ +/* + * This file is part of the HTML rendering engine for KDE. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2004 Allan Sandfeld Jensen (kde@carewolf.com) + * + * 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. + * + */ +#ifndef RENDER_LIST_H +#define RENDER_LIST_H + +#include "rendering/render_block.h" + +// ### list-style-position, list-style-image is still missing + +namespace DOM +{ + class DocumentImpl; +} + +namespace khtml +{ + +class RenderListItem; +class RenderListMarker; +class CounterNode; + +// ----------------------------------------------------------------------------- + +class RenderListItem : public RenderBlock +{ + friend class RenderListMarker; +// friend class CounterListItem; + +public: + RenderListItem(DOM::NodeImpl*); + + virtual const char *renderName() const { return "RenderListItem"; } + + virtual void setStyle(RenderStyle *style); + + virtual bool isListItem() const { return true; } + + void setValue( long v ) { predefVal = v; } + + virtual void layout( ); + virtual void detach( ); + virtual void calcMinMaxWidth(); + //virtual short marginLeft() const; + //virtual short marginRight() const; + + void setInsideList(bool b ) { m_insideList = b; } + +protected: + + void updateMarkerLocation(); + void resetListMarker() { m_marker = 0; } + + RenderListMarker *m_marker; + CounterNode *m_counter; + signed long predefVal : 30; + bool m_insideList : 1; + bool m_deleteMarker: 1; +}; + +// ----------------------------------------------------------------------------- + +class RenderListMarker : public RenderBox +{ +public: + RenderListMarker(DOM::NodeImpl* node); + ~RenderListMarker(); + + virtual void setStyle(RenderStyle *style); + + virtual const char *renderName() const { return "RenderListMarker"; } + // so the marker gets to layout itself. Only needed for + // list-style-position: inside + + virtual void paint(PaintInfo& i, int xoff, int yoff); + virtual void layout( ); + virtual void calcMinMaxWidth(); + + virtual short lineHeight( bool firstLine ) const; + virtual short baselinePosition( bool firstLine ) const; + + virtual void setPixmap( const QPixmap &, const QRect&, CachedImage *); + + virtual void calcWidth(); + + virtual bool isListMarker() const { return true; } + + virtual short markerWidth() const { return m_markerWidth; } + + RenderListItem* listItem() const { return m_listItem; } + void setListItem(RenderListItem* listItem) { m_listItem = listItem; } + + bool listPositionInside() const + { return !m_listItem->m_insideList || style()->listStylePosition() == INSIDE; } + +protected: + friend class RenderListItem; + + QString m_item; + CachedImage *m_listImage; + short m_markerWidth; + RenderListItem* m_listItem; +}; + +// Implementation of list-item counter +// ### should replace most list-item specific code in renderObject::getCounter +/* +class CounterListItem : public CounterNode +{ +public: + int count() const; + + virtual void recount( bool first = false ); + virtual void setSelfDirty(); + +}; */ + +} //namespace + +#endif diff --git a/khtml/rendering/render_object.cpp b/khtml/rendering/render_object.cpp new file mode 100644 index 000000000..dfb2e06df --- /dev/null +++ b/khtml/rendering/render_object.cpp @@ -0,0 +1,2325 @@ +/** + * This file is part of the html renderer for KDE. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2000-2003 Dirk Mueller (mueller@kde.org) + * (C) 2002-2006 Apple Computer, Inc. + * (C) 2006 Germain Garand <germain@ebooksfrance.org> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include "rendering/render_object.h" +#include "rendering/render_table.h" +#include "rendering/render_list.h" +#include "rendering/render_canvas.h" +#include "rendering/render_block.h" +#include "rendering/render_arena.h" +#include "rendering/render_layer.h" +#include "rendering/render_line.h" +#include "rendering/render_inline.h" +#include "rendering/render_text.h" +#include "rendering/render_replaced.h" +#include "rendering/render_generated.h" +#include "rendering/counter_tree.h" + +#include "xml/dom_elementimpl.h" +#include "xml/dom_docimpl.h" +#include "dom/dom_doc.h" +#include "misc/htmlhashes.h" +#include "misc/loader.h" + +#include <kdebug.h> +#include <kglobal.h> +#include <qpainter.h> +#include "khtmlview.h" +#include <khtml_part.h> + +#include <assert.h> +using namespace DOM; +using namespace khtml; + +#define RED_LUMINOSITY 30 +#define GREEN_LUMINOSITY 59 +#define BLUE_LUMINOSITY 11 +#define INTENSITY_FACTOR 25 +#define LIGHT_FACTOR 0 +#define LUMINOSITY_FACTOR 75 + +#define MAX_COLOR 255 +#define COLOR_DARK_THRESHOLD 51 +#define COLOR_LIGHT_THRESHOLD 204 + +#define COLOR_LITE_BS_FACTOR 45 +#define COLOR_LITE_TS_FACTOR 70 + +#define COLOR_DARK_BS_FACTOR 30 +#define COLOR_DARK_TS_FACTOR 50 + +#define LIGHT_GRAY qRgb(192, 192, 192) +#define DARK_GRAY qRgb(96, 96, 96) + +#ifndef NDEBUG +static void *baseOfRenderObjectBeingDeleted; +#endif + +//#define MASK_DEBUG + +void* RenderObject::operator new(size_t sz, RenderArena* renderArena) throw() +{ + return renderArena->allocate(sz); +} + +void RenderObject::operator delete(void* ptr, size_t sz) +{ + assert(baseOfRenderObjectBeingDeleted == ptr); + + // Stash size where detach can find it. + *(size_t *)ptr = sz; +} + +RenderObject *RenderObject::createObject(DOM::NodeImpl* node, RenderStyle* style) +{ + RenderObject *o = 0; + khtml::RenderArena* arena = node->getDocument()->renderArena(); + switch(style->display()) + { + case NONE: + break; + case INLINE: + o = new (arena) RenderInline(node); + break; + case BLOCK: + o = new (arena) RenderBlock(node); + break; + case INLINE_BLOCK: + o = new (arena) RenderBlock(node); + break; + case LIST_ITEM: + o = new (arena) RenderListItem(node); + break; + case RUN_IN: + case COMPACT: + o = new (arena) RenderBlock(node); + break; + case TABLE: + case INLINE_TABLE: + style->setFlowAroundFloats(true); + o = new (arena) RenderTable(node); + break; + case TABLE_ROW_GROUP: + case TABLE_HEADER_GROUP: + case TABLE_FOOTER_GROUP: + o = new (arena) RenderTableSection(node); + break; + case TABLE_ROW: + o = new (arena) RenderTableRow(node); + break; + case TABLE_COLUMN_GROUP: + case TABLE_COLUMN: + o = new (arena) RenderTableCol(node); + break; + case TABLE_CELL: + o = new (arena) RenderTableCell(node); + break; + case TABLE_CAPTION: + o = new (arena) RenderBlock(node); + break; + } + return o; +} + + +RenderObject::RenderObject(DOM::NodeImpl* node) + : CachedObjectClient(), + m_style( 0 ), + m_node( node ), + m_parent( 0 ), + m_previous( 0 ), + m_next( 0 ), + m_verticalPosition( PositionUndefined ), + m_needsLayout( false ), + m_normalChildNeedsLayout( false ), + m_markedForRepaint( false ), + m_posChildNeedsLayout( false ), + m_minMaxKnown( false ), + m_floating( false ), + + m_positioned( false ), + m_overhangingContents( false ), + m_relPositioned( false ), + m_paintBackground( false ), + + m_isAnonymous( node->isDocumentNode() ), + m_recalcMinMax( false ), + m_isText( false ), + m_inline( true ), + m_attached( false ), + + m_replaced( false ), + m_mouseInside( false ), + m_hasFirstLine( false ), + m_isSelectionBorder( false ), + m_isRoot( false ), + m_afterPageBreak( false ), + m_needsPageClear( false ), + m_containsPageBreak( false ), + m_hasOverflowClip( false ), + m_doNotDelete( false ) +{ + assert( node ); + if (node->getDocument()->documentElement() == node) setIsRoot(true); +} + +RenderObject::~RenderObject() +{ + const BackgroundLayer* bgLayer = m_style->backgroundLayers(); + while (bgLayer) { + if(bgLayer->backgroundImage()) + bgLayer->backgroundImage()->deref(this); + bgLayer = bgLayer->next(); + } + + if (m_style) + m_style->deref(); +} + + + +RenderObject* RenderObject::objectBelow() const +{ + RenderObject* obj = firstChild(); + if ( !obj ) { + obj = nextSibling(); + if ( !obj ) + { + obj = parent(); + while (obj && !obj->nextSibling()) + obj = obj->parent(); + if (obj) + obj = obj->nextSibling(); + } + } + return obj; +} + +RenderObject* RenderObject::objectAbove() const +{ + RenderObject* obj = previousSibling(); + if ( !obj ) + return parent(); + + RenderObject* last = obj->lastChild(); + while ( last ) + { + obj = last; + last = last->lastChild(); + } + return obj; +} +/* +bool RenderObject::isRoot() const +{ + return !isAnonymous() && + element()->getDocument()->documentElement() == element(); +}*/ + +bool RenderObject::isHR() const +{ + return element() && element()->id() == ID_HR; +} + +bool RenderObject::isHTMLMarquee() const +{ + return element() && element()->renderer() == this && element()->id() == ID_MARQUEE; +} + +void RenderObject::addChild(RenderObject* , RenderObject *) +{ + KHTMLAssert(0); +} + +RenderObject* RenderObject::removeChildNode(RenderObject* ) +{ + KHTMLAssert(0); + return 0; +} + +void RenderObject::removeChild(RenderObject *o ) +{ + setNeedsLayout(true); + removeChildNode( o ); +} + +void RenderObject::appendChildNode(RenderObject*) +{ + KHTMLAssert(0); +} + +void RenderObject::insertChildNode(RenderObject*, RenderObject*) +{ + KHTMLAssert(0); +} + +RenderObject *RenderObject::nextRenderer() const +{ + if (firstChild()) + return firstChild(); + else if (nextSibling()) + return nextSibling(); + else { + const RenderObject *r = this; + while (r && !r->nextSibling()) + r = r->parent(); + if (r) + return r->nextSibling(); + } + return 0; +} + +RenderObject *RenderObject::previousRenderer() const +{ + if (previousSibling()) { + RenderObject *r = previousSibling(); + while (r->lastChild()) + r = r->lastChild(); + return r; + } + else if (parent()) { + return parent(); + } + else { + return 0; + } +} + +static void addLayers(RenderObject* obj, RenderLayer* parentLayer, RenderObject*& newObject, + RenderLayer*& beforeChild) +{ + if (obj->layer()) { + if (!beforeChild && newObject) { + // We need to figure out the layer that follows newObject. We only do + // this the first time we find a child layer, and then we update the + // pointer values for newObject and beforeChild used by everyone else. + beforeChild = newObject->parent()->findNextLayer(parentLayer, newObject); + newObject = 0; + } + parentLayer->addChild(obj->layer(), beforeChild); + return; + } + + for (RenderObject* curr = obj->firstChild(); curr; curr = curr->nextSibling()) + addLayers(curr, parentLayer, newObject, beforeChild); +} + +void RenderObject::addLayers(RenderLayer* parentLayer, RenderObject* newObject) +{ + if (!parentLayer) + return; + + RenderObject* object = newObject; + RenderLayer* beforeChild = 0; + ::addLayers(this, parentLayer, object, beforeChild); +} + +void RenderObject::removeLayers(RenderLayer* parentLayer) +{ + if (!parentLayer) + return; + + if (layer()) { + parentLayer->removeChild(layer()); + return; + } + + for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) + curr->removeLayers(parentLayer); +} + +void RenderObject::moveLayers(RenderLayer* oldParent, RenderLayer* newParent) +{ + if (!newParent) + return; + + if (layer()) { + if (oldParent) + oldParent->removeChild(layer()); + newParent->addChild(layer()); + return; + } + + for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) + curr->moveLayers(oldParent, newParent); +} + +RenderLayer* RenderObject::findNextLayer(RenderLayer* parentLayer, RenderObject* startPoint, + bool checkParent) +{ + // Error check the parent layer passed in. If it's null, we can't find anything. + if (!parentLayer) + return 0; + + // Step 1: If our layer is a child of the desired parent, then return our layer. + RenderLayer* ourLayer = layer(); + if (ourLayer && ourLayer->parent() == parentLayer) + return ourLayer; + + // Step 2: If we don't have a layer, or our layer is the desired parent, then descend + // into our siblings trying to find the next layer whose parent is the desired parent. + if (!ourLayer || ourLayer == parentLayer) { + for (RenderObject* curr = startPoint ? startPoint->nextSibling() : firstChild(); + curr; curr = curr->nextSibling()) { + RenderLayer* nextLayer = curr->findNextLayer(parentLayer, 0, false); + if (nextLayer) + return nextLayer; + } + } + + // Step 3: If our layer is the desired parent layer, then we're finished. We didn't + // find anything. + if (parentLayer == ourLayer) + return 0; + + // Step 4: If |checkParent| is set, climb up to our parent and check its siblings that + // follow us to see if we can locate a layer. + if (checkParent && parent()) + return parent()->findNextLayer(parentLayer, this, true); + + return 0; +} + +RenderLayer* RenderObject::enclosingLayer() const +{ + const RenderObject* curr = this; + while (curr) { + RenderLayer *layer = curr->layer(); + if (layer) + return layer; + curr = curr->parent(); + } + return 0; +} + +RenderLayer* RenderObject::enclosingStackingContext() const +{ + RenderLayer* l = enclosingLayer(); + while (l && !l->isStackingContext()) + l = l->parent(); + return l; +} + +int RenderObject::offsetLeft() const +{ + if ( isPositioned() ) + return xPos(); + + if ( isBody() && style()->htmlHacks() ) + return 0; + + int x = xPos(); + if (isRelPositioned()) { + int y = 0; + static_cast<const RenderBox*>(this)->relativePositionOffset(x, y); + } + + RenderObject* offsetPar = offsetParent(); + for( RenderObject* curr = parent(); + curr && curr != offsetPar; + curr = curr->parent() ) + x += curr->xPos(); + + if ( offsetPar && offsetPar->isBody() && style()->htmlHacks() ) + x += offsetPar->xPos(); + + return x; +} + +int RenderObject::offsetTop() const +{ + if ( isPositioned() ) + return yPos(); + + if ( isBody() && style()->htmlHacks() ) + return 0; + + int y = yPos(); + if (isRelPositioned()) { + int x = 0; + static_cast<const RenderBox*>(this)->relativePositionOffset(x, y); + } + RenderObject* offsetPar = offsetParent(); + for( RenderObject* curr = parent(); + curr && curr != offsetPar; + curr = curr->parent() ) + y += curr->yPos(); + + if ( offsetPar && offsetPar->isBody() && style()->htmlHacks() ) + y += offsetPar->yPos(); + + return y; +} + +RenderObject* RenderObject::offsetParent() const +{ + if (isBody()) + return 0; + + // can't really use containing blocks here (#113280) + bool skipTables = isPositioned() || isRelPositioned(); + bool strict = !style()->htmlHacks(); + RenderObject* curr = parent(); + while (curr && (!curr->element() || + (!curr->isPositioned() && !curr->isRelPositioned() && + !(strict && skipTables ? curr->isRoot() : curr->isBody())))) { + if (!skipTables && curr->element() && (curr->isTableCell() || curr->isTable())) + break; + curr = curr->parent(); + } + return curr; +} + +// IE extensions. +// clientWidth and clientHeight represent the interior of an object +short RenderObject::clientWidth() const +{ + return width() - borderLeft() - borderRight() - + (layer() ? layer()->verticalScrollbarWidth() : 0); +} + +int RenderObject::clientHeight() const +{ + return height() - borderTop() - borderBottom() - + (layer() ? layer()->horizontalScrollbarHeight() : 0); +} + +// scrollWidth/scrollHeight is the size including the overflow area +short RenderObject::scrollWidth() const +{ + return (hasOverflowClip() && layer()) ? layer()->scrollWidth() : overflowWidth() - overflowLeft(); +} + +int RenderObject::scrollHeight() const +{ + return (hasOverflowClip() && layer()) ? layer()->scrollHeight() : overflowHeight() - overflowTop(); +} + +bool RenderObject::hasStaticX() const +{ + return (style()->left().isVariable() && style()->right().isVariable()); +} + +bool RenderObject::hasStaticY() const +{ + return (style()->top().isVariable() && style()->bottom().isVariable()); +} + +void RenderObject::setPixmap(const QPixmap&, const QRect& /*r*/, CachedImage* image) +{ + //repaint bg when it finished loading + if(image && parent() && style() && style()->backgroundLayers()->containsImage(image)) { + isBody() ? canvas()->repaint() : repaint(); + } +} + +void RenderObject::setNeedsLayout(bool b, bool markParents) +{ + bool alreadyNeededLayout = m_needsLayout; + m_needsLayout = b; + if (b) { + if (!alreadyNeededLayout && markParents && m_parent) { + dirtyFormattingContext( false ); + markContainingBlocksForLayout(); + } + } + else { + m_posChildNeedsLayout = false; + m_normalChildNeedsLayout = false; + } +} + +void RenderObject::setChildNeedsLayout(bool b, bool markParents) +{ + bool alreadyNeededLayout = m_normalChildNeedsLayout; + m_normalChildNeedsLayout = b; + if (b) { + if (!alreadyNeededLayout && markParents) + markContainingBlocksForLayout(); + } + else { + m_posChildNeedsLayout = false; + m_normalChildNeedsLayout = false; + } +} + +void RenderObject::markContainingBlocksForLayout() +{ + RenderObject *o = container(); + RenderObject *last = this; + + while (o) { + if (!last->isText() && (last->style()->position() == FIXED || last->style()->position() == ABSOLUTE)) { + if (o->m_posChildNeedsLayout) + return; + o->m_posChildNeedsLayout = true; + } + else { + if (o->m_normalChildNeedsLayout) + return; + o->m_normalChildNeedsLayout = true; + } + + last = o; + o = o->container(); + } + + last->scheduleRelayout(); +} + +RenderBlock *RenderObject::containingBlock() const +{ + if(isTableCell()) + return static_cast<RenderBlock*>( parent()->parent()->parent() ); + if (isCanvas()) + return const_cast<RenderBlock*>( static_cast<const RenderBlock*>(this) ); + + RenderObject *o = parent(); + if(m_style->position() == FIXED) { + while ( o && !o->isCanvas() ) + o = o->parent(); + } + else if(m_style->position() == ABSOLUTE) { + while (o && + ( o->style()->position() == STATIC || ( o->isInline() && !o->isReplaced() ) ) && !o->isCanvas()) { + // for relpos inlines, return the nearest block - it will host the positioned objects list + if (o->isInline() && !o->isReplaced() && o->style()->position() == RELATIVE) + return o->containingBlock(); + o = o->parent(); + } + } else { + while(o && ( ( o->isInline() && !o->isReplaced() ) || o->isTableRow() || o->isTableSection() || + o->isTableCol() || o->isFrameSet() ) ) + o = o->parent(); + } + // this is just to make sure we return a valid element. + // the case below should never happen... + if(!o || !o->isRenderBlock()) { + if(!isCanvas()) { +#ifndef NDEBUG + kdDebug( 6040 ) << this << ": " << renderName() << "(RenderObject): No containingBlock!" << endl; + kdDebug( 6040 ) << kdBacktrace() << endl; + const RenderObject* p = this; + while (p->parent()) p = p->parent(); + p->printTree(); +#endif + } + return 0L; + } + + return static_cast<RenderBlock*>( o ); +} + +short RenderObject::containingBlockWidth() const +{ + // ### + return containingBlock()->contentWidth(); +} + +int RenderObject::containingBlockHeight() const +{ + // ### + return containingBlock()->contentHeight(); +} + +bool RenderObject::sizesToMaxWidth() const +{ + // Marquees in WinIE are like a mixture of blocks and inline-blocks. They size as though they're blocks, + // but they allow text to sit on the same line as the marquee. + if (isFloating() || isCompact() || + (isInlineBlockOrInlineTable() && !isHTMLMarquee()) || + (element() && (element()->id() == ID_BUTTON || element()->id() == ID_LEGEND))) + return true; + + // Children of a horizontal marquee do not fill the container by default. + // FIXME: Need to deal with MAUTO value properly. It could be vertical. + if (parent()->style()->overflowX() == OMARQUEE) { + EMarqueeDirection dir = parent()->style()->marqueeDirection(); + if (dir == MAUTO || dir == MFORWARD || dir == MBACKWARD || dir == MLEFT || dir == MRIGHT) + return true; + } + +#ifdef APPLE_CHANGES // ### what the heck is a flexbox? + // Flexible horizontal boxes lay out children at their maxwidths. Also vertical boxes + // that don't stretch their kids lay out their children at their maxwidths. + if (parent()->isFlexibleBox() && + (parent()->style()->boxOrient() == HORIZONTAL || parent()->style()->boxAlign() != BSTRETCH)) + return true; +#endif + + return false; +} + +// from Mozilla's nsCSSColorUtils.cpp +static int brightness(int red, int green, int blue) +{ + + int intensity = (red + green + blue) / 3; + + int luminosity = + ((RED_LUMINOSITY * red) / 100) + + ((GREEN_LUMINOSITY * green) / 100) + + ((BLUE_LUMINOSITY * blue) / 100); + + return ((intensity * INTENSITY_FACTOR) + + (luminosity * LUMINOSITY_FACTOR)) / 100; +} + +static void calc3DColor(QColor &color, bool darken) +{ + int rb = color.red(); + int gb = color.green(); + int bb = color.blue(); + + int brightness_ = brightness(rb,gb,bb); + + int f0, f1; + if (brightness_ < COLOR_DARK_THRESHOLD) { + f0 = COLOR_DARK_BS_FACTOR; + f1 = COLOR_DARK_TS_FACTOR; + } else if (brightness_ > COLOR_LIGHT_THRESHOLD) { + f0 = COLOR_LITE_BS_FACTOR; + f1 = COLOR_LITE_TS_FACTOR; + } else { + f0 = COLOR_DARK_BS_FACTOR + + (brightness_ * + (COLOR_LITE_BS_FACTOR - COLOR_DARK_BS_FACTOR) / MAX_COLOR); + f1 = COLOR_DARK_TS_FACTOR + + (brightness_ * + (COLOR_LITE_TS_FACTOR - COLOR_DARK_TS_FACTOR) / MAX_COLOR); + } + + if (darken) { + int r = rb - (f0 * rb / 100); + int g = gb - (f0 * gb / 100); + int b = bb - (f0 * bb / 100); + if ((r == rb) && (g == gb) && (b == bb)) + color = (color == Qt::black) ? DARK_GRAY : Qt::black; + else + color.setRgb(r, g, b); + } else { + int r = kMin(rb + (f1 * (MAX_COLOR - rb) / 100), 255); + int g = kMin(gb + (f1 * (MAX_COLOR - gb) / 100), 255); + int b = kMin(bb + (f1 * (MAX_COLOR - bb) / 100), 255); + if ((r == rb) && (g == gb) && (b == bb)) + color = (color == Qt::white) ? LIGHT_GRAY : Qt::white; + else + color.setRgb(r, g, b); + } +} + +void RenderObject::drawBorder(QPainter *p, int x1, int y1, int x2, int y2, + BorderSide s, QColor c, const QColor& textcolor, EBorderStyle style, + int adjbw1, int adjbw2, bool invalidisInvert) +{ + int width = (s==BSTop||s==BSBottom?y2-y1:x2-x1); + + if(style == DOUBLE && width < 3) + style = SOLID; + + if(!c.isValid()) { + if(invalidisInvert) + { + p->setRasterOp(Qt::XorROP); + c = Qt::white; + } + else { + if(style == INSET || style == OUTSET || style == RIDGE || style == + GROOVE) + c = Qt::white; + else + c = textcolor; + } + } + + switch(style) + { + case BNATIVE: + case BNONE: + case BHIDDEN: + // should not happen + if(invalidisInvert && p->rasterOp() == Qt::XorROP) + p->setRasterOp(Qt::CopyROP); + + return; + case DOTTED: + if ( width == 1 ) { + // workaround Qt brokenness + p->setPen(QPen(c, width, Qt::SolidLine)); + switch(s) { + case BSBottom: + case BSTop: + for ( ; x1 < x2; x1 += 2 ) + p->drawPoint( x1, y1 ); + break; + case BSRight: + case BSLeft: + for ( ; y1 < y2; y1 += 2 ) + p->drawPoint( x1, y1 ); + } + break; + } + + p->setPen(QPen(c, width, Qt::DotLine)); + /* nobreak; */ + case DASHED: + if(style == DASHED) + p->setPen(QPen(c, width == 1 ? 0 : width, width == 1 ? Qt::DotLine : Qt::DashLine)); + + if (width > 0) + switch(s) { + case BSBottom: + case BSTop: + p->drawLine(x1, (y1+y2)/2, x2, (y1+y2)/2); + break; + case BSRight: + case BSLeft: + p->drawLine((x1+x2)/2, y1, (x1+x2)/2, y2); + break; + } + + break; + case DOUBLE: + { + int third = (width+1)/3; + + if (adjbw1 == 0 && adjbw2 == 0) + { + p->setPen(Qt::NoPen); + p->setBrush(c); + switch(s) + { + case BSTop: + case BSBottom: + p->drawRect(x1, y1 , x2-x1, third); + p->drawRect(x1, y2-third, x2-x1, third); + break; + case BSLeft: + p->drawRect(x1 , y1+1, third, y2-y1-1); + p->drawRect(x2-third, y1+1, third, y2-y1-1); + break; + case BSRight: + p->drawRect(x1 , y1+1, third, y2-y1-1); + p->drawRect(x2-third, y1+1, third, y2-y1-1); + break; + } + } + else + { + int adjbw1bigthird; + if (adjbw1>0) adjbw1bigthird = adjbw1+1; + else adjbw1bigthird = adjbw1 - 1; + adjbw1bigthird /= 3; + + int adjbw2bigthird; + if (adjbw2>0) adjbw2bigthird = adjbw2 + 1; + else adjbw2bigthird = adjbw2 - 1; + adjbw2bigthird /= 3; + + switch(s) + { + case BSTop: + drawBorder(p, x1+kMax((-adjbw1*2+1)/3,0), y1 , x2-kMax((-adjbw2*2+1)/3,0), y1 + third, s, c, textcolor, SOLID, adjbw1bigthird, adjbw2bigthird); + drawBorder(p, x1+kMax(( adjbw1*2+1)/3,0), y2 - third, x2-kMax(( adjbw2*2+1)/3,0), y2 , s, c, textcolor, SOLID, adjbw1bigthird, adjbw2bigthird); + break; + case BSLeft: + drawBorder(p, x1 , y1+kMax((-adjbw1*2+1)/3,0), x1+third, y2-kMax((-adjbw2*2+1)/3,0), s, c, textcolor, SOLID, adjbw1bigthird, adjbw2bigthird); + drawBorder(p, x2 - third, y1+kMax(( adjbw1*2+1)/3,0), x2 , y2-kMax(( adjbw2*2+1)/3,0), s, c, textcolor, SOLID, adjbw1bigthird, adjbw2bigthird); + break; + case BSBottom: + drawBorder(p, x1+kMax(( adjbw1*2+1)/3,0), y1 , x2-kMax(( adjbw2*2+1)/3,0), y1+third, s, c, textcolor, SOLID, adjbw1bigthird, adjbw2bigthird); + drawBorder(p, x1+kMax((-adjbw1*2+1)/3,0), y2-third, x2-kMax((-adjbw2*2+1)/3,0), y2 , s, c, textcolor, SOLID, adjbw1bigthird, adjbw2bigthird); + break; + case BSRight: + drawBorder(p, x1 , y1+kMax(( adjbw1*2+1)/3,0), x1+third, y2-kMax(( adjbw2*2+1)/3,0), s, c, textcolor, SOLID, adjbw1bigthird, adjbw2bigthird); + drawBorder(p, x2-third, y1+kMax((-adjbw1*2+1)/3,0), x2 , y2-kMax((-adjbw2*2+1)/3,0), s, c, textcolor, SOLID, adjbw1bigthird, adjbw2bigthird); + break; + default: + break; + } + } + break; + } + case RIDGE: + case GROOVE: + { + EBorderStyle s1; + EBorderStyle s2; + if (style==GROOVE) + { + s1 = INSET; + s2 = OUTSET; + } + else + { + s1 = OUTSET; + s2 = INSET; + } + + int adjbw1bighalf; + int adjbw2bighalf; + if (adjbw1>0) adjbw1bighalf=adjbw1+1; + else adjbw1bighalf=adjbw1-1; + adjbw1bighalf/=2; + + if (adjbw2>0) adjbw2bighalf=adjbw2+1; + else adjbw2bighalf=adjbw2-1; + adjbw2bighalf/=2; + + switch (s) + { + case BSTop: + drawBorder(p, x1+kMax(-adjbw1 ,0)/2, y1 , x2-kMax(-adjbw2,0)/2, (y1+y2+1)/2, s, c, textcolor, s1, adjbw1bighalf, adjbw2bighalf); + drawBorder(p, x1+kMax( adjbw1+1,0)/2, (y1+y2+1)/2, x2-kMax( adjbw2+1,0)/2, y2 , s, c, textcolor, s2, adjbw1/2, adjbw2/2); + break; + case BSLeft: + drawBorder(p, x1 , y1+kMax(-adjbw1 ,0)/2, (x1+x2+1)/2, y2-kMax(-adjbw2,0)/2, s, c, textcolor, s1, adjbw1bighalf, adjbw2bighalf); + drawBorder(p, (x1+x2+1)/2, y1+kMax( adjbw1+1,0)/2, x2 , y2-kMax( adjbw2+1,0)/2, s, c, textcolor, s2, adjbw1/2, adjbw2/2); + break; + case BSBottom: + drawBorder(p, x1+kMax( adjbw1 ,0)/2, y1 , x2-kMax( adjbw2,0)/2, (y1+y2+1)/2, s, c, textcolor, s2, adjbw1bighalf, adjbw2bighalf); + drawBorder(p, x1+kMax(-adjbw1+1,0)/2, (y1+y2+1)/2, x2-kMax(-adjbw2+1,0)/2, y2 , s, c, textcolor, s1, adjbw1/2, adjbw2/2); + break; + case BSRight: + drawBorder(p, x1 , y1+kMax( adjbw1 ,0)/2, (x1+x2+1)/2, y2-kMax( adjbw2,0)/2, s, c, textcolor, s2, adjbw1bighalf, adjbw2bighalf); + drawBorder(p, (x1+x2+1)/2, y1+kMax(-adjbw1+1,0)/2, x2 , y2-kMax(-adjbw2+1,0)/2, s, c, textcolor, s1, adjbw1/2, adjbw2/2); + break; + } + break; + } + case INSET: + case OUTSET: + calc3DColor(c, (style == OUTSET && (s == BSBottom || s == BSRight)) || + (style == INSET && ( s == BSTop || s == BSLeft ) ) ); + /* nobreak; */ + case SOLID: + p->setPen(Qt::NoPen); + p->setBrush(c); + Q_ASSERT(x2>=x1); + Q_ASSERT(y2>=y1); + if (adjbw1==0 && adjbw2 == 0) { + p->drawRect(x1,y1,x2-x1,y2-y1); + return; + } + QPointArray quad(4); + switch(s) { + case BSTop: + quad.setPoints(4, + x1+kMax(-adjbw1,0), y1, + x1+kMax( adjbw1,0), y2, + x2-kMax( adjbw2,0), y2, + x2-kMax(-adjbw2,0), y1); + break; + case BSBottom: + quad.setPoints(4, + x1+kMax( adjbw1,0), y1, + x1+kMax(-adjbw1,0), y2, + x2-kMax(-adjbw2,0), y2, + x2-kMax( adjbw2,0), y1); + break; + case BSLeft: + quad.setPoints(4, + x1, y1+kMax(-adjbw1,0), + x1, y2-kMax(-adjbw2,0), + x2, y2-kMax( adjbw2,0), + x2, y1+kMax( adjbw1,0)); + break; + case BSRight: + quad.setPoints(4, + x1, y1+kMax( adjbw1,0), + x1, y2-kMax( adjbw2,0), + x2, y2-kMax(-adjbw2,0), + x2, y1+kMax(-adjbw1,0)); + break; + } + p->drawConvexPolygon(quad); + break; + } + + if(invalidisInvert && p->rasterOp() == Qt::XorROP) + p->setRasterOp(Qt::CopyROP); +} + +void RenderObject::paintBorder(QPainter *p, int _tx, int _ty, int w, int h, const RenderStyle* style, bool begin, bool end) +{ + const QColor& tc = style->borderTopColor(); + const QColor& bc = style->borderBottomColor(); + const QColor& lc = style->borderLeftColor(); + const QColor& rc = style->borderRightColor(); + + bool tt = style->borderTopIsTransparent(); + bool bt = style->borderBottomIsTransparent(); + bool rt = style->borderRightIsTransparent(); + bool lt = style->borderLeftIsTransparent(); + + EBorderStyle ts = style->borderTopStyle(); + EBorderStyle bs = style->borderBottomStyle(); + EBorderStyle ls = style->borderLeftStyle(); + EBorderStyle rs = style->borderRightStyle(); + + bool render_t = ts > BHIDDEN && !tt; + bool render_l = ls > BHIDDEN && begin && !lt; + bool render_r = rs > BHIDDEN && end && !rt; + bool render_b = bs > BHIDDEN && !bt; + + if(render_t) { + bool ignore_left = + (tc == lc) && (tt == lt) && + (ts >= OUTSET) && + (ls == DOTTED || ls == DASHED || ls == SOLID || ls == OUTSET); + + bool ignore_right = + (tc == rc) && (tt == rt) && + (ts >= OUTSET) && + (rs == DOTTED || rs == DASHED || rs == SOLID || rs == INSET); + + drawBorder(p, _tx, _ty, _tx + w, _ty + style->borderTopWidth(), BSTop, tc, style->color(), ts, + ignore_left?0:style->borderLeftWidth(), + ignore_right?0:style->borderRightWidth()); + } + + if(render_b) { + bool ignore_left = + (bc == lc) && (bt == lt) && + (bs >= OUTSET) && + (ls == DOTTED || ls == DASHED || ls == SOLID || ls == OUTSET); + + bool ignore_right = + (bc == rc) && (bt == rt) && + (bs >= OUTSET) && + (rs == DOTTED || rs == DASHED || rs == SOLID || rs == INSET); + + drawBorder(p, _tx, _ty + h - style->borderBottomWidth(), _tx + w, _ty + h, BSBottom, bc, style->color(), bs, + ignore_left?0:style->borderLeftWidth(), + ignore_right?0:style->borderRightWidth()); + } + + if(render_l) + { + bool ignore_top = + (tc == lc) && (tt == lt) && + (ls >= OUTSET) && + (ts == DOTTED || ts == DASHED || ts == SOLID || ts == OUTSET); + + bool ignore_bottom = + (bc == lc) && (bt == lt) && + (ls >= OUTSET) && + (bs == DOTTED || bs == DASHED || bs == SOLID || bs == INSET); + + drawBorder(p, _tx, _ty, _tx + style->borderLeftWidth(), _ty + h, BSLeft, lc, style->color(), ls, + ignore_top?0:style->borderTopWidth(), + ignore_bottom?0:style->borderBottomWidth()); + } + + if(render_r) + { + bool ignore_top = + (tc == rc) && (tt == rt) && + (rs >= DOTTED || rs == INSET) && + (ts == DOTTED || ts == DASHED || ts == SOLID || ts == OUTSET); + + bool ignore_bottom = + (bc == rc) && (bt == rt) && + (rs >= DOTTED || rs == INSET) && + (bs == DOTTED || bs == DASHED || bs == SOLID || bs == INSET); + + drawBorder(p, _tx + w - style->borderRightWidth(), _ty, _tx + w, _ty + h, BSRight, rc, style->color(), rs, + ignore_top?0:style->borderTopWidth(), + ignore_bottom?0:style->borderBottomWidth()); + } +} + +void RenderObject::paintOutline(QPainter *p, int _tx, int _ty, int w, int h, const RenderStyle* style) +{ + int ow = style->outlineWidth(); + if(!ow) return; + + const QColor& oc = style->outlineColor(); + EBorderStyle os = style->outlineStyle(); + int offset = style->outlineOffset(); + +#ifdef APPLE_CHANGES + if (style->outlineStyleIsAuto()) { + p->initFocusRing(ow, offset, oc); + addFocusRingRects(p, _tx, _ty); + p->drawFocusRing(); + p->clearFocusRing(); + return; + } +#endif + + _tx -= offset; + _ty -= offset; + w += 2*offset; + h += 2*offset; + + drawBorder(p, _tx-ow, _ty-ow, _tx, _ty+h+ow, BSLeft, + QColor(oc), style->color(), + os, ow, ow, true); + + drawBorder(p, _tx-ow, _ty-ow, _tx+w+ow, _ty, BSTop, + QColor(oc), style->color(), + os, ow, ow, true); + + drawBorder(p, _tx+w, _ty-ow, _tx+w+ow, _ty+h+ow, BSRight, + QColor(oc), style->color(), + os, ow, ow, true); + + drawBorder(p, _tx-ow, _ty+h, _tx+w+ow, _ty+h+ow, BSBottom, + QColor(oc), style->color(), + os, ow, ow, true); + +} + +void RenderObject::paint( PaintInfo&, int /*tx*/, int /*ty*/) +{ +} + +void RenderObject::repaintRectangle(int x, int y, int w, int h, Priority p, bool f) +{ + if(parent()) parent()->repaintRectangle(x, y, w, h, p, f); +} + +#ifdef ENABLE_DUMP + +QString RenderObject::information() const +{ + QString str; + int x; int y; + absolutePosition(x,y); + x += inlineXPos(); + y += inlineYPos(); + QTextStream ts( &str, IO_WriteOnly ); + ts << renderName() + << "(" << (style() ? style()->refCount() : 0) << ")" + << ": " << (void*)this << " "; + ts << "{" << x << " " << y << "} "; + if (isInline()) ts << "il "; + if (childrenInline()) ts << "ci "; + if (isFloating()) ts << "fl "; + if (isAnonymous()) ts << "an "; + if (isRelPositioned()) ts << "rp "; + if (isPositioned()) ts << "ps "; + if (isReplaced()) ts << "rp "; + if (overhangingContents()) ts << "oc "; + if (needsLayout()) ts << "nl "; + if (minMaxKnown()) ts << "mmk "; + if (m_recalcMinMax) ts << "rmm "; + if (mouseInside()) ts << "mi "; + if (style() && style()->zIndex()) ts << "zI: " << style()->zIndex(); + if (style() && style()->hasAutoZIndex()) ts << "zI: auto "; + if (element()) { + if (element()->active()) ts << "act "; + if (element()->hasAnchor()) ts << "anchor "; + if (element()->focused()) ts << "focus "; + ts << " <" << getTagName(element()->id()) << ">"; + + } else if (isPseudoAnonymous() && style() && style()->styleType() != RenderStyle::NOPSEUDO) { + ts << " <" << getTagName(node()->id()); + QString pseudo; + switch (style()->styleType()) { + case RenderStyle::FIRST_LETTER: + pseudo = ":first-letter"; break; + case RenderStyle::BEFORE: + pseudo = ":before"; break; + case RenderStyle::AFTER: + pseudo = ":after"; break; + default: + pseudo = ":pseudo-element"; + } + ts << pseudo; + ts << ">"; + } + ts << " (" << xPos() << "," << yPos() << "," << width() << "," << height() << ")" + << " [" << minWidth() << "-" << maxWidth() << "]" + << " { mT: " << marginTop() << " qT: " << isTopMarginQuirk() + << " mB: " << marginBottom() << " qB: " << isBottomMarginQuirk() + << "}" + << (isTableCell() ? + ( QString::fromLatin1(" [r=") + + QString::number( static_cast<const RenderTableCell *>(this)->row() ) + + QString::fromLatin1(" c=") + + QString::number( static_cast<const RenderTableCell *>(this)->col() ) + + QString::fromLatin1(" rs=") + + QString::number( static_cast<const RenderTableCell *>(this)->rowSpan() ) + + QString::fromLatin1(" cs=") + + QString::number( static_cast<const RenderTableCell *>(this)->colSpan() ) + + QString::fromLatin1("]") ) : QString::null ); + if ( layer() ) + ts << " layer=" << layer(); + if ( continuation() ) + ts << " continuation=" << continuation(); + if (isText()) + ts << " \"" << QConstString(static_cast<const RenderText *>(this)->text(), kMin(static_cast<const RenderText *>(this)->length(), 10u)).string() << "\""; + return str; +} + +void RenderObject::printTree(int indent) const +{ + QString ind; + ind.fill(' ', indent); + + kdDebug() << ind << information() << endl; + + RenderObject *child = firstChild(); + while( child != 0 ) + { + child->printTree(indent+2); + child = child->nextSibling(); + } +} + +static QTextStream &operator<<(QTextStream &ts, const QRect &r) +{ + return ts << "at (" << r.x() << "," << r.y() << ") size " << r.width() << "x" << r.height(); +} + +//A bit like getTagName, but handles XML, too. +static QString lookupTagName(NodeImpl* node) { + return node->getDocument()->getName(NodeImpl::ElementId, node->id()).string(); +} + +void RenderObject::dump(QTextStream &ts, const QString &ind) const +{ + if ( !layer() ) + ts << endl; + + ts << ind << renderName(); + + if (style() && style()->zIndex()) { + ts << " zI: " << style()->zIndex(); + } + + if (element()) { + QString tagName(lookupTagName(element())); + if (!tagName.isEmpty()) { + ts << " {" << tagName << "}"; + } + } else if (isPseudoAnonymous() && style() && style()->styleType() != RenderStyle::NOPSEUDO) { + QString pseudo; + QString tagName(lookupTagName(node())); + switch (style()->styleType()) { + case RenderStyle::FIRST_LETTER: + pseudo = ":first-letter"; break; + case RenderStyle::BEFORE: + pseudo = ":before"; break; + case RenderStyle::AFTER: + pseudo = ":after"; break; + default: + pseudo = ":pseudo-element"; + } + ts << " {" << tagName << pseudo << "}"; + } + + QRect r(xPos(), yPos(), width(), height()); + ts << " " << r; + + if ( parent() ) + ts << style()->createDiff( *parent()->style() ); + + if (isAnonymous()) { ts << " anonymousBox"; } + if (isFloating()) { ts << " floating"; } + if (isPositioned()) { ts << " positioned"; } + if (isRelPositioned()) { ts << " relPositioned"; } + if (isText()) { ts << " text"; } + if (isInline()) { ts << " inline"; } + if (isReplaced()) { ts << " replaced"; } + if (shouldPaintBackgroundOrBorder()) { ts << " paintBackground"; } + if (needsLayout()) { ts << " needsLayout"; } + if (minMaxKnown()) { ts << " minMaxKnown"; } + if (overhangingContents()) { ts << " overhangingContents"; } + if (hasFirstLine()) { ts << " hasFirstLine"; } + if (afterPageBreak()) { ts << " afterPageBreak"; } +} +#endif + +void RenderObject::selectionStartEnd(int& spos, int& epos) +{ + if (parent()) + parent()->selectionStartEnd(spos, epos); +} + +void RenderObject::setStyle(RenderStyle *style) +{ + if (m_style == style) + return; + + RenderStyle::Diff d = m_style ? m_style->diff( style ) : RenderStyle::Layout; + //qDebug("m_style: %p new style, diff=%d", m_style, d); + + Priority pri = NormalPriority; + if (m_style) { + pri = HighPriority; + if ( d >= RenderStyle::Visible && !isText() && m_parent && + ( d == RenderStyle::Position || + m_style->outlineWidth() > style->outlineWidth() || + (!m_style->hidesOverflow() && style->hidesOverflow()) || + ( m_style->hasClip() && !(m_style->clip() == style->clip()) ) ) ) { + // schedule a repaint with the old style + if (layer() && !isInlineFlow()) + layer()->repaint(pri); + else + repaint(pri); + } + + if ( ( isFloating() && m_style->floating() != style->floating() ) || + ( isPositioned() && m_style->position() != style->position() && + style->position() != ABSOLUTE && style->position() != FIXED ) ) + removeFromObjectLists(); + + if ( layer() ) { + if ( ( m_style->hasAutoZIndex() != style->hasAutoZIndex() || + m_style->zIndex() != style->zIndex() || + m_style->visibility() != style->visibility() ) ) { + layer()->stackingContext()->dirtyZOrderLists(); + layer()->dirtyZOrderLists(); + } + } + + // reset style flags + m_floating = false; + m_positioned = false; + m_relPositioned = false; + m_paintBackground = false; + m_hasOverflowClip = false; + } + + // only honour z-index for non-static objects + // ### and objects with opacity + if ( style->position() == STATIC ) { + if ( isRoot() ) + style->setZIndex( 0 ); + else + style->setHasAutoZIndex(); + } + + RenderStyle *oldStyle = m_style; + m_style = style; + + updateBackgroundImages(oldStyle); + + m_style->ref(); + + if (oldStyle) + oldStyle->deref(); + + setShouldPaintBackgroundOrBorder(m_style->hasBorder() || m_style->hasBackground()); + + m_hasFirstLine = (style->getPseudoStyle(RenderStyle::FIRST_LINE) != 0); + if (m_parent) { + if (d == RenderStyle::Position && !attemptDirectLayerTranslation()) + d = RenderStyle::Layout; + + if ( d > RenderStyle::Position) { + // we must perform a full layout + if (!isText() && d == RenderStyle::CbLayout) { + dirtyFormattingContext( true ); + } + setNeedsLayoutAndMinMaxRecalc(); + } else if (!isText() && d >= RenderStyle::Visible) { + // a repaint is enough + if (layer()) { + if (canvas() && canvas()->needsWidgetMasks()) { + // update our widget masks + RenderLayer *p, *d = 0; + for (p=layer()->parent();p;p=p->parent()) + if (p->hasOverlaidWidgets()) d=p; + if (d) // deepest + d->updateWidgetMasks( canvas()->layer() ); + } + } + if (layer() && !isInlineFlow()) + layer()->repaint(pri); + else + repaint(pri); + } + } +} + +bool RenderObject::attemptDirectLayerTranslation() +{ + // When the difference between two successive styles is only 'Position' + // we may attempt to save a layout by directly updating the object position. + + KHTMLAssert( m_style->position() != STATIC ); + if (!layer()) + return false; + setInline(m_style->isDisplayInlineType()); + setPositioned(m_style->position() != RELATIVE); + setRelPositioned(m_style->position() == RELATIVE); + int oldXPos = xPos(); + int oldYPos = yPos(); + int oldWidth = width(); + int oldHeight = height(); + calcWidth(); + calcHeight(); + if (oldWidth != width() || oldHeight != height()) { + // implicit size change or overconstrained dimensions: + // we'll need a layout. + setWidth(oldWidth); + setHeight(oldHeight); + // kdDebug() << "Layer translation failed for " << information() << endl; + return false; + } + layer()->updateLayerPosition(); + if (m_style->position() != FIXED) { + bool needsDocSizeUpdate = true; + RenderObject *cb = container(); + while (cb) { + if (cb->hasOverflowClip() && cb->layer()) { + cb->layer()->checkScrollbarsAfterLayout(); + needsDocSizeUpdate = false; + break; + } + cb = cb->container(); + } + if (needsDocSizeUpdate && canvas()) { + bool posXOffset = (xPos()-oldXPos >= 0); + bool posYOffset = (yPos()-oldYPos >= 0); + canvas()->updateDocSizeAfterLayerTranslation(this, posXOffset, posYOffset); + } + } + // success + return true; +} + +void RenderObject::dirtyFormattingContext( bool checkContainer ) +{ + if (m_markedForRepaint && !checkContainer) + return; + m_markedForRepaint = true; + if (layer() && (style()->position() == FIXED || style()->position() == ABSOLUTE)) + return; + if (m_parent && (checkContainer || style()->width().isVariable() || style()->height().isVariable() || + !(isFloating() || flowAroundFloats() || isTableCell()))) + m_parent->dirtyFormattingContext(false); +} + +void RenderObject::repaintDuringLayout() +{ + if (canvas()->needsFullRepaint() || isText()) + return; + if (layer() && !isInlineFlow()) { + layer()->repaint( NormalPriority, true ); + } else { + repaint(); + canvas()->deferredRepaint( this ); + } +} + +void RenderObject::setOverhangingContents(bool p) +{ + if (m_overhangingContents == p) + return; + + RenderBlock *cb = containingBlock(); + if (p) + { + m_overhangingContents = true; + KHTMLAssert( cb != this || isCanvas()); + if (cb && cb != this) + cb->setOverhangingContents(); + } + else + { + RenderObject *n; + bool c=false; + + for( n = firstChild(); n != 0; n = n->nextSibling() ) + { + if (n->isPositioned() || n->overhangingContents()) + c=true; + } + + if (c) + return; + else + { + m_overhangingContents = false; + KHTMLAssert( cb != this ); + if (cb && cb != this) + cb->setOverhangingContents(false); + } + } +} + +void RenderObject::updateBackgroundImages(RenderStyle* oldStyle) +{ + // FIXME: This will be slow when a large number of images is used. Fix by using a dict. + const BackgroundLayer* oldLayers = oldStyle ? oldStyle->backgroundLayers() : 0; + const BackgroundLayer* newLayers = m_style ? m_style->backgroundLayers() : 0; + for (const BackgroundLayer* currOld = oldLayers; currOld; currOld = currOld->next()) { + if (currOld->backgroundImage() && (!newLayers || !newLayers->containsImage(currOld->backgroundImage()))) + currOld->backgroundImage()->deref(this); + } + for (const BackgroundLayer* currNew = newLayers; currNew; currNew = currNew->next()) { + if (currNew->backgroundImage() && (!oldLayers || !oldLayers->containsImage(currNew->backgroundImage()))) + currNew->backgroundImage()->ref(this); + } +} + +QRect RenderObject::viewRect() const +{ + return containingBlock()->viewRect(); +} + +bool RenderObject::absolutePosition(int &xPos, int &yPos, bool f) const +{ + RenderObject* p = parent(); + if (p) { + p->absolutePosition(xPos, yPos, f); + if ( p->hasOverflowClip() ) + p->layer()->subtractScrollOffset( xPos, yPos ); + return true; + } + else + { + xPos = yPos = 0; + return false; + } +} + +void RenderObject::caretPos(int /*offset*/, int /*flags*/, int &_x, int &_y, int &width, int &height) +{ + _x = _y = height = -1; + width = 1; // the caret has a default width of one pixel. If you want + // to check for validity, only test the x-coordinate for >= 0. +} + +int RenderObject::paddingTop() const +{ + int w = 0; + Length padding = m_style->paddingTop(); + if (padding.isPercent()) + w = containingBlock()->contentWidth(); + w = padding.minWidth(w); + if ( isTableCell() && padding.isVariable() ) + w = static_cast<const RenderTableCell *>(this)->table()->cellPadding(); + return w; +} + +int RenderObject::paddingBottom() const +{ + int w = 0; + Length padding = style()->paddingBottom(); + if (padding.isPercent()) + w = containingBlock()->contentWidth(); + w = padding.minWidth(w); + if ( isTableCell() && padding.isVariable() ) + w = static_cast<const RenderTableCell *>(this)->table()->cellPadding(); + return w; +} + +int RenderObject::paddingLeft() const +{ + int w = 0; + Length padding = style()->paddingLeft(); + if (padding.isPercent()) + w = containingBlock()->contentWidth(); + w = padding.minWidth(w); + if ( isTableCell() && padding.isVariable() ) + w = static_cast<const RenderTableCell *>(this)->table()->cellPadding(); + return w; +} + +int RenderObject::paddingRight() const +{ + int w = 0; + Length padding = style()->paddingRight(); + if (padding.isPercent()) + w = containingBlock()->contentWidth(); + w = padding.minWidth(w); + if ( isTableCell() && padding.isVariable() ) + w = static_cast<const RenderTableCell *>(this)->table()->cellPadding(); + return w; +} + +RenderObject *RenderObject::container() const +{ + // This method is extremely similar to containingBlock(), but with a few notable + // exceptions. + // (1) It can be used on orphaned subtrees, i.e., it can be called safely even when + // the object is not part of the primary document subtree yet. + // (2) For normal flow elements, it just returns the parent. + // (3) For absolute positioned elements, it will return a relative positioned inline. + // containingBlock() simply skips relpositioned inlines and lets an enclosing block handle + // the layout of the positioned object. This does mean that calcAbsoluteHorizontal and + // calcAbsoluteVertical have to use container(). + EPosition pos = m_style->position(); + RenderObject *o = 0; + if( pos == FIXED ) { + // container() can be called on an object that is not in the + // tree yet. We don't call canvas() since it will assert if it + // can't get back to the canvas. Instead we just walk as high up + // as we can. If we're in the tree, we'll get the root. If we + // aren't we'll get the root of our little subtree (most likely + // we'll just return 0). + o = parent(); + while ( o && o->parent() ) o = o->parent(); + } + else if ( pos == ABSOLUTE ) { + // Same goes here. We technically just want our containing block, but + // we may not have one if we're part of an uninstalled subtree. We'll + // climb as high as we can though. + o = parent(); + while (o && o->style()->position() == STATIC && !o->isCanvas()) + o = o->parent(); + } + else + o = parent(); + return o; +} + +DOM::DocumentImpl* RenderObject::document() const +{ + return m_node->getDocument(); +} + +void RenderObject::remove() +{ + if ( parent() ) + //have parent, take care of the tree integrity + parent()->removeChild(this); +} + +void RenderObject::removeFromObjectLists() +{ + // in destruction mode, don't care. + if ( !document()->renderer() ) return; + + if (isFloating()) { + RenderBlock* outermostBlock = containingBlock(); + for (RenderBlock* p = outermostBlock; p && !p->isCanvas() && p->containsFloat(this);) { + outermostBlock = p; + if (p->isFloatingOrPositioned()) + break; + p = p->containingBlock(); + } + + if (outermostBlock) + outermostBlock->markAllDescendantsWithFloatsForLayout(this); + } + + if (isPositioned()) { + RenderObject *p; + for (p = parent(); p; p = p->parent()) { + if (p->isRenderBlock()) + static_cast<RenderBlock*>(p)->removePositionedObject(this); + } + } +} + +RenderArena* RenderObject::renderArena() const +{ + return m_node->getDocument()->renderArena(); +} + +void RenderObject::detach() +{ + detachCounters(); + + deleteInlineBoxes(); + remove(); + + // make sure our DOM-node don't think we exist + if ( node() && node()->renderer() == this) + node()->setRenderer(0); + + // by default no refcounting + arenaDelete(renderArena(), this); +} + +void RenderObject::arenaDelete(RenderArena *arena, void *base) +{ +#ifndef NDEBUG + void *savedBase = baseOfRenderObjectBeingDeleted; + baseOfRenderObjectBeingDeleted = base; +#endif + delete this; +#ifndef NDEBUG + baseOfRenderObjectBeingDeleted = savedBase; +#endif + + // Recover the size left there for us by operator delete and free the memory. + arena->free(*(size_t *)base, base); +} + +void RenderObject::arenaDelete(RenderArena *arena) +{ + // static_cast unfortunately doesn't work, since we multiple inherit + // in eg. RenderWidget. + arenaDelete(arena, dynamic_cast<void *>(this)); +} + +FindSelectionResult RenderObject::checkSelectionPoint( int _x, int _y, int _tx, int _ty, DOM::NodeImpl*& node, int & offset, SelPointState &state ) +{ +#if 0 + NodeInfo info(true, false); + if ( nodeAtPoint( info, _x, _y, _tx, _ty ) && info.innerNode() ) + { + RenderObject* r = info.innerNode()->renderer(); + if ( r ) { + if ( r == this ) { + node = info.innerNode(); + offset = 0; // we have no text... + return SelectionPointInside; + } + else + return r->checkSelectionPoint( _x, _y, _tx, _ty, node, offset, state ); + } + } + //kdDebug(6030) << "nodeAtPoint Failed. Fallback - hmm, SelectionPointAfter" << endl; + node = 0; + offset = 0; + return SelectionPointAfter; +#endif + int off = offset; + DOM::NodeImpl* nod = node; + + for (RenderObject *child = firstChild(); child; child=child->nextSibling()) { + // ignore empty text boxes, they produce totally bogus information + // for caret navigation (LS) + if (child->isText() && !static_cast<RenderText *>(child)->inlineTextBoxCount()) + continue; + +// kdDebug(6040) << "iterating " << (child ? child->renderName() : "") << "@" << child << (child->isText() ? " contains: \"" + QConstString(static_cast<RenderText *>(child)->text(), kMin(static_cast<RenderText *>(child)->length(), 10u)).string() + "\"" : QString::null) << endl; +// kdDebug(6040) << "---------- checkSelectionPoint recursive -----------" << endl; + khtml::FindSelectionResult pos = child->checkSelectionPoint(_x, _y, _tx+xPos(), _ty+yPos(), nod, off, state); +// kdDebug(6040) << "-------- end checkSelectionPoint recursive ---------" << endl; +// kdDebug(6030) << this << " child->findSelectionNode returned result=" << pos << " nod=" << nod << " off=" << off << endl; + switch(pos) { + case SelectionPointBeforeInLine: + case SelectionPointInside: + //kdDebug(6030) << "RenderObject::checkSelectionPoint " << this << " returning SelectionPointInside offset=" << offset << endl; + node = nod; + offset = off; + return SelectionPointInside; + case SelectionPointBefore: + //x,y is before this element -> stop here + if ( state.m_lastNode ) { + node = state.m_lastNode; + offset = state.m_lastOffset; + //kdDebug(6030) << "RenderObject::checkSelectionPoint " << this << " before this child " + // << node << "-> returning SelectionPointInside, offset=" << offset << endl; + return SelectionPointInside; + } else { + node = nod; + offset = off; + //kdDebug(6030) << "RenderObject::checkSelectionPoint " << this << " before us -> returning SelectionPointBefore " << node << "/" << offset << endl; + return SelectionPointBefore; + } + break; + case SelectionPointAfter: + if (state.m_afterInLine) break; + // fall through + case SelectionPointAfterInLine: + if (pos == SelectionPointAfterInLine) state.m_afterInLine = true; + //kdDebug(6030) << "RenderObject::checkSelectionPoint: selection after: " << nod << " offset: " << off << " afterInLine: " << state.m_afterInLine << endl; + state.m_lastNode = nod; + state.m_lastOffset = off; + // No "return" here, obviously. We must keep looking into the children. + break; + } + } + // If we are after the last child, return lastNode/lastOffset + // But lastNode can be 0L if there is no child, for instance. + if ( state.m_lastNode ) + { + node = state.m_lastNode; + offset = state.m_lastOffset; + } + //kdDebug(6030) << "fallback - SelectionPointAfter node=" << node << " offset=" << offset << endl; + return SelectionPointAfter; +} + +bool RenderObject::mouseInside() const +{ + if (!m_mouseInside && continuation()) + return continuation()->mouseInside(); + return m_mouseInside; +} + +bool RenderObject::nodeAtPoint(NodeInfo& info, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction, bool inside) +{ + int tx = _tx + xPos(); + int ty = _ty + yPos(); + + inside |= ( style()->visibility() != HIDDEN && + (_y >= ty) && (_y < ty + height()) && (_x >= tx) && (_x < tx + width())) || isRoot() || isBody(); + bool inOverflowRect = inside; + if ( !inOverflowRect ) { + int ol = overflowLeft(); + int ot = overflowTop(); + QRect overflowRect( tx+ol, ty+ot, overflowWidth()-ol, overflowHeight()-ot ); + inOverflowRect = overflowRect.contains( _x, _y ); + } + + // ### table should have its own, more performant method + if (hitTestAction != HitTestSelfOnly && + (( !isRenderBlock() || + !static_cast<RenderBlock*>( this )->isPointInScrollbar( _x, _y, _tx, _ty )) && + (overhangingContents() || inOverflowRect || isInline() || isRoot() || isCanvas() || + isTableRow() || isTableSection() || inside || mouseInside() ))) { + if ( hitTestAction == HitTestChildrenOnly ) + inside = false; + if ( hasOverflowClip() && layer() ) + layer()->subtractScrollOffset(tx, ty); + for (RenderObject* child = lastChild(); child; child = child->previousSibling()) + if (!child->layer() && child->nodeAtPoint(info, _x, _y, tx, ty, HitTestAll)) + inside = true; + } + + if (inside) + setInnerNode(info); + + return inside; +} + + +void RenderObject::setInnerNode(NodeInfo& info) +{ + if (!info.innerNode() && !isInline() && continuation()) { + // We are in the margins of block elements that are part of a continuation. In + // this case we're actually still inside the enclosing inline element that was + // split. Go ahead and set our inner node accordingly. + info.setInnerNode(continuation()->element()); + if (!info.innerNonSharedNode()) + info.setInnerNonSharedNode(continuation()->element()); + } + + if (!info.innerNode() && element()) + info.setInnerNode(element()); + + if(!info.innerNonSharedNode() && element()) + info.setInnerNonSharedNode(element()); +} + + +short RenderObject::verticalPositionHint( bool firstLine ) const +{ + short vpos = m_verticalPosition; + if ( m_verticalPosition == PositionUndefined || firstLine ) { + vpos = getVerticalPosition( firstLine ); + if ( !firstLine ) + const_cast<RenderObject *>(this)->m_verticalPosition = vpos; + } + return vpos; + +} + +short RenderObject::getVerticalPosition( bool firstLine, RenderObject* ref ) const +{ + // vertical align for table cells has a different meaning + int vpos = 0; + if ( !isTableCell() && isInline() ) { + EVerticalAlign va = style()->verticalAlign(); + if ( va == TOP ) { + vpos = PositionTop; + } else if ( va == BOTTOM ) { + vpos = PositionBottom; + } else { + if (!ref) ref = parent(); + bool checkParent = ref->isInline() && !ref->isReplacedBlock() && + !( ref->style()->verticalAlign() == TOP || ref->style()->verticalAlign() == BOTTOM ); + vpos = checkParent ? ref->verticalPositionHint( firstLine ) : 0; + // don't allow elements nested inside text-top to have a different valignment. + if ( va == BASELINE ) + return vpos; + else if ( va == LENGTH ) + return vpos - style()->verticalAlignLength().width( lineHeight( firstLine ) ); + + const QFont &f = ref->font( firstLine ); + int fontsize = f.pixelSize(); + + if ( va == SUB ) + vpos += fontsize/5 + 1; + else if ( va == SUPER ) + vpos -= fontsize/3 + 1; + else if ( va == TEXT_TOP ) { + vpos += baselinePosition( firstLine ) - (QFontMetrics(f).ascent() + QFontMetrics(f).leading()/2); + } else if ( va == MIDDLE ) { + QRect b = QFontMetrics(f).boundingRect('x'); + vpos += -b.height()/2 - lineHeight( firstLine )/2 + baselinePosition( firstLine ); + } else if ( va == TEXT_BOTTOM ) { + vpos += QFontMetrics(f).descent() + QFontMetrics(f).leading()/2; + if ( !isReplaced() ) + vpos -= fontMetrics(firstLine).descent(); + } else if ( va == BASELINE_MIDDLE ) + vpos += - lineHeight( firstLine )/2 + baselinePosition( firstLine ); + } + } + return vpos; +} + +short RenderObject::lineHeight( bool firstLine ) const +{ + // Inline blocks are replaced elements. Otherwise, just pass off to + // the base class. If we're being queried as though we're the root line + // box, then the fact that we're an inline-block is irrelevant, and we behave + // just like a block. + + if (isReplaced() && (!isInlineBlockOrInlineTable() || !needsLayout())) + return height()+marginTop()+marginBottom(); + + Length lh; + if( firstLine && hasFirstLine() ) { + RenderStyle *pseudoStyle = style()->getPseudoStyle(RenderStyle::FIRST_LINE); + if ( pseudoStyle ) + lh = pseudoStyle->lineHeight(); + } + else + lh = style()->lineHeight(); + + // its "unset", choose nice default + if ( lh.value() < 0 ) + return style()->fontMetrics().lineSpacing(); + + if ( lh.isPercent() ) + return lh.minWidth( style()->font().pixelSize() ); + + // its fixed + return lh.value(); +} + +short RenderObject::baselinePosition( bool firstLine ) const +{ + // Inline blocks are replaced elements. Otherwise, just pass off to + // the base class. If we're being queried as though we're the root line + // box, then the fact that we're an inline-block is irrelevant, and we behave + // just like a block. + + if (isReplaced() && (!isInlineBlockOrInlineTable() || !needsLayout())) + return height()+marginTop()+marginBottom(); + + const QFontMetrics &fm = fontMetrics( firstLine ); + return fm.ascent() + ( lineHeight( firstLine) - fm.height() ) / 2; +} + +void RenderObject::invalidateVerticalPositions() +{ + m_verticalPosition = PositionUndefined; + RenderObject *child = firstChild(); + while( child ) { + child->invalidateVerticalPositions(); + child = child->nextSibling(); + } +} + +void RenderObject::recalcMinMaxWidths() +{ + KHTMLAssert( m_recalcMinMax ); + +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << " recalcMinMaxWidths() this=" << this <<endl; +#endif + + RenderObject *child = firstChild(); + int cmin=0; + int cmax=0; + + while( child ) { + bool test = false; + if ( ( m_minMaxKnown && child->m_recalcMinMax ) || !child->m_minMaxKnown ) { + cmin = child->minWidth(); + cmax = child->maxWidth(); + test = true; + } + if ( child->m_recalcMinMax ) + child->recalcMinMaxWidths(); + if ( !child->m_minMaxKnown ) + child->calcMinMaxWidth(); + if ( m_minMaxKnown && test && (cmin != child->minWidth() || cmax != child->maxWidth()) ) + m_minMaxKnown = false; + child = child->nextSibling(); + } + + // we need to recalculate, if the contains inline children, as the change could have + // happened somewhere deep inside the child tree + if ( ( !isInline() || isReplacedBlock() ) && childrenInline() ) + m_minMaxKnown = false; + + if ( !m_minMaxKnown ) + calcMinMaxWidth(); + m_recalcMinMax = false; +} + +void RenderObject::scheduleRelayout(RenderObject *clippedObj) +{ + if (!isCanvas()) return; + KHTMLView *view = static_cast<RenderCanvas *>(this)->view(); + if ( view ) + view->scheduleRelayout(clippedObj); +} + + +void RenderObject::removeLeftoverAnonymousBoxes() +{ +} + +InlineBox* RenderObject::createInlineBox(bool /*makePlaceHolderBox*/, bool /*isRootLineBox*/) +{ + KHTMLAssert(false); + return 0; +} + +void RenderObject::getTextDecorationColors(int decorations, QColor& underline, QColor& overline, + QColor& linethrough, bool quirksMode) +{ + RenderObject* curr = this; + do { + RenderStyle *st = curr->style(); + int currDecs = st->textDecoration(); + if (currDecs) { + if (currDecs & UNDERLINE) { + decorations &= ~UNDERLINE; + underline = st->color(); + } + if (currDecs & OVERLINE) { + decorations &= ~OVERLINE; + overline = st->color(); + } + if (currDecs & LINE_THROUGH) { + decorations &= ~LINE_THROUGH; + linethrough = st->color(); + } + } + curr = curr->parent(); + if (curr && curr->isRenderBlock() && curr->continuation()) + curr = curr->continuation(); + } while (curr && decorations && (!quirksMode || !curr->element() || + (curr->element()->id() != ID_A && curr->element()->id() != ID_FONT))); + + // If we bailed out, use the element we bailed out at (typically a <font> or <a> element). + if (decorations && curr) { + RenderStyle *st = curr->style(); + if (decorations & UNDERLINE) + underline = st->color(); + if (decorations & OVERLINE) + overline = st->color(); + if (decorations & LINE_THROUGH) + linethrough = st->color(); + } +} + +int RenderObject::maximalOutlineSize(PaintAction p) const +{ + if (p != PaintActionOutline) + return 0; + return static_cast<RenderCanvas*>(document()->renderer())->maximalOutlineSize(); +} + +void RenderObject::collectBorders(QValueList<CollapsedBorderValue>& borderStyles) +{ + for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) + curr->collectBorders(borderStyles); +} + +bool RenderObject::flowAroundFloats() const +{ + return isReplaced() || hasOverflowClip() || style()->flowAroundFloats(); +} + +bool RenderObject::usesLineWidth() const +{ + // 1. All auto-width objects that avoid floats should always use lineWidth + // 2. For objects with a specified width, we match WinIE's behavior: + // (a) tables use contentWidth + // (b) <hr>s use lineWidth + // (c) all other objects use lineWidth in quirks mode and contentWidth in strict mode. + return (flowAroundFloats() && (style()->width().isVariable() || isHR() || (style()->htmlHacks() && !isTable()))); +} + +bool RenderObject::hasCounter(const QString& counter) const +{ + if (style() && (!isText() || isCounter())) { + if (lookupCounter(counter)) return true; + if (style()->hasCounterReset(counter)) { + return true; + } + else if (style()->hasCounterIncrement(counter)) { + return true; + } + } + if (counter == "list-item") { + if (isListItem()) return true; + if (element() && ( + element()->id() == ID_OL || + element()->id() == ID_UL || + element()->id() == ID_MENU || + element()->id() == ID_DIR)) + return true; + } else + if (counter == "-khtml-quotes" && isQuote()) { + return (static_cast<const RenderQuote*>(this)->quoteCount() != 0); + } + return false; +} + +CounterNode* RenderObject::getCounter(const QString& counter, bool view, bool counters) +{ +// kdDebug( 6040 ) << renderName() << " getCounter(" << counter << ")" << endl; + + if (!style()) return 0; + + if (isText() && !isCounter()) return 0; + + CounterNode *i = lookupCounter(counter); + if (i) return i; + int val = 0; + + if (style()->hasCounterReset(counter) || isRoot()) { + i = new CounterReset(this); + val = style()->counterReset(counter); + if (style()->hasCounterIncrement(counter)) { + val += style()->counterIncrement(counter); + } +// kdDebug( 6040 ) << renderName() << " counter-reset: " << counter << " " << val << endl; + } + else + if (style()->hasCounterIncrement(counter)) { + i = new CounterNode(this); + val = style()->counterIncrement(counter); +// kdDebug( 6040 ) << renderName() << " counter-increment: " << counter << " " << val << endl; + } + else if (counter == "list-item") { + if (isListItem()) { + if (element() && element()->id() == ID_LI) { + DOMString v = static_cast<ElementImpl*>(element())->getAttribute(ATTR_VALUE); + if ( !v.isEmpty() ) { + i = new CounterReset(this); + val = v.toInt(); +// kdDebug( 6040 ) << renderName() << " counter-reset: " << counter << " " << val << endl; + } + } + if (!i) { + i = new CounterNode(this); + val = 1; +// kdDebug( 6040 ) << renderName() << " counter-increment: " << counter << " " << val << endl; + } + } + else + if (element() && element()->id() == ID_OL) { + i = new CounterReset(this); + DOMString v = static_cast<ElementImpl*>(element())->getAttribute(ATTR_START); + if ( !v.isEmpty() ) + val = v.toInt()-1; + else + val = 0; +// kdDebug( 6040 ) << renderName() << " counter-reset: " << counter << " " << val << endl; + } + else + if (element() && + (element()->id() == ID_UL || + element()->id() == ID_MENU|| + element()->id() == ID_DIR)) + { + i = new CounterReset(this); + val = 0; +// kdDebug( 6040 ) << renderName() << " counter-reset: " << counter << " " << val << endl; + } + } + else if (counter == "-khtml-quotes" && isQuote()) { + i = new CounterNode(this); + val = static_cast<RenderQuote*>(this)->quoteCount(); + } + + if (!i) { + i = new CounterNode(this); + val = 0; +// kdDebug( 6040 ) << renderName() << " counter-increment: " << counter << " " << val << endl; + } + i->setValue(val); + if (view) i->setIsVisual(); + if (counters) i->setHasCounters(); + + insertCounter(counter, i); + + if (!isRoot()) { + CounterNode *last=0, *current=0; + RenderObject *n = previousSibling(); + while(n) { + if (n->hasCounter(counter)) { + current = n->getCounter(counter); + break; + } + else + n = n->previousSibling(); + } + last = current; + + CounterNode *sibling = current; + // counter-reset on same render-level is our counter-parent + if (last) { + // Found render-sibling, now search for later counter-siblings among its render-children + n = n->lastChild(); + while (n) { + if (n->hasCounter(counter)) { + current = n->getCounter(counter); + if (last->parent() == current->parent() || sibling == current->parent()) { + last = current; + // If the current counter is not the last, search deeper + if (current->nextSibling()) { + n = n->lastChild(); + continue; + } + else + break; + } + } + n = n->previousSibling(); + } + if (sibling->isReset()) + { + if (last != sibling) + sibling->insertAfter(i, last); + else + sibling->insertAfter(i, 0); + } + else if (last->parent()) + last->parent()->insertAfter(i, last); + } + else if (parent()) { + // Nothing found among siblings, let our parent search + last = parent()->getCounter(counter, false); + if (last->isReset()) + last->insertAfter(i, 0); + else if (last->parent()) + last->parent()->insertAfter(i, last); + } + } + + return i; +} + +CounterNode* RenderObject::lookupCounter(const QString& counter) const +{ + QDict<khtml::CounterNode>* counters = document()->counters(this); + if (counters) + return counters->find(counter); + else + return 0; +} + +void RenderObject::detachCounters() +{ + QDict<khtml::CounterNode>* counters = document()->counters(this); + if (!counters) return; + + QDictIterator<khtml::CounterNode> i(*counters); + + while (i.current()) { + (*i)->remove(); + delete (*i); + ++i; + } + document()->removeCounters(this); +} + +void RenderObject::insertCounter(const QString& counter, CounterNode* val) +{ + QDict<khtml::CounterNode>* counters = document()->counters(this); + + if (!counters) { + counters = new QDict<khtml::CounterNode>(11); + document()->setCounters(this, counters); + } + + counters->insert(counter, val); +} + +void RenderObject::updateWidgetMasks() { + for (RenderObject* curr = firstChild(); curr; curr = curr->nextSibling()) { + if ( curr->isWidget() && static_cast<RenderWidget*>(curr)->needsMask() ) { + QWidget* w = static_cast<RenderWidget*>(curr)->widget(); + if (!w) + return; + RenderLayer* l = curr->enclosingStackingContext(); + QRegion r = l ? l->getMask() : QRegion(); + int x,y; + if (!r.isNull() && curr->absolutePosition(x,y)) { + int pbx = curr->borderLeft()+curr->paddingLeft(); + int pby = curr->borderTop()+curr->paddingTop(); + x+= pbx; + y+= pby; + r = r.intersect(QRect(x,y, + curr->width()-pbx-curr->borderRight()-curr->paddingRight(), + curr->height()-pby-curr->borderBottom()-curr->paddingBottom())); +#ifdef MASK_DEBUG + QMemArray<QRect> ar = r.rects(); + kdDebug(6040) << "|| Setting widget mask for " << curr->information() << endl; + for (int i = 0; i < ar.size() ; ++i) { + kdDebug(6040) << " " << ar[i] << endl; + } +#endif + r.translate(-x,-y); + w->setMask(r); + } else { + w->clearMask(); + } + } + else if (!curr->layer() || !curr->layer()->isStackingContext()) + curr->updateWidgetMasks(); + + } +} + +QRegion RenderObject::visibleFlowRegion(int x, int y) const +{ + QRegion r; + for (RenderObject* ro=firstChild();ro;ro=ro->nextSibling()) { + if( !ro->layer() && !ro->isInlineFlow() && ro->style()->visibility() == VISIBLE) { + const RenderStyle *s = ro->style(); + if (ro->isRelPositioned()) + static_cast<const RenderBox*>(ro)->relativePositionOffset(x,y); + if ( s->backgroundImage() || s->backgroundColor().isValid() || s->hasBorder() ) + r += QRect(x + ro->effectiveXPos(),y + ro->effectiveYPos(), ro->effectiveWidth(), ro->effectiveHeight()); + else + r += ro->visibleFlowRegion(x+ro->xPos(), y+ro->yPos()); + } + } + return r; +} + +#undef RED_LUMINOSITY +#undef GREEN_LUMINOSITY +#undef BLUE_LUMINOSITY +#undef INTENSITY_FACTOR +#undef LIGHT_FACTOR +#undef LUMINOSITY_FACTOR + +#undef MAX_COLOR +#undef COLOR_DARK_THRESHOLD +#undef COLOR_LIGHT_THRESHOLD + +#undef COLOR_LITE_BS_FACTOR +#undef COLOR_LITE_TS_FACTOR + +#undef COLOR_DARK_BS_FACTOR +#undef COLOR_DARK_TS_FACTOR + +#undef LIGHT_GRAY +#undef DARK_GRAY + diff --git a/khtml/rendering/render_object.h b/khtml/rendering/render_object.h new file mode 100644 index 000000000..6b98a5fa4 --- /dev/null +++ b/khtml/rendering/render_object.h @@ -0,0 +1,865 @@ +/* + * This file is part of the html renderer for KDE. + * + * Copyright (C) 2000-2003 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000-2003 Dirk Mueller (mueller@kde.org) + * (C) 2002-2003 Apple Computer, Inc. + * (C) 2004 Allan Sandfeld Jensen + * + * 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. + * + */ +#ifndef render_object_h +#define render_object_h + +#include <qcolor.h> +#include <qrect.h> +#include <assert.h> +#include <qvaluelist.h> + +#include <kdebug.h> +#include <kglobal.h> + +#include "xml/dom_docimpl.h" +#include "misc/khtmllayout.h" +#include "misc/loader_client.h" +#include "misc/helper.h" +#include "rendering/render_style.h" + +class QPainter; +class QTextStream; +class CSSStyle; +class KHTMLView; + +#ifndef NDEBUG +#define KHTMLAssert( x ) if( !(x) ) { \ + const RenderObject *o = this; while( o->parent() ) o = o->parent(); \ + o->printTree(); \ + qDebug(" this object = %p, %s", (void*) this, kdBacktrace().latin1() ); \ + assert( x ); \ +} +#else +#define KHTMLAssert( x ) +#endif + +/* + * The painting of a layer occurs in three distinct phases. Each phase involves + * a recursive descent into the layer's render objects. The first phase is the background phase. + * The backgrounds and borders of all blocks are painted. Inlines are not painted at all. + * Floats must paint above block backgrounds but entirely below inline content that can overlap them. + * In the foreground phase, all inlines are fully painted. Inline replaced elements will get all + * three phases invoked on them during this phase. + */ + +typedef enum { + PaintActionElementBackground = 0, + PaintActionChildBackground, + PaintActionChildBackgrounds, + PaintActionFloat, + PaintActionForeground, + PaintActionOutline, + PaintActionSelection, + PaintActionCollapsedTableBorders +} PaintAction; + +typedef enum { + HitTestAll = 0, + HitTestSelfOnly = 1, + HitTestChildrenOnly = 2 +} HitTestAction; + +typedef enum { + PageBreakNormal = 0, // all rules apply + PageBreakHarder = 1, // page-break-inside: avoid is ignored + PageBreakForced = 2 // page-break-after/before: avoid, orphans and widows ignored +} PageBreakLevel; + +typedef enum { + LowPriority = 0, + NormalPriority = 1, + HighPriority = 2, + RealtimePriority = 3 +} Priority; + +inline PageBreakLevel operator| (PageBreakLevel a, PageBreakLevel b) { + if (a == PageBreakForced || b == PageBreakForced) + return PageBreakForced; + if (a == PageBreakHarder || b == PageBreakHarder) + return PageBreakHarder; + return PageBreakNormal; +} + +namespace DOM { + class HTMLAreaElementImpl; + class DOMString; + class NodeImpl; + class DocumentImpl; + class ElementImpl; + class EventImpl; +} + +namespace khtml { + class RenderFlow; + class RenderStyle; + class RenderTable; + class CachedObject; + class RenderObject; + class RenderCanvas; + class RenderText; + class RenderFrameSet; + class RenderArena; + class RenderLayer; + class RenderBlock; + class InlineBox; + class InlineFlowBox; + class CounterNode; + +/** + * Base Class for all rendering tree objects. + */ +class RenderObject : public CachedObjectClient +{ + RenderObject(const RenderObject&); + RenderObject& operator=(const RenderObject&); +public: + + RenderObject(DOM::NodeImpl* node); + virtual ~RenderObject(); + + RenderObject *parent() const { return m_parent; } + + RenderObject *previousSibling() const { return m_previous; } + RenderObject *nextSibling() const { return m_next; } + + virtual RenderObject *firstChild() const { return 0; } + virtual RenderObject *lastChild() const { return 0; } + + RenderObject *nextRenderer() const; + RenderObject *previousRenderer() const; + + virtual bool childAllowed() const { return false; } + virtual int borderTopExtra() const { return 0; } + virtual int borderBottomExtra() const { return 0; } + + virtual RenderLayer* layer() const { return 0; } + RenderLayer* enclosingLayer() const; + RenderLayer* enclosingStackingContext() const; + void addLayers(RenderLayer* parentLayer, RenderObject* newObject); + void removeLayers(RenderLayer* parentLayer); + void moveLayers(RenderLayer* oldParent, RenderLayer* newParent); + RenderLayer* findNextLayer(RenderLayer* parentLayer, RenderObject* startPoint, + bool checkParent=true); + virtual void positionChildLayers() { } + virtual bool requiresLayer() const { + return isRoot()/* ### */ || isPositioned() || isRelPositioned() || hasOverflowClip(); + } + + // ### rename to overflowClipRect and clipRect + virtual QRect getOverflowClipRect(int /*tx*/, int /*ty*/) + { return QRect(0,0,0,0); } + virtual QRect getClipRect(int /*tx*/, int /*ty*/) { return QRect(0,0,0,0); } + bool hasClip() const { return isPositioned() && style()->hasClip(); } + bool hasOverflowClip() const { return m_hasOverflowClip; } + + bool scrollsOverflow() const { return scrollsOverflowX() || scrollsOverflowY(); } + bool scrollsOverflowX() const { return hasOverflowClip() && (style()->overflowX() == OSCROLL || style()->overflowX() == OAUTO); } + bool scrollsOverflowY() const { return hasOverflowClip() && (style()->overflowY() == OSCROLL || style()->overflowY() == OAUTO); } + + virtual int getBaselineOfFirstLineBox() { return -1; } // Tables and blocks implement this. + virtual InlineFlowBox* getFirstLineBox() { return 0; } // Tables and blocks implement this. + + // Whether or not a positioned element requires normal flow x/y to be computed + // to determine its position. + bool hasStaticX() const; + bool hasStaticY() const; + + // Linear tree traversal + RenderObject *objectBelow() const; + RenderObject *objectAbove() const; + + // Returns if an object has counter-increment or counter-reset + bool hasCounter(const QString& counter) const; + // Calculates the value of the counter + CounterNode* getCounter(const QString& counter, bool view = false, bool counters = false); + // Detaches all counterNodes + void detachCounters(); + + +protected: + // Helper functions for counter-cache + void insertCounter(const QString& counter, CounterNode* value); + CounterNode* lookupCounter(const QString& counter) const; + +public: + ////////////////////////////////////////// + // RenderObject tree manipulation + virtual void addChild(RenderObject *newChild, RenderObject *beforeChild = 0); + void removeChild(RenderObject *oldChild); + + // raw tree manipulation + virtual RenderObject* removeChildNode(RenderObject* child); + virtual void appendChildNode(RenderObject* child); + virtual void insertChildNode(RenderObject* child, RenderObject* before); + ////////////////////////////////////////// + + ////////////////////////////////////////// + // Helper functions. Dangerous to use! + void setPreviousSibling(RenderObject *previous) { m_previous = previous; } + void setNextSibling(RenderObject *next) { m_next = next; } + void setParent(RenderObject *parent) { m_parent = parent; } + ////////////////////////////////////////// + +public: + virtual const char *renderName() const { return "RenderObject"; } +#ifdef ENABLE_DUMP + QString information() const; + virtual void printTree(int indent=0) const; + virtual void dump(QTextStream &stream, const QString &ind = QString::null) const; +#endif + + static RenderObject *createObject(DOM::NodeImpl* node, RenderStyle* style); + + // Overloaded new operator. Derived classes must override operator new + // in order to allocate out of the RenderArena. + void* operator new(size_t sz, RenderArena* renderArena) throw(); + + // Overridden to prevent the normal delete from being called. + void operator delete(void* ptr, size_t sz); + +private: + // The normal operator new is disallowed on all render objects. + void* operator new(size_t sz); + +public: + RenderArena* renderArena() const; + virtual RenderFlow* continuation() const { return 0; } + virtual bool isInlineContinuation() const { return false; } + + + bool isRoot() const { return m_isRoot && !m_isAnonymous; } + void setIsRoot(bool b) { m_isRoot = b; } + bool isHR() const; + // some helper functions... + virtual bool isRenderBlock() const { return false; } + virtual bool isRenderInline() const { return false; } + virtual bool isInlineFlow() const { return false; } + virtual bool isBlockFlow() const { return false; } + virtual bool isInlineBlockOrInlineTable() const { return false; } + virtual bool childrenInline() const { return false; } + virtual bool isBox() const { return false; } + virtual bool isRenderReplaced() const { return false; } + + virtual bool isGlyph() const { return false; } + virtual bool isCounter() const { return false; } + virtual bool isQuote() const { return false; } + virtual bool isListItem() const { return false; } + virtual bool isListMarker() const { return false; } + virtual bool isCanvas() const { return false; } + virtual bool isBR() const { return false; } + virtual bool isTableCell() const { return false; } + virtual bool isTableRow() const { return false; } + virtual bool isTableSection() const { return false; } + virtual bool isTableCol() const { return false; } + virtual bool isTable() const { return false; } + virtual bool isWidget() const { return false; } + virtual bool isBody() const { return false; } + virtual bool isFormElement() const { return false; } + virtual bool isFrameSet() const { return false; } + virtual bool isApplet() const { return false; } + + bool isHTMLMarquee() const; + + bool isAnonymous() const { return m_isAnonymous; } + void setIsAnonymous(bool b) { m_isAnonymous = b; } + bool isAnonymousBlock() const { return isAnonymous() && style()->display() == BLOCK && node()->isDocumentNode(); } + bool isPseudoAnonymous() const { return isAnonymous() && !node()->isDocumentNode(); } + + bool isFloating() const { return m_floating; } + bool isPositioned() const { return m_positioned; } + bool isRelPositioned() const { return m_relPositioned; } + bool isText() const { return m_isText; } + bool isInline() const { return m_inline; } + bool isCompact() const { return style()->display() == COMPACT; } // compact + bool isRunIn() const { return style()->display() == RUN_IN; } // run-in object + bool mouseInside() const; + bool isReplaced() const { return m_replaced; } + bool isReplacedBlock() const { return isInline() && isReplaced() && isRenderBlock(); } + bool shouldPaintBackgroundOrBorder() const { return m_paintBackground; } + bool needsLayout() const { return m_needsLayout || m_normalChildNeedsLayout || m_posChildNeedsLayout; } + bool markedForRepaint() const { return m_markedForRepaint; } + void setMarkedForRepaint(bool m) { m_markedForRepaint = m; } + bool selfNeedsLayout() const { return m_needsLayout; } + bool posChildNeedsLayout() const { return m_posChildNeedsLayout; } + bool normalChildNeedsLayout() const { return m_normalChildNeedsLayout; } + bool minMaxKnown() const{ return m_minMaxKnown; } + bool overhangingContents() const { return m_overhangingContents; } + bool hasFirstLine() const { return m_hasFirstLine; } + bool isSelectionBorder() const { return m_isSelectionBorder; } + bool recalcMinMax() const { return m_recalcMinMax; } + + RenderCanvas* canvas() const; + // don't even think about making this method virtual! + DOM::DocumentImpl* document() const; + DOM::NodeImpl* element() const { return isAnonymous() ? 0L : m_node; } + DOM::NodeImpl* node() const { return m_node; } + + /** + * returns the object containing this one. can be different from parent for + * positioned elements + */ + RenderObject *container() const; + + void setOverhangingContents(bool p=true); + void markContainingBlocksForLayout(); + void dirtyFormattingContext( bool checkContainer ); + void repaintDuringLayout(); + void setNeedsLayout(bool b, bool markParents = true); + void setChildNeedsLayout(bool b, bool markParents = true); + void setMinMaxKnown(bool b=true) { + m_minMaxKnown = b; + if ( !b ) { + RenderObject *o = this; + RenderObject *root = this; + while( o ) { // ### && !o->m_recalcMinMax ) { + o->m_recalcMinMax = true; + root = o; + o = o->m_parent; + } + } + } + void setNeedsLayoutAndMinMaxRecalc() { + setMinMaxKnown(false); + setNeedsLayout(true); + } + void setPositioned(bool b=true) { m_positioned = b; } + void setRelPositioned(bool b=true) { m_relPositioned = b; } + void setFloating(bool b=true) { m_floating = b; } + void setInline(bool b=true) { m_inline = b; } + void setMouseInside(bool b=true) { m_mouseInside = b; } + void setShouldPaintBackgroundOrBorder(bool b=true) { m_paintBackground = b; } + void setRenderText() { m_isText = true; } + void setReplaced(bool b=true) { m_replaced = b; } + void setHasOverflowClip(bool b = true) { m_hasOverflowClip = b; } + void setIsSelectionBorder(bool b=true) { m_isSelectionBorder = b; } + + void scheduleRelayout(RenderObject *clippedObj = 0); + + void updateBackgroundImages(RenderStyle* oldStyle); + + virtual InlineBox* createInlineBox(bool makePlaceHolderBox, bool isRootLineBox); + + virtual short lineHeight( bool firstLine ) const; + virtual short verticalPositionHint( bool firstLine ) const; + virtual short baselinePosition( bool firstLine ) const; + short getVerticalPosition( bool firstLine, RenderObject* ref=0 ) const; + + /* + * Print the object and its children, clipped by (x|y|w|h). + * (tx|ty) is the calculated position of the parent + */ + struct PaintInfo { + PaintInfo(QPainter* _p, const QRect& _r, PaintAction _phase) + : p(_p), r(_r), phase(_phase), outlineObjects(0) {} + ~PaintInfo() { delete outlineObjects; } + QPainter* p; + QRect r; + PaintAction phase; + QValueList<RenderFlow *>* outlineObjects; // used to list which outlines should be painted by a block with inline children + }; + virtual void paint( PaintInfo& i, int tx, int ty); + + void paintBorder(QPainter *p, int _tx, int _ty, int w, int h, const RenderStyle* style, bool begin=true, bool end=true); + void paintOutline(QPainter *p, int _tx, int _ty, int w, int h, const RenderStyle* style); + + virtual void paintBoxDecorations(PaintInfo&, int /*_tx*/, int /*_ty*/) {} + + virtual void paintBackgroundExtended(QPainter* /*p*/, const QColor& /*c*/, const BackgroundLayer */*bgLayer*/, + int /*clipy*/, int /*cliph*/, int /*_tx*/, int /*_ty*/, + int /*w*/, int /*height*/, int /*bleft*/, int /*bright*/, int /*pleft*/, int /*pright*/ ) {} + + + /* + * This function calculates the minimum & maximum width that the object + * can be set to. + * + * when the Element calls setMinMaxKnown(true), calcMinMaxWidth() will + * be no longer called. + * + * when a element has a fixed size, m_minWidth and m_maxWidth should be + * set to the same value. This has the special meaning that m_width, + * contains the actual value. + * + * assumes calcMinMaxWidth has already been called for all children. + */ + virtual void calcMinMaxWidth() { } + + /* + * Does the min max width recalculations after changes. + */ + void recalcMinMaxWidths(); + + /* + * Calculates the actual width of the object (only for non inline + * objects) + */ + virtual void calcWidth() {} + + /* + * Calculates the actual width of the object (only for non inline + * objects) + */ + virtual void calcHeight() {} + + /* + * This function should cause the Element to calculate its + * width and height and the layout of its content + * + * when the Element calls setNeedsLayout(false), layout() is no + * longer called during relayouts, as long as there is no + * style sheet change. When that occurs, m_needsLayout will be + * set to true and the Element receives layout() calls + * again. + */ + virtual void layout() = 0; + + /* This function performs a layout only if one is needed. */ + void layoutIfNeeded() { if (needsLayout()) layout(); } + + // used for element state updates that can not be fixed with a + // repaint and do not need a relayout + virtual void updateFromElement() {} + + // Called immediately after render-object is inserted + virtual void attach() { m_attached = true; } + bool attached() { return m_attached; } + // The corresponding closing element has been parsed. ### remove me + virtual void close() { } + + virtual int availableHeight() const { return 0; } + + // Whether or not the element shrinks to its max width (rather than filling the width + // of a containing block). HTML4 buttons, legends, and floating/compact elements do this. + bool sizesToMaxWidth() const; + + /* + * NeesPageClear indicates the object crossed a page-break but could not break itself and now + * needs to be moved clear by its parent. + */ + void setNeedsPageClear(bool b = true) { m_needsPageClear = b; } + virtual bool needsPageClear() const { return m_needsPageClear; } + + /* + * ContainsPageBreak indicates the object contains a clean page-break. + * ### should be removed and replaced with (crossesPageBreak && !needsPageClear) + */ + void setContainsPageBreak(bool b = true) { m_containsPageBreak = b; } + virtual bool containsPageBreak() const { return m_containsPageBreak; } + + virtual int pageTopAfter(int y) const { if (parent()) return parent()->pageTopAfter(y); else return 0; } + + virtual int crossesPageBreak(int top, int bottom) const + { if (parent()) return parent()->crossesPageBreak(top, bottom); else return 0; } + + // Checks if a page-break before child is possible at the given page-break level + // false means the child should attempt the break self. + virtual bool canClear(RenderObject */*child*/, PageBreakLevel level) + { if (parent()) return parent()->canClear(this, level); else return false; } + + void setAfterPageBreak(bool b = true) { m_afterPageBreak = b; }; + void setBeforePageBreak(bool b = true) { m_beforePageBreak = b; }; + virtual bool afterPageBreak() const { return m_afterPageBreak; } + virtual bool beforePageBreak() const { return m_beforePageBreak; } + + // does a query on the rendertree and finds the innernode + // and overURL for the given position + // if readonly == false, it will recalc hover styles accordingly + class NodeInfo + { + friend class RenderImage; + friend class RenderFlow; + friend class RenderInline; + friend class RenderText; + friend class RenderWidget; + friend class RenderObject; + friend class RenderFrameSet; + friend class RenderLayer; + friend class DOM::HTMLAreaElementImpl; + public: + NodeInfo(bool readonly, bool active) + : m_innerNode(0), m_innerNonSharedNode(0), m_innerURLElement(0), m_readonly(readonly), m_active(active) + { } + + DOM::NodeImpl* innerNode() const { return m_innerNode; } + DOM::NodeImpl* innerNonSharedNode() const { return m_innerNonSharedNode; } + DOM::NodeImpl* URLElement() const { return m_innerURLElement; } + bool readonly() const { return m_readonly; } + bool active() const { return m_active; } + + private: + void setInnerNode(DOM::NodeImpl* n) { m_innerNode = n; } + void setInnerNonSharedNode(DOM::NodeImpl* n) { m_innerNonSharedNode = n; } + void setURLElement(DOM::NodeImpl* n) { m_innerURLElement = n; } + + DOM::NodeImpl* m_innerNode; + DOM::NodeImpl* m_innerNonSharedNode; + DOM::NodeImpl* m_innerURLElement; + bool m_readonly; + bool m_active; + }; + + /** contains stateful information for a checkSelectionPoint call + */ + struct SelPointState { + /** last node that was before the current position */ + DOM::NodeImpl *m_lastNode; + /** offset of last node */ + long m_lastOffset; + /** true when the last node had the result SelectionAfterInLine */ + bool m_afterInLine; + + SelPointState() : m_lastNode(0), m_lastOffset(0), m_afterInLine(false) + {} + }; + + virtual FindSelectionResult checkSelectionPoint( int _x, int _y, int _tx, int _ty, + DOM::NodeImpl*&, int & offset, + SelPointState & ); + virtual bool nodeAtPoint(NodeInfo& info, int x, int y, int tx, int ty, HitTestAction, bool inside = false); + void setInnerNode(NodeInfo& info); + + // set the style of the object. + virtual void setStyle(RenderStyle *style); + + // returns the containing block level element for this element. + RenderBlock *containingBlock() const; + + // return just the width of the containing block + virtual short containingBlockWidth() const; + // return just the height of the containing block + virtual int containingBlockHeight() const; + + // size of the content area (box size minus padding/border) + virtual short contentWidth() const { return 0; } + virtual int contentHeight() const { return 0; } + + // intrinsic extend of replaced elements. undefined otherwise + virtual short intrinsicWidth() const { return 0; } + virtual int intrinsicHeight() const { return 0; } + + // relative to parent node + virtual void setPos( int /*xPos*/, int /*yPos*/ ) { } + virtual void setWidth( int /*width*/ ) { } + virtual void setHeight( int /*height*/ ) { } + + virtual int xPos() const { return 0; } + virtual int yPos() const { return 0; } + + /** the position of the object from where it begins drawing, including + * its negative overflow + */ + int effectiveXPos() const { return xPos() + (hasOverflowClip() ? 0 : overflowLeft()); } + + /** the position of the object from where it begins drawing, including + * its negative overflow + */ + int effectiveYPos() const { return yPos() + (hasOverflowClip() ? -borderTopExtra() : kMin(overflowTop(), -borderTopExtra())); } + + /** Leftmost coordinate of this inline element relative to containing + * block. Always zero for non-inline elements. + */ + virtual int inlineXPos() const { return 0; } + /** Topmost coordinate of this inline element relative to containing + * block. Always zero for non-inline elements. + */ + virtual int inlineYPos() const { return 0; } + + // calculate client position of box + virtual bool absolutePosition(int &/*xPos*/, int &/*yPos*/, bool fixed = false) const; + + // width and height are without margins but include paddings and borders + virtual short width() const { return 0; } + virtual int height() const { return 0; } + + // The height of a block when you include overflow spillage out of + // the bottom of the block (e.g., a <div style="height:25px"> that + // has a 100px tall image inside it would have an overflow height + // of borderTop() + paddingTop() + 100px. + virtual int overflowHeight() const { return height(); } + virtual int overflowWidth() const { return width(); } + // how much goes over the left hand side (0 or a negative number) + virtual int overflowTop() const { return 0; } + virtual int overflowLeft() const { return 0; } + + /** + * Returns the height that is effectively considered when contemplating the + * object as a whole -- usually the overflow height, or the height if clipped. + */ + int effectiveHeight() const { return hasOverflowClip() ? height() + borderTopExtra() + borderBottomExtra() : + kMax(overflowHeight() - overflowTop(), height() + borderTopExtra() + borderBottomExtra()); } + /** + * Returns the width that is effectively considered when contemplating the + * object as a whole -- usually the overflow width, or the width if clipped. + */ + int effectiveWidth() const { return hasOverflowClip() ? width() : overflowWidth() - overflowLeft(); } + + // IE extensions, heavily used in ECMA + virtual short offsetWidth() const { return width(); } + virtual int offsetHeight() const { return height() + borderTopExtra() + borderBottomExtra(); } + virtual int offsetLeft() const; + virtual int offsetTop() const; + virtual RenderObject* offsetParent() const; + short clientWidth() const; + int clientHeight() const; + short scrollWidth() const; + int scrollHeight() const; + + virtual bool isSelfCollapsingBlock() const { return false; } + short collapsedMarginTop() const { return maxTopMargin(true)-maxTopMargin(false); } + short collapsedMarginBottom() const { return maxBottomMargin(true)-maxBottomMargin(false); } + + virtual bool isTopMarginQuirk() const { return false; } + virtual bool isBottomMarginQuirk() const { return false; } + virtual short maxTopMargin(bool positive) const + { return positive ? kMax( int( marginTop() ), 0 ) : - kMin( int( marginTop() ), 0 ); } + virtual short maxBottomMargin(bool positive) const + { return positive ? kMax( int( marginBottom() ), 0 ) : - kMin( int( marginBottom() ), 0 ); } + + virtual short marginTop() const { return 0; } + virtual short marginBottom() const { return 0; } + virtual short marginLeft() const { return 0; } + virtual short marginRight() const { return 0; } + + virtual int paddingTop() const; + virtual int paddingBottom() const; + virtual int paddingLeft() const; + virtual int paddingRight() const; + + virtual int borderTop() const { return style()->borderTopWidth(); } + virtual int borderBottom() const { return style()->borderBottomWidth(); } + virtual int borderLeft() const { return style()->borderLeftWidth(); } + virtual int borderRight() const { return style()->borderRightWidth(); } + + virtual short minWidth() const { return 0; } + virtual int maxWidth() const { return 0; } + + RenderStyle* style() const { return m_style; } + RenderStyle* style( bool firstLine ) const { + RenderStyle *s = m_style; + if( firstLine && hasFirstLine() ) { + RenderStyle *pseudoStyle = style()->getPseudoStyle(RenderStyle::FIRST_LINE); + if ( pseudoStyle ) + s = pseudoStyle; + } + return s; + } + + void getTextDecorationColors(int decorations, QColor& underline, QColor& overline, + QColor& linethrough, bool quirksMode=false); + + enum BorderSide { + BSTop, BSBottom, BSLeft, BSRight + }; + void drawBorder(QPainter *p, int x1, int y1, int x2, int y2, BorderSide s, + QColor c, const QColor& textcolor, EBorderStyle style, + int adjbw1, int adjbw2, bool invalidisInvert = false); + + // Used by collapsed border tables. + virtual void collectBorders(QValueList<CollapsedBorderValue>& borderStyles); + + // force a complete repaint + virtual void repaint(Priority p = NormalPriority) { if(m_parent) m_parent->repaint(p); } + virtual void repaintRectangle(int x, int y, int w, int h, Priority p=NormalPriority, bool f=false); + + virtual unsigned int length() const { return 1; } + + virtual bool isHidden() const { return isFloating() || isPositioned(); } + + // Special objects are objects that are neither really inline nor blocklevel + bool isFloatingOrPositioned() const { return (isFloating() || isPositioned()); }; + virtual bool hasOverhangingFloats() const { return false; } + virtual bool hasFloats() const { return false; } + virtual bool containsFloat(RenderObject* /*o*/) const { return false; } + virtual void markAllDescendantsWithFloatsForLayout(RenderObject* /*floatToRemove*/ = 0) {} + + bool flowAroundFloats() const; + bool usesLineWidth() const; + + // positioning of inline children (bidi) + virtual void position(InlineBox*, int, int, bool) {} +// virtual void position(int, int, int, int, int, bool, bool, int) {} + + // Applied as a "slop" to dirty rect checks during the outline painting phase's dirty-rect checks. + int maximalOutlineSize(PaintAction p) const; + + enum SelectionState { + SelectionNone, + SelectionStart, + SelectionInside, + SelectionEnd, + SelectionBoth + }; + + virtual SelectionState selectionState() const { return SelectionNone;} + virtual void setSelectionState(SelectionState) {} + + /** + * Flags which influence the appearence and position + * @param CFOverride input overrides existing character, caret should be + * cover the whole character + * @param CFOutside coordinates are to be interpreted outside of the + * render object + * @param CFOutsideEnd coordinates are to be interpreted at the outside + * end of the render object (only valid if CFOutside is also set) + */ + enum CaretFlags { CFOverride = 0x01, CFOutside = 0x02, CFOutsideEnd = 0x04 }; + + /** + * Returns the content coordinates of the caret within this render object. + * @param offset zero-based offset determining position within the render object. + * @param flags combination of enum CaretFlags + * @param _x returns the left coordinate + * @param _y returns the top coordinate + * @param width returns the caret's width + * @param height returns the caret's height + */ + virtual void caretPos(int offset, int flags, int &_x, int &_y, int &width, int &height); + + // returns the lowest position of the lowest object in that particular object. + // This 'height' is relative to the topleft of the margin box of the object. + // Implemented in RenderFlow. + virtual int lowestPosition(bool /*includeOverflowInterior*/=true, bool /*includeSelf*/=true) const { return 0; } + virtual int rightmostPosition(bool /*includeOverflowInterior*/=true, bool /*includeSelf*/=true) const { return 0; } + virtual int leftmostPosition(bool /*includeOverflowInterior*/=true, bool /*includeSelf*/=true) const { return 0; } + virtual int highestPosition(bool /*includeOverflowInterior*/=true, bool /*includeSelf*/=true) const { return 0; } + + // recursively invalidate current layout + // unused: void invalidateLayout(); + + virtual void calcVerticalMargins() {} + void removeFromObjectLists(); + + virtual void deleteInlineBoxes(RenderArena* arena=0) {(void)arena;} + virtual void detach( ); + + void setDoNotDelete(bool b) { m_doNotDelete = b; } + bool doNotDelete() const { return m_doNotDelete; } + + const QFont &font(bool firstLine) const { + return style( firstLine )->font(); + } + + const QFontMetrics &fontMetrics(bool firstLine) const { + return style( firstLine )->fontMetrics(); + } + + /** returns the lowest possible value the caret offset may have to + * still point to a valid position. + * + * Returns 0 by default. + */ + virtual long minOffset() const { return 0; } + /** returns the highest possible value the caret offset may have to + * still point to a valid position. + * + * Returns 0 by default, as generic elements are considered to have no + * width. + */ + virtual long maxOffset() const { return 0; } + + virtual void setPixmap(const QPixmap &, const QRect&, CachedImage *); + + QRegion visibleFlowRegion(int x, int y) const; + +protected: + virtual void selectionStartEnd(int& spos, int& epos); + + virtual QRect viewRect() const; + void remove(); + void invalidateVerticalPositions(); + bool attemptDirectLayerTranslation(); + void updateWidgetMasks(); + + virtual void removeLeftoverAnonymousBoxes(); + + void arenaDelete(RenderArena *arena); + +private: + RenderStyle* m_style; + DOM::NodeImpl* m_node; + RenderObject *m_parent; + RenderObject *m_previous; + RenderObject *m_next; + + short m_verticalPosition; + + bool m_needsLayout : 1; + bool m_normalChildNeedsLayout : 1; + bool m_markedForRepaint : 1; + bool m_posChildNeedsLayout : 1; + + bool m_minMaxKnown : 1; + bool m_floating : 1; + + bool m_positioned : 1; + bool m_overhangingContents : 1; + bool m_relPositioned : 1; + bool m_paintBackground : 1; // if the box has something to paint in the + // background painting phase (background, border, etc) + + bool m_isAnonymous : 1; + bool m_recalcMinMax : 1; + bool m_isText : 1; + bool m_inline : 1; + bool m_attached : 1; + + bool m_replaced : 1; + bool m_mouseInside : 1; + bool m_hasFirstLine : 1; + bool m_isSelectionBorder : 1; + + bool m_isRoot : 1; + + bool m_beforePageBreak : 1; + bool m_afterPageBreak : 1; + + bool m_needsPageClear : 1; + bool m_containsPageBreak : 1; + + bool m_hasOverflowClip : 1; + + bool m_doNotDelete : 1; // This object should not be auto-deleted + + // ### we have 16 + 26 bits. + + + void arenaDelete(RenderArena *arena, void *objectBase); + + friend class RenderLayer; + friend class RenderListItem; + friend class RenderContainer; + friend class RenderCanvas; +}; + + +enum VerticalPositionHint { + PositionTop = -0x4000, + PositionBottom = 0x4000, + PositionUndefined = 0x3fff +}; + +} //namespace +#endif diff --git a/khtml/rendering/render_replaced.cpp b/khtml/rendering/render_replaced.cpp new file mode 100644 index 000000000..a74338e29 --- /dev/null +++ b/khtml/rendering/render_replaced.cpp @@ -0,0 +1,939 @@ +/** + * This file is part of the HTML widget for KDE. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * Copyright (C) 2000-2003 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2003 Apple Computer, Inc. + * Copyright (C) 2004 Germain Garand (germain@ebooksfrance.org) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ +#include "render_replaced.h" +#include "render_layer.h" +#include "render_canvas.h" +#include "render_line.h" + +#include "render_arena.h" + +#include <assert.h> +#include <qwidget.h> +#include <qpainter.h> +#include <qevent.h> +#include <qapplication.h> +#include <qlineedit.h> +#include <kglobalsettings.h> +#include <qobjectlist.h> +#include <qvaluevector.h> + +#include "khtml_ext.h" +#include "khtmlview.h" +#include "xml/dom2_eventsimpl.h" +#include "khtml_part.h" +#include "xml/dom_docimpl.h" +#include <kdebug.h> + +bool khtml::allowWidgetPaintEvents = false; + +using namespace khtml; +using namespace DOM; + + +RenderReplaced::RenderReplaced(DOM::NodeImpl* node) + : RenderBox(node) +{ + // init RenderObject attributes + setReplaced(true); + + m_intrinsicWidth = 300; + m_intrinsicHeight = 150; +} + +void RenderReplaced::calcMinMaxWidth() +{ + KHTMLAssert( !minMaxKnown()); + +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << "RenderReplaced::calcMinMaxWidth() known=" << minMaxKnown() << endl; +#endif + + m_width = calcReplacedWidth(); + m_width = calcBoxWidth( m_width ); + + if ( style()->width().isPercent() || style()->height().isPercent() || + style()->maxWidth().isPercent() || style()->maxHeight().isPercent() || + style()->minWidth().isPercent() || style()->minHeight().isPercent() ) { + m_minWidth = 0; + m_maxWidth = m_width; + } + else + m_minWidth = m_maxWidth = m_width; + + setMinMaxKnown(); +} + +void RenderReplaced::position(InlineBox* box, int /*from*/, int /*len*/, bool /*reverse*/) +{ + setPos( box->xPos(), box->yPos() ); +} + +// ----------------------------------------------------------------------------- + +RenderWidget::RenderWidget(DOM::NodeImpl* node) + : RenderReplaced(node) +{ + m_widget = 0; + // a widget doesn't support being anonymous + assert(!isAnonymous()); + m_view = node->getDocument()->view(); + m_arena.reset(renderArena()); + m_resizePending = false; + m_discardResizes = false; + m_isKHTMLWidget = false; + m_needsMask = false; + + // this is no real reference counting, its just there + // to make sure that we're not deleted while we're recursed + // in an eventFilter of the widget + ref(); +} + +void RenderWidget::detach() +{ + remove(); + deleteInlineBoxes(); + + if ( m_widget ) { + if ( m_view ) { + m_view->setWidgetVisible(this, false); + m_view->removeChild( m_widget ); + } + + m_widget->removeEventFilter( this ); + m_widget->setMouseTracking( false ); + } + + deref(); +} + +RenderWidget::~RenderWidget() +{ + KHTMLAssert( refCount() <= 0 ); + + if(m_widget) { + m_widget->hide(); + m_widget->deleteLater(); + } +} + +class QWidgetResizeEvent : public QEvent +{ +public: + enum { Type = QEvent::User + 0xbee }; + QWidgetResizeEvent( int _w, int _h ) : + QEvent( ( QEvent::Type ) Type ), w( _w ), h( _h ) {} + int w; + int h; +}; + +void RenderWidget::resizeWidget( int w, int h ) +{ + // ugly hack to limit the maximum size of the widget ( as X11 has problems if + // its bigger ) + h = kMin( h, 3072 ); + w = kMin( w, 2000 ); + + if (m_widget->width() != w || m_widget->height() != h) { + m_resizePending = isKHTMLWidget(); + ref(); + element()->ref(); + QApplication::postEvent( this, new QWidgetResizeEvent( w, h ) ); + element()->deref(); + deref(); + } +} + +void RenderWidget::cancelPendingResize() +{ + if (!m_widget) + return; + m_discardResizes = true; + QApplication::sendPostedEvents(this, QWidgetResizeEvent::Type); + m_discardResizes = false; +} + +bool RenderWidget::event( QEvent *e ) +{ + if ( m_widget && (e->type() == (QEvent::Type)QWidgetResizeEvent::Type) ) { + m_resizePending = false; + if (m_discardResizes) + return true; + QWidgetResizeEvent *re = static_cast<QWidgetResizeEvent *>(e); + m_widget->resize( re->w, re->h ); + repaint(); + } + // eat all events - except if this is a frame (in which case KHTMLView handles it all) + if ( ::qt_cast<KHTMLView *>( m_widget ) ) + return QObject::event( e ); + return true; +} + +void RenderWidget::flushWidgetResizes() //static +{ + QApplication::sendPostedEvents( 0, QWidgetResizeEvent::Type ); +} + +void RenderWidget::setQWidget(QWidget *widget) +{ + if (widget != m_widget) + { + if (m_widget) { + m_widget->removeEventFilter(this); + disconnect( m_widget, SIGNAL( destroyed()), this, SLOT( slotWidgetDestructed())); + m_widget->hide(); + m_widget->deleteLater(); //Might happen due to event on the widget, so be careful + m_widget = 0; + } + m_widget = widget; + if (m_widget) { + connect( m_widget, SIGNAL( destroyed()), this, SLOT( slotWidgetDestructed())); + m_widget->installEventFilter(this); + + if ( (m_isKHTMLWidget = !strcmp(m_widget->name(), "__khtml")) && !::qt_cast<QFrame*>(m_widget)) + m_widget->setBackgroundMode( QWidget::NoBackground ); + + if (m_widget->focusPolicy() > QWidget::StrongFocus) + m_widget->setFocusPolicy(QWidget::StrongFocus); + // if we've already received a layout, apply the calculated space to the + // widget immediately, but we have to have really been full constructed (with a non-null + // style pointer). + if (!needsLayout() && style()) { + resizeWidget( m_width-borderLeft()-borderRight()-paddingLeft()-paddingRight(), + m_height-borderTop()-borderBottom()-paddingTop()-paddingBottom() ); + } + else + setPos(xPos(), -500000); + } + m_view->setWidgetVisible(this, false); + m_view->addChild( m_widget, 0, -500000); + if ( m_widget ) m_widget->hide(); + m_resizePending = false; + } +} + +void RenderWidget::layout( ) +{ + KHTMLAssert( needsLayout() ); + KHTMLAssert( minMaxKnown() ); + if ( m_widget ) { + resizeWidget( m_width-borderLeft()-borderRight()-paddingLeft()-paddingRight(), + m_height-borderTop()-borderBottom()-paddingTop()-paddingBottom() ); + if (!isKHTMLWidget() && !isFrame() && !m_needsMask) { + m_needsMask = true; + RenderLayer* rl = enclosingStackingContext(); + RenderLayer* el = enclosingLayer(); + while (rl && el && el != rl) { + if (el->renderer()->style()->position() != STATIC) { + m_needsMask = false; + break; + } + el = el->parent(); + } + if (m_needsMask) { + if (rl) rl->setHasOverlaidWidgets(); + canvas()->setNeedsWidgetMasks(); + } + } + } + + setNeedsLayout(false); +} + +void RenderWidget::updateFromElement() +{ + if (m_widget) { + // Color: + QColor color = style()->color(); + QColor backgroundColor = style()->backgroundColor(); + + if ( color.isValid() || backgroundColor.isValid() ) { + QPalette pal(QApplication::palette(m_widget)); + + int contrast_ = KGlobalSettings::contrast(); + int highlightVal = 100 + (2*contrast_+4)*16/10; + int lowlightVal = 100 + (2*contrast_+4)*10; + + if (backgroundColor.isValid()) { + if (!isKHTMLWidget()) + widget()->setEraseColor(backgroundColor ); + for ( int i = 0; i < QPalette::NColorGroups; ++i ) { + pal.setColor( (QPalette::ColorGroup)i, QColorGroup::Background, backgroundColor ); + pal.setColor( (QPalette::ColorGroup)i, QColorGroup::Light, backgroundColor.light(highlightVal) ); + pal.setColor( (QPalette::ColorGroup)i, QColorGroup::Dark, backgroundColor.dark(lowlightVal) ); + pal.setColor( (QPalette::ColorGroup)i, QColorGroup::Mid, backgroundColor.dark(120) ); + pal.setColor( (QPalette::ColorGroup)i, QColorGroup::Midlight, backgroundColor.light(110) ); + pal.setColor( (QPalette::ColorGroup)i, QColorGroup::Button, backgroundColor ); + pal.setColor( (QPalette::ColorGroup)i, QColorGroup::Base, backgroundColor ); + } + } + if ( color.isValid() ) { + struct ColorSet { + QPalette::ColorGroup cg; + QColorGroup::ColorRole cr; + }; + const struct ColorSet toSet [] = { + { QPalette::Active, QColorGroup::Foreground }, + { QPalette::Active, QColorGroup::ButtonText }, + { QPalette::Active, QColorGroup::Text }, + { QPalette::Inactive, QColorGroup::Foreground }, + { QPalette::Inactive, QColorGroup::ButtonText }, + { QPalette::Inactive, QColorGroup::Text }, + { QPalette::Disabled,QColorGroup::ButtonText }, + { QPalette::NColorGroups, QColorGroup::NColorRoles }, + }; + const ColorSet *set = toSet; + while( set->cg != QPalette::NColorGroups ) { + pal.setColor( set->cg, set->cr, color ); + ++set; + } + + QColor disfg = color; + int h, s, v; + disfg.hsv( &h, &s, &v ); + if (v > 128) + // dark bg, light fg - need a darker disabled fg + disfg = disfg.dark(lowlightVal); + else if (disfg != Qt::black) + // light bg, dark fg - need a lighter disabled fg - but only if !black + disfg = disfg.light(highlightVal); + else + // black fg - use darkgray disabled fg + disfg = Qt::darkGray; + pal.setColor(QPalette::Disabled,QColorGroup::Foreground,disfg); + } + + m_widget->setPalette(pal); + } + else + m_widget->unsetPalette(); + // Border: + QFrame* frame = ::qt_cast<QFrame*>(m_widget); + if (frame) { + if (shouldPaintBackgroundOrBorder()) + { + frame->setFrameShape(QFrame::NoFrame); + } + } + + } + + RenderReplaced::updateFromElement(); +} + +void RenderWidget::slotWidgetDestructed() +{ + if (m_view) + m_view->setWidgetVisible(this, false); + m_widget = 0; +} + +void RenderWidget::setStyle(RenderStyle *_style) +{ + RenderReplaced::setStyle(_style); + if(m_widget) + { + m_widget->setFont(style()->font()); + if (style()->visibility() != VISIBLE) { + if (m_view) + m_view->setWidgetVisible(this, false); + m_widget->hide(); + } + } + + // Don't paint borders if the border-style is native + // or borders are not supported on this widget + if (!canHaveBorder() || + (style()->borderLeftStyle() == BNATIVE && + style()->borderRightStyle() == BNATIVE && + style()->borderTopStyle() == BNATIVE && + style()->borderBottomStyle() == BNATIVE)) + { + setShouldPaintBackgroundOrBorder(false); + } +} + +void RenderWidget::paint(PaintInfo& paintInfo, int _tx, int _ty) +{ + _tx += m_x; + _ty += m_y; + + if (shouldPaintBackgroundOrBorder() && + (paintInfo.phase == PaintActionChildBackground || paintInfo.phase == PaintActionChildBackgrounds)) + paintBoxDecorations(paintInfo, _tx, _ty); + + if (!m_widget || !m_view || paintInfo.phase != PaintActionForeground) + return; + + // not visible or not even once layouted + if (style()->visibility() != VISIBLE || m_y <= -500000 || m_resizePending ) + return; + + if ( (_ty > paintInfo.r.bottom()) || (_ty + m_height <= paintInfo.r.top()) || + (_tx + m_width <= paintInfo.r.left()) || (_tx > paintInfo.r.right()) ) + return; + + int xPos = _tx+borderLeft()+paddingLeft(); + int yPos = _ty+borderTop()+paddingTop(); + + bool khtmlw = isKHTMLWidget(); + int childw = m_widget->width(); + int childh = m_widget->height(); + if ( (childw == 2000 || childh == 3072) && m_widget->inherits( "KHTMLView" ) ) { + KHTMLView *vw = static_cast<KHTMLView *>(m_widget); + int cy = m_view->contentsY(); + int ch = m_view->visibleHeight(); + + + int childx = m_view->childX( m_widget ); + int childy = m_view->childY( m_widget ); + + int xNew = xPos; + int yNew = childy; + + // qDebug("cy=%d, ch=%d, childy=%d, childh=%d", cy, ch, childy, childh ); + if ( childh == 3072 ) { + if ( cy + ch > childy + childh ) { + yNew = cy + ( ch - childh )/2; + } else if ( cy < childy ) { + yNew = cy + ( ch - childh )/2; + } +// qDebug("calculated yNew=%d", yNew); + } + yNew = kMin( yNew, yPos + m_height - childh ); + yNew = kMax( yNew, yPos ); + if ( yNew != childy || xNew != childx ) { + if ( vw->contentsHeight() < yNew - yPos + childh ) + vw->resizeContents( vw->contentsWidth(), yNew - yPos + childh ); + vw->setContentsPos( xNew - xPos, yNew - yPos ); + } + xPos = xNew; + yPos = yNew; + } + m_view->setWidgetVisible(this, true); + m_view->addChild(m_widget, xPos, yPos ); + m_widget->show(); + if (khtmlw) + paintWidget(paintInfo, m_widget, xPos, yPos); +} + +#include <private/qinternal_p.h> + +// The PaintBuffer class provides a shared buffer for widget painting. +// +// It will grow to encompass the biggest widget encountered, in order to avoid +// constantly resizing. +// When it grows over maxPixelBuffering, it periodically checks if such a size +// is still needed. If not, it shrinks down to the biggest size < maxPixelBuffering +// that was requested during the overflow lapse. + +class PaintBuffer: public QObject +{ +public: + static const int maxPixelBuffering = 320*200; + static const int leaseTime = 20*1000; + + static QPixmap *grab( QSize s = QSize() ) { + if (!m_inst) + m_inst = new PaintBuffer; + return m_inst->getBuf( s ); + } + static void release() { m_inst->m_grabbed = false; } +protected: + PaintBuffer(): m_overflow(false), m_grabbed(false), + m_timer(0), m_resetWidth(0), m_resetHeight(0) {}; + void timerEvent(QTimerEvent* e) { + assert( m_timer == e->timerId() ); + if (m_grabbed) + return; + m_buf.resize(m_resetWidth, m_resetHeight); + m_resetWidth = m_resetHeight = 0; + killTimer( m_timer ); + m_timer = 0; + } + + QPixmap *getBuf( QSize s ) { + assert( !m_grabbed ); + if (s.isEmpty()) + return 0; + + m_grabbed = true; + bool cur_overflow = false; + + int nw = kMax(m_buf.width(), s.width()); + int nh = kMax(m_buf.height(), s.height()); + + if (!m_overflow && (nw*nh > maxPixelBuffering)) + cur_overflow = true; + + if (nw != m_buf.width() || nh != m_buf.height()) + m_buf.resize(nw, nh); + + if (cur_overflow) { + m_overflow = true; + m_timer = startTimer( leaseTime ); + } else if (m_overflow) { + if( s.width()*s.height() > maxPixelBuffering ) { + killTimer( m_timer ); + m_timer = startTimer( leaseTime ); + } else { + if (s.width() > m_resetWidth) + m_resetWidth = s.width(); + if (s.height() > m_resetHeight) + m_resetHeight = s.height(); + } + } + return &m_buf; + } +private: + static PaintBuffer* m_inst; + QPixmap m_buf; + bool m_overflow; + bool m_grabbed; + int m_timer; + int m_resetWidth; + int m_resetHeight; +}; + +PaintBuffer *PaintBuffer::m_inst = 0; + +static void copyWidget(const QRect& r, QPainter *p, QWidget *widget, int tx, int ty) +{ + if (r.isNull() || r.isEmpty() ) + return; + QRegion blit(r); + QValueVector<QWidget*> cw; + QValueVector<QRect> cr; + + if (widget->children()) { + // build region + QObjectListIterator it = *widget->children(); + for (; it.current(); ++it) { + QWidget* const w = ::qt_cast<QWidget *>(it.current()); + if ( w && !w->isTopLevel() && !w->isHidden()) { + QRect r2 = w->geometry(); + blit -= r2; + r2 = r2.intersect( r ); + r2.moveBy(-w->x(), -w->y()); + cr.append(r2); + cw.append(w); + } + } + } + QMemArray<QRect> br = blit.rects(); + + const int cnt = br.size(); + const bool external = p->device()->isExtDev(); + QPixmap* const pm = PaintBuffer::grab( widget->size() ); + if (!pm) + { + kdWarning(6040) << "Rendering widget [ " << widget->className() << " ] failed due to invalid size." << endl; + return; + } + + // fill background + if ( external ) { + // even hackier! + QPainter pt( pm ); + const QColor c = widget->colorGroup().base(); + for (int i = 0; i < cnt; ++i) + pt.fillRect( br[i], c ); + } else { + QRect dr; + for (int i = 0; i < cnt; ++i ) { + dr = br[i]; + dr.moveBy( tx, ty ); + dr = p->xForm( dr ); + bitBlt(pm, br[i].topLeft(), p->device(), dr); + } + } + + // send paint event + QPainter::redirect(widget, pm); + QPaintEvent e( r, false ); + QApplication::sendEvent( widget, &e ); + QPainter::redirect(widget, 0); + + // transfer result + if ( external ) + for ( int i = 0; i < cnt; ++i ) + p->drawPixmap(QPoint(tx+br[i].x(), ty+br[i].y()), *pm, br[i]); + else + for ( int i = 0; i < cnt; ++i ) + bitBlt(p->device(), p->xForm( QPoint(tx, ty) + br[i].topLeft() ), pm, br[i]); + + // cleanup and recurse + PaintBuffer::release(); + QValueVector<QWidget*>::iterator cwit = cw.begin(); + QValueVector<QWidget*>::iterator cwitEnd = cw.end(); + QValueVector<QRect>::const_iterator crit = cr.begin(); + for (; cwit != cwitEnd; ++cwit, ++crit) + copyWidget(*crit, p, *cwit, tx+(*cwit)->x(), ty+(*cwit)->y()); +} + +void RenderWidget::paintWidget(PaintInfo& pI, QWidget *widget, int tx, int ty) +{ + QPainter* const p = pI.p; + allowWidgetPaintEvents = true; + + const bool dsbld = QSharedDoubleBuffer::isDisabled(); + QSharedDoubleBuffer::setDisabled(true); + QRect rr = pI.r; + rr.moveBy(-tx, -ty); + const QRect r = widget->rect().intersect( rr ); + copyWidget(r, p, widget, tx, ty); + QSharedDoubleBuffer::setDisabled(dsbld); + + allowWidgetPaintEvents = false; +} + +bool RenderWidget::eventFilter(QObject* /*o*/, QEvent* e) +{ + // no special event processing if this is a frame (in which case KHTMLView handles it all) + if ( ::qt_cast<KHTMLView *>( m_widget ) ) + return false; + if ( !element() ) return true; + + + static bool directToWidget = false; + if (directToWidget) { + //We're trying to get the event to the widget + //promptly. So get out of here.. + return false; + } + + ref(); + element()->ref(); + + bool filtered = false; + + //kdDebug() << "RenderWidget::eventFilter type=" << e->type() << endl; + switch(e->type()) { + case QEvent::FocusOut: + // First, forward it to the widget, so that Qt gets a precise + // state of the focus before pesky JS can try changing it.. + directToWidget = true; + QApplication::sendEvent(m_widget, e); + directToWidget = false; + filtered = true; //We already delivered it! + + // Don't count popup as a valid reason for losing the focus + // (example: opening the options of a select combobox shouldn't emit onblur) + if ( QFocusEvent::reason() != QFocusEvent::Popup ) + handleFocusOut(); + break; + case QEvent::FocusIn: + //As above, forward to the widget first... + directToWidget = true; + QApplication::sendEvent(m_widget, e); + directToWidget = false; + filtered = true; //We already delivered it! + + //kdDebug(6000) << "RenderWidget::eventFilter captures FocusIn" << endl; + element()->getDocument()->setFocusNode(element()); +// if ( isEditable() ) { +// KHTMLPartBrowserExtension *ext = static_cast<KHTMLPartBrowserExtension *>( element()->view->part()->browserExtension() ); +// if ( ext ) ext->editableWidgetFocused( m_widget ); +// } + break; + case QEvent::KeyPress: + case QEvent::KeyRelease: + // TODO this seems wrong - Qt events are not correctly translated to DOM ones, + // like in KHTMLView::dispatchKeyEvent() + if (element()->dispatchKeyEvent(static_cast<QKeyEvent*>(e),false)) + filtered = true; + break; + + case QEvent::Wheel: + if (widget()->parentWidget() == view()->viewport()) { + // don't allow the widget to react to wheel event unless its + // currently focused. this avoids accidentally changing a select box + // or something while wheeling a webpage. + if (qApp->focusWidget() != widget() && + widget()->focusPolicy() <= QWidget::StrongFocus) { + static_cast<QWheelEvent*>(e)->ignore(); + QApplication::sendEvent(view(), e); + filtered = true; + } + } + break; + default: + break; + }; + + element()->deref(); + + // stop processing if the widget gets deleted, but continue in all other cases + if (hasOneRef()) + filtered = true; + deref(); + + return filtered; +} + +void RenderWidget::EventPropagator::sendEvent(QEvent *e) { + switch(e->type()) { + case QEvent::MouseButtonPress: + mousePressEvent(static_cast<QMouseEvent *>(e)); + break; + case QEvent::MouseButtonRelease: + mouseReleaseEvent(static_cast<QMouseEvent *>(e)); + break; + case QEvent::MouseButtonDblClick: + mouseDoubleClickEvent(static_cast<QMouseEvent *>(e)); + break; + case QEvent::MouseMove: + mouseMoveEvent(static_cast<QMouseEvent *>(e)); + break; + case QEvent::KeyPress: + keyPressEvent(static_cast<QKeyEvent *>(e)); + break; + case QEvent::KeyRelease: + keyReleaseEvent(static_cast<QKeyEvent *>(e)); + break; + default: + break; + } +} + +void RenderWidget::ScrollViewEventPropagator::sendEvent(QEvent *e) { + switch(e->type()) { + case QEvent::MouseButtonPress: + viewportMousePressEvent(static_cast<QMouseEvent *>(e)); + break; + case QEvent::MouseButtonRelease: + viewportMouseReleaseEvent(static_cast<QMouseEvent *>(e)); + break; + case QEvent::MouseButtonDblClick: + viewportMouseDoubleClickEvent(static_cast<QMouseEvent *>(e)); + break; + case QEvent::MouseMove: + viewportMouseMoveEvent(static_cast<QMouseEvent *>(e)); + break; + case QEvent::KeyPress: + keyPressEvent(static_cast<QKeyEvent *>(e)); + break; + case QEvent::KeyRelease: + keyReleaseEvent(static_cast<QKeyEvent *>(e)); + break; + default: + break; + } +} + +bool RenderWidget::handleEvent(const DOM::EventImpl& ev) +{ + bool ret = false; + switch(ev.id()) { + case EventImpl::MOUSEDOWN_EVENT: + case EventImpl::MOUSEUP_EVENT: + case EventImpl::MOUSEMOVE_EVENT: { + if (!ev.isMouseEvent()) break; + const MouseEventImpl &me = static_cast<const MouseEventImpl &>(ev); + QMouseEvent* const qme = me.qEvent(); + + int absx = 0; + int absy = 0; + + absolutePosition(absx, absy); + QPoint p(me.clientX() - absx + m_view->contentsX(), + me.clientY() - absy + m_view->contentsY()); + QMouseEvent::Type type; + int button = 0; + int state = 0; + + if (qme) { + button = qme->button(); + state = qme->state(); + type = qme->type(); + } else { + switch(me.id()) { + case EventImpl::MOUSEDOWN_EVENT: + type = QMouseEvent::MouseButtonPress; + break; + case EventImpl::MOUSEUP_EVENT: + type = QMouseEvent::MouseButtonRelease; + break; + case EventImpl::MOUSEMOVE_EVENT: + default: + type = QMouseEvent::MouseMove; + break; + } + switch (me.button()) { + case 0: + button = LeftButton; + break; + case 1: + button = MidButton; + break; + case 2: + button = RightButton; + break; + default: + break; + } + if (me.ctrlKey()) + state |= ControlButton; + if (me.altKey()) + state |= AltButton; + if (me.shiftKey()) + state |= ShiftButton; + if (me.metaKey()) + state |= MetaButton; + } + +// kdDebug(6000) << "sending event to widget " +// << " pos=" << p << " type=" << type +// << " button=" << button << " state=" << state << endl; + QMouseEvent e(type, p, button, state); + QScrollView * sc = ::qt_cast<QScrollView*>(m_widget); + if (sc && !::qt_cast<QListBox*>(m_widget)) + static_cast<ScrollViewEventPropagator *>(sc)->sendEvent(&e); + else + static_cast<EventPropagator *>(m_widget)->sendEvent(&e); + ret = e.isAccepted(); + break; + } + case EventImpl::KEYDOWN_EVENT: + // do nothing; see the mapping table below + break; + case EventImpl::KEYUP_EVENT: { + if (!ev.isKeyRelatedEvent()) break; + + const KeyEventBaseImpl& domKeyEv = static_cast<const KeyEventBaseImpl &>(ev); + if (domKeyEv.isSynthetic() && !acceptsSyntheticEvents()) break; + + QKeyEvent* const ke = domKeyEv.qKeyEvent(); + static_cast<EventPropagator *>(m_widget)->sendEvent(ke); + ret = ke->isAccepted(); + break; + } + case EventImpl::KEYPRESS_EVENT: { + if (!ev.isKeyRelatedEvent()) break; + + const KeyEventBaseImpl& domKeyEv = static_cast<const KeyEventBaseImpl &>(ev); + if (domKeyEv.isSynthetic() && !acceptsSyntheticEvents()) break; + + // See KHTMLView::dispatchKeyEvent: autorepeat is just keypress in the DOM + // but it's keyrelease+keypress in Qt. So here we do the inverse mapping as + // the one done in KHTMLView: generate two events for one DOM auto-repeat keypress. + // Similarly, DOM keypress events with non-autorepeat Qt event do nothing here, + // because the matching Qt keypress event was already sent from DOM keydown event. + + // Reverse drawing as the one in KHTMLView: + // DOM: Down Press | Press | Up + // Qt: (nothing) Press | Release(autorepeat) + Press(autorepeat) | Release + // + // Qt::KeyPress is sent for DOM keypress and not DOM keydown to allow + // sites to block a key with onkeypress, #99749 + + QKeyEvent* const ke = domKeyEv.qKeyEvent(); + if (ke->isAutoRepeat()) { + QKeyEvent releaseEv( QEvent::KeyRelease, ke->key(), ke->ascii(), ke->state(), + ke->text(), ke->isAutoRepeat(), ke->count() ); + static_cast<EventPropagator *>(m_widget)->sendEvent(&releaseEv); + } + static_cast<EventPropagator *>(m_widget)->sendEvent(ke); + ret = ke->isAccepted(); + break; + } + case EventImpl::MOUSEOUT_EVENT: { + QEvent moe( QEvent::Leave ); + QApplication::sendEvent(m_widget, &moe); + break; + } + case EventImpl::MOUSEOVER_EVENT: { + QEvent moe( QEvent::Enter ); + QApplication::sendEvent(m_widget, &moe); + view()->part()->resetHoverText(); + break; + } + default: + break; + } + return ret; +} + +void RenderWidget::deref() +{ + if (_ref) _ref--; +// qDebug( "deref(%p): width get count is %d", this, _ref); + if (!_ref) { + khtml::SharedPtr<RenderArena> guard(m_arena); //Since delete on us gets called -first-, + //before the arena free + arenaDelete(m_arena.get()); + } +} + +FindSelectionResult RenderReplaced::checkSelectionPoint(int _x, int _y, int _tx, int _ty, DOM::NodeImpl*& node, int &offset, SelPointState &) +{ +#if 0 + kdDebug(6040) << "RenderReplaced::checkSelectionPoint(_x="<<_x<<",_y="<<_y<<",_tx="<<_tx<<",_ty="<<_ty<<")" << endl + << "xPos: " << xPos() << " yPos: " << yPos() << " width: " << width() << " height: " << height() << endl + << "_ty + yPos: " << (_ty + yPos()) << " + height: " << (_ty + yPos() + height()) << "; _tx + xPos: " << (_tx + xPos()) << " + width: " << (_tx + xPos() + width()) << endl; +#endif + node = element(); + offset = 0; + + if ( _y < _ty + yPos() ) + return SelectionPointBefore; // above -> before + + if ( _y > _ty + yPos() + height() ) { + // below -> after + // Set the offset to the max + offset = 1; + return SelectionPointAfter; + } + if ( _x > _tx + xPos() + width() ) { + // to the right + // ### how to regard bidi in replaced elements? (LS) + offset = 1; + return SelectionPointAfterInLine; + } + + // The Y matches, check if we're on the left + if ( _x < _tx + xPos() ) { + // ### how to regard bidi in replaced elements? (LS) + return SelectionPointBeforeInLine; + } + + offset = _x > _tx + xPos() + width()/2; + return SelectionPointInside; +} + +#ifdef ENABLE_DUMP +void RenderWidget::dump(QTextStream &stream, const QString &ind) const +{ + RenderReplaced::dump(stream,ind); + if ( widget() ) + stream << " color=" << widget()->foregroundColor().name() + << " bg=" << widget()->backgroundColor().name(); + else + stream << " null widget"; +} +#endif + +#include "render_replaced.moc" + diff --git a/khtml/rendering/render_replaced.h b/khtml/rendering/render_replaced.h new file mode 100644 index 000000000..97c14a0a8 --- /dev/null +++ b/khtml/rendering/render_replaced.h @@ -0,0 +1,169 @@ +/* + * This file is part of the HTML widget for KDE. + * + * Copyright (C) 1999 Lars Knoll (knoll@kde.org) + * + * 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. + * + */ +#ifndef render_replaced_h +#define render_replaced_h + +#include "rendering/render_block.h" +#include <qobject.h> +#include <qscrollview.h> + +class KHTMLView; +class QWidget; + +namespace DOM +{ + class MouseEventImpl; +} + +namespace khtml { + +class RenderReplaced : public RenderBox +{ +public: + RenderReplaced(DOM::NodeImpl* node); + + virtual const char *renderName() const { return "RenderReplaced"; } + virtual bool isRenderReplaced() const { return true; } + + virtual bool childAllowed() const { return false; } + + virtual void calcMinMaxWidth(); + + virtual short intrinsicWidth() const { return m_intrinsicWidth; } + virtual int intrinsicHeight() const { return m_intrinsicHeight; } + + void setIntrinsicWidth(int w) { m_intrinsicWidth = w; } + void setIntrinsicHeight(int h) { m_intrinsicHeight = h; } + + virtual void position(InlineBox*, int, int, bool); + + // Return before, after (offset set to max), or inside the replaced element, + // at @p offset + virtual FindSelectionResult checkSelectionPoint( int _x, int _y, int _tx, int _ty, + DOM::NodeImpl*& node, int & offset, + SelPointState & ); + + /** returns the lowest possible value the caret offset may have to + * still point to a valid position. + * + * Returns 0. + */ + virtual long minOffset() const { return 0; } + /** returns the highest possible value the caret offset may have to + * still point to a valid position. + * + * Returns 0. + */ + virtual long maxOffset() const { return 0; } + +protected: + short m_intrinsicWidth; + short m_intrinsicHeight; +}; + + +class RenderWidget : public QObject, public RenderReplaced, public khtml::Shared<RenderWidget> +{ + Q_OBJECT +public: + RenderWidget(DOM::NodeImpl* node); + virtual ~RenderWidget(); + + virtual void setStyle(RenderStyle *style); + virtual void paint( PaintInfo& i, int tx, int ty ); + virtual bool isWidget() const { return true; }; + + virtual bool isFrame() const { return false; } + + virtual void detach( ); + virtual void layout( ); + + virtual void updateFromElement(); + + QWidget *widget() const { return m_widget; } + KHTMLView* view() const { return m_view; } + + void deref(); + + void cancelPendingResize(); + bool needsMask() const { return m_needsMask; } + + static void paintWidget(PaintInfo& pI, QWidget *widget, int tx, int ty); + virtual bool handleEvent(const DOM::EventImpl& ev); + +#ifdef ENABLE_DUMP + virtual void dump(QTextStream &stream, const QString &ind) const; +#endif + + // for ECMA to flush all pending resizes + KDE_EXPORT static void flushWidgetResizes(); + +public slots: + void slotWidgetDestructed(); + bool isKHTMLWidget() const { return m_isKHTMLWidget; } + +protected: + virtual bool canHaveBorder() const { return false; } + + virtual bool acceptsSyntheticEvents() const { return true; } + + virtual void handleFocusOut() {} + bool event( QEvent *e ); + + bool eventFilter(QObject* /*o*/, QEvent* e); + void setQWidget(QWidget *widget); + void resizeWidget( int w, int h ); + + QWidget *m_widget; + KHTMLView* m_view; + + //Because we mess with normal detach due to ref/deref, + //we need to keep track of the arena ourselves + //so it doesn't get yanked from us, etc. + SharedPtr<RenderArena> m_arena; + + bool m_resizePending; + bool m_discardResizes; + bool m_isKHTMLWidget; + bool m_needsMask; + +public: + virtual int borderTop() const { return canHaveBorder() ? RenderReplaced::borderTop() : 0; } + virtual int borderBottom() const { return canHaveBorder() ? RenderReplaced::borderBottom() : 0; } + virtual int borderLeft() const { return canHaveBorder() ? RenderReplaced::borderLeft() : 0; } + virtual int borderRight() const { return canHaveBorder() ? RenderReplaced::borderRight() : 0; } + + class EventPropagator : public QWidget { + public: + void sendEvent(QEvent *e); + }; + class ScrollViewEventPropagator : public QScrollView { + public: + void sendEvent(QEvent *e); + }; +}; + +extern bool allowWidgetPaintEvents; + +} + +#endif diff --git a/khtml/rendering/render_style.cpp b/khtml/rendering/render_style.cpp new file mode 100644 index 000000000..936964b98 --- /dev/null +++ b/khtml/rendering/render_style.cpp @@ -0,0 +1,1300 @@ +/* + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 1999 Antti Koivisto (koivisto@kde.org) + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * Copyright (C) 2002-2003 Dirk Mueller (mueller@kde.org) + * Copyright (C) 2002-2005 Apple Computer, Inc. + * Copyright (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + +#include "xml/dom_stringimpl.h" +#include "css/cssstyleselector.h" +#include "css/css_valueimpl.h" +#include "render_style.h" + +#include "kdebug.h" + +using namespace khtml; +using namespace DOM; + +/* CSS says Fixed for the default padding value, but we treat variable as 0 padding anyways, and like + * this is works fine for table paddings aswell + */ +StyleSurroundData::StyleSurroundData() + : margin( Fixed ), padding( Variable ) +{ +} + +StyleSurroundData::StyleSurroundData(const StyleSurroundData& o ) + : Shared<StyleSurroundData>(), + offset( o.offset ), margin( o.margin ), padding( o.padding ), + border( o.border ) +{ +} + +bool StyleSurroundData::operator==(const StyleSurroundData& o) const +{ + return offset==o.offset && margin==o.margin && + padding==o.padding && border==o.border; +} + +StyleBoxData::StyleBoxData() + : z_index( 0 ), z_auto( true ) +{ + min_width = min_height = RenderStyle::initialMinSize(); + max_width = max_height = RenderStyle::initialMaxSize(); + box_sizing = RenderStyle::initialBoxSizing(); +} + +StyleBoxData::StyleBoxData(const StyleBoxData& o ) + : Shared<StyleBoxData>(), + width( o.width ), height( o.height ), + min_width( o.min_width ), max_width( o.max_width ), + min_height ( o.min_height ), max_height( o.max_height ), + box_sizing( o.box_sizing), + z_index( o.z_index ), z_auto( o.z_auto ) +{ +} + +bool StyleBoxData::operator==(const StyleBoxData& o) const +{ + return + width == o.width && + height == o.height && + min_width == o.min_width && + max_width == o.max_width && + min_height == o.min_height && + max_height == o.max_height && + box_sizing == o.box_sizing && + z_index == o.z_index && + z_auto == o.z_auto; +} + +StyleVisualData::StyleVisualData() + : textDecoration(RenderStyle::initialTextDecoration()), + palette( QApplication::palette() ) +{ +} + +StyleVisualData::~StyleVisualData() { +} + +StyleVisualData::StyleVisualData(const StyleVisualData& o ) + : Shared<StyleVisualData>(), + clip( o.clip ), textDecoration(o.textDecoration), + palette( o.palette ) +{ +} + +BackgroundLayer::BackgroundLayer() +:m_image(RenderStyle::initialBackgroundImage()), + m_bgAttachment(RenderStyle::initialBackgroundAttachment()), + m_bgClip(RenderStyle::initialBackgroundClip()), + m_bgOrigin(RenderStyle::initialBackgroundOrigin()), + m_bgRepeat(RenderStyle::initialBackgroundRepeat()), + m_backgroundSize(RenderStyle::initialBackgroundSize()), + m_next(0) +{ + m_imageSet = m_attachmentSet = m_clipSet = m_originSet = + m_repeatSet = m_xPosSet = m_yPosSet = m_backgroundSizeSet = false; +} + +BackgroundLayer::BackgroundLayer(const BackgroundLayer& o) +{ + m_next = o.m_next ? new BackgroundLayer(*o.m_next) : 0; + m_image = o.m_image; + m_xPosition = o.m_xPosition; + m_yPosition = o.m_yPosition; + m_bgAttachment = o.m_bgAttachment; + m_bgClip = o.m_bgClip; + m_bgOrigin = o.m_bgOrigin; + m_bgRepeat = o.m_bgRepeat; + m_backgroundSize = o.m_backgroundSize; + m_imageSet = o.m_imageSet; + m_attachmentSet = o.m_attachmentSet; + m_clipSet = o.m_clipSet; + m_originSet = o.m_originSet; + m_repeatSet = o.m_repeatSet; + m_xPosSet = o.m_xPosSet; + m_yPosSet = o.m_yPosSet; + m_backgroundSizeSet = o.m_backgroundSizeSet; +} + +BackgroundLayer::~BackgroundLayer() +{ + delete m_next; +} + +BackgroundLayer& BackgroundLayer::operator=(const BackgroundLayer& o) { + if (m_next != o.m_next) { + delete m_next; + m_next = o.m_next ? new BackgroundLayer(*o.m_next) : 0; + } + + m_image = o.m_image; + m_xPosition = o.m_xPosition; + m_yPosition = o.m_yPosition; + m_bgAttachment = o.m_bgAttachment; + m_bgClip = o.m_bgClip; + m_bgOrigin = o.m_bgOrigin; + m_bgRepeat = o.m_bgRepeat; + m_backgroundSize = o.m_backgroundSize; + + m_imageSet = o.m_imageSet; + m_attachmentSet = o.m_attachmentSet; + m_originSet = o.m_originSet; + m_repeatSet = o.m_repeatSet; + m_xPosSet = o.m_xPosSet; + m_yPosSet = o.m_yPosSet; + m_backgroundSizeSet = o.m_backgroundSizeSet; + + return *this; +} + +bool BackgroundLayer::operator==(const BackgroundLayer& o) const { + return m_image == o.m_image && m_xPosition == o.m_xPosition && m_yPosition == o.m_yPosition && + m_bgAttachment == o.m_bgAttachment && m_bgClip == o.m_bgClip && m_bgOrigin == o.m_bgOrigin && m_bgRepeat == o.m_bgRepeat && + m_backgroundSize.width == o.m_backgroundSize.width && m_backgroundSize.height == o.m_backgroundSize.height && + m_imageSet == o.m_imageSet && m_attachmentSet == o.m_attachmentSet && m_repeatSet == o.m_repeatSet && + m_xPosSet == o.m_xPosSet && m_yPosSet == o.m_yPosSet && m_backgroundSizeSet == o.m_backgroundSizeSet && + ((m_next && o.m_next) ? *m_next == *o.m_next : m_next == o.m_next); +} + +void BackgroundLayer::fillUnsetProperties() +{ + BackgroundLayer* curr; + for (curr = this; curr && curr->isBackgroundImageSet(); curr = curr->next()); + if (curr && curr != this) { + // We need to fill in the remaining values with the pattern specified. + for (BackgroundLayer* pattern = this; curr; curr = curr->next()) { + curr->m_image = pattern->m_image; + pattern = pattern->next(); + if (pattern == curr || !pattern) + pattern = this; + } + } + + for (curr = this; curr && curr->isBackgroundXPositionSet(); curr = curr->next()); + if (curr && curr != this) { + // We need to fill in the remaining values with the pattern specified. + for (BackgroundLayer* pattern = this; curr; curr = curr->next()) { + curr->m_xPosition = pattern->m_xPosition; + pattern = pattern->next(); + if (pattern == curr || !pattern) + pattern = this; + } + } + + for (curr = this; curr && curr->isBackgroundYPositionSet(); curr = curr->next()); + if (curr && curr != this) { + // We need to fill in the remaining values with the pattern specified. + for (BackgroundLayer* pattern = this; curr; curr = curr->next()) { + curr->m_yPosition = pattern->m_yPosition; + pattern = pattern->next(); + if (pattern == curr || !pattern) + pattern = this; + } + } + + for (curr = this; curr && curr->isBackgroundAttachmentSet(); curr = curr->next()); + if (curr && curr != this) { + // We need to fill in the remaining values with the pattern specified. + for (BackgroundLayer* pattern = this; curr; curr = curr->next()) { + curr->m_bgAttachment = pattern->m_bgAttachment; + pattern = pattern->next(); + if (pattern == curr || !pattern) + pattern = this; + } + } + + for (curr = this; curr && curr->isBackgroundClipSet(); curr = curr->next()); + if (curr && curr != this) { + // We need to fill in the remaining values with the pattern specified. + for (BackgroundLayer* pattern = this; curr; curr = curr->next()) { + curr->m_bgClip = pattern->m_bgClip; + pattern = pattern->next(); + if (pattern == curr || !pattern) + pattern = this; + } + } + + for (curr = this; curr && curr->isBackgroundOriginSet(); curr = curr->next()); + if (curr && curr != this) { + // We need to fill in the remaining values with the pattern specified. + for (BackgroundLayer* pattern = this; curr; curr = curr->next()) { + curr->m_bgOrigin = pattern->m_bgOrigin; + pattern = pattern->next(); + if (pattern == curr || !pattern) + pattern = this; + } + } + + for (curr = this; curr && curr->isBackgroundRepeatSet(); curr = curr->next()); + if (curr && curr != this) { + // We need to fill in the remaining values with the pattern specified. + for (BackgroundLayer* pattern = this; curr; curr = curr->next()) { + curr->m_bgRepeat = pattern->m_bgRepeat; + pattern = pattern->next(); + if (pattern == curr || !pattern) + pattern = this; + } + } + + for (curr = this; curr && curr->isBackgroundSizeSet(); curr = curr->next()); + if (curr && curr != this) { + // We need to fill in the remaining values with the pattern specified. + for (BackgroundLayer* pattern = this; curr; curr = curr->next()) { + curr->m_backgroundSize = pattern->m_backgroundSize; + pattern = pattern->next(); + if (pattern == curr || !pattern) + pattern = this; + } + } +} + +void BackgroundLayer::cullEmptyLayers() +{ + BackgroundLayer *next; + for (BackgroundLayer *p = this; p; p = next) { + next = p->m_next; + if (next && !next->isBackgroundImageSet() && + !next->isBackgroundXPositionSet() && !next->isBackgroundYPositionSet() && + !next->isBackgroundAttachmentSet() && !next->isBackgroundClipSet() && + !next->isBackgroundOriginSet() && !next->isBackgroundRepeatSet() && + !next->isBackgroundSizeSet()) { + delete next; + p->m_next = 0; + break; + } + } +} + +StyleBackgroundData::StyleBackgroundData() +{} + +StyleBackgroundData::StyleBackgroundData(const StyleBackgroundData& o) + : Shared<StyleBackgroundData>(), m_background(o.m_background), m_outline(o.m_outline) +{} + +bool StyleBackgroundData::operator==(const StyleBackgroundData& o) const +{ + return m_background == o.m_background && m_color == o.m_color && m_outline == o.m_outline; +} + +StyleGeneratedData::StyleGeneratedData() : Shared<StyleGeneratedData>(), content(0), counter_reset(0), counter_increment(0) {} + +StyleGeneratedData::~StyleGeneratedData() +{ + if (counter_reset) counter_reset->deref(); + if (counter_increment) counter_increment->deref(); + delete content; +} + +StyleGeneratedData::StyleGeneratedData(const StyleGeneratedData& o) + : Shared<StyleGeneratedData>(), content(0), + counter_reset(o.counter_reset), counter_increment(o.counter_increment) +{ + if (o.content) content = new ContentData(*o.content); + if (counter_reset) counter_reset->ref(); + if (counter_increment) counter_increment->ref(); +} + +bool StyleGeneratedData::contentDataEquivalent(const StyleGeneratedData* otherStyle) const +{ + ContentData* c1 = content; + ContentData* c2 = otherStyle->content; + + while (c1 && c2) { + if (c1->_contentType != c2->_contentType) + return false; + if (c1->_contentType == CONTENT_TEXT) { + DOM::DOMString c1Str(c1->_content.text); + DOM::DOMString c2Str(c2->_content.text); + if (c1Str != c2Str) + return false; + } + else if (c1->_contentType == CONTENT_OBJECT) { + if (c1->_content.object != c2->_content.object) + return false; + } + else if (c1->_contentType == CONTENT_COUNTER) { + if (c1->_content.counter != c2->_content.counter) + return false; + } + else if (c1->_contentType == CONTENT_QUOTE) { + if (c1->_content.quote != c2->_content.quote) + return false; + } + + c1 = c1->_nextContent; + c2 = c2->_nextContent; + } + + return !c1 && !c2; +} + +static bool compareCounterActList(const CSSValueListImpl* ca, const CSSValueListImpl* cb) { + // weeee.... + CSSValueListImpl* a = const_cast<CSSValueListImpl*>(ca); + CSSValueListImpl* b = const_cast<CSSValueListImpl*>(cb); + + if (!a && !b) return true; + if (!a || !b) return false; + if (a->length() != b->length()) return false; + for(uint i=0; i< a->length(); i++) { + CSSValueImpl *ai = a->item(i); + CSSValueImpl *bi = b->item(i); + assert(ai && ai->cssValueType() == CSSValue::CSS_CUSTOM); + assert(bi && bi->cssValueType() == CSSValue::CSS_CUSTOM); + CounterActImpl* caa = static_cast<CounterActImpl*>(ai); + CounterActImpl* cab = static_cast<CounterActImpl*>(bi); + if (caa->value() != cab->value()) return false; + if (caa->counter() != cab->counter()) return false; + } + return true; +} + +bool StyleGeneratedData::counterDataEquivalent(const StyleGeneratedData* otherStyle) const +{ + return compareCounterActList(counter_reset, otherStyle->counter_reset) && + compareCounterActList(counter_increment, otherStyle->counter_increment); +} + +bool StyleGeneratedData::operator==(const StyleGeneratedData& o) const +{ + return contentDataEquivalent(&o) && counterDataEquivalent(&o); +} + +StyleMarqueeData::StyleMarqueeData() +{ + increment = RenderStyle::initialMarqueeIncrement(); + speed = RenderStyle::initialMarqueeSpeed(); + direction = RenderStyle::initialMarqueeDirection(); + behavior = RenderStyle::initialMarqueeBehavior(); + loops = RenderStyle::initialMarqueeLoopCount(); +} + +StyleMarqueeData::StyleMarqueeData(const StyleMarqueeData& o) +:Shared<StyleMarqueeData>(), increment(o.increment), speed(o.speed), loops(o.loops), + behavior(o.behavior), direction(o.direction) +{} + +bool StyleMarqueeData::operator==(const StyleMarqueeData& o) const +{ + return (increment == o.increment && speed == o.speed && direction == o.direction && + behavior == o.behavior && loops == o.loops); +} + +StyleCSS3NonInheritedData::StyleCSS3NonInheritedData() +:Shared<StyleCSS3NonInheritedData>() +, opacity(RenderStyle::initialOpacity()) +{ +} + +StyleCSS3NonInheritedData::StyleCSS3NonInheritedData(const StyleCSS3NonInheritedData& o) +:Shared<StyleCSS3NonInheritedData>(), + opacity(o.opacity), +#ifdef APPLE_CHANGES + flexibleBox(o.flexibleBox), +#endif + marquee(o.marquee) +{ +} + +bool StyleCSS3NonInheritedData::operator==(const StyleCSS3NonInheritedData& o) const +{ + return + opacity == o.opacity && +#ifdef APPLE_CHANGES + flexibleBox == o.flexibleBox && +#endif + marquee == o.marquee; +} + +StyleCSS3InheritedData::StyleCSS3InheritedData() +:Shared<StyleCSS3InheritedData>(), textShadow(0) +#ifdef APPLE_CHANGES +, userModify(READ_ONLY), textSizeAdjust(RenderStyle::initialTextSizeAdjust()) +#endif +{ + +} + +StyleCSS3InheritedData::StyleCSS3InheritedData(const StyleCSS3InheritedData& o) +:Shared<StyleCSS3InheritedData>() +{ + textShadow = o.textShadow ? new ShadowData(*o.textShadow) : 0; +#ifdef APPLE_CHANGES + userModify = o.userModify; + textSizeAdjust = o.textSizeAdjust; +#endif +} + +StyleCSS3InheritedData::~StyleCSS3InheritedData() +{ + delete textShadow; +} + +bool StyleCSS3InheritedData::operator==(const StyleCSS3InheritedData& o) const +{ + return shadowDataEquivalent(o) +#ifdef APPLE_CHANGES + && (userModify == o.userModify) && (textSizeAdjust == o.textSizeAdjust) +#endif + ; +} + +bool StyleCSS3InheritedData::shadowDataEquivalent(const StyleCSS3InheritedData& o) const +{ + if (!textShadow && o.textShadow || textShadow && !o.textShadow) + return false; + if (textShadow && o.textShadow && (*textShadow != *o.textShadow)) + return false; + return true; +} + +StyleInheritedData::StyleInheritedData() + : indent( RenderStyle::initialTextIndent() ), line_height( RenderStyle::initialLineHeight() ), + style_image( RenderStyle::initialListStyleImage() ), + font(), color( RenderStyle::initialColor() ), + border_hspacing( RenderStyle::initialBorderHorizontalSpacing() ), + border_vspacing( RenderStyle::initialBorderVerticalSpacing() ), + widows( RenderStyle::initialWidows() ), orphans( RenderStyle::initialOrphans() ), + quotes(0) +{ +} + +StyleInheritedData::~StyleInheritedData() +{ + if (quotes) quotes->deref(); +} + +StyleInheritedData::StyleInheritedData(const StyleInheritedData& o ) + : Shared<StyleInheritedData>(), + indent( o.indent ), line_height( o.line_height ), style_image( o.style_image ), + font( o.font ), color( o.color ), + border_hspacing( o.border_hspacing ), + border_vspacing( o.border_vspacing ), + widows(o.widows), orphans(o.orphans) +{ + quotes = o.quotes; + if (quotes) quotes->ref(); +} + +bool StyleInheritedData::operator==(const StyleInheritedData& o) const +{ + return + indent == o.indent && + line_height == o.line_height && + border_hspacing == o.border_hspacing && + border_vspacing == o.border_vspacing && + style_image == o.style_image && + font == o.font && + color == o.color && + border_hspacing == o.border_hspacing && + border_vspacing == o.border_vspacing && + quotes == o.quotes && + widows == o.widows && + orphans == o.orphans ; + + // doesn't work because structs are not packed + //return memcmp(this, &o, sizeof(*this))==0; +} + +RenderStyle::RenderStyle() +{ +// counter++; + if (!_default) + _default = new RenderStyle(true); + + box = _default->box; + visual = _default->visual; + background = _default->background; + surround = _default->surround; + generated = _default->generated; + css3NonInheritedData = _default->css3NonInheritedData; + css3InheritedData = _default->css3InheritedData; + + inherited = _default->inherited; + + setBitDefaults(); + + pseudoStyle = 0; +} + +RenderStyle::RenderStyle(bool) +{ + setBitDefaults(); + + box.init(); + visual.init(); + background.init(); + surround.init(); + generated.init(); + css3NonInheritedData.init(); +#ifdef APPLE_CHANGES // ### yet to be merged + css3NonInheritedData.access()->flexibleBox.init(); +#endif + css3NonInheritedData.access()->marquee.init(); + css3InheritedData.init(); + inherited.init(); + + pseudoStyle = 0; +} + +RenderStyle::RenderStyle(const RenderStyle& o) + : Shared<RenderStyle>(), + inherited_flags( o.inherited_flags ), noninherited_flags( o.noninherited_flags ), + box( o.box ), visual( o.visual ), background( o.background ), surround( o.surround ), generated(o.generated), + css3NonInheritedData( o.css3NonInheritedData ), css3InheritedData( o.css3InheritedData ), + inherited( o.inherited ), pseudoStyle( 0 ) +{} + +void RenderStyle::inheritFrom(const RenderStyle* inheritParent) +{ + css3InheritedData = inheritParent->css3InheritedData; + inherited = inheritParent->inherited; + inherited_flags = inheritParent->inherited_flags; + + // Simulate ":after,:before { white-space: pre-line }" + if (styleType() == AFTER || styleType() == BEFORE) + setWhiteSpace(PRE_LINE); +} + +RenderStyle::~RenderStyle() +{ + RenderStyle *ps = pseudoStyle; + RenderStyle *prev = 0; + + while (ps) { + prev = ps; + ps = ps->pseudoStyle; + // to prevent a double deletion. + // this works only because the styles below aren't really shared + // Dirk said we need another construct as soon as these are shared + prev->pseudoStyle = 0; + prev->deref(); + } +} + +bool RenderStyle::operator==(const RenderStyle& o) const +{ +// compare everything except the pseudoStyle pointer + return (inherited_flags == o.inherited_flags && + noninherited_flags == o.noninherited_flags && + box == o.box && + visual == o.visual && + background == o.background && + surround == o.surround && + generated == o.generated && + css3NonInheritedData == o.css3NonInheritedData && + css3InheritedData == o.css3InheritedData && + inherited == o.inherited); +} + +enum EPseudoBit { NO_BIT = 0x0, + FIRST_LINE_BIT = 0x1, FIRST_LETTER_BIT = 0x2, SELECTION_BIT = 0x4, + BEFORE_BIT = 0x8, AFTER_BIT = 0x10, MARKER_BIT = 0x20, + REPLACED_BIT = 0x40 + }; + +static int pseudoBit(RenderStyle::PseudoId pseudo) +{ + switch (pseudo) { + case RenderStyle::BEFORE: + return BEFORE_BIT; + case RenderStyle::AFTER: + return AFTER_BIT; + case RenderStyle::MARKER: + return MARKER_BIT; + case RenderStyle::REPLACED: + return REPLACED_BIT; + case RenderStyle::FIRST_LINE: + return FIRST_LINE_BIT; + case RenderStyle::FIRST_LETTER: + return FIRST_LETTER_BIT; + case RenderStyle::SELECTION: + return SELECTION_BIT; + default: + return NO_BIT; + } +} + +bool RenderStyle::hasPseudoStyle(PseudoId pseudo) const +{ + return (pseudoBit(pseudo) & noninherited_flags.f._pseudoBits) != 0; +} + +void RenderStyle::setHasPseudoStyle(PseudoId pseudo, bool b) +{ + if (b) + noninherited_flags.f._pseudoBits |= pseudoBit(pseudo); + else + noninherited_flags.f._pseudoBits &= ~(pseudoBit(pseudo)); +} + +RenderStyle* RenderStyle::getPseudoStyle(PseudoId pid) const +{ + if (!hasPseudoStyle(pid)) return 0; + + RenderStyle *ps = 0; + if (noninherited_flags.f._styleType==NOPSEUDO) + for (ps = pseudoStyle; ps; ps = ps->pseudoStyle) + if (ps->noninherited_flags.f._styleType==pid) + break; + return ps; +} + +RenderStyle* RenderStyle::addPseudoStyle(PseudoId pid) +{ + if (hasPseudoStyle(pid)) return getPseudoStyle(pid); + + RenderStyle *ps = 0; + + switch (pid) { + case FIRST_LETTER: // pseudo-elements (FIRST_LINE has a special handling) + case SELECTION: + case BEFORE: + case AFTER: + ps = new RenderStyle(); + break; + default: + ps = new RenderStyle(*this); // use the real copy constructor to get an identical copy + } + ps->ref(); + ps->noninherited_flags.f._styleType = pid; + ps->pseudoStyle = pseudoStyle; + + pseudoStyle = ps; + + setHasPseudoStyle(pid, true); + + return ps; +} + +void RenderStyle::removePseudoStyle(PseudoId pid) +{ + RenderStyle *ps = pseudoStyle; + RenderStyle *prev = this; + + while (ps) { + if (ps->noninherited_flags.f._styleType==pid) { + prev->pseudoStyle = ps->pseudoStyle; + ps->deref(); + return; + } + prev = ps; + ps = ps->pseudoStyle; + } + + setHasPseudoStyle(pid, false); +} + + +bool RenderStyle::inheritedNotEqual( RenderStyle *other ) const +{ + return + ( + inherited_flags != other->inherited_flags || + inherited != other->inherited || + css3InheritedData != other->css3InheritedData + ); +} + +/* + compares two styles. The result gives an idea of the action that + needs to be taken when replacing the old style with a new one. + + CbLayout: The containing block of the object needs a relayout. + Layout: the RenderObject needs a relayout after the style change + Visible: The change is visible, but no relayout is needed + NonVisible: The object does need neither repaint nor relayout after + the change. + + ### TODO: + A lot can be optimised here based on the display type, lots of + optimisations are unimplemented, and currently result in the + worst case result causing a relayout of the containing block. +*/ +RenderStyle::Diff RenderStyle::diff( const RenderStyle *other ) const +{ + // we anyway assume they are the same +// EDisplay _display : 5; + + // NonVisible: +// ECursor _cursor_style : 4; +// EUserInput _user_input : 2; as long as :enabled is not impl'd + +// ### this needs work to know more exactly if we need a relayout +// or just a repaint + +// non-inherited attributes +// DataRef<StyleBoxData> box; +// DataRef<StyleVisualData> visual; +// DataRef<StyleSurroundData> surround; + +// inherited attributes +// DataRef<StyleInheritedData> inherited; + + if ( *box.get() != *other->box.get() || + *visual.get() != *other->visual.get() || + (*surround.get() != *other->surround.get() + && (other->position() == STATIC || other->position() != position())) || + !(inherited->indent == other->inherited->indent) || + !(inherited->line_height == other->inherited->line_height) || + !(inherited->style_image == other->inherited->style_image) || + !(inherited->font == other->inherited->font) || + !(inherited->border_hspacing == other->inherited->border_hspacing) || + !(inherited->border_vspacing == other->inherited->border_vspacing) || + !(inherited_flags.f._visuallyOrdered == other->inherited_flags.f._visuallyOrdered) || + !(inherited_flags.f._htmlHacks == other->inherited_flags.f._htmlHacks) || + !(noninherited_flags.f._textOverflow == other->noninherited_flags.f._textOverflow) ) + return CbLayout; + + // changes causing Layout changes: + +// only for tables: +// _border_collapse +// EEmptyCell _empty_cells : 2 ; +// ECaptionSide _caption_side : 2; +// ETableLayout _table_layout : 1; +// EPosition _position : 2; +// EFloat _floating : 2; + if ( ((int)noninherited_flags.f._display) >= TABLE ) { + if ( !(inherited_flags.f._empty_cells == other->inherited_flags.f._empty_cells) || + !(inherited_flags.f._caption_side == other->inherited_flags.f._caption_side) || + !(inherited_flags.f._border_collapse == other->inherited_flags.f._border_collapse) || + !(noninherited_flags.f._table_layout == other->noninherited_flags.f._table_layout) || + !(noninherited_flags.f._position == other->noninherited_flags.f._position) || + !(noninherited_flags.f._floating == other->noninherited_flags.f._floating) || + !(noninherited_flags.f._flowAroundFloats == other->noninherited_flags.f._flowAroundFloats) || + !(noninherited_flags.f._unicodeBidi == other->noninherited_flags.f._unicodeBidi) ) + return CbLayout; + } + +// only for lists: +// EListStyleType _list_style_type : 5 ; +// EListStylePosition _list_style_position :1; + if (noninherited_flags.f._display == LIST_ITEM ) { + if ( !(inherited_flags.f._list_style_type == other->inherited_flags.f._list_style_type) || + !(inherited_flags.f._list_style_position == other->inherited_flags.f._list_style_position) ) + return Layout; + } + +// ### These could be better optimised +// ETextAlign _text_align : 3; +// ETextTransform _text_transform : 4; +// EDirection _direction : 1; +// EWhiteSpace _white_space : 2; +// EClear _clear : 2; + if ( !(inherited_flags.f._text_align == other->inherited_flags.f._text_align) || + !(inherited_flags.f._text_transform == other->inherited_flags.f._text_transform) || + !(inherited_flags.f._direction == other->inherited_flags.f._direction) || + !(inherited_flags.f._white_space == other->inherited_flags.f._white_space) || + !(noninherited_flags.f._clear == other->noninherited_flags.f._clear) + ) + return Layout; + + // Overflow returns a layout hint. + if (noninherited_flags.f._overflowX != other->noninherited_flags.f._overflowX || + noninherited_flags.f._overflowY != other->noninherited_flags.f._overflowY) + return Layout; + +// only for inline: +// EVerticalAlign _vertical_align : 4; + + if ( !(noninherited_flags.f._display == INLINE) && + !(noninherited_flags.f._vertical_align == other->noninherited_flags.f._vertical_align) ) + return Layout; + + if (*surround.get() != *other->surround.get()) { + assert( other->position() != STATIC ); // this style is positioned or relatively positioned + if ( surround->hasSamePBMData(*other->surround.get()) && // padding/border/margin are identical + (other->position() == RELATIVE || + !(other->left().isVariable() && other->right().isVariable()) && // X isn't static + !(other->top().isVariable() && other->bottom().isVariable()) )) // neither is Y + // therefore only the offset is different + return Position; + return Layout; + } + + // Visible: +// EVisibility _visibility : 2; +// int _text_decorations : 4; +// DataRef<StyleBackgroundData> background; + if (inherited->color != other->inherited->color || + !(inherited_flags.f._visibility == other->inherited_flags.f._visibility) || + !(inherited_flags.f._text_decorations == other->inherited_flags.f._text_decorations) || + !(noninherited_flags.f._hasClip == other->noninherited_flags.f._hasClip) || + visual->textDecoration != other->visual->textDecoration || + *background.get() != *other->background.get() || + css3NonInheritedData->opacity != other->css3NonInheritedData->opacity || + !css3InheritedData->shadowDataEquivalent(*other->css3InheritedData.get()) + ) + return Visible; + + RenderStyle::Diff ch = Equal; + // Check for visible pseudo-changes: + if (hasPseudoStyle(FIRST_LINE) != other->hasPseudoStyle(FIRST_LINE)) + ch = Visible; + else + if (hasPseudoStyle(FIRST_LINE) && other->hasPseudoStyle(FIRST_LINE)) + ch = getPseudoStyle(FIRST_LINE)->diff(other->getPseudoStyle(FIRST_LINE)); + + if (ch != Equal) return ch; + + // Check for visible pseudo-changes: + if (hasPseudoStyle(SELECTION) != other->hasPseudoStyle(SELECTION)) + ch = Visible; + else + if (hasPseudoStyle(SELECTION) && other->hasPseudoStyle(SELECTION)) + ch = getPseudoStyle(SELECTION)->diff(other->getPseudoStyle(SELECTION)); + + return ch; +} + + +RenderStyle* RenderStyle::_default = 0; + +void RenderStyle::cleanup() +{ + delete _default; + _default = 0; +} + +void RenderStyle::setPaletteColor(QPalette::ColorGroup g, QColorGroup::ColorRole r, const QColor& c) +{ + visual.access()->palette.setColor(g,r,c); +} + +void RenderStyle::adjustBackgroundLayers() +{ + if (backgroundLayers()->next()) { + // First we cull out layers that have no properties set. + accessBackgroundLayers()->cullEmptyLayers(); + + // Next we repeat patterns into layers that don't have some properties set. + accessBackgroundLayers()->fillUnsetProperties(); + } +} + +void RenderStyle::setClip( Length top, Length right, Length bottom, Length left ) +{ + StyleVisualData *data = visual.access(); + data->clip.top = top; + data->clip.right = right; + data->clip.bottom = bottom; + data->clip.left = left; +} + +void RenderStyle::setQuotes(DOM::QuotesValueImpl* q) +{ + DOM::QuotesValueImpl *t = inherited->quotes; + inherited.access()->quotes = q; + if (q) q->ref(); + if (t) t->deref(); +} + +QString RenderStyle::openQuote(int level) const +{ + if (inherited->quotes) + return inherited->quotes->openQuote(level); + else + return "\""; // 0 is default quotes +} + +QString RenderStyle::closeQuote(int level) const +{ + if (inherited->quotes) + return inherited->quotes->closeQuote(level); + else + return "\""; // 0 is default quotes +} + +void RenderStyle::addContent(CachedObject* o) +{ + if (!o) + return; // The object is null. Nothing to do. Just bail. + + StyleGeneratedData *t_generated = generated.access(); + + ContentData* lastContent = t_generated->content; + while (lastContent && lastContent->_nextContent) + lastContent = lastContent->_nextContent; + + ContentData* newContentData = new ContentData; + + if (lastContent) + lastContent->_nextContent = newContentData; + else + t_generated->content = newContentData; + + // o->ref(); + newContentData->_content.object = o; + newContentData->_contentType = CONTENT_OBJECT; +} + +void RenderStyle::addContent(DOM::DOMStringImpl* s) +{ + if (!s) + return; // The string is null. Nothing to do. Just bail. + + StyleGeneratedData *t_generated = generated.access(); + + ContentData* lastContent = t_generated->content; + while (lastContent && lastContent->_nextContent) + lastContent = lastContent->_nextContent; + + if (lastContent) { + if (lastContent->_contentType == CONTENT_TEXT) { + // We can augment the existing string and share this ContentData node. + DOMStringImpl* oldStr = lastContent->_content.text; + DOMStringImpl* newStr = oldStr->copy(); + newStr->ref(); + oldStr->deref(); + newStr->append(s); + lastContent->_content.text = newStr; + return; + } + } + + ContentData* newContentData = new ContentData; + + if (lastContent) + lastContent->_nextContent = newContentData; + else + t_generated->content = newContentData; + + newContentData->_content.text = s; + newContentData->_content.text->ref(); + newContentData->_contentType = CONTENT_TEXT; + +} + +void RenderStyle::addContent(DOM::CounterImpl* c) +{ + if (!c) + return; + + StyleGeneratedData *t_generated = generated.access(); + + ContentData* lastContent = t_generated->content; + while (lastContent && lastContent->_nextContent) + lastContent = lastContent->_nextContent; + + ContentData* newContentData = new ContentData; + + if (lastContent) + lastContent->_nextContent = newContentData; + else + t_generated->content = newContentData; + + c->ref(); + newContentData->_content.counter = c; + newContentData->_contentType = CONTENT_COUNTER; +} + +void RenderStyle::addContent(EQuoteContent q) +{ + if (q == NO_QUOTE) + return; + + StyleGeneratedData *t_generated = generated.access(); + + ContentData* lastContent = t_generated->content; + while (lastContent && lastContent->_nextContent) + lastContent = lastContent->_nextContent; + + ContentData* newContentData = new ContentData; + + if (lastContent) + lastContent->_nextContent = newContentData; + else + t_generated->content = newContentData; + + newContentData->_content.quote = q; + newContentData->_contentType = CONTENT_QUOTE; +} + +// content: normal is the same as having no content at all +void RenderStyle::setContentNormal() { + if (generated->content != 0) { + delete generated->content; + generated.access()->content = 0; + } +} + +// content: none, add an empty content node +void RenderStyle::setContentNone() { + setContentNormal(); + generated.access()->content = new ContentData; +} + +void RenderStyle::setContentData(ContentData *data) { + if (data != generated->content) { + if (data) + generated.access()->content = new ContentData(*data); + else + generated.access()->content = 0; + } +} + +ContentData::ContentData(const ContentData& o) : _contentType(o._contentType) +{ + switch (_contentType) { + case CONTENT_OBJECT: + _content.object = o._content.object; + break; + case CONTENT_TEXT: + _content.text = o._content.text; + _content.text->ref(); + break; + case CONTENT_COUNTER: + _content.counter = o._content.counter; + _content.counter->ref(); + break; + case CONTENT_QUOTE: + _content.quote = o._content.quote; + break; + case CONTENT_NONE: + default: + break; + } + + _nextContent = o._nextContent ? new ContentData(*o._nextContent) : 0; +} + +ContentData::~ContentData() +{ + clearContent(); +} + +void ContentData::clearContent() +{ + delete _nextContent; + _nextContent = 0; + + switch (_contentType) + { + case CONTENT_OBJECT: + _content.object = 0; + break; + case CONTENT_TEXT: + _content.text->deref(); + _content.text = 0; + break; + case CONTENT_COUNTER: + _content.counter->deref(); + _content.counter = 0; + break; + case CONTENT_QUOTE: + _content.quote = NO_QUOTE; + break; + default: + ; + } +} + +void RenderStyle::setTextShadow(ShadowData* val, bool add) +{ + StyleCSS3InheritedData* css3Data = css3InheritedData.access(); + if (!add) { + delete css3Data->textShadow; + css3Data->textShadow = val; + return; + } + + ShadowData* last = css3Data->textShadow; + while (last->next) last = last->next; + last->next = val; +} + +ShadowData::ShadowData(const ShadowData& o) +:x(o.x), y(o.y), blur(o.blur), color(o.color) +{ + next = o.next ? new ShadowData(*o.next) : 0; +} + +bool ShadowData::operator==(const ShadowData& o) const +{ + if ((next && !o.next) || (!next && o.next) || + (next && o.next && *next != *o.next)) + return false; + + return x == o.x && y == o.y && blur == o.blur && color == o.color; +} + +static bool hasCounter(const DOM::DOMString& c, CSSValueListImpl *l) +{ + int len = l->length(); + for(int i=0; i<len; i++) { + CounterActImpl* ca = static_cast<CounterActImpl*>(l->item(i)); + Q_ASSERT(ca != 0); + if (ca->m_counter == c) return true; + } + return false; +} + +bool RenderStyle::hasCounterReset(const DOM::DOMString& c) const +{ + if (generated->counter_reset) + return hasCounter(c, generated->counter_reset); + else + return false; +} + +bool RenderStyle::hasCounterIncrement(const DOM::DOMString& c) const +{ + if (generated->counter_increment) + return hasCounter(c, generated->counter_increment); + else + return false; +} + +static short readCounter(const DOM::DOMString& c, CSSValueListImpl *l) +{ + int len = l->length(); + for(int i=0; i<len; i++) { + CounterActImpl* ca = static_cast<CounterActImpl*>(l->item(i)); + Q_ASSERT(ca != 0); + if (ca->m_counter == c) return ca->m_value; + } + return 0; +} + +short RenderStyle::counterReset(const DOM::DOMString& c) const +{ + if (generated->counter_reset) + return readCounter(c, generated->counter_reset); + else + return 0; +} + +short RenderStyle::counterIncrement(const DOM::DOMString& c) const +{ + if (generated->counter_increment) + return readCounter(c, generated->counter_increment); + else + return 0; +} + +void RenderStyle::setCounterReset(CSSValueListImpl *l) +{ + CSSValueListImpl *t = generated->counter_reset; + generated.access()->counter_reset = l; + if (l) l->ref(); + if (t) t->deref(); +} + +void RenderStyle::setCounterIncrement(CSSValueListImpl *l) +{ + CSSValueListImpl *t = generated->counter_increment; + generated.access()->counter_increment = l; + if (l) l->ref(); + if (t) t->deref(); +} + +#ifdef ENABLE_DUMP + +static QString describeFont( const QFont &f) +{ + QString res = "'" + f.family() + "' "; + + if ( f.pointSize() > 0) + res += QString::number( f.pointSize() ) + "pt"; + else + res += QString::number( f.pixelSize() ) + "px"; + + if ( f.bold() ) + res += " bold"; + if ( f.italic() ) + res += " italic"; + if ( f.underline() ) + res += " underline"; + if ( f.overline() ) + res += " overline"; + if ( f.strikeOut() ) + res += " strikeout"; + return res; +} + +QString RenderStyle::createDiff( const RenderStyle &parent ) const +{ + QString res; + if ( color().isValid() && parent.color() != color() ) + res += " [color=" + color().name() + "]"; + if ( backgroundColor().isValid() && parent.backgroundColor() != backgroundColor() ) + res += " [bgcolor=" + backgroundColor().name() + "]"; + if ( parent.font() != font() ) + res += " [font=" + describeFont( font() ) + "]"; + + return res; +} +#endif + +RenderPageStyle::RenderPageStyle() : next(0), m_pageType(ANY_PAGE) +{ +} + +RenderPageStyle::~RenderPageStyle() +{ + delete next; +} + +RenderPageStyle* RenderPageStyle::getPageStyle(PageType type) +{ + RenderPageStyle *ps = 0; + for (ps = this; ps; ps = ps->next) + if (ps->m_pageType==type) + break; + return ps; +} + +RenderPageStyle* RenderPageStyle::addPageStyle(PageType type) +{ + RenderPageStyle *ps = getPageStyle(type); + + if (!ps) + { + ps = new RenderPageStyle(*this); // use the real copy constructor to get an identical copy + ps->m_pageType = type; + + ps->next = next; + next = ps; + } + + return ps; +} + +void RenderPageStyle::removePageStyle(PageType type) +{ + RenderPageStyle *ps = next; + RenderPageStyle *prev = this; + + while (ps) { + if (ps->m_pageType==type) { + prev->next = ps->next; + delete ps; + return; + } + prev = ps; + ps = ps->next; + } +} diff --git a/khtml/rendering/render_style.h b/khtml/rendering/render_style.h new file mode 100644 index 000000000..f8c2affd7 --- /dev/null +++ b/khtml/rendering/render_style.h @@ -0,0 +1,1517 @@ +/* + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 2000-2003 Lars Knoll (knoll@kde.org) + * (C) 2000 Antti Koivisto (koivisto@kde.org) + * (C) 2000-2003 Dirk Mueller (mueller@kde.org) + * (C) 2003-2005 Apple Computer, Inc. + * (C) 2004-2006 Allan Sandfeld Jensen (kde@carewolf.com) + * + * 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. + * + */ +#ifndef RENDERSTYLE_H +#define RENDERSTYLE_H + +/* + * WARNING: + * -------- + * + * The order of the values in the enums have to agree with the order specified + * in cssvalues.in, otherwise some optimizations in the parser will fail, + * and produce invaliud results. + */ + +#include <qcolor.h> +#include <qfont.h> +#include <qfontmetrics.h> +#include <qptrlist.h> +#include <qpalette.h> +#include <qapplication.h> + +#include "dom/dom_misc.h" +#include "dom/dom_string.h" +#include "misc/khtmllayout.h" +#include "misc/shared.h" +#include "rendering/font.h" + +#include <assert.h> + +#define SET_VAR(group,variable,value) \ + if (!(group->variable == value)) \ + group.access()->variable = value; + +#ifndef ENABLE_DUMP +#ifndef NDEBUG +#define ENABLE_DUMP 1 +#endif +#endif + +namespace DOM { + class DOMStringImpl; + class ShadowValueImpl; + class QuotesValueImpl; + class CounterImpl; + class CSSValueListImpl; + class CounterActImpl; +} + +namespace khtml { + + class CachedImage; + class CachedObject; + +template <class DATA> +class DataRef +{ +public: + + DataRef() + { + data=0; + } + DataRef( const DataRef<DATA> &d ) + { + data = d.data; + data->ref(); + } + + ~DataRef() + { + if(data) data->deref(); + } + + const DATA* operator->() const + { + return data; + } + + const DATA* get() const + { + return data; + } + + + DATA* access() + { + if (!data->hasOneRef()) + { + data->deref(); + data = new DATA(*data); + data->ref(); + } + return data; + } + + void init() + { + data = new DATA; + data->ref(); + } + + DataRef<DATA>& operator=(const DataRef<DATA>& d) + { + if (data==d.data) + return *this; + if (data) + data->deref(); + data = d.data; + + data->ref(); + + return *this; + } + + bool operator == ( const DataRef<DATA> &o ) const { + return (*data == *(o.data) ); + } + bool operator != ( const DataRef<DATA> &o ) const { + return (*data != *(o.data) ); + } + +private: + DATA* data; +}; + + +//------------------------------------------------ + +//------------------------------------------------ +// Box model attributes. Not inherited. + +struct LengthBox +{ + LengthBox() + { + } + LengthBox( LengthType t ) + : left( t ), right ( t ), top( t ), bottom( t ) {} + + Length left; + Length right; + Length top; + Length bottom; + Length& operator=(Length& len) + { + left=len; + right=len; + top=len; + bottom=len; + return len; + } + + bool operator==(const LengthBox& o) const + { + return left==o.left && right==o.right && top==o.top && bottom==o.bottom; + } + + + bool nonZero() const { return left.value() || right.value() || top.value() || bottom.value(); } +}; + + + +enum EPosition { + STATIC, RELATIVE, ABSOLUTE, FIXED +}; + +enum EFloat { + FNONE = 0, FLEFT = 0x01, FRIGHT = 0x02, FLEFT_ALIGN = 0x05, FRIGHT_ALIGN = 0x06 +}; + + +//------------------------------------------------ +// Border attributes. Not inherited. + + +// These have been defined in the order of their precedence for border-collapsing. Do +// not change this order! +enum EBorderStyle { + BNATIVE, BNONE, BHIDDEN, INSET, GROOVE, RIDGE, OUTSET, DOTTED, DASHED, SOLID, DOUBLE +}; + +class BorderValue +{ +public: + BorderValue() : width( 3 ), style( BNONE ) {} + + QColor color; + unsigned short width : 12; + EBorderStyle style : 6; + + bool nonZero(bool checkStyle = true) const { + return width != 0 && !(checkStyle && style == BNONE); + } + + bool isTransparent() const { + return color.isValid() && qAlpha(color.rgb()) == 0; + } + + bool operator==(const BorderValue& o) const + { + return width==o.width && style==o.style && color==o.color; + } + + bool operator!=(const BorderValue& o) const + { + return !(*this == o); + } +}; + +class OutlineValue : public BorderValue +{ + public: + OutlineValue() + { + _offset = 0; + _auto = false; + } + + bool operator==(const OutlineValue& o) const + { + return width==o.width && style==o.style && color==o.color && _offset == o._offset && _auto == o._auto; + } + + bool operator!=(const OutlineValue& o) const + { + return !(*this == o); + } + + int _offset; + bool _auto; +}; + +enum EBorderPrecedence { BOFF, BTABLE, BCOLGROUP, BCOL, BROWGROUP, BROW, BCELL }; + +struct CollapsedBorderValue +{ + CollapsedBorderValue() :border(0), precedence(BOFF) {} + CollapsedBorderValue(const BorderValue* b, EBorderPrecedence p) :border(b), precedence(p) {} + + int width() const { return border && border->nonZero() ? border->width : 0; } + EBorderStyle style() const { return border ? border->style : BHIDDEN; } + bool exists() const { return border; } + QColor color() const { return border ? border->color : QColor(); } + bool isTransparent() const { return border ? border->isTransparent() : true; } + + bool operator==(const CollapsedBorderValue& o) const + { + if (!border) return !o.border; + if (!o.border) return false; + return *border == *o.border && precedence == o.precedence; + } + + const BorderValue* border; + EBorderPrecedence precedence; +}; + +class BorderData : public Shared<BorderData> +{ +public: + BorderValue left; + BorderValue right; + BorderValue top; + BorderValue bottom; + + bool hasBorder() const + { + return left.nonZero() || right.nonZero() || top.nonZero() || bottom.nonZero(); + } + + unsigned short borderLeftWidth() const { + if (left.style == BNONE || left.style == BHIDDEN || left.style == BNATIVE) + return 0; + return left.width; + } + + unsigned short borderRightWidth() const { + if (right.style == BNONE || right.style == BHIDDEN || right.style == BNATIVE) + return 0; + return right.width; + } + + unsigned short borderTopWidth() const { + if (top.style == BNONE || top.style == BHIDDEN || top.style == BNATIVE) + return 0; + return top.width; + } + + unsigned short borderBottomWidth() const { + if (bottom.style == BNONE || bottom.style == BHIDDEN || bottom.style == BNATIVE) + return 0; + return bottom.width; + } + + bool operator==(const BorderData& o) const + { + return left==o.left && right==o.right && top==o.top && bottom==o.bottom; + } + +}; + +class StyleSurroundData : public Shared<StyleSurroundData> +{ +public: + StyleSurroundData(); + + StyleSurroundData(const StyleSurroundData& o ); + bool operator==(const StyleSurroundData& o) const; + bool operator!=(const StyleSurroundData& o) const { + return !(*this == o); + } + bool hasSamePBMData(const StyleSurroundData& o) const { + return (margin == o.margin) && (padding == o.padding) && (border == o.border); + } + + LengthBox offset; + LengthBox margin; + LengthBox padding; + BorderData border; +}; + + +//------------------------------------------------ +// Box attributes. Not inherited. + +enum EBoxSizing { + BORDER_BOX, CONTENT_BOX +}; + +class StyleBoxData : public Shared<StyleBoxData> +{ +public: + StyleBoxData(); + + StyleBoxData(const StyleBoxData& o ); + + + // copy and assignment +// StyleBoxData(const StyleBoxData &other); +// const StyleBoxData &operator = (const StyleBoxData &other); + + bool operator==(const StyleBoxData& o) const; + bool operator!=(const StyleBoxData& o) const { + return !(*this == o); + } + + Length width; + Length height; + + Length min_width; + Length max_width; + + Length min_height; + Length max_height; + + Length vertical_align; + + EBoxSizing box_sizing; + + signed int z_index :31; + bool z_auto : 1; +}; + +//------------------------------------------------ +// Random visual rendering model attributes. Not inherited. + +enum EOverflow { + OVISIBLE, OHIDDEN, OSCROLL, OAUTO, OMARQUEE +}; + +enum EVerticalAlign { + BASELINE, MIDDLE, SUB, SUPER, TEXT_TOP, + TEXT_BOTTOM, TOP, BOTTOM, BASELINE_MIDDLE, LENGTH +}; + +enum EClear{ + CNONE = 0, CLEFT = 1, CRIGHT = 2, CBOTH = 3 +}; + +enum ETableLayout { + TAUTO, TFIXED +}; + +enum EUnicodeBidi { + UBNormal, Embed, Override +}; + +class StyleVisualData : public Shared<StyleVisualData> +{ +public: + StyleVisualData(); + + ~StyleVisualData(); + + StyleVisualData(const StyleVisualData& o ); + + bool operator==( const StyleVisualData &o ) const { + return ( clip == o.clip && + palette == o.palette ); + } + bool operator!=( const StyleVisualData &o ) const { + return !(*this == o); + } + + LengthBox clip; + unsigned textDecoration : 4; // Text decorations defined *only* by this element. + + QPalette palette; //widget styling with IE attributes + +}; + +//------------------------------------------------ +enum EBackgroundBox { + BGBORDER, BGPADDING, BGCONTENT +}; + +enum EBackgroundRepeat { + REPEAT, REPEAT_X, REPEAT_Y, NO_REPEAT +}; + +struct LengthSize { + Length width; + Length height; +}; + +struct BackgroundLayer { +public: + BackgroundLayer(); + ~BackgroundLayer(); + + CachedImage* backgroundImage() const { return m_image; } + Length backgroundXPosition() const { return m_xPosition; } + Length backgroundYPosition() const { return m_yPosition; } + bool backgroundAttachment() const { return m_bgAttachment; } + EBackgroundBox backgroundClip() const { return m_bgClip; } + EBackgroundBox backgroundOrigin() const { return m_bgOrigin; } + EBackgroundRepeat backgroundRepeat() const { return m_bgRepeat; } + LengthSize backgroundSize() const { return m_backgroundSize; } + + BackgroundLayer* next() const { return m_next; } + BackgroundLayer* next() { return m_next; } + + bool isBackgroundImageSet() const { return m_imageSet; } + bool isBackgroundXPositionSet() const { return m_xPosSet; } + bool isBackgroundYPositionSet() const { return m_yPosSet; } + bool isBackgroundAttachmentSet() const { return m_attachmentSet; } + bool isBackgroundClipSet() const { return m_clipSet; } + bool isBackgroundOriginSet() const { return m_originSet; } + bool isBackgroundRepeatSet() const { return m_repeatSet; } + bool isBackgroundSizeSet() const { return m_backgroundSizeSet; } + + void setBackgroundImage(CachedImage* i) { m_image = i; m_imageSet = true; } + void setBackgroundXPosition(const Length& l) { m_xPosition = l; m_xPosSet = true; } + void setBackgroundYPosition(const Length& l) { m_yPosition = l; m_yPosSet = true; } + void setBackgroundAttachment(bool b) { m_bgAttachment = b; m_attachmentSet = true; } + void setBackgroundClip(EBackgroundBox b) { m_bgClip = b; m_clipSet = true; } + void setBackgroundOrigin(EBackgroundBox b) { m_bgOrigin = b; m_originSet = true; } + void setBackgroundRepeat(EBackgroundRepeat r) { m_bgRepeat = r; m_repeatSet = true; } + void setBackgroundSize(const LengthSize& b) { m_backgroundSize = b; m_backgroundSizeSet = true; } + + void clearBackgroundImage() { m_imageSet = false; } + void clearBackgroundXPosition() { m_xPosSet = false; } + void clearBackgroundYPosition() { m_yPosSet = false; } + void clearBackgroundAttachment() { m_attachmentSet = false; } + void clearBackgroundClip() { m_clipSet = false; } + void clearBackgroundOrigin() { m_originSet = false; } + void clearBackgroundRepeat() { m_repeatSet = false; } + void clearBackgroundSize() { m_backgroundSizeSet = false; } + + void setNext(BackgroundLayer* n) { if (m_next != n) { delete m_next; m_next = n; } } + + BackgroundLayer& operator=(const BackgroundLayer& o); + BackgroundLayer(const BackgroundLayer& o); + + bool operator==(const BackgroundLayer& o) const; + bool operator!=(const BackgroundLayer& o) const { + return !(*this == o); + } + + bool containsImage(CachedImage* c) const { if (c == m_image) return true; if (m_next) return m_next->containsImage(c); return false; } + + bool hasImage() const { + if (m_image) + return true; + return m_next ? m_next->hasImage() : false; + } + bool hasFixedImage() const { + if (m_image && !m_bgAttachment) + return true; + return m_next ? m_next->hasFixedImage() : false; + } + + void fillUnsetProperties(); + void cullEmptyLayers(); + + CachedImage* m_image; + + Length m_xPosition; + Length m_yPosition; + + bool m_bgAttachment : 1; + EBackgroundBox m_bgClip : 2; + EBackgroundBox m_bgOrigin : 2; + EBackgroundRepeat m_bgRepeat : 2; + + LengthSize m_backgroundSize; + + bool m_imageSet : 1; + bool m_attachmentSet : 1; + bool m_clipSet : 1; + bool m_originSet : 1; + bool m_repeatSet : 1; + bool m_xPosSet : 1; + bool m_yPosSet : 1; + bool m_backgroundSizeSet : 1; + + BackgroundLayer* m_next; +}; + +class StyleBackgroundData : public Shared<StyleBackgroundData> +{ +public: + StyleBackgroundData(); + ~StyleBackgroundData() {} + StyleBackgroundData(const StyleBackgroundData& o ); + + bool operator==(const StyleBackgroundData& o) const; + bool operator!=(const StyleBackgroundData &o) const { + return !(*this == o); + } + + BackgroundLayer m_background; + QColor m_color; + OutlineValue m_outline; +}; + +enum EQuoteContent { + NO_QUOTE = 0, OPEN_QUOTE, CLOSE_QUOTE, NO_OPEN_QUOTE, NO_CLOSE_QUOTE +}; + +enum ContentType { + CONTENT_NONE = 0, CONTENT_NORMAL, CONTENT_OBJECT, + CONTENT_TEXT, CONTENT_COUNTER, CONTENT_QUOTE +}; + +struct ContentData { + ContentData() : _contentType( CONTENT_NONE ), _nextContent(0) {} + ContentData(const ContentData& o); + ~ContentData(); + void clearContent(); + + DOM::DOMStringImpl* contentText() + { if (_contentType == CONTENT_TEXT) return _content.text; return 0; } + CachedObject* contentObject() + { if (_contentType == CONTENT_OBJECT) return _content.object; return 0; } + DOM::CounterImpl* contentCounter() + { if (_contentType == CONTENT_COUNTER) return _content.counter; return 0; } + EQuoteContent contentQuote() + { if (_contentType == CONTENT_QUOTE) return _content.quote; return NO_QUOTE; } + + ContentType _contentType; + + union { + CachedObject* object; + DOM::DOMStringImpl* text; + DOM::CounterImpl* counter; + EQuoteContent quote; + } _content ; + + ContentData* _nextContent; +}; + +class StyleGeneratedData : public Shared<StyleGeneratedData> +{ +public: + StyleGeneratedData(); + ~StyleGeneratedData(); + StyleGeneratedData(const StyleGeneratedData& o ); + + bool operator==(const StyleGeneratedData& o) const; + bool operator!=(const StyleGeneratedData &o) const { + return !(*this == o); + } + + bool contentDataEquivalent(const StyleGeneratedData* otherStyle) const; + bool counterDataEquivalent(const StyleGeneratedData* otherStyle) const; + + ContentData *content; + DOM::CSSValueListImpl *counter_reset; + DOM::CSSValueListImpl *counter_increment; +}; + +//------------------------------------------------ +// CSS3 Marquee Properties + +enum EMarqueeBehavior { MNONE, MSCROLL, MSLIDE, MALTERNATE, MUNFURL }; +enum EMarqueeDirection { MAUTO = 0, MLEFT = 1, MRIGHT = -1, MUP = 2, MDOWN = -2, MFORWARD = 3, MBACKWARD = -3 }; + +class StyleMarqueeData : public Shared<StyleMarqueeData> +{ +public: + StyleMarqueeData(); + StyleMarqueeData(const StyleMarqueeData& o); + + bool operator==(const StyleMarqueeData& o) const; + bool operator!=(const StyleMarqueeData& o) const { + return !(*this == o); + } + + Length increment; + int speed; + + int loops; // -1 means infinite. + + EMarqueeBehavior behavior : 3; + EMarqueeDirection direction : 3; +}; + +// This struct holds information about shadows for the text-shadow and box-shadow properties. +struct ShadowData { + ShadowData(int _x, int _y, int _blur, const QColor& _color) + :x(_x), y(_y), blur(_blur), color(_color), next(0) {} + ShadowData(const ShadowData& o); + + ~ShadowData() { delete next; } + + bool operator==(const ShadowData& o) const; + bool operator!=(const ShadowData &o) const { + return !(*this == o); + } + + int x; + int y; + int blur; + QColor color; + ShadowData* next; +}; + +// This struct is for rarely used non-inherited CSS3 properties. By grouping them together, +// we save space, and only allocate this object when someone actually uses +// a non-inherited CSS3 property. +class StyleCSS3NonInheritedData : public Shared<StyleCSS3NonInheritedData> +{ +public: + StyleCSS3NonInheritedData(); + ~StyleCSS3NonInheritedData() {} + StyleCSS3NonInheritedData(const StyleCSS3NonInheritedData& o); + + bool operator==(const StyleCSS3NonInheritedData& o) const; + bool operator!=(const StyleCSS3NonInheritedData &o) const { + return !(*this == o); + } + + float opacity; // Whether or not we're transparent. +#ifdef APPLE_CHANGES // ### we don't have those (yet) + DataRef<StyleFlexibleBoxData> flexibleBox; // Flexible box properties +#endif + DataRef<StyleMarqueeData> marquee; // Marquee properties +}; + +// This struct is for rarely used inherited CSS3 properties. By grouping them together, +// we save space, and only allocate this object when someone actually uses +// an inherited CSS3 property. +class StyleCSS3InheritedData : public Shared<StyleCSS3InheritedData> +{ + public: + StyleCSS3InheritedData(); + ~StyleCSS3InheritedData(); + StyleCSS3InheritedData(const StyleCSS3InheritedData& o); + + bool operator==(const StyleCSS3InheritedData& o) const; + bool operator!=(const StyleCSS3InheritedData &o) const { + return !(*this == o); + } + bool shadowDataEquivalent(const StyleCSS3InheritedData& o) const; + + ShadowData* textShadow; // Our text shadow information for shadowed text drawing. +#ifdef APPLE_CHANGES + EUserModify userModify : 2; // Flag used for editing state + bool textSizeAdjust : 1; // An Apple extension. Not really CSS3 but not worth making a new struct over. +#endif + private: + StyleCSS3InheritedData &operator=(const StyleCSS3InheritedData &); +}; + +//------------------------------------------------ +// Inherited attributes. +// +// the inherited-decoration and inherited-shadow attributes +// are inherited from the +// first parent which is block level +// + +enum EWhiteSpace { + NORMAL, PRE, NOWRAP, PRE_WRAP, PRE_LINE, KHTML_NOWRAP +}; + +enum ETextAlign { + TAAUTO, LEFT, RIGHT, CENTER, JUSTIFY, KHTML_LEFT, KHTML_RIGHT, KHTML_CENTER +}; + +enum ETextTransform { + CAPITALIZE, UPPERCASE, LOWERCASE, TTNONE +}; + +enum EDirection { + LTR, RTL +}; + +enum ETextDecoration { + TDNONE = 0x0 , UNDERLINE = 0x1, OVERLINE = 0x2, LINE_THROUGH= 0x4, BLINK = 0x8 +}; + +enum EPageBreak { + PBAUTO, PBALWAYS, PBAVOID, + /* reserved for later use: */ + PBLEFT, PBRIGHT +}; + +class StyleInheritedData : public Shared<StyleInheritedData> +{ + StyleInheritedData& operator=(const StyleInheritedData&); +public: + StyleInheritedData(); + ~StyleInheritedData(); + StyleInheritedData(const StyleInheritedData& o ); + + bool operator==(const StyleInheritedData& o) const; + bool operator != ( const StyleInheritedData &o ) const { + return !(*this == o); + } + + Length indent; + // could be packed in a short but doesn't + // make a difference currently because of padding + Length line_height; + + CachedImage *style_image; + + khtml::Font font; + QColor color; + + short border_hspacing; + short border_vspacing; + + // Paged media properties. + short widows; + short orphans; + + DOM::QuotesValueImpl* quotes; +}; + + +enum EEmptyCell { + SHOW, HIDE +}; + +enum ECaptionSide { + CAPTOP, CAPBOTTOM, CAPLEFT, CAPRIGHT +}; + + +enum EListStyleType { + // Symbols: + LDISC, LCIRCLE, LSQUARE, LBOX, LDIAMOND, + // Numeric: + LDECIMAL, DECIMAL_LEADING_ZERO, ARABIC_INDIC, LAO, PERSIAN, URDU, THAI, TIBETAN, + // Algorithmic: + LOWER_ROMAN, UPPER_ROMAN, HEBREW, ARMENIAN, GEORGIAN, + // Ideographic: + CJK_IDEOGRAPHIC, JAPANESE_FORMAL, JAPANESE_INFORMAL, + SIMP_CHINESE_FORMAL, SIMP_CHINESE_INFORMAL, TRAD_CHINESE_FORMAL, TRAD_CHINESE_INFORMAL, + // Alphabetic: + LOWER_GREEK, UPPER_GREEK, LOWER_ALPHA, LOWER_LATIN, UPPER_ALPHA, UPPER_LATIN, + HIRAGANA, KATAKANA, HIRAGANA_IROHA, KATAKANA_IROHA, + // Special: + LNONE +}; + +inline bool isListStyleCounted(EListStyleType type) +{ + switch(type) { + case LDISC: case LCIRCLE: case LSQUARE: case LBOX: case LDIAMOND: + case LNONE: + return false; + default: + return true; + } +} + +enum EListStylePosition { OUTSIDE, INSIDE }; + +enum EVisibility { VISIBLE, HIDDEN, COLLAPSE }; + +enum ECursor { + CURSOR_AUTO, CURSOR_CROSS, CURSOR_DEFAULT, CURSOR_POINTER, CURSOR_PROGRESS, CURSOR_MOVE, + CURSOR_E_RESIZE, CURSOR_NE_RESIZE, CURSOR_NW_RESIZE, CURSOR_N_RESIZE, CURSOR_SE_RESIZE, CURSOR_SW_RESIZE, + CURSOR_S_RESIZE, CURSOR_W_RESIZE, CURSOR_TEXT, CURSOR_WAIT, CURSOR_HELP +}; + +enum EUserInput { + UI_ENABLED, UI_DISABLED, UI_NONE +}; + +//------------------------------------------------ + +enum EDisplay { + INLINE, BLOCK, LIST_ITEM, RUN_IN, + COMPACT, INLINE_BLOCK, TABLE, INLINE_TABLE, + TABLE_ROW_GROUP, TABLE_HEADER_GROUP, TABLE_FOOTER_GROUP, TABLE_ROW, + TABLE_COLUMN_GROUP, TABLE_COLUMN, TABLE_CELL, + TABLE_CAPTION, NONE +}; + +class RenderStyle : public Shared<RenderStyle> +{ + friend class CSSStyleSelector; +public: + KDE_EXPORT static void cleanup(); + + // pseudo elements + enum PseudoId { + NOPSEUDO, FIRST_LINE, FIRST_LETTER, SELECTION, + BEFORE, AFTER, REPLACED, MARKER + }; + +protected: + +// !START SYNC!: Keep this in sync with the copy constructor in render_style.cpp + + // inherit + struct InheritedFlags { + // 64 bit inherited, update unused when adding to the struct, or the operator will break. + bool operator==( const InheritedFlags &other ) const + { return _iflags ==other._iflags; } + bool operator!=( const InheritedFlags &other ) const + { return _iflags != other._iflags; } + + union { + struct { + EEmptyCell _empty_cells : 1 ; + ECaptionSide _caption_side : 2; + EListStyleType _list_style_type : 6; + EListStylePosition _list_style_position :1; + + EVisibility _visibility : 2; + ETextAlign _text_align : 4; + ETextTransform _text_transform : 2; + unsigned _text_decorations : 4; + ECursor _cursor_style : 5; + + EDirection _direction : 1; + bool _border_collapse : 1 ; + EWhiteSpace _white_space : 3; + // non CSS2 inherited + bool _visuallyOrdered : 1; + bool _htmlHacks :1; + EUserInput _user_input : 2; + + bool _page_break_inside : 1; // AUTO/AVOID + + unsigned int unused : 27; + } f; + Q_UINT64 _iflags; + }; + } inherited_flags; + +// don't inherit + struct NonInheritedFlags { + // 64 bit non-inherited, update unused when adding to the struct, or the operator will break. + bool operator==( const NonInheritedFlags &other ) const + { return _niflags == other._niflags; } + bool operator!=( const NonInheritedFlags &other ) const + { return _niflags != other._niflags; } + + union { + struct { + EDisplay _display : 5; + EDisplay _originalDisplay: 5; + EOverflow _overflowX : 4 ; + EOverflow _overflowY : 4 ; + EVerticalAlign _vertical_align : 4; + EClear _clear : 2; + EPosition _position : 2; + EFloat _floating : 3; + ETableLayout _table_layout : 1; + bool _flowAroundFloats :1; + + EPageBreak _page_break_before : 3; + EPageBreak _page_break_after : 3; + + PseudoId _styleType : 4; + bool _hasClip : 1; + unsigned _pseudoBits : 8; + EUnicodeBidi _unicodeBidi : 2; + + // non CSS2 non-inherited + bool _textOverflow : 1; // Whether or not lines that spill out should be truncated with "..." + + unsigned int unused : 11; + } f; + Q_UINT64 _niflags; + }; + } noninherited_flags; + +// non-inherited attributes + DataRef<StyleBoxData> box; + DataRef<StyleVisualData> visual; + DataRef<StyleBackgroundData> background; + DataRef<StyleSurroundData> surround; + DataRef<StyleGeneratedData> generated; + DataRef<StyleCSS3NonInheritedData> css3NonInheritedData; + +// inherited attributes + DataRef<StyleCSS3InheritedData> css3InheritedData; + DataRef<StyleInheritedData> inherited; + +// list of associated pseudo styles + RenderStyle* pseudoStyle; + +// !END SYNC! + +// static default style + static RenderStyle* _default; + +private: + RenderStyle(const RenderStyle*) {} + +protected: + void setBitDefaults() + { + inherited_flags.f._empty_cells = initialEmptyCells(); + inherited_flags.f._caption_side = initialCaptionSide(); + inherited_flags.f._list_style_type = initialListStyleType(); + inherited_flags.f._list_style_position = initialListStylePosition(); + inherited_flags.f._visibility = initialVisibility(); + inherited_flags.f._text_align = initialTextAlign(); + inherited_flags.f._text_transform = initialTextTransform(); + inherited_flags.f._text_decorations = initialTextDecoration(); + inherited_flags.f._cursor_style = initialCursor(); + inherited_flags.f._direction = initialDirection(); + inherited_flags.f._border_collapse = initialBorderCollapse(); + inherited_flags.f._white_space = initialWhiteSpace(); + inherited_flags.f._visuallyOrdered = false; + inherited_flags.f._htmlHacks=false; + inherited_flags.f._user_input = UI_NONE; + inherited_flags.f._page_break_inside = true; + inherited_flags.f.unused = 0; + + noninherited_flags._niflags = 0L; // for safety: without this, the equality method sometimes + // makes use of uninitialised bits according to valgrind + + noninherited_flags.f._display = noninherited_flags.f._originalDisplay = initialDisplay(); + noninherited_flags.f._overflowX = initialOverflowX(); + noninherited_flags.f._overflowY = initialOverflowY(); + noninherited_flags.f._vertical_align = initialVerticalAlign(); + noninherited_flags.f._clear = initialClear(); + noninherited_flags.f._position = initialPosition(); + noninherited_flags.f._floating = initialFloating(); + noninherited_flags.f._table_layout = initialTableLayout(); + noninherited_flags.f._flowAroundFloats= initialFlowAroundFloats(); + noninherited_flags.f._page_break_before = initialPageBreak(); + noninherited_flags.f._page_break_after = initialPageBreak(); + noninherited_flags.f._styleType = NOPSEUDO; + noninherited_flags.f._hasClip = false; + noninherited_flags.f._pseudoBits = 0; + noninherited_flags.f._unicodeBidi = initialUnicodeBidi(); + noninherited_flags.f._textOverflow = initialTextOverflow(); + noninherited_flags.f.unused = 0; + } + +public: + + RenderStyle(); + // used to create the default style. + RenderStyle(bool); + RenderStyle(const RenderStyle&); + + ~RenderStyle(); + + void inheritFrom(const RenderStyle* inheritParent); + + PseudoId styleType() const { return noninherited_flags.f._styleType; } + void setStyleType(PseudoId pi) { noninherited_flags.f._styleType = pi; } + bool isGenerated() const { + if (styleType() == AFTER || styleType() == BEFORE || styleType() == MARKER || styleType() == REPLACED) + return true; + else + return false; + } + + bool hasPseudoStyle(PseudoId pi) const; + void setHasPseudoStyle(PseudoId pi, bool b=true); + RenderStyle* getPseudoStyle(PseudoId pi) const; + RenderStyle* addPseudoStyle(PseudoId pi); + void removePseudoStyle(PseudoId pi); + + bool operator==(const RenderStyle& other) const; + bool isFloating() const { return !(noninherited_flags.f._floating == FNONE); } + bool hasMargin() const { return surround->margin.nonZero(); } + bool hasBorder() const { return surround->border.hasBorder(); } + bool hasOffset() const { return surround->offset.nonZero(); } + + bool hasBackground() const { + if (backgroundColor().isValid() && qAlpha(backgroundColor().rgb()) > 0) + return true; + else + return background->m_background.hasImage(); + } + bool hasFixedBackgroundImage() const { return background->m_background.hasFixedImage(); } + + bool visuallyOrdered() const { return inherited_flags.f._visuallyOrdered; } + void setVisuallyOrdered(bool b) { inherited_flags.f._visuallyOrdered = b; } + +// attribute getter methods + + EDisplay display() const { return noninherited_flags.f._display; } + EDisplay originalDisplay() const { return noninherited_flags.f._originalDisplay; } + + Length left() const { return surround->offset.left; } + Length right() const { return surround->offset.right; } + Length top() const { return surround->offset.top; } + Length bottom() const { return surround->offset.bottom; } + + EPosition position() const { return noninherited_flags.f._position; } + EFloat floating() const { return noninherited_flags.f._floating; } + + Length width() const { return box->width; } + Length height() const { return box->height; } + Length minWidth() const { return box->min_width; } + Length maxWidth() const { return box->max_width; } + Length minHeight() const { return box->min_height; } + Length maxHeight() const { return box->max_height; } + + const BorderData& border() const { return surround->border; } + const BorderValue& borderLeft() const { return surround->border.left; } + const BorderValue& borderRight() const { return surround->border.right; } + const BorderValue& borderTop() const { return surround->border.top; } + const BorderValue& borderBottom() const { return surround->border.bottom; } + + unsigned short borderLeftWidth() const { return surround->border.borderLeftWidth(); } + EBorderStyle borderLeftStyle() const { return surround->border.left.style; } + const QColor& borderLeftColor() const { return surround->border.left.color; } + bool borderLeftIsTransparent() const { return surround->border.left.isTransparent(); } + unsigned short borderRightWidth() const { return surround->border.borderRightWidth(); } + EBorderStyle borderRightStyle() const { return surround->border.right.style; } + const QColor& borderRightColor() const { return surround->border.right.color; } + bool borderRightIsTransparent() const { return surround->border.right.isTransparent(); } + unsigned short borderTopWidth() const { return surround->border.borderTopWidth(); } + EBorderStyle borderTopStyle() const { return surround->border.top.style; } + const QColor& borderTopColor() const { return surround->border.top.color; } + bool borderTopIsTransparent() const { return surround->border.top.isTransparent(); } + unsigned short borderBottomWidth() const { return surround->border.borderBottomWidth(); } + EBorderStyle borderBottomStyle() const { return surround->border.bottom.style; } + const QColor& borderBottomColor() const { return surround->border.bottom.color; } + bool borderBottomIsTransparent() const { return surround->border.bottom.isTransparent(); } + + unsigned short outlineSize() const { return outlineWidth() + outlineOffset(); } + unsigned short outlineWidth() const + { if(background->m_outline.style == BNONE || background->m_outline.style == BHIDDEN) return 0; + else return background->m_outline.width; } + EBorderStyle outlineStyle() const { return background->m_outline.style; } + bool outlineStyleIsAuto() const { return background->m_outline._auto; } + const QColor & outlineColor() const { return background->m_outline.color; } + + EOverflow overflowX() const { return noninherited_flags.f._overflowX; } + EOverflow overflowY() const { return noninherited_flags.f._overflowY; } + bool hidesOverflow() const { + // either both overflow are visible or none are + return overflowX() != OVISIBLE; + } + + EVisibility visibility() const { return inherited_flags.f._visibility; } + EVerticalAlign verticalAlign() const { return noninherited_flags.f._vertical_align; } + Length verticalAlignLength() const { return box->vertical_align; } + + Length clipLeft() const { return visual->clip.left; } + Length clipRight() const { return visual->clip.right; } + Length clipTop() const { return visual->clip.top; } + Length clipBottom() const { return visual->clip.bottom; } + LengthBox clip() const { return visual->clip; } + bool hasClip() const { return noninherited_flags.f._hasClip; } + + EUnicodeBidi unicodeBidi() const { return noninherited_flags.f._unicodeBidi; } + + EClear clear() const { return noninherited_flags.f._clear; } + ETableLayout tableLayout() const { return noninherited_flags.f._table_layout; } + + const QFont & font() const { return inherited->font.f; } + // use with care. call font->update() after modifications + const Font &htmlFont() { return inherited->font; } + const QFontMetrics & fontMetrics() const { return inherited->font.fm; } + + const QColor & color() const { return inherited->color; } + Length textIndent() const { return inherited->indent; } + ETextAlign textAlign() const { return inherited_flags.f._text_align; } + ETextTransform textTransform() const { return inherited_flags.f._text_transform; } + int textDecorationsInEffect() const { return inherited_flags.f._text_decorations; } + int textDecoration() const { return visual->textDecoration; } + int wordSpacing() const { return inherited->font.wordSpacing; } + int letterSpacing() const { return inherited->font.letterSpacing; } + + EDirection direction() const { return inherited_flags.f._direction; } + Length lineHeight() const { return inherited->line_height; } + + EWhiteSpace whiteSpace() const { return inherited_flags.f._white_space; } + bool autoWrap() const { + if (whiteSpace() == NORMAL || whiteSpace() == PRE_WRAP || whiteSpace() == PRE_LINE) + return true; + // nowrap | pre + return false; + } + bool preserveLF() const { + if (whiteSpace() == PRE || whiteSpace() == PRE_WRAP || whiteSpace() == PRE_LINE) + return true; + // normal | nowrap + return false; + } + bool preserveWS() const { + if (whiteSpace() == PRE || whiteSpace() == PRE_WRAP) + return true; + // normal | nowrap | pre-line + return false; + } + + const QColor & backgroundColor() const { return background->m_color; } + CachedImage *backgroundImage() const { return background->m_background.m_image; } + EBackgroundRepeat backgroundRepeat() const { return background->m_background.m_bgRepeat; } + bool backgroundAttachment() const { return background->m_background.m_bgAttachment; } + Length backgroundXPosition() const { return background->m_background.m_xPosition; } + Length backgroundYPosition() const { return background->m_background.m_yPosition; } + BackgroundLayer* accessBackgroundLayers() { return &(background.access()->m_background); } + const BackgroundLayer* backgroundLayers() const { return &(background->m_background); } + + // returns true for collapsing borders, false for separate borders + bool borderCollapse() const { return inherited_flags.f._border_collapse; } + short borderHorizontalSpacing() const { return inherited->border_hspacing; } + short borderVerticalSpacing() const { return inherited->border_vspacing; } + EEmptyCell emptyCells() const { return inherited_flags.f._empty_cells; } + ECaptionSide captionSide() const { return inherited_flags.f._caption_side; } + + EListStyleType listStyleType() const { return inherited_flags.f._list_style_type; } + CachedImage *listStyleImage() const { return inherited->style_image; } + EListStylePosition listStylePosition() const { return inherited_flags.f._list_style_position; } + + Length marginTop() const { return surround->margin.top; } + Length marginBottom() const { return surround->margin.bottom; } + Length marginLeft() const { return surround->margin.left; } + Length marginRight() const { return surround->margin.right; } + + Length paddingTop() const { return surround->padding.top; } + Length paddingBottom() const { return surround->padding.bottom; } + Length paddingLeft() const { return surround->padding.left; } + Length paddingRight() const { return surround->padding.right; } + + ECursor cursor() const { return inherited_flags.f._cursor_style; } + + short widows() const { return inherited->widows; } + short orphans() const { return inherited->orphans; } + bool pageBreakInside() const { return inherited_flags.f._page_break_inside; } + EPageBreak pageBreakBefore() const { return noninherited_flags.f._page_break_before; } + EPageBreak pageBreakAfter() const { return noninherited_flags.f._page_break_after; } + + DOM::QuotesValueImpl* quotes() const { return inherited->quotes; } + QString openQuote(int level) const; + QString closeQuote(int level) const; + + // CSS3 Getter Methods + EBoxSizing boxSizing() const { return box->box_sizing; } + int outlineOffset() const { + if (background->m_outline.style == BNONE || background->m_outline.style == BHIDDEN) return 0; + return background->m_outline._offset; + } + ShadowData* textShadow() const { return css3InheritedData->textShadow; } + float opacity() { return css3NonInheritedData->opacity; } + EUserInput userInput() const { return inherited_flags.f._user_input; } + + Length marqueeIncrement() { return css3NonInheritedData->marquee->increment; } + int marqueeSpeed() { return css3NonInheritedData->marquee->speed; } + int marqueeLoopCount() { return css3NonInheritedData->marquee->loops; } + EMarqueeBehavior marqueeBehavior() { return css3NonInheritedData->marquee->behavior; } + EMarqueeDirection marqueeDirection() { return css3NonInheritedData->marquee->direction; } + bool textOverflow() const { return noninherited_flags.f._textOverflow; } + // End CSS3 Getters + +// attribute setter methods + + void setDisplay(EDisplay v) { noninherited_flags.f._display = v; } + void setOriginalDisplay(EDisplay v) { noninherited_flags.f._originalDisplay = v; } + void setPosition(EPosition v) { noninherited_flags.f._position = v; } + void setFloating(EFloat v) { noninherited_flags.f._floating = v; } + + void setLeft(Length v) { SET_VAR(surround,offset.left,v) } + void setRight(Length v) { SET_VAR(surround,offset.right,v) } + void setTop(Length v) { SET_VAR(surround,offset.top,v) } + void setBottom(Length v){ SET_VAR(surround,offset.bottom,v) } + + void setWidth(Length v) { SET_VAR(box,width,v) } + void setHeight(Length v) { SET_VAR(box,height,v) } + + void setMinWidth(Length v) { SET_VAR(box,min_width,v) } + void setMaxWidth(Length v) { SET_VAR(box,max_width,v) } + void setMinHeight(Length v) { SET_VAR(box,min_height,v) } + void setMaxHeight(Length v) { SET_VAR(box,max_height,v) } + + void resetBorderTop() { SET_VAR(surround, border.top, BorderValue()) } + void resetBorderRight() { SET_VAR(surround, border.right, BorderValue()) } + void resetBorderBottom() { SET_VAR(surround, border.bottom, BorderValue()) } + void resetBorderLeft() { SET_VAR(surround, border.left, BorderValue()) } + void resetOutline() { SET_VAR(background, m_outline, OutlineValue()) } + + void setBackgroundColor(const QColor& v) { SET_VAR(background, m_color, v) } + + void setBorderLeftWidth(unsigned short v) { SET_VAR(surround,border.left.width,v) } + void setBorderLeftStyle(EBorderStyle v) { SET_VAR(surround,border.left.style,v) } + void setBorderLeftColor(const QColor & v) { SET_VAR(surround,border.left.color,v) } + void setBorderRightWidth(unsigned short v) { SET_VAR(surround,border.right.width,v) } + void setBorderRightStyle(EBorderStyle v) { SET_VAR(surround,border.right.style,v) } + void setBorderRightColor(const QColor & v) { SET_VAR(surround,border.right.color,v) } + void setBorderTopWidth(unsigned short v) { SET_VAR(surround,border.top.width,v) } + void setBorderTopStyle(EBorderStyle v) { SET_VAR(surround,border.top.style,v) } + void setBorderTopColor(const QColor & v) { SET_VAR(surround,border.top.color,v) } + void setBorderBottomWidth(unsigned short v) { SET_VAR(surround,border.bottom.width,v) } + void setBorderBottomStyle(EBorderStyle v) { SET_VAR(surround,border.bottom.style,v) } + void setBorderBottomColor(const QColor & v) { SET_VAR(surround,border.bottom.color,v) } + void setOutlineWidth(unsigned short v) { SET_VAR(background,m_outline.width,v) } + void setOutlineStyle(EBorderStyle v, bool isAuto = false) + { + SET_VAR(background,m_outline.style,v) + SET_VAR(background,m_outline._auto, isAuto) + } + void setOutlineColor(const QColor & v) { SET_VAR(background,m_outline.color,v) } + + void setOverflowX(EOverflow v) { noninherited_flags.f._overflowX = v; } + void setOverflowY(EOverflow v) { noninherited_flags.f._overflowY = v; } + void setVisibility(EVisibility v) { inherited_flags.f._visibility = v; } + void setVerticalAlign(EVerticalAlign v) { noninherited_flags.f._vertical_align = v; } + void setVerticalAlignLength(Length l) { SET_VAR(box, vertical_align, l ) } + + void setClipLeft(Length v) { SET_VAR(visual,clip.left,v) } + void setClipRight(Length v) { SET_VAR(visual,clip.right,v) } + void setClipTop(Length v) { SET_VAR(visual,clip.top,v) } + void setClipBottom(Length v) { SET_VAR(visual,clip.bottom,v) } + void setClip( Length top, Length right, Length bottom, Length left ); + void setHasClip( bool b ) { noninherited_flags.f._hasClip = b; } + + void setUnicodeBidi( EUnicodeBidi b ) { noninherited_flags.f._unicodeBidi = b; } + + void setClear(EClear v) { noninherited_flags.f._clear = v; } + void setTableLayout(ETableLayout v) { noninherited_flags.f._table_layout = v; } + bool setFontDef(const khtml::FontDef & v) { + // bah, this doesn't compare pointers. broken! (Dirk) + if (!(inherited->font.fontDef == v)) { + inherited.access()->font = Font( v ); + return true; + } + return false; + } + + void setColor(const QColor & v) { SET_VAR(inherited,color,v) } + void setTextIndent(Length v) { SET_VAR(inherited,indent,v) } + void setTextAlign(ETextAlign v) { inherited_flags.f._text_align = v; } + void setTextTransform(ETextTransform v) { inherited_flags.f._text_transform = v; } + void addToTextDecorationsInEffect(int v) { inherited_flags.f._text_decorations |= v; } + void setTextDecorationsInEffect(int v) { inherited_flags.f._text_decorations = v; } + void setTextDecoration(unsigned v) { SET_VAR(visual, textDecoration, v); } + void setDirection(EDirection v) { inherited_flags.f._direction = v; } + void setLineHeight(Length v) { SET_VAR(inherited,line_height,v) } + + void setWhiteSpace(EWhiteSpace v) { inherited_flags.f._white_space = v; } + + void setWordSpacing(int v) { SET_VAR(inherited,font.wordSpacing,v) } + void setLetterSpacing(int v) { SET_VAR(inherited,font.letterSpacing,v) } + + void clearBackgroundLayers() { background.access()->m_background = BackgroundLayer(); } + void inheritBackgroundLayers(const BackgroundLayer& parent) { background.access()->m_background = parent; } + void adjustBackgroundLayers(); + + void setBorderCollapse(bool collapse) { inherited_flags.f._border_collapse = collapse; } + void setBorderHorizontalSpacing(short v) { SET_VAR(inherited,border_hspacing,v) } + void setBorderVerticalSpacing(short v) { SET_VAR(inherited,border_vspacing,v) } + + void setEmptyCells(EEmptyCell v) { inherited_flags.f._empty_cells = v; } + void setCaptionSide(ECaptionSide v) { inherited_flags.f._caption_side = v; } + + void setListStyleType(EListStyleType v) { inherited_flags.f._list_style_type = v; } + void setListStyleImage(CachedImage *v) { SET_VAR(inherited,style_image,v)} + void setListStylePosition(EListStylePosition v) { inherited_flags.f._list_style_position = v; } + + void resetMargin() { SET_VAR(surround, margin, LengthBox(Fixed)) } + void setMarginTop(Length v) { SET_VAR(surround,margin.top,v) } + void setMarginBottom(Length v) { SET_VAR(surround,margin.bottom,v) } + void setMarginLeft(Length v) { SET_VAR(surround,margin.left,v) } + void setMarginRight(Length v) { SET_VAR(surround,margin.right,v) } + + void resetPadding() { SET_VAR(surround, padding, LengthBox(Variable)) } + void setPaddingTop(Length v) { SET_VAR(surround,padding.top,v) } + void setPaddingBottom(Length v) { SET_VAR(surround,padding.bottom,v) } + void setPaddingLeft(Length v) { SET_VAR(surround,padding.left,v) } + void setPaddingRight(Length v) { SET_VAR(surround,padding.right,v) } + + void setCursor( ECursor c ) { inherited_flags.f._cursor_style = c; } + + bool htmlHacks() const { return inherited_flags.f._htmlHacks; } + void setHtmlHacks(bool b=true) { inherited_flags.f._htmlHacks = b; } + + bool flowAroundFloats() const { return noninherited_flags.f._flowAroundFloats; } + void setFlowAroundFloats(bool b=true) { noninherited_flags.f._flowAroundFloats = b; } + + int zIndex() const { return box->z_auto? 0 : box->z_index; } + void setZIndex(int v) { SET_VAR(box,z_auto,false ); SET_VAR(box, z_index, v); } + bool hasAutoZIndex() const { return box->z_auto; } + void setHasAutoZIndex() { SET_VAR(box, z_auto, true ); } + + void setWidows(short w) { SET_VAR(inherited, widows, w); } + void setOrphans(short o) { SET_VAR(inherited, orphans, o); } + void setPageBreakInside(bool b) { inherited_flags.f._page_break_inside = b; } + void setPageBreakBefore(EPageBreak b) { noninherited_flags.f._page_break_before = b; } + void setPageBreakAfter(EPageBreak b) { noninherited_flags.f._page_break_after = b; } + + void setQuotes(DOM::QuotesValueImpl* q); + + // CSS3 Setters + void setBoxSizing( EBoxSizing b ) { SET_VAR(box,box_sizing,b); } + void setOutlineOffset(unsigned short v) { SET_VAR(background,m_outline._offset,v) } + void setTextShadow(ShadowData* val, bool add=false); + void setOpacity(float f) { SET_VAR(css3NonInheritedData, opacity, f); } + void setUserInput(EUserInput ui) { inherited_flags.f._user_input = ui; } + + void setMarqueeIncrement(const Length& f) { SET_VAR(css3NonInheritedData.access()->marquee, increment, f); } + void setMarqueeSpeed(int f) { SET_VAR(css3NonInheritedData.access()->marquee, speed, f); } + void setMarqueeDirection(EMarqueeDirection d) { SET_VAR(css3NonInheritedData.access()->marquee, direction, d); } + void setMarqueeBehavior(EMarqueeBehavior b) { SET_VAR(css3NonInheritedData.access()->marquee, behavior, b); } + void setMarqueeLoopCount(int i) { SET_VAR(css3NonInheritedData.access()->marquee, loops, i); } + void setTextOverflow(bool b) { noninherited_flags.f._textOverflow = b; } + // End CSS3 Setters + + QPalette palette() const { return visual->palette; } + void setPaletteColor(QPalette::ColorGroup g, QColorGroup::ColorRole r, const QColor& c); + void resetPalette() // Called when the desktop color scheme changes. + { + const_cast<StyleVisualData *>(visual.get())->palette = QApplication::palette(); + } + + bool useNormalContent() const { return generated->content == 0; } + ContentData* contentData() const { return generated->content; } + bool contentDataEquivalent(const RenderStyle* otherStyle) const + { + return generated->contentDataEquivalent(otherStyle->generated.get()); + } + void addContent(DOM::DOMStringImpl* s); + void addContent(CachedObject* o); + void addContent(DOM::CounterImpl* c); + void addContent(EQuoteContent q); + void setContentNone(); + void setContentNormal(); + void setContentData(ContentData* content); + + DOM::CSSValueListImpl* counterReset() const { return generated->counter_reset; } + DOM::CSSValueListImpl* counterIncrement() const { return generated->counter_increment; } + void setCounterReset(DOM::CSSValueListImpl* v); + void setCounterIncrement(DOM::CSSValueListImpl* v); + bool hasCounterReset(const DOM::DOMString& c) const; + bool hasCounterIncrement(const DOM::DOMString& c) const; + short counterReset(const DOM::DOMString& c) const; + short counterIncrement(const DOM::DOMString& c) const; + + + bool inheritedNotEqual( RenderStyle *other ) const; + + enum Diff { Equal, NonVisible = Equal, Visible, Position, Layout, CbLayout }; + Diff diff( const RenderStyle *other ) const; + + bool isDisplayReplacedType() { + return display() == INLINE_BLOCK ||/* display() == INLINE_BOX ||*/ display() == INLINE_TABLE; + } + bool isDisplayInlineType() { + return display() == INLINE || isDisplayReplacedType(); + } + bool isOriginalDisplayInlineType() { + return originalDisplay() == INLINE || originalDisplay() == INLINE_BLOCK || + /*originalDisplay() == INLINE_BOX ||*/ originalDisplay() == INLINE_TABLE; + } + + +#ifdef ENABLE_DUMP + QString createDiff( const RenderStyle &parent ) const; +#endif + + // Initial values for all the properties + static bool initialBackgroundAttachment() { return true; } + static EBackgroundBox initialBackgroundClip() { return BGBORDER; } + static EBackgroundBox initialBackgroundOrigin() { return BGPADDING; } + static EBackgroundRepeat initialBackgroundRepeat() { return REPEAT; } + static LengthSize initialBackgroundSize() { return LengthSize(); } + static bool initialBorderCollapse() { return false; } + static EBorderStyle initialBorderStyle() { return BNONE; } + static ECaptionSide initialCaptionSide() { return CAPTOP; } + static EClear initialClear() { return CNONE; } + static EDirection initialDirection() { return LTR; } + static EDisplay initialDisplay() { return INLINE; } + static EEmptyCell initialEmptyCells() { return SHOW; } + static EFloat initialFloating() { return FNONE; } + static EListStylePosition initialListStylePosition() { return OUTSIDE; } + static EListStyleType initialListStyleType() { return LDISC; } + static EOverflow initialOverflowX() { return OVISIBLE; } + static EOverflow initialOverflowY() { return OVISIBLE; } + static EPageBreak initialPageBreak() { return PBAUTO; } + static bool initialPageBreakInside() { return true; } + static EPosition initialPosition() { return STATIC; } + static ETableLayout initialTableLayout() { return TAUTO; } + static EUnicodeBidi initialUnicodeBidi() { return UBNormal; } + static DOM::QuotesValueImpl* initialQuotes() { return 0; } + static EBoxSizing initialBoxSizing() { return CONTENT_BOX; } + static ETextTransform initialTextTransform() { return TTNONE; } + static EVisibility initialVisibility() { return VISIBLE; } + static EWhiteSpace initialWhiteSpace() { return NORMAL; } + static Length initialBackgroundXPosition() { return Length(); } + static Length initialBackgroundYPosition() { return Length(); } + static short initialBorderHorizontalSpacing() { return 0; } + static short initialBorderVerticalSpacing() { return 0; } + static ECursor initialCursor() { return CURSOR_AUTO; } + static QColor initialColor() { return Qt::black; } + static CachedImage* initialBackgroundImage() { return 0; } + static CachedImage* initialListStyleImage() { return 0; } + static unsigned short initialBorderWidth() { return 3; } + static int initialLetterWordSpacing() { return 0; } + static Length initialSize() { return Length(); } + static Length initialMinSize() { return Length(0, Fixed); } + static Length initialMaxSize() { return Length(UNDEFINED, Fixed); } + static Length initialOffset() { return Length(); } + static Length initialMargin() { return Length(Fixed); } + static Length initialPadding() { return Length(Variable); } + static Length initialTextIndent() { return Length(Fixed); } + static EVerticalAlign initialVerticalAlign() { return BASELINE; } + static int initialWidows() { return 2; } + static int initialOrphans() { return 2; } + static Length initialLineHeight() { return Length(-100, Percent); } + static ETextAlign initialTextAlign() { return TAAUTO; } + static ETextDecoration initialTextDecoration() { return TDNONE; } + static bool initialFlowAroundFloats() { return false; } + static int initialOutlineOffset() { return 0; } + static float initialOpacity() { return 1.0f; } + static int initialMarqueeLoopCount() { return -1; } + static int initialMarqueeSpeed() { return 85; } + static Length initialMarqueeIncrement() { return Length(6, Fixed); } + static EMarqueeBehavior initialMarqueeBehavior() { return MSCROLL; } + static EMarqueeDirection initialMarqueeDirection() { return MAUTO; } + static bool initialTextOverflow() { return false; } +}; + +class RenderPageStyle { + friend class CSSStyleSelector; +public: + enum PageType { NO_PAGE = 0, ANY_PAGE, FIRST_PAGE, LEFT_PAGES, RIGHT_PAGES }; + + RenderPageStyle(); + ~RenderPageStyle(); + + PageType pageType() { return m_pageType; } + + RenderPageStyle* getPageStyle(PageType type); + RenderPageStyle* addPageStyle(PageType type); + void removePageStyle(PageType type); + + Length marginTop() const { return margin.top; } + Length marginBottom() const { return margin.bottom; } + Length marginLeft() const { return margin.left; } + Length marginRight() const { return margin.right; } + + Length pageWidth() const { return m_pageWidth; } + Length pageHeight() const { return m_pageHeight; } + + void setMarginTop(Length v) { margin.top = v; } + void setMarginBottom(Length v) { margin.bottom = v; } + void setMarginLeft(Length v) { margin.left = v; } + void setMarginRight(Length v) { margin.right = v; } + + void setPageWidth(Length v) { m_pageWidth = v; } + void setPageHeight(Length v) { m_pageHeight = v; } + +protected: + RenderPageStyle *next; + PageType m_pageType; + + LengthBox margin; + Length m_pageWidth; + Length m_pageHeight; +}; + +} // namespace + +#endif + diff --git a/khtml/rendering/render_table.cpp b/khtml/rendering/render_table.cpp new file mode 100644 index 000000000..592db9dbd --- /dev/null +++ b/khtml/rendering/render_table.cpp @@ -0,0 +1,3070 @@ +/** + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 1997 Martin Jones (mjones@kde.org) + * (C) 1997 Torben Weis (weis@kde.org) + * (C) 1998 Waldo Bastian (bastian@kde.org) + * (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 2003 Apple Computer, Inc. + * (C) 2005 Allan Sandfeld Jensen (kde@carewolf.com) + * + * 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 TABLE_DEBUG +//#define TABLE_PRINT +//#define DEBUG_LAYOUT +//#define BOX_DEBUG +#include "rendering/render_table.h" +#include "rendering/render_replaced.h" +#include "rendering/render_canvas.h" +#include "rendering/table_layout.h" +#include "html/html_tableimpl.h" +#include "html/html_formimpl.h" +#include "misc/htmltags.h" +#include "misc/htmlattrs.h" +#include "rendering/render_line.h" +#include "xml/dom_docimpl.h" + +#include <kglobal.h> + +#include <qapplication.h> +#include <qstyle.h> + +#include <kdebug.h> +#include <assert.h> + +using namespace khtml; +using namespace DOM; + +RenderTable::RenderTable(DOM::NodeImpl* node) + : RenderBlock(node) +{ + + tCaption = 0; + head = foot = firstBody = 0; + tableLayout = 0; + m_currentBorder = 0; + + has_col_elems = false; + hspacing = vspacing = 0; + padding = 0; + needSectionRecalc = false; + padding = 0; + + columnPos.resize( 2 ); + columnPos.fill( 0 ); + columns.resize( 1 ); + columns.fill( ColumnStruct() ); + + columnPos[0] = 0; +} + +RenderTable::~RenderTable() +{ + delete tableLayout; +} + +void RenderTable::setStyle(RenderStyle *_style) +{ + ETableLayout oldTableLayout = style() ? style()->tableLayout() : TAUTO; + if ( _style->display() == INLINE ) _style->setDisplay( INLINE_TABLE ); + if ( _style->display() != INLINE_TABLE ) _style->setDisplay(TABLE); + if ( !_style->flowAroundFloats() ) _style->setFlowAroundFloats(true); + RenderBlock::setStyle(_style); + + // init RenderObject attributes + setInline(style()->display()==INLINE_TABLE && !isPositioned()); + setReplaced(style()->display()==INLINE_TABLE); + + // In the collapsed border model, there is no cell spacing. + hspacing = collapseBorders() ? 0 : style()->borderHorizontalSpacing(); + vspacing = collapseBorders() ? 0 : style()->borderVerticalSpacing(); + columnPos[0] = hspacing; + + if ( !tableLayout || style()->tableLayout() != oldTableLayout ) { + delete tableLayout; + + // According to the CSS2 spec, you only use fixed table layout if an + // explicit width is specified on the table. Auto width implies auto table layout. + if (style()->tableLayout() == TFIXED && !style()->width().isVariable()) { + tableLayout = new FixedTableLayout(this); +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << "using fixed table layout" << endl; +#endif + } else + tableLayout = new AutoTableLayout(this); + } +} + +short RenderTable::lineHeight(bool b) const +{ + // Inline tables are replaced elements. Otherwise, just pass off to + // the base class. + if (isReplaced()) + return height()+marginTop()+marginBottom(); + return RenderBlock::lineHeight(b); +} + +short RenderTable::baselinePosition(bool b) const +{ + // Inline tables are replaced elements. Otherwise, just pass off to + // the base class. + if (isReplaced()) + return height()+marginTop()+marginBottom(); + return RenderBlock::baselinePosition(b); +} + + +void RenderTable::addChild(RenderObject *child, RenderObject *beforeChild) +{ +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(Table)::addChild( " << child->renderName() << ", " << + (beforeChild ? beforeChild->renderName() : "0") << " )" << endl; +#endif + bool wrapInAnonymousSection = false; + + switch(child->style()->display()) + { + case TABLE_CAPTION: + if (child->isRenderBlock()) + tCaption = static_cast<RenderBlock *>(child); + break; + case TABLE_COLUMN: + case TABLE_COLUMN_GROUP: + has_col_elems = true; + break; + case TABLE_HEADER_GROUP: + if ( !head ) { + if (child->isTableSection()) + head = static_cast<RenderTableSection *>(child); + } + else if ( !firstBody ) + if (child->isTableSection()) + firstBody = static_cast<RenderTableSection *>(child); + break; + case TABLE_FOOTER_GROUP: + if ( !foot ) { + if (child->isTableSection()) + foot = static_cast<RenderTableSection *>(child); + break; + } + // fall through + case TABLE_ROW_GROUP: + if(!firstBody) + if (child->isTableSection()) + firstBody = static_cast<RenderTableSection *>(child); + break; + case TABLE_CELL: + case TABLE_ROW: + wrapInAnonymousSection = true; + break; + case BLOCK: +// case BOX: + case COMPACT: + case INLINE: + case INLINE_BLOCK: +// case INLINE_BOX: + case INLINE_TABLE: + case LIST_ITEM: + case NONE: + case RUN_IN: + case TABLE: + // The special TABLE > FORM quirk allows the form to sit directly under the table + if (child->element() && child->element()->isHTMLElement() && child->element()->id() == ID_FORM) + wrapInAnonymousSection = !static_cast<HTMLFormElementImpl*>(child->element())->isMalformed(); + else + wrapInAnonymousSection = true; + break; + } + + if (!wrapInAnonymousSection) { + RenderContainer::addChild(child, beforeChild); + return; + } + + if (!beforeChild && lastChild() && lastChild()->isTableSection() && lastChild()->isAnonymous()) { + lastChild()->addChild(child); + return; + } + + RenderObject *lastBox = beforeChild; + RenderObject *nextToLastBox = beforeChild; + while (lastBox && lastBox->parent()->isAnonymous() && + !lastBox->isTableSection() && lastBox->style()->display() != TABLE_CAPTION) { + nextToLastBox = lastBox; + lastBox = lastBox->parent(); + } + if (lastBox && lastBox->isAnonymous()) { + lastBox->addChild(child, nextToLastBox); + return; + } + + if (beforeChild && !beforeChild->isTableSection()) + beforeChild = 0; + RenderTableSection* section = new (renderArena()) RenderTableSection(document() /* anonymous */); + RenderStyle* newStyle = new RenderStyle(); + newStyle->inheritFrom(style()); + newStyle->setDisplay(TABLE_ROW_GROUP); + section->setStyle(newStyle); + addChild(section, beforeChild); + section->addChild(child); +} + +void RenderTable::calcWidth() +{ + if ( isPositioned() ) { + calcAbsoluteHorizontal(); + } + + RenderBlock *cb = containingBlock(); + int availableWidth = cb->lineWidth( m_y ); + + LengthType widthType = style()->width().type(); + if(widthType > Relative && style()->width().value() > 0) { + // Percent or fixed table + // Percent is calculated from contentWidth, not available width + m_width = calcBoxWidth(style()->width().minWidth( cb->contentWidth() )); + } else { + // Subtract out any fixed margins from our available width for auto width tables. + int marginTotal = 0; + if (!style()->marginLeft().isVariable()) + marginTotal += style()->marginLeft().width(availableWidth); + if (!style()->marginRight().isVariable()) + marginTotal += style()->marginRight().width(availableWidth); + + // Subtract out our margins to get the available content width. + int availContentWidth = kMax(0, availableWidth - marginTotal); + + // Ensure we aren't bigger than our max width or smaller than our min width. + m_width = kMin(availContentWidth, m_maxWidth); + } + + m_width = kMax (m_width, m_minWidth); + + // Finally, with our true width determined, compute our margins for real. + m_marginRight=0; + m_marginLeft=0; + + calcHorizontalMargins(style()->marginLeft(),style()->marginRight(),availableWidth); +} + +void RenderTable::layout() +{ + KHTMLAssert( needsLayout() ); + KHTMLAssert( minMaxKnown() ); + KHTMLAssert( !needSectionRecalc ); + + if (posChildNeedsLayout() && !normalChildNeedsLayout() && !selfNeedsLayout()) { + // All we have to is lay out our positioned objects. + layoutPositionedObjects(true); + setNeedsLayout(false); + return; + } + + if (markedForRepaint()) { + repaintDuringLayout(); + setMarkedForRepaint(false); + } + + m_height = 0; + initMaxMarginValues(); + + int oldWidth = m_width; + calcWidth(); + m_overflowWidth = m_width; + + if (tCaption && (oldWidth != m_width || tCaption->style()->height().isPercent())) + tCaption->setChildNeedsLayout(true); + + // the optimization below doesn't work since the internal table + // layout could have changed. we need to add a flag to the table + // layout that tells us if something has changed in the min max + // calculations to do it correctly. +// if ( oldWidth != m_width || columns.size() + 1 != columnPos.size() ) + tableLayout->layout(); + +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(Table)::layout1() width=" << width() << ", marginLeft=" << marginLeft() << " marginRight=" << marginRight() << endl; +#endif + + setCellWidths(); + + // layout child objects + int calculatedHeight = 0; + + RenderObject *child = firstChild(); + while( child ) { + // FIXME: What about a form that has a display value that makes it a table section? + if ( child->needsLayout() && !(child->element() && child->element()->id() == ID_FORM) ) + child->layout(); + if ( child->isTableSection() ) { + static_cast<RenderTableSection *>(child)->calcRowHeight(); + calculatedHeight += static_cast<RenderTableSection *>(child)->layoutRows( 0 ); + } + child = child->nextSibling(); + } + + // ### collapse caption margin + if(tCaption && tCaption->style()->captionSide() != CAPBOTTOM) { + tCaption->setPos(tCaption->marginLeft(), tCaption->marginTop()+m_height); + m_height += tCaption->height() + tCaption->marginTop() + tCaption->marginBottom(); + } + + int bpTop = borderTop() + (collapseBorders() ? 0 : paddingTop()); + int bpBottom = borderBottom() + (collapseBorders() ? 0 : paddingBottom()); + + m_height += bpTop; + + int oldHeight = m_height; + if (isPositioned()) + m_height += calculatedHeight + bpBottom; + calcHeight(); + int newHeight = m_height; + m_height = oldHeight; + + Length h = style()->height(); + int th = -(bpTop + bpBottom); // Tables size as though CSS height includes border/padding. + if (isPositioned()) + th += newHeight; + else if (h.isFixed()) + th += h.value(); + else if (h.isPercent()) + th += calcPercentageHeight(h); + + // layout rows + if ( th > calculatedHeight ) { + // we have to redistribute that height to get the constraint correctly + // just force the first body to the height needed + // ### FIXME This should take height constraints on all table sections into account and distribute + // accordingly. For now this should be good enough + if (firstBody) { + firstBody->calcRowHeight(); + firstBody->layoutRows( th - calculatedHeight ); + } + else if (!style()->htmlHacks()) { + // Completely empty tables (with no sections or anything) should at least honor specified height + // in strict mode. + m_height += th; + } + } + + int bl = borderLeft(); + if (!collapseBorders()) + bl += paddingLeft(); + + // position the table sections + if ( head ) { + head->setPos(bl, m_height); + m_height += head->height(); + } + RenderObject *body = firstBody; + while ( body ) { + if ( body != head && body != foot && body->isTableSection() ) { + body->setPos(bl, m_height); + m_height += body->height(); + } + body = body->nextSibling(); + } + if ( foot ) { + foot->setPos(bl, m_height); + m_height += foot->height(); + } + + m_height += bpBottom; + + if(tCaption && tCaption->style()->captionSide()==CAPBOTTOM) { + tCaption->setPos(tCaption->marginLeft(), tCaption->marginTop()+m_height); + m_height += tCaption->height() + tCaption->marginTop() + tCaption->marginBottom(); + } + + if (canvas()->pagedMode()) { + RenderObject *child = firstChild(); + // relayout taking real position into account + while( child ) { + if ( !(child->element() && child->element()->id() == ID_FORM) ) { + child->setNeedsLayout(true); + child->layout(); + if (child->containsPageBreak()) setContainsPageBreak(true); + if (child->needsPageClear()) setNeedsPageClear(true); + } + child = child->nextSibling(); + } + } + + //kdDebug(0) << "table height: " << m_height << endl; + + // table can be containing block of positioned elements. + // ### only pass true if width or height changed. + layoutPositionedObjects( true ); + + m_overflowHeight = m_height; + + setNeedsLayout(false); +} + +void RenderTable::setCellWidths() +{ +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(Table, this=0x" << this << ")::setCellWidths()" << endl; +#endif + + RenderObject *child = firstChild(); + while( child ) { + if ( child->isTableSection() ) + static_cast<RenderTableSection *>(child)->setCellWidths(); + child = child->nextSibling(); + } +} + +void RenderTable::paint( PaintInfo& pI, int _tx, int _ty) +{ + if(needsLayout()) return; + + _tx += xPos(); + _ty += yPos(); + +#ifdef TABLE_PRINT + kdDebug( 6040 ) << "RenderTable::paint() w/h = (" << width() << "/" << height() << ")" << endl; +#endif + if (!overhangingContents() && !isRelPositioned() && !isPositioned()) + { + int os = 2*maximalOutlineSize(pI.phase); + if((_ty > pI.r.y() + pI.r.height() + os) || (_ty + height() < pI.r.y() - os)) return; + if((_tx > pI.r.x() + pI.r.width() + os) || (_tx + width() < pI.r.x() - os)) return; + } + +#ifdef TABLE_PRINT + kdDebug( 6040 ) << "RenderTable::paint(2) " << _tx << "/" << _ty << " (" << _y << "/" << _h << ")" << endl; +#endif + + if (pI.phase == PaintActionOutline) + paintOutline(pI.p, _tx, _ty, width(), height(), style()); + + if(( pI.phase == PaintActionElementBackground || pI.phase == PaintActionChildBackground ) + && shouldPaintBackgroundOrBorder() && style()->visibility() == VISIBLE) + paintBoxDecorations(pI, _tx, _ty); + + if ( pI.phase == PaintActionElementBackground ) + return; + + PaintAction oldphase = pI.phase; + if ( pI.phase == PaintActionChildBackgrounds ) + pI.phase = PaintActionChildBackground; + + for( RenderObject *child = firstChild(); child; child = child->nextSibling()) + if ( child->isTableSection() || child == tCaption ) + child->paint( pI, _tx, _ty ); + + if (collapseBorders() && + (pI.phase == PaintActionElementBackground || pI.phase == PaintActionChildBackground) + && style()->visibility() == VISIBLE) { + // Collect all the unique border styles that we want to paint in a sorted list. Once we + // have all the styles sorted, we then do individual passes, painting each style of border + // from lowest precedence to highest precedence. + pI.phase = PaintActionCollapsedTableBorders; + QValueList<CollapsedBorderValue> borderStyles; + collectBorders(borderStyles); +#if 0 + QString m; + for (uint i = 0; i < borderStyles.count(); i++) + m += QString("%1 ").arg((*borderStyles.at(i)).width()); + kdDebug(6040) << m << endl; +#endif + QValueListIterator<CollapsedBorderValue> it = borderStyles.begin(); + QValueListIterator<CollapsedBorderValue> end = borderStyles.end(); + for (; it != end; ++it) { + m_currentBorder = &*it; + for (RenderObject *child = firstChild(); child; child = child->nextSibling()) { + if (child->isTableSection()) + child->paint(pI, _tx, _ty); + } + } + m_currentBorder = 0; + } + + pI.phase = oldphase; +#ifdef BOX_DEBUG + outlineBox(p, _tx, _ty, "blue"); +#endif +} + +void RenderTable::paintBoxDecorations(PaintInfo &pI, int _tx, int _ty) +{ + int w = width(); + int h = height(); + + // Account for the caption. + if (tCaption) { + int captionHeight = (tCaption->height() + tCaption->marginBottom() + tCaption->marginTop()); + h -= captionHeight; + if (tCaption->style()->captionSide() != CAPBOTTOM) + _ty += captionHeight; + } + + int my = kMax(_ty,pI.r.y()); + int mh; + if (_ty<pI.r.y()) + mh= kMax(0,h-(pI.r.y()-_ty)); + else + mh = kMin(pI.r.height(),h); + + paintBackground(pI.p, style()->backgroundColor(), style()->backgroundLayers(), my, mh, _tx, _ty, w, h); + + if (style()->hasBorder() && !collapseBorders()) + paintBorder(pI.p, _tx, _ty, w, h, style()); +} + +void RenderTable::calcMinMaxWidth() +{ + KHTMLAssert( !minMaxKnown() ); + + if ( needSectionRecalc ) + recalcSections(); + +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(Table " << this << ")::calcMinMaxWidth()" << endl; +#endif + + tableLayout->calcMinMaxWidth(); + + if (tCaption) { + tCaption->calcWidth(); + if (tCaption->marginLeft()+tCaption->marginRight()+tCaption->minWidth() > m_minWidth) + m_minWidth = tCaption->marginLeft()+tCaption->marginRight()+tCaption->minWidth(); + } + + setMinMaxKnown(); +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << " END: (Table " << this << ")::calcMinMaxWidth() min = " << m_minWidth << " max = " << m_maxWidth << endl; +#endif +} + +void RenderTable::close() +{ +// kdDebug( 6040 ) << "RenderTable::close()" << endl; + setNeedsLayoutAndMinMaxRecalc(); +} + +void RenderTable::splitColumn( int pos, int firstSpan ) +{ + // we need to add a new columnStruct + int oldSize = columns.size(); + columns.resize( oldSize + 1 ); + int oldSpan = columns[pos].span; +// qDebug("splitColumn( %d,%d ), oldSize=%d, oldSpan=%d", pos, firstSpan, oldSize, oldSpan ); + KHTMLAssert( oldSpan > firstSpan ); + columns[pos].span = firstSpan; + memmove( columns.data()+pos+1, columns.data()+pos, (oldSize-pos)*sizeof(ColumnStruct) ); + columns[pos+1].span = oldSpan - firstSpan; + + // change width of all rows. + RenderObject *child = firstChild(); + while ( child ) { + if ( child->isTableSection() ) { + RenderTableSection *section = static_cast<RenderTableSection *>(child); + int size = section->grid.size(); + int row = 0; + if ( section->cCol > pos ) + section->cCol++; + while ( row < size ) { + section->grid[row].row->resize( oldSize+1 ); + RenderTableSection::Row &r = *section->grid[row].row; + memmove( r.data()+pos+1, r.data()+pos, (oldSize-pos)*sizeof( RenderTableCell * ) ); +// qDebug("moving from %d to %d, num=%d", pos, pos+1, (oldSize-pos-1) ); + r[pos+1] = r[pos] ? (RenderTableCell *)-1 : 0; + row++; + } + } + child = child->nextSibling(); + } + columnPos.resize( numEffCols()+1 ); + setNeedsLayoutAndMinMaxRecalc(); +} + +void RenderTable::appendColumn( int span ) +{ + // easy case. + int pos = columns.size(); +// qDebug("appendColumn( %d ), size=%d", span, pos ); + int newSize = pos + 1; + columns.resize( newSize ); + columns[pos].span = span; + //qDebug("appending column at %d, span %d", pos, span ); + + // change width of all rows. + RenderObject *child = firstChild(); + while ( child ) { + if ( child->isTableSection() ) { + RenderTableSection *section = static_cast<RenderTableSection *>(child); + int size = section->grid.size(); + int row = 0; + while ( row < size ) { + section->grid[row].row->resize( newSize ); + section->cellAt( row, pos ) = 0; + row++; + } + + } + child = child->nextSibling(); + } + columnPos.resize( numEffCols()+1 ); + setNeedsLayoutAndMinMaxRecalc(); +} + +RenderTableCol *RenderTable::colElement( int col ) { + if ( !has_col_elems ) + return 0; + RenderObject *child = firstChild(); + int cCol = 0; + while ( child ) { + if ( child->isTableCol() ) { + RenderTableCol *colElem = static_cast<RenderTableCol *>(child); + int span = colElem->span(); + if ( !colElem->firstChild() ) { + cCol += span; + if ( cCol > col ) + return colElem; + } + + RenderObject *next = child->firstChild(); + if ( !next ) + next = child->nextSibling(); + if ( !next && child->parent()->isTableCol() ) + next = child->parent()->nextSibling(); + child = next; + } else if (child == tCaption) { + child = child->nextSibling(); + } else + break; + } + return 0; +} + +void RenderTable::recalcSections() +{ + tCaption = 0; + head = foot = firstBody = 0; + has_col_elems = false; + + RenderObject *child = firstChild(); + // We need to get valid pointers to caption, head, foot and firstbody again + while ( child ) { + switch(child->style()->display()) { + case TABLE_CAPTION: + if ( !tCaption && child->isRenderBlock() ) { + tCaption = static_cast<RenderBlock*>(child); + tCaption->setNeedsLayout(true); + } + break; + case TABLE_COLUMN: + case TABLE_COLUMN_GROUP: + has_col_elems = true; + break; + case TABLE_HEADER_GROUP: + if (child->isTableSection()) { + RenderTableSection *section = static_cast<RenderTableSection *>(child); + if (!head) + head = section; + else if (!firstBody) + firstBody = section; + if (section->needCellRecalc) + section->recalcCells(); + } + break; + case TABLE_FOOTER_GROUP: + if (child->isTableSection()) { + RenderTableSection *section = static_cast<RenderTableSection *>(child); + if (!foot) + foot = section; + else if (!firstBody) + firstBody = section; + if (section->needCellRecalc) + section->recalcCells(); + } + break; + case TABLE_ROW_GROUP: + if (child->isTableSection()) { + RenderTableSection *section = static_cast<RenderTableSection *>(child); + if (!firstBody) + firstBody = section; + if (section->needCellRecalc) + section->recalcCells(); + } + break; + default: + break; + } + child = child->nextSibling(); + } + needSectionRecalc = false; + setNeedsLayout(true); +} + +RenderObject* RenderTable::removeChildNode(RenderObject* child) +{ + setNeedSectionRecalc(); + return RenderContainer::removeChildNode( child ); +} + +int RenderTable::borderLeft() const +{ + if (collapseBorders()) { + // FIXME: For strict mode, returning 0 is correct, since the table border half spills into the margin, + // but I'm working to get this changed. For now, follow the spec. + return 0; + } + return RenderBlock::borderLeft(); +} + +int RenderTable::borderRight() const +{ + if (collapseBorders()) { + // FIXME: For strict mode, returning 0 is correct, since the table border half spills into the margin, + // but I'm working to get this changed. For now, follow the spec. + return 0; + } + return RenderBlock::borderRight(); +} + +int RenderTable::borderTop() const +{ + if (collapseBorders()) { + // FIXME: For strict mode, returning 0 is correct, since the table border half spills into the margin, + // but I'm working to get this changed. For now, follow the spec. + return 0; + } + return RenderBlock::borderTop(); +} + +int RenderTable::borderBottom() const +{ + if (collapseBorders()) { + // FIXME: For strict mode, returning 0 is correct, since the table border half spills into the margin, + // but I'm working to get this changed. For now, follow the spec. + return 0; + } + return RenderBlock::borderBottom(); +} + +RenderTableSection* RenderTable::sectionAbove(const RenderTableSection* section, bool skipEmptySections) const +{ + if (section == head) + return 0; + RenderObject *prevSection = (section == foot ? lastChild() : const_cast<RenderTableSection *>(section))->previousSibling(); + while (prevSection) { + if (prevSection->isTableSection() && prevSection != head && prevSection != foot && (!skipEmptySections || static_cast<RenderTableSection*>(prevSection)->numRows())) + break; + prevSection = prevSection->previousSibling(); + } + if (!prevSection && head && (!skipEmptySections || head->numRows())) + prevSection = head; + return static_cast<RenderTableSection*>(prevSection); +} + +RenderTableSection* RenderTable::sectionBelow(const RenderTableSection* section, bool skipEmptySections) const +{ + if (section == foot) + return 0; + RenderObject *nextSection = (section == head ? firstChild() : const_cast<RenderTableSection *>(section))->nextSibling(); + while (nextSection) { + if (nextSection->isTableSection() && nextSection != head && nextSection != foot && (!skipEmptySections || static_cast<RenderTableSection*>(nextSection)->numRows())) + break; + nextSection = nextSection->nextSibling(); + } + if (!nextSection && foot && (!skipEmptySections || foot->numRows())) + nextSection = foot; + return static_cast<RenderTableSection*>(nextSection); +} + +RenderTableCell* RenderTable::cellAbove(const RenderTableCell* cell) const +{ + // Find the section and row to look in + int r = cell->row(); + RenderTableSection* section = 0; + int rAbove = 0; + if (r > 0) { + // cell is not in the first row, so use the above row in its own section + section = cell->section(); + rAbove = r-1; + } else { + section = sectionAbove(cell->section(), true); + if (section) + rAbove = section->numRows() - 1; + } + + // Look up the cell in the section's grid, which requires effective col index + if (section) { + int effCol = colToEffCol(cell->col()); + RenderTableCell* aboveCell; + // If we hit a span back up to a real cell. + do { + aboveCell = section->cellAt(rAbove, effCol); + effCol--; + } while (aboveCell == (RenderTableCell *)-1 && effCol >=0); + return (aboveCell == (RenderTableCell *)-1) ? 0 : aboveCell; + } else { + return 0; + } +} + +RenderTableCell* RenderTable::cellBelow(const RenderTableCell* cell) const +{ + // Find the section and row to look in + int r = cell->row() + cell->rowSpan() - 1; + RenderTableSection* section = 0; + int rBelow = 0; + if (r < cell->section()->numRows()-1) { + // The cell is not in the last row, so use the next row in the section. + section = cell->section(); + rBelow= r+1; + } else { + section = sectionBelow(cell->section(), true); + if (section) + rBelow = 0; + } + + // Look up the cell in the section's grid, which requires effective col index + if (section) { + int effCol = colToEffCol(cell->col()); + RenderTableCell* belowCell; + // If we hit a colspan back up to a real cell. + do { + belowCell = section->cellAt(rBelow, effCol); + effCol--; + } while (belowCell == (RenderTableCell *)-1 && effCol >=0); + return (belowCell == (RenderTableCell *)-1) ? 0 : belowCell; + } else { + return 0; + } +} + +RenderTableCell* RenderTable::cellLeft(const RenderTableCell* cell) const +{ + RenderTableSection* section = cell->section(); + int effCol = colToEffCol(cell->col()); + if (effCol == 0) + return 0; + + // If we hit a colspan back up to a real cell. + RenderTableCell* prevCell; + do { + prevCell = section->cellAt(cell->row(), effCol-1); + effCol--; + } while (prevCell == (RenderTableCell *)-1 && effCol >=0); + return (prevCell == (RenderTableCell *)-1) ? 0 : prevCell; +} + +RenderTableCell* RenderTable::cellRight(const RenderTableCell* cell) const +{ + int effCol = colToEffCol(cell->col()+cell->colSpan()); + if (effCol >= numEffCols()) + return 0; + RenderTableCell* result = cell->section()->cellAt(cell->row(), effCol); + return (result == (RenderTableCell*)-1) ? 0 : result; +} + +#ifdef ENABLE_DUMP +void RenderTable::dump(QTextStream &stream, const QString &ind) const +{ + RenderBlock::dump(stream, ind); + + if (tCaption) + stream << " tCaption"; + if (head) + stream << " head"; + if (foot) + stream << " foot"; + + stream << " [cspans:"; + for ( unsigned int i = 0; i < columns.size(); i++ ) + stream << " " << columns[i].span; + stream << "]"; +} + +#endif + +FindSelectionResult RenderTable::checkSelectionPoint( int _x, int _y, int _tx, int _ty, DOM::NodeImpl*& node, int & offset, SelPointState &state ) +{ + int off = offset; + DOM::NodeImpl* nod = node; + + FindSelectionResult pos; + TableSectionIterator it(this); + for (; *it; ++it) { + pos = (*it)->checkSelectionPoint(_x, _y, _tx + m_x, _ty + m_y, nod, off, state); + switch(pos) { + case SelectionPointBeforeInLine: + case SelectionPointInside: + //kdDebug(6030) << "RenderTable::checkSelectionPoint " << this << " returning SelectionPointInside offset=" << offset << endl; + node = nod; + offset = off; + return SelectionPointInside; + case SelectionPointBefore: + //x,y is before this element -> stop here + if ( state.m_lastNode ) { + node = state.m_lastNode; + offset = state.m_lastOffset; + //kdDebug(6030) << "RenderTable::checkSelectionPoint " << this << " before this child " + // << node << "-> returning SelectionPointInside, offset=" << offset << endl; + return SelectionPointInside; + } else { + node = nod; + offset = off; + //kdDebug(6030) << "RenderTable::checkSelectionPoint " << this << " before us -> returning SelectionPointBefore " << node << "/" << offset << endl; + return SelectionPointBefore; + } + break; + case SelectionPointAfter: + if (state.m_afterInLine) break; + // fall through + case SelectionPointAfterInLine: + if (pos == SelectionPointAfterInLine) state.m_afterInLine = true; + //kdDebug(6030) << "RenderTable::checkSelectionPoint: selection after: " << nod << " offset: " << off << " afterInLine: " << state.m_afterInLine << endl; + state.m_lastNode = nod; + state.m_lastOffset = off; + // No "return" here, obviously. We must keep looking into the children. + break; + } + } + // If we are after the last child, return lastNode/lastOffset + // But lastNode can be 0L if there is no child, for instance. + if ( state.m_lastNode ) + { + node = state.m_lastNode; + offset = state.m_lastOffset; + } + // Fallback + return SelectionPointAfter; +} + +// -------------------------------------------------------------------------- + +RenderTableSection::RenderTableSection(DOM::NodeImpl* node) + : RenderBox(node) +{ + // init RenderObject attributes + setInline(false); // our object is not Inline + cCol = 0; + cRow = -1; + needCellRecalc = false; +} + +RenderTableSection::~RenderTableSection() +{ + clearGrid(); +} + +void RenderTableSection::detach() +{ + // recalc cell info because RenderTable has unguarded pointers + // stored that point to this RenderTableSection. + if (table()) + table()->setNeedSectionRecalc(); + + RenderBox::detach(); +} + +void RenderTableSection::setStyle(RenderStyle* _style) +{ + // we don't allow changing this one + if (style()) + _style->setDisplay(style()->display()); + else if (_style->display() != TABLE_FOOTER_GROUP && _style->display() != TABLE_HEADER_GROUP) + _style->setDisplay(TABLE_ROW_GROUP); + + RenderBox::setStyle(_style); +} + +void RenderTableSection::addChild(RenderObject *child, RenderObject *beforeChild) +{ +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(TableSection)::addChild( " << child->renderName() << ", beforeChild=" << + (beforeChild ? beforeChild->renderName() : "0") << " )" << endl; +#endif + if ( !child->isTableRow() ) { + // TBODY > FORM quirk (???) + if (child->element() && child->element()->isHTMLElement() && child->element()->id() == ID_FORM && + static_cast<HTMLFormElementImpl*>(child->element())->isMalformed()) + { + RenderContainer::addChild(child, beforeChild); + return; + } + + RenderObject* last = beforeChild; + if (!last) + last = lastChild(); + if (last && last->isAnonymous()) { + last->addChild(child); + return; + } + + // If beforeChild is inside an anonymous cell/row, insert into the cell or into + // the anonymous row containing it, if there is one. + RenderObject* lastBox = last; + while (lastBox && lastBox->parent()->isAnonymous() && !lastBox->isTableRow()) + lastBox = lastBox->parent(); + if (lastBox && lastBox->isAnonymous()) { + lastBox->addChild(child, beforeChild); + return; + } + + RenderObject* row = new (renderArena()) RenderTableRow(document() /* anonymous table */); + RenderStyle* newStyle = new RenderStyle(); + newStyle->inheritFrom(style()); + newStyle->setDisplay(TABLE_ROW); + row->setStyle(newStyle); + addChild(row, beforeChild); + row->addChild(child); + return; + } + + if (beforeChild) + setNeedCellRecalc(); + + cRow++; + cCol = 0; + + ensureRows( cRow+1 ); + KHTMLAssert( child->isTableRow() ); + grid[cRow].rowRenderer = static_cast<RenderTableRow*>(child); + + if (!beforeChild) { + grid[cRow].height = child->style()->height(); + if ( grid[cRow].height.isRelative() ) + grid[cRow].height = Length(); + } + + + RenderContainer::addChild(child,beforeChild); +} + +void RenderTableSection::ensureRows( int numRows ) +{ + int nRows = grid.size(); + int nCols = table()->numEffCols(); + if ( numRows > nRows ) { + grid.resize( numRows ); + for ( int r = nRows; r < numRows; r++ ) { + grid[r].row = new Row( nCols ); + grid[r].row->fill( 0 ); + grid[r].rowRenderer = 0; + grid[r].baseLine = 0; + grid[r].height = Length(); + } + } + +} + +void RenderTableSection::addCell( RenderTableCell *cell, RenderTableRow *row ) +{ + int rSpan = cell->rowSpan(); + int cSpan = cell->colSpan(); + QMemArray<RenderTable::ColumnStruct> &columns = table()->columns; + int nCols = columns.size(); + + // ### mozilla still seems to do the old HTML way, even for strict DTD + // (see the annotation on table cell layouting in the CSS specs and the testcase below: + // <TABLE border> + // <TR><TD>1 <TD rowspan="2">2 <TD>3 <TD>4 + // <TR><TD colspan="2">5 + // </TABLE> + while ( cCol < nCols && cellAt( cRow, cCol ) ) + cCol++; + +// qDebug("adding cell at %d/%d span=(%d/%d)", cRow, cCol, rSpan, cSpan ); + + if ( rSpan == 1 ) { + // we ignore height settings on rowspan cells + Length height = cell->style()->height(); + if ( height.value() > 0 || (height.isRelative() && height.value() >= 0) ) { + Length cRowHeight = grid[cRow].height; + switch( height.type() ) { + case Percent: + if ( !cRowHeight.isPercent() || + (cRowHeight.isPercent() && cRowHeight.value() < height.value() ) ) + grid[cRow].height = height; + break; + case Fixed: + if ( cRowHeight.type() < Percent || + ( cRowHeight.isFixed() && cRowHeight.value() < height.value() ) ) + grid[cRow].height = height; + break; + case Relative: +#if 0 + // we treat this as variable. This is correct according to HTML4, as it only specifies length for the height. + if ( cRowHeight.type == Variable || + ( cRowHeight.type == Relative && cRowHeight.value < height.value ) ) + grid[cRow].height = height; + break; +#endif + default: + break; + } + } + } + + // make sure we have enough rows + ensureRows( cRow + rSpan ); + + grid[cRow].rowRenderer = row; + + int col = cCol; + // tell the cell where it is + RenderTableCell *set = cell; + while ( cSpan ) { + int currentSpan; + if ( cCol >= nCols ) { + table()->appendColumn( cSpan ); + currentSpan = cSpan; + } else { + if ( cSpan < columns[cCol].span ) + table()->splitColumn( cCol, cSpan ); + currentSpan = columns[cCol].span; + } + int r = 0; + while ( r < rSpan ) { + if ( !cellAt( cRow + r, cCol ) ) { +// qDebug(" adding cell at %d, %d", cRow + r, cCol ); + cellAt( cRow + r, cCol ) = set; + } + r++; + } + cCol++; + cSpan -= currentSpan; + set = (RenderTableCell *)-1; + } + if ( cell ) { + cell->setRow( cRow ); + cell->setCol( table()->effColToCol( col ) ); + } +} + + + +void RenderTableSection::setCellWidths() +{ +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(Table, this=0x" << this << ")::setCellWidths()" << endl; +#endif + QMemArray<int> &columnPos = table()->columnPos; + + int rows = grid.size(); + for ( int i = 0; i < rows; i++ ) { + Row &row = *grid[i].row; + int cols = row.size(); + for ( int j = 0; j < cols; j++ ) { + RenderTableCell *cell = row[j]; +// qDebug("cell[%d,%d] = %p", i, j, cell ); + if ( !cell || cell == (RenderTableCell *)-1 ) + continue; + int endCol = j; + int cspan = cell->colSpan(); + while ( cspan && endCol < cols ) { + cspan -= table()->columns[endCol].span; + endCol++; + } + int w = columnPos[endCol] - columnPos[j] - table()->borderHSpacing(); +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << "setting width of cell " << cell << " " << cell->row() << "/" << cell->col() << " to " << w << " colspan=" << cell->colSpan() << " start=" << j << " end=" << endCol << endl; +#endif + int oldWidth = cell->width(); + if ( w != oldWidth ) { + cell->setNeedsLayout(true); + cell->setWidth( w ); + } + } + } +} + +short RenderTableSection::width() const +{ + return table()->width(); +} + + +void RenderTableSection::calcRowHeight() +{ + int indx; + RenderTableCell *cell; + + int totalRows = grid.size(); + int vspacing = table()->borderVSpacing(); + + rowPos.resize( totalRows + 1 ); + rowPos[0] = vspacing + borderTop(); + + for ( int r = 0; r < totalRows; r++ ) { + rowPos[r+1] = 0; + + int baseline=0; + int bdesc = 0; +// qDebug("height of row %d is %d/%d", r, grid[r].height.value, grid[r].height.type ); + int ch = grid[r].height.minWidth( 0 ); + int pos = rowPos[r] + ch + (grid[r].rowRenderer ? vspacing : 0); + + if ( pos > rowPos[r+1] ) + rowPos[r+1] = pos; + + Row *row = grid[r].row; + int totalCols = row->size(); + int totalRows = grid.size(); + bool pagedMode = canvas()->pagedMode(); + + grid[r].needFlex = false; + + for ( int c = 0; c < totalCols; c++ ) { + cell = cellAt(r, c); + if ( !cell || cell == (RenderTableCell *)-1 ) + continue; + if ( r < totalRows - 1 && cellAt(r+1, c) == cell ) + continue; + + if ( ( indx = r - cell->rowSpan() + 1 ) < 0 ) + indx = 0; + + if (cell->cellPercentageHeight() != -1) { + cell->setCellPercentageHeight(-1); + cell->setChildNeedsLayout(true, false); + if (cell->hasFlexedAnonymous()) { + for (RenderObject* o = cell->firstChild(); o ; o = o->nextSibling()) + if (o->isAnonymousBlock()) + o->setChildNeedsLayout(true, false); + } + if (pagedMode) cell->setNeedsLayout(true); + cell->layoutIfNeeded(); + } + + ch = cell->style()->height().width(0); + if ( cell->height() > ch) + ch = cell->height(); + + if (!cell->style()->height().isVariable()) + grid[r].needFlex = true; + + pos = rowPos[indx] + ch + (grid[r].rowRenderer ? vspacing : 0); + + if ( pos > rowPos[r+1] ) + rowPos[r+1] = pos; + + // find out the baseline + EVerticalAlign va = cell->style()->verticalAlign(); + if (va == BASELINE || va == TEXT_BOTTOM || va == TEXT_TOP + || va == SUPER || va == SUB) + { + int b=cell->baselinePosition(); + if (b > cell->borderTop() + cell->paddingTop()) { + if (b>baseline) + baseline=b; + + int td = rowPos[ indx ] + ch - b; + if (td>bdesc) + bdesc = td; + } + } + } + + //do we have baseline aligned elements? + if (baseline) { + // increase rowheight if baseline requires + int bRowPos = baseline + bdesc + (grid[r].rowRenderer ? vspacing : 0); + if (rowPos[r+1]<bRowPos) + rowPos[r+1]=bRowPos; + + grid[r].baseLine = baseline; + } + + if ( rowPos[r+1] < rowPos[r] ) + rowPos[r+1] = rowPos[r]; +// qDebug("rowpos(%d)=%d", r, rowPos[r] ); + } +} + +int RenderTableSection::layoutRows( int toAdd ) +{ + int rHeight; + int rindx; + int totalRows = grid.size(); + int hspacing = table()->borderHSpacing(); + int vspacing = table()->borderVSpacing(); + + // Set the width of our section now. The rows will also be this width. + m_width = table()->contentWidth(); + + if (markedForRepaint()) { + repaintDuringLayout(); + setMarkedForRepaint(false); + } + + if (toAdd && totalRows && (rowPos[totalRows] || !nextSibling())) { + + int totalHeight = rowPos[totalRows] + toAdd; +// qDebug("layoutRows: totalHeight = %d", totalHeight ); + + int dh = toAdd; + int totalPercent = 0; + int numVariable = 0; + for ( int r = 0; r < totalRows; r++ ) { + if ( grid[r].height.isVariable() && !emptyRow(r)) + numVariable++; + else if ( grid[r].height.isPercent() ) + totalPercent += grid[r].height.value(); + } + if ( totalPercent ) { +// qDebug("distributing %d over percent rows totalPercent=%d", dh, totalPercent ); + // try to satisfy percent + int add = 0; + if ( totalPercent > 100 ) + totalPercent = 100; + int rh = rowPos[1]-rowPos[0]; + for ( int r = 0; r < totalRows; r++ ) { + if ( totalPercent > 0 && grid[r].height.isPercent() ) { + int toAdd = kMin( dh, (totalHeight * grid[r].height.value() / 100)-rh ); + // If toAdd is negative, then we don't want to shrink the row (this bug + // affected Outlook Web Access). + toAdd = kMax(0, toAdd); + add += toAdd; + dh -= toAdd; + totalPercent -= grid[r].height.value(); +// qDebug( "adding %d to row %d", toAdd, r ); + } + if ( r < totalRows-1 ) + rh = rowPos[r+2] - rowPos[r+1]; + rowPos[r+1] += add; + } + } + if ( numVariable ) { + // distribute over non-empty variable rows +// qDebug("distributing %d over variable rows numVariable=%d", dh, numVariable ); + int add = 0; + int toAdd = dh/numVariable; + for ( int r = 0; r < totalRows; r++ ) { + if ( grid[r].height.isVariable() && !emptyRow(r)) { + add += toAdd; + } + rowPos[r+1] += add; + } + dh -= add; + } + if (dh>0 && rowPos[totalRows]) { + // if some left overs, distribute weighted. + int tot=rowPos[totalRows]; + int add=0; + int prev=rowPos[0]; + for ( int r = 0; r < totalRows; r++ ) { + //weight with the original height + add+=dh*(rowPos[r+1]-prev)/tot; + prev=rowPos[r+1]; + rowPos[r+1]+=add; + } + dh -= add; + } + if (dh > totalRows) { + // distribute to tables with all empty rows + int add=0; + int toAdd = dh/totalRows; + for ( int r = 0; r < totalRows; r++ ) { + add += toAdd; + rowPos[r+1] += add; + } + dh -= add; + } + // Finally distribute round-off values + if (dh > 0) { + // There is not enough for every row + int add=0; + for ( int r = 0; r < totalRows; r++ ) { + if (add < dh) add++; + rowPos[r+1] += add; + } + dh -= add; + } + assert (dh == 0); + } + + int leftOffset = borderLeft() + hspacing; + + int nEffCols = table()->numEffCols(); + for ( int r = 0; r < totalRows; r++ ) + { + Row *row = grid[r].row; + int totalCols = row->size(); + +#ifdef APPLE_CHANGES + // in WC, rows and cells share the same coordinate space, so that rows can have + // dimensions in the layer system. This is of dubious value, and a heavy maintenance burden + // (RenderObject's coordinates can't be used deterministically anymore) so we'll consider other options. + + // Set the row's x/y position and width/height. + if (grid[r].rowRenderer) { + grid[r].rowRenderer->setPos(0, rowPos[r]); + grid[r].rowRenderer->setWidth(m_width); + grid[r].rowRenderer->setHeight(rowPos[r+1] - rowPos[r] - vspacing); + } +#endif + + for ( int c = 0; c < nEffCols; c++ ) + { + RenderTableCell *cell = cellAt(r, c); + if (!cell || cell == (RenderTableCell *)-1 ) + continue; + if ( r < totalRows - 1 && cell == cellAt(r+1, c) ) + continue; + + if ( ( rindx = r-cell->rowSpan()+1 ) < 0 ) + rindx = 0; + + rHeight = rowPos[r+1] - rowPos[rindx] - vspacing; + + // Force percent height children to lay themselves out again. + // This will cause, e.g., textareas to grow to + // fill the area. FIXME: <div>s and blocks still don't + // work right. We'll need to have an efficient way of + // invalidating all percent height objects in a render subtree. + // For now, we just handle immediate children. -dwh + + bool flexAllChildren = grid[r].needFlex || (!table()->style()->height().isVariable() && rHeight != cell->height()); + cell->setHasFlexedAnonymous(false); + if ( flexAllChildren && flexCellChildren(cell) ) { + cell->setCellPercentageHeight(kMax(0, + rHeight - cell->borderTop() - cell->paddingTop() - + cell->borderBottom() - cell->paddingBottom())); + cell->layoutIfNeeded(); + + } + { +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << "setting position " << r << "/" << c << ": " + << table()->columnPos[c] /*+ padding */ << "/" << rowPos[rindx] << " height=" << rHeight<< endl; +#endif + + EVerticalAlign va = cell->style()->verticalAlign(); + int te=0; + switch (va) + { + case SUB: + case SUPER: + case TEXT_TOP: + case TEXT_BOTTOM: + case BASELINE: + te = getBaseline(r) - cell->baselinePosition() ; + break; + case TOP: + te = 0; + break; + case MIDDLE: + te = (rHeight - cell->height())/2; + break; + case BOTTOM: + te = rHeight - cell->height(); + break; + default: + break; + } + te = kMax( 0, te ); +#ifdef DEBUG_LAYOUT + // kdDebug( 6040 ) << "CELL " << cell << " te=" << te << ", be=" << rHeight - cell->height() - te << ", rHeight=" << rHeight << ", valign=" << va << endl; +#endif + cell->setCellTopExtra( te ); + cell->setCellBottomExtra( rHeight - cell->height() - te); + } + if (style()->direction()==RTL) { + cell->setPos( + table()->columnPos[(int)totalCols] - + table()->columnPos[table()->colToEffCol(cell->col()+cell->colSpan())] + + leftOffset, + rowPos[rindx] ); + } else { + cell->setPos( table()->columnPos[c] + leftOffset, rowPos[rindx] ); + } + } + } + + m_height = rowPos[totalRows]; + return m_height; +} + +bool RenderTableSection::flexCellChildren(RenderObject* p) const +{ + if (!p) + return false; + RenderObject* o = p->firstChild(); + bool didFlex = false; + while (o) { + if (!o->isText() && o->style()->height().isPercent()) { + if (o->isWidget()) { + // cancel resizes from transitory relayouts + static_cast<RenderWidget *>(o)->cancelPendingResize(); + } + o->setNeedsLayout(true, false); + p->setChildNeedsLayout(true, false); + didFlex = true; + } else if (o->isAnonymousBlock() && flexCellChildren( o )) { + p->setChildNeedsLayout(true, false); + if (p->isTableCell()) + static_cast<RenderTableCell*>(p)->setHasFlexedAnonymous(); + didFlex = true; + } + o = o->nextSibling(); + } + return didFlex; +} + +inline static RenderTableRow *firstTableRow(RenderObject *row) +{ + while (row && !row->isTableRow()) + row = row->nextSibling(); + return static_cast<RenderTableRow *>(row); +} + +inline static RenderTableRow *nextTableRow(RenderObject *row) +{ + row = row ? row->nextSibling() : row; + while (row && !row->isTableRow()) + row = row->nextSibling(); + return static_cast<RenderTableRow *>(row); +} + +int RenderTableSection::lowestPosition(bool includeOverflowInterior, bool includeSelf) const +{ + int bottom = RenderBox::lowestPosition(includeOverflowInterior, includeSelf); + if (!includeOverflowInterior && hasOverflowClip()) + return bottom; + + for (RenderObject *row = firstChild(); row; row = row->nextSibling()) { + for (RenderObject *cell = row->firstChild(); cell; cell = cell->nextSibling()) + if (cell->isTableCell()) { + int bp = cell->yPos() + cell->lowestPosition(false); + bottom = kMax(bottom, bp); + } + } + + return bottom; +} + +int RenderTableSection::rightmostPosition(bool includeOverflowInterior, bool includeSelf) const +{ + int right = RenderBox::rightmostPosition(includeOverflowInterior, includeSelf); + if (!includeOverflowInterior && hasOverflowClip()) + return right; + + for (RenderObject *row = firstChild(); row; row = row->nextSibling()) { + for (RenderObject *cell = row->firstChild(); cell; cell = cell->nextSibling()) + if (cell->isTableCell()) { + int rp = cell->xPos() + cell->rightmostPosition(false); + right = kMax(right, rp); + } + } + + return right; +} + +int RenderTableSection::leftmostPosition(bool includeOverflowInterior, bool includeSelf) const +{ + int left = RenderBox::leftmostPosition(includeOverflowInterior, includeSelf); + if (!includeOverflowInterior && hasOverflowClip()) + return left; + + for (RenderObject *row = firstChild(); row; row = row->nextSibling()) { + for (RenderObject *cell = row->firstChild(); cell; cell = cell->nextSibling()) + if (cell->isTableCell()) { + int lp = cell->xPos() + cell->leftmostPosition(false); + left = kMin(left, lp); + } + } + + return left; +} + +int RenderTableSection::highestPosition(bool includeOverflowInterior, bool includeSelf) const +{ + int top = RenderBox::highestPosition(includeOverflowInterior, includeSelf); + if (!includeOverflowInterior && hasOverflowClip()) + return top; + + for (RenderObject *row = firstChild(); row; row = row->nextSibling()) { + for (RenderObject *cell = row->firstChild(); cell; cell = cell->nextSibling()) + if (cell->isTableCell()) { + int hp = cell->yPos() + cell->highestPosition(false); + top = kMin(top, hp); + } + } + + return top; +} + +// Search from first_row to last_row for the row containing y +static unsigned int findRow(unsigned int first_row, unsigned int last_row, + const QMemArray<int> &rowPos, int y) +{ + unsigned int under = first_row; + unsigned int over = last_row; + int offset = (over - under)/2; + while (over-under > 1) { + if (rowPos[under+offset] <= y) + under = under+offset; + else + over = under+offset; + offset = (over - under)/2; + } + + assert(under == first_row || rowPos[under] <= y); + assert(over == last_row || rowPos[over] > y); + + return under; +} + +static void findRowCover(unsigned int &startrow, unsigned int &endrow, + const QMemArray<int> &rowPos, + int min_y, int max_y) +{ + assert(max_y >= min_y); + unsigned int totalRows = endrow; + + unsigned int index = 0; + // Initial binary search boost: + if (totalRows >= 8) { + int offset = (endrow - startrow)/2; + while (endrow - startrow > 1) { + index = startrow+offset; + if (rowPos[index] <= min_y ) + // index is below both min_y and max_y + startrow = index; + else + if (rowPos[index] > max_y) + // index is above both min_y and max_y + endrow = index; + else + // index is within the selection + break; + offset = (endrow - startrow)/2; + } + } + + // Binary search for startrow + startrow = findRow(startrow, endrow, rowPos, min_y); + // Binary search for endrow + endrow = findRow(startrow, endrow, rowPos, max_y) + 1; + if (endrow > totalRows) endrow = totalRows; +} + +void RenderTableSection::paint( PaintInfo& pI, int tx, int ty ) +{ + unsigned int totalRows = grid.size(); + unsigned int totalCols = table()->columns.size(); + + tx += m_x; + ty += m_y; + + if (pI.phase == PaintActionOutline) + paintOutline(pI.p, tx, ty, width(), height(), style()); + + CollapsedBorderValue *cbs = table()->currentBorderStyle(); + int cbsw2 = cbs ? cbs->width()/2 : 0; + int cbsw21 = cbs ? (cbs->width()+1)/2 : 0; + + int x = pI.r.x(), y = pI.r.y(), w = pI.r.width(), h = pI.r.height(); + // check which rows and cols are visible and only paint these + int os = 2*maximalOutlineSize(pI.phase); + unsigned int startrow = 0; + unsigned int endrow = totalRows; + findRowCover(startrow, endrow, rowPos, y - os - ty - kMax(cbsw21, os), y + h + os - ty + kMax(cbsw2, os)); + + // A binary search is probably not worthwhile for coloumns + unsigned int startcol = 0; + unsigned int endcol = totalCols; + if ( style()->direction() == LTR ) { + for ( ; startcol < totalCols; startcol++ ) { + if ( tx + table()->columnPos[startcol+1] + kMax(cbsw21, os) > x - os ) + break; + } + for ( ; endcol > 0; endcol-- ) { + if ( tx + table()->columnPos[endcol-1] - kMax(cbsw2, os) < x + w + os ) + break; + } + } + if ( startcol < endcol ) { + // draw the cells + for ( unsigned int r = startrow; r < endrow; r++ ) { + // paint the row + if (grid[r].rowRenderer) { + int height = rowPos[r+1] - rowPos[r] - table()->borderVSpacing(); + grid[r].rowRenderer->paintRow(pI, tx, ty + rowPos[r], width(), height); + } + + unsigned int c = startcol; + Row *row = grid[r].row; + Row *nextrow = (r < endrow - 1) ? grid[r+1].row : 0; + // since a cell can be -1 (indicating a colspan) we might have to search backwards to include it + while ( c && (*row)[c] == (RenderTableCell *)-1 ) + c--; + for ( ; c < endcol; c++ ) { + RenderTableCell *cell = (*row)[c]; + if ( !cell || cell == (RenderTableCell *)-1 || nextrow && (*nextrow)[c] == cell ) + continue; +#ifdef TABLE_PRINT + kdDebug( 6040 ) << "painting cell " << r << "/" << c << endl; +#endif + if (pI.phase == PaintActionElementBackground || pI.phase == PaintActionChildBackground) { + // We need to handle painting a stack of backgrounds. This stack (from bottom to top) consists of + // the column group, column, row group, row, and then the cell. + RenderObject* col = table()->colElement(c); + RenderObject* colGroup = 0; + if (col) { + RenderStyle *style = col->parent()->style(); + if (style->display() == TABLE_COLUMN_GROUP) + colGroup = col->parent(); + } + RenderObject* row = cell->parent(); + + // ### + // Column groups and columns first. + // FIXME: Columns and column groups do not currently support opacity, and they are being painted "too late" in + // the stack, since we have already opened a transparency layer (potentially) for the table row group. + // Note that we deliberately ignore whether or not the cell has a layer, since these backgrounds paint "behind" the + // cell. + cell->paintBackgroundsBehindCell(pI, tx, ty, colGroup); + cell->paintBackgroundsBehindCell(pI, tx, ty, col); + + // Paint the row group next. + cell->paintBackgroundsBehindCell(pI, tx, ty, this); + + // Paint the row next, but only if it doesn't have a layer. If a row has a layer, it will be responsible for + // painting the row background for the cell. + if (!row->layer()) + cell->paintBackgroundsBehindCell(pI, tx, ty, row); + } + + if ((!cell->layer() && !cell->parent()->layer()) || pI.phase == PaintActionCollapsedTableBorders) + cell->paint(pI, tx, ty); + } + } + } +} + +void RenderTableSection::recalcCells() +{ + cCol = 0; + cRow = -1; + clearGrid(); + grid.resize( 0 ); + + for (RenderObject *row = firstChild(); row; row = row->nextSibling()) { + if (row->isTableRow()) { + cRow++; + cCol = 0; + ensureRows( cRow+1 ); + grid[cRow].rowRenderer = static_cast<RenderTableRow*>(row); + + for (RenderObject *cell = row->firstChild(); cell; cell = cell->nextSibling()) + if (cell->isTableCell()) + addCell( static_cast<RenderTableCell *>(cell), static_cast<RenderTableRow *>(row) ); + } + } + needCellRecalc = false; + setNeedsLayout(true); +} + +void RenderTableSection::clearGrid() +{ + int rows = grid.size(); + while ( rows-- ) { + delete grid[rows].row; + } +} + +bool RenderTableSection::emptyRow(int rowNum) { + Row &r = *grid[rowNum].row; + const int s = r.size(); + RenderTableCell *cell; + for(int i=0; i<s; i++) { + cell = r[i]; + if (!cell || cell==(RenderTableCell*)-1) + continue; + return false; + } + return true; +} + +RenderObject* RenderTableSection::removeChildNode(RenderObject* child) +{ + setNeedCellRecalc(); + return RenderContainer::removeChildNode( child ); +} + +bool RenderTableSection::canClear(RenderObject */*child*/, PageBreakLevel level) +{ + // We cannot clear rows yet. + return parent()->canClear(this, level); +} + +void RenderTableSection::addSpaceAt(int pos, int dy) +{ + const int nEffCols = table()->numEffCols(); + const int totalRows = numRows(); + for ( int r = 0; r < totalRows; r++ ) { + if (rowPos[r] >= pos) { + rowPos[r] += dy; + int rindx; + for ( int c = 0; c < nEffCols; c++ ) + { + RenderTableCell *cell = cellAt(r, c); + if (!cell || cell == (RenderTableCell *)-1 ) + continue; + if ( r > 0 && cell == cellAt(r-1, c) ) + continue; + + if ( ( rindx = r-cell->rowSpan()+1 ) < 0 ) + rindx = 0; + + cell->setPos(cell->xPos(), rowPos[r]); + } + } + } + if (rowPos[totalRows] >= pos) + rowPos[totalRows] += dy; + m_height = rowPos[totalRows]; + + setContainsPageBreak(true); +} + + +#ifdef ENABLE_DUMP +void RenderTableSection::dump(QTextStream &stream, const QString &ind) const +{ + RenderContainer::dump(stream,ind); + + stream << " grid=(" << grid.size() << "," << table()->numEffCols() << ")"; + for ( unsigned int r = 0; r < grid.size(); r++ ) { + for ( int c = 0; c < table()->numEffCols(); c++ ) { + if ( cellAt( r, c ) && cellAt( r, c ) != (RenderTableCell *)-1 ) + stream << " (" << cellAt( r, c )->row() << "," << cellAt( r, c )->col() << "," + << cellAt(r, c)->rowSpan() << "," << cellAt(r, c)->colSpan() << ") "; + else + stream << " null cell"; + } + } +} +#endif + +static RenderTableCell *seekCell(RenderTableSection *section, int row, int col) +{ + if (row < 0 || col < 0 || row >= section->numRows()) return 0; + // since a cell can be -1 (indicating a colspan) we might have to search backwards to include it + while ( col && section->cellAt( row, col ) == (RenderTableCell *)-1 ) + col--; + + return section->cellAt(row, col); +} + +/** Looks for the first element suitable for text selection, beginning from + * the last. + * @param base search is restricted within this node. This node must have + * a renderer. + * @return the element or @p base if no suitable element found. + */ +static NodeImpl *findLastSelectableNode(NodeImpl *base) +{ + NodeImpl *last = base; + // Look for last text/cdata node that has a renderer, + // or last childless replaced element + while ( last && !(last->renderer() + && ((last->nodeType() == Node::TEXT_NODE || last->nodeType() == Node::CDATA_SECTION_NODE) + || (last->renderer()->isReplaced() && !last->renderer()->lastChild())))) + { + NodeImpl *next = last->lastChild(); + if ( !next ) next = last->previousSibling(); + while ( last && last != base && !next ) + { + last = last->parentNode(); + if ( last && last != base ) + next = last->previousSibling(); + } + last = next; + } + + return last ? last : base; +} + +FindSelectionResult RenderTableSection::checkSelectionPoint( int _x, int _y, int _tx, int _ty, DOM::NodeImpl*& node, int & offset, SelPointState &state ) +{ + // Table sections need extra treatment for selections. The rows are scanned + // from top to bottom, and within each row, only the cell that matches + // the given position best is descended into. + + unsigned int totalRows = grid.size(); + unsigned int totalCols = table()->columns.size(); + +// absolutePosition(_tx, _ty, false); + + _tx += m_x; + _ty += m_y; + +// bool save_last = false; // true to save last investigated cell + + if (needsLayout() || _y < _ty) return SelectionPointBefore; +// else if (_y >= _ty + height()) save_last = true; + + // Find the row containing the pointer + int row_idx = findRow(0, totalRows, rowPos, _y - _ty); + + int col_idx; + if ( style()->direction() == LTR ) { + for ( col_idx = (int)totalCols - 1; col_idx >= 0; col_idx-- ) { + if ( _tx + table()->columnPos[col_idx] < _x ) + break; + } + if (col_idx < 0) col_idx = 0; + } else { + for ( col_idx = 0; col_idx < (int)totalCols; col_idx++ ) { + if ( _tx + table()->columnPos[col_idx] > _x ) + break; + } + if (col_idx >= (int)totalCols) col_idx = (int)totalCols + 1; + } + + FindSelectionResult pos = SelectionPointBefore; + + RenderTableCell *cell = seekCell(this, row_idx, col_idx); + // ### dunno why cell can be 0, maybe due to weird spans? (LS) + if (cell) { + SelPointState localState; + pos = cell->checkSelectionPoint(_x, _y, _tx, _ty, node, offset, localState); + } + + if (pos != SelectionPointBefore) return pos; + + // store last column of last line + row_idx--; + col_idx = totalCols - 1; + cell = seekCell(this, row_idx, col_idx); + + // end of section? take previous section + RenderTableSection *sec = this; + if (!cell) { + sec = *--TableSectionIterator(sec); + if (!sec) return pos; + + cell = seekCell(sec, sec->grid.size() - 1, col_idx); + if (!cell) return pos; + } + + // take last child of previous cell, and store this one as last node + NodeImpl *element = cell->element(); + if (!element) return SelectionPointBefore; + + element = findLastSelectableNode(element); + + state.m_lastNode = element; + state.m_lastOffset = element->maxOffset(); + return SelectionPointBefore; +} + +// Hit Testing +bool RenderTableSection::nodeAtPoint(NodeInfo& info, int x, int y, int tx, int ty, HitTestAction action, bool inside) +{ + // Table sections cannot ever be hit tested. Effectively they do not exist. + // Just forward to our children always. + tx += m_x; + ty += m_y; + + for (RenderObject* child = lastChild(); child; child = child->previousSibling()) { + // FIXME: We have to skip over inline flows, since they can show up inside table rows + // at the moment (a demoted inline <form> for example). If we ever implement a + // table-specific hit-test method (which we should do for performance reasons anyway), + // then we can remove this check. + if (!child->layer() && !child->isInlineFlow() && child->nodeAtPoint(info, x, y, tx, ty, action, inside)) { + return true; + } + } + + return false; +} + + +// ------------------------------------------------------------------------- + +RenderTableRow::RenderTableRow(DOM::NodeImpl* node) + : RenderContainer(node) +{ + // init RenderObject attributes + setInline(false); // our object is not Inline +} + +RenderObject* RenderTableRow::removeChildNode(RenderObject* child) +{ + RenderTableSection *s = section(); + if (s) + s->setNeedCellRecalc(); + + return RenderContainer::removeChildNode( child ); +} + +void RenderTableRow::detach() +{ + RenderTableSection *s = section(); + if (s) + s->setNeedCellRecalc(); + + RenderContainer::detach(); +} + +void RenderTableRow::setStyle(RenderStyle* newStyle) +{ + if (section() && style() && style()->height() != newStyle->height()) + section()->setNeedCellRecalc(); + + newStyle->setDisplay(TABLE_ROW); + RenderContainer::setStyle(newStyle); +} + +void RenderTableRow::addChild(RenderObject *child, RenderObject *beforeChild) +{ +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(TableRow)::addChild( " << child->renderName() << " )" << ", " << + (beforeChild ? beforeChild->renderName() : "0") << " )" << endl; +#endif + + if ( !child->isTableCell() ) { + // TR > FORM quirk (???) + if (child->element() && child->element()->isHTMLElement() && child->element()->id() == ID_FORM && + static_cast<HTMLFormElementImpl*>(child->element())->isMalformed()) + { + RenderContainer::addChild(child, beforeChild); + return; + } + + RenderObject* last = beforeChild; + if (!last) + last = lastChild(); + if (last && last->isAnonymous() && last->isTableCell()) { + last->addChild(child); + return; + } + + // If beforeChild is inside an anonymous cell, insert into the cell. + if (last && !last->isTableCell() && last->parent() && last->parent()->isAnonymous()) { + last->parent()->addChild(child, beforeChild); + return; + } + + RenderTableCell* cell = new (renderArena()) RenderTableCell(document() /* anonymous object */); + RenderStyle* newStyle = new RenderStyle(); + newStyle->inheritFrom(style()); + newStyle->setDisplay(TABLE_CELL); + cell->setStyle(newStyle); + addChild(cell, beforeChild); + cell->addChild(child); + return; + } + + RenderTableCell* cell = static_cast<RenderTableCell*>(child); + + section()->addCell( cell, this ); + + RenderContainer::addChild(cell,beforeChild); + + if ( beforeChild || nextSibling() ) + section()->setNeedCellRecalc(); +} + +void RenderTableRow::layout() +{ + KHTMLAssert( needsLayout() ); + KHTMLAssert( minMaxKnown() ); + + RenderObject *child = firstChild(); + const bool pagedMode = canvas()->pagedMode(); + while( child ) { + if ( child->isTableCell() ) { + RenderTableCell *cell = static_cast<RenderTableCell *>(child); + if (pagedMode) { + cell->setNeedsLayout(true); + int oldHeight = child->height(); + cell->layout(); + if (oldHeight > 0 && child->containsPageBreak() && child->height() != oldHeight) + section()->addSpaceAt(child->yPos()+1, child->height() - oldHeight); + } else + if ( child->needsLayout() ) { + if (markedForRepaint()) + cell->setMarkedForRepaint( true ); + cell->calcVerticalMargins(); + cell->layout(); + cell->setCellTopExtra(0); + cell->setCellBottomExtra(0); + if (child->containsPageBreak()) setContainsPageBreak(true); + } + } + child = child->nextSibling(); + } + setMarkedForRepaint(false); + setNeedsLayout(false); +} + +int RenderTableRow::offsetLeft() const +{ + RenderObject *child = firstChild(); + while (child && !child->isTableCell()) + child = child->nextSibling(); + + if (!child) + return 0; + + return child->offsetLeft(); +} + +int RenderTableRow::offsetTop() const +{ + RenderObject *child = firstChild(); + while (child && !child->isTableCell()) + child = child->nextSibling(); + + if (!child) + return 0; + + return child->offsetTop() - + static_cast<RenderTableCell*>(child)->cellTopExtra(); +} + +int RenderTableRow::offsetHeight() const +{ + RenderObject *child = firstChild(); + while (child && !child->isTableCell()) + child = child->nextSibling(); + + if (!child) + return 0; + + return child->offsetHeight() + + static_cast<RenderTableCell*>(child)->cellTopExtra() + + static_cast<RenderTableCell*>(child)->cellBottomExtra(); +} + +short RenderTableRow::offsetWidth() const +{ + RenderObject *fc = firstChild(); + RenderObject *lc = lastChild(); + while (fc && !fc->isTableCell()) + fc = fc->nextSibling(); + while (lc && !lc->isTableCell()) + lc = lc->previousSibling(); + if (!lc || !fc) + return 0; + + return lc->xPos()+lc->width()-fc->xPos(); +} + +void RenderTableRow::paintRow( PaintInfo& pI, int tx, int ty, int w, int h ) +{ + if (pI.phase == PaintActionOutline) + paintOutline(pI.p, tx, ty, w, h, style()); +} + +void RenderTableRow::paint(PaintInfo& i, int tx, int ty) +{ + KHTMLAssert(layer()); + if (!layer()) + return; + + for (RenderObject* child = firstChild(); child; child = child->nextSibling()) { + if (child->isTableCell()) { + // Paint the row background behind the cell. + if (i.phase == PaintActionElementBackground || i.phase == PaintActionChildBackground) { + RenderTableCell* cell = static_cast<RenderTableCell*>(child); + cell->paintBackgroundsBehindCell(i, tx, ty, this); + } + if (!child->layer()) + child->paint(i, tx, ty); + } + } +} + +// Hit Testing +bool RenderTableRow::nodeAtPoint(NodeInfo& info, int x, int y, int tx, int ty, HitTestAction action, bool inside) +{ + // Table rows cannot ever be hit tested. Effectively they do not exist. + // Just forward to our children always. + for (RenderObject* child = lastChild(); child; child = child->previousSibling()) { + // FIXME: We have to skip over inline flows, since they can show up inside table rows + // at the moment (a demoted inline <form> for example). If we ever implement a + // table-specific hit-test method (which we should do for performance reasons anyway), + // then we can remove this check. + if (!child->layer() && !child->isInlineFlow() && child->nodeAtPoint(info, x, y, tx, ty, action, inside)) { + return true; + } + } + + return false; +} + +// ------------------------------------------------------------------------- + +RenderTableCell::RenderTableCell(DOM::NodeImpl* _node) + : RenderBlock(_node) +{ + _col = -1; + _row = -1; + updateFromElement(); + setShouldPaintBackgroundOrBorder(true); + _topExtra = 0; + _bottomExtra = 0; + m_percentageHeight = -1; + m_hasFlexedAnonymous = false; + m_widthChanged = false; +} + +void RenderTableCell::detach() +{ + if (parent() && section()) + section()->setNeedCellRecalc(); + + RenderBlock::detach(); +} + +void RenderTableCell::updateFromElement() +{ + DOM::NodeImpl *node = element(); + if ( node && (node->id() == ID_TD || node->id() == ID_TH) ) { + DOM::HTMLTableCellElementImpl *tc = static_cast<DOM::HTMLTableCellElementImpl *>(node); + cSpan = tc->colSpan(); + rSpan = tc->rowSpan(); + } else { + cSpan = rSpan = 1; + } +} + +Length RenderTableCell::styleOrColWidth() +{ + Length w = style()->width(); + if (colSpan() > 1 || !w.isVariable()) + return w; + RenderTableCol* col = table()->colElement(_col); + if (col) + w = col->style()->width(); + return w; +} + +void RenderTableCell::calcMinMaxWidth() +{ + KHTMLAssert( !minMaxKnown() ); +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(TableCell)::calcMinMaxWidth() known=" << minMaxKnown() << endl; +#endif + + if (section()->needCellRecalc) + section()->recalcCells(); + + RenderBlock::calcMinMaxWidth(); + if (element() && style()->whiteSpace() == NORMAL) { + // See if nowrap was set. + Length w = styleOrColWidth(); + DOMString nowrap = static_cast<ElementImpl*>(element())->getAttribute(ATTR_NOWRAP); + if (!nowrap.isNull() && w.isFixed() && + m_minWidth < w.value() ) + // Nowrap is set, but we didn't actually use it because of the + // fixed width set on the cell. Even so, it is a WinIE/Moz trait + // to make the minwidth of the cell into the fixed width. They do this + // even in strict mode, so do not make this a quirk. Affected the top + // of hiptop.com. + m_minWidth = w.value(); + } + + setMinMaxKnown(); +} + +void RenderTableCell::calcWidth() +{ +} + +void RenderTableCell::setWidth( int width ) +{ + if ( width != m_width ) { + m_width = width; + m_widthChanged = true; + } +} + +void RenderTableCell::layout() +{ + layoutBlock( m_widthChanged ); + m_widthChanged = false; +} + +void RenderTableCell::close() +{ + RenderBlock::close(); + +#ifdef DEBUG_LAYOUT + kdDebug( 6040 ) << renderName() << "(RenderTableCell)::close() total height =" << m_height << endl; +#endif +} + +bool RenderTableCell::requiresLayer() const { + // table-cell display is never positioned (css 2.1-9.7), so the only time a layer is needed + // is when overflow != visible (or when there is opacity when we support it) + return /* style()->opacity() < 1.0f || */ hasOverflowClip() || isRelPositioned(); +} + +void RenderTableCell::repaintRectangle(int x, int y, int w, int h, Priority p, bool f) +{ + RenderBlock::repaintRectangle(x, y, w, h + _topExtra + _bottomExtra, p, f); +} + +bool RenderTableCell::absolutePosition(int &xPos, int &yPos, bool f) const +{ + bool result = RenderBlock::absolutePosition(xPos, yPos, f); + xPos -= parent()->xPos(); // Rows are in the same coordinate space, so don't add their offset in. + yPos -= parent()->yPos(); + return result; +} + +int RenderTableCell::pageTopAfter(int y) const +{ + return section()->pageTopAfter(y+m_y + _topExtra) - (m_y + _topExtra); +} + +short RenderTableCell::baselinePosition( bool ) const +{ + RenderObject* o = firstChild(); + int offset = paddingTop() + borderTop(); + if (!o) return offset + contentHeight(); + while (o->firstChild()) { + if (!o->isInline()) + offset += o->paddingTop() + o->borderTop(); + o = o->firstChild(); + } + + if (!o->isInline()) + return paddingTop() + borderTop() + contentHeight(); + + offset += o->baselinePosition( true ); + return offset; +} + + +void RenderTableCell::setStyle( RenderStyle *newStyle ) +{ + if (parent() && section() && style() && style()->height() != newStyle->height()) + section()->setNeedCellRecalc(); + + newStyle->setDisplay(TABLE_CELL); + RenderBlock::setStyle( newStyle ); + setShouldPaintBackgroundOrBorder(true); + + if (newStyle->whiteSpace() == KHTML_NOWRAP) { + // Figure out if we are really nowrapping or if we should just + // use normal instead. If the width of the cell is fixed, then + // we don't actually use NOWRAP. + if (newStyle->width().isFixed()) + newStyle->setWhiteSpace(NORMAL); + else + newStyle->setWhiteSpace(NOWRAP); + } +} + +bool RenderTableCell::nodeAtPoint(NodeInfo& info, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction, bool inside) +{ + int tx = _tx + m_x; + int ty = _ty + m_y; + + // also include the top and bottom extra space + inside |= style()->visibility() != HIDDEN + && (_y >= ty) && (_y < ty + height() + _topExtra + _bottomExtra) + && (_x >= tx) && (_x < tx + width()); + + return RenderBlock::nodeAtPoint(info, _x, _y, _tx, _ty, hitTestAction, inside); +} + +// The following rules apply for resolving conflicts and figuring out which border +// to use. +// (1) Borders with the 'border-style' of 'hidden' take precedence over all other conflicting +// borders. Any border with this value suppresses all borders at this location. +// (2) Borders with a style of 'none' have the lowest priority. Only if the border properties of all +// the elements meeting at this edge are 'none' will the border be omitted (but note that 'none' is +// the default value for the border style.) +// (3) If none of the styles are 'hidden' and at least one of them is not 'none', then narrow borders +// are discarded in favor of wider ones. If several have the same 'border-width' then styles are preferred +// in this order: 'double', 'solid', 'dashed', 'dotted', 'ridge', 'outset', 'groove', and the lowest: 'inset'. +// (4) If border styles differ only in color, then a style set on a cell wins over one on a row, +// which wins over a row group, column, column group and, lastly, table. It is undefined which color +// is used when two elements of the same type disagree. +static CollapsedBorderValue compareBorders(const CollapsedBorderValue& border1, + const CollapsedBorderValue& border2) +{ + // Sanity check the values passed in. If either is null, return the other. + if (!border2.exists()) return border1; + if (!border1.exists()) return border2; + + // Rule #1 above. + if (border1.style() == BHIDDEN || border2.style() == BHIDDEN) + return CollapsedBorderValue(); // No border should exist at this location. + + // Rule #2 above. A style of 'none' has lowest priority and always loses to any other border. + if (border2.style() == BNONE) return border1; + if (border1.style() == BNONE) return border2; + + // The first part of rule #3 above. Wider borders win. + if (border1.width() != border2.width()) + return border1.width() > border2.width() ? border1 : border2; + + // The borders have equal width. Sort by border style. + if (border1.style() != border2.style()) + return border1.style() > border2.style() ? border1 : border2; + + // The border have the same width and style. Rely on precedence (cell over row over row group, etc.) + return border1.precedence >= border2.precedence ? border1 : border2; +} + +CollapsedBorderValue RenderTableCell::collapsedLeftBorder() const +{ + // For border left, we need to check, in order of precedence: + // (1) Our left border. + CollapsedBorderValue result(&style()->borderLeft(), BCELL); + + // (2) The previous cell's right border. + RenderTableCell* prevCell = table()->cellLeft(this); + if (prevCell) { + result = compareBorders(result, CollapsedBorderValue(&prevCell->style()->borderRight(), BCELL)); + if (!result.exists()) return result; + } + else if (col() == 0) { + // (3) Our row's left border. + result = compareBorders(result, CollapsedBorderValue(&parent()->style()->borderLeft(), BROW)); + if (!result.exists()) return result; + + // (4) Our row group's left border. + result = compareBorders(result, CollapsedBorderValue(§ion()->style()->borderLeft(), BROWGROUP)); + if (!result.exists()) return result; + } + + // (5) Our column's left border. + RenderTableCol* colElt = table()->colElement(col()); + if (colElt) { + result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderLeft(), BCOL)); + if (!result.exists()) return result; + } + + // (6) The previous column's right border. + if (col() > 0) { + colElt = table()->colElement(col()-1); + if (colElt) { + result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderRight(), BCOL)); + if (!result.exists()) return result; + } + } + + if (col() == 0) { + // (7) The table's left border. + result = compareBorders(result, CollapsedBorderValue(&table()->style()->borderLeft(), BTABLE)); + if (!result.exists()) return result; + } + + return result; +} + +CollapsedBorderValue RenderTableCell::collapsedRightBorder() const +{ + RenderTable* tableElt = table(); + bool inLastColumn = false; + int effCol = tableElt->colToEffCol(col()+colSpan()-1); + if (effCol == tableElt->numEffCols()-1) + inLastColumn = true; + + // For border right, we need to check, in order of precedence: + // (1) Our right border. + CollapsedBorderValue result = CollapsedBorderValue(&style()->borderRight(), BCELL); + + // (2) The next cell's left border. + if (!inLastColumn) { + RenderTableCell* nextCell = tableElt->cellRight(this); + if (nextCell) { + result = compareBorders(result, CollapsedBorderValue(&nextCell->style()->borderLeft(), BCELL)); + if (!result.exists()) return result; + } + } + else { + // (3) Our row's right border. + result = compareBorders(result, CollapsedBorderValue(&parent()->style()->borderRight(), BROW)); + if (!result.exists()) return result; + + // (4) Our row group's right border. + result = compareBorders(result, CollapsedBorderValue(§ion()->style()->borderRight(), BROWGROUP)); + if (!result.exists()) return result; + } + + // (5) Our column's right border. + RenderTableCol* colElt = table()->colElement(col()+colSpan()-1); + if (colElt) { + result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderRight(), BCOL)); + if (!result.exists()) return result; + } + + // (6) The next column's left border. + if (!inLastColumn) { + colElt = tableElt->colElement(col()+colSpan()); + if (colElt) { + result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderLeft(), BCOL)); + if (!result.exists()) return result; + } + } + else { + // (7) The table's right border. + result = compareBorders(result, CollapsedBorderValue(&tableElt->style()->borderRight(), BTABLE)); + if (!result.exists()) return result; + } + + return result; +} + +CollapsedBorderValue RenderTableCell::collapsedTopBorder() const +{ + // For border top, we need to check, in order of precedence: + // (1) Our top border. + CollapsedBorderValue result = CollapsedBorderValue(&style()->borderTop(), BCELL); + + RenderTableCell* prevCell = table()->cellAbove(this); + if (prevCell) { + // (2) A previous cell's bottom border. + result = compareBorders(result, CollapsedBorderValue(&prevCell->style()->borderBottom(), BCELL)); + if (!result.exists()) return result; + } + + // (3) Our row's top border. + result = compareBorders(result, CollapsedBorderValue(&parent()->style()->borderTop(), BROW)); + if (!result.exists()) return result; + + // (4) The previous row's bottom border. + if (prevCell) { + RenderObject* prevRow = 0; + if (prevCell->section() == section()) + prevRow = parent()->previousSibling(); + else + prevRow = prevCell->section()->lastChild(); + + if (prevRow) { + result = compareBorders(result, CollapsedBorderValue(&prevRow->style()->borderBottom(), BROW)); + if (!result.exists()) return result; + } + } + + // Now check row groups. + RenderTableSection* currSection = section(); + if (row() == 0) { + // (5) Our row group's top border. + result = compareBorders(result, CollapsedBorderValue(&currSection->style()->borderTop(), BROWGROUP)); + if (!result.exists()) return result; + + // (6) Previous row group's bottom border. + currSection = table()->sectionAbove(currSection); + if (currSection) { + result = compareBorders(result, CollapsedBorderValue(&currSection->style()->borderBottom(), BROWGROUP)); + if (!result.exists()) + return result; + } + } + + if (!currSection) { + // (8) Our column's top border. + RenderTableCol* colElt = table()->colElement(col()); + if (colElt) { + result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderTop(), BCOL)); + if (!result.exists()) return result; + } + + // (9) The table's top border. + result = compareBorders(result, CollapsedBorderValue(&table()->style()->borderTop(), BTABLE)); + if (!result.exists()) return result; + } + + return result; +} + +CollapsedBorderValue RenderTableCell::collapsedBottomBorder() const +{ + // For border top, we need to check, in order of precedence: + // (1) Our bottom border. + CollapsedBorderValue result = CollapsedBorderValue(&style()->borderBottom(), BCELL); + + RenderTableCell* nextCell = table()->cellBelow(this); + if (nextCell) { + // (2) A following cell's top border. + result = compareBorders(result, CollapsedBorderValue(&nextCell->style()->borderTop(), BCELL)); + if (!result.exists()) return result; + } + + // (3) Our row's bottom border. (FIXME: Deal with rowspan!) + result = compareBorders(result, CollapsedBorderValue(&parent()->style()->borderBottom(), BROW)); + if (!result.exists()) return result; + + // (4) The next row's top border. + if (nextCell) { + result = compareBorders(result, CollapsedBorderValue(&nextCell->parent()->style()->borderTop(), BROW)); + if (!result.exists()) return result; + } + + // Now check row groups. + RenderTableSection* currSection = section(); + if (row()+rowSpan() >= static_cast<RenderTableSection*>(currSection)->numRows()) { + // (5) Our row group's bottom border. + result = compareBorders(result, CollapsedBorderValue(&currSection->style()->borderBottom(), BROWGROUP)); + if (!result.exists()) return result; + + // (6) Following row group's top border. + currSection = table()->sectionBelow(currSection); + if (currSection) { + result = compareBorders(result, CollapsedBorderValue(&currSection->style()->borderTop(), BROWGROUP)); + if (!result.exists()) + return result; + } + } + + if (!currSection) { + // (8) Our column's bottom border. + RenderTableCol* colElt = table()->colElement(col()); + if (colElt) { + result = compareBorders(result, CollapsedBorderValue(&colElt->style()->borderBottom(), BCOL)); + if (!result.exists()) return result; + } + + // (9) The table's bottom border. + result = compareBorders(result, CollapsedBorderValue(&table()->style()->borderBottom(), BTABLE)); + if (!result.exists()) return result; + } + + return result; +} + +int RenderTableCell::borderLeft() const +{ + if (table()->collapseBorders()) { + CollapsedBorderValue border = collapsedLeftBorder(); + if (border.exists()) + return (border.width()+1)/2; // Give the extra pixel to top and left. + return 0; + } + return RenderBlock::borderLeft(); +} + +int RenderTableCell::borderRight() const +{ + if (table()->collapseBorders()) { + CollapsedBorderValue border = collapsedRightBorder(); + if (border.exists()) + return border.width()/2; + return 0; + } + return RenderBlock::borderRight(); +} + +int RenderTableCell::borderTop() const +{ + if (table()->collapseBorders()) { + CollapsedBorderValue border = collapsedTopBorder(); + if (border.exists()) + return (border.width()+1)/2; // Give the extra pixel to top and left. + return 0; + } + return RenderBlock::borderTop(); +} + +int RenderTableCell::borderBottom() const +{ + if (table()->collapseBorders()) { + CollapsedBorderValue border = collapsedBottomBorder(); + if (border.exists()) + return border.width()/2; + return 0; + } + return RenderBlock::borderBottom(); +} + +#ifdef BOX_DEBUG +#include <qpainter.h> + +static void outlineBox(QPainter *p, int _tx, int _ty, int w, int h) +{ + p->setPen(QPen(QColor("yellow"), 3, Qt::DotLine)); + p->setBrush( Qt::NoBrush ); + p->drawRect(_tx, _ty, w, h ); +} +#endif + +void RenderTableCell::paint(PaintInfo& pI, int _tx, int _ty) +{ + +#ifdef TABLE_PRINT + kdDebug( 6040 ) << renderName() << "(RenderTableCell)::paint() w/h = (" << width() << "/" << height() << ")" << " _y/_h=" << pI.r.y() << "/" << pI.r.height() << endl; +#endif + + if (needsLayout()) return; + + _tx += m_x; + _ty += m_y/* + _topExtra*/; + + RenderTable *tbl = table(); + + // check if we need to do anything at all... + int os = kMax(tbl->currentBorderStyle() ? (tbl->currentBorderStyle()->border->width+1)/2 : 0, 2*maximalOutlineSize(pI.phase)); + if (!overhangingContents() && ((_ty >= pI.r.y() + pI.r.height() + os) + || (_ty + _topExtra + m_height + _bottomExtra <= pI.r.y() - os))) return; + + if (pI.phase == PaintActionOutline) { + paintOutline( pI.p, _tx, _ty, width(), height() + borderTopExtra() + borderBottomExtra(), style()); + } + + if (pI.phase == PaintActionCollapsedTableBorders && style()->visibility() == VISIBLE) { + int w = width(); + int h = height() + borderTopExtra() + borderBottomExtra(); + paintCollapsedBorder(pI.p, _tx, _ty, w, h); + } + else + RenderBlock::paintObject(pI, _tx, _ty + _topExtra, false); + +#ifdef BOX_DEBUG + ::outlineBox( p, _tx, _ty - _topExtra, width(), height() + borderTopExtra() + borderBottomExtra()); +#endif +} + +static EBorderStyle collapsedBorderStyle(EBorderStyle style) +{ + if (style == OUTSET) + style = GROOVE; + else if (style == INSET) + style = RIDGE; + return style; +} + +struct CollapsedBorder { + CollapsedBorder(){} + + CollapsedBorderValue border; + RenderObject::BorderSide side; + bool shouldPaint; + int x1; + int y1; + int x2; + int y2; + EBorderStyle style; +}; + +class CollapsedBorders +{ +public: + CollapsedBorders() :count(0) {} + + void addBorder(const CollapsedBorderValue& b, RenderObject::BorderSide s, bool paint, + int _x1, int _y1, int _x2, int _y2, + EBorderStyle _style) + { + if (b.exists() && paint) { + borders[count].border = b; + borders[count].side = s; + borders[count].shouldPaint = paint; + borders[count].x1 = _x1; + borders[count].x2 = _x2; + borders[count].y1 = _y1; + borders[count].y2 = _y2; + borders[count].style = _style; + count++; + } + } + + CollapsedBorder* nextBorder() { + for (int i = 0; i < count; i++) { + if (borders[i].border.exists() && borders[i].shouldPaint) { + borders[i].shouldPaint = false; + return &borders[i]; + } + } + + return 0; + } + + CollapsedBorder borders[4]; + int count; +}; + +static void addBorderStyle(QValueList<CollapsedBorderValue>& borderStyles, CollapsedBorderValue borderValue) +{ + if (!borderValue.exists() || borderStyles.contains(borderValue)) + return; + + QValueListIterator<CollapsedBorderValue> it = borderStyles.begin(); + QValueListIterator<CollapsedBorderValue> end = borderStyles.end(); + for (; it != end; ++it) { + CollapsedBorderValue result = compareBorders(*it, borderValue); + if (result == *it) { + borderStyles.insert(it, borderValue); + return; + } + } + + borderStyles.append(borderValue); +} + +void RenderTableCell::collectBorders(QValueList<CollapsedBorderValue>& borderStyles) +{ + addBorderStyle(borderStyles, collapsedLeftBorder()); + addBorderStyle(borderStyles, collapsedRightBorder()); + addBorderStyle(borderStyles, collapsedTopBorder()); + addBorderStyle(borderStyles, collapsedBottomBorder()); +} + +void RenderTableCell::paintCollapsedBorder(QPainter* p, int _tx, int _ty, int w, int h) +{ + if (!table()->currentBorderStyle()) + return; + + CollapsedBorderValue leftVal = collapsedLeftBorder(); + CollapsedBorderValue rightVal = collapsedRightBorder(); + CollapsedBorderValue topVal = collapsedTopBorder(); + CollapsedBorderValue bottomVal = collapsedBottomBorder(); + + // Adjust our x/y/width/height so that we paint the collapsed borders at the correct location. + int topWidth = topVal.width(); + int bottomWidth = bottomVal.width(); + int leftWidth = leftVal.width(); + int rightWidth = rightVal.width(); + + _tx -= leftWidth/2; + _ty -= topWidth/2; + w += leftWidth/2 + (rightWidth+1)/2; + h += topWidth/2 + (bottomWidth+1)/2; + + bool tt = topVal.isTransparent(); + bool bt = bottomVal.isTransparent(); + bool rt = rightVal.isTransparent(); + bool lt = leftVal.isTransparent(); + + EBorderStyle ts = collapsedBorderStyle(topVal.style()); + EBorderStyle bs = collapsedBorderStyle(bottomVal.style()); + EBorderStyle ls = collapsedBorderStyle(leftVal.style()); + EBorderStyle rs = collapsedBorderStyle(rightVal.style()); + + bool render_t = ts > BHIDDEN && !tt && (topVal.precedence != BCELL || *topVal.border == style()->borderTop()); + bool render_l = ls > BHIDDEN && !lt && (leftVal.precedence != BCELL || *leftVal.border == style()->borderLeft()); + bool render_r = rs > BHIDDEN && !rt && (rightVal.precedence != BCELL || *rightVal.border == style()->borderRight()); + bool render_b = bs > BHIDDEN && !bt && (bottomVal.precedence != BCELL || *bottomVal.border == style()->borderBottom()); + + // We never paint diagonals at the joins. We simply let the border with the highest + // precedence paint on top of borders with lower precedence. + CollapsedBorders borders; + borders.addBorder(topVal, BSTop, render_t, _tx, _ty, _tx + w, _ty + topWidth, ts); + borders.addBorder(bottomVal, BSBottom, render_b, _tx, _ty + h - bottomWidth, _tx + w, _ty + h, bs); + borders.addBorder(leftVal, BSLeft, render_l, _tx, _ty, _tx + leftWidth, _ty + h, ls); + borders.addBorder(rightVal, BSRight, render_r, _tx + w - rightWidth, _ty, _tx + w, _ty + h, rs); + + for (CollapsedBorder* border = borders.nextBorder(); border; border = borders.nextBorder()) { + if (border->border == *table()->currentBorderStyle()) + drawBorder(p, border->x1, border->y1, border->x2, border->y2, border->side, + border->border.color(), style()->color(), border->style, 0, 0); + } +} + +void RenderTableCell::paintBackgroundsBehindCell(PaintInfo& pI, int _tx, int _ty, RenderObject* backgroundObject) +{ + if (!backgroundObject) + return; + + RenderTable* tableElt = table(); + if (backgroundObject != this) { + _tx += m_x; + _ty += m_y + _topExtra; + } + + int w = width(); + int h = height() + borderTopExtra() + borderBottomExtra(); + _ty -= borderTopExtra(); + + int my = kMax(_ty,pI.r.y()); + int end = kMin( pI.r.y() + pI.r.height(), _ty + h ); + int mh = end - my; + + QColor c = backgroundObject->style()->backgroundColor(); + const BackgroundLayer* bgLayer = backgroundObject->style()->backgroundLayers(); + + if (bgLayer->hasImage() || c.isValid()) { + // We have to clip here because the background would paint + // on top of the borders otherwise. This only matters for cells and rows. + bool hasLayer = backgroundObject->layer() && (backgroundObject == this || backgroundObject == parent()); + if (hasLayer && tableElt->collapseBorders()) { + pI.p->save(); + QRect clipRect(_tx + borderLeft(), _ty + borderTop(), w - borderLeft() - borderRight(), h - borderTop() - borderBottom()); + clipRect = pI.p->xForm(clipRect); + QRegion creg(clipRect); + QRegion old = pI.p->clipRegion(); + if (!old.isNull()) + creg = old.intersect(creg); + pI.p->setClipRegion(creg); + } + paintBackground(pI.p, c, bgLayer, my, mh, _tx, _ty, w, h); + if (hasLayer && tableElt->collapseBorders()) + pI.p->restore(); + } +} + +void RenderTableCell::paintBoxDecorations(PaintInfo& pI, int _tx, int _ty) +{ + RenderTable* tableElt = table(); + bool drawBorders = true; + // Moz paints bgcolor/bgimage on <td>s in quirks mode even if + // empty-cells are on. Fixes regression on #43426, attachment #354 + if (!tableElt->collapseBorders() && style()->emptyCells() == HIDE && !firstChild()) + drawBorders = false; + if (!style()->htmlHacks() && !drawBorders) return; + + // Paint our cell background. + paintBackgroundsBehindCell(pI, _tx, _ty, this); + + int w = width(); + int h = height() + borderTopExtra() + borderBottomExtra(); + _ty -= borderTopExtra(); + + if (drawBorders && style()->hasBorder() && !tableElt->collapseBorders()) + paintBorder(pI.p, _tx, _ty, w, h, style()); +} + + +#ifdef ENABLE_DUMP +void RenderTableCell::dump(QTextStream &stream, const QString &ind) const +{ + RenderFlow::dump(stream,ind); + stream << " row=" << _row; + stream << " col=" << _col; + stream << " rSpan=" << rSpan; + stream << " cSpan=" << cSpan; +// *stream << " nWrap=" << nWrap; +} +#endif + +// ------------------------------------------------------------------------- + +RenderTableCol::RenderTableCol(DOM::NodeImpl* node) + : RenderContainer(node), m_span(1) +{ + // init RenderObject attributes + setInline(true); // our object is not Inline + updateFromElement(); +} + +void RenderTableCol::updateFromElement() +{ + DOM::NodeImpl *node = element(); + if ( node && (node->id() == ID_COL || node->id() == ID_COLGROUP) ) { + DOM::HTMLTableColElementImpl *tc = static_cast<DOM::HTMLTableColElementImpl *>(node); + m_span = tc->span(); + } else + m_span = ! ( style() && style()->display() == TABLE_COLUMN_GROUP ); +} + +#ifdef ENABLE_DUMP +void RenderTableCol::dump(QTextStream &stream, const QString &ind) const +{ + RenderContainer::dump(stream,ind); + stream << " _span=" << m_span; +} +#endif + +// ------------------------------------------------------------------------- + +TableSectionIterator::TableSectionIterator(RenderTable *table, bool fromEnd) +{ + if (fromEnd) { + sec = table->foot; + if (sec) return; + + sec = static_cast<RenderTableSection *>(table->lastChild()); + while (sec && (!sec->isTableSection() + || sec == table->head || sec == table->foot)) + sec = static_cast<RenderTableSection *>(sec->previousSibling()); + if (sec) return; + + sec = table->head; + } else { + sec = table->head; + if (sec) return; + + sec = static_cast<RenderTableSection *>(table->firstChild()); + while (sec && (!sec->isTableSection() + || sec == table->head || sec == table->foot)) + sec = static_cast<RenderTableSection *>(sec->nextSibling()); + if (sec) return; + + sec = table->foot; + }/*end if*/ + +} + +TableSectionIterator &TableSectionIterator::operator ++() +{ + RenderTable *table = sec->table(); + if (sec == table->head) { + + sec = static_cast<RenderTableSection *>(table->firstChild()); + while (sec && (!sec->isTableSection() + || sec == table->head || sec == table->foot)) + sec = static_cast<RenderTableSection *>(sec->nextSibling()); + if (sec) return *this; + + } else if (sec == table->foot) { + sec = 0; + return *this; + + } else { + + do { + sec = static_cast<RenderTableSection *>(sec->nextSibling()); + } while (sec && (!sec->isTableSection() || sec == table->head || sec == table->foot)); + if (sec) return *this; + + }/*end if*/ + + sec = table->foot; + return *this; +} + +TableSectionIterator &TableSectionIterator::operator --() +{ + RenderTable *table = sec->table(); + if (sec == table->foot) { + + sec = static_cast<RenderTableSection *>(table->lastChild()); + while (sec && (!sec->isTableSection() + || sec == table->head || sec == table->foot)) + sec = static_cast<RenderTableSection *>(sec->previousSibling()); + if (sec) return *this; + + } else if (sec == table->head) { + sec = 0; + return *this; + + } else { + + do { + sec = static_cast<RenderTableSection *>(sec->previousSibling()); + } while (sec && (!sec->isTableSection() || sec == table->head || sec == table->foot)); + if (sec) return *this; + + }/*end if*/ + + sec = table->foot; + return *this; +} + +#undef TABLE_DEBUG +#undef DEBUG_LAYOUT +#undef BOX_DEBUG diff --git a/khtml/rendering/render_table.h b/khtml/rendering/render_table.h new file mode 100644 index 000000000..979b92187 --- /dev/null +++ b/khtml/rendering/render_table.h @@ -0,0 +1,524 @@ +/* + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 1997 Martin Jones (mjones@kde.org) + * (C) 1997 Torben Weis (weis@kde.org) + * (C) 1998 Waldo Bastian (bastian@kde.org) + * (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 1999 Antti Koivisto (koivisto@kde.org) + * (C) 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. + * + */ +#ifndef RENDER_TABLE_H +#define RENDER_TABLE_H + +#include <qcolor.h> +#include <qptrvector.h> + +#include "rendering/render_box.h" +#include "rendering/render_block.h" +#include "rendering/render_style.h" + +#include "misc/khtmllayout.h" + +namespace DOM { + class DOMString; +} + +namespace khtml { + +class RenderTable; +class RenderTableSection; +class RenderTableRow; +class RenderTableCell; +class RenderTableCol; +class TableLayout; + +class RenderTable : public RenderBlock +{ +public: + + RenderTable(DOM::NodeImpl* node); + ~RenderTable(); + + virtual const char *renderName() const { return "RenderTable"; } + + virtual void setStyle(RenderStyle *style); + + virtual bool isTable() const { return true; } + + int getColumnPos(int col) const + { return columnPos[col]; } + + int borderHSpacing() const { return hspacing; } + int borderVSpacing() const { return vspacing; } + + bool collapseBorders() const { return style()->borderCollapse(); } + int borderLeft() const; + int borderRight() const; + int borderTop() const; + int borderBottom() const; + int paddingLeft() const { return collapseBorders() ? 0 : RenderBlock::paddingLeft(); } + int paddingRight() const { return collapseBorders() ? 0 : RenderBlock::paddingRight(); } + int paddingTop() const { return collapseBorders() ? 0 : RenderBlock::paddingTop(); } + int paddingBottom() const { return collapseBorders() ? 0 : RenderBlock::paddingBottom(); } + + const QColor &bgColor() const { return style()->backgroundColor(); } + + uint cellPadding() const { return padding; } + void setCellPadding( uint p ) { padding = p; } + + // overrides + virtual void addChild(RenderObject *child, RenderObject *beforeChild = 0); + virtual void paint( PaintInfo&, int tx, int ty); + virtual void paintBoxDecorations(PaintInfo&, int _tx, int _ty); + virtual void layout(); + virtual void calcMinMaxWidth(); + virtual void close(); + + virtual short lineHeight(bool b) const; + virtual short baselinePosition(bool b) const; + + virtual void setCellWidths( ); + + virtual void calcWidth(); + + virtual FindSelectionResult checkSelectionPoint( int _x, int _y, int _tx, int _ty, + DOM::NodeImpl*& node, int & offset, + SelPointState & ); + +#ifdef ENABLE_DUMP + virtual void dump(QTextStream &stream, const QString &ind) const; +#endif + struct ColumnStruct { + enum { + WidthUndefined = 0xffff + }; + ColumnStruct() { + span = 1; + width = WidthUndefined; + } + ushort span; + ushort width; // the calculated position of the column + }; + + QMemArray<int> columnPos; + QMemArray<ColumnStruct> columns; + + void splitColumn( int pos, int firstSpan ); + void appendColumn( int span ); + int numEffCols() const { return columns.size(); } + int spanOfEffCol( int effCol ) const { return columns[effCol].span; } + int colToEffCol( int col ) const { + int c = 0; + int i = 0; + while ( c < col && i < (int)columns.size() ) { + c += columns[i].span; + i++; + } + return i; + } + int effColToCol( int effCol ) const { + int c = 0; + for ( int i = 0; i < effCol; i++ ) + c += columns[i].span; + return c; + } + + int bordersPaddingAndSpacing() const { + return borderLeft() + borderRight() + + (collapseBorders() ? 0 : (paddingLeft() + paddingRight() + (numEffCols()+1) * borderHSpacing())); + } + + RenderTableCol *colElement( int col ); + + void setNeedSectionRecalc() { needSectionRecalc = true; } + + virtual RenderObject* removeChildNode(RenderObject* child); + + RenderTableSection* sectionAbove(const RenderTableSection*, bool skipEmptySections = false) const; + RenderTableSection* sectionBelow(const RenderTableSection*, bool skipEmptySections = false) const; + + RenderTableCell* cellAbove(const RenderTableCell* cell) const; + RenderTableCell* cellBelow(const RenderTableCell* cell) const; + RenderTableCell* cellLeft(const RenderTableCell* cell) const; + RenderTableCell* cellRight(const RenderTableCell* cell) const; + + CollapsedBorderValue* currentBorderStyle() { return m_currentBorder; } + + RenderTableSection *firstBodySection() const { return firstBody; } + RenderFlow* caption() const { return tCaption; } + +protected: + + void recalcSections(); + + friend class AutoTableLayout; + friend class FixedTableLayout; + + RenderFlow *tCaption; + RenderTableSection *head; + RenderTableSection *foot; + RenderTableSection *firstBody; + + TableLayout *tableLayout; + + CollapsedBorderValue* m_currentBorder; + + bool has_col_elems : 1; + uint needSectionRecalc : 1; + uint padding : 22; + + ushort hspacing; + ushort vspacing; + + friend class TableSectionIterator; +}; + +// ------------------------------------------------------------------------- + +class RenderTableSection : public RenderBox +{ +public: + RenderTableSection(DOM::NodeImpl* node); + ~RenderTableSection(); + virtual void detach(); + + virtual void setStyle(RenderStyle *style); + + virtual const char *renderName() const { return "RenderTableSection"; } + + // overrides + virtual void addChild(RenderObject *child, RenderObject *beforeChild = 0); + virtual bool isTableSection() const { return true; } + + virtual short lineHeight(bool) const { return 0; } + virtual void position(InlineBox*, int, int, bool) {} + + virtual short width() const; + + virtual FindSelectionResult checkSelectionPoint( int _x, int _y, int _tx, int _ty, + DOM::NodeImpl*& node, int & offset, + SelPointState & ); + +#ifdef ENABLE_DUMP + virtual void dump(QTextStream &stream, const QString &ind) const; +#endif + + void addCell( RenderTableCell *cell, RenderTableRow *row ); + + void setCellWidths(); + void calcRowHeight(); + int layoutRows( int height ); + + RenderTable *table() const { return static_cast<RenderTable *>(parent()); } + + typedef QMemArray<RenderTableCell *> Row; + struct RowStruct { + Row *row; + RenderTableRow* rowRenderer; + int baseLine; + Length height; + bool needFlex; + }; + + RenderTableCell *&cellAt( int row, int col ) { + return (*(grid[row].row))[col]; + } + RenderTableCell *cellAt( int row, int col ) const { + return (*(grid[row].row))[col]; + } + + virtual int lowestPosition(bool includeOverflowInterior, bool includeSelf) const; + virtual int rightmostPosition(bool includeOverflowInterior, bool includeSelf) const; + virtual int leftmostPosition(bool includeOverflowInterior, bool includeSelf) const; + virtual int highestPosition(bool includeOverflowInterior, bool includeSelf) const; + + int borderLeft() const { return table()->collapseBorders() ? 0 : RenderBox::borderLeft(); } + int borderRight() const { return table()->collapseBorders() ? 0 : RenderBox::borderRight(); } + int borderTop() const { return table()->collapseBorders() ? 0 : RenderBox::borderTop(); } + int borderBottom() const { return table()->collapseBorders() ? 0 : RenderBox::borderBottom(); } + + virtual void paint( PaintInfo& i, int tx, int ty); + + int numRows() const { return grid.size(); } + int getBaseline(int row) {return grid[row].baseLine;} + + void setNeedCellRecalc() { + needCellRecalc = true; + table()->setNeedSectionRecalc(); + } + + virtual RenderObject* removeChildNode(RenderObject* child); + + virtual bool canClear(RenderObject *child, PageBreakLevel level); + void addSpaceAt(int pos, int dy); + + virtual bool nodeAtPoint(NodeInfo& info, int x, int y, int tx, int ty, HitTestAction action, bool inside); + + // this gets a cell grid data structure. changing the number of + // columns is done by the table + QMemArray<RowStruct> grid; + QMemArray<int> rowPos; + + signed short cRow; + ushort cCol; + bool needCellRecalc; + + void recalcCells(); +protected: + void ensureRows( int numRows ); + void clearGrid(); + bool emptyRow(int rowNum); + bool flexCellChildren(RenderObject* p) const; + + + friend class TableSectionIterator; +}; + +// ------------------------------------------------------------------------- + +class RenderTableRow : public RenderContainer +{ +public: + RenderTableRow(DOM::NodeImpl* node); + + virtual void detach(); + + virtual void setStyle( RenderStyle* ); + virtual const char *renderName() const { return "RenderTableRow"; } + virtual bool isTableRow() const { return true; } + virtual void addChild(RenderObject *child, RenderObject *beforeChild = 0); + + virtual short offsetWidth() const; + virtual int offsetHeight() const; + virtual int offsetLeft() const; + virtual int offsetTop() const; + + virtual short lineHeight( bool ) const { return 0; } + virtual void position(InlineBox*, int, int, bool) {} + + virtual bool nodeAtPoint(NodeInfo& info, int x, int y, int tx, int ty, HitTestAction action, bool inside); + + virtual void layout(); + + virtual RenderObject* removeChildNode(RenderObject* child); + + // The only time rows get a layer is when they have transparency. + virtual bool requiresLayer() const { return /* style()->opacity() < 1.0f; */ false ; } + virtual void paint(PaintInfo& i, int tx, int ty); + + void paintRow( PaintInfo& i, int tx, int ty, int w, int h); + + RenderTable *table() const { return static_cast<RenderTable *>(parent()->parent()); } + RenderTableSection *section() const { return static_cast<RenderTableSection *>(parent()); } +}; + +// ------------------------------------------------------------------------- + +class RenderTableCell : public RenderBlock +{ +public: + RenderTableCell(DOM::NodeImpl* node); + + virtual void layout(); + virtual void detach(); + + virtual const char *renderName() const { return "RenderTableCell"; } + virtual bool isTableCell() const { return true; } + + // ### FIX these two... + long cellIndex() const { return 0; } + void setCellIndex( long ) { } + + unsigned short colSpan() const { return cSpan; } + void setColSpan( unsigned short c ) { cSpan = c; } + + unsigned short rowSpan() const { return rSpan; } + void setRowSpan( unsigned short r ) { rSpan = r; } + + int col() const { return _col; } + void setCol(int col) { _col = col; } + int row() const { return _row; } + void setRow(int r) { _row = r; } + + Length styleOrColWidth(); + + // overrides + virtual void calcMinMaxWidth(); + virtual void calcWidth(); + virtual void setWidth( int width ); + virtual void setStyle( RenderStyle *style ); + virtual bool requiresLayer() const; + + int borderLeft() const; + int borderRight() const; + int borderTop() const; + int borderBottom() const; + + CollapsedBorderValue collapsedLeftBorder() const; + CollapsedBorderValue collapsedRightBorder() const; + CollapsedBorderValue collapsedTopBorder() const; + CollapsedBorderValue collapsedBottomBorder() const; + virtual void collectBorders(QValueList<CollapsedBorderValue>& borderStyles); + + virtual void updateFromElement(); + + void setCellTopExtra(int p) { _topExtra = p; } + void setCellBottomExtra(int p) { _bottomExtra = p; } + int cellTopExtra() const { return _topExtra; } + int cellBottomExtra() const { return _bottomExtra; } + + int pageTopAfter(int x) const; + + virtual void paint( PaintInfo& i, int tx, int ty); + + void paintCollapsedBorder(QPainter* p, int x, int y, int w, int h); + void paintBackgroundsBehindCell(PaintInfo& i, int _tx, int _ty, RenderObject* backgroundObject); + + virtual void close(); + + // lie position to outside observers + virtual int yPos() const { return m_y + _topExtra; } + + virtual void repaintRectangle(int x, int y, int w, int h, Priority p=NormalPriority, bool f=false); + virtual bool absolutePosition(int &xPos, int &yPos, bool f = false) const; + + virtual short baselinePosition( bool = false ) const; + + virtual bool nodeAtPoint(NodeInfo& info, int _x, int _y, int _tx, int _ty, HitTestAction hitTestAction, bool inside); + + RenderTable *table() const { return static_cast<RenderTable *>(parent()->parent()->parent()); } + RenderTableSection *section() const { return static_cast<RenderTableSection *>(parent()->parent()); } + +#ifdef ENABLE_DUMP + virtual void dump(QTextStream &stream, const QString &ind) const; +#endif + + bool widthChanged() { + bool retval = m_widthChanged; + m_widthChanged = false; + return retval; + } + + int cellPercentageHeight() const + { return m_percentageHeight; } + void setCellPercentageHeight(int h) + { m_percentageHeight = h; } + bool hasFlexedAnonymous() const + { return m_hasFlexedAnonymous; } + void setHasFlexedAnonymous(bool b=true) + { m_hasFlexedAnonymous = b; } + +protected: + virtual void paintBoxDecorations(PaintInfo& p, int _tx, int _ty); + virtual int borderTopExtra() const { return _topExtra; } + virtual int borderBottomExtra() const { return _bottomExtra; } + + short _row; + short _col; + ushort rSpan; + ushort cSpan; + int _topExtra; + signed int _bottomExtra : 30; + bool m_widthChanged : 1; + bool m_hasFlexedAnonymous : 1; + int m_percentageHeight; +}; + + +// ------------------------------------------------------------------------- + +class RenderTableCol : public RenderContainer +{ +public: + RenderTableCol(DOM::NodeImpl* node); + + virtual const char *renderName() const { return "RenderTableCol"; } + + virtual bool isTableCol() const { return true; } + + virtual short lineHeight( bool ) const { return 0; } + virtual void position(InlineBox*, int, int, bool) {} + virtual void layout() {} + virtual bool requiresLayer() const { return false; } + + virtual void updateFromElement(); + +#ifdef ENABLE_DUMP + virtual void dump(QTextStream &stream, const QString& ind) const; +#endif + + int span() const { return m_span; } + void setSpan( int s ) { m_span = s; } + +private: + int m_span; +}; + +// ------------------------------------------------------------------------- + +/** This class provides an iterator to iterate through the sections of a + * render table in their visual order. + * + * In HTML, sections are specified in the order of THEAD, TFOOT, and TBODY. + * Visually, TBODY sections appear between THEAD and TFOOT, which this iterator + * takes into regard. + * @author Leo Savernik + * @internal + * @since 3.2 + */ +class TableSectionIterator { +public: + + /** + * Initializes a new iterator + * @param table table whose sections to iterate + * @param fromEnd @p true, begin with last section, @p false, begin with + * first section. + */ + TableSectionIterator(RenderTable *table, bool fromEnd = false); + + /** + * Initializes a new iterator + * @param start table section to start with. + */ + TableSectionIterator(RenderTableSection *start) : sec(start) {} + + /** + * Uninitialized iterator. + */ + TableSectionIterator() {} + + /** Returns the current section, or @p 0 if the end has been reached. + */ + RenderTableSection *operator *() const { return sec; } + + /** Moves to the next section in visual order. */ + TableSectionIterator &operator ++(); + + /** Moves to the previous section in visual order. */ + TableSectionIterator &operator --(); + +private: + RenderTableSection *sec; +}; + +} +#endif + diff --git a/khtml/rendering/render_text.cpp b/khtml/rendering/render_text.cpp new file mode 100644 index 000000000..a862c3ad5 --- /dev/null +++ b/khtml/rendering/render_text.cpp @@ -0,0 +1,1546 @@ +/** + * This file is part of the DOM implementation for KDE. + * + * Copyright (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 2000-2003 Dirk Mueller (mueller@kde.org) + * (C) 2003 Apple Computer, Inc. + * (C) 2004-2005 Allan Sandfeld Jensen (kde@carewolf.com) + * + * 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 +//#define BIDI_DEBUG + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include "rendering/render_text.h" +#include "rendering/render_canvas.h" +#include "rendering/break_lines.h" +#include "rendering/render_arena.h" +#include "xml/dom_nodeimpl.h" + +#include "misc/loader.h" +#include "misc/helper.h" + +#include <qbitmap.h> +#include <qimage.h> +#include <qpainter.h> +#include <kdebug.h> +#include <kglobal.h> +#include <assert.h> +#include <limits.h> +#include <math.h> + +#ifdef HAVE_ALLOCA_H +// explicitly included for systems that don't provide it in stdlib.h +#include <alloca.h> +#else +#include <stdlib.h> +#endif + +using namespace khtml; +using namespace DOM; + +#ifndef NDEBUG +static bool inInlineTextBoxDetach; +#endif + +void InlineTextBox::detach(RenderArena* renderArena) +{ + if (m_parent) + m_parent->removeFromLine(this); + +#ifndef NDEBUG + inInlineTextBoxDetach = true; +#endif + delete this; +#ifndef NDEBUG + inInlineTextBoxDetach = false; +#endif + + // Recover the size left there for us by operator delete and free the memory. + renderArena->free(*(size_t *)this, this); +} + +void* InlineTextBox::operator new(size_t sz, RenderArena* renderArena) throw() +{ + return renderArena->allocate(sz); +} + +void InlineTextBox::operator delete(void* ptr, size_t sz) +{ + assert(inInlineTextBoxDetach); + + // Stash size where detach can find it. + *(size_t *)ptr = sz; +} + +void InlineTextBox::selectionStartEnd(int& sPos, int& ePos) +{ + int startPos, endPos; + if (object()->selectionState() == RenderObject::SelectionInside) { + startPos = 0; + endPos = renderText()->string()->l; + } else { + renderText()->selectionStartEnd(startPos, endPos); + if (object()->selectionState() == RenderObject::SelectionStart) + endPos = renderText()->string()->l; + else if (object()->selectionState() == RenderObject::SelectionEnd) + startPos = 0; + } + + sPos = kMax(startPos - m_start, 0); + ePos = kMin(endPos - m_start, (int)m_len); +} + +RenderObject::SelectionState InlineTextBox::selectionState() +{ + RenderObject::SelectionState state = object()->selectionState(); + if (state == RenderObject::SelectionStart || state == RenderObject::SelectionEnd || + state == RenderObject::SelectionBoth) { + int startPos, endPos; + renderText()->selectionStartEnd(startPos, endPos); + + bool start = (state != RenderObject::SelectionEnd && startPos >= m_start && startPos < m_start + m_len); + bool end = (state != RenderObject::SelectionStart && endPos > m_start && endPos <= m_start + m_len); + if (start && end) + state = RenderObject::SelectionBoth; + else if (start) + state = RenderObject::SelectionStart; + else if (end) + state = RenderObject::SelectionEnd; + else if ((state == RenderObject::SelectionEnd || startPos < m_start) && + (state == RenderObject::SelectionStart || endPos > m_start + m_len)) + state = RenderObject::SelectionInside; + } + return state; +} + +void InlineTextBox::paint(RenderObject::PaintInfo& i, int tx, int ty) +{ + if (object()->isBR() || object()->style()->visibility() != VISIBLE || + m_truncation == cFullTruncation || i.phase == PaintActionOutline) + return; + + if (i.phase == PaintActionSelection && object()->selectionState() == RenderObject::SelectionNone) + // When only painting the selection, don't bother to paint if there is none. + return; + + int xPos = tx + m_x; + int w = width(); + if ((xPos >= i.r.x() + i.r.width()) || (xPos + w <= i.r.x())) + return; + + // Set our font. + RenderStyle* styleToUse = object()->style(m_firstLine); + int d = styleToUse->textDecorationsInEffect(); + if (styleToUse->font() != i.p->font()) + i.p->setFont(styleToUse->font()); + const Font *font = &styleToUse->htmlFont(); + bool haveSelection = selectionState() != RenderObject::SelectionNone; + + // Now calculate startPos and endPos, for painting selection. + // We paint selection while endPos > 0 + int ePos = 0, sPos = 0; + if (haveSelection && !object()->canvas()->staticMode()) { + selectionStartEnd(sPos, ePos); + } + if (styleToUse->color() != i.p->pen().color()) + i.p->setPen(styleToUse->color()); + + if (m_len > 0 && i.phase != PaintActionSelection) { + int endPoint = m_len; + if (m_truncation != cNoTruncation) + endPoint = m_truncation - m_start; + if (styleToUse->textShadow()) + paintShadow(i.p, font, tx, ty, styleToUse->textShadow()); + if (!haveSelection || sPos != 0 || ePos != m_len) { + font->drawText(i.p, m_x + tx, m_y + ty + m_baseline, renderText()->string()->s, renderText()->string()->l, m_start, endPoint, + m_toAdd, m_reversed ? QPainter::RTL : QPainter::LTR); + } + } + + if (d != TDNONE && i.phase != PaintActionSelection && styleToUse->htmlHacks()) { + i.p->setPen(styleToUse->color()); + paintDecoration(i.p, font, tx, ty, d); + } + + if (haveSelection && i.phase == PaintActionSelection) { + //kdDebug(6040) << this << " paintSelection with startPos=" << sPos << " endPos=" << ePos << endl; + if ( sPos < ePos ) + paintSelection(font, renderText(), i.p, styleToUse, tx, ty, sPos, ePos, d); + } +} + +/** returns the proper ::selection pseudo style for the given element + * @return the style or 0 if no ::selection pseudo applies. + */ +inline const RenderStyle *retrieveSelectionPseudoStyle(const RenderObject *obj) +{ + // http://www.w3.org/Style/CSS/Test/CSS3/Selectors/20021129/html/tests/css3-modsel-162.html + // is of the opinion that ::selection of parent elements is also to be applied + // to children, so let's do it. + while (obj) { + const RenderStyle *style = obj->style()->getPseudoStyle(RenderStyle::SELECTION); + if (style) return style; + + obj = obj->parent(); + }/*wend*/ + return 0; +} + +void InlineTextBox::paintSelection(const Font *f, RenderText *text, QPainter *p, RenderStyle* style, int tx, int ty, int startPos, int endPos, int deco) +{ + if(startPos > m_len) return; + if(startPos < 0) startPos = 0; + + QColor hc; + QColor hbg; + const RenderStyle* pseudoStyle = retrieveSelectionPseudoStyle(text); + if (pseudoStyle) { + // ### support outline (mandated by CSS3) + // ### support background-image? (optional by CSS3) + if (pseudoStyle->backgroundColor().isValid()) + hbg = pseudoStyle->backgroundColor(); + hc = pseudoStyle->color(); + } else { + const QColorGroup &grp = style->palette().active(); + hc = grp.highlightedText(); + hbg = grp.highlight(); + // ### should be at most retrieved once per render text + QColor bg = khtml::retrieveBackgroundColor(text); + // It may happen that the contrast is -- well -- virtually non existent. + // In this case, simply swap the colors, thus in compliance with + // NN4 (win32 only), IE, and Mozilla. + if (!khtml::hasSufficientContrast(hbg, bg)) + qSwap(hc, hbg); + } + + p->setPen(hc); + + //kdDebug( 6040 ) << "textRun::painting(" << QConstString(text->str->s + m_start, m_len).string().left(30) << ") at(" << m_x+tx << "/" << m_y+ty << ")" << endl; + + const bool needClipping = startPos != 0 || endPos != m_len; + + if (needClipping) { + p->save(); + + int visualSelectionStart = f->width(text->str->s, text->str->l, m_start, startPos, m_start, m_start + m_len, m_toAdd); + int visualSelectionEnd = f->width(text->str->s, text->str->l, m_start, endPos, m_start, m_start + m_len, m_toAdd); + int visualSelectionWidth = visualSelectionEnd - visualSelectionStart; + if (m_reversed) { + visualSelectionStart = f->width(text->str->s, text->str->l, m_start, m_len) - visualSelectionEnd; + } + + QRect selectionRect(m_x + tx + visualSelectionStart, m_y + ty, visualSelectionWidth, height()); + QRegion r(selectionRect); + if (p->hasClipping()) + r &= p->clipRegion(QPainter::CoordPainter); + p->setClipRegion(r, QPainter::CoordPainter); + } + + f->drawText(p, m_x + tx, m_y + ty + m_baseline, text->str->s, text->str->l, + m_start, m_len, m_toAdd, + m_reversed ? QPainter::RTL : QPainter::LTR, + needClipping ? 0 : startPos, needClipping ? m_len : endPos, + hbg, m_y + ty, height(), deco); + + if (needClipping) p->restore(); +} + +void InlineTextBox::paintDecoration( QPainter *pt, const Font *f, int _tx, int _ty, int deco) +{ + _tx += m_x; + _ty += m_y; + + if (m_truncation == cFullTruncation) + return; + + int width = m_width - 1; + if (m_truncation != cNoTruncation) { + width = static_cast<RenderText*>(m_object)->width(m_start, m_truncation - m_start, m_firstLine); + } + + RenderObject *p = object(); + + QColor underline, overline, linethrough; + p->getTextDecorationColors(deco, underline, overline, linethrough, p->style()->htmlHacks()); + + if(deco & UNDERLINE){ + pt->setPen(underline); + f->drawDecoration(pt, _tx, _ty, baseline(), width, height(), Font::UNDERLINE); + } + if (deco & OVERLINE) { + pt->setPen(overline); + f->drawDecoration(pt, _tx, _ty, baseline(), width, height(), Font::OVERLINE); + } + if(deco & LINE_THROUGH) { + pt->setPen(linethrough); + f->drawDecoration(pt, _tx, _ty, baseline(), width, height(), Font::LINE_THROUGH); + } + // NO! Do NOT add BLINK! It is the most annouing feature of Netscape, and IE has a reason not to + // support it. Lars +} + +void InlineTextBox::paintShadow(QPainter *pt, const Font *f, int _tx, int _ty, const ShadowData *shadow ) +{ + int x = m_x + _tx + shadow->x; + int y = m_y + _ty + shadow->y; + const RenderText* text = renderText(); + + if (shadow->blur <= 0) { + QColor c = pt->pen().color(); + pt->setPen(shadow->color); + f->drawText(pt, x, y+m_baseline, text->str->s, text->str->l, + m_start, m_len, m_toAdd, + m_reversed ? QPainter::RTL : QPainter::LTR); + pt->setPen(c); + + } + else { + const int thickness = shadow->blur; + const int w = m_width+2*thickness; + const int h = m_height+2*thickness; + const QRgb color = shadow->color.rgb(); + const int gray = qGray(color); + const bool inverse = (gray < 100); + const QRgb bgColor = (inverse) ? qRgb(255,255,255) : qRgb(0,0,0); + QPixmap pixmap(w, h); + pixmap.fill(bgColor); + QPainter p; + + p.begin(&pixmap); + p.setPen(shadow->color); + p.setFont(pt->font()); + f->drawText(&p, thickness, thickness+m_baseline, text->str->s, text->str->l, + m_start, m_len, m_toAdd, + m_reversed ? QPainter::RTL : QPainter::LTR); + + p.end(); + QImage img = pixmap.convertToImage().convertDepth(32); + + int md = thickness*thickness; // max-dist^2 + + // blur map (division cache) + float *bmap = (float*)alloca(sizeof(float)*(md+1)); + for(int n=0; n<=md; n++) { + float f; + f = n/(float)(md+1); + f = 1.0 - f*f; + bmap[n] = f; + } + + float factor = 0.0; // maximal potential opacity-sum + for(int n=-thickness; n<=thickness; n++) + for(int m=-thickness; m<=thickness; m++) { + int d = n*n+m*m; + if (d<=md) + factor += bmap[d]; + } + + // arbitratry factor adjustment to make shadows solid. + factor = factor/1.333; + + // alpha map + float* amap = (float*)alloca(sizeof(float)*(h*w)); + memset(amap, 0, h*w*(sizeof(float))); + for(int j=thickness; j<h-thickness; j++) { + for(int i=thickness; i<w-thickness; i++) { + QRgb col= img.pixel(i,j); + if (col == bgColor) continue; + float g = qGray(col); + if (inverse) + g = (255-g)/(255-gray); + else + g = g/gray; + for(int n=-thickness; n<=thickness; n++) { + for(int m=-thickness; m<=thickness; m++) { + int d = n*n+m*m; + if (d>md) continue; + float f = bmap[d]; + amap[(i+m)+(j+n)*w] += (g*f); + } + } + } + } + + QImage res(w,h,32); + res.setAlphaBuffer(true); + int r = qRed(color); + int g = qGreen(color); + int b = qBlue(color); + + // divide by factor + factor = 1.0/factor; + + for(int j=0; j<h; j++) { + for(int i=0; i<w; i++) { + int a = (int)(amap[i+j*w] * factor * 255.0); + if (a > 255) a = 255; + res.setPixel(i,j, qRgba(r,g,b,a)); + } + } + + pt->drawImage(x-thickness, y-thickness, res, 0, 0, -1, -1, Qt::DiffuseAlphaDither | Qt::ColorOnly | Qt::PreferDither); + } + // Paint next shadow effect + if (shadow->next) paintShadow(pt, f, _tx, _ty, shadow->next); +} + +/** + * Distributes pixels to justify text. + * @param numSpaces spaces left, will be decremented by one + * @param toAdd number of pixels left to be distributed, will have the + * amount of pixels distributed during this call subtracted. + * @return number of pixels to distribute + */ +static inline int justifyWidth(int &numSpaces, int &toAdd) { + int a = 0; + if ( numSpaces ) { + a = toAdd/numSpaces; + toAdd -= a; + numSpaces--; + }/*end if*/ + return a; +} + +FindSelectionResult InlineTextBox::checkSelectionPoint(int _x, int _y, int _tx, int _ty, const Font *f, RenderText *text, int & offset, short lineHeight) +{ +// kdDebug(6040) << "InlineTextBox::checkSelectionPoint " << this << " _x=" << _x << " _y=" << _y +// << " _tx+m_x=" << _tx+m_x << " _ty+m_y=" << _ty+m_y << endl; + offset = 0; + + if ( _y < _ty + m_y ) + return SelectionPointBefore; // above -> before + + if ( _y > _ty + m_y + lineHeight ) { + // below -> after + // Set the offset to the max + offset = m_len; + return SelectionPointAfter; + } + if ( _x > _tx + m_x + m_width ) { + // to the right + return SelectionPointAfterInLine; + } + + // The Y matches, check if we're on the left + if ( _x < _tx + m_x ) { + return SelectionPointBeforeInLine; + } + + // consider spacing for justified text + int toAdd = m_toAdd; + bool justified = text->style()->textAlign() == JUSTIFY && toAdd > 0; + int numSpaces = 0; + if (justified) { + + for( int i = 0; i < m_len; i++ ) + if ( text->str->s[m_start+i].category() == QChar::Separator_Space ) + numSpaces++; + + }/*end if*/ + + int delta = _x - (_tx + m_x); + //kdDebug(6040) << "InlineTextBox::checkSelectionPoint delta=" << delta << endl; + int pos = 0; + if ( m_reversed ) { + delta -= m_width; + while(pos < m_len) { + int w = f->width( text->str->s, text->str->l, m_start + pos); + if (justified && text->str->s[m_start + pos].category() == QChar::Separator_Space) + w += justifyWidth(numSpaces, toAdd); + int w2 = w/2; + w -= w2; + delta += w2; + if(delta >= 0) break; + pos++; + delta += w; + } + } else { + while(pos < m_len) { + int w = f->width( text->str->s, text->str->l, m_start + pos); + if (justified && text->str->s[m_start + pos].category() == QChar::Separator_Space) + w += justifyWidth(numSpaces, toAdd); + int w2 = w/2; + w -= w2; + delta -= w2; + if(delta <= 0) break; + pos++; + delta -= w; + } + } +// kdDebug( 6040 ) << " Text --> inside at position " << pos << endl; + offset = pos; + return SelectionPointInside; +} + +int InlineTextBox::offsetForPoint(int _x, int &ax) const +{ + // Do binary search for finding out offset, saves some time for long + // runs. + int start = 0; + int end = m_len; + ax = m_x; + int offset = (start + end) / 2; + while (end - start > 0) { + // always snap to the right column. This makes up for "jumpy" vertical + // navigation. + if (end - start == 1) start = end; + + offset = (start + end) / 2; + ax = m_x + widthFromStart(offset); + if (ax > _x) end = offset; + else if (ax < _x) start = offset; + else break; + } + return m_start + offset; +} + +int InlineTextBox::widthFromStart(int pos) const +{ + // gasp! sometimes pos is i < 0 which crashes Font::width + pos = kMax(pos, 0); + + const RenderText *t = renderText(); + Q_ASSERT(t->isText()); + const Font *f = t->htmlFont(m_firstLine); + const QFontMetrics &fm = t->fontMetrics(m_firstLine); + + int numSpaces = 0; + // consider spacing for justified text + bool justified = t->style()->textAlign() == JUSTIFY; + //kdDebug(6000) << "InlineTextBox::width(int)" << endl; + if (justified && m_toAdd > 0) do { + //kdDebug(6000) << "justify" << endl; + +// QConstString cstr = QConstString(t->str->s + m_start, m_len); + for( int i = 0; i < m_len; i++ ) + if ( t->str->s[m_start+i].category() == QChar::Separator_Space ) + numSpaces++; + if (numSpaces == 0) break; + + int toAdd = m_toAdd; + int w = 0; // accumulated width + int start = 0; // start of non-space sequence + int current = 0; // current position + while (current < pos) { + // add spacing + while (current < pos && t->str->s[m_start + current].category() == QChar::Separator_Space) { + w += f->getWordSpacing(); + w += f->getLetterSpacing(); + w += justifyWidth(numSpaces, toAdd); + w += fm.width(' '); // ### valid assumption? (LS) + current++; start++; + }/*wend*/ + if (current >= pos) break; + + // seek next space + while (current < pos && t->str->s[m_start + current].category() != QChar::Separator_Space) + current++; + + // check run without spaces + if ( current > start ) { + w += f->width(t->str->s + m_start, m_len, start, current - start); + start = current; + } + } + + return w; + + } while(false);/*end if*/ + + //kdDebug(6000) << "default" << endl; + // else use existing width function + return f->width(t->str->s + m_start, m_len, 0, pos); + +} + +long InlineTextBox::minOffset() const +{ + return m_start; +} + +long InlineTextBox::maxOffset() const +{ + return m_start + m_len; +} + +int InlineTextBox::placeEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth, bool& foundBox) +{ + if (foundBox) { + m_truncation = cFullTruncation; + return -1; + } + + int ellipsisX = ltr ? blockEdge - ellipsisWidth : blockEdge + ellipsisWidth; + + // For LTR, if the left edge of the ellipsis is to the left of our text run, then we are the run that will get truncated. + if (ltr) { + if (ellipsisX <= m_x) { + // Too far. Just set full truncation, but return -1 and let the ellipsis just be placed at the edge of the box. + m_truncation = cFullTruncation; + foundBox = true; + return -1; + } + + if (ellipsisX < m_x + m_width) { + if (m_reversed) + return -1; // FIXME: Support LTR truncation when the last run is RTL someday. + + foundBox = true; + + int ax; + int offset = offsetForPoint(ellipsisX, ax) - 1; + if (offset <= m_start) { + // No characters should be rendered. Set ourselves to full truncation and place the ellipsis at the min of our start + // and the ellipsis edge. + m_truncation = cFullTruncation; + return kMin(ellipsisX, (int)m_x); + } + + // Set the truncation index on the text run. The ellipsis needs to be placed just after the last visible character. + m_truncation = offset; + return widthFromStart(offset - m_start); + } + } + else { + // FIXME: Support RTL truncation someday, including both modes (when the leftmost run on the line is either RTL or LTR) + } + return -1; +} + +// ----------------------------------------------------------------------------- + +InlineTextBoxArray::InlineTextBoxArray() +{ + setAutoDelete(false); +} + +int InlineTextBoxArray::compareItems( Item d1, Item d2 ) +{ + assert(d1); + assert(d2); + + return static_cast<InlineTextBox*>(d1)->m_y - static_cast<InlineTextBox*>(d2)->m_y; +} + +// remove this once QVector::bsearch is fixed +int InlineTextBoxArray::findFirstMatching(Item d) const +{ + int len = count(); + + if ( !len ) + return -1; + if ( !d ) + return -1; + int n1 = 0; + int n2 = len - 1; + int mid = 0; + bool found = false; + while ( n1 <= n2 ) { + int res; + mid = (n1 + n2)/2; + if ( (*this)[mid] == 0 ) // null item greater + res = -1; + else + res = ((QGVector*)this)->compareItems( d, (*this)[mid] ); + if ( res < 0 ) + n2 = mid - 1; + else if ( res > 0 ) + n1 = mid + 1; + else { // found it + found = true; + break; + } + } + /* if ( !found ) + return -1; */ + // search to first one equal or bigger + while ( found && (mid > 0) && !((QGVector*)this)->compareItems(d, (*this)[mid-1]) ) + mid--; + return mid; +} + +// ------------------------------------------------------------------------------------- + +RenderText::RenderText(DOM::NodeImpl* node, DOMStringImpl *_str) + : RenderObject(node) +{ + // init RenderObject attributes + setRenderText(); // our object inherits from RenderText + + m_minWidth = -1; + m_maxWidth = -1; + str = _str; + if(str) str->ref(); + KHTMLAssert(!str || !str->l || str->s); + + m_selectionState = SelectionNone; + m_hasReturn = true; + +#ifdef DEBUG_LAYOUT + QConstString cstr(str->s, str->l); + kdDebug( 6040 ) << "RenderText ctr( "<< cstr.string().length() << " ) '" << cstr.string() << "'" << endl; +#endif +} + +void RenderText::setStyle(RenderStyle *_style) +{ + if ( style() != _style ) { + bool changedText = ((!style() && ( _style->textTransform() != TTNONE || + !_style->preserveLF() || !_style->preserveWS() )) || + (style() && (style()->textTransform() != _style->textTransform() || + style()->whiteSpace() != _style->whiteSpace()))); + + RenderObject::setStyle( _style ); + m_lineHeight = RenderObject::lineHeight(false); + + if (!isBR() && changedText) { + DOM::DOMStringImpl* textToTransform = originalString(); + if (textToTransform) + setText(textToTransform, true); + } + } +} + +RenderText::~RenderText() +{ + KHTMLAssert(m_lines.count() == 0); + if(str) str->deref(); +} + +void RenderText::deleteInlineBoxes(RenderArena* arena) +{ + // this is a slight variant of QArray::clear(). + // We don't delete the array itself here because its + // likely to be used in the same size later again, saves + // us resize() calls + unsigned int len = m_lines.size(); + if (len) { + if (!arena) + arena = renderArena(); + for(unsigned int i=0; i < len; i++) { + InlineTextBox* s = m_lines.at(i); + if (s) + s->detach(arena); + m_lines.remove(i); + } + } + + KHTMLAssert(m_lines.count() == 0); +} + +bool RenderText::isTextFragment() const +{ + return false; +} + +DOM::DOMStringImpl* RenderText::originalString() const +{ + return element() ? element()->string() : 0; +} + +InlineTextBox * RenderText::findInlineTextBox( int offset, int &pos, bool checkFirstLetter ) +{ + // The text boxes point to parts of the rendertext's str string + // (they don't include '\n') + // Find the text box that includes the character at @p offset + // and return pos, which is the position of the char in the run. + + // FIXME: make this use binary search? Dirk says it won't work :-( (LS) + (void)checkFirstLetter; +#if 0 + if (checkFirstLetter && forcedMinOffset()) { +// kdDebug(6040) << "checkFirstLetter: forcedMinOffset: " << forcedMinOffset() << endl; + RenderFlow *firstLetter = static_cast<RenderFlow *>(previousSibling()); + if (firstLetter && firstLetter->isFlow() && firstLetter->isFirstLetter()) { + RenderText *letterText = static_cast<RenderText *>(firstLetter->firstChild()); + //kdDebug(6040) << "lettertext: " << letterText << " minOfs: " << letterText->minOffset() << " maxOfs: " << letterText->maxOffset() << endl; + if (offset >= letterText->minOffset() && offset <= letterText->maxOffset()) { + InlineTextBox *result = letterText->findInlineTextBox(offset, pos, false); + //kdDebug(6040) << "result: " << result << endl; + if (result) return result; + } + } + } +#endif + + if ( m_lines.isEmpty() ) + return 0L; + + // The text boxes don't resemble a contiguous coverage of the text, there + // may be holes. Therefore, we snap to the nearest previous text box if + // the given offset happens to point to such a hole. + + InlineTextBox* s = m_lines[0]; + uint count = m_lines.count(); + uint si = 0; + uint nearest_idx = 0; // index of nearest text box + int nearest = INT_MAX; // nearest distance +//kdDebug(6040) << "s[" << si << "] m_start " << s->m_start << " m_end " << (s->m_start + s->m_len) << endl; + while(!(offset >= s->m_start && offset <= s->m_start + s->m_len) + && ++si < count) + { + int dist = offset - (s->m_start + s->m_len); +//kdDebug(6040) << "dist " << dist << " nearest " << nearest << endl; + if (dist >= 0 && dist <= nearest) { + nearest = dist; + nearest_idx = si - 1; + }/*end if*/ + s = m_lines[si]; +//kdDebug(6040) << "s[" << si << "] m_start " << s->m_start << " m_end " << (s->m_start + s->m_len) << endl; + } +//kdDebug(6040) << "nearest_idx " << nearest_idx << " count " << count << endl; + if (si >= count) s = m_lines[nearest_idx]; + // we are now in the correct text box + pos = kMin(offset - s->m_start, int( s->m_len )); + //kdDebug(6040) << "offset=" << offset << " s->m_start=" << s->m_start << endl; + return s; +} + +bool RenderText::nodeAtPoint(NodeInfo& info, int _x, int _y, int _tx, int _ty, HitTestAction /*hitTestAction*/, bool /*inBox*/) +{ + assert(parent()); + + bool inside = false; + if (style()->visibility() != HIDDEN) { + InlineTextBox *s = m_lines.count() ? m_lines[0] : 0; + int si = 0; + while(s) { + if((_y >=_ty + s->m_y) && (_y < _ty + s->m_y + s->m_height) && + (_x >= _tx + s->m_x) && (_x <_tx + s->m_x + s->m_width) ) { + inside = true; + break; + } + + s = si < (int) m_lines.count()-1 ? m_lines[++si] : 0; + } + } + + // #### ported over from Safari. Can this happen at all? (lars) + + if (inside && element()) { + if (info.innerNode() && info.innerNode()->renderer() && + !info.innerNode()->renderer()->isInline()) { + // Within the same layer, inlines are ALWAYS fully above blocks. Change inner node. + info.setInnerNode(element()); + + // Clear everything else. + info.setInnerNonSharedNode(0); + info.setURLElement(0); + } + + if (!info.innerNode()) + info.setInnerNode(element()); + + if(!info.innerNonSharedNode()) + info.setInnerNonSharedNode(element()); + } + + return inside; +} + +FindSelectionResult RenderText::checkSelectionPoint(int _x, int _y, int _tx, int _ty, DOM::NodeImpl*& node, int &offset, SelPointState &) +{ +// kdDebug(6040) << "RenderText::checkSelectionPoint " << this << " _x=" << _x << " _y=" << _y +// << " _tx=" << _tx << " _ty=" << _ty << endl; +//kdDebug(6040) << renderName() << "::checkSelectionPoint x=" << xPos() << " y=" << yPos() << " w=" << width() << " h=" << height() << " m_lines.count=" << m_lines.count() << endl; + + NodeImpl *lastNode = 0; + int lastOffset = 0; + FindSelectionResult lastResult = SelectionPointAfter; + + for(unsigned int si = 0; si < m_lines.count(); si++) + { + InlineTextBox* s = m_lines[si]; + FindSelectionResult result; + const Font *f = htmlFont( si==0 ); + result = s->checkSelectionPoint(_x, _y, _tx, _ty, f, this, offset, m_lineHeight); + +// kdDebug(6040) << "RenderText::checkSelectionPoint " << this << " line " << si << " result=" << result << " offset=" << offset << endl; + if ( result == SelectionPointInside ) // x,y is inside the textrun + { + offset += s->m_start; // add the offset from the previous lines +// kdDebug(6040) << "RenderText::checkSelectionPoint inside -> " << offset << endl; + node = element(); + return SelectionPointInside; + } else if ( result == SelectionPointBefore ) { + if (!lastNode) { + // x,y is before the textrun -> stop here + offset = 0; +// kdDebug(6040) << "RenderText::checkSelectionPoint " << this << "before us -> returning Before" << endl; + node = element(); + return SelectionPointBefore; + } + } else if ( result == SelectionPointBeforeInLine ) { + offset = s->m_start; + node = element(); + return SelectionPointInside; + } else if ( result == SelectionPointAfterInLine ) { + lastOffset = s->m_start + s->m_len; + lastNode = element(); + lastResult = result; + // no return here + } + + } + + if (lastNode) { + offset = lastOffset; + node = lastNode; +// kdDebug(6040) << "RenderText::checkSelectionPoint: lastNode " << lastNode << " lastOffset " << lastOffset << endl; + return lastResult; + } + + // set offset to max + offset = str->l; + //qDebug("setting node to %p", element()); + node = element(); +// kdDebug(6040) << "RenderText::checkSelectionPoint: node " << node << " offset " << offset << endl; + return SelectionPointAfter; +} + +void RenderText::caretPos(int offset, int flags, int &_x, int &_y, int &width, int &height) +{ + if (!m_lines.count()) { + _x = _y = height = -1; + width = 1; + return; + } + + int pos; + InlineTextBox * s = findInlineTextBox( offset, pos, true ); + RenderText *t = s->renderText(); +// kdDebug(6040) << "offset="<<offset << " pos="<<pos << endl; + + const QFontMetrics &fm = t->metrics( s->m_firstLine ); + height = fm.height(); // s->m_height; + + _x = s->m_x + s->widthFromStart(pos); + _y = s->m_y + s->baseline() - fm.ascent(); + width = 1; + if (flags & CFOverride) { + width = offset < maxOffset() ? fm.width(str->s[offset]) : 1; + }/*end if*/ +#if 0 + kdDebug(6040) << "_x="<<_x << " s->m_x="<<s->m_x + << " s->m_start"<<s->m_start + << " s->m_len" << s->m_len << " _y=" << _y << endl; +#endif + + int absx, absy; + + if (absolutePosition(absx,absy)) + { + //kdDebug(6040) << "absx=" << absx << " absy=" << absy << endl; + _x += absx; + _y += absy; + } else { + // we don't know our absolute position, and there is no point returning + // just a relative one + _x = _y = -1; + } +} + +long RenderText::minOffset() const +{ + if (!m_lines.count()) return 0; + // FIXME: it is *not* guaranteed that the first run contains the lowest offset + // Either make this a linear search (slow), + // or maintain an index (needs much mem), + // or calculate and store it in bidi.cpp (needs calculation even if not needed) + // (LS) + return m_lines[0]->m_start; +} + +long RenderText::maxOffset() const +{ + int count = m_lines.count(); + if (!count) return str->l; + // FIXME: it is *not* guaranteed that the last run contains the highest offset + // Either make this a linear search (slow), + // or maintain an index (needs much mem), + // or calculate and store it in bidi.cpp (needs calculation even if not needed) + // (LS) + return m_lines[count - 1]->m_start + m_lines[count - 1]->m_len; +} + +bool RenderText::absolutePosition(int &xPos, int &yPos, bool) const +{ + return RenderObject::absolutePosition(xPos, yPos, false); +} + +bool RenderText::posOfChar(int chr, int &x, int &y) +{ + if (!parent()) + return false; + parent()->absolutePosition( x, y, false ); + + int pos; + InlineTextBox * s = findInlineTextBox( chr, pos ); + + if ( s ) { + // s is the line containing the character + x += s->m_x; // this is the x of the beginning of the line, but it's good enough for now + y += s->m_y; + return true; + } + + return false; +} + +void RenderText::paint( PaintInfo& /*pI*/, int /*tx*/, int /*ty*/) +{ + KHTMLAssert( false ); +} + +void RenderText::calcMinMaxWidth() +{ + KHTMLAssert( !minMaxKnown() ); + + // ### calc Min and Max width... + m_minWidth = m_beginMinWidth = m_endMinWidth = 0; + m_maxWidth = 0; + + if (isBR()) + return; + + int currMinWidth = 0; + int currMaxWidth = 0; + m_hasBreakableChar = m_hasBreak = m_hasBeginWS = m_hasEndWS = false; + + // ### not 100% correct for first-line + const Font *f = htmlFont( false ); + int wordSpacing = style()->wordSpacing(); + int len = str->l; + bool isSpace = false; + bool firstWord = true; + bool firstLine = true; + for(int i = 0; i < len; i++) + { + unsigned short c = str->s[i].unicode(); + bool isNewline = false; + + // If line-breaks survive to here they are preserved + if ( c == '\n' ) { + m_hasBreak = true; + isNewline = true; + isSpace = false; + } else + isSpace = c == ' '; + + if ((isSpace || isNewline) && i == 0) + m_hasBeginWS = true; + if ((isSpace || isNewline) && i == len-1) + m_hasEndWS = true; + + if (i && c == SOFT_HYPHEN) + continue; + + int wordlen = 0; + while( i+wordlen < len && (i+wordlen == 0 || str->s[i+wordlen].unicode() != SOFT_HYPHEN) && + !(isBreakable( str->s, i+wordlen, str->l )) ) + wordlen++; + + if (wordlen) + { +#ifndef APPLE_CHANGES + int w = f->width(str->s, str->l, i, wordlen); +#else + int w = widthFromCache(f, i, wordlen); +#endif + currMinWidth += w; + currMaxWidth += w; + + // Add in wordspacing to our maxwidth, but not if this is the last word. + if (wordSpacing && !containsOnlyWhitespace(i+wordlen, len-(i+wordlen))) + currMaxWidth += wordSpacing; + + if (firstWord) { + firstWord = false; + m_beginMinWidth = w; + } + m_endMinWidth = w; + + if(currMinWidth > m_minWidth) m_minWidth = currMinWidth; + currMinWidth = 0; + + i += wordlen-1; + } + else { + // Nowrap can never be broken, so don't bother setting the + // breakable character boolean. Pre can only be broken if we encounter a newline. + if (style()->autoWrap() || isNewline) + m_hasBreakableChar = true; + + if(currMinWidth > m_minWidth) m_minWidth = currMinWidth; + currMinWidth = 0; + + if (isNewline) // Only set if isPre was true and we saw a newline. + { + if ( firstLine ) { + firstLine = false; + m_beginMinWidth = currMaxWidth; + } + + if(currMaxWidth > m_maxWidth) m_maxWidth = currMaxWidth; + currMaxWidth = 0; + } + else + { + currMaxWidth += f->width( str->s, str->l, i + wordlen ); + } + } + } + + if(currMinWidth > m_minWidth) m_minWidth = currMinWidth; + if(currMaxWidth > m_maxWidth) m_maxWidth = currMaxWidth; + + if (!style()->autoWrap()) { + m_minWidth = m_maxWidth; + if (style()->preserveLF()) { + if (firstLine) + m_beginMinWidth = m_maxWidth; + m_endMinWidth = currMaxWidth; + } + } + + setMinMaxKnown(); + //kdDebug( 6040 ) << "Text::calcMinMaxWidth(): min = " << m_minWidth << " max = " << m_maxWidth << endl; + +} + +int RenderText::minXPos() const +{ + if (!m_lines.count()) + return 0; + int retval=6666666; + for (unsigned i=0;i < m_lines.count(); i++) + { + retval = kMin ( retval, int( m_lines[i]->m_x )); + } + return retval; +} + +int RenderText::inlineXPos() const +{ + return minXPos(); +} + +int RenderText::inlineYPos() const +{ + return m_lines.isEmpty() ? 0 : m_lines[0]->yPos(); +} + +const QFont &RenderText::font() +{ + return style()->font(); +} + +void RenderText::setText(DOMStringImpl *text, bool force) +{ + if( !force && str == text ) return; + + DOMStringImpl *oldstr = str; + if(text && style()) + str = text->collapseWhiteSpace(style()->preserveLF(), style()->preserveWS()); + else + str = text; + if(str) str->ref(); + if(oldstr) oldstr->deref(); + + if ( str && style() ) { + oldstr = str; + switch(style()->textTransform()) { + case CAPITALIZE: + { + RenderObject *o; + bool runOnString = false; + + // find previous non-empty text renderer if one exists + for (o = previousRenderer(); o; o = o->previousRenderer()) { + if (!o->isInlineFlow()) { + if (!o->isText()) + break; + + DOMStringImpl *prevStr = static_cast<RenderText*>(o)->string(); + // !prevStr can happen with css like "content:open-quote;" + if (!prevStr) + break; + + if (prevStr->length() == 0) + continue; + QChar c = (*prevStr)[prevStr->length() - 1]; + if (!c.isSpace()) + runOnString = true; + + break; + } + } + + str = str->capitalize(runOnString); + } + break; + + + + case UPPERCASE: str = str->upper(); break; + case LOWERCASE: str = str->lower(); break; + case NONE: + default:; + } + str->ref(); + oldstr->deref(); + } + + // ### what should happen if we change the text of a + // RenderBR object ? + KHTMLAssert(!isBR() || (str->l == 1 && (*str->s) == '\n')); + KHTMLAssert(!str->l || str->s); + + setNeedsLayoutAndMinMaxRecalc(); +#ifdef BIDI_DEBUG + QConstString cstr(str->s, str->l); + kdDebug( 6040 ) << "RenderText::setText( " << cstr.string().length() << " ) '" << cstr.string() << "'" << endl; +#endif +} + +int RenderText::height() const +{ + int retval; + if ( m_lines.count() ) + retval = m_lines[m_lines.count()-1]->m_y + m_lineHeight - m_lines[0]->m_y; + else + retval = metrics( false ).height(); + + return retval; +} + +short RenderText::lineHeight( bool firstLine ) const +{ + if ( firstLine ) + return RenderObject::lineHeight( firstLine ); + + return m_lineHeight; +} + +short RenderText::baselinePosition( bool firstLine ) const +{ + const QFontMetrics &fm = metrics( firstLine ); + return fm.ascent() + + ( lineHeight( firstLine ) - fm.height() ) / 2; +} + +InlineBox* RenderText::createInlineBox(bool, bool isRootLineBox) +{ + KHTMLAssert( !isRootLineBox ); + return new (renderArena()) InlineTextBox(this); +} + +void RenderText::position(InlineBox* box, int from, int len, bool reverse) +{ +//kdDebug(6040) << "position: from="<<from<<" len="<<len<<endl; + // ### should not be needed!!! + // asserts sometimes with pre (that unibw-hamburg testcase). ### find out why + //KHTMLAssert(!(len == 0 || (str->l && len == 1 && *(str->s+from) == '\n') )); + // It is now needed. BRs need text boxes too otherwise caret navigation + // gets stuck (LS) + // if (len == 0 || (str->l && len == 1 && *(str->s+from) == '\n') ) return; + + reverse = reverse && !style()->visuallyOrdered(); + +#ifdef DEBUG_LAYOUT + QChar *ch = str->s+from; + QConstString cstr(ch, len); +#endif + + KHTMLAssert(box->isInlineTextBox()); + InlineTextBox *s = static_cast<InlineTextBox *>(box); + s->m_start = from; + s->m_len = len; + s->m_reversed = reverse; + //kdDebug(6040) << "m_start: " << s->m_start << " m_len: " << s->m_len << endl; + + if(m_lines.count() == m_lines.size()) + m_lines.resize(m_lines.size()*2+1); + + m_lines.insert(m_lines.count(), s); + //kdDebug(6040) << this << " " << renderName() << "::position inserted" << endl; +} + +unsigned int RenderText::width(unsigned int from, unsigned int len, bool firstLine) const +{ + if(!str->s || from > str->l ) return 0; + if ( from + len > str->l ) len = str->l - from; + + const Font *f = htmlFont( firstLine ); + return width( from, len, f ); +} + +unsigned int RenderText::width(unsigned int from, unsigned int len, const Font *f) const +{ + if(!str->s || from > str->l ) return 0; + if ( from + len > str->l ) len = str->l - from; + + if ( f == &style()->htmlFont() && from == 0 && len == str->l ) + return m_maxWidth; + + int w = f->width(str->s, str->l, from, len ); + + //kdDebug( 6040 ) << "RenderText::width(" << from << ", " << len << ") = " << w << endl; + return w; +} + +short RenderText::width() const +{ + int w; + int minx = 100000000; + int maxx = 0; + // slooow + for(unsigned int si = 0; si < m_lines.count(); si++) { + InlineTextBox* s = m_lines[si]; + if(s->m_x < minx) + minx = s->m_x; + if(s->m_x + s->m_width > maxx) + maxx = s->m_x + s->m_width; + } + + w = kMax(0, maxx-minx); + + return w; +} + +void RenderText::repaint(Priority p) +{ + RenderObject *cb = containingBlock(); + if(cb) + cb->repaint(p); +} + +bool RenderText::isFixedWidthFont() const +{ + return QFontInfo(style()->font()).fixedPitch(); +} + +short RenderText::verticalPositionHint( bool firstLine ) const +{ + return parent()->verticalPositionHint( firstLine ); +} + +const QFontMetrics &RenderText::metrics(bool firstLine) const +{ + if( firstLine && hasFirstLine() ) { + RenderStyle *pseudoStyle = style()->getPseudoStyle(RenderStyle::FIRST_LINE); + if ( pseudoStyle ) + return pseudoStyle->fontMetrics(); + } + return style()->fontMetrics(); +} + +const Font *RenderText::htmlFont(bool firstLine) const +{ + const Font *f = 0; + if( firstLine && hasFirstLine() ) { + RenderStyle *pseudoStyle = style()->getPseudoStyle(RenderStyle::FIRST_LINE); + if ( pseudoStyle ) + f = &pseudoStyle->htmlFont(); + } else { + f = &style()->htmlFont(); + } + return f; +} + +bool RenderText::containsOnlyWhitespace(unsigned int from, unsigned int len) const +{ + unsigned int currPos; + for (currPos = from; + currPos < from+len && (str->s[currPos] == '\n' || str->s[currPos].direction() == QChar::DirWS); + currPos++); + return currPos >= (from+len); +} + +void RenderText::trimmedMinMaxWidth(short& beginMinW, bool& beginWS, + short& endMinW, bool& endWS, + bool& hasBreakableChar, bool& hasBreak, + short& beginMaxW, short& endMaxW, + short& minW, short& maxW, bool& stripFrontSpaces) +{ + bool preserveWS = style()->preserveWS(); + bool preserveLF = style()->preserveLF(); + bool autoWrap = style()->autoWrap(); + if (preserveWS) + stripFrontSpaces = false; + + int len = str->l; + if (len == 0 || (stripFrontSpaces && str->containsOnlyWhitespace())) { + maxW = 0; + hasBreak = false; + return; + } + + minW = m_minWidth; + maxW = m_maxWidth; + beginWS = stripFrontSpaces ? false : m_hasBeginWS; + endWS = m_hasEndWS; + + beginMinW = m_beginMinWidth; + endMinW = m_endMinWidth; + + hasBreakableChar = m_hasBreakableChar; + hasBreak = m_hasBreak; + + if (stripFrontSpaces && (str->s[0].direction() == QChar::DirWS || (!preserveLF && str->s[0] == '\n'))) { + const Font *f = htmlFont( false ); + QChar space[1]; space[0] = ' '; + int spaceWidth = f->width(space, 1, 0); + maxW -= spaceWidth; + } + + stripFrontSpaces = !preserveWS && m_hasEndWS; + + if (!autoWrap) + minW = maxW; + else if (minW > maxW) + minW = maxW; + + // Compute our max widths by scanning the string for newlines. + if (hasBreak) { + const Font *f = htmlFont( false ); + bool firstLine = true; + beginMaxW = endMaxW = maxW; + for(int i = 0; i < len; i++) + { + int linelen = 0; + while( i+linelen < len && str->s[i+linelen] != '\n') + linelen++; + + if (linelen) + { +#ifndef APPLE_CHANGES + endMaxW = f->width(str->s, str->l, i, linelen); +#else + endMaxW = widthFromCache(f, i, linelen); +#endif + if (firstLine) { + firstLine = false; + beginMaxW = endMaxW; + } + i += linelen; + } + else if (firstLine) { + beginMaxW = 0; + firstLine = false; + } + if (i == len-1) + // A <pre> run that ends with a newline, as in, e.g., + // <pre>Some text\n\n<span>More text</pre> + endMaxW = 0; + } + } +} + +#ifdef ENABLE_DUMP + +static QString quoteAndEscapeNonPrintables(const QString &s) +{ + QString result; + result += '"'; + for (uint i = 0; i != s.length(); ++i) { + QChar c = s.at(i); + if (c == '\\') { + result += "\\\\"; + } else if (c == '"') { + result += "\\\""; + } else { + ushort u = c.unicode(); + if (u >= 0x20 && u < 0x7F) { + result += c; + } else { + QString hex; + hex.sprintf("\\x{%X}", u); + result += hex; + } + } + } + result += '"'; + return result; +} + +static void writeTextRun(QTextStream &ts, const RenderText &o, const InlineTextBox &run) +{ + ts << "text run at (" << run.m_x << "," << run.m_y << ") width " << run.m_width << ": " + << quoteAndEscapeNonPrintables(o.data().string().mid(run.m_start, run.m_len)); +} + +void RenderText::dump(QTextStream &stream, const QString &ind) const +{ + RenderObject::dump( stream, ind ); + + for (unsigned int i = 0; i < m_lines.count(); i++) { + stream << endl << ind << " "; + writeTextRun(stream, *this, *m_lines[i]); + } +} +#endif + +RenderTextFragment::RenderTextFragment(DOM::NodeImpl* _node, DOM::DOMStringImpl* _str, + int startOffset, int endOffset) +:RenderText(_node, _str->substring(startOffset, endOffset)), +m_start(startOffset), m_end(endOffset), m_generatedContentStr(0) +{} + +RenderTextFragment::RenderTextFragment(DOM::NodeImpl* _node, DOM::DOMStringImpl* _str) +:RenderText(_node, _str), m_start(0) +{ + m_generatedContentStr = _str; + if (_str) { + _str->ref(); + m_end = _str->l; + } + else + m_end = 0; +} + +RenderTextFragment::~RenderTextFragment() +{ + if (m_generatedContentStr) + m_generatedContentStr->deref(); +} + +bool RenderTextFragment::isTextFragment() const +{ + return true; +} + +DOM::DOMStringImpl* RenderTextFragment::originalString() const +{ + DOM::DOMStringImpl* result = 0; + if (element()) + result = element()->string(); + else + result = contentString(); + if (result && (start() > 0 || start() < result->l)) + result = result->substring(start(), end()); + return result; +} + +#undef BIDI_DEBUG +#undef DEBUG_LAYOUT diff --git a/khtml/rendering/render_text.h b/khtml/rendering/render_text.h new file mode 100644 index 000000000..c65973c99 --- /dev/null +++ b/khtml/rendering/render_text.h @@ -0,0 +1,345 @@ +/* + * This file is part of the DOM implementation for KDE. + * + * (C) 1999-2003 Lars Knoll (knoll@kde.org) + * (C) 2000-2003 Dirk Mueller (mueller@kde.org) + * (C) 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. + * + */ +#ifndef RENDERTEXT_H +#define RENDERTEXT_H + +#include "dom/dom_string.h" +#include "xml/dom_stringimpl.h" +#include "xml/dom_textimpl.h" +#include "rendering/render_object.h" +#include "rendering/render_line.h" + +#include <qptrvector.h> +#include <assert.h> + +class QPainter; +class QFontMetrics; + +// Define a constant for soft hyphen's unicode value. +#define SOFT_HYPHEN 173 + +const int cNoTruncation = -1; +const int cFullTruncation = -2; + +namespace khtml +{ + class RenderText; + class RenderStyle; + +class InlineTextBox : public InlineBox +{ +public: + InlineTextBox(RenderObject *obj) + :InlineBox(obj), + // ### necessary as some codepaths (<br>) do *not* initialize these (LS) + m_start(0), m_len(0), m_truncation(cNoTruncation), m_reversed(false), m_toAdd(0) + { + } + + void detach(RenderArena* renderArena); + + virtual void clearTruncation() { m_truncation = cNoTruncation; } + virtual int placeEllipsisBox(bool ltr, int blockEdge, int ellipsisWidth, bool& foundBox); + + // Overloaded new operator. Derived classes must override operator new + // in order to allocate out of the RenderArena. + void* operator new(size_t sz, RenderArena* renderArena) throw(); + + // Overridden to prevent the normal delete from being called. + void operator delete(void* ptr, size_t sz); + +private: + // The normal operator new is disallowed. + void* operator new(size_t sz) throw(); + +public: + void setSpaceAdd(int add) { m_width -= m_toAdd; m_toAdd = add; m_width += m_toAdd; } + int spaceAdd() { return m_toAdd; } + + virtual bool isInlineTextBox() const { return true; } + + void paint(RenderObject::PaintInfo& i, int tx, int ty); + void paintDecoration(QPainter *pt, const Font *f, int _tx, int _ty, int decoration); + void paintShadow(QPainter *pt, const Font* f, int _tx, int _ty, const ShadowData *shadow ); + void paintSelection(const Font *f, RenderText *text, QPainter *p, RenderStyle* style, int tx, int ty, int startPos, int endPos, int deco); + + void selectionStartEnd(int& sPos, int& ePos); + RenderObject::SelectionState selectionState(); + + // Return before, after (offset set to max), or inside the text, at @p offset + FindSelectionResult checkSelectionPoint(int _x, int _y, int _tx, int _ty, const Font *f, RenderText *text, int & offset, short lineheight); + + bool checkVerticalPoint(int _y, int _ty, int _h, int height) + { if((_ty + m_y > _y + _h) || (_ty + m_y + m_baseline + height < _y)) return false; return true; } + + /** + * determines the offset into the DOMString of the character the given + * coordinate points to. + * The returned offset is never out of range. + * @param _x given coordinate (relative to containing block) + * @param ax returns exact coordinate the offset corresponds to + * (relative to containing block) + * @return the offset (relative to the RenderText object, not to this run) + */ + int offsetForPoint(int _x, int &ax) const; + + /** + * calculates the with of the specified chunk in this text box. + * @param pos zero-based position within the text box up to which + * the width is to be determined + * @return the width in pixels + */ + int widthFromStart(int pos) const; + + /** returns the lowest possible value the caret offset may have to + * still point to a valid position. + */ + virtual long minOffset() const; + /** returns the highest possible value the caret offset may have to + * still point to a valid position. + */ + virtual long maxOffset() const; + + /** returns the associated render text + */ + const RenderText *renderText() const; + RenderText *renderText(); + + int m_start; + unsigned short m_len; + + int m_truncation; // Where to truncate when text overflow is applied. + // We use special constants to denote no truncation (the whole run paints) + // and full truncation (nothing paints at all). + + bool m_reversed : 1; + unsigned m_toAdd : 14; // for justified text +private: + // this is just for QVector::bsearch. Don't use it otherwise + InlineTextBox(int _x, int _y) + :InlineBox(0) + { + m_x = _x; + m_y = _y; + m_reversed = false; + }; + friend class RenderText; +}; + +class InlineTextBoxArray : public QPtrVector<InlineTextBox> +{ +public: + InlineTextBoxArray(); + + InlineTextBox* first(); + + int findFirstMatching( Item ) const; + virtual int compareItems( Item, Item ); +}; + +class RenderText : public RenderObject +{ + friend class InlineTextBox; + +public: + RenderText(DOM::NodeImpl* node, DOM::DOMStringImpl *_str); + virtual ~RenderText(); + + virtual bool isTextFragment() const; + virtual DOM::DOMStringImpl* originalString() const; + + virtual const char *renderName() const { return "RenderText"; } + + virtual void setStyle(RenderStyle *style); + + + virtual void paint( PaintInfo& i, int tx, int ty ); + + virtual void deleteInlineBoxes(RenderArena* arena=0); + + DOM::DOMString data() const { return str; } + DOM::DOMStringImpl *string() const { return str; } + + virtual InlineBox* createInlineBox(bool, bool); + + virtual void layout() {assert(false);} + + virtual bool nodeAtPoint(NodeInfo& info, int x, int y, int tx, int ty, HitTestAction hitTestAction, bool inBox); + + // Return before, after (offset set to max), or inside the text, at @p offset + virtual FindSelectionResult checkSelectionPoint( int _x, int _y, int _tx, int _ty, + DOM::NodeImpl*& node, int & offset, + SelPointState & ); + + unsigned int length() const { if (str) return str->l; else return 0; } + QChar *text() const { if (str) return str->s; else return 0; } + unsigned int stringLength() const { return str->l; } // non virtual implementation of length() + virtual void position(InlineBox* box, int from, int len, bool reverse); + + virtual unsigned int width(unsigned int from, unsigned int len, const Font *f) const; + virtual unsigned int width(unsigned int from, unsigned int len, bool firstLine = false) const; + virtual short width() const; + virtual int height() const; + + // height of the contents (without paddings, margins and borders) + virtual short lineHeight( bool firstLine ) const; + virtual short baselinePosition( bool firstLine ) const; + + // overrides + virtual void calcMinMaxWidth(); + virtual short minWidth() const { return m_minWidth; } + virtual int maxWidth() const { return m_maxWidth; } + + void trimmedMinMaxWidth(short& beginMinW, bool& beginWS, + short& endMinW, bool& endWS, + bool& hasBreakableChar, bool& hasBreak, + short& beginMaxW, short& endMaxW, + short& minW, short& maxW, bool& stripFrontSpaces); + + bool containsOnlyWhitespace(unsigned int from, unsigned int len) const; + + ushort startMin() const { return m_startMin; } + ushort endMin() const { return m_endMin; } + + // returns the minimum x position of all runs relative to the parent. + // defaults to 0. + int minXPos() const; + + virtual int inlineXPos() const; + virtual int inlineYPos() const; + + bool hasReturn() const { return m_hasReturn; } + + virtual const QFont &font(); + virtual short verticalPositionHint( bool firstLine ) const; + + bool isFixedWidthFont() const; + + void setText(DOM::DOMStringImpl *text, bool force=false); + + virtual SelectionState selectionState() const {return m_selectionState;} + virtual void setSelectionState(SelectionState s) {m_selectionState = s; } + virtual void caretPos(int offset, int flags, int &_x, int &_y, int &width, int &height); + virtual bool absolutePosition(int &/*xPos*/, int &/*yPos*/, bool f = false) const; + bool posOfChar(int ch, int &x, int &y); + + virtual short marginLeft() const { return style()->marginLeft().minWidth(0); } + virtual short marginRight() const { return style()->marginRight().minWidth(0); } + + virtual void repaint(Priority p=NormalPriority); + + bool hasBreakableChar() const { return m_hasBreakableChar; } + const QFontMetrics &metrics(bool firstLine) const; + const Font *htmlFont(bool firstLine) const; + + DOM::TextImpl *element() const + { return static_cast<DOM::TextImpl*>(RenderObject::element()); } + + /** returns the lowest possible value the caret offset may have to + * still point to a valid position. + */ + virtual long minOffset() const; + + /** returns the highest possible value the caret offset may have to + * still point to a valid position. + */ + virtual long maxOffset() const; + + /** returns the number of inline text boxes + */ + unsigned inlineTextBoxCount() const { return m_lines.count(); } + /** returns the array of inline text boxes for this render text. + */ + const InlineTextBoxArray &inlineTextBoxes() const { return m_lines; } + +#ifdef ENABLE_DUMP + virtual void dump(QTextStream &stream, const QString &ind) const; +#endif + + /** Find the text box that includes the character at @p offset + * and return pos, which is the position of the char in the run. + * @param offset zero-based offset into DOM string + * @param pos returns relative position within text box + * @param checkFirstLetter passing @p true will also regard :first-letter + * boxes, if available. + * @return the text box, or 0 if no match has been found + */ + InlineTextBox * findInlineTextBox( int offset, int &pos, + bool checkFirstLetter = false ); + +protected: // members + InlineTextBoxArray m_lines; + DOM::DOMStringImpl *str; // + + short m_lineHeight; + short m_minWidth; + int m_maxWidth; + short m_beginMinWidth; + short m_endMinWidth; + + SelectionState m_selectionState : 3 ; + bool m_hasReturn : 1; + bool m_hasBreakableChar : 1; + bool m_hasBreak : 1; + bool m_hasBeginWS : 1; + bool m_hasEndWS : 1; + + ushort m_startMin : 8; + ushort m_endMin : 8; +}; + +inline const RenderText* InlineTextBox::renderText() const +{ return static_cast<RenderText*>( object() ); } + +inline RenderText* InlineTextBox::renderText() +{ return static_cast<RenderText*>( object() ); } + +// Used to represent a text substring of an element, e.g., for text runs that are split because of +// first letter and that must therefore have different styles (and positions in the render tree). +// We cache offsets so that text transformations can be applied in such a way that we can recover +// the original unaltered string from our corresponding DOM node. +class RenderTextFragment : public RenderText +{ +public: + RenderTextFragment(DOM::NodeImpl* _node, DOM::DOMStringImpl* _str, + int startOffset, int endOffset); + RenderTextFragment(DOM::NodeImpl* _node, DOM::DOMStringImpl* _str); + ~RenderTextFragment(); + + virtual bool isTextFragment() const; + virtual const char *renderName() const { return "RenderTextFragment"; } + + uint start() const { return m_start; } + uint end() const { return m_end; } + + DOM::DOMStringImpl* contentString() const { return m_generatedContentStr; } + virtual DOM::DOMStringImpl* originalString() const; + +private: + uint m_start; + uint m_end; + DOM::DOMStringImpl* m_generatedContentStr; +}; +} // end namespace +#endif diff --git a/khtml/rendering/table_layout.cpp b/khtml/rendering/table_layout.cpp new file mode 100644 index 000000000..bf3daad3a --- /dev/null +++ b/khtml/rendering/table_layout.cpp @@ -0,0 +1,1193 @@ +/* + * This file is part of the HTML rendering engine for KDE. + * + * Copyright (C) 2002 Lars Knoll (knoll@kde.org) + * (C) 2002 Dirk Mueller (mueller@kde.org) + * + * 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. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public License + * along with this library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ +#include "table_layout.h" +#include "render_table.h" + +#include <kglobal.h> + +using namespace khtml; + +// #define DEBUG_LAYOUT + +/* + The text below is from the CSS 2.1 specs. + + Fixed table layout + ------------------ + + With this (fast) algorithm, the horizontal layout of the table does + not depend on the contents of the cells; it only depends on the + table's width, the width of the columns, and borders or cell + spacing. + + The table's width may be specified explicitly with the 'width' + property. A value of 'auto' (for both 'display: table' and 'display: + inline-table') means use the automatic table layout algorithm. + + In the fixed table layout algorithm, the width of each column is + determined as follows: + + 1. A column element with a value other than 'auto' for the 'width' + property sets the width for that column. + + 2. Otherwise, a cell in the first row with a value other than + 'auto' for the 'width' property sets the width for that column. If + the cell spans more than one column, the width is divided over the + columns. + + 3. Any remaining columns equally divide the remaining horizontal + table space (minus borders or cell spacing). + + The width of the table is then the greater of the value of the + 'width' property for the table element and the sum of the column + widths (plus cell spacing or borders). If the table is wider than + the columns, the extra space should be distributed over the columns. + + + In this manner, the user agent can begin to lay out the table once + the entire first row has been received. Cells in subsequent rows do + not affect column widths. Any cell that has content that overflows + uses the 'overflow' property to determine whether to clip the + overflow content. + +_____________________________________________________ + + This is not quite true when comparing to IE. IE always honors + table-layout:fixed and treats a variable table width as 100%. Makes + a lot of sense, and is implemented here the same way. + +*/ + +FixedTableLayout::FixedTableLayout( RenderTable *table ) + : TableLayout ( table ) +{ +} + +FixedTableLayout::~FixedTableLayout() +{ +} + +int FixedTableLayout::calcWidthArray() +{ + int usedWidth = 0; + + // iterate over all <col> elements + RenderObject *child = table->firstChild(); + int cCol = 0; + int nEffCols = table->numEffCols(); + width.resize( nEffCols ); + width.fill( Length( Variable ) ); + +#ifdef DEBUG_LAYOUT + qDebug("FixedTableLayout::calcWidthArray()" ); + qDebug(" col elements:"); +#endif + + Length grpWidth; + while ( child ) { + if ( child->isTableCol() ) { + RenderTableCol *col = static_cast<RenderTableCol *>(child); + int span = col->span(); + if ( col->firstChild() ) { + grpWidth = col->style()->width(); + } else { + Length w = col->style()->width(); + if ( w.isVariable() ) + w = grpWidth; + int effWidth = 0; + if ( w.isFixed() && w.value() > 0 ) { + effWidth = w.value(); + effWidth = KMIN( effWidth, 32760 ); + } +#ifdef DEBUG_LAYOUT + qDebug(" col element: effCol=%d, span=%d: %d w=%d type=%d", + cCol, span, effWidth, w.value(), w.type()); +#endif + int usedSpan = 0; + int i = 0; + while ( usedSpan < span ) { + if( cCol + i >= nEffCols ) { + table->appendColumn( span - usedSpan ); + nEffCols++; + width.resize( nEffCols ); + width[nEffCols-1] = Length(); + } + int eSpan = table->spanOfEffCol( cCol+i ); + if ( (w.isFixed() || w.isPercent()) && w.value() > 0 ) { + width[cCol+i] = Length( w.value() * eSpan, w.type() ); + usedWidth += effWidth * eSpan; +#ifdef DEBUG_LAYOUT + qDebug(" setting effCol %d (span=%d) to width %d(type=%d)", + cCol+i, eSpan, width[cCol+i].value(), width[cCol+i].type() ); +#endif + } + usedSpan += eSpan; + i++; + } + cCol += i; + } + } else { + break; + } + + RenderObject *next = child->firstChild(); + if ( !next ) + next = child->nextSibling(); + if ( !next && child->parent()->isTableCol() ) { + next = child->parent()->nextSibling(); + grpWidth = Length(); + } + child = next; + } + +#ifdef DEBUG_LAYOUT + qDebug(" first row:"); +#endif + // iterate over the first row in case some are unspecified. + RenderTableSection *section = table->head; + if ( !section ) + section = table->firstBody; + if ( !section ) + section = table->foot; + if ( section && section->firstChild() ) { + cCol = 0; + // get the first cell in the first row + child = section->firstChild()->firstChild(); + while ( child ) { + if ( child->isTableCell() ) { + RenderTableCell *cell = static_cast<RenderTableCell *>(child); + Length w = cell->styleOrColWidth(); + int span = cell->colSpan(); + int effWidth = 0; + if ( (w.isFixed() || w.isPercent()) && w.value() > 0 ) { + effWidth = w.value(); + effWidth = kMin( effWidth, 32760 ); + } +#ifdef DEBUG_LAYOUT + qDebug(" table cell: effCol=%d, span=%d: %d", cCol, span, effWidth); +#endif + int usedSpan = 0; + int i = 0; + while ( usedSpan < span ) { + Q_ASSERT( cCol + i < nEffCols ); + int eSpan = table->spanOfEffCol( cCol+i ); + // only set if no col element has already set it. + if ( width[cCol+i].isVariable() && !w.isVariable() ) { + width[cCol+i] = Length( w.value()*eSpan, w.type() ); + usedWidth += effWidth*eSpan; +#ifdef DEBUG_LAYOUT + qDebug(" setting effCol %d (span=%d) to width %d(type=%d)", + cCol+i, eSpan, width[cCol+i].value(), width[cCol+i].type() ); +#endif + } +#ifdef DEBUG_LAYOUT + else { + qDebug(" width of col %d already defined (span=%d)", cCol, table->spanOfEffCol( cCol ) ); + } +#endif + usedSpan += eSpan; + i++; + } + cCol += i; + } else { + Q_ASSERT( false ); + } + child = child->nextSibling(); + } + } + + return usedWidth; + +} + +void FixedTableLayout::calcMinMaxWidth() +{ + // we might want to wait until we have all of the first row before + // layouting for the first time. + + // only need to calculate the minimum width as the sum of the + // cols/cells with a fixed width. + // + // The maximum width is kMax( minWidth, tableWidth ) if table + // width is fixed. If table width is percent, we set maxWidth to + // unlimited. + + int bs = table->bordersPaddingAndSpacing(); + int tableWidth = 0; + if (table->style()->width().isFixed()) { + tableWidth = table->calcBoxWidth(table->style()->width().value()); + } + + int mw = calcWidthArray() + bs; + table->m_minWidth = kMin( kMax( mw, tableWidth ), 0x7fff ); + table->m_maxWidth = table->m_minWidth; + + if ( !tableWidth ) { + bool haveNonFixed = false; + for ( unsigned int i = 0; i < width.size(); i++ ) { + if ( !width[i].isFixed() ) { + haveNonFixed = true; + break; + } + } + if ( haveNonFixed ) + table->m_maxWidth = 0x7fff; + } +#ifdef DEBUG_LAYOUT + qDebug("FixedTableLayout::calcMinMaxWidth: minWidth=%d, maxWidth=%d", table->m_minWidth, table->m_maxWidth ); +#endif +} + +void FixedTableLayout::layout() +{ + int tableWidth = table->width() - table->bordersPaddingAndSpacing(); + int available = tableWidth; + int nEffCols = table->numEffCols(); +#ifdef DEBUG_LAYOUT + qDebug("FixedTableLayout::layout: tableWidth=%d, numEffCols=%d", tableWidth, nEffCols); +#endif + + + QMemArray<int> calcWidth; + calcWidth.resize( nEffCols ); + calcWidth.fill( -1 ); + + // first assign fixed width + for ( int i = 0; i < nEffCols; i++ ) { + if ( width[i].isFixed() ) { + calcWidth[i] = width[i].value(); + available -= width[i].value(); + } + } + + // assign percent width + if ( available > 0 ) { + int totalPercent = 0; + for ( int i = 0; i < nEffCols; i++ ) + if ( width[i].isPercent() ) + totalPercent += width[i].value(); + + // calculate how much to distribute to percent cells. + int base = tableWidth * totalPercent / 100; + if ( base > available ) + base = available; + +#ifdef DEBUG_LAYOUT + qDebug("FixedTableLayout::layout: assigning percent width, base=%d, totalPercent=%d", base, totalPercent); +#endif + for ( int i = 0; available > 0 && i < nEffCols; i++ ) { + if ( width[i].isPercent() ) { + // totalPercent may be 0 below if all %-width specifed are 0%. (#172557) + int w = totalPercent ? base * width[i].value() / totalPercent : 0; + available -= w; + calcWidth[i] = w; + } + } + } + + // assign variable width + if ( available > 0 ) { + int totalVariable = 0; + for ( int i = 0; i < nEffCols; i++ ) + if ( width[i].isVariable() ) + totalVariable++; + + for ( int i = 0; available > 0 && i < nEffCols; i++ ) { + if ( width[i].isVariable() ) { + // totalVariable may be 0 below if all the variable widths specified are 0. + int w = totalVariable ? available / totalVariable : 0; + available -= w; + calcWidth[i] = w; + totalVariable--; + } + } + } + + for ( int i = 0; i < nEffCols; i++ ) + if ( calcWidth[i] < 0 ) + calcWidth[i] = 0; // IE gives min 1 px... + + // spread extra space over columns + if ( available > 0 ) { + int total = nEffCols; + // still have some width to spread + int i = nEffCols; + while ( i-- ) { + int w = available / total; + available -= w; + total--; + calcWidth[i] += w; + } + } + + int pos = 0; + int hspacing = table->borderHSpacing(); + for ( int i = 0; i < nEffCols; i++ ) { +#ifdef DEBUG_LAYOUT + qDebug("col %d: %d (width %d)", i, pos, calcWidth[i] ); +#endif + table->columnPos[i] = pos; + pos += calcWidth[i] + hspacing; + } + table->columnPos[table->columnPos.size()-1] = pos; +} + +// ------------------------------------------------------------------------- +// ------------------------------------------------------------------------- + + +AutoTableLayout::AutoTableLayout( RenderTable* table ) + : TableLayout( table ) +{ + percentagesDirty = true; + effWidthDirty = true; + total_percent = 0; + hasPercent = false; +} + +AutoTableLayout::~AutoTableLayout() +{ +} + +/* recalculates the full structure needed to do layouting and minmax calculations. + This is usually calculated on the fly, but needs to be done fully when table cells change + dynamically +*/ +void AutoTableLayout::recalcColumn( int effCol ) +{ + Layout &l = layoutStruct[effCol]; + + RenderObject *child = table->firstChild(); + // first we iterate over all rows. + + RenderTableCell *fixedContributor = 0; + RenderTableCell *maxContributor = 0; + + while ( child ) { + if ( child->isTableSection() ) { + RenderTableSection *section = static_cast<RenderTableSection *>(child); + int numRows = section->numRows(); + RenderTableCell *last = 0; + for ( int i = 0; i < numRows; i++ ) { + RenderTableCell *cell = section->cellAt( i, effCol ); + if ( cell == (RenderTableCell *)-1 ) + continue; + if ( cell && cell->colSpan() == 1 ) { + // A cell originates in this column. Ensure we have + // a min/max width of at least 1px for this column now. + l.minWidth = kMax(int( l.minWidth ), 1); + l.maxWidth = kMax(int( l.maxWidth ), 1); + + if ( !cell->minMaxKnown() ) + cell->calcMinMaxWidth(); + if ( cell->minWidth() > l.minWidth ) + l.minWidth = cell->minWidth(); + if ( cell->maxWidth() > l.maxWidth ) { + l.maxWidth = cell->maxWidth(); + maxContributor = cell; + } + + Length w = cell->styleOrColWidth(); + w.l.value = kMin( 32767, kMax( 0, w.value() ) ); + switch( w.type() ) { + case Fixed: + // ignore width=0 + if ( w.value() > 0 && !l.width.isPercent() ) { + int wval = cell->calcBoxWidth(w.value()); + if ( l.width.isFixed() ) { + // Nav/IE weirdness + if ((wval > l.width.value()) || + ((l.width.value() == wval) && (maxContributor == cell))) { + l.width.l.value = wval; + fixedContributor = cell; + } + } else { + l.width = Length( wval, Fixed ); + fixedContributor = cell; + } + } + break; + case Percent: + hasPercent = true; + if ( w.value() > 0 && (!l.width.isPercent() || w.value() > l.width.value() ) ) + l.width = w; + break; + case Relative: + if ( w.isVariable() || (w.isRelative() && w.value() > l.width.value() ) ) + l.width = w; + default: + break; + } + } else { + if ( cell && (!effCol || section->cellAt( i, effCol-1 ) != cell) ) { + // This spanning cell originates in this column. Ensure we have + // a min/max width of at least 1px for this column now. + l.minWidth = kMax(int( l.minWidth ), 1); + l.maxWidth = kMax(int( l.maxWidth ), 1); + insertSpanCell( cell ); + } + last = cell; + } + } + } + child = child->nextSibling(); + } + + // Nav/IE weirdness + if ( l.width.isFixed() ) { + if ( table->style()->htmlHacks() + && (l.maxWidth > l.width.value()) && (fixedContributor != maxContributor)) { + l.width = Length(); + fixedContributor = 0; + } + } + + l.maxWidth = kMax(l.maxWidth, int(l.minWidth)); +#ifdef DEBUG_LAYOUT + qDebug("col %d, final min=%d, max=%d, width=%d(%d)", effCol, l.minWidth, l.maxWidth, l.width.value(), l.width.type() ); +#endif + + // ### we need to add col elements aswell +} + + +void AutoTableLayout::fullRecalc() +{ + percentagesDirty = true; + hasPercent = false; + effWidthDirty = true; + + int nEffCols = table->numEffCols(); + layoutStruct.resize( nEffCols ); + layoutStruct.fill( Layout() ); + spanCells.fill( 0 ); + + RenderObject *child = table->firstChild(); + Length grpWidth; + int cCol = 0; + while ( child ) { + if ( child->isTableCol() ) { + RenderTableCol *col = static_cast<RenderTableCol *>(child); + int span = col->span(); + if ( col->firstChild() ) { + grpWidth = col->style()->width(); + } else { + Length w = col->style()->width(); + if ( w.isVariable() ) + w = grpWidth; + if ( (w.isFixed() && w.value() == 0) || + (w.isPercent() && w.value() == 0) ) + w = Length(); + int cEffCol = table->colToEffCol( cCol ); +#ifdef DEBUG_LAYOUT + qDebug(" col element %d (eff=%d): Length=%d(%d), span=%d, effColSpan=%d", cCol, cEffCol, w.value(), w.type(), span, table->spanOfEffCol(cEffCol ) ); +#endif + if ( !w.isVariable() && span == 1 && cEffCol < nEffCols ) { + if ( table->spanOfEffCol( cEffCol ) == 1 ) { + layoutStruct[cEffCol].width = w; + if (w.isFixed() && layoutStruct[cEffCol].maxWidth < w.value()) + layoutStruct[cEffCol].maxWidth = w.value(); + } + } + cCol += span; + } + } else { + break; + } + + RenderObject *next = child->firstChild(); + if ( !next ) + next = child->nextSibling(); + if ( !next && child->parent()->isTableCol() ) { + next = child->parent()->nextSibling(); + grpWidth = Length(); + } + child = next; + } + + + for ( int i = 0; i < nEffCols; i++ ) + recalcColumn( i ); +} + +static bool shouldScaleColumns(RenderTable* table) +{ + // A special case. If this table is not fixed width and contained inside + // a cell, then don't bloat the maxwidth by examining percentage growth. + bool scale = true; + while (table) { + Length tw = table->style()->width(); + if ((tw.isVariable() || tw.isPercent()) && !table->isPositioned()) { + RenderBlock* cb = table->containingBlock(); + while (cb && !cb->isCanvas() && !cb->isTableCell() && + cb->style()->width().isVariable() && !cb->isPositioned()) + cb = cb->containingBlock(); + + table = 0; + if (cb && cb->isTableCell() && + (cb->style()->width().isVariable() || cb->style()->width().isPercent())) { + if (tw.isPercent()) + scale = false; + else { + RenderTableCell* cell = static_cast<RenderTableCell*>(cb); + if (cell->colSpan() > 1 || cell->table()->style()->width().isVariable()) + scale = false; + else + table = cell->table(); + } + } + } + else + table = 0; + } + return scale; +} + +void AutoTableLayout::calcMinMaxWidth() +{ +#ifdef DEBUG_LAYOUT + qDebug("AutoTableLayout::calcMinMaxWidth"); +#endif + fullRecalc(); + + int spanMaxWidth = calcEffectiveWidth(); + int minWidth = 0; + int maxWidth = 0; + int maxPercent = 0; + int maxNonPercent = 0; + + int remainingPercent = 100; + for ( unsigned int i = 0; i < layoutStruct.size(); i++ ) { + minWidth += layoutStruct[i].effMinWidth; + maxWidth += layoutStruct[i].effMaxWidth; + if ( layoutStruct[i].effWidth.isPercent() ) { + int percent = kMin(layoutStruct[i].effWidth.value(), remainingPercent); + int pw = ( layoutStruct[i].effMaxWidth * 100) / kMax(percent, 1); + remainingPercent -= percent; + maxPercent = kMax( pw, maxPercent ); + } else { + maxNonPercent += layoutStruct[i].effMaxWidth; + } + } + + if (shouldScaleColumns(table)) { + maxNonPercent = (maxNonPercent * 100 + 50) / kMax(remainingPercent, 1); + maxWidth = kMax( maxNonPercent, maxWidth ); + maxWidth = kMax( maxWidth, maxPercent ); + } + + maxWidth = kMax( maxWidth, spanMaxWidth ); + + int bs = table->bordersPaddingAndSpacing(); + minWidth += bs; + maxWidth += bs; + + Length tw = table->style()->width(); + if ( tw.isFixed() && tw.value() > 0 ) { + int width = table->calcBoxWidth(tw.value()); + minWidth = kMax( minWidth, width ); + maxWidth = minWidth; + } + + table->m_maxWidth = kMin(maxWidth, 0x7fff); + table->m_minWidth = kMin(minWidth, 0x7fff); +#ifdef DEBUG_LAYOUT + qDebug(" minWidth=%d, maxWidth=%d", table->m_minWidth, table->m_maxWidth ); +#endif +} + +/* + This method takes care of colspans. + effWidth is the same as width for cells without colspans. If we have colspans, they get modified. + */ +int AutoTableLayout::calcEffectiveWidth() +{ + int tMaxWidth = 0; + + unsigned int nEffCols = layoutStruct.size(); + int hspacing = table->borderHSpacing(); +#ifdef DEBUG_LAYOUT + qDebug("AutoTableLayout::calcEffectiveWidth for %d cols", nEffCols ); +#endif + for ( unsigned int i = 0; i < nEffCols; i++ ) { + layoutStruct[i].effWidth = layoutStruct[i].width; + layoutStruct[i].effMinWidth = layoutStruct[i].minWidth; + layoutStruct[i].effMaxWidth = layoutStruct[i].maxWidth; + } + + for ( unsigned int i = 0; i < spanCells.size(); i++ ) { + RenderTableCell *cell = spanCells[i]; + if ( !cell || cell == (RenderTableCell *)-1 ) + break; + int span = cell->colSpan(); + + Length w = cell->styleOrColWidth(); + if ( !w.isRelative() && w.value() == 0 ) + w = Length(); // make it Variable + + int col = table->colToEffCol( cell->col() ); + unsigned int lastCol = col; + int cMinWidth = cell->minWidth() + hspacing; + int cMaxWidth = cell->maxWidth() + hspacing; + int totalPercent = 0; + int minWidth = 0; + int maxWidth = 0; + bool allColsArePercent = true; + bool allColsAreFixed = true; + bool haveVariable = false; + int fixedWidth = 0; +#ifdef DEBUG_LAYOUT + int cSpan = span; +#endif + while ( lastCol < nEffCols && span > 0 ) { + switch( layoutStruct[lastCol].width.type() ) { + case Percent: + totalPercent += layoutStruct[lastCol].width.value(); + allColsAreFixed = false; + break; + case Fixed: + if (layoutStruct[lastCol].width.value() > 0) { + fixedWidth += layoutStruct[lastCol].width.value(); + allColsArePercent = false; + // IE resets effWidth to Variable here, but this breaks the konqueror about page and seems to be some bad + // legacy behavior anyway. mozilla doesn't do this so I decided we don't either. + break; + } + // fall through + case Variable: + haveVariable = true; + // fall through + default: + // If the column is a percentage width, do not let the spanning cell overwrite the + // width value. This caused a mis-rendering on amazon.com. + // Sample snippet: + // <table border=2 width=100%>< + // <tr><td>1</td><td colspan=2>2-3</tr> + // <tr><td>1</td><td colspan=2 width=100%>2-3</td></tr> + // </table> + if (!layoutStruct[lastCol].effWidth.isPercent()) { + layoutStruct[lastCol].effWidth = Length(); + allColsArePercent = false; + } + else + totalPercent += layoutStruct[lastCol].effWidth.value(); + allColsAreFixed = false; + } + span -= table->spanOfEffCol( lastCol ); + minWidth += layoutStruct[lastCol].effMinWidth; + maxWidth += layoutStruct[lastCol].effMaxWidth; + lastCol++; + cMinWidth -= hspacing; + cMaxWidth -= hspacing; + } +#ifdef DEBUG_LAYOUT + qDebug(" colspan cell %p at effCol %d, span %d, type %d, value %d cmin=%d min=%d fixedwidth=%d", cell, col, cSpan, w.type(), w.value(), cMinWidth, minWidth, fixedWidth ); +#endif + + // adjust table max width if needed + if ( w.isPercent() ) { + if ( totalPercent > w.value() || allColsArePercent ) { + // can't satify this condition, treat as variable + w = Length(); + } else { + int spanMax = kMax( maxWidth, cMaxWidth ); +#ifdef DEBUG_LAYOUT + qDebug(" adjusting tMaxWidth (%d): spanMax=%d, value=%d, totalPercent=%d", tMaxWidth, spanMax, w.value(), totalPercent ); +#endif + tMaxWidth = kMax( tMaxWidth, spanMax * 100 / w.value() ); + + // all non percent columns in the span get percent values to sum up correctly. + int percentMissing = w.value() - totalPercent; + int totalWidth = 0; + for ( unsigned int pos = col; pos < lastCol; pos++ ) { + if ( !(layoutStruct[pos].width.isPercent() ) ) + totalWidth += layoutStruct[pos].effMaxWidth; + } + + for ( unsigned int pos = col; pos < lastCol && totalWidth > 0; pos++ ) { + if ( !(layoutStruct[pos].width.isPercent() ) ) { + int percent = percentMissing * layoutStruct[pos].effMaxWidth / totalWidth; +#ifdef DEBUG_LAYOUT + qDebug(" col %d: setting percent value %d effMaxWidth=%d totalWidth=%d", pos, percent, layoutStruct[pos].effMaxWidth, totalWidth ); +#endif + totalWidth -= layoutStruct[pos].effMaxWidth; + percentMissing -= percent; + if ( percent > 0 ) + layoutStruct[pos].effWidth = Length( percent, Percent ); + else + layoutStruct[pos].effWidth = Length(); + } + } + + } + } + + // make sure minWidth and maxWidth of the spanning cell are honoured + if ( cMinWidth > minWidth ) { + if ( allColsAreFixed ) { +#ifdef DEBUG_LAYOUT + qDebug("extending minWidth of cols %d-%d to %dpx currentMin=%d accroding to fixed sum %d", col, lastCol-1, cMinWidth, minWidth, fixedWidth ); +#endif + for ( unsigned int pos = col; fixedWidth > 0 && pos < lastCol; pos++ ) { + int w = kMax( int( layoutStruct[pos].effMinWidth ), cMinWidth * layoutStruct[pos].width.value() / fixedWidth ); +#ifdef DEBUG_LAYOUT + qDebug(" col %d: min=%d, effMin=%d, new=%d", pos, layoutStruct[pos].effMinWidth, layoutStruct[pos].effMinWidth, w ); +#endif + fixedWidth -= layoutStruct[pos].width.value(); + cMinWidth -= w; + layoutStruct[pos].effMinWidth = w; + } + + } else if ( allColsArePercent ) { + int maxw = maxWidth; + int minw = minWidth; + int cminw = cMinWidth; + + for ( unsigned int pos = col; maxw > 0 && pos < lastCol; pos++ ) { + if ( layoutStruct[pos].effWidth.isPercent() && layoutStruct[pos].effWidth.value()>0 && fixedWidth <= cMinWidth) { + int w = layoutStruct[pos].effMinWidth; + w = kMax( w, cminw*layoutStruct[pos].effWidth.value()/totalPercent ); + w = kMin(layoutStruct[pos].effMinWidth+(cMinWidth-minw), w); +#ifdef DEBUG_LAYOUT + qDebug(" col %d: min=%d, effMin=%d, new=%d", pos, layoutStruct[pos].effMinWidth, layoutStruct[pos].effMinWidth, w ); +#endif + maxw -= layoutStruct[pos].effMaxWidth; + minw -= layoutStruct[pos].effMinWidth; + cMinWidth -= w; + layoutStruct[pos].effMinWidth = w; + } + } + } else { +#ifdef DEBUG_LAYOUT + qDebug("extending minWidth of cols %d-%d to %dpx currentMin=%d", col, lastCol-1, cMinWidth, minWidth ); +#endif + int maxw = maxWidth; + int minw = minWidth; + + // Give min to variable first, to fixed second, and to others third. + for ( unsigned int pos = col; maxw > 0 && pos < lastCol; pos++ ) { + if ( layoutStruct[pos].width.isFixed() && haveVariable && fixedWidth <= cMinWidth ) { + int w = kMax( int( layoutStruct[pos].effMinWidth ), layoutStruct[pos].width.value() ); + fixedWidth -= layoutStruct[pos].width.value(); + minw -= layoutStruct[pos].effMinWidth; +#ifdef DEBUG_LAYOUT + qDebug(" col %d: min=%d, effMin=%d, new=%d", pos, layoutStruct[pos].effMinWidth, layoutStruct[pos].effMinWidth, w ); +#endif + maxw -= layoutStruct[pos].effMaxWidth; + cMinWidth -= w; + layoutStruct[pos].effMinWidth = w; + } + } + + for ( unsigned int pos = col; maxw > 0 && pos < lastCol && minw < cMinWidth; pos++ ) { + if ( !(layoutStruct[pos].width.isFixed() && haveVariable && fixedWidth <= cMinWidth) ) { + int w = kMax( int( layoutStruct[pos].effMinWidth ), cMinWidth * layoutStruct[pos].effMaxWidth / maxw ); + w = kMin(layoutStruct[pos].effMinWidth+(cMinWidth-minw), w); + +#ifdef DEBUG_LAYOUT + qDebug(" col %d: min=%d, effMin=%d, new=%d", pos, layoutStruct[pos].effMinWidth, layoutStruct[pos].effMinWidth, w ); +#endif + maxw -= layoutStruct[pos].effMaxWidth; + minw -= layoutStruct[pos].effMinWidth; + cMinWidth -= w; + layoutStruct[pos].effMinWidth = w; + } + } + } + } + if ( !w.isPercent() ) { + if ( cMaxWidth > maxWidth ) { +#ifdef DEBUG_LAYOUT + qDebug("extending maxWidth of cols %d-%d to %dpx", col, lastCol-1, cMaxWidth ); +#endif + for ( unsigned int pos = col; maxWidth > 0 && pos < lastCol; pos++ ) { + int w = kMax( int( layoutStruct[pos].effMaxWidth ), cMaxWidth * layoutStruct[pos].effMaxWidth / maxWidth ); +#ifdef DEBUG_LAYOUT + qDebug(" col %d: max=%d, effMax=%d, new=%d", pos, layoutStruct[pos].effMaxWidth, layoutStruct[pos].effMaxWidth, w ); +#endif + maxWidth -= layoutStruct[pos].effMaxWidth; + cMaxWidth -= w; + layoutStruct[pos].effMaxWidth = w; + } + } + } else { + for ( unsigned int pos = col; pos < lastCol; pos++ ) + layoutStruct[pos].maxWidth = kMax(layoutStruct[pos].maxWidth, int(layoutStruct[pos].minWidth) ); + } + } + effWidthDirty = false; + +// qDebug("calcEffectiveWidth: tMaxWidth=%d", tMaxWidth ); + return tMaxWidth; +} + +/* gets all cells that originate in a column and have a cellspan > 1 + Sorts them by increasing cellspan +*/ +void AutoTableLayout::insertSpanCell( RenderTableCell *cell ) +{ + if ( !cell || cell == (RenderTableCell *)-1 || cell->colSpan() == 1 ) + return; + +// qDebug("inserting span cell %p with span %d", cell, cell->colSpan() ); + int size = spanCells.size(); + if ( !size || spanCells[size-1] != 0 ) { + spanCells.resize( size + 10 ); + for ( int i = 0; i < 10; i++ ) + spanCells[size+i] = 0; + size += 10; + } + + // add them in sort. This is a slow algorithm, and a binary search or a fast sorting after collection would be better + unsigned int pos = 0; + int span = cell->colSpan(); + while ( pos < spanCells.size() && spanCells[pos] && span > spanCells[pos]->colSpan() ) + pos++; + memmove( spanCells.data()+pos+1, spanCells.data()+pos, (size-pos-1)*sizeof( RenderTableCell * ) ); + spanCells[pos] = cell; +} + + +void AutoTableLayout::layout() +{ + // table layout based on the values collected in the layout structure. + int tableWidth = table->width() - table->bordersPaddingAndSpacing(); + int available = tableWidth; + int nEffCols = table->numEffCols(); + + if ( nEffCols != (int)layoutStruct.size() ) { + qWarning("WARNING: nEffCols is not equal to layoutstruct!" ); + fullRecalc(); + nEffCols = table->numEffCols(); + } +#ifdef DEBUG_LAYOUT + qDebug("AutoTableLayout::layout()"); +#endif + + if ( effWidthDirty ) + calcEffectiveWidth(); + +#ifdef DEBUG_LAYOUT + qDebug(" tableWidth=%d, nEffCols=%d", tableWidth, nEffCols ); + for ( int i = 0; i < nEffCols; i++ ) { + qDebug(" effcol %d is of type %d value %d, minWidth=%d, maxWidth=%d", + i, layoutStruct[i].width.type(), layoutStruct[i].width.value(), + layoutStruct[i].minWidth, layoutStruct[i].maxWidth ); + qDebug(" effective: type %d value %d, minWidth=%d, maxWidth=%d", + layoutStruct[i].effWidth.type(), layoutStruct[i].effWidth.value(), + layoutStruct[i].effMinWidth, layoutStruct[i].effMaxWidth ); + } +#endif + + bool havePercent = false; + bool haveRelative = false; + int totalRelative = 0; + int numVariable = 0; + int numFixed = 0; + int totalVariable = 0; + int totalFixed = 0; + int totalPercent = 0; + int allocVariable = 0; + + // fill up every cell with it's minWidth + for ( int i = 0; i < nEffCols; i++ ) { + int w = layoutStruct[i].effMinWidth; + layoutStruct[i].calcWidth = w; + available -= w; + Length& width = layoutStruct[i].effWidth; + switch( width.type()) { + case Percent: + havePercent = true; + totalPercent += width.value(); + break; + case Relative: + haveRelative = true; + totalRelative += width.value(); + break; + case Fixed: + numFixed++; + totalFixed += layoutStruct[i].effMaxWidth; + // fall through + break; + case Variable: + case Static: + numVariable++; + totalVariable += layoutStruct[i].effMaxWidth; + allocVariable += w; + } + } + + // allocate width to percent cols + if ( available > 0 && havePercent ) { + for ( int i = 0; i < nEffCols; i++ ) { + const Length &width = layoutStruct[i].effWidth; + if ( width.isPercent() ) { + int w = kMax ( int( layoutStruct[i].effMinWidth ), width.minWidth( tableWidth ) ); + available += layoutStruct[i].calcWidth - w; + layoutStruct[i].calcWidth = w; + } + } + if ( totalPercent > 100 ) { + // remove overallocated space from the last columns + int excess = tableWidth*(totalPercent-100)/100; + for ( int i = nEffCols-1; i >= 0; i-- ) { + if ( layoutStruct[i].effWidth.isPercent() ) { + int w = layoutStruct[i].calcWidth; + int reduction = kMin( w, excess ); + // the lines below might look inconsistent, but that's the way it's handled in mozilla + excess -= reduction; + int newWidth = kMax( int (layoutStruct[i].effMinWidth), w - reduction ); + available += w - newWidth; + layoutStruct[i].calcWidth = newWidth; + //qDebug("col %d: reducing to %d px (reduction=%d)", i, newWidth, reduction ); + } + } + } + } +#ifdef DEBUG_LAYOUT + qDebug("percent satisfied: available is %d", available); +#endif + + // then allocate width to fixed cols + if ( available > 0 ) { + for ( int i = 0; i < nEffCols; ++i ) { + const Length &width = layoutStruct[i].effWidth; + if ( width.isFixed() && width.value() > layoutStruct[i].calcWidth ) { + available += layoutStruct[i].calcWidth - width.value(); + layoutStruct[i].calcWidth = width.value(); + } + } + } +#ifdef DEBUG_LAYOUT + qDebug("fixed satisfied: available is %d", available); +#endif + + // now satisfy relative + if ( available > 0 ) { + for ( int i = 0; i < nEffCols; i++ ) { + const Length &width = layoutStruct[i].effWidth; + if ( width.isRelative() && width.value() ) { + // width=0* gets effMinWidth. + int w = width.value()*tableWidth/totalRelative; + available += layoutStruct[i].calcWidth - w; + layoutStruct[i].calcWidth = w; + } + } + } + + // now satisfy variable + if ( available > 0 && numVariable ) { + available += allocVariable; // this gets redistributed + //qDebug("redistributing %dpx to %d variable columns. totalVariable=%d", available, numVariable, totalVariable ); + for ( int i = 0; i < nEffCols; i++ ) { + const Length &width = layoutStruct[i].effWidth; + if ( width.isVariable() && totalVariable != 0 ) { + int w = kMax( int ( layoutStruct[i].calcWidth ), + available * layoutStruct[i].effMaxWidth / totalVariable ); + available -= w; + totalVariable -= layoutStruct[i].effMaxWidth; + layoutStruct[i].calcWidth = w; + } + } + } +#ifdef DEBUG_LAYOUT + qDebug("variable satisfied: available is %d", available ); +#endif + + // spread over fixed colums + if ( available > 0 && numFixed) { + // still have some width to spread, distribute to fixed columns + for ( int i = 0; i < nEffCols; i++ ) { + const Length &width = layoutStruct[i].effWidth; + if ( width.isFixed() ) { + int w = available * layoutStruct[i].effMaxWidth / totalFixed; + available -= w; + totalFixed -= layoutStruct[i].effMaxWidth; + layoutStruct[i].calcWidth += w; + } + } + } + +#ifdef DEBUG_LAYOUT + qDebug("after fixed distribution: available=%d", available ); +#endif + + // spread over percent colums + if ( available > 0 && hasPercent && totalPercent < 100) { + // still have some width to spread, distribute weighted to percent columns + for ( int i = 0; i < nEffCols; i++ ) { + const Length &width = layoutStruct[i].effWidth; + if ( width.isPercent() ) { + int w = available * width.value() / totalPercent; + available -= w; + totalPercent -= width.value(); + layoutStruct[i].calcWidth += w; + if (!available || !totalPercent) break; + } + } + } + +#ifdef DEBUG_LAYOUT + qDebug("after percent distribution: available=%d", available ); +#endif + + // spread over the rest + if ( available > 0 ) { + int total = nEffCols; + // still have some width to spread + int i = nEffCols; + while ( i-- ) { + int w = available / total; + available -= w; + total--; + layoutStruct[i].calcWidth += w; + } + } + +#ifdef DEBUG_LAYOUT + qDebug("after equal distribution: available=%d", available ); +#endif + // if we have overallocated, reduce every cell according to the difference between desired width and minwidth + // this seems to produce to the pixel exaxt results with IE. Wonder is some of this also holds for width distributing. + if ( available < 0 ) { + // Need to reduce cells with the following prioritization: + // (1) Variable + // (2) Relative + // (3) Fixed + // (4) Percent + // This is basically the reverse of how we grew the cells. + if (available < 0) { + int mw = 0; + for ( int i = nEffCols-1; i >= 0; i-- ) { + Length &width = layoutStruct[i].effWidth; + if (width.isVariable()) + mw += layoutStruct[i].calcWidth - layoutStruct[i].effMinWidth; + } + + for ( int i = nEffCols-1; i >= 0 && mw > 0; i-- ) { + Length &width = layoutStruct[i].effWidth; + if (width.isVariable()) { + int minMaxDiff = layoutStruct[i].calcWidth-layoutStruct[i].effMinWidth; + int reduce = available * minMaxDiff / mw; + layoutStruct[i].calcWidth += reduce; + available -= reduce; + mw -= minMaxDiff; + if ( available >= 0 ) + break; + } + } + } + + if (available < 0) { + int mw = 0; + for ( int i = nEffCols-1; i >= 0; i-- ) { + Length &width = layoutStruct[i].effWidth; + if (width.isRelative()) + mw += layoutStruct[i].calcWidth - layoutStruct[i].effMinWidth; + } + + for ( int i = nEffCols-1; i >= 0 && mw > 0; i-- ) { + Length &width = layoutStruct[i].effWidth; + if (width.isRelative()) { + int minMaxDiff = layoutStruct[i].calcWidth-layoutStruct[i].effMinWidth; + int reduce = available * minMaxDiff / mw; + layoutStruct[i].calcWidth += reduce; + available -= reduce; + mw -= minMaxDiff; + if ( available >= 0 ) + break; + } + } + } + + if (available < 0) { + int mw = 0; + for ( int i = nEffCols-1; i >= 0; i-- ) { + Length &width = layoutStruct[i].effWidth; + if (width.isFixed()) + mw += layoutStruct[i].calcWidth - layoutStruct[i].effMinWidth; + } + + for ( int i = nEffCols-1; i >= 0 && mw > 0; i-- ) { + Length &width = layoutStruct[i].effWidth; + if (width.isFixed()) { + int minMaxDiff = layoutStruct[i].calcWidth-layoutStruct[i].effMinWidth; + int reduce = available * minMaxDiff / mw; + layoutStruct[i].calcWidth += reduce; + available -= reduce; + mw -= minMaxDiff; + if ( available >= 0 ) + break; + } + } + } + + if (available < 0) { + int mw = 0; + for ( int i = nEffCols-1; i >= 0; i-- ) { + Length &width = layoutStruct[i].effWidth; + if (width.isPercent()) + mw += layoutStruct[i].calcWidth - layoutStruct[i].effMinWidth; + } + + for ( int i = nEffCols-1; i >= 0 && mw > 0; i-- ) { + Length &width = layoutStruct[i].effWidth; + if (width.isPercent()) { + int minMaxDiff = layoutStruct[i].calcWidth-layoutStruct[i].effMinWidth; + int reduce = available * minMaxDiff / mw; + layoutStruct[i].calcWidth += reduce; + available -= reduce; + mw -= minMaxDiff; + if ( available >= 0 ) + break; + } + } + } + } + + //qDebug( " final available=%d", available ); + + int pos = 0; + for ( int i = 0; i < nEffCols; i++ ) { +#ifdef DEBUG_LAYOUT + qDebug("col %d: %d (width %d)", i, pos, layoutStruct[i].calcWidth ); +#endif + table->columnPos[i] = pos; + pos += layoutStruct[i].calcWidth + table->borderHSpacing(); + } + table->columnPos[table->columnPos.size()-1] = pos; + +} + + +void AutoTableLayout::calcPercentages() const +{ + total_percent = 0; + for ( unsigned int i = 0; i < layoutStruct.size(); i++ ) { + if ( layoutStruct[i].width.isPercent() ) + total_percent += layoutStruct[i].width.value(); + } + percentagesDirty = false; +} + +#undef DEBUG_LAYOUT diff --git a/khtml/rendering/table_layout.h b/khtml/rendering/table_layout.h new file mode 100644 index 000000000..bc28a91a5 --- /dev/null +++ b/khtml/rendering/table_layout.h @@ -0,0 +1,112 @@ +/* + * This file is part of the HTML rendering engine for KDE. + * + * Copyright (C) 2002 Lars Knoll (knoll@kde.org) + * (C) 2002 Dirk Mueller (mueller@kde.org) + * + * 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. + * + * 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. + * + */ +#ifndef TABLE_LAYOUT_H +#define TABLE_LAYOUT_H + +#include <qmemarray.h> +#include <misc/khtmllayout.h> + +namespace khtml { + +class RenderTable; +class RenderTableCell; + +// ------------------------------------------------------------------------- + +class TableLayout +{ +public: + TableLayout( RenderTable *t ) : table( t ) {} + virtual ~TableLayout() {} + + virtual void calcMinMaxWidth() = 0; + virtual void layout() = 0; + +protected: + RenderTable *table; +}; + +// ------------------------------------------------------------------------- + +class FixedTableLayout : public TableLayout +{ +public: + FixedTableLayout( RenderTable *table ); + ~FixedTableLayout(); + + void calcMinMaxWidth(); + void layout(); + +protected: + int calcWidthArray(); + + QMemArray<Length> width; +}; + +// ------------------------------------------------------------------------- + +class AutoTableLayout : public TableLayout +{ +public: + AutoTableLayout( RenderTable *table ); + ~AutoTableLayout(); + + void calcMinMaxWidth(); + void layout(); + + +protected: + void fullRecalc(); + void recalcColumn( int effCol ); + int totalPercent() const { + if ( percentagesDirty ) + calcPercentages(); + return total_percent; + } + void calcPercentages() const; + int calcEffectiveWidth(); + void insertSpanCell( RenderTableCell *cell ); + + struct Layout { + Layout() : minWidth( 1 ), maxWidth( 1 ), + effMinWidth( 0 ), effMaxWidth( 0 ), + calcWidth( 0 ) {} + Length width; + Length effWidth; + short minWidth; + int maxWidth; + short effMinWidth; + int effMaxWidth; + short calcWidth; + }; + + QMemArray<Layout> layoutStruct; + QMemArray<RenderTableCell *>spanCells; + bool hasPercent : 1; + mutable bool percentagesDirty : 1; + mutable bool effWidthDirty : 1; + mutable unsigned short total_percent; +}; + +} +#endif diff --git a/khtml/rendering/table_layout.txt b/khtml/rendering/table_layout.txt new file mode 100644 index 000000000..14a74bd1e --- /dev/null +++ b/khtml/rendering/table_layout.txt @@ -0,0 +1,74 @@ +CSS describes two ways of layouting tables. Auto layout (the NS4 +compliant HTML table layout) and fixed layout. The fixed layout +strategy is described in detail in the CSS specs and will not be +repeated here. + +Due to the fact that two layout strategies exist, it is rather natural +to encapsulate the layouting process in a TableLayout class. Two types +(FixedTableLayout and AutoTableLayout) exist. AutoTableLayout is the +default and also need a quirks flags for NS compatibility +mode. FixedTableLayout will be used if a style rule sets the +table-layout property to fixed. + +The grid of table cells is stored in each table section, as spans +can't pass section borders. Changing the number of cols in the grid +has to be done by the table to keep all grids (for all sections) in +sync. The grid only stores effective columns. The table below would +only result in one column being allocated in the grid: + +<table><tr><td colspan=1000>foo</td></tr></table> + +Once a colspan get's used, the column is split into its subparts. To +do this, the table has to store the colspans of effective columns in a +structure. + + + + +NS & Mozilla compliant table layouting algorithm (AutoTableLayout) +------------------------------------------------------------------ + +The table layout algorithm computes a set of layout hints from the +informations in the table cells, <col> elements and style +sheets. Hints from different sources are treated a bit differently in +the collection stage. + +In addition certain operations are only done in quirks (NS4 compat) +mode. + +Resizing the table doesn't require you to build up this information +again. All that is needed is a list of layout hints for each column. + +The algorithm below describes to the best of our knowledge the way +table alyouting is handled in a NS compatible way. + +There are two stages, the collection stage (assocaited with +calcMinMaxWidth() in khtml) and the stage assigning width to cells +(associated with layout()). + +A set of hinted widths are used to determine the final table layout in +the layouting stage. + +enum hintType { + MinWidth, + DesiredWidth, + FixedWidth, + MinWidthAdjusted, + DesiredWidthAdjusted, + FixedWidthAdjusted, + PercentWidth, + PercentWidthAdjusted, + ProportionalWidth +}; + +One width (in pixels) for each hintType describes how the column +wishes to be layouted. The layouting stage operates only on an array +of hints for each column. + +An additional totalCellSpacing variable is used to know about the +remaining width to distribute. + +Collection stage: +----------------- + + |