diff options
Diffstat (limited to 'dw/textblock.cc')
-rw-r--r-- | dw/textblock.cc | 1528 |
1 files changed, 1181 insertions, 347 deletions
diff --git a/dw/textblock.cc b/dw/textblock.cc index 2c4ca20b..21de1991 100644 --- a/dw/textblock.cc +++ b/dw/textblock.cc @@ -1,7 +1,7 @@ /* * Dillo Widget * - * Copyright 2005-2007, 2012-2013 Sebastian Geerken <sgeerken@dillo.org> + * Copyright 2005-2007, 2012-2014 Sebastian Geerken <sgeerken@dillo.org> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,13 +25,14 @@ #include "../lout/debug.hh" #include <stdio.h> -#include <math.h> +#include <math.h> // remove again? +#include <limits.h> /* * Local variables */ - /* The tooltip under mouse pointer in current textblock. No ref. hold. - * (having one per view looks not worth the extra clutter). */ +/* The tooltip under mouse pointer in current textblock. No ref. hold. + * (having one per view looks not worth the extra clutter). */ static dw::core::style::Tooltip *hoverTooltip = NULL; @@ -82,7 +83,7 @@ void Textblock::WordImgRenderer::getBgArea (int *x, int *y, int *width, *x = textblock->allocation.x + this->xWordWidget; *y = textblock->lineYOffsetCanvas (line); *width = textblock->words->getRef(wordNo)->size.width; - *height = line->boxAscent + line->boxDescent; + *height = line->borderAscent + line->borderDescent; } void Textblock::WordImgRenderer::getRefArea (int *xRef, int *yRef, @@ -193,7 +194,7 @@ void Textblock::setPenaltyHyphen (int penaltyHyphen) { penalties[PENALTY_HYPHEN][0] = penaltyHyphen; } - + void Textblock::setPenaltyHyphen2 (int penaltyHyphen2) { penalties[PENALTY_HYPHEN][1] = penaltyHyphen2; @@ -224,17 +225,18 @@ Textblock::Textblock (bool limitTextWidth) { DBG_OBJ_CREATE ("dw::Textblock"); registerName ("dw::Textblock", &CLASS_ID); - setFlags (BLOCK_LEVEL); - setFlags (USES_HINTS); setButtonSensitive(true); + containingBlock = NULL; hasListitemValue = false; - innerPadding = 0; + leftInnerPadding = 0; line1Offset = 0; ignoreLine1OffsetSometimes = false; mustQueueResize = false; redrawY = 0; + DBG_OBJ_SET_NUM ("redrawY", redrawY); lastWordDrawn = -1; + DBG_OBJ_SET_NUM ("lastWordDrawn", lastWordDrawn); /* * The initial sizes of lines and words should not be @@ -250,22 +252,23 @@ Textblock::Textblock (bool limitTextWidth) nonTemporaryLines = 0; words = new misc::NotSoSimpleVector <Word> (1); anchors = new misc::SimpleVector <Anchor> (1); - - //DBG_OBJ_SET_NUM(this, "num_lines", num_lines); + outOfFlowMgr = NULL; wrapRefLines = wrapRefParagraphs = -1; - //DBG_OBJ_SET_NUM(this, "last_line_width", last_line_width); - //DBG_OBJ_SET_NUM(this, "last_line_par_min", last_line_par_min); - //DBG_OBJ_SET_NUM(this, "last_line_par_max", last_line_par_max); - //DBG_OBJ_SET_NUM(this, "wrap_ref", wrap_ref); + DBG_OBJ_SET_NUM ("lines.size", lines->size ()); + DBG_OBJ_SET_NUM ("words.size", words->size ()); + DBG_OBJ_SET_NUM ("wrapRefLines", wrapRefLines); + DBG_OBJ_SET_NUM ("wrapRefParagraphs", wrapRefParagraphs); hoverLink = -1; - // random values - availWidth = 100; - availAscent = 100; - availDescent = 0; + // -1 means undefined. + lineBreakWidth = -1; + DBG_OBJ_SET_NUM ("lineBreakWidth", lineBreakWidth); + + verticalOffset = 0; + DBG_OBJ_SET_NUM ("verticalOffset", verticalOffset); this->limitTextWidth = limitTextWidth; @@ -276,6 +279,8 @@ Textblock::Textblock (bool limitTextWidth) hlEnd[layer].index = 0; hlEnd[layer].nChar = 0; } + + initNewLine (); } Textblock::~Textblock () @@ -285,18 +290,8 @@ Textblock::~Textblock () /* make sure not to call a free'd tooltip (very fast overkill) */ hoverTooltip = NULL; - for (int i = 0; i < words->size(); i++) { - Word *word = words->getRef (i); - - if (word->content.type == core::Content::WIDGET) - delete word->content.widget; - - removeWordImgRenderer (i); - removeSpaceImgRenderer (i); - - word->style->unref (); - word->spaceStyle->unref (); - } + for (int i = 0; i < words->size(); i++) + cleanupWord (i); for (int i = 0; i < anchors->size(); i++) { Anchor *anchor = anchors->getRef (i); @@ -309,6 +304,16 @@ Textblock::~Textblock () delete words; delete anchors; + if(outOfFlowMgr) { + // I feel more comfortable by letting the textblock delete these + // widgets, instead of doing this in ~OutOfFlowMgr. + + for (int i = 0; i < outOfFlowMgr->getNumWidgets (); i++) + delete outOfFlowMgr->getWidget (i); + + delete outOfFlowMgr; + } + /* Make sure we don't own widgets anymore. Necessary before call of parent class destructor. (???) */ words = NULL; @@ -323,51 +328,132 @@ Textblock::~Textblock () */ void Textblock::sizeRequestImpl (core::Requisition *requisition) { - PRINTF ("[%p] SIZE_REQUEST: ...\n", this); + DBG_OBJ_ENTER0 ("resize", 0, "sizeRequestImpl"); + + int newLineBreakWidth = getAvailWidth (true); + if (newLineBreakWidth != lineBreakWidth) { + lineBreakWidth = newLineBreakWidth; + wrapRefLines = 0; + DBG_OBJ_SET_NUM ("lineBreakWidth", lineBreakWidth); + DBG_OBJ_SET_NUM ("wrapRefLines", wrapRefLines); + } rewrap (); showMissingLines (); if (lines->size () > 0) { - Line *lastLine = lines->getRef (lines->size () - 1); - requisition->width = lastLine->maxLineWidth; - - PRINTF ("[%p] SIZE_REQUEST: lastLine->maxLineWidth = %d\n", - this, lastLine->maxLineWidth); - - PRINTF ("[%p] SIZE_REQUEST: lines[0]->boxAscent = %d\n", - this, lines->getRef(0)->boxAscent); - PRINTF ("[%p] SIZE_REQUEST: lines[%d]->top = %d\n", - this, lines->size () - 1, lastLine->top); - PRINTF ("[%p] SIZE_REQUEST: lines[%d]->boxAscent = %d\n", - this, lines->size () - 1, lastLine->boxAscent); - PRINTF ("[%p] SIZE_REQUEST: lines[%d]->boxDescent = %d\n", - this, lines->size () - 1, lastLine->boxDescent); - - /* Note: the breakSpace of the last line is ignored, so breaks - at the end of a textblock are not visible. */ - requisition->ascent = lines->getRef(0)->boxAscent; - requisition->descent = lastLine->top - + lastLine->boxAscent + lastLine->boxDescent - - lines->getRef(0)->boxAscent; + Line *firstLine = lines->getRef(0), *lastLine = lines->getLastRef (); + + // Note: the breakSpace of the last line is ignored, so breaks + // at the end of a textblock are not visible. + + requisition->width = lastLine->maxLineWidth + leftInnerPadding + + getStyle()->boxDiffWidth (); + + // Also regard collapsing of this widget top margin and the top + // margin of the first line box: + requisition->ascent = calcVerticalBorder (getStyle()->padding.top, + getStyle()->borderWidth.top, + getStyle()->margin.top, + firstLine->borderAscent, + firstLine->marginAscent); + + // And here, regard collapsing of this widget bottom margin and the + // bottom margin of the last line box: + requisition->descent = + // (BTW, this line: + lastLine->top - firstLine->borderAscent + lastLine->borderAscent + + // ... is 0 for a block with one line, so special handling + // for this case is not necessary.) + calcVerticalBorder (getStyle()->padding.bottom, + getStyle()->borderWidth.bottom, + getStyle()->margin.bottom, + lastLine->borderDescent, lastLine->marginDescent); } else { - requisition->width = 0; // before: lastLineWidth; - requisition->ascent = 0; - requisition->descent = 0; + requisition->width = leftInnerPadding + getStyle()->boxDiffWidth (); + requisition->ascent = getStyle()->boxOffsetY (); + requisition->descent = getStyle()->boxRestHeight ();; } - PRINTF ("[%p] SIZE_REQUEST: inner padding = %d, boxDiffWidth = %d\n", - this, innerPadding, getStyle()->boxDiffWidth ()); + requisition->ascent += verticalOffset; + + if (mustBeWidenedToAvailWidth ()) { + DBG_OBJ_MSGF ("resize", 1, + "before considering lineBreakWidth (= %d): %d * (%d + %d)", + lineBreakWidth, requisition->width, requisition->ascent, + requisition->descent); + if (requisition->width < lineBreakWidth) + requisition->width = lineBreakWidth; + } else + DBG_OBJ_MSG ("resize", 1, "lineBreakWidth needs no consideration"); - requisition->width += innerPadding + getStyle()->boxDiffWidth (); - requisition->ascent += getStyle()->boxOffsetY (); - requisition->descent += getStyle()->boxRestHeight (); + DBG_OBJ_MSGF ("resize", 1, "before correction: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); - if (requisition->width < availWidth) - requisition->width = availWidth; + correctRequisition (requisition, core::splitHeightPreserveAscent); - PRINTF ("[%p] SIZE_REQUEST: %d x %d + %d\n", this, requisition->width, - requisition->ascent, requisition->descent); + // Dealing with parts out of flow, which may overlap the borders of + // the text block. Base lines are ignored here: they do not play a + // role (currently) and caring about them (for the future) would + // cause too much problems. + + // Notice that the order is not typical: correctRequisition should + // be the last call. However, calling correctRequisition after + // outOfFlowMgr->getSize may result again in a size which is too + // small for floats, so triggering again (and again) the resize + // idle function resulting in CPU hogging. See also + // getExtremesImpl. + // + // Is this really what we want? An alternative could be that + // OutOfFlowMgr::getSize honours CSS attributes an corrected sizes. + + DBG_OBJ_MSGF ("resize", 1, "before considering OOF widgets: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + + if (outOfFlowMgr) { + int oofWidth, oofHeight; + outOfFlowMgr->getSize (requisition, &oofWidth, &oofHeight); + + // Floats must be within the *content* area, not the *margin* + // area (which is equivalent to the requisition / + // allocation). For this reason, boxRestWidth() and + // boxRestHeight() must be considered. + + if (oofWidth + boxRestWidth () > requisition->width) + requisition->width = oofWidth + boxRestWidth (); + if (oofHeight + boxRestHeight () + > requisition->ascent + requisition->descent) + requisition->descent = + oofHeight + boxRestHeight () - requisition->ascent; + } + + DBG_OBJ_MSGF ("resize", 1, "final: %d * (%d + %d)", + requisition->width, requisition->ascent, requisition->descent); + DBG_OBJ_LEAVE (); +} + +int Textblock::calcVerticalBorder (int widgetPadding, int widgetBorder, + int widgetMargin, int lineBorderTotal, + int lineMarginTotal) +{ + DBG_OBJ_ENTER ("resize", 0, "calcVerticalBorder", "%d, %d, %d, %d, %d", + widgetPadding, widgetBorder, widgetMargin, lineBorderTotal, + lineMarginTotal); + + int result; + + if (widgetPadding == 0 && widgetBorder == 0) { + if (lineMarginTotal - lineBorderTotal >= widgetMargin) + result = lineMarginTotal; + else + result = widgetMargin + lineBorderTotal; + } else + result = lineMarginTotal + widgetPadding + widgetBorder + widgetMargin; + + DBG_OBJ_MSGF ("resize", 0, "=> %d", result); + DBG_OBJ_LEAVE (); + + return result; } /** @@ -375,67 +461,162 @@ void Textblock::sizeRequestImpl (core::Requisition *requisition) */ void Textblock::getWordExtremes (Word *word, core::Extremes *extremes) { - if (word->content.type == core::Content::WIDGET) { - if (word->content.widget->usesHints ()) - word->content.widget->getExtremes (extremes); - else { - if (core::style::isPerLength - (word->content.widget->getStyle()->width)) { - extremes->minWidth = 0; - if (word->content.widget->hasContents ()) - extremes->maxWidth = 1000000; - else - extremes->maxWidth = 0; - } else if (core::style::isAbsLength - (word->content.widget->getStyle()->width)) { - /* Fixed lengths are only applied to the content, so we have to - * add padding, border and margin. */ - extremes->minWidth = extremes->maxWidth = - core::style::absLengthVal (word->content.widget->getStyle() - ->width) - + word->style->boxDiffWidth (); - } else - word->content.widget->getExtremes (extremes); - } - } else { - extremes->minWidth = word->size.width; - extremes->maxWidth = word->size.width; - } + if (word->content.type == core::Content::WIDGET_IN_FLOW) + word->content.widget->getExtremes (extremes); + else + extremes->minWidth = extremes->minWidthIntrinsic = extremes->maxWidth = + extremes->maxWidthIntrinsic = extremes->adjustmentWidth = + word->size.width; } void Textblock::getExtremesImpl (core::Extremes *extremes) { - PRINTF ("[%p] GET_EXTREMES ...\n", this); + DBG_OBJ_ENTER0 ("resize", 0, "getExtremesImpl"); + + // TODO Can extremes depend on the available width? Should not; if + // they do, the following code must be reactivated, but it causes + // an endless recursion. +#if 0 + int newLineBreakWidth = getAvailWidth (true); + if (newLineBreakWidth != lineBreakWidth) { + lineBreakWidth = newLineBreakWidth; + wrapRefParagraphs = 0; + DBG_OBJ_SET_NUM ("lineBreakWidth", lineBreakWidth); + DBG_OBJ_SET_NUM ("wrapRefParagraphs", wrapRefLines); + } +#endif fillParagraphs (); if (paragraphs->size () == 0) { /* empty page */ extremes->minWidth = 0; + extremes->minWidthIntrinsic = 0; extremes->maxWidth = 0; + extremes->maxWidthIntrinsic = 0; + extremes->adjustmentWidth = 0; } else { Paragraph *lastPar = paragraphs->getLastRef (); extremes->minWidth = lastPar->maxParMin; + extremes->minWidthIntrinsic = lastPar->maxParMinIntrinsic; extremes->maxWidth = lastPar->maxParMax; + extremes->maxWidthIntrinsic = lastPar->maxParMaxIntrinsic; + extremes->adjustmentWidth = lastPar->maxParAdjustmentWidth; + + DBG_OBJ_MSGF ("resize", 1, "paragraphs[%d]->maxParMin = %d (%d)", + paragraphs->size () - 1, lastPar->maxParMin, + lastPar->maxParMinIntrinsic); + DBG_OBJ_MSGF ("resize", 1, "paragraphs[%d]->maxParMax = %d (%d)", + paragraphs->size () - 1, lastPar->maxParMax, + lastPar->maxParMaxIntrinsic); } - int diff = innerPadding + getStyle()->boxDiffWidth (); + DBG_OBJ_MSGF ("resize", 0, "after considering paragraphs: %d (%d) / %d (%d)", + extremes->minWidth, extremes->minWidthIntrinsic, + extremes->maxWidth, extremes->maxWidthIntrinsic); + + int diff = leftInnerPadding + getStyle()->boxDiffWidth (); extremes->minWidth += diff; + extremes->minWidthIntrinsic += diff; extremes->maxWidth += diff; + extremes->maxWidthIntrinsic += diff; + extremes->adjustmentWidth += diff; + + DBG_OBJ_MSGF ("resize", 0, "after adding diff: %d (%d) / %d (%d)", + extremes->minWidth, extremes->minWidthIntrinsic, + extremes->maxWidth, extremes->maxWidthIntrinsic); + + // For the order, see similar reasoning in sizeRequestImpl. + + correctExtremes (extremes, true); - PRINTF ("[%p] GET_EXTREMES => %d / %d\n", - this, extremes->minWidth, extremes->maxWidth); + DBG_OBJ_MSGF ("resize", 0, "after correction: %d (%d) / %d (%d)", + extremes->minWidth, extremes->minWidthIntrinsic, + extremes->maxWidth, extremes->maxWidthIntrinsic); + + if (outOfFlowMgr) { + int oofMinWidth, oofMaxWidth; + outOfFlowMgr->getExtremes (extremes, &oofMinWidth, &oofMaxWidth); + + DBG_OBJ_MSGF ("resize", 1, "OOFM correction: %d / %d", + oofMinWidth, oofMaxWidth); + + extremes->minWidth = misc::max (extremes->minWidth, oofMinWidth); + extremes->minWidthIntrinsic = + misc::max (extremes->minWidthIntrinsic, oofMinWidth); + extremes->maxWidth = misc::max (extremes->maxWidth, oofMaxWidth); + extremes->maxWidthIntrinsic = + misc::max (extremes->maxWidthIntrinsic, oofMinWidth); + extremes->adjustmentWidth = + misc::max (extremes->adjustmentWidth, oofMinWidth); + } + + DBG_OBJ_MSGF ("resize", 0, + "finally, after considering OOFM: %d (%d) / %d (%d)", + extremes->minWidth, extremes->minWidthIntrinsic, + extremes->maxWidth, extremes->maxWidthIntrinsic); + DBG_OBJ_LEAVE (); } void Textblock::sizeAllocateImpl (core::Allocation *allocation) { - PRINTF ("[%p] SIZE_ALLOCATE: %d, %d, %d x %d + %d\n", - this, allocation->x, allocation->y, allocation->width, - allocation->ascent, allocation->descent); + DBG_OBJ_ENTER ("resize", 0, "sizeAllocateImpl", "%d, %d; %d * (%d + %d)", + allocation->x, allocation->y, allocation->width, + allocation->ascent, allocation->descent); showMissingLines (); + // In some cases, this allocation results in child allocation which + // exceed the top of this allocation, which will then result in an + // endless resize idle cascade and CPU hogging (when floats come + // into play). + // + // Example: + // + // <div id="id1" style="height: 50px"> + // <div id="id2">...</div> + // <div> + // + // Assume that the inner section, div#id2, has a height of 200px = + // 100px (ascent) + 100px (descent). For the outer section, + // div#id1, this will be initially calculated for the size, but + // then (because of CSS 'height') reduced to 50px = 50px (ascent) + + // 0px (descent). Without the following correction, the inner + // section (div#id2) would be allocated at 50px top of the + // allocation of the outer section: childAllocation->y = + // allocation->y - 50. + // + // For this reason, we calculat "childBaseAllocation", which will + // avoid this case; in the example above, the height will be 50px = + // 100px (ascent) - 50px (descent: negative). + + childBaseAllocation.x = allocation->x; + childBaseAllocation.y = allocation->y; + childBaseAllocation.width = allocation->width; + childBaseAllocation.ascent = + misc::max (allocation->ascent, + // Reconstruct the initial size; see + // Textblock::sizeRequestImpl. + (lines->size () > 0 ? + calcVerticalBorder (getStyle()->padding.top, + getStyle()->borderWidth.top, + getStyle()->margin.top, + lines->getRef(0)->borderAscent, + lines->getRef(0)->marginAscent) : + getStyle()->boxOffsetY ()) + verticalOffset); + childBaseAllocation.descent = + allocation->ascent + allocation->descent - childBaseAllocation.ascent; + + DBG_OBJ_SET_NUM ("childBaseAllocation.x", childBaseAllocation.x); + DBG_OBJ_SET_NUM ("childBaseAllocation.y", childBaseAllocation.y); + DBG_OBJ_SET_NUM ("childBaseAllocation.width", childBaseAllocation.width); + DBG_OBJ_SET_NUM ("childBaseAllocation.ascent", childBaseAllocation.ascent); + DBG_OBJ_SET_NUM ("childBaseAllocation.descent", childBaseAllocation.descent); + + if (containingBlock->outOfFlowMgr) + containingBlock->outOfFlowMgr->sizeAllocateStart (this, allocation); + int lineIndex, wordIndex; Line *line; Word *word; @@ -443,13 +624,32 @@ void Textblock::sizeAllocateImpl (core::Allocation *allocation) core::Allocation childAllocation; core::Allocation *oldChildAllocation; - if (allocation->width != this->allocation.width) { + if (allocation->x != this->allocation.x || + allocation->y != this->allocation.y || + allocation->width != this->allocation.width) { redrawY = 0; + DBG_OBJ_SET_NUM ("redrawY", redrawY); } + DBG_OBJ_MSG_START (); + for (lineIndex = 0; lineIndex < lines->size (); lineIndex++) { + DBG_OBJ_MSGF ("resize", 1, "line %d", lineIndex); + DBG_OBJ_MSG_START (); + + // Especially for floats, allocation->width may be different + // from the line break width, so that for centered and right + // text, the offsets have to be recalculated again. However, if + // the allocation width is greater than the line break width, + // due to wide unbreakable lines (large image etc.), use the + // original line break width. + calcTextOffset (lineIndex, + misc::min (childBaseAllocation.width, lineBreakWidth)); + line = lines->getRef (lineIndex); - xCursor = lineXOffsetWidget (line); + xCursor = line->textOffset; + + DBG_OBJ_MSGF ("resize", 1, "xCursor = %d (initially)", xCursor); for (wordIndex = line->firstWord; wordIndex <= line->lastWord; wordIndex++) { @@ -457,11 +657,21 @@ void Textblock::sizeAllocateImpl (core::Allocation *allocation) if (wordIndex == lastWordDrawn + 1) { redrawY = misc::min (redrawY, lineYOffsetWidget (line)); + DBG_OBJ_SET_NUM ("redrawY", redrawY); } - if (word->content.type == core::Content::WIDGET) { + if (word->content.type == core::Content::WIDGET_IN_FLOW) { + DBG_OBJ_MSGF ("resize", 1, + "allocating widget in flow: line %d, word %d", + lineIndex, wordIndex); + + childAllocation.x = xCursor + childBaseAllocation.x; + + DBG_OBJ_MSGF ("resize", 1, "childAllocation.x = %d + %d = %d", + xCursor, childBaseAllocation.x, childAllocation.x); + /** \todo Justification within the line is done here. */ - childAllocation.x = xCursor + allocation->x; + /* align=top: childAllocation.y = line->top + allocation->y; */ @@ -469,16 +679,19 @@ void Textblock::sizeAllocateImpl (core::Allocation *allocation) /* align=bottom (base line) */ /* Commented lines break the n2 and n3 test cases at * http://www.dillo.org/test/img/ */ - childAllocation.y = - lineYOffsetCanvasAllocation (line, allocation) - + (line->boxAscent - word->size.ascent) - - word->content.widget->getStyle()->margin.top; - childAllocation.width = word->size.width; - childAllocation.ascent = word->size.ascent - + word->content.widget->getStyle()->margin.top; - childAllocation.descent = word->size.descent - + word->content.widget->getStyle()->margin.bottom; + childAllocation.y = lineYOffsetCanvas (line) + + (line->borderAscent - word->size.ascent); + + DBG_OBJ_MSGF ("resize", 1, + "childAllocation.y = %d + (%d - %d) = %d", + lineYOffsetCanvas (line), + line->borderAscent, word->size.ascent, + childAllocation.y); + childAllocation.width = word->size.width; + childAllocation.ascent = word->size.ascent; + childAllocation.descent = word->size.descent; + oldChildAllocation = word->content.widget->getAllocation(); if (childAllocation.x != oldChildAllocation->x || @@ -488,9 +701,11 @@ void Textblock::sizeAllocateImpl (core::Allocation *allocation) * so we need to redraw from this line onwards. */ redrawY = misc::min (redrawY, lineYOffsetWidget (line)); + DBG_OBJ_SET_NUM ("redrawY", redrawY); if (word->content.widget->wasAllocated ()) { redrawY = misc::min (redrawY, oldChildAllocation->y - this->allocation.y); + DBG_OBJ_SET_NUM ("redrawY", redrawY); } } else if (childAllocation.ascent + childAllocation.descent != @@ -513,40 +728,134 @@ void Textblock::sizeAllocateImpl (core::Allocation *allocation) core::Content::BREAK)) { int childChangedY = - misc::min(childAllocation.y - allocation->y + + misc::min(childAllocation.y - childBaseAllocation.y + childAllocation.ascent + childAllocation.descent, oldChildAllocation->y - this->allocation.y + oldChildAllocation->ascent + oldChildAllocation->descent); redrawY = misc::min (redrawY, childChangedY); + DBG_OBJ_SET_NUM ("redrawY", redrawY); } else { redrawY = misc::min (redrawY, lineYOffsetWidget (line)); + DBG_OBJ_SET_NUM ("redrawY", redrawY); } } word->content.widget->sizeAllocate (&childAllocation); } xCursor += (word->size.width + word->effSpace); + DBG_OBJ_MSGF ("resize", 1, "xCursor = %d (after word %d)", + xCursor, wordIndex); + DBG_MSG_WORD("resize", 1, "<i>that is:</i> ", wordIndex, ""); } + + DBG_OBJ_MSG_END (); } + DBG_OBJ_MSG_END (); + + if (containingBlock->outOfFlowMgr) + containingBlock->outOfFlowMgr->sizeAllocateEnd (this); + for (int i = 0; i < anchors->size(); i++) { Anchor *anchor = anchors->getRef(i); int y; - if (anchor->wordIndex >= words->size()) { + if (anchor->wordIndex >= words->size() || + // Also regard not-yet-existing lines. + lines->size () <= 0 || + anchor->wordIndex > lines->getLastRef()->lastWord) { y = allocation->y + allocation->ascent + allocation->descent; } else { Line *line = lines->getRef(findLineOfWord (anchor->wordIndex)); - y = lineYOffsetCanvasAllocation (line, allocation); + y = lineYOffsetCanvas (line); } changeAnchor (anchor->name, y); } + + DBG_OBJ_LEAVE (); +} + +int Textblock::getAvailWidthOfChild (Widget *child, bool forceValue) +{ + DBG_OBJ_ENTER ("resize", 0, "Textblock/getAvailWidthOfChild", "%p, %s", + child, forceValue ? "true" : "false"); + + int width; + + if (child->getStyle()->width == core::style::LENGTH_AUTO) { + // No width specified: similar to standard implementation (see + // there), but "leftInnerPadding" has to be considered, too. + DBG_OBJ_MSG ("resize", 1, "no specification"); + if (forceValue) + width = misc::max (getAvailWidth (true) - boxDiffWidth () + - leftInnerPadding, + 0); + else + width = -1; + } else + width = Widget::getAvailWidthOfChild (child, forceValue); + + if (forceValue && this == child->getContainer () && + !mustBeWidenedToAvailWidth ()) { + core::Extremes extremes; + getExtremes (&extremes); + if (width > extremes.maxWidth - boxDiffWidth () - leftInnerPadding) + width = extremes.maxWidth - boxDiffWidth () - leftInnerPadding; + } + + DBG_OBJ_MSGF ("resize", 1, "=> %d", width); + DBG_OBJ_LEAVE (); + return width; +} + + + +void Textblock::containerSizeChangedForChildren () +{ + DBG_OBJ_ENTER0 ("resize", 0, "containerSizeChangedForChildren"); + + for (int i = 0; i < words->size (); i++) { + Word *word = words->getRef (i); + if (word->content.type == core::Content::WIDGET_IN_FLOW) + word->content.widget->containerSizeChanged (); + } + + if (outOfFlowMgr) + outOfFlowMgr->containerSizeChangedForChildren (); + + DBG_OBJ_LEAVE (); +} + +bool Textblock::affectsSizeChangeContainerChild (Widget *child) +{ + DBG_OBJ_ENTER ("resize", 0, + "Textblock/affectsSizeChangeContainerChild", "%p", child); + + // See Textblock::getAvailWidthForChild() and Textblock::oofSizeChanged(): + // Extremes changes affect the size of the child, too: + bool ret; + if (!mustBeWidenedToAvailWidth () && + (extremesQueued () || extremesChanged ())) + ret = true; + else + ret = Widget::affectsSizeChangeContainerChild (child); + + DBG_OBJ_MSGF ("resize", 1, "=> %s", ret ? "true" : "false"); + DBG_OBJ_LEAVE (); + return ret; +} + +bool Textblock::usesAvailWidth () +{ + return true; } void Textblock::resizeDrawImpl () { + DBG_OBJ_ENTER0 ("draw", 0, "resizeDrawImpl"); + queueDrawArea (0, redrawY, allocation.width, getHeight () - redrawY); if (lines->size () > 0) { Line *lastLine = lines->getRef (lines->size () - 1); @@ -554,99 +863,141 @@ void Textblock::resizeDrawImpl () * draw any new added words (see sizeAllocateImpl()). */ lastWordDrawn = lastLine->lastWord; + DBG_OBJ_SET_NUM ("lastWordDrawn", lastWordDrawn); } redrawY = getHeight (); + DBG_OBJ_SET_NUM ("redrawY", redrawY); + + DBG_OBJ_LEAVE (); } void Textblock::markSizeChange (int ref) { - PRINTF ("[%p] MARK_SIZE_CHANGE (%d): %d => ...\n", this, ref, wrapRefLines); + DBG_OBJ_ENTER ("resize", 0, "markSizeChange", "%d", ref); - /* By the way: ref == -1 may have two different causes: (i) flush() - calls "queueResize (-1, true)", when no rewrapping is necessary; - and (ii) a word may have parentRef == -1 , when it is not yet - added to a line. In the latter case, nothing has to be done - now, but addLine(...) will do everything necessary. */ - if (ref != -1) { - if (wrapRefLines == -1) - wrapRefLines = ref; - else - wrapRefLines = misc::min (wrapRefLines, ref); - } + if (OutOfFlowMgr::isRefOutOfFlow (ref)) { + assert (outOfFlowMgr != NULL); + outOfFlowMgr->markSizeChange (ref); + } else { + PRINTF ("[%p] MARK_SIZE_CHANGE (%d): %d => ...\n", + this, ref, wrapRefLines); + + /* By the way: ref == -1 may have two different causes: (i) flush() + calls "queueResize (-1, true)", when no rewrapping is necessary; + and (ii) a word may have parentRef == -1 , when it is not yet + added to a line. In the latter case, nothing has to be done + now, but addLine(...) will do everything necessary. */ + if (ref != -1) { + if (wrapRefLines == -1) + wrapRefLines = OutOfFlowMgr::getLineNoFromRef (ref); + else + wrapRefLines = misc::min (wrapRefLines, + OutOfFlowMgr::getLineNoFromRef (ref)); + } - PRINTF (" ... => %d\n", wrapRefLine); + DBG_OBJ_SET_NUM ("wrapRefLines", wrapRefLines); + + // It seems that sometimes (even without floats) the lines + // structure is changed, so that wrapRefLines may refers to a + // line which does not exist anymore. Should be examined + // again. Until then, setting wrapRefLines to the same value is + // a workaround. + markExtremesChange (ref); + } - // It seems that sometimes the lines structure is changed, so that - // wrapRefLines may refers to a line which does not exist - // anymore. Should be examined again. Until then, setting - // wrapRefLines to the same value is a workaround. - markExtremesChange (ref); + DBG_OBJ_LEAVE (); } void Textblock::markExtremesChange (int ref) { - PRINTF ("[%p] MARK_EXTREMES_CHANGE (%d): %d => ...\n", - this, ref, wrapRefParagraphs); - - /* By the way: ref == -1 may have two different causes: (i) flush() - calls "queueResize (-1, true)", when no rewrapping is necessary; - and (ii) a word may have parentRef == -1 , when it is not yet - added to a line. In the latter case, nothing has to be done - now, but addLine(...) will do everything necessary. */ - if (ref != -1) { - if (wrapRefParagraphs == -1) - wrapRefParagraphs = ref; - else - wrapRefParagraphs = misc::min (wrapRefParagraphs, ref); + DBG_OBJ_ENTER ("resize", 1, "markExtremesChange", "%d", ref); + + if (OutOfFlowMgr::isRefOutOfFlow (ref)) { + assert (outOfFlowMgr != NULL); + outOfFlowMgr->markExtremesChange (ref); + } else { + PRINTF ("[%p] MARK_EXTREMES_CHANGE (%d): %d => ...\n", + this, ref, wrapRefParagraphs); + + /* By the way: ref == -1 may have two different causes: (i) flush() + calls "queueResize (-1, true)", when no rewrapping is necessary; + and (ii) a word may have parentRef == -1 , when it is not yet + added to a line. In the latter case, nothing has to be done + now, but addLine(...) will do everything necessary. */ + if (ref != -1) { + if (wrapRefParagraphs == -1) + wrapRefParagraphs = OutOfFlowMgr::getLineNoFromRef (ref); + else + wrapRefParagraphs = + misc::min (wrapRefParagraphs, + OutOfFlowMgr::getLineNoFromRef (ref)); + } + + DBG_OBJ_SET_NUM ("wrapRefParagraphs", wrapRefParagraphs); } - PRINTF (" ... => %d\n", wrapRefParagraphs); + DBG_OBJ_LEAVE (); } -void Textblock::setWidth (int width) +void Textblock::notifySetAsTopLevel() { - /* If limitTextWidth is set to YES, a queueResize() may also be - * necessary. */ - if (availWidth != width || limitTextWidth) { - //DEBUG_MSG(DEBUG_REWRAP_LEVEL, - // "setWidth: Calling queueResize, " - // "in page with %d word(s)\n", - // words->size()); + PRINTF ("%p becomes toplevel\n", this); + containingBlock = this; + PRINTF ("-> %p is its own containing block\n", this); +} - availWidth = width; - queueResize (0, false); - mustQueueResize = false; - redrawY = 0; - } +bool Textblock::isContainingBlock (Widget *widget) +{ + return + // Of course, only textblocks are considered as containing + // blocks. + widget->instanceOf (Textblock::CLASS_ID) && + // The second condition: that this block is "out of flow", in a + // wider sense. + (// The toplevel widget is "out of flow", since there is no + // parent, and so no context. + widget->getParent() == NULL || + // A similar reasoning applies to a widget with another parent + // than a textblock (typical example: a table cell (this is + // also a text block) within a table widget). + !widget->getParent()->instanceOf (Textblock::CLASS_ID) || + // Inline blocks are containing blocks, too. + widget->getStyle()->display == core::style::DISPLAY_INLINE_BLOCK || + // Same for blocks with 'overview' set to another value than + // (the default value) 'visible'. + widget->getStyle()->overflow != core::style::OVERFLOW_VISIBLE || + // Finally, "out of flow" in a narrower sense: floats and + // absolute positions. + OutOfFlowMgr::isWidgetOutOfFlow (widget)); } -void Textblock::setAscent (int ascent) +void Textblock::notifySetParent () { - if (availAscent != ascent) { - //DEBUG_MSG(DEBUG_REWRAP_LEVEL, - // "setAscent: Calling queueResize, " - // "in page with %d word(s)\n", - // words->size()); + PRINTF ("%p becomes a child of %p\n", this, getParent()); - availAscent = ascent; - queueResize (0, false); - mustQueueResize = false; - } + // Search for containing Box. + containingBlock = NULL; + + for (Widget *widget = this; widget != NULL && containingBlock == NULL; + widget = widget->getParent()) + if (isContainingBlock (widget)) { + containingBlock = (Textblock*)widget; + + if (containingBlock == this) { + PRINTF ("-> %p is its own containing block\n", this); + } else { + PRINTF ("-> %p becomes containing block of %p\n", + containingBlock, this); + } + } + + assert (containingBlock != NULL); } -void Textblock::setDescent (int descent) +bool Textblock::isBlockLevel () { - if (availDescent != descent) { - //DEBUG_MSG(DEBUG_REWRAP_LEVEL, - // "setDescent: Calling queueResize, " - // "in page with %d word(s)\n", - // words->size()); - - availDescent = descent; - queueResize (0, false); - mustQueueResize = false; - } + return true; } bool Textblock::buttonPressImpl (core::EventButton *event) @@ -743,8 +1094,8 @@ bool Textblock::sendSelectionEvent (core::SelectionState::EventType eventType, } else { Line *lastLine = lines->getRef (lines->size () - 1); int yFirst = lineYOffsetCanvasI (0); - int yLast = lineYOffsetCanvas (lastLine) + lastLine->boxAscent + - lastLine->boxDescent; + int yLast = lineYOffsetCanvas (lastLine) + lastLine->borderAscent + + lastLine->borderDescent; if (event->yCanvas < yFirst) { // Above the first line: take the first word. wordIndex = 0; @@ -753,19 +1104,20 @@ bool Textblock::sendSelectionEvent (core::SelectionState::EventType eventType, wordIndex = words->size () - 1; charPos = core::SelectionState::END_OF_WORD; } else { - Line *line = lines->getRef (findLineIndex (event->yWidget)); + Line *line = + lines->getRef (findLineIndexWhenAllocated (event->yWidget)); // Pointer within the break space? - if (event->yWidget > - (lineYOffsetWidget (line) + line->boxAscent + line->boxDescent)) { + if (event->yWidget > (lineYOffsetWidget (line) + + line->borderAscent + line->borderDescent)) { // Choose this break. wordIndex = line->lastWord; charPos = core::SelectionState::END_OF_WORD; - } else if (event->xWidget < lineXOffsetWidget (line)) { + } else if (event->xWidget < line->textOffset) { // Left of the first word in the line. wordIndex = line->firstWord; } else { - int nextWordStartX = lineXOffsetWidget (line); + int nextWordStartX = line->textOffset; for (wordIndex = line->firstWord; wordIndex <= line->lastWord; @@ -778,7 +1130,8 @@ bool Textblock::sendSelectionEvent (core::SelectionState::EventType eventType, if (event->xWidget >= wordStartX && event->xWidget < nextWordStartX) { // We have found the word. - int yWidgetBase = lineYOffsetWidget (line) + line->boxAscent; + int yWidgetBase = + lineYOffsetWidget (line) + line->borderAscent; if (event->xWidget >= nextWordStartX - word->effSpace) { charPos = core::SelectionState::END_OF_WORD; @@ -860,8 +1213,9 @@ bool Textblock::sendSelectionEvent (core::SelectionState::EventType eventType, } } } - it = new TextblockIterator (this, core::Content::SELECTION_CONTENT, - wordIndex); + + it = new TextblockIterator (this, core::Content::maskForSelection (true), + false, wordIndex); r = selectionHandleEvent (eventType, it, charPos, link, event); it->unref (); return r; @@ -877,65 +1231,6 @@ core::Iterator *Textblock::iterator (core::Content::Type mask, bool atEnd) return new TextblockIterator (this, mask, atEnd); } - -/** - * Calculate the size of a widget within the page. - * (Subject of change in the near future!) - */ -void Textblock::calcWidgetSize (core::Widget *widget, core::Requisition *size) -{ - core::Requisition requisition; - int availWidth, availAscent, availDescent; - core::style::Style *wstyle = widget->getStyle(); - - /* We ignore line1_offset[_eff]. */ - availWidth = this->availWidth - getStyle()->boxDiffWidth () - innerPadding; - availAscent = this->availAscent - getStyle()->boxDiffHeight (); - availDescent = this->availDescent; - - if (widget->usesHints ()) { - widget->setWidth (availWidth); - widget->setAscent (availAscent); - widget->setDescent (availDescent); - widget->sizeRequest (size); - } else { - if (wstyle->width == core::style::LENGTH_AUTO || - wstyle->height == core::style::LENGTH_AUTO) - widget->sizeRequest (&requisition); - - if (wstyle->width == core::style::LENGTH_AUTO) - size->width = requisition.width; - else if (core::style::isAbsLength (wstyle->width)) - /* Fixed lengths are only applied to the content, so we have to - * add padding, border and margin. */ - size->width = core::style::absLengthVal (wstyle->width) - + wstyle->boxDiffWidth (); - else - size->width = - core::style::multiplyWithPerLength (availWidth, wstyle->width); - - if (wstyle->height == core::style::LENGTH_AUTO) { - size->ascent = requisition.ascent; - size->descent = requisition.descent; - } else if (core::style::isAbsLength (wstyle->height)) { - /* Fixed lengths are only applied to the content, so we have to - * add padding, border and margin. */ - size->ascent = core::style::absLengthVal (wstyle->height) - + wstyle->boxDiffHeight (); - size->descent = 0; - } else { - size->ascent = - core::style::multiplyWithPerLength (wstyle->height, availAscent); - size->descent = - core::style::multiplyWithPerLength (wstyle->height, availDescent); - } - } - - /* ascent and descent in words do not contain margins. */ - size->ascent -= wstyle->margin.top; - size->descent -= wstyle->margin.bottom; -} - /* * Draw the decorations on a word. */ @@ -994,13 +1289,13 @@ void Textblock::drawText(core::View *view, core::style::Style *style, if (isStart) { /* \bug No way to know about non-ASCII punctuation. */ bool initial_seen = false; - + for (int i = 0; i < start; i++) if (!ispunct(text[i])) initial_seen = true; if (initial_seen) break; - + int after = 0; text += start; while (ispunct(text[after])) @@ -1009,7 +1304,7 @@ void Textblock::drawText(core::View *view, core::style::Style *style, after = layout->nextGlyph(text, after); if (after > len) after = len; - + char *initial = layout->textToUpper(text, after); int newlen = strlen(initial) + len-after; str = (char *)malloc(newlen + 1); @@ -1020,7 +1315,7 @@ void Textblock::drawText(core::View *view, core::style::Style *style, } break; } - + view->drawText(style->font, style->color, shading, x, y, str ? str : text + start, str ? strlen(str) : len); if (str) @@ -1049,8 +1344,8 @@ void Textblock::drawWord (Line *line, int wordIndex1, int wordIndex2, for (int i = wordIndex1; i <= wordIndex2; i++) w += words->getRef(i)->size.width; w += words->getRef(wordIndex2)->hyphenWidth; - drawBox (view, style, area, xWidget, yWidgetBase - line->boxAscent, - w, line->boxAscent + line->boxDescent, false); + drawBox (view, style, area, xWidget, yWidgetBase - line->borderAscent, + w, line->borderAscent + line->borderDescent, false); } if (wordIndex1 == wordIndex2 && !drawHyphen) { @@ -1080,7 +1375,7 @@ void Textblock::drawWord (Line *line, int wordIndex1, int wordIndex2, text[p++] = hyphenDrawChar[i]; text[p++] = 0; } - + drawWord0 (wordIndex1, wordIndex2, text, totalWidth, drawHyphen, style, view, area, xWidget, yWidgetBase); } @@ -1245,8 +1540,17 @@ void Textblock::drawSpace(int wordIndex, core::View *view, */ void Textblock::drawLine (Line *line, core::View *view, core::Rectangle *area) { - int xWidget = lineXOffsetWidget(line); - int yWidgetBase = lineYOffsetWidget (line) + line->boxAscent; + DBG_OBJ_ENTER ("draw", 0, "drawLine", "..., %d, %d, %d * %d", + area->x, area->y, area->width, area->height); + + int xWidget = line->textOffset; + int yWidgetBase = lineYOffsetWidget (line) + line->borderAscent; + + DBG_OBJ_MSGF ("draw", 1, "line from %d to %d (%d words), at (%d, %d)", + line->firstWord, line->lastWord, words->size (), + xWidget, yWidgetBase); + DBG_MSG_WORD ("draw", 0, "<i>line starts with: </i>", line->firstWord, ""); + DBG_MSG_WORD ("draw", 0, "<i>line ends with: </i>", line->lastWord, ""); for (int wordIndex = line->firstWord; wordIndex <= line->lastWord && xWidget < area->x + area->width; @@ -1256,10 +1560,10 @@ void Textblock::drawLine (Line *line, core::View *view, core::Rectangle *area) if (xWidget + wordSize + word->hyphenWidth + word->effSpace >= area->x) { if (word->content.type == core::Content::TEXT || - word->content.type == core::Content::WIDGET) { + word->content.type == core::Content::WIDGET_IN_FLOW) { if (word->size.width > 0) { - if (word->content.type == core::Content::WIDGET) { + if (word->content.type == core::Content::WIDGET_IN_FLOW) { core::Widget *child = word->content.widget; core::Rectangle childArea; @@ -1290,8 +1594,8 @@ void Textblock::drawLine (Line *line, core::View *view, core::Rectangle *area) if (word->spaceStyle->hasBackground ()) drawBox (view, word->spaceStyle, area, xWidget + wordSize, - yWidgetBase - line->boxAscent, word->effSpace, - line->boxAscent + line->boxDescent, false); + yWidgetBase - line->borderAscent, word->effSpace, + line->borderAscent + line->borderDescent, false); drawSpace(wordIndex, view, area, xWidget + wordSize, yWidgetBase); } @@ -1300,13 +1604,46 @@ void Textblock::drawLine (Line *line, core::View *view, core::Rectangle *area) } xWidget += wordSize + word->effSpace; } + + DBG_OBJ_LEAVE (); } /** - * Find the first line index that includes y, relative to top of widget. + * Find the first line index that includes y, which is given in widget + * coordinates. */ int Textblock::findLineIndex (int y) { + return wasAllocated () ? + findLineIndexWhenAllocated (y) : findLineIndexWhenNotAllocated (y); +} + +int Textblock::findLineIndexWhenNotAllocated (int y) +{ + if (lines->size() == 0) + return -1; + else + return + findLineIndex (y, calcVerticalBorder (getStyle()->padding.top, + getStyle()->borderWidth.top, + getStyle()->margin.top, + lines->getRef(0)->borderAscent, + lines->getRef(0)->marginAscent)); +} + +int Textblock::findLineIndexWhenAllocated (int y) +{ + assert (wasAllocated ()); + return findLineIndex (y, childBaseAllocation.ascent); +} + +int Textblock::findLineIndex (int y, int ascent) +{ + DBG_OBJ_ENTER ("events", 0, "findLineIndex", "%d, %d", y, ascent); + + core::Allocation alloc; + alloc.ascent = ascent; // More is not needed. + int maxIndex = lines->size () - 1; int step, index, low = 0; @@ -1314,12 +1651,12 @@ int Textblock::findLineIndex (int y) while ( step > 1 ) { index = low + step; if (index <= maxIndex && - lineYOffsetWidgetI (index) <= y) + lineYOffsetWidgetIAllocation (index, &alloc) <= y) low = index; step = (step + 1) >> 1; } - if (low < maxIndex && lineYOffsetWidgetI (low + 1) <= y) + if (low < maxIndex && lineYOffsetWidgetIAllocation (low + 1, &alloc) <= y) low++; /* @@ -1332,6 +1669,10 @@ int Textblock::findLineIndex (int y) * Dw_page_find_link() --EG * That function has now been inlined into Dw_page_motion_notify() --JV */ + + DBG_OBJ_MSGF ("events", 1, "=> %d", low); + DBG_OBJ_LEAVE (); + return low; } @@ -1340,12 +1681,13 @@ int Textblock::findLineIndex (int y) */ int Textblock::findLineOfWord (int wordIndex) { - int high = lines->size () - 1, index, low = 0; - - // TODO regard also not-yet-existing lines? - if (wordIndex < 0 || wordIndex >= words->size ()) + if (wordIndex < 0 || wordIndex >= words->size () || + // Also regard not-yet-existing lines. + lines->size () <= 0 || wordIndex > lines->getLastRef()->lastWord) return -1; + int high = lines->size () - 1, index, low = 0; + while (true) { index = (low + high) / 2; if (wordIndex >= lines->getRef(index)->firstWord) { @@ -1396,14 +1738,14 @@ Textblock::Word *Textblock::findWord (int x, int y, bool *inSpace) *inSpace = false; - if ((lineIndex = findLineIndex (y)) >= lines->size ()) + if ((lineIndex = findLineIndexWhenAllocated (y)) >= lines->size ()) return NULL; line = lines->getRef (lineIndex); - yWidgetBase = lineYOffsetWidget (line) + line->boxAscent; - if (yWidgetBase + line->boxDescent <= y) + yWidgetBase = lineYOffsetWidget (line) + line->borderAscent; + if (yWidgetBase + line->borderDescent <= y) return NULL; - xCursor = lineXOffsetWidget (line); + xCursor = line->textOffset; for (wordIndex = line->firstWord; wordIndex <= line->lastWord;wordIndex++) { word = words->getRef (wordIndex); lastXCursor = xCursor; @@ -1432,23 +1774,37 @@ Textblock::Word *Textblock::findWord (int x, int y, bool *inSpace) void Textblock::draw (core::View *view, core::Rectangle *area) { - PRINTF ("DRAW: %d, %d, %d x %d\n", - area->x, area->y, area->width, area->height); + DBG_OBJ_ENTER ("draw", 0, "draw", "%d, %d, %d * %d", + area->x, area->y, area->width, area->height); int lineIndex; Line *line; - drawWidgetBox (view, area, false); + // Instead of drawWidgetBox, use drawBox to include verticalOffset. + if (getParent() == NULL) { + // The toplevel (parent == NULL) widget is a special case, which + // we leave to drawWidgetBox; verticalOffset will here always 0. + assert (verticalOffset == 0); + drawWidgetBox (view, area, false); + } else + drawBox (view, getStyle(), area, 0, verticalOffset, allocation.width, + getHeight() - verticalOffset, false); - lineIndex = findLineIndex (area->y); + lineIndex = findLineIndexWhenAllocated (area->y); for (; lineIndex < lines->size (); lineIndex++) { line = lines->getRef (lineIndex); if (lineYOffsetWidget (line) >= area->y + area->height) break; + DBG_OBJ_MSGF ("draw", 0, "line %d (of %d)", lineIndex, lines->size ()); drawLine (line, view, area); } + + if(outOfFlowMgr) + outOfFlowMgr->draw(view, area); + + DBG_OBJ_LEAVE (); } /** @@ -1457,11 +1813,22 @@ void Textblock::draw (core::View *view, core::Rectangle *area) Textblock::Word *Textblock::addWord (int width, int ascent, int descent, short flags, core::style::Style *style) { + DBG_OBJ_ENTER ("construct.word", 0, "addWord", "%d * (%d + %d), %d, %p", + width, ascent, descent, flags, style); + + if (lineBreakWidth == -1) { + lineBreakWidth = getAvailWidth (true); + DBG_OBJ_SET_NUM ("lineBreakWidth", lineBreakWidth); + } + words->increase (); + DBG_OBJ_SET_NUM ("words.size", words->size ()); int wordNo = words->size () - 1; initWord (wordNo); fillWord (wordNo, width, ascent, descent, flags, style); - return words->getRef (wordNo);; + + DBG_OBJ_LEAVE (); + return words->getRef (wordNo); } /** @@ -1476,6 +1843,21 @@ void Textblock::initWord (int wordNo) word->spaceImgRenderer = NULL; } +void Textblock::cleanupWord (int wordNo) +{ + Word *word = words->getRef (wordNo); + + if (word->content.type == core::Content::WIDGET_IN_FLOW) + delete word->content.widget; + /** \todo Widget references? What about texts? */ + + removeWordImgRenderer (wordNo); + removeSpaceImgRenderer (wordNo); + + word->style->unref (); + word->spaceStyle->unref (); +} + void Textblock::removeWordImgRenderer (int wordNo) { Word *word = words->getRef (wordNo); @@ -1581,7 +1963,7 @@ int Textblock::textWidth(const char *text, int start, int len, if (isStart) { /* \bug No way to know about non-ASCII punctuation. */ bool initial_seen = false; - + for (int i = 0; i < start; i++) if (!ispunct(text[i])) initial_seen = true; @@ -1589,7 +1971,7 @@ int Textblock::textWidth(const char *text, int start, int len, ret = layout->textWidth(style->font, text+start, len); } else { int after = 0; - + text += start; while (ispunct(text[after])) after++; @@ -1676,7 +2058,8 @@ void Textblock::calcTextSize (const char *text, size_t len, void Textblock::addText (const char *text, size_t len, core::style::Style *style) { - PRINTF ("[%p] ADD_TEXT (%d characters)\n", this, (int)len); + DBG_OBJ_ENTER ("construct.word", 0, "addText", "..., %d, %p", + (int)len, style); // Count dividing characters. int numParts = 1; @@ -1736,10 +2119,10 @@ void Textblock::addText (const char *text, size_t len, foundDiv = j; } } - + if (foundDiv != -1) { int lDiv = strlen (divChars[foundDiv].s); - + if (divChars[foundDiv].charRemoved) { assert (divChars[foundDiv].penaltyIndexLeft != -1); assert (divChars[foundDiv].penaltyIndexRight == -1); @@ -1824,7 +2207,7 @@ void Textblock::addText (const char *text, size_t len, // Finished! for (int i = 0; i < numParts; i++) { short flags = 0; - + // If this parts adjoins at least one division characters, // for which canBeHyphenated is set to false (this is the // case for soft hyphens), do not hyphenate. @@ -1847,14 +2230,14 @@ void Textblock::addText (const char *text, size_t len, flags |= Word::WORD_START; if (i == numParts - 1) flags |= Word::WORD_END; - + addText0 (text + partStart[i], partEnd[i] - partStart[i], flags, style, &wordSize[i]); //printf ("[%p] %d: added word part: ", this, words->size() - 1); //printWordWithFlags (words->getLastRef()); //printf ("\n"); - + //PRINTF("H... [%d] '", i); //for (int j = partStart[i]; j < partEnd[i]; j++) // PUTCHAR(text[j]); @@ -1865,6 +2248,7 @@ void Textblock::addText (const char *text, size_t len, setBreakOption (word, style, penalties[partPenaltyIndex[i]][0], penalties[partPenaltyIndex[i]][1], false); + DBG_SET_WORD (words->size () - 1); if (charRemoved[i]) // Currently, only unconditional hyphens (UTF-8: @@ -1881,6 +2265,8 @@ void Textblock::addText (const char *text, size_t len, } } } + + DBG_OBJ_LEAVE (); } void Textblock::calcTextSizes (const char *text, size_t textLen, @@ -1923,6 +2309,19 @@ void Textblock::calcTextSizes (const char *text, size_t textLen, void Textblock::addText0 (const char *text, size_t len, short flags, core::style::Style *style, core::Requisition *size) { + DBG_OBJ_ENTER ("construct.word", 0, "addText0", + "..., %d, %s:%s:%s:%s:%s:%s:%s, %p, %d * (%d + %d)", + (int)len, + // Ugly copy&paste from printWordFlags: + (flags & Word::CAN_BE_HYPHENATED) ? "h?" : "--", + (flags & Word::DIV_CHAR_AT_EOL) ? "de" : "--", + (flags & Word::PERM_DIV_CHAR) ? "dp" : "--", + (flags & Word::DRAW_AS_ONE_TEXT) ? "t1" : "--", + (flags & Word::UNBREAKABLE_FOR_MIN_WIDTH) ? "um" : "--", + (flags & Word::WORD_START) ? "st" : "--", + (flags & Word::WORD_END) ? "en" : "--", + style, size->width, size->ascent, size->descent); + //printf("[%p] addText0 ('", this); //for (size_t i = 0; i < len; i++) // putchar(text[i]); @@ -1936,7 +2335,17 @@ void Textblock::addText0 (const char *text, size_t len, short flags, word->content.type = core::Content::TEXT; word->content.text = layout->textZone->strndup(text, len); + DBG_SET_WORD (words->size () - 1); + + // The following debug message may be useful to identify the + // different textblocks. + + //if (words->size() == 1) + // printf ("[%p] first word: '%s'\n", this, text); + processWord (words->size () - 1); + + DBG_OBJ_LEAVE (); } /** @@ -1944,28 +2353,57 @@ void Textblock::addText0 (const char *text, size_t len, short flags, */ void Textblock::addWidget (core::Widget *widget, core::style::Style *style) { - Word *word; - core::Requisition size; + DBG_OBJ_ENTER ("construct.word", 0, "addWidget", "%p, %p", widget, style); /* We first assign -1 as parent_ref, since the call of widget->size_request * will otherwise let this Textblock be rewrapped from the beginning. * (parent_ref is actually undefined, but likely has the value 0.) At the, * end of this function, the correct value is assigned. */ widget->parentRef = -1; + DBG_OBJ_SET_NUM_O (widget, "parentRef", widget->parentRef); - PRINTF ("%p becomes child of %p\n", widget, this); - - widget->setParent (this); widget->setStyle (style); - calcWidgetSize (widget, &size); - word = addWord (size.width, size.ascent, size.descent, 0, style); + PRINTF ("adding the %s %p to %p (word %d) ...\n", + widget->getClassName(), widget, this, words->size()); + + if (containingBlock->outOfFlowMgr == NULL) { + containingBlock->outOfFlowMgr = new OutOfFlowMgr (containingBlock); + DBG_OBJ_ASSOC (containingBlock, containingBlock->outOfFlowMgr); + } + + if (OutOfFlowMgr::isWidgetOutOfFlow (widget)) { + PRINTF (" -> out of flow.\n"); - word->content.type = core::Content::WIDGET; - word->content.widget = widget; + widget->setParent (containingBlock); + widget->setGenerator (this); + containingBlock->outOfFlowMgr->addWidgetOOF (widget, this, + words->size ()); + Word *word = addWord (0, 0, 0, 0, style); + word->content.type = core::Content::WIDGET_OOF_REF; + word->content.widget = widget; - //DBG_OBJ_ARRSET_PTR (page, "words.%d.content.widget", words->size() - 1, - // word->content.widget); + // After a out-of-flow reference, breaking is allowed. (This avoids some + // problems with breaking near float definitions.) + setBreakOption (word, style, 0, 0, false); + } else { + PRINTF (" -> within flow.\n"); + + widget->setParent (this); + + // TODO Replace (perhaps) later "textblock" by "OOF aware widget". + if (widget->instanceOf (Textblock::CLASS_ID)) + containingBlock->outOfFlowMgr->addWidgetInFlow ((Textblock*)widget, + this, words->size ()); + + core::Requisition size; + widget->sizeRequest (&size); + Word *word = addWord (size.width, size.ascent, size.descent, 0, style); + word->content.type = core::Content::WIDGET_IN_FLOW; + word->content.widget = widget; + } + + DBG_SET_WORD (words->size () - 1); processWord (words->size () - 1); //DBG_OBJ_SET_NUM (word->content.widget, "parent_ref", @@ -1975,6 +2413,8 @@ void Textblock::addWidget (core::Widget *widget, core::style::Style *style) // "Assigning parent_ref = %d to added word %d, " // "in page with %d word(s)\n", // lines->size () - 1, words->size() - 1, words->size()); + + DBG_OBJ_LEAVE (); } /** @@ -1986,8 +2426,11 @@ void Textblock::addWidget (core::Widget *widget, core::style::Style *style) */ bool Textblock::addAnchor (const char *name, core::style::Style *style) { + DBG_OBJ_ENTER ("construct.word", 0, "addAnchor", "\"%s\", %p", name, style); + char *copy; int y; + bool result; // Since an anchor does not take any space, it is safe to call // addAnchor already here. @@ -2005,7 +2448,7 @@ bool Textblock::addAnchor (const char *name, core::style::Style *style) * \todo It may be necessary for future uses to save the anchor in * some way, e.g. when parts of the widget tree change. */ - return false; + result = false; else { Anchor *anchor; @@ -2013,8 +2456,12 @@ bool Textblock::addAnchor (const char *name, core::style::Style *style) anchor = anchors->getRef(anchors->size() - 1); anchor->name = copy; anchor->wordIndex = words->size(); - return true; + result = true; } + + DBG_OBJ_MSGF ("construct.word", 0, "=> %s", result ? "true" : "false"); + DBG_OBJ_LEAVE (); + return result; } @@ -2023,39 +2470,62 @@ bool Textblock::addAnchor (const char *name, core::style::Style *style) */ void Textblock::addSpace (core::style::Style *style) { + DBG_OBJ_ENTER ("construct.word", 0, "addSpace", "%p", style); + int wordIndex = words->size () - 1; if (wordIndex >= 0) { fillSpace (wordIndex, style); + DBG_SET_WORD (wordIndex); accumulateWordData (wordIndex); correctLastWordExtremes (); } + + DBG_OBJ_LEAVE (); } /** * Add a break option (see setBreakOption() for details). Used instead - * of addStyle for ideographic characters. + * of addSpace for ideographic characters. * * When "forceBreak" is true, a break is even possible within PRE etc. */ void Textblock::addBreakOption (core::style::Style *style, bool forceBreak) { + DBG_OBJ_ENTER ("construct.word", 0, "addBreakOption", "%p, %s", + style, forceBreak ? "true" : "false"); + int wordIndex = words->size () - 1; if (wordIndex >= 0) { setBreakOption (words->getRef(wordIndex), style, 0, 0, forceBreak); + DBG_SET_WORD (wordIndex); // Call of accumulateWordData() is not needed here. correctLastWordExtremes (); } + + DBG_OBJ_LEAVE (); } void Textblock::fillSpace (int wordNo, core::style::Style *style) { + DBG_OBJ_ENTER ("construct.word", 0, "fillSpace", "%d, ...", wordNo); + + DBG_OBJ_MSGF ("construct.word", 1, "style.white-space = %s", + style->whiteSpace == core::style::WHITE_SPACE_NORMAL ? "normal" + : style->whiteSpace == core::style::WHITE_SPACE_PRE ? "pre" + : style->whiteSpace == core::style::WHITE_SPACE_NOWRAP ? + "nowrap" + : style->whiteSpace == core::style::WHITE_SPACE_PRE_WRAP ? + "pre-wrap" + : style->whiteSpace == core::style::WHITE_SPACE_PRE_LINE ? + "pre-line" : "???"); + // Old comment: - // + // // According to // http://www.w3.org/TR/CSS2/text.html#white-space-model: "line // breaking opportunities are determined based on the text // prior to the white space collapsing steps". - // + // // So we call addBreakOption () for each Textblock::addSpace () // call. This is important e.g. to be able to break between // foo and bar in: <span style="white-space:nowrap">foo </span> @@ -2067,30 +2537,26 @@ void Textblock::fillSpace (int wordNo, core::style::Style *style) // TODO: This line does not work: addBreakOption (word, style); - // Do not override a previously set break penalty. - if (!word->content.space) { + if (// Do not override a previously set break penalty: + !word->content.space && + // OOF references are considered specially, and must not have a space: + word->content.type != core::Content::WIDGET_OOF_REF) { setBreakOption (word, style, 0, 0, false); word->content.space = true; - word->effSpace = word->origSpace = style->font->spaceWidth + - style->wordSpacing; - - //DBG_OBJ_ARRSET_NUM (this, "words.%d.origSpace", wordIndex, - // word->origSpace); - //DBG_OBJ_ARRSET_NUM (this, "words.%d.effSpace", wordIndex, - // word->effSpace); - //DBG_OBJ_ARRSET_NUM (this, "words.%d.content.space", wordIndex, - // word->content.space); - + word->origSpace = word->effSpace = + style->font->spaceWidth + style->wordSpacing; removeSpaceImgRenderer (wordNo); word->spaceStyle->unref (); word->spaceStyle = style; style->ref (); - + setSpaceImgRenderer (wordNo); } + + DBG_OBJ_LEAVE (); } /** @@ -2102,19 +2568,24 @@ void Textblock::setBreakOption (Word *word, core::style::Style *style, int breakPenalty1, int breakPenalty2, bool forceBreak) { + DBG_OBJ_ENTER ("construct.word", 0, "setBreakOption", "..., %d, %d, %s", + breakPenalty1, breakPenalty2, forceBreak ? "true" : "false"); + // TODO: lineMustBeBroken should be independent of the penalty // index? Otherwise, examine the last line. if (!word->badnessAndPenalty.lineMustBeBroken(0)) { - if (forceBreak || isBreakAllowed (word)) + if (forceBreak || isBreakAllowed (style)) word->badnessAndPenalty.setPenalties (breakPenalty1, breakPenalty2); else word->badnessAndPenalty.setPenalty (PENALTY_PROHIBIT_BREAK); } + + DBG_OBJ_LEAVE (); } -bool Textblock::isBreakAllowed (Word *word) +bool Textblock::isBreakAllowed (core::style::Style *style) { - switch (word->style->whiteSpace) { + switch (style->whiteSpace) { case core::style::WHITE_SPACE_NORMAL: case core::style::WHITE_SPACE_PRE_LINE: case core::style::WHITE_SPACE_PRE_WRAP: @@ -2123,11 +2594,12 @@ bool Textblock::isBreakAllowed (Word *word) case core::style::WHITE_SPACE_PRE: case core::style::WHITE_SPACE_NOWRAP: return false; + + default: + // compiler happiness + lout::misc::assertNotReached (); + return false; } - - // compiler happiness - lout::misc::assertNotReached (); - return false; } @@ -2136,11 +2608,17 @@ bool Textblock::isBreakAllowed (Word *word) */ void Textblock::addParbreak (int space, core::style::Style *style) { + DBG_OBJ_ENTER ("construct.word", 0, "addParbreak", "%d, %p", + space, style); + DBG_OBJ_MSG ("construct.word", 0, + "<i>No nesting! Strack trace may be incomplete.</i>"); + DBG_OBJ_LEAVE (); + Word *word; /* A break may not be the first word of a page, or directly after the bullet/number (which is the first word) in a list item. (See - also comment in Dw_page_size_request.) */ + also comment in sizeRequest.) */ if (words->size () == 0 || (hasListitemValue && words->size () == 1)) { /* This is a bit hackish: If a break is added as the @@ -2149,23 +2627,23 @@ void Textblock::addParbreak (int space, core::style::Style *style) a widget is used as a text box (lists, blockquotes, list items etc) -- then we simply adjust the break before, in a way that the space is in any case visible. */ - Widget *widget; - - /* Find the widget where to adjust the breakSpace. */ - for (widget = this; - widget->getParent() && - widget->getParent()->instanceOf (Textblock::CLASS_ID); + /* Find the widget where to adjust the breakSpace. (Only + consider normal flow, no floats etc.) */ + for (Widget *widget = this; + widget->getParent() != NULL && + widget->getParent()->instanceOf (Textblock::CLASS_ID) && + !OutOfFlowMgr::isRefOutOfFlow (widget->parentRef); widget = widget->getParent ()) { Textblock *textblock2 = (Textblock*)widget->getParent (); int index = textblock2->hasListitemValue ? 1 : 0; bool isfirst = (textblock2->words->getRef(index)->content.type - == core::Content::WIDGET + == core::Content::WIDGET_IN_FLOW && textblock2->words->getRef(index)->content.widget == widget); if (!isfirst) { - /* The page we searched for has been found. */ + /* The text block we searched for has been found. */ Word *word2; - int lineno = widget->parentRef; + int lineno = OutOfFlowMgr::getLineNoFromRef (widget->parentRef); if (lineno > 0 && (word2 = @@ -2174,7 +2652,8 @@ void Textblock::addParbreak (int space, core::style::Style *style) word2->content.type == core::Content::BREAK) { if (word2->content.breakSpace < space) { word2->content.breakSpace = space; - textblock2->queueResize (lineno, false); + textblock2->queueResize + (OutOfFlowMgr::createRefNormalFlow (lineno), false); textblock2->mustQueueResize = false; } } @@ -2182,6 +2661,7 @@ void Textblock::addParbreak (int space, core::style::Style *style) } /* Otherwise continue to examine parents. */ } + /* Return in any case. */ return; } @@ -2195,7 +2675,7 @@ void Textblock::addParbreak (int space, core::style::Style *style) misc::max (word->content.breakSpace, space); lastLine->breakSpace = misc::max (word->content.breakSpace, - lastLine->marginDescent - lastLine->boxDescent, + lastLine->marginDescent - lastLine->borderDescent, lastLine->breakSpace); return; } @@ -2205,6 +2685,10 @@ void Textblock::addParbreak (int space, core::style::Style *style) word->content.type = core::Content::BREAK; word->badnessAndPenalty.setPenalty (PENALTY_FORCE_BREAK); word->content.breakSpace = space; + + DBG_SET_WORD (words->size () - 1); + + breakAdded (); processWord (words->size () - 1); } @@ -2213,6 +2697,8 @@ void Textblock::addParbreak (int space, core::style::Style *style) */ void Textblock::addLinebreak (core::style::Style *style) { + DBG_OBJ_ENTER ("construct.word", 0, "addLinebreak", "%p", style); + Word *word; if (words->size () == 0 || @@ -2230,9 +2716,41 @@ void Textblock::addLinebreak (core::style::Style *style) word->content.type = core::Content::BREAK; word->badnessAndPenalty.setPenalty (PENALTY_FORCE_BREAK); word->content.breakSpace = 0; + + DBG_SET_WORD (words->size () - 1); + + breakAdded (); processWord (words->size () - 1); + + DBG_OBJ_LEAVE (); } +/** + * Called directly after a (line or paragraph) break has been added. + */ +void Textblock::breakAdded () +{ + assert (words->size () >= 1); + assert (words->getRef(words->size () - 1)->content.type + == core::Content::BREAK); + + // Any space before is removed. It is not used; on the other hand, + // this snippet (an example from a real-world debugging session) + // would cause problems: + // + // <input style="width: 100%" .../> + // <button ...>...</button> + // + // (Notice the space between <input> and <button>, and also that + // the HTML parser will insert a BREAK between them.) The <input> + // would be given the available width ("width: 100%"), but the + // actual width (Word::totalWidth) would include the space, so that + // the width of the line is larger than the available width. + + if (words->size () >= 2) + words->getRef(words->size () - 2)->origSpace = + words->getRef(words->size () - 2)->effSpace = 0; +} /** * \brief Search recursively through widget. @@ -2242,6 +2760,10 @@ void Textblock::addLinebreak (core::style::Style *style) */ core::Widget *Textblock::getWidgetAtPoint(int x, int y, int level) { + //printf ("%*s-> examining the %s %p (%d, %d, %d x (%d + %d))\n", + // 3 * level, "", getClassName (), this, allocation.x, allocation.y, + // allocation.width, allocation.ascent, allocation.descent); + int lineIndex, wordIndex; Line *line; @@ -2252,7 +2774,15 @@ core::Widget *Textblock::getWidgetAtPoint(int x, int y, int level) return NULL; } - lineIndex = findLineIndex (y - allocation.y); + // First, search for widgets out of flow, notably floats, since + // there are cases where they overlap child textblocks. Should + // later be refined using z-index. + Widget *oofWidget = + outOfFlowMgr ? outOfFlowMgr->getWidgetAtPoint (x, y, level) : NULL; + if (oofWidget) + return oofWidget; + + lineIndex = findLineIndexWhenAllocated (y - allocation.y); if (lineIndex < 0 || lineIndex >= lines->size ()) { return this; @@ -2263,12 +2793,14 @@ core::Widget *Textblock::getWidgetAtPoint(int x, int y, int level) for (wordIndex = line->firstWord; wordIndex <= line->lastWord;wordIndex++) { Word *word = words->getRef (wordIndex); - if (word->content.type == core::Content::WIDGET) { + if (word->content.type == core::Content::WIDGET_IN_FLOW) { core::Widget * childAtPoint; - childAtPoint = word->content.widget->getWidgetAtPoint (x, y, - level + 1); - if (childAtPoint) { - return childAtPoint; + if (word->content.widget->wasAllocated ()) { + childAtPoint = word->content.widget->getWidgetAtPoint (x, y, + level + 1); + if (childAtPoint) { + return childAtPoint; + } } } } @@ -2288,7 +2820,8 @@ void Textblock::handOverBreak (core::style::Style *style) Line *lastLine = lines->getRef (lines->size () - 1); if (lastLine->breakSpace != 0 && (parent = getParent()) && - parent->instanceOf (Textblock::CLASS_ID)) { + parent->instanceOf (Textblock::CLASS_ID) && + parent->getStyle()->display != core::style::DISPLAY_BLOCK) { Textblock *textblock2 = (Textblock*) parent; textblock2->addParbreak(lastLine->breakSpace, style); } @@ -2303,13 +2836,16 @@ void Textblock::handOverBreak (core::style::Style *style) */ void Textblock::flush () { - PRINTF ("[%p] FLUSH => %s (parentRef = %d)\n", - this, mustQueueResize ? "true" : "false", parentRef); + DBG_OBJ_ENTER0 ("resize", 0, "flush"); if (mustQueueResize) { + DBG_OBJ_MSG ("resize", 0, "mustQueueResize set"); + queueResize (-1, true); mustQueueResize = false; } + + DBG_OBJ_LEAVE (); } @@ -2344,7 +2880,7 @@ void Textblock::changeLinkColor (int link, int newColor) old_style->unref(); break; } - case core::Content::WIDGET: + case core::Content::WIDGET_IN_FLOW: { core::Widget *widget = word->content.widget; styleAttrs = *widget->getStyle(); styleAttrs.color = core::style::Color::create (layout, @@ -2362,7 +2898,7 @@ void Textblock::changeLinkColor (int link, int newColor) } if (changed) queueDrawArea (0, lineYOffsetWidget(line), allocation.width, - line->boxAscent + line->boxDescent); + line->borderAscent + line->borderDescent); } } @@ -2387,13 +2923,311 @@ void Textblock::queueDrawRange (int index1, int index2) if (line1idx >= 0 && line2idx >= 0) { Line *line1 = lines->getRef (line1idx), *line2 = lines->getRef (line2idx); - int y = lineYOffsetWidget (line1) + line1->boxAscent - + int y = lineYOffsetWidget (line1) + line1->borderAscent - line1->contentAscent; - int h = lineYOffsetWidget (line2) + line2->boxAscent + + int h = lineYOffsetWidget (line2) + line2->borderAscent + line2->contentDescent - y; queueDrawArea (0, y, allocation.width, h); } } +void Textblock::setVerticalOffset (int verticalOffset) +{ + DBG_OBJ_ENTER ("resize", 0, "setVerticalOffset", "%d", verticalOffset); + + if (this->verticalOffset != verticalOffset) { + this->verticalOffset = verticalOffset; + DBG_OBJ_SET_NUM ("verticalOffset", verticalOffset); + mustQueueResize = true; + queueDraw (); // Could perhaps be optimized. + } + + DBG_OBJ_LEAVE (); +} + +bool Textblock::mustBeWidenedToAvailWidth () +{ + DBG_OBJ_ENTER0 ("resize", 0, "mustBeWidenedToAvailWidth"); + bool toplevel = getParent () == NULL, + block = getStyle()->display == core::style::DISPLAY_BLOCK, + vloat = getStyle()->vloat != core::style::FLOAT_NONE, + result = toplevel || (block && !vloat); + DBG_OBJ_MSGF ("resize", 0, + "=> %s (toplevel: %s, block: %s, float: %s)", + result ? "true" : "false", toplevel ? "true" : "false", + block ? "true" : "false", vloat ? "true" : "false"); + DBG_OBJ_LEAVE (); + return result; +} + +/** + * Called by dw::OutOfFlowMgr when the border has changed due to a + * float (or some floats). + * + * "y", which given in widget coordinates, denotes the minimal + * position (when more than one float caused this), "vloat" the + * floating widget belonging to "y". + */ +void Textblock::borderChanged (int y, Widget *vloat) +{ + DBG_OBJ_ENTER ("resize", 0, "borderChanged", "%d, %p", y, vloat); + + int lineIndex = findLineIndex (y); + DBG_OBJ_MSGF ("resize", 1, "Line index: %d (of %d).", + lineIndex, lines->size ()); + + // Nothing to do at all, when lineIndex >= lines->size (), + // i. e. the change is below the bottom of this widget. + if (lineIndex < lines->size ()) { + int wrapLineIndex; + if (lineIndex < 0) + // Rewrap all. + wrapLineIndex = 0; + else + wrapLineIndex = lineIndex; + + int realWrapLineIndex = wrapLineIndex; + // The following two variables are only used for debugging: + int minWrapLineIndex = wrapLineIndex, maxWrapLineIndex = wrapLineIndex; + + if (vloat->getGenerator() == this && lines->size () > 0) { + bool found = false; + // Sometimes, the respective word is not yet part of a + // line. Nothing to do, but because of the assertion below + // (and also for performace reasons) this should be + // considered. TODO: Integrate this below. + for (int wordIndex = + lines->size() > 0 ? lines->getLastRef()->lastWord + 1 : 0; + !found && wordIndex < words->size(); wordIndex++) { + Word *word = words->getRef (wordIndex); + if (word->content.type == core::Content::WIDGET_OOF_REF && + word->content.widget == vloat) + found = true; + } + + // We search for the line of the float reference. There are + // two cases when this is not the line corresponsing to y: + // + // (1) When the float was moved down, due to collisions with + // other floats: in this case, the line number gets + // smaller (since the float reference is before). + // + // (2) In some cases, the line number may become larger, due + // to the per-line optimization of the words: initially, + // lines->size() - 1 is assigned, but it may happen that + // the float reference is put into another line. + // + // Only in the first case, a correction is neccessary, but a + // test for the second case is useful. (TODO: I've forgotten + // why a correction is neccessary.) + // + // Searched is done in the following order: + // + // - wrapLineIndex, + // - wrapLineIndex - 1, + // - wrapLineIndex + 1, + // - wrapLineIndex - 2, + // - wrapLineIndex + 2, + // + // etc. until either the float reference has been found or + // all lines have been searched (the latter triggers an + // abortion). + + bool exceedsBeginning = false, exceedsEnd = false; + for (int i = 0; !found; i++) { + bool exceeds; + int lineIndex2; + if (i % 2 == 0) { + // even: +0, +1, +2, ... + lineIndex2 = realWrapLineIndex + i / 2; + if (i > 0) + exceeds = exceedsEnd = lineIndex2 >= lines->size (); + else + exceeds = exceedsEnd = false; + } else { + // odd: -1, -2, ... + lineIndex2 = realWrapLineIndex - (i + 1) / 2; + exceeds = exceedsBeginning = lineIndex2 < 0; + } + + DBG_OBJ_MSGF ("resize", 2, + "lineIndex2 = %d (of %d), exceeds = %s, " + "exceedsBeginning = %s, exceedsEnd = %s", + lineIndex2, lines->size (), + exceeds ? "true" : "false", + exceedsBeginning ? "true" : "false", + exceedsEnd ? "true" : "false"); + + if (exceedsBeginning && exceedsEnd) + break; + + if (!exceeds) { + Line *line = lines->getRef (lineIndex2); + for (int wordIndex = line->firstWord; + !found && wordIndex <= line->lastWord; wordIndex++) { + Word *word = words->getRef (wordIndex); + if (word->content.type == core::Content::WIDGET_OOF_REF && + word->content.widget == vloat) { + found = true; + // Correct only by smaller values (case (1) above): + realWrapLineIndex = + misc::min (realWrapLineIndex, lineIndex2); + } + } + + minWrapLineIndex = misc::min (minWrapLineIndex, lineIndex2); + maxWrapLineIndex = misc::max (maxWrapLineIndex, lineIndex2); + } + } + + assert (found); + } + + DBG_OBJ_MSGF ("resize", 1, + "wrapLineIndex: corrected from %d to %d (%d lines total); " + "searched between %d and %d; this is the GB: %s", + wrapLineIndex, realWrapLineIndex, lines->size (), + minWrapLineIndex, maxWrapLineIndex, + vloat->getGenerator() == this ? "yes" : "no"); + + queueResize (OutOfFlowMgr::createRefNormalFlow (realWrapLineIndex), true); + + // Notice that the line no. realWrapLineIndex may not exist yet. + if (realWrapLineIndex == 0) + lastWordDrawn = misc::min (lastWordDrawn, -1); + else + lastWordDrawn = + misc::min (lastWordDrawn, + lines->getRef(realWrapLineIndex - 1)->lastWord); + DBG_OBJ_SET_NUM ("lastWordDrawn", lastWordDrawn); + + // TODO Is the following necessary? Or even useless? + //redrawY = + // misc::min (redrawY, + // lineYOffsetWidget (lines->getRef (realWrapLineIndex))); + //DBG_OBJ_SET_NUM ("redrawY", redrawY); + } + + DBG_OBJ_LEAVE (); +} + +void Textblock::clearPositionChanged () +{ + DBG_OBJ_ENTER0 ("resize", 0, "clearPositionChanged"); + // Not very efficient (actually, a rewrapping could be easily + // avoided), but this case should not occur very often. + queueResize (0, false); + DBG_OBJ_LEAVE (); +} + +void Textblock::oofSizeChanged (bool extremesChanged) +{ + DBG_OBJ_ENTER ("resize", 0, "oofSizeChanged", "%s", + extremesChanged ? "true" : "false"); + queueResize (-1, extremesChanged); + + // See Textblock::getAvailWidthForChild(): Extremes changes may become also + // relevant for the children, under certain conditions: + if (extremesChanged && !mustBeWidenedToAvailWidth ()) + containerSizeChanged (); + + DBG_OBJ_LEAVE (); +} + +RegardingBorder *Textblock::getWidgetRegardingBorderForLine (Line *line) +{ + return getWidgetRegardingBorderForLine (line->firstWord, line->lastWord); +} + +RegardingBorder *Textblock::getWidgetRegardingBorderForLine (int lineNo) +{ + // Can also be used for a line not yet existing. + int firstWord = lineNo == 0 ? 0 : lines->getRef(lineNo - 1)->lastWord + 1; + int lastWord = lineNo < lines->size() ? + lines->getRef(lineNo)->lastWord : words->size() - 1; + return getWidgetRegardingBorderForLine (firstWord, lastWord); +} + +RegardingBorder *Textblock::getWidgetRegardingBorderForLine (int firstWord, + int lastWord) +{ + DBG_OBJ_ENTER ("resize", 0, "getWidgetRegardingBorderForLine", "%d, %d", + firstWord, lastWord); + DBG_OBJ_MSGF ("resize", 1, "words.size = %d", words->size ()); + + RegardingBorder *widgetRegardingBorder = NULL; + + if (firstWord < words->size ()) { + // Any instance of a subclass of WidgetRegardingBorder is always + // between two line breaks, and so the first word of the line. + Word *word = words->getRef (firstWord); + + DBG_MSG_WORD ("resize", 1, "<i>first word:</i> ", firstWord, ""); + + if (word->content.type == core::Content::WIDGET_IN_FLOW) { + Widget *widget = word->content.widget; + if (widget->instanceOf (RegardingBorder::CLASS_ID) && + // Exclude some cases where a widget constitutes a new + // container (see definition of float container in + // Textblock::isContainingBlock). + widget->getStyle()->display != core::style::DISPLAY_INLINE_BLOCK && + widget->getStyle()->overflow == core::style::OVERFLOW_VISIBLE) + widgetRegardingBorder = (RegardingBorder*)widget; + + // (TODO: It would look nicer if there is one common place + // for such definitions. Will be fixed in "dillo_grows", not + // here.) + } + } + + DBG_OBJ_MSGF ("resize", 1, "=> %p", widgetRegardingBorder); + DBG_OBJ_LEAVE (); + return widgetRegardingBorder; +} + +/** + * Includes margin, border, and padding. + */ +int Textblock::yOffsetOfLineToBeCreated () +{ + // This method does not return an exact result: the position of the + // new line, which does not yet exist, cannot be calculated, since + // the top margin of the new line (which collapses either with the + // top margin of the textblock widget, or the bottom margin of the + // last line) must be taken into account. However, this method is + // only called for positioning floats; here, a slight incorrectness + // does not cause real harm. + + // (Similar applies to the line *height*, which calculated in an + // iterative way; see wrapWordInFlow. Using the same approach for + // the *position* is possible, but not worth the increased + // complexity.) + + DBG_OBJ_ENTER0 ("line.yoffset", 0, "yOffsetOfLineToBeCreated"); + + int result; + + if (lines->size () == 0) { + result = verticalOffset + calcVerticalBorder (getStyle()->padding.top, + getStyle()->borderWidth.top, + getStyle()->margin.top, + 0, 0); + DBG_OBJ_MSGF ("line.yoffset", 1, "first line: ... = %d", result); + } else { + Line *firstLine = lines->getRef (0), *lastLine = lines->getLastRef (); + result = verticalOffset + calcVerticalBorder (getStyle()->padding.top, + getStyle()->borderWidth.top, + getStyle()->margin.top, + firstLine->borderAscent, + firstLine->marginAscent) + - firstLine->borderAscent + lastLine->top + lastLine->totalHeight (0); + DBG_OBJ_MSGF ("line.yoffset", 1, "other line: ... = %d", result); + } + + DBG_OBJ_LEAVE (); + + return result; +} + } // namespace dw |